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 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 the
FindHeader method of the MessageHeaders collection, as well as the method
GetReaderAtHeader, 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!!!