Share via


REST in WCF: Varying response content type based on HTTP Request Headers

Damian Mehers made a comment on my blog post from April, but I felt it was worth a full reblog.

Damian's used the same WCF extensibility points I used to produce some boilerplate that varies the response content type from JSON to XML, based on the Accept or Content-Type header of the GET request.  He extends WebHttpBehavior to return an IDispatchMessageFormatter that does either JSON or XML.

    class WebHttpBehavior2Ex : WebHttpBehavior

    {

        protected override IDispatchMessageFormatter GetReplyDispatchFormatter(OperationDescription operationDescription,

                                                                               ServiceEndpoint endpoint)

        {

            WebGetAttribute webGetAttribute = operationDescription.Behaviors.Find<WebGetAttribute>();

            DynamicResponseTypeAttribute mapAcceptedContentTypeToResponseEncodingAttribute =

                operationDescription.Behaviors.Find<DynamicResponseTypeAttribute>();

 

            if (webGetAttribute != null && mapAcceptedContentTypeToResponseEncodingAttribute != null) {

                // We need two formatters, since we don't know what type we will need until runtime

                webGetAttribute.ResponseFormat = WebMessageFormat.Json;

                IDispatchMessageFormatter jsonDispatchMessageFormatter =

                    base.GetReplyDispatchFormatter(operationDescription, endpoint);

                webGetAttribute.ResponseFormat = WebMessageFormat.Xml;

                IDispatchMessageFormatter xmlDispatchMessageFormatter =

                    base.GetReplyDispatchFormatter(operationDescription, endpoint);

                return new DynamicFormatter() {

                    jsonDispatchMessageFormatter = jsonDispatchMessageFormatter,

                    xmlDispatchMessageFormatter = xmlDispatchMessageFormatter };

            }

            return base.GetReplyDispatchFormatter(operationDescription, endpoint);

        }

    }

And then in the DynamicFormatter code, he just picks the formatter as appropriate:

    class DynamicFormatter : IDispatchMessageFormatter

    {

        public IDispatchMessageFormatter jsonDispatchMessageFormatter { get; set; }

        public IDispatchMessageFormatter xmlDispatchMessageFormatter { get; set; }

 

        public void DeserializeRequest(System.ServiceModel.Channels.Message message, object[] parameters)

        {

            throw new NotImplementedException();

        }

 

        public System.ServiceModel.Channels.Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)

        {

            Message request = OperationContext.Current.RequestContext.RequestMessage;

 

            // This code is based on ContentTypeBasedDispatch example in WCF REST Starter Kit Samples

            // It calls either

            HttpRequestMessageProperty prop = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];

 

            string accepts = prop.Headers[HttpRequestHeader.Accept];

            if (accepts != null)

            {

                if (accepts.Contains("text/xml") || accepts.Contains("application/xml"))

                {

                    return xmlDispatchMessageFormatter.SerializeReply(messageVersion, parameters, result);

                }

                else if (accepts.Contains("application/json"))

                {

                    return jsonDispatchMessageFormatter.SerializeReply(messageVersion, parameters, result);

                }

            }

            else

            {

                string contentType = prop.Headers[HttpRequestHeader.ContentType];

                if (contentType != null)

                {

                    if (contentType.Contains("text/xml") || contentType.Contains("application/xml"))

                    {

                        return xmlDispatchMessageFormatter.SerializeReply(messageVersion, parameters, result);

                    }

                    else if (contentType.Contains("application/json"))

                    {

                        return jsonDispatchMessageFormatter.SerializeReply(messageVersion, parameters, result);

                    }

                }

            }

            return xmlDispatchMessageFormatter.SerializeReply(messageVersion, parameters, result);

        }

 

    }

It's pretty nifty and it requires no changes to the app logic.  You need to use a custom ServiceHost and use a custom attribute on each Operation.  Because it uses the HTTP headers and not the URI itself to determine content-type of the response, I think it has some nice benefits over the approach I described in April.

Damian's got a full VS2008 solution will all the boilerplate code.

check it out.

Comments

  • Anonymous
    November 16, 2008
    Hi Dino - Thanks for sharing this.  I really like Damian's approach as well. One thing I noticed while starting to use it in conjunction with the WCF REST starter kit is that there also needs to be support for dynamic request serialization. So, I’ve extended your code a bit and merged it into the starter kit code to enable this. You can download source here: http://daptivate.com/archive/2008/11/16/wcf-and-rest-an-approach-to-using-the-content-type-and-accept-http-headers-for-object-serialization.aspx Any chance this capability can make it into the next version of the starter kit? :)

  • Anonymous
    November 18, 2008
    Kyle!  I like the way you are thinking.   I like your suggestion - will get back to you on it.  

  • Anonymous
    June 19, 2009
    The WCF REST Contrib library allows you to do this as well: http://wcfrestcontrib.codeplex.com/

  • Anonymous
    December 02, 2009
    Great idea but it somehow interferes with the WebProtocolException processing.  When I plug this behavior in and the operation throws a WebProtocolException, instead of my error object being returned, I just get a generic "Request Error". The SerializeReply method is not called on error so I can't explain how it could interfere with error processing. Trying to change big brother's ways is never this easy ;).

  • Anonymous
    March 02, 2010
    @The Farmer: That's pretty easy: Just let WebHttpBehavior2Ex inherit from WebHttpBehavior2 instead of WebHttpBehavior.

  • Anonymous
    March 10, 2012
    Probably good info, but the code formatting is a disaster on the web.. I will find another page ....

  • Anonymous
    February 20, 2013
    Cry me a river Drew. The information is quite useful. Thx OP!