Tuesday, October 18, 2011

Steps to make Silverlight enabled WCF service working under SSL protocol

This is a step by step instruction to help people to setup their Silverlight applications running under SSL(HTTPS) protocol:

1) Create a Silverlight Application called SilverlightWCFSSL(select Silverlight Application Template) under VS2011. Two projects should be created in the solution. One is SilverlightWCFSSL Silverlight project, the other is SilverlightWCFSSL.Web Web project.

2) Add a Silverlight Enabled WCF service called DummyService.svc to the Web project: 

using System.ServiceModel;
using System.ServiceModel.Activation;

namespace SilverlightWCFSSL.Web
{
    [ServiceContract(Namespace = "")]
    [SilverlightFaultBehavior]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class DummyService
    {
        [OperationContract]
        public string DoWork()
        {
            // Add your operation implementation here
            return "Hello World";
        }      
    }
}

3) Add a ServiceReference to the Silverlight project to point to this DummyService.  Give it name as "DummyService".

4) Add code to the Silverlight MainPage.Xaml to call this service:

<UserControl x:Class="SilverlightWCFSSL.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Button Content="ServiceTest" Click="Button_Click"/>
    </Grid>
</UserControl>

using System.Windows;
using System.Windows.Controls;

namespace SilverlightWCFSSL
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var svc = new DummyService.DummyServiceClient();
            svc.DoWorkCompleted += (s, ee) =>
            {
                if (ee.Error != null)
                    MessageBox.Show(ee.Error.Message);
                else
                    MessageBox.Show(ee.Result);
            };
            svc.DoWorkAsync();
        }
    }
}

5) Set the Web project as Start up project,  SilverlightWCFSSLTestPage.html as start up page. Hit F5 to test the application. We should get "Hello World" message box when click the "Test Service" button.

6) Before we switch to HTTPS protocol, we want to change our code a little so it would minimize the hassle for later. We want to add this WCFHelper class to the Silverlight Project:
 
using System;
using System.ServiceModel.Channels;
using System.Windows;

namespace SilverlightWCFSSL
{
    public class WCFHelper
    {
        public static T CreateService<T>(string serviceURL)
        {
            System.ServiceModel.Channels.CustomBinding binding = new System.ServiceModel.Channels.CustomBinding();
            binding.Elements.Add(new BinaryMessageEncodingBindingElement());
            if (Application.Current.Host.Source.AbsoluteUri.StartsWith("https"))
                binding.Elements.Add(new HttpsTransportBindingElement { MaxBufferSize = Int32.MaxValue, MaxReceivedMessageSize = Int32.MaxValue });
            else
                binding.Elements.Add(new HttpTransportBindingElement { MaxBufferSize = Int32.MaxValue, MaxReceivedMessageSize = Int32.MaxValue });
            Uri uri;
            if (serviceURL.StartsWith("http"))
                uri = new Uri(serviceURL);
            else
                uri = new Uri(Application.Current.Host.Source, serviceURL);

            System.ServiceModel.EndpointAddress address = new System.ServiceModel.EndpointAddress(uri);
            try
            {
                return (T)Activator.CreateInstance(typeof(T), binding, address);
            }
            catch (Exception e)
            {
                string s = e.Message;
            }
            return default(T);
        }
    }
}

7) Modify the Service calling code to use WCFHelper to create the service. The reason we want to do this is to by-pass the ClientConfig reading which could give us a lot of trouble when we switch between DEV server and IIS, between HTTP and HTTPS:

private void Button_Click(object sender, RoutedEventArgs e)
        {
            //var svc = new DummyService.DummyServiceClient();
            var svc = WCFHelper.CreateService<DummyService.DummyServiceClient>("../DummyService.svc");
            svc.DoWorkCompleted += (s, ee) =>
            {
                if (ee.Error != null)
                    MessageBox.Show(ee.Error.Message);
                else
                    MessageBox.Show(ee.Result);
            };
            svc.DoWorkAsync();
        }

8) Test the Application again. It should still work as before.

9) Now we are ready to switch the application to run under SSL protocol. We want to use IIS to host the app when using SSL:

Open the Web project property page and go to the "Web" tab. We want to use local IIS Web server instead of VS DEV server to test the application under SSL.  Select "Use Local IIS Web Server" radio Button. Click "Create Virtual Direction" button to create a Virtual directory under IIS. Change the Project Url to "https://localhost/SilverlightWCFSSL.Web"

