ASP.NET Core in Action 20 Creating an HTTP API using web API controllers

20 Creating an HTTP API using web API controllers

This chapter covers

• Creating a web API controller to return JavaScript Object Notation (JSON) to clients
• Using attribute routing to customize your URLs
• Generating a response using content negotiation
• Applying common conventions with the [ApiController] attribute

In chapters 13 through 19 you worked through each layer of a server-side rendered ASP.NET Core application, using Razor Pages and Model-View-Controller (MVC) controllers to render HTML to the browser. In part 1 of this book you saw a different type of ASP.NET Core application, using minimal APIs to serve JSON for client-side SPAs or mobile apps. In this chapter you’ll learn about web API controllers, which fit somewhere in between!

You can apply much of what you’ve already learned to web API controllers; they use the same routing system as minimal APIs and the same MVC design pattern, model binding, and validation as Razor Pages.

In this chapter you’ll learn how to define web API controllers and actions, and see how similar they are to the Razor Pages and controllers you already know. You’ll learn how to create an API model to return data and HTTP status codes in response to a request, in a way that client apps can understand.

After exploring how the MVC design pattern applies to web API controllers, you’ll see how a related topic works with web APIs: routing. We’ll look at how explicit attribute routing works with action methods, touching on many of the same concepts we covered in chapters 6 and 14.

One of the big features added in ASP.NET Core 2.1 was the [ApiController] attribute. This attribute applies several common conventions used in web APIs, reducing the amount of code you must write yourself. In section 20.5 you’ll learn how automatic 400 Bad Requests for invalid requests, model-binding parameter inference, and ProblemDetails support make building APIs easier and more consistent.

You’ll also learn how to format the API models returned by your action methods using content negotiation, to ensure that you generate a response that the calling client can understand. As part of this, you’ll learn how to add support for additional format types, such as Extensible Markup Language (XML), so that you can generate XML responses if the client requests it.

Finally, I discuss some of the differences between API controllers and minimal API applications, and when you should choose one over the other. Before we get to that, we look at how to get started. In section 20.1 you’ll see how to create a web API project and add your first API controller.

20.1 Creating your first web API project

In this section you’ll learn how to create an ASP.NET Core web API project and create your first web API controllers. You’ll see how to use controller action methods to handle HTTP requests and how to use ActionResults to generate a response.

NOTE as I mentioned previously that a web API project is a standard ASP.NET Core project, which uses the MVC framework and web API controllers.

Some people think of the MVC design pattern as applying only to applications that render their UI directly, like the Razor views you’ve seen in previous chapters or MVC controllers with Razor views. However, in ASP.NET Core, I feel the MVC pattern applies equally well when building a web API. For web APIs, the view part of the MVC pattern involves generating a machine-friendly response rather than a user-friendly response.

As a parallel to this, you create web API controllers in ASP.NET Core in the same way you create traditional MVC controllers. The only thing that differentiates them from a code perspective is the type of data they return. MVC controllers typically return a ViewResult; web API controllers generally return raw .NET objects from their action methods, or an IActionResult instance such as StatusCodeResult, as you saw in chapter 15.

You can create a new web API project in Visual Studio using the same process you’ve seen previously in Visual Studio. Choose File > New, and in the Create a new project dialog box, select the ASP.NET Core Web API template. Enter your project name in the Configure your new project dialog box, and review the Additional information box, shown in figure 20.1, before choosing Create. If you’re using the command-line interface (CLI), you can create a similar template using dotnet new webapi.

alt text

Figure 20.1 The Additional information screen. This screen follows on from the Configure your new project dialog box and lets you customize the template that generates your application.

The web API template configures the ASP.NET Core project for web API controllers only in Program.cs, as shown in listing 20.1. If you compare this template with the MVC controller project in chapter 19, you’ll see that the web API project uses AddControllers() instead of AddControllersWithViews(). This adds only the services needed for controllers but omits the services for rendering Razor views. Also, the API controllers are added using MapControllers() instead of MapControllerRoute(), as web API controller typically use explicit routing instead of conventional routing. The default web API template also adds the OpenAPI services and endpoints required by the Swagger UI, as you saw in chapter 11.

Listing 20.1 Program.cs for the default web API project

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();  #A

builder.Services.AddEndpointsApiExplorer();    #B
builder.Services.AddSwaggerGen();    #B

WebApplication app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();    #C
    app.UseSwaggerUI();  #C
}

app.UseHttpsRedirection();
app.UseAuthorization();

app.MapControllers();  #D

app.Run();

❶ AddControllers adds the necessary services for web API controllers to your application.
❷ Adds services required to generate the Swagger/OpenAPI specification document
❸ Adds Swagger UI middleware for exploring your web API endpoints
❹ MapControllers configures the web API controller actions in your app as endpoints.

The program in listing 20.1 instructs your application to find all the web API controllers in your application and configure them in the EndpointMiddleware. Each action method becomes an endpoint and can receive requests when the RoutingMiddleware maps an incoming URL to the action method.

NOTE Technically, you can include Razor Pages, minimal APIs, and web API controllers in the same app, but I prefer to keep them separate where possible. There are certain aspects (such as error handling and authentication) that are made easier by keeping them separate. Of course, running two separate applications has its own difficulties!

You can add a web API controller to your project by creating a new .cs file anywhere in your project. Traditionally, this file is placed in a folder called Controllers, but that’s not a technical requirement.

Tip Vertical slice architecture and feature folders are (fortunately) becoming more popular in .NET circles. With these approaches, you organize your project based on features instead of technical concepts like controllers and models.

Listing 20.2 shows an example of a simple controller, with a single endpoint, that returns an IEnumerable when executed. This example highlights the similarity with traditional MVC controllers (using action methods and a base class) and minimal APIs (returning the response object directly to be serialized later).

Listing 20.2 A simple web API controller

[ApiController]    #A
public class FruitController : ControllerBase         #B
{
    List<string> _fruit = new List<string>    #C
    {                                         #C
        "Pear",                               #C
        "Lemon",                              #C
        "Peach"                               #C
    };                                        #C
    [HttpGet("fruit")]                           #D
    public IEnumerable<string> Index()      #E
    {                      #F
        return _fruit;     #F
    }                      #F
}

❶ The [ApiController] attribute opts in to common conventions.
❷ The ControllerBase class provides helper functions.
❸ This would typically come from a dependency injection (DI) injected service instead.
❹ The [HttpGet] attribute defines the route template used to call the action.
❺ The name of the action method, Index, isn’t used for routing. It can be anything you like.
❻ The controller exposes a single action method that returns the list of fruit.

When invoked, this endpoint returns the list of strings serialized to JSON, as shown in figure 20.2.

alt text

Figure 20.2 Testing the web API in listing 20.2 by accessing the URL in the browser. A GET request is made to the /fruit URL, which returns a List<string> that is serialized to JSON.

Web API controllers typically use the [ApiController] attribute (introduced in .NET Core 2.1) and derive from the ControllerBase class. The base class provides several helper methods for generating results, and the [ApiController] attribute automatically applies some common conventions, as you’ll see in section 20.5.

Tip The Controller base class is typically used when you use MVC controllers with Razor views. You don’t need to return Razor views with web API controllers, so ControllerBase is the better option.

In listing 20.2 you can see that the action method, Index, returns a list of strings directly from the action method. When you return data from an action like this, you’re providing the API model for the request. The client will receive this data. It’s formatted into an appropriate response, a JSON representation of the list in the case of figure 20.2, and sent back to the browser with a 200 OK status code.

Tip Web API controllers format data as JSON by default. You’ll see how to format the returned data in other ways in section 20.6. Minimal API endpoints that return data directly (rather than via an IResult) will format data only as JSON; there are no other options.

The URL at which a web API controller action is exposed is handled in the same way as for traditional MVC controllers and Razor Pages: using routing. The [HttpGet("fruit")] attribute applied to the Index method indicates that the method should use the route template "fruit" and should respond to HTTP GET requests. You’ll learn more about attribute routing in section 20.4, but it’s similar to the minimal API routing that you’re already familiar with.

In listing 20.2 data is returned directly from the action method, but you don’t have to do that. You’re free to return an IActionResult instead, and often this is required. Depending on the desired behavior of your API, you sometimes want to return data, and other times you may want to return a raw HTTP status code, indicating whether the request was successful. For example, if an API call is made requesting details of a product that does not exist, you might want to return a 404 Not Found status code.

NOTE This is similar to the patterns you used in minimal APIs. But remember, minimal APIs use IResult, web API controllers, MVC controllers, and Razor Pages use IActionResult.

Listing 20.3 shows an example of where you must return an IActionResult. It shows another action on the same FruitController as before. This method exposes a way for clients to fetch a specific fruit by an id, which we’ll assume for this example is an index into the list of _fruit you defined in the previous listing. Model binding is used to set the value of the id parameter from the request.

NOTE API controllers use the same model binding infrastructure as Razor Pages to bind action method parameters to the incoming request. Model binding and validation work the same way you saw in chapter 16: you can bind the request to simple primitives, as well as to complex C# objects. The only difference is that there isn’t a PageModel with [BindProperty] properties; you can bind only to action method parameters.

Listing 20.3 A web API action returning IActionResult to handle error conditions

[HttpGet("fruit/{id}")]                #A
public ActionResult<string> View(int id)    #B
{
    if (id >= 0 && id < _fruit.Count)   #C
    {
        return _fruit[id];    #D
    }
    return NotFound();    #E
}

❶ Defines the route template for the action method
❷ The action method returns an ActionResult, so it can return a string or an IActionResult.
❸ An element can be returned only if the id value is a valid _fruit element index.
❹ Returning the data directly returns the data with a 200 status code.
❺ NotFound returns a NotFoundResult, which sends a 404 status code.

In the successful path for the action method, the id parameter has a value greater than 0 and less than the number of elements in _fruit. When that’s true, the value of the element is returned to the caller. As in listing 20.2, this is achieved by simply returning the data directly, which generates a 200 status code and returns the element in the response body, as shown in figure 20.3. You could also have returned the data using an OkResult, by returning Ok(_fruit[id]), using the Ok helper method on the ControllerBase class; under the hood, the result is identical.

NOTE Some people get uneasy when they see the phrase helper method, but there’s nothing magic about the ControllerBase helpers; they’re shorthand for creating a new IActionResult of a given type. You don’t have to take my word for it, though. You can always view the source code for the base class on GitHub at http://mng.bz/5wQB.

alt text

Figure 20.3 Data returned from an action method is serialized into the response body, and it generates a response with status code 200 OK.

If the id is outside the bounds of the _fruit list, the method calls NotFound() to create a NotFoundResult. When executed, this method generates a 404 Not Found status code response. The [ApiController] attribute automatically converts the response into a standard ProblemDetails instance, as shown in figure 20.4.

alt text

Figure 20.4 The [ApiController] attribute converts error responses (in this case a 404 response) into the standard ProblemDetails format.

One aspect you might find confusing from listing 20.3 is that for the successful case, we return a string, but the method signature of View says we return an ActionResult<string>. How is that possible? Why isn’t there a compiler error?

The generic ActionResult<T> uses some fancy C# gymnastics with implicit conversions to make this possible. Using ActionResult<T> has two benefits:

• You can return either an instance of T or an ActionResult implementation like NotFoundResult from the same method. This can be convenient, as in listing 20.3.

• It enables better integration with ASP.NET Core’s OpenAPI support.

You’re free to return any type of ActionResult from your web API controllers, but you’ll commonly return StatusCodeResult instances, which set the response to a specific status code, with or without associated data. NotFoundResult and OkResult both derive from StatusCodeResult, for example. Another commonly used status code is 400 Bad Request, which is normally returned when the data provided in the request fails validation. You can generate this using a BadRequestResult, but in many cases the [ApiController] attribute can automatically generate 400 responses for you, as you’ll see in section 20.5.

Tip You learned about various ActionResults in chapter 15. BadRequestResult, OkResult, and NotFoundResult all inherit from StatusCodeResult and set the appropriate status code for their type (400, 200, and 404, respectively). Using these wrapper classes makes the intention of your code clearer than relying on other developers to understand the significance of the various status code numbers.

Once you’ve returned an ActionResult (or other object) from your controller, it’s serialized to an appropriate response. This works in several ways, depending on

• The formatters that your app supports
• The data you return from your method
• The data formats the requesting client can handle

You’ll learn more about formatters and serializing data in section 20.6, but before we go any further, it’s worth zooming out a little and exploring the parallels between traditional server-side rendered applications and web API endpoints. The two are similar, so it’s important to establish the patterns that they share and where they differ.

20.2 Applying the MVC design pattern to a web API

In ASP.NET Core, the same underlying framework is used in conjunction with web API controllers, Razor Pages, and MVC controllers with views. You’ve already seen this yourself; the web API FruitController you created in section 20.2 looks similar to the MVC controllers you saw in chapter 19.

Consequently, even if you’re building an application that consists entirely of web APIs, using no server-side rendering of HTML, the MVC design pattern still applies. Whether you’re building traditional web applications or web APIs, you can structure your application virtually identically.

By now I hope you’re nicely familiar with how ASP.NET Core handles a request. But in case you’re not, figure 20.5 shows how the framework handles a typical Razor Pages request after it passes through the middleware pipeline. This example shows how a request to view the available fruit on a traditional grocery store website might look.

alt text

Figure 20.5 Handling a request to a traditional Razor Pages application, in which the view generates an HTML response that’s sent back to the user. This diagram should be familiar by now!

The RoutingMiddleware routes the request to view all the fruit listed in the apples category to the Fruit.cshtml Razor Page. The EndpointMiddleware then constructs a binding model, validates it, sets it as a property on the Razor Page’s PageModel, and sets the ModelState property on the PageModel base class with details of any validation errors. The page handler interacts with the application model by calling into services, talking to a database, and fetching any necessary data.

Finally, the Razor Page executes its Razor view using the PageModel to generate the HTML response. The response returns through the middleware pipeline and out to the user’s browser.

How would this change if the request came from a client-side or mobile application? If you want to serve machine-readable JSON instead of HTML, what is different for web API controllers? As shown in figure 20.6, the answer is “very little.” The main changes are related to switching from Razor Pages to controllers and actions, but as you saw in chapter 19, both approaches use the same general paradigms.

alt text

Figure 20.6 A call to a web API endpoint in an e-commerce ASP.NET Core web application. The ghosted portion of the diagram is identical to figure 20.5.

As before, the routing middleware selects an endpoint to invoke based on the incoming URL. For API controllers this is a controller and action instead of a Razor Page.

After routing comes model-binding, in which the binder creates a binding model and populates it with values from the request. web API controllers often accept data in more formats than Razor Pages, such as XML, but otherwise the model-binding process is the same as for the Razor Pages request. Validation also occurs in the same way, and the ModelState property on the ControllerBase base class is populated with any validation errors.

NOTE Web APIs use input formatters to accept data sent to them in a variety of formats. Commonly these formats are JSON or XML, but you can create input formatters for any sort of type, such as CSV. I show how to enable the XML input formatter in section 20.6. You can see how to create a custom input formatter at http://mng.bz/e5gG.

The action method is the equivalent of the Razor Page handler; it interacts with the application model in the same way. This is an important point; by separating the behavior of your app into an application model instead of incorporating it into your pages and controllers themselves, you’re able to reuse the business logic of your application with multiple UI paradigms.

Tip Where possible, keep your page handlers and controllers as simple as practicable. Move all your business logic decisions into the services that make up your application model, and keep your Razor Pages and API controllers focused on the mechanics of interacting with a user or client.

After the application model has returned the data necessary to service the request—the fruit objects in the apples category—you see the first significant difference between API controllers and Razor Pages. Instead of adding values to the PageModel to be used in a Razor view, the action method creates an API model. This is analogous to the PageModel, but rather than containing data used to generate an HTML view, it contains the data that will be sent back in the response.

DEFINITION View models and PageModels contain both the data required to build a response and metadata about how to build the response. API models typically contain only the data to be returned in the response.

When we looked at the Razor Pages app, we used the PageModel in conjunction with a Razor view template to build the final response. With the web API app, we use the API model in conjunction with an output formatter. An output formatter, as the name suggests, serializes the API model into a machine-readable response, such as JSON or XML. The output formatter forms the V in the web API version of MVC by choosing an appropriate representation of the data to return.

Finally, as for the Razor Pages app, the generated response is sent back through the middleware pipeline, passing through each of the configured middleware components, and back to the original caller.

I hope the parallels between Razor Pages and web APIs are clear. The majority of the behavior is identical; only the response varies. Everything from when the request arrives to the interaction with the application model is similar between the paradigms.

Most of the differences between Razor Pages and web APIs have less to do with the way the framework works under the hood and are instead related to how the different paradigms are used. For example, in the next section you’ll learn how the routing constructs you learned about in chapters 6 and 15 are used with web APIs, using attribute routing.

20.3 Attribute routing: Linking action methods to URLs

In this section you’ll learn about attribute routing: the mechanism for associating web API controller actions with a given route template. You’ll see how to associate controller actions with specific HTTP verbs like GET and POST and how to avoid duplication in your templates.

We covered route templates in depth in chapter 6 in the context of minimal APIs, and again in chapter 14 with Razor Pages, and you’ll be pleased to know that you use exactly the same route templates with API controllers. The only difference is how you specify the templates. With Razor Pages you use the @page directive, and with minimal APIs you use MapGet() or MapPost(), whereas with API controllers you use routing attributes.

NOTE All three paradigms use explicit routing under the hood. The alternative, conventional routing, is typically used with traditional MVC controllers and views, as described in chapter 19. As I’ve mentioned, I don’t recommend using that approach generally, so I don’t cover conventional routing in this book.

With attribute routing, you decorate each action method in an API controller with an attribute and provide the associated route template for the action method, as shown in the following listing.

Listing 20.4 Attribute routing example

public class HomeController: Controller
{
    [Route("")]                  #A
    public IActionResult Index()
    {
         /* method implementation*/
    }

    [Route("contact")]             #B
    public IActionResult Contact()
    {
         /* method implementation*/
    }
}

❶ The Index action will be executed when the / URL is requested.
❷ The Contact action will be executed when the /contact URL is requested.

Each [Route] attribute defines a route template that should be associated with the action method. In the example provided, the / URL maps directly to the Index method and the /contact URL maps to the Contact method.

Attribute routing maps URLs to a specific action method, but a single action method can still have multiple route templates and hence can correspond to multiple URLs. Each template must be declared with its own RouteAttribute, as shown in this listing, which shows the skeleton of a web API for a car-racing game.

Listing 20.5 Attribute routing with multiple attributes

public class CarController
{
    [Route("car/start")]         #A
    [Route("car/ignition")]      #A
    [Route("start-car")]         #A
    public IActionResult Start()    #B
    {
         /* method implementation*/
    }

    [Route("car/speed/{speed}")]             #C
    [Route("set-speed/{speed}")]             #C
    public IActionResult SetCarSpeed(int speed)  
    {
         /* method implementation*/
    }
}

❶ The Start method will be executed when any of these route templates is matched.
❷ The name of the action method has no effect on the route template.
❸ The RouteAttribute template can contain route parameters, in this case {speed}.

The listing shows two different action methods, both of which can be accessed from multiple URLs. For example, the Start method will be executed when any of the following URLs is requested:

/car/start
/car/ignition
/start-car

These URLs are completely independent of the controller and action method names; only the value in the RouteAttribute matters.

