为什么这个自定义格式化程序适用于单个资源而不适用于集合?

时间:2018-01-05 20:29:23

标签: .net asp.net-core

我正在为一系列微服务创建一个概念证明,目前我正致力于内容协商。

我在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的请求将返回accept标头中指定的内容。这只是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}");
  }
}

1 个答案:

答案 0 :(得分:1)

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

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

根据您要返回的收集类型,您必须确保检查返回true。一种方法是检查类型是否可分配给IEnumerable<TodoItem>

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

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

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