2.7 模型

在ASP.NET MVC框架中,模型(Model)负责核心应用程序或业务逻辑的应用程序部件。ASP.NET MVC应用程序中的模型类不直接处理来自浏览器的输入,也不生成到浏览器的HTML输出。它用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。

“模型”有对数据直接访问的权力,例如对数据库的访问。“模型”不依赖“视图”和“控制器”,也就是说,模型不关心它会如何被显示或是如何被操作。但是模型中数据的变化一般会通过一种刷新机制来公布。为了实现这种机制,那些用于监视此模型的视图必须事先在此模型上注册,从而使视图可以了解在数据模型上发生的改变。通常情况下,模型扮演着以下4大功能:

· 提供对业务逻辑数据(属性)及操作方法的封装。

· 实现与数据库之间的访问操作。

· 实现在控制器和视图之间传值的功能。

· 在视图层实现强类型对象绑定与数据验证。

2.7.1 模型的创建

在ASP.NET MVC项目中,模型类通常放在Models文件夹中,在大型项目开发中为便于管理,通常将模型类单独放在一个项目中,生成单一的程序集,这样模型可以在不同的项目中得到共享。

在VisualStudio中创建模型的过程如下:

(1)在MVC应用程序解决方案的Models文件夹上右击,在弹出的快捷菜单中依次选择“添加”→“类”命令(见图2-17),弹出如图2-18所示的对话框。

图2-17 选择创建Model的菜单

图2-18 添加Model的对话框

(2)在图2-18对话框中选择“类”模板,然后设置类名,如输入User,单击“添加”按钮,完成模型User类的创建。

(3)以上步骤只是添加了模型类文件,但User类是个空类,打开类文件后,添加模型类属性,代码如下:

        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Web;
        namespace LMS.Models
        {
            public class User
            {
                //定义模型的属性
                public int ID { get; set; }                   //ID
                public string Name { get; set; }             //姓名
                public char Gender { get; set; }             //性别
                public DateTime Birthday { get; set; }       //出生日期
                public bool IsSpecialty { get; set; }        //是否有特长
            }
        }

2.7.2 模型的绑定

在ASP.NET MVC中,用户请求到服务器的数据将被包装为模型数据对象,这个数据对象通常也被视图用来提供显示的数据。在ASP.NET MVC中提供了非常灵活的模型绑定机制,在IModelBinder接口中,定义了绑定模型数据的约定,并提供了一个接口的默认实现DefaultModelBinder。在大多数情况下,仅仅通过DefaultModelBinder就可以完成模型的绑定,当然也可以自定义一个模型绑定类来实现IModelBinder接口,完成特定类型的模型绑定。以下为IModelBinder的代码:

        public interface IModelBinder
        {
            object BindModel(ControllerContext controllerContext,
                              ModelBindContext bindingContext);
        }

ASP.NET MVC中,提供模型绑定机制的目的在于,让控制器获取表单或URL中的参数变得更加简单。默认情况下,ASP.NET MVC使用DefaultModelBinder来绑定模型的数据。控制器中的行为在接受参数时,会按照如下顺序查找匹配的数据:

(1)接收表单集合(FormCollection)中的数据。

(2)接收路由(RouteData)中的数据。

(3)接收URL参数(QueryString)中的数据。

如果Action方法的参数类型是值类型,那么DefaultModelBinder将寻找与Action参数名称匹配的参数。如果没有对应的参数,那么Action的参数将试图赋予空引用。因此,对于简单类型的参数来说,参数的类型应该是可控的。

以下代码在视图与模型中绑定了Birthday和IsSpecialty两个属性。

        @Html.EditorFor(model =>model.Birthday)
        @Html.EditorFor(model =>model.IsSpecialty)

在对应的Action中,参数格式如下:

        [HttpPost]
        public ActionResult Create(DateTime birthday, bool isSpecialty)
        {
            //处理接受对象的逻辑(略)

            return View();
        }

以上代码中的两个参数名称与视图中的模型绑定的属性名称相同,这样就可以保证Action在接收数据时会自动根据DefaultModelBinder绑定机制来获取对应的数据。

多数情况下,我们会通过一个Model对象来处理复杂的参数,如参数为一个对象,这时DefaultModelBinder会遍历Model对象的属性来绑定参数。以下代码中,Action接受了一个对象作为参数。

        [HttpPost]
        public ActionResult Create(Models.User user)
        {
            //处理接受对象的逻辑(略)

            return View();
        }

