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 文件夾里面。
