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 (7)
  1. Kyle Beyer says:

    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? 🙂

  2. cheeso says:

    Kyle!  I like the way you are thinking.   I like your suggestion – will get back to you on it.  

  3. Mike O'Brien says:

    The WCF REST Contrib library allows you to do this as well:

    http://wcfrestcontrib.codeplex.com/

  4. The Farmer says:

    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 ;).

  5. Max says:

    @The Farmer:

    That’s pretty easy: Just let WebHttpBehavior2Ex inherit from WebHttpBehavior2 instead of WebHttpBehavior.

  6. Drew says:

    Probably good info, but the code formatting is a disaster on the web.. I will find another page ….

  7. Joe says:

    Cry me a river Drew. The information is quite useful. Thx OP!

Comments are closed.

Skip to main content