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:
- SOAP Header
- Message Inspector
- Client Context and Server Context class
- 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.
[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:
public class CustomMessageInspector : IClientMessageInspector, IDispatchMessageInspector
{
#region Message Inspector of the Service
public object AfterReceiveRequest(ref Message request,
IClientChannel channel, InstanceContext instanceContext)
{
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
Message messageCopy = buffer.CreateMessage();
ServiceHeader customData = CustomHeader.ReadHeader(request);
ServerContext customContext = new ServerContext();
if (customData != null)
{
customContext.KerberosID = customData.KerberosID;
customContext.SiteminderToken = customData.SiteminderToken;
}
OperationContext.Current.IncomingMessageProperties.Add(
"CurrentContext", customContext);
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
OperationContext.Current.Extensions.Remove(ServerContext.Current);
}
#endregion
#region Message Inspector of the Consumer
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
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);
request.Headers.Add(header);
return null;
}
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.
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.
public class ServerContext : IExtension<OperationContext>
{
public string EmployeeID;
public string WindowsLogonID;
public string KerberosID;
public string SiteminderToken;
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:
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:
[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:
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
.
[CustomBehavior]
public class WCFService : IWCFService
{
}
That’s all! Enjoy your custom header passing using behavior specified.
Hope this will help!!!