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.