NOTE By default, the controller and action name have no bearing on the URLs or route templates when RouteAttributes are used.

The templates used in route attributes are standard route templates, the same as you used in chapter 6. You can use literal segments, and you’re free to define route parameters that will extract values from the URL, as shown by the SetCarSpeed method in listing 20.5. That method defines two route templates, both of which define a route parameter, {speed}.

Tip I’ve used multiple [Route] attributes on each action in this example, but it’s best practice to expose your action at a single URL. This will make your API easier to understand and for other applications to consume.

As in all parts of ASP.NET Core, route parameters represent a segment of the URL that can vary. As with minimal APIs, and Razor Pages, the route parameters in your RouteAttribute templates can

• Be optional
• Have default values
• Use route constraints

For example, you could update the SetCarSpeed method in the previous listing to constrain {speed} to an integer and to default to 20 like so:

[Route("car/speed/{speed=20:int}")]
[Route("set-speed/{speed=20:int}")]
public IActionResult SetCarSpeed(int speed)

NOTE As discussed in chapter 6, don’t use route constraints for validation. For example, if you call the preceding "set-speed/{speed=20:int}" route with an invalid value for speed, /set-speed/oops, you will get a 404 Not Found response, as the route does not match. Without the int constraint, you would receive the more sensible 400 Bad Request response.

If you managed to get your head around routing in chapter 6, routing with web API controllers shouldn’t hold any surprises for you. One thing you might begin noticing when you start using attribute routing with web API controllers is the amount you repeat yourself. Minimal APIs use route groups to reduce duplication, and Razor Pages removes a lot of the repetition by using conventions to calculate route templates based on the Razor Page’s filename. So what can we use with web API controllers?

20.3.1 Combining route attributes to keep your route templates DRY

Adding route attributes to all of your web API controllers can get a bit tedious, especially if you’re mostly following conventions where your routes have a standard prefix, such as "api" or the controller name. Generally, you’ll want to ensure that you don’t repeat yourself (DRY) when it comes to these strings. The following listing shows two action methods with several [Route] attributes. (This is for demonstration purposes only. Stick to one per action if you can!)

Listing 20.6 Duplication in RouteAttribute templates

public class CarController
{
    [Route("api/car/start")]             #A
    [Route("api/car/ignition")]          #A
    [Route("start-car")]
    public IActionResult Start()
    {
         /* method implementation*/
    }

    [Route("api/car/speed/{speed}")]     #A
    [Route("set-speed/{speed}")]
    public IActionResult SetCarSpeed(int speed)
    {
         /* method implementation*/
    }
}

❶ Multiple route templates use the same “api/car” prefix.

There’s quite a lot of duplication here; you’re adding "api/car" to most of your routes. Presumably, if you decided to change this to "api/vehicles", you’d have to go through each attribute and update it. Code like that is asking for a typo to creep in!

To alleviate this pain, it’s possible to apply RouteAttributes to controllers, in addition to action methods. When a controller and an action method both have a route attribute, the overall route template for the method is calculated by combining the two templates.

Listing 20.7 Combining RouteAttribute templates

[Route("api/car")]
public class CarController
{
    [Route("start")]          #A
    [Route("ignition")]       #B
    [Route("/start-car")]          #C
    public IActionResult Start()
    {
         /* method implementation*/
    }

    [Route("speed/{speed}")]          #D
    [Route("/set-speed/{speed}")]                  #E
    public IActionResult SetCarSpeed(int speed)
    {
         /* method implementation*/
    }
}

❶ Combines to give “api/car/start”
❷ Combines to give “api/car/ignition”
❸ Does not combine because it starts with /; gives the “start-car” template
❹ Combines to give “api/car/speed/{speed}”
❺ Does not combine because it starts with /; gives the “set-speed/{speed}” template

Combining attributes in this way can reduce some of the duplication in your route templates and makes it easier to add or change the prefixes (such as switching "car" to "vehicle") for multiple action methods. To ignore the RouteAttribute on the controller and create an absolute route template, start your action method route template with a slash (/). Using a controller RouteAttribute reduces a lot of the duplication, but you can go one better by using token replacement.

20.3.2 Using token replacement to reduce duplication in attribute routing

The ability to combine attribute routes is handy, but you’re still left with some duplication if you’re prefixing your routes with the name of the controller, or if your route templates always use the action name. If you wish, you can simplify even further!

Attribute routes support the automatic replacement of [action] and [controller] tokens in your attribute routes. These will be replaced with the name of the action and the controller (without the “Controller” suffix), respectively. The tokens are replaced after all attributes have been combined, which can be useful when you have controller inheritance hierarchies. This listing shows how you can create a BaseController class that applies a consistent route template prefix to all the web API controllers in your application.

Listing 20.8 Token replacement in RouteAttributes

[Route("api/[controller]")]                      #A
public abstract class BaseController { }       #B

public class CarController : BaseController
{
    [Route("[action]")]          #C
    [Route("ignition")]             #D
    [Route("/start-car")]          #E
    public IActionResult Start()
    {
         /* method implementation*/
    }
}

❶ You can apply attributes to a base class, and derived classes will inherit them.
❷ Token replacement happens last, so [controller] is replaced with “car” not “base”.
❸ Combines and replaces tokens to give the “api/car/start” template
❹ Combines and replaces tokens to give the “api/car/ignition” template
❺ Does not combine with base attributes because it starts with /, so it remains as “start-car”

Warning If you use token replacement for [controller] or [action], remember that renaming classes and methods will change your public API. If that worries you, you can stick to using static strings like "car" instead.

When combined with everything you learned in chapter 6, we’ve covered pretty much everything there is to know about attribute routing. There’s just one more thing to consider: handling different HTTP request types like GET and POST.

20.3.3 Handling HTTP verbs with attribute routing

In Razor Pages, the HTTP verb, such as GET or POST, isn’t part of the routing process. The RoutingMiddleware determines which Razor Page to execute based solely on the route template associated with the Razor Page. It’s only when a Razor Page is about to be executed that the HTTP verb is used to decide which page handler to execute: OnGet for the GET verb, or OnPost for the POST verb, for example.

Web API controllers work like minimal API endpoints: the HTTP verb takes part in the routing process itself. So a GET request may be routed to one action, and a POST request may be routed to a different action, even if the request used the same URL.

The [Route] attribute we’ve used so far responds to all HTTP verbs. Instead, an action should typically only handle a single verb. Instead of the [Route] attribute, you can use

• [HttpPost] to handle POST requests
• [HttpGet] to handle GET requests
• [HttpPut] to handle PUT requests

