Warning – bit of a sketchy post this one so apply a pinch of salt, just an idea I’ve been toying with…
One of the things that I find a bit mind boggling about Silverlight 4 is the new support for pluggable navigation.
I wrote about it a little here but there are a better series of posts on it over here on David Poll’s excellent site.
The basic idea is that Silverlight 3 introduced the the Frame control. You tell the Frame control ( via its Source property or via its Navigate method ) to navigate to a Uri and it will attempt to load content from that Uri and display it within the bounds of the Frame and maintain a navigation stack around the content that it is navigating to.
In Silverlight 3, that content needed to point to a real control. That is – you caused the frame to navigate to a control derived from UserControl or, more likely, Page which has some extra navigation support on it and you had built that control into one of the assemblies in your XAP so it is “real” in the sense that it’s a compiled control shipped with the app.
An app can have more than one Frame and a top-level Frame can be simply managing its own content from the point of view of navigation back/forward to pages or it can integrate with the browser’s navigation stack ( by way of a hidden iframe on the hosting page ). If the Frame is integrated with the browser’s navigation then the fragment part of a Uri like this one typed into a browser’s address bar;
- //myserver/myapp.html#/pages/control.xaml?p1=v1&p2=v2
( i.e. the /pages/control.xaml?p1=v1&p2=v2 ) will be passed to the Frame and the frame will then navigate to that Uri and load that content. If the content is a Page then it can use its NavigationService property to further drive navigation without needing to know which Frame it is currently part of. It can also use its NavigationContext property to get to that query string.
This integration allows the “deep linking” idea whereby a URL like the one above can be emailed to somebody and when they follow it they will “jump” right into content served by Silverlight.
There’s also the idea that the Frame has a UriMapper property whereby you can map incoming URIs such that if you have an incoming URI like;
- //myserver/myapp.html#/books/history/american
you can then have that mapped to something like;
- /pages/book.xaml?category=history&type=american
which means that you can hand out prettier URI’s outside of the application and your application’s structure is abstracted from the URIs. The UriMapper property is of type UriMapperBase and there’s a default UriMapper that can do that kind of flexible mapping above but you can also write your own if you want something else.
It’s worth saying that this kind of navigation integrates with things like the Silverlight HyperlinkButton so that HyperlinkButton can drive navigation and it does the right thing if it’s used within Frame content to drive that particular Frame or can be explicitly told which Frame to drive the navigation for if it is hosted outside of Frame content.
In Silverlight 4, the Frame has a new property on it called ContentLoader which is of type INavigationContentLoader. If you don’t set the property, the framework will use PageResourceContentLoader which will use the Silverlight 3 behaviour and expect to use the URI that you provide as a way of finding an embedded control as a resource in an assembly. That is ( as the docs say ) it loads pages from the application package (.xap) file.
If you implement it;
public interface INavigationContentLoader { IAsyncResult BeginLoad(Uri targetUri, Uri currentUri, AsyncCallback userCallback, object asyncState); void CancelLoad(IAsyncResult asyncResult); bool CanLoad(Uri targetUri, Uri currentUri); LoadResult EndLoad(IAsyncResult asyncResult); }
then you can do whatever you want to drive how the Frame goes about turning URIs into content. That content ( as represented by LoadResult ) needs to be returned as either a Page or a UserControl.
But you can do anything and David has already used that to do some very cool stuff that shows how flexible this is;
- On Demand Loading of Assemblies with Silverlight Navigation
- Authentication/Authorization in a Content Loader
I was looking to experiment with another example that illustrated flexibility and I wanted to use the navigation support to navigate through some “server-side content”. It occurred to me that something like a “WCF Data Service” ( i.e. formerly an “Astoria” service or a “ADO.NET Data Service” ) might provide some nice navigable content.
I built the standard WCF Data Service for my Northwind database. That is, I did;
- New website.
- Add entity data model for Northwind DB.
- Add every table to the model.
- Add data service.
- Expose entity model via data service and enable read access to all the entity sets.
and that exposes my Northwind DB over an AtomPub/Atom RESTful set of services and via the “links” in the data I can very easily navigate from ( e.g. ) the list of Customers to a specific Customer to that customer’s Orders and so on.
I wondered – can I plug this in to Silverlight 4’s navigation?
That is, rather than having Silverlight navigate from one control to another, can I have Slverlight navigate from one lump of data to another and then just use some control ( the same one every time ) to display the resulting data and let the navigation framework do the right thing for me?
Note – I really just wanted an example here that would show the flexibility of the navigation system. What I’m actually doing with it might come across as a bit “weird” in that there are definitely other ways of achieving the same thing that don’t involve the navigation system.
Anyway, for my experiement I wrote a little content loader and plugged it into my Frame as below;
<nav:Frame Grid.Row="1" Background="LightGray" Margin="6" FontSize="16" x:Name="myFrame"> <nav:Frame.ContentLoader> <local:ContentLoader BaseServiceUri="http://localhost:32768/NavigationDemo.Web/DataService.svc" /> </nav:Frame.ContentLoader> </nav:Frame>
my ContentLoader uses a BaseServiceUri property which is pointing to my data service. So, when the frame is navigated to a URI such as this one which is a WCF Data Service ( or OData ) URI;
/Customers(‘ALFKI’)/Orders
for my back-end service it will combine that with BaseServiceUri and navigate to;
http://locahost:32768/NavigationDemo/Web/DataService.svc/Customers(‘ALFKI’)/Orders
so my Frame actually navigates to data and not UI as such but my ContentLoader “does the right thing” in terms of;
- Making an async HTTP request to that URI
- Grabbing the results using SyndicationItem/SyndicationFeed to deserialize it from ATOM
- Turning those results into a set of bindable objects
- Returning a UserControl that contains a DataGrid bound to that set of bindable objects
- Doing a bit of trickery to make sure that any links on a SyndicationItem are turned into clickable HyperlinkButtons in order to continue the navigation.
A fragment of the code looks a little like this ( I’ve not included all of it as there’s quite a lot right now ) and it’s fairly rough and ready;
public class ContentLoader : INavigationContentLoader { public IAsyncResult BeginLoad(Uri targetUri, Uri currentUri, AsyncCallback userCallback, object asyncState) { Dispatcher dispatcher = Application.Current.RootVisual.Dispatcher; Uri uri = MakeUriFromBaseServiceAndTarget(targetUri); request = (HttpWebRequest)WebRequest.Create(uri); AsyncCallback localCallback = (asyncResult) => { dispatcher.BeginInvoke(() => { userCallback(asyncResult); }); }; return (request.BeginGetResponse(localCallback, asyncState)); } public bool CanLoad(Uri targetUri, Uri currentUri) { return (true); } public void CancelLoad(IAsyncResult asyncResult) { throw new NotImplementedException(); } public LoadResult EndLoad(IAsyncResult asyncResult) { HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult); Stream responseStream = response.GetResponseStream(); List<object> items = ResponseToBindableObjects(responseStream); return (new LoadResult(new GridDisplayControl(items))); }
As an example of use, I can cause my app to navigate as below;
and I could click (e.g.) on the Orders link for my ALFKI customer and the navigation will jump;
and I can then navigate backwards/forwards between these pages or I can actually type a URI like that into my browser’s address bar and achieve the same thing;
and that’s as far as I’ve gone so far. The code’s a bit thrown together at the moment but I thought it was an interesting “idea” and highlighted just how flexible the navigation framework can be.