ASP.NET 网页简介 - 创建一致的布局

作者 Tom FitzMacken

本教程介绍如何使用布局为使用 ASP.NET 网页 的网站上的页面创建一致的外观。 它假定你已通过删除 ASP.NET 网页 中的数据库数据完成了该系列。

学习内容:

  • 什么是布局页面。
  • 如何将布局页面与动态内容组合在一起。
  • 如何将值传递给布局页。

关于布局

到目前为止,你已创建的页面都是完整的独立页面。 它们都属于同一站点,但它们没有任何通用元素或标准外观。

大多数网站具有一致的外观和布局。 例如,如果转到 Microsoft.com/web 网站并环顾四周,则会看到页面都遵循整体布局和视觉主题:

Microsoft.com/web 显示页眉、导航区域、内容区域和页脚布局的网站页面

创建此布局的 低效 方法是在每个页面上分别定义页眉、导航栏和页脚。 每次都会复制相同的标记。 如果要更改某些内容 (例如更新页脚) ,则必须单独更改每个页面。

这就是 布局页面 的用处。 在 ASP.NET 网页 中,可以定义一个布局页面,为网站上的页面提供整体容器。 例如,布局页可以包含页眉、导航区域和页脚。 布局页包含一个占位符,main内容所在的位置。

然后,可以定义仅包含该页面的标记和代码的单个内容页。 内容页不必是完整的 HTML 页面;它们甚至不必具有 <body> 元素。 它们还有一行代码,告知 ASP.NET 要在其中显示内容的布局页面。 下图大致显示了此关系的工作原理:

显示两个内容页和一个布局页面的概念图

当你看到它的运行情况时,这种交互很容易理解。 在本教程中,你将更改电影页面以使用布局。

添加布局页

首先创建一个布局页,该页面定义具有页眉、页脚和main内容区域的典型页面布局。 在 WebPagesMovies 网站中,添加名为 _Layout.cshtml 的 CSHTML 页

前导下划线 ( _ ) 字符很重要。 如果页面名称以下划线开头,ASP.NET 不会直接将页面发送到浏览器。 此约定允许您定义网站所需的页面,但用户不应能够直接请求。

将页面中的内容替换为以下内容:

<!DOCTYPE html>
<html>
  <head>
    <title>My Movie Site</title>
    <link href="~/Styles/Movies.css" rel="stylesheet" type="text/css" />
  </head>
  <body>
    <div id="container">
        <div id="header">
          <h1>My Movie Site</h1>
        </div>
        <div id="main">
          @RenderBody()
        </div>
        <div id="footer">
          &copy; @DateTime.Now.Year My Movie Site
        </div>
    </div>
  </body>
</html>

如你所看到的,此标记只是 HTML,它使用 <div> 元素定义页面中的三个部分,以及一 <div> 个元素来保存这三个部分。 页脚包含一些 Razor 代码: @DateTime.Now.Year,它将呈现页面中该位置的当前年份。

请注意,有一个名为 Movies.css 的样式表的链接。 样式表是定义元素的物理布局详细信息的位置。 稍后你将创建它。

_Layout.cshtml 页中唯一不寻常的功能是 @Render.Body() 行。 这是当此布局与其他页面合并时内容将转到的占位符。

添加 .css 文件

定义实际排列 (即页面上元素的外观) 的首选方法是使用级联样式表 (CSS) 规则。 因此,你将创建一个 .css 文件,其中包含新布局的规则。

在 WebMatrix 中,选择站点的根。 然后在功能区的“ 文件 ”选项卡中,单击“ 新建 ”按钮下的箭头,然后单击“ 新建文件夹”。

功能区中“新建”下的“新建文件夹”选项。

将新文件夹命名 为 Styles

将新文件夹命名为“Styles”

在新的 Styles 文件夹中,创建名为 Movies.css 的文件。

创建新的 Movies.css 文件

将新的 .css 文件的内容替换为以下内容:

html{ height:100%; margin:0; padding:0; }

body {
  height:60%;
  font-family:'Trebuchet MS',  'Arial', 'Helvetica', 'sans-serif';
  font-size:10pt;
  background-color: LightGray;
  line-height:1.6em;
}

h1{ font-size:1.6em; }
h2{ font-size:1.4em; }