There are similar attributes for all the standard HTTP verbs, like DELETE and OPTIONS. You can use these attributes instead of the [Route] attribute to specify that an action method should correspond to a single verb, as shown in the following listing.

Listing 20.9 Using HTTP verb attributes with attribute routing

public class AppointmentController
{
    [HttpGet("/appointments")]                #A
    public IActionResult ListAppointments()   #A
    {                                         #A
        /* method implementation */           #A
    }                                         #A

    [HttpPost("/appointments")]                 #B
    public IActionResult CreateAppointment()    #B
    {                                           #B
        /* method implementation */             #B
    }                                           #B
}

❶ Executed only in response to GET /appointments
❷ Executed only in response to POST /appointments

If your application receives a request that matches the route template of an action method but doesn’t match the required HTTP verb, you’ll get a 405 Method not allowed error response. For example, if you send a DELETE request to the /appointments URL in the previous listing, you’ll get a 405 error response.

When you’re building web API controllers, there is some code that you’ll find yourself writing repeatedly. The [ApiController] attribute is designed to handle some of this for you and reduce the amount of boilerplate you need.

20.4 Using common conventions with [ApiController]

In this section you’ll learn about the [ApiController] attribute and how it can reduce the amount of code you need to write to create consistent web API controllers. You’ll learn about the conventions it applies, why they’re useful, and how to turn them off if you need to.

The [ApiController] attribute was introduced in .NET Core 2.1 to simplify the process of creating web API controllers. To understand what it does, it’s useful to look at an example of how you might write a web API controller without the [ApiController] attribute and compare that with the code required to achieve the same thing with the attribute.

Listing 20.10 Creating a web API controller without the [ApiController] attribute

public class FruitController : ControllerBase
{
    List<string> _fruit = new List<string>     #A
    {                                          #A
        "Pear", "Lemon", "Peach"               #A
    };                                         #A

    [HttpPost("fruit")]                              #B
    public ActionResult Update([FromBody] UpdateModel model)     #C
    {
        if (!ModelState.IsValid)                             #D
        {                                                    #D
             return BadRequest(                              #D
                new ValidationProblemDetails(ModelState));   #D
        }                                                    #D

        if (model.Id < 0 || model.Id > _fruit.Count)             
        {
            return NotFound(new ProblemDetails()              #E
            {                                                 #E
                Status = 404,                                 #E
                Title = "Not Found",                          #E
                Type = "https://tools.ietf.org/html/rfc7231"  #E
                       + "#section-6.5.4",                    #E
            });                                               #E
        }                                                     #E
        _fruit[model.Id] = model.Name;    #F
        return Ok();                      #F
    }

    public class UpdateModel                                    
    {                                                           
        public int Id { get; set; }                             

        [Required]                         #G
        public string Name { get; set; }   #G
    }
}

❶ The list of strings serves as the application model in this example.
❷ Web APIs use attribute routing to define the route templates.
❸ The [FromBody] attribute indicates that the parameter should be bound to the request body.
❹ You need to check if model validation succeeded and return a 400 response if it failed.
❺ If the data sent does not contain a valid ID, returns a 404 ProblemDetails response
❻ Updates the model and returns a 200 Response
❼ UpdateModel is valid only if the Name value is provided, as set by the [Required] attribute.

This example demonstrates many common features and patterns used with web API controllers:

• Web API controllers read data from the body of a request, typically sent as JSON. To ensure the body is read as JSON and not as form values, you have to apply the [FromBody] attribute to the method parameters to ensure it is model-bound correctly.

• As discussed in chapter 16, after model binding, the model is validated, but it’s up to you to act on the validation results. You should return a 400 Bad Request response if the values provided failed validation. You typically want to provide details of why the request was invalid: this is done in listing 20.10 by returning a ValidationProblemDetails object in the response body, built from the ModelState.

• Whenever you return an error status, such as a 404 Not Found, where possible you should return details of the problem that will allow the caller to diagnose the issue. The ProblemDetails class is the recommended way of doing that in ASP.NET Core.
The code in listing 20.10 is representative of what you might see in an ASP.NET Core API controller before .NET Core 2.1. The introduction of the [ApiController] attribute in .NET Core 2.1 (and subsequent refinement in later versions) makes this same code much simpler, as shown in the following listing.

Listing 20.11 Creating a web API controller with the [ApiController] attribute

[ApiController]                               #A
public class FruitController : ControllerBase
{
    List<string> _fruit = new List<string>
    {
        "Pear", "Lemon", "Peach"
    };

    [HttpPost("fruit")]
    public ActionResult Update(UpdateModel model)    #B
    {                                                    #C
        if (model.Id < 0 || model.Id > _fruit.Count)
        {
            return NotFound();   #D
        }

        _fruit[model.Id] = model.Name;

        return Ok();
    }

    public class UpdateModel
    {
        public int Id { get; set; }

        [Required]
        public string Name { get; set; }
    }
}

❶ Adding the [ApiController] attribute applies several conventions common to API controllers.
❷ The [FromBody] attribute is assumed for complex action method parameters.
❸ The model validation is automatically checked, and if invalid, returns a 400 response.
❹ Error status codes are automatically converted to a ProblemDetails object.

If you compare listing 20.10 with listing 20.11, you’ll see that all the bold code in listing 20.10 can be removed and replaced with the [ApiController] attribute in listing 20.11. The [ApiController] attribute automatically applies several conventions to your controllers:

• Attribute routing—You must use attribute routing with your controllers; you can’t use conventional routing—not that you would, as we’ve discussed this approach only for API controllers anyway.
• Automatic 400 responses—I said in chapter 16 that you should always check the value of ModelState.IsValid in your Razor Page handlers and MVC actions, but the [ApiController] attribute does this for you by adding a filter, as we did with minimal APIs in chapter 7. We’ll cover MVC filters in detail in chapters 21 and 22.
• Model binding source inference—Without the [ApiController] attribute, complex types are assumed to be passed as form values in the request body. For web APIs, it’s much more common to pass data as JSON, which ordinarily requires adding the [FromBody] attribute. The [ApiController] attribute takes care of that for you.
• ProblemDetails for error codes—You often want to return a consistent set of data when an error occurs in your API. The [ApiController] attribute intercepts any error status codes returned by your controller (for example, a 404 Not Found response), and converts them to ProblemDetails responses.

When it was introduced, a key feature of the [ApiController] attribute was the Problem Details support, but as I described in chapter 5, the same automatic conversion to Problem Details is now supported by the default ExceptionHandlerMiddleware and StatusCodePagesMiddleware. Nevertheless, the [ApiController] conventions can significantly reduce the amount of boilerplate code you have to write and ensure that validation failures are handled automatically, for example.

