MVC3课程中的几个问题整理
这是几个小问题,整理出来给大家参考
1. 如何为不同类型的属性设置不同的编辑界面
备注:这个实例的源代码,可以通过这里下载 MvcApplicationEditTemplate.rar
我们探讨到了针对不同的属性,MVC3其实可以专门定制不同的编辑界面。这个概念,其实早在MVC出来之前,就曾经有过。之前有一个Dynamic Data的网站模板 ,里面大致也是用到了所谓的编辑器模板(FieldTemplates)的概念。
我们可以大致看一眼之前的做法
注意,Dynamic Data这个模板,是基于Web Forms实现的一个应用架构。这里不作进一步展开,如果有兴趣的朋友,可以参考:http://msdn.microsoft.com/en-us/library/ee845452.aspx
提一下Dynamic Data,是希望给大家一个初步印象,确实可以针对不同类型的字段,设置不同的界面来编辑或者显示。MVC中也提供了这样的机制。
下面我们来做一个常见的例子,我们希望那些类型为DateTime的属性,在页面上编辑的时候,自动调用jquery的一个日历效果让用户可以选择,而不是简单的一个文本框。
准备一个业务实体类型(Employee)
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcApplicationEditTemplate.Models { public class Employee { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime Birthday { get; set; } } }
准备一个HomeController
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MvcApplicationEditTemplate.Controllers { public class HomeController : Controller { // // GET: /Home/ public ActionResult Index() { var emp = new Models.Employee(); return View(emp); } } }
为这个Action生成一个Edit视图
@model MvcApplicationEditTemplate.Models.Employee @{ ViewBag.Title = "Index"; } <h2>Index</h2> @using (Html.BeginForm()) { @Html.ValidationSummary(true) <fieldset> <legend>Employee</legend> @Html.HiddenFor(model => model.ID) <div class="editor-label"> @Html.LabelFor(model => model.FirstName) </div> <div class="editor-field"> @Html.EditorFor(model => model.FirstName) @Html.ValidationMessageFor(model => model.FirstName) </div> <div class="editor-label"> @Html.LabelFor(model => model.LastName) </div> <div class="editor-field"> @Html.EditorFor(model => model.LastName) @Html.ValidationMessageFor(model => model.LastName) </div> <div class="editor-label"> @Html.LabelFor(model => model.Birthday) </div> <div class="editor-field"> @Html.EditorFor(model => model.Birthday) @Html.ValidationMessageFor(model => model.Birthday) </div> <p> <input type="submit" value="Save" /> </p> </fieldset> } <div> @Html.ActionLink("Back to List", "Index") </div>
注意看这里的语法:EditorFor。这是MVC比较推荐的做法,我发现不少朋友可能还是习惯自己去些input,但我个人觉得那不是一个很好的做法。
预览效果
我们的需求就是,将Birthday下面这个文本框定制一下,让他可以自动显示出来一个日历。(我们将采用jquery ui来实现)
添加一个EditorTemplate
由于Birthday属性是DateTime类型,所以我们可以针对这个类型设计一个编辑器模板。为了共享,我们可以将这个模板放在Shared目录下面
请注意,这里的目录名必须叫做:EditorTemplates。那个模板的名称也必须叫DateTime.cshtml。 再一次领教了MVC中约定胜于配置的特性吧。
@model System.DateTime @Html.TextBox("",ViewData.TemplateInfo.FormattedModelValue,new {date_picker=true})
在这个文件中,我们只需要指定两行代码。(这是Razor表达式语法),从上面的语法可以看出,我们其实还是放了一个文本框(TextBox),但这里的关键在于,我们会通过jquery来给这个文本框添加特效。
添加一个jscript文件(EditorBehavior.js)
/// <reference path="jquery-1.5.1-vsdoc.js" /> $(function () { $(":input[date-picker]").datepicker(); });
在视图中引入这个脚本文件
@model MvcApplicationEditTemplate.Models.Employee @{ ViewBag.Title = "Index"; } <h2>Index</h2> <script src="@Url.Content("~/Scripts/jquery-ui-1.8.11.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/EditorBehavior.js")" type="text/javascript"></script> @using (Html.BeginForm()) { @Html.ValidationSummary(true) <fieldset> <legend>Employee</legend> @Html.HiddenFor(model => model.ID) <div class="editor-label"> @Html.LabelFor(model => model.FirstName) </div> <div class="editor-field"> @Html.EditorFor(model => model.FirstName) @Html.ValidationMessageFor(model => model.FirstName) </div> <div class="editor-label"> @Html.LabelFor(model => model.LastName) </div> <div class="editor-field"> @Html.EditorFor(model => model.LastName) @Html.ValidationMessageFor(model => model.LastName) </div> <div class="editor-label"> @Html.LabelFor(model => model.Birthday) </div> <div class="editor-field"> @Html.EditorFor(model => model.Birthday) @Html.ValidationMessageFor(model => model.Birthday) </div> <p> <input type="submit" value="Save" /> </p> </fieldset> } <div> @Html.ActionLink("Back to List", "Index") </div>
最后预览得到的效果就是下面这样。按照这样的思路,我们还可以给其他类型的属性指定编辑器模板。
这个实例的源代码,可以通过这里下载 MvcApplicationEditTemplate.rar
2. 为什么脚本移动位置之后,页面的样式变化了
这是一个小问题,我们先来看一下默认情况下MVC项目的样式
这个布局和样式,是在_layout.cshtml中定义好的
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script> </head> <body> <div class="page"> <header> <div id="title"> <h1> My MVC Application</h1> </div> <div id="logindisplay"> @Html.Partial("_LogOnPartial") </div> <nav> <ul id="menu"> <li>@Html.ActionLink("Home", "Index", "Home")</li> <li>@Html.ActionLink("About", "About", "Home")</li> </ul> </nav> </header> <section id="main"> @RenderBody() </section> <footer> </footer> </div> </body> </html>
出于网站设计优化的考虑,我们知道应该尽可能地将javascript的引用放在页面底部。Yahoo有一个专门的文章讲这方面的内容:Best Practices for Speeding Up Your Web Site
所以,你可能会尝试将脚本移动到footer里面,例如下面这样
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> </head> <body> <div class="page"> <header> <div id="title"> <h1> My MVC Application</h1> </div> <div id="logindisplay"> @Html.Partial("_LogOnPartial") </div> <nav> <ul id="menu"> <li>@Html.ActionLink("Home", "Index", "Home")</li> <li>@Html.ActionLink("About", "About", "Home")</li> </ul> </nav> </header> <section id="main"> @RenderBody() </section> <footer> <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script> </footer> </div> </body> </html>
看起来很好,但是再次打开网站的话,就会发现网页的样式有些问题
这是为什么呢?原因是MVC3默认使用了HTML 5的语法来构建页面,目前HTML 5因为还没有成为事实上的标准,所以使用了一个专门的javascript库来提供支持。这个javascript库就是:modernizr-1.7.min.js, 这是一个开源的作品,请参考这里 http://www.modernizr.com/
在页面中用到的header,section,footer这类的tag其实是html 5专用的,所以如果上面这个脚本没有预先加载,可能显示的时候,就会有些问题。
所以,我们应该将modernizr-1.7.min.js放在header里面去。这样就和谐了
值得一说的是,这个库只有10KB左右,所以将它放在header里面对性能影响很小。
我们还可以使用微软提供的CDN功能,尽可能地减少用户需要下载的javascript的体积。关于CDN,请参考这里:http://www.asp.net/ajaxlibrary/cdn.ashx
3.Remote验证
MVC3提供了一个新增的数据验证功能,叫做Remote验证,意思是可以利用客户端javascript,发起异步的请求,调用服务器代码进行验证。这个功能很不错,例如有一个页面提供用户输入用户名进行注册,我们经常需要检测用户名是否已经被别人占用。这种验证显然是无法在客户端直接提供,而是需要利用服务器代码来实现。
下面来看一个例子
首先,我们要为类型定义Annotation,需要注意的是,Remote这个Annotation是MVC专用的,所以要using System.Web.Mvc
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ComponentModel.DataAnnotations; using System.Web.Mvc; namespace MvcApplicationEditTemplate.Models { public class Employee { public int ID { get; set; } [Required] [Remote("IsNameValid","Home")] public string FirstName { get; set; } public string LastName { get; set; } public DateTime Birthday { get; set; } } }
这里指定了一个远程验证用的Controller(Home)和Action(IsNameValid)
所以,接下来我们准备这样一个Action
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MvcApplicationEditTemplate.Controllers { public class HomeController : Controller { // // GET: /Home/ public ActionResult Index() { var emp = new Models.Employee(); return View(emp); } public ActionResult IsNameValid(string FirstName) { if(FirstName == "ares") { return Json(true, JsonRequestBehavior.AllowGet); } return Json("Your name is invalid", JsonRequestBehavior.AllowGet); } } }
注意,这里只是做了一个例子,直接比较FirstName是不是等于ares。实际情况下,这里可以执行数据库查询,得到结果。
还需要注意的是,这里必须返回json的数据,并且必须设置为AllowGet。
接下来,在视图中,要添加两个脚本引用,如下所示
远程验证的效果如下