1. 程式人生 > >[Abp 原始碼分析]十七、ASP.NET Core 整合

[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 專案裡面的 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 授權過濾器

2.4.3 引數校驗過濾器

2.4.4 審計日誌過濾器

其實這個過濾器,在文章 《十五、自動審計記錄》 有講到過,作用比較簡單。就是構造一個 AuditInfo 物件,然後再呼叫 IAuditingStore 提供的持久化功能將審計資訊儲存起來。

2.4.5 異常過濾器

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 資料夾裡面。