asp.net core mvc 之 DynamicApi


  這段時間閑賦在家,感覺手癢,故想折騰一些東西.

  由於之前移植了一個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>();
        }

 

 

 

 

大功告成

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM