.Net Core 扩展使用Refit

.Net Core 扩展使用Refit

标签(空格分隔): 未分类


在.net core 2.1当中,目前可以是用HttpClientFactory进行Http的调用,它的使用方法我不再多说,具体参见(https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.2#consumption-patterns)。微软的官网写的非常详细了。

在调用过程中使用Refit可以进行强类型的客户端调用,个人比较喜欢这个。目前还有一个组件是老九大佬写的一个类似的客户端(https://github.com/dotnetcore/WebApiClient)

在WebApi当中使用Refit。

 public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddHttpClient("github", c => { c.BaseAddress = new Uri("https://api.github.com"); }) .AddTypedClient(c => Refit.RestService.For<IGitHubApi>(c)); services.AddRefitClient<IGitHubApi>(); } 

接口的定义为:

 [Headers("User-Agent: Refit Integration Tests")] //[RefitClient("github")] public interface IGitHubApi { [Get("/users/{username}")] Task<User> GetUser(string userName); }

当我们有很多接口需要注入的时候,就得写很多的类似强类型的AddRefitClient的代码,个人觉得非常的麻烦。是否能够根据标签程序集进行批量注入。
Refit的注入,其实注入的是一个RestService

 public static IHttpClientBuilder AddRefitClient<T>(this IServiceCollection services, RefitSettings settings = null) where T : class { services.AddSingleton(provider => RequestBuilder.ForType<T>(settings)); return services.AddHttpClient(UniqueName.ForType<T>()) .AddTypedClient((client, serviceProvider) => RestService.For<T>(client, serviceProvider.GetService<IRequestBuilder<T>>())); }

具体的类型是在代码编译阶段用BuildTask生成了带有RefitHttpAttribute的接口类型的实体类。

具体创建的类型如下:

[ExcludeFromCodeCoverage, DebuggerNonUserCode, Preserve]internal class AutoGeneratedIGitHubApi : IGitHubApi{ // Fields [CompilerGenerated, DebuggerBrowsable(0)] private HttpClient <Client>k__BackingField; private readonly IRequestBuilder requestBuilder; // Methods public AutoGeneratedIGitHubApi(HttpClient client, IRequestBuilder requestBuilder) { this.Client = client; this.requestBuilder = requestBuilder; } public virtual Task<User> GetUser(string userName) { object[] objArray1 = new object[] { userName }; object[] objArray = objArray1; Type[] typeArray1 = new Type[] { typeof(string) }; return (Task<User>) this.requestBuilder.BuildRestResultFuncForMethod("GetUser", typeArray1, null)(this.Client, objArray); } // Properties public HttpClient Client { get; protected set; }}

目前我们要解决的是如何批量的进行注入的问题:个人在Refit的源码上添加了一些代码以支持批量注入

 //配置的实体 public class ApiSettings { public List<ApiOption> Apis { get; set; } = new List<ApiOption>(); } public class ApiOption { public string Name { get; set; } public string Version { get; set; } //httpClient的baseAddress的配置 public string Url { get; set; } } //配置的搜索的程序集的名称 public class DISettings { public List<string> RefitClientAssemblyNames { get; set; } = new List<string>(); } /// <summary> /// 批量注入时打上这个标签标明是Refit的接口 /// </summary> public class RefitClientAttribute : Attribute { /// <summary> /// appsetting中的apiname /// </summary> public string ApiName { get; set; } public RefitClientAttribute(string apiName) { ApiName = apiName; } } /// <summary> /// 基于HttpClient的扩展方法 /// </summary> public static class RefitHttpClientExtension { public static IServiceCollection ScanAddHttpClient(this IServiceCollection serviceCollection, ApiSettings settings) { #region 批量注入HttpClient if (settings == null) { throw new ArgumentNullException("settings"); } var apis = settings.Apis; if (apis == null || apis.Count == 0) { throw new ArgumentNullException("settings.Apis"); } foreach (var ass in apis) { serviceCollection.AddHttpClient(ass.Name).ConfigureHttpClient(client => client.BaseAddress = new Uri(ass.Url)); } #endregion return serviceCollection; } public static IServiceCollection ScanAddRefitClient(this IServiceCollection serviceCollection, IConfiguration configration, RefitSettings refitSettings = null) { DISettings dISettings = configration.GetSection("DISettings").Get<DISettings>(); if (dISettings == null) { throw new ArgumentNullException("dISettings", "配置文件中不存在DISettings节点"); } if (dISettings == null || dISettings.RefitClientAssemblyNames == null || dISettings.RefitClientAssemblyNames.Count == 0) { throw new ArgumentNullException("dISettings.RefitClientAssemblyNames", "配置文件DISettings节点不存在RefitClientAssemblyNames节点!!"); } ApiSettings apiSettings = configration.GetSection("ApiSettings").Get<ApiSettings>(); if (apiSettings == null) { throw new ArgumentNullException("apiSettings", "配置文件中不存在ApiSettings节点"); } if (apiSettings.Apis == null || apiSettings.Apis.Count == 0) { throw new ArgumentNullException("apiSettings.Apis", "配置文件ApiSettings节点不存在Apis节点!!"); } return serviceCollection.ScanAddRefitClient(apiSettings, dISettings, refitSettings); } public static IServiceCollection ScanAddRefitClient(this IServiceCollection serviceCollection, ApiSettings apiSettings, DISettings dISettings, RefitSettings refitSettings = null) { #region 批量注入HttpClient serviceCollection.ScanAddHttpClient(apiSettings); #endregion var registTypes = GetAttributeType(dISettings); serviceCollection.ScanAddRefitClient(registTypes, refitSettings); return serviceCollection; } public static IServiceCollection ScanAddRefitClient(this IServiceCollection serviceCollection, List<Type> registTypes, RefitSettings refitSettings = null) { foreach (var type in registTypes) { var name = type.GetCustomAttribute<RefitClientAttribute>().ApiName; serviceCollection.AddRefitClient(type, name, refitSettings); } return serviceCollection; } private static List<Type> GetAttributeType(DISettings options) { var registTypes = new List<Type>(); var assemblys = options.RefitClientAssemblyNames; foreach (var assembly in assemblys) { registTypes.AddRange(GetAssemblyType(assembly, type => { var refitAttrs = type.GetCustomAttributes<RefitClientAttribute>(); if (refitAttrs == null || refitAttrs.Count() == 0) { return false; } else { return true; } })); } return registTypes; } private static List<Type> GetAssemblyType(string assemblyName, Predicate<Type> predicate = null) { var registTypes = new List<Type>(); var ass = Assembly.Load(new AssemblyName(assemblyName)); var types = ass.DefinedTypes.Where(p => p.IsPublic && p.IsInterface).Select(x => x.AsType())?.ToList(); foreach (var type in types) { if (predicate == null) { registTypes.Add(type); continue; } if (predicate(type)) { registTypes.Add(type); } } return registTypes; } } 

在HttpClientFactoryExtensions扩展类中新增了两个基于Type的扩展方法而不是泛型的扩展方法

 /// <summary> /// Adds a Refit client to the DI container /// </summary> /// <param name="services">container</param> /// <param name="t">type of refit interface</param> /// <param name="httpclientName">httpclient‘s name</param> /// <param name="settings">Optional. Settings to configure the instance with</param> /// <returns></returns> public static IServiceCollection AddRefitClient(this IServiceCollection services,Type t,string httpclientName, RefitSettings settings = null) { var builder = RequestBuilder.ForType(t, settings); services.AddSingleton(provider => builder); services.AddSingleton(t, provider => { var client=provider.GetService<IHttpClientFactory>().CreateClient(httpclientName); if (client == null) { throw new Exception($"please inject the httpclient named {httpclientName} httpclient!! "); } return RestService.For(client, builder, t); }); return services; } /// <summary> /// Adds a Refit client to the DI container /// </summary> /// <param name="services">container</param> /// <param name="t">type of refit interface</param> /// <param name="client>httpclient</param> /// <param name="settings">Optional. Settings to configure the instance with</param> /// <returns></returns> public static IServiceCollection AddRefitClient(this IServiceCollection services, Type t, HttpClient client,RefitSettings settings = null) { var builder = RequestBuilder.ForType(t, settings); services.AddSingleton(provider => builder); services.AddSingleton(t, provider => { return RestService.For(client, builder, t); }); return services; }

第三:在是基于Refit的源码RestService改写下能够支持RestService.For(Type interType)的方法

 public static object For(HttpClient client, IRequestBuilder builder,Type t) { var generatedType = typeMapping.GetOrAdd(t, GetGeneratedType(t)); return Activator.CreateInstance(generatedType, client, builder); } //它就是在这里获取生成好的接口的注入类型的名字 static Type GetGeneratedType(Type t) { string typeName = UniqueName.ForType(t); var generatedType = Type.GetType(typeName); if (generatedType == null) { var message = t.Name + " doesn‘t look like a Refit interface. Make sure it has at least one " + "method with a Refit HTTP method attribute and Refit is installed in the project."; throw new InvalidOperationException(message); } return generatedType; } //获取名字方法也是泛型的也要加一个基于Type的 public static string ForType(Type t) { string typeName; if (t.IsNested) { var className = "AutoGenerated" + t.DeclaringType.Name + t.Name; typeName = t.AssemblyQualifiedName.Replace(t.DeclaringType.FullName + "+" + t.Name, t.Namespace + "." + className); } else { var className = "AutoGenerated" + t.Name; if (t.Namespace == null) { className = $"{className}.{className}"; } typeName = t.AssemblyQualifiedName.Replace(t.Name, className); } return typeName; }

在AppSetting文件当中配置一些配置:

{ "ApiSettings": { "Apis": [ { "Name": "gitHubApi", "Version": "", "Url": "https://api.github.com" } ] }, "DISettings": { "RefitClientAssemblyNames": [ "RefitWebTest" ] }}

Refit的接口主要打上标签即可

 [Headers("User-Agent: Refit Integration Tests")] //参数的名字和配置文件的Apis:Name一致即可 [RefitClient("gitHubApi")] public interface IGitHubApi { [Get("/users/{username}")] Task<User> GetUser(string userName); }

改写以后的使用:

 //批量注入带有RefitClientAttribute标签的接口 services.ScanAddRefitClient(Configuration); 

这样就实现了基于Refit的批量注入,注入的HttpClient的名字为配置文件当中的名字。

最后打包成Nuget包才能正确的使用,个人尝试发现必须Nuget包的名字是Refit才能正确的生成接口的代理类,(我知道改包名不是很好=。=)改个nuget的名字都不行,不知道为什么不知道有没有大佬知道,望指教!!
贴一下Refit的BuildTask的targets文件

<?xml version="1.0" encoding="utf-8"?><Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <CoreCompileDependsOn> $(CoreCompileDependsOn); GenerateRefitStubs; </CoreCompileDependsOn> </PropertyGroup> <PropertyGroup> <IntermediateOutputPath Condition="$(IntermediateOutputPath) == ‘‘ Or $(IntermediateOutputPath) == ‘*Undefined*‘">$(MSBuildProjectDirectory)obj\$(Configuration)\</IntermediateOutputPath> <RefitTaskAssemblyFile Condition="‘$(MSBuildRuntimeType)‘ == ‘Core‘">$(MSBuildThisFileDirectory)..\MSBuildCore20\InterfaceStubGenerator.BuildTasks.dll</RefitTaskAssemblyFile> <RefitTaskAssemblyFile Condition="‘$(MSBuildRuntimeType)‘ != ‘Core‘">$(MSBuildThisFileDirectory)..\MSBuildFull46\InterfaceStubGenerator.BuildTasks.dll</RefitTaskAssemblyFile> <RefitMinCoreVersionRequired>2.0</RefitMinCoreVersionRequired> <!-- Our default CLI version for error checking purposes --> <RefitNetCoreAppVersion>$(BundledNETCoreAppTargetFrameworkVersion)</RefitNetCoreAppVersion> <RefitNetCoreAppVersion Condition="‘$(RefitNetCoreAppVersion)‘ == ‘‘">1.0</RefitNetCoreAppVersion> <!-- Refit internal namespace to be added to internally generated Refit code. Can be overriden by user in case of namespace clashes. --> <RefitInternalNamespace Condition=" ‘$(RefitInternalNamespace)‘ == ‘‘ ">$(RootNamespace)</RefitInternalNamespace> </PropertyGroup> <UsingTask TaskName="Refit.Generator.Tasks.GenerateStubsTask" AssemblyFile="$(RefitTaskAssemblyFile)" /> <Target Name="GenerateRefitStubs" BeforeTargets="CoreCompile"> <Error Condition="‘$(MSBuildRuntimeType)‘ == ‘Core‘ and ‘$(RefitMinCoreVersionRequired)‘ > ‘$(RefitNetCoreAppVersion)‘ " Text="Refit requires at least the .NET Core SDK v2.0 to run with ‘dotnet build‘" ContinueOnError="false" /> <GenerateStubsTask SourceFiles="@(Compile)" BaseDirectory="$(MSBuildProjectDirectory)" OutputFile="$(IntermediateOutputPath)\RefitStubs.g.cs" RefitInternalNamespace="$(RefitInternalNamespace)" /> <Message Text="Processed Refit Stubs" /> <ItemGroup Condition="Exists(‘$(IntermediateOutputPath)\RefitStubs.g.cs‘)"> <Compile Include="$(IntermediateOutputPath)\RefitStubs.g.cs" /> </ItemGroup> </Target></Project>

测试发现,只要改了打包的报名称,这个Task就不执行了,不知道为啥!!
如有知道忘告知!!

相关文章