4

I have a fairly standard sort/filter/page search form but need control over the format of the url. The sort/filter/page parameters should all be part of the url so that, for example, the address could be emailed to someone.

When another filter parameter is added, a POST request is made. My controller method looks like this:

[HttpPost]
public ActionResult Search(string filterField,
                           Operator filterOperator, 
                           string filterValue, 
                           PeopleGroupSearchModel model);

The PeopleGroupSearchModel is populated from query string parameters. The filter* parameters are coming from the posted form values.

I would like to parse the provided filter values, which will then add a filter to a collection in the model called Filters. Then, take the updated model and convert it to the appropriate url and pass that as the response to the user.

So, for example, if they are on this page:

PeopleGroup/Search?page=4&sort=Country

... and POST:

  • filterField = PeopleGroupName
  • filterOperator = Equals
  • filterValue = Zulu

... once all the processing is done, the address in their browser should be something like:

PeopleGroup/Search?page=4&sort=Country&PeopleGroupName=Zulu&PeopleGroupName_op=Equals

So, more or less what I am trying to do:

[HttpGet]
public ActionResult Search(PeopleGroupSearchModel model)
{
    PeopleGroupData.Search(model);
    ViewData.Model = model;
    return View();
}

[HttpPost]
public ActionResult Search(string filterField,
                           Operator filterOperator,
                           string filterValue,
                           PeopleGroupSearchModel model)
{
    PeopleGroupFilter filter = ParseFilter(filterField, 
                                           filterOperator, 
                                           filterValue);
    model.Filters.Add(filter);
    return RedirectToAction("Search", ???);
}

I am very new to MVC, so if I am going about this completely the wrong way, please let me know!

Dave Mateer
  • 17,608
  • 15
  • 96
  • 149

3 Answers3

11

There are a couple of possibilities to implement the Redirect-After-Post pattern (which is what you are after here and which is a very good pattern IMHO) in ASP.NET MVC:

  1. Use TempData. In the POST action store the model inside TempData and redirect:

    TempData["model"] = model;
    return RedirectToAction("Search");
    

    and then inside the Search action check for TempData existence to fetch the model:

    PeopleGroupSearchModel model = TempData["model"] as PeopleGroupSearchModel;
    

    The drawback of this approach is that TempData is persisted only for a single redirect meaning that if the user hits F5 while on the Search GET action you are screwed. This could be alleviated by using Session instead. But of course Session introduces another problems of scalability. So I am not fan of this approach.

  2. Pass all properties on the request:

    return RedirectToAction("Search", new {
        prop1 = model.Prop1,
        prop2 = model.Prop2, 
        ....
    });
    

    Now when redirected to the Search GET action the Default model binder will be able to reconstruct the model. An obvious drawback of this approach is that if your model has many properties and even worse properties of complex types this could quickly become a cumbersome solution. You could probably serialize the model using some text format such as JSON as a query string parameter. Of course query string parameters are limited between different browsers so this could also be a no-no.

  3. Persist the model in some data storage and retrieve an unique id so that it can later be retrieved from this storage:

    int id = Persist(model);
    return RedirectToAction("Search", new { id = id });
    

    And then in the GET action retrieve the model from this very same persistence storage using the id. I like this approach and use it most of the time. If persisting to the aforementioned datastore is expensive you could use caching.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • Thank you for the suggestions. #2 meets the requirement of putting the parameter values in the query string, but that sure seems cumbersome, like you said. In the end I stopped trying to fight with MVC and just did what I was trying to do with the query string directly. – Dave Mateer Jul 27 '11 at 19:33
2

You can put the values you need to keep into ControllerContext.RouteData.Values

    public ActionResult TestRedirect()
    {
        RouteValueDictionary routeValues = ControllerContext.RouteData.Values;
        routeValues.Add("Key1","value1");
        routeValues.Add("Key2","value2");

        return RedirectToAction("TargetRedirect", routeValues);
    }

If you need something more generic you could loop through the postet form elements.

EDIT

It would look something like the top rated answer here: How do you persist querystring values in asp.net mvc?

Community
  • 1
  • 1
Mathias F
  • 15,906
  • 22
  • 89
  • 159
  • I don't think this works. It seems to lose all the other query string parameters, unless I'm doing something wrong. – Dave Mateer Jul 25 '11 at 20:48
  • You are right, existing querystring parameters are not in ControllerContext.RouteData.Values. You would have to add them, which I guess is not what you want. – Mathias F Jul 25 '11 at 21:11
2

This is probably a violation of MVC priciples, but once I stopped fighting the framework and just thought about what I was trying to do in HTTP-land, this simple solution works for me:

[HttpPost]
public ActionResult Search(PeopleGroupColumn filterField,
                           Operator filterOperator,
                           string filterValue)
{
    var collection =
        HttpUtility.ParseQueryString(Request.QueryString.ToString());
    collection.Set(filterField.ToString(), filterValue);
    collection.Set(filterField.ToString() + "_op", filterOperator.ToString());
    return new RedirectResult(
        Request.Url.AbsolutePath + "?" + collection.ToString());
}
Dave Mateer
  • 17,608
  • 15
  • 96
  • 149