MVC-03 控制器(4)

七、模型绑定

    在ASP.NET MVC中是通过模型绑定(Model Binding)达到解析客户端传来的数据。

1.简单模型绑定

    当网页上有个窗体,且窗体内有个名为Username的输入字段,而Action的参数也定义了一个名为Username的参数,只要窗体的域名与Action方法上的参数名称一样,那么Action在被运行的时候,就会通过DefaultModelBinder类型将窗体或QueryString传来的数据进行处理,将原本传来的字符串数据转换成对应的.NET类型并传给Action方法的同名参数里。

    我们用个简单的例子来描述“简单模型绑定”的过程,请先参考以下动作方法的程序代码,Action名称为TestForm,它会通过简单模型绑定取得从客户端窗体传来的Username参数,最后会将该参数传入ViewData.Model让View使用。

        [HttpPost]
        public ActionResult TestForm(string Username)
        {
            ViewData.Model = Username;
            return View();
        }

     以下是相关的视图页面。

<h2>TestForm</h2>

<form method="post">
    <p>
        使用者名称:
        <input type="text" name="Username" />
    </p>
    <p>
        您输入的使用者名称为:@Model
    </p>
    <input type="submit" />
</form>

    当表单提交后,你会发现窗体上的Username字段已经被成功传送到TestForm这个Action里,并且在Action里也成功接收到Username参数的信息,所以ViewData.Model才会有值,且View上的@Model才会正确显示文字在页面上。
    如果在VS2012中利用断点功能检查Action运行时是否真正接收到客户端表单传来的数据,应该可以发现表单信息的确已经被填入TestForm动作方法的Username参数里。

2.使用FormCollection取得窗体信息

    除了通过简单模型绑定取得窗体传来的单栏信息外,还可以通过FormCollection一次取得整份窗体传来的信息。如下程序演示,只要设置一个FormCollection类型的参数,就可以取得所有从窗体传来的信息,这种用法如同使用以前的Request.Form一样。不过,在ASP.NET MVC里还是建议尽量不要使用Request.Form来取得窗体信息。

除了通过简单模型绑定取得窗体传来的单个表单属性外,还可以通过(   )一次取得整份窗体传来的信息。

A.ViewBag    B.ViewData    C.TempData    D.FormCollection

    我们将上一小节的演示重新改写Action的部分,代码如下。

        [HttpPost]
        public ActionResult TestForm(FormCollection form)
        {
            ViewData.Model = form["Username"];
            return View();
        }

3.复杂模型绑定

    我们一样延续上一小节的演示,另外自定义一个名为UserForm的类别,且定义了三个属性(Properties),此时,Action若直接以UserForm类型来接收窗体信息也是没有问题的,只要表单域名称与UserForm类型中的属性名称一样,同样可以将客户端窗体信息自动绑定到form参数的同名属性上,代码如下。

        [HttpPost]
        public ActionResult TestForm(UserForm form)
        {
            ViewData.Model = form.Username;
            return View();
        }

    通过这种方式做模型绑定还有个好处,那就是我们可以利用VS2012的Intellisense快捷提示功能,帮助我们快速完成属性名称的输入。

    再举一个例子接收复杂模型绑定。假设窗体中有四个字段,分别为Type、Name、Email和Body,代码如下。

<form method="post">
    Type
    <input type="radio" name="Type" value="1" checked="checked" />
    Type1
    <input type="radio" name="Type" value="2" checked="checked" />
    Type2
    <br />
       Name
       <input id="Name" name="Name" type="text" value="" />
       <br />
     
       Email
      <input id="Email" name="Email" type="text" value="" />
      <br />

       Body
       <textarea cols="20" id="Body" name="Body" rows="2"></textarea>
       <br />

       <input type="submit" />

</form>

    数据模型与Action定义如下。

    public class GuestbookForm
    {
        public int Type { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public string Body { get; set; }
    }
        [HttpPost]
        public ActionResult TestForm(GuestbookForm gbook)
        {
            return View();
        }

    当客户端送出窗体到Save动作,ASP.NET MVC的DefaultModelBinder会很神奇地自动将字段信息映射到Action的gbook参数中。

4.多个复杂模型绑定

5.判断模型绑定的验证结果

    当Controller在模型绑定完成后,会得到一个完整的ModelState对象,这个对象将包括模型绑定的过程中收集到的各种信息,其中有模型绑定在输入验证后的状态、模型绑定过程中发生的异常、以及模型绑定时发生的异常,因此,当模型绑定发生输入验证失败时,会在Action里得到一个ModelState.IsValid为false的属性,此时,你就可以判断程序是否要继续运行下去,例如,原本想要通过模型绑定取得的信息新增至数据库,就可以改成新增错误消息到页面上。

    我们延续之前的演示,试着判断模型绑定成功与否。首先,声明一个含有模型验证属性的数据模型,并定义一个含有ModelState.IsValid判断条件的Action方法:

    public class GuestbookForm
    {
        [Required]
        public int Type { get; set; }
        [Required]
        public string Name { get; set; }
        [Required]
        public string Email { get; set; }
        [Required]
        public string Body { get; set; }
    }
        [HttpPost]
        public ActionResult TestForm(GuestbookForm gbook)
        {
            if (!ModelState.IsValid)
            {
                //已验证出无效的模型绑定,有些字段不符合格式要求
                return View();
            }

            //验证成功,此时可以将信息写入数据库
            //InsertIntoDB(gbook);

            return Redirect("/");
        }

    在视图的部分完全不用改写,在ModelState.IsValid这行设置一个断点,试着在只输入Type与Name字段的情况下输出窗体,当窗体接收信息时,你会发现ModelState.IsValid的值为false,如下图。

6.模型绑定验证失败的错误详细信息

    除了可以在Action中验证模型绑定的验证状态外,在Action中还可以通过ModelState属性取得ASP.NET MVC内建的验证失败错误消息。

    若要取得在模型绑定的过程中总共有多少属性会被绑定,可以通过以下程序取得:

ModelState.Count

    若要取得特定属性在绑定过程中是否出现错误,可用以下程序取得:

if(ModelState["Email"].Errors.Count>0)
{
    //...
}

    若要取得特定属性在绑定过程中出现的第一个错误,以及其错误消息或Exception对象,可用以下程序取得:

if(ModelState["Email"].Errors.Count>0)
{
    ModelError err=ModelState["Email"].Errors[0];
    var errMsg=err.ErrorMessage;
    var errExp=err.Exception;
}

    除了可以取得模型绑定过程中内建的验证失败信息外,还可以自行增加模型绑定验证失败的信息。

        [HttpPost]
        public ActionResult TestForm(GuestbookForm gbook)
        {
            if (!ModelState.IsValid)
            {
                //已验证出无效的模型绑定,有些字段不符合格式要求

                if (gbook.Email == null)
                    ModelState.AddModelError("Email", "请输入Email字段");

                return View();
            }

            //验证成功,此时可以将信息写入数据库
            //InsertIntoDB(gbook);

            return Redirect("/");
        }

7.清空模型绑定状态

    在Action里除了得到这些模型绑定的详细信息外,ModelState对象里的信息也一样会传送到View里,如果希望模型绑定状态(ModelState)不要传送到View里,还可以将模型绑定的所有状态清空,让View页面上的强类型信息不受模型绑定状态的影响,代码如下。

        [HttpPost]
        public ActionResult TestForm(GuestbookForm gbook)
        {
            if (!ModelState.IsValid)
            {
                //已验证出无效的模型绑定,有些字段不符合格式要求

                //清空模型绑定状态
                ModelState.Clear();

                return View();
            }

            //验证成功,此时可以将信息写入数据库
            //InsertIntoDB(gbook);

            return Redirect("/");
        }

8.使用Bind属性限制可被更新的数据模型属性

    复杂模型绑定的验证技巧在实际中经常使用也非常方便,但有一个很明显的限制,那就是模型在做绑定的时候,是在Action运行时就完成了,而且不管Model有多少字段,只要客户端有窗体过来就会自动绑定,看来方便,但实际上是有安全风险的。

    因为客户端的表单域非常容易被窜改,如果黑客企图从窗体塞如一些额外的表单域,只要猜到正确的属性名称,就可以通过ASP.NET MVC的模型绑定功能自动将数据绑定到特定对象的同名属性里。

    举个实际的例子来说,假设你有个数据模型名为Member,其属性定义如下,其中LastLoginTime属性代表的是“上次登录时间”。

    public class Member
    {
        public int Id { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
        public DateTime? LastLoginTime { get; set; }
    }

    而你的客户端窗体上只有让用户输入Username与Password而已,所以当你使用模型绑定的方式传入Member信息后,会预期LastLoginTime字段应该不会绑定到任何信息,而且该字段传入之后的同名属性值应该为null才对。程序代码如下:

        public ActionResult UpdateProfile(Member member)
        {
            //TODO:更新数据库中的Member信息

            return View();
        }

    但如果黑客这时窜改了客户端窗体,多塞一个LastLoginTime字段上去,并设置任意时间,那么你数据库中的这条信息,其LastLoginTime字段可能就会被用户任意窜改,如此一来,ASP.NET MVC程序就会有风险,因此不得不小心。
   此时,可通过ASP.NET MVC内建的Bind属性(Attribute)并套用在该数据模型的参数上,明确声明有哪些字段可以被自动绑定进来,或是哪些字段该被排除在自动绑定的名单外。以下演示程序就是声明Member参数在自动绑定时要排除LastLoginTime字段的信息:

        public ActionResult UpdateProfile([Bind(Exclude="LastLoginTime")]Member member)
        {
            //TODO:更新数据库中的Member信息

            return View();
        }

    如果你想明确指明“只有”哪些字段需要绑定,可以使用Include具名参数。

        public ActionResult UpdateProfile([Bind(Include="Password")]Member member)
        {
            //TODO:更新数据库中的Member信息

            return View();
        }

 

通过ASP.NET MVC内建的( )属性,可以明确声明哪些字段可以被自动绑定进来,或是哪些字段该被排除在自动绑定的名单外。

A.Include    B.Exclude    C.Bind    D.Filter   

      如果你不希望在每个Action的参数都套用Bind属性的话,也可以套用在数据模型声明定义的地方,这样一来,整个项目的模型都不需要额外的声明了。

    [Bind(Include="Username,Password")]
    public class Member
    {
        public int Id { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
        public DateTime? LastLoginTime { get; set; }
    }

9.使用UpdataeModel与TryUpdateModel

posted @ 2013-12-05 18:18  liesl  阅读(558)  评论(0编辑  收藏  举报