[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 接口,并且不是泛型類型、不是抽象類型、訪問級別為 publicif (!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);// 模型綁定配置,如果有的話,默認為 NULLconfiguration?.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: QueryStringRequestCultureProvideroptions.RequestCultureProviders.Insert(1, userProvider);options.RequestCultureProviders.Insert(2, new AbpLocalizationHeaderRequestCultureProvider());//3: CookieRequestCultureProvideroptions.RequestCultureProviders.Insert(4, new AbpDefaultRequestCultureProvider());//5: AcceptLanguageHeaderRequestCultureProvideroptionsAction?.Invoke(options);userProvider.CookieProvider = options.RequestCultureProviders.OfType<CookieRequestCultureProvider>().FirstOrDefault();userProvider.HeaderProvider = options.RequestCultureProviders.OfType<AbpLocalizationHeaderRequestCultureProvider>().FirstOrDefault();app.UseRequestLocalization(options);} }這些組件都存放在?Abp.AspNetCore?庫下面的?Localization?文件夾里面。
作者:myzony
出處:https://www.cnblogs.com/myzony/p/9993386.html
公眾號“碼俠江湖”所發表內容注明來源的,版權歸原出處所有(無法查證版權的或者未注明出處的均來自網絡,系轉載,轉載的目的在于傳遞更多信息,版權屬于原作者。如有侵權,請聯系,筆者會第一時間刪除處理!
掃描二維碼
獲取更多精彩
碼俠江湖
喜歡就點個在看再走吧
總結
以上是生活随笔為你收集整理的[Abp 源码分析]ASP.NET Core 集成的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C#多线程和异步(二)——Task和as
- 下一篇: [Abp 源码分析]后台作业与后台工作者