As is common in ASP.NET Core, you will be most productive if you follow the conventions rather than trying to fight them. However, if you don’t like some of the conventions introduced by [ApiController],or want to customize them, you can easily do so.

You can customize the web API controller conventions your application uses by calling ConfigureApiBehaviorOptions() on the IMvcBuilder object returned from the AddControllers() method in your Program.cs file. For example, you could disable the automatic 400 responses on validation failure, as shown in the following listing.

Listing 20.12 Customizing [ApiAttribute] behaviors

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
    .ConfigureApiBehaviorOptions(options =>         #A
    {
        options.SuppressModelStateInvalidFilter = true;    #B
    });

// ...

❶ Controls which conventions are applied by providing a configuration lambda
❷ This would disable the automatic 400 responses for invalid requests.

Tip You can disable all the automatic features enabled by the [ApiController] attribute, but I encourage you to stick to the defaults unless you really need to change them. You can read more about disabling features in the documentation at https://docs.microsoft.com/aspnet/core/web-api.

The ability to customize each aspect of your web API controllers is one of the key differentiators with minimal APIs. In the next section you’ll learn how to control the format of the data returned by your web API controllers—whether that’s JSON, XML, or a different, custom format.

20.5 Generating a response from a model

This brings us to the final topic in this chapter: formatting a response. It’s common for API controllers to return JSON these days, but that’s not always the case. In this section you’ll learn about content negotiation and how to enable additional output formats such as XML.

Consider this scenario: you’ve created a web API action method for returning a list of cars, as in the following listing. It invokes a method on your application model, which hands back the list of data to the controller. Now you need to format the response and return it to the caller.

Listing 20.13 A web API controller to return a list of cars

[ApiController]
public class CarsController : Controller
{
    [HttpGet("api/cars")]              #A
    public IEnumerable<string> ListCars()      #B
    {
        return new string[]                      #C
            { "Nissan Micra", "Ford Focus" };    #C
    }
}

❶ The action is executed with a request to GET /api/cars.
❷ The API model containing the data is an IEnumerable.
❸ This data would normally be fetched from the application model.

You saw in section 20.2 that it’s possible to return data directly from an action method, in which case the middleware formats it and returns the formatted data to the caller. But how does the middleware know which format to use? After all, you could serialize it as JSON, as XML, or even with a simple ToString() call.

Warning Remember that in this chapter I’m talking only about web API controller responses. Minimal APIs support only automatic serialization to JSON, nothing else.

The process of determining the format of data to send to clients is known generally as content negotiation (conneg). At a high level, the client sends a header indicating the types of content it can understand—the Accept header—and the server picks one of these, formats the response, and sends a Content-Type header in the response, indicating which type it chose.

The Accept and Content-Type headers
The Accept header is sent by a client as part of a request to indicate the type of content that the client can handle. It consists of a number of MIME types, with optional weightings (from 0 to 1) to indicate which type would be preferred. For example, the application/json,text/xml;q=0.9,text/plain;q=0.6 header indicates that the client can accept JSON, XML, and plain text, with weightings of 1.0, 0.9, and 0.6, respectively. JSON has a weighting of 1.0, as no explicit weighting was provided. The weightings can be used during content negotiation to choose an optimal representation for both parties.

The Content-Type header describes the data sent in a request or response. It contains the MIME type of the data, with an optional character encoding. For example, the application/json; charset=utf-8 header would indicate that the body of the request or response is JSON, encoded using UTF-8.

For more on MIME types, see the Mozilla documentation: http://mng.bz/gop8. You can find the RFC for content negotiation at http://mng.bz/6DXo.

You’re not forced into sending only a Content-Type the client expects, and in some cases, you may not even be able to handle the types it requests. What if a request stipulates that it can accept only Microsoft Excel spreadsheets? It’s unlikely you’d support that, even if that’s the only Accept type the request contains.

When you return an API model from an action method, whether directly (as in listing 20.13) or via an OkResult or other StatusCodeResult, ASP.NET Core always returns something in the response. If it can’t honor any of the types stipulated in the Accept header, it will fall back to returning JSON by default. Figure 20.7 shows that even though XML was requested, the API controller formatted the response as JSON.

alt text

Figure 20.7 Even though the request was made with an Accept header of text/xml, the response returned was JSON, as the server was not configured to return XML.

Warning In legacy ASP.NET, objects were serialized to JSON using PascalCase, where properties start with a capital letter. In ASP.NET Core, objects are serialized using camelCase by default, where properties start with a lowercase letter.

However the data is sent, it’s serialized by an IOutputFormatter implementation. ASP.NET Core ships with a limited number of output formatters out of the box, but as always, it’s easy to add additional ones or change the way the defaults work.

20.5.1 Customizing the default formatters: Adding XML support

As with most of ASP.NET Core, the Web API formatters are completely customizable. By default, only formatters for plain text (text/plain), HTML (text/html), and JSON (application/json) are configured. Given the common use case of single-page application (SPAs) and mobile applications, this will get you a long way. But sometimes you need to be able to return data in a different format, such as XML.

Newtonsoft.Json vs. System.Text.Json
Newtonsoft.Json, also known as Json.NET, has for a long time been the canonical way to work with JSON in .NET. It’s compatible with every version of .NET under the sun, and it will no doubt be familiar to virtually all .NET developers. Its reach was so great that even ASP.NET Core took a dependency on it!

That all changed with the introduction of a new library in ASP.NET Core 3.0, System .Text.Json, which focuses on performance. In .NET Core 3.0 onward, ASP.NET Core uses System.Text.Json by default instead of Newtonsoft.Json.

The main difference between the libraries is that System.Text.Json is picky about its JSON. It will generally only deserialize JSON that matches its expectations. For example, System.Text.Json won’t deserialize JSON that uses single quotes around strings; you have to use double quotes.

If you’re creating a new application, this is generally not a problem; you quickly learn to generate the correct JSON. But if you’re converting an application to ASP.NET Core or are sending JSON to a third party you don’t control, these limitations can be real stumbling blocks.

Luckily, you can easily switch back to the Newtonsoft.Json library instead. Install the Microsoft.AspNetCore.Mvc.NewtonsoftJson package into your project and update the AddControllers() method in Program.cs to the following:

builder.Services.AddControllers()
.AddNewtonsoftJson();

