本次要和大家分享的是webapi的模型验证,讲解的内容可能不单单是做验证,但都是围绕模型来说明的;首先来吐槽下,今天下午老板为自己买了套新办公家具,看起来挺好说明老板有钱,不好的是我们干技术的又成了搬运工(谁叫技术部男的多呢哈哈),话说让我们搬点儿什么小座椅板凳就够了吧,为什么4大箱的家具都让我们动手,每箱东西拆分出来每件几乎需要至少4个人才能挪到的东西,而且不少呢,这是让我们搬完后不用上班的节奏吧;我很想问的是买这么贵的东西,难道不给包送和组装?行政部门就不能请点搬运工,非要节约这点钱(技术可不是兼职的搬运工啊)?东西少那是锻炼身体没关系,东西多了那就是折磨人,这里说的想必也是大众技术朋友们的心声吧呵呵;好了不多说了,本章内容希望大家喜欢,也希望各位多多扫码支持和点赞谢谢:
» 增加模型验证
» 自定义过滤器,输出模型验证信息
» FromUri和FromBody用途
下面一步一个脚印的来分享:
» 增加模型验证
首先,我们测试用例使用上一篇的 MoStudent 学生类,模型验证需要在对应提交类中的需要验证格式的属性增加一些注解标记,常用的标记有:
. Required:必须满足不为空
. RegularExpression:正则表达式验证
. StringLength:指定字符允许的范围
. DataType:数据类型,常用于密码类型,如: DataType.Password
. Range:指定数字允许的范围
这里我们实例的 MoStudent 类用到的注解,如下代码:
public class MoStudent
{
public int Id { get; set; }
[Required(ErrorMessage = "名称必须填写")]
[RegularExpression(@"\w{2,15}", ErrorMessage = "名称应为2-15长度的字母组合")]
public string Name { get; set; }
public bool Sex { get; set; }
public DateTime Birthday { get; set; }
}
然后,我们把保存学生的方法内容定义成这样,iis访问地址为 http://localhost:1001/s/add ,代码对应:
[Route("add")]
[HttpPost]
public HttpResponseMessage AddStudent(MoStudent moStudent)
{
if (ModelState.IsValid)
{
return Request.CreateResponse(HttpStatusCode.OK, moStudent);
}
return Request.CreateResponse(HttpStatusCode.BadRequest, ModelState);
}
这里的关键代码是 ModelState.IsValid ,通过这个来判断用户录入的信息是否满足我们定义在实体类中的注解规则,不满足我这里直接输出访问状态为 HttpStatusCode.BadRequest ,再来咋们用ajax提交添加学生表单到这个接口,但不录入任何学生信息,会得到console输出的返回的信息:

这里能看到404,明显从上面的代码来看走到了 Request.CreateResponse(HttpStatusCode.BadRequest, ModelState); 这段代码,也就是说 ModelState.IsValid 的结果是false,验证没有通过,因为我们在录入学生信息页面的文本框内,没有输入任何信息就直接提交的表单,所以这里验证没有通过;但是作为一个webapi接口而言,这种如果提交格式不正确,就直接返回400错误,这样明显不友好,那么我们需要做一下变动,首先需要定义一个公共接口返回类 MoResult ,该类主要用来作为webapi接口的统一返回数据格式标准(这也是通常接口需要的一种响应数据规则格式),我们定义如下格式代码:
1 /// <summary>
2 /// 结果输出类
3 /// </summary>
4 public class MoResult
5 {
6
7 /// <summary>
8 /// 0:失败,1:成功 其他
9 /// </summary>
10 public EnResultStatus Status { get; set; }
11
12 /// <summary>
13 /// 错误信息
14 /// </summary>
15 public object Msg { get; set; }
16
17 /// <summary>
18 /// 对象信息
19 /// </summary>
20 public object Data { get; set; }
21 }
22
23 /// <summary>
24 /// 枚举类型
25 /// </summary>
26 public enum EnResultStatus
27 {
28 失败 = 0,
29 成功 = 1
30 }
然后,调整下apiController对应保存学生信息的Action方法代码如:
1 [Route("add")]
2 [HttpPost]
3 public HttpResponseMessage AddStudent(MoStudent moStudent)
4 {
5
6 var moResult = new MoResult() { Msg = EnResultStatus.失败 };
7 try
8 {
9 if (ModelState.IsValid)
10 {
11 var isfalse = db.Save(moStudent);
12 moResult.Status = isfalse ? EnResultStatus.成功 : EnResultStatus.失败;
13 moResult.Data = moStudent;
14 }
15 else
16 {
17 //如果验证是吧,只取第一个错误信息返回
18 var item = ModelState.Values.Take(1).SingleOrDefault();
19 moResult.Msg = item.Errors.Where(b => !string.IsNullOrWhiteSpace(b.ErrorMessage)).Take(1).SingleOrDefault().ErrorMessage;
20 }
21 }
22 catch (Exception ex)
23 {
24 moResult.Msg = ex.Message;
25 }
26 return Request.CreateResponse(HttpStatusCode.OK, moResult);
27 }
需要注意的几点是:
1. 这里我们通过 MoResult 的Data属性来返回我们添加成功Student输入信息;
2. 如果 ModelState.IsValid 验证失败,取第一个验证错误信息返回给调用方,其目的让调用方能够提醒用户哪些信息录入的不全或者格式不对
好了咋们来一起看下这个时候ajax提交的空表单数据后,获取到webapi接口返回的数据信息如:

