Sunday, July 5, 2009

Additional CallContext information in WCF

Yeah, this might be a little old…but someone asked me how to send context information from client to the service with WCF.

Note: In general, you should not rely on client provided information about security, authentication, licensing, etc. Use this code and sample as an idea on how to implement your custom security. If you’re trying to send sensitive information over the network, use at your own risk.

WCF client and services might be (and probably are) in two separate AppDomain or even on separate machines across the network, so how do we send extra information from the client to the service when the service call is made? Fortunately, WCF provides enough extensibility points for us to participate in the action and change the default behavior. It is pretty much easy to add context information to the message header on the client side, and the on the server side extract the custom data from the message header. Added data to the message header can be a DataContract / Serialized class.

[DataContract]
public class MessageInfo
{
public MessageInfo(Guid sessionId, CultureInfo culture, DateTime requestDate)
{
this.sessionId = sessionId;
this.culture = culture;
this.requestDate = requestDate;
}

[DataMember]
public DateTime RequestDate
{
get;
set;
}

[DataMember]
public Guid SessionId
{
get;
set;
}

[DataMember]
public CultureInfo Culture
{
get;
set;
}
}
Client uses IClientMessageInspector to add context data before the message is handed over to WCF for the delivery.
/// <summary>
///
Flows the client's MessageInfo to the service.
/// </summary>
public class ClientMessageInfoInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
request.Headers.Add(MessageHeader.CreateHeader(MessageHeaderKeys.MessageInfoKey, string.Empty, GetInfo()));
return null;
}

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

private MessageInfo GetInfo()
{
return new MessageInfo(Guid.NewGuid(), Thread.CurrentThread.CurrentUICulture, DateTime.Now);
}
}
On the other hand, the service side uses IDispatchMessageInspector interface to extract the data from message header.
/// <summary>
///
Reads the MessageInfo of client and updates the service.
/// </summary>
public class ServiceMessageInfoInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
int headerIndex = request.Headers.FindHeader(MessageHeaderKeys.MessageInfoKey, string.Empty);
if (headerIndex != -1)
{
MessageInfo ui = request.Headers.GetHeader<MessageInfo>(headerIndex);

Thread.CurrentThread.CurrentUICulture = ui.Culture;
Thread.CurrentThread.CurrentCulture = ui.Culture;
            Console.WriteLine("Request recieved on {0}", ui.RequestDate);
}

return null;
}

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

To wire things up, you also need to create a new behavior by implementing IEndpointBehavior and registering it with your endpoint, either by code, or configuration. Should you use a single behavior for both client and server, here’s how to do it:

public class MessageInfoBehavior : IEndpointBehavior
{
public void Validate(ServiceEndpoint endpoint)
{
}

public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
ServiceMessageInfoInspector inspector = new ServiceMessageInfoInspector();
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
}

public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
ClientMessageInfoInspector inspector = new ClientMessageInfoInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
}

Hope this helps.


Submit this story to DotNetKicks Shout it

No comments: