Fu busy this time at home, feeling itchy, and would like to toss something.
Because before a transplant c # version of spring cloud feign client (end https://github.com/daixinkai/feign.net ), so I want to get hold of the server supporting dynamic interface, ie the interface of the service function. Although ABP framework interior contains a powerful DynamicWebApi, but I just want a simple independent components to achieve the following results:
There is a business service:
public interface ITestService { Task<string> GetName(int id); }
Automatically generate an interface similar to the following
[Route("api/test")] public class TestController : ControllerBase { public TestController(ITestService testService) { _testService = test service; } ITestService _testService; [HttpGet("name/{id}")] public Task<string> GetName(int id) { return _testService.GetName(id); } }
Project Address: https://github.com/daixinkai/Microsoft.AspNetCore.Mvc.DynamicApi
-------------------------------------------------------------------------------------------------
First define a DynamicApiAttribute, alternative RouteAttribute function
[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; } }
The idea is to find the tagged interfaces DynamicApiAttribute characteristics, generate a proxy type registered for the controller
1. BuildProxyType: Define a TypeBuilder
Mr. Cheng is a type of the current interface types of fields:
FieldBuilder interfaceInstanceFieldBuilder = typeBuilder.DefineField("_interfaceInstance", interfaceType, FieldAttributes.Private);
Generating a constructor, a receiving object type of the current interface, and assigned to the fields described above
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);
Find all interface methods, all to generate
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); } }
Finally, do not forget to copy the characteristics
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); }
This type of proxy is generated finished
2. Register to Mvc framework
Since the default ControllerFeatureProvider does not support the type of proxy generation, you need a custom implementation
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 eggs with no } } } builder.AddApplicationPart(DynamicApiProxy.DynamicAssembly.AssemblyBuilder); builder.PartManager.FeatureProviders.Add(new DynamicApiControllerFeatureProvider()); return builder; }
This completed, is not very simple and practical! In addition DynamicApi support Mvc built-in Filter
3. test
[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); } }
Finally, do not forget injection services
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).AddDynamicApi(); services.AddTransient<ITestService, TestService>(); }
Done