[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 授權過濾器
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 多語言處理
針對於多語言的處理規則,其實在文章 《[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 資料夾裡面。