Tuesday, September 25, 2012

Calling WCF Service using ChannelFactory from a Silverlight Application

Today I am going to write about consuming a WCF service using Channel Model. As you might know there are several ways to create WCF client. One is letting the Visual Studio create the proxy using adding service reference. In this way, there is kind of a disadvantage which is, if the service changes for any reason you have to regenerate it.

Now I am going to create a WCF client (which is a Silverlight application) using this ChannelFactory method. ChannelFactory is most useful when you are dealing with a known Interface. If you can share the contract assemblies (interface) between the service and the client, ChannelFactory would be the ideal solution to consume the web service. If you did not understand what I meant by sharing the interface, let’s start off with the code then you can get the picture clearly.

First I am creating a Silverlight application and I am creating a web project to host my Silverlight application. Then I am adding a WCF Service Application (MyWcfServiceApp) to my solution. I am deleting the default service and it’s interface and I am creating a new WCF service which is “MyService.svc”.

MyService.svc
    public class MyService : IMyService
    {
        public string  DoWork(string s)
        {
            return s;
        }
    }
IMyService.cs
    [ServiceContract]
    public interface IMyService
    {
        [OperationContract]
        string DoWork(string s);
    }
Now I am modifying the web.config file in the MyWcfServiceApp project and I am adding bindings to my created web service. After that I am getting the web.config file as follows. The ones I have added are marked in Bold text.
<?xml version="1.0"?>
<configuration>

  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.serviceModel>
      <bindings>
          <customBinding>
              <binding name="binaryHttpBinding">
                  <binaryMessageEncoding />
                  <httpTransport maxReceivedMessageSize="2147483647" />
              </binding>
          </customBinding>
      </bindings>
      <services>
          <service name="MyWcfServiceApp.MyService">
              <endpoint address="" binding="customBinding" bindingConfiguration="binaryHttpBinding"
              contract="MyWcfServiceApp.IMyService" />
          </service>
      </services>
      
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
 <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
  
</configuration>
So after completing that step I am moving to my Silverlight application. First I need to add a reference to “System.ServiceModel.dll”.

Untitled2
Adding reference to System.ServiceModel
Now comes the most interesting part. Since I am using ChannelFactory, first let’s get to know what this really is. What MSDN says about ChannelFactory is as follows.

ChannelFactory<TChannel> Class
  • A factory that creates channels of different types that are used by clients to send messages to variously configured service endpoints.
  • TChannel : The type of channel produced by the channel factory. This type must be either IOutputChannel or IRequestChannel.
So which means the Channel Factory class is used to construct a channel between the client and server without creating a Proxy. So the channel would be the contract of the service which is the interface. So moving back to our example, I am modifying MainPage.xaml file in my Silverlight application as follows. In here the endpoint address is the address of my service.
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            Binding customBinding = new CustomBinding(
                new BinaryMessageEncodingBindingElement(),
                new HttpTransportBindingElement { MaxReceivedMessageSize = 2147483647 }
                );

            EndpointAddress address = new EndpointAddress("http://localhost:3289/MyService.svc");
            ChannelFactory<IMyService> clientChannel = new ChannelFactory<IMyService>(customBinding, address);
        }
    }
I have created the ChannelFactory of type IMyService. Now I am facing a problem here, because the contract IMyService cannot be found in the Silverlight Application. Because It’s in the WCF Service Application and even I can’t add a reference to MyWcfServiceApplication from My Silverlight application because only Silverlight projects can be referenced to a Silverlight application.

Untitled3
Add project reference

So what I would do now is, I am creating a IMyService interface in my Silverlight Application. After that my code will refer the interface I have created just now. And that’s what I meant by using ChannelFactory with a known interface and sharing the interface between client and the server.

Now I am modifying the above code as follows to call my DoWork method in my WCF service.
    public partial class MainPage : UserControl
    {
        private IMyService _imyService;

        public MainPage()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            Binding customBinding = new CustomBinding(
                new BinaryMessageEncodingBindingElement(),
                new HttpTransportBindingElement { MaxReceivedMessageSize = 2147483647 }
                );

            EndpointAddress address = new EndpointAddress("http://localhost:3289/MyService.svc");
            ChannelFactory<IMyService> clientChannel = new ChannelFactory<IMyService>(customBinding, address);
            if (clientChannel == null)
            {
                throw new Exception("Failed to create channel.");
            }
            _imyService = clientChannel.CreateChannel();
            string s = _imyService.DoWork("Hello World");
        }
    }
Now when I run this, I am getting the following error message.

Untitled4
Synchronous operation
It's a pretty clear error message, because synchronous operations are not supported in Silverlight. We should make everything asynchronous. So I am modifying my service contract as follows. Please note that here I am modifying the “IMyService” interface in my Silverlight application. I don’t have to change “IMyService” interface in MyWcfServiceApp.
    [ServiceContract]
    public interface IMyService
    {
        [OperationContract(AsyncPattern = true)]
        IAsyncResult BeginDoWork(string s, AsyncCallback callback, object state);

        string EndDoWork(IAsyncResult result);
    }
Now I am modifying MainPage.xaml as follows. I have added a textbox to show the return value from the WCF service and I am calling Dispatcher.BeginInvoke in the callback method.  This is necessary because we’re accessing the UI thread, omitting this will result in an invalid cross thread access exception.
    public partial class MainPage : UserControl
    {
        private IMyService _imyService;

        public MainPage()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            Binding customBinding = new CustomBinding(
                new BinaryMessageEncodingBindingElement(),
                new HttpTransportBindingElement { MaxReceivedMessageSize = 2147483647 }
                );

            EndpointAddress address = new EndpointAddress("http://localhost:3289/MyService.svc");
            ChannelFactory<IMyService> clientChannel = new ChannelFactory<IMyService>(customBinding, address);
            if (clientChannel == null)
            {
                throw new Exception("Failed to create channel.");
            }
            _imyService = clientChannel.CreateChannel();
            IAsyncResult asyncResult = _imyService.BeginDoWork("Hello World", GetResult, _imyService);
        }

        // Implement the callback. 
        void GetResult(IAsyncResult asyncResult)
        {
            Deployment.Current.Dispatcher.BeginInvoke(delegate()
            {
                txtResult.Text = ((IMyService)asyncResult.AsyncState).EndDoWork(asyncResult);
            });
        }
    }
Now when I run this, again I will get the following error which is “Attempting to access a service in a cross-domain way”.

Untitled1
Attempting to access a service in a cross-domain way
For that I have to create 2 xml files which are ClientAccessPolicy.xml and crossdomain.xml andI am placing them inside my WCF Service application project.

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>
That’s it. Now when I run this application, I am getting the result I have expected.

Untitled5
Result
Happy Coding,

Regards,
Jaliya

No comments:

Post a Comment