本文共 18926 字,大约阅读时间需要 63 分钟。
点击上方蓝字关注我们
整个 Abp 框架最为核心的除了 Abp 库之外,其次就是 Abp.AspNetCore 库了。虽然 Abp 本身是可以用于控制台程序的,不过那样的话 Abp 就基本没什么用,还是需要集合 ASP.NET Core 才能发挥它真正的作用。
在 Abp.AspNetCore 库里面,Abp 通过 WindsorRegistrationHelper.CreateServiceProvider()
接管了 ASP.NET Core 自带的 Ioc 容器。除此之外,还针对 Controller
的生成规则也进行了替换,以便实现 Dynamic API 功能。
总的来说,整个 Abp 框架与 ASP.NET Core 集成的功能都放在这个库里面的,所以说这个库还是相当重要的。这个项目又依赖于 Abp.Web.Common 库,这个库是存放了很多公用方法或者工具类的,后面也会有讲述。
首先在 Abp.AspNetCore 库里面,Abp 提供了两个扩展方法。
第一个则是 AddAbp<TStartupModule>()
方法。
该方法是 IServiceCollection
的扩展方法,用于在 ASP.NET Core 项目里面的 Startup
的 ConfigureService()
进行配置。通过该方法,Abp 会接管默认的 DI 框架,改为使用 Castle Windsor,并且进行一些 MVC 相关的配置。
第二个则是 UseAbp()
方法。
该方法是 IApplicationBuilder
的扩展方法,用于 Startup
类里面的 Configure()
配置。通过该方法,Abp 会执行一系列初始化操作,在这个时候 Abp 框架才算是真正地启动了起来。
下面则是常规的用法:
public class Startup{ public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(); return services.AddAbp(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); app.UseAbp(); }}
基本上可以说,UseAbp()
就是整个 Abp 框架的入口点,负责调用 AbpBootstrapper
来初始化整个 Abp 项目并加载各个模块。
在 Abp.AspNetCore 库中,基本上都是针对 ASP.NET Core 的一些相关组件进行替换。大体上有过滤器、控制器、多语言、动态 API、CSRF 防御组件这几大块东西,下面我们先按照 AddAbp()
方法与 UseAbp()
方法内部注入的顺序依次进行讲解。
首先我们讲解一下 AddAbp()
方法与 UseAbp()
方法的内部做了什么操作吧。
我们首先查看 AddAbp()
方法,该方法存在于 AbpServiceCollectionExtensions.cs
文件之中。
public static IServiceProvider AddAbp(this IServiceCollection services, [CanBeNull] Action optionsAction = null) where TStartupModule : AbpModule{ // 传入启动模块,构建 AddAbpBootstrapper 对象,并将其注入到 Ioc 容器当中 var abpBootstrapper = AddAbpBootstrapper (services, optionsAction); // 配置 ASP.NET Core 相关的东西 ConfigureAspNetCore(services, abpBootstrapper.IocManager); // 返回一个新的 IServiceProvider 用于替换自带的 DI 框架 return WindsorRegistrationHelper.CreateServiceProvider(abpBootstrapper.IocManager.IocContainer, services);}
该方法作为 IServiceCollection
的扩展方法存在,方便用户进行使用,而在 ConfigureAspNetCore()
方法之中,主要针对 ASP.NET Core 进行了相关的配置。
private static void ConfigureAspNetCore(IServiceCollection services, IIocResolver iocResolver){ // 手动注入 HTTPContext 访问器等 services.TryAddSingleton(); services.TryAddSingleton (); // 替换掉默认的控制器构造类,改用 DI 框架负责控制器的创建 services.Replace(ServiceDescriptor.Transient ()); // 替换掉默认的视图组件构造类,改用 DI 框架负责视图组件的创建 services.Replace(ServiceDescriptor.Singleton ()); // 替换掉默认的 Antiforgery 类 (主要用于非浏览器的客户端进行调用) services.Replace(ServiceDescriptor.Transient ()); services.Replace(ServiceDescriptor.Transient ()); // 添加 Feature Provider,用于判断某个类型是否为控制器 var partManager = services.GetSingletonServiceOrNull (); partManager?.FeatureProviders.Add(new AbpAppServiceControllerFeatureProvider(iocResolver)); // 配置 JSON 序列化 services.Configure (jsonOptions => { jsonOptions.SerializerSettings.ContractResolver = new AbpMvcContractResolver(iocResolver) { NamingStrategy = new CamelCaseNamingStrategy() }; }); // 配置 MVC 相关的东西,包括控制器生成和过滤器绑定 services.Configure (mvcOptions => { mvcOptions.AddAbp(services); }); // 配置 Razor 相关参数 services.Insert(0, ServiceDescriptor.Singleton >( new ConfigureOptions ( (options) => { options.FileProviders.Add(new EmbeddedResourceViewFileProvider(iocResolver)); } ) ) );}
之后来到 mvcOptions.AddAbp(services);
所指向的类型,可以看到如下代码:
internal static class AbpMvcOptionsExtensions{ public static void AddAbp(this MvcOptions options, IServiceCollection services) { AddConventions(options, services); AddFilters(options); AddModelBinders(options); } // 添加 Abp 定义的 Controller 约定,主要用于配置 Action 方法的 HttpMethod 与路由 private static void AddConventions(MvcOptions options, IServiceCollection services) { options.Conventions.Add(new AbpAppServiceConvention(services)); } // 添加各种过滤器 private static void AddFilters(MvcOptions options) { options.Filters.AddService(typeof(AbpAuthorizationFilter)); options.Filters.AddService(typeof(AbpAuditActionFilter)); options.Filters.AddService(typeof(AbpValidationActionFilter)); options.Filters.AddService(typeof(AbpUowActionFilter)); options.Filters.AddService(typeof(AbpExceptionFilter)); options.Filters.AddService(typeof(AbpResultFilter)); } // 添加 Abp 定义的模型绑定器,主要是为了处理时间类型 private static void AddModelBinders(MvcOptions options) { options.ModelBinderProviders.Insert(0, new AbpDateTimeModelBinderProvider()); }}
这里面所做的工作基本上都是进行一些组件的注入与替换操作。
Abp 框架的初始化与加载则是在 UseAbp()
方法里面进行的,首先看它的两个重载方法。
public static class AbpApplicationBuilderExtensions{ public static void UseAbp(this IApplicationBuilder app) { app.UseAbp(null); } public static void UseAbp([NotNull] this IApplicationBuilder app, ActionoptionsAction) { Check.NotNull(app, nameof(app)); var options = new AbpApplicationBuilderOptions(); // 获取用户传入的配置操作 optionsAction?.Invoke(options); // 是否启用 Castle 的日志工厂 if (options.UseCastleLoggerFactory) { app.UseCastleLoggerFactory(); } // Abp 框架开始加载并初始化 InitializeAbp(app); // 是否根据请求进行本地化处理 if (options.UseAbpRequestLocalization) { //TODO: 这个中间件应该放在授权中间件之后 app.UseAbpRequestLocalization(); } // 是否使用安全头 if (options.UseSecurityHeaders) { app.UseAbpSecurityHeaders(); } } // ... 其他代码}
在 UseAbp()
当中你需要注意的是 InitializeAbp(app);
方法。该方法在调用的时候,Abp 才会真正开始地进行初始化。在这个时候,Abp 会遍历所有项目并且执行它们的模块的三个生命周期方法。当所有模块都被调用过之后,Abp 框架就已经准备就绪了。
private static void InitializeAbp(IApplicationBuilder app){ // 使用 IApplicationBuilder 从 IServiceCollection 中获取之前 AddAbp() 所注入的 AbpBootstrapper 对象 var abpBootstrapper = app.ApplicationServices.GetRequiredService(); // 调用 AbpBootstrapper 的初始化方法,加载所有模块 abpBootstrapper.Initialize(); // 绑定 ASP.NET Core 的生命周期,当网站关闭时,调用 AbpBootstrapper 对象的 Dispose() 方法 var applicationLifetime = app.ApplicationServices.GetService (); applicationLifetime.ApplicationStopping.Register(() => abpBootstrapper.Dispose());}
如果说要了解 Abp 某一个库的话,第一步肯定是阅读该库提供的模块类型。因为不管是哪一个库,都会有一个模块进行库的基本配置与初始化动作,而且肯定是这个库第一个被 Abp 框架所调用到的类型。
首先我们按照模块的生命周期来阅读模块的源代码,下面是模块的预加载 (PreInitialize()
)方法:
[DependsOn(typeof(AbpWebCommonModule))]public class AbpAspNetCoreModule : AbpModule{ public override void PreInitialize() { // 添加一个新的注册规约,用于批量注册视图组件 IocManager.AddConventionalRegistrar(new AbpAspNetCoreConventionalRegistrar()); IocManager.Register(); Configuration.ReplaceService (DependencyLifeStyle.Transient); Configuration.ReplaceService (DependencyLifeStyle.Transient); Configuration.ReplaceService (DependencyLifeStyle.Transient); Configuration.Modules.AbpAspNetCore().FormBodyBindingIgnoredTypes.Add(typeof(IFormFile)); Configuration.MultiTenancy.Resolvers.Add (); Configuration.MultiTenancy.Resolvers.Add (); Configuration.MultiTenancy.Resolvers.Add (); } // ... 其他代码}
可以看到在预加载方法内部,该模块通过 ReplaceService
替换了许多接口实现,也有很多注册了许多组件,这其中就包括模块的配置类 IAbpAspNetCoreConfiguration
。
[DependsOn(typeof(AbpWebCommonModule))]public class AbpAspNetCoreModule : AbpModule{ // ... 其他代码 public override void Initialize() { IocManager.RegisterAssemblyByConvention(typeof(AbpAspNetCoreModule).GetAssembly()); } // ... 其他代码}
初始化方法也更加简洁,则是通过 IocManager
提供的程序集扫描注册来批量注册一些组件。这里执行了该方法之后,会调用 BasicConventionalRegistrar
与 AbpAspNetCoreConventionalRegistrar
这两个注册器来批量注册符合规则的组件。
[DependsOn(typeof(AbpWebCommonModule))]public class AbpAspNetCoreModule : AbpModule{ // ... 其他代码 public override void PostInitialize() { AddApplicationParts(); ConfigureAntiforgery(); } private void AddApplicationParts() { // 获得当前库的配置类 var configuration = IocManager.Resolve(); // 获得 ApplicationPart 管理器,用于发现指定程序集的应用服务,使其作为控制器进行初始化 var partManager = IocManager.Resolve (); // 获得模块管理器,用于插件模块的加载 var moduleManager = IocManager.Resolve (); // 获得控制器所在的程序集集合 var controllerAssemblies = configuration.ControllerAssemblySettings.Select(s => s.Assembly).Distinct(); foreach (var controllerAssembly in controllerAssemblies) { // 用程序集构造 AssemblyPart ,以便后面通过 AbpAppServiceControllerFeatureProvider 判断哪些类型是控制器 partManager.ApplicationParts.Add(new AssemblyPart(controllerAssembly)); } // 从插件的程序集 var plugInAssemblies = moduleManager.Modules.Where(m => m.IsLoadedAsPlugIn).Select(m => m.Assembly).Distinct(); foreach (var plugInAssembly in plugInAssemblies) { partManager.ApplicationParts.Add(new AssemblyPart(plugInAssembly)); } } // 配置安全相关设置 private void ConfigureAntiforgery() { IocManager.Using >(optionsAccessor => { optionsAccessor.Value.HeaderName = Configuration.Modules.AbpWebCommon().AntiForgery.TokenHeaderName; }); }}
该模块的第三个生命周期方法主要是为了提供控制器所在的程序集,以便 ASP.NET Core MVC 进行控制器构造,其实这里仅仅是添加程序集的,而程序集有那么多类型,那么 MVC 是如何判断哪些类型是控制器类型的呢?这个问题在下面一节进行解析。
接着上一节的疑问,那么 MVC 所需要的控制器从哪儿来呢?其实是通过在 AddAbp()
所添加的 AbpAppServiceControllerFeatureProvider
实现的。
private static void ConfigureAspNetCore(IServiceCollection services, IIocResolver iocResolver){ // ... 其他代码 var partManager = services.GetSingletonServiceOrNull(); partManager?.FeatureProviders.Add(new AbpAppServiceControllerFeatureProvider(iocResolver)); // ... 其他代码}
下面我们分析一下该类型的内部构造是怎样的,首先看一下它的定义与构造器:
public class AbpAppServiceControllerFeatureProvider : ControllerFeatureProvider{ private readonly IIocResolver _iocResolver; public AbpAppServiceControllerFeatureProvider(IIocResolver iocResolver) { _iocResolver = iocResolver; } // ... 其他代码}
类型定义都比较简单,继承自 ControllerFeatureProvider
,然后在构造函数传入了一个解析器。在该类型内部,重写了父类的一个 IsController()
方法,这个方法会传入一个 TypeInfo
对象。其实你看到这里应该就明白了,之前在模块当中添加的程序集,最终会被 MVC 解析出所有类型然后调用这个 Provider 来判断哪些类型是控制器。
如果该类型是控制器的话,则返回 True,不是控制器则返回 False
。
public class AbpAppServiceControllerFeatureProvider : ControllerFeatureProvider{ // ... 其他代码 protected override bool IsController(TypeInfo typeInfo) { // 获得 Type 对象 var type = typeInfo.AsType(); // 判断传入的类型是否继承自 IApplicationService 接口,并且不是泛型类型、不是抽象类型、访问级别为 public if (!typeof(IApplicationService).IsAssignableFrom(type) || !typeInfo.IsPublic || typeInfo.IsAbstract || typeInfo.IsGenericType) { // 不满足上述条件则说明这个类型不能作为一个控制器 return false; } // 获取类型上面是否标注有 RemoteServiceAttribute 特性。 var remoteServiceAttr = ReflectionHelper.GetSingleAttributeOrDefault(typeInfo); // 如果有该特性,并且在特性内部的 IsEnabled 为 False 则该类型不能作为一个控制器 if (remoteServiceAttr != null && !remoteServiceAttr.IsEnabledFor(type)) { return false; } // 从模块配置当中取得一个 Func 委托,该委托用于指定某些特性类型是否为一个控制器 var configuration = _iocResolver.Resolve ().ControllerAssemblySettings.GetSettingOrNull(type); return configuration != null && configuration.TypePredicate(type); }}
在 MVC 确定好哪些类型是控制器之后,来到了 AbpAppServiceConvention
内部,在这个方法内部则要进行路由和 Action 的一些具体参数。
这里我们首先看一下这个 AbpAppServiceConvention
类型的基本定义与构造。
public class AbpAppServiceConvention : IApplicationModelConvention{ // 模块的配置类 private readonly Lazy_configuration; public AbpAppServiceConvention(IServiceCollection services) { // 使用 Services 获得模块的配置类,并赋值 _configuration = new Lazy (() => services .GetSingletonService () .IocManager .Resolve (), true); } // 实现的 IApplicationModelConvention 定义的 Apply 方法 public void Apply(ApplicationModel application) { // 遍历控制器 foreach (var controller in application.Controllers) { var type = controller.ControllerType.AsType(); var configuration = GetControllerSettingOrNull(type); // 判断控制器类型是否继承自 IApplicationService 接口 if (typeof(IApplicationService).GetTypeInfo().IsAssignableFrom(type)) { // 重新定义控制器名字,如果控制器名字有以 ApplicationService.CommonPostfixes 定义的后缀结尾,则移除后缀之后,再作为控制器名字 controller.ControllerName = controller.ControllerName.RemovePostFix(ApplicationService.CommonPostfixes); // 模型绑定配置,如果有的话,默认为 NULL configuration?.ControllerModelConfigurer(controller); // 配置控制器 Area 路由 ConfigureArea(controller, configuration); // 配置控制器路由与 Action 等... ConfigureRemoteService(controller, configuration); } else { var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault (type.GetTypeInfo()); if (remoteServiceAtt != null && remoteServiceAtt.IsEnabledFor(type)) { ConfigureRemoteService(controller, configuration); } } } } // ... 其他代码}
这里我们再跳转到 ConfigureRemoteService()
方法内部可以看到其定义如下:
private void ConfigureRemoteService(ControllerModel controller, [CanBeNull] AbpControllerAssemblySetting configuration){ // 配置控制器与其 Action 的可见性 ConfigureApiExplorer(controller); // 配置 Action 的路由 ConfigureSelector(controller, configuration); // 配置 Action 传参形式 ConfigureParameters(controller);}
【注意】
AbpAppServiceControllerFeatureProvider 与 AbpAppServiceConvention 的调用都是在第一次请求接口的时候才会进行初始化,所以这就会造成第一次接口请求缓慢的问题,因为要做太多的初始化工作了。
过滤器是在 AddAbp()
的时候被注入到 MVC 里面的,这些过滤器其实大部分在之前的 Abp 源码分析都有见过。
工作单元过滤器是针对于启用了 UnitOfWorkAttribute
特性标签的应用服务/控制器进行处理。其核心思想就是在调用接口时,在最外层就使用 IUnitOfWorkManager
构建一个新的工作单元,然后将应用服务/控制器的调用就包在内部了。
首先来看一下这个过滤器内部定义与构造器:
public class AbpUowActionFilter : IAsyncActionFilter, ITransientDependency{ // 工作单元管理器 private readonly IUnitOfWorkManager _unitOfWorkManager; // ASP.NET Core 配置类 private readonly IAbpAspNetCoreConfiguration _aspnetCoreConfiguration; // 工作单元配置类 private readonly IUnitOfWorkDefaultOptions _unitOfWorkDefaultOptions; public AbpUowActionFilter( IUnitOfWorkManager unitOfWorkManager, IAbpAspNetCoreConfiguration aspnetCoreConfiguration, IUnitOfWorkDefaultOptions unitOfWorkDefaultOptions) { _unitOfWorkManager = unitOfWorkManager; _aspnetCoreConfiguration = aspnetCoreConfiguration; _unitOfWorkDefaultOptions = unitOfWorkDefaultOptions; } // ... 其他代码}
可以看到在这个工作单元过滤器,他通过实现 ITransientDependency
来完成自动注入,之后使用构造注入了两个配置类和一个工作单元管理器。
在其 OnActionExecutionAsync()
方法内部的代码如下:
public class AbpUowActionFilter : IAsyncActionFilter, ITransientDependency{ // ... 其他代码 public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // 判断当前调用是否是控制器方法 if (!context.ActionDescriptor.IsControllerAction()) { // 如果不是,则不执行任何操作 await next(); return; } // 获得控制器/应用服务所标记的工作单元特性 var unitOfWorkAttr = _unitOfWorkDefaultOptions .GetUnitOfWorkAttributeOrNull(context.ActionDescriptor.GetMethodInfo()) ?? _aspnetCoreConfiguration.DefaultUnitOfWorkAttribute; // 如果特性的 IsDisabled 为 True 的话,不执行任何操作 if (unitOfWorkAttr.IsDisabled) { await next(); return; } // 使用工作单元管理器开启一个新的工作单元 using (var uow = _unitOfWorkManager.Begin(unitOfWorkAttr.CreateOptions())) { var result = await next(); if (result.Exception == null || result.ExceptionHandled) { await uow.CompleteAsync(); } } }}
逻辑也很简单,这里就不再赘述了。
授权过滤器的基本原理在文章 《[Abp 源码分析]十一、权限验证》 有讲到过,这里就不在赘述。
参数校验过滤器在文章 《[Abp 源码分析]十四、DTO 自动验证》 有讲到过,这里不再赘述。
其实这个过滤器,在文章 《十五、自动审计记录》 有讲到过,作用比较简单。就是构造一个 AuditInfo
对象,然后再调用 IAuditingStore
提供的持久化功能将审计信息储存起来。
异常过滤器在文章 《[Abp 源码分析]十、异常处理》 有讲解,这里不再赘述。
这个东西其实就是用于包装返回值的,因为只要使用的 Abp 框架,其默认的返回值都会进行包装,那我们可以通过 DontWarpAttribute
来取消掉这层包装。
那么包装是在什么地方进行的呢?其实就在 AbpResultFilter
的内部进行的。
public class AbpResultFilter : IResultFilter, ITransientDependency{ private readonly IAbpAspNetCoreConfiguration _configuration; private readonly IAbpActionResultWrapperFactory _actionResultWrapperFactory; public AbpResultFilter(IAbpAspNetCoreConfiguration configuration, IAbpActionResultWrapperFactory actionResultWrapper) { _configuration = configuration; _actionResultWrapperFactory = actionResultWrapper; } public virtual void OnResultExecuting(ResultExecutingContext context) { if (!context.ActionDescriptor.IsControllerAction()) { return; } var methodInfo = context.ActionDescriptor.GetMethodInfo(); var wrapResultAttribute = ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault( methodInfo, _configuration.DefaultWrapResultAttribute ); if (!wrapResultAttribute.WrapOnSuccess) { return; } // 包装对象 _actionResultWrapperFactory.CreateFor(context).Wrap(context); } public virtual void OnResultExecuted(ResultExecutedContext context) { //no action }}
这里传入了 context ,然后基于这个返回值来进行不同的操作:
public class AbpActionResultWrapperFactory : IAbpActionResultWrapperFactory{ public IAbpActionResultWrapper CreateFor(ResultExecutingContext actionResult) { Check.NotNull(actionResult, nameof(actionResult)); if (actionResult.Result is ObjectResult) { return new AbpObjectActionResultWrapper(actionResult.HttpContext.RequestServices); } if (actionResult.Result is JsonResult) { return new AbpJsonActionResultWrapper(); } if (actionResult.Result is EmptyResult) { return new AbpEmptyActionResultWrapper(); } return new NullAbpActionResultWrapper(); }}
就继承自 MVC 的两个类型,然后重新做了一些判断逻辑进行处理,这里直接参考 AbpAutoValidateAntiforgeryTokenAuthorizationFilter
与 AbpValidateAntiforgeryTokenAuthorizationFilter
源码。
如果不太懂 AntiforgeryToken 相关的知识,可以参考 这一篇 博文进行了解。
针对于多语言的处理规则,其实在文章 《[Abp 源码分析]十三、多语言(本地化)处理》 就有讲解,这里只说明一下,在这个库里面通过 IApplicationBuilder
的一个扩展方法 UseAbpRequestLocalization()
注入的一堆多语言相关的组件。
public static void UseAbpRequestLocalization(this IApplicationBuilder app, ActionoptionsAction = null){ var iocResolver = app.ApplicationServices.GetRequiredService (); using (var languageManager = iocResolver.ResolveAsDisposable ()) { // 获得当前服务器支持的区域文化列表 var supportedCultures = languageManager.Object .GetLanguages() .Select(l => CultureInfo.GetCultureInfo(l.Name)) .ToArray(); var options = new RequestLocalizationOptions { SupportedCultures = supportedCultures, SupportedUICultures = supportedCultures }; var userProvider = new AbpUserRequestCultureProvider(); //0: QueryStringRequestCultureProvider options.RequestCultureProviders.Insert(1, userProvider); options.RequestCultureProviders.Insert(2, new AbpLocalizationHeaderRequestCultureProvider()); //3: CookieRequestCultureProvider options.RequestCultureProviders.Insert(4, new AbpDefaultRequestCultureProvider()); //5: AcceptLanguageHeaderRequestCultureProvider optionsAction?.Invoke(options); userProvider.CookieProvider = options.RequestCultureProviders.OfType ().FirstOrDefault(); userProvider.HeaderProvider = options.RequestCultureProviders.OfType ().FirstOrDefault(); app.UseRequestLocalization(options); }}
这些组件都存放在 Abp.AspNetCore 库下面的 Localization 文件夹里面。
作者:myzony
出处:https://www.cnblogs.com/myzony/p/9993386.html
公众号“码侠江湖”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!
扫描二维码
获取更多精彩
码侠江湖
喜欢就点个在看再走吧