MVC:页面组织: Layout / section / PartialView / ChildAction / Editor
Layout
重用相同HTML代码。
_ViewStart.cshtml
ASP.NET MVC默认使用_ViewStart.cshtml指定默认的Layout页面:
Layout = "~/Views/Shared/_Layout.cshtml";
该_Layout.cshtml位于 ~/Views/Shared文件夹下,其中有一行:
@RenderBody()
ASP.NET MVC在运行/页面呈现时就会将其替换为内容页(比如Index.cshtml)的内容。
可以把@RenderBody()想象成一个“占位符”。
注意:
- 按惯例,所有“共用(Shared)”的.cshtml文件,用下划线(_)开头
- 全局共用的.cshtml页面,通常放置在Shared文件夹中
-
内容页中可覆盖全局_ViewStart中设定:null表示不使用layout
演示:对比_ViewStart中有无(被注释)Layout
section
两个页面共用一个Layout,但是他们除了@RenderBody()的那一部分不一样,还有其他一些小地方(section)也不一样。
但是一个Layout只能有一个@RenderBody()占位。
这就需要使用section:在RenderBody以外重用页面片段。
在Layout中声明
<h3>说明</h3>
<p>@RenderSection("description")</p>
在内容页中指定section内容:
@section description{
<p>Register的说明</p>
}
注意:
- 一个Layout可以有多个section,他们用不同的名字(@RenderSection()的第一个参数)区分
-
如果内容页中有@section,Layout中就一定要有相应的@RenderSection(),否则会报错,因为逻辑上有问题:@seciton里的内容放哪里?除非在Layout中事先做判断:
@if (IsSectionDefined("xxx"))
{
@RenderSection("xxx")
}
-
类似的,Layout中(默认的)@RenderSection(),内容页就一定要呼应有@section,否则会报错;除非在Render时指定第二个参数required为false
@RenderSection("scripts", required: false)
Model共用
内容页的Model数据,Layout可以使用:(演示)
- 弱类型,ViewBag和ViewData:内容页赋值,_Layout直接使用。
-
强类型,@Model.Name:因为不能在Layout上声明@model类型,所以被编译成dynamic,没有智能提示,所以一般不如直接使用弱类型。
PS:如果就是要使用强类型且要求智能提示,可以为Layout专门声明一个基类Model,让其他所有(不然会报错哟)使用在该Layout的内容页的Model继承它:public class LayoutModel
然后在Layout中声明model
{
public string Name { get; set; }
//……
public class Student : LayoutModel
{
public int Id { get; set; }
@model _17bang.Models.LayoutModel
因为ASP.NET MVC对Layout和内容页是分开编译的(利用报错演示,更详细的代码待学习.NET core RazorPages时查看)
Layout的嵌套
此外,可以有多个嵌套的_Layout页面。比如:所有的页面共用header和footer,但是一部分页面是三列,一部分页面是两列,怎么办?
- 仍然保留带有header和footer的“总”_Layout
- 生成两列的_LayoutOf2Columns和_LayoutOf3Columns,但这两个“Columns”Layout使用“总”_Layout做Layout
- 内容页选择使用_LayoutOf2Columns或_LayoutOf3Columns
注意section也要一样的传递。
Layout和section还是有局限的。
网站中存在大量“布局无关”(位置随意)的、可重用的Html片段,比如:
这时候我们就需要使用其他可重用组件:
PartialView
它分为两个部分:
声明/创建
PartialView的创建和普通View(.cshtml)类似
因为partial view只是一个“部分页面”,Partial View可以声明@model,但不应该有_Layout
同样的,partial view也没有Controller和Action
调用
我们把“调用partial view的页面”称之为父页面。在父页面中使用@Html.Partial(),传入参数:
viewName
指定其路径,同return View(viewName))(复习)。
代码如下所示:
-
绝对路径
@*_LogOnStatus.cshtml在~/Views/User/下,注意.cshtml后缀不能省略*@
@Html.Partial("~/Views/User/_LogOnStatus.cshtml")
-
“类”相对路径
@*_LogOnStatus.cshtml和当前页面在同一个文件夹,*@
@*或者_LogOnStatus.cshtml在Shared文件夹下*@
@Html.Partial("_LogOnStatus")
@*_LogOnStatus.cshtml在当前Action对应文件夹的子文件夹Yz下*@
@*或者_LogOnStatus.cshtml在Shared/Yz文件夹下*@
@Html.Partial("Yz/_LogOnStatus")
model值
在实际开发中,PartialView的内容也需要“动态”呈现。
可以将一个ViewDataDictionary对象作为参数传递给PartialView:
@Html.Partial("_Partial", new ViewDataDictionary { { "id", 8 }, { "name", "阿泰" } })
F12演示:ViewDataDictionary的定义
这样在PartialView中,可以直接使用ViewData或者ViewBag:
<a href="/User/@ViewData["id"]">@ViewBag.name</a>
也可以传递强类型的Model:
@Html.Partial("_Partial", Model.Major)
这时候就可以/需要(以便智能提示)在PartialView上声明@model
@model _17bang.Models.Student
然后像普通view中使用model一样:
<a href="/User/@Model.Id">@Model.Name</a>
需要注意的是:如果传递给PartialView的model(比如Model.Major)为null值,或者根本就没有声明传递,ASP.NET MVC会把当前父页面的Model传递过去,于是出现这样的报错信息:
The model item passed into the dictionary is of type '_17bang.Models.Student', but this dictionary requires a model item of type '_17bang.Models.Major'.
即这样三种写法其实是一样的:
@Html.Partial("_Partial", Model.Major) @*Model.Major=null*@
@Html.Partial("_Partial", null)
@Html.Partial("_Partial")
Action()
按@Html.Partial()的方式调用PartilView,Model完全依赖于父页面。
如果想摆脱这种依赖,就要使用@Html.Action(),比如:
@Html.Action("_Reminder", "Register", new { id = 32 })
其中:
- Reminder:action name,必填
- Register:controller name,可选,默认为当前Controller
- new { id = 32 }:route data,可选,通常使用匿名对象
public PartialViewResult _Reminder(int id) //惯例:片段Html的Action和View都前缀下划线
{
return PartialView();
}
注意 routeValue(id)的传值。
PartialView()的参数和View()的参数几乎一致。
通过PartialView() 返回的是一个部分页面(PartialViewResult),它和普通ViewResult最大的区别是:不受_ViewStart.cshtml内容控制(通常是自动引入_Layout)
Action中就可以写需要的后台逻辑,这就是ChildAction和Partial的最大区别!
[ChildActionOnly]
在Action上可以添加 [ChildActionOnly],使其只能被其他View调用,不能独立响应HTTP请求。
换言之,如果没有[ChildActionOnly],该Action就可以被直接调用。
(演示:浏览器输入ChildAction的url访问……)
执行顺序
断点演示:
- 首先进入父Action
- 父Action执行完成,然后进入View页面执行
- 直到@Html.Action()调用进入ChildAction
---------- 以下内容在 Model绑定 之后再讲 --------------
Editor
主要适用于form表单提交/子Model重用。
假设我们注册页面的“邀请人”部分会被重用:
-
有封装好的Model:
namespace ViewModel.Register
{
public class IndexModel
{
public InviterModel Inviter { get; set; }
public class InviterModel
{
public string UserName { get; set; }
public string Code { get; set; }
}
-
相应的PartialView:
@model ViewModel.Register.InviterModel
<div>
<label>邀请人:</label>
@Html.TextBoxFor(m => m.UserName)
</div>
<div>
<label>邀请码:</label>
@Html.TextBoxFor(m => m.Code)
</div>
那么,在Register.cshtml页面应该如何调用呢?
使用PartialView和ChildAction生成的页面看上去都没有问题,但在POST的时候(断点演示):
- PartialView:Register.IndexModel.InviterModel为null值
-
ChildAction:
- 首先进入父Action,且此时Register.IndexModel.InviterModel仍然为null值
- 此后在子ChildAction时,InviterModel可以有值
@想一想@:为什么?
EditorTemplates
如果想在父Action中就能直接取到子Model的值
首先,把对应的.cshtml文件(如:Inviter.cshtml)放在:
- ~/Shared/EditorTemplates文件夹下,或者
-
父页面所在文件夹的EditorTemplates文件夹下,如 ~/Register/EditorTemplates
然后,在父页面中调用:
@Html.EditorFor(m => m.Inviter, "_Inviter")
断点演示:子model被成功绑定。
为什么呢?
因为Editor生成的form表单不一样:
<input id="Inviter_UserName" name="Inviter.UserName" type="text" value="">
注意它的name,不是UserName,而是Inviter.UserName- 复习:Model绑定-复杂(自定义类型)属性
- #体会#:前后端分离
templateName
参数templateName可以省略,这时候MVC默认会在EditorTemplates下寻找和model同名的.cshtml文件做template,比如InviterModel,注意Model后缀
但如果template没有找到,并不会报错,只是没有template的样式等……(演示)
且无论如何,EditorTemplate只能放在EditorTemplates文件夹下。
#常见面试题:Partial()/Action()/Editor的区别?#
- Html片段:可重用
- 不能直接被Http请求
- Model依赖于父页面,没有“后台逻辑”
作业
-
为所有页面引入包含页头页脚的_Layout,并为css和js代码预留了section,不同的页面呈现不同的Title:
-
基于作业1,再实现
- 主体内容不同的两列(侧边栏)布局
- 侧边栏通过PartialView填充
-
新建一个Partial view:_User.cshtml,
- _User.cshtml对应着一个UserPageModel,
- UserPageModel中有Id、Name和Level属性,供_User.cshtml
-
_User.cshtml里是一个链接:
- 文本为UserPageModel的Name
- 链接指向:/User/@Id
- 根据Level显示不同的颜色:3级以下灰色;3-8级蓝色;8级以上红色
- 在其他页面(比如:/Problem)上使用_User.cshtml(如:显示求助的作者)
- 把之前的分页抽象成PartialView:_Pager.cshtml,并分别使用@Html.Partial()和.Action()调用
本站大部分文章、数据、图片均来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了您的权益请来信告知我们删除。邮箱:1451803763@qq.com