• 模型模板


    前面的Html辅助器,如Html.CheckBoxFor和Html.TextBoxFor等,是明确指定了要使用的html元素。mvc框架支持另一种方法,叫做模板视图辅助器(Templated View Helper),在这样的辅助器中,指定想要显示或编辑的模型对象或属性,而让mvc框架去判断应该用什么样的html元素。

    一、使用模板视图辅助器

    1、为指定的模型属性生成html

    使用模板视图辅助器,意味着我们不必考虑要指定用什么样的html元素来表现一个模型属性,而是只要说出想显示哪个属性,让mvc框架自己去判断采用什么html元素来表现它。

    新建mvc3项目MvcApp,在解决方案管理器中鼠标右击文件夹“Models”,选择Add->Class,添加一个类库文件,取名为TestModelClass.cs,在里面建立如下类:

    namespace MvcApp.Models
    {
        public class Person
        {
            public int PersonId { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public DateTime BirthDate { get; set; }
            public Address HomeAddress { get; set; }
            public bool IsApproved { get; set; }
            public Role Role { get; set; }
        }
    
        public class Address
        {
            public string Line1 { get; set; }
            public string Line2 { get; set; }
            public string City { get; set; }
            public string PostalCode { get; set; }
            public string Country { get; set; }
        }
    
        public enum Role
        {
            Admin,
            User,
            Guest
        }
    }

    再新建HomeController控制器,完成Index动作方法:

    public ActionResult Index()
    {
        Person p1 = new Person { FirstName = "Joe", LastName = "Smith", IsApproved = true };
        return View(p1);
     }

    在Index动作方法上添加默认试图Index.cshtml:

    @model MvcApp.Models.Person
    
    @{
        ViewBag.Title = "Index";
    }
    
    <h2>Person</h2>
    <div class="field">
    <label>Name:</label>
    @Html.EditorFor(x=>x.FirstName)
    @Html.EditorFor(x=>x.LastName)
    </div>
    <div class="field">
    <label>Approved:</label>
    @Html.EditorFor(x=>x.IsApproved)
    </div>

    这里使用了Html.EditorFor辅助器,它生成一个对属性进行编辑的html元素。显示结果为:

    image

    生成的html代码为:

    <!DOCTYPE html>
    <html>
    <head>
        <title>Index</title>
        <link href="/Content/Site.css" rel="stylesheet" type="text/css" />
        <script src="/Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
    </head>
    <body>
    <h2>Person</h2>
    <div class="field">
    <label>Name:</label>
    <input class="text-box single-line" id="FirstName" name="FirstName" type="text" value="Joe" />
    <input class="text-box single-line" id="LastName" name="LastName" type="text" value="Smith" />
    </div>
    <div class="field">
    <label>Approved:</label>
    <input checked="checked" class="check-box" id="IsApproved" name="IsApproved" type="checkbox" value="true" /><input name="IsApproved" type="hidden" value="false" />
    </div>
    </body>
    </html>

    可以看到,通过“x=>x.属性名”指定的属性名被用于id和name标签属性,并为FirstName和LastName属性创建了文本框(text),并为IsApproved属性创建了一个复选框(checkbox)。

    也可以用另一种形式来生成html元素:以只读形式显示模型对象的值,不允许编辑。例如,将Index.cshtml修改为:

    @model MvcApp.Models.Person
    
    @{
        ViewBag.Title = "Index2";
    }
    
    <h2>Person</h2>
    <div class="field">
    <label>Name:</label>
    @Html.DisplayFor(x=>x.FirstName)
    @Html.DisplayFor(x => x.LastName)
    </div>
    <div class="field">
    <label>Approved:</label>
    @Html.DisplayFor(x => x.IsApproved)
    </div>

    执行后,显示结果为:

    image

    生成的html代码为:

    <!DOCTYPE html>
    <html>
    <head>
        <title>Index2</title>
        <link href="/Content/Site.css" rel="stylesheet" type="text/css" />
        <script src="/Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
    </head>
    <body>
    <h2>Person</h2>
    <div class="field">
    <label>Name:</label>
    Joe
    Smith
    </div>
    <div class="field">
    <label>Approved:</label>
    <input checked="checked" class="check-box" disabled="disabled" type="checkbox" />
    </div>
    </body>
    </html>

    现在的html可以让用户看到信息,但是不能编辑。由于大多数mvc程序都是要么显示数据,要么编辑数据,所以模板辅助器是很方便的。

    Html.Display("FirstName")  以只读方式显示指定属性的值。

    Html.DisplayFor(x=>x.FirstName) 上一辅助器的强类型版本。

    Html.Editor("FirstName")  编辑指定属性的值,根据该属性的类型和元数据选择合适的编辑器,如文本框、复选框等。

    Html.EditorFor(x=>x.FirstName) 上一辅助器的强类型版本。

    Html.Label("FirstName")  用<label>标签显示指定属性的属性名,而不是属性值。

    Html.LabelFor(x=>x.FirstName)  上一辅助器的强类型版本。

    Html.DisplayText("FirstName")   绕过所有模板,渲染指定模型属性的简单字符串表示。

    Html.DisplayTextFor(x=>x.FirstName)  上一辅助器的强类型版本。

    2、为一个模型对象的所有属性生成html

    除了可以单独为指定的模型属性生成html以外,还可以为一个模型对象的所有属性生成html的辅助器。这一个过程称为支架(scaffolding)。

    Html.DisplayForModel()

    Html.EditorForModel()

    Html.LabelForModel()

    都是针对整个模型对象里的所有属性来进行渲染。

    例如,在HomeController中新建一个动作方法:

            public ViewResult Scaffold()
            {
                Person p1 = new Person { PersonId = 1,
                    FirstName = "Joe", 
                    LastName = "Smith",
                    BirthDate = DateTime.Parse("2014/6/12"),
                    IsApproved = true,
                    Role = Role.User
                };
                return View(p1);
            }

    为它新建默认视图Scaffold.cshtml:

    @model MvcApp.Models.Person
    
    @{
        ViewBag.Title = "Scaffold";
    }
    
    <h2>Person</h2>
    @Html.EditorForModel()

    执行后的效果为:

    image

    在使用辅助器时,Html.EditorForModel()为模型对象中的属性生成html标签和编辑元素,不需要把模型对象作为参数传递给这个辅助器,因为是强类型。自动将每个元素都进行处理。

    3、设置生成html的样式

    查看该页面的html源代码:

    <h2>Person</h2>
    <div class="editor-label"><label for="PersonId">PersonId</label></div>
    <div class="editor-field"><input class="text-box single-line" id="PersonId" name="PersonId" type="text" value="1" /> </div>
    <div class="editor-label"><label for="FirstName">FirstName</label></div>
    <div class="editor-field"><input class="text-box single-line" id="FirstName" name="FirstName" type="text" value="Joe" /> </div>
    <div class="editor-label"><label for="LastName">LastName</label></div>
    <div class="editor-field"><input class="text-box single-line" id="LastName" name="LastName" type="text" value="Smith" /> </div>
    <div class="editor-label"><label for="BirthDate">BirthDate</label></div>
    <div class="editor-field"><input class="text-box single-line" id="BirthDate" name="BirthDate" type="text" value="2014/6/12 0:00:00" /> </div>
    <div class="editor-label"><label for="IsApproved">IsApproved</label></div>
    <div class="editor-field"><input checked="checked" class="check-box" id="IsApproved" name="IsApproved" type="checkbox" value="true" /><input name="IsApproved" type="hidden" value="false" /> </div>
    <div class="editor-label"><label for="Role">Role</label></div>
    <div class="editor-field"><input class="text-box single-line" id="Role" name="Role" type="text" value="User" /> </div>

    可以看到,每个元素显示的名字,和对应的编辑狂都是由<div>标签在控制,显示元素名字的<div>标签,用的class是"editor-label",构成编辑框的<div>用的class是"editor-field"。

    打开解决方案管理器中的Content/Site.css文件,找到类.editor-label 和.editor-field,可以看到默认的样式为:

    .editor-label 
    {
        margin: 1em 0 0 0;
    }
    
    .editor-field 
    {
        margin: 0.5em 0 0 0;
    }

    现在想让同一个元素的名字和对应的编辑框在一行上,就可以修改这两个类的样式如下:

    .editor-label 
    {
        margin: 1em 0 0 0;
        clear:left;
        float:left;
        min-width: 100px;
        vertical-align:middle;
    }
    
    .editor-field 
    {
        margin: 0.5em 0 0 0;
        width:150px;
        float:left;
    }

    下面再执行,可以看到显示效果如下:

    image

    4、使用模型元数据

    使用模板视图辅助器,尤其是使用它为一个模型对象的所有属性生成html时,有一个比较大的问题就是如何去控制这些属性,哪些需要显示,哪些不想显示出来,用什么类型显示等等。比如上一个例子,PersonId希望不要显示出来,因为正常情况下的程序几乎都不可能让用户直接去编辑Id值,第二个问题就是BirthDate显示成的是日期时间型,但是我们希望得到日期型。

    这就需要采用模型元数据(Metadata)为这些辅助器提供指示,元数据是用注解属性来表示的,通过注解属性及参数值,给视图辅助器提供一系列指令。

    (1)用元数据控制编辑及可见性

    在Person类中,PersonId是不想让用户看到或编辑的属性。可以用HiddenInput注解属性,它会使辅助器渲染一个隐藏的input字段:

        public class Person
        {
            [HiddenInput]
            public int PersonId { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public DateTime BirthDate { get; set; }
            public Address HomeAddress { get; set; }
            public bool IsApproved { get; set; }
            public Role Role { get; set; }
        }

    使用[HiddenInput]这个注解属性时,Html.EditorFor和Html.EditorForModel辅助器会对这个被修饰的属性渲染一个只读字段,例如:

    image

    [HiddenInput]显示了PersonId属性的值,但用户不能编辑它。为该属性生成的html如下:

    <div class="editor-label"><label for="PersonId">PersonId</label></div>
    <div class="editor-field">1<input id="PersonId" name="PersonId" type="hidden" value="1" /> </div>

    id和name的值是PersonId,type的值是hidden,value的值是1,这是一个隐藏的input元素。当把这个编辑视图用于表单时,这个隐藏的input字段也是有用的。(模型绑定和模型验证还会用到)

    如果想完全隐藏一个属性,可以把HiddenInput注解属性中的DisplayValue值设为false,如下:

        public class Person
        {
            [HiddenInput(DisplayValue=false)]
            public int PersonId { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public DateTime BirthDate { get; set; }
            public Address HomeAddress { get; set; }
            public bool IsApproved { get; set; }
            public Role Role { get; set; }
        }

    仍然是生成隐藏的input字段,以便PersonId属性的值可以被包含到任何要递交的表单中去。但使用DisplayValue=false,可以将属性整个隐藏起来,用户也看不到,而不只是不能编辑的问题。

    image

    查看html代码,可以看到如下的代码:

    <h2>Person</h2>
    <input id="PersonId" name="PersonId" type="hidden" value="1" />
    <div class="editor-label"><label for="FirstName">FirstName</label></div>
    <div class="editor-field"><input class="text-box single-line" id="FirstName" name="FirstName" type="text" value="Joe" /> </div>
    <div class="editor-label"><label for="LastName">LastName</label></div>
    <div class="editor-field"><input class="text-box single-line" id="LastName" name="LastName" type="text" value="Smith" /> </div>
    <div class="editor-label"><label for="BirthDate">BirthDate</label></div>
    <div class="editor-field"><input class="text-box single-line" id="BirthDate" name="BirthDate" type="text" value="2014/6/12 0:00:00" /> </div>
    <div class="editor-label"><label for="IsApproved">IsApproved</label></div>
    <div class="editor-field"><input checked="checked" class="check-box" id="IsApproved" name="IsApproved" type="checkbox" value="true" /><input name="IsApproved" type="hidden" value="false" /> </div>
    <div class="editor-label"><label for="Role">Role</label></div>
    <div class="editor-field"><input class="text-box single-line" id="Role" name="Role" type="text" value="User" /> </div>

    另外,如果想把一个属性从生成的html中完全排除掉,而不仅仅是隐藏,那可以使用ScaffoldColumn注解属性。例如:

    public class Person
        {
            [ScaffoldColumn(false)]
            public int PersonId { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public DateTime BirthDate { get; set; }
            public Address HomeAddress { get; set; }
            public bool IsApproved { get; set; }
            public Role Role { get; set; }
        }

    当辅助器看到ScaffoldColumn注解属性时,会完全跳过该属性,不会创建隐藏input元素,该属性的细节就不会包含在生成的html中。生成html的外观与使用HiddenInput注解属性的情况相同,但是表单递交时就没有该属性的值被返回,这对模型绑定是有影响的。另外就是ScaffoldColumn注解属性对单属性辅助器不起作用,如果在视图中调用@Html.EditorFor(m=>m.PersonId),那么,即使有ScaffoldColumn注解属性存在,也会生成PersonId属性的编辑视图。

    (2)使用用于标签的元数据

    默认情况下,Label、LbaelFor、LabelForModel,以及EditorForModel辅助器以属性名作为它们生成的标签元素的内容(也就是生成html的label元素)。

    例如,像下面这样渲染一个标签:

    @Html.LabelFor(m=>m.BirthDate)

    生成的html元素如下:

    <label for="BirthDate">BirthDate</label>

    当然,给属性定义的名字通常不是希望显示给用户的提示名字,为此可以使用DisplayName注解属性,例如:

    [Display(Name="出生日期")]
     public DateTime BirthDate { get; set; }

    当辅助器对BirthDate渲染html标签时,将Display注解属性,并用Name参数的值作为其内部文本,生成的html标签如下:

    <div class="editor-label"><label for="BirthDate">出生日期</label></div>
    <div class="editor-field"><input class="text-box single-line" id="BirthDate" name="BirthDate" type="text" value="2014/6/12 0:00:00" /> </div>

    另外,还可以对一个类加一个名字,这也是为了避免在前端hmtl中直接写关于一个类的名字,设想一下,如果这个类的名字信息修改了,那就要对每个写了这个类名字的前端页面逐一修改。我们可以采用DisplayName,来对一个类取显示名字,然后用@Html.LabelForModel()来显示这个名字。例如:

        [DisplayName("人员信息")]
        public class Person
        {
            [HiddenInput(DisplayValue=false)]
            public int PersonId { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            
            [Display(Name="出生日期")]
            public DateTime BirthDate { get; set; }
            public Address HomeAddress { get; set; }
            public bool IsApproved { get; set; }
            public Role Role { get; set; }
        }

    将Scaffold.cshtml修改为:

    @model MvcApp.Models.Person
    
    @{
        ViewBag.Title = "Scaffold";
    }
    
    <h2>Person</h2>
    <h4>@Html.LabelForModel()</h4>
    @Html.EditorForModel()

    注意,这里就用了@Html.LabelForModel()来显示了用DisplayName注解属性给类取的名字,显示效果如下:

    image

    @Html.LabelForModel()生成的html代码为:

    <h4><label for="">人员信息</label></h4>

    (3)使用用于数据值的元数据

    我们也可以用元数据为如何显示一个模型属性提供一些指示,可以用这个办法解决出生日期属性包含时间的问题。需要使用的注解属性是DataType:

        [DisplayName("人员信息")]
        public class Person
        {
            [HiddenInput(DisplayValue=false)]
            public int PersonId { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            
            [DataType(DataType.Date)]
            [Display(Name="出生日期")]
            public DateTime BirthDate { get; set; }
            public Address HomeAddress { get; set; }
            public bool IsApproved { get; set; }
            public Role Role { get; set; }
        }

    DataType注解属性以DataType枚举中的一个值为参数,显示结果为:

    image

    DataType枚举中的一些常用值:

    DateTime ——日期时间

    Date ——日期

    Time ——时间

    Text ——显示单行文本

    MultilineText ——将值渲染在一个文本区(textarea)元素中

    Password ——以密码形式显示数据

    Url ——将数据显示为一个url(用html的a标签)

    EmailAddress ——将数据显示为一个e-mail地址(使用带有mailto的href的a标签)

    注意这些值的效果依赖于它们所关联的属性类型,以及所使用的辅助器。例如,MultilineText值会让Editor辅助器创建一个html的文本区元素,但display辅助器对这个值是忽略的。同样,Url值只对display辅助器起作用,它渲染一个Html的a标签以创建一个链接。

    (4)使用元数据选择显示模板

    用显示模板来生成Html,使用UIHint注解属性来指定想用的模板,以渲染一个属性的html。例如:

        [DisplayName("人员信息")]
        public class Person
        {
            [HiddenInput(DisplayValue=false)]
            public int PersonId { get; set; }
            
            [UIHint("MultilineText")]
            public string FirstName { get; set; }
            public string LastName { get; set; }
            
            [DataType(DataType.Date)]
            [Display(Name="出生日期")]
            public DateTime BirthDate { get; set; }
            public Address HomeAddress { get; set; }
            public bool IsApproved { get; set; }
            public Role Role { get; set; }
        }

    这里对FirstName指定了显示模板[UIHint("MultilineText")],当与编辑辅助器(如EditorFor或EditorForModel)一起使用时,它会为FirstName属性渲染一个html多行文本框,如下显示:

    image

    常用的UIHint显示模板如下:(内建的mvc框架的视图模板)

    Boolean——(Editor辅助器)渲染一个bool值的复选框。如果是nullable的bool?值,则渲染一个带有True、False、Not Set选项的select元素。(Display辅助器)与编辑辅助器相同,但附加了disable标签属性,使生成的html元素为只读。

    Collection——(Editor辅助器)为IEnumerable序列中的每一个元素渲染一个相应的模板,该序列中的各个项不必是同种类型。(Display辅助器)与编辑辅助器相同。

    Decimal——(Editor辅助器)渲染一个单行文本框的input元素,并对数据值格式化,显示两位小数。(Display辅助器)渲染格式化成两位小数的数据值。

    EmailAddress——(Editor辅助器)将值渲染在一个单行文本框的input元素中。(Display辅助器)用html的a标签生成一个链接,且href标记属性格式化成一个mailto的url。

    HiddenInput——(Editor辅助器)创建隐藏的input元素。(Display辅助器)与编辑辅助器相同。

    Html——(Editor辅助器)将值渲染在一个单行文本框的input元素中。(Display辅助器)用html的a标签生成一个链接。

    MultilineText——(Editor辅助器)渲染一个含有改数据值的html textarea元素。(Display辅助器)生成数据值。

    Object

    Password——(Editor辅助器)将值渲染在一个单行文本框的input元素中,不以明文显示,可以编辑。(Display辅助器)渲染数据值,字符是非隐蔽的。

    String——(Editor辅助器)将值渲染在单行文本框的input元素中。(Display辅助器)渲染数据值。

    Text——(Editor辅助器)等同于String模板。(Display辅助器)等同于String模板。

    Url——(Editor辅助器)将值渲染在单行文本框的input元素中。

    (5)把元数据运用于伙伴类(Buddy Class)

    有些情况下不能直接在实体模型类型上使用元数据,如前面的[HiddenInput]、[HiddenInput(DisplayValue=false)]、[Display(Name="出生日期")]、 [DisplayName("人员信息")]、[DataType(DataType.Date)]、[UIHint("MultilineText")]等元数据。因为有些模型类是自动生成的,就像使用EF实体框架之类的ORM工具那样。对这种自动生成的类所做的任何修改,比如运用一些注解属性等,都会在工具对类的再次更新时丢失。

    如果想用EF实体框架来自动生成模型类,又想在这些类上使用元数据,那就应该确保把这些模型类定义成分部类(partial类),并创建第二个分部类以包含这些元数据。许多自动生成类的工具默认情况下都是创建分部类,包括EF实体框架也是这样。例如,前面的Person例子,定义的时候像下面这样处理:

    a.分部模型类

        public partial class Person
        {
            public int PersonId { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public DateTime BirthDate { get; set; }
            public Address HomeAddress { get; set; }
            public bool IsApproved { get; set; }
            public Role Role { get; set; }
        }

    由EF工具自动生成,或者自己建立,里面没有添加任何元数据。如果是EF自动建立,当EF对它进行更新时,也就是再次生成这个类时,在其上所做的修改都会丢失(比如加的注解属性等)。因此,我们创建第二个分部类,这让我们能够做一些保持附加信息的事情。当编译器建立应用程序时,这两个分部类将被合并起来。

    b.定义元数据伙伴(第二个分部类,与Person要同名)

        [MetadataType(typeof(PersonMetadataSource))]
        public partial class Person
        {
        }

    分部类(partial clss)必须同名,而且要在同一个命名空间中用partial关键字声明。用于元数据目的的关键注解属性是[MetadataType(typeof(PersonMetadataSource))],参数中的PersonMetadataSource就是伙伴类(buddy class),通过把伙伴类的类型作为参数,让我们把伙伴类与Person类联系在一起。也就意味着Person类的元数据可以在名为PersonMetadataSource的伙伴类中找到,伙伴类定义如下

    c.伙伴类

        [DisplayName("人员信息")]
        class PersonMetadataSource
        {
            [HiddenInput(DisplayValue = false)]
            public int PersonId { get; set; }
    
            [UIHint("MultilineText")]
            public string FirstName { get; set; }
    
            [DataType(DataType.Date)]
            [Display(Name = "出生日期")]
            public DateTime BirthDate { get; set; }
        }

    不用给全,只需要把带了元数据的属性给出就可以了。

    执行后,显示效果,跟上一个例子一样。

    5、使用复合类型参数

    前面的例子中,在使用支架辅助器EditorForModel和DisplayForModel时,并未生成所有属性,例子中忽略了一个HomeAddress属性。发生这种情况是因为Object模板只对简单类型进行操作,这包括固有的c#类型,如int、bool、double等,还包括许多普通的框架类型,如Guid、DateTime等。

    这就使得支架是非递归的,给定一个要处理的对象,支架模板视图辅助器将只生成简单属性类型的html,而会忽略本身是复合对象的任何属性。因此,如果要生成一个复合属性的html,我们必须明确地处理复合类属性。

    可以用一个EditorForModel来处理我们视图模型对象的简单属性,然后用一个明确的Html.EditorFor调用来为HomeAddress属性(复合属性)生成html。而且,我们可以把各种元数据运用于Address类,就像在Person类上所做的那样。

    修改Scaffold.cshtml如下:

    @model MvcApp.Models.Person
    
    @{
        ViewBag.Title = "Scaffold";
    }
    
    <h2>Person</h2>
    <h4>@Html.LabelForModel()</h4>
    
    <div class="column">
        @Html.EditorForModel()
    </div>
    
    <div class="column">
        @Html.EditorFor(m=>m.HomeAddress)
    </div>

    /Models/TestModelClass.cs内容如下:

    namespace MvcApp.Models
    {
        public partial class Person
        {
            public int PersonId { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public DateTime BirthDate { get; set; }
            public Address HomeAddress { get; set; }
            public bool IsApproved { get; set; }
            public Role Role { get; set; }
        }
    
        [MetadataType(typeof(PersonMetadataSource))]
        public partial class Person
        {
        }
    
        [DisplayName("人员信息")]
        class PersonMetadataSource
        {
            [HiddenInput(DisplayValue = false)]
            public int PersonId { get; set; }
    
            //[UIHint("MultilineText")]
            public string FirstName { get; set; }
    
            [DataType(DataType.Date)]
            [Display(Name = "出生日期")]
            public DateTime BirthDate { get; set; }
        }
        
        public class Address
        {
            public string Line1 { get; set; }
            public string Line2 { get; set; }
            public string City { get; set; }
            public string PostalCode { get; set; }
            public string Country { get; set; }
        }
    
        public enum Role
        {
            Admin,
            User,
            Guest
        }
    }

    HomeController.cs中的动作方法Scaffold代码如下:

            public ViewResult Scaffold()
            {
                Person p1 = new Person { PersonId = 1,
                    FirstName = "Joe", 
                    LastName = "Smith",
                    BirthDate = DateTime.Parse("2014/6/12"),
                    IsApproved = true,
                    Role = Role.User
                };
                return View(p1);
            }

    在/Content/Site.css中增加样式如下:

    /*Additional Styles 新增样式
    ********************************************************/
    .label 
    {
        vertical-align:middle;
    }
    .check-box 
    {
        vertical-align:bottom;
        margin: .5em 0 0 0;
    }
    
    div.column 
    {
        float:left;
        width:auto;
    }
    .single-line 
    {
        width: 100px;
    }

    执行后的显示效果为:

    image

    二、定制模板视图辅助器系统

    前面演示了如何用元数据来形成模板辅助器渲染数据的方式。但对于mvc框架,还有一些高级选项,能够让我们完全定制模板辅助器。

    1、创建自定义编辑模板(Editor)

    以Person类中的Role属性为例,这个属性的取值,是自定义的枚举类型Role中的某一个值,先看一下使用内建的模板辅助器对这个属性进行渲染的例子。假设还是在Scaffold.cshtml中添加了如下代码:

    <p>
        @Html.LabelFor(m => m.Role):
        @Html.EditorFor(m => m.Role)
    </p>
    <p>
        @Html.LabelFor(m=>m.Role):
        @Html.DisplayFor(m=>m.Role)
    </p>

    这一部分生成的显示效果为:

    image

    Label和Display的模板都可以符合要求,但是Editor模板生成的效果不太符合我们的要求。枚举类型Role中只定义了三个值(Admin、User、Guest),现在生成的编辑框允许用户给这个属性输入任意值。对于这样的情况我们就可以采用自定义编辑模板。步骤如下:

    在/Views/Shared文件夹中新建一个名为EditroTemplates的文件夹,然后右击该文件夹,选择Add->View,为视图取名为Role,并选中Create as a partial view。再选中Create a strongly-typed view创建强类型视图,把Model class设置为Role。创建了这个分部视图后,就可以添加标准的Razor语法,以生成所需要的编辑视图。创建其html有很多方法,最简单的是混合使用静态html元素和Razor标签

    @model MvcApp.Models.Role
    @using MvcApp.Models
    
    <select id="Role" name="Role">
        @foreach (Role value in Enum.GetValues(typeof(Role)))
        {
            <option value="@value" @(Model == value ? "selected="selected"" : "")>
                @value
            </option>
        }
    </select>

    这个分部视图创建了一个html的select元素,并用Role枚举中的每个值填充它的option元素。在foreach循环中检查所传递的值是否与当前元素匹配,以便能正确设置selected属性。

    当要生成这个属性的一个Editor视图时,这个分部视图就被用来生成html。例如Scaffold.cshtml

    @model MvcApp.Models.Person
    
    @{
        ViewBag.Title = "Scaffold";
    }
    
    <p>
        @Html.LabelFor(m => m.Role):
        @Html.EditorFor(m => m.Role)
    </p>
    <p>
        @Html.LabelFor(m=>m.Role):
        @Html.DisplayFor(m=>m.Role)
    </p>
    
    <h2>Person</h2>
    <h4>@Html.LabelForModel()</h4>
    
    <div class="column">
        @Html.EditorForModel()
    </div>
    
    <div class="column">
        @Html.EditorFor(m=>m.HomeAddress)
    </div>

    生成的效果为:

    image

    可以看到无论是使用的单个属性辅助器还是支架辅助器,它们都会查找并使用这个Role.cshtml模板。注意,模板那的名称(指Role.cshtml中的Role)对应于属性的类型而不是属性名,因此凡是使用了Role类型的属性都会运用这个自定义模板。

    在这个例子中,包括文件夹的名字,以及文件名,都是要遵循的约定,不能随便乱取。Views/Shared/EditorTemplates文件夹,放在里面的Role.cshtml都是遵循约定。

    Role.cshtml就是自定义模板(Template)。Role.cshtml模板之所以能够工作,是因为mvc框架在使用内建模板之前,会对一个给定的c#类型查找自定义模板。查找的顺序如下:

    (1)传递给辅助器的模板——例如,Html.EditorFor(m=>m.SomeProperty, “MyTemplate”)将导致使用MyTemplate模板。

    (2)由元数据注解属性指定的任意模板,如UIHint。

    (3)与元数据指定的任意数据类型相关联的模板,如DataType注解属性。

    (4)与待处理数据类型的.NET类名对应的任意模板。(与类型同名的模板)

    (5)如果被处理的数据类型是一个简单类型,那么便采用内建的String模板。

    (6)对应于数据类型基类的任意模板。

    (7)如果数据类型实现IEnumerable,那么将使用内建的Collection模板。

    (8)如果上述全部失败,将使用Object模板——服从于支架非递归规则。

    在模板搜索的每一个阶段,mvc框架都会查找一个名为EditorTemplates/<name>DisplayTemplates/<name>的模板。对于本例子中的Role.cshtml模板,满足上面顺序中的第4步,因为我们创建了一个名为Role.cshtml的模板,并把它放在了/Views/Shared/EditorTemplates文件夹中。

    自定义模板是用搜索常规视图的同样方式来查找的,这意味着我们可以创建一个控制器专用的自定义模板,并把它放在/Views/<controller>/EditorTemplates文件夹中,以覆盖在/Views/Shared文件夹中找到的模板。

    2、创建自定义显示模板(display)

    过程类似于创建自定义编辑模板,只不过自定义显示模板被放在DisplayTemplates文件夹中。下面的例子演示了一个Role枚举的自定义显示模板,把这个模板文件创建为/Views/Shared/DisplayTemplates/Role.cshtml

    @model MvcApp.Models.Role
    @using MvcApp.Models
    
    @foreach (Role value in Enum.GetValues(typeof(Role)))
    {
        if (value == Model)
        {
            <b>@value</b>
        }
        else
        {
            @value
        }
    }

    这个模板列举枚举Role中的所有值,如果与传递给当前视图的数据值相等,那就用加粗显示。

    例如,如果在Scaffold.cshtml中有使用Display辅助器:

    <p>
        @Html.LabelFor(m => m.Role):
        @Html.EditorFor(m => m.Role)
    </p>
    <p>
        @Html.LabelFor(m=>m.Role):
        @Html.DisplayFor(m=>m.Role)
    </p>

    那么执行后的显示结果为:

    image

    可以看到在Display辅助器对应的地方,当前数据被加粗了。

    3、创建泛型模板

    不光是可以创建类型专用的模板,例如,还可以创建一个工作与所有枚举的模板,并且随后指定用UIHint注解属性来选择这个模板。根据前面的模板搜索顺序,可以看到用UIHint注解属性指定的模板优先于类型专用模板。

    下面在/Views/Shared/EditorTemplates文件夹中新建Enum.cshtml模板,这个模板是对c#枚举的一个更通用的处理。

    @model Enum
    
    @Html.DropDownListFor(m=>m, Enum.GetValues(Model.GetType())
        .Cast<Enum>()
        .Select(m=>{
            string enumVal=Enum.GetName(Model.GetType(),m);
            return new SelectListItem(){
                Selected = (Model.ToString()==enumVal),
                Text=enumVal,
                Value=enumVal
            };
        }
        ))

    该模板的视图模型是Enum,它可以枚举任何类型。可以再次混用静态html和Razor语法来创建这个模板,但在这个例子中使用了强类型的DropDownListFor辅助器,并使用了一些LINQ来把枚举值转换成SelectListItems。

    使用的时候,就用:

    [UIHint("Enum")]
    public Role Role { get; set; }

    4、替换内建模板

    如果创建一个与内建模板同名的自定义模板,mvc框架将优先于内建模板来使用这个自定义版本。

    比如,对于bool类型的IsApproved属性

        public partial class Person
        {
            public int PersonId { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public DateTime BirthDate { get; set; }
            public Address HomeAddress { get; set; }
            public bool IsApproved { get; set; }
            public Role Role { get; set; }
        }
    
        [MetadataType(typeof(PersonMetadataSource))]
        public partial class Person
        {
        }
    
        [DisplayName("人员信息")]
        class PersonMetadataSource
        {
            [HiddenInput(DisplayValue = false)]
            public int PersonId { get; set; }
    
            //[UIHint("MultilineText")]
            public string FirstName { get; set; }
    
            [DataType(DataType.Date)]
            [Display(Name = "出生日期")]
            public DateTime BirthDate { get; set; }
    
            //[UIHint("Enum")]
            public Role Role { get; set; }
        }

    HomeController里的Scaffold动作方法为:

            public ViewResult Scaffold()
            {
                Person p1 = new Person { PersonId = 1,
                    FirstName = "Joe", 
                    LastName = "Smith",
                    BirthDate = DateTime.Parse("2014/6/12"),
                    IsApproved = true,
                    Role = Role.User
                };
                return View(p1);
            }

    如果使用了Display辅助器,来显示bool类型的IsApproved属性

    <p>
        @Html.LabelFor(m=>m.IsApproved):
        @Html.DisplayFor(m=>m.IsApproved)
    </p>

    显示效果为:

    image

    因为用的是Display辅助器,所以不能编辑,以灰色显示。

    现在自定义内建模板Boolean,要在Display辅助器中起效果,把它放在/Views/Shared/DisplayTemplates/Boolean.cshtml中。

    @model bool?
    
    @if (ViewData.ModelMetadata.IsNullableValueType && Model == null)
    {
        @:True False <b>Not Set</b>
    }
    else if (Model.Value)
    {
        @:<b>True</b> False Not Set
    }
    else
    {
        @:True <b>False</b> Not Set
    }

    显示结果为:

    image

    5、使用ViewData.TemplateInfo属性

    mvc提供ViewData.TemplateInfo属性,使得编写自定义视图模板更容易。该属性返回一个TemplateInfo对象。

    这个类的一些常用成员:

    FormattedModelValue——返回当前模型的字符串表示,所返回的字符串考虑了格式化元数据(如DataType注解属性等)。

    GetFullHtmlFieldId()——返回可以用于html的id标签属性的字符串。

    GetFullHtmlFieldName()——返回可以用于html的name标签属性的字符串。

    HtmlFieldPrefix——返回字段前缀

    (1)关注数据格式化

    下面举一个例子,使Model中能使用这个placeholder,在编辑框上给出提示信息。

    首先,我们要新建一个PlaceHolderAttribute类,让它继承Attribute, IMetadataAware两个接口。在解决方案管理器中新建一个文件夹Infrastructures,在文件夹里新建类库文件PlaceHolderAttribute.cs

    namespace MvcApp.Infrastructures
    {
        public class PlaceHolderAttribute : Attribute, IMetadataAware
        {
            private readonly string _placeholder;
            public PlaceHolderAttribute(string placeholder)
            {
                _placeholder = placeholder;
            }
    
            public void OnMetadataCreated(ModelMetadata metadata)
            {
                metadata.AdditionalValues["placeholder"] = _placeholder;
            }
        }
    }

    再自定义一个PlaceHolder的显示模板,一般用文本框接收的数据通常都是string类型的,所以针对string类型,编写类型的自定义模板,所以以类型名string为文件名,创建类型的自定义模板,位置在/Views/Shared/EditorTemplates/string.cshtml

    @{
        var placeholder = string.Empty;
        if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("placeholder"))
        {
            placeholder = ViewData.ModelMetadata.AdditionalValues["placeholder"] as string;
        }
    }
    @Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { placeholder = placeholder })

    接下来就可以在模型类上使用我们自定义的PlaceHolder类(当然,全称是PlaceHolderAttribute累,使用时只用Attribute之前的名字)了,例如:

    public partial class Person
        {
            public int PersonId { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public DateTime BirthDate { get; set; }
            public Address HomeAddress { get; set; }
            public bool IsApproved { get; set; }
            public Role Role { get; set; }
        }
    
        [MetadataType(typeof(PersonMetadataSource))]
        public partial class Person
        {
        }
    
        [DisplayName("人员信息")]
        class PersonMetadataSource
        {
            [HiddenInput(DisplayValue = false)]
            public int PersonId { get; set; }
    
            //[UIHint("MultilineText")]
            [PlaceHolder("firstname")]
            public string FirstName { get; set; }
    
            [DataType(DataType.Date)]
            [Display(Name = "出生日期")]
            public DateTime BirthDate { get; set; }
    
            //[UIHint("Enum")]
            public Role Role { get; set; }
        }

    这个例子中,只对FirstName使用了我们自定义的PlaceHolder类

    [PlaceHolder("firstname")]
          public string FirstName { get; set; }

    当用 @Html.EditorFor(m=>m.FirstName)来生成FirstName属性时,如果文本框中没有内容,或者有内容时把内容删除掉,那么PlaceHolder的提示信息就会显示出来,以灰色显示。

    image

    在这个例子中,我们定义了string.cshtml后,导致css格式发生了错位,所以右边第二列有错位重叠的现象。

    (2)使用html前缀

    当渲染一个有层次结构的视图时,mvc框架会跟踪正在渲染的属性名,并通过TemplateInfo对象的HtmlFieldPrefix属性,为我们提供一个唯一的参考点。例如Person类的HomeAddress属性,这是一个Address对象。如果像下面这样渲染一个属性的模板:

    @Html.EditorFor(m=>m.HomeAddress.PostalCode)

    那么,传递给这个模板的HtmlFieldPrefix的值将是HomeAddress.PostalCode。HtmlFieldPrefix属性所渲染的值通常不能直接作为html标签属性的值,因此TemplateInfo对象包含了GetFullHtmlFieldId方法和GetFullHtmlFieldName方法,以便把这个唯一的标识转换成可用的东西。

    lyj

  • 相关阅读:
    Delphi映射模式实验
    restTemplate工具类
    RestTemplate中几种常见的请求方式
    RestTemplate(一)
    java.util.NoSuchElementException: No value present
    使用jsonRpc进行远程调用的时候com.googlecode.jsonrpc4j.HttpException: stream is closed
    SLF4J: Class path contains multiple SLF4J bindings.警告解决
    如何更改自己博客(博客园的)的背景
    Django中扩展Paginator实现分页
    bootstrap 导航栏
  • 原文地址:https://www.cnblogs.com/brown-birds/p/3799191.html
Copyright © 2020-2023  润新知