一、 学习MVC注意事项

1. 了解不同的项目类型

      从ASP.NET 2.0开始,vs针对网站开发区分了两种项目类型,一种是'项目'(Website Project),另一种是'网址'(Web Application Project)。两者最大的差别在于'项目'采用动态编译的架构运作。如:中只要编辑并存储了'App_Code'目录下的类或强类型数据集,就会使整个网站项目在后台重新编译,有时甚至会阻碍在vs中进行操作,若项目规模变大,就很容易拖慢开发速度。而MVC要求项目类型最好是'网址',可以利用自动化辅助功能开发。

2. 初学者常犯的错误

      对于习惯'项目'的人来说,当程序被修改保存后,即可直接切换至浏览器进行测试。但由于MVC需以'网址'的项目类型来开发,所以当程序被修改之后,都要先手动生成项目,才能将修改的结果编译进'bin'目录下组建中,这样才可以切换到浏览器测试,否则所做的修改都不会反映到页面上。

3. 小心使用Request对象和Response对象

      虽然在Controller中可以访问Request对象,但对于从客户端传来的QueryString或Form数据,建议不要通过Request对象取得,以免MVC项目难以维护。

     Response对象也是早期ASP和ASP.NET Web Forms中常用的对象,在controller中建议尽量不要使用。当然,使用它来响应页面内容更要不得。

     不过有规定就例外,只是提醒尽量不要用,并非不能用。如自行开发CAPTCHA图片验证功能时,可能需要通过Response对象响应经过动态运算的图片。

4. 不要在视图中编写过多的程序逻辑

       在开发MVC视图时,一定要尽可能的简化逻辑。不要将过多的商业逻辑写在代码里。

 

二、改良MVC项目

1. 使用视图数据模型

    上一篇中的留言板中,因为先前在 MvcGuestbook.edmx文件创建的'留言板'数据模型包含了5个字段,但是'id'与'建立时间'这两个字段在存储留言中用不到。这样,通过动作窗口接收数据时会有一定的安全风险。所以一些开发,针对一些有特殊目的的窗口或字段,会采用自定义模型的方式来定义。

