zoukankan      html  css  js  c++  java
  • (转)深入浅出 MVC 数据验证 [附演示源码]

    上次的文章得到了很多读者的支持,所以感谢一下大家,特别感谢指出不足之处的几位朋友,我在原文中已经进行了修改。

    P.S. 图片中的 dozer.net.cn 其实打开后就是我博客园的地址,会自动跳转,这个好记一点

    今天想在这里给大家介绍一下MVC的数据验证框架(后端验证)。

    1、MVC中的数据验证框架有何优点?

    在Asp.net时代,或者没有使用MVC的验证框架,一般是在BLL层中进行数据验证,但是BLL层的返回值又只能返回一个东西,比如一个字符串,而实际情况中,数据验证是很复杂的。

    这时候,BLL层和网站会分离的不彻底,因为很多代码不得不在网站中写。

     

    而在MVC的数据验证框架中,甚至可以不用BLL层,而在比BLL层更底层的Model层书写数据验证的代码。

    并且最后能在网页上显示出来。 

    此图这就是最后的效果

    2、深入浅出?

    此框架有个优点,非常灵活,我这里用正常的三层架构来写。

    因为灵活,我可以把数据验证写在任何一层。

     

    i)写在Controller里:这是最简单的方法,但是也是最不推荐的方法, 因为不能体现分层思想

    ii)写在BLL中:如果对一个数据验证的时候,需要牵扯到别的数据,就应该把验证写在这一层,比如一个Article Model的Category值是1,查询这个分类是否存在

    iii)写在Model中:一些底层的标准应该写在这一层,因为这些标准在任何情况下都不能违反,比如帐号名长度不能超过20个字符

     

    下面,我会一步步讲3中验证方法介绍给大家

    3、前端和后端的结合

    完整地看过MVC教程的人应该都知道如何使用 ModelState,其实MVC验证框架就是利用它,将验证的结果显示在页面中。

     

    下面看一个例子:

    Controller
    [HttpPost]
    //如果表单中input的name属性和Model的字段一样,那可以直接以Model形式传入一个Action
    public ActionResult Exp1(Models.UserModel user)
    {
    //判断
    if (user.Name.Length > 20)
    {
    //如果错误,调用ModelState的AddModelError方法,第一个参数需要输入出错的字段名
    ModelState.AddModelError("Name", "名字不得超过20个字符");
    }
    //判断ModelState中是否有错误
    if (ModelState.IsValid)
    {
    //如果没错误,返回首页
    return RedirectToAction("Index");
    }
    else
    {
    //如果有错误,继续输入信息
    return View(user);
    }
    }

    这里在Controller中一个Action中进行了数据验证,并且把结果放入了ModelState中,那怎么在前端页面显示呢?

     

    如果不了解MVC的验证框架,其实可以直接自动生成,看看标准做法

    在代码上右击,点Add View

    选择创建强类型View,并且在内容中选择Edit

     

    这是自动生成的View

     

     

    OK,下面我可以运行了。。。

     

    由于前端的页面View是自动生成的,所以有些读者可能没看懂,为什么我刚刚在后端的数据验证信息会显示到前端去了呢?

    其实关键就是利用了这个:<%= Html.ValidationMessageFor(model => model.Name) %>

    不理解强类型方法、或还在使用MVC1.0的读者可以看这个:<%= Html.ValidationMessage("Name") %>

    (除了ValidationMessage函数外,还有其它几个函数,可以达到不同的效果,读者可以自行研究下,这几个函数都是以Validation开头的)

     

    小结:在Controller中验证数据,放入ModelState(其核心是一个字典),然后在利用函数读取

    这样,就达到了数据验证时前端和后端相结合的效果。

    4、如何将数据验证代码放入业务逻辑层?

    上面那部分,我们看到了MVC数据验证框架的核心,ModelState。

    只要在ModelState中添加错误就可以在前端页面中显示了。

     

    所以,这个部分的关键就是让BLL操作ModelState

    这里,有2中方法可以参考,其中,第二种方案我是参考了xVal来实现的

     

    i)方案一:在调用BLL函数的时候直接传入ModelState对象

    优点:这个是最好理解的,我直接传入ModelState对象,让BLL操作它不就可以了?

    缺点:BLL是业务逻辑层,它不应该知道自己被谁调用了,也就是说,BLL层中不应该出现任何MVC特有的东西(ModelState对象)

     

    下面就让我来实现它:

    BLL
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;

    namespace MvcApplication1.BLL
    {
    public static class UserBLL
    {
    public static void Edit(Models.UserModel user, ModelStateDictionary ModelState)
    {
    if (user.Name.Length > 20)
    {
    //如果错误,调用ModelState的AddModelError方法,第一个参数需要输入出错的字段名
    ModelState.AddModelError("Name", "名字不得超过20个字符");
    }
    if (ModelState.IsValid)
    {
    //在这里我可以写一些代码,因为完成了验证,我就可以开始更新数据库了
    }
    }
    }
    }
    Controller
    [HttpPost]
    public ActionResult Exp2(Models.UserModel user)
    {
    //调用BLL中的函数
    BLL.UserBLL.Edit(user, ModelState);

    if (ModelState.IsValid)
    {
    return RedirectToAction("Index");
    }
    else
    {
    //这里,前端页面不用改,所以我直接利用第一个例子中的前端页面
    return View("Exp1",user);
    }
    }

     

    OK,直接运行,结果和上一个方法一样

     

    总结:在调用BLL的时候把ModelState传入

     

     

    ii)方案二:通过错误捕捉,将BLL和Controller关联起来

    如果使用方案一,会出现这样一个问题:

    如果我的项目不用MVC了,要移植怎么办?

    新的架构中也有类似于ModelState的东西。总不能把所有的BLL改一遍吧?

    所以,我们需要方案二。

     

    这里是通过编写一个自定义Exception类,然后这个自定义Exception类有2个功能:

    1、加入错误

    2、把错误转换到ModelState中(如果要转移到别的架构,可以再写一个新的转移方法)

     

    下面的代码就是这个自定义Exception类

    ModelExceptions
    //必须继承自Exception
    public class ModelExceptions : Exception
    {
    //存放错误信息的List
    List<string[]> errors = new List<string[]>();

    //判断是否有错误
    public bool IsValid
    {
    get
    {
    return errors.Count == 0 ? true : false;
    }
    }

    //增加错误信息
    public void AddError(string name, string message)
    {
    this.errors.Add(new string[] { name, message });
    }

    //填充ModelState
    public void FillModelState(ModelStateDictionary modelstate)
    {
    foreach (var e in this.errors)
    {
    modelstate.AddModelError(e[
    0], e[1]);
    }
    }
    }

    接下来是在Controller中的代码

    Controller
    [HttpPost]
    public ActionResult Exp3(Models.UserModel user)
    {
    //用try来捕捉错误
    try
    {
    BLL.UserBLL.Edit(user);
    }
    catch (ModelExceptions e)
    {
    //如果发生了错误,就填充到ModelState中
    e.FillModelState(ModelState);
    }
    if (ModelState.IsValid)
    {
    return RedirectToAction("Index");
    }
    else
    {
    //这里,前端页面不用改,所以我直接利用第一个例子中的前端页面
    return View("Exp1", user);
    }
    }

    然后是在BLL中的代码

    BLL
    public static void Edit(Models.UserModel user)
    {
    var e
    = new ModelExceptions();
    if (user.Name.Length > 20)
    {
    //如果错误,调用ModelState的AddModelError方法,第一个参数需要输入出错的字段名
    e.AddError("Name", "名字不得超过20个字符");
    }
    if (e.IsValid)
    {
    //在这里我可以写一些代码,因为完成了验证,我就可以开始更新数据库了
    }
    else
    {
    //如果有错误,就抛出错误
    throw e;
    }
    }

    总结:简单的做法,在后期会反而会带来很多麻烦,所以推荐方案二。而且方案二也不是很麻烦,反而让人感觉很清晰

    5、如何将数据验证代码放入Model中?

    前面,在BLL中验证数据已经很好了,但是又出现了一个问题

    一个Model,很多限制是固定的,比如长度不能超过20个字符

    但是我在BLL中有很多过程,比如修改,删除等

    那我岂不是要在所有的过程中都多这个进行验证?

    其实你也可以通过写一个函数来解决这个问题

     

    但是,我(Model)的名字有没有超过20个字符是我自己的事情,凭什么要你来鉴定?我自己说了算!

     

    插播笑话一则:在我家,大事我说了算,小事我老婆说了算~ 那什么是大事?什么是小事?像美国打不打伊拉克,这就是大事;别的都是小事……

     

    OK,言归正传…

     

    如何把数据验证交给Model呢?这里需要引用一个DLL

     

    然后在Model中这样做

    Model
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.ComponentModel.DataAnnotations;

    namespace MvcApplication1.Models
    {
    public class UserModel
    {
    public string Name { get; set; }


    //属性前加上Attribute
    [Required(ErrorMessage = "密码不能为空")]
    [StringLength(
    20, ErrorMessage = "密码长度不能超过20个字符")]
    public string Password { get; set; }
    }
    }

    引用 System.ComponentModel.DataAnnotations 命名空间,并且在Model属性前加上Attribute(C# 新特性:特征),这样就可以了

    请自行查看System.ComponentModel.DataAnnotations命名空间,看看可以有哪些验证方法

    当然也可以自定义验证,查看默认验证的定义,看看它继承了哪个类,自己仿写就可以了

     

    我这里没有取消掉BLL中的验证,2种验证可以混合使用

     

    OK,那在Controller和BLL中需要做什么?我们需要做一定的修改

    Controller
    [HttpPost]
    //MVC在传入这个Model的时候已经进行了验证,并且把错误放去了ModelState
    public ActionResult Exp4(Models.UserModel user)
    {
    try
    {
    //别的不变,除了这里,我们需要传入ModelState.IsValid
    BLL.UserBLL.Edit(user,ModelState.IsValid);
    }
    catch (ModelExceptions e)
    {
    e.FillModelState(ModelState);
    }
    if (ModelState.IsValid)
    {
    return RedirectToAction("Index");
    }
    else
    {
    return View("Exp1", user);
    }
    }
    BLL
    public static void Edit(Models.UserModel user,bool IsValid)
    {
    var e
    = new ModelExceptions();
    if (user.Name.Length > 20)
    {
    e.AddError(
    "Name", "名字不得超过20个字符");
    }
    //别的不变,但在这里,我除了要判断e中是否有错误外,还要判断ModelState中是否有错误
    if (e.IsValid && IsValid)
    {
    //在这里我可以写一些代码,因为完成了验证,我就可以开始更新数据库了
    }
    else
    {
    throw e;
    }
    }

    我注释了相对了上个例子改动的地方

     

     

    并且混合使用了2中验证方法

    什么时候用Model验证?  验证Model固有的属性

    什么时候用BLL验证? 当需要验证一些复杂关系的时候

     

     

    另外,为什么要把ModelState.IsValid传入BLL?

    因为Model验证是在这个Model传入这个方法的时候就已经完成的

     如果不传入,那BLL验证中虽然没错误,但不代表整个过程没有错误。

    对数据库的操作要知道完整的验证信息,如果不传入,会导致程序BUG

    总结:合理的根据情况来放置数据验证代码的位置才是王道

    6、如何更改错误提示的样式?

    我这里用了MVC基本的那个例程,里面包含了CSS样式表

    而默认情况下,虽然会出现譬如“名称过长”这样的文字信息,但是却没有样式(默认是正常字体,正常颜色)

     

    那如何修改?

    其实打开MVC默认的CSS样式表就不难发现,这些错误信息都有固定的class,所以只要写一个CSS的class即可

     

    那怎么才能知道class名是什么呢? 最方便的方法,做好页面后在浏览器中看一下即可

     

    7、Entity Framework中,如何在Model中编写数据验证?

    Entity Framework会自动生成Model,虽然是可以修改的,但是强烈建议不要直接修改Model原始代码

    其实微软早就想到这一点了,它生成的Model都是 partial class(部分类)

    也就是说,同一个类的代码可以分几部分,写在不同的地方

     

    具体写法如下,写在不同的地方,但需要在同一个命名空间下

    User
    [MetadataType(typeof(UserMetaData))]
    public partial class User { }
    public class UserMetaData
    {
    [Required(ErrorMessage
    = "名字为空")]
    [StringLength(
    10, ErrorMessage = "名字长度不得超过10个字符")]
    public string Name { get; set; }

    [Required(ErrorMessage
    = "密码为空")]
    [StringLength(
    20, ErrorMessage = "密码长度不得超过20个字符")]
    public string Password { get; set; }

    [Required(ErrorMessage
    = "帐号为空")]
    [StringLength(
    10, ErrorMessage = "帐号长度不得超过10个字符")]
    public string Passport { get; set; }
    }

    这样写好后,便可以在Entity Framework中使用Model验证了

    8、Ending

    演示中的源码:下载

     

    如果感觉有收获,那就点一下支持吧~

    如有疑问或者我文章中有不妥之处,请在下方留言,或者发送邮件到:dozer@dozer.net.cn

     

    如欲转载,请加入本文地址和署名,谢谢配合!

  • 相关阅读:
    express不是内部或外部命令,也不是可运行的程序或批处理文件
    Microsoft VBScript 运行时错误 错误 '800a0046' 没有权限 解决方法
    Scripting.FileSystemObject对象的详细技巧指南
    (asp)JScript读写、复制、移动文件 asp也就那回事(4)
    SQL2000系统表、存储过程、函数的功能介绍及应用
    深入浅出:全面理解SQL Server权限体系
    SqlServer2008基础知识:安全与权限
    php-fpm
    PHP获取当前url路径的函数及服务器变量:$_SERVER["QUERY_STRING"],$_SERVER["REQUEST_URI"],$_SERVER["SCRIPT_NAME"],$_SER
    jQuery UI Datepicker中文显示
  • 原文地址:https://www.cnblogs.com/fcsh820/p/1710192.html
Copyright © 2011-2022 走看看