Tuesday, February 8, 2011

Asp.net MVP and URL Routing with WebForms. Part3 - Understanding URL Routing in Asp.net 4.0


DARE TO SHARE?
Introduction

If you have read Part1 and Part2 of this article, you already know about the fact that we've been exploring the basics of Asp.net Model View Presenter (MVP) and URL Routing in Asp.net WebForm. In this part of the article, we would learn the basics of URL Routing in Asp.net.

Well, before jumping into the details, lets ask a silly question, "What is URL Routing"?

If we are talking about a traditional Asp.net WebForm application, we already are well-aware of the fact that, if we have an aspx page at the following folder location:

\Products.aspx?CategoryId=1&ProductId=1

We need to access the page using the following url:

http://sitename/Products.aspx?CategoryId=1&ProductId=2

There is no routing taking place here. The Requested file path "/Products.aspx" is simply being mapped to the physical file location "\Products.aspx" within the web root folder of the Asp.net application.

Now, at some point some people started talking about implementing URLs which are cleaner, easy to remember, much SEO friendly and "hackable" (Parts of URL could be omitted to to get a different result). For example, instead of file extensions (Say, .aspx) and querystrings (say, CategoryId=1&ProductId=2) in the URLs they started suggesting the kind of URLs which are as follows:

http://sitename/Products/1/2

