Daniel Vaughan

free range code

Calling Web Services from Silverlight as the Browser is Closed

clock October 19, 2009 23:07 by author Daniel Vaughan

Introduction

Today I was reading an excellent post by my fellow Disciple Laurent Bugnion, which led on to a short discussion about performing actions after a user attempts to close a browser window. It got me thinking about the capability to dispatch a web service call in Silverlight just after a user attempts to close the browser window or navigate elsewhere. I have been asked the question before, yet before now, have not attempted to answer it. So today I decided to do some experimenting.

There are two things I wanted to look at. Firstly, I wanted to allow a web service to be called after the Silverlight application’s Exit event is raised. Secondly, I wanted to provide the Silverlight application with the opportunity to cancel, or at least interrupt the close window process.

In the first scenario I found the way I could achieve the call after the Silverlight applications Exit event was raised, was to call a JavaScript method from Silverlight, and then finally a PageMethod; to perform the housekeeping.

 

Figure: A web service is called after the user closes the browser.

We see that in our App.xaml.cs the Exit event handler is called, which then calls a JavaScript function, as demonstrated in the following excerpt:

void Application_Exit(object sender, EventArgs e)
{
    HtmlPage.Window.Invoke("NotifyWebserviceOnClose", 
          new[] { guid.ToString() });
}

We then call a PageMethod on the page hosting the application like so:

function NotifyWebserviceOnClose(identifier) {
    PageMethods.SignOff(identifier, OnSignOffComplete);            
}

The PageMethod is where we are able to update a list of connected clients, or do whatever takes our fancy.

[WebMethod]
[System.Web.Script.Services.ScriptMethod] 
public static void SignOff(string identifier)
{
    /* Update your list of signed in users here etc. */
    System.Diagnostics.Debug.WriteLine("*** Signing Off: " + identifier);
}

Caveat lector- According to Laurent this is not reliable. So, you will need to test it for yourself. I’ve tested it locally in Firefox 3.5 and IE8. But, your results may differ.

Now the second thing I wanted to look at was the ability to provide some interaction with the user, either cancelling or calling a web service, when the user attempts to close the browser; but before the browser is actually closed.

The way I did this was to place an onbeforeunload handler in the body tag of the page, as shown:

<body onbeforeunload="OnClose()">

I then created the OnClose handler in a JavaScript script block as shown in the following excerpt:

function OnClose() {           
    var plugin = document.getElementById("silverlightControl");
    var shutdownManager = plugin.content.ShutdownManager;
    var shutdownResult = shutdownManager.Shutdown();
    if (shutdownResult) {
        event.returnValue = shutdownResult;
    }
}

Here we see that we retrieve the Silverlight application and resolve an instance of the ShutdownManager class present in the Silverlight App class. The ShutdownManager is a class that is able to perform some arbitrary duty when the application is closing. It is made accessible to the host webpage by registering it with a call to RegisterScriptableObject.

void Application_Startup(object sender, StartupEventArgs e)
{
    HtmlPage.RegisterScriptableObject("ShutdownManager", shutdownManager);
    this.RootVisual = new MainPage();
}

ShutdownManager

The ShutdownManager is decorated with two attributes that allow it to be consumed by an external source. They are SciptableType and ScriptableMember. As we see below, they are used to allow the ShutdownManager class to be called via Ajax.

[ScriptableType]
public class ShutdownManager
{
    [ScriptableMember]
    public string Shutdown()
    {
        string result = null;
        var page = (MainPage)Application.Current.RootVisual;

        if (page.Dirty)
        {
            var messageBoxResult = MessageBox.Show(
                "Save changes you have made?", "Save changes?", MessageBoxButton.OKCancel);
            if (messageBoxResult == MessageBoxResult.OK)
            {
                var waitingWindow = new WaitingWindow();
                waitingWindow.Show();

                waitingWindow.NoticeText = "Saving work on server. Please wait...";
                page.SetNotice("Saving work on server. Please wait...");

                var client = new ShutdownServiceClient();
                client.SaveWorkCompleted += ((sender, e) =>
                    {
                        waitingWindow.NoticeText = "It is now safe to close this window.";
                        page.SetNotice("It is now safe to close this window.");
                        page.Dirty = false;
                        waitingWindow.Close();
                    });
                client.SaveWorkAsync("Test id");
                result = "Saving changes.";
            }
            else
            {
                result = "If you close this window you will lose any unsaved work.";
            }
        }

        return result;
    }
}

