点击上方蓝字关注我们

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 集成相关推荐

  1. 【转】ABP源码分析八:Logger集成

    ABP使用Castle日志记录工具,并且可以使用不同的日志类库,比如:Log4Net, NLog, Serilog... 等等.对于所有的日志类库,Castle提供了一个通用的接口来实现,我们可以很方 ...

  2. 【转】ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  3. [Abp 源码分析]权限验证

    点击上方蓝字关注我们 0.简介 Abp 本身集成了一套权限验证体系,通过 ASP.NET Core 的过滤器与 Castle 的拦截器进行拦截请求,并进行权限验证.在 Abp 框架内部,权限分为两块, ...

  4. [Abp 源码分析]多租户体系与权限验证

    点击上方蓝字关注我们 0.简介 承接上篇文章我们会在这篇文章详细解说一下 Abp 是如何结合 IPermissionChecker 与 IFeatureChecker 来实现一个完整的多租户系统的权限 ...

  5. 【转】ABP源码分析三:ABP Module

    Abp是基于模块化设计思想进行构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modul ...

  6. 【转】ABP源码分析四十二:ZERO的身份认证

    ABP Zero模块通过自定义实现Asp.Net Identity完成身份认证功能, 对Asp.Net Identity做了较大幅度的扩展.同时重写了ABP核心模块中的permission功能,以实现 ...

  7. 【转】ABP源码分析四十一:ZERO的Audit,Setting,Background Job

    AuditLog: 继承自Entity<long>的实体类.封装AuditLog的信息. AuditingStore: 实现了IAuditingStore接口,实现了将AuditLog的信 ...

  8. 【转】ABP源码分析三十九:ABP.Hangfire

    ABP对HangFire的集成主要是通过实现IBackgroundJobManager接口的HangfireBackgroundJobManager类完成的. HangfireBackgroundJo ...

  9. 【转】ABP源码分析三十六:ABP.Web.Api

    这里的内容和ABP 动态webapi没有关系.除了动态webapi,ABP必然是支持使用传统的webApi.ABP.Web.Api模块中实现了一些同意的基础功能,以方便我们创建和使用asp.net w ...

最新文章

  1. 强化学习在携程酒店推荐排序中的应用探索
  2. NDIS6缓冲相关实验和经验
  3. xm文件怎样转换成mp3_酷狗音乐如何将歌曲转换成MP3格式?方法超级简单
  4. JQuery的ready函数与JS的onload的区别详解
  5. 【图像处理】——Python OpenCV实现形态学膨胀、腐蚀开闭操作(可以用于图像滤波、图像分割等)
  6. C++字符串分割替换 ubuntu版本
  7. 将EntityManager.refresh添加到所有Spring数据存储库
  8. Day1 了解web前端
  9. Linux系统开机自动加载驱动module
  10. Linux中创建用户并且配置sudo权限,百分百简单有效
  11. win11如何自动启用禁用设置时区 windows11自动启用禁用设置时区的步骤方法
  12. 编程基础(三)——体系结构之二
  13. doors二次开发-dxl开发
  14. matlab编程计算正态分布,MATLAB如何使用normpdf函数计算正态分布的概率密度
  15. python histogram函数_Python numpy.histogram_bin_edges函数方法的使用
  16. B-spline Curves 学习之B样条曲线定义(4)
  17. 当你觉得焦虑,烦躁的时候怎么办?
  18. 树莓派和主机相互PING
  19. macbook pro m1 在 EXCEL 中安装 Excel2Latex
  20. fitbit aria体脂秤二次开发遇到的问题

热门文章

  1. 62、滑动窗口的最大值
  2. js(jQuery)获取时间的方法及常用时间类
  3. Swift数据类型简介(二)
  4. 男人会为女人改变多少
  5. ZGY的Excel特征提取器初期版本完成
  6. javac手动编译servlet
  7. 什么是自然语言处理,它如何工作?
  8. Java基类共同属性设置_多选择基类的访问属性-Java初学笔记
  9. mysql 1005 - can't create table_关于创建数据表报错一例(ERROR 1005 Can’t create table (errno: 121))...
  10. Autofac之自动装配