Sponsored By Aspose - File Format APIs for .NET

Aspose are the market leader of .NET APIs for file business formats – natively work with DOCX, XLSX, PPT, PDF, MSG, MPP, images formats and many more!

Keeping a long running Silverlight application alive under forms authentication

This is the scenario : we have a Silverlight application (SL app) which run is running unattended on a big screen in an office. It displays the actual positions of objects on a map. Once every 10 minutes the app queries the hosting web server for position information. The information is not public, it is using asp.net forms authentication to guard it against unauthorized eyes.

To get this to work as intended required some special attention. In this post I will give an overview. The site is using ajax. Also Silverlight is doing a lot of async communication. The combination of async (partial) postback and forms authentication has some quircks on itself, I wrote a little post on that short ago. But as said, there are more quircks.

The server

The user sessions are running a long long time. The risk is that within a session IIS will recycle the underlying web application. When that happens the session state will be lost because, by default, session state is stored inproc.  The first step is to store the sesssion state in sql server. This will keep the session information alive when the application is recycled. Setting up sql server is no big deal, this is a good overview. The thing to watch is that now everything you store in the session has to be explicitly serializable. Which usually boils down to setting the serializable attribute and adding default constructors.

The service

A Silverlight application gets it’s data from a service. Our basic service queries a repository to get specific data.

public class KaartData

{

    public static List<Kaart> Kaarten(KaartSoort soort, KaartPositie topLeft, KaartPositie bottomRight)

    {

        KaartRepository repository = RepositoryFactory.KaartRepository();

        return repository.ListKaarten(soort, topLeft, bottomRight);

    }

}

This service is published in a WCF service. A SL app can easily communicate over WCF with a service which is hosted in the same web application as the SL-app itself is running in. It should be possible to host the service somewhere else but that will introduce a large amount of security settings you will have to solve.

In VS there is a template for such a service.

0904161

The result is pretty straightforward

[ServiceContract(Namespace = “Datema”)]

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]

public class KaartService

{

    [OperationContract]

    public List<Kaart> Kaarten(KaartSoort soort, KaartPositie topLeft, KaartPositie bottomRight)

    {

        return Datema.DatemaDirect.KaartServices.KaartData.Kaarten(soort, topLeft, bottomRight);

    }

}

As the service is embedded in the website it is guarded by forms authentication. That’s as intended, we don’t want unauthorized people or software to consume our service. But that will hit back, as we will see later.

The Silverlight Application

The application itself is built around Deep Earth. This is an open source project which combines the power of Silverlight with that of a geo image servers like virtual Earth, Yahoo maps, Open Street Maps and many others.

The xaml markup wraps up a deepearth map

<UserControl x:Class=ChartMap.Page

    xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation

    xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml

    xmlns:DeepEarth=clr-namespace:DeepEarth;assembly=DeepEarth >

 

    <Grid x:Name=LayoutRoot>

        <DeepEarth:Map x:Name=Map>

        </DeepEarth:Map>

        <StackPanel HorizontalAlignment=Left VerticalAlignment=Top  Margin=10 >

            <Slider x:Name=SliderZoom Orientation=Vertical Value=1 Height=200 Minimum=1 Maximum=20  ValueChanged=Slider_ValueChanged></Slider>

        </StackPanel>

    </Grid>

</UserControl>

A SL-app is living in the browser. The code behind does the registration.

private readonly GeometryLayer mapLayer;

 

public Page()

{

    InitializeComponent();

    if (Map.BaseLayer == null)

    {

        Map.BaseLayer = new TileLayer(MapMode.Aerial);

    }

 

    Loaded += ((theMap, args) => HtmlPage.RegisterScriptableObject(“ChartMap”, this));

    mapLayer = new GeometryLayer(Map);

 

}

The constructor also initializes a basic geometrylayer. That’s an aerial view of the world, to our app it’s just a background. A background you can zoom into in great detail. Check the deepearth site for more on that.

