Monday, November 2, 2015

String trimming WCF message inspector for XML binding

How often your users copy-paste data from somewhere to your web app and then post something that is incorrect because some extra characters are copied from the source text? Is user name really “ John ” “ Smith ”?

Sooner or later at the server side, the input strings are validated but often what you do first is a simple trimming so that “ John” becomes “John”. The question is then not “to trim or not to trim” but rather “when the trimming should occur to be as ellegant as possible”.

String trimming on HTTP input is a convenient solution. The data that is processed by an MVC controller, a WebApi controller or a WCF service code is already trimmed so that you don’t have to put any extra effort on trimming at the business level of your code. This approach is common and solutions for MVC or WebAPI can easily be found.

My specific problem was that I needed the same for WCF and unfortunately, I couldn’t find anything ready to use. My previous experience with WCF message inspectors suggested that a smart inspector is a way to go. A series of blog entries by Carlos Figueria is a valuable source of information on that topic, however this specific requirement of a trimming inspector has no previous solutions (or I couldn’t find any).

Without any further ado, let’s see how such message inspector is implemented:

/// <summary>
/// WCF inspektor trimujący stringi
/// </summary>
public class StringTrimmingMessageInspector : IDispatchMessageInspector
{
    #region IDispatchMessageInspector Members
 
    public object AfterReceiveRequest(
        ref System.ServiceModel.Channels.Message request,
        IClientChannel channel, InstanceContext instanceContext )
    {
        if ( !request.IsEmpty )
        {
            MessageBuffer buffer = request.CreateBufferedCopy( Int32.MaxValue );
            request = buffer.CreateMessage();
 
            XmlDocument xmlDoc = new XmlDocument();
 
            using ( MemoryStream ms = new MemoryStream() )
            {
                XmlWriter writer = XmlWriter.Create( ms );
                request.WriteMessage( writer ); // the message was consumed here
                writer.Flush();
                ms.Position = 0;
                xmlDoc.Load( ms );
            }
 
            XmlNamespaceManager manager = new XmlNamespaceManager( xmlDoc.NameTable );
            manager.AddNamespace( "s", "http://schemas.xmlsoap.org/soap/envelope/" );
 
            XmlNode bodyNode = xmlDoc.SelectSingleNode( "//s:Envelope/s:Body", manager );
            this.ChangeNode( bodyNode );
 
            Message newMessage = Message.CreateMessage( request.Version, request.Headers.Action, bodyNode.FirstChild );
            newMessage.Properties.CopyProperties( request.Properties );
 
            request = newMessage;
        }
 
        return null;
    }
 
    private void ChangeMessage( XmlDocument doc )
    {
        if ( doc == null ) return;
 
        ChangeNode( doc.DocumentElement );
    }
 
    private void ChangeNode( XmlNode node )
    {
        if ( node == null ) return;
        if ( node.NodeType == XmlNodeType.Text ) node.InnerText = node.InnerText.Trim();
 
        foreach ( XmlNode childNode in node.ChildNodes )
            ChangeNode( childNode );
    }
 
    public void BeforeSendReply( ref System.ServiceModel.Channels.Message reply, object correlationState )
    {
    }
 
    #endregion
}

The inspector assumes the message is always an XML document and this is true for XML based bindings. A request is always a SOAP envelope that has headers and a body. Inside the inspector I retrieve the body node of an incoming message and I run a simple recursive function on the node that walks over the tree of XML nodes and whenever it find a node of text type, it trims its contents. I then recreate the message using traversed node as the body.

To apply the inspector, I also need a simple endpoint behavior

/// <summary>
/// Podstawowa infrastruktura rozszerzania
/// </summary>
public class StringTrimmingBehavior : IEndpointBehavior
{
    #region IEndpointBehavior Members
 
    public void AddBindingParameters( ServiceEndpoint endpoint, 
       System.ServiceModel.Channels.BindingParameterCollection bindingParameters )
    {
    }
 
    public void ApplyClientBehavior( ServiceEndpoint endpoint, 
       System.ServiceModel.Dispatcher.ClientRuntime clientRuntime )
    {
    }
 
    public void ApplyDispatchBehavior( ServiceEndpoint endpoint, 
       System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher )
    {
        endpointDispatcher.DispatchRuntime.MessageInspectors
           .Add( new StringTrimmingMessageInspector() );
    }
 
    public void Validate( ServiceEndpoint endpoint )
    {
    }
 
    #endregion
}
And this is it, please feel free to use/modify this for your own purposes.

No comments: