【ASP.NET Core】第15章:使用 ASP.NET Core Razor Pages 构建网站

第15章:使用 ASP.NET Core Razor Pages 构建网站

本章介绍如何使用 Microsoft ASP.NET Core 在服务器端构建具有现代 HTTP 架构的网站。您将学习如何使用随 ASP.NET Core 2.0 引入的 ASP.NET Core Razor Pages 功能站 以及ASP. NET Core 2.1引入的 Razor 类库功能 构建简单的网站 Northwind.Web
本章将涵盖以下主题

  • 了解网络开发
  • 了解ASP.NET Core
  • 探索 ASP.NET Core Razor 页面
  • 使用 Entity Framework Core 和 ASP.NET Core
  • 使用razor class 库
  • 配置服务和 HTTP 请求管道

了解客户端 Web 开发技术

HTML5:它用于网页的内容和结构。
CSS3:这用于应用于网页上元素的样式。
JavaScript:它用于对网页上所需的任何业务逻辑进行编码>

Understanding ASP. NET Core

ASP. NET MVC 是 2009 年发布的,目的是将 web 开发人员的关注点清楚地分开,这些模型暂时存储数据; 视图,使用 UI 中的各种格式呈现数据; 控制器,获取模型并将其传递给视图。这种分离实现了改进的重用和单元测试

ASP.NET Core 于 2016 年发布,结合了 .NET Framework 技术,如 MVC、Web API 和 SignalR,以及更新的技术,如 Razor Pages、gRPC 和 Blazor,所有这些都在现代.NET上运行。 因此,它可以跨平台执行。 ASP.NET Core 有许多项目模板可帮助你开始使用其支持的技术。

Classic ASP. NET versus modern ASP. NET Core

ASP.NET Core 是对 ASP.NET 的重大重新设计.它消除了对系统的依赖。Web.d11 程序集和 IIS 以及由模块化的轻量级包组成,就像现代 NET 的其余部分一样。 ASP.NET Core 仍然支持使用 IIS 作为 Web 服务器。但有更好的选择。
您可以在 Windows、macOS 和 Linux 上跨平台开发和运行 ASP.NET Core 应用程序。 微软甚至创建了一个名为 Kestrel 的跨平台、超高性能的 Web 服务器,并且整个堆栈都是开源的

HTTPS 是 HTTP 的安全加密版本。


ASP.NET Core 可以从环境变量中读取以确定要使用的托管环境