This will switch ASP.NET Core’s formatters to use Newtonsoft.Json behind the scenes, instead of System.Text.Json. For more details on the differences between the libraries, see Microsoft’s article “Compare Newtonsoft.Json to System.Text.Json, and migrate to System.Text.Json”: http://mng.bz/0mRJ. For more advice on when to switch to the Newtonsoft.Json formatter, see the section “Add Newtonsoft.Json-based JSON format support” in Microsoft’s “Format response data in ASP.NET Core Web API” documentation: http://mng.bz/zx11.

You can add XML output to your application by adding an output formatter. You configure your application’s formatters in Program.cs by customizing the IMvcBuilder object returned from AddControllers(). To add the XML output formatter, use the following:

services.AddControllers()
    .AddXmlSerializerFormatters();

NOTE Technically, this also adds an XML input formatter, which means your application can now receive XML in requests too. Previously, sending a request with XML in the body would respond with a 415 Unsupported Media Type response. For a detailed look at formatters, including creating a custom formatter, see the documentation at http://mng.bz/e5gG.

With this simple change, your API controllers can now format responses as XML as well as JSON. Running the same request as shown in figure 20.7 with XML support enabled means the app will respect the text/xml accept header. The formatter serializes the string array to XML as requested instead of defaulting to JSON, as shown in figure 20.8.

alt text

Figure 20.8 With the XML output formatters added, the Accept header’ text/xml value is respected, and the response is serialized to XML.

This is an example of content negotiation, where the client has specified which formats it can handle and the server selects one of those, based on what it can produce. This approach is part of the HTTP protocol, but there are some quirks to be aware of when relying on it in ASP.NET Core. You won’t often run into these, but if you’re not aware of them when they hit you, they could have you scratching your head for hours!

20.5.2 Choosing a response format with content negotiation

Content negotiation is where a client says which types of data it can accept using the Accept header and the server picks the best one it can handle. Generally speaking, this works as you’d hope: the server formats the data using a type the client can understand.

The ASP.NET Core implementation has some special cases that are worth bearing in mind:

• By default, ASP.NET Core returns only application/json, text/plain, and text/html MIME types. You can add IOutputFormatters to make other types available, as you saw in the previous section for text/xml.
• By default, if you return null as your API model, whether from an action method or by passing null in a StatusCodeResult, the middleware returns a 204 No Content response.
• When you return a string as your API model, if no Accept header is set, ASP.NET Core formats the response as text/plain.
• When you use any other class as your API model, and there’s no Accept header or none of the supported formats was requested, the first formatter that can generate a response is used (typically JSON by default).
• If the middleware detects that the request is probably from a browser (the accept header contains /), it will not use conneg. Instead, it formats the response as though an Accept header was not provided, using the default formatter (typically JSON).

These defaults are relatively sane, but they can certainly bite you if you’re not aware of them. That last point in particular, where the response to a request from a browser is virtually always formatted as JSON, has certainly caught me out when trying to test XML requests locally!

As you should expect by now, all these rules are configurable; you can easily change the default behavior in your application if it doesn’t fit your requirements. For example, the following listing, taken from Program.cs, shows how you can force the middleware to respect the browser’s Accept header and remove the text/plain formatter for strings.

Listing 20.14 Customizing MVC to respect the browser’s Accept header in web APIs

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>      #A
{
    options.RespectBrowserAcceptHeader = true;         #B
    options.OutputFormatters.RemoveType<StringOutputFormatter>();   #C
});

❶ AddControllers has an overload that takes a lambda function.
❷ False by default; several other properties are also available to be set.
❸ Removes the output formatter that formats strings as text/plain

In most cases, conneg should work well for you out of the box, whether you’re building an SPA or a mobile application. In some cases, you may find you need to bypass the usual conneg mechanisms for specific action methods, and there are various ways to achieve this, but I won’t cover them in this book as I’ve found I rarely need to use them. For details, see Microsoft’s “Format response data in ASP.NET Core Web API” documentation: http://mng.bz/zx11.

At this point we’ve covered the main points of using API controllers, but you probably still have one major question: why would I use web API controllers over minimal APIs? That’s a great question, and one we’ll look at in section 20.6.

20.6 Choosing between web API controllers and minimal APIs

In part 1 of this book you learned all about using minimal APIs to build a JSON API. Minimal APIs are the new kid on the block, being introduced in .NET 6, but they are growing up quickly. With all the new features introduced in .NET 7 (discussed in chapter 5), minimal APIs are emerging as a great way to build HTTP APIs in modern .NET.

By contrast, web API controllers have been around since day one. They were introduced in their current form in ASP.NET Core 1.0 and were heavily inspired by the web API framework from legacy ASP.NET. The designs, patterns, and concepts used by web API controllers haven’t changed much since then, so if you’ve ever used web API controllers, they should look familiar in .NET 7.

The difficult question in .NET 7 is if you need to build an API, which should you use, minimal APIs or web API controllers? Both have their pros and cons, and a large part of the decision will be personal preference, but to help your decision, you should ask yourself several questions:

  1. Do you need to return data in multiple formats using content negotiation?
  2. Is performance critical to your application?
  3. Do you have complex filtering requirements?
  4. Is this a new project?
  5. Do you already have experience with web API controllers?
  6. Do you prefer convention over configuration?

Questions 1-3 in this list are focused on technical differences between minimal APIs and web API controllers. Web API controllers support content negotiation (conneg), which allows clients to request data be returned in a particular format: JSON, XML, or CSV, for example, as you learned in section 20.5. Web API controllers support this feature out of the box, so if it’s crucial for your application, it may be better to choose web API controllers over minimal APIs.

Tip If you want to use content negotiation with minimal APIs, it’s possible but not built in. I show how to add conneg to minimal APIs using the open-source library Carter on my blog: http://mng.bz/o12d.

Question 2 is about performance. Everyone wants the most performant app, but there’s a real question of how important it is. Are you going to be regularly benchmarking your application and looking for any regressions? If so, minimal APIs are probably going to be a better choice, as they’re often more performant than web API controllers.

The MVC framework that web API controllers use relies on a lot of conventions and reflection for discovering your controllers and a complex filter pipeline. These are obviously highly optimized, but if you’re writing an application where you need to squeeze out every little bit of throughput, minimal APIs will likely help get you there more easily. For most applications, the overhead of the MVC framework will be negligible when compared with any database or network access in your app, so this is worth worrying about only for performance-sensitive apps.

Question 3 focuses on filtering. You learned about filtering with minimal APIs in chapter 5: filters allow you to attach a processing pipeline to your minimal API endpoints and can be used to do things like automatic validation. Web API controllers (as well as MVC controllers and Razor Pages) also have a filter pipeline, but it’s much more complex than the simple pipeline used by minimal APIs, as you’ll see in chapters 21 and 22.

