From e2d27905b170a27515b856617a74e01a61cdd440 Mon Sep 17 00:00:00 2001 From: xljiulang <366193849@qq.com> Date: Sat, 22 Jun 2024 23:04:13 +0800 Subject: [PATCH 1/2] =?UTF-8?q?#261=20=E7=9A=84=E4=B8=80=E7=A7=8D=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WebApiClientCore.Abstractions/IApiFilter.cs | 15 +++++ .../RequestDelegate.cs | 11 ++++ .../Implementations/ApiPipeBuilder.cs | 60 +++++++++++++++++ .../Implementations/ApiRequestExecutor.cs | 66 ++++++++++--------- .../DefaultApiActionInvoker.cs | 22 +++++-- .../DefaultApiActionInvokerProvider.cs | 16 ++++- 6 files changed, 150 insertions(+), 40 deletions(-) create mode 100644 WebApiClientCore.Abstractions/RequestDelegate.cs create mode 100644 WebApiClientCore/Implementations/ApiPipeBuilder.cs diff --git a/WebApiClientCore.Abstractions/IApiFilter.cs b/WebApiClientCore.Abstractions/IApiFilter.cs index 4a85352a..0c1d4f89 100644 --- a/WebApiClientCore.Abstractions/IApiFilter.cs +++ b/WebApiClientCore.Abstractions/IApiFilter.cs @@ -7,6 +7,21 @@ namespace WebApiClientCore /// public interface IApiFilter { + /// + /// 执行请求 + /// TODO 是否要增加新接口?而不是接口默认实现 + /// + /// 请求上下文 + /// 请求委托 + /// + async Task ExecuteAsync(ApiRequestContext request, RequestDelegate next) + { + await this.OnRequestAsync(request).ConfigureAwait(false); + var response = await next(request).ConfigureAwait(false); + await this.OnResponseAsync(response).ConfigureAwait(false); + return response; + } + /// /// 请求前 /// diff --git a/WebApiClientCore.Abstractions/RequestDelegate.cs b/WebApiClientCore.Abstractions/RequestDelegate.cs new file mode 100644 index 00000000..a3ba74f7 --- /dev/null +++ b/WebApiClientCore.Abstractions/RequestDelegate.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace WebApiClientCore +{ + /// + /// 请求委托 + /// + /// + /// + public delegate Task RequestDelegate(ApiRequestContext request); +} diff --git a/WebApiClientCore/Implementations/ApiPipeBuilder.cs b/WebApiClientCore/Implementations/ApiPipeBuilder.cs new file mode 100644 index 00000000..3e90ee36 --- /dev/null +++ b/WebApiClientCore/Implementations/ApiPipeBuilder.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace WebApiClientCore.Implementations +{ + /// + /// 表示应用程序创建者 + /// + public class ApiPipeBuilder + { + private readonly RequestDelegate fallbackHandler; + private readonly List> middlewares = new(); + + /// + /// 应用程序创建者 + /// + /// 回退处理者 + public ApiPipeBuilder(RequestDelegate fallbackHandler) + { + this.fallbackHandler = fallbackHandler; + } + + /// + /// 创建处理应用请求的委托 + /// + /// + public RequestDelegate Build() + { + var handler = fallbackHandler; + for (var i = middlewares.Count - 1; i >= 0; i--) + { + handler = middlewares[i](handler); + } + return handler; + } + + + /// + /// 使用中间件 + /// + /// + /// + public ApiPipeBuilder Use(Func> middleware) + { + return Use(next => request => middleware(request, next)); + } + + /// + /// 使用中间件 + /// + /// + /// + public ApiPipeBuilder Use(Func middleware) + { + middlewares.Add(middleware); + return this; + } + } +} \ No newline at end of file diff --git a/WebApiClientCore/Implementations/ApiRequestExecutor.cs b/WebApiClientCore/Implementations/ApiRequestExecutor.cs index 792838ca..931ec14e 100644 --- a/WebApiClientCore/Implementations/ApiRequestExecutor.cs +++ b/WebApiClientCore/Implementations/ApiRequestExecutor.cs @@ -12,20 +12,46 @@ namespace WebApiClientCore.Implementations static class ApiRequestExecutor { /// - /// 执行上下文 + /// 创建请求委托 /// - /// 请求上下文 + /// /// - public static async Task ExecuteAsync(ApiRequestContext request) + public static RequestDelegate Build(ApiRequestContext request) { - await HandleRequestAsync(request).ConfigureAwait(false); - using var requestAbortedLinker = new CancellationTokenLinker(request.HttpContext.CancellationTokens); + var builder = new ApiPipeBuilder(async request => + { + // TODO 这里要缓存 requestAbortedLinker + using var requestAbortedLinker = new CancellationTokenLinker(request.HttpContext.CancellationTokens); + return await ApiRequestSender.SendAsync(request, requestAbortedLinker.Token).ConfigureAwait(false); + }); + + builder.Use(next => async request => + { + await HandleRequestAsync(request).ConfigureAwait(false); + var response = await next(request).ConfigureAwait(false); + await HandleResponseAsync(response).ConfigureAwait(false); + + // TODO 在这里释放 requestAbortedLinker 才正确 + + return response; + }); + + // GlobalFilter + foreach (var filter in request.HttpContext.HttpApiOptions.GlobalFilters) + { + builder.Use(filter.ExecuteAsync); + } + + // FilterAttribute + foreach (var filter in request.ActionDescriptor.FilterAttributes) + { + builder.Use(filter.ExecuteAsync); + } - var response = await ApiRequestSender.SendAsync(request, requestAbortedLinker.Token).ConfigureAwait(false); - await HandleResponseAsync(response).ConfigureAwait(false); - return response; + return builder.Build(); } + /// /// 处理请求上下文 /// @@ -61,18 +87,6 @@ private static async Task HandleRequestAsync(ApiRequestContext context) { await @return.OnRequestAsync(context).ConfigureAwait(false); } - - // GlobalFilter请求前执行 - foreach (var filter in context.HttpContext.HttpApiOptions.GlobalFilters) - { - await filter.OnRequestAsync(context).ConfigureAwait(false); - } - - // Filter请求前执行 - foreach (var filter in context.ActionDescriptor.FilterAttributes) - { - await filter.OnRequestAsync(context).ConfigureAwait(false); - } } /// @@ -109,18 +123,6 @@ private static async Task HandleResponseAsync(ApiResponseContext context) context.Exception = ex; } } - - // GlobalFilter请求后执行 - foreach (var filter in context.HttpContext.HttpApiOptions.GlobalFilters) - { - await filter.OnResponseAsync(context).ConfigureAwait(false); - } - - // Filter请求后执行 - foreach (var filter in context.ActionDescriptor.FilterAttributes) - { - await filter.OnResponseAsync(context).ConfigureAwait(false); - } } /// diff --git a/WebApiClientCore/Implementations/DefaultApiActionInvoker.cs b/WebApiClientCore/Implementations/DefaultApiActionInvoker.cs index 20c59f79..3356d1c2 100644 --- a/WebApiClientCore/Implementations/DefaultApiActionInvoker.cs +++ b/WebApiClientCore/Implementations/DefaultApiActionInvoker.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.Options; +using System; using System.Diagnostics; using System.Net.Http; using System.Runtime.ExceptionServices; @@ -15,6 +16,11 @@ namespace WebApiClientCore.Implementations [DebuggerDisplay("Member = {ActionDescriptor.Member}")] public class DefaultApiActionInvoker : ApiActionInvoker, IITaskReturnConvertable { + /// + /// 请求委托 + /// + private RequestDelegate? requestDelegate; + /// /// 获取Action描述 /// @@ -24,9 +30,11 @@ public class DefaultApiActionInvoker : ApiActionInvoker, IITaskReturnCo /// Action执行器 /// /// - public DefaultApiActionInvoker(ApiActionDescriptor actionDescriptor) + /// + public DefaultApiActionInvoker(ApiActionDescriptor actionDescriptor, IOptionsMonitor httpApiOptionsMonitor) { this.ActionDescriptor = actionDescriptor; + httpApiOptionsMonitor.OnChange(options => this.requestDelegate = null); } /// @@ -65,7 +73,7 @@ public virtual async Task InvokeAsync(HttpClientContext context, object var httpContext = new HttpContext(context, requestMessage); var requestContext = new ApiRequestContext(httpContext, this.ActionDescriptor, arguments, new DefaultDataCollection()); - return await InvokeAsync(requestContext).ConfigureAwait(false); + return await this.InvokeAsync(requestContext).ConfigureAwait(false); } catch (HttpRequestException) { @@ -89,10 +97,12 @@ public virtual async Task InvokeAsync(HttpClientContext context, object /// /// /// - private static async Task InvokeAsync(ApiRequestContext request) + private async Task InvokeAsync(ApiRequestContext request) { -#nullable disable - var response = await ApiRequestExecutor.ExecuteAsync(request).ConfigureAwait(false); + this.requestDelegate ??= ApiRequestExecutor.Build(request); // 不需要 lock,并发 build 也不影响结果 + var response = await this.requestDelegate(request).ConfigureAwait(false); + +#nullable disable if (response.ResultStatus == ResultStatus.HasResult) { return (TResult)response.Result; diff --git a/WebApiClientCore/Implementations/DefaultApiActionInvokerProvider.cs b/WebApiClientCore/Implementations/DefaultApiActionInvokerProvider.cs index d324f555..62ffbab2 100644 --- a/WebApiClientCore/Implementations/DefaultApiActionInvokerProvider.cs +++ b/WebApiClientCore/Implementations/DefaultApiActionInvokerProvider.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Options; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; namespace WebApiClientCore.Implementations @@ -8,6 +9,17 @@ namespace WebApiClientCore.Implementations /// public class DefaultApiActionInvokerProvider : IApiActionInvokerProvider { + private readonly IOptionsMonitor httpApiOptionsMonitor; + + /// + /// ApiActionInvoker提供者 + /// + /// + public DefaultApiActionInvokerProvider(IOptionsMonitor httpApiOptionsMonitor) + { + this.httpApiOptionsMonitor = httpApiOptionsMonitor; + } + /// /// 创建Action执行器 /// @@ -37,7 +49,7 @@ protected virtual ApiActionInvoker CreateDefaultActionInvoker(ApiActionDescripto { var resultType = actionDescriptor.Return.DataType.Type; var invokerType = typeof(DefaultApiActionInvoker<>).MakeGenericType(resultType); - return invokerType.CreateInstance(actionDescriptor); + return invokerType.CreateInstance(actionDescriptor, this.httpApiOptionsMonitor); } } } From cc17c2c34d476ed4a341565151aa4914ef10189c Mon Sep 17 00:00:00 2001 From: xljiulang <366193849@qq.com> Date: Sun, 23 Jun 2024 10:53:03 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E7=A1=AE=E4=BF=9Dfilter=E7=9A=84OnResponse?= =?UTF-8?q?Async=E5=9C=A8HandleResponseAsync=E4=B9=8B=E5=90=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Implementations/ApiRequestExecutor.cs | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/WebApiClientCore/Implementations/ApiRequestExecutor.cs b/WebApiClientCore/Implementations/ApiRequestExecutor.cs index 931ec14e..b0006738 100644 --- a/WebApiClientCore/Implementations/ApiRequestExecutor.cs +++ b/WebApiClientCore/Implementations/ApiRequestExecutor.cs @@ -28,24 +28,58 @@ public static RequestDelegate Build(ApiRequestContext request) builder.Use(next => async request => { await HandleRequestAsync(request).ConfigureAwait(false); + return await next(request).ConfigureAwait(false); + }); + + // GlobalFilter.OnRequestAsync + foreach (var filter in request.HttpContext.HttpApiOptions.GlobalFilters) + { + builder.Use(next => async request => + { + await filter.OnRequestAsync(request).ConfigureAwait(false); // 应该把 next 做为 OnRequestAsync 的参数,在OnRequestAsync里调用才正确 + return await next(request).ConfigureAwait(false); + }); + } + + // FilterAttribute.OnRequestAsync + foreach (var filter in request.ActionDescriptor.FilterAttributes) + { + builder.Use(next => async request => + { + await filter.OnRequestAsync(request).ConfigureAwait(false); // 应该把 next 做为 OnRequestAsync 的参数,在OnRequestAsync里调用才正确 + return await next(request).ConfigureAwait(false); + }); + } + + builder.Use(next => async request => + { var response = await next(request).ConfigureAwait(false); await HandleResponseAsync(response).ConfigureAwait(false); // TODO 在这里释放 requestAbortedLinker 才正确 - return response; }); - // GlobalFilter + // GlobalFilter.OnResponseAsync foreach (var filter in request.HttpContext.HttpApiOptions.GlobalFilters) { - builder.Use(filter.ExecuteAsync); + builder.Use(next => async request => + { + var response = await next(request).ConfigureAwait(false); + await filter.OnResponseAsync(response).ConfigureAwait(false); + return response; + }); } - // FilterAttribute + // FilterAttribute.OnResponseAsync foreach (var filter in request.ActionDescriptor.FilterAttributes) { - builder.Use(filter.ExecuteAsync); + builder.Use(next => async request => + { + var response = await next(request).ConfigureAwait(false); + await filter.OnResponseAsync(response).ConfigureAwait(false); + return response; + }); } return builder.Build();