Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replacing content of WCF Message

Tags:

xml

wcf

I'm trying to set up a Routing Service that will sit in the DMZ of my network, and allow external people to access some internally hosted WCF services. I've got everything set up and working, but when I forward on the MEX services, it points our external clients to our internal address, which obviously they can not access.

Microsoft seems to recommend making a copy of the wsdl, which would probably work, but would require me to make a new copy of the wsdl every time the service definitions change, which they do quite often, and seems like overkill. The only thing that needs to be changed is a address in the mex message.

       <endpoint address="http://appsrv1:8781/ProcessManagementService/"
            binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IProcessManagementService"
            contract="IProcessManagementService" name="WSHttpBinding_IProcessManagementService" />

It seems as though using an IDispatchMessageInspector, I should be able to intercept the mex message and replace the internal server name with the external server name, and then I would only have to touch the Routing service when I need to add or remove a service, rather than every time I make any change.

I don't have much experience with XML readers, writers, and so on, so I'm looking for some guidance on how to proceed. If I could just read the xml content into a string, I could perform a replace operation to substitute the external address for the internal one, then replace the message content of the reply with my modified version. How do I go about doing that, or is there a better way to modify the content of a WCF message?

Edit: So this is what I've cobbled together so far.

public class EndpointReplacementInspector : IDispatchMessageInspector
{
    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {           
        var ms = new MemoryStream();
        var w = XmlWriter.Create(ms, new XmlWriterSettings { Indent = true, IndentChars = "  ", OmitXmlDeclaration = true });
        var bodyReader = reply.GetReaderAtBodyContents();
        w.WriteStartElement("s", "Body", "http://schemas.xmlsoap.org/soap/envelope/");
        while (bodyReader.NodeType != XmlNodeType.EndElement && bodyReader.LocalName != "Body" && bodyReader.NamespaceURI != "http://schemas.xmlsoap.org/soap/envelope/")
        {
            if (bodyReader.NodeType != XmlNodeType.Whitespace)
            {
                w.WriteNode(bodyReader, true);
            }
            else
            {
                bodyReader.Read(); // ignore whitespace; maintain if you want
            }
        }
        w.WriteEndElement();
        w.Flush();
        var body = Encoding.UTF8.GetString(ms.ToArray());
        body = body.Replace("internalserver", "externalserver");            
        var replacedMessage = Message.CreateMessage(XmlReader.Create(new StringReader(body)), int.MaxValue, reply.Version);
        replacedMessage.Headers.CopyHeadersFrom(reply.Headers);
        replacedMessage.Properties.CopyProperties(reply.Properties);
        reply = replacedMessage;    
    }
}

And it seems to mostly work. However, the XMLReader is throwing an exception "Data at the root level is invalid. Line 1, position 1." when I attempt to create the message. I don't know where to start on that one.

Edit 2:

Ok, Now I've got a method that extracts the message into an xdocument, then sends that to a string, then edits it, then pulls that back into an xdocument, and I get a "Connection Forcibly Closed" when I send back a message containing that edited message. Terrible.

Edit 3:

After testing, simply extracting the message from the reply to an xdoc and then loading it into a new message is enough to cause the "Connection Forcibly closed" issue. This must not be the right way to edit messages. I'm looking for examples or experience on how to best approach this.

like image 848
tom.dietrich Avatar asked Sep 02 '11 16:09

tom.dietrich


2 Answers

I got an answer from the MSDN Forums. my problem was that I was changing the length of the string, but not resetting the length of the MemoryStream, which meant bytes at the end were not being reported.

Here is a working replacement function.

 public Message ChangeString(Message oldMessage, string from, string to)
        {
            MemoryStream ms = new MemoryStream();
            XmlWriter xw = XmlWriter.Create(ms);
            oldMessage.WriteMessage(xw);
            xw.Flush();
            string body = Encoding.UTF8.GetString(ms.ToArray());
            xw.Close();

            body = body.Replace(from, to);

            ms = new MemoryStream(Encoding.UTF8.GetBytes(body));
            XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(ms, new XmlDictionaryReaderQuotas());
            Message newMessage = Message.CreateMessage(xdr, int.MaxValue, oldMessage.Version);
            newMessage.Properties.CopyProperties(oldMessage.Properties);
            return newMessage; 
       }
like image 99
tom.dietrich Avatar answered Sep 20 '22 10:09

tom.dietrich


If you're using .NET 4.0 you should just need to configure the <useRequestHeadersForMetadataAddress>, or UseRequestHeadersForMetadataAddressElement in code, for your service.

like image 1
Drew Marsh Avatar answered Sep 21 '22 10:09

Drew Marsh