In this post, I’ll look at some basic navigation functionality in Silverlight 3. Silverlight 3 navigation is based on navigating a Frame control to a particular Page control. On top of allowing non-linear navigation throughout your Silverlight application,
· the Frame control will integrate with your browser’s history and address bar,
· Allowing you to provide deep-links to Pages within your application and provide a more web-like experience within Silverlight applications.
· Silverlight 3 navigation allows you to pass data through query strings, perform fragment navigation.
Basic navigation to pages in your main (application) project is simple – HyperlinkButtons can target a Frame, and specify the path to the Page’s XAML file, like so:
|
<navigation:Frame x:Name="ContentFrame" Source="/Views/Home.xaml"> </navigation:Frame> <StackPanel> <HyperlinkButton NavigateUri="/Views/Home.xaml" TargetName="ContentFrame" Content="home"/> <HyperlinkButton NavigateUri="/Views/About.xaml" TargetName="ContentFrame" Content="about"/> </StackPanel> |
In this case, my Pages are “Home” and “About”, located in a Views folder within my Silverlight Application project. Clicking on the links results in changes to the address bar in my web browser as below.
http://localhost:1556/Arun.Manglick.SilverlightTestPage.aspx#/Views/About.xaml
This navigation functionality is very valuable in and of itself, but what happens if I want to put my pages in a separate assembly, ripe for reuse in other applications? You’ll notice that the URIs for the Pages were relative to the application project (i.e. “/Views/Home.xaml” represents “<ApplicationProject>/Views/Home.xaml”).
If you want to access Pages in referenced assemblies, you can do so using the same “short” syntax: “/<ReferencedAssemblyName>;component/<PathToPage>/<PageName>.xaml”
With this syntax, I can place (and reference) pages in a class library that will be referenced by my Silverlight application, allowing me to create a structure like the one in the image below (a simplified example, I know, but it does illustrate the point!).
For this project structure, my hyperlinks look like this:
|
<navigation:Frame x:Name="ContentFrame" Source="/Views/Home.xaml"> </navigation:Frame> <StackPanel x:Name="LinksStackPanel"> <HyperlinkButton NavigateUri="/Views/Home.xaml" TargetName="ContentFrame" Content="home"/> <HyperlinkButton NavigateUri="/Views/About.xaml" TargetName="ContentFrame" Content="about"/> <HyperlinkButton NavigateUri="/PageClassLibrary;component/Pages/PageInLibrary.xaml" TargetName="ContentFrame" Content="page in a class library"/> </StackPanel> |
Upon running and clicking on the “page in a class library” link, you’ll notice that the address bar shows a rather long and ugly URI:
http://localhost:1556/Arun.Manglick.SilverlightTestPage.aspx#/PageClassLibrary;component/Pages/PageInLibrary.xaml
UriMapper:
Thankfully, we’ve supplied a great feature for the Frame control that helps you deal with ungainly URIs – the UriMapper. UriMappers allow you to write code that takes a URI as input and produces a different URI as output. The output URI will be used to locate the page, while the input URI is what the user will see. In the SDK, we’ve supplied a default UriMapper that allows you to use some basic pattern matching to map URIs and keep your deep-links nice and tidy.
In my case, I’d be quite happy if I could get rid of the “/PageClassLibrary;component" that I had to add to my URI to reference Pages in a class library altogether. Instead, I’d like my URI to look like this: “/Pages/PageInLibrary.xaml”. To accomplish this, I add a UriMapper to my Frame:
|
<navigation:Frame Source="/Views/Home.xaml"> <navigation:Frame.UriMapper> <uriMapper:UriMapper> <uriMapper:UriMapping Uri="/Pages/{path}" MappedUri="/PageClassLibrary;component/Pages/{path}" /> </uriMapper:UriMapper> </navigation:Frame.UriMapper> </navigation:Frame> <StackPanel> <HyperlinkButton NavigateUri="/Views/Home.xaml" TargetName="ContentFrame" Content="home"/> <HyperlinkButton NavigateUri="/Views/About.xaml" TargetName="ContentFrame" Content="about"/> <HyperlinkButton NavigateUri="/PageClassLibrary;component/Pages/PageInLibrary.xaml" TargetName="ContentFrame" Content="page in a class library"/> <HyperlinkButton NavigateUri="/Pages/PageInLibrary.xaml" TargetName="ContentFrame" Content="(mapped URI) page in a class library"/> </StackPanel> |
Now, users who navigate to the page are greeted with this, much “prettier” URL: http://localhost:1556/Arun.Manglick.SilverlightTestPage.aspx#/Pages/PageInLibrary.xaml
And there you have it! Dividing your pages across multiple assemblies doesn’t have to degrade user experience, and once you’re familiar with the style of URI that the navigation framework expects for such pages, it’s no more complex than standard navigation within your Silverlight application.
One last thought…
Before I leave you, I think it’s important to point something out: the Silverlight 3 SDK ships with a project template for Navigation for Visual Studio. This template makes it really easy to get started with a styleable, Navigation-enabled Silverlight application, and does quite a lot for you, including specifying some default UriMappings:
|
<navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}" Source="/Home" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed"> <navigation:Frame.UriMapper> <uriMapper:UriMapper> <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/> <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/> </uriMapper:UriMapper> </navigation:Frame.UriMapper> </navigation:Frame> |
However, if you look closely at the UriMappings, you’ll notice that “/{pageName}” will match our “/<AssemblyName>;component” syntax, mapping it to something else entirely, and preventing any attempts to link directly to such URIs.
There are a number of ways to address this issue, depending on your desired URI scheme, such as:
- Delete the UriMappings entirely and go back to the full paths for all of the hyperlinks
- Modify the existing UriMappings so that the “catch-all” doesn’t match the desired syntax
- Add UriMappings for each referenced library before the catch-all mapping, like so:
|
<navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}" Source="/Home" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed"> <navigation:Frame.UriMapper> <uriMapper:UriMapper> <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/> <uriMapper:UriMapping Uri="/Pages/{path}" MappedUri="/PageClassLibrary;component/Pages/{path}" /> <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/> </uriMapper:UriMapper> </navigation:Frame.UriMapper> </navigation:Frame> |
- Add a UriMapping to restore the original syntax before the catch-all mapping, like so:
|
<navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}" Source="/Home" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed"> <navigation:Frame.UriMapper> <uriMapper:UriMapper> <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/> <uriMapper:UriMapping Uri="/{assemblyName};component/{path}" MappedUri="/{assemblyName};component/{path}" /> <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/> </uriMapper:UriMapper> </navigation:Frame.UriMapper> </navigation:Frame> |
This list is certainly not exhaustive, but some combination of these options is likely to help you re-enable navigation to Pages in referenced assemblies.
If you’re still having trouble getting this to work, let me know, and I’ll try to help you troubleshoot!
Thanks & Regards,
Arun Manglick