Monday, January 20, 2014

Custom Route Handler



Custom Route Handler-By Narendra Shrestha

If we look at the ongoing trend at the world of developers, then MVC is the sweet cake to most of us.  Right now we are enjoying the seamless integration property and extensibility of the MVC framework. But to get here we have came a long way. Everything gets clear when we turn the wheel of time. We would get faint memories of us exploiting dashing feature of ASP.NET Web Forms. Those days were like sweet sip of wine. I mean we can’t completely discount Web Forms. Web Forms virtually posed no burden during development. If we were migrating from Windows Application Development, everything seemed so familiar just like a Déjà vu. This resulted in low development time, drastically improving the project performance in respect of deadline. The real issue with Web Forms started to creep in after the deployment of the project. The web is stateless. This is a universal truth. However, Web Form tried to recreate the experience of windows form development by taking turn toward state-full nature. The state was maintained through the heavy exploitation of view states. Since view states stays in the server side, the problem went from bad to worse with increased number of page hits. With every page hits, there increased demand to maintain the view state for every client. As a result, the loading time of the page rose unconventionally. We can only guess our client at the other end frowning and frustrated

Do these reasons make Web Forms obsolete? No. Web Form is here to stay. MVC Framework and Web Forms are like nuts and bolt for the developer. We adore MVC’s state less nature, but at the same time we can’t give up so many features of Web Forms. It’s like a double affair. We only need to know the center of gravity to balance the unsteady boat.
There are numerous ways of integrating MVC and Web Form together inside a single project. Your preferred method depends upon the degree of freedom you permit. That is, how much you want your solution to embrace MVC methodologies or deflect away from the standards.
While rendering “aspx” pages, we have to specify the file name and qualifying virtual path to render the page. Suppose the requirement is that your application must support both the filename like URL and the MVC like URL

I.e. of type “http:www.mydomain/mypage.aspx” and “http:www.myDomain/MyController/MyAction”
Then “IRouteHandler” can be one of the promising shoulders. You may question yourself why you want to do this. For example you may be moving your application from the Web Form to the MVC framework. You require a functioning application, i.e. one which supports both the old and new URLs at the same time.  We must support old URLs since somebody may have already saved them for their reference. That means our application must support fall back concept. 

However, some “aspx” pages may be too costly to be replaced. Let’s say that we have an “aspx” page that makes the good use of “aspx grid controls” and other controls such as “aspx charts controls”. Deadline is not far away from the shore and you must have a very good reason to convince your team mates on the alternates of “aspx grid controls”. Mean while the quality control is too much cautious about the requirement. Time is the most critical factor in situations like these.

This is where the momentum shifts toward Web Form controls. Rendering something equivalent of “gridview” in View of MVC may be sometime too demanding. Some functionality that you assume readily in Web Form may be very hard to replicate in MVC framework. Reverse engineering is the worst step especially when time factor is limited. In such cases we don’t have any options than to stick to our basics i.e. Web Form Controls. 

IRouteHandler

This is an interface which can come handy while implementing our own route handler. The reason for writing custom route handler in MVC framework is to skip MVC’s own MvcRouteHandler. You may be wondering why someone would think of skipping MvcRouteHandler. The liable reason is to skip MVC routing request pipeline. 

If we look at the code listed below, it displays how to register the routes:-

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

Listing 1:-register route

This can be done alternatively as listed in the code below

Route Default = new Route("{Home}/{Controller}",
 new RouteValueDictionary {{ "controller", "Home" },{ "action", "Index" }},
 new MvcRouteHandler());
        routes.Add("Default", Default);

Listing 2:-alternative way of registering routes

There is no difference between the effect produced by both “Listing 1” and “Listing 2”. Actually we have done the same thing in both listings. In “Listing 1” we have directly registered route by specifying its name, URL pattern and default values. Whereas, in “Listing 2”, we have defined a route named “Default” and stated it to be of type “MvcRouteHandler”, and after that added it to the route table. 

However, in “Listing 1” we didn’t defined route handler type because “MapRoute” method itself is devoted toward registering routes of type MvcRouteHandler

MvcRouteHandler” implements the interface “IRouteHandler”. “MvcRouteHandler” is responsible for passing the request down the pipeline which is targeted toward MVC framework. In order to implement our own customizing route handler, we need to implement interface “IRouteHandler”. Before diving into an example, let’s take a look at this interface.

IRouteHandler” interface has a method “GetHttpHandler” which accepts the parameter of type “RequestContext” and returns the instance of a class that implements interface “IHttpHandler”.

The signature of this method is as shown in the listing below

IHttpHandler GetHttpHandler(RequestContext requestContext)


RequestContext requestContext
This contains all the necessary information regarding the request and route data.
IHttpHandler
This interface is implemented by custom class which handles the HttpRequest in order to process the request.

Now if we look at the interface “IHttpHandler”, it has a method “ProcessRequest” which accepts the parameter of type “HttpContext”. Inside this method we handle the HttpRequest. The signature of this method is as shown below:-

