ASP.NET MVC RouteValueDictionary and Complex Objec

2019-06-20 03:04发布

问题:

What is the best method to retain the results of a form post (view model) across search results page?

I have a search form that contains checkboxes. This form is build up using a view model like

public class SearchViewModel
{
    public string Name { get; set; }
    public string[] Colors { get; set; }
}

When this view model gets posted back I use the values to build a query (using EF). The results are turned into a PagedList.

    public class SearchController : Controller
    {
    public ActionResult Index()
    {
        //this displays the search form.
        return View();
    }

    public ActionResult Results(string game, SearchViewModel vm)
    {
        //this displays the results
        ViewBag.SearchViewModel = vm;
        var matches = _repository.AsQueryable()
            .ColorOr(vm.Colors)
            .WhereIf(vm.Name.IsNotEmpty(), x => x.Name.Contains(vm.Name.Trim()));

            return View(matches.ToPagedList(1, 10));
    }
}

Now that the results are displayed I would like to use Html.PagedListPager and RouteValueDictionary to create paging.

@Html.PagedListPager((IPagedList)Model, page => Url.Action("Results", new RouteValueDictionary(ViewBag.SearchViewModel)))

However; the URL created looks like this:

http://localhost:5139/search?Name=test&Colors=System.String[]&PageIndex=0

The values for Colors ends up being the type not the values. I was hoping the URL looks more like:

 http://localhost:5139/search?Name=test&Colors=[Blue,Pink,Yellow]&PageIndex=0
  1. What is the best method to retain the results of a form post (view model) across search results page?
  2. Can RouteValueDictionary support complex objects?
  3. Should I use something like unbinder
  4. Would I be better off using ViewData or Session?

回答1:

What I've done for cases like this, which I find simple, yet powerful, is serialized my view model object to JSON (in your case SearchViewModel), using something like NewtonSoft JSON.net then with the resulting JSON string, do a simple compression of the string via the zlib.net Zlib.DeflateStream class (you could also use something like AES Rijndael but will no doubt be slower and you want speed first and foremost) and then pass the resulting Base64 string across your QueryString.

Then when you are ready to use it again (it's effectively a viewstate), simply decompress the JSON string and deserialize it from JSON into the respective .NET object (again in your case SearchViewModel).

Worked a treat for me, and you don't end up with a URL that is unmanageable or any real measurable performance impacts that I've seen with only a handful of form fields being serialized.

I will elaborate with a code sample soon.

UPDATE: Code samples...

This is what I would do in your particular scenario:

In Results(string, SearchViewModel) action:

public ActionResult Results(string encryptedUrlViewModel, string game, SearchViewModel vm)
{
    SearchViewModel searchUrlViewModel = null;
    if (!string.IsNullOrEmpty(searchUrl)) {
      // only first submission, no url view model set yet, so compress it and store..
      encryptedUrlViewModel = Convert.ToBase64String(
        DeflateStream.CompressString(JsonConvert.SerializeObject(vm)));
      ViewBag.EncryptedUrlViewModel = encryptedUrlViewModel;
    }
    else {
      var jsonUrlViewModel = DeflateStream.UncompressString(Convert.FromBase64String(encryptedUrlViewModel));
      searchUrlViewModel = JsonConvert.DeserializeObject(jsonUrlViewModel, typeof(SearchViewModel)) as SearchViewModel;
      // at this point you should have a serialized 'SearchViewModel' object 
      // ready to use which you can then tweak your query below with.
    }
    var matches = _repository.AsQueryable()
        .ColorOr(vm.Colors)
        .WhereIf(vm.Name.IsNotEmpty(), x => x.Name.Contains(vm.Name.Trim()));

    return View(matches.ToPagedList(1, 10));
}

In view:

@Html.PagedListPager((IPagedList)Model, page => Url.Action("Results", new { encryptedUrlViewModel = ViewBag.EncryptedUrlViewModel }))

The code may require some tweaks, untested in your scenario but it will be something like that, best of luck :)

You should really consider though, if you are wanting to carry the user's request in the URL across paging then one would consider why the form wasn't made as a GET request as opposed to a POST request in the first place. Any reason you particularly wanted it POST? I think GET will carry your Colors array properly, but make sure your view model is setup right. See this Haacked article for model binding to lists.



回答2:

I had the same problem but with search parameters. We had a color parameter that was a list of color names the search engine was using. So you could tick black and blue and the result contained black as well as blue products.

I ended up using Unbound.

using Unbound;
Unbinder u = new Unbinder();

@Url.Action("Index", new RouteValueDictionary(u.Unbind(SearchParams)))

Which results in a link like:

/MyRoute?color[0]=black&color[1]=blue