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

Using WCF Service with Silverlight

0.00/5 (No votes)
30 Sep 2011 1  
This article shows how to use WCF service with Silverlight

Introduction

In one of our products, we had to use WCF service with Silverlight. While working on Silverlight and WCF, I found out some very interesting things and I feel that those are worth sharing. Whenever we want to work with Silverlight and need some kind of service communication, we would encounter those common problems. In this article, we are going to discuss a few interesting findings about WCF and Silverlight Bridge.

For the purpose of demonstration, we are going to use a demo application with some simple class. Let's say we have an expense management application and we have a client build with Silverlight. And the client communicated to server via WCF. Let's take this example to fit our explanation process.

Using WCF with Silverlight

In this section, we will see how we can use WCF service with Silverlight. I am sure every reader has a nice grip on what is WCF and how we can use it in web applications. Some of the stuff is repeated and discussed again and may sound familiar. But as we need some parts as a subset, I am discussing it again. First, we would look at the basics and then we would see some other relevant tricks and information.

The WCF Basics

WCF has three basic building blocks. Those are called A,B,C of WCF. A stands for Address, B stands for Binding and C stands for contract. In a later section, we would see the most common knowledge that is necessary to work with WCF, of course communication with Silverlight context. WCF can be hosted in several ways. Most common ones are given below:

  • Hosting in Internet Information Services
  • Hosting in Windows Process Activation Service
  • Hosting in a Windows Service Application
  • Hosting in a Managed Application

We are not going to discuss about the above hosting options in this article as those need further study, and assumes to not be the focused area of the article. I would recommend that you spend some time on MSDN or Google to learn more about WCF hosting process.

The Service Contract and Operation Contract

I am sure we all know what a service contract is, but I am still discussing it as it is a beginner article. We make any class a service contract by adding one simple class attribute "[ServiceContract]" but it's better to declare an interface first and then apply class attribute to that interface and then implement class in a derived class. Now the methods that we would want to expose as part of the service need to be decorated with a method attribute named "[OperationContract]".

Below we have a code block with an interface that has been declared as a service contract and then we have implemented a derived class from the interface to define the methods that would be invoked via clients.

Service Contract

[ServiceContract]
public interface IMoneyService
{
    [OperationContract]
    ServiceResponse AddExpense(Expense expense);
    [OperationContract]
    ServiceResponse UpdateExpense(Expense expense);
    [OperationContract]
    ServiceResponse DeleteExpense(Expense expense);
    [OperationContract]
    ServiceResponse GetExpenseByID(int expenseId);
    [OperationContract]
    ServiceResponse AddCategory(Category category);
    [OperationContract]
    ServiceResponse UpdateCategory(Category category);
    [OperationContract]
    ServiceResponse DeleteCategory(Category category);
    [OperationContract]
    ServiceResponse GetCategoryByID(int categoryId);
}

Implementation of the Contract