1.模型绑定的方法

(1)隐式模型绑定

隐式模型绑定用于在Action方法中接收参数,可以是值类型或引用类型,当Action在接收请求时,会根据DefaultModelBinder的绑定原理获取参数值。

以下代码演示了接受参数为值类型的情况:

        public ActionResult Create(int id)
        {
            return View();
        }

以下代码中Action的参数id可以匹配如下3种情况的数据。

① 路由规则匹配,如:

        http://localhost/Home/Create/1

② URL参数匹配,如:

        http://localhost/Home/ Create? id=1

③ 表单中name=id的<input>控件匹配,如:

        @using(Html.BeginForm()){
            <input type="text" name="id" />
            <input type="submit" value="提交">
        }

以下代码演示了参数为引用类型的情况。

① 视图为强类型视图:

        @model LMS.Models.User
        @{
            Layout =null;
        }

        <! DOCTYPE html>
        <html>
        <head>
            <meta name="viewport" content="width=device-width" />
            <title>创建用户</title>
        </head>
        <body>
            @using (Html.BeginForm())
            {
                @Html.AntiForgeryToken()
                <div class="form-horizontal">
                    <h4>User</h4>
                    <hr />
                    @Html.ValidationSummary(true)
                    <div class="form-group">
                        @Html.LabelFor(model =>model.Name,
                            new { @class ="control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model =>model.Name)
                            @Html.ValidationMessageFor(model =>model.Name)
                        </div>
                    </div>
                    .
                    </div>
                    <div class="form-group">
                        <div class="col-md-offset-2 col-md-10">
                            <input type="submit" value="Create"
                              class="btn btn-default" />
                        </div>
                    </div>
                </div>
            }
        </body>
        </html>

② 控制器代码:

        [HttpPost]
        public ActionResult Create(User user)
        {
            //处理逻辑(略)
        }

以上Create方法中的参数为User对象,在视图向控制器提交时,会自动将强类型视图中的所有与模型绑定的控件以模型(User对象)的方式提交给控制器。

(2)显示模型绑定

如果在Action的参数中没有显示指定接收的模型参数,则可以使用显示模型绑定方法获取视图中提取的数据。

UpdateModel与TryUpdateModel都用于显示模型绑定。UpdateModel在模型绑定失败后会抛出一个异常。通常UpdateModel要用try...catch语句块包起来。TryUpdateModel不会抛出异常,而是返回一个布尔类型的值,true表示绑定成功,false表示绑定失败。举例如下。

UpdateModel方法的使用:

        [HttpPost]
        public ActionResult Create()
        {
            User model =new User();
            try
            {
                //绑定模型
                UpdateModel(model);
                //处理逻辑(略)
            }
            catch(Exception ex)
            {
                //异常处理(略)
                //返回到视图
                return View(model);
            }
        }

TryUpdateModel方法的使用:

        [HttpPost]
        public ActionResult Create()
        {
            User model =new User();
            //模型绑定,返回结果为是否绑定成功
            if (TryUpdateModel(model))
            {
                //绑定成功后的逻辑处理(略)
            }
            else
            {
                //绑定失败后的逻辑处理
                return View(model);
            }
        }

2.模型绑定的限制

如果不希望DefaultModelBinder对Action中的某个参数进行绑定,可以通过BindAttribute和UpdateModel方法进行说明。

(1)BindAttribute

BindAttribute通过在模型类中指定特性来完成绑定限制。它定义了如下的三个属性。

· Include:表示需要绑定的属性,各个属性之间以逗号进行分隔。

· Exclude:表示不需要绑定的属性,各个属性之前以逗号分隔。

· Prefix:表示请求参数的前缀。

这些标签可以定义在Model上,说明在参数绑定过程中需要绑定的属性或者不需要绑定的属性,如:

        [Bind(Exclude ="Name, Birthday")]
        public class User
        {
            public int ID { get; set; }
            public string Name { get; set; }
            public char Gender { get; set; }
            public DateTime Birthday { get; set; }
            public bool IsSpecialty { get; set; }
            public int WorkingYears { get; set; }
        }

以上类中通过Exclude指定了模型在绑定时不需要Name和Birthday属性,以后当Action接收参数时就不会接收到Name和Birthday属性对应的值。跟踪的情况如图2-19所示。

图2-19 Exclude属性的效果

(2)UpdateModel

UpdateModel用于在Action中显式指定需要绑定的模型属性。如下面的代码中指定了需要绑定的ID和Name属性、排除绑定的Birthday属性。

        [HttpPost]
        public ActionResult Create()
        {
            //实例化用户类
            Models.User user =new Models.User();

            //指定绑定模型中的ID、Name属性,排除Birthday属性
            UpdateModel(
                user,                              //模型对象
                "",                              //属性前缀
                new[] { "ID", "Name" },        //Include的属性集合
                new[] { "Birthday" }             //Exclude的属性集合
            );

            //处理接受对象的逻辑(略)

            return View();
        }

2.7.3 模型的验证

在实际应用中,当需要用户交互输入时,开发人员都会加入一些验证代码,这样可以有效地避免应用异常的出现,也可以使应用的错误提示信息清晰明了地显示在客户端,有利于异常的定位,同时也提高了用户的体验。特别是在商业应用项目中使用验证功能,可以在数据存入存储设备前进行格式以及内容的校验,这样也提高了数据存储的安全性。

ASP.NET MVC采用Model绑定,为目标Action生成了相应的参数列表,但是在真正执行目标Action方法之前,还需要对绑定的参数实施验证以确保其有效性。ASP. NET MVC中提供了4种不同的编程模式来对绑定的参数进行验证,分别是:

· 数据元(Metadata)数据的验证

· ValidationAttribute特性的验证

· 实现IValidatableObject接口的验证

· 实现IDataErrorInfo接口的验证

但最为典型的使用数据元(Metadata)的语义验证。语义验证是将当前的输入数据根据特定数据限制的代码进行验证。

例如,指定某个TextBox为必须输入,或者限定某个TextBox内容的长度,或者使用正则表达式控制其输入的内容,比如验证日期格式时发生不匹配的数据,则有图2-20的提示:

图2-20 语义验证示例

在ASP.NET MVC中,可以对实体的属性设置Attribute以使MVC框架对用户输入数据的合法性进行验证,它们隶属于“System.ComponentModel.DataAnnotations”命名空间,常用验证属性如表2-6所示。

表2-6 常用的验证Attribute

(1)RequiredAttribute常用的属性如表2-7所示。

表2-7 RequiredAttribute常用的属性

下面的代码实现一个非空验证:

        public class CustomerMetaData
        {
            //标注标题不能为空
            //使用自定义的错误提示语言
            [Required(ErrorMessage ="标题不能为空。")]
            public string Title{get; set; }

            //标注名称不能为空
            //使用默认的错误提示语言
            [Required()]
            public string Name{get; set; };
        }

(2)StringLength常用的属性如表2-8所示。

表2-8 StringLength常用的属性

下面的代码实现了一个长度验证:

        public class ProductMetadata
        {
            [ScaffoldColumn(true)]           //true表示展示到页面上,false表示不展示
                                              到页面上
            [StringLength(4, ErrorMessage ="照片缩略图名称不能超过4个字符。")]
            public string ThumbnailPhotoFileName{get; set; }
        }

(3)Range常用的属性如表2-9所示。

表2-9 Range常用的属性

下面的代码实现了一定范围的验证。

        public class ProductMetaData
        {
            //浮点型范围的验证
            [Range(100, 200, ErrorMessage ="重量的值必须为100~200g。")]
            public float Weight{get; set; }

            //整型范围的验证,成熟期为30~45天
            [Range(30,45, ErrorMessage ="成熟期必须为30~45天.")]
            public int MaturePeriod {get; set; };

            //时间范围的验证
            [Range(typeof(DateTime), "2016-4-1", "2016-6-1",
                ErrorMessage ="允许销售的时间必须在2016-4-1和2016-6-1之间。")]
            public Datatime SellDate{get; set; };
        }

(4)RegularExpression常用的属性如表2-10所示。

表2-10 RegularExpression常用的属性

下面的代码实现一个正则表达式的验证:

        public class CustomerMetaData
        {
            //允许40个大、小写字母
            //并且使用自定义错误信息
            [RegularExpression(@"^[a-z, A-Z''-'\s]{1,40}$",
                ErrorMessage ="只能使用大、小写字母。")]
            public object FirstName;
        }

(5)CustomerValidation常用的属性如表2-11所示。

表2-11 CustomerValidation常用的属性