In most cases the filtering provided by minimal APIs will be perfectly adequate for your needs. The main cases where minimal API filtering will fall down will be when you already have an application that uses web API controllers and want to reuse some complex filters. In these cases, there may be no way to translate your existing web API filters to minimal API filters. If the filtering is important, then you may need to stick with web API controllers.

This leads to question 4: are you building a new application or working on an existing application? If this is a new application, I would be strongly in favor of using minimal APIs. Minimal APIs are conceptually simpler than web API controllers, are faster because of this, and are receiving a lot of improvements from the ASP.NET Core team. If there’s no other compelling reason to choose web API controllers in your new project, I suggest defaulting to minimal APIs.

On the other hand, if you have an existing web API controller application, I would be strongly inclined to stick with web API controllers. While it’s perfectly possible to mix minimal APIs and web API controllers in the same application, I would favor consistency over using the new hotness.

Question 5 considers how familiar you already are with web API controllers. If you’re coming from legacy ASP.NET or have already used web API controllers in ASP.NET Core and need to be productive quickly, you might decide to stick with web API controllers.

I consider this one of the weaker arguments, as minimal APIs are conceptually simpler than web API controllers; if you already know web API controllers, you will likely pick up minimal APIs easily. That said, the differences in the model binding approaches can be a little confusing, and you may decide it’s not worth the investment or frustration if things don’t work as you expect.

The final question comes down entirely to taste and preference: do you like minimal APIs? web API controllers heavily follow the “convention over configuration” paradigm (though not to the extent of MVC controllers and Razor Pages). By contrast, you must be far more explicit with minimal APIs. Minimal APIs also don’t enforce any particular grouping, unlike web API controllers, which all follow the “action methods in a controller class” pattern.

Different people prefer different approaches. Web API controllers mean less manual wiring up of components, but this necessarily means more magic and more rigidity around how you structure your applications.

By contrast, minimal API endpoints must be explicitly added to the WebApplication instance, but this also means you have more flexibility around how to group your endpoints. You can put all your endpoints in Program.cs, create natural groupings for them in separate classes, or create a file per endpoint or any pattern you choose.

Tip You can also more easily layer on helper frameworks to minimal APIs, such as Carter (https://github.com/CarterCommunity/Carter), which can provide some structure and support functionality if you want it.

Overall, the choice is up to you whether web API controllers or minimal APIs are better for your application. Table 20.1 summarizes the questions and where you should favor one approach over the other, but the final choice is up to you!

Table 20.1 Choosing between minimal APIs with web API controllers

Question Minimal APIs Web API controllers
1. Do you need conneg? Can’t use conneg out of the box Built-in and extensible
2. How critical is performance? More performant than web API controllers Less performant than minimal APIs
3. Complex filtering? Have a simple, extensible filter pipeline Have a complex, nonlinear, filter pipeline
4. Is this a new project? Minimal APIs are getting many new features and are a focus of the ASP.NET Core team The MVC framework is receiving small new features, but is less of a focus.
5. Do you have experience with web API controllers? Minimal APIs share many of the same concepts, but have subtle differences in model binding Web API controllers may be familiar to users of legacy ASP.NET or older ASP.NET Core versions
6. Do you prefer convention over configuration? Requires a lot of explicit configuration Convention- and discovery-based, which can appear more magic when you’re unfamiliar

That brings us to the end of this chapter on web APIs. In the next chapter we’ll look at one of more advanced topics of MVC and Razor Pages: the filter pipeline and how you can use it to reduce duplication in your code. The good news is that it’s similar to minimal API filters in principle. The bad news is that it’s far more complicated!

20.7 Summary

Web API action methods can return data directly or can use ActionResult<T> to generate an arbitrary response. If you return more than one type of result from an action method, the method signature must return ActionResult<T>.

The data returned by a web API action is sometimes called an API model. It contains the data that will be serialized and send back to the client. This differs from view models and PageModels, which contain both data and metadata about how to generate the response.

Web APIs are associated with route templates by applying RouteAttributes to your action methods. These give you complete control over the URLs that make up your application’s API.

Route attributes applied to a controller combine with the attributes on action methods to form the final template. These are also combined with attributes on inherited base classes. You can use inherited attributes to reduce the amount of duplication in the attributes, such as where you’re using a common prefix on your routes.

By default, the controller and action name have no bearing on the URLs or route templates when you use attribute routing. However, you can use the "[controller]" and "[action]" tokens in your route templates to reduce repetition. They’ll be replaced with the current controller and action name.

The [HttpPost] and [HttpGet] attributes allow you to choose between actions based on the request’s HTTP verb when two actions correspond to the same URL. This is a common pattern in RESTful applications.

The [ApiController] attribute applies several common conventions to your controllers. Controllers decorated with the attribute automatically bind to a request’s body instead of using form values, automatically generate a 400 Bad Request response for invalid requests, and return ProblemDetails objects for status code errors. This can dramatically reduce the amount of boilerplate code you must write.

You can control which of the conventions to apply by using the ConfigureApiBehaviorOptions() method and providing a configuration lambda. This is useful if you need to fit your API to an existing specification, for example.

By default, ASP.NET Core formats the API model returned from a web API controller as JSON. In contrast to legacy ASP.NET, JSON data is serialized using camelCase rather than PascalCase. You should consider this change if you get errors or missing values when using data from your API.

ASP.NET Core 3.0 onwards uses System.Text.Json, which is a strict, high performance library for JSON serialization and deserialization. You can replace this serializer with the common Newtonsoft.Json formatter by calling AddNewtonsoftJson() on the return value from services.AddControllers().

Content negotiation occurs when the client specifies the type of data it can handle and the server chooses a return format based on this. It allows multiple clients to call your API and receive data in a format they can understand.

By default, ASP.NET Core can return text/plain, text/html, and application/json, but you can add formatters if you need to support other formats.

You can add XML formatters by calling AddXmlSerializerFormatters() on the return value from services.AddControllers() in your Startup class. These can format the response as XML, as well as receive XML in a request body.

Content negotiation isn’t used when the Accept header contains /, such as in most browsers. Instead, your application uses the default formatter, JSON. You can disable this option by setting the RespectBrowserAcceptHeader option to true when adding your controller services in Program.cs.

You can mix web API Controllers and minimal API endpoints in the same application, but you may find it easier to use one or the other.

Choose web API controllers when you need content negotiation, when you have complex filtering requirements, when you have experience with web controllers, or when you prefer convention over configuration for your apps.

Choose minimal API endpoints when performance is critical, when you prefer explicit configuration over automatic conventions, or when you’re starting a new app.

Leave a Reply

Your email address will not be published. Required fields are marked *