详解ABP框架的参数有效性验证和权限验证

参数有效性验证
应用程序的输入数据首先应该被检验是否有效。输入的数据能被用户或其他应用程序提交。在Web应用中,通常进行2次数据有效性检验:包括客户端检验和服务端检验。客户端的检验主要是使用户有一个好的用户体验。 首先最好是在客户端检验其表单输入的有效性并且展示给客户端的那些字段输入是无效的。但是,服务器端的校验是更关键和不可缺失的(不要只做客户端检验而不做服务器端检验)。

服务器端的检验通常是被应用服务(层)执行,应用服务(层)中的方法首先检验数据的有效性,然后才使用这些通过验证的数据。ABP的基础设施提供了自动检验输入数据有效性的方法。

应用服务(层)方法得到一个数据传输对象(DTO)作为输入。ABP有一个IValidate的接口,DTO通过实现这个接口能够检验数据的有效性。由于IInputDto扩展自IValidate,所以你可以直接实现IInputDto 接口来对数据传输对象(DTO)检验其有效性。

使用数据注解
ABP提供数据注解的特性。假设我们正在开发一个创建任务的应用服务并且得到了一个输入,请看下面示例:

public class CreateTaskInput : IInputDto
{
  public int? AssignedPersonId { get; set; }

  [Required]
  public string Description { get; set; }
}

在这里,Description 属性被标记为 Required。AssignedPersonId 是可选的。在 System.ComponentModel.DataAnnotations 命名空间中,还有很多这样的特性 ( 例如: MaxLength, MinLength, RegularExpression 等等 )。

在System.ComponentModel.DataAnnotations 命名空间中,请看Task application service 的实现

public class TaskAppService : ITaskAppService
{
  private readonly ITaskRepository _taskRepository;
  private readonly IPersonRepository _personRepository;

  public TaskAppService(ITaskRepository taskRepository, IPersonRepository personRepository)
  {
    _taskRepository = taskRepository;
    _personRepository = personRepository;
  }

  public void CreateTask(CreateTaskInput input)
  {
    var task = new Task { Description = input.Description };

    if (input.AssignedPersonId.HasValue)
    {
      task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);
    }

    _taskRepository.Insert(task);
  }
}

正如你所看到的,这里没有写任何的数据验证性代码(指对Description属性的验证)因为ABP会自动去检验数据的有效性。ABP也会检验输入数据是否为null。如果为空则会抛出AbpValidationException 异常。所以你不需要写检验数据是否为null值的代码。如果有任何属性的输入数据是无效的它也会抛出相同的异常。

这个机制近似于 ASP.NET MVC 的验证功能,注意:这里的应用服务类不是继承自Controller,它是用在Web应用的一个普通类。

自定义检验
如果数据注解的方式不能满足你的需求,你可以实现ICustomValidate接口,请看下面示例:

public class CreateTaskInput : IInputDto, ICustomValidate
{
  public int? AssignedPersonId { get; set; }

  public bool SendEmailToAssignedPerson { get; set; }

  [Required]
  public string Description { get; set; }

  public void AddValidationErrors(List<ValidationResult> results)
  {
    if (SendEmailToAssignedPerson && (!AssignedPersonId.HasValue || AssignedPersonId.Value <= 0))
    {
      results.Add(new ValidationResult("AssignedPersonId must be set if SendEmailToAssignedPerson is true!"));
    }
  }
}

ICustomValidate 接口声明了一个可被实现的AddValidationErrors方法。这里我们有一个叫做 SendEmailToAssignedPerson 的属性。如果该属性是真,AssignedPersonId 属性会被检验是否有效,否则该属性可以为空。如果有验证错误,我们必须添加把这些验证结果添加到结果集合中。(就是将ValidationResult 添加到results)

设置缺省值
在检验数据有效性后,我们需要执行一个额外的操作来整理DTO参数。ABP定义了一个IShouldNormalize接口,这个接口声明了一个 Normalize的方法。如果你实现了这个接口,在检验数据有效性后,Normalize方法会被调用。假设我们的DTO需要一个排序方向的数据。如果这个Sorting属性没有被提供数据,那么在Normalize我们可以给Sorting设置一个缺省值。

public class GetTasksInput : IInputDto, IShouldNormalize
{
  public string Sorting { get; set; }

  public void Normalize()
  {
    if (string.IsNullOrWhiteSpace(Sorting))
    {
      Sorting = "Name ASC";
    }
  }
}

