Navigation Data Markup and Defaults

There are currently two approaches to setting Control properties from StateContext Data:
  1. Programmatically in the codebehind
  2. Data binding to a NavigationDataSource
However, the former approach should be avoided because codebehinds should be as lean as possible and the latter can be cumbersome if only one way data binding is required.

In VS 2012 Control properties can be set from StateContext Data in markup using the syntax {NavigationData key}. For example, to set a Literal's Text property to the StateContext item with key 'name' see below.

<asp:Literal ID="Literal1" runat="server" Text="{NavigationData name}" />
Before using this feature the NavigationControlBuilderInterceptor must be registered in the Web.config as shown below.

<compilation debug="true" targetFramework="4.5" controlBuilderInterceptorType="Navigation.NavigationDataControlBuilderInterceptor, Navigation">
	<!-- other config elided -->
</compilation>
The StateContext Data item does not have to be a string, any convertible Type is valid. Format strings are also supported using the syntax {NavigationData key,format}. For example, if the 'date' key maps to a DateTime in StateContext then to use its short date representation see below.

<asp:Literal ID="Literal1" runat="server" Text="{NavigationData date,{0:d}}" />
Typically values set using markup syntax will be saved in ViewState by the individual Controls. This is an unnecessary overhead because StateContext Data is stored in ControlState. Although this can be addressed by disabling ViewState on the Control, sometimes this option is not available. Finer grained ViewState management is achieved using the syntax {NavigationData* key}, the asterisk indicating the Control property won't be set until after ViewState has been persisted (see below).

<asp:Literal ID="Literal1" runat="server" Text="{NavigationData* name}" />
The markup syntax is not limited to string Control properties. For example, it can be used to determine a Control's visibility (see below).

<asp:Button ID="Button1" runat="server" Visible="{NavigationData show}" />
Because the Button's Visible property is a boolean the StateContext Data item with key 'show' must also be a boolean. This can lead to messy initialisation logic that checks if a value for 'show' is present in StateContext Data and, if not, sets it up with a default boolean value. To avoid this the Navigation framework allows defaults to be configured for a State. The syntax for assigning defaults is key1?type1=value1, key2?type2=value2, where the valid type values are string, bool, int, long, float, double, decimal, datetime, byte and char, with the default being string. An example for specifying a bool default for 'show' is given below.

<state key="S1" page="~/P1.aspx" defaults="show?bool=false">
	<transition key="T1" to="S2"/>
</state>
Setting a bool Control property from the negation of a StateContext Data item is supported using the syntax {NavigationData !key}. For example, for a bool stored in StateContext Data with key 'hide' its negation can be used as shown below.

<asp:Button ID="Button1" runat="server" Visible="{NavigationData !hide}" />
NavigationData can also be built in NavigationData markup using the syntax {NavigationData key1?type1=value1, key2?type2=value2} in a similar fashion to the NavigationData expression. The example expression used above can be rewritten using markup syntax as shown below.

<cc1:NavigationHyperLink ID="NavigationHyperLink1" runat="server" Action="D1" ToData="{NavigationData title=Mr, age?int=18}" />
When it comes to building NavigationData, markup syntax has many advantages over expressions. One advantage is that the former supports the inclusion of current StateContext Data items. Prepending an & before the first key means all the current StateContext Data will be added to the NavigationData, the equivalent to setting IncludeCurrentData to true on a NavigationHyperLink. To add or exclude an individual item of StateContext Data prepend a + or - to the desired key without associating a type or value. Take the example shown below where NavigationData is built manually that includes all the StateContext Data apart from the 'title'.

NavigationData toData = new NavigationData(true);
toData["title"] = null;
StateController.Refresh(toData);
This same NavigationData can be built in markup syntax using an & to include all StateContext Data and a – to exclude the 'title' as shown below.

<cc1:NavigationHyperLink ID="NavigationHyperLink1" runat="server" Action="D1" ToData="{NavigationData &-title}" />
In fact specifying the + and – is optional because it can be determined based on whether or not the & prefix is present. So the markup of the preceding example could be simplified to {NavigationData &title}.

Another advantage of the markup syntax over the expression syntax is that it is unnecessary to specify the type of the data because it can be determined from the StateInfo configuration. The Navigation framework allows defaultTypes to be configured. The syntax for assigning defaultTypes is key1=type1, key2=type2, where the valid type values are string, bool, int, long, float, double, decimal, datetime, byte and char. In the example, the following shows how to define the default type of the 'age' NavigationData item as an int.

<state key="S1" page="~/P1.aspx" defaults="show?bool=false" defaultTypes="age=int">
	<transition key="T1" to="S2"/>
