Hire a web Developer and Designer to upgrade and boost your online presence with cutting edge Technologies

Saturday, August 27, 2022

Content Negotiation in Web API

Content negotiation is the process of selecting the best resource for a response when multiple resource representations are available. Content negotiation is an HTTP feature that has been around for a while, but for one reason or another, it is, maybe, a bit underused.

In short, content negotiation lets you choose or rather “negotiate” the content you want to get in response to the REST API request.

 

Let’s dive in.

How Does Content Negotiation Work?

Content negotiation happens when a client specifies the media type it wants as a response to the request Accept header. By default, ASP.NET Core Web API returns a JSON formatted result and it will ignore the browser Accept header.

ASP.NET Core supports the following media types by default:

  • application/json
  • text/json
  • text/plain

To try this out let’s create a default Web API project and simple model for a BlogPost:

public class BlogPost
{
public string Title { get; set; }
public string MetaDescription { get; set; }
public bool Published { get; set; }
}

And one class that lists all the blog posts called Blog:

public class Blog
{
public string Name { get; set; }
public string Description { get; set; }
public List<BlogPost> BlogPosts { get; set; }
public Blog()
{
BlogPosts = new List<BlogPost>();
}
}

We are going to create a controller called BlogController with only one method Get() that ads one blog post and one blog and returns them as a result:

[Route("api/blog")]
public class BlogController : Controller
{
public IActionResult Get()
{
var blogs = new List<Blog>();
var blogPosts = new List<BlogPost>();
blogPosts.Add(new BlogPost
{
Title = "Content Negotiation in Web API",
MetaDescription = "Content negotiation is the process of selecting the best resource for a response when multiple resource representations are available.",
Published = true
});
blogs.Add(new Blog()
{
Name = "Code Maze",
Description = "C#, .NET and Web Development Tutorials",
BlogPosts = blogPosts
});
return Ok(blogs);
}
}

Content negotiation is implemented by ObjectResult and the Ok() method inherits from OkObjectResult that inherits from ObjectResult. That means our controller method is able to return the content negotiated response.

Although the object creation logic is in the controller. You should not implement your controllers like this. This is just for the sake of simplicity. To learn more of the best practices you can read our article on 10 Things You Should Avoid in Your ASP.NET Core Controllers or our article on  Top REST API best practices article

We are returning the result with the Ok() helper method which returns the object and the status code 200 OK all the time.

Return the Default JSON Response

If we run our application right now, we’ll get a JSON response by default when run in a browser:

[
{
"name": "Code Maze",
"description": "C#, .NET and Web Development Tutorials",
"blogPosts": [
{
"title": "Content Negotiation in Web API",
"metaDescription": "Content negotiation is the process of selecting the best resource for a response when multiple resource representations are available.",
"published": true
}
]
}
]

For the sake of testing the responses properly we’re going to use Postman:

You can clearly see that the default result when calling GET on /api/blog returns our JSON result. Those of you with sharp eyes might have even noticed that we used the Accept header with application/xml to try forcing the server to return other media types like plain text and XML.

But that doesn’t work. Why?

Because we need to configure server formatters to format a response the way we want it.

Let’s see how to do that.

Return XML Response

A server does not explicitly specify where it formats a response to JSON. But we can override it by changing configuration options through the AddControllers() method options. By default, it can be found in the Program class and it looks like this:

builder.Services.AddControllers();

We can add the following options to enable the server to format the XML response when the client tries negotiating for it:

builder.Services.AddControllers(options =>
{
options.RespectBrowserAcceptHeader = true;
}).AddXmlSerializerFormatters();

First things first, we must tell a server to respect the Accept header. After that, we just add the AddXmlSerializerFormatters() method to support XML formatters.

Now that we have our server configured let’s test the content negotiation once more:

<ArrayOfBlog xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ContentNegotiation.Models">
<Blog>
<BlogPosts>
<BlogPost>
<MetaDescription>Content negotiation is the process of selecting the best resource for a response when multiple resource representations are available.</MetaDescription>
<Published>true</Published>
<Title>Content Negotiation in Web API</Title>
</BlogPost>
</BlogPosts>
<Description>C#, .NET and Web Development Tutorials</Description>
<Name>Code Maze</Name>
</Blog>
</ArrayOfBlog>

Let’s see what happens now if we fire the same request through Postman:

There is our XML response, the response is no longer a default one. By changing the Accept header we can get differently formatted responses.

But what if despite all this flexibility a client requests a media type that a server doesn’t know how to format?

