Chapter7:CONTENT NEGOTIATION

7 CONTENT NEGOTIATION

Content negotiation is one of the quality-of-life improvements we can add to our 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?
内容协商是我们可以添加到 REST API 中的生活质量改进之一,以使其更加用户友好和灵活。当我们设计一个API时,这不就是我们首先想要实现的目标吗?

Content negotiation is an HTTP feature that has been around for a while, but for one reason or another, it is often a bit underused.
内容协商是一项已经存在了一段时间的HTTP功能,但由于某种原因,它通常没有得到充分利用。

In short, content negotiation lets you choose or rather “negotiate” the content you want in to get in response to the REST API request.
简而言之,内容协商允许您选择或更确切地说“协商”您想要获取的内容以响应 REST API 请求。

7.1 What Do We Get Out of the Box ?

By default, ASP.NET Core Web API returns a JSON formatted result.
默认情况下,ASP.NET 核心 Web API 返回 JSON 格式的结果。

We can confirm that by looking at the response from the GetCompanies action:
我们可以通过查看GetCompanies的回复来确认这一点:

https://localhost:5001/api/companies

Alt text

We can clearly see that the default result when calling GET on /api/companies returns the JSON result. We have also used the Accept header (as you can see in the picture above) to try forcing the server to return other media types like plain text and XML.
我们可以清楚地看到,调用 GET 时的默认结果/api/companies 返回 JSON 结果。我们还使用Accept 标头(如上图所示)尝试强制服务器返回其他媒体类型,如纯文本和 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.
让我们看看如何做到这一点。

7.2 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 AddControllers method.
服务器不会显式指定它格式化 JSON 响应的位置。但是,您可以通过 AddControllers 方法更改配置选项来覆盖它。

We can add the following options to enable the server to format the XML response when the client tries negotiating for it:
我们可以添加以下选项,使服务器能够在客户端尝试协商 XML 响应时格式化它:

        public void ConfigureServices(IServiceCollection services)
        {

            services.ConfigureCors();
            services.ConfigureIISIntegration();
            services.ConfigureLoggerService();
            services.ConfigureSqlContext(Configuration);
            services.ConfigureRepositoryManager();
            services.AddAutoMapper(typeof(Startup));

            services.AddControllers(config =>
            {
                config.RespectBrowserAcceptHeader = true;
            }).AddXmlDataContractSerializerFormatters();


            services.AddControllers();
        }

First things first, we must tell a server to respect the Accept header. After that, we just add the AddXmlDataContractSerializerFormatters method to support XML formatters.
首先,我们必须告诉服务器接受 Accept 标头。之后,我们只需添加 AddXmlDataContractSerializerFormatters 方法来支持 XML 格式化程序。

Now that we have our server configured, let’s test the content negotiation once more.
现在我们已经配置了服务器,让我们再次测试内容协商。

7.3 Testing Content Negotiation

Let’s see what happens now if we fire the same request through Postman:
让我们看看如果我们通过 Postman 触发相同的请求,现在会发生什么:

https://localhost:5001/api/companies

Alt text

There is our XML response.
有我们的 XML 响应。

Now by changing the Accept header from text/xml to text/json, we can get differently formatted responses — and that is quite awesome, wouldn’t you agree?
现在,通过将 Accept 标头从 text/xml 更改为 text/json,我们可以获得不同格式的响应——这非常棒,你不同意吗?

Okay, 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?
但是,尽管有所有这些灵活性,但客户端请求服务器不知道如何格式化的媒体类型怎么办?

7.4 Restrictin Media Types

Currently, it – the server – will default to a JSON type.
目前,它(服务器)将默认为 JSON 类型。

But we can restrict this behavior by adding one line to the configuration:
但是我们可以通过在配置中添加一行来限制此行为:

services.AddControllers(config =>
{
    config.RespectBrowserAcceptHeader = true;
    config.ReturnHttpNotAcceptable = true;
}).AddXmlDataContractSerializerFormatters();

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.
我们添加了 ReturnHttpNotAcceptable = true 选项,该选项告诉服务器,如果客户端尝试协商服务器不支持的媒体类型,则应返回 406 不可接受状态代码。

This will make our 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.
这将使我们的应用程序更具限制性,并强制 API 使用者仅请求服务器支持的类型。406 状态代码就是为此目的而创建的。

Now, let’s try fetching the text/css media type using Postman to see what happens:
现在,让我们尝试使用 Postman 获取文本/css 媒体类型,看看会发生什么:

https://localhost:5001/api/companies

Alt text

And as expected, there is no response body and all we get is a nice 406 Not Acceptable status code.
正如预期的那样,没有响应正文,我们得到的只是一个不错的 406 不可接受状态代码。

