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:
- Do it yourself (write something like a session agent service)
- 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();
property.StatusCode = System.Net.HttpStatusCode.OK;
reply.Properties[HttpResponseMessageProperty.Name] = property;
}
}
public object AfterReceiveRequest(ref Message request,
IClientChannel channel, InstanceContext instanceContext)
{
return null;
}
}
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>
-->
<extensions>
<behaviorExtensions>
<add name="myFault" type="SilverlightWCF.Web.MyFaultBehavior,
SilverlightWCF.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
...
<behaviors>
-->
<endpointBehaviors>
<behavior name="myFaultBehavior">
<myFault/>
</behavior>
</endpointBehaviors>
...
-->
<endpoint address="" binding="customBinding" bindingConfiguration="customBinding0"
contract="SilverlightWCF.Web.MyCoolService" behaviorConfiguration="myFaultBehavior" />
...
-->
<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)
{
}
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;
EndpointAddress objEndpoint = null;
CustomBinding cb = new CustomBinding();
BinaryMessageEncodingBindingElement bmebe =
new BinaryMessageEncodingBindingElement();
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);
using (new OperationContextScope(_serviceClient.InnerChannel))
{
_serviceClient.ServiceCallOne("my first message to the server");
HttpResponseMessageProperty response = (HttpResponseMessageProperty)
OperationContext.Current.IncomingMessageProperties
[HttpResponseMessageProperty.Name];
_sharedCookie = response.Headers["Set-Cookie"];
}
using (new OperationContextScope(_serviceClient.InnerChannel))
{
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