Introduction
By default, exceptions thrown in a WCF service are not returned to the Silverlight client, and everybody knows exception handling is critical for any enterprise solution, i.e., to notify users that a fault happens or for debugging purposes.
Before writing this article, I searched a lot about how to retrieve exceptions from Web Services to a Silverlight client. Most articles talk about implementing a custom error object whose attributes are populated with the error exception in the Web Service and the object passed from the Web Service to the Silverlight client. Although this can be quite a good solution, this adds additional overhead in retrieving the exception message from client side. What we want here is to retrieve the error directly from the e
event handler, such as below:
private void customerService_Completed(object sender,
GenerateErrorCompletedEventArgs e)
{
if (e.Error == null)
{
myOutput.Text = "Sucess";
}
else
{
myOutput.Text = e.Error.Message.ToString();
}
}
Background
By default, WCF services return fault messages with an HTTP 500 response code. Due to limitations in the browser networking stack, the bodies of these messages are inaccessible within Silverlight, and consequently, the fault messages cannot be read by the client.
To override this constraint and to make Silverlight clients retrieve faults, the key is to make the WCF service return the fault message with an HTTP 200 response code instead of the HTTP 500 response code. This change enables Silverlight to read the body of the message, and also enables WCF clients of the same service to continue working using their normal fault-handling procedures; hence, no refactoring is needed for existing client codes.
Using the Code
The ServiceFaultBehavior
class from ServiceFaultBehavior.cs in the attached source code implements the IEndpointBehavior
interface used for writing custom endpoint behaviors. The main implementation is the ApplyDispatchBehavior
method which applies the ServiceFaultMessageInspector
to the WCF endpoint behavior.
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Configuration;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
namespace Service
{
public class ServiceFaultBehavior : BehaviorExtensionElement, IEndpointBehavior
{
public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
EndpointDispatcher endpointDispatcher)
{
ServiceFaultMessageInspector inspector = new ServiceFaultMessageInspector();
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
}
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(ServiceFaultBehavior); }
}
protected override object CreateBehavior()
{
return new ServiceFaultBehavior();
}
}
}
It is the ServiceFaultMessageInspector
class which changes the HTTP response from HTTP 500 to HTTP 200, if the reply is a Fault, by using its BeforeSendReply
method.
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Configuration;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
namespace Service
{
public class ServiceFaultMessageInspector : 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;
}
}
}
Configuration
Don't forget to configure the <system.serviceModel>
section in the WCF Service web.config to apply the new WCF endpoint behavior to the service.
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="serviceFaults"
type="Service.ServiceFaultBehavior,
Service,
Version=1.0.0.0,
Culture=neutral,
PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="ServiceFaultBehavior">
<serviceFaults/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="Service.CustomerServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<services>
<service behaviorConfiguration="Service.CustomerServiceBehavior"
name="Service.CustomerService">
<endpoint address="" behaviorConfiguration="ServiceFaultBehavior"
binding="basicHttpBinding" contract="Service.CustomerService"/>
-->
</service>
</services>
</system.serviceModel>
After this modification is made to the WCF service, faults will be accessible to Silverlight clients that access this service.
Retrieving Faults for Debugging
Two types of SOAP faults can be sent: Declared and Undeclared. Declared SOAP faults are those in which an operation has a FaultContractAttribute
attribute that specifies a custom SOAP fault type. These SOAP faults are used in production. Undeclared SOAP faults are not specified in the contract for an operation. These SOAP faults are used only for debugging.
Undeclared SOAP faults are useful for debugging a service. It is simple to enable these undefined faults to propagate the complete information about an exception in the fault message sent from the service to the client. This is done by setting the includeExceptionDetailInFaults
attribute of the <serviceDebug>
configuration element to true
.
<serviceDebug includeExceptionDetailInFaults="true"/>