[AspNetCompatibilityRequirements
	(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MoneyService : IMoneyService
{
    public ServiceResponse AddExpense(Expense expense)
    {
        var response = new ServiceResponse();
        using (var manager = new ExpenseManager())
        {
            try
            {
                response.Result = manager.AddExpense(expense);
                response.IsSuccess = true;
            }
            catch (Exception exception)
            {
                response.ServiceException = exception;
                response.IsSuccess = false;
                response.ErrorMessage = "Unable to add Expense";
            }
        }
        return response;
    }
    
    ///Other method's implementation.... goes bellow
    ///....
}    

In the above example, we didn't put down all the implementation in one method to demonstrate the idea.

The Data Contract

If we want to transfer custom data via WCF from server to client, we have to apply "[DataContract]" class attribute on the custom data type. Besides, all primitive datatypes can be used as a transferable data. Below we have added a simple class that we have used as our custom type to transfer data from service to client.

You might need to use KnownType attribute in case you have nested custom type in your data contract.

[KnownType(<span style="COLOR: blue">typeof</span>(your-custom-type))]

Below, a sample Data contract implementation is given.

[DataContract]
public class ServiceResponse
{
    private string _errorMessage;
    [DataMember]
    public string ErrorMessage
    {
        get { return _errorMessage; }
        set { _errorMessage = value; }
    }
    private object _result;
    [DataMember]
    public object Result
    {
        get { return _result; }
        set { _result = value; }
    }
    private bool _isSuccess;
    [DataMember]
    public bool IsSuccess
    {
        get { return _isSuccess; }
        set { _isSuccess = value; }
    }
    private Exception _serviceException;
    [DataMember]
    public Exception ServiceException
    {
        get { return _serviceException; }
        set { _serviceException = value; }
    }
}

ASP.NET Compatibility

This particular scenario is very useful if we host the WCF service in the IIS environment. The idea is to share the same HttpContext in service methods and inner methods so that we can access session and application data of the web application where the WCF service is being exposed.

var context = HttpContext.Current;
var path = context.Server.MapPath("~/MyPics");

if (!Directory.Exists(path))
{
    Directory.CreateDirectory(path);
}

var userFolder = string.Format("{0}\\{1}", path, userId);

if (!Directory.Exists(userFolder))
{
    Directory.CreateDirectory(userFolder);
}    

In the above example, we want to find a folder name "MyPics" where the pictures of a particular user will be saved. So we need the httpContext where the service is running.

Below, the service configuration is given. The key stuff is the tag "serviceHostingEnvironment" we have to set the attribute aspNetCompatibilityEnabled="true".

    <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="PEM.MoneyTrackingServiceAspNetAjaxBehavior">
          <enableWebScript />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
      multipleSiteBindingsEnabled="true" />
    <services>
      <service name="PEM.MoneyTrackingService">
        <endpoint address="" 
	behaviorConfiguration="PEM.MoneyTrackingServiceAspNetAjaxBehavior"
          binding="webHttpBinding" contract="PEM.MoneyTrackingService" />
      </service>
    </services>
   </system.serviceModel>

After that, we have to put the AspNetCompatibilityRequirements class attribute with value RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed to the desired service contract implementation. Note that this attribute cannot be added before the interface which is the contract. We have to add it before the service contract implementation.

[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements
	(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MoneyTrackingService
{
    [OperationContract]
    public void DoWork()
    {
        return;
    }
}

Adding Service Reference to Silverlight

Alright. We have the basics on our finger tip, now it's time to make the service workable. Here are the steps we have to do.

  • Step 1: Define Data Contract that we want to transmit via service. Add "[DataContract]" class attribute to class and "[DataMember]" property attribute to properties. If we have any enum in the DataContract we have to apply EnumMember attribute.
  • Step 2: Define interface for ServiceContract with methods, add service contract attribute. mark each method that we want to expose to OperationContract.
  • Step 3: Define a derived class for ServiceContract implementation. Add the necessary class attribute if we need ASP.NET compatibility.
    In our case, we have defined those classes and interface in a separate project named MoneyTracking.Service.
  • Step 4: Add a reference to the project where we defined the classes. If we defined all the classes and interfaces in the web project, then no worries.
  • Step 5: Add a svc file. Get rid of the "cs" file. Modify the XML for the .svc file. put down the proper service name.
     <%@ ServiceHost Language="C#" Debug="true" 
    	Service="full Qualified name goes here" %>
  • Step 6: Define proper service definition in service model tag of web.config. We have defined the XML in the previous section, we can also copy from there. Note that we should at mex binding as well.
  • Step 7: Browser the service to see if everything is working fine or not.
  • Step 8: Go to the Silverlight project where we want to consume the service, right click on the project's reference node and select add Service Reference, which will bring the configure add service reference wizard. Rest of the work is self explanatory in the wizard.

When you finished adding the service, you will have a service reference node where the service will be added. Visual Studio also adds lots of code behind the scenes, an also a ServiceReferences.ClientConfig file. We will see the uses in later sections.

ServiceReferences.ClientConfig

<configuration>
<system.serviceModel>
    <bindings>
        <basicHttpBinding>
            <binding name="BasicHttpBinding_IMoneyService" maxBufferSize="2147483647"
                maxReceivedMessageSize="2147483647">
                <security mode="None" />
            </binding>
        </basicHttpBinding>
    </bindings>
    <client>
        <endpoint address="http://localhost/MoneyTrackingService.svc"
            binding="basicHttpBinding" 
		bindingConfiguration="BasicHttpBinding_IMoneyService"
            contract="MoneyTrackingServiceReference.IMoneyService"
            name="BasicHttpBinding_IMoneyService" />
    </client>
</system.serviceModel>
</configuration>    

Using the Channel Factory

So far, we have three ways to use a WCF service in Silverlight:

  • Using a service reference
  • Using chanel Factory
  • Using Client base

Below, we have explained how the chanel factory can be used to define a WCF service in Silverlight side. Note that in this case, we don't have to use any sort of reference for the service. But how? Silverlight only allows asynchronous model of service method call, so we cannot use the same OperationContract. We can copy all the classes that are DataContract as a link and use in the Silverlight project, for the service contract we have to define a new class with ServiceContract attribute. Note that the methods need to have [OperationContract(AsyncPattern = true)] method attribute. For each method in the ServiceContract in server, we have to define two methods with pre-fix "Begin" and "End" so if we have a method named "AddExpense" in server, in the client class we have to define two methods named "BeginAddExpense" and "EndAddExpense". Below, a complete code has been given for the class IMoneyService of client.

Example of AnyncPattern Contract

namespace MoneyTracking.Service
{
    [ServiceContract]
    public interface IMoneyService
    {
        [OperationContract(AsyncPattern = true)]
        IAsyncResult BeginAddExpense
		(Expense expense, AsyncCallback callback, object state);
        
        [OperationContract(AsyncPattern = true)]
        IAsyncResult BeginUpdateExpense
		(Expense expense, AsyncCallback callback, object state);
        
        [OperationContract(AsyncPattern = true)]
        IAsyncResult BeginDeleteExpense
		(Expense expense, AsyncCallback callback, object state);
        
        [OperationContract(AsyncPattern = true)]
        IAsyncResult BeginGetExpenseByID
		(int expenseId, AsyncCallback callback, object state);
        
        [OperationContract(AsyncPattern = true)]
        IAsyncResult BeginAddCategory
		(Category category, AsyncCallback callback, object state);
        
        [OperationContract(AsyncPattern = true)]
        IAsyncResult BeginUpdateCategory
		(Category category, AsyncCallback callback, object state);
        
        [OperationContract(AsyncPattern = true)]
        IAsyncResult BeginDeleteCategory
		(Category category, AsyncCallback callback, object state);

        [OperationContract(AsyncPattern = true)]
        IAsyncResult BeginGetCategoryByID
		(int categoryId, AsyncCallback callback, object state);

        ServiceResponse EndAddExpense(IAsyncResult result);

        ServiceResponse EndUpdateExpense(IAsyncResult result);

        ServiceResponse EndDeleteExpense(IAsyncResult result);

        ServiceResponse EndGetExpenseByID(IAsyncResult result);

        ServiceResponse EndAddCategory(IAsyncResult result);

        ServiceResponse EndUpdateCategory(IAsyncResult result);

        ServiceResponse EndDeleteCategory(IAsyncResult result);

        ServiceResponse EndGetCategoryByID(IAsyncResult result);
    }
}

Example of chanelFactory

Now we have defined the interface. It's time to build the service using ChanelFactory. Below a simple code is given to demonstrate how we can create a service client and invoke a method.

private void UsingChanelFactory(object sender, RoutedEventArgs e)
{
    var basicHttpBinding = new BasicHttpBinding();
    var endpointAddress = new EndpointAddress
    ("http://localhost/MoneyTrackingWeb/MoneyTrackingService.svc");
    var moneyService = new ChannelFactory
    <moneytracking.service.imoneyservice>
    (basicHttpBinding, endpointAddress).CreateChannel();
    moneyService.BeginAddCategory
    (new MoneyTracking.Common.Category(), ASyncronousCallBack, moneyService);
}

private void ASyncronousCallBack(IAsyncResult ar)
{
    if(ar.IsCompleted)
    {
        MoneyTracking.Common.ServiceResponse serviceResponse = 
        ((MoneyTracking.Service.IMoneyService)ar.AsyncState).EndAddCategory(ar);
        if(serviceResponse.IsSuccess)
        {
            //do your work here
        }
    }
}  

Making Synchronous Call

By default, the methods that we have in service client in Silverlight are asynchronous method calls. A simple code is given below to demonstrate the idea. A Client has been initiated and then a completed event has been subscribed to and then call the asynchronous method.

Example of Asynchronous call

public void AddExpense()
{
    var client = new MoneyServiceClient();
    var category = new Category();
    //fill category attributes here            
    client.AddCategoryCompleted += ClientAddCategoryCompleted;
    client.AddCategoryAsync(category);
}

void ClientAddCategoryCompleted(object sender, AddCategoryCompletedEventArgs e)
{
    ServiceResponse serviceResponse = e.Result;
    if(serviceResponse.IsSuccess)
        Categories.Add((Category) serviceResponse.Result);
}

Example of Synchronous Call

In some scenarios, we need to have a synchronous call, since Silverlight does not allow the synchronous call we can always bypass and use the same system in such a way so that we have a synchronous call, the idea is to stop the current code execution until the completed event is been fired.

We have defined a custom class AsyncCallStatus<T> a custom status which will be passed via Async Method. But the magic class which make all this possible is "AutoResetEvent", we have the current execution freeze using _autoResetEvent.WaitOne(). When in completed event, we get the result back we simply set the  _autoResetEvent.Set(); which again resumes the process.

Note that in Silverlight 4, we have to use ThreadPool.QueueUserWorkItem(MethodNameGoesHere); to start the process otherwise AutoResetEvent.WaitOne() will stop the current tread execution, and completed event won't fire at all, in fact call the server will never be invoked. Below, we have put down the complete listing of a method called synchronously.

private void SyncronousCall(object sender, RoutedEventArgs e)
{
    ThreadPool.QueueUserWorkItem(AddExpenseInServer);
    
}

private void AddExpenseInServer(object state)
{
    Expense addedExpense = AddExpense();
    Dispatcher.BeginInvoke(() =>
                               {
                                   StatusMessage.Content = "Expense is been Added";
                                   Expenses.Add(addedExpense);
                               });
}

private Expense AddExpense()
{
    var asyncCallStatus = new AsyncCallStatus<AddExpenseCompletedEventArgs>();
    var client = new MoneyServiceClient();
    client.AddExpenseCompleted += ClientAddExpenseCompleted;
    client.AddExpenseAsync(new Expense(),asyncCallStatus);
    _autoResetEvent.WaitOne();
    if (asyncCallStatus.CompletedEventArgs.Error != null)
    {
        throw asyncCallStatus.CompletedEventArgs.Error;
    }
    var serviceResponse = asyncCallStatus.CompletedEventArgs.Result;
    if (serviceResponse.IsSuccess)
    {
        return serviceResponse.Result as Expense;
    }
    else
        return null;
}

void ClientAddExpenseCompleted(object sender, AddExpenseCompletedEventArgs e)
{
    var status = e.UserState as AsyncCallStatus<AddExpenseCompletedEventArgs>;
    if (status != null) status.CompletedEventArgs = e;
    _autoResetEvent.Set();
}

private readonly AutoResetEvent _autoResetEvent = new AutoResetEvent(false);

public class AsyncCallStatus<T>
{
    public T CompletedEventArgs { get; set; }
}    

Cross Domain Service Call

By default, Silverlight can invoke any method from the originator site. So if the Silverlight component is invoked from "http://mytestside.com/silverlightTestPage.aspx", the component can call any WCF service hosted in "http://mytestside.com/testservice.svc". But let's say we want to call a WCF service which is hosted on "http://myotherside.com/otherservice.svc", we would encounter an error something like this:

The way to archive cross domain service call in Silverlight is pretty simple in fact. We have to put two XML files named "clientaccesspolicy.xml" and "crossdomain.xml" file in root folder of the service. So if we have a service named "http://mytestserver.com/myservice.svc", we need to put down those two files in "http://mytestserver.com/clientaccesspolicy.xml". Both the file and its content are given below.

clientaccesspolicy.xml

<?xml version="1.0" encoding="utf-8" ?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="SOAPAction">
        <domain uri="*"/>
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

crossdomain.xml

<?xml version="1.0" ?>
<!DOCTYPE cross-domain-policy SYSTEM 
	"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
  <allow-http-request-headers-from domain="*" headers="SOAPAction,Content-Type"/>
</cross-domain-policy>

You can simulate the crossdomain class in the local environment also. For this, modify the host file:

127.0.0.1 testserver.com 
127.0.0.1 testclient.com

Now you can run your project from testclient and service will be consumed from testserver.

Transfer Large Data

In this section, we would see how we can customize the WCF service so that we can transfer large amount of data. We often encounter an error while communicating that max limit of array size has been crossed or perhaps an exception that endpoint not found, it's simply because there could be a size issue. Note that the endpoint not found exception can occur for many reasons.

To overcome the limitation, we need to customize the basicHttpBinding. We have to define a bindings section under <system.servicemodel> and under that we would customize the basicHttpBinding. For this, we have to create another sub section under binding named "<basicHttpBinding>". Here we can put multiple bindings and of course we can set all the advanced attributes.

Below, we have put down both client and server configuration.

Service Configuration on Server

<system.serviceModel>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" 
	aspNetCompatibilityEnabled="true"/>
    <bindings>
      <basicHttpBinding>
        <binding name="MoneyTrackingServiceBinding" maxBufferPoolSize="2147483647"
                 maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">
          <readerQuotas  maxDepth="2147483647" maxStringContentLength="2147483647" 
                         maxArrayLength="2147483647" maxBytesPerRead="2147483647" 
                         maxNameTableCharCount="2147483647">
          </readerQuotas>
        </binding>
      </basicHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MoneyTrackingServiceBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="MoneyTracking.Service.MoneyService" 
               behaviorConfiguration="MoneyTrackingServiceBehavior">
        <endpoint address="" binding="basicHttpBinding"
                  bindingConfiguration="MoneyTrackingServiceBinding" 
                  contract="MoneyTracking.Service.IMoneyService" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      </service>
    </services>
</system.serviceModel>    

Here under service, we have added behaviorConfiguration to "" in service and under that in endpoint section we have also set "bindingConfiguration" to our desired configuration name.

Client Side Configuration

<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="MoneyTrackingServiceBinding" maxBufferSize="2147483647"
                    maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647" />
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost/MoneyTrackingWeb/MoneyTrackingService.svc"
                binding="basicHttpBinding" 
		bindingConfiguration="MoneyTrackingServiceBinding"
                contract="MoneyTrackingServiceReference.IMoneyService"
                name="BasicHttpBinding_IMoneyService" />
        </client>
    </system.serviceModel>
</configuration>    

The above service configuration section is for the client, note that both server and client binding configuration name should match and the value should also match. In client service configuration fine we don't have to define the "readerQuotas", infact that would give you an exception. And we don't need behaviour configuration. You guys can also copy and paste the above configurations in your context, while doing so you have to fix the contract names and service names.

Summary

In this short article, we have seen the basics of WCF service. We have discussed only those parts that are necessary to build a simple WCF service that can be consumed by a Silverlight client. Then we have seen two ways of creating a client proxy for Silverlight. We can add service reference and also can use chanel factory to build client. Chanel factory gives more control over client proxy.

After that, we have seen how we can use some small policy XML files to extend the service so that it can be consumed via Silverlight components running in a different domain. Lastly, we have seen how we can customize the binding to support large amount of data transfer. There are lots of challenges in real life development regarding WCF and Silverlight. I hope to put together more content in a future article.

References

History

  • 29th September, 2011: Initial version

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