开发者

Replacing content of WCF Message

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/en开发者_高级运维velope/");
        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.


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; 
       }


For those who prefer to work with Linq to XML.

 private void ChangeMessage(ref Message reply)
        {

            Message newMessage = null;               
            MessageBuffer msgbuf = reply.CreateBufferedCopy(int.MaxValue);
            Message tmpMessage = msgbuf.CreateMessage();

            XmlDictionaryReader xdr = tmpMessage.GetReaderAtBodyContents();

            XElement bodyEl = XElement.Load(xdr.ReadSubtree());

            var someElement= bodyEl.Descendants().FirstOrDefault(b => b.Name.LocalName == "somename");

            XElement newElement = getNewSomeElement();//private method

            someElement.ReplaceWith(newElement);//replacing node into body

            MemoryStream ms = new MemoryStream();
            XmlWriter xw = XmlWriter.Create(ms);
            bodyEl.Save(xw);
            xw.Flush();
            xw.Close();

            ms.Position = 0;
            XmlReader xr = XmlReader.Create(ms);

            //create new message from modified XML document
            newMessage = Message.CreateMessage(reply.Version, null, xr);
            newMessage.Headers.CopyHeadersFrom(reply);
            newMessage.Properties.CopyProperties(reply.Properties);

            reply = newMessage;

        }


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