Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Web API action selection based on Accept header

Queue long introduction...

I have a resource defined at

http://my-awesome-product.com/api/widgets/3

which represents a widget with an id of 3. In Web API I would define a controller to serve that resource as follows:

public class WidgetsController : ApiController
{
    public Widget Get(int id)
    {
        return new Widget(...);
    }
}

Now, the Widget class could potentially be quite big, and wanting to save bandwidth from the database to the web server, and from the web server to the client, I create several DTO classes which contain a restricted number of fields of the overall Widget. For example:

public class WidgetSummary
{
    public int Id { get; set; }
    public string Code { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
}

public class FullWidgetForEditing
{
    public int Id { get; set; }
    public string Code { get; set; }
    public string Description { get; set; }
    public decimal Weight { get; set; }
    public decimal Price { get; set; }
    public Color Color { get; set; }
    public decimal Width { get; set; }
    public decimal Height { get; set; }
    public decimal Depth { get; set; }
}

public class WidgetForDropDownList
{
    public int Id { get; set; }
    public string Code { get; set; }
}

With these Widget representations, the client would request the WidgetSummary for display on a summary grid of all the Widgets in the system, it would request the FullWidgetForEditing on the edit page of a specific Widget, and it would request the WidgetForDropDownList for use in a drop down list on the order form.

From what I understand about REST, to access a Widget there should be a single URL as it is a single resource, regardless of its representation and the client should specify Accept headers with a media type parameter to retrieve the Widget in its different forms, e.g. my-awesome-product.type=widgetsummary, my-awesome-product.type=fullwidgetforediting, and my-awesome-product.type=widgetfordropdownlist.

I could achieve that in Web API by inspecting the headers of the request like so:

public class WidgetsController : ApiController
{
    public object Get(int id)
    {
        if (Request.Headers.Accept.Any(x => x.Parameters.Any(y => y.Name == "my-awesome-product.type" && y.Value == "widgetsummary")))
        {
            return new WidgetSummary(...);
        }
        else if (Request.Headers.Accept.Any(x => x.Parameters.Any(y => y.Name == "my-awesome-product.type" && y.Value == "fullwidgetforediting")))
        {
            return new FullWidgetForEditing(...);
        }
        else if (Request.Headers.Accept.Any(x => x.Parameters.Any(y => y.Name == "my-awesome-product.type" && y.Value == "widgetfordropdownlist")))
        {
            return new WidgetForDropDownList(...);
        }

        throw new HttpResponseException(HttpStatusCode.NotAcceptable);
    }
}

However, this gets messy quickly as the number of types grow and makes unit testing more difficult. What I would really like to do is the following:

public class WidgetsController : ApiController
{
    [HttpGet(MediaType = "my-awesome-product.type", MediaTypeValue = "widgetsummary")]
    public WidgetSummary GetWidgetSummary(int id)
    {
        return new WidgetSummary();
    }

    [HttpGet(MediaType = "my-awesome-product.type", MediaTypeValue = "fullwidgetforediting")]
    public FullWidgetForEditing GetFullWidgetForEditing(int id)
    {
        return new FullWidgetForEditing();
    }

    [HttpGet(MediaType = "my-awesome-product.type", MediaTypeValue = "widgetfordropdownlist")]
    public WidgetForDropDownList GetWidgetForDropDownList(int id)
    {
        return new WidgetForDropDownList();
    }
}

And have Web API route to the specific action method based on the media type. I investigated overriding the ApiControllerActionSelector, however, I started pulling in large quantities of the existing code for this attribute because I really want the default action selection, but in the case of ambiguous actions, I want to filter the list of actions based on the media type. I really want it to be non-intrusive so that I can mix convention routing, standard attribute routing (which is in Web API v2) and this hypothetical attribute routing in the same controller if need be.

Question time: is it currently possible to implement such a routing strategy with Web API as it stands? Would I have to completely reimplement the ApiControllerActionSelector to achieve this (and then make sure my reimplementation stays up to date)?

like image 846
flipchart Avatar asked Aug 26 '13 07:08

flipchart


People also ask

What is Web API Accept header?

The Accept request HTTP header indicates which content types, expressed as MIME types, the client is able to understand. The server uses content negotiation to select one of the proposals and informs the client of the choice with the Content-Type response header.

What is the difference between IActionResult and IHttpActionResult?

IHttpActionResult is for ASP.NET Web Api, while IActionResult is for ASP.NET Core. There's no such thing as "Web Api" in ASP.NET Core. It's all just "Core". However, some people still refer to creating an ASP.NET Core API as a "Web Api", which adds to the confusion.

What is HttpResponseMessage in Web API?

A HttpResponseMessage allows us to work with the HTTP protocol (for example, with the headers property) and unifies our return type. In simple words an HttpResponseMessage is a way of returning a message/data from your action. The following is a quick glimpse of that: // GetEmployee action.


1 Answers

So here is the question you need to ponder. Are those really three representations, or are the three distinct resources? If you decided to cache those representations, would the URI be sufficient to identify the cached representation, or would you also need to vary on the accept header?

If you accept that they are different enough to be resources then they should be identified by different URLs. If you decide to use hyperlinks in your responses, would you not want the ability to point to a 'widgetsummary' independently from a 'widgeteditform'?

Usually multiple representations are needed when you want to support different client devices that are only capable of supporting a certain media types. e.g. Device A gets Rep1 and device B gets Rep2. It's rare that you want Device A to access both Device A and Rep1 and Rep2.

Just my thoughts...

like image 98
Darrel Miller Avatar answered Sep 22 '22 13:09

Darrel Miller