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.

Saturday, February 26, 2011

Steps to Deploy Silverlight Business Application

Many people have trouble deploying Silverlight Business Applications to IIS. They tested everything under Visual Studio. But once they deploy the app to IIS, the app stops working. Either Silverlight can't connect to the service any more, or there are database connection related errors.

If you have trouble deploying your Silverlight Business Applications, check this general RIA deployment guide first:
http://blogs.msdn.com/b/bradsevertson/archive/2011/02/17/a-guide-to-deploying-ria-services-solutions.aspx 

If you are still having trouble, let's do this step by step to deploy a Silverlight application created by using the Silverlight Business Application Template:

1) Create a Silverlight Business Application in Visual Studio (VS).

Run your app by hitting F5 under VS. The application's main page is loaded in your default browser. Click the Login button. First you need to Register a user: Click "Register Now" button on the Login window; enter your user information to register. After you are done the login window is closed and you should already be logged in. Click Logout button to Logout. Click Login Button to login again. If everything works at this point, we can consider we already tested every feature of this application and ready to deploy it.  

You may wonder how everything just works by itself and you haven't even written any code yet. Where is the user data stored at? Those things are done by the code generated by VS.

When VS create this project, there is a SQL server database file ASPNETDB.mdf created under the App_Data folder in the Web Project.  This DB file holds application user membership data used by the authentication service. The connection to this DB is defined under the Web.Config <connectionstrings/> section.  It looks something like this:

<connectionStrings>
            <add name="ApplicationServices" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" providerName="System.Data.SqlClient"/>
</connectionStrings>

If you are using earlier version of the RIA service (earlier than RIA service SP1 beta), you may not see the connection string under the Web.Config.  If this is the case,  the default connection string used by ASP.NET authentication service is defined in the machine.config. You can check your machine.config under x:\<windows>\Microsoft.NET\Framework\<version>\config\machine.config.

You should see the following connection string defined in machine.config:

<connectionStrings>
            <add name="LocalSqlServer" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" providerName="System.Data.SqlClient"/>
</connectionStrings>

If you are on earlier version of RIA and do not see connection string in your Web.Config,  first thing you should do is to add one to override the connection string set in machine.config.

Add the following to your Web.config file:
<connectionStrings>
    <clear/> <!-- This is important, make sure you have this line-->
    <add name="LocalSqlServer" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" providerName="System.Data.SqlClient"/>
</connectionStrings>

When you are under development, the project is using SQL Express to access the DB file. Remember SQL Express is for development only. When you finally deploy your app, you should use a full version SQL server in your production.  But for testing purposes, you can still use SQL Express to test your deployment.

2) Change the connection string to use SQL server authentication in the Web.Config:

The default connection string uses Windows authentication. It works under the DEV server when you are running the app under VS because the thread is running under the current windows user account. But when you are running the app under IIS, the thread is running under "NT AUTHORITY\NETWORK SERVICE" user account.  This user account does not have the right to access the SQL server unless you give it that right.  The best practice for a Web application is to use SQL server authentication.

If this is your original connection string:
<add name="ApplicationServices" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" providerName="System.Data.SqlClient"/>

Change it to the following:
<add name="ApplicationServices"
         connectionString="data source=.\SQLEXPRESS;Integrated Security=False;User ID=YOURUSERID; Password=YOURPASSWORD;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=False" providerName="System.Data.SqlClient" />

If this is your original connection string:
<add name="LocalSqlServer" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" providerName="System.Data.SqlClient"/>

Change it to the following:
<add name="LocalSqlServer"
         connectionString="data source=.\SQLEXPRESS;Integrated Security=False;User ID=YOURUSERID; Password=YOURPASSWORD;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=False" providerName="System.Data.SqlClient" />

3) Include ASPNETDB file in the deployment package.

If you want to deploy the ASPNETDB.mdf file with your solution, you need to include this file into your Web Project.

Click Project Menu on the VS Top Menu bar, select "Show All files". You should now see the ASPNETDB.mdf file under the App_Data folder under the Web project. It is not included in the Project by default.  Right click the file and select "Include in Project".

Any files you want to include in the deployment package, you need to include them into the project. Otherwise, the folder or files won't be published.

4) Build your solution. Do a final test of your app under VS.

4) Publish your app to a local file folder

If everything works at this point, you can publish the solution using the publish feature under VS. Right click your Web project to select "Publish ...". Select File System as your publish target. Select a local file folder to publish the package.

5) Test your deployment on your local IIS.

This is a important step. Before you finally publish the application to the hosting server, test it on your local IIS first, so you still have a chance to debug the problems if you find any. 

Open the IIS Admin tool (under Computer\Manage\Service and Applications) on your local computer. Add an Application under Default Web Site to point to the published folder you just created. Make sure that you select ASP.NET v4.0 as the application pool.

Under IIS Admin, right click the web application, go to Authentication Details, make sure only the Anonymous authentication and Forms authentication are enabled.

6) Test your App by enter "http://localhost/YourApplication/YourTestPage.html" in a browser.