Restricting Media Types in Content Negotiation

Currently, the response will default to JSON if the media type is not recognized.

But we can restrict this behavior by adding one line to the configuration:

builder.Services.AddControllers(options =>
{
options.RespectBrowserAcceptHeader = true;
options.ReturnHttpNotAcceptable = true;
}).AddXmlSerializerFormatters();

We’ve added the ReturnHttpNotAcceptable = true option, which tells the server that if the client tries to negotiate for the media type the server doesn’t support, it should return the 406 Not Acceptable status code.

This will make your application more restrictive and force the API consumer to request only the types the server supports. The 406 status code is created for this purpose. You can find more details about that in our HTTP Reference article, or if you want to go even deeper you can check out the RFC2616.

Now, let’s try fetching the text/css media type using Postman to see what happens:

And as expected, there is no response body, and all we get is a nice 406 Not Acceptable status code.

Custom Formatters in ASP.NET Core

Let’s imagine we are making a public REST API and it needs to support content negotiation for a type that is not “in the box”.

ASP.NET Core supports the creation of custom formatters. Their purpose is to give you the flexibility to create your own formatter for any media types you need to support.

We can make the custom formatter using the following method:

  • Create an output formatter class that inherits the TextOutputFormatter class
  • Create an input formatter class that inherits the TextInputformatter class
  • Add input and output classes to InputFormatters and OutputFormatters collections the same way as we did for the XML formatter

Let’s implement a custom CSV output formatter for our example.

Implementing a Custom Formatter in ASP.NET Core

Since we are only interested in formatting responses in this article, we need to implement only an output formatter. We would need an input formatter only if a request body contained a corresponding type.

The idea is to format a response to return the list of blogs and their corresponding list of blog posts in a CSV format.

Let’s add a CsvOutputFormatter class to our project:

public class CsvOutputFormatter : TextOutputFormatter
{
public CsvOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/csv"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
protected override bool CanWriteType(Type? type)
=> typeof(Blog).IsAssignableFrom(type)
|| typeof(IEnumerable<Blog>).IsAssignableFrom(type);
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var response = context.HttpContext.Response;
var buffer = new StringBuilder();
if (context.Object is IEnumerable<Blog>)
{
foreach (var Blog in (IEnumerable<Blog>)context.Object)
{
FormatCsv(buffer, Blog);
}
}
else
{
FormatCsv(buffer, (Blog)context.Object);
}
await response.WriteAsync(buffer.ToString(), selectedEncoding);
}
private static void FormatCsv(StringBuilder buffer, Blog blog)
{
foreach (var blogPost in blog.BlogPosts)
{
buffer.AppendLine($"{blog.Name},\"{blog.Description},\"{blogPost.Title},\"{blogPost.Published}\"");
}
}
}

There are a few things to note here.

In the constructor, we define which media type this formatter should parse as well as encodings.

The CanWriteType method is overridden, and it indicates whether or not the Blog type can be written by this serializer. The WriteResponseBodyAsync method constructs the response. And finally, we have the FormatCsv method that formats a response the way we want it.

The class is pretty straightforward to implement, and the main thing that you should focus on is the FormatCsv method logic.

Now, we just need to add the newly made CsvOutputFormatter to the list of OutputFormatters in the AddMvcOptions() method:

builder.Services.AddControllers(options =>
{
options.RespectBrowserAcceptHeader = true;
options.ReturnHttpNotAcceptable = true;
}).AddXmlSerializerFormatters()
.AddMvcOptions(options => options.OutputFormatters.Add(new CsvOutputFormatter()));

Now let’s run this and see if it actually works. This time we will put the application/csv as the value for the Accept header in a Postman request:

It works, our only entry is formatted as a CSV response.

There is a great page about custom formatters in ASP.NET Core if you want to learn more about them. 

Conclusion

In this blog post, we went through a concrete implementation of the content negotiation mechanism in an ASP.NET Core project. We have learned about formatters and how to make a custom one, and how to set them up in your project configuration as well.

We have also learned how to restrict an application only to certain content types, and not accept any others.

ASP.NET Core Web API – How to Handle Get Request

 The Get Request is something we can’t miss while creating API because it is the most common request. So, it is very important to learn more about handling that kind of request.

In the previous post, we have created a repository pattern for collecting the data from the database.

Now, it is time to use that repository for business logic.

We are going to keep all the database logic inside the repository classes. Controllers will be responsible for handling requests, model validation, and returning responses to the frontend part of the application.

