[Abp 源码分析]ASP.NET Core 集成

Posted 码侠江湖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[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 接口,并且不是泛型类型、不是抽象类型、访问级别为 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);
}
}

[Abp 源码分析]ASP.NET Core 集成

2.3.1 路由与 HTTP.Method 配置

在 MVC 确定好哪些类型是控制器之后,来到了 AbpAppServiceConvention 内部,在这个方法内部则要进行路由和 Action 的一些具体参数。

[Abp 源码分析]ASP.NET Core 集成

这里我们首先看一下这个 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 文件夹里面。

出处:https://www.cnblogs.com/myzony/p/9993386.html




扫描二维码

获取更多精彩

码侠江湖





喜欢就点个在看再走吧

以上是关于[Abp 源码分析]ASP.NET Core 集成的主要内容,如果未能解决你的问题,请参考以下文章

使用 ASP.NET Core, Entity Framework Core 和 ABP 创建N层Web应用 第二篇

基于DDD的.NET开发框架 - ABP日志Logger集成

ABP(ASP.NET core +Multi PageWeb Application) 根据官网给的例子做的简单的页面

ABP vue+asp.net core yarn serve报 Cannot find module 'typescript/package.json错误

关于Asp.net core配置信息读取的源码分析梳理

关于Asp.net core配置信息读取的源码分析梳理