void ProcessRequest(HttpContext context)


Also there is another read only property called “IsReusable”. The boolean value in the getter section specifies whether the same instance of “IHttpHandler” can be used for another request.
The following is the signature of this property.

       public bool IsReusable
       {
           get { return false; }
       }

Example

We are continuing the previously mentioned requirement in this example. Suppose that we have a nice working “MyWebForm.aspx” page which moderately uses the “aspx gridview” control. We are fine with the page but only thing that we want to extend is the URL to refer to the page. The following listing shows the “aspx” page and code behind for the page “MyWebForm.aspx”. (In our project we have place this page inside the folder named “Grid” i.e. nested right inside the root project folder)

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="MyWebForm.aspx.cs" Inherits="RouteHandler.MyWebForm" %>

<%@ Register Assembly="System.Web.DataVisualization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
    Namespace="System.Web.UI.DataVisualization.Charting" TagPrefix="asp" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:GridView ID="grdSales" runat="server" AutoGenerateColumns="false">
            <HeaderStyle BackColor="Gray" Font-Bold="true" ForeColor="White" />
            <RowStyle BackColor="White" ForeColor="Black" />
            <AlternatingRowStyle BackColor="Wheat" ForeColor="Black" />
            <Columns>
                <asp:TemplateField HeaderText="Department" ControlStyle-Width="100px">
                    <ItemTemplate>
                        <asp:Label ID="lblDepartment" runat="server"
Text='<%# Eval("Department") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="Total sales" ControlStyle-Width="100px">
                    <ItemTemplate>
                        <asp:Label ID="lblTotalSales" runat="server"
Text='<%#((decimal)Eval("TotalSales")).ToString("C")%>'>
   </asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="Total Profit" ControlStyle-Width="100px">
                    <ItemTemplate>
                        <asp:Label ID="lblTotalProfit" runat="server"
Text='<%#((decimal)Eval("TotalProfit")).ToString("C")%>'>
                       </asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="Year" ControlStyle-Width="100px">
                    <ItemTemplate>
                        <asp:Label ID="lblYear" runat="server"
Text='<%# Eval("Year") %>'></asp:Label>
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
        </asp:GridView>
    </div>
    </form>
</body>
</html>

Listing 3:-MyWebForm.aspx

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using RouteHandler.Models;
using System.Web.UI.DataVisualization.Charting;
using System.Drawing;

namespace RouteHandler
{
    public partial class MyWebForm : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            string department = "food";
            if (Request.QueryString["department"] != null)
            {
                department = Request.QueryString["department"].ToString();
            }
            Sale[] myDepartmentSale = SalesChart().
   Where(a =>a.Department==department).ToArray();
            grdSales.DataSource = myDepartmentSale;
            grdSales.DataBind();
        }
       

public Sale[] SalesChart()
        {
            Sale[] mySales =
            {
              
                new Sale(){Department="Food",TotalProfit=45678.35M,TotalSales=3434223.6M,Year=1990},
                   new Sale(){Department="Food",TotalProfit=43435.23M,TotalSales=2434334.2M,Year=1991},
                   new Sale(){Department="Food",TotalProfit=34567.89M,TotalSales=4453345.3M,Year=1992},
                   new Sale(){Department="Food",TotalProfit=23432.34M,TotalSales=4342424.3M,Year=1993},
                   new Sale(){Department="Food",TotalProfit=33443.34M,TotalSales=5343234.5M,Year=1994},
                   new Sale(){Department="Food",TotalProfit=42343.49M,TotalSales=5342432.0M,Year=1995},
                   new Sale(){Department="Food",TotalProfit=34324.0M,TotalSales=3834933.3M,Year=1996},
                   new Sale(){Department="Food",TotalProfit=54345.0M,TotalSales=5345453.5M,Year=1997},
                   new Sale(){Department="Food",TotalProfit=33434.0M,TotalSales=3433423M,Year=1998},
                   new Sale(){Department="Food",TotalProfit=43435.23M,TotalSales=2434334.2M,Year=1999},
                   new Sale(){Department="Clothing",TotalProfit=5678.35M,TotalSales=434223.6M,Year=1990},
                   new Sale(){Department="Clothing",TotalProfit=3435.23M,TotalSales=434334.2M,Year=1991},
                   new Sale(){Department="Clothing",TotalProfit=4567.89M,TotalSales=453345.3M,Year=1992},
                   new Sale(){Department="Clothing",TotalProfit=3432.34M,TotalSales=342424.3M,Year=1993},
                   new Sale(){Department="Clothing",TotalProfit=3443.34M,TotalSales=343234.5M,Year=1994},
                   new Sale(){Department="Clothing",TotalProfit=2343.49M,TotalSales=342432.0M,Year=1995},
                   new Sale(){Department="Clothing",TotalProfit=4324.0M,TotalSales=834933.3M,Year=1996},
                   new Sale(){Department="Clothing",TotalProfit=4345.0M,TotalSales=345453.5M,Year=1997},
                   new Sale(){Department="Clothing",TotalProfit=4344.0M,TotalSales=433423M,Year=1998},
                   new Sale(){Department="Clothing",TotalProfit=4435.23M,TotalSales=434334.2M,Year=1999},

            };
            return mySales;
        }
    }
}

