WebApi自定义BufferedMediaTypeFormatter的WriteToStream未触发问题
看起来你已经把自定义Formatter注册到了Web API配置里,而且CanWriteType也返回了true,但WriteToStream就是不执行,这种情况通常是Web API的格式化选择逻辑里有匹配冲突,或者你的Formatter配置有遗漏。我帮你梳理几个关键问题点和修复方案:
1. 媒体类型匹配冲突:SupportedMediaTypes未包含响应的ContentType
你的FileMediaFormatter构造函数里只添加了text/html作为支持的媒体类型,但在SetDefaultContentHeaders里,你根据fType把响应的ContentType改成了application/vnd.openxmlformats-officedocument.spreadsheetml.sheet(Excel)或text/plain(文本)。
Web API选择Formatter时,会检查Formatter的SupportedMediaTypes是否包含请求Accept头指定的类型,或者响应要输出的媒体类型。如果你的Formatter的SupportedMediaTypes里没有这些Excel/文本的媒体类型,即使CanWriteType返回true,Web API也会认为这个Formatter无法处理该媒体类型,跳过执行WriteToStream。
修复方案:在Formatter的构造函数里添加所有你要支持的媒体类型:
public FileMediaFormatter() { SupportedMediaTypes.Add(new MediaTypeHeaderValue(SupportedMediaType)); // text/html SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")); SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain")); }
2. 客户端Angular的请求Accept头不匹配
Web API会优先根据客户端发送的Accept请求头来选择Formatter。如果你的Angular请求里的Accept头是默认的application/json,而你的Formatter支持的类型里没有这个,Web API会优先选择JsonFormatter,而不是你的自定义Formatter。
修复方案:在Angular发起导出请求时,设置Accept头为你的Formatter支持的类型,比如:
this.http.get('/api/export', { headers: new HttpHeaders().set('Accept', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'), responseType: 'blob' }).subscribe(blob => { // 处理下载逻辑 });
3. 避免使用HttpContext.Current(非直接原因,但会导致潜在问题)
你在WriteToStream和SetDefaultContentHeaders里用了HttpContext.Current.Request来获取查询参数,这在Web API的异步环境下可能会出现上下文丢失的问题,建议改用Web API的消息对象来获取请求参数:
// 在WriteToStream里获取fType var queryParams = content.RequestMessage.GetQueryNameValuePairs(); var fTypeObj = queryParams.FirstOrDefault(q => q.Key.Equals("fType", StringComparison.OrdinalIgnoreCase)).Value; // 在SetDefaultContentHeaders里可以通过headers.RequestMessage来获取请求 var queryParams = headers.RequestMessage.GetQueryNameValuePairs(); var fTypeObj = queryParams.FirstOrDefault(q => q.Key.Equals("fType", StringComparison.OrdinalIgnoreCase)).Value;
4. 确认控制器返回的是实体类型而非HttpResponseMessage
如果你的控制器返回的是HttpResponseMessage,Web API不会自动应用Formatter,需要你手动指定或者直接返回要导出的实体集合(比如IEnumerable<T>)。例如:
// 正确的写法:直接返回实体集合 public IHttpActionResult GetExportData() { var exportData = _service.GetDataToExport(); return Ok(exportData); } // 如果用HttpResponseMessage,需要手动指定Formatter public HttpResponseMessage GetExportData() { var exportData = _service.GetDataToExport(); var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new ObjectContent<IEnumerable<YourEntity>>(exportData, new FileMediaFormatter()) }; return response; }
5. 检查CanWriteType的逻辑是否存在隐性问题
虽然你说CanWriteType返回true,但可以加日志确认传入的type是否正确。比如,当返回的是List<T>时,type.GetGenericArguments()[0].IsClass是否真的返回true?如果T是值类型,这个条件会返回false,但你说CanWriteType返回true,所以这个可能性低,但可以加日志排查:
public override bool CanWriteType(Type type) { // 添加日志记录type.FullName Debug.WriteLine($"CanWriteType called for type: {type.FullName}"); if (ExportableResolver.Instance.Value.CanConvert(type)) { Debug.WriteLine($"CanConvert returned true for {type.FullName}"); return true; } if (!type.IsGenericType) { Debug.WriteLine($"{type.FullName} is not generic, returning false"); return false; } var arguments = type.GetGenericArguments(); if (arguments.Length != 1) { Debug.WriteLine($"{type.FullName} has {arguments.Length} generic args, returning false"); return false; } var ienumType = typeof(IEnumerable<>).MakeGenericType(arguments[0]); if (!ienumType.IsAssignableFrom(type)) { Debug.WriteLine($"{type.FullName} is not assignable to IEnumerable<{arguments[0].FullName}>, returning false"); return false; } var isClass = arguments[0].IsClass; Debug.WriteLine($"{arguments[0].FullName} is class: {isClass}"); return isClass; }
按照上面的步骤排查,应该能解决WriteToStream未被调用的问题。
内容的提问来源于stack exchange,提问作者mashta gidi

