.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.For()返回的一个类型。
具体见源码:
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就不执行了,不知道为啥!!
如有知道忘告知!!
.Net Core 扩展使用Refit的更多相关文章
- 自动注册服务NET Core扩展IServiceCollection
NET Core扩展IServiceCollection自动注册服务 前言 在ASP.NET Core中使用依赖注入中使用很简单,只需在Startup类的ConfigureServices()方法中, ...
- Orchard Module,Theme,Core扩展加载概述
Orchard 源码探索(Module,Theme,Core扩展加载概述) 参考: http://www.orchardch.com/Blog/20120830071458 1. host.Initi ...
- ServiceStack.Redis 的 ASP.NET Core 扩展库
给大家安利一款 ServiceStack.Redis 的 ASP.NET Core 扩展库,它是基于 ServiceStack.Redis.Core 开发的. 简单易用,开源免费,使用ASP.NET ...
- Log4net 的 ASP.NET Core 扩展库
给大家安利一款 log4net 的 ASP.NET Core 扩展库,它是基于 log4net 开发的. 简单易用,开源免费,使用ASP.NET Core自身提供的DI容器来实现服务的注册和消费.直接 ...
- ASP.NET Core扩展库
亲爱的.Neter们,在我们日复一日的编码过程中是不是会遇到一些让人烦恼的事情: 日志配置太过复杂,各种模板.参数也搞不清楚,每次都要去查看日志库的文档,还需要复制粘贴一些重复代码,好无赖 当需要类型 ...
- ASP.NET Core扩展库之日志
上一篇我们对Xfrogcn.AspNetCore.Extensions扩展库功能进行了简单的介绍,从这一篇文章开始,我将逐步介绍扩展库中的核心功能. 日志作为非业务的通用领域基础功能, ...
- .NET Core扩展IServiceCollection自动注册服务
前言 在ASP.NET Core中使用依赖注入中使用很简单,只需在Startup类的ConfigureServices()方法中,通过IServiceCollection接口进行注入即可,其它的无需关 ...
- Orchard 源码探索(Module,Theme,Core扩展加载概述)
参考: http://www.orchardch.com/Blog/20120830071458 1. host.Initialize(); private static IOrchardHost H ...
- .Net Core扩展 SharpPlugs简单上手
SharpPlugs .Net Core 鋒利扩展,这是本人的开源项目 地址是 GitHub地址 大家喜欢 的话可以加个星哦 当前功能 DI AutoMapper ElasticSearch WebA ...
随机推荐
- linux学习系列二
vim是由vi发展而来,具有语法高亮显示,多视图编辑,代码折叠,支持插件等功能,vim成为了linux发行版本的标配. 1. vim工作模式 1. 普通模式:实现基本的光标移动和大量的快捷操作 2. ...
- Hibernate技术
Hibernate中3个重要的类: 配置类(configuration) 负责管理Hibernate的配置信息,包含数据库连接URL.数据库用户.数据库密麻麻.数据库驱动等. 会话工厂类(Sessio ...
- Neutron RPC API Layer
Client Side Here is an example of an rpc client definition: import oslo_messaging from neutron.commo ...
- 百度地图省市县乡镇街道对应ZOOM级别
百度地图省市县乡镇街道对应ZOOM级别
- SpringMVC 文件上传及下载
首先需要导入jar包 创建一个jsp页面 package cn.happy.Controller; import java.io.File; import javax.servlet.http.Htt ...
- httpRequest.CookieContainer= cookie 与 httpRequest.Headers.Add("Cookie", cookie)
这两天做了一个获取cookie并且携带此cookie去请求另外一个url地址,中间携带cookie用了两种方式:1. httpRequest.CookieContainer= cookie (此coo ...
- 分享知识-快乐自己:Caused by: org.hibernate.tool.schema.extract.spi.SchemaExtractionException: More than one table found in namespace (, ) : Dept (XXX)
在命名空间(,)中找到多个表 - SchemaExtractionException? 问题: 尝试在Java应用程序中使用Hibernate将一些值保存到表中时,我一直面临着这个奇怪的异常. 但是, ...
- scanf和cin的返回值
需要连续从标准输入读取数据时,可以采用下面两种不同的方式判断文件结束: [cpp] view plaincopy int i; while(scanf("%d",&i) ...
- 6 Python 数据类型—字符串
字符串是 Python 中最常用的数据类型.我们可以使用引号('或")来创建字符串. 创建字符串很简单,只要为变量分配一个值即可. var1 = 'Hello World!' var2 = ...
- 关于对H264码流的TS的封装的相关代码实现
1 写在开始之前 在前段时间有分享一个H264封装ps流到相关文章的,这次和大家分享下将H264封装成TS流到相关实现,其实也是工作工作需要.依照上篇一样,分段说明每个数据头的封装情况,当然,一样也会 ...