By doing so, our controllers won’t be overwhelmed with the code thus making the code easier to read and maintain as well.


So, let’s start.Controllers and Routing in WEB API

To create a controller, right-click on the Controllers folder inside the main project and Add/Controller. Then from the menu choose API Controller - Empty and name it OwnerController.cs:

using Microsoft.AspNetCore.Mvc;
namespace AccountOwnerServer.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class OwnerController : ControllerBase
{
}
}

Every web API controller class inherits from the ControllerBase abstract class that provides all the necessary behavior for the derived class.

Also, above the controller class we can see this part of the code:

[Route("api/[controller]")]

This represents the routing and we will talk a little bit about the routing inside Web APIs.

Web API routing routes the incoming HTTP requests to the particular action method inside the Web API controller.

There are two types of routings:

  1. Convention based routing and
  2. Attribute routing

Convention-based routing is called that way because it establishes a convention for the URL paths. The first part makes the mapping for the controller name, the second part makes the mapping for the action method, and the third part is used for the optional parameter. We can configure this type of routing in the Startup class in the Configure method:

Attribute routing uses the attributes to map the routes directly to the action methods inside the controller. Usually, we place the base route above the controller class, as you can notice in our Web API controller class. Similarly, for the specific action methods, we create their routes right above them.

GetAllOwners GET Request in .NET Core

Let’s start.

First, let’s change the base route from: [Route("api/[controller]")] to: [Route("api/owner")]. Even though the first route will work just fine, with the second example we are more specific to show that this routing should point to the OwnerController.

Now it is time to create the first action method to return all the owners from the database.

In the IOwnerRepository interface create a definition for theGetAllOwners method :

public interface IOwnerRepository
{
IEnumerable<Owner> GetAllOwners();
}

Then implement that interface inside the OwnerRepository class:

namespace Repository
{
public class OwnerRepository : RepositoryBase<Owner>, IOwnerRepository
{
public OwnerRepository(RepositoryContext repositoryContext)
:base(repositoryContext)
{
}
public IEnumerable<Owner> GetAllOwners()
{
return FindAll()
.OrderBy(ow => ow.Name)
.ToList();
}
}
}

Finally, we need to return all the owners by using the GetAllOwners method inside the Web API action.

The purpose of the action methods, inside Web API controllers, is not only to return the results. It is the main purpose, but not the only one. You need to pay attention to the status codes of your Web API responses as well. Additionally, you’ll have to decorate your actions with the HTTP attributes which will mark the type of the HTTP request to that action.

You can read more on HTTP and find some HTTP request examples in part 1 of our HTTP series.

Finally, let’s modify the OwnerController:

using Contracts;
using Microsoft.AspNetCore.Mvc;
using System;
namespace AccountOwnerServer.Controllers
{
[Route("api/owner")]
[ApiController]
public class OwnerController : ControllerBase
{
private ILoggerManager _logger;
private IRepositoryWrapper _repository;
public OwnerController(ILoggerManager logger, IRepositoryWrapper repository)
{
_logger = logger;
_repository = repository;
}
[HttpGet]
public IActionResult GetAllOwners()
{
try
{
var owners = _repository.Owner.GetAllOwners();
_logger.LogInfo($"Returned all owners from database.");
return Ok(owners);
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong inside GetAllOwners action: {ex.Message}");
return StatusCode(500, "Internal server error");
}
}
}
}

Let us explain this code a bit.

First of all, we inject the logger and repository services inside the constructor. Then by decorating the GetAllOwners action with [HttpGet] attribute, we are mapping this action to the GET request. Finally, we use both injected parameters to log the messages and to get the data from the repository class.

The IActionResult interface supports using a variety of methods, which return not only the result but the status codes as well. In this situation, the OK method returns all the owners and also the status code 200 which stands for OK. If an exception occurs, we will return the internal server error with the status code 500.

You can read more about status codes by reading The HTTP series – References.

One more thing. In this series, we are not using the service layer because we didn’t want to make things more complicated for this small project. But if you want to use it in your projects, which we strongly recommend, please read our Onion Architecture article to see how it should be done.

