DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Building a RESTful Service Using ASP.NET Core and dotConnect for PostgreSQL
  • Revolutionizing Content Management
  • Architecting Scalable ASP.NET Core Web APIs With Abstract Factory Method and Onion Architecture
  • Building a Microservices API Gateway With YARP in ASP.NET Core Web API

Trending

  • Medallion Architecture: Efficient Batch and Stream Processing Data Pipelines With Azure Databricks and Delta Lake
  • How AI Agents Are Transforming Enterprise Automation Architecture
  • 5 Subtle Indicators Your Development Environment Is Under Siege
  • Teradata Performance and Skew Prevention Tips
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. How to Implement Content Negotiation in ASP.NET Core 2.0

How to Implement Content Negotiation in ASP.NET Core 2.0

Content negotiation is one of those quality-of-life improvements you can add to your REST API to make it more user-friendly and flexible. Learn how to use it!

By 
Vladimir Pecanac user avatar
Vladimir Pecanac
·
Feb. 16, 18 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
14.0K Views

Join the DZone community and get the full member experience.

Join For Free

For the complete and up-to-date version of the article visit: https://code-maze.com/content-negotiation-dotnet-core/

Content negotiation is one of those quality-of-life improvements you can add to your REST API to make it more user-friendly and flexible. And when we design an API, isn't that what we want to achieve in the first place?

There are many things to keep in mind when designing a REST API and we've written recently about it in our Top REST API best practices article. Content negotiation is an HTTP feature which 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 in to get in response to the REST API request. If you want to learn how content negotiation works behind the scenes, you can download our Complete Guide to HTTP Book for free and look it up in the advanced features section.

Today, we are going through the content negotiation implementation in ASP.NET Core.

What DO You Get Out of the Box?

By default, ASP.NET Core Web API returns a JSON formatted result.

Let's make a default Web API project and remove the default ValuesController. Instead, we are going to make our own controller (with blackjack and hookers), BlogController with only one method:

[Route("api/[controller]")]
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 .NET Core",
MetaDescription = "Content negotiation is one of those quality-of-life improvements you can add to your REST API to make it more user-friendly and flexible. And when we design the API, isn't that what we want to achieve in the first place?",
Published = true
});

blogs.Add(new Blog()
{
Name = "Code Maze",
Description = "A practical programmers resource",
BlogPosts = blogPosts
});

return Ok(blogs);
}
}

Things to note about this simple example:

  • We are using two classes: Blog and BlogPosts to create an object to return as a response object
  • We are utilizing the IActionResult interface provided by ASP.NET Core as a generic return type for different types of responses our methods might have
  • The object creation logic is in the controller. You should not implement your controllers like this; this is just for the sake of simplicity
  • We are returning the result with the Ok helper method which returns the object and the status code 200 OK

How to Use Postman to Test Your API

Postman is a nice little tool you can use to test your APIs easily. Now, let's try calling the method using Postman and see what we get as a response.

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 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.

Changing the Default Configuration of Our Project

A server does not explicitly specify where it formats a response to JSON. But you can override it by changing configuration options through the AddMvc method options. By default, it looks like this:

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

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

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(config =>
{
// Add XML Content Negotiation
config.RespectBrowserAcceptHeader = true;
config.InputFormatters.Add(new XmlSerializerInputFormatter());
config.OutputFormatters.Add(new XmlSerializerOutputFormatter());
});
}

First things first, we must tell a server to respect the Accept header. After that, we can add XML formatters to enable the XML formatting.MvcXmlSerializerOutputFormatter and are both part of the Microsoft.AspNetCore.Mvc.Formatters, so we need to add a reference to that library.

Now that we have our server configured let's test the content negotiation once more.

Testing the Content Negotiation

Let's see what happens now if we fire the same request through Postman.

There is our XML response.

That was easy, wasn't it?

Now by changing the Accept header from text/xml to text/json, we can get differently formatted responses which is awesome, wouldn't you agree?

Ok, that was nice and easy.

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

Currently, it will default to a JSON type.

But you can restrict this behavior by adding one line to the configuration.

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

config.InputFormatters.Add(new XmlSerializerInputFormatter());
config.OutputFormatters.Add(new XmlSerializerOutputFormatter());
});
}

We 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 Complete Guide to HTTP book, 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.

So far so good.

More About Formatters

Let's imagine you are making a public REST API and it needs to support content negotiation for a type that is not "in the box". Rare as it might occur, you need to have a mechanism to do this.

So, how can you do that?

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

Now let's have some fun and implement a custom CSV formatter for our example.

Implementing a Custom Formatter

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("text/csv"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}

protected override bool CanWriteType(Type type)
{
if (typeof(Blog).IsAssignableFrom(type) || typeof(IEnumerable<Blog>).IsAssignableFrom(type))
{
return base.CanWriteType(type);
}

return false;
}

public override 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);
}

using (var writer = context.WriterFactory(response.Body, selectedEncoding))
{
return writer.WriteAsync(buffer.ToString());
}
}

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 that 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 formatter to the list of OutputFormatters in the AddMvcOptions.

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

config.InputFormatters.Add(new XmlSerializerInputFormatter());
config.OutputFormatters.Add(new XmlSerializerOutputFormatter());
config.OutputFormatters.Add(new CsvOutputFormatter());
});
}

Now let's run this and see if it actually works. This time we will put the text/csv as the value for the Accept header.

Well, what do you know, it works!

Since we only have one blog and one blog post in our example, there is only one line in the response.

You can play around with source code to see what happens when you add more blogs and blog posts.

There is a great page about custom formatters in ASP.NET Core if you want to learn more about them. You can also check out the implementation of the input and output formatters for the vcard content type if you need more examples.

Consuming APIs Programmatically

Up until now, we have used Postman to play around with the example. But, I feel you need to try out to consume some REST APIs using content negotiation we described here by making some requests programmatically instead of using the third party tool.

For that purpose, we have laid out a few great ways to consume RESTful API. You can find some of the best tools that .NET provides to consume any REST API. Be sure to check it out and try consuming some APIs.

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.

You should be able both to design and consume REST APIs using content negotiation now. It really is a great mechanism, and we have great tools to implement it in our projects, easily. So, there are no excuses!

If you want to play around with the source code, you can find it here:Download source code from GitHub.

Thanks for reading and please leave a comment in the comment section.

ASP.NET ASP.NET Core Web API Media type REST Web Protocols code style Input and output (medicine) Blog

Published at DZone with permission of Vladimir Pecanac. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Building a RESTful Service Using ASP.NET Core and dotConnect for PostgreSQL
  • Revolutionizing Content Management
  • Architecting Scalable ASP.NET Core Web APIs With Abstract Factory Method and Onion Architecture
  • Building a Microservices API Gateway With YARP in ASP.NET Core Web API

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!