URLs as the above ones are :

  • Easy to remember (For obvious reasons that it is smaller, simpler and more logical).
  • More SEO friendly (Because it doesn't have ugly Query strings and extension which are never used in search queries in popular search engines like Google).
  • Hackable (Because if user hits http://sitename/Products/1, the product listing page appears with listing items having category id=1).

Now, wait a minute. A web page in Asp.net WebForms has to be an .aspx page, no matter what is the URL. Its still the same even in Asp.net 4.0 (And perhaps will remain the same). So, the Product details page would still be the same one as we had earlier (http://sitename/Products.aspx), right? If that is so, how such SEO Friendly and  extension less URLs will be mapped to this actual aspx pages?

That's where URL Routing comes. In Asp.net WebForm, URL Routing means mapping incoming URL Requests (Without extension and query string) to actual aspx file along with passing necessary parameters.

The Asp.net framework (Since Asp.net 3.5 SP1 and in 4.0) has built-in support for custom URL routing which is easy enough to use and map extension less and SEO friendly hackable URLs to actual aspx pages.

What if there was no built-in URL Routing?

Pretty good question. If there was no built-in URL routing facility in Asp.net WebForm, and we still require to implement it, we had to do something like the following:

  • Capture each and every request probably in Application_BeginRequest() in Global.asax to map each routing URL Request to a Rout Handler class, along with specifying the target page to execute.
  • Create a Rout Handler class, Parse URL to retrieve parameter values and to determine the target page to execute along with passing the parameter values via HttpContext.Items  and execute the target page.
  • Retrieve parameter values from HttpContext.Items and perform necessary executions in the Page.

The following figure depicts the basic idea:

URL Routing without Framework support in Asp.net
Figure : URL Routing without Framework support in Asp.net
Frankly speaking, the above model is not that easy to implement, specially, creating the RoutHandler. Lucky that we have URL Routing facility built in now with Asp.net WebForms which have built in Rout Handler. So, all we have to do is to define the URL Mappings in Application_BeginRequest() and develop the Pages. The framework does the rests.

ABCs of URL Routing

The newly added System.Web.Routing.RouteTable class has a static property "Routes", which is of type "RouteCollection" (Which is nothing but a Generic collection of type RouteBase). The "Routes"  property has two very important methods as follows:

RouterCollection routes = RouteTable.Routes;
routes.MapRoute() //For mapping to MVC controller, used for MVC applications
routes.MapPageRoute() For mapping to WebForm pages, used for MVP applications

So, in a WebForm application, the MapPageRoute() is needed to be used in the Global.asax as follows:

//Maps a URL http://mysite/Products/CategoryId/ProductId to http://mysite/Products.aspx along with
//passing the parameter values for CategoryId and ProductId
void RegisterRoutes(RouteCollection routes)
{
    routes.MapPageRoute(
    "product-info",  //Just a friendly name which could be used to refer the route later
    "/Products/{CategoryId}/{ProductId}", // URL path with Query string parameters in {}
    "~/Products.aspx"); // Actual Page which will handle and process the request
}

void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
}

Now, guess what happens when a Request arrives at Application_Start for the following URL:

http://mysite/Products/1/2

A Route entry is added to the RouteCollection for mapping the URL request to "/Products.aspx" and two parameter values are read from the URL CategoryId and ProductId. These are added to the Page.RouteData Hashtable as CategoryId=1 and ProductId=2.

The RouteHandler redirects to ~/Products.aspx and these two properties are available to retrieve in the Pages code behind as follows:

protected void Page_Load(object sender, EventArgs e)
{
    int CategoryId = Convert.ToInt32(Page.RouteData["CategoryId"]);
    int ProductId = Convert.ToInt32(Page.RouteData["ProductId:"]);
}

Or, the parameter values could also be retrieved in the Page markup as follows:

<asp:Label ID="Label1" runat="server" Text="<%$RouteValue:ProductId%>" />

In reality, these parameter values are stored in Key/Value pairs in HttpContext.Current.Items (A cache storage that could be used as long as the current Request is being served.), which are internally being accessed by Page.RouteData or RouteValue expression in markup.

Different routing examples

Routing with specifying default values and constraints

void RegisterRoutes(RouteCollection routes)
{
    routes.MapPageRoute(
    "product-info",  //Just a friendly name which could be used to refer the route later

    "/Products/{CategoryId}/{ProductId}", // URL path with Query string parameters in {}

    "~/Products.aspx", // Actual Page which will handle and process the request

    true, // Check physical URL address

    new RouteValueDictionary { //Default parameter values if not provided in URL
    { "CategoryId", 1 },
    { "ProductId", 1 } },

    new RouteValueDictionary { // Constraints specified for parameters (Has to be 6 digit numeric value)
    { "CategoryId", @"\d{6}" },
    { "ProductId", @"\d{6}" } }
    );
}

Capturing URL values with "Catch all"

For capturing any other parameter value after the URL part "/Products/1/1", there is a way to specify the parameter patterns as {*parameterName}. For example, according to the following RegisterRoutes() method, the "Details/En-US" will be captured by the "OtherParams" parameter for the following URL:

/Products/1/1/Details/en-US

void RegisterRoutes(RouteCollection routes)
{
    routes.MapPageRoute(
    "product-info",  //Just a friendly name which could be used to refer the route later

    "/Products/{CategoryId}/{ProductId}/{*OtherParams}", // {*OtherParams} will capture any value after
    // /Products/1/1
    "~/Products.aspx", // Actual Page which will handle and process the request
}

Retrieving actual URLs by route name

The friendly name (Which was provided while adding the Route) could be used to retrieve the actual URL from the RouteCollection to use the URL for redirection or some other stuff. Its as easy as follows:

string Url = Page.GetRouteUrl("product-info");
Response.Redirect(Url);

Or, there is a short-cut:

Response.RedirectToRoute("product-info");

Assigning NavigateUrl of Hyperlinks using Routes

There are a number ways to follow when a HyperLink's NavigateUrl property is to be assigned:

One approach is to assign "Hard coded" URLs as follows:

<asp:HyperLink ID="HyperLink1" runat="server" 
    NavigateUrl="~/Products/1/1"> 
    View Product Details 
</asp:HyperLink>

Or, another approach could be use Route expression as follows:

<asp:HyperLink ID="HyperLink1" runat="server" 
    NavigateUrl="<%$RouteUrl:CategoryId=1,ProductId=1%>">
    View Product Details
</asp:HyperLink>

The later approach is better because, if for any reason a route mapping is changed, you just need to modify the Route mapping in Global.asax and no where else. But, like the first case, if "Hard coded" URLs are implemented in application wide, you also need to change them in all places.

Enjoy URL Routing, its fun.

    1 comments:

    Pravesh Singh said...

    Hi,

    I was reading your article and I would like to appreciate you for making it very simple and understandable. This article gives me a basic idea of URL Routing in ASP.Net 3.5(IIS7) and it helped me a lot. Thanks for sharing with us. Check out this link too its also having nice post with wonderful explanation on URL Routing in ASP.Net 3.5(IIS7), for more details check this....

    http://mindstick.com/Articles/9992a0bc-90f5-4f04-823a-31f901b61643/?URL%20Routing%20in%20ASP.Net%203.5%28IIS7%29

    Thank you very much!

    Post a Comment