将视图添加到 ASP.NET Core MVC 应用Add a view to an ASP.NET Core MVC app

作者:Rick AndersonBy Rick Anderson

在本部分中,将修改 HelloWorldController 类,进而使用 Razor 视图文件来顺利封装为客户端生成 HTML 响应的过程。In this section you modify the HelloWorldController class to use Razor view files to cleanly encapsulate the process of generating HTML responses to a client.

使用 Razor 创建视图模板文件。You create a view template file using Razor. 基于 Razor 的模板具有“.cshtml”文件扩展名 。Razor-based view templates have a .cshtml file extension. 它们提供了一种巧妙的方法来使用 C# 创建 HTML 输出。They provide an elegant way to create HTML output with C#.

当前,Index 方法返回带有在控制器类中硬编码的消息的字符串。Currently the Index method returns a string with a message that's hard-coded in the controller class. HelloWorldController 类中,将 Index 方法替换为以下代码:In the HelloWorldController class, replace the Index method with the following code:

public IActionResult Index()
{
    return View();
}

上面的代码调用控制器的 View 方法。The preceding code calls the controller's View method. 它使用视图模板来生成 HTML 响应。It uses a view template to generate an HTML response. 控制器方法(亦称为“操作方法” ,如上面的 Index 方法)通常返回 IActionResult(或派生自 ActionResult 的类),而不是 string 等类型。Controller methods (also known as action methods), such as the Index method above, generally return an IActionResult (or a class derived from ActionResult), not a type like string.

添加视图Add a view

  • 右键单击“视图”文件夹,然后单击“添加”>“新文件夹”,并将文件夹命名为“HelloWorld”。 Right click on the Views folder, and then Add > New Folder and name the folder HelloWorld.

  • 右键单击“Views/HelloWorld”文件夹,然后单击“添加”>“新项”。 Right click on the Views/HelloWorld folder, and then Add > New Item.

  • 在“添加新项 - MvcMovie”对话框中 In the Add New Item - MvcMovie dialog

    • 在右上角的搜索框中,输入“视图” In the search box in the upper-right, enter view

    • 选择“Razor 视图” Select Razor View

    • 保持“名称”框的值:Index.cshtml 。Keep the Name box value, Index.cshtml.

    • 选择“添加” Select Add

“添加新项”对话框

使用以下内容替换 Razor 视图文件 Views/HelloWorld/Index.cshtml 的内容 :Replace the contents of the Views/HelloWorld/Index.cshtml Razor view file with the following:

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>Hello from our View Template!</p>

导航到 https://localhost:xxxx/HelloWorldNavigate to https://localhost:xxxx/HelloWorld. HelloWorldController 中的 Index 方法作用不大;它运行 return View(); 语句,指定此方法应使用视图模板文件来呈现对浏览器的响应。The Index method in the HelloWorldController didn't do much; it ran the statement return View();, which specified that the method should use a view template file to render a response to the browser. 由于未指定视图模板文件名称,MVC 默认使用默认视图文件。Because a view template file name wasn't specified, MVC defaulted to using the default view file. 默认视图文件具有与方法 (Index) 相同的名称,因此在 /Views/HelloWorld/Index.cshtml 中使用。The default view file has the same name as the method (Index), so in the /Views/HelloWorld/Index.cshtml is used. 下面图片显示了视图中硬编码的The image below shows the string "Hello from our View Template!" 字符串“Hello from our View Template!”hard-coded in the view.

浏览器窗口

更改视图和布局页面Change views and layout pages

选择菜单链接(“MvcMovie”、“首页”和“隐私”) 。Select the menu links (MvcMovie, Home, and Privacy). 每页显示相同的菜单布局。Each page shows the same menu layout. 菜单布局是在 Views/Shared/_Layout.cshtml 文件中实现的 。The menu layout is implemented in the Views/Shared/_Layout.cshtml file. 打开 Views/Shared/_Layout.cshtml 文件 。Open the Views/Shared/_Layout.cshtml file.

布局模板使你能够在一个位置指定网站的 HTML 容器布局,然后将它应用到网站中的多个页面。Layout templates allow you to specify the HTML container layout of your site in one place and then apply it across multiple pages in your site. 查找 @RenderBody() 行。Find the @RenderBody() line. RenderBody 是显示创建的所有特定于视图的页面的占位符,已包装在布局页面中 。RenderBody is a placeholder where all the view-specific pages you create show up, wrapped in the layout page. 例如,如果选择“隐私”链接,Views/Home/Privacy.cshtml 视图将在 RenderBody 方法中呈现 。For example, if you select the Privacy link, the Views/Home/Privacy.cshtml view is rendered inside the RenderBody method.

  • 在标题和页脚元素中,将 MvcMovie 更改为 Movie AppIn the title and footer elements, change MvcMovie to Movie App.
  • 将定位点元素 <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">MvcMovie</a> 更改为 <a class="navbar-brand" asp-controller="Movies" asp-action="Index">Movie App</a>Change the anchor element <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">MvcMovie</a> to <a class="navbar-brand" asp-controller="Movies" asp-action="Index">Movie App</a>.