#container{
   min-height:100%;
   position:relative;
   left:10%;
}

#header{
  padding:8px;
  width:80%;
  background-color:#4b6c9e;
  color:White;
}

#main{
  width:80%;
  padding: 8px;
  padding-bottom:4em;
  background-color:White;
}

#footer{
  width:80%;
  height:2em;
  padding:8px;
  margin-top:-2em;
  background-color:LightGray;
}

.head { background-color: #E8E8E8; font-weight: bold; color: #FFF; }
.grid th, .grid td { border: 1px solid #C0C0C0; padding: 5px; }
.alt { background-color: #E8E8E8; color: #000; }
.selected {background-color:Yellow;}
span.caption {width:100px;}
span.dataDisplay {font-weight:bold;}

我们不会对这些 CSS 规则说太多,只是要注意两点。 一个是,除了设置字体和大小外,规则还使用绝对定位来建立页眉、页脚和main内容区域的位置。 如果你不熟悉 CSS 中的定位,可以在 W3Schools 站点上阅读 CSS 定位 教程。

另一个需要注意的是,在底部,我们复制了最初在 Movies.cshtml 文件中单独定义的样式规则。 使用 ASP.NET 网页显示数据简介教程中使用了这些规则,使WebGrid帮助程序呈现向表中添加了条带的标记。 (如果要对样式定义使用 .css 文件,则不妨将整个网站的样式规则放在其中。)

更新电影文件以使用布局

现在,您可以更新网站中的现有文件以使用新布局。 打开 Movies.cshtml 文件。 在顶部,作为第一行代码,添加以下内容:

Layout = "~/_Layout.cshtml";

页面现在以这种方式开始:

@{
    Layout = "~/_Layout.cshtml";

    var db = Database.Open("WebPagesMovies") ;
    var selectCommand = "SELECT * FROM Movies";
    var searchTerm = "";

    // Etc.

这一行代码告知 ASP.NET,当 “电影 ”页运行时,它应与 _Layout.cshtml 文件合并。

由于 Movies.cshtml 文件现在使用布局页,因此可以从由 _Layout.cshtml 文件负责的 Movies.cshtml 页中删除标记。 取出 <!DOCTYPE><html><body> 开始和结束标记。 取出整个 <head> 元素及其内容,其中包括网格的样式规则,因为现在已在 .css 文件中获取这些规则。 使用时,将现有 <h1> 元素更改为 元素 <h2> ;布局页中已有一个 <h1> 元素。 将 <h2> 文本更改为“列出电影”。

通常,无需在内容页面中进行此类更改。 当您使用布局页面启动网站时,您将创建内容页面时,无需所有这些元素即可开始。 不过,在这种情况下,你要将独立页面转换为使用布局的页面,因此需要一些清理。

完成后, Movies.cshtml 页面将如下所示:

@{
    Layout = "~/_Layout.cshtml";

    var db = Database.Open("WebPagesMovies") ;
    var selectCommand = "SELECT * FROM Movies";
    var searchTerm = "";

    if(!Request.QueryString["searchGenre"].IsEmpty() ) {
        selectCommand = "SELECT * FROM Movies WHERE Genre = @0";
        searchTerm = Request.QueryString["searchGenre"];
    }

    if(!Request.QueryString["searchTitle"].IsEmpty() ) {
      selectCommand = "SELECT * FROM Movies WHERE Title LIKE @0";
      searchTerm = "%" + Request.QueryString["searchTitle"] + "%";
    }

    var selectedData = db.Query(selectCommand, searchTerm);
    var grid = new WebGrid(source: selectedData, defaultSort: "Genre", rowsPerPage:3);
}
  <h2>List Movies</h2>
  <form method="get">
    <div>
      <label for="searchGenre">Genre to look for:</label>
      <input type="text" name="searchGenre"
         value="@Request.QueryString["searchGenre"]" />
      <input type="Submit" value="Search Genre" /><br/>
      (Leave blank to list all movies.)<br/>
    </div>
    <div>
       <label for="SearchTitle">Movie title contains the following:</label>
       <input type="text" name="searchTitle" value="@Request.QueryString["searchTitle"]" />
       <input type="Submit" value="Search Title" /><br/>
    </div>
  </form>
  <div>
    @grid.GetHtml(
        tableStyle: "grid",
        headerStyle: "head",
        alternatingRowStyle: "alt",
        columns: grid.Columns(
            grid.Column(format: @<a href="~/EditMovie?id=@item.ID">Edit</a>),
            grid.Column("Title"),
            grid.Column("Genre"),
            grid.Column("Year"),
            grid.Column(format: @<a href="~/DeleteMovie?id=@item.ID">Delete</a>)
       )
    )
  </div>
  <p><a href="~/AddMovie">Add a movie</a></p>

测试布局

现在可以看到布局的外观。 在 WebMatrix 中,右键单击 Movies.cshtml 页,然后选择“ 在浏览器中启动”。 当浏览器显示页面时,页面如下所示:

使用布局呈现的电影页面

ASP.NET 已将 Movies.cshtml 页面的内容合并到方法所在的RenderBody_Layout.cshtml 页中。 当然, _Layout.cshtml 页面引用定义页面外观的 .css 文件。

更新 AddMovie 页面以使用布局

布局的真正好处是,你可以将它们用于网站中的所有页面。 打开 AddMovie.cshtml 页。

你可能还记得 ,AddMovie.cshtml 页面最初包含一些 CSS 规则来定义验证错误消息的外观。 由于您现在拥有网站的 .css 文件,因此您可以将这些规则移动到 .css 文件。 从 AddMovie.cshtml 文件中删除它们,并将其添加到 Movies.css 文件的底部。 你正在移动以下规则:

.field-validation-error {
  font-weight:bold;
  color:red;
  background-color:yellow;
 }
.validation-summary-errors{
  border:2px dashed red;
  color:red;
  background-color:yellow;
  font-weight:bold;
  margin:12px;
}

现在,在 AddMovie.cshtml 中执行与对 Movies.cshtml 所做的相同类型的更改 - 添加 Layout="~/_Layout.cshtml; 和删除现在无关的 HTML 标记。 将 <h1> 元素更改为 <h2>。 完成后,页面将如以下示例所示:

@{
    Layout = "~/_Layout.cshtml";
    Validation.RequireField("title", "You must enter a title");
    Validation.RequireField("genre", "Genre is required");
    Validation.RequireField("year", "You haven't entered a year");

    var title = "";
    var genre = "";
    var year = "";

    if(IsPost){
        if(Validation.IsValid()){
            title = Request.Form["title"];
            genre = Request.Form["genre"];
            year = Request.Form["year"];

            var db = Database.Open("WebPagesMovies");
            var insertCommand =
                "INSERT INTO Movies (Title, Genre, Year) Values(@0, @1, @2)";
            db.Execute(insertCommand, title, genre, year);
            Response.Redirect("~/Movies");
        }
    }
}
  <h2>Add a Movie</h2>
    @Html.ValidationSummary()
 <form method="post">
  <fieldset>
    <legend>Movie Information</legend>
    <p><label for="title">Title:</label>
      <input type="text" name="title" value="@Request.Form["title"]" />
      @Html.ValidationMessage("title")
    </p>

    <p><label for="genre">Genre:</label>
      <input type="text" name="genre" value="@Request.Form["genre"]" />
      @Html.ValidationMessage("genre")
    </p>

    <p><label for="year">Year:</label>
      <input type="text" name="year" value="@Request.Form["year"]" />
      @Html.ValidationMessage("year")
    </p>

    <p><input type="submit" name="buttonSubmit" value="Add Movie" /></p>
  </fieldset>
  </form>

运行页面。 现在如下图所示:

使用布局呈现的“添加电影”页面

您希望对网站中的页面进行类似的更改- EditMovie.cshtmlDeleteMovie.cshtml。 但是,在执行此操作之前,你可以对布局进行另一次更改,使其更灵活一点。

将标题信息传递到布局页

创建的 _Layout.cshtml 页面有一个 <title> 设置为“我的电影网站”的 元素。 大多数浏览器将此元素的内容显示为选项卡上的文本:

浏览器选项卡中显示的页面 <标题> 元素

此游戏信息是通用的。 假设希望标题文本更特定于当前页。 (搜索引擎还使用标题文本来确定页面的内容。) 可以将内容页(如 Movies.cshtmlAddMovie.cshtml )中的信息传递到布局页,然后使用该信息自定义布局页面呈现的内容。

再次打开 Movies.cshtml 页面。 在顶部的代码中,添加以下行:

Page.Title = "List Movies";

对象 Page 在所有 .cshtml 页面上可用,用于此目的,即在页面及其布局之间共享信息。

打开 “_Layout.cshtml ”页。 更改 <title> 元素,使其类似于以下标记:

<title>@Page.Title</title>

此代码在页面的该 Page.Title 位置的 属性中呈现任何信息。

运行 Movies.cshtml 页。 这一次,浏览器选项卡显示作为 的值 Page.Title传递的内容:

显示动态创建的标题的浏览器选项卡

如果需要,请在浏览器中查看页面源。 可以看到 元素 <title> 呈现为 <title>List Movies</title>

提示

Page 对象

Page 一个有用功能是它是动态对象 , Title 属性不是固定或保留名称。 可以将 任何 名称用于 对象的值 Page 。 例如,可以使用名为 Page.CurrentNamePage.MyPage的属性轻松传递标题。 唯一的限制是名称必须遵循可以命名的属性的常规规则。 (例如,名称不能包含 space.)

可以使用 对象传递任意数量的值 Page 。 如果要将电影信息传递到布局页,可以使用 和 Page.GenrePage.MovieYear之类的Page.MovieTitle内容传递值。 (或你为存储信息而发明的任何其他名称。) 唯一的要求(可能很明显)是在内容页和布局页面中使用相同的名称。

使用 Page 对象传递的信息不仅限于要显示在布局页上的文本。 可以将值传递给布局页,然后布局页中的代码可以使用该值决定是否显示页面的某个部分、要使用的 .css 文件等。 在 对象中 Page 传递的值与在代码中使用的任何其他值类似。 只是值源自内容页,并传递到布局页。

打开 AddMovie.cshtml 页,并在代码顶部添加一行,为 AddMovie.cshtml 页面提供标题:

Page.Title = "Add a Movie";

运行 AddMovie.cshtml 页。 可看到新标题:

显示动态创建的“添加影片”标题的浏览器选项卡

更新剩余页面以使用布局

现在,您可以完成网站中的剩余页面,以便它们使用新布局。 依次打开 EditMovie.cshtmlDeleteMovie.cshtml ,并在其中进行相同的更改。

添加链接到布局页的代码行:

Layout = "~/_Layout.cshtml";

添加一行以设置页面标题:

Page.Title = "Edit a Movie";

或:

Page.Title = "Delete a Movie";

删除所有无关的 HTML 标记 - 基本上,只保留元素内部 <body> 的位 (加上顶部) 的代码块。

<h1> 元素更改为 <h2> 元素。

进行这些更改后,请测试每个更改,并确保其正确显示且标题正确。

关于布局页面的分别想法

在本教程中,你创建了 一个 _Layout.cshtml 页面,并使用 RenderBody 方法合并了另一个页面中的内容。 这是在网页中使用布局的基本模式。

布局页面具有我们此处未介绍的其他功能。 例如,可以嵌套布局页 - 一个布局页可以依次引用另一个布局页。 如果使用需要不同布局的网站子部分,嵌套布局可能很有用。 还可以使用其他方法 (例如, RenderSection) 在布局页中设置命名节。

布局页面和 .css 文件的组合功能非常强大。 正如你将在下一个教程系列中看到的,在 WebMatrix 中,你可以基于模板创建一个网站,该 模板为你提供了一个具有预生成功能的网站。 模板充分利用布局页面和 CSS 来创建外观极佳且具有菜单等功能的网站。 下面是基于模板的网站主页的屏幕截图,其中显示了使用布局页面和 CSS 的功能:

由 WebMatrix 网站模板创建的布局,其中显示了标题、导航区域、内容区域、可选部分和登录链接

电影页面的完整列表 (更新为使用布局页面)

@{
    Layout = "~/_Layout.cshtml";
    Page.Title = "List Movies";

    var db = Database.Open("WebPagesMovies") ;
    var selectCommand = "SELECT * FROM Movies";
    var searchTerm = "";

    if(!Request.QueryString["searchGenre"].IsEmpty() ) {
        selectCommand = "SELECT * FROM Movies WHERE Genre = @0";
        searchTerm = Request.QueryString["searchGenre"];
    }

    if(!Request.QueryString["searchTitle"].IsEmpty() ) {
        selectCommand = "SELECT * FROM Movies WHERE Title LIKE @0";
        searchTerm = "%" + Request.QueryString["searchTitle"] + "%";
    }

    var selectedData = db.Query(selectCommand, searchTerm);
    var grid = new WebGrid(source: selectedData, defaultSort: "Genre", rowsPerPage:3);
}

<h2>List Movies</h2>
    <form method="get">
      <div>
        <label for="searchGenre">Genre to look for:</label>
        <input type="text" name="searchGenre" value="@Request.QueryString["searchGenre"]" />
        <input type="Submit" value="Search Genre" /><br/>
        (Leave blank to list all movies.)<br/>
      </div>

      <div>
        <label for="SearchTitle">Movie title contains the following:</label>
        <input type="text" name="searchTitle" value="@Request.QueryString["searchTitle"]" />
        <input type="Submit" value="Search Title" /><br/>
      </div>
    </form>

<div>
    @grid.GetHtml(
        tableStyle: "grid",
        headerStyle: "head",
        alternatingRowStyle: "alt",
        columns: grid.Columns(
    grid.Column(format: @<a href="~/EditMovie?id=@item.ID">Edit</a>),
    grid.Column("Title"),
    grid.Column("Genre"),
    grid.Column("Year"),
    grid.Column(format: @<a href="~/DeleteMovie?id=@item.ID">Delete</a>)
        )
    )
</div>
<p><a href="~/AddMovie">Add a movie</a></p>

添加影片页面的完整页面列表 (更新布局)

@{
    Layout = "~/_Layout.cshtml";
    Page.Title = "Add a Movie";

    Validation.RequireField("title", "You must enter a title");
    Validation.RequireField("genre", "Genre is required");
    Validation.RequireField("year", "You haven't entered a year");

    var title = "";
    var genre = "";
    var year = "";

    if(IsPost){
        if(Validation.IsValid()){
            title = Request.Form["title"];
            genre = Request.Form["genre"];
            year = Request.Form["year"];

            var db = Database.Open("WebPagesMovies");
            var insertCommand = "INSERT INTO Movies (Title, Genre, Year) VALUES(@0, @1, @2)";
            db.Execute(insertCommand, title, genre, year);

            Response.Redirect("~/Movies");
        }
    }
}

<h2>Add a Movie</h2>
@Html.ValidationSummary()
<form method="post">
<fieldset>
    <legend>Movie Information</legend>
    <p><label for="title">Title:</label>
        <input type="text" name="title" value="@Request.Form["title"]" />
        @Html.ValidationMessage("title")

    <p><label for="genre">Genre:</label>
        <input type="text" name="genre" value="@Request.Form["genre"]" />
        @Html.ValidationMessage("genre")

    <p><label for="year">Year:</label>
        <input type="text" name="year" value="@Request.Form["year"]" />
        @Html.ValidationMessage("year")

    <p><input type="submit" name="buttonSubmit" value="Add Movie" /></p>
</fieldset>
</form>

针对布局) 更新的“删除影片页面” (的完整页面列表

@{
    Layout = "~/_Layout.cshtml";
    Page.Title = "Delete a Movie";

    var title = "";
    var genre = "";
    var year = "";
    var movieId = "";

    if(!IsPost){
        if(!Request.QueryString["ID"].IsEmpty() && Request.QueryString["ID"].AsInt() > 0){
            movieId = Request.QueryString["ID"];
            var db = Database.Open("WebPagesMovies");
            var dbCommand = "SELECT * FROM Movies WHERE ID = @0";
            var row = db.QuerySingle(dbCommand, movieId);
            if(row != null) {
                title = row.Title;
                genre = row.Genre;
                year = row.Year;
            }
            else{
                Validation.AddFormError("No movie was found for that ID.");
                // If you are using a version of ASP.NET Web Pages 2 that's
                // earlier than the RC release, comment out the preceding
                // statement and uncomment the following one.
                //ModelState.AddFormError("No movie was found for that ID.");
            }
        }
        else{
            Validation.AddFormError("No movie was found for that ID.");
            // If you are using a version of ASP.NET Web Pages 2 that's
            // earlier than the RC release, comment out the preceding
            // statement and uncomment the following one.
            //ModelState.AddFormError("No movie was found for that ID.");
        }
    }

    if(IsPost && !Request["buttonDelete"].IsEmpty()){
        movieId = Request.Form["movieId"];
        var db = Database.Open("WebPagesMovies");
        var deleteCommand = "DELETE FROM Movies WHERE ID = @0";
        db.Execute(deleteCommand, movieId);
        Response.Redirect("~/Movies");
    }

}

<h2>Delete a Movie</h2>
@Html.ValidationSummary()
<p><a href="~/Movies">Return to movie listing</a></p>

<form method="post">
<fieldset>
<legend>Movie Information</legend>

<p><span>Title:</span>
    <span>@title</span></p>

<p><span>Genre:</span>
    <span>@genre</span></p>

<p><span>Year:</span>
    <span>@year</span></p>

<input type="hidden" name="movieid" value="@movieId" />
<p><input type="submit" name="buttonDelete" value="Delete Movie" /></p>
</fieldset>
</form>

“编辑影片页面”的完整页面列表 (更新布局)

@{
    Layout = "~/_Layout.cshtml";
    Page.Title = "Edit a Movie";

    var title = "";
    var genre = "";
    var year = "";
    var movieId = "";

    if(!IsPost){
        if(!Request.QueryString["ID"].IsEmpty() && Request.QueryString["ID"].IsInt()) {
            movieId = Request.QueryString["ID"];
            var db = Database.Open("WebPagesMovies");
            var dbCommand = "SELECT * FROM Movies WHERE ID = @0";
            var row = db.QuerySingle(dbCommand, movieId);

            if(row != null) {
                title = row.Title;
                genre = row.Genre;
                year = row.Year;
            }
            else{
                Validation.AddFormError("No movie was selected.");
                // If you are using a version of ASP.NET Web Pages 2 that's
                // earlier than the RC release, comment out the preceding
                // statement and uncomment the following one.
                //ModelState.AddFormError("No movie was selected.");
            }
        }
        else{
            Validation.AddFormError("No movie was selected.");
            // If you are using a version of ASP.NET Web Pages 2 that's
            // earlier than the RC release, comment out the preceding
            // statement and uncomment the following one.
            //ModelState.AddFormError("No movie was selected.");
        }
    }

    if(IsPost){
        Validation.RequireField("title", "You must enter a title");
        Validation.RequireField("genre", "Genre is required");
        Validation.RequireField("year", "You haven't entered a year");
        Validation.RequireField("movieid", "No movie ID was submitted!");

        title = Request.Form["title"];
        genre = Request.Form["genre"];
        year = Request.Form["year"];
        movieId = Request.Form["movieId"];

        if(Validation.IsValid()){
            var db = Database.Open("WebPagesMovies");
            var updateCommand = "UPDATE Movies SET Title=@0, Genre=@1, Year=@2 WHERE Id=@3";
            db.Execute(updateCommand, title, genre, year, movieId);
            Response.Redirect("~/Movies");
        }
    }
}

<h2>Edit a Movie</h2>
@Html.ValidationSummary()
<form method="post">
<fieldset>
    <legend>Movie Information</legend>

    <p><label for="title">Title:</label>
        <input type="text" name="title" value="@title" /></p>

    <p><label for="genre">Genre:</label>
        <input type="text" name="genre" value="@genre" /></p>

    <p><label for="year">Year:</label>
        <input type="text" name="year" value="@year" /></p>

    <input type="hidden" name="movieid" value="@movieId" />

    <p><input type="submit" name="buttonSubmit" value="Submit Changes" /></p>
</fieldset>
</form>
<p><a href="~/Movies">Return to movie listing</a></p>

即将上一步

在下一教程中,你将了解如何将网站发布到 Internet,以便每个人都可以看到它。

其他资源

  • 创建一致的外观 — 一篇文章提供有关使用布局的更多详细信息。 它还介绍如何将值传递给显示或隐藏部分内容的布局页面。
  • 使用 Razor 嵌套布局页面 — Mike Brind 博客中提供了有关如何嵌套布局页面的示例。 (包括 pages 的下载。)