When I wrote the first post in this series, there was tremendous amount of interest generated and also a lot of feedback requesting to post some of the advanced features. Like I said earlier, ASP.NET 4.0 has lots of new features some of them as simple as Page.Title whereas so as big as caching improvements. This post covers one such feature which is Routing in Webforms. Although Routing was available even in .NET 3.5 SP1, (check this excellent post by Phil Haack on implementing Routing in ASP.NET 3.5 with .NET 3.5 SP1), it was kind of less known. Also the plumbing work was too much for getting it implemented.
However, this has been much simplified in ASP.NET 4.0. To give a background, System.Web.Routing is the namespace that provides the all important RouteTable & PageRouteHandler class. Initially System.Web.Routing was an integral part of ASP.NET MVC. However, the team must have anticipated that Routing is more important even for Webforms and hence they moved this DLL outside the scope of just MVC and made it available to Webforms as well.
Importance of Routing: Getting friendlier URLs which help in better search engine optimization and indexing. Cleaner URLs that can be bookmarked than the unfriendly querystring based approach. As more and more URLs are available, the chances of improvement in search engine ranking becomes higher. These are some of the general advantages of Routing and friendly URLs.
Ok, now that the context is established, lets start with our sample. To begin with, I am using Visual Studio 2010 Beta 1 (download link) and Northwind Sample Database (download link)
I created a “File – New Project – ASP.NET Web Application” leaving the default .NET 4.0 as the framework option. Then, I created a bunch of pages i.e Products.aspx, Categories.aspx and also the Global.asax (Add – New Item – Global Application Class)
On the Default.aspx page, I added a GridView and configured it to use the Northwind Database Connection String and the Categories Table therein. I modified the auto-generated bound columns with a Template Column to accomodate our link to Categories Page. The modified GridView code looks as below:-
<asp:GridView ID="GridView1" runat="server" AllowPaging="True"
AllowSorting="True" AutoGenerateColumns="False" CellPadding="4"
DataKeyNames="CategoryID" DataSourceID="SqlDataSource1" ForeColor="#333333"
GridLines="None">
<AlternatingRowStyle BackColor="White" />
<Columns>
<asp:TemplateField HeaderText="CategoryName" SortExpression="CategoryName">
<ItemTemplate>
<a href="Categories/<%# Eval("CategoryName") %>"><asp:Label ID="Label1" runat="server" Text='<%# Bind("CategoryName") %>'></asp:Label></a>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Description" HeaderText="Description"
SortExpression="Description" />
</Columns>
<FooterStyle BackColor="#990000" Font-Bold="True" ForeColor="White" />
<HeaderStyle BackColor="#990000" Font-Bold="True" ForeColor="White" />
<PagerStyle BackColor="#FFCC66" ForeColor="#333333" HorizontalAlign="Center" />
<RowStyle BackColor="#FFFBD6" ForeColor="#333333" />
<SelectedRowStyle BackColor="#FFCC66" Font-Bold="True" ForeColor="Navy" />
</asp:GridView>
As you can see, the Item Template for Category Name is modified to sport a hyperlink to “Categories” page followed by the CategoryName itself. This would mean that the URL for a category, say Beverages would point to Categories/Beverages
Similarly, on the Categories page, I added a GridView and configured it to use the “Allphabetical List of Products” Table. I also modified the Bound field for ProductName to a template column to have a link to another Products Page. The modified GridView code looks as below:-
<asp:GridView ID="GridView1" runat="server" AllowPaging="True"
AllowSorting="True" AutoGenerateColumns="False" CellPadding="4"
DataSourceID="SqlDataSource1" ForeColor="#333333" GridLines="None">
<AlternatingRowStyle BackColor="White" />
<Columns>
<asp:TemplateField HeaderText="CategoryName" SortExpression="CategoryName">
<ItemTemplate>
<a href="Products/<%# Eval("ProductName") %>"><asp:Label ID="Label1" runat="server" Text='<%# Bind("ProductName") %>'></asp:Label></a>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit"
SortExpression="QuantityPerUnit" />
<asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice"
SortExpression="UnitPrice" />
<asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock"
SortExpression="UnitsInStock" />
<asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder"
SortExpression="UnitsOnOrder" />
<asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel"
SortExpression="ReorderLevel" />
<asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
SortExpression="Discontinued" />
</Columns>
<EditRowStyle BackColor="#2461BF" />
<FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
<HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
<PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" />
<RowStyle BackColor="#EFF3FB" />
<SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
</asp:GridView>
Note, while configuring above GridView, in the screen where we configure DataSource, I also added a where condition to accomodate the Route Request. The screen looks as below:-
Note that I had selected the Where condition from the first screen and specified “CategoryName” under Column,“=” under Operator and “Route” under Source. Also specified are the RouteKey “catname” and DefaultValue “Beverages”. Post this, I just had to click on “Add” and then “Ok” to get a conditional select statement in the SQL DataSource (note: for the purpose of this demo, I have used SQL DataSource. But this would work even if you used any other datasource type / written ADO.NET Code). The “Route” type is new feature added under Source in Visual Studio 2010.
Once the above configuration is done, the SQL DataSource code looks as below:-
<asp:SqlDataSource ID="SqlDataSource1" runat="server"
ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
SelectCommand="SELECT [ProductName], [QuantityPerUnit], [UnitPrice], [UnitsInStock], [UnitsOnOrder], [ReorderLevel], [Discontinued] FROM [Alphabetical list of products] WHERE ([CategoryName] LIKE '%' + @CategoryName + '%')">
<SelectParameters>
<asp:RouteParameter DefaultValue="Beverages" Name="CategoryName"
RouteKey="catname" Type="String" />
</SelectParameters>
</asp:SqlDataSource>
I have also added a label to the page just to show the term used to filter and the value for that can be picked up from the Page.RouteData values in the codebehind as follows:-
protected void Page_Load(object sender, EventArgs e)
{
if (Page.RouteData.Values["catname"] != null)
{
lblDisplay.Text += "<b>" + Page.RouteData.Values["catname"].ToString() + "</b>";
}
else
{
lblDisplay.Visible = false;
}
}
Before getting into Route Configuration, I also added a DetailsView control in the Products.aspx page to show the complete details of a product. And when configuring the DataSource for the DetailsView, I again specified the WHERE condition to the picked up from the RouteData that would come from the above GridView in Catagories Page.
Once this is done, all that is pending is to configure the Route Values. In .NET 3.5 SP1 if you want to establish routing, you would have to manually create the WebFormRouteHandler Class and make sure all the pages inherit from this class. However, in .NET 4.0, it has been much simplied. All I had to do was open the Global.asax and add the following
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.Add("ProductRoute", new Route("Categories/Products/{productname}",
new PageRouteHandler("~/Products.aspx")));
RouteTable.Routes.Add("CategoryRoute", new Route("Categories/{catname}",
new PageRouteHandler("~/Categories.aspx")));
}
(note you would need to add System.Web.Routing namespace to be able to use PageRouteHandler, RouteTable classes etc.,)
So, in the Default.aspx page, all the Catagories would have a link that points to /Categories/<CategoryName> and in the Categories.aspx page, all the ProductNames would have a link that points to /Categories/Products/<ProductName>
A typical URL is http://localhost/Categories/Condiments and http://localhost/Categories/Products/Aniseed%20Syrup
Note that similarly, we have close to 10 URLs for Beverages and around 80 URLs (a URL for each product as above) for Products in this particular application.