.NET 7 中的 HttpResult 接口

.NET 7 中的 HttpResult 接口

Intro

在前面的文章中,我们提到了 .NET 7 引入了 Endpoint Filter 来支持 Endpoint 的过滤器,有了这个接口就想着把之前的统一 API response 的 filter 改造一下支持 endpoint filter,然而这个一直等到了 .NET 7 Preview 7 才得以实现,在 .NET 7 Preview 7 中引入了一些接口使得我们可以匹配 HttpResult 的类型

HttpResult Interface

.NET 6 开始引入了 Minimal API, 但是要匹配一系列 IResult 相关的 response 比如 Results.Ok, Results.NotFound 会比较困难,在 .NET 7 有了一些改进

在 .NET 7 Preview 3 中,出于方便单元测试的考虑,开放了一些 HttpResult 的类型比如:OkObjectHttpResult/NotFoundHttpResult 等,但是始终没有一个类似于 MVC 里的 ObjectResult 一样的类型,使得我们如果想要匹配 response 的类型就会很麻烦,终于在 .NET 7 Preview 7,引入了一系列的接口,我们可以通过这些接口进行模式匹配来获取这些 HttpResult 中的 Value/StatusCode 等等,新增加的接口如下:

Microsoft.AspNetCore.Http.IContentTypeHttpResult
Microsoft.AspNetCore.Http.IFileHttpResult
Microsoft.AspNetCore.Http.INestedHttpResult
Microsoft.AspNetCore.Http.IStatusCodeHttpResult
Microsoft.AspNetCore.Http.IValueHttpResult
Microsoft.AspNetCore.Http.IValueHttpResult<TValue>

可以参考 PR:https://Github.com/dotnet/aspnetcore/pull/42385/files

如果我们想要匹配 response 的返回值就可以使用 IValueHttpResult 来匹配,比如:

if (result is IValueHttpResult valueHttpResult)
    return valueHttpResult.Value;

也可以使用 IStatusCodeHttpResult 来匹配 response status

if (result is IStatusCodeHttpResult statusCodeResult)
    return statusCodeResult.StatusCode;

ApiResultFilter

实现了一个简单的统一 response 的 ApiResultFilter ,在原来的基础上增加了 EndpointFilter 的支持,实现如下:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class ApiResultFilter : Attribute
    , IResultFilterIExceptionFilter
#if NET7_0_OR_GREATER
    , IEndpointFilter
#endif

{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is ObjectResult { Value: not Result } objectResult)
        {
            var result = new Result<object>()
            {
                Data = objectResult.Value,
                Status = HttpStatusCode2ResultStatus(objectResult.StatusCode)
            };
            objectResult.Value = result;
        }
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
    }

    public void OnException(ExceptionContext context)
    {
        var result = Result.Fail(context.Exception.ToString(), ResultStatus.ProcessFail);
        context.Result = new ObjectResult(result) { StatusCode = 500 };
    }
#if NET7_0_OR_GREATER

    public async ValueTask<objectInvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
    {
        try
        {
            var result = await next(context);
            if (result is Result or ObjectResult { Value: Result } or IValueHttpResult { Value: Result })
            {
                return result;
            }

            if (result is ObjectResult { Value: not Result } objectResult)
            {
                return new Result<object>()
                {
                    Data = objectResult.Value, Status = HttpStatusCode2ResultStatus(objectResult.StatusCode)
                };
            }

            if (result is IValueHttpResult { Value: not Result } valueHttpResult)
            {
                var status = result is IStatusCodeHttpResult statusCodeHttpResult
                    ? HttpStatusCode2ResultStatus(statusCodeHttpResult.StatusCode)
                    : HttpStatusCode2ResultStatus(200);
                return new Result<object>() { Data = valueHttpResult.Value, Status = status };
            }

            return new Result<object>()
            {
                Data = result, Status = HttpStatusCode2ResultStatus(context.HttpContext.Response.StatusCode)
            };
        }
        catch (Exception ex)
        {
            return Result.Fail(ex.ToString(), ResultStatus.ProcessFail);
        }
    }
#endif

    private static ResultStatus HttpStatusCode2ResultStatus(int? statusCode)
    {
        statusCode ??= 200;
        var status = ResultStatus.Success;
        if (Enum.IsDefined(typeof(ResultStatus), statusCode.Value))
        {
            status = (ResultStatus)statusCode.Value;
        }

        if (status == ResultStatus.None)
        {
            status = ResultStatus.Success;
        }

        return status;
    }
}

下面看一个使用的示例:

var app = WebApplication.Create(args);
app.Map("/Hello", () => "Hello Minimal API!")
    .AddEndpointFilter<ApiResultFilter>();
app.Map("/HelloV3", () => Results.Ok(new { Name = "test" }))
    .AddEndpointFilter<ApiResultFilter>();
app.Map("/HelloV4", () => Results.Ok(Result.Success(new { Name = "test" })))
    .AddEndpointFilter<ApiResultFilter>();
await app.RunAsync();

访问一个直接返回一个字符串的接口:

.NET 7 中的 HttpResult 接口

访问返回一个 IResult 的接口

.NET 7 中的 HttpResult 接口

访问返回一个 ResultModel 的 API

.NET 7 中的 HttpResult 接口

使用控制器 API 示例:

[Route("api/[controller]")]
public class ValuesControllerControllerBase
{
    [HttpGet("[action]")]
    public IActionResult Test()
    {
        return Ok(new { Name = "Amazing .NET" });
    }
}

API response 示例:

.NET 7 中的 HttpResult 接口

References

  • https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-7-preview-7/#new-httpresults-interfaces
  • https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-7-preview-3/#improved-unit-testability-for-minimal-route-handlers
  • https://github.com/dotnet/aspnetcore/issues/41470
  • https://github.com/dotnet/aspnetcore/issues/42187
  • https://github.com/dotnet/aspnetcore/pull/42385/files
  • https://github.com/WeihanLi/WeihanLi.Web.Extensions/tree/dev/samples/WeihanLi.Web.Extensions.Samples


原文始发于微信公众号(amazingdotnet):.NET 7 中的 HttpResult 接口

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/59675.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!