ASP.NET MVC 2 Templates, Part 4: Custom Object Templates[翻译]
原文链接
ASP.NET MVC 2 Templates, Part 1: Introduction[翻译]
ASP.NET MVC 2 Templates, Part 2: ModelMetadata[翻译]
ASP.NET MVC 2 Templates, Part 3: Default Templates[翻译]
自定义模版
在Part 3,我们看到把内置模版写成.ascx文件是什么样子的.在这篇文章,我们会讨论Object模版的一些自定义方法,使得其有不同的特性和不同的显示模版UI.
下面就是这个例子的代码:
Models/SampleModel.cs
using System.ComponentModel.DataAnnotations; using System.Web.Mvc; namespace TemplatesPart4.Models { public class SampleModel { public static SampleModel Create() { return new SampleModel { Boolean = true, EmailAddress = "admin@contoso.com", Decimal = 21.1234M, Integer = 42, Hidden = "Uneditable", HiddenAndInvisible = "Also uneditable", Html = "This is <b>HTML</b> enabled", MultilineText = "This\r\nhas\r\nmultiple\r\nlines", NullableBoolean = null, Password = "supersecret", String = "A simple string", Url = "http://www.cnblogs.com/lemontea", ChildModel = new ChildModel { FirstName = "zhang", LastName = "weiwen", } }; } public bool Boolean { get; set; } [DataType(DataType.EmailAddress)] public string EmailAddress { get; set; } public decimal Decimal { get; set; } [HiddenInput] public string Hidden { get; set; } [HiddenInput(DisplayValue = false)] public string HiddenAndInvisible { get; set; } [DataType(DataType.Html)] public string Html { get; set; } [Required] [Range(10, 100)] public int Integer { get; set; } [DataType(DataType.MultilineText)] public string MultilineText { get; set; } public bool? NullableBoolean { get; set; } [DataType(DataType.Password)] public string Password { get; set; } public string String { get; set; } [DataType(DataType.Url)] public string Url { get; set; } [DisplayFormat(NullDisplayText = "(null value)")] public ChildModel ChildModel { get; set; } } }
Models/ChildModel.cs
using System.ComponentModel.DataAnnotations; namespace TemplatesPart4.Models { [DisplayColumn("FullName")] public class ChildModel { [Required, StringLength(25)] public string FirstName { get; set; } [Required, StringLength(25)] public string LastName { get; set; } [ScaffoldColumn(false)] public string FullName { get { return FirstName + " " + LastName; } } } }
Controllers/HomeController.cs
using System.Web.Mvc; using TemplatesPart4.Models; namespace TemplatesPart4.Controllers { public class HomeController : Controller { static SampleModel model = SampleModel.Create(); public ActionResult Index() { return View(model); } public ViewResult Edit() { return View(model); } [HttpPost] [ValidateInput(false)] public ActionResult Edit(SampleModel editedModel) { if (ModelState.IsValid) { model = editedModel; return RedirectToAction("Details"); } return View(editedModel); } } }
Views/Home/Index.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<TemplatesPart4.Models.SampleModel>" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> Index </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>Index</h2> <fieldset style="padding: 1em; margin: 0; border: solid 1px #999;"> <%= Html.DisplayForModel() %> </fieldset> <p> <%= Html.ActionLink("Edit", "Edit") %></p> </asp:Content>
Views/Home/Edit.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<TemplatesPart4.Models.SampleModel>" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> Edit </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>Edit</h2> <% using (Html.BeginForm()) { %> <fieldset style="padding: 1em; margin: 0; border: solid 1px #999;"> <%= Html.ValidationSummary("Broken stuff:") %> <%= Html.EditorForModel() %> <input type="submit" value=" Submit " /> </fieldset> <% } %> <p> <%= Html.ActionLink("Details", "Index") %></p> </asp:Content>
默认显示
运行,界面如下图,这时没有任何自定义项:
译注:原文中的截图会生成ChildModel,(null value),不知是不是版本原因,ASP.NET MVC 2 RTM版本的Object模版应该是不会渲染这个属性的(!metadata.IsComplexType),前面的文章和下面同理.RTM官方版本的Object模版如下:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> <script runat="server"> bool ShouldShow(ModelMetadata metadata) { return metadata.ShowForDisplay && metadata.ModelType != typeof(System.Data.EntityState) && !metadata.IsComplexType && !ViewData.TemplateInfo.Visited(metadata); } </script> <% if (Model == null) { %> <%= ViewData.ModelMetadata.NullDisplayText %> <% } else if (ViewData.TemplateInfo.TemplateDepth > 1) { %> <%= ViewData.ModelMetadata.SimpleDisplayText %> <% } else { %> <% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => ShouldShow(pm))) { %> <% if (prop.HideSurroundingHtml) { %> <%= Html.Display(prop.PropertyName) %> <% } else { %> <% if (!String.IsNullOrEmpty(prop.GetDisplayName())) { %> <div class="display-label"><%= prop.GetDisplayName() %></div> <% } %> <div class="display-field"><%= Html.Display(prop.PropertyName) %></div> <% } %> <% } %> <% } %>
编辑页面:
表格布局
表格布局是最普通的名值对布局.注意编辑版本的模版在required字段的label前添加星号(*).
Views/Shared/DisplayTemplates/Object.ascx
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> <% if (Model == null) { %> <%= ViewData.ModelMetadata.NullDisplayText %> <% } else if (ViewData.TemplateInfo.TemplateDepth > 1) { %> <%= ViewData.ModelMetadata.SimpleDisplayText %> <% } else { %> <table cellpadding="0" cellspacing="0" border="0"> <% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm))) { %> <% if (prop.HideSurroundingHtml) { %> <%= Html.Display(prop.PropertyName) %> <% } else { %> <tr> <td> <div class="display-label" style="text-align: right;"> <%= prop.GetDisplayName() %> </div> </td> <td> <div class="display-field"> <%= Html.Display(prop.PropertyName) %> </div> </td> </tr> <% } %> <% } %> </table> <% } %>
截图:
Views/Shared/EditorTemplates/Object.ascx
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> <% if (ViewData.TemplateInfo.TemplateDepth > 1) { %> <%= ViewData.ModelMetadata.SimpleDisplayText %> <% } else { %> <table cellpadding="0" cellspacing="0" border="0"> <% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm))) { %> <% if (prop.HideSurroundingHtml) { %> <%= Html.Editor(prop.PropertyName) %> <% } else { %> <tr> <td> <div class="editor-label" style="text-align: right;"> <%= prop.IsRequired ? "*" : "" %> <%= Html.Label(prop.PropertyName) %> </div> </td> <td> <div class="editor-field"> <%= Html.Editor(prop.PropertyName) %> <%= Html.ValidationMessage(prop.PropertyName, "*") %> </div> </td> </tr> <% } %> <% } %> </table> <% } %>
截图:
Shallow Dive vs. Deep Dive
在上面的截图中,ChildModel显示为"(null value)",ChildModel是一个复合model,所以所以它遵循shallow dive的原则,在我们有一个ChildModel对象前,它显示了我们在model设置的NullDisplayText特性值.
注意即使在编辑版本中,我们也不能编辑ChildModel,因为shallow dive原则阻止我们递归呈现编辑UI.
如果我们改变编辑模版,把第一个 if 语句去掉(阻止deep dive的那个),这时就会为ChildModel显示可编辑字段:
在SampleModel的Create()方法中加入:
ChildModel = new ChildModel { FirstName = "zhang", LastName = "weiwen", }
截图:
因为我们还没有改变我们的Object显示模版,我们仍然使用shallow dive原则.另外,它显示全名是因为定义ChildModel时使用了DataAnnotations特性[DisplayColumn]来说明"当要显示这个复合对象时显示这一列",我门指定[DisplayColumn]给命名为FullName的综合属性,这个属性通常不会显示,因为我们附注了[ScaffoldColumn(false)].
如果我们改变Object的显示模版为deep dive,那么显示如下:
结语
这篇文章,我给你介绍了几个自定义Object模版的方法,用于显示不同的界面.包括表格布局代替行布局,required字段前添加星号,而且实现复合对象嵌套复合对象的Deep Dive场景.下一篇文章,我会怎么改变所有模版实现围绕母版页布局,灵感来自Eric Hexter的文章Opinionated Input Builders.