Adding Custom SOAP Headers in WCF
First, you need to decide whether to create the custom header using the MessageHeader.CreateHeader method, or by extending the abstract MessageHeader class.
Under the hood, the CreateHeader method returns an instance of an internal class called XmlObjectSerializerHeader, which uses a serializer to write the header:
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
Type type;
lock (this.syncRoot)
{
if (this.serializer == null)
{
XmlObjectSerializerHeader xmlObjectSerializerHeader = this;
if (this.objectToSerialize == null)
{
type = typeof(object);
}
else
{
type = this.objectToSerialize.GetType();
}
xmlObjectSerializerHeader.serializer = DataContractSerializerDefaults.CreateSerializer(type, this.Name, this.Namespace, 2147483647);
}
this.serializer.WriteObjectContent(writer, this.objectToSerialize);
}
}
If you want full control over the header writing method, and less objects created in the process, consider creating your own message header class. It can be also necessary when you want to format the header’s content in a way that’s not a straight-forward serialized version of its value. For example, if you want to write a header like the following:
<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<CustomHeader xmlns="https://schemas.microsoft.com/42">
<Id>4a851a6d-c85c-4bd3-9584-c00740332aa4</Id>
</CustomHeader>
</s:Header>
<s:Body />
</s:Envelope>
Using the XmlObjectSerializerHeader will produce the following:
<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<CustomHeader xmlns="https://schemas.microsoft.com/42">
<id>4a851a6d-c85c-4bd3-9584-c00740332aa4</id>
</CustomHeader>
</s:Header>
<s:Body />
</s:Envelope>
Because the XmlDictionaryWriter encoded the value’s XML special characters. What you want is to build the custom header yourself:
/// <summary>
/// Represents a custom message header.
/// </summary>
public class CustomMessageHeader : MessageHeader
{
private const string HeaderName = "CustomHeader";
private const string HeaderNamespace = "https://schemas.microsoft.com/42";
/// <summary>
/// Initializes a new instance of the <see cref="CustomMessageHeader" /> class.
/// </summary>
public CustomMessageHeader()
{
Id = Guid.NewGuid();
}
/// <summary>
/// Gets the name of the message header.
/// </summary>
/// <returns>The name of the message header.</returns>
public override string Name
{
get { return HeaderName; }
}
/// <summary>
/// Gets the namespace of the message header.
/// </summary>
/// <returns>The namespace of the message header.</returns>
public override string Namespace
{
get { return HeaderNamespace; }
}
/// <summary>
/// Gets the request ID.
/// </summary>
/// <returns>A <see cref="Guid"/> that uniquely identifies a request.</returns>
public Guid Id { get; private set; }
/// <summary>
/// Called when the header content is serialized using the specified XML writer.
/// </summary>
/// <param name="writer">
/// An <see cref="T:System.Xml.XmlDictionaryWriter" /> that is used to serialize the header contents.
/// </param>
/// <param name="messageVersion">
/// The object that contains information related to the version of SOAP associated with a message and its exchange.
/// </param>
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteElementString("Id", Id.ToString());
}
}
Now, to inject that custom message header, messages need to be intercepted before they are sent to the server. This can be done by implementing the IClientMessageInspector.BeforeSendRequest method:
/// <summary>
/// Represents a message inspector object that can be added to the <c>MessageInspectors</c> collection to view or modify messages.
/// </summary>
public class ClientMessageInspector : IClientMessageInspector
{
/// <summary>
/// Enables inspection or modification of a message before a request message is sent to a service.
/// </summary>
/// <param name="request">The message to be sent to the service.</param>
/// <param name="channel">The WCF client object channel.</param>
/// <returns>
/// The object that is returned as the <paramref name="correlationState " /> argument of
/// the <see cref="M:System.ServiceModel.Dispatcher.IClientMessageInspector.AfterReceiveReply(System.ServiceModel.Channels.Message@,System.Object)" /> method.
/// This is null if no correlation state is used.The best practice is to make this a <see cref="T:System.Guid" /> to ensure that no two
/// <paramref name="correlationState" /> objects are the same.
/// </returns>
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
CustomMessageHeader header = new CustomMessageHeader();
request.Headers.Add(header);
return header.Id;
}
/// <summary>
/// Enables inspection or modification of a message after a reply message is received but prior to passing it back to the client application.
/// </summary>
/// <param name="reply">The message to be transformed into types and handed back to the client application.</param>
/// <param name="correlationState">Correlation state data.</param>
public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
// Nothing special here
}
}
This interception is a behavior or an extension of WCF endpoints, so one more method needs to be implemented to apply that behavior to the endpoint, the IEndpointBehavior.ApplyClientBehavior method:
/// <summary>
/// Represents a run-time behavior extension for a client endpoint.
/// </summary>
public class CustomEndpointBehavior : IEndpointBehavior
{
/// <summary>
/// Implements a modification or extension of the client across an endpoint.
/// </summary>
/// <param name="endpoint">The endpoint that is to be customized.</param>
/// <param name="clientRuntime">The client runtime to be customized.</param>
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new ClientMessageInspector());
}
/// <summary>
/// Implement to pass data at runtime to bindings to support custom behavior.
/// </summary>
/// <param name="endpoint">The endpoint to modify.</param>
/// <param name="bindingParameters">The objects that binding elements require to support the behavior.</param>
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
// Nothing special here
}
/// <summary>
/// Implements a modification or extension of the service across an endpoint.
/// </summary>
/// <param name="endpoint">The endpoint that exposes the contract.</param>
/// <param name="endpointDispatcher">The endpoint dispatcher to be modified or extended.</param>
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
// Nothing special here
}
/// <summary>
/// Implement to confirm that the endpoint meets some intended criteria.
/// </summary>
/// <param name="endpoint">The endpoint to validate.</param>
public void Validate(ServiceEndpoint endpoint)
{
// Nothing special here
}
}
The last step is to add the new behavior to the WCF endpoint:
Endpoint.EndpointBehaviors.Add(new CustomEndpointBehavior());
Voilà!
Comments
Anonymous
April 21, 2013
I think ClientMessageInspectors is MessageInspectors in NET 4.5Anonymous
January 22, 2014
In the ClientMessageInspector class, what is the CustomMessageHeader namespace? I cannot find it.Anonymous
January 22, 2014
Brad, CustomMessageHeader is your own class, the example above includes an implementation of CustomMessageHeader.Anonymous
January 22, 2014
Thanks, I noticed that immediately after I hit Post. It's been a really long day. lol I do have a question though in using this. How can the custom header to something like: BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential); binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName; binding.MessageEncoding = WSMessageEncoding.Text; CustomBinding customBinding = new CustomBinding(binding); SecurityBindingElement element = customBinding.Elements.Find<SecurityBindingElement>(); element.IncludeTimestamp = false; EndpointAddress address = new EndpointAddress("https://URLforSERVICE?wsdl"); Svc.CreateInterfaceClient myClient = new Svc.CreateInterfaceClient(customBinding, address); myClient.ClientCredentials.UserName.UserName = "MYUSERNAME"; myClient.ClientCredentials.UserName.Password = "MYPASSWORD";Anonymous
January 23, 2014
Brad, you need to use the ClientBase<TChannel> constructor that takes in a ServiceEndpoint: msdn.microsoft.com/.../dd449131(v=vs.110).aspx And follow the last step listed above, yourEndpoint.EndpointBehaviors.Add(new CustomEndpointBehavior()), to add the custom behavior.Anonymous
January 26, 2014
Any chance you can provide an example of how to do that? I'm sorry for so many questions.....I'm a newbie. :(Anonymous
June 05, 2014
Great, Its really helped me.