Bassem Mohsen's Programming Blog

"High thoughts must have high language." —Aristophanes

  • The Author

    Bassem Mohsen - Freelance Windows and
    Web application developer. Technical writer. 24 yo. Based in Cairo, Egypt.

  • MCTS certification logo

Archive for March, 2012

Create URL Based on Request Route Values and Querystring Parameters (ASP.NET MVC3)

Posted by Bassem Mohsen on March 25, 2012

The Problem

You are building an Index web page to list some kind of items in your domain model (for example products). You want to provide a form to filter items and links to select the sort order (for example highest price, lowest price, best rated). You do not want to display too many items on one page so you need a pager section with links to next and previous pages.

This kind of page is normally retrieved using a GET request. So the querystring contains all the filter, sort and page information. The querystring will look like this:

?keywords=fused%20glass&MinPrice=20&MaxPrice=50&SortProperty=Price&SortDirection=Ascending&Page=3&PageSize=20

The URL generated for the sorting links should keep all the querystring parameters irrelevant to sorting and should add or change the values of SortProperty, SortDirection and Page (Page should be set to 1). Likewise the paging links should keep all the querystring parameters irrelevant to paging and add or change the value of the Page parameter only.

Html.ActionLink and Url.Action do not preserve the current request querystring parameters by default (but they do preserve the route values embedded in the URL path). One solution to this problem is to set all the parameters manually

@Html.ActionLink("5", "Index", "Product",

    new

    {

        keywords = Model.Keyword,

        MinPrice = Model.MinPrice,

        MaxPrice = Model.MaxPrice,

        SortProperty = Model.SortProperty,

        SortDirection = Model.SortDiretion,

        Page = 5,

        PageSize = Model.PageSize

    },

    new { @class = "page-link" })

But this is neither elegant nor maintainable, and you will have to write a lot of code.

My Solution

Create an extension method for ViewContext that builds a RouteValueDictionary based on the request route values, querystring parameters and the new route values you specify. Why ViewContext? An instance of this class is available to the View code, and it has access to the request route values and querystring.

using System.Collections.Specialized;

using System.ComponentModel;

using System.Linq;

using System.Web.Mvc;

using System.Web.Routing;

 

namespace MyMvcApplication.Utilities

{

    public static class ViewContextExtensions

    {

        /// <summary>

        /// Builds a RouteValueDictionary that combines the request route values, the querystring parameters,

        /// and the passed newRouteValues. Values from newRouteValues override request route values and querystring

        /// parameters having the same key.

        /// </summary>

        public static RouteValueDictionary GetCombinedRouteValues(this ViewContext viewContext, object newRouteValues)

        {

            RouteValueDictionary combinedRouteValues = new RouteValueDictionary(viewContext.RouteData.Values);

 

            NameValueCollection queryString = viewContext.RequestContext.HttpContext.Request.QueryString;

            foreach (string key in queryString.AllKeys.Where(key => key != null))

                combinedRouteValues[key] = queryString[key];

 

            if (newRouteValues != null)

            {

                foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(newRouteValues))

                    combinedRouteValues[descriptor.Name] = descriptor.GetValue(newRouteValues);

            }

 

            return combinedRouteValues;

        }

    }

}

To use the extension method in a View you need to import the namespace containing it.

@using MyMvcApplication.Utilities

Then you can call GetCombinedRouteValues to build a RouteValueDictionary for Html.ActionLink or Url.Action

@Html.ActionLink("5", "Index", "Product",

    ViewContext.GetCombinedRouteValues(new { Page = 5 }),

    new Dictionary<string, object> { { "class", "page-link" } })

We only had to declare the parameter whose value is to be changed. If the Page parameter does not exist in the request URL, it will be added in the generated URL. If it does exist, its value will be changed to 5.

Posted in Tips & Tricks | Tagged: , , , | 4 Comments »