权限验证
几乎所有的企业级应用程序都会有不同级别的权限验证。权限验证是用于检查用户是否允许某些指定操作。Abp有基础设施让你来实现权限验证。

注意:关于IPermissionChecker接口

Abp权限系统使用IPermissionChecker去检查授权。同时你可以根据需要实现你自己的方式,在module-zero项目中已经完整实现了。如果IPermissionChecker没有被实现,NullPermissionChecker会被使用于授权所有权限给每个人。

定义权限
在使用验证权限前,我们需要为每一个操作定义唯一的权限。Abp的设计是基于模块化,所以不同的模块可以有不同的权限。为了定义权限,一个模块应该创建AuthorizationProvider的派生类。MyAuthorizationProvider继承自AuthorizationProvider,换句话说就是AuthorizationProvider派生出MyAuthorizationProvider。例子如下:

public class MyAuthorizationProvider : AuthorizationProvider
{
  public override void SetPermissions(IPermissionDefinitionContext context)
  {
    var administration = context.CreatePermission("Administration");

    var userManagement = administration.CreateChildPermission("Administration.UserManagement");
    userManagement.CreateChildPermission("Administration.UserManagement.CreateUser");

    var roleManagement = administration.CreateChildPermission("Administration.RoleManagement");
  }
}

IPermissionDefinitionContext 有方法去获取和创建权限。

一个权限有以下属性:

  • Name:系统范围内的唯一名字。把它定义为一个字符串常量是个不错的注意。我们倾向于将“.”分割不同的层级,但并不要求这么做。你可以设置你任何喜欢的名字。唯一的规则就是这个名字必须是唯一的。
  • Display Name:使用一个本地化的字符串去显示权限到UI。
  • Description:和Display Name类似。
  • IsGrantedByDefault:此权限是否授权给(已登陆)所有用户,除非显示指定。通常设置为False(默认值)。
  • MultiTenancySides:对租户应用程序,一个权限可以基于租户或者主机(原文:host)。这是个枚举标识,因此权限可以应用于不同方面(原文:Both Sides)。

一个权限可以有父权限和子权限。当然,这不会影响权限检查,它只是在UI层对权限归类有好处。创建authorizationprovider之后,我们应该在模块的PreIntialize方法对它进行注册。如下:

Configuration.Authorization.Providers.Add<MyAuthorizationProvider>()

authorizationprovider会自动注册到依赖注入系统中。因此,authorization provider可以注入任何依赖(像是Repository)从而使用其他资源去创建权限定义。

检查权限
1.使用AbpAuthorize特性(Using AbpAuthorize attribute)

AbpAuthorize(AbpMvcAuthorize 对应 MVC Controllers and AbpApiAuthorize 对应 Web API Controllers)特性是最简单和常用的方法去检查权限。请考虑如下application service方法:

[AbpAuthorize("Administration.UserManagement.CreateUser")]
public void CreateUser(CreateUserInput input)
{
  //A user can not execute this method if he is not granted for "Administration.UserManagement.CreateUser" permission.
}

没有获得“Administration.UserManagement.CreateUser”权限的用户不能够调用CreateUser。

AbpAuthorize 特性也检查当前用户是否登录 (使用 IAbpSession.UserId)。因此,如果我们将某个方法声明为AbpAuthorize 特性,它至少会检查用户是否登录。代码如下: [AbpAuthorize]

public void SomeMethod(SomeMethodInput input)
{
  //A user can not execute this method if he did not login.
}

2.AbpAuthorize属性说明(AbpAuthorize attribute notes)

Abp使用动态方法拦截进行权限验证。因此,使用AbpAuthorize特性的方法会有些限制。如下:

不能应用于私有(private)方法
不能应用于静态(static)方法
不能应用于非注入(non-injected)类(我们必须用依赖注入)。
此外,

AbpAuthorize特性可以应用于任何的Public方法,如果此方法被接口调用(比如在Application Services中通过接口调用)
方法是虚(virtual)方法,如果此方法直接被类引用进行调用(像是ASP.NET MVC 或 Web API 的控制器)。
方式是虚(virtual)方法,如果此方法是protected。
注意:有三种AbpAuthorize 特性:

(1)在应用程序服务中(application layer),我们使用Abp.Authorization.AbpAuthorize;
(2)在MVC控制器(web layer)中,我们使用Abp.Web.Mvc.Authorization.AbpMvcAuthorize;
(3)在ASP.NET Web API,我们使用 Abp.WebApi.Authorization.AbpApiAuthorize。
这三个类继承自不同的地方。

