[AbpvNext源码分析]-9.接⼝参数的验证
⼀、简要说明
ABP vNext 针对接⼝参数的校验⼯作,分别由过滤器和两步完成。过滤器内部使⽤的 ASP.NET Core MVC 所提供的 IModelStateValidator 进⾏处理,⽽使⽤的是ABP vNext ⾃⼰提供的⼀套 IObjectValidator 进⾏校验⼯作。
关于参数验证相关的代码,分布在以下三个项⽬当中:
Volo.Abp.AspNetCore.Mvc
Volo.Abp.Validation
Volo.Abp.FluentValidation
通过 MVC 的过滤器和 ABP vNext 提供的,我们能够快速地对接⼝的参数、对象的属性进⾏统⼀的验证处理,⽽不会将这些代码扩散到业务层当中。
⽂章信息:
基于的 ABP vNext 版本:1.0.0
创作⽇期:2019 年 10 ⽉ 22 ⽇晚
更新⽇期:暂⽆
⼆、源码分析
2.1 模型验证过滤器
模型验证过滤器是直接使⽤的 MVC 那⼀套模型验证机制,基于数据注解的⽅式进⾏校验。数据注解也就是存放在 System.ComponentModel.DataAnnotations 命名空间下⾯的⼀堆特性定义,例如我们经常在 DTO 上⾯使⽤的 [Required] 、[StringLength] 特性等,如果想知道更多的数据注解⽤法,可以前往 进⾏学习。
2.1.1 过滤器的注⼊
模型验证过滤器 (AbpValidationActionFilter) 的定义存放在 Volo.Abp.AspNetCore.Mvc 项⽬内部,它是在模块的 ConfigureService() ⽅法中被注⼊到 IoC 容器的。
AbpAspNetCoreMvcModule ⾥⾯的相关代码:
namespace Volo.Abp.AspNetCore.Mvc
{
[DependsOn(
typeof(AbpAspNetCoreModule),
typeof(AbpLocalizationModule),
typeof(AbpApiVersioningAbstractionsModule),
typeof(AbpAspNetCoreMvcContractsModule),
typeof(AbpUiModule)
)]
public class AbpAspNetCoreMvcModule : AbpModule
{
/
asp查看源码配置ui/
public override void ConfigureServices(ServiceConfigurationContext context)
{
// ...
Configure<MvcOptions>(mvcOptions =>
{
mvcOptions.AddAbp(context.Services);
});
}
// ...
}
}
上述代码是调⽤对 MvcOptions 编写的 AddAbp(this MvcOptions, IServiceCollection) 扩展⽅法,传⼊了我们的 IoC 注册容器(IServiceCollection)。
AbpMvcOptionsExtensions ⾥⾯的相关代码:
internal static class AbpMvcOptionsExtensions
{
public static void AddAbp(this MvcOptions options, IServiceCollection services)
{
AddConventions(options, services);
// 注册过滤器。
AddFilters(options);
AddModelBinders(options);
AddMetadataProviders(options, services);
}
// ...
private static void AddFilters(MvcOptions options)
{
// 我们的参数验证过滤器。
options.Filters.AddService(typeof(AbpValidationActionFilter));
options.Filters.AddService(typeof(AbpUowActionFilter));
options.Filters.AddService(typeof(AbpExceptionFilter));
}
// ...
}
到这⼀步,我们的 AbpValidationActionFilter 会被添加到 IoC 容器当中,以供 ASP.NET Core Mvc 框架进⾏使⽤。
2.1.2 过滤器的验证流程
我们的验证过滤器通过上述步骤,已经被注⼊到 IoC 容器当中了,以后我们每次的接⼝调⽤都会进⼊ AbpValidationActionFilter 的 OnActionExecutionAsync() ⽅法内部。在这个过滤器的内部实现代码中,我们看到 ABP 为我们注⼊了⼀个 IModelStateValidator 对象。
public class AbpValidationActionFilter : IAsyncActionFilter, ITransientDependency
{
private readonly IModelStateValidator _validator;
public AbpValidationActionFilter(IModelStateValidator validator)
{
_validator = validator;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
//TODO: Configuration to disable validation for controllers..?
//TODO: 是否应该增加⼀个配置项,以便开发⼈员禁⽤验证功能?
// 判断当前请求是否是⼀个控制器⾏为,是则返回 true。
// 第⼆个条件会判断当前的接⼝返回值是 IActionResult、JsonResult、ObjectResult、NoContentResult 的⼀种,是则返回 true。
// 这⾥则会忽略不是控制器的⽅法,控制器类型不是上述类型任意⼀种也会被忽略。
if (!context.ActionDescriptor.IsControllerAction() ||
!context.ActionDescriptor.HasObjectResult())
{
await next();
return;
}
// 调⽤验证器进⾏验证操作。
_validator.Validate(context.ModelState);
await next();
}
}
过滤器的⾏为很简单,判断当前的 API 请求是否符合条件,不符合则不进⾏参数验证,否则调⽤ IModelStateValidator 的 Validate ⽅法,将模型状态传递给它进⾏处理。
这个接⼝从名字上看,应该是模型状态验证器。因为我们接⼝上⾯的参数,在 ASP.NET Core MVC 的使⽤当中,会进⾏模型绑定,即建⽴对象到 Http 请求参数的映射。
public interface IModelStateValidator
{
void Validate(ModelStateDictionary modelState);
void AddErrors(IAbpValidationResult validationResult, ModelStateDictionary modelState);
}
ABP vNext 的默认实现是 ModelStateValidator ,它的内部实现也很简单。就是遍历 ModelStateDictionary 对象的错误信息,将其添加到⼀个 AbpValidationResult 对象内部的 List 集合。这样做的⽬的,是⽅便后⾯ ABP vNext 进⾏错误抛出。
public class ModelStateValidator : IModelStateValidator, ITransientDependency
{
public virtual void Validate(ModelStateDictionary modelState)
{
var validationResult = new AbpValidationResult();
AddErrors(validationResult, modelState);
if (validationResult.Errors.Any())
{
throw new AbpValidationException(
"ModelState is not valid! See ValidationErrors for details.",
validationResult.Errors
);
}
public virtual void AddErrors(IAbpValidationResult validationResult, ModelStateDictionary modelState)
{
if (modelState.IsValid)
{
return;
}
foreach (var state in modelState)
{
foreach (var error in state.Value.Errors)
{
validationResult.Errors.Add(new ValidationResult(error.ErrorMessage, new[] { state.Key }));
}
}
}
}
2.1.3 结果的包装
当过滤器抛出了 AbpValidationException 异常之后,ABP vNext 会在异常过滤器 (AbpExceptionFilter) 内部捕获这个特定异常 (取决于异常继承的 IHasValidationErrors 接⼝),并对其进⾏特殊的包装。
[Serializable]
public class AbpValidationException : AbpException,
IHasLogLevel,
// 注意这个接⼝。
IHasValidationErrors,
IExceptionWithSelfLogging
{
// ...
}
2.1.4 数据注解的验证
这⼀节相当于是⼀个扩展知识,帮助我们了解数据注解的⼯作机制,以及 ModelStateDictionary 是怎么被填充的。
扩展阅读:
2.2 对象验证
ABP vNext 除了使⽤ ASP.NET Core MVC 提供的模型验证功能,⾃⼰也提供了⼀个单独的验证模块。我们先来看看模块类型内部所执⾏的操作:
public class AbpValidationModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
// 添加注册类。
context.Services.OnRegistred(ValidationInterceptorRegistrar.RegisterIfNeeded);
// 添加对象验证的辅助对象。
AutoAddObjectValidationContributors(context.Services);
}
private static void AutoAddObjectValidationContributors(IServiceCollection services)
{
var contributorTypes = new List<Type>();
/
/ 在类型注册的时候,如果类型实现了 IObjectValidationContributor 接⼝,则认定是验证器的辅助类。
services.OnRegistred(context =>
{
if (typeof(IObjectValidationContributor).IsAssignableFrom(context.ImplementationType))
{
contributorTypes.Add(context.ImplementationType);
}
});
// 最后向 Options 类型添加辅助类的类型定义。
services.Configure<AbpValidationOptions>(options =>
{
options.ObjectValidationContributors.AddIfNotContains(contributorTypes);
});
}
模块在启动时进⾏了两个操作,第⼀是为框架注册对象验证,第⼆则是添加 辅助类型(IObjectValidationContributor) 的定义到配置类中,⽅便后续进⾏使⽤。
2.2.1 的注⼊
的注⼊⾏为很简单,主要注册的类型实现了 IValidationEnabled 接⼝,就会为其注⼊。
public static class ValidationInterceptorRegistrar
{
public static void RegisterIfNeeded(IOnServiceRegistredContext context)
{
if (typeof(IValidationEnabled).IsAssignableFrom(context.ImplementationType))
{
context.Interceptors.TryAdd<ValidationInterceptor>();
}
}
}
2.2.2 的⾏为
public class ValidationInterceptor : AbpInterceptor, ITransientDependency
{
private readonly IMethodInvocationValidator _methodInvocationValidator;
public ValidationInterceptor(IMethodInvocationValidator methodInvocationValidator)
{
_methodInvocationValidator = methodInvocationValidator;
}
public override void Intercept(IAbpMethodInvocation invocation)
{
Validate(invocation);
invocation.Proceed();
}
public override async Task InterceptAsync(IAbpMethodInvocation invocation)
{
Validate(invocation);
await invocation.ProceedAsync();
}
protected virtual void Validate(IAbpMethodInvocation invocation)
{
_methodInvocationValidator.Validate(
new MethodInvocationValidationContext(
invocation.TargetObject,
invocation.Method,
invocation.Arguments
)
);
}
}
内部只会调⽤ IMethodInvocationValidator 对象提供的 Validate() ⽅法,在调⽤时会将⽅法的参数,⽅法类型等数据封装到 MethodInvocationValidationContext 。这个上下⽂类型,本⾝就继承了前⾯提到的 AbpValidationResult 类型,在其内部增加了存储参数信息的属性。
public class MethodInvocationValidationContext : AbpValidationResult
{
public object TargetObject { get; }
// ⽅法的元数据信息。
public MethodInfo Method { get; }
// ⽅法的具体参数值。
public object[] ParameterValues { get; }
// ⽅法的参数信息。
public ParameterInfo[] Parameters { get; }
public MethodInvocationValidationContext(object targetObject, MethodInfo method, object[] parameterValues)
{
TargetObject = targetObject;
Method = method;
ParameterValues = parameterValues;
Parameters = method.GetParameters();
}
接下来我们看⼀下真正的 对象验证器 ,也就是 IMethodInvocationValidator 的默认实现 MethodInvocationValidator 当中具体的操作。
// ...
public virtual void Validate(MethodInvocationValidationContext context)
{
// ...
AddMethodParameterValidationErrors(context);
if (context.Errors.Any())
{
ThrowValidationError(context);
}
}
// ...
protected virtual void AddMethodParameterValidationErrors(MethodInvocationValidationContext context)
{
// 循环调⽤ IObjectValidator 的 GetErrors ⽅法,捕获参数的具体错误。
for (var i = 0; i < context.Parameters.Length; i++)
{
AddMethodParameterValidationErrors(context, context.Parameters[i], context.ParameterValues[i]);
}
}
protected virtual void AddMethodParameterValidationErrors(IAbpValidationResult context, ParameterInfo parameterInfo, object parameterValue) {
var allowNulls = parameterInfo.IsOptional ||
parameterInfo.IsOut ||
TypeHelper.IsPrimitiveExtended(parameterInfo.ParameterType, includeEnums: true);
// 添加错误信息到 Errors ⾥⾯,⽅便后⾯抛出。
context.Errors.AddRange(
_objectValidator.GetErrors(
parameterValue,
parameterInfo.Name,
allowNulls
)
);
}
2.2.3 “真正”的参数验证器
我们看到,即便是在 IMethodInvocationValidator 内部,也没有真正地进⾏参数验证⼯作,⽽是调⽤了 IObjectValidator 进⾏对象验证处理,其接⼝定义如下:
public interface IObjectValidator
{
void Validate(
object validatingObject,
string name = null,
bool allowNull = false
);
List<ValidationResult> GetErrors(
object validatingObject, // 待验证的值。
string name = null, // 参数的名字。
bool allowNull = false // 是否允许可空。
)
;
}
它的默认实现代码如下:
public class ObjectValidator : IObjectValidator, ITransientDependency
{
protected IHybridServiceScopeFactory ServiceScopeFactory { get; }
protected AbpValidationOptions Options { get; }
public ObjectValidator(IOptions<AbpValidationOptions> options, IHybridServiceScopeFactory serviceScopeFactory)
{
ServiceScopeFactory = serviceScopeFactory;
Options = options.Value;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论