Tuesday, August 17, 2010

Silverlight, WCF, IIS, HTTPS, BasicHttpBinding, and UserNamePasswordValidator

Creating a WCF service and Silverlight client that work together seems like a fairly common requirement. You would probably also require username/password and HTTPS. And it would be best if your development bindings were similar to deployment bindings. Here is what I used:
  • Visual Studio 2010, .NET 4.0
  • Windows 7
  • IIS 7
Note that you cannot use the built-in ASP.NET Development Server. It does not support HTTPS. And you cannot use Message credentials without HTTPS. Here is how I did it:
  • Make sure you have IIS and the "IIS Metabase and IIS6 configuration compatibility" installed.
  • Run Visual Studio as an Administrator so that you can access IIS configuration features from within Visual Studio.
  • Create a new WCF Service Application.
    • Change the debug options on the Project Properties, Web tab to Use Local IIS Web Server.
    • Press the Create a Virtual Directory button.
    • Test that your service provides metadata. (http://localhost/WcfService1/Service1.svc?wsdl)
    • Optional: Enable directory browsing on your virtual directory.
  • Setup IIS, IE, and Visual Studio for HTTPS.
    • In Internet Information Server (IIS) Manager, select the local computer and the "Server Certificates."
    • Create a Self-Signed Certificate.
    • On the web site (not the virtual directory), add a binding for HTTPS and test that it works. (https://localhost/WcfService1/Service1.svc)
    • In Internet Explorer, install the self-signed certificate, placing the certificate in the Trusted Root Certification Authorities.
    • Change the WCF project's Project URL to use https and the machine name, instead of http and the localhost. (https://mishael/WcfService1)
    • Test that Visual Studio can launch the web service view in Internet Explorer without a certificate error.
  • Create a new Silverlight Application.
    • Add a Service Reference to the WCF service.
    • Add a button that calls the WCF service.

      int counter = 0;
      private void button1_Click(object sender, RoutedEventArgs e)
      {
          var c = new Service1Client();
          c.GetDataCompleted += c_GetDataCompleted;
          c.GetDataAsync(++counter);
      }
      
      void c_GetDataCompleted(object sender, GetDataCompletedEventArgs e)
      {
          if (e.Error != null)
              textBlock1.Text = e.Error.Message;
          else
              textBlock1.Text = e.Result;
      }
    • Edit the generated ServiceReferences.ClientConfig to use https instead of http.
    • Edit the generated ServiceReferences.ClientConfig to use security mode of Transport.

      security mode="Transport"
  • Using the WCF Service Configuration Tool, update the WCF service as follows:
    • Create a new service, with the endpoint address pointing to the services.
    • Create a New Binding Configuration for basicHttpBinding. On the Security tab, change the mode to Transport.
    • Use the new binding configuration for the service endpoint. It will be something like this:

      
        
          
            
          
        
      
      
        
          
        
      
    • Testing the service endpoint with Internet Explorer (https://mishael/WcfService1/Service1.svc) reveals that there is an issue with multipleSiteBindingsEnabled. Comment out the multipleSiteBindingsEnabled attribute in the web.config.
  • Test that the Silverlight app can successfully communicate with the WCF service using HTTPS, including stopping at breakpoints in both the Silverlight client and WCF service.
  • Enable client username and password authentication as follows:
    • In the Web.config, switch the security from "Transport" to "TransportWithMessageCredential". and a client message credential type of "UserName".


      
         
      
    • In the ServiceReferences.ClientConfig, switch the security from "Transport" to "TransportWithMessageCredential".
    • Define a UserNamePasswordValidator class to do the username and password validation. (The base class UserNamePasswordValidator is in System.IdentifyModel.dll.)

      namespace WcfService1
      {
          public class MyUserNamePasswordValidator: UserNamePasswordValidator
          {
              public override void Validate(string userName, string password)
              {
                  if (userName == "Wally" && password == "password")
                      return;
                  throw new SecurityTokenValidationException("Unrecognized user.");
              }
          }
      }
    • Update the service behavior to use the new UserNamePasswordValidator class.

      
        
          
            
          
        
      
    • Update the Silverlight client code set a username and password.

      var c = new Service1Client();
      c.ClientCredentials.UserName.UserName = "Wally";
      c.ClientCredentials.UserName.Password = "password";
      
    • Using breakpoints, verify that the UserNamePasswordValidator class is being called.
  • In your WCF operation, throw a FaultException. Testing will reveal that on the client, all you get is a CommunicationException. Improve the availability of exception messages sent to the client as follows:
    • Update the Web.config to includeExceptionDetailInFaults.
      serviceDebug includeExceptionDetailInFaults="true"
    • Create a BehaviorExtensionElement class using the code here.
    • Update the Web.config to include this behavior extension.
      
        
          
        
      
      
    • Update the Web.config to add the extension to the endpoint behavior.
      
              
                
              
            
      
    • Update the Web.config to use this behavior for your endpoint.
      behaviorConfiguration="SilverlightFaultBehavior"

    2 comments:

    1. Thanks for this post. Having weird issue i finally got my service to take the silverlight behavior for custom error code however it does not seem to work as The returned exception is only notfound.

      ReplyDelete
    2. Great post, well explained, congratulations!

      ReplyDelete