很明显这个错误信息就是咋们在实体 MoStudent 中对Name属性做的一个验证错误信息,再这里一个前端调用webapi接口验证的流程基本就完事了,当然一般这种空验证也是需要前端自己验证的,好了咋们也来看下,如果录入完成学生信息的表单提交过后会有什么样子的数据返回呢:

» 自定义过滤器,输出模型验证信息
首先,我们需要明确的是,任意webapi接口基本都是需要有请求参数的格式验证的,如果按照上一节直接在实体类中增加注解验证,那么我们就没必要在每一个api接口对应的action中再写代码 ModelState.IsValid 去判断是否验证成功,或者是获取验证后的错误信息了,因为webapi提供一个 ActionFilterAttribute Action过滤器,这个作用主要是请求到api的Action方法时候用来执行一些东西,本实例通过继承她,来自定义一个验证过滤器 ValidateModelAttribute ,主要用来统一获取验证错误信息,并且输出响应给调用方,下面先来看下代码:
1 /// <summary>
2 /// 模型格式验证
3 /// </summary>
4 public class ValidateModelAttribute : ActionFilterAttribute
5 {
6
7
8 public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
9 {
10
11 if (!actionContext.ModelState.IsValid)
12 {
13
14 var moResult = new MoResult() { Msg = "请求数据异常", Status = EnResultStatus.失败 };
15 //自定义错误信息
16 var item = actionContext.ModelState.Values.Take(1).SingleOrDefault();
17 moResult.Msg = item.Errors.Where(b => !string.IsNullOrWhiteSpace(b.ErrorMessage)).Take(1).SingleOrDefault().ErrorMessage;
18 actionContext.Response = actionContext.Request.CreateResponse(System.Net.HttpStatusCode.OK, moResult);
19 }
20 }
21 }
同第一小节的代码差不多,主要用来判断是否通过实体类的注解验证,如果不通过获取第一个错误信息返回给调用方;这里要注意的是我们重写了 ActionFilterAttribute 过滤器中的 OnActionExecuting 方法;好了下面我们还需要把自定义的 ValidateModelAttribute 在 App_Start/WebApiConfig.cs 加入一下 config.Filters.Add(new ValidateModelAttribute()); 这个代码,意思吧自定义的过滤器假如到webapi中;再来咋们直接就可以在我们添加学生的Action上方使用 [ValidateModel] 标记来验证调用方传递给webapi的参数了,我们把学生添加Action AddStudent 代码改为如下所示:
1 [Route("add")]
2 [HttpPost]
3 [ValidateModel] //这里是自定义过滤
4 public HttpResponseMessage AddStudent(MoStudent moStudent)
5 {
6
7 var moResult = new MoResult();
8 try
9 {
10 var isfalse = db.Save(moStudent);
11 moResult.Status = isfalse ? EnResultStatus.成功 : EnResultStatus.失败;
12 moResult.Data = moStudent;
13 }
14 catch (Exception ex)
15 {
16 moResult.Msg = ex.Message;
17 }
18 return Request.CreateResponse(HttpStatusCode.OK, moResult);
19 }
然后,再用ajax提交一下空表单数据,会得到如图所示的信息:

