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();