Listing 4:-MyWebForm.aspx.cs

In code behind, we have a method called “SalesChart”. We have the used an array of type “Sale”. The class “Sale” is defined inside “Model” of our MVC application project. The structure of this class is as listed below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace RouteHandler.Models
{
    public class Sale
    {
        public string Department { get; set; }
        public decimal TotalSales { get; set; }
        public decimal TotalProfit { get; set; }
        public int Year { get; set; }
    }
}

Listing 5:-MyWebForm.aspx.cs 

We have hard coded data inside the method “SalesChart” for array of type “Sale[]”. But in real scenario, we must fetch data from backend by aid of enterprise library or entity framework. If we run the project and enter the URL http://localhost:3198/grid/MyWebform.aspx?department=Clothing, we can see the output as below


Figure 1:-MyWebform.aspx page output before implementation of custom route handler


However, your main goal is to able to retrieve same page by short and sweet URL like http://localhost:3198/grid/Clothing
To do so, we will first start with “Global.asax.cs” page to register our own routes.

public static void RegisterRoutes(RouteCollection routes)
{
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            Route myRoute = new Route("Grid/{Department}", new MyRouteHandler());
            routes.Add("MyRoute", myRoute);
            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
         new { controller = "Home", action = "SelectDepartment",
              id = UrlParameter.Optional }
     );
}

Listing 6:- Registering routes

In above listing, we have defined a Route called myRoute with URL type “Grid/{Department}” and it implements customized route handler of type  MyRouteHandler” that is defined inside infrastructure folder within project root folder. The Listing for “MyRouteHandler” is as shown below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;

namespace RouteHandler.Infrastructure
{
    public class MyRouteHandler : IRouteHandler
    {
        #region IRouteHandler Members

        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            return new MyHttpHandler();
        }
        #endregion
    }

    public class MyHttpHandler : IHttpHandler
    {

        #region IHttpHandler Members

        public bool IsReusable
        {
            get { return false; }
        }

        public void ProcessRequest(HttpContext context)
        {



string department =
Context.Request.RequestContext.RouteData.Values["Department"].
ToString();
                    HttpContext.Current.Server. 
                                Execute("MyWebForm.aspx?department=" + department);

        }

        #endregion
    }
}

Listing 7:- MyRouteHandler.cs

In above listing the main area to look is the method “ProcessRequest”. Inside this method, we have accessed the route value for the “Department” from the route value dictionary. Next we rendered the “MyWebForm.aspx” page through “Execute” method. This method executes the respective handler for the current request i.e. “HttpContext”. The parameter to the “Exceute” method is the virtual path. We also have included the query string inside this virtual path.

We need to create a “Home” controller and then add an action “SelectDepartment”. The following listing demonstrates the same.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using RouteHandler.Models;

namespace RouteHandler.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home
        [HttpGet]
        public ActionResult SelectDepartment()
        {
            return View();
        }
        [HttpPost]
        public ActionResult SelectDepartment(string department)
        {
            return RedirectToRoute("MyRoute", new { Department = department });
        }
    }
}

Listing 8:- Home controller

Inside “Home” controller, we have two actions with same name i.e. “SelectDepartment”. In “GET” attributed action; we added a View called “SelectDepartment”. The code for the view is listed as below

@{
    ViewBag.Title = "SelectDepartment";
}

<h2>SelectDepartment</h2>
@{
    ViewBag.Title = "Search";
}
@using (Html.BeginForm())
{
    @Html.DropDownList("department", new SelectList(new[] { "Food", "Clothing" }));
                                                                                 
    <input type="submit" value="View Data" />
}

Listing 9:- SelectDepartment.cshtml

After posting the request from this view, we redirect to route named “MyRoute” (registered in route table). We also have specified the value of the route value “Department” to be the selection of dropdown list.

If we run our solution,



Figure 2: Select Department View


We select “Food” from the dropdown list. If we click View Data, we will be taken to “MyWebFrom.aspx “. We can see our grid rendered nicely as in the figure below.



Figure 3:-MyWebForm.aspx page output after implementing route handler

The distinguishable change which we can see is URL. Instead of “aspx” file like URL, we have now clean and readable URL i.e. http://localhost:3198/Grid/Food . If you enter the “aspx” file like URL http://localhost:3198/grid/MyWebform.aspx?department=Food , we will still get the desired output as shown below.



Figure 4:-MyWebForm.aspx


Thus, we have met the requirement. We have cleaned the URL and at the same time we have supported the old URL. There may be countless way to perform this. We have chosen Route Handler as the way because our main goal was to implement our own handler. There are so many benefits that come along with managed URLs. First it improves the readability of the URLs. Managed URLs are easy to remember and can be read over to someone without any difficulty. More importantly it improves our page ranking in the search engine.

3 comments: