One of the common problems in Rich Internet Applications is browser navigation. Users are used to clicking links, moving between pages and being able to get back to where they came from by clicking the browsers back button. In Rich Internet Applications this is often not the case. You spend all your time on one page, and user interaction normally don’t navigate away from the page but invoke small web service calls back to the server. If the user clicks the back button they might end up on the login page or the page they visited before, which is not what the user expected.
There are different techniques to control the back and forward navigation in AJAX applications, but because of differences between browsers getting consistent behavior can get quite hard. Thankfully support for navigation history was added to ASP.NET AJAX in .NET 3.5 SP1, giving us a browser agnostic way of dealing with navigation history. At Tech Ed Sydney Jordan Knight gave a chalk talk showing how to use the ASP.NET AJAX client scripts from Silverlight to add browser navigation support to a Silverlight application. Jordan has now made his presentation available on his blog both as a blog post and as a screen cast.
In Jordan’s approach he uses a separate JavaScript class that encapsulates the ASP.NET AJAX functionality, and acts as a proxy object between Silverlight and the ASP.NET AJAX client library. This works really nicely, and let you reuse the same JavaScript class for non-Silverlight purposes as well. The drawback is that the code is not self-contained inside the Silverlight application, and you have to include an external JavaScript file on the page. Another problem is that his example currently only supports a single key-value pair for the history state. The AJAX framework supports any JavaScript object literal to store navigation state, something that can be useful if you need to keep track of more than one value. For example you might want to keep track of both customer id, and which view you have opened for that specific customer. In that case you would have to keep track of multiple history state values. The ASP.NET AJAX library will encode the state object into the URL of the page whenever the user adds a new history point. This means that you have some limitations to the size of the state object, as well as the complexity of the state object. You can’t have deep object structures or complex types to represent history.
I decided to see if I could find a way to add browser navigation to my Dive Log sample application without introducing more JavaScript than absolutely necessary. I also want a generic implementation that can be reused in any Silverlight application with little effort. The behavior I want to achieve is to add a history point every time the user selects a dive. When the user clicks back and forward in the browser, the application should select the previous selected dive. I also want the browser navigation code to fit nicely with the Model-View-ViewModel architecture of the application.
The first thing I did was to define the interface I want to use for the history manager. Programming against interfaces is good practice, and helps you decouple your components. The interface is straight forward, and mimics the history management functionality of the ASP.NET AJAX client library.
The interface has a method to add a new history point, and an event that executes when the user navigates back or forward. The interface uses a generic type for the state object.
To manage history I’ve implemented the interface on a history manager class. This class is also generic, giving the consumer of the class freedom to choose any object to represent navigation state.
The constructor adds an event handler to the ASP.NET AJAX navigate event. We want the HistoryManager class to handle the event, so the HandleNavigate method marked as a scriptable member. This means that JavaScript can access that member when the HistoryObject is passed to the browser. The code to add the event listener is based on a blog post from Rai Kaimal who uses a similar technique.
The first thing the constructor does is to registering the HistoryManager class as a scriptable object. That means that you can start coding against the CLR object from JavaScript. The next piece of code is a string holding a chunk of JavaScript. The JavaScript does two things. First it creates a Function object, which is a concept of ASP.NET AJAX that gives you delegate-like functionality. The function points to the HandleNavigate method on the HistoryManager object we exposed to the browser. The second thing it does is to add this function as the handler for navigate event exposed by the Sys.Application object. The event was added in .NET 3.5 SP1. The final step in the constructor is to tell the browser to evaluate the JavaScript. Once the script is evaluated the HandleNavigation method will fire on the CLR HistoryManager class whenever navigation occurs.
The HandleNavigate method accepts one ScriptObject parameter which will contain the navigation state. ScriptObject is the type used for any object passed between Silverlight and JavaScript. The cool thing about the ScriptObject is that you can convert it back to a real CLR type if you know the shape of the object. In our case we convert it back to the generic type. Next step is to create the generic event argument and trigger the Navigate event.
The next piece of code in the HistoryManager class is the AddHistoryPoint method. This method gets called whenever the Silverlight application wants to record a new point in the browser navigation history.
We use the same technique to build a string of JavaScript and tell the browser to evaluate it. The JavaScript string contains a JSON representation of the state object. The ToJson method is an extension method that gives easy access to JSON serialization of any object. Preferably I would like to pass the state object directly to the JavaScript function, but because of problems with the type-system introduced in ASP.NET AJAX the parameter validation fails when I pass in a CLR type directly.
Now that the HistoryManager class is ready we can start using it in the Dive Log application. At first thought it might be a good idea to add the HistoryManager as a property on the Application object, but this would cause problems with testing. The Application object will be different when executing in a unit testing context than when executing as a standalone application. Instead I decided to make the IManageHistory interface a new dependency on the PageViewModel. This was done by simply adding a new parameter to the constructor. I also specify which type I want to use to represent the navigation state.
The Dive Log application uses Ninject for dependency injection, so next step is to register the HistoryManager in the Ninject container. This is done by adding a new line to the Ninject module configuration class.
One interesting point about the configuration is the binding behavior. We specify that we want to use a singleton behavior for our HistoryManager. Ninject will make sure that there will be only one instance of the HistoryManager object that will be shared between any classes that has the IManageHistory interface as a dependency. For more information about binding behaviors in Ninject check out the Ninject Dojo.
Now that we have set up the binding and the Viewmodel has a reference to the HistoryManager we can start tracking navigation. In the Dive Log application we want to add a history point whenever the selected dive changes. When the navigate event fires we want to get the ID of the dive from the state object and select that dive. To make navigation work for new dives that hasn’t been assigned an ID yet we use the hash-code to identify the object. The history tracking code is added to the setter of the SelectedDive property in the ViewModel.
In the navigate event handler we use a simple LINQ query to check if we can find the dive and if so select it.
When we run the application and select dives we can see how the back and forward buttons light up. If click the pulldown menu we can see each point, and when we click the buttons we navigate between dives.
Summary Deep integration with the browser is important to give an optimal user experience. There are multiple approaches to do this, but the APS.NET AJAX client library in .NET 3.5 SP1 makes it really easy. By using generic classes and interfaces we get a history manager class that is strongly typed, making it really easy to pass your own state objects between your C# Silverlight code and the ASP.NET AJAX client library.
I have deployed a new version of the Dive Log application you can play with online. You can also download the updated source code that now contains the history management classes. The code should be easy to refactor and use in your own project. Everything is self contained, making it easier to maintain and deploy your Silverlight application. The only requirement is that you have .NET 3.5 SP1 installed and a ScriptManager with history tracking turned on.
Remember Me
a@href@title, strike
Page rendered at Thursday, September 02, 2010 9:45:44 PM (W. Europe Daylight Time, UTC+02:00)
Powered by newtelligence dasBlog 2.3.9074.18820
© Copyright 2010, Jonas Follesø
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.
This blog theme is inspired by a theme original designed and copyrighted 2007, by Alexander Groß and is used with his explicit permission.