.NET Core 2 MVC接口设Accept为text/csv时返回415错误求助
嘿,我来帮你搞定这个POST请求返回415 Unsupported Media Type的问题!你遇到的这个错误主要有两个核心原因:一是ASP.NET Core默认没配置能解析text/csv格式请求体的输入格式化器,二是框架本身也没有内置CSV输出的处理逻辑。下面一步步给你捋清楚解决方案:
先搞对请求头的用法(避免低级错误)
如果你只是希望接口返回CSV文件,但请求体还是JSON格式,那完全不需要设置Content-Type: text/csv!Content-Type是告诉后端「我的请求体是什么格式」,而Accept才是告诉后端「我想要什么格式的响应」。要是你错误地把Content-Type设为text/csv,但实际发的是JSON请求体,后端肯定懵圈,直接返回415。
只有当你的请求体真的是CSV格式时,才需要设置Content-Type: text/csv——这时候咱们再去配置输入格式化器。
配置CSV输出格式化器(实现返回CSV文件)
ASP.NET Core 2 MVC没有自带CSV输出的处理能力,所以得咱们自己写一个简单的格式化器:
1. 创建CSV输出格式化器类
using Microsoft.AspNetCore.Mvc.Formatters; using System.Reflection; using System.Text; public class CsvOutputFormatter : TextOutputFormatter { public CsvOutputFormatter() { SupportedMediaTypes.Add("text/csv"); SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.Unicode); } protected override bool CanWriteType(Type type) { // 这里根据你的实际返回类型调整,比如支持IEnumerable<T>或者特定模型 return typeof(IEnumerable).IsAssignableFrom(type); } public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { var response = context.HttpContext.Response; var buffer = new StringBuilder(); // 假设你返回的是IEnumerable<YourModel>,这里替换成你实际的模型类 var data = context.Object as IEnumerable<YourModel>; if (data == null) return; // 写CSV表头(用模型的属性名) var properties = typeof(YourModel).GetProperties(BindingFlags.Public | BindingFlags.Instance); buffer.AppendLine(string.Join(",", properties.Select(p => p.Name))); // 写数据行,注意处理包含逗号的字段(转义一下) foreach (var item in data) { var values = properties.Select(p => { var val = p.GetValue(item)?.ToString(); return val?.Contains(",") ? $"\"{val}\"" : val; }); buffer.AppendLine(string.Join(",", values)); } await response.WriteAsync(buffer.ToString(), selectedEncoding); } }
2. 在Startup.cs里注册这个格式化器
打开Startup.cs,找到ConfigureServices方法,把CSV格式化器加到MVC配置里:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => { // 添加咱们自定义的CSV输出格式化器 options.OutputFormatters.Add(new CsvOutputFormatter()); // 把csv后缀和text/csv媒体类型关联起来(可选,但方便路由里指定格式) options.FormatterMappings.SetMediaTypeMappingForFormat("csv", "text/csv"); }) .SetCompatibilityVersion(CompatibilityVersion.Version_2_2); // 对应你的.NET Core 2版本 }
如果需要接收CSV格式的请求体(添加输入格式化器)
要是你的POST请求体真的是CSV格式,那还得加一个输入格式化器,让后端能解析这个CSV内容:
1. 创建CSV输入格式化器类
using Microsoft.AspNetCore.Mvc.Formatters; using System.Reflection; using System.Text; public class CsvInputFormatter : TextInputFormatter { public CsvInputFormatter() { SupportedMediaTypes.Add("text/csv"); SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.Unicode); } protected override bool CanReadType(Type type) { // 支持解析为IEnumerable<YourModel>,替换成你实际的模型类 return typeof(IEnumerable<YourModel>).IsAssignableFrom(type); } public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) { using var reader = new StreamReader(context.HttpContext.Request.Body, encoding); var content = await reader.ReadToEndAsync(); var lines = content.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); if (lines.Length == 0) { return await InputFormatterResult.SuccessAsync(new List<YourModel>()); } var properties = typeof(YourModel).GetProperties(BindingFlags.Public | BindingFlags.Instance); var data = new List<YourModel>(); // 跳过第一行表头(如果你的CSV有表头的话,要是没有就去掉这个循环) for (int i = 1; i < lines.Length; i++) { var values = lines[i].Split(','); var item = Activator.CreateInstance<YourModel>(); for (int j = 0; j < properties.Length && j < values.Length; j++) { var value = values[j].Trim('"'); // 去掉转义的引号 if (!string.IsNullOrEmpty(value)) { properties[j].SetValue(item, Convert.ChangeType(value, properties[j].PropertyType)); } } data.Add(item); } return await InputFormatterResult.SuccessAsync(data); } }
2. 在Startup.cs里注册输入格式化器
还是在ConfigureServices的MVC配置里,加上输入格式化器:
services.AddMvc(options => { options.InputFormatters.Add(new CsvInputFormatter()); // 添加CSV输入格式化器 options.OutputFormatters.Add(new CsvOutputFormatter()); options.FormatterMappings.SetMediaTypeMappingForFormat("csv", "text/csv"); }) .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
最后调整控制器动作
确保你的控制器动作保留[Produces("application/json", "text/csv")]特性,并且返回对应的数据类型:
[HttpPost] [Produces("application/json", "text/csv")] public async Task<IActionResult> YourAction([FromBody] YourInputModel input) // 如果是接收CSV请求体,就改成 [FromBody] IEnumerable<YourModel> input { // 你的业务逻辑,获取要返回的数据 var resultData = await GetYourDataAsync(input); // 要是想直接触发文件下载,可以手动返回FileResult(可选,格式化器也能自动处理) if (Request.Headers["Accept"].Contains("text/csv")) { // 这里也可以用上面的格式化器逻辑生成CSV内容,或者直接让框架自动处理 return Ok(resultData); // 或者手动生成后返回下载: // var csvContent = GenerateCsv(resultData); // return File(Encoding.UTF8.GetBytes(csvContent), "text/csv", "export.csv"); } return Ok(resultData); }
这样调整之后,当你设置Accept: text/csv(请求体是JSON的话,Content-Type保持application/json),接口就会返回CSV格式的响应;如果确实需要发送CSV请求体,添加输入格式化器后也能正常解析,不会再出现415错误啦!
内容的提问来源于stack exchange,提问作者masteroleary

