模型模板_.NET_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > .NET > 模型模板

模型模板

 2014/6/25 17:54:15  褐色鸟群  程序员俱乐部  我要评论(0)
  • 摘要:前面的Html辅助器,如Html.CheckBoxFor和Html.TextBoxFor等,是明确指定了要使用的html元素。mvc框架支持另一种方法,叫做模板视图辅助器(TemplatedViewHelper),在这样的辅助器中,指定想要显示或编辑的模型对象或属性,而让mvc框架去判断应该用什么样的html元素。一、使用模板视图辅助器1、为指定的模型属性生成html使用模板视图辅助器,意味着我们不必考虑要指定用什么样的html元素来表现一个模型属性,而是只要说出想显示哪个属性
  • 标签:模板

前面的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

发表评论
用户名: 匿名