So far so good.
目前为止,一切都好。

7.5 More About Formatters

If we want our API to support content negotiation for a type that is not “in the box,” we need to have a mechanism to do this.
如果我们希望我们的 API 支持非“盒子里”类型的内容协商,我们需要有一种机制来做到这一点。

So, how can we do that?
那么,我们该怎么做呢?

ASP.NET Core supports the creation of custom formatters. Their purpose is to give us the flexibility to create our formatter for any media types we need to support.
ASP.NET 核心支持创建自定义格式化程序。它们的目的是使我们能够灵活地为我们需要支持的任何媒体类型创建格式化程序。

We can make the custom formatter by using the following method:
我们可以使用以下方法制作自定义格式化程序:

  • Create an output formatter class that inherits the TextOutputFormatter class.
    创建继承文本输出格式化程序类。

  • Create an input formatter class that inherits the TextInputformatter class.
    创建继承TextInput格式化程序类。

  • Add input and output classes to the InputFormatters and OutputFormatters collections the same way we did for the XML formatter.
    将输入和输出类添加到 InputFormatters 和 OutputFormatters 集合,方法与 XML 格式化程序相同。

Now let’s have some fun and implement a custom CSV formatter for our example.
现在,让我们玩得开心,并为我们的示例实现自定义 CSV 格式化程序。

7.6 Implementing a Custom Formatter

Since we are only interested in formatting responses, 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 companies in a CSV format.
这个想法是格式化响应以 CSV 格式返回公司列表。

Let’s add a CsvOutputFormatter class to our main project:
让我们将一个 CsvOutputFormatter 类添加到我们的主项目中:

//CsvOutputFormatter.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Entities.DataTransferObjects;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;

namespace CompanyEmployees
{
    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(CompanyDto).IsAssignableFrom(type) || typeof(IEnumerable<CompanyDto>).IsAssignableFrom(type)) { return base.CanWriteType(type); }
            return false;
        }
        public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
        {
            var response = context.HttpContext.Response;
            var buffer = new StringBuilder();
            if (context.Object is IEnumerable<CompanyDto>)
            {
                foreach (var company in (IEnumerable<CompanyDto>)context.Object)
                { FormatCsv(buffer, company); }
            }
            else
            {
                FormatCsv(buffer, (CompanyDto)context.Object);
            }
            await response.WriteAsync(buffer.ToString());
        }
        private static void FormatCsv(StringBuilder buffer, CompanyDto company)
        {
            buffer.AppendLine($"{company.Id},\"{company.Name},\"{company.FullAddress}\"");
        }
    }
}

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 CompanyDto type can be written by this serializer.
    方法被重写,它指示此序列化程序是否可以写入 CompanyDto 类型。WriteResponseBodyAsync 方法构造响应。

  • The WriteResponseBodyAsync method constructs the response.
    WriteResponseBodyAsync 方法构造响应。

  • And finally, we have the FormatCsv method that formats a response the way we want it.
    最后,我们有 FormatCsv 方法,它可以按照我们想要的方式格式化响应。

The class is pretty straightforward to implement, and the main thing that you should focus on is the FormatCsvmethod logic.
该类的实现非常简单,您应该关注的主要内容是 FormatCsvmethod 逻辑。

Now we just need to add the newly made formatter to the list of OutputFormatters in the ServicesExtensions class:
现在我们只需要将新制作的格式化程序添加到 ServicesExtensions 类中的 OutputFormatters 列表中:

public static IMvcBuilder AddCustomCSVFormatter(this IMvcBuilder builder) => 
    builder.AddMvcOptions(config => config.OutputFormatters.Add(new CsvOutputFormatter()));

And to call it in the AddControllers:
并在AddControllers中调用它:

services.AddControllers(config =>
{
    config.RespectBrowserAcceptHeader = true;
    config.ReturnHttpNotAcceptable = true;
}).AddXmlDataContractSerializerFormatters()
.AddCustomCSVFormatter();

Let’s run this and see if it actually works. This time we will put text/csv as the value for the Accept header:
让我们运行它,看看它是否真的有效。这次我们将把text/csv 作为接受标头的值:

https://localhost:5001/api/companies

Alt text

Well, what do you know, it works!
好吧,你知道什么,它有效!

In this chapter, we finished working with GET requests in our project and we are ready to move on to the POST PUT and DELETE requests. We have a lot more ground to cover, so let’s get down to business.
在本章中,我们完成了项目中的GET请求,我们准备继续处理POST PUT和DELETE请求。我们还有很多事情要做,所以让我们开始做正题。

发表评论