详解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 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() 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类使用,而不需要显示注入。