Automating WCF headers and safe-guarding against unhandled exception in WCF services

March 1st, 2009 | Tags:

I needed the ability to pass along information about the client to a service, without having to explicitly specify them as parameters in the operations.

The easiest way to do this, is to implement a behavior extension on the WCF client that will grab the information and inject it into the WCF message as headers. The service can then scan the headers and see if the said headers are present, and if so, act on them.

Writing WCF extensions is not as difficult as it sounds, and is quite useful in a variety of scenarios.

Normally, WCF channels can only fault once, meaning that unhandled exceptions within a WCF service will cause the channel to be set to the “Faulted” state, and subsequent calls on that channel will fail with the infamous “The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.

This means that your service has to catch all exceptions and either deal with them or pass them back to the client wrapped up in the FaultException class.

One way would be to write a try/catch bock in each and every operation you have on your service, and wrap them up in a FaultException and pass them along to the client.  Another would be to subscribe to events and detect when the channel becomes faulted, reinitialize the channel and so on. Tedious, I say!

Here’s a less tedious solution:

public sealed class SafeErrorHandler : IErrorHandler
{
    bool IErrorHandler.HandleError(Exception error)
    {
        // Handle all exceptions
        return true;
    }

    void IErrorHandler.ProvideFault(Exception error,
        MessageVersion version, ref Message fault)
    {
        // Wrap the unknown exception inside a
        // safe FaultException instance
        FaultException _faultException =
            new FaultException(error.Message);
        MessageFault _messageFault =
            _faultException.CreateMessageFault();
        fault = Message.CreateMessage(version,
            _messageFault, _faultException.Action);
    }
}

public sealed class SafeErrorsAttribute : Attribute,
    IServiceBehavior
{
    void IServiceBehavior.ApplyDispatchBehavior(
        ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase)
    {
        IErrorHandler _errorHandler = new SafeErrorHandler();

        foreach (ChannelDispatcherBase _channelDispatcherBase
            in serviceHostBase.ChannelDispatchers)
        {
            ChannelDispatcher _channelDispatcher =
                _channelDispatcherBase as ChannelDispatcher;
            _channelDispatcher.ErrorHandlers.Add(_errorHandler);
        }
    }

    void IServiceBehavior.Validate(
        ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase)
    {
        // Do nothing
    }

    void IServiceBehavior.AddBindingParameters(
        ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase,
        Collection<ServiceEndpoint> endpoints,
        BindingParameterCollection bindingParameters)
    {
        // Do nothing
    }
}

[SafeErrors]
public class TestService : ITestService
{
    public void TestExceptions()
    {
        throw new InvalidCastException();
    }
}

That’s it! Just put all the code except the TestService class inside a utility assembly and add the [SafeErrors] attribute to your WCF services and voila! Whenever you raise an unhandled exception in the WCF service, the SafeErrorHandler will neatly catch it and wrap it up in a FaultException for you. The FaultException is raised on the client, but the channel remains un-faulted and you can continue to make calls on the channel. 

The code is fairly self explanatory. Both the IErrorHandler and the IServiceBehavior are part of WCF, and are declared in the System.ServiceModel.Dispatcher and the System.ServiceModel.Description namespaces respectively.

To automagically pass a header from the WCF client to the service, without having to explicitly include it as a parameter to the service operation, you do much the same thing, except with a little bit more plumbing. Since service clients tend to be auto-generated code, modifying the generated code and placing an attribute on the client proxy specifying that the proxy should do something special, like take it upon itself to inject some interesting headers into the message is not the best of ideas. Although it would work, the next time you update the service reference, those changes would be lost.

Instead, we create a fully fledged WCF extension, and register it on the client side in the app.config file.

Place this code in a class library which you will use from both the client and the server:

[DataContract(Namespace = TestHeaderContext.HeaderNamespace)]
public sealed class TestHeaderContext
{
    internal const string HeaderName = "TestHeader";
    internal const string HeaderNamespace =
        "urn:Test.HeaderTest:v1";

    // These are the fields that will be serialized and
    // passed through the header
    [DataMember]
    public string Value { get; set; }

    [DataMember]
    public int IntValue { get; set; }

    // You can add as many data members as you want.
    // The header consists of an entire TestHeaderContext
    // serialized object graph.

    public TestHeaderContext()
    {
        // Set default values here
        Value = WindowsIdentity.GetCurrent().Name;
        IntValue = 42;
    }

    // This is to make it thread safe, so that each thread
    // on the client has its own instance of the
    // TestHeaderContext class. So if you have a multi-
    // threaded client, where each thread can connect to
    // a WCF service, they can each have their own values
    // being passed to the service.
    [ThreadStatic]
    private static TestHeaderContext mCurrent;

    // These are the values owned by the client's current
    // thread, that will be passed to the service.
    public static TestHeaderContext Current
    {
        get
        {
            if (mCurrent == null)
                mCurrent = new TestHeaderContext();
            return mCurrent;
        }
    }