Here we see that once the Shutdown method is called we test if the main (Silverlight) page is dirty. If so, we prompt the user to save changes. If changes are to be saved we dispatch our async web service call using a service client. The key thing to note here is that we are returning a string value if the page is deemed dirty. This string value then indicates to the consuming JavaScript code that the window should not be closed immediately. Once the call makes it back to our JavaScript onbeforeload handler, we assign the event.returnValue the value of this string. If this value is not an empty string it will cause the web browser to display a dialog; asking the user whether they really wish to leave the page.

Figure: Browser prompts with ‘Are you sure ...’ message.

In the demo application accompanying this post, you will notice that we simulate a user modification. This is done by ticking the Page dirty checkbox.

 

Figure: The demonstration application simulates a modification to the data model.

Unticked, closing the browser will result in the window closing immediately. However, ticked will cause the presentation of a Save Changes dialog, as shown below.

Figure: Save changes dialog prompts user.

What happens if the user cancels the operation? Remember our event.returnValue string in the JavaScript OnClose function. If the user cancels, then this value is assigned a string value of “If you close this window you will lose any unsaved work.” We can see how the browser chooses to display the string shown below:

Figure: User is prompted with browser initiated dialog.

Obviously we must deal with when the user selects the nominal Ok button in the save dialog. To provide a somewhat more agreeable user experience, I have dropped in a Silverlight ChildWindow control, with a ProgressBar control for good measure.

Figure: A progress bar is presented to the user while saving.

Once the user hits Ok on the Save Changes dialog, that web service call is dispatched almost immediately. In order for the web service call to occur though, we must perform interrupt the thread so that our Silverlight App has a chance to dispatch the call. We can do that with a JavaScript alert call, or as we see here, we assign the event.returnValue, which causes a dialog to be displayed in much the same manner.

Conclusion

In this post we have seen how it is possible to call a web service after a Silverlight App’s Exit event has been raised. While this may not be reliable, I’d be interested to know your experience with this approach, and certainly of any alternate approaches. Finally we looked at interacting with the user, to allow cancellation of a close window event or to perform last minute web service calls. This was achieved using the onbeforeunload event and a JavaScript handler.

Thank you for reading.

Download the sample (557.06 kb)



Article Published: Project Metadata Generation using T4

clock September 3, 2009 22:58 by author Daniel Vaughan

Metadata Generation with T4 Overview

This article is an elaboration of my previous experimentation with T4 (Text Template Transformation Toolkit) and describes how to use T4, which is built into Visual Studio 2008, and the Visual Studio automation object model API, to generate member and type information for an entire project. Generated metadata can then be applied to such things as dispensing with string literals in XAML binding expressions and overcoming the INotifyPropertyChanged property name string code smell, or indeed any place you need to refer to a property, method, or field by its string name. There is also experimental support for obfuscation, so member names can be retrieved correctly even after obfuscation. I've also ported the template to VB.NET, so our VB friends can join in on the action too.

View Article



Transparent WCF Channel Management with Unity

clock July 30, 2009 13:55 by author Daniel Vaughan

Introduction

It is generally considered good form to define a separate ServiceContract interface for all WCF services. By doing so, it decouples the contract from the implementation. Still, if we consume a service contract via conventional means such as generating a proxy using a ChannelFactory or using a ServiceReference generated proxy, we couple the service with the WCF infrastructure. So what could be wrong with that? Well, say if we have a local version of a service, and a remote implementation of the service, one must be retrieved using a WCF specific measure like those just mentioned, while the other can be registered with our DI container and retrieved that way. But the code that is shared between the local and remote implementations must be aware of this, and this inhibits reusability. Wouldn’t it be better if retrieval of all services was done homogeneously? We can indeed achieve this, and without any coupling to the WCF infrastructure. I will explain how in just a moment, but first some background.

