ASP.NET Core MVC — IActionResult执行(2) 如何自定义一个OutputFormatter

在之前的IActionResult与执行的工作原理笔记中,我们直接新建了一个Utf8ForExcelCsvResult类来返回一个Csv文件。但是这种代码有个局限性——无法动态返回不同格式的数据。不妨考虑我们有一个Action方法,调用后返回一个列表:

1
2
3
4
5
6
7
8
public IActionResult Privacy()
{
var records = new List<Foo> {
new Foo { Id = 1, Name = "你好" },
new Foo { Id = 2, Name = "こんにちは" },
};
return new ObjectResult(records);
}

通常情况下,我们希望它能返回JSON。但是我们希望这个方法不只是简单地把结果转换成JSON,而是可以在不改代码的前提下,根据需要,有选择地生成CSV格式的数据。

这种需求可以通过新建一个CsvOutputFormatter(参见我的SO回答):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class CsvOutputFormatter : TextOutputFormatter
{
private readonly UTF8Encoding _encoding;

public CsvOutputFormatter()
{
_encoding = new UTF8Encoding(true);
SupportedEncodings.Add(_encoding);
SupportedMediaTypes.Add(Microsoft.Net.Http.Headers.MediaTypeHeaderValue.Parse("text/csv"));
}

public override void WriteResponseHeaders(OutputFormatterWriteContext ctx )
{
var response = ctx.HttpContext.Response;
response.Headers.Add("Content-Disposition", $"attachment; filename=test.csv");
response.ContentType = "text/csv";
}

public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
var response = context.HttpContext.Response;
var preamble = _encoding.GetPreamble();
response.Body.Write(preamble, 0, preamble.Length);
using (var writer = context.WriterFactory(response.Body, _encoding))
{
var csv = new CsvWriter(writer);
csv.Configuration.HasHeaderRecord = true;
csv.WriteRecords((IEnumerable<object>)context.Object);
await writer.FlushAsync();
}
}

}

然后,我们需要把这个OutputFormatter注册到MvcOptions中:

1
2
3
services.AddMvc(o => {
o.OutputFormatters.Add(new CsvOutputFormatter());
});

最后,在请求时带上Accept: text/csv请求头即可。