[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<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<AspNetCoreAppModule>();
}
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<TStartupModule>(this IServiceCollection services, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
where TStartupModule : AbpModule
{
// 传入启动模块,构建 AddAbpBootstrapper 对象,并将其注入到 Ioc 容器当中
var abpBootstrapper = AddAbpBootstrapper<TStartupModule>(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<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
// 替换掉默认的控制器构造类,改用 DI 框架负责控制器的创建
services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
// 替换掉默认的视图组件构造类,改用 DI 框架负责视图组件的创建
services.Replace(ServiceDescriptor.Singleton<IViewComponentActivator, ServiceBasedViewComponentActivator>());
// 替换掉默认的 Antiforgery 类 (主要用于非浏览器的客户端进行调用)
services.Replace(ServiceDescriptor.Transient<AutoValidateAntiforgeryTokenAuthorizationFilter, AbpAutoValidateAntiforgeryTokenAuthorizationFilter>());
services.Replace(ServiceDescriptor.Transient<ValidateAntiforgeryTokenAuthorizationFilter, AbpValidateAntiforgeryTokenAuthorizationFilter>());
// 添加 Feature Provider,用于判断某个类型是否为控制器
var partManager = services.GetSingletonServiceOrNull<ApplicationPartManager>();
partManager?.FeatureProviders.Add(new AbpAppServiceControllerFeatureProvider(iocResolver));
// 配置 JSON 序列化
services.Configure<MvcJsonOptions>(jsonOptions =>
{
jsonOptions.SerializerSettings.ContractResolver = new AbpMvcContractResolver(iocResolver)
{
NamingStrategy = new CamelCaseNamingStrategy()
};
});
// 配置 MVC 相关的东西,包括控制器生成和过滤器绑定
services.Configure<MvcOptions>(mvcOptions =>
{
mvcOptions.AddAbp(services);
});
// 配置 Razor 相关参数
services.Insert(0,
ServiceDescriptor.Singleton<IConfigureOptions<RazorViewEngineOptions>>(
new ConfigureOptions<RazorViewEngineOptions>(
(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<AbpApplicationBuilderOptions> 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 的初始化方法,加载所有模块
abpBootstrapper.Initialize();
// 绑定 ASP.NET Core 的生命周期,当网站关闭时,调用 AbpBootstrapper 对象的 Dispose() 方法
var applicationLifetime = app.ApplicationServices.GetService<IApplicationLifetime>();
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<IAbpAspNetCoreConfiguration, AbpAspNetCoreConfiguration>();
Configuration.ReplaceService<IPrincipalAccessor, AspNetCorePrincipalAccessor>(DependencyLifeStyle.Transient);
Configuration.ReplaceService<IAbpAntiForgeryManager, AbpAspNetCoreAntiForgeryManager>(DependencyLifeStyle.Transient);
Configuration.ReplaceService<IClientInfoProvider, HttpContextClientInfoProvider>(DependencyLifeStyle.Transient);
Configuration.Modules.AbpAspNetCore().FormBodyBindingIgnoredTypes.Add(typeof(IFormFile));
Configuration.MultiTenancy.Resolvers.Add<DomainTenantResolveContributor>();
Configuration.MultiTenancy.Resolvers.Add<HttpHeaderTenantResolveContributor>();
Configuration.MultiTenancy.Resolvers.Add<HttpCookieTenantResolveContributor>();
}
// ... 其他代码
}
可以看到在预加载方法内部,该模块通过 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<AbpAspNetCoreConfiguration>();
// 获得 ApplicationPart 管理器,用于发现指定程序集的应用服务,使其作为控制器进行初始化
var partManager = IocManager.Resolve<ApplicationPartManager>();
// 获得模块管理器,用于插件模块的加载
var moduleManager = IocManager.Resolve<IAbpModuleManager>();
// 获得控制器所在的程序集集合
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<IOptions<AntiforgeryOptions>>(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<ApplicationPartManager>();
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<RemoteServiceAttribute>(typeInfo);
// 如果有该特性,并且在特性内部的 IsEnabled 为 False 则该类型不能作为一个控制器
if (remoteServiceAttr != null && !remoteServiceAttr.IsEnabledFor(type))
{
return false;
}
// 从模块配置当中取得一个 Func 委托,该委托用于指定某些特性类型是否为一个控制器
var configuration = _iocResolver.Resolve<AbpAspNetCoreConfiguration>().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<AbpAspNetCoreConfiguration> _configuration;
public AbpAppServiceConvention(IServiceCollection services)
{
// 使用 Services 获得模块的配置类,并赋值
_configuration = new Lazy<AbpAspNetCoreConfiguration>(() => services
.GetSingletonService<AbpBootstrapper>()
.IocManager
.Resolve<AbpAspNetCoreConfiguration>(), 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<RemoteServiceAttribute>(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 授权过滤器
授权过滤器的基本原理在文章 《[Abp 源码分析]十一、权限验证》 有讲到过,这里就不在赘述。
2.4.3 参数校验过滤器
参数校验过滤器在文章 《[Abp 源码分析]十四、DTO 自动验证》 有讲到过,这里不再赘述。
2.4.4 审计日志过滤器
其实这个过滤器,在文章 《十五、自动审计记录》 有讲到过,作用比较简单。就是构造一个 AuditInfo
对象,然后再调用 IAuditingStore
提供的持久化功能将审计信息储存起来。
2.4.5 异常过滤器
异常过滤器在文章 《[Abp 源码分析]十、异常处理》 有讲解,这里不再赘述。
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 多语言处理
针对于多语言的处理规则,其实在文章 《[Abp 源码分析]十三、多语言(本地化)处理》 就有讲解,这里只说明一下,在这个库里面通过 IApplicationBuilder
的一个扩展方法 UseAbpRequestLocalization()
注入的一堆多语言相关的组件。
public static void UseAbpRequestLocalization(this IApplicationBuilder app, Action<RequestLocalizationOptions> optionsAction = null)
{
var iocResolver = app.ApplicationServices.GetRequiredService<IIocResolver>();
using (var languageManager = iocResolver.ResolveAsDisposable<ILanguageManager>())
{
// 获得当前服务器支持的区域文化列表
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<CookieRequestCultureProvider>().FirstOrDefault();
userProvider.HeaderProvider = options.RequestCultureProviders.OfType<AbpLocalizationHeaderRequestCultureProvider>().FirstOrDefault();
app.UseRequestLocalization(options);
}
}
这些组件都存放在 Abp.AspNetCore 库下面的 Localization 文件夹里面。