【问题标题】:Why does this custom formatter work for individual resources but not the collection?为什么此自定义格式化程序适用于单个资源而不适用于集合?
【发布时间】:2018-06-15 16:38:22
【问题描述】:

我正在为一组微服务创建概念验证,目前我正在处理内容协商。

我找到了Jeremy Likness 的一系列帖子,并开始查看他的demo 显示使用自定义格式化程序进行内容协商的帖子。

我从 GitHub 克隆了项目并运行了该项目。当我从 Postman 和 Insomnia 发送请求时,除了一个用例之外,我得到了预期的结果。问题是我无法为集合获得 CSV 格式的响应。

对于每个请求,我都会添加 accept 标头和适当的值(text/csvtext/xmltext/json)。

请求单个资源 (http://localhost:5000/api/todo/1) 时,使用自定义格式化程序将响应格式化为 CSV。这是预期的行为。但是,在请求集合 (http://localhost:5000/api/todo) 时,响应格式为 JSON。

我错过了什么?我在做什么来获取单个资源的 CSV 而不是集合的 JSON?包含accept: text/xmlaccept: text/json 的请求返回接受标头中指定的内容。这只是text/csv 的问题。

我在 Visual Studio 2017 和 VS Code 中运行代码,结果相同。

更新 #1 - 我在 CsvFormatter.WriteResponseBodyAsync() 上放置了一个断点。请求单个资源时会触发断点。请求集合时没有命中断点。

这里是startup.cs:

public class Startup
{       
  public void ConfigureServices(IServiceCollection services)
  {
    services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));
    services.AddMvc(options =>
    {
      options.RespectBrowserAcceptHeader = true;
      options.FormatterMappings.SetMediaTypeMappingForFormat("xml", MediaTypeHeaderValue.Parse("text/xml"));
      options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValue.Parse("text/json"));
      options.FormatterMappings.SetMediaTypeMappingForFormat("csv", MediaTypeHeaderValue.Parse("text/csv"));
      options.OutputFormatters.Add(new CsvFormatter());
    })
      .AddXmlSerializerFormatters();
  }

  public void Configure(IApplicationBuilder app)
  {
    app.UseMvc();
  }
}

这是自定义格式化程序:

public class CsvFormatter : TextOutputFormatter
{
  public CsvFormatter()
  {
    SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/csv"));
    SupportedEncodings.Add(Encoding.UTF8);
    SupportedEncodings.Add(Encoding.Unicode);
  }

  protected override bool CanWriteType(System.Type type)
  {
    return type == typeof(TodoItem);
  }

  public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
  {
    var response = context.HttpContext.Response;
    var buffer = new StringBuilder();
    if (context.Object is IEnumerable<TodoItem>)
    {
      foreach(var todoItem in (IEnumerable<TodoItem>)context.Object)
      {
        FormatCsv(buffer, todoItem);
      }
    }
    else
    {
      FormatCsv(buffer, (TodoItem)context.Object);
    }
    using (var writer = context.WriterFactory(response.Body, selectedEncoding))
    {
      return writer.WriteAsync(buffer.ToString());
    }
  }

  private static void FormatCsv(StringBuilder buffer, TodoItem item)
  {
    buffer.AppendLine($"{item.Id},\"{item.Name}\",{item.IsComplete}");
  }
}

【问题讨论】:

  • 返回类型 == typeof(TodoItem) || typeof(IEnumerable)??

标签: .net asp.net-core


【解决方案1】:
protected override bool CanWriteType(System.Type type)
{
    return type == typeof(TodoItem);
}

显然,这使您的格式化程序仅在返回 TodoItem 类型的实体时运行。如果您希望格式化程序仅针对 TodoItem 的集合运行,则必须在此处进行检查。

根据您返回的集合类型,您必须确保检查返回true。一种方法是检查类型是否可分配给IEnumerable&lt;TodoItem&gt;

return typeof(TodoItem).IsAssignableFrom(type) ||
    typeof(IEnumerable<TodoItem>).IsAssignableFrom(type);

这样,您可以返回数组、列表和任何其他类型的可枚举。


顺便说一句。查看您的实现,您是否有理由使用 StringBuilder 作为缓冲区?您可以直接写入输出流。

【讨论】:

  • 关于StringBuilder的使用,我不是原代码的作者。我在研究如何解决一些内容协商问题时找到了代码。感谢您的建议,我会在自己的实现中对此进行研究。
猜你喜欢
  • 1970-01-01
  • 2013-06-23
  • 2012-01-24
  • 1970-01-01
  • 1970-01-01
  • 2021-06-18
  • 1970-01-01
  • 2013-01-07
  • 1970-01-01
相关资源
最近更新 更多