API controllers

In the post about controllers we looked at how the http requests map to action methods in controllers to generate responses in HTML or any other ViewResult compliant format. An API controller on the other hand simply let you get access to application data. The general structure resembles that of regular controllers, yet they don’t generate HTML but data objects (e.g. as JSON format) as a response.
API controllers are useful because you can acquire data without the formatting and interpretation of HTML. This in turn gives you more flexibility in terms of clients that can use your application.

To get a more general view into Controllers in MVC see my Intro to controllers

What this post will cover:

  • What problems does it solve on a high level
  • What a Web API is
  • Intro to work with content negotiation and formatting
  • Basic Routing with api controllers via Route Attribute

What this post will not cover:

  • REST in detail
  • Testing of api controllers
  • Client side solution to speed and efficiency issues
  • Design of web API’s

What problems do API controllers solve

With API controllers you can tackle three common problems of web applications. The first one is the speed deficiency with applications that returns HTML as a response and with each further action returns another HTML response. The speed is impeded from the synchronous style of waiting on a response and the non zero network latencies. (think Post/redirect/get pattern)

This also creates the second issue: an efficiency problem. This is because all the parsed HTML has to be parsed again from the client (most likely a browser) because a newly generated HTML that slightly differs from the parsed before invalidates the cached values.

The third problem that arises is that Data is only accessible by the provided methods of the controller, not like in REST with HATEOAS (link) and navigable Data, this can be subsumed as the openness issue.
Problems arise when data should be used in another application. Instead of the user interaction that is possible with the given HTML, the data itself might be of equal or greater value to the user base and customers.

While one works with api controllers, the most common problems will be with Content Formatting (the way data objects are serialized). We will look at how this can be done, later in this post.

REST with API Controllers

As mentioned above the goal of an API controller is to create an MVC controller that provides data access without the encapsulation in HTML. The industry agrees somewhat that this should be realized with RESTful or REST style applications and API’s.

I will not go into detail in this post, what REST is and why it is so useful. But for starters, REST is an architectural pattern that is build around the architecture of the web and to embrace the characteristics of http and the web in general. Every information is viewed as a representation of a resource at a server. With a request this representation is then transferred to the client. (hence the name Representational state transfer). To get hold of and modify those data one uses the http verbs like GET, PUT, POST, DELETE. The URL specifies which representation the verb is performed on.

e.g. URL: /api/mymodels/1

The api part of the URL is used to separate data from HTML/user interaction.
The mymodels part indicates a collection of mymodel data objects. and the /1 indicates that I want to access the first item in this collection.

Verb URL Description Payloads
GET /api/reservations Retrieves all objects

Create an API controller

To create an API controller you need to take the same steps as with a standard controller. Yet to fully configure it you will need to do some extra work.
So you can either create a POCO controller or derive from the Controller base class. (I prefer the latter, because it gets me easier access to request context data and such, see my post on creating a MVC Controller).

A very important distinction for API Controllers is, that they can only be accessed by the use of the Route Attribute and not the standard routing configuration that is set up in the Startup class of your MVC application.

As you might remember from the post on Routing, you can apply the Route Attribute on the controller class and specify the basic route for the whole controller.

[Route("api/[controller]")]
public class MyModelController : Controller
{
//...omitted
}

So with the Route Attribute defined as in the example above you get the benefit that all actions of the controller will already have the /api/MyApi prefixed to their action. Think of a GET request with id, that uses the following URL: /api/mymodels/1, the needed action with RouteAttribute would look like so

[HttpGet("{id}")]
public MyModel Get(int id)
{
//...omitted
}

The HttpGet Attribute restricts the access for requests with given verbs. You can also Specify the use of multiple verbs with the AcceptVerbs attribute.

(todo table of all verbs and their corresponding Attributes)

Another difference to standard ViewResult controllers is, that the name of action method is not part of the URL. So if you create and design an API Controllers it is most effective to use all the same base URL as with the Route Attribute defined on a class level, and only differentiated with the Http_XXX attribute and optional segments (like the id in the HttpGet route above).

Return values of API controllers

API controller actions do not rely on ViewResult objects for their return values. Instead they return Data objects like:

[HttpGet]
public IEnumerable<MyModel> Get()
=> repository.MyModels;

Using a controller this way with the route /api/mymodels will let MVC take care of the serialization of the data objects. We will look further into this when we look at the topic of Content Formatting later in this post. But for now we use the default serialization mechanism, that uses JSON.Net to serialize C# objects to JSON format ( JSON format is the de facto standard for modern web applications). Even though this is the default behavior, it can be modified like pretty much anything in asp net core mvc.

With the default mechanism it is pretty convenient to simply return C# objects from controller methods. You can also return an IActionResult from those action methods. For this the Controller offers special methods like the NotFound() or OK(mymodel).

