大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。
当前系列: ASP.NET 修改讲义
通常来说,一个网站的所有网页都会有相同的HTML框架结构(即html中包裹head+body),body中又由页头(header)、页脚(footer)和正文组成,这里面,HTML框架结构、header和footer基本上都是不变的、可以共用的,所以我们可以使用:

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

演示:注册登录,Body里面有一点点的不一样

两个页面共用一个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
    {
        public string Name { get; set; }
    //……
    public class Student : LayoutModel
    {
        public int Id { get; set; }
    然后在Layout中声明model
    @model _17bang.Models.LayoutModel
但代码块中声明的数据不行。

因为ASP.NET MVC对Layout和内容页是分开编译的(利用报错演示,更详细的代码待学习.NET core RazorPages时查看)

Layout的嵌套

此外,可以有多个嵌套的_Layout页面。比如:所有的页面共用header和footer,但是一部分页面是三列,一部分页面是两列,怎么办?

  1. 仍然保留带有header和footer的“总”_Layout
  2. 生成两列的_LayoutOf2Columns和_LayoutOf3Columns,但这两个“Columns”Layout使用“总”_Layout做Layout
  3. 内容页选择使用_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,可选,通常使用匿名对象
然后,在上述指定的Controller中,添加一个Action
    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访问……)

执行顺序

断点演示:

  1. 首先进入父Action
  2. 父Action执行完成,然后进入View页面执行
  3. 直到@Html.Action()调用进入ChildAction
以后讲filter的时候会更有用。


---------- 以下内容在 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的区别?# 

  1. Html片段:可重用
  2. 不能直接被Http请求
  3. Model依赖于父页面,没有“后台逻辑” 


作业

  1. 为所有页面引入包含页头页脚的_Layout,并为css和js代码预留了section,不同的页面呈现不同的Title:
  2. 基于作业1,再实现
    1. 主体内容不同的两列(侧边栏)布局
    2. 侧边栏通过PartialView填充    
  3. 新建一个Partial view:_User.cshtml,
    1. _User.cshtml对应着一个UserPageModel,
    2. UserPageModel中有Id、Name和Level属性,供_User.cshtml
    3. _User.cshtml里是一个链接:
      • 文本为UserPageModel的Name
      • 链接指向:/User/@Id
      • 根据Level显示不同的颜色:3级以下灰色;3-8级蓝色;8级以上红色
    4. 在其他页面(比如:/Problem)上使用_User.cshtml(如:显示求助的作者)
  4. 把之前的分页抽象成PartialView:_Pager.cshtml,并分别使用@Html.Partial()和.Action()调用
学习笔记
源栈学历
今天学习不努力,明天努力找工作

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 ASP.NET 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码