asp.net-mvcroutesstrongly-typed-viewrequest.querystring

ASP.NET MVC, Querystring, routes and default binder, how do I combine this?


I have an ASP.NET MVC site that uses strongly typed views. In my case, a controller action could look like this:

public ActionResult List(MyStrongType data)

When submitting the page (view) the response will generate a URL that looks something like this (yes, I know routing could generate a nicer URL):

http://localhost/Ad/List?F.ShowF=0&ALS.CP=30&ALS.L=0&ALS.OB=0&ALS.ST=0&S=&LS.L1=&LS.L2=&CS.C1=32&CS.C2=34&CS.C3=&ALS.ST=0

If I submit the page again, I can see that the data object in the action is set properly (according to the URL)(default binder).

The problem is: Say that I am to add page buttons (to change the page) for a list on my sitepage, the list will be controlled by settings like filter, sortorder, amount of pages per page and so on (controlled by the querystring). First, I need to include all current query parameters in the URL, and then I need to update the page parameter without tampering with the other query parameters. How can I genereate this URL from the view/"HTML helper"?

I could of course manipulate the URL string manually, but this will involve a lot of work and it will be hard to keep up to date if a route is changed, there must be a easier way? Like some kind of querystring collection that can be altered on service side (like ASP.NET Request.QueryString)?

I would hope to not involve the route, but I post the one I got so far anyway:

    routes.MapRoute(
        "Default",
        "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );

    routes.MapRoute(
        "TreeEditing",
        "{controller}/{action}/{name}/{id}",
        new { controller = "MyCategory", action = "Add", name = string.Empty, id = -1 }
    );

BestRegards

Edit 1: It's possible to set the query parameters like this (in view):

<%= url.Action(new {controller="search", action="result", query="Beverages", Page=2})%>

But this will only generate a URL like this (with the default route):

/search/result?query=Beverages&page=2

The rest of the parameters will be missing as you can see.

I could of course add every known parameter in this URL action, but if any query parameter is added or changed there will be a lot of work to keep everything up to date.

I have read the article ASP.NET MVC Framework (Part 2): URL Routing, but how do I find an answer to my problem?


Solution

  • It sounds to me like the problem you have is that you want to be able to easily persist query string values from the current request and render them into the URLs of links in your view. One solution would be to create an HtmlHelper method that returns the existing query string with some changes. I created an extension method for the HtmlHelper class that takes an object and merges its property names and values with the query string from the current request, and returns the modified querystring. It looks like this:

    public static class StackOverflowExtensions
    {
        public static string UpdateCurrentQueryString(this HtmlHelper helper, object parameters)
        {
            var newQueryStringNameValueCollection = new NameValueCollection(HttpContext.Current.Request.QueryString);
            foreach (var propertyInfo in parameters.GetType().GetProperties(BindingFlags.Public))
            {
                newQueryStringNameValueCollection[propertyInfo.Name] = propertyInfo.GetValue(parameters, null).ToString();
            }
    
            return ToQueryString(newQueryStringNameValueCollection);
        }
    
        private static string ToQueryString(NameValueCollection nvc)
        {
            return "?" + string.Join("&", Array.ConvertAll(nvc.AllKeys, key => string.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(nvc[key]))));
        }
    }
    

    It will loop through the query string values from the current request and merge in the properties defined on the object you passed in. So your view code might look like this:

    <a href='/SomeController/SomeAction<%=Html.GetCurrentQueryStringWithReplacements(new {page = "2", parameter2 = "someValue"})%>'>Some Link</a>
    

    This is basically saying "keep the query string from the current request, but change the page and parameter2 values, or create them if they didn't exist." Note that if your current request has a "page" query string parameter, this method will overwrite the value from the current request with the one you explicitly pass in from the view. In this case, if your querystring was:

    ?parameter1=abc&page=1
    

    It would become:

    ?parameter1=abc&page=2&parameter2=someValue
    

    EDIT: The above implementation will probably not work with the dictionary lookup of querystring parameter names you described. Here is an implementation and that uses a dictionary instead of an object:

        public static string UpdateCurrentQueryString(this HtmlHelper helper, Dictionary<string, string> newParameters)
        {
            var newQueryStringNameValueCollection = new NameValueCollection(HttpContext.Current.Request.QueryString);
            foreach (var parameter in newParameters)
            {
                newQueryStringNameValueCollection[parameter.Key] = parameter.Value;
            }
    
            return ToQueryString(newQueryStringNameValueCollection);
        }
    

    Your view would call the function by doing an inline initialization of a dictionary and passing it to the helper function like this:

    <a href='/SomeController/SomeAction<%=Html.GetCurrentQueryStringWithReplacements(new Dictionary<string,string>() {
    { QuerystringHandler.Instance.KnownQueryParameters[QuaryParameters.PageNr], "2" },
    { QuerystringHandler.Instance.KnownQueryParameters[QuaryParameters.AnotherParam], "1234" }})%>'>Some Link</a>