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 项目里面的 StartupConfigureService() 进行配置。通过该方法,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 提供的程序集扫描注册来批量注册一些组件。这里执行了该方法之后,会调用 BasicConventionalRegistrarAbpAspNetCoreConventionalRegistrar 这两个注册器来批量注册符合规则的组件。

[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 的两个类型,然后重新做了一些判断逻辑进行处理,这里直接参考 AbpAutoValidateAntiforgeryTokenAuthorizationFilterAbpValidateAntiforgeryTokenAuthorizationFilter 源码。

如果不太懂 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 文件夹里面。

4.点此跳转到总目录

[Abp 源码分析]十七、ASP.NET Core 集成的更多相关文章

  1. ABP源码分析十七:DTO 自动校验的实现

    对传给Application service对象中的方法的DTO参数,ABP都会在方法真正执行前自动完成validation(根据标注到DTO对象中的validate规则). ABP是如何做到的? 思 ...

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

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

  3. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  4. ABP源码分析二十七:ABP.Entity Framework

    IRepository:接口定义了Repository常见的方法 AbpRepositoryBase:实现了IRepository接口的常见方法 EfRepositoryBase:实现了AbpRepo ...

  5. ABP源码分析三十七:ABP.Web.Api Script Proxy API

    ABP提供Script Proxy WebApi为所有的Dynamic WebApi生成访问这些WebApi的JQuery代理,AngularJs代理以及TypeScriptor代理.这些个代理就是j ...

  6. ABP源码分析四十七:ABP中的异常处理

    ABP 中异常处理的思路是很清晰的.一共五种类型的异常类. AbpInitializationException用于封装ABP初始化过程中出现的异常,只要抛出AbpInitializationExce ...

  7. [Abp 源码分析]零、文章目录

    0.系列文章目录 一.Abp 框架启动流程分析 二.模块系统 三.依赖注入 四.模块配置 五.系统设置 六.工作单元的实现 七.仓储与 Entity Framework Core 八.缓存管理 九.事 ...

  8. [Abp 源码分析]三、依赖注入

    0.简要介绍 在 Abp 框架里面,无时无刻不存在依赖注入,关于依赖注入的作用与好处我就不在这里多加赘述了,网上有很多解释的教程.在 [Abp 源码分析]一.Abp 框架启动流程分析 里面已经说过,A ...

  9. [Abp 源码分析]十一、权限验证

    0.简介 Abp 本身集成了一套权限验证体系,通过 ASP.NET Core 的过滤器与 Castle 的拦截器进行拦截请求,并进行权限验证.在 Abp 框架内部,权限分为两块,一个是功能(Featu ...

随机推荐

  1. 搭建 eclipse,maven,tomcat 环境

    1,安装jdk 2,安装eclipse,可以写java程序 3,安装tomcat,可以写简单的web页面 4,安装maven,再eclipse中可以构建maven管理的java程序 5,将maven程 ...

  2. Python 实现 KD-Tree 最近邻算法

    这里将写了一个KDTree类,仅实现了最近邻,K近邻之后若有时间再更新: from collections import namedtuple from operator import itemget ...

  3. python 10大算法之二 LogisticRegression 笔记

    使用的包 import matplotlib.pyplot as plt import pandas as pd import numpy as npfrom sklearn import datas ...

  4. aliyun install Discourse log

    apt update apt install wget wget -qO- https://get.docker.com/ | sh vim /etc/default/docker DOCKER_OP ...

  5. Excel—图表函数

    OFFSET(引用函数)它可以找一个区域,把一个区域的值都拿回来 函数语法:OFFSET(从哪个单元格开始,这个单元格下移多少行开始,然后现在的位置再往右多少列开始,要取的值是现在的位置开始下移多少行 ...

  6. SeaJS之use方法

    SeaJS 是一个js模块加载器.在 SeaJS 看来,一个文件就是一个模块.所有模块都遵循 CMD 规范 define(function(require, exports, module) { va ...

  7. 【java】-- java反射机制

    参考文章:https://blog.csdn.net/sinat_38259539/article/details/71799078    https://blog.csdn.net/wanderlu ...

  8. 【ABP.Net】2.多数据库支持&&初始化数据库

    abp默认连接的数据库是MSSQL,但是在开发过程中往往很多开发者不满足于mssql. 所以这里演示一下把mssql改成postgresql,来进行接下来的系统开发. abp的orm是用EF的.那么我 ...

  9. 数据分析 大数据之路 四 numpy 2

    NumPy 数学函数 NumPy 提供了标准的三角函数:sin().cos().tan(import numpy as np a = np.array([0,30,45,60,90])print (' ...

  10. MyBatis3系列__Demo地址

    一直光写博客了,并且感觉贴代码有点麻烦,但是以后的博客也尽量说的清楚,此外,觉得贴一下demo会好一些: 当然了,需要能够FQ哈,如果不能FQ的话建议百度或者参考这个:https://secure.s ...