這個部分我打算用上下兩個部分來將整個結構來講完,在我們讀ABP中的代碼之后我們一直有一個疑問?在ABP中為什么要定義Interceptor和Filter,甚至這兩者之間我們都能找到一些對應關系,比如:AuthorizationInterceptor和AbpAuthorizationFilter,AuditingInterceptor和AbpAuditActionFilter,甚至這些代碼的實現也是調用相同的接口和實現?那么ABP為什么要采用這種方式呢?在前面的章節中我已經充分介紹過了ABP中的各種Interceptor,這一部分我們將重點來理解ABP中的另外一種類型Filter,在理解本篇文章之前,建議先對Asp.Net Core中的Filter有一個清晰的理解,這里建議看微軟官方的文檔使自己對整個Filter有一個清晰的認識,或者讀這篇博客來理解到底什么是Asp.Net Core 中的Filter。
在有了前面的預備知識以后我們就可以來探討ABP中的Filter到底是怎么一回事了,還是和以前的分析思路一樣,我們來看看整個Filter在ABP中是怎樣添加並運行的,在我們的項目代碼中當我們在Asp.Net Core中使用ABP框架時,我們要做的第一步就是在Startup類的ConfigureServices方法中將我們的ABP添加到DI容器中,ConfigureServices是用來將服務注冊到DI容器用的,在Asp.Net Core的Demo程序中我們經常可以看到下面的代碼:
// ...
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
}
在這里services就是一個DI容器。此例把MVC的服務注冊到DI容器,等到需要用到MVC服務時,才從DI容器取得物件實例。在我們的ABP中整個實現也是按照這個思路來進行的,首先是在ConfigureServices方法中我們調用AddAbp方法,在這個方法的主要作用按照ABP中的注釋是:Integrates ABP to AspNet Core.主要就是將ABP集成到整個Asp.Net Core中,我們來簡要看看這個AddAbp方法到底做了些什么?
/// <summary>
/// Integrates ABP to AspNet Core.
/// </summary>
/// <typeparam name="TStartupModule">Startup module of the application which depends on other used modules. Should be derived from <see cref="AbpModule"/>.</typeparam>
/// <param name="services">Services.</param>
/// <param name="optionsAction">An action to get/modify options</param>
public static IServiceProvider AddAbp<TStartupModule>(this IServiceCollection services, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
where TStartupModule : AbpModule
{
var abpBootstrapper = AddAbpBootstrapper<TStartupModule>(services, optionsAction);
ConfigureAspNetCore(services, abpBootstrapper.IocManager);
return WindsorRegistrationHelper.CreateServiceProvider(abpBootstrapper.IocManager.IocContainer, services);
}
主要就是做了三件事:1 初始化整個AbpBootstrapper並創建唯一的實例。2 配置Asp.Net Core中的一些重要的服務及參數。 3 將我們之前注冊到DI容器中的所有IServiceCollection都添加到ABP中使用的Windsor Castel容器中來接管了 ASP.NET Core 自帶的IOC容器。而我們今天要討論和比較的兩個部分就包含在這幾個過程中,為整個ABP框架中添加Interceptor在第一個過程中完成,而我們今天要講的ABP中的Filter就是包含在第二個過程中,那么我們先來看看第二個過程。
//Configure MVC
services.Configure<MvcOptions>(mvcOptions =>
{
mvcOptions.AddAbp(services);
});
在第二個過程我們只選取和添加Filter相關的代碼部分,這部分的重點在於mvcOptions.AddAbp的方法,我們來看看里面的實現。
internal static class AbpMvcOptionsExtensions
{
public static void AddAbp(this MvcOptions options, IServiceCollection services)
{
AddConventions(options, services);
AddFilters(options);
AddModelBinders(options);
}
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));
}
private static void AddModelBinders(MvcOptions options)
{
options.ModelBinderProviders.Insert(0, new AbpDateTimeModelBinderProvider());
}
}
看到了吧,這里我們向MvcOptions對應的Filter里面添加了6中不同的Filter,看到這個我們就知道了接下來我們就要對這幾種Filter進行分析和總結。
一 AbpAuthorizationFilter
在分析這部分之前我們可以和之前講到的AuthorizationInterceptor來做一個對比,因為我們在分析代碼的時候發現這兩個方法大部分的代碼都是重復的,在AuthorizationInterceptor中能夠對某個類型或者這個類型中的Public或者是NonPublic方法是否定義了自定義的AbpAuthorize或者RequiresFeature屬性,只有定義了這些屬性才會進行攔截操作。兩個實現的內部都是調用IAuthorizationHelper中定義的Authorize方法,然后執行CheckFeatures和CheckPermissions這兩個過程,這兩個過程在之前的部分有介紹這里就不再贅述。這里面我們發現在AbpAuthorizationFilter中只會攔截ControllerAction,所以可以這么理解AbpAuthorizationFilter只要用於攔截Controller中定義的一些方法,如果不是一個控制器方法則直接返回,而AuthorizationInterceptor則通過 Castle Windsor Interceptor 來驗證普通類型的方法,來檢測當前用戶是否有權限進行調用。所以這里我們需要進行區分。
另外在AbpAuthorizationFilter這一部分中,我們可以看到整個過程使用了兩個Catch來捕獲特定的異常,然后進行特定的處理,這些處理包括記錄日志、觸發異常事件、以及對ObjectResult的返回類型采用AjaxResponse對象進行封裝信息等一系列處理,這里我們可以看一下源代碼的處理過程。
public class AbpAuthorizationFilter : IAsyncAuthorizationFilter, ITransientDependency
{
public ILogger Logger { get; set; }
// 權限驗證類,這個才是真正針對權限進行驗證的對象
private readonly IAuthorizationHelper _authorizationHelper;
// 異常包裝器主要是用來封裝沒有授權時返回的錯誤信息
private readonly IErrorInfoBuilder _errorInfoBuilder;
// 事件總線處理器在這里用於觸發一個未授權請求引發的事件,用戶可以監聽此事件來進行自己的處理
private readonly IEventBus _eventBus;
// 構造注入
public AbpAuthorizationFilter(
IAuthorizationHelper authorizationHelper,
IErrorInfoBuilder errorInfoBuilder,
IEventBus eventBus)
{
_authorizationHelper = authorizationHelper;
_errorInfoBuilder = errorInfoBuilder;
_eventBus = eventBus;
Logger = NullLogger.Instance;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
// 如果注入了 IAllowAnonymousFilter 過濾器則允許所有匿名請求
if (context.Filters.Any(item => item is IAllowAnonymousFilter))
{
return;
}
// 如果不是一個控制器方法則直接返回
if (!context.ActionDescriptor.IsControllerAction())
{
return;
}
// 開始使用 IAuthorizationHelper 來進行權限校驗
try
{
await _authorizationHelper.AuthorizeAsync(
context.ActionDescriptor.GetMethodInfo(),
context.ActionDescriptor.GetMethodInfo().DeclaringType
);
}
// 如果是未授權異常的處理邏輯
catch (AbpAuthorizationException ex)
{
// 記錄日志
Logger.Warn(ex.ToString(), ex);
// 觸發異常事件
_eventBus.Trigger(this, new AbpHandledExceptionData(ex));
// 如果接口的返回類型為 ObjectResult,則采用 AjaxResponse 對象進行封裝信息
if (ActionResultHelper.IsObjectResult(context.ActionDescriptor.GetMethodInfo().ReturnType))
{
context.Result = new ObjectResult(new AjaxResponse(_errorInfoBuilder.BuildForException(ex), true))
{
StatusCode = context.HttpContext.User.Identity.IsAuthenticated
? (int) System.Net.HttpStatusCode.Forbidden
: (int) System.Net.HttpStatusCode.Unauthorized
};
}
else
{
context.Result = new ChallengeResult();
}
}
// 其他異常則顯示為內部異常信息
catch (Exception ex)
{
Logger.Error(ex.ToString(), ex);
_eventBus.Trigger(this, new AbpHandledExceptionData(ex));
if (ActionResultHelper.IsObjectResult(context.ActionDescriptor.GetMethodInfo().ReturnType))
{
context.Result = new ObjectResult(new AjaxResponse(_errorInfoBuilder.BuildForException(ex)))
{
StatusCode = (int) System.Net.HttpStatusCode.InternalServerError
};
}
else
{
//TODO: How to return Error page?
context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.InternalServerError);
}
}
}
}
二 AbpAuditActionFilter
在使用審計ActionFilter之前,我們也建議先看看之前介紹AuditingInterceptor的文章,通過對比你發現這兩個部分核心也是相同的,其內部都是調用IAuditingHelper中定義的CreateAuditInfo來創建審計日志,那么我們着重看看這兩者在實現方式上面有哪些不同的地方,在方法(AuditingInterceptor中的Intercept)調用之前,首先也是通過AbpCrossCuttingConcerns.IsApplied(invocation.InvocationTarget, AbpCrossCuttingConcerns.Auditing)方法來驗證當前方法是否已經應用過審計功能,原因也好理解同一個方法不應該被AuditingInterceptor和AbpAuditActionFilter中的方法攔截兩次,所以這個判斷很好理解就是用於判斷當前調用方法是否已經被應用過審計功能了,如果應用過則直接跳過去。我們來看看IsApplied方法中到底做了些什么?
public static bool IsApplied([NotNull] object obj, [NotNull] string concern)
{
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
}
if (concern == null)
{
throw new ArgumentNullException(nameof(concern));
}
return (obj as IAvoidDuplicateCrossCuttingConcerns)?.AppliedCrossCuttingConcerns.Contains(concern) ?? false;
}
在這個方法的內部首先將當前當前類型轉換為IAvoidDuplicateCrossCuttingConcerns接口形式,如果轉換成功則判斷接口中定義的AppliedCrossCuttingConcerns字符串List是否已經包含了AbpCrossCuttingConcerns.Auditing=“AbpAuditing”這個常量,那么在ABP框架中哪種類型的對象能夠轉換為IAvoidDuplicateCrossCuttingConcerns接口形式呢?我們發現在ABP中默認有兩種類型繼承自IAvoidDuplicateCrossCuttingConcerns接口,一種是ApplicationService另外一種就是DynamicApiController<T>,第二種不太常見,但是第一種在應用層每一個應用服務對象都要繼承自ApplicationService,所以說應用層對象都能夠轉換成IAvoidDuplicateCrossCuttingConcerns形式,那么我們通過閱讀源碼發現這兩個作用的范圍是不同的,AbpAuditActionFilter主要用於審計Controller層中添加了Audited屬性的公共並且非匿名登錄的一些方法的審計工作日志輸出工作,而AuditingInterceptor中的Intercept方法主要用於其他一些添加了Audited屬性的公共方法並且非匿名登錄的一些方法的普通類型的方法或類型的審計工作,通過這里相信你對這兩者已經有一個清晰的認識啦,接下來我們就是看一看IAuditingHelper中做了些什么,其實里面主要是將當前執行方法的TeantId、UserID、ServiceName、名稱、類型、參數、耗時、Exception、TotalMilliseconds等參數自動保存到數據庫中,從而方便我們來查詢這些方法的執行情況。
三 AbpValidationActionFilter
這個部分的內容在ValidationInterceptor中的上篇和下篇有過詳細的對比,這里不再贅述。
后面的AbpUowActionFilter、AbpExceptionFilter、以及AbpResultFilter將在下一篇中具體講到,當前篇主要就是包含這些內容。
最后,點擊這里返回整個ABP系列的主目錄。
