Blendable MVVM: WCF and Asynch Data Sources

Posted Monday, October 26, 2009 by

In the previous posts we've been retrieving our data from a synchronous data source, for the most part in Silverlight we'll be retrieving data sources from aseparate server, in this case through a WCF Service. Silverlight has a restriction that all network calls must be asynchronous in order to avoid locking up the browser UI (a good thing). It does however make the code a little more complicated.

The functionality of the CocktailsService will stay the same, we're just going to migrate it over to the server and host it as a WCF service. The code for the service is remains exactly the same, just shifted now to the server and hosted as a WCF service. If you'd like to learn more about setting up a WCF service you can read more on MSDN.

[ServiceContract]

public interface ICocktailService

{

    [OperationContract]

    IEnumerable<Cocktail> GetCocktails();

 

    [OperationContract]

    IEnumerable<Cocktail> GetCocktailsSimilarTo(Cocktail cocktail);

}

Then we generate a client proxy using Visual Studios "Add Service Reference". The proxy generated includes any types from the data contracts (the Cocktail class), the proxy client (CocktailsServiceClient) and a service interface (ICocktailsService). One thing to notice if you've done any WCF work before is that the interface doesn't contain the events exposed by the actual client class.

We'll now need to alter our Cocktails view model to deal with the new interface. Basically it's a relatively simple change from synchronous methods to using async ones. It should look like this...

public class CocktailsViewModel : ViewModelBase<CocktailsViewModel>

{

    private readonly ICocktailService cocktailService;

 

    public CocktailsViewModel(ICocktailService cocktailService)

    {

        this.cocktailService = cocktailService;

 

        AvailableCocktails = new ObservableCollection<Cocktail>();

        SimilarCocktails = new ObservableCollection<Cocktail>();

 

        GetSimilarCocktailsCommand = new DelegateCommand<Cocktail>(GetSimilarCocktails);

 

        cocktailService.BeginGetCocktails(OnGetCocktails, null);

    }

 

    private void OnGetCocktails(IAsyncResult ar)

    {

        var cocktails = cocktailService.EndGetCocktails(ar);

 

        AvailableCocktails.AddRange(cocktails);

    }

 

    public ObservableCollection<Cocktail> AvailableCocktails

    {

        get;

        set;

    }

 

    public ObservableCollection<Cocktail> SimilarCocktails

    {

        get;

        set;

    }

 

    public ICommand GetSimilarCocktailsCommand

    {

        get;

        private set;

    }

 

    protected void GetSimilarCocktails(Cocktail cocktail)

    {

        cocktailService.BeginGetCocktailsSimilarTo(cocktail, OnGetCocktailsSimilarTo, null);

    }

 

    private void OnGetCocktailsSimilarTo(IAsyncResult ar)

    {

        var cocktails = cocktailService.EndGetCocktailsSimilarTo(ar);

 

        SimilarCocktails.Replace(cocktails);

    }

}

There is a slight problem with the code above. Silverlight expects property changed notifications (including collection changed from ObservableCollection) to be on the UI thread. The async result methods will be on a separate thread (The generated service client will dispatch the OnXCompleted events back to the UI thread but not the asynch methods). There a couple of ways we can solve this.

  1. Manually dispatch any updates to properties back to the UI thread.
  2. Create our own ObservableCollection that ensures all notifications are on the correct thread. An example of this can be found at "Adding to an ObservableCollection from a background thread"

For this example we'll just be going with the first. We'll update our ViewModelBase to take a reference to the Application dispatcher and have a simple helper method.

protected ViewModelBase()

{

    Dispatcher = Deployment.Current.Dispatcher;

}

 

protected Dispatcher Dispatcher

{

    get; private set;

}

 

protected void InvokeOnUIThread(Action action)

{

    Dispatcher.BeginInvoke(action);

}

We then alter the offending methods to use the helper.

private void OnGetCocktails(IAsyncResult ar)

{

    var cocktails = cocktailService.EndGetCocktails(ar);

 

    InvokeOnUIThread(() => AvailableCocktails.AddRange(cocktails));

}

private void OnGetCocktailsSimilarTo(IAsyncResult ar)

{

    var cocktails = cocktailService.EndGetCocktailsSimilarTo(ar);

 

    InvokeOnUIThread(() => SimilarCocktails.Replace(cocktails));

}

Our updates to the collections will be dispatched off to the UI thread and the exception will be avoided.

Shout it kick it on DotNetKicks.com

Resuing types in Silverlight Service References

Posted Thursday, June 4, 2009 by

A question I see a lot on sites like Stack Overflow and the Silverlight forums is around sharing or reusing types in WCF Service References, either between multiple services or between the client and server.