The constructor registers the application (this) on the HtmlPage under the name ChartMap. We’ll meet that later on in the javascript.

The application exposes methods to JavaScript by setting the Scriptable attribute

[ScriptableMember]

public void ShowKaarten(KaartSoort vanSoort)

{

    var tl = new KaartPositie();

    tl.Hoogte = Map.GeoBounds.Bottom;

    tl.Lengte = Map.GeoBounds.Left;

    var br = new KaartPositie();

    br.Hoogte = Map.GeoBounds.Top;

    br.Lengte = Map.GeoBounds.Right;

    var svcProxy = new KaartServiceProvider();

    svcProxy.GetKaarten(vanSoort, tl, br, DrawKaarten);

}

This method needs the WCF service to get the data.

Consuming the WCF service in Silverlight

The first step is to add a service reference. For this to work you have to disable the forms authentication, else VS cannot get to the service to generate a proxy.  The generated proxy, KaartServiceClient, can only be invoked asynchronous.

To encapsulate the specific initialization and async coding aspects of the the proxy I have created a helper class

internal class KaartServiceProvider

{

    private readonly KaartServiceClient svc;

 

    internal KaartServiceProvider()

    {

        var addres = new Uri(Application.Current.Host.Source, “/Services/KaartService.svc”);

        svc = new KaartServiceClient(“BasicHttpBinding_KaartService”, new EndpointAddress(addres));

    }

 

    internal void GetKaarten(KaartSoort soort, KaartPositie tl, KaartPositie br,

                           Action<ObservableCollection<Kaart>> kaarten)

    {

        svc.OpenCompleted += ((sender, e) => svc.KaartenAsync(soort, tl, br));

        svc.KaartenCompleted += ((sender, e) => kaarten(e.Result));

        svc.OpenAsync();

    }

}

It wraps the proxy svc. In the constructor the proper address of the service is assembled. As the service is hosted by the same application as the Silverlight app I can use Application.Current.Host.Source to get the right uri.

The GetKaarten method is doing the work. It is passed the parameters to the service and also passes kaarten a callback method to catch the result of the service invocation. The service is explicitly opened, also this has to be done async. OpenCompleted will start the real work by firing KaarternAsync. When that completes the callback method will update the map with the fresh data.

[ScriptableMember]

public void ShowKaarten(KaartSoort vanSoort)

{

    ….

    var svcProxy = new KaartServiceProvider();

    svcProxy.GetKaarten(vanSoort, tl, br, DrawKaarten);

}

private void DrawKaarten(ObservableCollection<Kaart> kaarten)

{

    mapLayer.Clear();

    foreach (var kaart in kaarten)

        mapLayer.Add(new VisualKaart(kaart));

}

 

The Silverlight app on a web page

The Silverlight app is running in the browser. So the way to program it is through JavaScript. It takes a little puzzling to find the object to talk to. Remember the silverlight app registered itself as ChartMap. To fire the scriptable webmethod you need the Content.ChartMap property of the SL-object.

A silverlight application can be used in several styles of web apps. A classical asp.net works well, an MVC app works even better. The latter is easier because the views in MVC are really client side views, the same enviroment the SL-app is living in. Classical Asp.net is more focused on server side code.

It will look like this in an MVC view. The SL- app is between the <object> tags and has id Wereld. The button’s  onclick event fires the SL method.

<select id=”kaartSoort”>

    <option value=”0″>ENC</option>

    <option value=”1″>ARCS</option>

</select>

<input type=”button” value=”Toon kaarten” onclick=”Wereld.Content.ChartMap.ShowKaarten(kaartSoort.value)” />

