Navigation Data

Moving between pages invariably requires data to be passed and in ASP.NET there are a variety of techniques at a developer’s disposal, e.g., the query string, request, session or even a Page’s PreviousPage property. To illustrate some problems of an ill-conceived data passing strategy consider a Page C linked to from Page A and Page B where Page C requires a date to be passed in order to display some date-specific search results:

  1. Page A passes it in the query string whereas Page B passes it in the session, causing Page C to have to look in two places OR
  2. Page A and Page B both use the query string but pass the date string in different formats, causing Page C to have to convert back to a date in two different ways
The Navigation Framework provides a consistent approach for a Page/State to access data passed into it, namely the Data property on the StateContext class. This property is of type NavigationData which represents a key/value pair collection. Overloads of the Navigation methods, GetNavigationLink and Navigate, detailed above accept a NavigationData parameter called 'toData'. The code below illustrates a typical Navigation passing an item of data (in the State Navigated from)

protected void Button_Click(object sender, EventArgs e)
{
	NavigationData toData = new NavigationData();
	toData["date"] = new DateTime(1980, 12, 12);
	StateController.Navigate("T1", toData);
}
and the code below illustrates the corresponding retrieval of this data (in the State Navigated to).

protected void Page_Load(object sender, EventArgs e)
{
	DateTime date = (DateTime)StateContext.Data["date"];
}
Regardless of whether the forward Navigation is via a Transition or Dialog key the State Navigated to always retrieves its values from the StateContext Data. This approach will bear more fruit in the future sections, e.g., Crumb Trail Data, Data Sorting and Paging and ASP.NET Ajax History, where backward, refresh and history navigations are discussed.

NavigationData and StateContext expose a dynamic Bag property (not available in VS 2005 and VS 2008) so that data can be passed and retrieved using the standard 'dot' notation. So the above code can be rewritten as

protected void Button_Click(object sender, EventArgs e)
{
	NavigationData toData = new NavigationData();
	toData.Bag.date = new DateTime(1980, 12, 12);
	StateController.Navigate("T1", toData);
}
and

protected void Page_Load(object sender, EventArgs e)
{
	DateTime date = StateContext.Bag.date;
}
NavigationData allows strongly-typed data to be passed (as can be seen from the DateTime object above). A restricted set of types are supported out-of-the-box: String, Boolean, Int16, Int32, Int64, Single, Double, Decimal, DateTime, TimeSpan, Byte, Char and Guid. ArrayList and Generic List<> types are supported provided all items contained are of the same type and that this type is itself supported.

Support for custom data types can be added provided they have an associated TypeConverter that can convert to and from a (culture invariant) string. The Implementation of a Custom TypeConverter is beyond the scope of this document, but once written the custom type needs registering with the Navigation Framework via configuration. As an example, for the .NET type DateTimeOffset, which has a TypeConverter but is not a supported out-of-the-box NavigationData type, see below.

<configuration>
	<configSections>
		<sectionGroup name="Navigation">
			<section name="StateInfo" type="Navigation.StateInfoSectionHandler, Navigation" />
			<section name="NavigationData" type="Navigation.ConverterInfoSectionHandler, Navigation"/>
		</sectionGroup>
	</configSections>
	<!-- other config elided -->
	<Navigation>
		<StateInfo configSource="StateInfo.config" />
		<NavigationData>
			<add type="System.DateTimeOffset, mscorlib"/>
		</NavigationData>
	</Navigation>
</configuration>
For full flexibility the converter can also be specified via configuration using the ‘converter’ attribute, an example of this is shown below where the shipped DateTimeOffset converter is ignored and a custom one used instead.

<configuration>
	<configSections>
		<sectionGroup name="Navigation">
			<section name="StateInfo" type="Navigation.StateInfoSectionHandler, Navigation"/>
			<section name="NavigationData" type="Navigation.ConverterInfoSectionHandler, Navigation"/>
		</sectionGroup>
	</configSections>
	<!-- other config elided -->
	<Navigation>
		<StateInfo configSource="StateInfo.config"/>
		<NavigationData>
			<add type="System.DateTimeOffset, mscorlib" converter="Custom.DateTimeOffsetConverter, Custom"/>
		</NavigationData>
	</Navigation>
</configuration>

Sample Web Site

To make the navigation from Listing to Details worthwhile, an identifier needs to be passed so the Details.aspx Page can display the information of the selected Person. Firstly, add an Id integer property to the Person class and set this Id for each Person in the People collection as shown below.

private static List<Person> _People = new List<Person>()
{
	new Person() { Id = 1, Name = "Bob", DateOfBirth = new DateTime(1980, 12, 12) },
	new Person() { Id = 2, Name = "Brenda", DateOfBirth = new DateTime(1970, 6, 1) },
	new Person() { Id = 3, Name = "Barney", DateOfBirth = new DateTime(1960,10,25) },
};
Secondly, add an empty GetDetails method to PersonSearch that Details.aspx will use to databind against, see below.

public Person GetDetails()
{
	return null;
}
Add a FormView to Details.aspx wired up to the GetDetails method, using the strongly-typed binding syntax as shown below.

<asp:FormView ID="FormView1" runat="server" ItemType="NavigationSample.Person"  SelectMethod="GetDetails" OnCallingDataMethods="FormView1_CallingDataMethods">
	<ItemTemplate>
		Name:
		<asp:Label ID="NameLabel" runat="server" Text='<%#: Item.Name %>' /><br />
		DateOfBirth:
		<asp:Label ID="DateOfBirthLabel" runat="server" Text='<%# Item.DateOfBirth.ToString("d") %>' />
	</ItemTemplate>
</asp:FormView>
Add a listener to the OncallingDataMethods event and set the PersonSearch class as the data retrieval object, see below.

protected void FormView1_CallingDataMethods(object sender, CallingDataMethodsEventArgs e)
{
	e.DataMethodsObject = new PersonSearch();
}
Pressing F5 will display the table of three people with the first column containing a Hyperlink, clicking this link will display the Details.aspx Page. However, the Details.aspx Page is still blank (aside from the Listing button) as the Person data cannot be populated until the Person Id, of the table row selected, is passed from the Listing State to Details State. The GetDetails method will then use this passed Id to return the associated Person.

To pass the Person Id from Listing to Details change the Search method of the PersonSearch class so that NavigationData containing the Id is passed into the GetNavigationLink method, as shown in below.

public IEnumerable Search()
{
	return from p in _People
			select new
			{
				p.Name,
				p.DateOfBirth,
				Link = StateController.GetNavigationLink("Select", new NavigationData(){ { "id", p.Id } })
			};
}
To utilise this passed Id change the GetDetails method of PersonSearch to retrieve it from StateContext Data as shown below. Pressing F5 and clicking the Hyperlink will display the Details.aspx Page populated with the data for the selected Person.

public Person GetDetails()
{
	return (from p in _People
			where p.Id == (int)StateContext.Data["id"]
			select p).First();
}

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