[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 授權過濾器

授權過濾器的基本原理在文章 《[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.點此跳轉到總目錄


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM