Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / WCF

Add Custom Message Header in WCF 4 Calls

4.82/5 (18 votes)
22 Mar 2012CPOL5 min read 181.9K  
How to add a custom message header in WCF 4 calls

Often, we want to pass some data to some or maybe all our service operations. This data is usually context data such as user tokens, or environmental preferences of the user or machine.

In simple web service, we can pass custom header information using Attribute called [SoapHeaderAttribute ("ServiceHeader", Direction=SoapHeaderDirection.In)]” along with Web Method signatures.

But in WCF, we cannot use the same attribute.

One way would be to pass it as an additional request parameter. But, each and every method call needs to have this parameter(s) repeatedly. Not a very clean solution. Also, if the data type of this parameter changes, all the method signatures and their calls need to be changed.

A nice and easy way to pass that data is to use Message Headers.

In WCF, to pass the custom header information along with method call, we need to implement custom inspector for client and service which will implement the BeforeSendRequest and AfterRecieveRequest methods to inject the custom header.

In order to do this, we need the following objects/classes:

  1. SOAP Header
  2. Message Inspector
  3. Client Context and Server Context class
  4. Custom Behavior

Let’s start creating these classes one by one.

1. SOAP Header

The CustomHeader class is used to create custom header for service in which we want to pass header information along with method call. The CustomHeader class contains the information that we want to pass along with method call. You can define the structure as per your needs.

C#
[DataContract]
public class ServiceHeader
{
    [DataMember]
    public string EmployeeID { get; set; }

    [DataMember]
    public string WindowsLogonID { get; set; }

    [DataMember]
    public string KerberosID { get; set; }

    [DataMember]
    public string SiteminderToken { get; set; }
}

public class CustomHeader : MessageHeader
{
    private const string CUSTOM_HEADER_NAME = "HeaderName";
    private const string CUSTOM_HEADER_NAMESPACE = "YourNameSpace";

    private ServiceHeader _customData;

    public ServiceHeader CustomData
    {
        get
        {
            return _customData;
        }
    }

    public CustomHeader()
    {
    }

    public CustomHeader(ServiceHeader customData)
    {
        _customData = customData;
    }

    public override string Name
    {
        get { return (CUSTOM_HEADER_NAME); }
    }

    public override string Namespace
    {
        get { return (CUSTOM_HEADER_NAMESPACE); }
    }

    protected override void OnWriteHeaderContents(
        System.Xml.XmlDictionaryWriter writer, MessageVersion messageVersion)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(ServiceHeader));
        StringWriter textWriter = new StringWriter();
        serializer.Serialize(textWriter, _customData);
        textWriter.Close();

        string text = textWriter.ToString();

        writer.WriteElementString(CUSTOM_HEADER_NAME, "Key", text.Trim());
    }

    public static ServiceHeader ReadHeader(Message request)
    {
        Int32 headerPosition = request.Headers.FindHeader(CUSTOM_HEADER_NAME, CUSTOM_HEADER_NAMESPACE);
        if (headerPosition == -1)
            return null;

        MessageHeaderInfo headerInfo = request.Headers[headerPosition];

        XmlNode[] content = request.Headers.GetHeader<XmlNode[]>(headerPosition);

        string text = content[0].InnerText;

        XmlSerializer deserializer = new XmlSerializer(typeof(ServiceHeader));
        TextReader textReader = new StringReader(text);
        ServiceHeader customData = (ServiceHeader)deserializer.Deserialize(textReader);
        textReader.Close();

        return customData;
    }
}

As you can see, it is a type inheriting from MessageHeader class. Notice the OnWriteHeaderContents override, which is invoked by WCF infrastructure to serialize the SOAP Header, and the ReadHeader static method that we will use later.

2. Message Inspector

SOAP Header needs to be added by the consumer and read by the service. To do this, we need a Message Inspector like the following one:

C#
/// <summary>
/// This class is used to inspect the message and headers on the server side,
/// This class is also used to intercept the message on the
/// client side, before/after any request is made to the server.
/// </summary>
public class CustomMessageInspector : IClientMessageInspector, IDispatchMessageInspector
{
    #region Message Inspector of the Service

