这个部分我打算用上下两个部分来将整个结构来讲完,在我们读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系列的主目录。

												

ABP中的Filter(上)的更多相关文章

  1. ABP中的Filter(下)

    接着上面的一个部分来叙述,这一篇我们来重点看ABP中的AbpUowActionFilter.AbpExceptionFilter.AbpResultFilter这三个部分也是按照之前的思路来一个个介绍 ...

  2. ABP中的拦截器之ValidationInterceptor(上)

    从今天这一节起就要深入到ABP中的每一个重要的知识点来一步步进行分析,在进行介绍ABP中的拦截器之前我们先要有个概念,到底什么是拦截器,在介绍这些之前,我们必须要了解AOP编程思想,这个一般翻译是面向 ...

  3. Abp中SwaggerUI的接口文档添加上传文件参数类型

    在使用Swashbuckle上传文件的时候,在接口文档中希望看到上传控件,但是C#中,没有FromBodyAttribute这个特性,所以需要在运行时,修改参数的swagger属性.   首先看下,最 ...

  4. ABP中的本地化处理(上)

    今天这篇文章主要来总结一下ABP中的多语言是怎么实现的,在后面我们将结合ABP中的源码和相关的实例来一步步进行说明,在介绍这个之前我们先来看看ABP的官方文档,通过这个文档我们就知道怎样在我们的系统中 ...

  5. ABP源码分析三十五:ABP中动态WebAPI原理解析

    动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类就可以对外提供WebAPI的功能, ...

  6. ABP中动态WebAPI原理解析

    ABP中动态WebAPI原理解析 动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类 ...

  7. JS组件系列——在ABP中封装BootstrapTable

    前言:关于ABP框架,博主关注差不多有两年了吧,一直迟迟没有尝试.一方面博主觉得像这种复杂的开发框架肯定有它的过人之处,系统的稳定性和健壮性比一般的开源框架肯定强很多,可是另一方面每每想到它繁琐的封装 ...

  8. ABP中的拦截器之AuditingInterceptor

    在上面两篇介绍了ABP中的ValidationInterceptor之后,我们今天来看看ABP中定义的另外一种Interceptor即为AuditingInterceptor,顾名思义就是一种审计相关 ...

  9. ABP中的模块初始化过程(一)

    在总结完整个ABP项目的结构之后,我们就来看一看ABP中这些主要的模块是按照怎样的顺序进行加载的,在加载的过程中我们会一步步分析源代码来进行解释,从而使自己对于整个框架有一个清晰的脉络,在整个Asp. ...

随机推荐

  1. Kubernetes的DaemonSet(上篇)

    背景 静儿作为美团容器化团队HULK的一员,经常需要和Kubernetes(k8s)打交道.第一次登陆node(宿主机)的时候,发现连续登陆几台都看到了Prometheus-Node-Exporter ...

  2. Java~关于开发工具和包包

    大叔也学java了,距离上学时接触的java已经有10多年了,看着确实有些陌生了,不过还是希望学学,感受一下这个当今最牛X的语言!开发工具IDE对于一个语言来说是很必要的,就是Csharp使用vs一样 ...

  3. springboot~mockMvc和asciidoctor生成基于TDD的API文档

    API文档是前端与后端快速开发,减少沟通成本的必要条件,有一份完善的文档是很必要的,由通过测试来生成文档的好处就是:测试数据有了,测试返回结果有了,而且可以对这些字段进行说明,很清晰,在springb ...

  4. QUIC协议原理分析(转)

    之前深入了解了一下HTTP1.1.2.0.SPDY等协议,发现HTTP层怎么优化,始终要面对TCP本身的问题.于是了解到了QUIC,这里分享一篇之前找到的有意义的文章. 原创地址:https://mp ...

  5. Kubernetes知识小普及

    大部分概念Kubernetes官网都有详细介绍,Kubernetes中文官网 https://kubernetes.io/zh/docs/tutorials/kubernetes-basics/ 官网 ...

  6. Java服务器内存过高&CPU过高问题排查

    一.内存过高 1.内存过高一般有两种情况:内存溢出和内存泄漏 (1)内存溢出:程序分配的内存超出物理机的内存大小,导致无法继续分配内存,出现OOM报错 (2)内存泄漏:不再使用的对象一直占据着内存不释 ...

  7. python异常处理的哲学

    所谓异常指的是程序的执行出现了非预期行为,就好比现实中的做一件事过程中总会出现一些意外的事.异常的处理是跨越编程语言的,和具体的编程细节相比,程序执行异常的处理更像是哲学.限于认知能力和经验所限,不可 ...

  8. 树莓派SSH连接快速教程

    树莓派系统一般都默认自带ssh 1.首先检查是否安装ssh没 dpkg - l | grep openssh 如果出现几个openssh-xxx,说明你已经安装了 如果没有 2.SSH服务安装 sud ...

  9. Springboot整合activemq

    今天呢心血来潮,也有很多以前的学弟问到我关于消息队列的一些问题,有个刚入门,有的有问题都来问我,那么今天来说说如何快速入门mq. 一.首先说下什么是消息队列? 1.消息队列是在消息的传输过程中保存消息 ...

  10. 剑指前端(前端入门笔记系列)—— JS基本数据类型及其类型转换

    基本数据类型 ECMAScript中有5中简单数据类型性(也称为基本数据类型):Undefined.Null.Boolean.Number和String,还有一种复杂数据类型——Object,Obje ...