In a lot of things that MVC brought (besides reminding me how much I hate classic asp) what I loved the most was the routing engine. The routing feature was beautiful because it provided for loose-coupling, whilst being structured and rich enough to be actionable. Given that, and for structural and development modularity purposes I adopted the .NET 3.5sp1 routing engine for an URL based-navigation framework for Silverlight.
Basically it works pretty much like MVC/asp.net routing, you set up the routes to yields UI Objects. In this case we pass in a relative url (like "Customer/EditOrder/39/") along with an optional name-value collection (a dictionary basically) as request parameters. You can think of the request parameters as QueryString's collection or Form's input-value collection. What you get out from the routing engine is a "NavigationResponse" type, that contains a UI Page (basically a FrameworkElement) and another name-value collection. The name-value collection in the response is a merged collection of the request name-values, plus what it parsed from the URL and any defaults name-values you had set up with the route.
The other thing I've tried is to marry navigation with the routing, particularly because Silverlight controls aren't inherently statefull as pertinent to navigation - think html pages, like when browsing a web-form and you click back, the page is rendered in a state you left it in, with some exceptions to the rule. How we achieve this "statefulness" is with the following interfaces.
Mostly we just use the IPageNavigator interface that brings together the two base interfaces, along with a title property. Hopefully it should be self-descriptive, but IPageInitializer interface is called to initialize a page pre-displaying it. The InitializePage method takes in a name-value collection, which as you can guess comes from the routing engine's response. The IPageState interface is used to extract and inject state into a page, so when we browse forward or back the UI get's a name-value collection to help it restore it's state. It's kind of like the viewstate in concept, but you have to put the relevant state in and out yourself as the controls themselves don't collude in state management. Also one of the thing we want with M-V-VM pattern, is to manage the state in the ViewModel - these interface helps with that by separating the state management. Additionally, the routing engine is geared to take this into consideration, so in the code to plug in routes we register a route handler with the following constructor.
public NavigationRouteHandler(
Func<ParametersDictionary, FrameworkElement> pageResolver,
Func<FrameworkElement, IPageNavigator> navigatorResolver)
{
...
}
Basically, it takes in two handlers, one to get a FrameworkElement for displaying in the UI and the other to get the page navigator (IPageNavigator) out for helping with the navigation. This separation of the FrameworkElement and IPageNavigation is in recognition of the ViewModel, but it doesn't force you - so, if you don't need a IPageNavigator or don't implement the interface just pass in null. Or if your page itself implements the IPageNavigator, then return the page. All the three scenarios are shown below, which also shows how to register routes, defaults and constrains:
void SetupRoutes()
{
// we can pass in default values in routes
var _defaults = new ParametersDictionary();
_defaults.Add("OrderId", -1);
// we can also pass in constraints
var _constraints = new ParametersDictionary();
_constraints.Add("OrderId", "\\w{2}");
// here we register a route with no IPageNavigator
RouteTable.Routes.Add("Sample1",
new Route("Customer/NewOrder", null,
new NavigationRouteHandler(p => new DummyControl1(), null)));
// here we register a route, with the page itself implementing
// the IPageNavigator and it also has default values passed in
RouteTable.Routes.Add("Sample2",
new Route("Customer/EditOrder/{OrderId}/", _defaults,
new NavigationRouteHandler(
p => new DummyControl2(), u => (IPageNavigator)u)));
// here we register a route with the page's ViewModel implementing the
// IPageNavigator via the DataContext and we also have constraints set
RouteTable.Routes.Add("Sample3",
new Route("Customer/DeleteOrder/{OrderId}/", null, _constraints,
new NavigationRouteHandler(
p => new DummyControl3(), u => (IPageNavigator)(u.DataContext))));
}
This is very similar to asp.net or MVC routing, the difference being we have a custom route handler for Silverlight / WPF that implements the IRouteHandler. And if you don't like it or your setup is hooked up differently, just create your own IRouteHandler implementation and you are good to go. Essentially it has the same extensibility semantics that MVC has, and the workflow is geared towards a request-response type of setup. I hope you did notice the {OrderId} parameters in the route URL, they are parsed and included in the response's name-value collection. One omission for Silverlight is that we can't pass in anonymous types to set up constraints or default values because we can't reflect on them outside the declaring assembly.
Like I said earlier I have tried to marry routing and navigation, and so we have something called navigation containers. The containers are simply interfaces that imply a navigation model, and I have two of them build-in - one is a simple navigation container which you pass in a URL and it just returns the UI but doesn't store any history or anything. Another one is called BrowsingContainer that stores history, caches the state, and can browse forward or back. Just like everything else you can create you own containers to implement say roles-based validation on URls or extend them for deep-linking within the browser.
A benefit with these interfaces is that you can expose them through a dependency injection container, and thus loosely couple the "pages" within the app. Another important point to remember is that you route against or rather navigate in a container even though the routes are registered statically/globally - this means we can have two or more navigation containers controls (think WPF BrowserControl) and each will work with all the routes registered. I also use these interfaces as ViewModels for navigation container controls, so you can easily create your own custom skins with all the glitter and jazz you want - I just sampled one with the Silverlight.FX transitions. It's quite easy.
There is a lot more to say, but for now lets keep it for the future - however I am working on integrating this with Prism 2 and creating an asp.net like site map control. In the sample below do notice that the pages keep their state during back/forward navigation and their memory consumption is constrained to the loaded page (plus the saved states). I'll put up the code later on CodePlex, it needs some scrubbing, Cheers.
PS: I wanted to mention and thank this excellent series on MVC routing.
Posted by Rishi on 10-Mar-09 5:39 AM, 18 Comments
Tags:
silverlight,
mvc,
silverlight,
routing,
asp.net,
routing,
navigation,
navigation,
mvc,
asp.net,
mvc,
asp.net