能看出自定义验证过滤器效果和我们之前直接写在代码中验证的提示信息一样,总体来说自定义的验证使得api中的Action代码精简了很多,很提倡使用;
» FromUri和FromBody用途
首先,我们来看一段代码:
1 [Route("all01_2/{id:int?}")]
2 [AcceptVerbs("POST", "GET")]
3 public HttpResponseMessage GetAllStudents01_2(int id = 0)
4 {
5 var students = db.GetAll();
6
7 if (id > 0)
8 {
9 students = students.Where(b => b.Id == id).ToList();
10 }
11
12 return Request.CreateResponse(HttpStatusCode.OK, students);
13 }
这里的action传递进来了一个名称id的参数,然后下面做了 id > 0 的判断来获取不同的数据,这种场景我们经常会遇到,并且查询条件的参数不止一个,这样来看我们会不断的在action里面增加传递的参数,格式可能会如此: public HttpResponseMessage GetAllStudents01_2(int id = 0,参数2,参数3,参数...) ,这样如果需要用到的参数有10个以上,通常看起来不是很方便,这个时候FromUri和FromBody的用途就来了;先来看FromUri,首先需要我们把上面的那个Action方法的入参格式改成这样:
1 [Route(@"all01_3/{id:int=0}/{name?}")]
2 [AcceptVerbs("POST", "GET")]
3 public HttpResponseMessage GetAllStudents01_3([FromUri]MoSearch moSearch)
4 {
5 var students = db.GetAll().AsEnumerable();
6
7 if (moSearch.Id > 0)
8 {
9 students = students.Where(b => b.Id == moSearch.Id);
10 }
11
12 if (!string.IsNullOrWhiteSpace(moSearch.Name))
13 {
14 students = students.Where(b => b.Name.Contains(moSearch.Name));
15 }
16
17 return Request.CreateResponse(HttpStatusCode.OK, students);
18 }
通过实体类 MoSearch 来获取调用端传递的参数,实体类MoSearch定义的属性字段如:
public class MoSearch
{
public int Id { get; set; }
public string Name { get; set; }
}
然后我们改造下查询学生列表的ajax请求方法如:
1 $("#btnSearch").on("click", function () {
2
3 var tabObj = $("#tab tbody");
4 tabObj.html('tr><td colspan="4">加载中...</td></tr>');
5
6
7 var url = "http://localhost:1001/s/all01_3/1";
8 var txtName = $("input[name='txtName']").val();
9 if (txtName.length > 0) {
10 url = url + "/" + txtName;
11 }
12
13 $.get(url, function (data) {
14
15 console.log(data);
16
17 var tabHtml = [];
18 $.each(data, function (i, item) {
19
20 tabHtml.push('<tr>');
21 tabHtml.push("<td>" + item.Id + "</td>");
22 tabHtml.push("<td>" + item.Name + "</td>");
23 tabHtml.push("<td>" + (item.Sex ? "男" : "女") + "</td>");
24 tabHtml.push("<td>" + item.Birthday + "</td>");
25 tabHtml.push('</tr>');
26 });
27 if (tabHtml.length <= 0) { tabHtml.push('tr><td colspan="4">暂无数据</td></tr>'); }
28
29 tabObj.html(tabHtml.join(''));
30 });
31 });
这里Id=1是固定了,直接查询id=1的学生信息,并且有一个可传递的参数学生名称txtName的值,如果用户没有输入学生名称,那么就不会加入到这个get请求中,下面来看下首先不输入学生姓名查询条件的效果:

这里能看出来只有学生编号为1的学生被查出来了,下面我们再录入姓名查询条件,能看到结果如:

能看到此时查不到名称为“小2”的学生信息,咋们再改成“小1”试试,

这个时候小1的学生能被查出来,这说明咋们的api的all01_3能够通过 FromUri]MoSearch moSearch 获取到我们输入的参数;下面我们使用FromBody来演示她的效果,首先需要修改js请求webapi格式如:

然后把webapi的all01_3修改为如下代码:
1 [Route(@"all01_3/{name?}")]
2 [AcceptVerbs("POST", "GET")]
3 public HttpResponseMessage GetAllStudents01_3([FromBody]MoSearch moSearch)
4 {
5 var students = db.GetAll().AsEnumerable();
6
7 if (moSearch.Id > 0)
8 {
9 students = students.Where(b => b.Id == moSearch.Id);
10 }
11
12 if (!string.IsNullOrWhiteSpace(moSearch.Name))
13 {
14 students = students.Where(b => b.Name.Contains(moSearch.Name));
15 }
16
17 return Request.CreateResponse(HttpStatusCode.OK, students);
18 }
主要区别吧FromUri换成了FromBody,最后咋们来一起看下没有录入学生名称查询条件的测试效果图:

录入学生查询条件后的效果图:

由上看出能正常查询出来数据,这说明 [FromBody]MoSearch moSearch 获取到了调用客户端的请求参数;好了FromBody和FromUri测试的例子就这些,这两者的区别这里总结下:
FromBody:主要用来获取post方式请求的参数
FromUri:主要获取url地址的请求参数(可以看成get方式)
这两者的效果对比我就不做了,有兴趣的朋友可以自己验证下;本次分享的内容就到这里吧,主要讲解了关于数据模型的一些知识,希望各位喜欢,多多点赞。