If you find problem running under local IIS, you can use "Attach to process ..." VS feature under the Debug menu to debug your code. For server side code debugging, attach to "w3wp.exe". For Silverlight code debugging, attach to the browser process that you are currently running at.

7) If everything works, you can deploy your app to the final hosting server by copying the whole folder over.

8) For the final deployment, switch to use full version SQL server. 

You can attach the ASPNETDB.mdf file to your SQL server, then change your connection string to point to that SQL server instance.
         connectionString="data source=YOURSQLSERVER; Initial Catalog=YourDBInstanceName;Integrated Security=False;User ID=YOURUSERID; Password=YOURPASSWORD; User Instance=False"

You should also consider to encrypt the connection string for security.  Read the instruction on how to do this:
http://msdn.microsoft.com/en-us/library/ms178372.aspx

Hope this step by step guide can help you understand the problems you are having and make your application deployment process a smoother one.

Saturday, November 6, 2010

How to Bind to Silverlight TreeView control's SelectedItem

I have no idea why the the Silverlight TreeView.SelectedItem Property is a read only Property. I know many people like me find needs to set this Property either in code or through binding.  What I have to do is to create a custom TreeView control which inherits from the original TreeView control, but with a new SelectedItem Property:

public class TreeViewEx : TreeView
{
        public TreeViewEx()
        {
            this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(TreeViewEx_SelectedItemChanged);
        }

        void TreeViewEx_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            this.SelectedItem = e.NewValue;
        }

        #region SelectedItem
      
        /// <summary>
        /// Gets or Sets the SelectedItem possible Value of the TreeViewItem object.
        /// </summary>
        public new object SelectedItem
        {
            get { return this.GetValue(TreeViewEx.SelectedItemProperty); }
            set { this.SetValue(TreeViewEx.SelectedItemProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public new static readonly DependencyProperty SelectedItemProperty =
            DependencyProperty.Register("SelectedItem", typeof(object), typeof(TreeViewEx), new PropertyMetadata(SelectedItemProperty_Changed));

        static void SelectedItemProperty_Changed(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            TreeViewEx targetObject = dependencyObject as TreeViewEx;
            if (targetObject != null)
            {
                TreeViewItem tvi = targetObject.FindItemNode(targetObject.SelectedItem) as TreeViewItem;
                if (tvi != null)
                    tvi.IsSelected = true;
            }
        }                                              
        #endregion SelectedItem  
 
        public TreeViewItem FindItemNode(object item)
        {
            TreeViewItem node = null;
            foreach (object data in this.Items)
            {
                node = this.ItemContainerGenerator.ContainerFromItem(data) as TreeViewItem;
                if (node != null)
                {
                    if (data == item)
                        break;
                    node = FindItemNodeInChildren(node, item);
                    if (node != null)
                        break;
                }
            }
            return node;
        }

        protected TreeViewItem FindItemNodeInChildren(TreeViewItem parent, object item)
        {
            TreeViewItem node = null;
            bool isExpanded = parent.IsExpanded;
            if (!isExpanded) //Can't find child container unless the parent node is Expanded once
            {
                parent.IsExpanded = true;
                parent.UpdateLayout();
            }
            foreach (object data in parent.Items)
            {
                node = parent.ItemContainerGenerator.ContainerFromItem(data) as TreeViewItem;
                if (data == item && node != null)
                    break;
                node = FindItemNodeInChildren(node, item);
                if (node != null)
                    break;
            }
            if (node == null && parent.IsExpanded != isExpanded)
                parent.IsExpanded = isExpanded;
            if (node != null)
                parent.IsExpanded = true;
            return node;
        }
}

Now the TreeViewEx.SelectedItem can be set either in code or through data binding.

Demo code can downloaded from here.

Thursday, October 14, 2010

Check list for "Remote Server NotFound" Error with WCF service

Countless people have seen the "remote server not found" error with their Silverlight/WCF application after the application is deployed to IIS.

The most likely cause of this error is that the Service URL is not set right. First thing to check is the ServiceReferences.ClientConfig file.

When you add the service reference under Visual Studio, the Service URL is point to the URL under the Dev Server. It is something like "http://localhost:[PORTNUMBER]/YourService.svc.  This URL certainly won't work when the application is finally deployed to IIS.  You need to change that URL to point to the service under IIS. 
 
The best way to make the ServiceReference file work in both development and production is to change the service URL to a relative URL:

<endpoint address="http://localhost:[PORTNUMBER]/YourService.svc" .../>
<endpoint address="../YourService.svc" .../>

If you are sure this is not your problem, the next step is to check if you can access your service by simply typing the service URL to a browser. If you see some kind of error message other than of 404 "NotFound" error, the error message usually can give you some kind of information.

One common problem you might find using this method is that the WCF feature is not turned on for IIS7. If this is the case, you need to turn on Windows feature "WCF HTTP Activation" under Control Panel\Programs\Turn Windows features on or off\Microsoft.NET Framework 3.0.