从零写一个Asp.net core手脚架(模型验证)
一个asp.net core项目,一定包含了各种的实体,在RESTful api里面,有很多的参数传递,不建立实体则大量的参数需要自定验证正确性,并且Action上面会写的密密麻麻的参数
在asp.net 2.0的时候,就推出了ModelState,顾名思义,这个是模型状态,用于验证实体对象的
如何模型验证
用法是在需要验证的地方打上继承了ValidationAttribute的特性,比如常见的RequiredAttribute,这个是验证对象是否存在
/// <summary> /// Admin Request Model /// </summary> public class AdminRequestModel { /// <summary> /// User /// </summary> [Required] public string User { get; set; } /// <summary> /// Account /// </summary> [Required] public string Account { get; set; } }
这是一个实体
我们要验证的Model里是“User”属性和“Account”属性不能为空
写在Action上面
/// <summary> /// Test Admin /// </summary> /// <param name="model"></param> /// <returns></returns> [HttpPost("Admin")] public ResponseModel Admin(AdminRequestModel model) { return new AdminResponseModel() { User = model.User }; }
在asp.net core 2.1之前版本的web api则改为
/// <summary> /// Test Admin /// </summary> /// <param name="model"></param> /// <returns></returns> [HttpPost("Admin")] public ResponseModel Admin([FromForm] AdminRequestModel model) { return new AdminResponseModel() { User = model.User }; }
进入了Action的就是通过了基础模型验证的实体对象,数据库操作之类的业务验证,就在Action里面处理
怎么启用模型验证
全局过滤器
/// <summary> /// Gold Filter Validate Model /// </summary> public class GoldModelFilter : IActionFilter { /// <summary> /// Action Before /// </summary> /// <param name="context"></param> public void OnActionExecuted(ActionExecutedContext context) { } /// <summary> /// Action After /// </summary> /// <param name="context"></param> public void OnActionExecuting(ActionExecutingContext context) { if (!context.ModelState.IsValid) throw new ValidateException( context.ModelState.Values .FirstOrDefault(item => item.Errors.Count > 0 ) .Errors.FirstOrDefault().ErrorMessage ); } }
然后在
Startup.cs的ConfigureService内写的AddMvc改为
services.AddMvc(options=> { options.Filters.Add<GoldModelFilter>(); }));
我们不需要全局验证的则这样写
public class ModelFilterAttribute:ActionFilterAttribute { /// <summary> /// Action Before /// </summary> /// <param name="context"></param> public override void OnActionExecuted(ActionExecutedContext context) { //leave out } /// <summary> /// Action After /// </summary> /// <param name="context"></param> public override void OnActionExecuting(ActionExecutingContext context) { //leave out } }
是不是很像,是的,就是一样的,只是一个是特性,一个是过滤器,内部的流程也差不多
ActionFilterAttribute还有很多东西,就暂不赘述了
我们在需要模型验证的Action上面打上特性就可以了
如何扩展自定义模型验证
CustomValidationAttribute,顾名思义这个是做自定义模型验证的,支持给属性、字段、方法、参数和实体对象验证的
他有两个参数,参数1是自定义的验证类Type,参数2是方法名
自定义验证类需要是公开类,自定义方法也是公开方法
方法参数1是object类型,参数2是ValidationContext类型
参数1是验证的内容,参数2是验证的上下文
包括
DisplayName 描述名
MemberName 成员名
ObjectInstance 整个验证的实体
ObjectType 整个验证的实体类型
还一个方法GetService
这个是获取服务的,依赖注入里面的服务,可以通过这个取出来
如果我们要对之前的AdminRequestModel这个实体验证User是否等于Account,不等于,则写一条错误日志,然后验证失败
/// <summary> /// Admin Validate /// </summary> public class AdminValidate { /// <summary> /// Ordinary Validate /// </summary> /// <param name="value"></param> /// <param name="validationContext"></param> /// <returns></returns> public static ValidationResult Ordinary(object value, ValidationContext validationContext) { if (value is AdminRequestModel model) { if (!model.User.Equals(model.Account)) { var logger = validationContext.GetService(typeof(ILogger<AdminValidate>)) as ILogger<AdminValidate>; logger.LogError("User not equals Account"); return new ValidationResult("User not equals Account"); } return ValidationResult.Success; } return new ValidationResult("Type Error"); } }
AdminRequestModel上面加一行[CustomValidation(typeof(AdminValidate), "Ordinary")]
这种是低复用的场景下,给特定的做验证的,如果我们这种验证很多,而且很多处都有相似的验证,那么可以写一个特性,继承ValidationAttribute
这个的验证的有两种场景,单纯的内容验证,没有功能性的,比如上面这种,验证失败,悄悄写一条错误日志,甚至发一个预警邮件
那么重构两个方法FormatErrorMessage和IsValid,前者返回错误信息,后者是验证的
除了验证,还有一些功能性的
那么请重构IsValid方法
代码和AdminValidate.Ordinary雷同
扩展
如果我们的过滤特性里面也有一些功能性呢?
/// <summary> /// Register Service Action Filter /// </summary> public class ServiceActionFilterAttribute : ActionFilterAttribute { private ILogger<ServiceActionFilterAttribute> Logger { get; } /// <summary> /// Generate /// </summary> /// <param name="logger"></param> public ServiceActionFilterAttribute(ILogger<ServiceActionFilterAttribute> logger) { Logger = logger; } /// <summary> /// Action Before /// </summary> /// <param name="context"></param> public override void OnActionExecuted(ActionExecutedContext context) { //leave out } /// <summary> /// Action After /// </summary> /// <param name="context"></param> public override void OnActionExecuting(ActionExecutingContext context) { //leave out } }
这样是不能打在Action上面了
这个时候,我们就要掏出ServiceFilterArribute或者TypeFilterAttribute
/// <summary> /// Test Admin /// </summary> /// <param name="model"></param> /// <returns></returns> [HttpPost("AdminDIFilterAttibute")] [ServiceFilter(typeof(ServiceActionFilterAttribute))] public ResponseModel AdminDIFilterAttibute(AdminRequestModel model) { return new AdminResponseModel() { User = model.User }; }
前者是Type必须注册在依赖注入容器里面的,后者是不需要的
项目地址: