动态内容意味着在运行时生成,这与静态内容,比如html,不同。
可以用四种方式对视图添加动态内容:内联代码、html辅助器方法、分部视图、子动作。
一、使用内联代码
内联代码就是以@符号开头的一条或多条C#语句。这是Razor视图引擎的核心,也是生成动态内容最简单而容易的方法。
1、将命名空间引入视图
2、Razor的html字符串编码
例,新建MVC项目DynamicData,在解决方案管理器中新建文件夹Infrastructure,在该文件夹中新建类库文件MyUtility.cs
namespace DynamicData.Infrastructure { public class MyUtility { public static string GetUsefulData() { return "<form>Enter your password:<input type=text>" + "<input type=submit value=\"Log In\"/></form>"; } } }
在HomeController中有动作方法Index()
public class HomeController : Controller { public ActionResult Index() { return View(); } }
对该动作方法添加默认视图Index.cshtml
@{ ViewBag.Title = "Index"; } <h2>Index</h2> Here is some data:@DynamicData.Infrastructure.MyUtility.GetUsefulData()
在Index.cshtml中可以用上面的方法访问MyUtility类中的GetUsefulData()方法,也可以在文件最开头引入MyUtility类的命名空间。
@using DynamicData.Infrastructure @{ ViewBag.Title = "Index"; } <h2>Index</h2> Here is some data:@MyUtility.GetUsefulData()
这里需注意GetUsefulData()方法的返回类型是用的string,所以,html将会被编码显示出来,而不起作用。显示结果为
如果想让GetUsefulData()方法返回的html起作用,那么可以使用MvcHtmlString作为返回类型,修改如下:
public static MvcHtmlString GetUsefulData() { return new MvcHtmlString("<form>Enter your password:<input type=text>" + "<input type=submit value=\"Log In\"/></form>"); }
这样,html就会起作用,产生结果为:
如果想把命名空间引入到项目中的所有视图,可以将这个命名空间添加到/Views/Web.config文件中。
<system.web.webPages.razor> <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <pages pageBaseType="System.Web.Mvc.WebViewPage"> <namespaces> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Routing" />
<add namespace="DynamicData.Infrastructure" /> </namespaces> </pages> </system.web.webPages.razor>
二、使用html辅助器
一个常见的需求是在视图中反复地用一些相同的html块。MVC用html辅助器来解决这一个问题。
1、创建内联辅助器
创建一个辅助器最直接的方式是在视图中做这件事,使用Razor的@helper标签。假设在HomeController中有动作方法TestHelper()
public ViewResult TestHelper() { string[] days = {"Monday", "Tuesday", "Wednesday", "etc"}; string[] fruits = { "Apples", "Mango", "Banana" }; ViewBag.Days = days; ViewBag.Fruits = fruits; return View(); }
对该动作方法添加默认视图TestHelper.cshtml
@{ ViewBag.Title = "TestHelper"; } @helper CreateList(string[] items) { <ul> @foreach(string item in items) { <li>@item</li> } </ul> } <h2>TestHelper</h2> Days of the week: <p /> @CreateList(ViewBag.Days) <p /> Fruit I like:<p /> @CreateList(ViewBag.Fruits)
这里的CreateList就是一个内联html辅助器,定义好后,就可以在本页面中多次使用。显示结果为:
2、创建外部辅助器方法
对上面的例子,在解决方案管理器中,新建了文件夹Infrastructure,在里面再建一个HtmlHelpers文件夹,在其中新建一个类库文件CustomHtmlHelpers.cs
创建外部Html辅助器List
namespace DynamicData.Infrastructure.HtmlHelpers { public static class CustomHtmlHelpers { public static MvcHtmlString List(this HtmlHelper html, string[] listItems) { TagBuilder tag = new TagBuilder("ul"); foreach (string item in listItems) { TagBuilder itemTag = new TagBuilder("li"); itemTag.SetInnerText(item); tag.InnerHtml += itemTag.ToString(); } return new MvcHtmlString(tag.ToString()); } } }
HomeController中的动作方法TestHelper()仍然不变:
public ViewResult TestHelper() { string[] days = {"Monday", "Tuesday", "Wednesday", "etc"}; string[] fruits = { "Apples", "Mango", "Banana" }; ViewBag.Days = days; ViewBag.Fruits = fruits; return View(); }
它对应的视图文件TestHelper.cshtml修改为:
@using DynamicData.Infrastructure.HtmlHelpers @{ ViewBag.Title = "TestHelper"; } <h2>TestHelper</h2> Days of the week: <p /> @Html.List(ViewBag.days as string[]) <p /> Fruit I like:<p /> @Html.List(ViewBag.Fruits as string[])
特别需要注意几个问题:
(1)在文件开头,要用@using DynamicData.Infrastructure.HtmlHelpers将这个要使用的外部html辅助器命名空间包含进来。
(2)在调用这个外部html辅助器时,使用的格式为 @Html.List(ViewBag.days as string[]),注意是用@Html.跟上辅助器名来调用的。
(3)特别要注意的是ViewBag里的变量都是Object类型,不会自动转换为实际类型,需要显式转换,这里使用ViewBag.days as string[]
TagBuilder类的一些成员
InnerHtml——将TabBuilder对象里的html字符串进行读/写,这个html字符串不编码,html要发挥作用。
SetInnerText(string)——设置html元素的文本内容。字符串将被编码,以使它安全显示。
AddCssClass(string)——把一个CSS的class添加到html元素。
MergeAttribute(string, string, bool)——把一个标签属性添加到html元素。第一个参数是标签属性名,第二个参数是它的值,bool参数指定是否替换已存在的同名标签属性。
3、使用MVC内建的html辅助器
MVC包含了一些生成常用html片段,或执行常规任务的内建html辅助器方法。
(1)创建表单
Html.BeginForm
Html.EndForm
一般使用using语句
@using (Html.BeginForm("Index", "Home")) { ... }
BeginForm方法会创建MvcForm类的一个实例。当退出using块时,.NET框架会调用MvcForm对象上的Dispose方法,并自动发布form的关闭标签,这省去了使用EndForm辅助器的需要。假设是默认路由,上面的BeginForm生成的html为:
<form action="/" method="post"> </form>
BeginForm也有重载形式,如果不带参数,就表示post回给生成当前视图的动作方法:
@using (Html.BeginForm())
{
...
}
(2)使用input
仅有一个html的form是没用的,还需要一些input元素。
@{ ViewBag.Title = "TestInput"; } <h2>TestInput</h2> @using (Html.BeginForm()) { <ul> <li>程序设计 @Html.CheckBox("myCheckbox1", false)</li> <li>英语 @Html.CheckBox("myCheckbox2", false)</li> <li>高数 @Html.CheckBox("myCheckbox3", false)</li> <li>离散数学 @Html.CheckBox("myCheckbox4", false)</li> </ul> <ul> <li>@Html.RadioButton("myRadio", "1", true) 第一个答案</li> <li>@Html.RadioButton("myRadio", "2", false) 第二个答案</li> <li>@Html.RadioButton("myRadio", "3", false) 第三个答案</li> <li>@Html.RadioButton("myRadio", "4", false) 第四个答案</li> </ul> <ul> <li>输入密码: @Html.Password("mypwd")</li> </ul> <ul> <li>@Html.TextBox("myTextbox", "文本框")</li> </ul> }
显示为:
p410,表15-4
大多数html辅助器方法都有重载版本,可以对html添加任意标签属性。
@Html.TextBox("MyTextBox", "MyValue", new{ @class="my-css-class", mycustomattribute="my=value"})
创建了一个文本框元素,该元素有附加的标签属性class和mycustomattribute。
以这种方式添加的最常用的标签属性是class,以便对该元素赋予css样式。必须在class前面缀以@字符。
另有强类型的input辅助器,见p411,表15-5
(3)创建select元素
Drop-dowm下拉列表,Multiple-select多项列表。
p413,表15-6。
例1,下拉列表
@Html.DropDownList("myList", new SelectList(new[]{"A", "B"}), "Choose")
生成的html为:
<select id="myList" name="myList">
<option value="">Choose</option> <option>A</option> <option>B</option> </select>
例2,在动作方法中生成下拉列表所需要的数据,再传递到视图生成下拉列表。
namespace DynamicData.Controllers { public class Region { public int RegionID { get; set; } public string RegionName { get; set; } } public class HomeController : Controller { public ActionResult Index() { return View(); } public ViewResult TestInput() { List<Region> regionData = new List<Region>{ new Region{RegionID = 7, RegionName = "Northern"}, new Region{RegionID = 3, RegionName = "Central"}, new Region{RegionID = 5, RegionName = "Southern"} }; ViewBag.region = new SelectList(regionData, "RegionID", "RegionName", 3); return View(); } } }
在动作方法TestInput对应的默认视图TestInput.cshtml中有:
@Html.DropDownList("myList2", ViewBag.region as SelectList)
执行后,生成的html为:
<select id="myList2" name="myList2">
<option value="7">Northern</option> <option selected="selected" value="3">Central</option> <option value="5">Southern</option> </select>
(4)生成链接和url
p414
(5)使用分段。分段(section)和布局(_layout)、视图(view)的关系。
分段的定义是用@section,格式如下:
@section 分段名{
...
}
对分段的引用是使用@RenderSection("分段名")
分段的定义必须放在视图中。同名的分段,在不同的视图中可以进行不同的定义。
要使用分段,可以在视图中使用,也可以在布局中使用。但在实验中发现,在视图里不能直接用@RenderSection("分段名")来使用分段,执行时会有异常产生。
The file "~/Views/Home/Testinput.cshtml" cannot be requested directly because it calls the "RenderSection" method.
也就是分段要通过@RenderSection("分段名")在_layout中使用,但是定义又要放在各个视图中。布局中使用了分段,但请求的视图里面又没有定义,那也要出现异常。所以在布局中做一个判断,在渲染一个视图的时候先判断下又没有定义这个分段,有就用,没有就不用。可以用IsSectionDefined来判断:
@if (IsSectionDefined("Footer")) { @RenderSection("Footer") } else { ... }
另外,如果只是使用RenderSection,也可以带一个参数来先判断这个分段有没有定义,有就调用,没有就不使用。
@RenderSection("Footer", false)
注意这些调用都是在_layout中进行的,而定义放在视图中。
假设有/Views/Home/TestInput.cshtml的定义:
@{ ViewBag.Title = "TestInput"; } <h2>TestInput</h2> @using (Html.BeginForm()) { <ul> <li>程序设计 @Html.CheckBox("myCheckbox1", false)</li> <li>英语 @Html.CheckBox("myCheckbox2", false)</li> <li>高数 @Html.CheckBox("myCheckbox3", false)</li> <li>离散数学 @Html.CheckBox("myCheckbox4", false)</li> </ul> <ul> <li>@Html.RadioButton("myRadio", "1", true) 第一个答案</li> <li>@Html.RadioButton("myRadio", "2", false) 第二个答案</li> <li>@Html.RadioButton("myRadio", "3", false) 第三个答案</li> <li>@Html.RadioButton("myRadio", "4", false) 第四个答案</li> </ul> <ul> <li>输入密码: @Html.Password("mypwd")</li> </ul> <ul> <li>@Html.TextBox("myTextbox", "文本框")</li> </ul> <ul> <li>@Html.DropDownList("myList", new SelectList(new[]{"A", "B"}), "Choose")</li> </ul> <ul> <li>@Html.DropDownList("myList2", ViewBag.region as SelectList)</li> </ul> } @section myHeader{ @foreach (string str in new[]{"Home", "List", "Edit"}){ <div style="float:left; padding:5px"> @Html.ActionLink(str, str) </div> } <div style = "clear:both" /> } @section myFooter{ <h4>this is the footer</h4> }
在视图文件末尾定义了连个section。在/Views/Shared/_Layout.cshtml中对分段进行了使用:
<!DOCTYPE html> <html> <head> <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> </head> <body> @RenderSection("myHeader", false) @RenderBody() @RenderSection("myFooter", false) </body> </html>
注意使用RenderSection时带了参数false,那么在渲染没有定义这个分段的视图时,就不使用这个分段。而在定义了这个分段的视图中,就可以使用。例如,在访问到TestInput.cshtml时,显示结果为:
分段限制比较大,包括把分段定义放在partial视图中,另一个普通视图用@Html.Partial("分部视图名")包含进分部视图,这样的情况_layout中使用该分部视图中的section也使用不到。
(6)分部视图(Partial View)
如果希望在不同的视图,或同一视图不同的地方,多次使用同样的Razor视图片段,那就可以用分部视图。
Razor视图引擎对分部视图的查找路径与普通视图一样。
/Views/<ControllerName>/视图名.cshtml
/Views/Shared/视图名.cshtml
这意味着,可以创建控制器专用的特殊版本的分部视图,并覆盖Shared文件夹中同名的分部视图。
例如,在上面的DynamicData项目中,新建一个分部视图:在Views/Shared/文件件上点击鼠标右键,选择Add->View。在弹出来的“Add view”对话框上,选中“Create as a partial view”选项,并给该分布视图取名为MyPartial。这时可以看到,创建分部视图后“use a layout or master page”就变得不可用。
创建好分部视图后,就可以在里面进行视图编辑。比如作为演示,我们在MyPartial分部视图里就给出了一句文本信息:
this is from partial
保存后,如果要使用该分部视图,可以用
@Html.Partial("分部视图名")
来调用该分部视图的内容。可以在一个视图中多次使用同一分部视图,也可以在不同的视图中使用它。就相当于把分部视图里的内容插入进来。
(7)使用强类型的分部视图
可以创建强类型分部视图,然后在调用这个分部视图时,传递要使用的视图模型对象。要创建强类型分部视图,只需要在创建分部视图时,选中“Create a strongly-typed view”,并在Model class上输入视图的模型类型就可以了。
例如,上面表示创建了名为MyStronglyTypedPartial的强类型分部视图,它以IEnumerable<string>作为其视图模型类型。代码如下:
@model IEnumerable<string> <p>this is from MyStronglyTypedPartial view</p> <ul> @foreach (string val in Model) { <li>@val</li> } </ul>
默认情况下,强类型分部视图会继承强类型父视图的视图模型对象。但更多的时候是在父视图中把一个视图模型对象作为参数传递给Html.Partial辅助器。例如,假设在一个“视图”中调用了该分部视图:
@Html.Partial("MyStronglyTypedPartial", new[]{"Apples", "Mangoes", "Oranges"})
需要注意,渲染分部视图并不调用动作方法(但可使用下面讲的子动作),因此,分部视图的视图模型对象必须在父视图中创建。在这个例子中,把一个字符串数组作为视图模型对象传递给了这个分部视图。显示结果为:
(8)使用子动作
子动作(Child Action)是在一个视图中调用的动作方法。
a.创建子动作
任何动作方法都可以作为一个子动作,只需要在前面添加[ChildActionOnly]注解属性。
ChildActionOnly注解属性确保一个动作方法只能在一个视图中作为一个子动作进行调用。例如在HomeController中定义了一个子动作:
[ChildActionOnly] public ActionResult Time() { return PartialView(DateTime.Now); }
定义了子动作后,需要创建调用这个动作时要渲染的内容。一般情况下子动作与分部视图相匹配,在默认情况下子动作也是默认访问与它同名的分部视图。下面表示为这个示例创建的Time.cshtml分部视图:
@model DateTime <p>The time is: @Model.ToShortTimeString()</p>
b.渲染子动作
可以用Html.Action辅助器来在视图中调用子动作。
如果使用子动作的视图,产生它的控制器跟子动作所在的是同一个控制器,就可以用下面的格式在视图中调用子动作:
@Html.Action("Time")
这里只有一个参数,用以指定要调用的动作方法名,所以要求,该动作方法和产生本视图的动作方法在同一控制器中。如果想调用其它控制器的动作方法,就需要提供控制器名,例如:
@Html.Action("Time", "Home")
第一个参数表示动作方法名,第二个参数表示控制器名。
通过new产生一个匿名对象,其属性名与子动作方法的参数名同名,那就可以把参数传递给动作方法。例如,假设把子动作改为:
[ChildActionOnly] public ActionResult Time(DateTime time) { return PartialView(time); }
在视图中的调用改为:
@Html.Action("Time", new{time=DateTime.Now})
就可以将一个名为time的DateTime对象传递给子动作的参数time。这里两个名字要保持一致都为time才能进行传递。
在视图里调用:分部视图、子动作。