C# WebApiClientCore原理剖析深入解读

B站影视 内地电影 2025-09-04 08:40 2

摘要:距离上次源码分享的时间,已经过去了大半年。这次终于我们又见面了。其实中途也写过一些文章,但全文审阅下来,发现学习价值不大,也就死在草稿箱了,另外主要有2个原因,一个是最近忙着我们团队的产品-意心ai的项目,还有一个是暂没找到比较好分享的源码(基本之前想分享的都

hello,大家好久不见,欢迎来到橙子老哥的分享时间,希望大家一起学习,一起进步。

欢迎加入.net意社区,第一时间了解我们的动态,文章第一时间分享至社区

添加橙子老哥微信加入官方微信群:1、前言

各位,好久不见,橙子老哥也是甚是想念。

距离上次源码分享的时间,已经过去了大半年。这次终于我们又见面了。其实中途也写过一些文章,但全文审阅下来,发现学习价值不大,也就死在草稿箱了,另外主要有2个原因,一个是最近忙着我们团队的产品-意心ai的项目,还有一个是暂没找到比较好分享的源码(基本之前想分享的都分享了一遍,包括Abp.vNext也做了全套视频系列)

闲来无事,日常啃码的时候,这不发现了比较好的源码,想给大家分享分享

开源地址:https://github.com/dotnetcore/WebApiClient

他是一个高性能高可扩展性的声明式http客户端库,用于做便捷的http请求

例如:我们只需定义个一个接口,就能直接拿这个接口进行发起请求:

[LoggingFilter]
[HttpHost("http://localhost:5000/")]
publicinterfaceIUserApi
{
[HttpGet("api/users/{id}")]
Task GetAsync(string id, CancellationToken token = default);

// POST application/json content
[HttpPost("api/users")]
Task PostJsonAsync([JsonContent] User user, CancellationToken token = default);

// POST application/xml content

Task PostXmlAsync([XmlContent] User user, CancellationToken token = default);

// POST x-www-form-urlencoded content

Task PostFormAsync([FormContent] User user, CancellationToken token = default);

// POST multipart/form-data content

Task PostFormDataAsync([FormDataContent] User user, FormDataFile avatar, CancellationToken token = default);
}
services.AddHttpApi
{
o.UseLogging = Environment.IsDevelopment;
o.HttpHost = new Uri("http://localhost:5000/");
});

直接使用接口发起请求

public classYourService
{
privatereadonly IUserApi userApi;
public YourService(IUserApi userApi)
{
this.userApi = userApi;
}

public async Task GetAsync
{
// 调用接口
var user = awaitthis.userApi.GetAsync(id:"id001");
...
}
}对于多方数据采集、微服务、Api调用等,非常方便,并且还更好集中管理,比起直接new一个不仅省事优雅太多,还不用关心其中性能、内存、设计等问题,http请求,通通交给他就行了

不觉得很奇怪吗? 为什么我们能直接依赖注入我们自己定义的接口,没有写实现,但却能直接用???还能直接发起请求,这么神奇,不得好好搞清楚,接下来就让我带大家一探究竟

老规矩,看源码,我们可以从入口开始,因为这个才是一切的开始

///
/// 添加HttpApi代理类到服务
///
///
///
///
public static IhttpClientBuilder AddHttpApiDynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] THttpApi>(
this IServiceCollection services) where THttpApi : class
{
var name = HttpApi.GetName(typeof(THttpApi));

services.AddWebApiClient;
services.NamedHttpApiType(name, typeof(THttpApi));
services.TryAddSingleton(typeof(HttpApiProvider));
services.TryAddTransient(serviceProvider =>
{
var httpApiProvider = serviceProvider.GetRequiredService
return httpApiProvider.CreateHttpApi(serviceProvider, name);
});

return services.AddHttpClient(name);
}

标标准准的添加扩展的方式,看起来也非常舒服

主要做了5件事

1. 获取接口类型的唯一名字

2. 添加内部核心的依赖注入

