这段时间闲赋在家,感觉手痒,故想折腾一些东西.
由于之前移植了一个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>(); }
大功告成