The NotFound returns a NotFoundResult object that produces a 404- Not Found response, where as the OK returns an ObjectResult that results in standard 200 – with the payload of the expected data objects as the formatted content.
It is not often necessary to override the default behavior, but if the need arises, it is entirely possible. (again for further information on this look at my post on MVC Controllers.

The API controller addresses the openness issue of a web application in the way that you can now access mymodel not only by HTML but also by JSON data. Yet the speed and efficiency issues still remain. You still need to wait synchronously for the api controller to return the requested data to the client.

This can be handled by asynchronous invocation from the client side. This is referred to as AJAX (asynchronous javascript and XML, where today it is mostly JSON). This technique is used for SPA (single page applications) where no reload of the html is necessary. Only the javascript makes calls to API controllers (in case of a AspNetCore server), that return data which is then rendered by the use of javascript. But as you might have guessed this is a huge topic of its own and I will probably look into this with another set of posts.

Next off we are looking at content Formatting.

Content Formatting

When you return a C# type as the value of an API controller action, MVC needs to work out how to format the data and send it to the client. We briefly saw above that the default mechanism simply returns the data objects as serialized JSON values.

Now we will look at how the default process works and how this can be fine tuned by the incoming request and the config of the application.
The content format that is selected by MVC depends on four factors:

  1. the format that the client will accept (defined by the Accept header in the request)
  2. the formats that MVC can produce
  3. the content policy specified by the action
  4. the C# Type returned by the action

The good thing is that the default policy works quite well for most applications. Nonetheless might it be helpful to you, to understand what is going on behind the scenes, just to help with formatting errors and such.

Default content policy

When neither the client nor the action method applies any restrictions to the useable formats, the default content policy is used. The result then is simple and predictable as it will be one of two categories:

  1. If the action method returns a string, this is encoded in the response as unmodified string with the Content-Type specified in the response header of text/plain.
  2. For all other data types the Content-Type is application/json and the data is formatted as JSON.

Strings are handled with text/plain because of the potential issue with double double-quotes.

Because most clients that will use your API will probably have an Accept header in their http request, which defines which MIME types it accepts, you will at least have to understand somewhat how to work it out with content negotiation in ASP .Net MVC core.

But for MVC only including the Accept header and specifying the accepted types is not sufficient to change the server outcome. The problem is, that MVC is configured (by default at least) to support only JSON as format, as I mentioned above.

[ JSON in MVC is supplied by JSON.Net and can be configured on its own with AddMvc().AddJsonOptions. See www.newtonsoft.com/json for more details on configurable aspects of JSON.Net.]

Content negotiation is the process where the client tells via the Accept header of the http request, which types it prefers (with relative values concerning the preference “q”), and the server sends a content type that is most appropriate according to that information.

So to make content negotiation with the clientside Accept header possible you need to add to the IServiceCollection in the ConfigureServices method. If you want to support XML additionally to JSON you will need to change the configure method like so:

public void ConfigureServices(IServiceCollection services)
{
...
services.AddMvc().AddXmlDataContractSerializerFormatters();
...
}

Now MVC can react to Accept headers that require XML.

You can also derive your own Formatter for proprietary formats from Microsoft.AspNetCore.Mvc.Formatters.OutputFormatter. This is not really advisable because JSON and XML already are implemented, but it is entirely possible if the need ever arises. I deem it to be somewhat an esoteric edge case and therefore will not look at it in this post.

Specifying an action data format

Instead of relying on the content negotiation process of MVC, you can also apply the Produces Attribute to an action method. What this does is, that it enforces the serialization into the specified format and overrides the content negotiation process.

[HttpGet("{id}")]
[Produces("application/json")]
public MyModel Get(int id)
{
//...omitted
}

The resulting ObjectResult will be forced to use this MIME type. It is also entirely possible to add several mime types with comma separated values like so:

[HttpGet("{id}")]
[Produces("application/json", "application/xml", "application/mymimetype")]
public MyModel Get(int id)
{
//...omitted
}

The counterpart of the Produces attribute is the Consumes Attribute. This will specify which types are allowed in the payload of the incoming request for the given action method.

e.g.

[HttpPost]
[Consumes("application/json")]
public void PostJson([FromBody] MyModel model)
{
// save to repository and such
}

In this example, all that is allowed in with a POST request has to be formatted as JSON. Else the response will generate a 406- not accepted error message. To enable content negotiation you would add a second method that handles Xml to the same controller:

[HttpPost]
[Consumes("application/xml")]
public void PostXml([FromBody] MyModel model)
{
// save to repository and such
}

To overcome client side restrictions regarding the Accept header, you can apply the FormatFilter Attribute. This will resolve any Mime Type to be used by the value that is specified in the query string of the URL and the defined formatters in the MVC Startup process.

Enable Full content negotiation

Produces/Consumes override the content negotiation process. To enable full negotiation instead of the MVC default of JSON as return type you would want to add the following to the Startup class of MVC, particular in the ConfigureServices method:

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddMvcOptions(options =>
{
options.RespectBrowserAcceptHeader = true;
options.ReturnHttpNotAcceptable = true;
});
}

 

Summary

In this post we briefly looked at how the API controller of asp net core mvc web api can be used to address the openness issue of a web application.

For this we looked at how to set up an api controller, what style of formatted content it returns with the http response and how this can be fine tuned for the needs of your application in terms of content negotiation.

We also looked at how the routing on an api controller is controlled by the Route Attribute and its accompanying http verb specific attributes.

For further infos read:


0 Comments

Leave a Reply

Avatar placeholder