Tuesday, June 21, 2011

Search Engine Friendly URL’s Using Routing in ASP.Net 3.5

What is Routing?
Routing is a technique which enables us to use a descriptive, search engine and human friendly URL’s for ASP.Net application to access a resource. In this technique, the URL will not map to a resource physically. For example, in a normal asp.net application, the URL "http://localhost/Articles/Default.aspx" will actually corresponds to a physical file called Default.aspx under the folder “Articles”.



Below are some of the examples of valid URLs using Routing,

http://localhost/ArticleSite/Articles/list/csharp
http://localhost/ArticleSite/Articles/list/java
http://localhost/ArticleSite/Articles/edit/csharp
http://localhost/Shopping/electronics/show/homeappliances
http://localhost/Shopping/electronics/show/mobiles

Advantages of Routing
Most often, we work on data driven websites where data is categorized and stored. For example, if we are building a shopping website we may have products in various categories like home appliances, mobiles, Apparels, Jewels, Perfumes, etc. To build a list page to display the products in a category, we will normally develop an asp.net page that accepts the category ID as query string to populate the product list. For example,
http://localhost/Shopping/productlist.aspx?CatID=C91E9918-BEC3-4DAA-A54B-0EC7E874245E

As you can see, the above URL is not readable and descriptive to understand the purpose it is going to serve. When we use routing in these scenarios we can make the URL more descriptive, readable, predictable and most importantly search engine friendly. Something similar to,

http://localhost/Shopping/electronics/show/mobiles

The above URL speaks itself i.e. it is intended to display a list of mobile phones in electronics category.

Initially, the ASP.Net routing engine is packed with the new ASP.Net MVC framework. Later, Microsoft shipped this feature (System.Web.Routing namespace) with .Netframework 3.5 SP1 to work with normal asp.net application.
Moving forward, we will implement a simple routing mechanism in ASP.Net 3.5. In order to provide routing in asp.net application, we need to first define routes and routing handler.

What is a Route?
 A route is a format or pattern of URL we define for application. The route can have placeholders and constant values. The place holders will be replaced with the values that we supply in the URL during processing.
For example,
{controller}/{action}/{Category}  -- /products/list/cars or /product/list/phones
Articles/{action}/{Category} --    /Articles/list/jQuery, /Articles/edit/jQuery

As you can see above, the placeholders are normally placed in { } and constants (Articles) are defined between the allowed delimiter /. The Routing engine will read place holder values, populate it into a name/value pairs and make it available for processing throughout the request.

Read this msdn article to know more about defining routes and adding constraints.
A Route is processed and resolved to a virtual path by a route handler. Once we defined our routes, we need to define our route hander to handle the request that follows the route pattern.

What is a RouteHandler?
A RouteHandler is an object that returns the instance of the actual ASP.NET page or HTTP Handler to complete the processing for the URL requested. A RouteHandler class should inherit the System.Web.Routing.IRouteHandler interface and implement GetHttpHandler() method.

To summarize, in order to work with routing, one should define Routes and RouteHandler to handle incoming request. A route should be registered with a Route handler in Application_Start event of the application object i.e. in global.asax file and it should be added to RouteTable object. Once registered, the incoming request is intercepted by the routing module; it matches the request URL with the registered routes and forwards them to the registered RouteHandler to complete the processing. The routing module is registered in HttpModules section of the web.config.

To understand the routing in ASP.Net, we will build a very simple asp.net application that displays list of employees in different departments.

Steps
1. Create a new Asp.Net website and include a new SqlExpress database in App_Data Folder.
2. Create 2 new tables called Employees and Department with some relevant columns.

Something like below image,

            In this example, we will create a simple route that has the department id of the employees in the URL instead of passing it as query string. Refer below pattern,
"Employees/{Dept}/{action}.aspx"

The valid URL’s for the above routes may be,
Employees/Sales/list.aspx
Employees/HR/list.aspx
Employees/IT/edit.aspx
Employees/Sales/delete.aspx

As I said earlier, we need to first create and register route to a route handler in Global.asax file. In order to register RouteHandler, we need to create the route handler to handle the request that has the url in the following route pattern
"Employees/{Dept}/{action}.aspx"