下列标记显示了突出显示的更改:The following markup shows the highlighted changes:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Movie App</title>

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"
              crossorigin="anonymous"
              integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="/>
    </environment>
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-controller="Movies" asp-action="Index">Movie App</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <partial name="_CookieConsentPartial" />
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2019 - Movie App - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>

    <environment include="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    </environment>
    <environment exclude="Development">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
                asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
                asp-fallback-test="window.jQuery"
                crossorigin="anonymous"
                integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
        </script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/js/bootstrap.bundle.min.js"
                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"
                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
                crossorigin="anonymous"
                integrity="sha256-E/V4cWE4qvAeO5MOhjtGtqDzPndRO1LBk8lJ/PR7CA4=">
        </script>
    </environment>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

在前面的标记中,省略了 asp-area 定位点标记帮助程序属性,因为此应用未使用区域In the preceding markup, the asp-area anchor Tag Helper attribute was omitted because this app is not using Areas.

说明Movies 控制器尚未实现。Note: The Movies controller has not been implemented. 此时,Movie App 链接不起作用。At this point, the Movie App link is not functional.

保存更改并选择“隐私”链接 。Save your changes and select the Privacy link. 请注意浏览器选项卡上的标题现在显示的是“隐私策略 - 电影应用” ,而不是“隐私策略 - Mvc 电影” :Notice how the title on the browser tab displays Privacy Policy - Movie App instead of Privacy Policy - Mvc Movie:

“隐私”选项卡

选择“主页”链接,请注意,标题和定位文本还会显示“电影应用” 。Select the Home link and notice that the title and anchor text also display Movie App. 我们能够在布局模板中进行一次更改,让网站上的所有页面都反映新的链接文本和新标题。We were able to make the change once in the layout template and have all pages on the site reflect the new link text and new title.

检查 Views/_ViewStart.cshtml 文件 :Examine the Views/_ViewStart.cshtml file:

@{
    Layout = "_Layout";
}

Views/_ViewStart.cshtml 文件将 Views/Shared/_Layout.cshtml 文件引入到每个视图中 。The Views/_ViewStart.cshtml file brings in the Views/Shared/_Layout.cshtml file to each view. 可以使用 Layout 属性设置不同的布局视图,或将它设置为 null,这样将不会使用任何布局文件。The Layout property can be used to set a different layout view, or set it to null so no layout file will be used.

更改 Views/HelloWorld/Index.cshtml 视图文件的 <h2> 元素 :Change the title and <h2> element of the Views/HelloWorld/Index.cshtml view file:

@{
    ViewData["Title"] = "Movie List";
}

<h2>My Movie List</h2>

<p>Hello from our View Template!</p>

稍稍对标题和 <h2> 元素进行一些更改,这样可以看出哪一段代码更改了显示。The title and <h2> element are slightly different so you can see which bit of code changes the display.

上述代码中的 ViewData["Title"] = "Movie List";ViewData 字典的 Title 属性设置为“Movie List”。ViewData["Title"] = "Movie List"; in the code above sets the Title property of the ViewData dictionary to "Movie List". Title 属性在布局页面中的 <title> HTML 元素中使用:The Title property is used in the <title> HTML element in the layout page:

<title>@ViewData["Title"] - Movie App</title>

保存更改并导航到 https://localhost:xxxx/HelloWorldSave the change and navigate to https://localhost:xxxx/HelloWorld. 请注意,浏览器标题、主标题和辅助标题已更改。Notice that the browser title, the primary heading, and the secondary headings have changed. (如果没有在浏览器中看到更改,则可能正在查看缓存的内容。(If you don't see changes in the browser, you might be viewing cached content. 在浏览器中按 Ctrl + F5 强制加载来自服务器的响应。)浏览器标题是使用我们在 Index.cshtml 视图模板中设置的 ViewData["Title"] 以及在布局文件中添加的额外“ - Movie App”创建的 。Press Ctrl+F5 in your browser to force the response from the server to be loaded.) The browser title is created with ViewData["Title"] we set in the Index.cshtml view template and the additional "- Movie App" added in the layout file.

还要注意,Index.cshtml 视图模板中的内容是如何与 Views/Shared/_Layout.cshtml 视图模板合并的,并且注意单个 HTML 响应被发送到了浏览器 。Also notice how the content in the Index.cshtml view template was merged with the Views/Shared/_Layout.cshtml view template and a single HTML response was sent to the browser. 凭借布局模板可以很容易地对应用程序中所有页面进行更改。Layout templates make it really easy to make changes that apply across all of the pages in your application. 若要了解更多信息,请参阅布局To learn more see Layout.

电影列表视图

但我们这一点点“数据”(在此示例中为“Hello from our View Template!”Our little bit of "data" (in this case the "Hello from our View Template!" 消息)是硬编码的。message) is hard-coded, though. MVC 应用程序有一个“V”(视图),而你已有一个“C”(控制器),但还没有“M”(模型)。The MVC application has a "V" (view) and you've got a "C" (controller), but no "M" (model) yet.

将数据从控制器传递给视图Passing Data from the Controller to the View

