.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就不执行了,不知道为啥!!
如有知道忘告知!!