Because there is no route attribute right above the action, the route for the action GetAllOwners will be api/owner (http://localhost:5000/api/owner).

Code Permissions and Testing the Result

We would like to point out one more thing inside GetAllOwners action. Right now, if you look at the repository structure, its classes inherit from the abstract RepositoryBase<T> class and also from its own interface which then inherits from the IRepositoryBase<T> interface. With this hierarchy in place, by typing _repository.Owner. you are able to call the custom method from the OwnerRepository class and also all of the methods from the abstract RepositoryBase<T> class.

If you want to avoid that type of behavior and to allow actions inside the controller to call only methods from the repository user classes, all you need to do is to remove IRepositoryBase<T> inheritance from IOwnerRepository. Consequently, only repository user classes will be able to call generic methods from RepositoryBase<T> class. Likewise, action methods communicate only with repository user classes.

It is all up to you, how you want to organize your code and permissions.

To check the result, we are going to use the Postman tool to send requests towards the application.

Also, you can learn more about how to consume web API programmatically using C# by reading A few great ways to consume restful api in c#.

Let’s start the application, start the Postman and create a request:


Excellent, everything is working as planned.

As you can see, we return all the data from the database with this action. Of course, you can add paging to this action and optimize it by returning only the part of the data.

Before we continue, we would like to show you one more thing. If you look at the model classes, you’ll notice that all properties have the same name as the columns they are mapped to. But you can have the property with a different name than the column it points to, and still to map each other. To achieve that you need to use attribute [Column]

So, let’s do something like that.

We are going to change the property names from AccountId and OwnerId to just Id in the Owner and Account classes. Also, we are going to add the [Column] property which will map the Id property to the right column in the database:

[Table("Account")]
public class Account
{
[Column("AccountId")]
public Guid Id { get; set; }
[Required(ErrorMessage = "Date created is required")]
public DateTime DateCreated { get; set; }
[Required(ErrorMessage = "Account type is required")]
public string AccountType { get; set; }
[Required(ErrorMessage = "Owner Id is required")]
public Guid OwnerId { get; set; }
[ForeignKey(nameof(Owner))]
public Guid OwnerId { get; set; }
public Owner Owner { get; set; }
}

[Table("Owner")]
public class Owner
{
[Column("OwnerId")]
public Guid Id { get; set; }
[Required(ErrorMessage = "Name is required")]
[StringLength(60, ErrorMessage = "Name can't be longer than 60 characters")]
public string Name { get; set; }
[Required(ErrorMessage = "Date of birth is required")]
public DateTime DateOfBirth { get; set; }
[Required(ErrorMessage = "Address is required")]
[StringLength(100, ErrorMessage = "Address can not be loner then 100 characters")]
public string Address { get; set; }
public ICollection<Account> Accounts { get; set; }
}

Now let’s continue.

Using DTO and AutoMapper

DTO or Data Transfer Object serves the purpose to transfer data from the server to the client. That is exactly what are we going to use it for.

If we take a look at the GetAllOwners action, we can see that we use the model Owner class to fetch the data from the database (_repository.Owner.GetAllOwners()returns a list of  Owner objects) and also to return that result to the client. And that is not a good practice. A much better practice is to have a model class to fetch the data from the database and to have a DTO class to return that result to the client. The DTO object could be exactly the same as the model object but still, it is much better to use DTO objects because if something changes in the database the model class must change but that doesn’t mean that the client wants changed results. Thus the DTO object will not change.

Having that said, let’s create a new folder DataTransferObjects in the Entities project and let’s create OwnerDto class inside:

public class OwnerDto
{
public Guid Id { get; set; }
public string? Name { get; set; }
public DateTime DateOfBirth { get; set; }
public string? Address { get; set; }
}

As you can see, we don’t have the Accounts property, because we don’t want to show that information to the client right now.

Now, all we would have to do is to map a returned list of owners from the database to the list of ownerDto. But, doing that manually is a boring job and if we have twenty or even more properties in our DTO class, it would be time-consuming as well. Luckily for us, there is a great tool that could help us a lot in the mapping process. Yes, it is AutoMapper.

Working with AutoMapper

AutoMapper is a library that helps us map different objects. To install it, we have to type this command in the Package Manager Console window:

PM> Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection

After the installation, we have to register it in the Program class:

builder.Services.AddAutoMapper(typeof(Program));

Now, we have to create a mapping profile class to tell AutoMapper how to execute mapping actions. So, let’s create a new class MappingProfile in the main project and modify it:

public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Owner, OwnerDto>();
}
}

Finally, we can modify the OwnerController:

