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 Widget
s 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)?
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.
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.
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.
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...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With