    /// <summary>
    /// This method is called on the server when a request is received from the client.
    /// </summary>
    /// <param name="request"></param>
    /// <param name="channel"></param>
    /// <param name="instanceContext"></param>
    /// <returns></returns>
    public object AfterReceiveRequest(ref Message request, 
           IClientChannel channel, InstanceContext instanceContext)
    {
        // Create a copy of the original message so that we can mess with it.
        MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
        request = buffer.CreateMessage();
        Message messageCopy = buffer.CreateMessage();

        // Read the custom context data from the headers
        ServiceHeader customData = CustomHeader.ReadHeader(request);

        // Add an extension to the current operation context so
        // that our custom context can be easily accessed anywhere.
        ServerContext customContext = new ServerContext();

        if (customData != null)
        {
            customContext.KerberosID = customData.KerberosID;
            customContext.SiteminderToken = customData.SiteminderToken;
        }
        OperationContext.Current.IncomingMessageProperties.Add(
                 "CurrentContext", customContext);
        return null;
    }

    /// <summary>
    /// This method is called after processing a method on the server side and just
    /// before sending the response to the client.
    /// </summary>
    /// <param name="reply"></param>
    /// <param name="correlationState"></param>
    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        // Do some cleanup
        OperationContext.Current.Extensions.Remove(ServerContext.Current);
    }

    #endregion

    #region Message Inspector of the Consumer

    /// <summary>
    /// This method will be called from the client side just before any method is called.
    /// </summary>
    /// <param name="request"></param>
    /// <param name="channel"></param>
    /// <returns></returns>
    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        // Prepare the request message copy to be modified
        MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
        request = buffer.CreateMessage();

        ServiceHeader customData = new ServiceHeader();

        customData.KerberosID = ClientContext.KerberosID;
        customData.SiteminderToken = ClientContext.SiteminderToken;

        CustomHeader header = new CustomHeader(customData);

        // Add the custom header to the request.
        request.Headers.Add(header);

        return null;
    }

    /// <summary>
    /// This method will be called after completion of a request to the server.
    /// </summary>
    /// <param name="reply"></param>
    /// <param name="correlationState"></param>
    public void AfterReceiveReply(ref Message reply, object correlationState)
    {

    }

    #endregion
}

As you can see from the code sample above, we use the IClientMessageInspector implementation to handle the addition of the header in the consumer-side code, while we use the IDispatchMessageInspector on the service side, to extract the header. It is interesting that the FindHeader method of the MessageHeaders collection as well as the method GetReaderAtHeader is provided by the same collection of Headers. The result of this last method is an XmlDictionaryReader that we use to read our custom header content, through the ReadHeader static method we’ve already introduced.

3. Client Context and Server Context class

The ClientContext class is used to store the header information before calling the method, so when you want to attach the custom header data, you just need to set the values for this ClientContext class. These values get fetched inside BeforeSendRequest method of CustomMessageInspector class and send along with the request made.

C#
/// <summary>
/// This class will act as a custom context in the client side to hold the context information.
/// </summary>
public class ClientContext
{
    public static string EmployeeID;
    public static string WindowsLogonID;
    public static string KerberosID;
    public static string SiteminderToken;
}

At server side, once custom header is received, it will be stored inside this ServerContext class object, so that we can access it anytime once request is received.

C#
/// <summary>
/// This class will act as a custom context, an extension to the OperationContext.
/// This class holds all context information for our application.
/// </summary>
public class ServerContext : IExtension<OperationContext>
{
    public string EmployeeID;
    public string WindowsLogonID;
    public string KerberosID;
    public string SiteminderToken;

    // Get the current one from the extensions that are added to OperationContext.
    public static ServerContext Current
    {
        get
        {
            return OperationContext.Current.Extensions.Find<ServerContext>();
        }
    }

    #region IExtension<OperationContext> Members
    public void Attach(OperationContext owner)
    {
    }

    public void Detach(OperationContext owner)
    {
    }
    #endregion
}

4. Custom Behavior

The service will be able to read the Key provided through the custom header simply querying the IncomingMessageProperties dictionary:

C#
OperationContext.Current.IncomingMessageProperties["CurrentContext"];

Of course, the Custom Message Inspector needs to be plugged into the WCF pipeline using a custom behavior like the following one:

