×
Namespaces

Variants
Actions
(Difference between revisions)

How to pass a complex object to a page by extending NavigationService on Windows Phone

From Nokia Developer Wiki
Jump to: navigation, search
Kunal Chowdhury (Talk | contribs)
(Kunal Chowdhury -)
hamishwillee (Talk | contribs)
m (Hamishwillee - Subedited/Reviewed)
Line 1: Line 1:
 
[[Category:Draft]][[Category:Application Framework on Windows Phone]][[Category:How To]][[Category:XAML]][[Category:Windows Phone 7.5]][[Category:Windows Phone 8]]
 
[[Category:Draft]][[Category:Application Framework on Windows Phone]][[Category:How To]][[Category:XAML]][[Category:Windows Phone 7.5]][[Category:Windows Phone 8]]
{{Abstract|This article explains how to pass a complex object to a Windows Phone page by extending the NavigationService.}}
+
{{Abstract|This article explains how to pass a complex object to a Windows Phone page by extending the [http://msdn.microsoft.com/en-us/library/windowsphone/develop/System.Windows.Navigation.NavigationService(v=vs.105).aspx NavigationService].}}
  
 
{{ArticleMetaData <!-- v1.3 -->
 
{{ArticleMetaData <!-- v1.3 -->
Line 23: Line 23:
  
 
== Introduction ==
 
== Introduction ==
The sealed class {{Icode|NavigationService}} present under {{Icode|System.Windows.Navigation}} namespace provides methods and events to support navigation from one page to another inside Windows Phone applications. As the class is Sealed, you can not extend it directly to implement your functionalities.
 
  
The {{Icode|Navigate(…)}} method of {{Icode|NavigationService}} only takes one parameter of type Uri where you can pass strings as part of query string parameters.This is simple if you are passing few strings as query string parameter value to the other page and you can get the value from the NavigationContext. But what about complex type values like Person, Employee, Product etc. that holds different simple and/or complex data? Here we are going to discuss about it further and see various alternative methods to achieve the said functionalities.
+
{{Icode|NavigationService}} (in '''System.Windows.Navigation''' namespace) provides methods and events to support navigation from one page to another in a Windows Phone app. The {{Icode|Navigate()}} method takes one parameter of type [http://msdn.microsoft.com/en-us/library/windowsphone/develop/system.uri(v=vs.105).aspx Uri]. You can pass strings between pages by specifying them as a Query String parameter when sending, and then retrieve them in the receiving page using the {{Icode|NavigationContext}}.
  
== Passing Complex Data Types using Extension Method ==
+
This is fine if all that is needed is to pass strings, but in some cases it would be useful to be able to pass other (more complex) objects between pages - for example "persons", "employees", "products". Unfortunately as the class is [http://msdn.microsoft.com/en-us/library/vstudio/88c54tsw.aspx sealed], you cannot extend it by inheritance to implement this additional functionality. You can however extend it by implementing a [http://msdn.microsoft.com/en-us/library/vstudio/bb383977.aspx C# extension method].
The workaround to this problem is assigning a static/shared variable or using the Application State to pass value from one page to other. But hold on… do we have any other alternative to that or can Navigation Service itself handle it? To answer your query, I will first say “No, you can not do this with Navigation Service” but secondly “Yes, you can do so”. Huh!!! Isn’t it a contradictory answer of my own reply? Okay, let me clarify what I said here first.  
+
  
The NavigationService does not allow you to pass a complex object or a complex data type as part of query string parameter. You can not even extend it using inheritance because it is a sealed class. But you can extend the class using an Extension method and do whatever you want to play with it.  
+
This article shows how to provide the required functionality. It provides two implementations: the first for sharing complex objects between any two pages, the second which uses a dictionary for storing the object and a key - making it easier to share objects that might be needed by multiple pages.
  
Let’s see the extension method to implement our own {{Icode|Navigate()}} method where we will be able to pass a complex data type to it:
+
 
 +
== Complex object in a static variable ==
 +
 
 +
While we could have a separate static variable in the app for holding values to be passed between pages, it is more elegant to use the {{Icode|NavigationService}}. While this class cannot be extended by inheritance, we can extend it using an extension method! This allows us to create a method overload which takes an arbitrary object, which we store in a static variable.
 +
 
 +
The implementation of the {{Icode|Navigate()}} method is as below:
 +
<code csharp>
 
     public static void Navigate(this NavigationService navigationService, Uri source, object data)
 
     public static void Navigate(this NavigationService navigationService, Uri source, object data)
 
     {
 
     {
Line 38: Line 42:
 
         navigationService.Navigate(source);
 
         navigationService.Navigate(source);
 
     }
 
     }
Here the first parameter says that, it is an extension method of {{Icode|NavigationService}} class, the second parameter takes the URI of the page where we want to navigate and the third parameter takes the data object. Here please note that, you don’t have to pass an instance of {{Icode|NavigationService}} as part of the first parameter because it is an extension method of it. You just have to pass the source and the data.
+
</code>
 +
The first parameter indicates that this is an extension method of {{Icode|NavigationService}} class, and should not be specified when the function is used. To use this function specify the target page (URI) and object to pass, respectively. The object passed to the method is stored in a static variable.
  
As stated earlier, call the {{Icode|NavigationService.Navigate(…)}} method passing the URI of the page and the object that you want to send to the destination page. Here is a sample code snippet for you to visualize it clearly:
+
Here is a code snippet showing how the extension method is used:
 +
<code csharp>
 
     private void OnNextButtonClicked(object sender, RoutedEventArgs e)
 
     private void OnNextButtonClicked(object sender, RoutedEventArgs e)
 
     {
 
     {
Line 46: Line 52:
 
         NavigationService.Navigate(new Uri("/NextPage.xaml", UriKind.Relative), person);
 
         NavigationService.Navigate(new Uri("/NextPage.xaml", UriKind.Relative), person);
 
     }
 
     }
Now once you are in the destination page, instead of calling {{Icode|NavigationContext}} to get the query string parameter value, you can call the {{Icode| GetNavigationData()}} method which is again an extension method of the class {{Icode|NavigationService}}. Calling the method will return you the passed object. Here is a code snippet of the same:
+
</code>
 +
 
 +
In the destination page we call another extension method {{Icode| GetNavigationData()}} to get the passed object (instead of calling {{Icode|NavigationContext}} to get the query string parameter value):
 +
<code csharp>
 
     protected override void OnNavigatedTo(NavigationEventArgs e)
 
     protected override void OnNavigatedTo(NavigationEventArgs e)
 
     {
 
     {
Line 54: Line 63:
 
         MessageBox.Show("Blog of " + person.Name + " is: " + person.Blog);
 
         MessageBox.Show("Blog of " + person.Name + " is: " + person.Blog);
 
     }
 
     }
As per your requirement, you can keep the data object in {{Icode|NavigationService}} as long as you want or you can nullify it once you retrieve it. This way you will be able to use the {{Icode|NavigationService}} and pass objects to second page in a completely cleaner way.
+
</code>
  
Here is my complete source code of the extension methods of my {{Icode|NavigationService}} that helped me in many way during my whole application:
+
 
 +
You can keep the data object in {{Icode|NavigationService}} or you can nullify it once you retrieve it - keeping the data object allows you to easily pass the same object to second page.
 +
 
 +
Below is the complete source code of the extension methods of the {{Icode|NavigationService}}:
 +
<code csharp>
 
     public static class Extensions
 
     public static class Extensions
 
     {
 
     {
Line 84: Line 97:
 
         }
 
         }
 
     }
 
     }
 +
</code>
  
  
== Using a Dictionary to Store the Hierarchy of the Navigation and it's associated Data ==
+
== Multi-page hierarchies - Using a Dictionary to store shared data ==
Sometime you might need to store complex object from one page to other and then another, e.g. ordering a product. In such scenario, the above simple code will not work for you. To handle this, you will need to store all the navigational data in a list like Dictionary and then use it whenever you need. To implement such functionality, you have to store the data in proper way and remove it when require as per your need.
+
  
To implement such behavior in your {{Icode|NavigationService}} extension, first declare a Dictionary of type <string, object> as shown below:
+
Consider the case of navigating into a multi-page hierarchy using the API defined in the previous section. If you navigate to a page using a particular object and then navigate to another page using another object then the first object that was passed is lost - if you go back to the page it will have access to data for the wrong page.
 +
 
 +
While it is possible to work around this in each page, it is again more elegant to store the appropriate navigation data in the extension method. In this section we store the data in a Dictionary and use a key to get the correct object when needed.
 +
 
 +
To implement such behaviour in your {{Icode|NavigationService}} extension, first declare a {{Icode|Dictionary}} of type {{Icode|<string, object>}} as shown below:
 +
<code csharp>
 
     private static readonly Dictionary<string, object> Data = new Dictionary<string, object>();
 
     private static readonly Dictionary<string, object> Data = new Dictionary<string, object>();
Now, you need to implement the {{Icode|Navigate}} method to pass Uri, key and data to that method. Make sure that, you are checking the pre-existance of the key in the dictionary.
+
</code>
 +
Then implement the {{Icode|Navigate()}} method to pass Uri, key and data to that method. Make sure that, you are checking the pre-existence of the key in the dictionary.
 +
<code csharp>
 
     public static void Navigate(this NavigationService navigationService, Uri source, string key, object data)
 
     public static void Navigate(this NavigationService navigationService, Uri source, string key, object data)
 
     {
 
     {
Line 102: Line 122:
 
         navigationService.Navigate(source);
 
         navigationService.Navigate(source);
 
     }
 
     }
Similarly, in the {{Icode|GetNavigationData}} method get the value from the dictionary and remove it by default before returning it to the caller method. In some case, you might need to store the data to use in multiple places. This method should handle that properly as shown in the below code snippet:
+
</code>
 +
Similarly, in the {{Icode|GetNavigationData()}} method get the value from the dictionary and remove it by default before returning it to the caller method. In some case, you might need to store the data to use in multiple places. This method should handle that properly as shown in the code snippet:
 +
<code csharp>
 
     public static object GetNavigationData(this NavigationContext context, string key, bool persistData = false)
 
     public static object GetNavigationData(this NavigationContext context, string key, bool persistData = false)
 
     {
 
     {
Line 114: Line 136:
 
         return value;
 
         return value;
 
     }
 
     }
Add some additional methods like Remove() and Clear() in the class to handle the dictionary object properly. Here is the new complete Extension methods of my {{Icode|NavigationService}} class:
+
</code>
 +
Add some additional methods like {{Icode|Remove()}} and {{Icode|Clear()}} in the class to handle the dictionary object properly. Here is the new complete extension methods of the {{Icode|NavigationService}} class:
 +
<code csharp>
 
     public static class Extensions
 
     public static class Extensions
 
     {
 
     {
Line 179: Line 203:
 
         }
 
         }
 
     }
 
     }
 +
</code>
  
 
+
== Summary  ==
== Further Improvements ==
+
This article has provided two different ways to implement extension methods to pass complex object to the {{Icode|NavigationService}} and use it in different pages. There may be other methods - please extend this article as needed!.
These were two different ways to implement extension method to pass complex object to the {{Icode|NavigationService}} and use it in different pages. I still think that, this code can be improve to make it easier for the developers.
+

Revision as of 05:34, 26 November 2013

This article explains how to pass a complex object to a Windows Phone page by extending the NavigationService.

SignpostIcon XAML 40.png
WP Metro Icon WP8.png
SignpostIcon WP7 70px.png
Article Metadata
Tested with
SDK: Windows Phone 7.1 SDK, Windows Phone 8.0 SDK
CompatibilityArticle
Created: Kunal Chowdhury (04 Nov 2013)
Last edited: hamishwillee (26 Nov 2013)

Contents

Introduction

NavigationService (in System.Windows.Navigation namespace) provides methods and events to support navigation from one page to another in a Windows Phone app. The Navigate() method takes one parameter of type Uri. You can pass strings between pages by specifying them as a Query String parameter when sending, and then retrieve them in the receiving page using the NavigationContext.

This is fine if all that is needed is to pass strings, but in some cases it would be useful to be able to pass other (more complex) objects between pages - for example "persons", "employees", "products". Unfortunately as the class is sealed, you cannot extend it by inheritance to implement this additional functionality. You can however extend it by implementing a C# extension method.

This article shows how to provide the required functionality. It provides two implementations: the first for sharing complex objects between any two pages, the second which uses a dictionary for storing the object and a key - making it easier to share objects that might be needed by multiple pages.


Complex object in a static variable

While we could have a separate static variable in the app for holding values to be passed between pages, it is more elegant to use the NavigationService. While this class cannot be extended by inheritance, we can extend it using an extension method! This allows us to create a method overload which takes an arbitrary object, which we store in a static variable.

The implementation of the Navigate() method is as below:

    public static void Navigate(this NavigationService navigationService, Uri source, object data)
{
Data = data;
navigationService.Navigate(source);
}

The first parameter indicates that this is an extension method of NavigationService class, and should not be specified when the function is used. To use this function specify the target page (URI) and object to pass, respectively. The object passed to the method is stored in a static variable.

Here is a code snippet showing how the extension method is used:

    private void OnNextButtonClicked(object sender, RoutedEventArgs e)
{
var person = new Person {Name = "Kunal Chowdhury", Blog = "www.kunal-chowdhury.com"};
NavigationService.Navigate(new Uri("/NextPage.xaml", UriKind.Relative), person);
}

In the destination page we call another extension method GetNavigationData() to get the passed object (instead of calling NavigationContext to get the query string parameter value):

    protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
 
var person = (Person)NavigationService.GetNavigationData();
MessageBox.Show("Blog of " + person.Name + " is: " + person.Blog);
}


You can keep the data object in NavigationService or you can nullify it once you retrieve it - keeping the data object allows you to easily pass the same object to second page.

Below is the complete source code of the extension methods of the NavigationService:

    public static class Extensions
{
private static object Data;
 
/// <summary>
/// Navigates to the content specified by uniform resource identifier (URI).
/// </summary>
/// <param name="navigationService">The navigation service.</param>
/// <param name="source">The URI of the content to navigate to.</param>
/// <param name="data">The data that you need to pass to the other page
/// specified in URI.</param>
public static void Navigate(this NavigationService navigationService, Uri source, object data)
{
Data = data;
navigationService.Navigate(source);
}
 
/// <summary>
/// Gets the navigation data passed from the previous page.
/// </summary>
/// <param name="service">The service.</param>
/// <returns>System.Object.</returns>
public static object GetNavigationData(this NavigationService service)
{
return Data;
}
}


Multi-page hierarchies - Using a Dictionary to store shared data

Consider the case of navigating into a multi-page hierarchy using the API defined in the previous section. If you navigate to a page using a particular object and then navigate to another page using another object then the first object that was passed is lost - if you go back to the page it will have access to data for the wrong page.

While it is possible to work around this in each page, it is again more elegant to store the appropriate navigation data in the extension method. In this section we store the data in a Dictionary and use a key to get the correct object when needed.

To implement such behaviour in your NavigationService extension, first declare a Dictionary of type <string, object> as shown below:

    private static readonly Dictionary<string, object> Data = new Dictionary<string, object>();

Then implement the Navigate() method to pass Uri, key and data to that method. Make sure that, you are checking the pre-existence of the key in the dictionary.

    public static void Navigate(this NavigationService navigationService, Uri source, string key, object data)
{
if (Data.ContainsKey(key))
{
Data.Remove(key);
}
 
Data.Add(key, data);
navigationService.Navigate(source);
}

Similarly, in the GetNavigationData() method get the value from the dictionary and remove it by default before returning it to the caller method. In some case, you might need to store the data to use in multiple places. This method should handle that properly as shown in the code snippet:

    public static object GetNavigationData(this NavigationContext context, string key, bool persistData = false)
{
var value = Data.ContainsKey(key) ? Data[key] : null;
 
if (persistData == false)
{
context.Remove(key);
}
 
return value;
}

Add some additional methods like Remove() and Clear() in the class to handle the dictionary object properly. Here is the new complete extension methods of the NavigationService class:

    public static class Extensions
{
private static readonly Dictionary<string, object> Data = new Dictionary<string, object>();
 
/// <summary>
/// Navigates to the content specified by uniform resource identifier (URI).
/// </summary>
/// <param name="navigationService">The navigation service.</param>
/// <param name="source">The URI of the content to navigate to.</param>
/// <param name="key">The key.</param>
/// <param name="data">The data that you need to pass to the other page
/// specified in URI.</param>
public static void Navigate(this NavigationService navigationService, Uri source, string key, object data)
{
if (Data.ContainsKey(key))
{
Data.Remove(key);
}
 
Data.Add(key, data);
navigationService.Navigate(source);
}
 
/// <summary>
/// Gets the navigation data passed from the previous page.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="key">The key.</param>
/// <param name="persistData">if set to <c>true</c> [persist data].</param>
/// <returns>System.Object.</returns>
public static object GetNavigationData(this NavigationContext context, string key, bool persistData = false)
{
var value = Data.ContainsKey(key) ? Data[key] : null;
 
if (persistData == false)
{
context.Remove(key);
}
 
return value;
}
 
/// <summary>
/// Removes the specified context.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="key">The key.</param>
public static void Remove(this NavigationContext context, string key)
{
if (Data.ContainsKey(key))
{
Data.Remove(key);
}
}
 
/// <summary>
/// Clears the specified context.
/// </summary>
/// <param name="context">The context.</param>
public static void Clear(this NavigationContext context)
{
Data.Clear();
}
}

Summary

This article has provided two different ways to implement extension methods to pass complex object to the NavigationService and use it in different pages. There may be other methods - please extend this article as needed!.

245 page views in the last 30 days.