Tuesday 30 April 2013

Using Web API filter for Polymorphic results

When starting a little deeper with Web API I found a block on the road: if I returned any object different of the return type declared in the action method signature, then the serialization mechanism throw an exception related to the type instantiated is not expected, the error message is like this: "Type 'Mvc4AppFilter.Models.Student' with data contract name 'Student:http://schemas.datacontract.org/2004/07/Mvc4AppFilter.Models' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer." when trying to serialize as XML, JSON works like a charm, why is this happening?
If we find the source code for class System.Net.HttpHttpRequestMessageExtensions at method with this signature, because there are several overloads:
public static HttpResponseMessage CreateResponse<T>(this HttpRequestMessage request, HttpStatusCode statusCode, T value, HttpConfiguration configuration)
We found the things related to content negotiation and checking for proper values from configuration, but at the end there's something like this:
return new HttpResponseMessage
{
    Content = new ObjectContent<T>(value, result.Formatter, mediaType),
    StatusCode = statusCode,

    RequestMessage = request
};
and the key point is that if the typeof(T) is not equal to value.GetType() then the ObjectContent throws the exception because of the mismatching of types and that occurs when for example we declare the method with return type Person and for some reason we return either a Worker object or Student object (where both inherit from Person)

The first solution was to extract the important sentences from this source code and create my own extension for CreateResponse and using the ObjectContent non generic version, but later I decided to group this code in a Filter, in this case an Action filter, and it will be executed after each action is executed, the goal is to transform the any object return type action to a properly configured HttpResponseMessage. Here's the code.
public class HttpResponseFilter : ActionFilterAttribute
{
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        var request = actionExecutedContext.Request;
        var response = actionExecutedContext.Response;
        if (response.Content != null && !response.Content.IsHttpResponseMessageContent() && response.Content is ObjectContent) 
        {
            var objContent = response.Content as ObjectContent;
            var value = objContent.Value;
            var result = request.CreateResponse(System.Net.HttpStatusCode.OK);
            if (value != null)
            {
                var config = request.GetConfiguration();
                var negociator = config.Services.GetContentNegotiator();
                var nresult = negociator.Negotiate(value.GetType(), request, config.Formatters);
                result.Content = new ObjectContent(value.GetType(), value, nresult.Formatter);
            }
            actionExecutedContext.Response = result;
        }
    }
}

Finally, we need to register the filter in application startup, i.e. in the file out of the box WebApiConfig.cs by adding this line:
config.Filters.Add(new HttpResponseFilter());
Hope it helps you to improve your code, the source can be downloaded here.

No comments:

Post a Comment