在MVC中,它继承自MVC自己的Authorize类。
在Web API,它继承自Web API 的Authorize类。因此,它最好是继承到MVC和Web API中。
但是,在Application 层,它完全是由Abp自己实现没有扩展子任何类。
3.使用IPermissionChecker

AbpAuthorize 适用于大部分的情况,但是某些情况下,我们还是需要自己在方法体里进行权限验证。我们可以注入和使用IPermissionChecker对象。如下边的代码所示:

 public void CreateUser(CreateOrUpdateUserInput input)
{
  if (!PermissionChecker.IsGranted("Administration.UserManagement.CreateUser"))
  {
    throw new AbpAuthorizationException("You are not authorized to create user!");
  }

  //A user can not reach this point if he is not granted for "Administration.UserManagement.CreateUser" permission.
}

当然,你可以写入任何逻辑,由于IsGranted方法只是简单返回true或false(它还有异步版本哦)。如你简单的检查一个权限并抛出一个异常如上边代码那样,你可以用Authorize方法:

public void CreateUser(CreateOrUpdateUserInput input)
{
  PermissionChecker.Authorize("Administration.UserManagement.CreateUser");

  //A user can not reach this point if he is not granted for "Administration.UserManagement.CreateUser" permission.
}

由于权限验证通常实现与Application层,ApplicationService基础类注入和定义了PermissionChecker属性。因此,权限检查器允许你在Application Service类使用,而不需要显示注入。

(0)

