Think HTML's IFrame, is perhaps the easiest way to describe what nRoute navigation containers are and do - or simply put when we navigate we do so in a container. Also, as I have mentioned enumerable times before, with nRoute we abstract content and resources as Urls, and in a yin-to-the-yang kind-of way navigation containers represent the realization of those abstractions.
Below is the class-model for various navigation containers (hereon referred to only as containers) in nRoute.
On the left side of the diagram above are the interfaces that represent the functionality availed by the containers, and on the right are the usable controls that implement the interfaces. The reasons for interface based implementations is primarily to enable instance-less representation and also to enable tacking the container-related functionality onto existing controls - both these points are highlighted below.
If you look at the NavigationService API you'll notice you don't actually need any of the pre-defined containers to handle a navigation request - all you need is an implementation of the INavigationHandler interface. This interface's responsibility is to handle navigation responses, and it has a very simple signature - it contains two methods, the ValidateRequest method ascertains as to if the handler can process the request, if true then the ProccessResponse method is given the response to handle.
Now, normally you would use the specialized containers controls that implement the INavigationHandler and other related interface, however it is also possible to append this interface on existing controls to turn them in containers like a stack panel or list box. Further, it is also possible to use this interface and the navigation facilities in a non-visual context - like using it with the ViewModel however semantically it is not designed for such use-cases.
The INavigationHandler interface mentioned above represents the bare-essential as far handling the response is concerned; however containers normally have richer use semantics and that is what the INavigationContainer interface encapsulates. The API is for the most part self-descriptive, it basically appends the INavigationHandler with informative events, state properties, and navigation-related methods.
Moreover, we usually build upon the INavigationContainer interface to create specialized containers such as the IBrowsingContainer - the idea of having specialized containers is to encapsulate different navigation-model functionalities such as back-forward navigation or history trails. Currently, out of the box in nRoute you get four usable container, implemented as controls and which I described earlier as follows:
|NavigationContentControl ||This is essentially a bare-bone and light-weight container based on the content control type, it can handle a navigation response but that's about it. It offers no events, history, or state related functionality - basically it has none of the richer use semantics present in an INavigationContainer implementation. Think Vanilla Light. |
|NavigationContainer ||This is also a simple and basic control that inherits and extends the NavigationContentControl, but it offers richer use-semantics based on the aforementioned INavigationContainer interface. However, it stores no history or state, and it simply navigates onwards from one Url to another. Think Direct and Onwards. |
|BrowsingContainer ||This builds on the NavigationContainer control, and offers web-like behaviour and state management features. Basically, it stores a stack of back and forward page's info with state - akin to an Internet browser. It enables back and forward navigation based on the stored history, and allows purging of the history and state. Think Very Web-Like. |
|StatefullContainer ||This container also builds on the NavigationContainer, but stores state for any page you have visited - the state is restored onto the page once you navigate onto it again. It doesn't have back or forward stacks, it only keeps one list of states - so simply your last saved state is restored once you navigate to the same page. Think Good Memory. |
The build-in containers cover most of the common use-cases, however the buck doesn't stop there - because it is relatively simple to create your own containers. And the benefit with custom containers is that you can precisely gear the container to whatever use-semantics your scenario mandates - see my forthcoming post for some samples. Also when designing your
One of the important use-semantics that containers exhibit is state-management - which basically involves saving and restoring the content's contextual (and visual) state. This is an opt-in feature that the content can participate, however it is the container's mandate in as far as using, applying, and maintaining the states is concerned - so if the container (like the NavigationContentControl container) doesn't administer state-management then the content's participation becomes irrelevant. Further, the container defines how and when the state will be administered - so for example the BrowserContainer only restores the content state when navigated back or forward, and not when you navigate to a new Url.
The figure above show the opt-in API for participating in state-management - it does three things, one initialize the state with the passed-in name-value collection, two save and restore state on demand, and lastly provide for a content title. The initialization part which I described in an earlier post gets passed in a merged name-value dictionary consisting of the navigation request's name-value collection, the default parameters name-values set while defining the route, and the parsed tokens' name-values from the Url. Note, this is separate from the ISupportRestoreState interface - which asks for a name-value dictionary to save the content's state, and that same state is later passed-in when restoring the content's state.
Though the opt-in mechanism for supporting state is rather "manual", it is also quite simple and low-impact as opposed to reflection based approaches. Also, one other nice thing about the opt-in interfaces is that you can use these with your ViewModels, which makes for a tidy integration story. Lastly, the opt-in feature also helps with the "separation of concerns" principle I've been harping about - the containers are only responsible for managing the states, whereas the content is responsible for extracting and using the state.
In my last post, I highlighted the composition capabilities brought to bear by nRoute - and without repeating myself, I'll just mention that containers are the fundamental building-blocks for composition in nRoute. However, the important thing to understand (and apply) is how you can construct and combine containers to create various use-models such like parent-child or tab-like constructs. See the demo app's technical overview, wherein we create a so-called workspace/blades model by composing a number of containers together. Also, as the container is only "tied" with the content at run-time, it gives you enormous flexibility and control as far the composition is concerned, independent of the content itself.
Default / Application-wide Containers
One of the common scenario with the use of containers is to annotate a default/application-wide container - which when processing navigation (without any specified target container) is automatically used to handle the response. To house a reference to the default/application-wide container we use the Application-class derivative (by default App.xaml), because it provides a well known host to similar application-wide functionality. For all the build-in containers we have corresponding Application-class derivatives, as shown below:
You might have noticed that the Application-class derivatives above specifically implemented the container's defining interface (e.g. IBrowsingContainer on BrowsingApplication type), the reason for that is to make the application class a fully-implemented proxy of an actual container (which would be in a VisualTree on some Page). This might look somewhat confusing, but what it allows us is to swap the actual container, without breaking the event-references or direct reference to the default/application-wide container represented by the Application derivative. With this behaviour you could enable tab like containers; in fact the demo app uses this proxy-type behaviour for setting the active workspace/blade as I explained earlier.
Two other thing enabled by using the application-level containers archetype is browser shell-integration and deep-linking support, however I'll have to cover those aspects in a separate post.
nRoute comes with a wide-array of navigation related behaviours build-in, and these behaviours when triggered automatically try and resolve a container to handle the response. The logical sequence to resolve the container is as follows; firstly, if you have specified the handling container (either by binding or by specifying an ElementName) it will just use that, and it's done. If that is not specified, it will look in its visual tree first and try and find the first parent that implements the INavigationHandler interface. This also means that it kind of works like a browser whereby when you click a link the hosting container (like the Html Page or IFrame) handles the navigation automatically for you, unless specified otherwise. If no handler is found in the parental visual tree, it will look at the current application object and see if a default/application-wide container is specified and use that. If it doesn't find a container at the application level, well it gives up and throws an exception.
Parts & States Model
Lastly the build-in containers use the parts and states design model introduced with Silverlight 2 - this enables you to customize both the visuals in various "navigation-states" as well as the transition effects in the course of the state changes. Also, the inheritance-root for all the containers squares up to the content container type - which as you known allows for a custom look-and-feel. One other point for customizing the visuals is the "ShowFailedVisualState" protected method - by default it shows a message box when the navigation fails, you can override it to customize the error visuals.
This concludes another part of the documentation for nRoute, however I am going to follow this post by a couple of examples that demonstrate the customizability and composition capabilities of the container infrastructure in nRoute.
Posted by Rishi on 15-May-09 10:14 AM, 9 Comments