When you create a Service Reference in a Silverlight project Visual Studio will generate proxies for the Service and Data Contracts. These are partial classes so if you want to extend them with some logic (or an interface for dependency injection) you can pretty easily. This has a few problems, if you have multiple services that use the same data contracts then each will have it's own proxies. For instance an OrderService and a ProductService will often share the Product type. Simply generating service references to both of those will cause dual Product types in your Silverlight client.

Thankfully Visual Studio has some tooling support to deal with this, however it has some tricks to make it work correctly. When you create a Service Reference there is an advanced option titled "Reuse types in referenced assemblies" with some options about which references to use. The first trick is that it's referenced assemblies and therefore will not use types in the assembly of the project you're adding the reference to.

So which types will it use:

  • Types in assemblies referenced by your project.
  • Types with the same name and namespace.
  • It doesn't matter if the client side type has the DataContract and DataMember attributes.
  • It doesn't matter if the client side type has the INotifyPropertyChanged interface (although I recommend it does).

It appears it looks for types that have the same Full Name as the DataContract type. It doesn't appear to examine the actual signature of the type, so if you're writing your own client types there could be a problem there if you're not careful.

So now that we know which types "Reuse types in referenced assemblies" will use how can be put this to use. As it only uses types in referenced assemblies we need an assembly (usually a Silverlight Class Library project) to add these types to. Normally this isn't a problem I think only once have I needed to add a project for just this purpose.

Now while you can create your own types for the Silverlight Service References I prefer to share the source code for these between the client and server. The method I use is "Add Existing Item" - "Add As Link" in Visual Studio. That way the code is only written once and defined on both the client and server. This could cause some problems with having the different CLR versions but for most service objects the shouldn't be a problem.There are lots of articles out there about sharing code between WPF and Silverlight so find the best method for you.

kick it on DotNetKicks.com

A client for duplex services in Silverlight

Posted Sunday, March 8, 2009 by

I'm currently working on a project that requires the use of duplex services to report progress on a long running event (I'll go more into the actual event in a later post). The reference material I started with was "How to: Build a Duplex Service" and "How to: Access a Duplex Service with the Channel Model", building the service itself its a relatively simple affair with a slight learning experience as I hadn't really used the Message class from WCF before.

The client is relatively simple as well, provided you can work your way through the copious amounts of asynchronous methods. Essentially all we're doing is opening a factory, then a channel and creating a receive message loop. This code looks really boilerplate so I encapsulated it into a nicely reusable DuplexServiceClient.

public class DuplexServiceClient

{

    private IChannelFactory<IDuplexSessionChannel> factory;

    private readonly Dictionary<string, IMessageHandler> handlers;

    private readonly string serviceUrl;

    private readonly SynchronizationContext uiThread = SynchronizationContext.Current;

    private IDuplexSessionChannel channel;

 

    public DuplexServiceClient(string serviceUrl)

    {

        this.serviceUrl = serviceUrl;

        handlers = new Dictionary<string, IMessageHandler>();

    }

 

    public void Open()

    {

        var binding = new PollingDuplexHttpBinding

        {

            InactivityTimeout = TimeSpan.FromMinutes(1)

        };

 

        factory = binding.BuildChannelFactory<IDuplexSessionChannel>(new BindingParameterCollection());

 

        var openResult = factory.BeginOpen(r =>

        {

            if(!r.CompletedSynchronously)

                CompleteOpenFactory(r);

        }, factory);

 

        if(openResult.CompletedSynchronously)

            CompleteOpenFactory(openResult);

    }

 

    public event EventHandler<MessageReceivedEventArgs> MessageReceived;

 

    protected virtual void OnMessegeReceived(MessageReceivedEventArgs e)

    {

        var messageReceived = MessageReceived;

 

        if(messageReceived != null)

            messageReceived(this, e);

    }

 

    public void SendMessage(string action, object data)

    {

        var message = Message.CreateMessage(MessageVersion.Soap11, action, data);

 

        var result = channel.BeginSend(message, r =>

        {

            if(!r.CompletedSynchronously)

                CompleteSend(r);

        }, channel);

 

        if(result.CompletedSynchronously)

            CompleteSend(result);

    }

 

    private void CompleteSend(IAsyncResult result)

    {

        channel.EndSend(result);

    }

 

    private void CompleteOpenFactory(IAsyncResult result)

    {

        factory.EndOpen(result);

 

        channel = factory.CreateChannel(new EndpointAddress(serviceUrl));

 

        var openResult = channel.BeginOpen(r =>

        {

            if(!r.CompletedSynchronously)

                CompleteOpenChannel(r);

        }, channel);

 

        if(openResult.CompletedSynchronously)

            CompleteOpenChannel(openResult);

    }

 

