在 ASP.NET 网页 (Razor) 网站中创建一致的布局

作者 Tom FitzMacken

本文介绍如何在 ASP.NET 网页 (Razor) 网站中使用布局页面来创建可重用的内容块 (,例如页眉和页脚) ,并为网站中的所有页面创建一致的外观。

你将了解的内容:

  • 如何创建可重用的内容块,如页眉和页脚。
  • 如何使用布局为网站中的所有页面创建一致的外观。
  • 如何在运行时将数据传递到布局页。

以下是本文中介绍的 ASP.NET 功能:

  • 内容块,即包含要插入到多个页面中的 HTML 格式内容的文件。
  • 布局页面,这些页面包含 HTML 格式的内容,可由网站上的页面共享。
  • RenderPageRenderBody、 和 RenderSection 方法,用于指示 ASP.NET 插入页面元素的位置。
  • 用于 PageData 在内容块和布局页之间共享数据的字典。

本教程中使用的软件版本

  • ASP.NET 网页 (Razor) 3

本教程还适用于 ASP.NET 网页 2。

关于布局页面

许多网站都有在每个页面上显示的内容,例如页眉和页脚,或告知用户已登录的框。 ASP.NET 可以创建一个单独的文件,其中包含可以包含文本、标记和代码的内容块,就像常规网页一样。 然后,可以将内容块插入到要显示信息的网站的其他页面中。 这样就不必将相同的内容复制并粘贴到每个页面中。 创建此类常见内容还可以更轻松地更新网站。 如果需要更改内容,只需更新单个文件,更改就会反映在插入内容的所有位置。

下图显示了内容块的工作原理。 当浏览器从 Web 服务器请求页面时,ASP.NET 在main页中调用 方法的RenderPage点插入内容块。 然后将完成 (合并) 页发送到浏览器。

显示 RenderPage 方法如何将引用页插入当前页的概念图。

在此过程中,你将创建一个页面,该页引用两个内容块, (页眉和页脚) 位于单独的文件中。 可以在网站中的任何页面中使用这些相同的内容块。 完成后,你将获得如下所示的页面:

显示浏览器中的一个页面的屏幕截图,该页面因运行包含对 RenderPage 方法的调用而生成。

  1. 在网站的根文件夹中,创建名为 Index.cshtml 的文件。

  2. 将现有标记替换为以下内容:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Main Page</title>
      </head>
      <body>
    
        <h1>Index Page Content</h1>
        <p>This is the content of the main page.</p>
    
      </body>
    </html>
    
  3. 在根文件夹中,创建名为 “共享”的文件夹。

    注意

    通常的做法是将网页之间共享的文件存储在名为 Shared 的文件夹中。

  4. “共享文件夹” 中,创建名为 _Header.cshtml 的文件。

  5. 将任何现有内容替换为以下内容:

    <div class="header">This is header text.</div>
    

    请注意,文件名为 _Header.cshtml,以下划线 (_) 作为前缀。 如果浏览器名称以下划线开头,ASP.NET 不会向浏览器发送页面。 这可以防止用户无意中请求 (或者直接) 这些页面。 最好使用下划线来命名其中包含内容块的页面,因为你并不真正希望用户能够请求这些页面 - 它们严格存在以插入到其他页面中。

  6. “共享文件夹” 中,创建名为 _Footer.cshtml 的文件,并将内容替换为以下内容:

    <div class="footer">&copy; 2012 Contoso Pharmaceuticals. All rights reserved.
    </div>
    
  7. Index.cshtml 页中,向 方法添加两个调用 RenderPage ,如下所示:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Main Page</title>
      </head>
      <body>
    
        @RenderPage("~/Shared/_Header.cshtml")
    
        <h1>Index Page Content</h1>
        <p>This is the content of the main page.</p>
    
        @RenderPage("~/Shared/_Footer.cshtml")
    
      </body>
    </html>
    

    这演示了如何将内容块插入网页。 调用 RenderPage 方法并向其传递要插入其内容的文件的名称。 在这里,你将 将 _Header.cshtml_Footer.cshtml 文件的内容插入 Index.cshtml 文件中。

  8. 在浏览器中运行 Index.cshtml 页。 (在 WebMatrix 中的“ 文件” 工作区中,右键单击该文件,然后选择“ 在浏览器中启动”。)

  9. 在浏览器中,查看页面源。 (例如,在 Internet Explorer 中,右键单击页面,然后单击“ 查看源”。)

    这样,你便可以看到发送到浏览器的网页标记,该标记将索引页标记与内容块组合在一起。 以下示例显示了为 Index.cshtml 呈现的页面源。 已将插入 Index.cshtml 的调用RenderPage替换为页眉和页脚文件的实际内容。

    <!DOCTYPE html>
    <html>
      <head>
        <title>Main Page</title>
      </head>
      <body>
    
      <div class="header">
        This is header text.
      </div>
    
        <h1>Index Page Content</h1>
        <p>This is the content of the main page.</p>
    
      <div class="footer">
        &copy; 2012 Contoso Pharmaceuticals. All rights reserved.
      </div>
    
      </body>
    </html>
    