To do this, right click your solution in solution explorer and include a class file. I have named it as EmployeeRouteHandler. Import the routing namespace System.Web.Routing. Now, inherit the class from IRouteHandler interface and implement GetHttpHanlder() method, which should  return the actual page or a handler to handle the request. Refer the code below,

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.Routing;
using System.Web.Compilation;
/// <summary>
/// Summary description for EmployeeRouteHandler
/// </summary>
public class EmployeeRouteHandler: IRouteHandler
{
       public EmployeeRouteHandler()
       {
              //
              // TODO: Add constructor logic here
              //
       }

    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        string DeptName = requestContext.RouteData.Values["Dept"] as string;
        HttpContext context = HttpContext.Current;
        context.Items.Add("Dept", DeptName);
        string action = requestContext.RouteData.Values["action"] as string;
        if(action.ToLower() == "list")
            return BuildManager.CreateInstanceFromVirtualPath("~/Default.aspx", typeof(Page)) as Page;
        else
            return BuildManager.CreateInstanceFromVirtualPath("~/Default.aspx", typeof(Page)) as Page;
    }


}


In the above code, we have extracted the place holder value(Department id and action in our case) from the RouteData object. Refer the below line,
string DeptName = requestContext.RouteData.Values["Dept"] as string;
string action = requestContext.RouteData.Values["action"] as string;

Note
The Routing engine will process the incoming URL and will populate the values that are matching the placeholders and will make it available throughout the request.

The actual Page object or the target page can be got by calling the CreateInstanceFromVirtualPath() method in BuildManager object.

Next, we need to populate the RouteTable with all the possible Routes and RouteHandler object to process the incoming request to the application. To do this, we need to first add a Global.asax file into our solution. Right click the solution, and select “Add New Item”. In the dialog, select “Global Application Class” and click OK.
Now, register all the Routes with their corresponding RouteHandler and populate it into the RouteTable object in Application_Start event. It is only "Employees/{Dept}/{action}.aspx" in our case.

Refer the code below,
<%@ Application Language="C#" %>
<%@ Import Namespace="System.Web.Routing" %>

<script runat="server">

    void Application_Start(object sender, EventArgs e)
    {
        RegisterRoutes(RouteTable.Routes);
    }

public static void RegisterRoutes(RouteCollection routes)
    {
        routes.Add("EmpOperation",new Route
        (
           "Employees/{Dept}/{action}.aspx",
                new EmployeeRouteHandler()
        )
       
        );
    }

Remember to import the System.Web.Routing namespace in order to access routing. Read this msdn article to know more about defining routes and adding constraints.

To list the employees in a department, drag a GridView control and SqlDataSource control into our Default.aspx page. Configure the Select Parameter collection to accept the DeptID as value to fetch the employees only in that department.
Refer the code below,

ASPX
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
        DataSourceID="SqlDataSource1">
        <Columns>
            <asp:BoundField DataField="EmpID" HeaderText="EmpID" InsertVisible="False"
                ReadOnly="True" SortExpression="EmpID" />
            <asp:BoundField DataField="EmpName" HeaderText="EmpName"
                SortExpression="EmpName" />
            <asp:BoundField DataField="Dept1" HeaderText="Dept" SortExpression="Dept1" />
            <asp:BoundField DataField="Age" HeaderText="Age" SortExpression="Age" />
            <asp:BoundField DataField="City" HeaderText="City" SortExpression="City" />
            <asp:BoundField DataField="Country" HeaderText="Country"
                SortExpression="Country" />
        </Columns>
    </asp:GridView>
    <asp:SqlDataSource ID="SqlDataSource1" runat="server"
        ConnectionString="<%$ ConnectionStrings:ConnectionString %>"
        SelectCommand="SELECT * FROM [Employees] Emp INNER JOIN Department d ON
        Emp.Dept=d.DeptID WHERE (Emp.[Dept] = @Dept)"
        onselecting="SqlDataSource1_Selecting">
        <SelectParameters>    
            <asp:QueryStringParameter DefaultValue="IT" Name="Dept" QueryStringField="Dept"
                Type="String" />            
        </SelectParameters>
    </asp:SqlDataSource>

CodeBehind
public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }
    protected void SqlDataSource1_Selecting(object sender, SqlDataSourceSelectingEventArgs e)
    {
        HttpContext context = HttpContext.Current;
        if(context.Items["Dept"] != null)
        e.Command.Parameters["@Dept"].Value = context.Items["Dept"].ToString();
    }
}
Execute the application and you can see it in action.
You can try the URLs.
http://localhost:16792/RoutingDemo/Employees/Sales/list.aspx
http://localhost:16792/RoutingDemo/Employees/IT/list.aspx
http://localhost:16792/RoutingDemo/Employees/HR/list.aspx