    private void CompleteOpenChannel(IAsyncResult result)

    {

        channel.EndOpen(result);

 

        ReceiveMessage(channel);

    }

 

    private void ReceiveMessage(IInputChannel receivingChannel)

    {

        var result = receivingChannel.BeginReceive(r =>

        {

            if(!r.CompletedSynchronously)

                CompleteReceiveMessage(r);

        }, receivingChannel);

 

        if(result.CompletedSynchronously)

            CompleteReceiveMessage(result);

    }

 

    private void CompleteReceiveMessage(IAsyncResult result)

    {

        var message = channel.EndReceive(result);

 

        if(message == null)

            return;

 

        var action = message.Headers.GetHeader<string>("Action", "");

 

        if(!String.IsNullOrEmpty(action) && handlers.ContainsKey(action))

        {

            var handler = handlers[action];

            var serializer = new DataContractSerializer(handler.BodyType);

            var body = serializer.ReadObject(message.GetReaderAtBodyContents());

 

            uiThread.Post(p => handler.Invoke(body), null);

        }

 

        uiThread.Post(p => OnMessegeReceived(new MessageReceivedEventArgs(message)), null);

 

        ReceiveMessage(channel);

    }

 

    public void RegisterMessageHandler<T>(string action, Action<T> handler)

    {

        if(handlers.ContainsKey(action))

            throw new InvalidOperationException("Handler already registered for action " + action);

 

        handlers.Add(action, new MessageHandler<T>(handler));

    }

 

    public void Close()

    {

        var result = channel.BeginClose(r =>

        {

            if(!r.CompletedSynchronously)

                CompleteClose(r);

        }, null);

 

        if(result.CompletedSynchronously)

            CompleteClose(result);

    }

 

    private void CompleteClose(IAsyncResult result)

    {

        channel.EndClose(result);

    }

}

 

One of the downsides to the duplex clients like this is that there's a single entry point for all messages being received by the duplex client. We need a nice way to inspect the incoming message and dispatch it to the appropriate handler. WCF has a method for this on the server side, Messages have an Action, this Action is used at the Endpoint to dispatch the message to the appropriate method on the service. For instance the Action "CompiledExperience/IOrderService/Process" is dispatched to the Process method of IOrderService in the CompiledExperience namespace (not the C# namespace but the namespace defined on the ServiceContract).

public class EncoderClient

{

    private readonly DuplexServiceClient serviceClient;

 

    public EncoderClient(string url)

    {

        serviceClient = new DuplexServiceClient(url);

        serviceClient.RegisterMessageHandler<EncodeSuccessMessage>("CompiledExperience/Video/Success", OnEncodeSuccess);

        serviceClient.RegisterMessageHandler<EncodeErrorMessage>("CompiledExperience/Video/Error", OnEncodeError);

        serviceClient.RegisterMessageHandler<EncodeProgressMessage>("CompiledExperience/Video/Progress", OnEncodeProgress);

    }

 

    public void Encode(int clientId, string fileName, string description, Size size)

    {

        serviceClient.Open();

 

        serviceClient.SendMessage("CompiledExperience/Video/Encode", new EncodeVideoMessage

           {

               ClientId = clientId,

               FileName = fileName,

               Description = description,

               Width = (int)size.Width,

               Height = (int)size.Height

           });

    }

 

    private void OnEncodeError(EncodeErrorMessage message)

    {

        serviceClient.Close();

    }

 

    private void OnEncodeSuccess(EncodeSuccessMessage message)

    {

        serviceClient.Close();

    }

 

    private void OnEncodeProgress(EncodeProgressMessage message)

    {

        // Report Progress here ...

    }

}

 

Sadly the Messages received on the duplex client channel don't preserve their Action. But we can duplicate something similar using the Message.Headers collection. On the server we'll need to add a Header to the Message named Action, it can be any string you want and doesn't need to follow the same style pattern that the WCF Endpoint dispatcher expects. This can be done like follows.

public static class MessageFactory

{

    public static Message Create<T>(string action, T data)

    {

        var message = Message.CreateMessage(MessageVersion.Soap11, action, data, new DataContractSerializer(typeof(T)));

 

        message.Headers.Add(MessageHeader.CreateHeader("Action", "", action));

 

        return message;

    }

}

 

On the client side we want to be able to register a handler for an action, as bonus we'll define what object the Message.Body is a serialization of. The handler can take a reference to that object. This way each handler is not dependent upon the Message class at all. This part does depend on the serialization being the same on both ends so I've gone with the default DataContract Serialization.

Hope this helps someone.

Page 1 of 11

Professional Windows App Development