10) Now we need to enable SSL for this application under IIS.  Read the following article to set up SSL on IIS7:
http://learn.iis.net/page.aspx/144/how-to-set-up-ssl-on-iis-7-and-above/

Select the SilverlightWCFSSL.Web application under IIS Manager, Click "SSL Settings". Select "Require SSL" check box.

11) Test our page under IIS by typing "https://localhost/SilverlightWCFSSL.Web/TestPage.html" in a browser.  You may get message saying "There is a problem with this website's security certificate". This is because we are using the dev certificate which is not issued by a trusted party. We can ignore that by clicking "Continue to this website (not recommended)." link to go to the page.

13) We should be able to see the page under SSL, but the service is not working yet. If we click the "Service Test" button we would get "404 Not Found" error.

We can Test our service by putting "https://localhost/SilverlightWCFSSL.Web/DummyService.svc" in a browser. We would get an error saying "Could not find a base address that matches scheme http for the endpoint with binding CustomBinding. Registered base address schemes are [https]."

14) Change the Web.Config to enable HTTPS for WCF service.

This is the current config for service running under HTTP protocol:

<configuration>
    <system.web>
        <compilation debug="true" targetFramework="4.0" />
    </system.web>
    <system.serviceModel>
        <behaviors>
            <serviceBehaviors>
                <behavior name="">
                    <serviceMetadata httpGetEnabled="true" />
                    <serviceDebug includeExceptionDetailInFaults="false" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <bindings>
            <customBinding>
                <binding name="SilverlightWCFSSL.Web.DummyService.customBinding0">
                    <binaryMessageEncoding />
                    <httpTransport />
                </binding>
            </customBinding>
        </bindings>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
            multipleSiteBindingsEnabled="true" />
        <services>
            <service name="SilverlightWCFSSL.Web.DummyService">
                <endpoint address="" binding="customBinding" bindingConfiguration="SilverlightWCFSSL.Web.DummyService.customBinding0"
                    contract="SilverlightWCFSSL.Web.DummyService" />
                <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
            </service>
        </services>
    </system.serviceModel>
</configuration>

For it to work under HTTPS protocol, we need to replace all the "http" to "https" in this config:

<configuration>
    <system.web>
        <compilation debug="true" targetFramework="4.0" />
    </system.web>
    <system.serviceModel>
        <behaviors>
            <serviceBehaviors>
                <behavior name="">
                    <serviceMetadata httpsGetEnabled="true" />
                    <serviceDebug includeExceptionDetailInFaults="false" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <bindings>
            <customBinding>
                <binding name="SilverlightWCFSSL.Web.DummyService.customBinding0">
                    <binaryMessageEncoding />
                    <httpsTransport />
                </binding>
            </customBinding>
        </bindings>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
            multipleSiteBindingsEnabled="true" />
        <services>
            <service name="SilverlightWCFSSL.Web.DummyService">
                <endpoint address="" binding="customBinding" bindingConfiguration="SilverlightWCFSSL.Web.DummyService.customBinding0"
                    contract="SilverlightWCFSSL.Web.DummyService" />
                <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
            </service>
        </services>
    </system.serviceModel>
</configuration>

15)  Save the Web.Config. Test the Service again by typing "https://localhost/SilverlightWCFSSL.Web/DummyService.svc" in a browser. Now we should see the correct response from the service.

16) Run the app, Click the "Test Service" button we should get the "Hello World" message.

Conclusion:

You can switch your app between HTTP and HTTPS mode as needed just by changing the Web.config file.  No need to change any code or rebuild the solution.

4 comments:

  1. Worked like a charm. Exactly what I needed.

    ReplyDelete
  2. Does not work for me unfortunately. I have used the exact same settings as you have provided here, but I have not configured the solution to run under IIS. Under the DEV server, the very first call I make to a service instantiated with your helper class, I get an error that: "The server did not provide a meaningful reply; this might be caused by a contract mismatch, a premature session shutdown or an internal server error."

    ReplyDelete
  3. Worked for me! Thx for taking the time to post

    ReplyDelete
  4. Thanks for this article. It was very useful for me.

    ReplyDelete