TIP : 通过'Entity Framework'生成的'Model.留言板'类当成ViewModel使用,若save动作设定的参数类型为'Model.留言板',而且直接将接收到的对象保存到数据库中的话,原本预期只会收到3个参数,但黑客如果知道其他字段的名称(例如创建时间),就可能会多输入一个Form数据,进而复制进你的数据库,并调过字段的默认值。这样,数据库就被注入数据了。

      此类数据模型大多专用于视图中,也称为视图数据模型,即ViewModel。下面是ViewModel的创建过程。

      首先,在'Models'目录下新建一个数据类型(也就是一般的C#类),并将其命名为'GuestbookForm.cs',如图:

       由于留下足迹窗口中只有3个字段,所以在此类中要定义3个属性。具体如下:

1 namespace MvcApplication1.Models
2 {
3     public class GuestbookForm
4     {
5         public string name { get; set; }
6         public string Email { get; set; }
7         public string content { get; set; }
8     }
9 }

       修改'/Views/Guestbook/Write.aspx'页面的"<% @page...%>"语句,将Inherits属性修改成强制类型,并将ViewPage的属性改为输入MvcApplication1.Models.GuestbookForml类,示例如下。注意,控制器的"return View();"语句不需要修改。

Inherits="System.Web.Mvc.ViewPage<MvcApplication1.Models.GuestbookForm>"

      接下来修改原程序中 '<%=Html.Label("name") %>'或'<%=Html.TextBox("name") %>'为MVC2.0新建的强制类型HTML辅助方法,如图,在此过程中还可以利用IntelliSense来编写程序,而且不必担心把字段名称写错。

NOTE: "Html.LabelFor(x=>x.name)"语句中的"x=>x.name"又称为Lambda表达式,是C#3.0/4.0提供的一种机制。

      至于最后输出到浏览器的页面,还是跟之前一模一样。

      接下来我们修改Save()动作。先回忆一下原来的声明,具体如下:

public ActionResult Save(string name,string Email,string content)

     将以上代码修改如下:

public ActionResult Save(Models.GuestbookForm data)

     我们将原来的3个参数改成只剩下一个,使用的一样是之前提到过的Model Binder机制。使用这个机制,将自动通过QueryString()方法或Form()方法传来的参数找到对应的强制类型中的属性。只要名称对上了,该机制就会把数据放入该对象的属性中。

      此时,Write视图和Save动作已经在无形中联系起来了,而且使用的是同一个数据模型。这种为了视图而另外定义的数据模型称为视图数据模型(ViewModel),修改完成的程序代码如下:

 1 [HttpPost]
 2         public ActionResult Save(Models.GuestbookForm data)
 3         {
 4             MvcApplication1.Models.GuestbookEntities db = new Models.GuestbookEntities();
 5 
 6             db.AddTo 留言板(new Models.留言板()
 7             {
 8                name =data.name,
 9                Email=data.Email,
10                content=data.content,
11                dtime=DateTime.Now
12 
13             });
14             
15             db.SaveChanges();
16             ViewData["name"] = data.name;
17             ViewData["Email"] = data.Email;
18             ViewData["content"] = data.content;
19 
20             return View();
21         }

      这段程序代码跟之前差不多,只是参数变少了。但Write视图与Save地址共享用一个ViewModel及用强制类型的方式开发项目有着非常高的价值。如果日后数据库字段名称被修改了,通过VS2010内置的重构机制,将能更有效的针对整个项目进行字段名称的替换。

三、使用窗口验证功能

      好比避免之前留言为空就直接提交到数据库的情况。MVC内置了数据验证机制,只要搭配ViewModel及NET 5 SP1,或者.NET 4.0提供的Data Annotations的函数库,就能定义数据验证规则,同时实现客户端的验证与服务器端的验证了。

      要让GuestbookForm这个ViewModel在使用时能验证'姓名'与'内容'字段为必填项,而且希望当用户输入E-mail数据时能验证其格式,可以通过Data Annotations函数库的声明设定将这些功能应用在ViewModel上。以下是应用Data Annotations函数库的程序代码,可以看出,这里只是多加载了System.ComponentModel.DataAnnotations命名空间和新建了几个属性(Attribute)而已。暂时看不懂,没关系,先看看名字。猜猜用途。

 

 1 using System.ComponentModel.DataAnnotations;
 2 
 3 namespace MvcApplication1.Models
 4 {
 5     public class GuestbookForm
 6     {
 7         [Required]
 8         public string name { get; set; }
 9         [DataType(DataType.EmailAddress)]
10         [RegularExpression(@"^[a-z0-9\._%+-]+@[a-z0-9\.-]+\.([a-z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|asia|jobs|museum)$")]
11         public string Email { get; set; }
12         [Required]
13         public string content { get; set; }
14     }
15 }

 

      利用ModelState.IsValid 验证,可以通过数据模型绑定进来的数据是否符合ViewModel字段验证的要求,如果验证失败,ModelState.IsValid验证就会返回"false"值,并转到"留下足迹"页面,如下:

 1  [HttpPost]
 2         public ActionResult Save(Models.GuestbookForm data)
 3         {
 4 
 5             if (!ModelState.IsValid)
 6             {
 7                 return RedirectToAction("Write");
 8             }
 9             MvcApplication1.Models.GuestbookEntities db = new Models.GuestbookEntities();
10 
11             db.AddTo 留言板(new Models.留言板()

至此,我们已经完成了服务器验证的设置工作,接着来看MVC是如何让实现"客户端JavaScript验证"机制的,一共有一下3个步骤。

       Step01:打开"/View/Shared/Site.Master"文件,在"<head>…</head>"标记之间添加以下3个项目内置的javascript文件。

    <script src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
    <script src="http://www.cnblogs.com/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
    <script src="http://www.cnblogs.com/Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>

       Step02:在"<%using (Html.BeginForm("save",RouteData.Values["controller"].ToString())) {%>"语句之前加入以下声明,以启用客户端验证功能。

 <%=Html.EnableClientValidation(); %>

      TIP:若在一个页面中有两个或者两个以上的窗口需要进行验证," <%=Html.EnableClientValidation(); %>"语句必须出现在第一个Html.BeginForm声明之前,否则验证将无法执行。

      若要使用" <%=Html.EnableClientValidation(); %>"语句验证功能,必须使用Html.BeginForm声明窗口,用户自行编写的<form>标签无法正常使用此功能。

       Step03: 添加字段验证失败时要显示的错误提示信息。

        首先,添加所有验证失败结果的Html.ValidationSummary()方法,示例如下:

 <%=Html.ValidationSummary() %>

        为每个字段加上ValidationMessageFor()方法,以显示验证失败的错误提示,示例如下:

 1       <%=Html.LabelFor(x => x.name) %>
 2       <%=Html.TextBoxFor(x => x.name) %>
 3       <%=Html.ValidationMessageFor(x=>x.name) %>
 4       <br />
 5       <%=Html.LabelFor(x=>x.content) %>
 6       <%=Html.TextBoxFor(x=>x.content)%>
 7       <%=Html.ValidationMessageFor(x=>x.content) %>
 8       <br />
 9       <%=Html.LabelFor(x=>x.Email) %>
10       <%=Html.TextBoxFor(x=>x.Email)%>
11       <%=Html.ValidationMessageFor(x=>x.Email) %>
12       <br />

       完成以上3步,调试即可开始测试。

       HTML4.01标准规定的命名规则如下:

  • 首字必须是英文字母(A~Z、a~z)。
  • 首字后面可以是英文字母(A~Z、a~z)、数字(0~9)、减号(-)、下划线(_)、冒号(:)、小数点(.)。

      若有中文名称,可以选中右键然后选取"重建"——"重新命名"选项,或将键盘光标移至该属性名称处,再按下【F2】功能键,都能快速更改属性名称。如图:

四、避免用户重复发送信息

      目前留言板中后会停留在"储存留言"页面,如果此时用户按下了浏览器的"刷新"按钮,将导致窗口重复发送信息,那么在数据库中就会有两条重复的数据。为解决这个问题,PRG(Post-Redirect-Get)样式的概念,是许多网站经常使用的技巧之一。当遇到需要传送的窗口数据的情况下,会先通过HTTP POST请求送出数据;在处理完数据之后,立即转向(Redirect)另一个网址;最后通过HTTP GET请求取得跳转后的页面。在此,修改Save动作,让Save动作不再显示结果页面,而是直接转向Result动作。步骤如下:

      Step01: 删除下列程序代码。

 

1             ViewData["name"] = data.name;
2             ViewData["Email"] = data.Email;
3             ViewData["content"] = data.content;
4 
5             return View();

 

      Step02: 在删除的程序代码处插入如下语句:

 return RedirectToAction("Result");

      Step03: 删除"/Views/Guestbook/Save.aspx"页面。

      Step04: 新建Result动作,示例如下:

      public ActionResult Result()
        {
            return View();
        }

       若要之前Save动作接收到的GuestbookForm类的数据传递给Result视图,以显示用户输入的摘要,这是就必须使用TempData机制来传递这种只会用到"1次"的数据。

      TempData是一个Directory对象,它与ViewData很像,但非常特殊。所有存储在TempData中的数据只能被参考一次,用过之后数据就会被清空。可以利用TempData的特性,将Save动作接收到的GuestbookForm类的数据传递给Result动作。修改Save动作的程序代码,如下:

 TempData["LastPostGuestbookFormData"] = data;

      Step05: 修改Result动作,程序代码如下:

1      public ActionResult Result()
2         {
3             if (TempData["LastPostGuestbookFormData"] == null)
4             {
5                 return RedirectToAction("Index");
6             }
7             var model = (Models.GuestbookForm)TempData["LastPostGuestbookFormData"];
8             return View(model);
9         }

      Step06: 新建Result动作视图。新建一个强类型视图,并选取适当的视图数据类型和视图内容之后,即可选取"Details"选项来显示单笔数据,如图:

       通过"视图内容"选项可以创建大部分的程序代码,只稍加修改,就可以得到我们需要的网页,示例如下:

 1 <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcApplication1.Models.GuestbookForm>" %>
 2 
 3 <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
 4     留言成功
 5 </asp:Content>
 6 <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
 7     <h2>留言成功</h2>
 8     <fieldset>
 9         <legend>Fields</legend>
10              
11         <div class="display-label">name</div>
12         <div class="display-field"><%: Model.name %></div>      
13         <div class="display-label">Email</div>
14         <div class="display-field"><%: Model.Email %></div>      
15         <div class="display-label">content</div>
16         <div class="display-field"><%: Model.content %></div>
17         
18     </fieldset>
19     <p>
20         <%: Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) %> |
21         <%: Html.ActionLink("回到留言清单", "Index") %>
22     </p>
23 
24 </asp:Content>

      再次运行,打开浏览器进行测试,留言成功后就会跳转到"http://localhost:1309/Guestbook/Result"页面,并显示上一页中GuestbookForm类的内容。如图:

      如果此时我们按下"刷新"按钮,由于"TempData["LastPostGuestbookFormData"]"语句已被删除,所以网页就会指向"http://localhost:1309/Guestbook"显示"留下足迹"的页面。

 

     

 

posted on 2012-11-21 18:10  Eleanore Lee  阅读(3147)  评论(0编辑  收藏  举报