这个部分我打算用上下两个部分来将整个结构来讲完,在我们读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. 《HelloGitHub》第 29 期

    公告 月刊现已支持 RSS 订阅 <HelloGitHub>第 29 期 兴趣是最好的老师,HelloGitHub 就是帮你找到兴趣! 简介 分享 GitHub 上有趣.入门级的开源项目. ...

  2. js事件循环机制辨析

     对于新接触js语言的人来说,最令人困惑的大概就是事件循环机制了.最开始这也困惑了我好久,花了我几个月时间通过书本,打代码,查阅资料不停地渐进地理解他.接下来我想要和大家分享一下,虽然可能有些许错误的 ...

  3. Spring Boot 2.x(十一):AOP实战--打印接口日志

    接口日志有啥用 在我们日常的开发过程中,我们可以通过接口日志去查看这个接口的一些详细信息.比如客户端的IP,客户端的类型,响应的时间,请求的类型,请求的接口方法等等,我们可以对这些数据进行统计分析,提 ...

  4. keil进阶教程

    前言 keil只懂得创建软件工程是远远不够的,如果要想顺心使用,应该要懂得部分配置,这样使用心情顺畅,码代码也会越发高效. 设置字号字体 编辑点击编辑菜单,会出现很多子目录,找到配置,点击进入设置页面 ...

  5. Spring Boot Security 整合 JWT 实现 无状态的分布式API接口

    简介 JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案.JSON Web Token 入门教程 - 阮一峰,这篇文章可以帮你了解JWT的概念.本文重点讲解Spring Boo ...

  6. Oracle 时间字段显示不正确,类型错误

    一.知识点 给Oracle的date类型字段设置默认值[设置为当前时间] to_date(to_char(sysdate,'yyyy-mm-dd hh24:mi:ss'),'yyyy-mm-dd hh ...

  7. 业务与IT技术

    最近听一个同事又再次提问关于业务比技术重要,是真的吗? 今天我们再来看一下.      一,什么是业务? 业务意指某种有目的的工作或工作项目.技术可以指人类对机器.硬件或人造器皿的运用,但它也可以包含 ...

  8. RowKey设计之单调递增行键/时序数据

    ​在一个集群中,一个导入数据的进程锁住不动,所有的client都在等待一个region (因而也就是一个单个节点),过了一会后,变成了下一个region…​如果使用了单调递增 或者时序的key便会造成 ...

  9. 好代码是管出来的——使用GitHub实现简单的CI/CD

    软件开发一般来说是一项团队作业,在本系列文章开始就提到过软件的编码是由一个团队“并行”完成的,为了保证编码任务正常完成,首先引入版本控制工具来完成代码管理,为了保证代码质量引入了代码分析器以及代码测试 ...

  10. MyDAL - 引用类型对象 .DeepClone() 深度克隆[深度复制] 工具 使用

    索引: 目录索引 一.API 列表 .DeepClone() 用于 Model / Entity / ... ... 等引用类型对象的深度克隆 特性说明 1.不需要对对象做任何特殊处理,直接 .Dee ...