如何在ASP.NET核心2.0中实现内容协商


有关本文的完整和最新版本,请访问:https://code-maze.com/content-negotiation-dotnet-core/

内容协商是其中一项生活质量改进,您可以将其添加到睡觉应用编程接口中,使其更加用户友好和灵活。当我们设计API时,这不是我们首先想要实现的吗?

在设计睡觉应用编程接口时,有很多事情需要牢记,我们最近在我们的Top REST API best practices article。内容协商是一个HTTP特性,它已经存在了一段时间,但由于这样或那样的原因,它可能还没有得到充分利用。

简而言之,内容协商让您选择或者更确切地说是“协商”您想要获取的内容,以响应睡觉接口请求。如果您想了解内容协商在幕后是如何运作的,您可以下载我们的Complete Guide to HTTP Book 免费,并在高级功能部分查找它。

今天,我们将介绍ASP.NET Core中的内容协商实现。

你能从盒子里拿到什么?

默认情况下,ASP.NET Core Web API返回JSON格式的结果。

让我们创建一个默认的Web API项目并删除默认的ValuesController。相反,我们将使用一种方法制作我们自己的控制器(使用21点和妓女),即BlogController:

[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);
}
}

关于这个简单的示例,需要注意的事项是:

  • 我们使用两个类:Blog BlogPosts 创建要作为响应对象返回的对象
  • 我们利用ASP.NET Core提供的IActionResult接口作为我们的方法可能具有的不同类型响应的泛型返回类型
  • 对象创建逻辑在控制器中。您不应该这样实现控制器;这只是为了简单起见。
  • 我们使用OK帮助器方法返回结果,该方法返回对象和状态代码200OK

如何使用邮递员测试您的API

Postman是一个不错的小工具,您可以使用它轻松地测试您的API。现在,让我们尝试使用Postman调用该方法,看看我们会得到什么响应。

您可以清楚地看到,调用Get On时的默认结果/api/blog返回我们的JSON结果。你们这些眼睛敏锐的人甚至可能已经注意到我们使用了接受标头尝试强制服务器返回其他媒体类型,如纯文本和XML。

但那不管用。为什么?

因为我们需要配置服务器格式化程序来按照我们想要的方式格式化响应。

让我们看看如何做到这一点。

更改我们项目的默认配置

服务器不显式指定将响应格式化到JSON的位置。但是,您可以通过更改配置选项来重写它。AddMvc方法选项。默认情况下,它看起来如下所示:

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

我们可以添加以下选项,以使服务器能够在客户端尝试协商XML响应时对其进行格式化。

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());
});
}

首先,我们必须告诉服务器尊重Accept标头。之后,我们可以添加XML格式化程序来启用XML格式化.MvcXmlSerializerOutputFormatter 并且都是Microsoft.AspNetCore.Mvc.Formatters, 因此,我们需要添加对该库的引用。

现在我们已经配置了服务器,让我们再次测试内容协商。

测试内容协商

现在让我们看看如果我们通过Postman发出相同的请求会发生什么。

这就是我们的XML响应。

那很容易,不是吗?

现在,通过将Accept标头从文本/XMLtext/json,我们可以得到不同格式的回复,这很棒,你不同意吗?

好的,这很好,很容易。

但是,如果尽管具有所有这些灵活性,但是如果客户端请求的媒体类型服务器不知道如何格式化,该怎么办呢?

限制媒体类型

目前,它将默认为JSON类型。

但您可以通过向配置添加一行来限制此行为。

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

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

我们添加了ReturnHttpNotAcceptable = true 选项,它告诉服务器,如果客户端试图协商服务器不支持的媒体类型,它应该返回406不可接受状态代码。

这将使您的应用程序更加严格,并强制API使用者仅请求服务器支持的类型。406状态代码就是为此目的而创建的。您可以在我们的Complete Guide to HTTP book,或者如果您想要更深入地了解,您可以查看RFC2616.

现在,让我们尝试获取文本/CSS使用邮递员查看发生的情况的媒体类型。

正如预期的那样,没有响应体,我们得到的只是一个漂亮的406不可接受状态代码。

到目前一切尚好。

更多关于ForMatters的信息

假设您正在公开一个睡觉API,并且它需要支持不是“现成的”类型的内容协商。尽管这种情况可能很少见,但您需要有一种机制来实现这一点。

那么,你怎么能做到这一点呢?

ASP.NET Core支持创建自定义格式化程序。它们的目的是让您可以灵活地为您需要支持的任何媒体类型创建自己的格式化程序。

我们可以使用以下方法制作自定义格式化程序:

  • 创建继承TextOutputForMatter类的输出格式化程序类
  • 创建继承TextInputFormatter类的输入格式化程序类
  • 将输入和输出类添加到InputFormaters和OutputFormaties集合,方法与我们对XML格式化程序所做的操作相同

现在,让我们来享受一些乐趣,并为我们的示例实现一个自定义的CSV格式化程序。

实现自定义格式化程序

因为在本文中我们只对格式化响应感兴趣,所以我们只需要实现一个输出格式化程序。只有当请求主体包含相应的类型时,我们才需要输入格式化程序。

我们的想法是格式化响应,以CSV格式返回博客列表及其相应的博客帖子列表。

让我们将CsvOutputForMatter类添加到我们的项目中。

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}\"");
}
}
}

这里有几点需要注意:

  • 在构造函数中,我们定义此格式化程序应该解析哪种媒体类型以及编码
  • CanWriteType 方法被重写,并且它指示博客类型可以由此序列化程序写入。
  • WriteResponseBodyAsync 构造响应的方法
  • 最后,我们还有FormatCsv 方法,以我们想要的方式格式化响应。

该类的实现相当简单,您应该关注的主要内容是FormatCsv 方法逻辑。

现在,我们只需要将新创建的格式化程序添加到AddMvcOptions中的OutputFormaters列表中。

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());
});
}

现在让我们运行一下,看看它是否真的起作用了。这一次我们将把文本/CSV作为Accept标头的值。

嗯,你知道吗,它起作用了!

由于我们的示例中只有一个博客和一篇博客帖子,因此响应中只有一行。

你可以玩耍source code若要查看添加更多博客和博客帖子时会发生什么情况,请执行以下操作。

有一个很棒的主页是关于custom formatters in ASP.NET Core如果你想了解更多关于它们的信息。您也可以退房。the implementation of the input and output formatters for the vcard content type如果你需要更多的例子。

以编程方式使用API

到目前为止,我们已经使用Postman处理了这个示例。但是,我觉得您需要尝试使用我们这里描述的内容协商方式消费一些睡觉API,您可以通过编程的方式发出一些请求,而不是使用第三方工具。

为此,我们已经制定了a few great ways to consume RESTful API。您可以找到.NET提供的一些使用任何睡觉应用编程接口的最佳工具。请务必查看并尝试使用一些API。

结论

在这篇博客文章中,我们介绍了ASP.NET核心项目中内容协商机制的具体实现。我们已经了解了格式化程序和如何定制格式化程序,以及如何在您的项目配置中设置格式化程序。

我们还学习了如何将应用程序限制为仅限于某些内容类型,而不接受任何其他内容类型。

您现在应该可以使用内容协商来设计和消费睡觉API。这真的是一个很好的机制,我们有很好的工具可以很容易地在我们的项目中实现它。所以,没有任何借口!

如果您想尝试一下源代码,可以在这里找到:Download source code from GitHub

感谢您的阅读,请在评论区留言。