Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cleanest way to model bind Accept header in .NET MVC

I'm implementing a REST layer in .NET MVC 3. I'm looking for a clean way to grab the Accept header to determine if I should return Json or Xml.

I would also like to be able to spoof this header with a GET parameter for debugging (I want this to persist to prod too).

Here's how I'm currently detecting this:

if (Request.AcceptTypes.Contains("application/json") || Request.Url.Query.Contains("application/json"))

This is the only place in my controller code that touches the Request object directly. I would like a cleaner, more testable way to read this. My ideal solution would be a parameter on the controller.

I tried several keywords to see if the default model binder would pick up on it, but nothing I tried worked.

So what's the cleanest way to get this information? A custom model binder? Can you provide an example?

like image 780
Travis Watson Avatar asked Jul 30 '12 23:07

Travis Watson


2 Answers

An action filter attribute would be a good, clean solution.

There's a good tutorial here : http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/understanding-action-filters-cs

like image 178
Andrew Cooper Avatar answered Oct 18 '22 00:10

Andrew Cooper


I'm not seeing any better alternatives to a custom model binder. I'll post my implementation of the binder here in case anyone else sees this. Using a model binder allows the Accept header to be strongly bound to a direct input on the action, allowing for direct testing of the return types and doesn't force you to artificially have more actions than you need, nor result to the dynamic typed viewdata/bag.

Here's the Model Binder with a supporting enum type:

public enum RequestAcceptType
{
    NotSpecified,
    Json,
    Xml
}

public class RequestAcceptTypeModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException("bindingContext");
        }

        RequestAcceptType acceptType = RequestAcceptType.NotSpecified;

        // Try for Json
        if (controllerContext.HttpContext.Request.AcceptTypes.Contains("application/json") || controllerContext.HttpContext.Request.Url.Query.Contains("application/json"))
        {
            acceptType = RequestAcceptType.Json;
        }

        // Default to Xml
        if (acceptType == RequestAcceptType.NotSpecified)
        {
            acceptType = RequestAcceptType.Xml;
        }

        return acceptType;
    }
}

Here's the relevant bit in Global.asax in the Application_Start method:

ModelBinders.Binders[typeof(RequestAcceptType)] = new RequestAcceptTypeModelBinder();

Then to use it in your actions, just make an argument (any name) with the enum type:

public ActionResult Index(RequestAcceptType acceptType)

If nobody responds with a better method in a couple days, I'll accept this as the answer.

like image 21
Travis Watson Avatar answered Oct 18 '22 00:10

Travis Watson