You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 18:03:20