.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, IResultFilter, IExceptionFilter
#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_GREATERpublic async ValueTask<object> InvokeAsync(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);}}
#endifprivate 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();
访问一个直接返回一个字符串的接口:
访问返回一个 IResult
的接口
访问返回一个 ResultModel
的 API
使用控制器 API 示例:
[Route("api/[controller]")]
public class ValuesController: ControllerBase
{[HttpGet("[action]")]public IActionResult Test(){return Ok(new { Name = "Amazing .NET" });}
}
API response 示例:
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