Click here to Skip to main content
15,867,488 members
Articles / WCF

Add Custom Message Header in WCF 4 Calls

Rate me:
Please Sign up or sign in to vote.
4.82/5 (18 votes)
22 Mar 2012CPOL5 min read 176.4K   30   41
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)


Written By
Technical Lead
India India
I write software using Microsoft web technologies since 2008. I have successfully delivered software products for Fortune 500 companies and startups.

Microsoft Certified Technologies Specialist - Web Applications Development with Microsoft .NET Framework 4.

Awarded as Microsoft Community Contributor of the year 2011.

Received several awards at various forums and my various articles got listed as "Article of the day" at ASP.NET Microsoft Official Website https://www.asp.net/

Visit My Blog:
https://ramanisandeep.wordpress.com/


Area of Expertise:
C#, ASP.NET, Web Services, WCF, ASP.NET MVC, SQL Server, WEB API, AngularJS, jQuery

Comments and Discussions

 
QuestionWhy is this so long and complex? Pin
Member 1495942317-Mar-22 13:18
Member 1495942317-Mar-22 13:18 
QuestionWhere to define CustomHeader and CustomBehavior class? Pin
Member 1031487117-Mar-20 20:44
Member 1031487117-Mar-20 20:44 
Questioni need source code of this work, i can call before send request with out adding behavior at runtime Pin
ManiLancer10-Oct-19 9:37
ManiLancer10-Oct-19 9:37 
Questioni cant find BehaviorExtension class that mentioned in web.config, can you help with that?? Pin
ManiLancer19-Feb-19 23:44
ManiLancer19-Feb-19 23:44 
QuestionUniqueness of ClientContext Pin
Messeret Tesfaye7-May-18 5:02
Messeret Tesfaye7-May-18 5:02 
QuestionImplementation Help Pin
Member 411546924-Mar-17 12:27
Member 411546924-Mar-17 12:27 
Questionwhat is service side and what is client side. Pin
shikha1722-Dec-15 10:56
shikha1722-Dec-15 10:56 
AnswerRe: what is service side and what is client side. Pin
Sandeepkumar Ramani25-Dec-15 2:53
Sandeepkumar Ramani25-Dec-15 2:53 
Questionwhere is ServerContext Pin
Philip Livingstone13-Jan-15 5:26
Philip Livingstone13-Jan-15 5:26 
AnswerRe: where is ServerContext Pin
Sandeepkumar Ramani15-Jan-15 4:09
Sandeepkumar Ramani15-Jan-15 4:09 
Questionis it needed to add configurations in web.config ? Pin
Dushmantha Baranige16-Dec-14 19:47
professionalDushmantha Baranige16-Dec-14 19:47 
AnswerRe: is it needed to add configurations in web.config ? Pin
Sandeepkumar Ramani11-Jan-15 5:25
Sandeepkumar Ramani11-Jan-15 5:25 
GeneralRe: is it needed to add configurations in web.config ? Pin
Dushmantha Baranige12-Jan-15 1:48
professionalDushmantha Baranige12-Jan-15 1:48 
GeneralRe: is it needed to add configurations in web.config ? Pin
Sandeepkumar Ramani15-Jan-15 4:25
Sandeepkumar Ramani15-Jan-15 4:25 
GeneralRe: is it needed to add configurations in web.config ? Pin
Dushmantha Baranige20-Jan-15 0:08
professionalDushmantha Baranige20-Jan-15 0:08 
GeneralMy vote of 3 Pin
lmcpferreira3-Apr-14 5:07
lmcpferreira3-Apr-14 5:07 
GeneralRe: My vote of 3 Pin
Sandeepkumar Ramani17-Jun-14 20:39
Sandeepkumar Ramani17-Jun-14 20:39 
BugA possible crippling bug Pin
lmcpferreira2-Apr-14 6:34
lmcpferreira2-Apr-14 6:34 
QuestionYou lost my vote Pin
ganeshvittal2-Feb-14 3:18
ganeshvittal2-Feb-14 3:18 
AnswerRe: You lost my vote Pin
Sandeepkumar Ramani7-Feb-14 0:38
Sandeepkumar Ramani7-Feb-14 0:38 
Questioncorrelation object returned from the AfterReceiveRequest method Pin
pgr_home3-Dec-13 4:41
professionalpgr_home3-Dec-13 4:41 
AnswerRe: correlation object returned from the AfterReceiveRequest method Pin
Sandeepkumar Ramani7-Feb-14 1:00
Sandeepkumar Ramani7-Feb-14 1:00 
GeneralMy vote of 5 Pin
Member 99781713-Sep-13 22:26
Member 99781713-Sep-13 22:26 
GeneralMy vote of 1 Pin
Member 100860441-Jul-13 4:06
Member 100860441-Jul-13 4:06 
GeneralRe: My vote of 1 Pin
Sandeepkumar Ramani16-Jul-13 5:26
Sandeepkumar Ramani16-Jul-13 5:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.