使用布局页创建一致外观

到目前为止,你已经看到,在多个页面上包含相同的内容很容易。 为网站创建一致外观的一种更结构化的方法是使用布局页面。 布局页定义网页的结构,但不包含任何实际内容。 创建布局页后,可以创建包含内容的网页,然后将其链接到布局页。 显示这些页面时,将根据布局页面设置这些页面的格式。 (在此意义上,布局页面充当其他页面中定义的内容的一种模板。)

布局页与任何 HTML 页面一样,只不过它包含对 方法的 RenderBody 调用。 方法在布局页中的位置 RenderBody 决定了将包含内容页中的信息的位置。

下图显示了如何在运行时组合内容页和布局页以生成已完成的网页。 浏览器请求内容页。 内容页中的代码指定要用于页面结构的布局页面。 在布局页中,在调用 方法的 RenderBody 点插入内容。 还可以通过调用 RenderPage 方法将内容块插入到布局页中,就像在上一节中一样。 网页完成后,会将其发送到浏览器。

显示浏览器中的一个页面的屏幕截图,该页面因运行包含对 RenderBody 方法的调用而生成。

以下过程演示如何创建布局页并将内容页链接到该页面。

  1. 在网站的 “共享文件夹” 中,创建名为 _Layout1.cshtml 的文件。

  2. 将任何现有内容替换为以下内容:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Structured Content </title>
        <link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
      </head>
      <body>
        @RenderPage("~/Shared/_Header2.cshtml")
        <div id="main">
          @RenderBody()
        </div>
        <div id="footer">
          &copy; 2012 Contoso Pharmaceuticals. All rights reserved.
        </div>
      </body>
    </html>
    

    RenderPage 布局页中使用 方法插入内容块。 布局页只能包含对 方法的 RenderBody 一次调用。

  3. “共享文件夹” 中,创建一个名为 _Header2.cshtml 的文件,并将任何现有内容替换为以下内容:

    <div id="header">Creating a Consistent Look</div>
    
  4. 在根文件夹中,创建一个新文件夹并将其命名为 “样式”。

  5. Styles 文件夹中,创建名为 Site.css 的文件并添加以下样式定义:

    h1 {
        border-bottom: 3px solid #cc9900;
        font: 2.75em/1.75em Georgia, serif;
        color: #996600;
    }
    
    ul {
        list-style-type: none;
    }
    
    body {
        margin: 0;
        padding: 1em;
        background-color: #ffffff;
        font: 75%/1.75em "Trebuchet MS", Verdana, sans-serif;
        color: #006600;
    }
    
    #list {
        margin: 1em 0 7em -3em;
        padding: 1em 0 0 0;
        background-color: #ffffff;
        color: #996600;
        width: 25%;
        float: left;
    }
    
    #header, #footer {
        margin: 0;
        padding: 0;
        color: #996600;
    }
    

    此处这些样式定义只是为了展示样式表如何与布局页一起使用。 如果需要,可以为这些元素定义自己的样式。

  6. 在根文件夹中,创建名为 Content1.cshtml 的文件,并将任何现有内容替换为以下内容:

    @{
        Layout = "~/Shared/_Layout1.cshtml";
    }
    
    <h1> Structured Content </h1>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit,
    sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
    Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
    nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
    reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
    pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
    culpa qui officia deserunt mollit anim id est laborum.</p>
    

    这是一个将使用布局页面的页面。 页面顶部的代码块指示要用于设置此内容格式的布局页面。

  7. 在浏览器中运行 Content1.cshtml 。 呈现的页面使用 _Layout1.cshtml 中定义的格式和样式表,以及 content1.cshtml 中定义的) 内容 (文本。

    [屏幕截图显示了在浏览器中运行内容 1 点 CSHTML。]

    可以重复步骤 6 以创建可共享相同布局页面的其他内容页。

    注意

    您可以设置网站,以便可以自动对文件夹中的所有内容页面使用相同的布局页面。 有关详细信息,请参阅自定义 ASP.NET 网页Site-Wide行为

设计具有多个内容分区的布局页面

一个内容页可以有多个分区,如果要使用具有多个区域且具有可替换内容的布局,这非常有用。 在内容页中,为每个部分指定一个唯一的名称。 (默认节未命名。) 在布局页中添加方法 RenderBody 以指定未命名 (默认) 节的显示位置。 然后,添加单独的 RenderSection 方法,以便单独呈现命名节。

下图显示了 ASP.NET 如何处理划分为多个部分的内容。 每个命名节都包含在内容页的节块中。 (它们命名HeaderList为 example.) 框架在调用 方法时RenderSection将内容部分插入布局页。 如前所述,未命名 (默认) 节插入到调用方法的位置 RenderBody

显示 RenderSection 方法如何将引用部分插入当前页的概念图。

此过程演示如何创建包含多个内容部分的内容页,以及如何使用支持多个内容节的布局页面呈现它。

  1. “共享文件夹” 中,创建名为 _Layout2.cshtml 的文件。

  2. 将任何现有内容替换为以下内容:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Multisection Content</title>
        <link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
      </head>
      <body>
        <div id="header">
          @RenderSection("header")
        </div>
        <div id="list">
          @RenderSection("list")
        </div>
        <div id="main">
          @RenderBody()
        </div>
        <div id="footer">
          &copy; 2012 Contoso Pharmaceuticals. All rights reserved.
        </div>
      </body>
    </html>
    

    使用 RenderSection 方法可同时呈现标题和列表部分。

  3. 在根文件夹中,创建名为 Content2.cshtml 的文件,并将任何现有内容替换为以下内容:

    @{
        Layout = "~/Shared/_Layout2.cshtml";
    }
    
    @section header {
        <div id="header">
            Creating a Consistent Look
        </div>
    }
    
    @section list {
        <ul>
            <li>Lorem</li>
            <li>Ipsum</li>
            <li>Dolor</li>
            <li>Consecte</li>
            <li>Eiusmod</li>
            <li>Tempor</li>
            <li>Incididu</li>
        </ul>
    }
    
    <h1>Multisection Content</h1>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit,
    sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
    Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
    nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
    reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
    pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
    culpa qui officia deserunt mollit anim id est laborum.</p>
    

    此内容页在页面顶部包含一个代码块。 每个命名节都包含在节块中。 页面的其余部分包含默认 (未命名) 内容部分。

  4. 在浏览器中运行 Content2.cshtml

    显示浏览器中的一个页面的屏幕截图,该页面因运行包含对 RenderSection 方法的调用而生成。

使内容分区可选

通常,在内容页中创建的节必须与布局页中定义的节匹配。 如果发生以下任一情况,可能会收到错误:

  • 内容页包含布局页中没有相应节的分区。
  • 布局页包含一个没有内容的分区。
  • 布局页包括尝试多次呈现同一节的方法调用。

但是,可以通过在布局页中声明节为可选来替代命名节的此行为。 这使你可以定义多个内容页,这些页面可以共享布局页面,但可能具有或可能不包含特定分区的内容。

  1. 打开 Content2.cshtml 并删除以下部分:

    @section header {
      <div id="header">
        Creating a Consistent Look
      </div>
    }
    
  2. 保存页面,然后在浏览器中运行它。 将显示错误消息,因为内容页不提供布局页中定义的节(即页眉部分)的内容。

    显示运行调用 RenderSection 方法的页面但未提供相应部分时发生的错误的屏幕截图。

  3. “共享文件夹” 中,打开 “_Layout2.cshtml ”页并替换以下行:

    @RenderSection("header")
    

    替换为以下代码:

    @RenderSection("header", required: false)
    

    作为替代方法,可以将上一行代码替换为以下代码块,这将产生相同的结果:

    @if (IsSectionDefined("header")) {
        @RenderSection("header")
    }
    
  4. 在浏览器中再次运行 Content2.cshtml 页。 (如果仍在浏览器中打开此页面,则只需刷新它。) 这次显示页面时没有错误,即使它没有标题。

将数据传递到布局页面

你可能在内容页中定义了需要在布局页中引用的数据。 如果是这样,则需要将数据从内容页传递到布局页。 例如,你可能想要显示用户的登录状态,或者可能希望根据用户输入显示或隐藏内容区域。

若要将数据从内容页传递到布局页,可以将值放入 PageData 内容页的 属性中。 属性 PageData 是名称/值对的集合,用于保存要在页面之间传递的数据。 然后,在布局页中,可以从 属性中 PageData 读取值。

下面是另一个关系图。 本示例演示 ASP.NET PageData 如何使用 属性将值从内容页传递到布局页。 当 ASP.NET 开始生成网页时,它会创建 PageData 集合。 在内容页中,编写代码以将数据放入集合中 PageDataPageData还可以由内容页中的其他部分或其他内容块访问集合中的值。

显示内容页如何填充 PageData 字典并将该信息传递到布局页面的概念图。

以下过程演示如何将数据从内容页传递到布局页。 当页面运行时,它将显示一个按钮,该按钮允许用户隐藏或显示布局页面中定义的列表。 当用户单击该按钮时,它会在 属性中 PageData 设置 true/false (布尔值) 值。 布局页读取该值,如果为 false,则隐藏列表。 值还用于在内容页中确定是显示 “隐藏列表” 按钮还是“ 显示列表” 按钮。

[屏幕截图显示“传递数据”页。]

  1. 在根文件夹中,创建名为 Content3.cshtml 的文件,并将任何现有内容替换为以下内容:

    @{
        Layout = "~/Shared/_Layout3.cshtml";
    
        PageData["Title"] = "Passing Data";
        PageData["ShowList"] = true;
    
        if (IsPost) {
            if (Request.Form["list"] == "off") {
                PageData["ShowList"] = false;
            }
        }
    }
    
    @section header {
      <div id="header">
        Creating a Consistent Look
      </div>
    }
    
    <h1>@PageData["Title"]</h1>
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit,
    sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
    Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
    nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
    reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
    pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
    culpa qui officia deserunt mollit anim id est laborum.</p>
    
    @if (PageData["ShowList"] == true) {
        <form method="post" action="">
          <input type="hidden" name="list" value="off" />
          <input type="submit" value="Hide List" />
        </form>
    }
    else {
        <form method="post" action="">
          <input type="hidden" name="list" value="on" />
          <input type="submit" value="Show List" />
        </form>
    }
    

    该代码在 属性中 PageData 存储两个数据片段 - 网页的标题,以及用于指定是否显示列表的 true 或 false。

    请注意,ASP.NET 允许使用代码块有条件地将 HTML 标记放入页面。 例如, if/else 页面正文中的 块确定要显示的窗体,具体取决于 是否 PageData["ShowList"] 设置为 true。

  2. “共享文件夹” 中,创建一个名为 _Layout3.cshtml 的文件,并将任何现有内容替换为以下内容:

    <!DOCTYPE html>
    <html>
      <head>
        <title>@PageData["Title"]</title>
        <link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
      </head>
      <body>
        <div id="header">
          @RenderSection("header")
        </div>
          @if (PageData["ShowList"] == true) {
              <div id="list">
                @RenderPage("~/Shared/_List.cshtml")
              </div>
          }
        <div id="main">
          @RenderBody()
        </div>
        <div id="footer">
          <p>&copy; 2012 Contoso Pharmaceuticals. All rights reserved.</p>
        </div>
      </body>
    </html>
    

    布局页在 元素中包含一个表达式, <title> 该表达式从 PageData 属性获取标题值。 它还使用 ShowList 属性的值 PageData 确定是否显示列表内容块。

  3. “共享文件夹” 中,创建名为 _List.cshtml 的文件,并将任何现有内容替换为以下内容:

    <ul>
      <li>Lorem</li>
      <li>Ipsum</li>
      <li>Dolor</li>
      <li>Consecte</li>
      <li>Eiusmod</li>
      <li>Tempor</li>
      <li>Incididu</li>
    </ul>
    
  4. 在浏览器中运行 Content3.cshtml 页。 将显示页面,其中列表在页面左侧可见,底部有一个 “隐藏列表 ”按钮。

    显示包含列表的页面和显示“隐藏列表”的按钮的屏幕截图。

  5. 单击“ 隐藏列表”。 列表消失,按钮更改为 “显示列表”。

    显示不包含列表的页面和显示“显示列表”按钮的屏幕截图。

  6. 单击“ 显示列表 ”按钮,然后再次显示该列表。

其他资源

自定义 ASP.NET 网页的Site-Wide行为