    // Get test header from the current incoming WCF
    // message, if any
    public static TestHeaderContext Incoming
    {
        get
        {
            if (OperationContext.Current != null)
            {
                if (OperationContext.Current.
                    IncomingMessageHeaders.FindHeader(
                        TestHeaderContext.HeaderName,
                    TestHeaderContext.HeaderNamespace) != -1)
                {
                    TestHeaderContext _header =
                        OperationContext.Current.
                        IncomingMessageHeaders.
                        GetHeader<TestHeaderContext>(
                            TestHeaderContext.HeaderName,
                            TestHeaderContext.
                                HeaderNamespace);
                    return _header;
                }
            }
            return null;
        }
    }
}

// This class is provides a BehaviorExtension, so that we
// can easily add this behavior to WCF clients, via the
// config file, and requiring no coding.
public sealed class TestHeaderExtension :
        BehaviorExtensionElement, IEndpointBehavior,
        IClientMessageInspector
{
    public override Type BehaviorType
    {
        get { return GetType(); }
    }

    protected override object CreateBehavior()
    {
        return new TestHeaderExtension();
    }

    object IClientMessageInspector.BeforeSendRequest(
        ref Message request, IClientChannel channel)
    {
        MessageHeader _messageHeader = MessageHeader.
                CreateHeader(
            TestHeaderContext.HeaderName,
            TestHeaderContext.HeaderNamespace,
            TestHeaderContext.Current);
        request.Headers.Add(_messageHeader);
        return null;
    }

    void IEndpointBehavior.ApplyClientBehavior(
        ServiceEndpoint endpoint,
        ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(
            new TestHeaderExtension());
    }

    void IClientMessageInspector.AfterReceiveReply(
        ref Message reply, object correlationState)
    {
        // Do nothing
    }       

    void IEndpointBehavior.AddBindingParameters(
        ServiceEndpoint endpoint,
        BindingParameterCollection bindingParameters)
    {
        // Do nothing
    }        

    void IEndpointBehavior.ApplyDispatchBehavior(
        ServiceEndpoint endpoint,
        EndpointDispatcher endpointDispatcher)
    {
        // Do nothing
    }

    void IEndpointBehavior.Validate(ServiceEndpoint endpoint)
    {
        // Do nothing
    }
}

On your client, either add the extension programatically, like this:

SomeServiceClient proxy = new SomeServiceClient();
proxy.ChannelFactory.Endpoint.Behaviors.Add(
    new TestHeaderExtension());

Or add it to your app.config file like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <behaviors>
            <endpointBehaviors>
                <behavior name="MyServiceBehavior">
                    <TestHeader />
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <extensions>
            <behaviorExtensions>
                <add name="TestHeader"
                    type="HeaderTestLib.TestHeaderExtension,
                    HeaderTestLib, Version=1.0.0.0,
                    Culture=neutral,
                    PublicKeyToken=null" />
            </behaviorExtensions>
        </extensions>
        <bindings>
            <wsHttpBinding>
            ...

Where HeaderTestLib.TestHeaderExtension, HeaderTestLib is the assembly name of the class library that has the TestHeaderExtension implementation.

You can also right click the app.config file and select “Edit WCF Configuration”, and do all this with point and click.

After the WCF configuration editor has launched, navigate to Advanced, Extensions and then to behavior element extensions. Click on the button “New”, and enter a descriptive name, such as TestHeader. Then, click on the button with the three dots and browse into the binary .DLL class library that implements the TestHeaderExtension, and select the TestHeaderExtension class, and click OK.

(Click on picture for a larger view)

wcf_bex_1

Next, navigate to Advanced, Endpoint Behaviors, and create a new behavior (or modify an existing one). Click on the button “Add”, and select the item with the descriptive name you created in the previous step (TestHeader), and click the button “Add”.

wcf_bex_2

You should now see something like this:

wcf_bex_3

And that’s it!

You have now created a fully fledged WCF extension, and registered it on the client side!

When you then call a method on the client proxy, the extension will intercept the call and serialize the TestHeaderContext.Current property into a header called “TestHeader”.

On the service side, you can access the TestHeaderContext.Incoming property to retrieve the header.

I decided against using the Current property for both the client and the service, since you can actually create a chain, the service calls another service, which in turn calls yet another service. Each call passing along different values, thus I wanted to separate the incoming and outgoing values (Incoming and Current properties).

The client code would look something like this:

TestHeaderContext.Current.Value = "Hello world!";

HeaderTestServiceClient client =
    new HeaderTestServiceClient();
client.GetData(0);

And your service something like this:

public class HeaderTestService : IHeaderTestService
{
    public string GetData(int value)
    {
        string _clientContextValue = "<none>";

        TestHeaderContext _header =
            TestHeaderContext.Incoming;
        if (_header != null)
            _clientContextValue = _header.Value;

        return string.Format("You entered: {0}. " +
            "Your context value is: {1}", value,
            _clientContextValue);
    }
}

The output of the client will be:

wcf_bex_output

And that’s it!

Please leave a comment and let me know if you enjoyed this article and would like to see more like it.

No comments yet.