例如 DOTNET_ENVIRONMENT 或 ASPNETCORE_ENVIRONMENT。
在 Northwind.Web 文件夹中,展开名为 Properties 的文件夹,打开名为 launchSettings.json 的文件,并记下名为 Northwind.Web 的配置文件,该配置文件将托管环境设置为 Development,如以下配置中突出显示的那样:
将 HTTP 的随机分配端口号更改为 5000,将 HTTPS 更改为 5001。

    "profiles": 
    {
    
    
    "Northwind.Web": {
    
    
      "commandName": "Project",
      "dotnetRunMessages": "true",
      "launchBrowser": true,
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
    
    
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },

将环境更改为Production。 或者,将 launchBrowser 更改为 false 以防止 Visual Studio 自动启动浏览器。


服务和管道的分离配置

  • ConfigureServices(IServiceCollection services):将依赖项服务添加到依赖项注入容器,例如 Razor Pages 支持、跨源资源共享 (CORS) 支持或用于使用 Northwind 数据库的数据库上下文。
  • Configure(IApplicationBuilder app, IWebHostEnvironment env):设置请求和响应流经的HTTP 管道。
  • 在 app 参数上调用各种 Use 方法,以按照应处理功能的顺序构建管道。Configure 方法设置HTTP 请求管道并启用端点路由。 它配置一个路由端点以等待请求,对于根路径的每个 HTTP GET 请求使用相同的映射/通过返回纯文本“Hello World!”来响应这些请求。

.NET Core项目结构

目录/文件 说明
依赖项 ASP.NET Core 开发、构建和运行过程中的依赖想,一般都是 NuGet 包和一些 SDK
Properties 配置,存放了一些 .json 文件用于配置 ASP.NET Core 项目
Propertics/launchSettings.json 启动配置文件,为一个 ASP.NET Core 应用保存特有的配置标准,用于应用的启动准备工作,包括环境变量,开发端口等
wwwroot 网站根目录,存放类似于 CSS、JS 和图片、还有 HTML 文件等静态资源文件的目录
Program.cs 这个文件包含了 ASP.NET Core 应用的 Main 方法,负责配置和启动应用程序
Startup.cs Startup.cs 文件是 ASP.NET Core 的项目的入口启动文件

在wwwroot目录下,添加一个index.html

HTML 简介-菜鸟教程

  • <!DOCTYPE html> 声明为 HTML5 文档
  • <html> 元素是 HTML 页面的根元素
  • <head> 元素包含了文档的元(meta)数据,如 <meta charset=“utf-8”> 定义网页编码格式为 utf-8。
  • <title> 元素描述了文档的标题
  • <body> 元素包含了可见的页面内容
  • <h1> 元素定义一个大标题
  • <p> 元素定义一个段落
  • <h2>~<h6> 第二-六级标题
  • HTML 链接 <a href=“https://www.baidu.com”> 这是一个链接</a>
  • HTML 图像 <img decoding=“async” src=“/images/logo.png” width=“258” height=“39” />
  • HTML 元素
开始标签 * 元素内容 结束标签 *
<p> 这是一个段落 </p>
<a href=“default.htm”> 这是一个链接 </a>
<br> 换行
  • HTML 属性: HTML 链接由 <a> 标签定义。链接的地址在 href 属性中指定
  • 分隔线 <hr>
  • <div align=“center” style=“border:1px solid red”>
    位于 div 元素中的文本,居中显示。
    </div>

启用静态和默认文件

启用静态文件,显式配置默认文件,并更改注册的 URL 路径以返回纯文本 Hello World! 回复

    app.UseDefaultFiles(); // index.html, default.html, and so on  
    app.UseStaticFiles(); 
    app.UseEndpoints(endpoints => {
    endpoints.MapGet("/hello", () => "Hello World!"); }); 

在 Chrome 中,输入 http://localhost:5000/hello 并注意它返回纯文本 Hello World!
如果所有网页都是静态的,也就是说,它们只能由网络编辑器手动更改,那么我们的网站编程工作就完成了。 但几乎所有网站都需要动态内容,这意味着通过执行代码在运行时生成的网页。 最简单的方法是使用名为 Razor Pages 的 ASP.NET Core 功能。


探索 ASP.NET Core Razor 页面

ASP.NET Core Razor Pages 允许开发人员轻松地将 C# 代码语句与 HTML 标记混合,使生成的网页动态化。 这就是他们使用 .cshtml 文件扩展名的原因。
按照惯例,ASP.NET Core 在名为 Pages 的文件夹中查找 Razor Pages。

启用 Razor 页面

  1. 在 Northwind.Web 项目文件夹中,创建一个名为 Pages 的文件夹。

  2. 将 index.html 文件复制到 Pages 文件夹中。

  3. 对于 Pages 文件夹中的文件,将文件扩展名从 .html 重命名为 .cshtml。

  4. 删除表示这是一个静态 HTML 页面的 <h2> 元素。

  5. 在Startup.cs中,在ConfigureServices方法中,添加一条语句,将ASP.NET Core Razor Pages及其相关服务,如模型绑定、授权、防伪、视图、标签助手等添加到构建器中,如图 在以下代码中

    services.AddRazorPages();

  6. 在Startup.cs中,在Configure方法中,在使用端点的配置中,添加调用MapRazorPages方法的语句,如下代码高亮显示:

    app.UseEndpoints(endpoints => {
            endpoints.MapRazorPages();
            endpoints.MapGet("/hello",  () => "Hello World!"); });  

将代码添加到 Razor 页面

在网页的 HTML 标记中,Razor 语法由 @ 符号表示。 Razor Pages 描述如下

  • 文件顶部需要 @page指令。

  • 他们可以选择有一个@functions 部分来定义以下任何内容:
    用于存储数据值的属性,例如在类定义中。 该类的实例自动实例化,命名为 Model,可以在特殊方法中设置其属性,您可以在 HTML 中获取属性值。
    名为OnGet、OnPost、OnDelete 等的方法在发出 HTTP 请求时执行,例如 GET、POST 和 DELETE。

  • 现在让我们将静态 HTML 页面转换为 Razor 页面:

    1. 在页面文件夹中,打开 index.cshtml。

    2. 将@page 语句添加到文件的顶部。

    3. 在@page语句之后,添加@functions语句块。

    4. 定义一个属性以将当天的名称存储为字符串值。

    5. 定义一个方法来设置DayName,在页面发起HTTP GET请求时执行,如下代码所示:

      @page
      
      @functions
      {
          public string? DayName { get; set; } //日期参数
      
          public void OnGet()
          {
              ViewData["Title"] = "Northwind B2B";
      
              Model.DayName = DateTime.Now.ToString("dddd");
          }
      }
      
    6. 在第二个 HTML 段落中输出日期名称,如以下标记中突出显示的那样:

      <p>It's @Model.DayName! Our customers include restaurants, hotels, and  cruise lines.</p>
      

将共享布局与 Razor Pages 结合使用

大多数网站都有不止一页。 如果每个页面都必须包含当前位于 index.cshtml 中的所有样板标记,那么管理起来会很痛苦。 因此,ASP.NET Core 有一个名为布局的功能。
要使用布局,我们必须创建一个 Razor 文件来定义所有 Razor 页面(和所有 MVC 视图)的默认布局,并将其存储在Shared文件夹中,以便按照约定轻松找到它。 该文件的名称可以是任何名称,因为我们会指定它,但 _Layout.cshtml 是一个很好的做法。
我们还必须创建一个专门命名的文件来设置所有 Razor 页面(和所有 MVC 视图)的默认布局文件。 此文件必须命名为 _ViewStart.cshtml

让我们看看实际的布局:

  1. 在 Pages 文件夹中,添加一个名为 _ViewStart.cshtml 的文件。 (Visual Studio 项模板名为 Razor View Start。)

  2. 修改其内容,如以下标记所示:

    @{
        Layout = "_Layout";
    }
    
  3. 在 Pages 文件夹中,创建一个名为 Shared 的文件夹。

  4. 在 Shared 文件夹中,创建一个名为 _Layout.cshtml 的文件。 (Visual Studio 项目模板名为 Razor Layout。)

  5. 修改 _Layout.cshtml 的内容(它类似于 index.cshtml,因此您可以从那里复制和粘贴 HTML 标记),如以下标记所示:

    <!doctype html>
    <html lang="en">
    
    <head>
    <!-- Required meta tags必需的元标记 -->
    <meta charset="utf-8" />
    <meta name="viewport" content=
        "width=device-width, initial-scale=1, shrink-to-fit=no" />
    
    <!-- Bootstrap CSS 样式 -->
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
    
    <title>@ViewData["Title"]</title>
    </head>
    
    <body>
        <div class="container">
            @RenderBody()
            <hr />
            <footer>
                <p>Copyright &copy; 2021 - @ViewData["Title"]</p>
                <!-- @ViewData["Title"] 是使用该布局的页面的变量 -->
            </footer>
        </div>
    
        <!-- JavaScript to enable features like carousel 启用轮播等功能的 JavaScript -->
        <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj" crossorigin="anonymous"></script>
    
        <!-- 在布局页,渲染名为 Scripts 的部分 -->
        @RenderSection("Scripts", required: false)
    
    </body>
    </html>
    

    查看前面的标记时,请注意以下事项:

    • <title> 是使用名为 ViewData 的字典中的服务器端代码动态设置的。 这是在 ASP.NET Core 网站的不同部分之间传递数据的简单方法。 在这种情况下,数据将设置在 Razor 页面类文件中,然后在共享布局中输出
    • @RenderBody() 标记被请求视图的插入点
    • 水平线页脚将出现在每页的底部。
    • 在布局的底部是一个脚本,用于实现我们稍后可以使用的一些很酷的 Bootstrap 功能,例如图像轮播。
    • 在 Bootstrap 的 <script> 元素之后,我们定义了一个名为 Scripts 的部分,以便 Razor 页面可以选择性地注入它需要的其他脚本。
  6. 修改 index.cshtml 以删除除 <div class=“jumbotron”> 及其内容之外的所有 HTML 标记,并将 C# 代码保留在您之前添加的 @functions 块中。

  7. OnGet 方法添加语句以将页面标题存储在 ViewData 字典中,并修改按钮以导航到供应商页面(我们将在下一节中创建),如以下标记中突出显示的那样:

    @page
    
    @functions
    {
        public string? DayName { get; set; } //日期参数
    
        public void OnGet()
        {
            ViewData["Title"] = "Northwind B2B";
    
            Model.DayName = DateTime.Now.ToString("dddd");
        }
    }
    <div class="jumbotron">
    <h1 class="display-3">Welcome to Northwind B2B</h1>
    <p class="lead">We supply products to our customers.</p>-
    <hr />
    <p>It's @Model.DayName! Our customers include restaurants, hotels, and cruise lines.</p>
    <p>
        <a class="btn btn-primary" href="suppliers">
        Learn more about our suppliers
        </a>
    </p>
    <p>
        <a class="btn btn-primary" href="packtfeatures/employees">
        Contact our employees
        </a>
    </p>
    <p>
        <a class="btn btn-primary" href="orders">
        How many orders have we taken?
        </a>
    </p>
    <p>
        <a class="btn btn-primary" href="customers">
        Our customers are global
        </a>
    </p>
    <p>
        <a class="btn btn-primary" href="functions">
        Play with functions
        </a>
    </p>
    </div>
    
  8. 启动该网站,使用 Chrome 访问它,注意它的行为与以前类似,尽管单击供应商按钮会出现 404 Not Found 错误,因为我们尚未创建该页面。

将代码隐藏文件与 Razor Pages 结合使用

有时,最好将 HTML 标记与数据和可执行代码分开,因此 Razor Pages 允许您通过将 C# 代码放在代码隐藏类文件中来做到这一点。 它们与 .cshtml 文件同名,但以 .cshtml.cs 结尾
您现在将创建一个显示供应商列表的页面。 在此示例中,我们将重点学习代码隐藏文件。 在下一个主题中,我们将从数据库中加载供应商列表,但现在,我们将使用硬编码的字符串值数组来模拟它:

    using Microsoft.AspNetCore.Mvc.RazorPages; // PageModel
    using Packt.Shared; // NorthwindContext
    using Microsoft.AspNetCore.Mvc; // [BindProperty], IActionResult

    namespace Northwind.Web.Pages; //Pages文件夹下

    public class SuppliersModel : PageModel
    {
    
       
        public IEnumerable<Supplier>? Suppliers {
    
     get; set; }//供应商查询结果
        //添加一个私有字段来存储 Northwind 数据库上下文,并添加一个构造函数来设置它
        private NorthwindContext db;//数据库上下文
        //构造函数: 初始化数据库上下文
        public SuppliersModel(NorthwindContext injectedContext)
        {
    
    
            db = injectedContext;
        }
        //从数据库上下文的 Suppliers 属性设置 Suppliers 属性
        public void OnGet() //处理控制器action方法的GET请求
        {
    
    
            ViewData["Title"] = "Northwind B2B - Suppliers";//设置字典中的变量Title
            //筛选供应商
            Suppliers = db.Suppliers
            .OrderBy(c => c.Country).ThenBy(c => c.CompanyName);
        }

        [BindProperty]
        public Supplier? Supplier {
    
     get; set; }// = null!;//属性:存储字符串集合

        public IActionResult OnPost() //提交新数据,更新后重定向
        {
    
    
            if ((Supplier is not null) && ModelState.IsValid)//获取一个值,该值指示此模型状态字典中的任何模型状态值是否无效或未经验证。
            {
    
    
                db.Suppliers.Add(Supplier);//添加供应商
                db.SaveChanges();
                return RedirectToPage("/suppliers");//重定向到页面,刷新数据
            }
            else
            {
    
    
                return Page(); // return to original page
            }
        }
    }

1.在 Pages 文件夹中,添加两个名为 Suppliers.cshtml 和供应商.cshtml.cs。 (Visual Studio 项模板名为 Razor Page - Empty,它会创建这两个文件。)
2.在名为Suppliers.cshtml.cs代码隐藏文件中添加语句,如下代码所示:
查看标记时,请注意以下事项:

  • SuppliersModel 继承自PageModel,因此它有ViewData 字典等成员用于共享数据。 您可以右键单击 PageModel 并选择 Go To Definition 以查看它具有更多有用的功能,例如当前请求的整个 HttpContext。
  • SuppliersModel 定义了一个属性,用于存储名为 Suppliers 的字符串值集合。
  • 当为此 Razor 页面发出 HTTP GET 请求时,Suppliers 属性会使用字符串值数组中的一些示例供应商名称进行填充。 稍后,我们将从 Northwind 数据库中填充它。

3.修改 Suppliers.cshtml 的内容,如下面的标记所示:
查看标记时,请注意以下事项:

  • 此 Razor 页面的模型类型设置为 SuppliersModel

  • 该页面输出带有 Bootstrap 样式的 HTML 表格。

  • 如果不为空,表中的数据行是通过循环遍历 Model 的Suppliers 属性生成的。

        @page
        @using Packt.Shared
        @model Northwind.Web.Pages.SuppliersModel
        @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
        <div class="row">
        <h1 class="display-2">Suppliers</h1>
        <table class="table">
            <thead class="thead-inverse">
            <tr>
                <th>Company Name</th>
                <th>Country</th> <!--为每个供应商呈现多个列-->
                <th>Phone</th>
            </tr>
            </thead>
            <tbody>
            @if (Model.Suppliers is not null)
            {
                @foreach(Supplier s in Model.Suppliers)
                {
                    <tr>
                    <td>@s.CompanyName</td>
                    <td>@s.Country</td>
                    <td>@s.Phone</td>
                    </tr>
                }
            }
            </tbody>
        </table>
        </div>
        <div class="row">
        <p>Enter details for a new supplier:</p>
        <form method="POST">
            <div><input asp-for="Supplier.CompanyName" placeholder="Company Name" /></div>
            <div><input asp-for="Supplier.Country" placeholder="Country" /></div>
            <div><input asp-for="Supplier.Phone" placeholder="Phone" /></div>
            <input type="submit" />
        </form>
        </div>
    

4.启动网站并使用 Chrome 访问它。


将 Entity Framework Core 与 ASP.NET Core 结合使用

Entity Framework Core 是将真实数据导入网站的自然方式。 在第 13 章,介绍 C# 和 .NET 的实际应用中,您创建了两对类库:一对用于实体模型,另一对用于 Northwind 数据库上下文,用于 SQL Server 或 SQLite 或两者。 您现在将在您的网站项目中使用它们。

将 Entity Framework Core 配置为服务

ASP.NET Core 所需的 Entity Framework Core 数据库上下文等功能必须在网站启动期间注册为服务。 GitHub 存储库解决方案和下面的代码使用 SQLite,但如果您愿意,也可以轻松使用 SQL Server。

让我们看看如何做:

  1. 在Northwind.Web 项目中,添加对Northwind.Common 的项目引用。 SQLite 或 SQL Server 的 DataContext 项目,如以下标记所示:

    <ProjectReference Include=“…\Northwind.Common.DataContext.Sqlite\ Northwind.Common.DataContext.Sqlite.csproj” />

  2. 构建 Northwind.Web 项目。

  3. 在 Startup.cs 中,导入命名空间以使用您的实体模型类型,如以下代码所示:

    using Packt.Shared; // AddNorthwindContext extension method

  4. 在ConfigureServices方法中添加一条语句,注册Northwind数据库上下文类,如下代码所示:

    services.AddNorthwindContext();

  5. 在 Northwind.Web 项目的 Pages 文件夹中,打开 Suppliers.cshtml.cs,并为我们的数据库上下文导入命名空间,如以下代码所示:

    using Packt.Shared; // NorthwindContext

…… 其他操作已在源码中注释


使用 Razor Pages 操作数据

您现在将添加插入新供应商的功能。

启用模型以插入实体

首先,您将修改供应商supplier model 模型,使其在访问者提交表单以插入新供应商时响应 HTTP POST 请求:

  1. 在 Northwind.Web 项目的 Pages 文件夹中,打开 Suppliers.cshtml.cs 并导入以下命名空间:
    using Microsoft.AspNetCore.Mvc; // [BindProperty], IActionResult

  2. 在 SuppliersModel 类中,添加一个属性来存储单个供应商和一个名为 OnPost 的方法,如果其模型有效,则将供应商添加到 Northwind 数据库中的 Suppliers 表中,如以下代码所示:

        [BindProperty]
        public Supplier? Supplier {
          
           get; set; }//添加了一个名为 Supplier 的属性,该属性使用 [BindProperty] 属性进行修饰,以便我们可以轻松地将网页上的 HTML 元素连接到 Supplier 类中的属性
    
        public IActionResult OnPost() //响应HTTP POST 请求的方法
        {
          
             //它检查所有属性值是否符合 Supplier 类实体模型的验证规则(例如 [Required] 和 [StringLength]
            if ((Supplier is not null) && ModelState.IsValid)//获取一个值,该值指示此模型状态字典中的任何模型状态值是否无效或未经验证。
            {
          
             //然后将供应商添加到现有表并将更改保存到数据库上下文
                db.Suppliers.Add(Supplier);//添加供应商
                //这将生成一个 SQL 语句来执行向数据库中的插入。 
                db.SaveChanges();
                //然后它重定向到供应商页面,以便访问者看到新添加的供应商。
                return RedirectToPage("/suppliers");//重定向到页面,刷新数据
            }
            else
            {
          
          
                return Page(); // return to original page
            }
        }
    

定义一个表单来插入一个新的供应商

接下来,您将修改 Razor 页面以定义访问者可以填写并提交以插入新供应商的表单:

  1. 在 Suppliers.cshtml 中,在 @model 声明之后添加标签助手,这样我们就可以在这个 Razor 页面上使用标签助手,例如 asp-for,如下标记所示:

    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

  2. 在文件底部,添加一个插入新供应商的表单,并使用asp for tag helper将Supplier类的CompanyName、Country和Phone属性绑定到输入框,如下标记所示 :

        <div class="row">
        <p>Enter details for a new supplier:</p>
        <form method="POST">
            <div><input asp-for="Supplier.CompanyName" placeholder="Company Name" /></div>
            <div><input asp-for="Supplier.Country" placeholder="Country" /></div>
            <div><input asp-for="Supplier.Phone" placeholder="Phone" /></div>
            <input type="submit" />
        </form>
        </div>
    
    
        <div class="row">
        <p>Enter details for a new supplier:</p>
        <form method="POST"> <!--带有 POST 方法的 <form> 元素是普通的 HTML-->
            <!--有名为 asp-for 的标记帮助程序的 <input> 元素支持数据绑定到 Razor 页面背后的模型。-->
            <div><input asp-for="Supplier.CompanyName" placeholder="Company Name" /></div>
            <div><input asp-for="Supplier.Country" placeholder="Country" /></div>
            <div><input asp-for="Supplier.Phone" placeholder="Phone" /></div>
            <input type="submit" /> <!--元素将使用该表单中任何其他元素的值向当前页面发出 HTTP POST 请求-->
        </form>
        </div>
    
  3. 启动网站。4. 单击了解有关我们供应商的更多信息,向下滚动到页面底部,输入 Bob’s Burgers, USA, and (603) 555-4567,然后单击提交。 5. 请注意,您会看到一个刷新的供应商表,其中添加了新的供应商。 6. 关闭 Chrome 并关闭网络服务器。


将依赖服务注入 Razor 页面

如果你有一个没有代码隐藏文件的 .cshtml Razor 页面,那么你可以使用 @inject指令 而不是构造函数参数注入来注入依赖服务,然后在中间使用 Razor 语法直接引用注入的数据库上下文 标记的

让我们创建一个简单的例子

  1. 在 Pages 文件夹中,添加一个名为 Orders.cshtml 的新文件。 (Visual Studio 项目
    模板名为 Razor Page - Empty,它会创建两个文件。 删除 .cs 文件。)

  2. 在 Orders.cshtml 中,编写代码以输出 Northwind 数据库中的订单数,如以下标记所示:

    @page
    @using Packt.Shared
    @inject NorthwindContext db 
    @{
    string title = "Orders";
    ViewData["Title"] = $"Northwind B2B - {title}";
    }
    <div class="row">
    <h1 class="display-2">@title</h1>
    <p>
        There are @db.Orders.Count() orders in the Northwind database.
    </p>
    </div>
    
  3. 启动网站。

  4. 导航到 /orders 并注意您看到 there are 830 orders in the Northwind database.

  5. 关闭 Chrome 并关闭网络服务器。


使用 Razor 类库

与 Razor 页面相关的所有内容都可以编译到类库中,以便在多个项目中更轻松地重用。 对于 ASP.NET Core 3.0 及更高版本,这可以包括静态文件(例如 HTML、CSS、JavaScript 库)和媒体资产(例如图像文件)。 网站可以使用类库中定义的 Razor 页面视图,也可以覆盖它。

创建 Razor 类库

让我们创建一个新的 Razor 类库:
使用您喜欢的代码编辑器添加新项目,如下表所定义:

1.项目模板:Razor类库/razorclasslib
2.复选框/switch: Support pages and views / -s
3.工作区/解决方案文件和文件夹:PracticalApps
4.项目文件和文件夹:Northwind.Razor.Employees

为 Visual Studio Code 禁用压缩文件夹

压缩文件夹功能是指如果层次结构中的中间文件夹不包含文件,则嵌套文件夹(例如/Areas/MyFeature/Pages/)以压缩形式显示

使用 EF Core 实现员工功能

现在我们可以添加对实体模型的引用,让员工显示在 Razor 类库中:

  1. 在Northwind.Razor.Employees 项目中,添加对Northwind 的项目引用。 SQLite 或 SQL Server 的 Common.DataContext 项目,请注意 SDK 是 Microsoft.NET.Sdk.Razor,如以下标记中突出显示的所示:

        <Project Sdk="Microsoft.NET.Sdk.Razor">
    
        <!-- change Sqlite to SqlServer if you prefer -->   
        <ItemGroup>
        <ProjectReference Include="..\Northwind.Common.DataContext.Sqlite \Northwind.Common.DataContext.Sqlite.csproj" />   
        </ItemGroup>
    
  2. 构建 Northwind.Razor.Employees 项目。

  3. 在 Areas 文件夹中,右击 MyFeature 文件夹,选择 Rename,输入新名称 PacktFeatures,回车。

  4. 在 PacktFeatures 文件夹的 Pages 子文件夹中,添加一个名为 _ViewStart.cshtml 的新文件。 (Visual Studio 项模板名为 Razor View Start。或者只是从 Northwind.Web 项目中复制它。)

  5. 修改 _ViewStart.cshtml 内容以通知此类库任何 Razor 页面都应查找与 Northwind.Web 项目中使用的名称相同的布局,如以下标记所示:

    @{
    Layout = “_Layout”;
    }

  6. 在 Pages 子文件夹中,将 Page1.cshtml 重命名为 Employees.cshtml,并将 Page1.cshtml.cs 重命名为 Employees.cshtml.cs

  7. 修改 Employees.cshtml.cs ,定义一个页面模型,其中包含从 Northwind 数据库加载的 Employee 实体实例数组,如下代码所示:

    using Microsoft.AspNetCore.Mvc.RazorPages; // PageModel
    using Packt.Shared; // Employee, NorthwindContext
    
    namespace PacktFeatures.Pages;
    
    public class EmployeesPageModel : PageModel
    {
          
          
        private NorthwindContext db;
    
        public EmployeesPageModel(NorthwindContext injectedContext)
        {
          
          
            db = injectedContext;
        }
    
        public Employee[] Employees {
          
           get; set; } = null!;// 员工实体实例数组
    
        public void OnGet()
        {
          
          
            ViewData["Title"] = "Northwind B2B - Employees";
            Employees = db.Employees.OrderBy(e => e.LastName)
            .ThenBy(e => e.FirstName).ToArray();
        }
    }
    
  8. 修改 Employees.cshtml,如以下标记所示:

    @page
    @using Packt.Shared
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 
    @model PacktFeatures.Pages.EmployeesPageModel
    <div class="row">
    <h1 class="display-2">Employees</h1>
    </div>
    <div class="row">
    @foreach(Employee employee in Model.Employees) //枚举
    {
    <div class="col-sm-3">
        <partial name="_Employee" model="employee" />
    </div>
    }
    </div>
    

    查看前面的标记时,请注意以下事项:

    • 我们导入 Packt.Shared 命名空间,以便我们可以使用其中的类,例如 Employee.
    • 我们添加了对标签助手的支持,以便我们可以使用 <partial> 元素。
    • 我们为此 Razor 页面声明 @model 类型使用您刚刚定义的页面模型类
    • 我们枚举模型中的 Employees,使用局部视图输出每个 Employees。

实现局部视图以显示单个员工

<partial> tag helper 标签助手是在 ASP.NET Core 2.1 中引入的。 局部视图就像 Razor 页面的一部分。 您将在接下来的几个步骤中创建一个以呈现单个员工

  1. 在 Northwind.Razor.Employees 项目的 Pages 文件夹中,创建一个 Shared 文件夹

  2. 在 Shared 文件夹中,创建一个名为 _Employee.cshtml 的文件。 (Visual Studio 项目模板名为 Razor View - Empty。)

  3. 修改 _Employee.cshtml,如以下标记所示:

    @model Packt.Shared.Employee
    <div class="card border-dark mb-3" style="max-width: 18rem;">
    <div class="card-header">@Model?.LastName, @Model?.FirstName</div>
    <div class="card-body text-dark">
        <h5 class="card-title">@Model?.Country</h5>
        <p class="card-text">@Model?.Notes</p>
    </div>
    </div>
    

    查看前面的标记时,请注意以下事项:

    • 按照惯例,partial views分部视图的名称以下划线开头
    • 如果您将局部视图放入共享文件夹,则可以自动找到它。
    • 此部分视图的模型类型是单个 Employee 实体。
    • 我们使用 Bootstrap 卡片样式来输出每个员工的信息。

使用和测试 Razor 类库

您现在将在网站项目中引用和使用 Razor 类库:

  1. 在 Northwind.Web 项目中,添加对 Northwind.Razor.Employees 项目的项目引用,如以下标记所示:

    <ProjectReference Include=“…\Northwind.Razor.Employees\Northwind.Razor.Employees.csproj”/>

  2. 修改 Pages\index.cshtml 以在链接到供应商页面之后添加一个段落,其中包含指向 Packt 功能员工页面的链接,如以下标记所示:

        <p>
            <a class="btn btn-primary" href="packtfeatures/employees">
            Contact our employees
            </a>
        </p>
    
  3. 启动网站,使用Chrome访问网站,点击Contact our employees按钮,可以看到员工名片


配置服务和 HTTP 请求管道

现在我们已经构建了一个网站,我们可以返回启动配置并更详细地查看服务和 HTTP 请求管道的工作方式。

认识端点路由 endpoint routing

在 ASP.NET Core 的早期版本中,路由系统和可扩展中间件系统并不总是能够轻松地协同工作; 例如,如果您想在中间件和 MVC 中实施诸如 CORS 之类的策略。 Microsoft 已通过 ASP.NET Core 2.2 引入的名为endpoint routing端点路由的系统投资改进路由。

良好实践:端点路由取代了 ASP.NET Core 2.1 及更早版本中使用的基于 IRouter 的路由。 如果可能,Microsoft 建议每个较旧的 ASP.NET Core 项目都迁移到endpoint routing终结点路由。

终结点路由旨在使需要路由的框架(例如 Razor Pages、MVC 或 Web API)与需要了解路由如何影响它们的中间件(例如本地化、授权、CORS 等)之间实现更好的互操作性。 端点路由之所以得名,是因为它将路由表表示为可由路由系统高效遍历的已编译端点树。 最大的改进之一是路由和操作方法选择的性能。

如果兼容性设置为 2.2 或更高版本,则它在 ASP.NET Core 2.2 或更高版本中默认打开。 使用 MapRoute 方法或使用属性注册的传统路由映射到新系统。
新的路由系统包括一个链接生成服务,注册为一个不需要 HttpContext 的依赖服务。

配置端点路由

端点路由需要对 UseRoutingUseEndpoints 方法进行一对调用:

  • UseRouting 标记做出路由决定的管道位置。
  • UseEndpoints 标记执行所选端点的管道位置。 在这些方法之间运行的中间件(例如本地化)可以看到选定的端点,并可以在必要时切换到不同的端点。
    终结点路由使用自 2010 年以来在 ASP.NET MVC 中使用的相同路由模板语法,以及 2013 年随 ASP.NET MVC 5 引入的 [Route] 属性。迁移通常只需要更改启动配置。
    MVC 控制器、Razor 页面和 SignalR 等框架过去是通过调用 UseMvc 或类似方法来启用的,但现在它们被添加到 UseEndpoints 方法调用中,因为它们都与中间件一起集成到同一个路由系统中。

查看我们项目中的端点路由配置

查看Startup.cs类文件,如下代码所示:

    public void ConfigureServices(IServiceCollection services)
    {
    
    
        services.AddRazorPages();
        services.AddNorthwindContext();
    }

Startup 类有两个方法,主机会自动调用它们来配置网站。
ConfigureServices 方法注册服务,然后可以在使用依赖注入需要它们提供的功能时检索这些服务。 我们的代码注册了两个服务:Razor PagesEF Core 数据库上下文

在 ConfigureServices 方法中注册服务

注册依赖服务的常用方法,包括结合其他注册服务的方法调用的服务,如下表所示:

  • AddMvcCore
    路由请求和调用控制器所需的最少服务集。 大多数网站将需要比这更多的配置。
  • AddAuthorization
    身份验证和授权服务。
  • AddDataAnnotations
    MVC 数据注释服务。
  • AddCacheTagHelper
    MVC 缓存标签助手服务
  • AddRazorPages
    Razor Pages 服务包括 Razor 视图引擎。 常用于简单的网站项目。 它调用以下附加方法:
    AddMvcCore AddAuthorization AddDataAnnotations AddCacheTagHelper
  • AddApiExplorer
    Web API 资源管理器服务
  • AddCors
    CORS 支持以增强安全性。
  • AddFormatterMappings
    URL 格式与其对应媒体类型之间的映射。
  • AddControllers
    控制器服务,但不是视图或页面的服务。 常用于 ASP.NET Core Web API 项目。 它调用以下附加方法:
    AddMvcCore AddAuthorization AddDataAnnotations AddCacheTagHelper AddApiExplorer AddCors AddFormatterMappings
  • AddViews
    支持 .cshtml 视图,包括默认约定。
  • AddRazorViewEngine
    支持 Razor 视图引擎,包括处理 @ 符号。
  • AddControllersWithViews
    控制器、视图和页面服务。 常用于ASP。 NET Core MVC 网站项目。 它调用以下附加方法:
    AddMvcCore AddAuthorization AddDataAnnotations AddCacheTagHelper AddApiExplorer AddCors AddFormatterMappings AddViews AddRazorViewEngine
  • AddMvc
    类似于 AddControllersWithViews,但你应该只将它用于向后兼容。
  • AddDbContext<T>
    您的 DbContext 类型及其可选的 DbContextOptions<TContext>。
  • AddNorthwindContext
    我们创建的自定义扩展方法可以更轻松地根据引用的项目为 SQLite 或 SQL Server 注册 NorthwindContext 类

在接下来的几章中使用 MVC 和 Web API 服务时,您将看到更多使用这些扩展方法注册服务的示例。

在 Configure 方法中设置 HTTP 请求管道

Configure 方法配置 HTTP 请求管道,它由连接的委托序列组成这些委托可以执行处理,然后决定自己返回响应或将处理传递给管道中的下一个委托。 返回的响应也可以被操纵
请记住,委托定义了委托实现可以插入的方法签名。 HTTP 请求管道的委托很简单,如以下代码所示:

    public delegate Task RequestDelegate(HttpContext context);

您可以看到输入参数是一个 HttpContext 。 这提供了对处理传入 HTTP 请求可能需要的所有内容的访问,包括 URL 路径、查询字符串参数、cookie 和用户代理

这些委托通常称为中间件,因为它们位于浏览器客户端网站或服务之间。
中间件委托使用以下方法之一或调用它们自身的自定义方法进行配置:

? Run:添加一个中间件委托,通过立即返回响应而不是调用下一个中间件委托来终止管道
? Map:添加一个中间件委托,当存在通常基于/hello 等URL 路径的匹配请求时,它会在管道中创建一个分支。
? Use:添加 构成管道一部分的 中间件委托,以便它可以决定是否要将请求传递给管道中的下一个委托,并且可以在下一个委托之前和之后修改请求和响应

为了方便起见,有许多扩展方法可以更轻松地构建管道,例如UseMiddleware<T>,其中 T 是一个类,它具有:

  1. 一个带有 RequestDelegate 参数的构造函数,将传递给下一个管道组件
  2. 有 HttpContext 参数的 Invoke 方法并返回一个 Task

关键中间件扩展方法总结

我们代码中使用的关键中间件扩展方法包括:
? UseDeveloperExceptionPage:捕获同步和异步系统。 来自管道的异常实例并生成 HTML 错误响应
? UseHsts:添加使用HSTS 的中间件,它添加了严格传输安全Strict-Transport Security标头。
? UseRouting:添加中间件,定义管道中的一个点,在该点做出路由决策,并且必须与对 UseEndpoints 的调用相结合,然后在其中执行处理。 这意味着对于我们的代码,任何匹配 / 或 /index 或 /suppliers 的 URL 路径都将映射到 Razor Pages,而 /hello 上的匹配将映射到匿名委托。 任何其他 URL 路径都将传递给下一个委托以进行匹配,例如静态文件。 这就是为什么,虽然看起来 Razor Pages 和 /hello 的映射发生在管道中的静态文件之后,但实际上它们具有优先级,因为 对UseRouting 的调用发生在 UseStaticFiles 之前
? UseHttpsRedirection:添加用于将 HTTP 请求重定向到 HTTPS 的中间件,因此在我们的代码中,对 http://localhost:5000 的请求将被修改为 https://localhost:5001
? UseDefaultFiles:添加在当前路径上启用默认文件映射的中间件,因此在我们的代码中它将识别 index.html 等文件。
? UseStaticFiles:添加在 wwwroot 中查找要在 HTTP 响应中返回的静态文件的中间件。
? UseEndpoints:添加要执行的中间件,以根据管道中早期做出的决策生成响应。 添加了两个端点,如以下子列表所示:

  • MapRazorPages:添加将 URL 路径(例如 /suppliers映射到名为 suppliers.cshtml 的 /Pages 文件夹中的 Razor 页面文件的中间件,并将结果作为 HTTP 响应返回 .
  • MapGet:添加将 URL 路径(例如 /hello映射到纯文本直接写入 HTTP 响应的内联委托的中间件。

可视化 HTTP 管道

HTTP 请求和响应管道可以可视化为一系列请求委托,一个接一个地调用,如下简化图所示,其中不包括一些中间件委托,例如 UseHsts

如前所述,UseRoutingUseEndpoints 方法必须一起使用。 虽然定义映射路由(例如 /hello)的代码是在 UseEndpoints 中编写的,但有关传入 HTTP 请求 URL 路径是否匹配以及因此执行哪个端点的决定是在管道中的 UseRouting 点做出的。


将匿名内联委托实现为中间件

可以将委托指定为内联匿名方法。 在为端点做出路由决策后,我们将注册一个插入管道
它将输出选择了哪个端点,以及处理一个特定的路由:/bonjour。 如果该路由匹配,它将以纯文本响应,而不会进一步调用管道:

  1. 在Startup.cs中,静态导入Console,如下代码所示:
    using static System.Console;
  2. 在调用UseRouting 之后和调用UseHttpsRedirection 之前添加语句以使用匿名方法作为中间件委托,如下代码所示
app.Use(async (HttpContext context, Func<Task> next) =>
{
    
       //表示可用于 URL 匹配或 URL 生成的 Microsoft.AspNetCore.Http.Endpoint。
    RouteEndpoint? rep = context.GetEndpoint() as RouteEndpoint;//获取当前请求的 Microsoft.AspNetCore.Http.Endpoint 的扩展方法
    if (rep is not null)
    {
    
    
        WriteLine($"Endpoint name: {
      
      rep.DisplayName}");//获取此端点的信息显示名称。
        WriteLine($"Endpoint route pattern: {
      
      rep.RoutePattern.RawText}");//获取解析路由模式时提供的原始文本。 可能为空。
    }

    if (context.Request.Path == "/bonjour")
    {
    
    
        // in the case of a match on URL path, this becomes a terminating
        // delegate that returns so does not call the next delegate
        // 在 URL 路径匹配的情况下,这将成为返回的终止委托,因此不会调用下一个委托
        await context.Response.WriteAsync("Bonjour Monde!");//你好世界
        return;
    }

    //我们可以在调用下一个委托之前修改请求 we could modify the request before calling the next delegate
    await next();
    //我们可以在调用下一个委托后修改响应 we could modify the response after calling the next delegate
});
  1. 启动网站。
  2. 在 Chrome 中,导航到 https://localhost:5001/ ,查看控制台输出并注意在端点路由/ 上有一个匹配项,它被处理为 /index,并执行了 Index.cshtml Razor 页面 返回响应
  3. 导航到 https://localhost:5001/suppliers 并注意您可以看到端点路由 /Suppliers 上存在匹配项,并且执行了 Suppliers.cshtml Razor 页面以返回响应
  4. 导航到 https://localhost:5001/index 并注意端点路由 /index 上存在匹配项,并且执行了 Index.cshtml Razor 页面以返回响应
  5. 导航到 https://localhost:5001/index.html 并注意没有输出写入控制台,因为端点路由没有匹配但静态文件有匹配,因此返回为 响应。 8. 导航到 https://localhost:5001/bonjour 并注意没有输出写入控制台,因为在端点路由上没有匹配项。 相反,我们的委托在 /bonjour 上匹配,直接写入响应流,并返回而不进行进一步处理。
  6. 关闭 Chrome 并关闭网络服务器。

总结

在本章中,您了解了使用 HTTP 进行 Web 开发的基础知识,如何构建一个返回静态文件的简单网站,以及使用 ASP.NET Core Razor Pages 和 Entity Framework Core 来创建根据信息动态生成的网页 一个数据库。
我们回顾了 HTTP 请求和响应管道、辅助扩展方法的作用以及如何添加自己的影响处理的中间件。
在下一章中,您将学习如何使用 ASP.NET Core MVC 构建更复杂的网站,它将构建网站的技术关注点分离为模型、视图和控制器,使它们更易于管理。

猜你喜欢

转载自blog.csdn.net/cxyhjl/article/details/130757566