控制器操作会被调用以响应传入的 URL 请求。Controller actions are invoked in response to an incoming URL request. 控制器类是编写处理传入浏览器请求的代码的地方。A controller class is where the code is written that handles the incoming browser requests. 控制器从数据源检索数据,并决定将哪些类型的响应发送回浏览器。The controller retrieves data from a data source and decides what type of response to send back to the browser. 可以从控制器使用视图模板来生成并格式化对浏览器的 HTML 响应。View templates can be used from a controller to generate and format an HTML response to the browser.

控制器负责提供所需的数据,使视图模板能够呈现响应。Controllers are responsible for providing the data required in order for a view template to render a response. 最佳做法:视图模板不应该直接执行业务逻辑或与数据库进行交互 。A best practice: View templates should not perform business logic or interact with a database directly. 相反,视图模板应仅使用由控制器提供给它的数据。Rather, a view template should work only with the data that's provided to it by the controller. 保持此“分离关注点”有助于保持代码干净以及可测试性和可维护性。Maintaining this "separation of concerns" helps keep the code clean, testable, and maintainable.

目前,HelloWorldController 类中的 Welcome 方法采用 nameID 参数,然后将值直接输出到浏览器。Currently, the Welcome method in the HelloWorldController class takes a name and a ID parameter and then outputs the values directly to the browser. 应将控制器更改为使用视图模板,而不是使控制器将此响应呈现为字符串。Rather than have the controller render this response as a string, change the controller to use a view template instead. 视图模板会生成动态响应,这意味着必须将适当的数据位从控制器传递给视图以生成响应。The view template generates a dynamic response, which means that appropriate bits of data must be passed from the controller to the view in order to generate the response. 为此,可以让控制器将视图模板所需的动态数据(参数)放置在视图模板稍后可以访问的 ViewData 字典中。Do this by having the controller put the dynamic data (parameters) that the view template needs in a ViewData dictionary that the view template can then access.

在 HelloWorldController.cs 中,更改 Welcome 方法以将 MessageNumTimes 值添加到 ViewData 字典 。In HelloWorldController.cs, change the Welcome method to add a Message and NumTimes value to the ViewData dictionary. ViewData 字典是一个动态对象,这意味着可以使用任何类型;只有将内容放在其中后 ViewData 对象才具有定义的属性。The ViewData dictionary is a dynamic object, which means any type can be used; the ViewData object has no defined properties until you put something inside it. MVC 模型绑定系统自动将命名参数(namenumTimes)从地址栏中的查询字符串映射到方法中的参数。The MVC model binding system automatically maps the named parameters (name and numTimes) from the query string in the address bar to parameters in your method. 完整的 HelloWorldController.cs 文件如下所示 :The complete HelloWorldController.cs file looks like this:

using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;

namespace MvcMovie.Controllers
{
    public class HelloWorldController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult Welcome(string name, int numTimes = 1)
        {
            ViewData["Message"] = "Hello " + name;
            ViewData["NumTimes"] = numTimes;

            return View();
        }
    }
}

ViewData 字典对象包含将传递给视图的数据。The ViewData dictionary object contains data that will be passed to the view.

创建一个名为 Views/HelloWorld/Welcome.cshtml 的 Welcome 视图模板 。Create a Welcome view template named Views/HelloWorld/Welcome.cshtml.

在 Welcome.cshtml 视图模板中创建一个循环,显示“Hello” NumTimesYou'll create a loop in the Welcome.cshtml view template that displays "Hello" NumTimes. 使用以下内容替换 Views/HelloWorld/Welcome.cshtml 的内容 :Replace the contents of Views/HelloWorld/Welcome.cshtml with the following:

@{
    ViewData["Title"] = "Welcome";
}

<h2>Welcome</h2>

<ul>
    @for (int i = 0; i < (int)ViewData["NumTimes"]; i++)
    {
        <li>@ViewData["Message"]</li>
    }
</ul>

保存更改并浏览到以下 URL:Save your changes and browse to the following URL:

https://localhost:xxxx/HelloWorld/Welcome?name=Rick&numtimes=4

数据取自 URL,并传递给使用 MVC 模型绑定器的控制器。Data is taken from the URL and passed to the controller using the MVC model binder . 控制器将数据打包到 ViewData 字典中,并将该对象传递给视图。The controller packages the data into a ViewData dictionary and passes that object to the view. 然后,视图将数据作为 HTML 呈现给浏览器。The view then renders the data as HTML to the browser.

“隐私”视图,显示了 Welcome 标签以及四个“Hello Rick”短语

在上面的示例中,我们使用 ViewData 字典将数据从控制器传递给视图。In the sample above, the ViewData dictionary was used to pass data from the controller to a view. 稍后在本教程中,我们将使用视图模型将数据从控制器传递给视图。Later in the tutorial, a view model is used to pass data from a controller to a view. 传递数据的视图模型方法通常比 ViewData 字典方法更为优先。The view model approach to passing data is generally much preferred over the ViewData dictionary approach. 有关详细信息,请参阅何时使用 ViewBag、ViewData 或 TempDataSee When to use ViewBag, ViewData, or TempData for more information.

在下一个教程中,将创建电影数据库。In the next tutorial, a database of movies is created.