ASP.NET Routing

In VS 2010 ASP.NET Routing was introduced to Web Forms, which enables the use of URLs that do not have to map to specific files.

Route URLs can be configured for a State by setting the ‘route’ attribute. The configuration can contain a mixture of routed and non-routed States. Taking the State Information configuration in State Information the State S2 can be assigned a route with one parameter as shown below.

<dialog key="D1" initial="S1" path="~/Default.aspx">
	<state key="S1" page="~/P1.aspx">
		<transition key="T1" to="S2"/>
	</state>
	<state key="S2" page="~/P2.aspx" route="R/{P}">
		<transition key="T2" to="S3"/>
	</state>
	<state key="S3" page="~/P3.aspx">
	</state>
</dialog>
This example route, R/{P}, will not be matched if its mandatory parameter P is not provided. Although this parameter can be specified as optional by marking it with an asterisk, e.g., R/{*P}, this technique does not work for more than one parameter. To solve this defaults, introduced in the Navigation Data Markup and Defaults section, can be used. An example for specifying string and int defaults for a route with two parameters is shown below.

<state key="S2" page="~/P2.aspx" route="R/{P}/{Q}" defaults="P=s,Q?int=0">
	<transition key="T2" to="S3"/>
</state>
ASP.NET Routing is associated with human-readable URLs. However, there are two currently two impediments to achieving this in the Navigation framework. To see what these are consider a routed URL produced on navigating from S1 to S2 and passing values of 'a' and '1' for the NavigationData items P and Q respectively:

R/a/12_3?c1=0-0
But the intended URL is R/a/1, so what are the extra bits and how can they be removed?

Objects other than strings stored in NavigationData will have characters appended, identifying their type, when they appear in URLs. That's why the '1' appears as 12_3 in the above example. To remedy this defaultTypes, introduced in the Navigation Data Markup and Defaults section, can be used. In the example, the configuration below shows how to define the default type of the Q NavigationData item as an int. Notice, the type is no longer required when specifying Q's default value.

<state key="S2" page="~/P2.aspx" route="R/{P}/{Q}" defaults="P=s,Q=0" defaultTypes="Q=int">
	<transition key="T2" to="S3"/>
</state>
By using default values the URL has become R/a/1?c1=0-0. The query string element is the information needed to maintain the Crumb Trail. This can be turned off by setting trackCrumbTrail to false as shown below. However, turning it off does come at the expense of back navigation.

<state key="S2" page="~/P2.aspx" route="R/{P}/{Q}" defaults="P=s,Q=0" defaultTypes="Q=int" trackCrumbTrail="false">
	<transition key="T2" to="S3"/>
</state>
The example URL is now R/a/1, as initially intended. Using forward navigation to reach S2 the URL will always be in this user-friendly format, but there are still times when using backward or history navigation that it might not be. Namely when an item is added to StateContext data that is not part of the initial navigation data passed in.

Consider that when P2.aspx loads a calculation is performed and consequently a new item U with value 'b' is added to StateContext data. If a forward navigation is performed to S3 and a subsequent back navigation to S2 the URL would be:

R/a/1?U=b
Although there's no need for U to be passed back to S2, because its value is calculated when the page loads, by default all items in StateContext at the time of the forward navigation to S3 will be passed during the back navigation. For such scenarios the Navigation framework allows derived data to be configured. StateContext Data is considered derived if it need not be passed during a navigation. Derived data is specified as a comma separated list of keys. In the example, the following shows how to define U as derived data for State S2.

<state key="S2" page="~/P2.aspx" route="R/{P}/{Q}" defaults="P=s,Q=0" defaultTypes="Q=int" derived="U">
	<transition key="T2" to="S3"/>
</state>
Now, under all types of navigation the URL to S2 will always be user-friendly.

URLs look slightly different in Navigation 1.5 when compared with Navigation 1.4 (with _ used as a separator instead of !). This is due to the change to the UrlEncode method introduced in VS 2012. To revert back to the original URL format add the configuration shown below (but be aware that this will cause longer URLs when the AntiXssEncoder is configured, as it is by default in VS 2012).

<configuration>
	<configSections>
		<sectionGroup name="Navigation">
			<section name="StateInfo" type="Navigation.StateInfoSectionHandler, Navigation"/>
			<section name="Settings" type="Navigation.NavigationSettings, Navigation"/>
		</sectionGroup>
	</configSections>
	<!-- other config elided -->
	<Navigation>
		<StateInfo configSource="StateInfo.config"/>
		<Settings originalUrlSeparators="true"/>
	</Navigation>
</configuration>

Sample Web Site

Configure the Listing State to have a route with the startRowIndex, maximumRows and sortExpression as parameters as shown below.

<state key="Listing" page="~/Listing.aspx" route="List/{startRowIndex}/{maximumRows}/{sortExpression}" title="Person Search" defaultTypes="startRowIndex=int,maximumRows=int" defaults="startRowIndex=0,maximumRows=10">
	<transition key="Select" to="Details"/>
</state>
Pressing F5 will throw an exception as no default value is supplied for the sortExpression parameter. Rather than mark the parameter as optional, i.e., List/{startRowIndex}/{maximumRows}/{*sortExpression}, a default sort expression will be supplied instead as shown below.

<state key="Listing" page="~/Listing.aspx" route="List/{startRowIndex}/{maximumRows}/{sortExpression}" title="Person Search" defaultTypes="startRowIndex=int,maximumRows=int" defaults="startRowIndex=0,maximumRows=10,sortExpression=Name">
	<transition key="Select" to="Details"/>
</state>
Pressing F5 and changing page size, page number and sorting produces a partially user-friendly URL (added by history). The defaultTypes specified for startRowIndex and maximumRows keeps the routed part of the URL friendly but work needs to be done to remove the query string.

The first query string parameter, c1, is used by the Navigation framework to track the crumb trail information. Since Listing is the first state, back navigation is not needed, so crumb trail tracking can safely be turned off by setting the trackCrumbTrail to false as shown below.

<state key="Listing" page="~/Listing.aspx" route="List/{startRowIndex}/{maximumRows}/{sortExpression}" title="Person Search" defaultTypes="startRowIndex=int,maximumRows=int" defaults="startRowIndex=0,maximumRows=10,sortExpression=Name" trackCrumbTrail="false">
	<transition key="Select" to="Details"/>
</state>
Pressing F5 again and paging for example shows the c1 parameter no longer appears in the query string. The only query string parameter left is totalRowCount. The totalRowCount need never be passed into the Listing State during a navigation because it is calculated based on the other navigation data passed in. This qualifies it as derived data and so by configuring it as such, below, will prevent it from appearing in the query string.

<state key="Listing" page="~/Listing.aspx" route="List/{startRowIndex}/{maximumRows}/{sortExpression}" title="Person Search" defaultTypes="startRowIndex=int,maximumRows=int" defaults="startRowIndex=0,maximumRows=10,sortExpression=Name" derived="totalRowCount" trackCrumbTrail="false">
	<transition key="Select" to="Details"/>
</state>
Pressing F5 and paging and sorting the results gives a user-friendly URL with no query string parameters.

Last edited Aug 4, 2013 at 6:32 PM by GrahamMendick, version 4