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.
ASP.NET MVC Training | MVC Online Training | MVC Training | ASP.NET MVC Training in Chennai | ASP.NET MVC 6 Training | Dot Net Training in Chennai
ReplyDelete.Net MVC Training | ASP.NET MVC Online Training | C# MVC Training | ASP.NET MVC Training in Chennai | Online ASP.NET MVC 6 Training | Dot Net Training in Chennai
Awesome explanation with simple technique.
ReplyDeleteMany thanks for this nice article ....
You're welcome Ankur. Thanks for your precious compliments :)
Delete