如何在 .Net Core 2.0 中使用内置 xml 或 json 格式化程序自定义接受标头值



更新: 我已经将一个小的测试项目上传到了github:链接

我正在使用 .Net Core 2 创建一个小型 Web 服务,并希望让客户端能够指定他们是否需要响应中的导航信息。Web API 应该只支持 xml 和 json,但如果客户端可以使用 接受:application/xml+hateoas 或 接受:application/json+hateoas 在他们的要求中。

我尝试像这样设置我的 AddMvc 方法:

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.RespectBrowserAcceptHeader = true;
options.ReturnHttpNotAcceptable = true;
options.FormatterMappings.SetMediaTypeMappingForFormat(
"xml", MediaTypeHeaderValue.Parse("application/xml"));
options.FormatterMappings.SetMediaTypeMappingForFormat(
"json", MediaTypeHeaderValue.Parse("application/json"));
options.FormatterMappings.SetMediaTypeMappingForFormat(
"xml+hateoas", MediaTypeHeaderValue.Parse("application/xml"));
options.FormatterMappings.SetMediaTypeMappingForFormat(
"json+hateoas", MediaTypeHeaderValue.Parse("application/json"));
})            
.AddJsonOptions(options => {
// Force Camel Case to JSON
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
})
.AddXmlSerializerFormatters()
.AddXmlDataContractSerializerFormatters()
;

我在我的控制器方法中使用 accept 标头来区分正常的 xml/json 响应和类似 hateoas 的响应,如下所示:

[HttpGet]
[Route("GetAllSomething")]
public async Task<IActionResult> GetAllSomething([FromHeader(Name = "Accept")]string accept)
{
...
bool generateLinks = !string.IsNullOrWhiteSpace(accept) && accept.ToLower().EndsWith("hateoas");
...
if (generateLinks)
{
AddNavigationLink(Url.Link("GetSomethingById", new { Something.Id }), "self", "GET");
}
...
}

因此,简而言之,我不想创建自定义格式化程序,因为唯一的"自定义"方法是在我的响应中包含或排除导航链接,但响应本身应该是基于 Accept 标头值的 xml 或 json。

我的模型类看起来像这样(其中主要是字符串和基本值):

[DataContract]
public class SomethingResponse
{
[DataMember]
public int Id { get; private set; }

从 Fiddler 调用我的服务时,我得到了不同接受值的以下结果:

  1. 接受:应用程序/json -> 状态代码 200,仅包含请求的数据。
  2. 接受:application/json+hateoas -> 状态代码 406(不可接受)。
  3. 接受:应用程序/xml -> 状态代码 504。[提琴手]ReadResponse() 失败:服务器未返回此请求的完整响应。服务器返回 468 字节。
  4. 接受:application/xml+hateoas -> 状态代码 406(不可接受)。

有人可以告诉我哪个设置是错误的吗?

格式到媒体类型的映射(SetMediaTypeMappingForFormat调用)无法按预期工作。此映射不使用请求中的Accept标头。它从路由数据或 URL 查询字符串中名为format的参数读取请求的格式。还应使用FormatFilter属性标记控制器或操作。有几篇关于基于FormatFilter属性的响应格式的好文章,请查看此处和此处。

要修复当前的格式映射,应执行以下操作:

  1. 重命名格式,使其不包含加号。特殊的+字符在传入URL时会给您带来麻烦。最好用-替换它:

    options.FormatterMappings.SetMediaTypeMappingForFormat(
    "xml-hateoas", MediaTypeHeaderValue.Parse("application/xml"));
    options.FormatterMappings.SetMediaTypeMappingForFormat(
    "json-hateoas", MediaTypeHeaderValue.Parse("application/json"));
    
  2. format参数添加到路由:

    [Route("GetAllSomething/{format}")]
    
  3. 无法
  4. 从标头中提取用于格式映射Accept格式,因此您将在 URL 中传递它。由于您需要知道控制器中逻辑的格式,因此您可以从路由到操作参数映射上述format以避免标头Accept重复:

    public async Task<IActionResult> GetAllSomething(string format)
    

    现在,您无需在标头中传递所需的格式Accept因为格式将从请求 URL 映射。

  5. 使用FormatFilter属性标记控制器或操作。

    最后的行动:

    [HttpGet]
    [Route("GetAllSomething/{format}")]
    [FormatFilter]
    public async Task<IActionResult> GetAllSomething(string format)
    {
    bool generateLinks = !string.IsNullOrWhiteSpace(format) && format.ToLower().EndsWith("hateoas");
    //  ...
    return await Task.FromResult(Ok(new SomeModel { SomeProperty = "Test" }));
    }
    

现在,如果您请求 URL/GetAllSomething/xml-hateoas(即使缺少Accept标头),FormatFilter会将formatxml-hateoas的值映射到application/xml,并且 XML 格式化程序将用于响应。请求的格式也可以在GetAllSomething操作的参数format访问。

GitHub 上具有格式化程序映射的示例项目

除了格式化程序映射之外,还可以通过将新的受支持媒体类型添加到现有媒体类型格式化程序来实现目标。支持的媒体类型存储在OutputFormatter.SupportedMediaTypes集合中,并填充在具体输出格式化程序的构造函数中,例如XmlSerializerOutputFormatter.您可以自己创建格式化程序实例(而不是使用AddXmlSerializerFormatters扩展调用),并将所需的媒体类型添加到SupportedMediaTypes集合中。要调整默认添加的 JSON 格式化程序,只需在options.OutputFormatters中找到它的实例:

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.RespectBrowserAcceptHeader = true;
options.ReturnHttpNotAcceptable = true;
options.InputFormatters.Add(new XmlSerializerInputFormatter());
var xmlOutputFormatter = new XmlSerializerOutputFormatter();
xmlOutputFormatter.SupportedMediaTypes.Add("application/xml+hateoas");
options.OutputFormatters.Add(xmlOutputFormatter);
var jsonOutputFormatter = options.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault();
jsonOutputFormatter?.SupportedMediaTypes.Add("application/json+hateoas");
})
.AddJsonOptions(options => {
// Force Camel Case to JSON
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
})
.AddXmlDataContractSerializerFormatters();
}

在这种情况下GetAllSomething应该与您原始问题中的相同。您还应该在标头Accept传递所需的格式,例如Accept: application/xml+hateoas.

GitHub 上具有自定义媒体类型的示例项目

相关内容

  • 没有找到相关文章

最新更新