<object data=”data:application/x-silverlight-2,” type=”application/x-silverlight-2″

    width=”100%” height=”100%” id=”Wereld”>

    <param name=”source” value=”/ClientBin/ChartMap.xap” />

    <param name=”onerror” value=”onSilverlightError” />

    <param name=”background” value=”white” />

    <param name=”minRuntimeVersion” value=”2.0.31005.0″ />

    <param name=”autoUpgrade” value=”true” />

    <a href=”http://go.microsoft.com/fwlink/?LinkID=124807″ style=”text-decoration: none;“>

        <img src=”http://go.microsoft.com/fwlink/?LinkId=108181″ alt=”Get Microsoft Silverlight”

            style=”border-style: none” />

    </a>

</object>

To keep the map up to date the method has to be fired over and over again, controlled by some kind of timer. Javascript has no real timers but using the setTimeOut method you can (recursively) call a method after an interval. In this example the function showMap updates the map and calls itself after 600 seconds.

var fleetId = 0;

var logoDisplayed = false;

var map;

 

function showMap() {

    map = document.getElementById(‘ctl00_ContentPlaceHolder1_TrackingMap’).Content.TrackingMap;

    map.ShowFleetTrack(fleetId);

    // Refresh map every 10 minutes

    setTimeout(“showMap()”, 600000);

}

This script is living on a classical asp.net page. Notice the ugly long id you need to get to the SL-app. I have moved all var’s out of the method to the page. Over time this code will build quite a call stack, not need for “invocation specific instance data”.

What’s wrong with this code ?

At first sight this code looks alright. But nevertheless it will hit an exception after a certain amount of time. Usually a quite cryptic Silverlight error. It took some puzzling to find out what went wrong.

The application stays on one and the same page. The script on the page hits the embedded service once every 10 minutes. The service is protected by forms authentication. On every roundtrip the authentication cookie is checked. But what does not happen is updating the timeout of forms authentication. When the user hits code behind a web page the session timeout of forms-authentication is reset. When the user’s page hits the embedded service this time-out is not reset. I’m not sure whether this is a bug or a feature but it is by no means the behavior you would expect.

To prevent the service from timing out the script has to hit some server side code behind the page. As a target I add a dummy webmethod to the page

public partial class FleetMap : System.Web.UI.Page

{

    [WebMethod]

    public static void HartBeat()

    {

        // No need to do anything, this is just a hartbeat from JavaScript to tell the page is still alive

        // This hartbeat prevents a time out of the authentication cookie

    }

A webmethod, part of ASP.net ajax, can be called directly from script.

function showMap() {

 

    PageMethods.HartBeat();

    map = document.getElementById(‘ctl00_ContentPlaceHolder1_TrackingMap’).Content.TrackingMap;

    map.ShowFleetTrack(fleetId);

    // Refresh map every 10 minutes

    setTimeout(“showMap()”, 600000);

}

 

In the loop the hartbeat tickles the server to notify the page is still alive. After which the SL-app can start firing its WCF requests.

I am not checking on exceptions here. In case the session has timed out issuing a HartBeat will redirect the user to the login page. As intended.

To conclude?

And now the app works as intended. Looking back it was a great experience combining all these new api’s. Communication between them works pretty good. Once you know how :) The bad guy was imho forms authentication. Which dates from a time when there was no such thing as partial or async postback. In a previous post I talked about the problems it has with ajax. Silverlight, another async poster, apparently has its own problems.

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Pingback: Tweets that mention Keeping a long running Silverlight application alive under forms authentication | Peter van Ooijen -- Topsy.com

  • http://codebetter.com/members/pvanooijen/default.aspx pvanooijen

    The main reason for this being a web-app is DeepEarth. There is no counterpart for WPF

    Another reason is deployment. The screen described is part of a website used by several customers. Instead of rolling out updates to customers we only have to update our webserver.

  • http://www.tavaresstudios.com Chris Tavares

    Thanks for the useful tips. I have a slightly different question: why was this a Silverlight app and not a client app? A long running kiosk without any user interaction sounds like a much better place for a regular client app. Then you don’t care about session state – let the client track it.