A message inspector is an extensibility object that can be used in the service model’s client runtime and dispatch runtime programmatically or through configuration and that can inspect and alter messages after they are received or before they are sent.
Any service (dispatcher) message inspector must implement the two IDispatchMessageInspector methods AfterReceiveRequest and BeforeSendReply.
AfterReceiveRequest is invoked by the dispatcher when a message has been received, processed by the channel stack and assigned to a service, but before it is deserialized and dispatched to an operation. If the incoming message was encrypted, the message is already decrypted when it reaches the message inspector. The method gets the requestmessage passed as a reference parameter, which allows the message to be inspected, manipulated or replaced as required. The return value can be any object and is used as a correlation state object that is passed to BeforeSendReply when the service returns a reply to the current message. In this sample, AfterReceiveRequest delegates the inspection (validation) of the message to the private, local method ValidateMessageBody and returns no correlation state object. This method ensures that no invalid messages pass into the service.
I am quoting the above texts from Microsoft MSDN because they are important and you should read before implementing message inspector.
It’s very important that you don’t try to read the original request message body. If you do, you will not be able to read it again during method invocation and you will be rewarded with the famous error- “This message cannot support the operation because it has been read.” If you need to really read the message body, you will need to make a copy of the message and read the copied version. Same is true for Reply message. If you try to read the reply message, the client will no longer get the original reply. Make sure you make a copy of the reply message and preserve the original version before reading it. Also, you must make sure your interception logic does not raise error unnecessarily.
In this article I will show how you can intercept messages at the server side by implementing IDispatchMessageInspector. Implementing IDispatchMessageInspector alone will not do the job and you will have to wire it with servicemodel’s runtime. We are going to collect some data and save them to an object which then can be persisted in a file or database.
Enough warning is said and there is no need to worry because the interception rocks! Here is the quick 1-2-3 of what we are going to need to make the interceptor work-
1) ServerInspector that implements IDispatchMessageInspector.
2) ServiceBehavior to attach the ServerInspector to DispatchRuntime. Then, you can simply add the ServiceBehavior as an attribute to the service implementation class.
3) If you prefer to attach the ServiceBehavior trhough configuration, you will need to implement ServiceBehaviorExtensionElement.
So, let’s jump on to the codes.
ServerInspector.cs:
[code language=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Dispatcher;
using System.Net;
using System.ServiceModel.Channels;
using System.ServiceModel;
using System.Runtime.Remoting.Messaging;
using System.Xml.Linq;
using System.Reflection;
using System.ServiceModel.Description;
namespace WCF.Behaviors
{
internal class ServerInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
ClientInfo cinfo = new ClientInfo();
//Collect any info that passed in the headers from Client, if any.
cinfo.ServerTimeStamp = DateTime.Now; //Timestamp after the call is received.
cinfo.Platform = “WCF”;
OperationDescription operationDesc = GetOperationDescription(OperationContext.Current);
if (operationDesc != null)
{
Type contractType = operationDesc.DeclaringContract.ContractType;
cinfo.Action = operationDesc.Name;
cinfo.TypeName = contractType.FullName;
cinfo.AssemblyName = contractType.Assembly.GetName().Name;
}
cinfo.ServerName = Dns.GetHostName();
cinfo.ServerProcessName = System.AppDomain.CurrentDomain.FriendlyName;
return cinfo;
}
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
//correlationState is of type ClientInformation and it is set in AfterReceiveRequest event.
if (correlationState != null)
{
ClientInfo cinfo = correlationState as ClientInfo;
if (cinfo != null)
{
cinfo.ServerEndTimeStamp = DateTime.Now;
//It’s okay to read the RequestMessage since the operation
//has been completed and message is serialized from stream
//(….stream…).
//RequestMessage on OperationContext is short lived.
//It would be too late to serialize the RequestMessage
//in another thread (exception raised: Message is closed)
cinfo.Request = OperationContext.Current.RequestContext.RequestMessage.ToString();
//Message can be read only once.
//Create a BufferedCopy of the Message and be sure to set
//the original message set to a value wich has not been
//copied nor read.
MessageBuffer mb = reply.CreateBufferedCopy(int.MaxValue);
Message responseMsg = mb.CreateMessage();
reply = mb.CreateMessage();
cinfo.Response = responseMsg.ToString();
if (reply.IsFault == true)
{
cinfo.IsError = true;
}
//Log cinfo async here;
}
}
}
private OperationDescription GetOperationDescription(OperationContext operationContext)
{
OperationDescription od = null;
string bindingName = operationContext.EndpointDispatcher.ChannelDispatcher.BindingName;
string methodName;
if (bindingName.Contains(“WebHttpBinding”))
{
//REST request
methodName = (string)operationContext.IncomingMessageProperties[“HttpOperationName”];
}
else
{
//SOAP request
string action = operationContext.IncomingMessageHeaders.Action;
methodName = operationContext.EndpointDispatcher.DispatchRuntime.Operations.FirstOrDefault(o => o.Action == action).Name;
}
EndpointAddress epa = operationContext.EndpointDispatcher.EndpointAddress;
ServiceDescription hostDesc = operationContext.Host.Description;
ServiceEndpoint ep = hostDesc.Endpoints.Find(epa.Uri);
if (ep != null)
{
od = ep.Contract.Operations.Find(methodName);
}
return od;
}
}
}
[/code]
ServiceBehavior.cs:
[code language=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel;
namespace WCF.Behaviors
{
public class ServiceBehavior: Attribute, IServiceBehavior
{
public ServiceBehavior()
{
}
#region IServiceBehavior Members
public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher cd = cdb as ChannelDispatcher;
if (cd != null)
{
foreach (EndpointDispatcher ed in cd.Endpoints)
{
ServerInspector serverInspector = ed.DispatchRuntime.MessageInspectors.Find
if (serverInspector == null)
{
ed.DispatchRuntime.MessageInspectors.Add(new ServerInspector());
}
}
}
}
}
public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
}
#endregion IServiceBehavior Members
}
}
[/code]
ServiceBehaviorExtensionElement.cs:
[code language=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Configuration;
namespace WCF.Behaviors
{
public class ServiceBehaviorExtensionElement : BehaviorExtensionElement
{
#region BehaviorExtensionElement Optimized
protected override object CreateBehavior()
{
return new ServiceBehavior();
}
public override Type BehaviorType
{
get { return typeof(ServiceBehavior); }
}
#endregion BehaviorExtensionElement Optimized
}
}
[/code]
ClientInfo.cs:
[code language=”csharp”]
using System;
using System.Runtime.Serialization;
using System.Text;
using System.Xml.Serialization;
using System.IO;
namespace WCF.Contracts
{
///
/// DataContractSerilizer supports Serializable attribute
///
[Serializable, XmlRoot(Namespace = “http://www.aspnet4you.com/services”)]
public class ClientInfo
{
///
///
public ClientInfo() { }
///
///
public string MachineName
{
get;
set;
}
///
///
public DateTime TimeStamp
{
get;
set;
}
///
///
public string Action
{
get;
set;
}
///
///
public string ApplicationName
{
get;
set;
}
///
///
public string ServerName
{
get;
set;
}
///
///
public DateTime ServerTimeStamp
{
get;
set;
}
///
///
public DateTime ServerEndTimeStamp
{
get;
set;
}
///
///
public string ServerProcessName
{
get;
set;
}
///
///
public string Request
{
get;
set;
}
///
///
public string Response
{
get;
set;
}
///
///
public bool IsError
{
get;
set;
}
///
///
public string AssemblyName
{
get;
set;
}
///
///
public string TypeName
{
get;
set;
}
///
///
public string Platform
{
get;
set;
}
}
}
[/code]
I am not covering the details on how to use the extension element in the configuration and I would leave that you for further reading at- http://msdn.microsoft.com/en-us/library/aa717047.aspx.
