点击上方蓝字关注我们
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
公众号“码侠江湖”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!
扫描二维码
获取更多精彩
码侠江湖
喜欢就点个在看再走吧