3. 添加·HttpApiProvider`依赖注入

4. 添加我们接口类型的依赖注入

5. 添加HttpClient

这里主要是在第三步和第三步比较巧妙,我们可以学习的到

我们可以根据使用情况进行反推,我们使用是通过一个依赖注入我们定义的接口来进行请求的,既然能依赖注入,那必须有他的实现在初始化的时候加入了服务容器中

这一步正对的第4步

var httpApiProvider = serviceProvider.GetRequiredService
return httpApiProvider.CreateHttpApi(serviceProvider, name);HttpApiProvider刚好就是第三步注入的泛型Api提供者,下一步使用了他的CreateHttpApi方法所以!一切的奥秘,就是在CreateHttpApi方法中,它为什么创建了我们接口的实现类型?

这个方法如下:

public THttpApi CreateHttpApi(IServiceProvider serviceProvider, string name)
{
//根据前面依赖注入的名称,获取到具体的httpClient
var httpClient = this.httpClientFactory.CreateClient(name);
//根据名称,获取到配置
var httpApiOptions = this.httpApiOptionsMonitor.Get(name);

//把零散的对象,简单包了一层,变成httpclient上下文
var httpClientContext = new HttpClientContext(httpClient, serviceProvider, httpApiOptions, name);
//再包一层,只是为了能做个Aop拦截器
var httpApiInterceptor = new HttpApiInterceptor(httpClientContext);
//包了两层的对象,交给了请求执行器
return this.httpApiActivator.CreateInstance(httpApiInterceptor);
}

这里我们注意两个点

1. httpApiOptionsMonitor(这个是IOptionsMonitor

2. httpApiActivator.CreateInstance(这个才是真正去创建类型的方法)

所以,它啥也没做,只是包了一层丢给了IHttpApiActivator 执行者

另外,由于前面是瞬态注入,这里又是使用的IOptionsMonitor获取的配置文件

services.TryAddTransient(serviceProvider =>
{
var httpApiProvider = serviceProvider.GetRequiredService
return httpApiProvider.CreateHttpApi(serviceProvider, name);
});

所以,你可以不重启程序,更改配置文件,就能让的配置实时生效哦~(是不是又get到了一点)

继续上述,上面啥也没做,包了一层又一层,东西丢给了IHttpApiActivator 请求执行者,我们看看它做了什么,这里倒有个很巧妙的设计

首先它有3个实现

1. DefaultHttpApiActivator(默认执行者)

2. ILEmitHttpApiActivator (IL执行者)

3. SourceGeneratorHttpApiActivator(源生成器执行者)

其实看到这里,就应该有一点眉目了,出现IL和源生成器,大概原理就是通过IL或者源生成器去生成对应接口的实现

它优先走的是DefaultHttpApiActivator,我们看看它做了什么 ///
/// 默认的THttpApi的实例创建器
///
///
///
///
///
public DefaultHttpApiActivator(IApiActionDescriptorProvider apiActionDescriptorProvider, IApiActionInvokerProvider actionInvokerProvider)
{
//是否源生成器支持
if (SourceGeneratorHttpApiActivator
{
//如果支持源生成器,用SourceGeneratorHttpApiActivator
this.httpApiActivator = new SourceGeneratorHttpApiActivator
}
elseif (RuntimeFeature.IsDynamicCodeCompiled)
{
//如果不支持源生成器,用ILEmitHttpApiActivator
this.httpApiActivator = new ILEmitHttpApiActivator
}
else
{
thrownew ProxyTypeCreateException(typeof(HttpApi));
}
}

///
/// 创建接口的实例
///
/// 接口拦截器
///
public THttpApi CreateInstance(IHttpApiInterceptor apiInterceptor)
{
//最终调用的是前面选择的执行器
returnthis.httpApiActivator.CreateInstance(apiInterceptor);
}现在,准备好了吗?我们就来看看它的IL实现我们看一下它的构造函数与CreateInstance很简单,只是activator的委托,所以实际做的内容都在构造函数的时候初始化了 ///
/// 运行时使用ILEmit动态创建THttpApi的代理类和代理类实例
///
public ILEmitHttpApiActivator(IApiActionDescriptorProvider apiActionDescriptorProvider, IApiActionInvokerProvider actionInvokerProvider)
{
//1:通过反射获取接口的所有方法
var apiMethods = HttpApi.FindApiMethods(typeof(THttpApi));

//2:通过反射方法,获取方法具体的ActionInvoker
this.actionInvokers = apiMethods
.Select(item => apiActionDescriptorProvider.CreateActionDescriptor(item, typeof(THttpApi)))
.Select(actionInvokerProvider.CreateActionInvoker)
.ToArray;
//3: 核心:构建类型
var proxyType = BuildProxyType(apiMethods);
//4:表达式构建委托,这个委托的入参刚好是actionInvokers,返回时实现的类型
this.activator = LambdaUtil.CreateCtorFunc
}

///
/// 创建接口的实例
///
/// 接口拦截器
///
public THttpApi CreateInstance(IHttpApiInterceptor apiInterceptor)
{
//对应上面第4步的委托,进行执行
returnthis.activator.Invoke(apiInterceptor, this.actionInvokers);
}

这里有个核心的点,就是第三步,通过接口的方法,如何构建具体的类型的(相当于,无中生有)

这里我们停留一下,因为它的第二步,也非常巧妙,如何获取到Action执行器它是上面第二步,真正走的是:CreateActionInvoker protected virtual ApiActionInvoker CreateDefaultActionInvoker(ApiActionDescriptor actionDescriptor)
{
var resultType = actionDescriptor.Return.DataType.Type;
var invokerType = typeof(DefaultApiActionInvoker).MakeGenericType(resultType);
return invokerType.CreateInstance
}这里返回的是,通过泛型反射DefaultApiActionInvoker,将Actions返回结果当成,反射创建对象也算是泛型比较常见的玩法了,所以第二步,拿到的是数组

这个数组,又塞给了第4步的委托中,供·CreateInstance·方法使用,形成了闭环

所以,现在又出现分支:

1. BuildProxyType(apiMethods) 进行构建代理类型

2. DefaultApiActionInvoker构建如何调用Api的行为

3. LambdaUtil.CreateCtorFunc 构建执行委托,外部调用

我们看看DefaultApiActionInvoker它是如何调用Api行为

///
/// 执行Action
///
/// 上下文
/// 参数值
///
public virtual async Task InvokeAsync(HttpClientContext context, object? arguments)
{
try
{
var requiredUri = context.HttpApiOptions.HttpHost ?? context.HttpClient.BaseAddress;
var useDefaultUserAgent = context.HttpApiOptions.UseDefaultUserAgent;
usingvar requestMessage = new HttpApiRequestMessageImpl(requiredUri, useDefaultUserAgent);

var httpContext = new HttpContext(context, requestMessage);
//前面啥也没做,就是去配置文件,构建HttpContext
var requestContext = new ApiRequestContext(httpContext, this.ActionDescriptor, arguments, new DefaultDataCollection);
//在InvokeAsync中进行执行
returnawait InvokeAsync(requestContext).ConfigureAwait(false);
}
catch (HttpRequestException)
{
throw;
}
catch (Exception ex)
{
#if NET5_0_OR_GREATER
if (ex is IStatusCodeException exception)
{
thrownew HttpRequestException(ex.Message, ex, exception.GetStatusCode);
}
#endif
thrownew HttpRequestException(ex.Message, ex);
}
}我们继续往InvokeAsync下探 ///
/// 第一层:执行Api方法
///
///
///
private static async Task InvokeAsync(ApiRequestContext request)
{
disable
//这里又是包了一层,真正的执行交给了ApiRequestExecutor.ExecuteAsync(request),这是一个静态方法在下面
var response = await ApiRequestExecutor.ExecuteAsync(request).ConfigureAwait(false);
if (response.ResultStatus == ResultStatus.HasResult)
{
return (TResult)response.Result;
}

if (response.ResultStatus == ResultStatus.HasException)
{
ExceptionDispatchInfo.Capture(response.Exception).Throw;
}

thrownew ApiReturnNotSupportedException(response);
enable
}

///
/// 第二层:执行上下文
///
/// 请求上下文
///
public static async Task ExecuteAsync(ApiRequestContext request)
{
await HandleRequestAsync(request).ConfigureAwait(false);
usingvar requestAbortedLinker = new CancellationTokenLinker(request.HttpContext.CancellationTokens);
//这里也没有做什么,继续包了一层,丢给了ApiRequestSender.SendAsync,也是静态方法
var response = await ApiRequestSender.SendAsync(request, requestAbortedLinker.Token).ConfigureAwait(false);
await HandleResponseAsync(response).ConfigureAwait(false);
return response;
}

///
/// 第三层:发送 http 请求
///
///
///
///
///
public static async Task SendAsync(ApiRequestContext context, CancellationToken requestAborted)
{
if (context.HttpContext.RequestMessage.RequestUri == null)
{
thrownew ApiInvalidConfigException(Resx.required_HttpHost);
}

try
{
//还在包?继续!
await SendCoreAsync(context, requestAborted).ConfigureAwait(false);
returnnew ApiResponseContext(context, requestAborted);
}
catch (Exception ex)
{
returnnew ApiResponseContext(context, requestAborted) { Exception = ex };
}
}

///
/// 第四层:发送 http 请求
///
///
///
///
///
private static async Task SendCoreAsync(ApiRequestContext context, CancellationToken requestAborted)
{
var actionCache = await context.GetCacheAsync.ConfigureAwait(false);
if (actionCache != null && actionCache.Value != null)
{
context.HttpContext.ResponseMessage = actionCache.Value;
}
else
{
var client = context.HttpContext.HttpClient;
var request = context.HttpContext.RequestMessage;
var completionOption = GetCompletionOption(context);
//这里构建好的HttpClient 发送即可
var response = await client.SendAsync(request, completionOption, requestAborted).ConfigureAwait(false);
context.HttpContext.ResponseMessage = response;
await context.SetCacheAsync(actionCache?.Key, response).ConfigureAwait(false);
}
}上面4步,最终调用HttpClient发送请求,这便是 DefaultApiActionInvoker实现了,朴素且奢华现在,我们构建好了,可以继续走BuildProxyType(apiMethods) 如何构建类型的 private static Type BuildProxyType(MethodInfo apiMethods)
{
//省略非核心代码

//构建构造函数
BuildCtor(builder, fieldApiInterceptor, fieldActionInvokers);
//构建方法
BuildMethods(builder, apiMethods, fieldApiInterceptor, fieldActionInvokers);
var proxyType = builder.CreateType;
return proxyType ?? throw new ProxyTypeCreateException(interfaceType);
}这里,通过构架类型的构造函数+方法,最后builder.CreateType进行创建类型

这两个方法,就是手撸IL了!这里笔者不再赘述,这玩意儿,懂得都懂,大概知道个是啥就行

private static void BuildCtor(TypeBuilder builder, FieldBuilder fieldApiInterceptor, FieldBuilder fieldActionInvokers)
{
// .ctor(IHttpApiInterceptor apiInterceptor, ApiActionInvoker actionInvokers)
var ctor = builder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, proxyTypeCtorArgTypes);

var il = ctor.GetILGenerator;

// this.apiInterceptor = 第一个参数
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Stfld, fieldApiInterceptor);

// this.actionInvokers = 第二个参数

il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Stfld, fieldActionInvokers);

il.Emit(OpCodes.Ret);
}

///
/// 生成代理类型的接口实现方法
///
/// 类型生成器
/// 接口的方法
/// 拦截器字段
/// action执行器字段
private static void BuildMethods(TypeBuilder builder, MethodInfo actionMethods, FieldBuilder fieldApiInterceptor, FieldBuilder fieldActionInvokers)
{
// private final hidebysig newslot virtual
const MethodAttributes implementAttribute = MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual;

for (var i = 0; i
{
var actionMethod = actionMethods[i];
var actionParameters = actionMethod.GetParameters;
var parameterTypes = actionParameters.Select(p => p.ParameterType).ToArray;
var actionMethodName = $"{actionMethod.DeclaringType?.FullName}.{actionMethod.Name}";

var methodBuilder = builder.DefineMethod(actionMethodName, implementAttribute, CallingConventions.Standard | CallingConventions.HasThis, actionMethod.ReturnType, parameterTypes);
builder.DefineMethodOverride(methodBuilder, actionMethod);
var iL = methodBuilder.GetILGenerator;

// this.apiInterceptor
iL.Emit(OpCodes.Ldarg_0);
iL.Emit(OpCodes.Ldfld, fieldApiInterceptor);

// this.actionInvokers[i]

iL.Emit(OpCodes.Ldfld, fieldActionInvokers);
iL.Emit(OpCodes.Ldc_I4, i);
iL.Emit(OpCodes.Ldelem_Ref);

// var arguments = new object[parameters.Length]
var arguments = iL.DeclareLocal(typeof(object));
iL.Emit(OpCodes.Ldc_I4, actionParameters.Length);
iL.Emit(OpCodes.Newarr, typeof(object));
iL.Emit(OpCodes.Stloc, arguments);

for (var j = 0; j
{
iL.Emit(OpCodes.Ldloc, arguments);
iL.Emit(OpCodes.Ldc_I4, j);
iL.Emit(OpCodes.Ldarg, j + 1);

var parameterType = parameterTypes[j];
if (parameterType.IsValueType || parameterType.IsGenericParameter)
{
iL.Emit(OpCodes.Box, parameterType);
}
iL.Emit(OpCodes.Stelem_Ref);
}

// 加载 arguments 参数

// Intercept(actionInvoker, arguments)
iL.Emit(OpCodes.Callvirt, interceptMethod);

if (actionMethod.ReturnType == typeof(void))
{
iL.Emit(OpCodes.Pop);
}

iL.Emit(OpCodes.Castclass, actionMethod.ReturnType);
iL.Emit(OpCodes.Ret);
}
}

9、源生成器实现-SourceGeneratorHttpApiActivator

看完IL实现,就到了源生成器实现了,这玩意儿才是一个大杀器!它的设计,其实和IL生成器很像,就是类型不用构建了,而是直接从源生成器中拿

private staticreadonly Type? _proxyClassType = SourceGeneratorProxyClassFinder.Find(typeof(THttpApi));
///
/// 通过查找类型代理类型创建实例
///
public SourceGeneratorHttpApiActivator(IApiActionDescriptorProvider apiActionDescriptorProvider, IApiActionInvokerProvider actionInvokerProvider)
{
var httpApiType = typeof(THttpApi);
//区别在这里!!。这里直接通过源生器拿到了具体类型
var proxyClassType = _proxyClassType;
if (proxyClassType == null)
{
var message = $"找不到{httpApiType}的代理类";
thrownew ProxyTypeCreateException(httpApiType, message);
}

this.actionInvokers = FindApiMethods(httpApiType, proxyClassType)
.Select(item => apiActionDescriptorProvider.CreateActionDescriptor(item, httpApiType))
.Select(actionInvokerProvider.CreateActionInvoker)
.ToArray;

this.activator = LambdaUtil.CreateCtorFunc
}

所以,为什么能直接拿到呢?这就是源生成器的伟大神通!

在项目还没有启动的时候(你撸代码的时候),这个代理类就已经通过源生成器生成在你的项目中,所以它能直接拿到

既然如此,我们就看看它是怎么生成的?

注意到:HttpApiSourceGenerator实现了ISourceGenerator源生成器,所以它才是真正的元婴大能!

这里,我直接解释逐行解释~


namespace WebApiClientCore.Analyzers.SourceGenerator
{
///
/// HttpApi 语法接收器
/// 用来在“语法分析阶段”收集我们关心的语法节点(这里是接口声明)
/// 后面会用 Compilation(语义分析)进一步确认哪些接口是 HttpApi
///
sealedclassHttpApiSyntaxReceiver : ISyntaxReceiver// 实现 Roslyn 的 ISyntaxReceiver 接口
{
///
/// HttpApi 接口的元数据名称(带命名空间的全称)
/// 用来在编译语义模型中查找类型
///
privateconststring IHttpApiTypeName = "WebApiClientCore.IHttpApi";

///
/// 表示 HttpApi 方法/参数会用到的特性接口全称
/// 例如定义 API 方法时需要标记此类特性
///
privateconststring IApiAttributeTypeName = "WebApiClientCore.IApiAttribute";

///
/// 收集到的接口语法节点列表(纯语法层面的 InterfaceDeclarationSyntax)
/// 注意,这里只是存了节点,还没有得到它对应的语义信息
///
privatereadonly List
new List

///
/// 遍历语法树时的回调
/// ISyntaxReceiver 的核心方法,编译器在分析每个语法节点时会调用它
///
void ISyntaxReceiver.OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
// 如果当前节点是接口声明(interface XXX)
if (syntaxNode is InterfaceDeclarationSyntax syntax)
{
// 加入接口列表,作为候选
this.interfaceSyntaxList.Add(syntax);
}
}

///
/// 在编译完成语法分析后,结合 Compilation(语义模型)获取真正的 HttpApi 接口类型
///
public IEnumerable GetHttpApiTypes(Compilation compilation)
{
// 根据元数据名称查找 IHttpApi 接口符号(语义层级)
var httpApi = compilation.GetTypeByMetadataName(IHttpApiTypeName);
if (httpApi == null) // 如果找不到说明引用没加
{
yieldbreak;
}

// 同样获取 IApiAttribute 的类型符号
var apiAttribute = compilation.GetTypeByMetadataName(IApiAttributeTypeName);

// 遍历我们之前收集的所有接口语法节点
foreach (var interfaceSyntax inthis.interfaceSyntaxList)
{
// 获取这个接口声明的语义符号
var @interface = compilation
.GetSemanticModel(interfaceSyntax.SyntaxTree)
.GetDeclaredSymbol(interfaceSyntax);

// 如果取到了符号,并且判断它符合 HttpApi 条件
if (@interface != null && IsHttpApiInterface(@interface, httpApi, apiAttribute))
{
// 返回这个接口符号
yieldreturn @interface;
}
}
}

///
/// 判断某个接口是否是 "HttpApi"
/// 条件:
/// 1. 自身或其继承链上实现了 IHttpApi 接口
/// 2. 或者拥有 IApiAttribute 类型的特性(接口、方法或参数)
///
private static bool IsHttpApiInterface(INamedTypeSymbol @interface, INamedTypeSymbol httpApi, INamedTypeSymbol? apiAttribute)
{
// 条件1:接口的基接口列表中包含 IHttpApi
if (@interface.AllInterfaces.Contains(httpApi))
{
returntrue;
}

// 如果连 IApiAttribute 都找不到就没法判断条件2
if (apiAttribute == null)
{
returnfalse;
}

// 条件2:
// 检查接口本身、它的所有基接口
// - 是否标记了 IApiAttribute 特性
// - 或者它的任意方法是否标记了该特性
// - 方法的任意参数是否标记了该特性
return @interface.AllInterfaces.Append(@interface).Any(i =>
HasAttribute(i, apiAttribute) || // 接口上有特性
i.GetMembers.OfType
HasAttribute(m, apiAttribute) || // 方法上有特性
m.Parameters.Any(p => HasAttribute(p, apiAttribute)) // 参数上有特性
)
);
}

///
/// 判断某个符号(接口、方法、参数等)是否声明了指定特性,
/// 特性类型匹配条件:它实现了指定的特性接口(IApiAttribute)
///
private static bool HasAttribute(ISymbol symbol, INamedTypeSymbol attribute)
{
foreach (var attr in symbol.GetAttributes)
{
var attrClass = attr.AttributeClass;
if (attrClass != null && attrClass.AllInterfaces.Contains(attribute))
{
returntrue;
}
}
returnfalse;
}
}
}

来源:opendotnet

相关推荐