</state>
With the default type configured for 'age' the ToData markup syntax above can be further simplified by removing the type as shown below.

<cc1:NavigationHyperLink ID="NavigationHyperLink1" runat="server" Action="D1" ToData="{NavigationData title=Mr, age=18}" />
Taking a slight detour from NavigationData markup syntax it is worth noting that defaults and defaultTypes work together so that if a defaultType is specified for a key then its type need not be duplicated in the defaults specification. So, the configuration of the type of the 'show' item can be moved out of defaults and into defaultTypes as shown below.

<state key="S1" page="~/P1.aspx" defaults="show=false" defaultTypes="age=int,show=bool">
	<transition key="T1" to="S2"/>
</state>
NavigationLinks can also be built using NavigationData markup syntax, allowing ASP.NET HyperLinks to be used in the place of NavigationHyperLinks. The syntax is {NavigationLink action,toData} where the action is either a child Transition or Dialog key and the toData is the NavigationData to pass and is subject to the same syntax restrictions that apply when building NavigationData in markup. For example, the same URL built above using a NavigationHyperLink can be built using a HyperLink as shown below.

<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="{NavigationLink D1, title=Mr, age=18}" />
Back and Refresh NavigationLinks can also be constructed in markup. The former is with the syntax {NavigationBackLink d} where d is the number of Crumbs to go back; and the latter is with the syntax {NavigationRefreshLink toData} where toData is the NavigationData to pass.

Specifying NavigationData markup syntax against a Control's change event allows StateContext Data to be updated from user input. For example, to set a StateContext Data key with the SelectedValue of a DropDownList the NavigationData markup is attached to the SelectedIndexChanged event as shown below. For TextBoxes this becomes the TextChanged event and for RadioButtons and CheckBoxes the CheckChanged event.

<asp:DropDownList ID="DropDownList1" runat="server" OnSelectedIndexChanged="{NavigationData key}" />
This NavigationData markup syntax can also be used against any Control events, not just change events. When the associated non-change event fires the StateContext Data item is toggled, i.e., if false it will be set to true and vice versa. See below for an example of binding to the Click event of a Button:

<asp:Button ID="Button1" runat="server" OnClick="{NavigationData show}" />
Although NavigationData markup syntax cannot be used before VS 2012, both defaults and defaultTypes can be used from VS 2005 onwards and they will prove useful in the section on ASP.NET Routing.

Sample Web Site

A common requirement, unsupported by the Pager Control, is display the total count of rows. This value is already stored in StateContext Data as totalRowCount, because it's needed by the Pager to determine whether there's a next page, so the NavigationData markup syntax can be used to display it on the page by adding a new Literal Control after the Pager in Listing.aspx as shown below. Remember to first register the NavigationDataControlBuilderInterceptor in the Web.config using the configuration shown above.

<asp:Literal ID="Literal1" runat="server" Text="{NavigationData totalRowCount}" />
To make this number meaningful a bit of descriptive text is necessary, so the string formatting capabilities of the syntax can be used to provide this context as shown below.

<asp:Literal ID="Literal1" runat="server" Text="{NavigationData totalRowCount,Total Count {0}}" />
The ToData of the change page size NavigationHyperLinks can be changed from NavigationData expressions to markup syntax as shown below.

<cc1:NavigationHyperLink ID="HyperLink1" runat="server" Direction="Refresh" ToData="{NavigationData startRowIndex?int=0,maximumRows?int=5}" Text="5" IncludeCurrentData="true" />
<cc1:NavigationHyperLink ID="HyperLink2" runat="server" Direction="Refresh" ToData="{NavigationData startRowIndex?int=0,maximumRows?int=10}" Text="10" IncludeCurrentData="true" />
The IncludeCurrentData properties can be removed and replaced with the & prefix markup syntax as shown below

<cc1:NavigationHyperLink ID="HyperLink1" runat="server" Direction="Refresh" ToData="{NavigationData &startRowIndex?int=0,maximumRows?int=5}" Text="5" />
<cc1:NavigationHyperLink ID="HyperLink2" runat="server" Direction="Refresh" ToData="{NavigationData &startRowIndex?int=0,maximumRows?int=10}" Text="10" />
The declaration of the types of the startRowIndex and maximumRows keys is repeated across both NavigationHyperLinks. This can be removed if defaultTypes are defined in the StateInfo.config as shown below.

<state key="Listing" page="~/Listing.aspx" title="Person Search" defaultTypes="startRowIndex=int,maximumRows=int">
	<transition key="Select" to="Details"/>
</state>
Then the type indication can be removed from the NavigationHyperLinks as shown below

