Search code examples
c#jsonweb-serviceswcf

How to convert x-www-form-urlencoded post Message to JSON post Message?


I have a requirement to support data being posted to our WCF service with a content type of x-www-form-urlencoded. Since WCF doesn't like to do that natively, the thought I had was to use a MessageInspector to intercept incoming messages with that content type, read out the body, convert it to a JSON string, and replace the request message.

Problem is that I can't seem to make a new Message object that my service actually likes. I can get the body and turn it into a JSON string just fine, but the new message I create causes errors instead of going on through to the appropriate service method.

This is what I've got currently. I've been tinkering with this for a day and a bit now, trying a few different ways, but have had little luck. I'll post below the error I'm currently getting.

Web service method I want to call:

[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "/PostTest", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
public string PostTest(TestObject thinger)
{
    return thinger.Thing;
}

The message inspector:

public class FormPostConverter : IDispatchMessageInspector
{
    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        var contentType = (request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty)?.Headers["Content-Type"];

        if (!request.IsEmpty && contentType == "application/x-www-form-urlencoded")
        {
            var body = HttpUtility.ParseQueryString(new StreamReader(request.GetBody<Stream>()).ReadToEnd());
            var json = new JavaScriptSerializer().Serialize(body.AllKeys.ToDictionary(k => k, k => body[k]));

            Message newMessage = Message.CreateMessage(MessageVersion.None, "", json, new DataContractJsonSerializer(typeof(string)));
            newMessage.Headers.CopyHeadersFrom(request);
            newMessage.Properties.CopyProperties(request.Properties);
            (newMessage.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty)?.Headers.Set(HttpRequestHeader.ContentType, "application/json");
            newMessage.Properties[WebBodyFormatMessageProperty.Name] = new WebBodyFormatMessageProperty(WebContentFormat.Json);
            request = newMessage;
        }

        return null;
    }

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

The error I'm getting:

Request Error The server encountered an error processing the request. The exception message is 'Expecting state 'Element'.. Encountered 'Text' with name '', namespace ''. '.


Solution

  • So, it seems I wasn't really wording very well what I was actually trying to accomplish, but after fighting with it some more I did finally figure it out.

    My resulting MessageInspector looks like this:

    public class FormPostConverter : IDispatchMessageInspector
    {
        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            var contentType = (request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty)?.Headers["Content-Type"];
    
            if (!request.IsEmpty && contentType == "application/x-www-form-urlencoded")
            {
                var body = HttpUtility.ParseQueryString(new StreamReader(request.GetBody<Stream>()).ReadToEnd());
                if (body != null && body.HasKeys())
                {
                    Message newMessage = Message.CreateMessage(MessageVersion.None, "", new XElement("root", body.AllKeys.Select(o => new XElement(o, body[o]))));
                    newMessage.Headers.CopyHeadersFrom(request);
                    newMessage.Properties.CopyProperties(request.Properties);
                    (newMessage.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty)?.Headers.Set(HttpRequestHeader.ContentType, "application/json");
                    newMessage.Properties[WebBodyFormatMessageProperty.Name] = new WebBodyFormatMessageProperty(WebContentFormat.Json);
                    request = newMessage;
                }
            }
    
            return true;
        }
    
        public void BeforeSendReply(ref Message reply, object correlationState)
        { }
    }
    

    Turns out all I really needed to be doing different was turning my body into XML elements instead of trying to do it as JSON. After inspecting a functioning POST that came in as JSON to begin with, I saw that at this stage it had already been turned into XML in the Message object.

    With this, I'm able to write my service methods normally (no Stream parameters and manually parsing out) and accept posts to them in application/json or in x-www-form-urlencoded content type.