C#
/// <summary>
/// This custom behavior class is used to add both client and server inspectors to
/// the corresponding WCF end points.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class CustomBehavior : Attribute, IServiceBehavior, IEndpointBehavior
{
    #region IEndpointBehavior Members

    void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, 
         System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }

    void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, 
             System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
    {
        CustomMessageInspector inspector = new CustomMessageInspector();
        clientRuntime.MessageInspectors.Add(inspector);
    }

    void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, 
             System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
    {
        ChannelDispatcher channelDispatcher = endpointDispatcher.ChannelDispatcher;
        if (channelDispatcher != null)
        {
            foreach (EndpointDispatcher ed in channelDispatcher.Endpoints)
            {
                CustomMessageInspector inspector = new CustomMessageInspector();
                ed.DispatchRuntime.MessageInspectors.Add(inspector);
            }
        }
    }

    void IEndpointBehavior.Validate(ServiceEndpoint endpoint) { }

    #endregion

    #region IServiceBehavior Members

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

    void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription desc, ServiceHostBase host)
    {
        foreach (ChannelDispatcher cDispatcher in host.ChannelDispatchers)
        {
            foreach (EndpointDispatcher eDispatcher in cDispatcher.Endpoints)
            {
                eDispatcher.DispatchRuntime.MessageInspectors.Add(new CustomMessageInspector());
            }
        }
    }

    void IServiceBehavior.Validate(ServiceDescription desc, ServiceHostBase host) { }

    #endregion
}

Implement the IEndpointBehavior interface to modify, examine, or extend some aspect of endpoint-wide execution at the application level for either client or service applications.

  • Use the AddBindingParameters method to pass custom data at runtime to enable bindings to support custom behavior.
  • Use the ApplyClientBehavior method to modify, examine, or insert extensions to an endpoint in a client application.
  • Use the ApplyDispatchBehavior method to modify, examine, or insert extensions to endpoint-wide execution in a service application.
  • Use the Validate method to confirm that a ServiceEndpoint meets specific requirements. This can be used to ensure that an endpoint has a certain configuration setting enabled, supports a particular feature and other requirements.

Implement IServiceBehavior to modify, examine, or extend some aspect of service-wide execution at the application level:

  • Use the ApplyDispatchBehavior method to change run-time property values or insert custom extension objects such as error handlers, message or parameter interceptors, security extensions, and other custom extension objects.
  • Use the Validate method to examine the description before constructs the executing service to confirm that it can execute properly.
  • Use the AddBindingParameters method to pass to a binding element the custom information for the service so that it can support the service correctly.

Adding Behavior to the Runtime

When you construct a ServiceHost or client-side ChannelFactory, the runtime reflects over the service types, reads the configuration file, and starts building an in-memory description of the service. Within ServiceHost, this description is made available to you via the Description property (of type ServiceDescription). Within ChannelFactory, it’s made available via the Endpoint property (of type ServiceEndpoint); the client-side description is limited to the target endpoint.

The ServiceDescription contains a full description of the service and each endpoint (ServiceEndpoint), including contracts (ContractDescription) and operations (OperationDescription). ServiceDescription provides a Behaviors property (a collection of type IServiceBehavior) that models a collection of service behaviors. Each ServiceEndpoint also has a Behaviors property (a collection of type IEndpointBehavior) that models the individual endpoint behaviors. Likewise, ContractDescription and OperationDescription each have an appropriate Behaviors property.

These behavior collections are automatically populated during the ServiceHost and ChannelFactory construction process with any behaviors that are found in your code (via attributes) or within the configuration file (more on this shortly). You can also add behaviors to these collections manually after construction. The following example shows how to add the CustomBehavior to the host as a service behavior:

C#
WCFServiceClient ws = new WCFServiceClient();
ws.ChannelFactory.Endpoint.Behaviors.Add(new CustomBehavior());

Adding Behavior with Attribute

During the ServiceHost/ChannelFactory construction process, the runtime reflects over the service types and configuration file and automatically adds any behaviors it finds to the appropriate behavior collections in the ServiceDescription.

C#
/// <summary>
/// Summary description for WCFService
/// </summary>
[CustomBehavior]
public class WCFService : IWCFService
{
}

That’s all! Enjoy your custom header passing using behavior specified.

Hope this will help!!!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)