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

Silverlight Session Management, Error handling, and Unit Test of WCF Service

0.00/5 (No votes)
15 Sep 2010 1  
Silverlight Session Management, Error handling, and Unit Test of WCF service

Requirements

I had the following requirements:

  • Session management.
    In the second web service call, I wanted to know something from the first call.

  • Impersonate user
    I didn't want to enter my credentials if my service had to work as my user.

  • Error handling
    It’s not very cool to get a white website after an error occurs.

  • Unit tests of the WCF service
    It would be great to have tests for the stuff above.

Let's start. I'm going to implement each of these topics separately, and hopefully get a solution working before this blog entry is finished. You can download the attached ZIP file which contains the solutions for the above requirements.

Introduction

I just started with a new Silverlight solution in Visual Studio 2008. The only thing I changed from the original is to assign a static port to the built in web server. This makes the life with WCF much easier. Then add a new Silverlight-enabled WCF Service to the web project, create some operation contract, and add a service reference to the Silverlight project.

Using the Code

1. Session management (with aspNetCompatibilityEnabled)

For this, there are two possibilities:

  1. Do it yourself (write something like a session agent service)
  2. Use the ASP session management

I guess there are lots of reasons not to use aspNetCompatibility (The most obvious might be performance), but the simplicity of it was the reason for me to choose it anyway. I have to say that I did not invent this, I just copied it from an article of SpoonStomper from the Silverlight forum.

I want to achieve the following goal. I press a button on my Silverlight application which sends a string to my service. Then I want to press another button and get back this string again (sounds really easy btw.).

To enable the ASP session management, you first have to edit the web.config.

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
//this should be default since Silverlight 3   

On your service class, you have to change the RequirementsMode from Allowed to Required:

[AspNetCompatibilityRequirements(RequirementsMode = 
	AspNetCompatibilityRequirementsMode.Required)] 
public class MyCoolService{ 

With this modification, you're ready to go. Now you can use the HttpContext.Current.Session for handling session stuff.

[OperationContract]
public void ServiceCallOne(string message)
{
    HttpContext.Current.Session["Test"] = message;
}

[OperationContract]
public string ServiceCallTwo()
{
    return (string)HttpContext.Current.Session["Test"];
} 

2. Impersonate User

The user impersonation is built on top of the previous topic. Since we are ASP.NET controlled, we can use the HttpContext to impersonate. Here you can find the description from Microsoft:

[OperationContract]
public string WhoAmIAfterImpersonate()
{
  ((WindowsIdentity)HttpContext.Current.User.Identity).Impersonate();
  return HttpContext.Current.User.Identity.Name;
}    

After the Impersonate, the service stuff will be executed as the user who has called it.
BTW, as far as I understand, this requires NTLM authentication.

3. Error Handling

Since it is not possible to catch a regular exception from a WCF service in a Silverlight application, there is another way to do it.

I just explain it in a short story, and give the example. You may find the whole story from Microsoft here.

First: Create a FaultBehavior class in your web project

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Web;

namespace SilverlightWCF.Web
{
    public class MyFaultBehavior : BehaviorExtensionElement, IEndpointBehavior
    {
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, 
		EndpointDispatcher endpointDispatcher)
        {
            SilverlightFaultMessageInspector inspector = 
				new SilverlightFaultMessageInspector();
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
        }
        public class SilverlightFaultMessageInspector : IDispatchMessageInspector
        {
            public void BeforeSendReply(ref Message reply, object correlationState)
            {
                if (reply.IsFault)
                {
                    HttpResponseMessageProperty property = 
				new HttpResponseMessageProperty();

                    // Here the response code is changed to 200.
                    property.StatusCode = System.Net.HttpStatusCode.OK;
                    reply.Properties[HttpResponseMessageProperty.Name] = property;
                }
            }
            public object AfterReceiveRequest(ref Message request, 
		IClientChannel channel, InstanceContext instanceContext)
            {
                // Do nothing to the incoming message.
                return null;
            }
        }
        // The following methods are stubs and not relevant.
        public void AddBindingParameters(ServiceEndpoint endpoint, 
			BindingParameterCollection bindingParameters)
        {
        }
        public void ApplyClientBehavior(ServiceEndpoint endpoint, 
					ClientRuntime clientRuntime)
        {
        }
        public void Validate(ServiceEndpoint endpoint)
        {
        }
        public override System.Type BehaviorType
        {
            get { return typeof(MyFaultBehavior); }
        }
        protected override object CreateBehavior()
        {
            return new MyFaultBehavior();
        }
    }
} 

Second: Edit the web.config

