[Abp 源码分析]十七、ASP.NET Core 集成
0. 简介
整个 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 库,这个库是存放了很多公用方法或者工具类的,后面也会有讲述。
1. 启动流程
首先在 Abp.AspNetCore 库里面,Abp 提供了两个扩展方法。
-
第一个则是
AddAbp
方法。() 该方法是
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 项目并加载各个模块。
2. 代码分析
在 Abp.AspNetCore 库中,基本上都是针对 ASP.NET Core 的一些相关组件进行替换。大体上有过滤器、控制器、多语言、动态 API、CSRF 防御组件这几大块东西,下面我们先按照 AddAbp()
方法与 UseAbp()
方法内部注入的顺序依次进行讲解。
首先我们讲解一下 AddAbp()
方法与 UseAbp()
方法的内部做了什么操作吧。
2.1 初始化操作
2.1.1 组件替换与注册
我们首先查看 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());
}
}
这里面所做的工作基本上都是进行一些组件的注入与替换操作。
2.1.2 Abp 框架加载与初始化
Abp 框架的初始化与加载则是在 UseAbp()
方法里面进行的,首先看它的两个重载方法。
public static class AbpApplicationBuilderExtensions
{
public static void UseAbp(this IApplicationBuilder app)
{
app.UseAbp(null);
}
public static void UseAbp([NotNull] this IApplicationBuilder app, Action optionsAction)
{
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());
}
2.2 AbpAspNetCoreModule 模块
如果说要了解 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 是如何判断哪些类型是控制器类型的呢?这个问题在下面一节进行解析。
2.3 控制器与动态 API
接着上一节的疑问,那么 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);
}
}
2.3.1 路由与 HTTP.Method 配置
在 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 的调用都是在第一次请求接口的时候才会进行初始化,所以这就会造成第一次接口请求缓慢的问题,因为要做太多的初始化工作了。
2.4 过滤器
过滤器是在 AddAbp()
的时候被注入到 MVC 里面的,这些过滤器其实大部分在之前的 Abp 源码分析都有见过。
2.4.1 工作单元过滤器
工作单元过滤器是针对于启用了 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();
}
}
}
}
逻辑也很简单,这里就不再赘述了。
2.4.2 授权过滤器
授权过滤器的基本原理在文章 有讲到过,这里就不在赘述。
2.4.3 参数校验过滤器
参数校验过滤器在文章 有讲到过,这里不再赘述。
2.4.4 审计日志过滤器
其实这个过滤器,在文章 有讲到过,作用比较简单。就是构造一个 AuditInfo
对象,然后再调用 IAuditingStore
提供的持久化功能将审计信息储存起来。
2.4.5 异常过滤器
异常过滤器在文章 有讲解,这里不再赘述。
2.4.6 返回值过滤器
这个东西其实就是用于包装返回值的,因为只要使用的 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();
}
}
2.3 CSRF 防御组件
就继承自 MVC 的两个类型,然后重新做了一些判断逻辑进行处理,这里直接参考 AbpAutoValidateAntiforgeryTokenAuthorizationFilter
与 AbpValidateAntiforgeryTokenAuthorizationFilter
源码。
如果不太懂 AntiforgeryToken 相关的知识,可以参考 这一篇 博文进行了解。
2.4 多语言处理
针对于多语言的处理规则,其实在文章 就有讲解,这里只说明一下,在这个库里面通过 IApplicationBuilder
的一个扩展方法 UseAbpRequestLocalization()
注入的一堆多语言相关的组件。
public static void UseAbpRequestLocalization(this IApplicationBuilder app, Action optionsAction = 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 文件夹里面。