public class OwnerController : ControllerBase
{
private ILoggerManager _logger;
private IRepositoryWrapper _repository;
private IMapper _mapper;
public OwnerController(ILoggerManager logger, IRepositoryWrapper repository, IMapper mapper)
{
_logger = logger;
_repository = repository;
_mapper = mapper;
}
[HttpGet]
public IActionResult GetAllOwners()
{
try
{
var owners = _repository.Owner.GetAllOwners();
_logger.LogInfo($"Returned all owners from database.");
var ownersResult = _mapper.Map<IEnumerable<OwnerDto>>(owners);
return Ok(ownersResult);
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong inside GetAllOwners action: {ex.Message}");
return StatusCode(500, "Internal server error");
}
}
}

We can send the same request from Postman and we are going to get the same result (without accounts), but now, with much better implementation. AutoMapper has great capabilities and you can learn more by reading Getting Started With AutoMapper in ASP.NET Core.

GetOwnerById GET Request in .NET Core

To continue, let’s modify the IOwnerRepository interface:

public interface IOwnerRepository
{
IEnumerable<Owner> GetAllOwners();
Owner GetOwnerById(Guid ownerId);
}

Then, let’s implement the interface in the OwnerRepository.cs:

public Owner GetOwnerById(Guid ownerId)
{
return FindByCondition(owner => owner.Id.Equals(ownerId))
.FirstOrDefault();
}

Finally, let’s change the OwnerController:

[HttpGet("{id}")]
public IActionResult GetOwnerById(Guid id)
{
try
{
var owner = _repository.Owner.GetOwnerById(id);
if (owner is null)
{
_logger.LogError($"Owner with id: {id}, hasn't been found in db.");
return NotFound();
}
else
{
_logger.LogInfo($"Returned owner with id: {id}");
var ownerResult = _mapper.Map<OwnerDto>(owner);
return Ok(ownerResult);
}
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong inside GetOwnerById action: {ex.Message}");
return StatusCode(500, "Internal server error");
}
}

We are going to use the Postman to send valid and invalid requests to check the results:

Invalid request:

Owner Details Request

Let’s continue by creating a logic to return the owner object with its account details.

First, we need to create the AccountDto class:

public class AccountDto
{
public Guid Id { get; set; }
public DateTime DateCreated { get; set; }
public string? AccountType { get; set; }
}

Then, we have to modify our OwnerDto class that will help us return the owner with all related accounts to it. If you want you can create an additional DTO class with name OwnerWithAccountsDto, but for the sake of simplicity, we are going to modify the existing DTO class:

public class OwnerDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public string Address { get; set; }
public IEnumerable<AccountDto>? Accounts { get; set; }
}

Notice the property Accounts which will bind all the accounts related to the certain owner. 

Let’s modify the interface accordingly:

public interface IOwnerRepository
{
IEnumerable<Owner> GetAllOwners();
Owner GetOwnerById(Guid ownerId);
Owner GetOwnerWithDetails(Guid ownerId);
}

Also, let’s modify the repository class:

public Owner GetOwnerWithDetails(Guid ownerId)
{
return FindByCondition(owner => owner.Id.Equals(ownerId))
.Include(ac => ac.Accounts)
.FirstOrDefault();
}

We are using the Include method to include all the accounts related to the current owner.

We have to add a new mapping rule in the MappingProfile class:

public MappingProfile()
{
CreateMap<Owner, OwnerDto>();
CreateMap<Account, AccountDto>();
}

Finally, let’s modify the controller:

[HttpGet("{id}/account")]
public IActionResult GetOwnerWithDetails(Guid id)
{
try
{
var owner = _repository.Owner.GetOwnerWithDetails(id);
if (owner == null)
{
_logger.LogError($"Owner with id: {id}, hasn't been found in db.");
return NotFound();
}
else
{
_logger.LogInfo($"Returned owner with details for id: {id}");
var ownerResult = _mapper.Map<OwnerDto>(owner);
return Ok(ownerResult);
}
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong inside GetOwnerWithDetails action: {ex.Message}");
return StatusCode(500, "Internal server error");
}
}

Result:

We have created these actions that use Repository Pattern logic synchronously but it could be done asynchronously as well. If you want to learn how to do that you can visit Implementing Async Repository in .NET Core. Although we strongly recommend finishing all the parts from this series for an easier understanding of the project’s business logic.

Conclusion

Requests using GET should only retrieve the data from the database, and all the actions inside the OwnerController class are written like that.

By reading this post you’ve  learned:

  • How to work with a controller class
  • What is routing and how to use it
  • How to handle GET requests in a web API
  • The way to use DTOs while handling requests

Thank you all for reading and I hope you found something useful in it.