ASP.NET 8中如何正确将ResultOrUnprocessable替换为FileResult?
问题:ASP.NET 8中如何让自定义Either类型
ResultOrUnprocessable<,>成功时正确返回FileResult 场景与代码
在ASP.NET 8控制器中,我定义了类似Either的自定义类型ResultOrUnprocessable<,>,并编写了返回该类型的方法:
[HttpPost] public ResultOrUnprocessable<FileResult, SomeErrors> GetFileOrError() { return File([2, 3, 5, 6, 2], MediaTypeNames.Application.Octet); }
我期望当ResultOrUnprocessable.IsSuccess为true时,ASP.NET能将其当作直接返回FileResult来处理,因此实现了IOutputFormatter:
public class ResultOrUnprocessableOutputFormatter : IOutputFormatter { private static readonly PropertyInfo ObjectPropertyInfo; private static readonly PropertyInfo ObjectTypePropertyInfo; static ResultOrUnprocessableOutputFormatter() { var t = typeof(OutputFormatterCanWriteContext); ObjectPropertyInfo = t.GetProperty("Object") ?? throw new MissingMemberException(); ObjectTypePropertyInfo = t.GetProperty("ObjectType") ?? throw new MissingMemberException(); } public bool CanWriteResult(OutputFormatterCanWriteContext context) { if (context.Object is not IResultOrUnprocessable resultOrUnprocessable) { return false; } if (resultOrUnprocessable.IsSuccess) { // 尝试将ResultOrUnprocessable<,>替换为FileResult ObjectPropertyInfo.SetValue(context, resultOrUnprocessable.ResultObject); ObjectTypePropertyInfo.SetValue(context, resultOrUnprocessable.ResultObject?.GetType()); return false; } return true; } public async Task WriteAsync(OutputFormatterWriteContext context) { if (context.Object is not IResultOrUnprocessable resultOrUnprocessable) { throw new InvalidCastException(); } var response = context.HttpContext.Response; if (!resultOrUnprocessable.IsSuccess) { var serialized = JsonSerializer.Serialize(resultOrUnprocessable.UnprocessableInfoObject); response.StatusCode = StatusCodes.Status422UnprocessableEntity; response.ContentType = MediaTypeNames.Application.Json; await response.WriteAsync(serialized); } else { throw new UnreachableException(); } } }
在Program.cs中的配置:
services.AddControllers(options => { options.OutputFormatters.RemoveType<StringOutputFormatter>(); // 返回字符串或整数时返回JSON而非纯文本 options.OutputFormatters.Insert(0, new ResultOrUnprocessableOutputFormatter()); })
异常现象
实际运行时,FileResult并未以正常的application/octet-stream类型返回,而是被序列化为JSON格式:
{ "filecontents": "AgMFBgI=", "contenttype": "application/octet-stream", "filedownloadname": "", "lastmodified": null, "entitytag": null, "enablerangeprocessing": false }
请问如何正确将ResultOrUnprocessable<FileResult, SomeErrors>替换为FileResult?
解决方案
问题核心:直接修改OutputFormatterCanWriteContext的属性无法触发ASP.NET内置的FileResult处理逻辑,因为格式化器选择流程已完成,属性修改不会重新触发选择。以下是两种可行方案:
方案1:实现IResultFilter(推荐)
通过结果过滤器在格式化流程前替换结果对象,让ASP.NET后续直接处理FileResult:
public class ResultOrUnprocessableResultFilter : IResultFilter { public void OnResultExecuting(ResultExecutingContext context) { if (context.Result is ObjectResult objectResult && objectResult.Value is IResultOrUnprocessable resultOrUnprocessable) { if (resultOrUnprocessable.IsSuccess && resultOrUnprocessable.ResultObject is IResult successResult) { // 替换为实际的IResult(FileResult实现了IResult) context.Result = successResult; } else if (!resultOrUnprocessable.IsSuccess) { // 处理失败场景,直接返回422和错误JSON var json = JsonSerializer.Serialize(resultOrUnprocessable.UnprocessableInfoObject); context.Result = new ContentResult { Content = json, ContentType = MediaTypeNames.Application.Json, StatusCode = StatusCodes.Status422UnprocessableEntity }; } } } public void OnResultExecuted(ResultExecutedContext context) { // 无需额外处理 } }
在Program.cs中注册过滤器(替换原有的OutputFormatter配置):
services.AddControllers(options => { options.OutputFormatters.RemoveType<StringOutputFormatter>(); options.Filters.Add<ResultOrUnprocessableResultFilter>(); })
方案2:让ResultOrUnprocessable<,>实现IResult
直接让自定义类型实现IResult接口,自行处理成功/失败的响应逻辑:
public class ResultOrUnprocessable<TResult, TError> : IResultOrUnprocessable, IResult where TResult : IResult { public bool IsSuccess { get; } public TResult? ResultObject { get; } public TError? UnprocessableInfoObject { get; } // 保留原有构造函数及逻辑... public async Task ExecuteAsync(HttpContext httpContext) { if (IsSuccess && ResultObject != null) { await ResultObject.ExecuteAsync(httpContext); } else if (!IsSuccess && UnprocessableInfoObject != null) { httpContext.Response.StatusCode = StatusCodes.Status422UnprocessableEntity; httpContext.Response.ContentType = MediaTypeNames.Application.Json; await JsonSerializer.SerializeAsync(httpContext.Response.Body, UnprocessableInfoObject); } } }
此方式无需额外配置OutputFormatter或过滤器,ASP.NET会直接调用ExecuteAsync处理响应。
内容的提问来源于stack exchange,提问作者Lavshyak
相关产品推荐
相关产品推荐