相关推荐

  • ABP框架中导航菜单的使用及JavaScript API获取菜单的方法

    每一个WEB应用程序都有导航菜单,Abp也为用户提供了通用的创建和显示菜单方式. 创建菜单 一个应用程序可能包含不同的模块,而每个模块都可能有它自己的菜单项.在Abp中,需要创建一个派生自NavigationProvider的类来定义一个菜单项. 假设我们有一个这样的主菜单: Tasks Reports Administration 1 User Management 2 Role Management 由上可知,Administration菜单项有两个子菜单项.对应的生成方法如下: publi

  • 基于ASP.NET MVC的ABP框架入门学习教程

    为什么使用ABP 我们近几年陆续开发了一些Web应用和桌面应用,需求或简单或复杂,实现或优雅或丑陋.一个基本的事实是:我们只是积累了一些经验或提高了对,NET的熟悉程度. 随着软件开发经验的不断增加,我们发现其实很多工作都是重复机械的,而且随着软件复杂度的不断提升,以往依靠经验来完成一些简单的增删改查的做法已经行不通了.特别是用户的要求越来越高,希望添加的功能越来多,目前这种开发模式,已经捉襟见肘.我很难想象如何在现有的模式下进行多系统的持续集成并添加一些新的特性. 开发一个系统时,我们不可避免

  • 详解ABP框架中Session功能的使用方法

    如果一个应用程序需要登录,则它必须知道当前用户执行了什么操作.因此ASP.NET在展示层提供了一套自己的SESSION会话对象,而ABP则提供了一个可以在任何地方 获取当前用户和租户的IAbpSession接口. 关于IAbpSession 需要获取会话信息则必须实现IAbpSession接口.虽然你可以用自己的方式去实现它(IAbpSession),但是它在module-zero项目中已经有了完整的实现. 注入Session IAbpSession通常是以属性注入的方式存在于需要它的类中,不需

  • 解析ABP框架中的事务处理和工作单元

    通用连接和事务管理方法 连接和事务管理是使用数据库的应用程序最重要的概念之一.当你开启一个数据库连接,什么时候开始事务,如何释放连接...诸如此类的. 正如大家都知道的,.Net使用连接池(connection pooling).因此,创建一个连接实际上是从连接池中取得一个连接,会这么做是因为创建新连接会有成本.如果没有任何连接存在于连接池中,一个新的连接对象会被创建并且添加到连接池中.当你释放连接,它实际上是将这个连接对象送回到连接池.这并不是实际意义上的释放.这个机制是由.Net所提供的.因

  • ABP框架的体系结构及模块系统讲解

    DDD分层 为了减少复杂性和提高代码的可重用性,采用分层架构是一种被广泛接受的技术. 为了实现分层的体系结构,ABP遵循DDD(领域驱动设计)的原则,将分为四个层次: 展现层(Presentation):提供一个用户界面,实现用户交互操作. 应用层(Application):进行展现层与领域层之间的协调,协调业务对象来执行特定的应用程序的任务.它不包含业务逻辑. 领域层(Domain):包括业务对象和业务规则,这是应用程序的核心层. 基础设施层(Infrastructure):提供通用技术来支持

  • ABP框架的基础配置及依赖注入讲解

    配置ABP 配置是通过在自己模块的PreInitialize方法中来实现的 代码示例如下: public class SimpleTaskSystemModule : AbpModule { public override void PreInitialize() { //在你的应用中添加语言包,这个是英语和作者的土耳其语. Configuration.Localization.Languages.Add(new LanguageInfo("en", "English&quo

  • 详解ABP框架中领域层的领域事件Domain events

    在C#中,一个类可以定义其专属的事件并且其它类可以注册该事件并监听,当事件被触发时可以获得事件通知.这对于对于桌面应用程序或独立的Windows Service来说非常有用.但是, 对于Web应用程序来说会有点问题,因为对象是根据请求(request)被创建并且它们的生命周期都很短暂.我们很难注册其它类别的事件.同样地,直接注册其它类别的事件也造成了类之间的耦合性. 在应用系统中,领域事件被用于解耦并且重用(re-use)商业逻辑. 事件总线 事件总线为一个单体(singleton)的对象,它由

  • 解析ABP框架中的数据传输对象与应用服务

    数据传输对象(DTOs) 数据传输对象(Data Transfer Objects)用于应用层和展现层的数据传输. 展现层传入数据传输对象(DTO)调用一个应用服务方法,接着应用服务通过领域对象执行一些特定的业务逻辑并且返回DTO给展现层.这样展现层和领域层被完全分离开了.在具有良好分层的应用程序中,展现层不会直接使用领域对象(仓库,实体). 1.数据传输对象的作用: 为每个应用服务方法创建DTO看起来是一项乏味耗时的工作.但如果你正确使用它们,这将会解救你的项目.为啥呢? (1)抽象领域层 (

  • 解析ABP框架领域层中的实体类与仓储类

    领域层 实体是DDD(领域驱动设计)的核心概念之一.Eric Evans是这样描述的"很多对象不是通过它们的属性定义的,而是通过一连串的连续性事件和标识定义的"(引用领域驱动设计一书). 译者注:对象不是通过它们的属性来下根本性的定义,而应该是通过它的线性连续性和标识性定义的..所以,实体是具有唯一标识的ID且存储在数据库中.实体通常被映射成数据库中的一个表. 实体类(Entity classes) 在ABP中,实体继承自Entity类,请看下面示例: public class Per

  • ASP.NET样板项目ABP框架的特性总结

    ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板. ASP.NET Boilerplate 基于DDD的经典分层架构思想,实现了众多DDD的概念(但没有实现所有DDD的概念). ABP的官方网站:http://www.aspnetboilerplate.com ABP在Github上的开源

  • ABP框架中的日志功能完全解析

    ASP.NET Boilerplate使用Castle Windsor's logging facility日志记录工具,并且可以使用不同的日志类库,比如:Log4Net, NLog, Serilog... 等等.对于所有的日志类库,Castle提供了一个通用的接口来实现,我们可以很方便的处理各种特殊的日志库,而且当业务需要的时候,很容易替换日志组件. 译者注释:Castle是什么:Castle是针对.NET平台的一个开源项目,从数据访问框架ORM到IOC容器,再到WEB层的MVC框架.AOP,

  • 详解ABP框架中的数据过滤器与数据传输对象的使用

    数据过滤器(Data filters) 在数据库开发中,我们一般会运用软删除(soft-delete)模式,即不直接从数据库删除数据,而是标记这笔数据为已删除.因此,如果实体被软删除了,那么它就应该不会在应用程序中被检索到.要达到这种效果,我们需要在每次检索实体的查询语句上添加SQL的Where条件IsDeleted = false.这是个乏味的工作,但它是个容易被忘掉的事情.因此,我们应该要有个自动的机制来处理这些问题. ABP提供数据过滤器(Data filters),它使用自动化的,基于规

  • 详解ABP框架中的日志管理和设置管理的基本配置

    日志管理 Server side(服务器端) ASP.NET Boilerplate使用Castle Windsor's logging facility日志记录工具,并且可以使用不同的日志类库,比如:Log4Net, NLog, Serilog... 等等.对于所有的日志类库,Castle提供了一个通用的接口来实现,我们可以很方便的处理各种特殊的日志库,而且当业务需要的时候,很容易替换日志组件. 译者注释:Castle是什么:Castle是针对.NET平台的一个开源项目,从数据访问框架ORM到

随机推荐