<system.serviceModel>
  <!--Add a behavior extension within the service model-->
  <extensions>
    <behaviorExtensions>
      <add name="myFault" type="SilverlightWCF.Web.MyFaultBehavior, 
	SilverlightWCF.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </behaviorExtensions>
  </extensions>

...

<behaviors>
  <!--Add a endpointBehavior below the behaviors-->
    <endpointBehaviors>
      <behavior name="myFaultBehavior">
        <myFault/>
      </behavior>
    </endpointBehaviors>

...

<!--Set the behaviorConfiguration of the endpoint-->
<endpoint address="" binding="customBinding" bindingConfiguration="customBinding0"
contract="SilverlightWCF.Web.MyCoolService" behaviorConfiguration="myFaultBehavior"  />

...

  <!--For debugging, it might be cool to have some more error information.
  to get this, set includeExceptionDetailInFaults to true-->
<serviceDebug includeExceptionDetailInFaults="true" /> 

Third: Create an operation contract which throws an exception.

Make sure not to throw a regular exception, but FaultException:

[OperationContract]
public void ThrowException()
{
    throw new FaultException("this is my Exception");
}  

Fourth: Handle the error within your Silverlight application

private void btnThrowException_Click(object sender, RoutedEventArgs e)
{
    serviceClient.ThrowExceptionCompleted += 
	new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>
		(serviceClient_ThrowExceptionCompleted);
    serviceClient.ThrowExceptionAsync();
}

void serviceClient_ThrowExceptionCompleted
	(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
    if (e.Error == null)
    {
        // In case of success
    }
    else if (e.Error is FaultException)
    {
        FaultException fault = e.Error as FaultException;
        txtThrowException.Text =  e.Error.Message;
    }
} 

4. Unit Test the WCF Service (Using Sessions)

Here I have to say, to unit test a single WCF method is not that difficult (it's like if you unit test a library).

But if you use the ASP.NET session to store information, the client (browser) handles this with cookies. If you want to unit test a method which needs a value from the session, you have to handle the cookies manually. Here you can find a very good explanation for handling cookies in ASMX/WCF services.

First, you have to create a unit test project. I had to do this by hand, since create unit test on a method did not create a new project.

Then use Add service reference to add a reference of the WEB/WCF project to your unit test project.

Now implement the test method:

[TestMethod]
public void TestMethod1()
{
     string _sharedCookie = string.Empty;

     //Creates connection to the WCF service
     //It's done by code, not via config file
     //The config file would be overwritten all the time
     EndpointAddress objEndpoint = null;
     CustomBinding cb = new CustomBinding();
     //binding depends how your web server is set up
     BinaryMessageEncodingBindingElement bmebe = 
		new BinaryMessageEncodingBindingElement();
     //TextMessageEncodingBindingElement tebe = 
	new TextMessageEncodingBindingElement(MessageVersion.Default, Encoding.UTF8);
     HttpTransportBindingElement htbe = new HttpTransportBindingElement();
     cb.Elements.Add(bmebe);
     cb.Elements.Add(htbe);
     objEndpoint = new EndpointAddress("http://localhost:11111/MyCoolService.svc");

     MyCoolServiceClient _serviceClient = new MyCoolServiceClient(cb, objEndpoint);

     //howto manage cookies: 
     //http://megakemp.wordpress.com/2009/02/06/managing-shared-cookies-in-wcf/
     using (new OperationContextScope(_serviceClient.InnerChannel))
     {
         _serviceClient.ServiceCallOne("my first message to the server");
         // Extract the cookie embedded in the received web service response
         // and stores it locally
         HttpResponseMessageProperty response = (HttpResponseMessageProperty)
         OperationContext.Current.IncomingMessageProperties
			[HttpResponseMessageProperty.Name];
         _sharedCookie = response.Headers["Set-Cookie"];
     }
     using (new OperationContextScope(_serviceClient.InnerChannel))
     {
         //this sets the cookie for the next service request
         HttpRequestMessageProperty request = new HttpRequestMessageProperty();
         request.Headers["Cookie"] = _sharedCookie;
         OperationContext.Current.OutgoingMessageProperties
			[HttpRequestMessageProperty.Name] = request;

         string returnValue = _serviceClient.ServiceCallTwo();
         Assert.AreEqual("my first message to the server", returnValue);
     }
} 

As you can see, I call two methods separately, and I return with the second method the session value which was set in the first method call.

Points of Interest

  • Silverlight 4 with Managed Extensibility Framework
  • Silverlight 4 binding
  • Downloading XAP dynamically in Silverlight 4

History

  • 15th September, 2010: Initial post

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