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.