Background

I choose to manage my service channels in a centralized way. I do this by using an IChannelManager implementation. You can find out more about the use of the IChannelManager implementation in a number of my articles. In particular here and here

There are a several advantages to this approach, not least of which is that the Channel Manager takes care of caching channel proxies, and recreating them if and when a fault occurs.

Using Unity’s IStaticFactoryConfiguration to Transparently Retrieve Proxies

Those familiar with Unity will know that to registers a type or an instance with the container, one can either define the association in config or at runtime by calling the Containers RegisterInstance or RegisterType methods. There is, however, a third approach: we can register a factory method to perform the retrieval by configuring the IStaticFactoryConfiguration. This enables us to have Unity drive the generation or retrieval of a channel proxy using the ChannelManagerSingleton.

Figure: Retrieval of a service channel via Unity.

Firstly we must register the extension with Unity. The extension can be found in the Microsoft.Practices.Unity.StaticFactory.dll assembly, and should be reference by your project. We then add the extension to our Unity container like so:

Container.AddNewExtension<StaticFactoryExtension>();

We are then able to have Unity retrieve the IChannelManager instance in order to retrieve the service proxy when it is requested.

var channelManager = UnitySingleton.Container.Resolve<IChannelManager>();
UnitySingleton.Container.Configure<IStaticFactoryConfiguration>().RegisterFactory<IMyService>(
container => channelManager.GetChannel<IMyService>());

Thus, we no longer need to retrieve the IChannelManager in our code in order to retrieve the channel proxy. All we need do is grab it straight from Unity like so:

var myService = UnitySingleton.Container.Resolve<IMyService>();

Opening the channel, testing connectivity, caching it etc., is all done behind the scenes!

There is a drawback to this approach. If, for whatever reason, the channel proxy is unable to be retrieved by the Channel Manager then Unity will raise a ResolutionFailedException, and the reason for the failure will be hidden as an inner exception. So, we must gear our code to be resilient to resolution failures and depend not only on Unit Tests discovering missing type registrations. This post covers simplex services, and I have still to explore an elegant way to achieve the same result for duplex services with their callback instances.

Conclusion

We have seen how, by using Unity, we are able to further abstract the retrieval of channel proxies. This allows us to consume services in the same way whether they be local or via WCF. By doing this we are able to achieve location agnostic service consumption, and increase the reusability of shared code across local and remote deployment scenarios. The source for the Channel Manager and other goodies can be found at http://calcium.codeplex.com/

 



Order the Book

Ready to take your Windows Phone development skills to the next level? My book is the first comprehensive, start-to-finish developer's guide to Microsoft's Windows Phone 8. In it I teach through complete sample apps that illuminate each key concept with fully explained code and real-world context. Windows Phone 8 Unleashed

Windows Phone Experts Windows Phone Experts
LinkedIn Group

 

Bio

Daniel VaughanDaniel Vaughan is co-founder and president of Outcoder, a Swiss software and consulting company dedicated to creating best-of-breed user experiences and leading-edge back-end solutions, using the Microsoft stack of technologiesin particular WPF, WinRT, and Windows Phone. 

Daniel is a four-time Microsoft MVP for Client Application Development, with experience across a wide range of industries including finance, e-commerce, and digital media. 
Daniel is the author of Windows Phone 7.5 Unleashed and Windows Phone 8 Unleashed, published by SAMS.

Daniel is a Silverlight and WPF Insider, a member of the WPF Disciples, and a member of the Microsoft Developer Guidance Advisory Council.
Daniel also sits on the advisory board of PebbleAge, a Swiss Financial Software company.

While originally from Australia and the UK, Daniel is currently based in Zurich Switzerland. 

Daniel is the developer behind several acclaimed Windows Phone apps including Intellicam and Splashbox; and is the creator of a number of popular open-source projects including the Calcium and Clog.

Daniel also manages the Windows Phone Experts group on LinkedIn; a group that has over 3000 independent developers, Microsoft employees, and Windows Phone enthusiasts.


E-mail Send mail

 

Microsoft MVP logo Disciple
WPF and Silverlight Insiders
 

 

 

Sign in