Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Frictionless WCF Service Consumption in Silverlight: Part 1

0.00/5 (No votes)
18 Sep 2010 2  
Consuming WCF Services in Silverlight without static code generation of the service proxy.

Introduction

This article will show you how to consume plain WCF services in Silverlight without the need to statically generate code for a service proxy (a.k.a. VS Add Service Reference wizard).

Background

While the recommended (by Microsoft) way to implement interaction with server-side in Silverlight is to use WCF RIA Services, I've found it to be very limiting. In fact, it wasn't working well with the way our EF entities are decorated. Some of the additional code attributes we've introduced for localization support have references to types that are not entities themselves, thus the code generation tool (invoked because of the presence of the WCF RIA Services Link) had merely ignored them, leaving us with non-compilable code. Also, poor support for DMZ-based deployment scenarios forced us to find other ways to deal with the problem. Instead of "fighting with the tool", I've decided to go the well-known route and simply use plain WCF Services. That decision, in its turn, introduced some friction, which I've set to eliminate within this article series.

Sample

Imagine that we have the following service definition:

[ServiceContract]
public class CalculatorService
{
    [OperationContract]
    public Number Add(Number fist, Number second)
    {
        return new Number {Value = fist.Value + second.Value};
    }
}

[DataContract]
public class Number
{
    [DataMember]
    public int Value
    {
        get; set;
    }
}

It is very easy to consume it in Silverlight - just use the Add Service Reference wizard in Visual Studio, and it will generate all the required plumbing code for you. I won't be listing every possible trait and problem incurred by using this simplistic approach (there are already a lot of discussions on the Internet); just the most notable (at least for me) are: hard to test (not easy to mock) generated proxies, bloating the size of the resulting XAP file, need to refresh the reference with every change (boring), no TDD. Also, this approach still doesn't resolve my problem with custom attributes, because the classes generated for the client-side are not the same as the classes on the server-side. What I want instead is to actually share the same class definitions between both sides.

Reusing server-side code

To reuse (share) class definitions, it is required to introduce a service interface and put it (and all related classes) in the separate assembly that should be visible to both the client and server code. However, this is not the case for Silverlight. In Silverlight, you cannot directly reference a CLR assembly. So instead, we need to create a separate Silverlight Class Library and include the class definitions by linking to source files.

[ServiceContract]
public interface ICalculatorService
{
    [OperationContract]
    Number Add(Number fist, Number second);
}

public class CalculatorService : ICalculatorService
{
    public Number Add(Number fist, Number second)
    {
        return new Number {Value = fist.Value + second.Value};
    }
}

Picture of linked files in Solution Explorer

Now if you regenerate the proxy (Update Service Reference in VS), the code generation tool will be able to reuse class definitions that we previously linked.

So being able to completely reuse server-side interfaces, the code generation requirement now seems to be redundant. What we only gain with it is just a generation of simple wrappers around the WCF IChannel interface. The creation and configuration of WCF channel can be done dynamically at runtime - there is no need to use the ClientBase<T> class for such a simple case.

Removing friction

First try

It is very easy to create and configure a WCF channel at runtime:

var factory = new ChannelFactory<ICalculatorService>(
           new BasicHttpBinding(),
           new EndpointAddress("http://localhost/SampleSilverlightWCF"));

ICalculatorService service = factory.CreateChannel();

This is what I first tried, and as soon as it was run, I got the following:

Erorr picture

Surprise, the WCF runtime in Silverlight forces you to use only an asynchronous interface for service calls. You can't directly use a synchronous service interface in Silverlight, and you either need to implement your service with an asynchronous pattern, or resort to service proxy generation (which will generate an asynchronous interface for your service). This looks like a showstopper.

Second try

I know that there should be a solution. I don't want to implement my service operations in an asynchronous fashion, nor do I want to use code generation. Stop, how come the generation of the service proxy solves this without forcing me to implement service operations as asynchronous!? Well, if you look at the generated code, you will see that it is not reusing the synchronous service interface; instead, it creates its asynchronous twin. This interface is then used to communicate with the synchronous service in an asynchronous fashion. Cool trick!

So I've ended up creating two interfaces: a plain synchronous one - to implement on the server-side; and its asynchronous copy - to be used on the client-side.

[ServiceContract]
public interface ICalculatorService
{
    [OperationContract]
    Number Add(Number fist, Number second);
}

[ServiceContract(Name="ICalculatorService")]
public interface ICalculatorServiceAsync
{
    [OperationContract(AsyncPattern = true)]
    IAsyncResult BeginAdd(Number fist, Number second, 
                          AsyncCallback callback, object state);
    void EndAdd(IAsyncResult result);
}

Now, to be able to successfully interact with the service, you need to use the asynchronous interface when creating the WCF channel:

ICalculatorServiceAsync service;

private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
    var factory = new ChannelFactory<ICalculatorServiceAsync>(
        new BasicHttpBinding(),
        new EndpointAddress("http://localhost/SampleSilverlightWCF/" + 
                            "Services/CalculatorService.svc"));

    service = factory.CreateChannel();

    service.BeginAdd(
        new Number { Value = 2 },
        new Number { Value = 2 },
        OnAddCompleted, null
    );
}

void OnAddCompleted(IAsyncResult ar)
{
    Number result = service.EndAdd(ar);
    Debug.Assert(result.Value == 4);
}

Works like a charm!

Conclusion

You are not required to use static code generation of the service proxy, nor are you required to sacrifice an interface and implementation of your WCF service in order to consume it in Silverlight. What you need is:

  • Share code definitions you want to reuse in Silverlight via source file linking
  • Create a secondary asynchronous interface to be used only in Silverlight (you don't need any implementation for it)

The secondary asynchronous interface should exhibit the following rules:

  • The interface should declare an alias to the original synchronous interface via [ServiceContract[Name="OriginalInterfaceName]]="
  • There should be a pair of BeginXXX/EndXXX methods for each operation in the synchronous interface
  • Each BeginXXX method should be marked with the [OperationContract(AsyncPattern=true)] attribute
  • Each BeginXXX method should have the same parameters in the same order as its original synchronous counterpart + two special parameters (AsyncCallback callback, object state), and return IAsyncResult
  • Each EndXXX method should return the type of the original method (or void) and accept only a single parameter of type iAsyncResult

Further articles

While we've removed "some" friction, there is still room for improvement. In the following articles of this series, I'll show you how to simplify the solution even further (removing the need for a secondary interface), how to simplify the handling of asynchronous service calls in Silverlight, and how to make WCF consumption in Silverlight more TDD friendly. Stay tuned.

See Part 2 of this series: Fixing the "All Is Async" problem.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here