ASP.Net Core MVC 之模型验证
在我们日常的表单中,我们总需要写很多的验证,这个是避免不了的,不写,安全上过不去,写了,又很繁琐。
我们不仅前端要写验证,后端也需要写验证,才能杜绝非法数据入侵。
例如下面的代码:在服务层的保存方法中进行参数验证
var jsonCode = new JsonCode { code = -1, msg = "校验失败" }; if (dtoModel.UserId.IsNullOrEmpty()) { jsonCode.msg = "请填写用户ID!"; return jsonCode; } if (string.IsNullOrWhiteSpace(dtoModel.CommitmentFileUrl)) { jsonCode.msg = "请提交科研诚信承诺书!"; return jsonCode; } if (dtoModel.CompanyName.IsNullOrEmpty()) { jsonCode.msg = "请填写技术转移机构名称!"; return jsonCode; } if (!Regex.IsMatch(dtoModel.CompanyName, @"^[\S\s]{4,20}$")) { jsonCode.msg = "请输入4-20个字的技术转移机构名称"; return jsonCode; } if (dtoModel.CreditCode.IsNullOrEmpty()) { jsonCode.msg = "请填写统一社会信用代码!"; return jsonCode; }
当表单字段多的时候,这个就写得很长,而且发布时候写一次,审核的后台也需要再写一次。是不是很繁琐。
下面就进入主题,介绍模型验证的应用(微软自带):
引入命名空间:System.ComponentModel.DataAnnotations
基础验证规则,请参考官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/models/validation?view=aspnetcore-6.0
这里仅讲解在项目中怎么应用和自定义显示验证信息
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; namespace 模型验证.Models { [Serializable] public class CreateUserDto { [Required(ErrorMessage ="请填写姓名")] public string Name { get; set; } } }
我们只需在实体上定义各个字段的校验规则,接下来就是在控制器中应用
public IActionResult Index(CreateUserDto user)//参数必须以对象形式,一般是createDto,即对应表达的字段内容。 { if(!ModelState.IsValid) { foreach(var item in ModelState.Values) //循环验证每个对象 { if (item.ValidationState == ModelValidationState.Invalid) //判断是否是模型绑定 { return new ContentResult(){Content= item.Errors.FirstOrDefault().ErrorMessage }; //返回第一个字段的错误信息 } } } return View(); }
运行起来:
显示验证信息。
如果我们加上Name参数呢,验证通过,显示页面。
以上是一个简单的例子,讲了在哪里定义验证规则,以及怎么获取自定义的验证信息来显示。
但是在实际项目中,我们不可能在每个控制器中去写这么一串代码输出异常。
实际项目中,我们需要用到过滤器了,下面定义一个过滤器:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace 模型验证.Models { public class ValidateFilter : IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { } public void OnActionExecuting(ActionExecutingContext context) { if (!context.ModelState.IsValid) { string msg = ""; foreach (var item in context.ModelState.Values) { if (item.ValidationState == ModelValidationState.Invalid) { if (item.Errors != null && item.Errors.Any()) { msg = item.Errors.FirstOrDefault().ErrorMessage; } break; } } context.Result = new JsonResult(new { code=-1,msg=msg}); //以json形式返回,在项目中也可以根据需要处理,比如ajax请求的,返回json,否则返回文本内容 } } } }
接着在startup.cs中应用过滤器:
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(options=> { options.Filters.Add<ValidateFilter>(); }); }
这时候,控制器中的代码就可以去掉了
public IActionResult Index(CreateUserDto user) { return View(); }
运行起来,就会执行过滤器的验证规则进行验证了。
这样大大减少了我们后端做验证的复杂度。
有了内置的验证属性之外,我们也可以自定义验证属性,还有模型内的验证,比如判断字段之间的关系;如下代码
继承 IValidatableObject, 编写Validate方法
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; namespace 模型验证.Models { [Serializable] public class CreateUserDto: IValidatableObject { [Required(ErrorMessage ="请填写姓名")] public string Name { get; set; } public string Desc { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { if(Name==Desc) { yield return new ValidationResult( "Name and Description can not be the same!", new[] { nameof(Name), nameof(Desc) } ); } if(Desc=="张三") { yield return new ValidationResult( "Name and Description can not be the same!", new[] { nameof(Name), nameof(Desc) } ); } } } }
上面界面的是全局的注入方式,但有些情况下,我们又不需要验证。
比如保存草稿的场景下,我们使用的是同一个模型。
保存草稿,必然就不需要全部必填。在点击“保存草稿”按钮时,服务端是不能去验证这个模型的。但是我们都全局注入了,那要怎么办呢?
我们修改验证的代码,改成带参数的。通过参数来判断是否验证。如下:
public class ValidateFilter : Attribute, IActionFilter { private bool _skipValidation; //是否跳过验证 public ValidateFilter(bool skipValidation=false) { _skipValidation = skipValidation; } public void OnActionExecuted(ActionExecutedContext context) { } public void OnActionExecuting(ActionExecutingContext context) { _skipValidation = context.ActionDescriptor.EndpointMetadata.Any(m => m is ValidateFilter validateModel && validateModel._skipValidation); //验证action中是否有过滤掉 if (!_skipValidation) { if (!context.ModelState.IsValid) { JsonCode jc = new JsonCode(); foreach (var item in context.ModelState.Values) { if (item.ValidationState == ModelValidationState.Invalid) { jc.code = -1000;//当模型验证失败后,返回-1000,方便前端拦截处理 if (item.Errors != null && item.Errors.Any()) { jc.msg = item.Errors?.FirstOrDefault()?.ErrorMessage; } break; } } context.Result = new JsonResult(jc); } } } }
上面代码中,我们定义了一个_skipValidation的参数来处理是否验证。,默认为false,即不跳过验证。
全局注入照常,我们只需要在控制器中再次引用验证属性即可。
比如在对应的不需要验证的方法上
[ValidateFilter(skipValidation: true)] public async Task<JsonResult> Save(string id, CreateRenCaiDto inputDto)
那假如,该方法体中,我们是动态判断是否要验证的。比如根据前端传递过来的参数,判断是否要验证。
我们可以在代码中这么写
if (!inputDto.IsDraft) //非草稿,验证模型 { if(!ModelState.IsValid) { return BadValidateResult(ModelState); } }
这样,即会再次验证。
/// <summary> /// 手动模型验证返回的信息 /// </summary> /// <param name="ModelState"></param> /// <returns></returns> public JsonResult BadValidateResult(ModelStateDictionary ModelState) { JsonCode jc = new JsonCode(); foreach (var item in ModelState.Values) { if (item.ValidationState == ModelValidationState.Invalid) { jc.code = -1000;//当模型验证失败后,返回-1000,方便前端拦截处理 if (item.Errors != null && item.Errors.Any()) { jc.msg = item.Errors?.FirstOrDefault()?.ErrorMessage; } break; } } return new JsonResult(jc); }
JsonCode是自己定义的一个json返回的对象。
有几点需要注意:
1:对于验证规则不支持的,那只能在业务端自行判断,判断后输出。
2:该种方式,实现了业务端在表单验证中的无感,不需要去关注验证过程,大大提升这方面的效率。
3:在dto上,建议不用共用dto,表单输入创建一个CreateXXXDto,输出创建一个XXXDto。 哪怕一模一样也不要去共用,后期应对变化会灵活些。
对于CreateDto中一般不定义主键Id,而是跟表单UI中内容一致的。利用automapper进行实体间映射到数据库实体。
2023年04月20日补充
若是在代码中,我们想验证某一个对象
可以如下这么用:
public async Task<IActionResult> MyAction(MyModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// To manually validate a specific model:
//重新验证,验证是自动的,但您可能希望手动重复验证。例如,您可能会计算某个属性的值,并希望在将属性设置为计算值后重新运行验证。要重新运行验证,请调用ModelStateDictionary.ClearValidationState以清除特定于正在验证的模型的验证,然后TryValidateModel
:
ModelState.ClearValidationState(nameof(MyModel));
if (!TryValidateModel(model, nameof(MyModel)))
{
return BadRequest();
}
// Do something with the valid model... }
private JsonResult BadRequest() { JsonCode jc = new JsonCode(); foreach (var item in ModelState.Values) { if (item.ValidationState == ModelValidationState.Invalid) { jc.code = -1000;//当模型验证失败后,返回-1000,方便前端拦截处理 if (item.Errors != null && item.Errors.Any()) { jc.msg = item.Errors?.FirstOrDefault()?.ErrorMessage; } break; } } return Json(jc); }
TryValidateModel 是控制器基类自带的方法,不在控制器中的,调用不到这个方法。
若是在abp 框架的创建的webapi项目,要使得模型验证有效,需要在 ConfigureServices 方法中添加以下代码
context.Services.Configure<ApiBehaviorOptions>(options => { options.SuppressModelStateInvalidFilter = true; });
.net模型验证文档
https://learn.microsoft.com/zh-cn/aspnet/core/mvc/models/validation?view=aspnetcore-6.0
更多分享,请大家关注我的个人公众号: