這段時間閑賦在家,感覺手癢,故想折騰一些東西.
由於之前移植了一個c#版本的spring cloud feign客戶端(https://github.com/daixinkai/feign.net),所以想弄個配套的服務端動態接口,實現服務即接口的功能.雖然ABP框架內部包含一個功能強大的DynamicWebApi,但是我只是想要一個獨立簡單的組件,用來實現以下效果:
有一個業務服務 :
public interface ITestService { Task<string> GetName(int id); }
自動生成類似以下的接口
[Route("api/test")] public class TestController : ControllerBase { public TestController(ITestService testService) { _testService = testService; } ITestService _testService; [HttpGet("name/{id}")] public Task<string> GetName(int id) { return _testService.GetName(id); } }
項目地址 : https://github.com/daixinkai/Microsoft.AspNetCore.Mvc.DynamicApi
-------------------------------------------------------------------------------------------------
首先定義一個DynamicApiAttribute,替代RouteAttribute的功能
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = true)] public class DynamicApiAttribute : Attribute, Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider { public DynamicApiAttribute() { } public DynamicApiAttribute(string template) { Template = template; } public string Template { get; set; }public int? Order { get; set; } public string Name { get; set; } }
思路就是查找標記了DynamicApiAttribute特性的接口,生成一個代理類型注冊為控制器
1. BuildProxyType: 定義一個 TypeBuilder
先生成一個類型為當前接口類型的字段 :
FieldBuilder interfaceInstanceFieldBuilder = typeBuilder.DefineField("_interfaceInstance", interfaceType, FieldAttributes.Private);
生成構造函數,接收一個當前接口類型的對象,並賦值給上述字段
ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor( MethodAttributes.Public, CallingConventions.Standard, new Type[] { interfaceType }); ILGenerator constructorIlGenerator = constructorBuilder.GetILGenerator(); constructorIlGenerator.Emit(OpCodes.Ldarg_0); constructorIlGenerator.Emit(OpCodes.Ldarg_1); constructorIlGenerator.Emit(OpCodes.Stfld, interfaceInstanceFieldBuilder); constructorIlGenerator.Emit(OpCodes.Ret);
查找接口的所有方法,全部生成
foreach (var method in interfaceType.GetMethodsIncludingBaseInterfaces()) { BuildMethod(typeBuilder, interfaceType, method, interfaceInstanceFieldBuilder); }
static void BuildMethod(TypeBuilder typeBuilder, Type interfaceType, MethodInfo method, FieldBuilder interfaceInstanceFieldBuilder) { MethodAttributes methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final; var parameters = method.GetParameters(); Type[] parameterTypes = parameters.Select(s => s.ParameterType).ToArray(); MethodBuilder methodBuilder = typeBuilder.DefineMethod(method.Name, methodAttributes, CallingConventions.Standard, method.ReturnType, parameterTypes); #region parameterName for (int i = 0; i < parameters.Length; i++) { methodBuilder.DefineParameter(i + 1, ParameterAttributes.None, parameters[i].Name); } #endregion typeBuilder.DefineMethodOverride(methodBuilder, method); ILGenerator iLGenerator = methodBuilder.GetILGenerator(); iLGenerator.Emit(OpCodes.Ldarg_0); // this iLGenerator.Emit(OpCodes.Ldfld, interfaceInstanceFieldBuilder); for (int i = 0; i < parameterTypes.Length; i++) { iLGenerator.Emit(OpCodes.Ldarg_S, i + 1); } iLGenerator.Emit(OpCodes.Call, method); iLGenerator.Emit(OpCodes.Ret); var datas = CustomAttributeData.GetCustomAttributes(method); foreach (var data in datas) { CustomAttributeBuilder customAttributeBuilder = new CustomAttributeBuilder(data.Constructor, data.ConstructorArguments.Select(s => s.Value).ToArray()); methodBuilder.SetCustomAttribute(customAttributeBuilder); } }
最后別忘了復制特性
var datas = CustomAttributeData.GetCustomAttributes(interfaceType); foreach (var data in datas) { CustomAttributeBuilder customAttributeBuilder = new CustomAttributeBuilder(data.Constructor, data.ConstructorArguments.Select(s => s.Value).ToArray()); typeBuilder.SetCustomAttribute(customAttributeBuilder); }
這樣代理類型就生成完畢了
2.注冊到Mvc框架中
由於默認ControllerFeatureProvider不支持生成的代理類型,需要自定義實現
public class DynamicApiControllerFeatureProvider : ControllerFeatureProvider { protected override bool IsController(TypeInfo typeInfo) { return typeInfo.IsProxyApi(); } }
public static IMvcBuilder AddDynamicApi(this IMvcBuilder builder) { var feature = new ControllerFeature(); foreach (AssemblyPart assemblyPart in builder.PartManager.ApplicationParts.OfType<AssemblyPart>()) { foreach (var type in assemblyPart.Types) { if (type.IsInterface && type.IsDefinedIncludingBaseInterfaces<DynamicApiAttribute>() && !type.IsDefined(typeof(NonDynamicApiAttribute)) && !type.IsGenericType) { feature.Controllers.Add(DynamicApiProxy.GetProxyType(type));//feature.Controllers.Add沒什么卵用 } } } builder.AddApplicationPart(DynamicApiProxy.DynamicAssembly.AssemblyBuilder); builder.PartManager.FeatureProviders.Add(new DynamicApiControllerFeatureProvider()); return builder; }
這樣就完成了,是不是很簡單實用! 另外DynamicApi支持Mvc內置的Filter
3. 測試一下
[DynamicApi("api/testService")] public interface ITestService { //[Microsoft.AspNetCore.Authorization.Authorize] [HttpGet("name/{id}")] Task<string> GetName(int id); } public class TestService : ITestService { public Task<string> GetName(int id) { return Task.FromResult("Name" + id); } }
最后別忘了注入服務
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).AddDynamicApi(); services.AddTransient<ITestService, TestService>(); }
大功告成