<cc1:NavigationHyperLink ID="HyperLink1" runat="server" Direction="Refresh" ToData="{NavigationData &startRowIndex=0,maximumRows=5}" Text="5" />
<cc1:NavigationHyperLink ID="HyperLink2" runat="server" Direction="Refresh" ToData="{NavigationData &startRowIndex=0,maximumRows=10}" Text="10" />
Similarly, by configuring the default values for the maximumRows and startRowIndex (see below) these values need not be repeated in markup syntax.

<state key="Listing" page="~/Listing.aspx" title="Person Search" defaultTypes="startRowIndex=int,maximumRows=int" defaults="startRowIndex=0,maximumRows=10">
	<transition key="Select" to="Details"/>
</state>
Removing the startRowIndex or maximumRows key from the ToData will mean its default value will be used. Keys are removed in markup syntax using the – prefix and so the following shows the markup simplified a stage further.

<cc1:NavigationHyperLink ID="HyperLink1" runat="server" Direction="Refresh" ToData="{NavigationData &-startRowIndex,maximumRows=5}" Text="5" />
<cc1:NavigationHyperLink ID="HyperLink2" runat="server" Direction="Refresh" ToData="{NavigationData &-startRowIndex,-maximumRows}" Text="10" />
The – prefix is actually superfluous because it can be inferred from the & prefix (using + and & together is meaningless) and so it's equivalent representation is shown below.

<cc1:NavigationHyperLink ID="HyperLink1" runat="server" Direction="Refresh" ToData="{NavigationData &startRowIndex,maximumRows=5}" Text="5" />
<cc1:NavigationHyperLink ID="HyperLink2" runat="server" Direction="Refresh" ToData="{NavigationData &startRowIndex,maximumRows}" Text="10" />
The NavigationHyperLinks can be replaced with ASP.NET HyperLinks by using RefreshLink markup syntax to set their NavigateUrl properties as shown below.

<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="{RefreshLink &startRowIndex,maximumRows=5}" Text="5" />
<asp:HyperLink ID="HyperLink2" runat="server" NavigateUrl="{RefreshLink &startRowIndex,maximumRows}" Text="10" />
And on the Details.aspx page NavigationBackLink markup syntax can be used to replace the NavigationHyperLink with an ASP.NET HyperLink as shown below.

<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="{NavigationBackLink 1}" Text="Person Search" />
Defaults are not just useful for markup syntax, because having defaults specified for startRowIndex and maximumRows means the Search method can be simplified so that these parameters are no longer nullable, as shown below

public IEnumerable<Person> Search(
	[NavigationData] string name, 
	[NavigationData] string minDateOfBirth, 
	[NavigationData] string sortExpression,
	[NavigationData] int startRowIndex,
	[NavigationData] int maximumRows)
{
	var q = from p in _People
			where (name == null || p.Name.ToUpperInvariant().Contains(name.ToUpperInvariant()))
			&& (minDateOfBirth == null || p.DateOfBirth >= DateTime.Parse(minDateOfBirth))
			select p;
	if (sortExpression != null)
		q = !sortExpression.EndsWith("DESC") ? q.OrderBy(p => p.Name) : q.OrderByDescending(p => p.Name);
	StateContext.Data["totalRowCount"] = q.Count();
	return q.Skip(startRowIndex).Take(maximumRows);
}
A DropDownList could be used to implement sorting instead of the Sorter Control introduced in Data Sorting and Paging. By assigning NavigationData markup syntax to its SelectedIndexChanged event and setting its AutoPostBack to true, as shown below, whenever the DropDownList selection changes the sortExpression in StateContext Data is updated and the GridView is rebound.

<asp:DropDownList ID="DropDownList1" runat="server"  OnSelectedIndexChanged="{NavigationData sortExpression}" AutoPostBack="true">
	<asp:ListItem Text="Name" />
	<asp:ListItem Text="Name DESC" />
</asp:DropDownList>
Pressing F5 and changing the drop down selection sorts the results. Selecting a person and returning to the list using the back navigation link shows that the grid has remembered the sort order but that the drop down has forgotten it. The following fixes this by setting the SelectedValue of the DropDownList from the StateContext Data using NavigationData markup syntax.

<asp:DropDownList ID="DropDownList1" runat="server" SelectedValue="{NavigationData sortExpression}" OnSelectedIndexChanged="{NavigationData sortExpression}" AutoPostBack="true">
	<asp:ListItem Text="Name" />
	<asp:ListItem Text="Name DESC" />
</asp:DropDownList>
This alternative sorting approach is not as good as the Sorter Control because it requires javascript to operate. The next section covers progressive enhancement to provide rich behavior when javascript is available but without jeopardising the experience when it's not.

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