ABP动态生成WebAPI
ABP框架可以动态生成WebApi,开发人员无需创建APIController,直接继承IApplicationService接口,即可对外发布webApi。
创建动态Web Api 控制器
例如,在Service层有一个ITestService接口,如下:
public interface ITestService : IApplicationService { List<int> GetTestMethod(); string GetAll(); string GetById(int id); }
该接口实现了“IApplicationService”接口,在该框架中,我们无需构建TestServiceApiController就可以对外发布webapi。用户可以直接通过访问“api/services/app/TestService/GetTestMethod”实现api 访问。
实现原理:
模块初始化阶段,注册动态API实现模块,在批量/单个注册方法中,执行“BatchApiControllerBuilder”的“Build”方法,遍历Application层程序集,查找所有已注册接口类型。然后根据类型信息获取服务名称,生成单个“ApiControllerBuilder”实例,依次执行“ApiControllerBuilder”中的方法。其中生成“action”是在“Builder”中实现的。
在“ApiControllerBuilder”的“Builder”方法中”,通过“Build”方法构建apiinfo信息并将“action”添加到controller中,最后在apicontroller管理类中注册controller信息。
以下对类和接口逐一分析
AbpApiController:集成了ApiController,框架中自定义的apicontroller都继承自该类;
IDynamicApiController:空接口,生成DynamicApiController标识;
DynamicApiController<T>:动态生成ApiController类,继承自“AbpApiController”,“IDynamicApiController”;
1 public class DynamicApiController<T>: AbpApiController, IDynamicApiController 2 { 3 public List<string> AppliedCrossCuttingConcerns { get; } 4 public DynamicApiController() 5 { 6 AppliedCrossCuttingConcerns = new List<string>(); 7 } 8 }
DynamicApiControllerInfo:封装ApiController基本信息,其中以字典的形式存储了“DynamicApiActionInfo”;
1 public DynamicApiControllerInfo( 2 string serviceName, 3 Type serviceInterfaceType, 4 Type apiControllerType, 5 Type interceptorType, 6 IFilter[] filters = null, 7 bool? isApiExplorerEnabled = null, 8 bool isProxyScriptingEnabled = true) 9 { 10 ServiceName = serviceName; 11 ServiceInterfaceType = serviceInterfaceType; 12 ApiControllerType = apiControllerType; 13 InterceptorType = interceptorType; 14 IsApiExplorerEnabled = isApiExplorerEnabled; 15 IsProxyScriptingEnabled = isProxyScriptingEnabled; 16 Filters = filters ?? new IFilter[] { }; //Assigning or initialzing the action filters. 17 Actions = new Dictionary<string, DynamicApiActionInfo>(StringComparer.InvariantCultureIgnoreCase); 18 }
IBatchApiControllerBuilder<T>/BatchApiControllerBuilder<T>:批量ApiController构建器,通过“Build”方法,根据程序集,批量生成“DynamicApiControllerInfo”;
internal class BatchApiControllerBuilder<T> : IBatchApiControllerBuilder<T> { #region 声明实例 private readonly string _servicePrefix; private readonly Assembly _assembly; private IFilter[] _filters; private Func<Type, string> _serviceNameSelector; private Func<Type, bool> _typePredicate; private bool _conventionalVerbs; private Action<IApiControllerActionBuilder<T>> _forMethodsAction; private bool? _isApiExplorerEnabled; private readonly IIocResolver _iocResolver; private readonly IDynamicApiControllerBuilder _dynamicApiControllerBuilder; private bool? _isProxyScriptingEnabled; #endregion #region 构造函数 public BatchApiControllerBuilder( IIocResolver iocResolver, IDynamicApiControllerBuilder dynamicApiControllerBuilder, Assembly assembly, string servicePrefix) { _iocResolver = iocResolver; _dynamicApiControllerBuilder = dynamicApiControllerBuilder; _assembly = assembly; _servicePrefix = servicePrefix; } #endregion #region 方法 public void Build() { var types = from type in _assembly.GetTypes() where (type.IsPublic || type.IsNestedPublic) && type.IsInterface && typeof(T).IsAssignableFrom(type) && _iocResolver.IsRegistered(type) && !RemoteServiceAttribute.IsExplicitlyDisabledFor(type) select type; if (_typePredicate != null) { types = types.Where(t => _typePredicate(t)); } foreach (var type in types) { var serviceName=_serviceNameSelector!=null? _serviceNameSelector(type) : GetConventionalServiceName(type); if (!string.IsNullOrWhiteSpace(_servicePrefix)) { serviceName = _servicePrefix + "/" + serviceName; } var builder = typeof(IDynamicApiControllerBuilder) .GetMethod("For", BindingFlags.Public | BindingFlags.Instance) .MakeGenericMethod(type) .Invoke(_dynamicApiControllerBuilder, new object[] { serviceName }); if (_filters != null) { builder.GetType() .GetMethod("WithFilters", BindingFlags.Public | BindingFlags.Instance) .Invoke(builder, new object[] { _filters }); } if (_isApiExplorerEnabled != null) { builder.GetType() .GetMethod("WithApiExplorer", BindingFlags.Public | BindingFlags.Instance) .Invoke(builder, new object[] { _isApiExplorerEnabled }); } if (_isProxyScriptingEnabled != null) { builder.GetType() .GetMethod("WithProxyScripts", BindingFlags.Public | BindingFlags.Instance) .Invoke(builder, new object[] { _isProxyScriptingEnabled.Value }); } if (_conventionalVerbs) { builder.GetType() .GetMethod("WithConventionalVerbs", BindingFlags.Public | BindingFlags.Instance) .Invoke(builder, new object[0]); } if (_forMethodsAction != null) { builder.GetType() .GetMethod("ForMethods", BindingFlags.Public | BindingFlags.Instance) .Invoke(builder, new object[] { _forMethodsAction }); } builder.GetType() .GetMethod("Build", BindingFlags.Public | BindingFlags.Instance) .Invoke(builder, new object[0]); } } private string GetConventionalServiceName(Type type) { var typeName = type.Name; typeName = typeName.RemovePostFix(ApplicationService.CommonPostfixes); if (typeName.Length > 1 && typeName.StartsWith("I") && char.IsUpper(typeName, 1)) { typeName = typeName.Substring(1); } return typeName.ToCamelCase(); } public IBatchApiControllerBuilder<T> ForMethods(Action<IApiControllerActionBuilder> action) { _forMethodsAction = action; return this; } public IBatchApiControllerBuilder<T> Where(Func<Type, bool> predicate) { _typePredicate = predicate; return this; } public IBatchApiControllerBuilder<T> WithApiExplorer(bool isEnabled) { _isApiExplorerEnabled = isEnabled; return this; } public IBatchApiControllerBuilder<T> WithConventionalVerbs() { _conventionalVerbs = true; return this; } public IBatchApiControllerBuilder<T> WithFilters(params IFilter[] filters) { _filters = filters; return this; } public IBatchApiControllerBuilder<T> WithProxyScripts(bool isEnabled) { _isProxyScriptingEnabled = isEnabled; return this; } public IBatchApiControllerBuilder<T> WithServiceName(Func<Type, string> serviceNameSelector) { _serviceNameSelector = serviceNameSelector; return this; } #endregion }
DynamicApiControllerBuilder/IDynamicApiControllerBuilder:动态ApiController构建器。“For”方法构建了“ApiControllerBuilder”实例;“ForAll”生成了“BatchApiControllerBuilder”,用于批量生成“DynamicApiControllerInfo”实例;
IApiControllerBuilder/ApiControllerBuilder:单个ApiController构建器,每个ABPApiCon以troller中存储了“Action”的基本信息。通过“Build”方法,生成“DynamicApiControllerInfo”实例,然后遍历“Action”,添加到“IDictionary<string, ApiControllerActionBuilder<T>>”字典中;
1 public void Build() 2 { 3 var controllerInfo = new DynamicApiControllerInfo( 4 ServiceName, 5 ServiceInterfaceType, 6 typeof(DynamicApiController<T>), 7 typeof(AbpDynamicApiControllerInterceptor<T>), 8 Filters, 9 IsApiExplorerEnabled, 10 IsProxyScriptingEnabled 11 ); 12 foreach (var actionBuilder in _actionBuilders.Values) 13 { 14 if (actionBuilder.DontCreate) 15 { 16 continue; 17 } 18 controllerInfo.Actions[actionBuilder.ActionName] = actionBuilder.BuildActionInfo(ConventionalVerbs); 19 20 } 21 _iocResolver.Resolve<DynamicApiControllerManager>().Register(controllerInfo); 22 }
DynamicApiActionInfo:封装了“Action”名称、请求方式等基本信息;
1 /// <summary> 2 /// 封装动态生成的ApiController的Action的信息 3 /// </summary> 4 public class DynamicApiActionInfo 5 { 6 /// <summary> 7 /// action 名称 8 /// </summary> 9 public string ActionName { get; private set; } 10 /// <summary> 11 /// 方法信息 12 /// </summary> 13 public MethodInfo Method { get; private set; } 14 public HttpVerb Verb { get; private set; } 15 /// <summary> 16 /// 过滤器 17 /// </summary> 18 public IFilter[] Filters { get; set; } 19 /// <summary> 20 /// Is API Explorer enabled. 21 /// </summary> 22 public bool? IsApiExplorerEnabled { get; set; } 23 /// <summary> 24 /// 构造函数 25 /// </summary> 26 /// <param name="actionName"></param> 27 /// <param name="verb"></param> 28 /// <param name="method"></param> 29 /// <param name="filters"></param> 30 /// <param name="isApiExplorerEnabled"></param> 31 public DynamicApiActionInfo( 32 string actionName, 33 HttpVerb verb, 34 MethodInfo method, 35 IFilter[] filters = null, 36 bool? isApiExplorerEnabled = null) 37 { 38 ActionName = actionName; 39 Verb = verb; 40 Method = method; 41 IsApiExplorerEnabled = isApiExplorerEnabled; 42 Filters = filters ?? new IFilter[] { }; //Assigning or initialzing the action filters. 43 } 44 }
IApiControllerActionBuilder/ApiControllerActionBuilder:“ApiActionController”构建器,生成“DynamicApiActionInfo”对象;
DynamicApiControllerManager:ApiController管理类,以字典的形式,管理控制器。当浏览器接受到“HttpRouteData”请求时,程序根据服务的名称从该类中查找相应的controller;
DynamicHttpControllerDescriptor:继承自“HttpControllerDescriptor”;
AbpHttpControllerSelector:继承自“DefaultHttpControllerSelector”,重写了“SelectController”方法,返回新的“HttpControllerDescriptor”。在该类中,根据路由信息中的服务类名称,查找制定的“DynamicApiControllerInfo”;
AbpApiControllerActionSelector:继承自ASP.Net WebAPI 的 ApiControllerActionSelector,AbpApiControllerActionSelector 通过调用DynamicApiServiceNameHelper的静态方法(传入routedata中的serviceNameWithAction)获取action实例;
AbpApiControllerActivator :实现了 IHttpControllerActivator接口,根据controller的类型生成指定的controller;
AbpDynamicApiControllerInterceptor<T> :方法拦截器,拦截“Action”请求,调用服务层中的方法。
try { invocation.ReturnValue=invocation.Method.Invoke(_proxiedObject, invocation.Arguments); } catch (TargetInvocationException targetInvocation) { if (targetInvocation.InnerException != null) { targetInvocation.InnerException.ReThrow(); } throw; }
拦截器在模块的初始化阶段注册:
1 public override void PostInitialize() 2 { 3 var httpConfiguration= IocManager.Resolve<IAbpWebApiConfiguration>().HttpConfiguration; 4 InitializeRoutes(httpConfiguration); 5 InitializeAspNetServices(httpConfiguration); 6 7 foreach (var controllerInfo in IocManager.Resolve<DynamicApiControllerManager>().GetAll()) 8 { 9 IocManager.IocContainer.Register( 10 Component.For(controllerInfo.InterceptorType).LifestyleTransient(), 11 Component.For(controllerInfo.ApiControllerType) 12 .Proxy.AdditionalInterfaces(controllerInfo.ServiceInterfaceType) 13 .Interceptors(controllerInfo.InterceptorType) 14 .LifestyleTransient() 15 ); 16 17 //LogHelper.Logger.DebugFormat("Dynamic web api controller is created for type '{0}' with service name '{1}'.", controllerInfo.ServiceInterfaceType.FullName, controllerInfo.ServiceName); 18 } 19 20 Configuration.Modules.AbpWebApi().HttpConfiguration.EnsureInitialized(); 21 //base.PostInitialize(); 22 }