in

ASP.NET Weblogs

Paolo Pialorsi - Bridge The Gap!

Living in a Service Oriented World

February 2008 - Posts

  • Handling custom SOAP headers via WCF Behaviors

    A few days ago a customer of mine asked me how to define a WCF behavior to add a custom SOAP Header to sent/received messages.

    The solution is not so far from what I've shown in the previous "Writing a WCF Message Inspector" post. In fact one way of working is to define a custom message inspector that writes/reads the custom SOAP Header.

    So first of all we need a SOAP Header. Here is the code to define a custom header to handle a random key (as a Guid) injected in every request sent from the consumer to the service:

    public class CustomHeader : MessageHeader
    {
        private String _key;

        public String Key
        {
            get
            {
                return (this._key);
            }
        }

        public CustomHeader(String key)
        {
            this._key = key;
        }

        public override string Name
        {
            get { return (CustomHeaderNames.CustomHeaderName); }
        }

        public override string Namespace
        {
            get { return (CustomHeaderNames.CustomHeaderNamespace); }
        }

        protected override void OnWriteHeaderContents(System.Xml.XmlDictionaryWriter writer, MessageVersion messageVersion)
        {
            // Write the content of the header directly using the XmlDictionaryWriter
            writer.WriteElementString(CustomHeaderNames.KeyName, this.Key);
        }

        public static CustomHeader ReadHeader(XmlDictionaryReader reader)
        {
            // Read the header content (key) using the XmlDictionaryReader
            if (reader.ReadToDescendant(CustomHeaderNames.KeyName, CustomHeaderNames.CustomHeaderNamespace))
            {
                String key = reader.ReadElementString();
                return (new CustomHeader(key));
            }
            else
            {
                return null;
            }
        }

    }

    public static class CustomHeaderNames
    {
        public const String CustomHeaderName = "CustomHeader";

        public const String KeyName = "Key";

        public const String CustomHeaderNamespace = "http://schemas.devleap.com/CustomHeader";

    }

    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.

    Such a SOAP Header need to be added by the consumer and read by the service. To do this we need a MessageInspector like the following one:

    public class CustomMessageInspector : IDispatchMessageInspector, IClientMessageInspector
    {
        #region Message Inspector of the Service

        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            // Look for my custom header in the request
            Int32 headerPosition = request.Headers.FindHeader(CustomHeaderNames.CustomHeaderName, CustomHeaderNames.CustomHeaderNamespace);

            // Get an XmlDictionaryReader to read the header content
            XmlDictionaryReader reader =
    request.Headers.GetReaderAtHeader(headerPosition);

            // Read it through its static method ReadHeader
            CustomHeader header = CustomHeader.ReadHeader(reader);

            // Add the content of the header to the IncomingMessageProperties dictionary
          
    OperationContext.Current.IncomingMessageProperties.Add("key", header.Key);

            return null;
        }

        public void BeforeSendReply(ref Message reply, object correlationState)
        {
        }

        #endregion

        #region Message Inspector of the Consumer

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
        }

        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();

            // Simulate to have a random Key generation process
            request.Headers.Add(new CustomHeader(Guid.NewGuid().ToString()));

            return null;
        }

        #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 the FindHeader method of the MessageHeaders collection, as well as the method GetReaderAtHeader, provided by the same collection of SOAP 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.

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

    OperationContext.Current.IncomingMessageProperties["key"]

    Of course this custom MessageInspector needs to be plugged into the WCF pipeline using a custom behavior like the following one:

    [AttributeUsage(AttributeTargets.Class)]
    public class CustomBehavior : Attribute, IEndpointBehavior
    {
        #region IEndpointBehavior Members

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

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

        public void 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);
                }
            }
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }

        #endregion
    }

    We also need an ExtensionElement to configure the behavior:

    public class CustomBehaviorExtensionElement : BehaviorExtensionElement
    {
        protected override object CreateBehavior()
        {
            return new CustomBehavior();
        }

        public override Type BehaviorType
        {
            get
            {
                return typeof(CustomBehavior);
            }
        }
    }

    At last we can configure in the .config file of our service and consumer the behavior. Here is the service side configuration:

    <system.serviceModel>

      <extensions>
        <behaviorExtensions>
          <add name="customBehavior" type="DevLeap.WCF.Behaviors.Extensions.CustomBehaviorExtensionElement, DevLeap.WCF.Behaviors.Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        </behaviorExtensions>
      </extensions>

    <services>
      <service name="DevLeap.WCF.Behaviors.Services.ServiceOne" behaviorConfiguration="serviceBehavior">
        <endpoint
         address="net.tcp://localhost:35001/ServiceOne/"
         binding="netTcpBinding"
         contract="DevLeap.WCF.Behaviors.Contracts.IServiceOne"
        behaviorConfiguration="endpointBehavior" />
      </service>
    </services>

    <behaviors>
      <endpointBehaviors>
        <behavior name="endpointBehavior">
         
    <customBehavior />
        </behavior>
      </endpointBehaviors>

      </behaviors>

    </system.serviceModel>

    And here is the configuration of the consumer-side:

    <system.serviceModel>

        <extensions>
          <behaviorExtensions>
            <add name="customBehavior" type="DevLeap.WCF.Behaviors.Extensions.CustomBehaviorExtensionElement, DevLeap.WCF.Behaviors.Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          </behaviorExtensions>
        </extensions>

        <client>
                <endpoint name="serviceOneEndpoint"
                    address="net.tcp://localhost:35001/ServiceOne/"
                    binding="netTcpBinding"
                    contract="DevLeap.WCF.Behaviors.Contracts.IServiceOne"
                    behaviorConfiguration="serviceOneBehavior" />
            </client>

        <behaviors>
          <endpointBehaviors>
            <behavior name="serviceOneBehavior">
              <customBehavior />
            </behavior>
          </endpointBehaviors>
        </behaviors>

    </system.serviceModel>

    That's all! Enjoy your custom SOAP header defining infrastructural protocols, but don't forget to check the wide range of WS-* specifications before inventing your own :-) ...

More Posts