【C# 10 和 .NET 6】使用MVC模式构建网站(笔记1)

news2024/12/25 0:54:42

6e80e2a0fe8c129c4e20a3bfc7ea87f7.png

Building Websites Using the Model-View-Controller Pattern

使用模型-视图-控制器模式构建网站

本章介绍使用 Microsoft ASP.NET Core MVC 在服务器端构建具有现代 HTTP 架构的网站,包括构成 ASP.NET Core MVC 项目的启动配置、身份验证、授权、路由、请求和响应管道、模型、视图和控制器

本章将涵盖以下主题

  • 设置 ASP.NET Core MVC 网站

  • 探索 ASP.NET Core MVC 网站

  • 自定义 ASP.NET Core MVC 网站

  • 查询数据库和使用显示模板

  • 使用异步任务提高可扩展性


1. 设置 ASP.NET Core MVC 网站

ASP.NET Core Razor Pages 非常适合简单的网站。对于更复杂的网站,最好有一个更正式的结构来管理这种复杂性。
这就是模型-视图-控制器 (MVC) 设计模式的用武之地。它使用 Razor Pages 等技术,但允许更清晰地分离技术问题,如下表所示:

  • 模型(Models):表示网站上使用的 数据实体和视图模型的类。

  • 视图(Views):Razor 文件,即.cshtml 文件, 将视图模型中的数据呈现到HTML 网页中。Blazor 使用 .razor 文件扩展名,但不要将它们与 Razor 文件混淆!

  • 控制器(Controllers):当 HTTP 请求到达 Web 服务器时执行代码的类。控制器方法通常创建一个可能包含实体模型的视图模型并将其传递给视图以生成 HTTP 响应以发送回 Web 浏览器或其他客户端。


1.1 创建 ASP.NET Core MVC 网站

您将使用项目模板创建一个 ASP.NET Core MVC 网站项目,该项目具有用于对用户进行身份验证和授权的数据库。Visual Studio 2022 默认使用 SQL Server LocalDB 作为账户数据库。Visual Studio Code(或更准确地说是 dotnet 工具)默认使用 SQLite,您可以指定一个开关来改为使用 SQL Server LocalDB。

让我们看看它的实际效果:

  1. 使用您喜欢的代码编辑器添加一个 MVC 网站项目,其身份验证帐户存储在数据库中,如下表所定义:

    1. 项目模板:ASP.NET Core Web App (Model-View-Controller) / mvc

    2. 语言:C#

    3. 工作区/解决方案文件和文件夹:PracticalApps

    4. 项目文件及文件夹:Northwind.Mvc

    5. Options: 认证类型Authentication Type:个人账户 Individual Accounts / --auth Individual

    6. 对于 Visual Studio,将所有其他选项保留为默认值

    7. 在vs2022中,右键解决方案-新建项目-ASP.NET Core Web应用(模型-视图-控制器)

  2. 在 Visual Studio Code 中,选择 Northwind.Mvc 作为活动的 OmniSharp 项目。

  3. 构建 Northwind.Mvc 项目。

  4. 在命令行或终端,使用帮助开关查看此项目模板的其他选项,如下命令所示:
    dotnet new mvc --help

  5. 注意结果,如以下部分输出所示:

    ASP.NET Core Web App (Model-View-Controller) (C#)
    Author: Microsoft
    Description: A project template for creating an ASP.NET Core application with example ASP.NET Core MVC Views and Controllers. This template can also be used for RESTful HTTP services.
    This template contains technologies from parties other than Microsoft, see https://aka.ms/aspnetcore/6.0-third-party-notices for details.

有很多选项,尤其是与身份验证相关的选项,如下表所示:

开关描述
-au|--auth要使用的身份验证类型:无(默认):此选项还允许您禁用 HTTPS。 个人:将注册用户及其密码存储在数据库中的个人身份验证(默认为 SQLite)。我们将在为本章创建的项目中使用它。IndividualB2C:使用 Azure AD B2C 的个人身份验证。SingleOrg:单个租户的组织身份验证。MultiOrg:多租户的组织身份验证。Windows:Windows 身份验证。主要用于内部网。
-uld|--use-local-db是否使用 SQL Server LocalDB 而不是 SQLite。此选项仅在指定 --auth Individual 或 --auth IndividualB2C 时适用。该值是一个可选的布尔值,默认值为 false。
-rrc|--razor runtime-compilation确定项目是否配置为在调试版本中使用 Razor 运行时编译。这可以提高调试期间的启动性能,因为它可以延迟 Razor 视图的编译。该值是一个可选的布尔值,默认值为 false。
-f|--framework项目的目标框架。值可以是:net6.0(默认)、net5.0 或 netcoreapp3.1

1.2 为 SQL Server LocalDB 创建身份验证数据库

如果您使用 Visual Studio 2022 创建 MVC 项目,或者您使用带有 -uld 或 --use-local-db 开关的 dotnet new mvc,则用于身份验证和授权的数据库将存储在 SQL Server LocalDB 中。但是数据库还不存在。让我们现在创建它。在命令提示符或终端的Northwind.Mvc 文件夹中,输入命令以运行数据库迁移,以便创建用于存储身份验证凭据的数据库,如以下命令所示:

dotnet ef database update

如果您使用 dotnet new 创建 MVC 项目,则用于身份验证和授权的数据库将存储在 SQLite 中,并且该文件已创建名为 app.db。身份验证数据库的连接字符串名为 DefaultConnection,它存储在 MVC 网站项目根文件夹中的 appsettings.json 文件中。

对于 SQL Server LocalDB(带有截断的连接字符串),请参阅以下标记:

"ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet Northwind.Mvc-……;Trusted_Connection=True;MultipleActiveResultSets=true"
  },

对于 SQLite,请参阅以下标记:

{
  "ConnectionStrings": {
    "DefaultConnection": "DataSource=app.db;Cache=Shared"   },

1.3 浏览默认的 ASP.NET Core MVC 网站

让我们回顾一下默认的 ASP.NET Core MVC 网站项目模板的行为:

  1. 在 Northwind.Mvc 项目中,展开 Properties 文件夹,打开
    launchSettings.json 文件,并记下为 HTTPS 和 HTTP 项目配置的随机端口号(您的将不同),如以下标记所示:

    "profiles": {
            "WebApplication1": {
            "commandName": "Project",
            "dotnetRunMessages": true,
            "launchBrowser": true,
            "applicationUrl": "https://localhost:7000;http://localhost:5116",
            "environmentVariables": {
                "ASPNETCORE_ENVIRONMENT": "Development"
            }
        },
  2. 将 HTTPS 的端口号更改为 5001,HTTP 的端口号更改为 5000,如以下标记所示:

    "applicationUrl": "https://localhost:5001;http://localhost:5000",
  3. 将更改保存到 launchSettings.json 文件。

  4. 启动网站。

  5. 启动 Chrome 并打开开发者工具。

  6. 导航到 http://localhost:5000/ 并注意以下内容,如图 15.1 所示:

  • HTTP 请求自动重定向到端口 5001 上的 HTTPS。

  • 顶部导航菜单带有指向主页、隐私、注册和登录的链接。如果视口宽度为 575 像素或更小,则导航会折叠成汉堡菜单。

  • 网站标题 Northwind.Mvc,显示在页眉和页脚中。

4fd1e43d048f8041727fdbfedce800b6.png


1.4 了解访客登记visitor registration

默认情况下,密码必须至少包含一个非字母数字字符,必须至少包含一位数字 (0-9),并且必须至少包含一个大写字母 (A-Z)。当我只是探索时,我在这种情况下使用 Pa$$w0rd。
MVC 项目模板遵循双重选择加入 (DOI) 的最佳实践,这意味着在填写电子邮件和密码进行注册后,将向该电子邮件地址发送一封电子邮件,访问者必须单击该电子邮件中的链接进行确认 他们想注册。我们尚未配置电子邮件提供商来发送该电子邮件,因此我们必须模拟该步骤:

  1. 在顶部导航菜单中,点击注册。

  2. 输入电子邮件和密码,然后单击注册按钮。(我用了test@example.com 和 Pa$$w0rd。)

  3. 单击带有文本的链接单击此处以确认您的帐户,并注意您将被重定向到您可以自定义的确认电子邮件网页。

  4. 在顶部导航菜单中,点击登录,输入您的电子邮件和密码(请注意,有一个可选的复选框可以记住您,如果访问者忘记了密码或想注册为新访问者,则有链接), 然后单击登录按钮。

  5. 在顶部导航菜单中单击您的电子邮件地址。这将导航到帐户管理页面。请注意,您可以设置电话号码、更改电子邮件地址、更改密码、启用双因素身份验证(如果您添加了身份验证器应用程序)以及下载和删除您的个人数据。

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


1.5 审查 MVC 网站项目结构

在代码编辑器中,在 Visual Studio 解决方案资源管理器(打开显示所有文件)或 Visual Studio Code EXPLORER 中,查看 MVC 网站项目的结构,如图 15.2 所示:

7437f9a37ae69a88afe88dba92f4d81f.png

稍后我们将更详细地查看其中的一些部分,但现在请注意以下几点:

  • Areas:此文件夹包含嵌套文件夹和将您的网站项目与用于身份验证的 ASP.NET Core Identity 集成所需的文件。

  • bin、obj:这些文件夹包含构建过程中所需的临时文件和项目的已编译程序集。

  • Controllers:此文件夹包含 C# 类,这些类具有获取模型并将其传递给视图的方法(称为actions操作),例如 HomeController.cs。

  • Data:该文件夹包含 ASP.NET Core Identity系统使用的 Entity Framework Core 迁移类,为身份验证和授权提供数据存储,例如 ApplicationDbContext.cs。

  • Models:此文件夹包含 C# 类,这些类表示由控制器收集在一起并传递给视图的所有数据,例如 ErrorViewModel.cs。

  • Properties:此文件夹包含 IIS 或 Windows 上的 IIS Express 的配置文件并在名为 launchSettings.json 的开发期间启动网站。此文件仅在本地开发机器上使用,不会部署到您的生产网站。

  • Views:此文件夹包含 .cshtml Razor 文件,这些文件组合了 HTML 和 C# 代码以动态生成 HTML 响应。 _ViewStart 文件设置默认布局_ViewImports 导入所有视图中使用的通用命名空间,如标签助手:

    • Home:此子文件夹包含主页和隐私页面的 Razor 文件。

    • Shared:此子文件夹包含用于共享布局的 Razor 文件、错误页面以及用于登录和验证脚本的两个部分视图。

  • wwwroot:该文件夹包含 网站使用的静态内容,例如用于样式的CSSJavaScript 库、用于该网站项目的JavaScript 和一个favicon.ico 文件。您还可以将图像和其他静态文件资源(如 PDF 文档)放在这里。项目模板包括 Bootstrap 和 jQuery 库。

  • app.db:这是存储注册访客的SQLite 数据库。(如果您使用 SQL Server LocalDB,则不需要它。)

  • appsettings.json 和appsettings.Development.json:这些文件包含您的网站可以在运行时加载的设置,例如,ASP.NET Core 身份系统的数据库连接字符串和日志记录级别。

  • Northwind.Mvc.csproj:该文件包含项目设置,例如使用 Web .NET SDK、用于确保将 app.db 文件复制到网站输出文件夹的 SQLite 条目,以及您的 NuGet 包列表 项目要求,包括:

    • Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

    • Microsoft.AspNetCore.Identity.EntityFrameworkCore

    • Microsoft.AspNetCore.Identity.UI

    • Microsoft.EntityFrameworkCore.Sqlite or Microsoft.EntityFrameworkCore. SqlServer

    • Microsoft.EntityFrameworkCore.Tools

  • Program.cs:该文件定义了一个包含主入口点的隐藏程序类。它构建了一个管道来处理传入的 HTTP 请求,并使用默认选项托管网站,例如配置 Kestrel Web 服务器和加载应用程序设置。它添加和配置您的网站所需的服务,例如用于身份验证的 ASP.NET Core Identity,用于身份数据存储的 SQLite 或 SQL Server 等,以及您的应用程序的路由。


1.6 查看 ASP.NET Core 身份数据库

打开 appsettings.json 以查找用于 ASP.NET Core Identity 数据库的连接字符串,如以下标记中针对 SQL Server LocalDB 突出显示的那样:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-Northwind.Mvc-DC9C4FAF-DD84-4FC9-B925-69A61240EDA7;Trusted_Connection=True;MultipleActiveResultSets=true",
    "NorthwindConnection": "Server=.;Database=Northwind;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

如果您将 SQL Server LocalDB 用于身份数据存储,则可以使用服务器资源管理器连接到数据库。您可以从 appsettings.json 文件复制并粘贴连接字符串(但删除 (localdb) 和mssqllocaldb 之间的第二个反斜杠)。
如果您安装了 SQLite 工具,例如 SQLiteStudio,那么您可以打开 SQLite app.db 数据库文件。
然后,您可以看到 ASP.NET Core Identity 系统用于注册用户和角色的表,包括用于存储注册访问者的 AspNetUsers 表。

良好实践:ASP.NET Core MVC 项目模板通过存储密码的哈希而不是密码本身来遵循良好实践,您将在第 20 章“保护您的数据和应用程序”中了解更多信息。


2. 探索 ASP.NET Core MVC 网站

让我们来看看构成现代 ASP.NET Core MVC 网站的各个部分。

2.1 了解 ASP.NET Core MVC 初始化

恰如其分,我们将从探索 MVC 网站的默认初始化和配置开始:

//第一段
    using Microsoft.AspNetCore.Identity; // IdentityUser  
    using Microsoft.EntityFrameworkCore; // UseSqlServer, UseSqlite
    using Northwind.Mvc.Data; // ApplicationDbContext
    using Packt.Shared; // AddNorthwindContext extension method
    using System.Net.Http.Headers; // MediaTypeWithQualityHeaderValue
    using Northwind.Mvc.Hubs; // ChatHub
    //第二部分
    var builder = WebApplication.CreateBuilder(args);//使用预配置的默认值初始化 WebApplicationBuilder 类的新实例。

    //向容器中添加服务。Add services to the container.

    builder.Services.AddHttpClient(name: "Northwind.WebApi",
    configureClient: options =>
    {
        options.BaseAddress = new Uri("https://localhost:5002/");
        options.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue(
        "application/json", 1.0));
    });

    builder.Services.AddHttpClient(name: "Minimal.WebApi",
    configureClient: options =>
    {
        options.BaseAddress = new Uri("https://localhost:5003/");
        options.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue(
        "application/json", 1.0));
    });

    builder.Services.AddHttpClient(name: "Northwind.OData",
    configureClient: options =>
    {
        options.BaseAddress = new Uri("https://localhost:5004/");
        options.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue(
        "application/json", 1.0));
    });

    builder.Services.AddHttpClient(name: "Northwind.GraphQL",
    configureClient: options =>
    {
    options.BaseAddress = new Uri("https://localhost:5005/");
    options.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue(
        "application/json", 1.0));
    });

    builder.Services.AddSignalR();//将 SignalR 服务添加到指定的 IServiceCollection。

    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

    builder.Services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(connectionString));
    /*结合 UseDeveloperExceptionPage,这会捕获与数据库相关的异常,
    * 这些异常可以通过使用实体框架迁移来解决。当这些异常发生时,
    * 将生成一个 HTML 响应,其中包含有关解决问题的可能操作的详细信息。
    这应该只在开发环境中启用。*/
    builder.Services.AddDatabaseDeveloperPageExceptionFilter();

    builder.Services.AddDefaultIdentity<IdentityUser>(options => 
    options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>() // 启用角色管理enable role management
    .AddEntityFrameworkStores<ApplicationDbContext>();

    builder.Services.AddControllersWithViews();

    // if you are using SQL Server
    string sqlServerConnection = builder.Configuration
    .GetConnectionString("NorthwindConnection");
    //builder.Services.AddNorthwindContext(sqlServerConnection);

    // if you are using SQLite default is ..\Northwind.db
    builder.Services.AddNorthwindContext();

    var app = builder.Build();
    //第三部分
    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
    app.UseMigrationsEndPoint();
    }
    else
    {
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");

    app.MapRazorPages();

    app.MapHub<ChatHub>("/chat");
    //第四部分 // blocking call
    app.Run();
  1. 打开 Program.cs 文件,注意它使用了顶级程序特性(因此有一个带有 Main 方法的隐藏 Program 类)。这个文件可以认为从上到下分为四个重要的部分。

  • .NET 5 和更早版本的 ASP.NET Core 项目模板使用 Startup 类将这些部分分离到单独的方法中,但对于 .NET 6,Microsoft 鼓励将所有内容放在一个 Program.cs 文件中。

第一段导入一些命名空间,如代码所示:

  • 请记住,默认情况下,许多其他命名空间是使用 .NET 6 及更高版本的隐式使用功能导入的。构建项目,然后全局导入的命名空间可以在以下路径中找到:obj\Debug\net6.0\Northwind.Mvc.GlobalUsings.g.cs。

第二部分创建并配置 Web 主机构建器。它使用 SQL Server 或 SQLite 注册一个应用程序数据库上下文,其数据库连接字符串从 appsettings.json 文件加载用于其数据存储,添加 ASP.NET Core Identity 进行身份验证并将其配置为使用应用程序数据库,并添加对 带有视图的MVC控制器的支持 ,如代码所示:

builder对象有两个常用的对象:ConfigurationServices

对 AddDbContext 的调用是注册依赖服务的示例。ASP.NET Core 实现了依赖注入 (DI) 设计模式,以便控制器等其他组件可以通过其构造函数请求所需的服务。开发人员在 Program.cs 的这一部分注册这些服务(或者.NET 5 如果使用 Startup 类,则在其 ConfigureServices 方法中注册。)

  • Configuration 包含您可以设置配置的所有位置的合并值:appsettings.json、环境变量、命令行参数等

  • Services 是注册依赖服务的集合

第三部分配置HTTP请求管道。如果网站在开发中运行,它会配置一个相对 URL 路径来运行数据库迁移,或者为生产环境配置一个更友好的错误页面和 HSTS。启用HTTPS重定向、静态文件、路由 和 ASP.NET Identity,并配置一个MVC默认路由和Razor Pages,如代码所示:

最佳实践:扩展方法 UseMigrationsEndPoint 有什么作用?您可以阅读官方文档,但帮助不大。例如,它没有告诉我们它默认定义了什么相对 URL 路径:<https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.migrationsendpointextensions.usemigrationsendpoint. 幸运的是,ASP.NET Core 是开源的,因此我们可以通过以下链接阅读源代码并了解它的作用:https://github.com/dotnet/aspnetcore/blob/main/src/Middleware/Diagnostics.EntityFrameworkCore/src/MigrationsEndPointOptions.cs#L18。 养成探索 ASP.NET Core 源代码的习惯,以了解其工作原理。

除了 UseAuthentication 和 UseAuthorization 方法之外,Program.cs 的这一部分中最重要的新方法是 MapControllerRoute ,它映射了 MVC 使用的默认路由。此路由非常灵活,因为它将映射到几乎任何传入 URL,正如您将在下一主题中看到的那样。
虽然我们不会在本章中创建任何 Razor 页面,但我们需要保留映射 Razor 页面支持的方法调用,因为我们的 MVC 网站使用 ASP.NET Core Identity 进行身份验证和授权,并且它的用户界面使用 Razor 类库 组件,例如访客注册和登录

第四部分也是最后一部分有一个线程阻塞方法调用,该方法调用运行网站并等待传入的 HTTP 请求响应,如代码所示.


2.2 了解默认 MVC 路由(route)

路由的职责是发现要实例化的控制器类的名称要执行的操作方法,并使用可选的 id 参数传递到将生成 HTTP 响应的方法中
为MVC配置了一条默认路由,如下代码所示:

endpoints.MapControllerRoute(name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

路由模式(route pattern) 在大括号 {} 中 包含称为 segments 的部分,它们就像方法的命名参数。这些(segments) 段的值可以是任何字符串。URL 中的段 segments 不区分大小写
路由模式查看浏览器请求的任何 URL 路径并匹配它以提取控制器的名称操作的名称可选的 id 值(? 符号表示其可选)。

如果用户没有输入这些名称,它将使用控制器的默认值 Home 和操作的索引(= 赋值设置命名段的默认值)。
下表包含 示例 URL 以及默认路由如何计算出控制器和操作的名称:

URLControllerActionID
/HomeIndex
/MuppetMuppetIndex
/Muppet/KermitMuppetKermit
/Muppet/Kermit/GreenMuppetKermitGreen
/ProductsProductsIndex
/Products/DetailProductsDetail
/Products/Detail/3ProductsDetail3

2.3 理解控制器和动作

在 MVC 中,C 代表控制器。从路由和传入 URL 中,ASP.NET Core 知道控制器的名称,因此它将查找用 [Controller] 属性修饰的类或派生自用该属性修饰的类,例如, 微软提供的名为 ControllerBase 的类,如下代码所示:

namespace Microsoft.AspNetCore.Mvc {  
    //  
    // Summary:
    // A base class for an MVC controller without view support.  
    [Controller]
    public abstract class ControllerBase   { 
        ...

2.4 了解 ControllerBase 类

正如您在 XML 注释中看到的,ControllerBase 不支持视图。它用于创建 Web 服务,您将在第 16 章“构建和使用 Web 服务”中看到。
ControllerBase 有许多用于处理当前 HTTP 上下文的有用属性,如下表所示:

PropertyDescription
Request只是 HTTP 请求。例如,标头、查询字符串参数、作为您可以读取的流的请求正文、内容类型和长度,以及 cookie。
Response只是 HTTP 响应。例如,标头、作为您可以写入的流的响应主体、内容类型和长度、状态代码和 cookie。还有一些委托,如 OnStarting 和 OnCompleted,您可以将方法连接到它们。
HttpContext有关当前 HTTP 上下文的所有内容,包括请求和响应、有关连接的信息、已在服务器上使用中间件启用的功能集合以及用于身份验证和授权的用户对象。

2.5 了解控制器类

Microsoft 提供了另一个名为 Controller 的类,如果您的类确实需要视图支持,则可以从中继承该类,如以下代码所示:

namespace Microsoft.AspNetCore.Mvc {   
    //   
    // Summary:
    // 具有视图支持的 MVC 控制器的基类 A base class for an MVC controller with view support.   
    public abstract class Controller : ControllerBase,     IActionFilter, IFilterMetadata, IAsyncActionFilter, IDisposable   
    {
         ...

控制器具有许多用于处理视图的有用属性,如下表所示:

PropertyDescription
ViewData控制器可以在其中存储键/值对的字典可在视图中访问。字典的生命周期仅适用于当前请求/响应.
ViewBag包装 ViewData 以提供更友好的语法来设置和获取字典值的动态对象.
TempData控制器可以在其中存储键/值对的字典,可在视图中访问。字典的生命周期适用于当前请求/响应和同一访问者会话的下一个请求/响应。这对于在初始请求期间存储值、使用重定向进行响应,然后在后续请求中读取存储的值非常有用.

控制器有许多用于处理视图的有用方法,如下表所示:

PropertyDescription
View执行呈现完整响应的视图(例如,动态生成的网页)后返回 ViewResult。可以使用约定选择视图或使用字符串名称指定视图。可以将模型传递给视图.
PartialView执行作为完整响应一部分的视图后返回 PartialViewResult,例如,动态生成的 HTML 块。可以使用约定选择视图或使用字符串名称指定视图。可以将模型传递给视图.
ViewComponent执行动态生成 HTML 的组件后返回一个 ViewComponentResult。必须通过指定其类型或名称来选择组件。对象可以作为参数传递.
Json返回包含 JSON 序列化对象的 JsonResult。这对于将简单的 Web API 实现为 MVC 控制器的一部分非常有用,该控制器主要返回 HTML 供人们查看.

2.6 了解控制器的职责

控制器的职责如下:

• 确定控制器需要处于有效状态并在其类构造函数中正常运行的服务
• 使用action name动作名称来标识要执行的方法
• 从HTTP 请求中提取参数
• 使用参数获取构建视图模型所需的任何其他数据,并将其传递给客户端的适当视图。例如,如果客户端是 Web 浏览器,那么呈现 HTML 的视图将是最合适的。其他客户可能更喜欢其他呈现方式,例如 PDF 文件或 Excel 文件等文档格式,或 JSON 或 XML 等数据格式。
• 将视图的结果作为具有适当状态代码的HTTP 响应返回给客户端。

让我们回顾一下用于生成主页隐私错误页面的控制器:

  1. 展开 Controllers 文件夹

  2. 打开名为 HomeController.cs 的文件

  3. 请注意,如以下代码所示:
    • 导入了额外的命名空间,我已经添加了注释以显示需要它们的类型。
    • 声明私有只读字段以存储对在构造函数中设置的HomeController 的记录器logger的引用。
    • 所有三个操作方法都调用名为 View 的方法并将结果作为 IActionResult 接口返回给客户端。
    • Error 操作方法将一个视图模型传递到它的视图中,并带有用于跟踪的请求ID。错误响应不会被缓存:

using Microsoft.AspNetCore.Mvc; // Controller, IActionResult 
    using Northwind.Mvc.Models; // ErrorViewModel 
    using System.Diagnostics; // Activity 

    namespace Northwind.Mvc.Controllers;
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

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

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

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }

如果访问者导航到路径 / 或 /Home,那么它相当于 /Home/Index,因为它们是默认路由中控制器和操作的默认名称。


2.7 了解视图搜索路径约定

Index 和 Privacy 方法在实现上是相同的,但它们返回不同的网页。这是因为惯例。对 View 方法的调用在 Razor 文件的不同路径中查找以生成网页。

让我们故意打破其中一个页面名称,以便我们可以看到默认搜索的路径:

  1. 在 Northwind.Mvc 项目中,展开 Views 文件夹,然后展开 Home 文件夹。

  2. 将 Privacy.cshtml 文件重命名为 Privacy2.cshtml。

  3. 启动网站。

  4. 启动 Chrome,导航到 https://localhost:5001/ ,单击Privacy,并记下搜索视图以呈现网页的路径(包括在 Shared folders for MVC views 和 Razor Pages 中),如下所示 图 15.3:

    ee82b929608e9f7d5107b18a6f0b14d6.png

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

  6. 将 Privacy2.cshtml 文件重命名回 Privacy.cshtml。

您现在已经了解了视图搜索路径约定,如下表所示:
• 特定 Razor 视图:/Views/{controller}/{action}.cshtml
• 共享 Razor 视图:/Views/Shared/{action}.cshtml
• 共享 Razor 页面:/Pages/Shared/{action}.cshtml


2.8 了解日志记录logging

您刚刚看到一些错误被捕获并写入控制台。您可以使用记录器以相同的方式将消息写入控制台

1、在Controllers文件夹下,在HomeController.cs中,在Index方法中,添加语句使用logger向控制台写入一些不同级别的消息,如下代码所示:

_logger.LogError("This is a serious error (not really!)"); 
    _logger.LogWarning("This is your first warning!");
    _logger.LogWarning("Second warning!");
    _logger.LogInformation("I am in the Index method of the HomeController.");

2.启动Northwind.Mvc网站项目。
3. 启动网络浏览器并导航至该网站的主页。
4. 在命令提示符或终端,记下消息,如以下输出所示:

fail: Northwind.Mvc.Controllers.HomeController[0]       This is a serious error (not really!) 
    warn: Northwind.Mvc.Controllers.HomeController[0]       This is your first warning!
    warn: Northwind.Mvc.Controllers.HomeController[0]       Second warning!
    info: Northwind.Mvc.Controllers.HomeController[0]       I am in the Index method of the HomeController.
  1. 关闭 Chrome 并关闭网络服务器。


2.9 了解过滤器

当您需要向多个控制器和操作添加一些功能时,您可以使用或定义您自己的过滤器,这些过滤器被实现为一个属性类
过滤器可以应用于以下级别:

  • 操作级别,通过使用属性装饰操作方法。这只会影响一个动作方法。

  • 控制器级别,通过使用属性装饰控制器类。这将影响控制器的所有方法。

  • 全局级别,通过将属性类型添加到 MvcOptions 实例的 Filters 集合中,可在调用AddControllersWithViews 方法时用于配置 MVC,如以下代码所示:

//将控制器的服务添加到指定的 
    //Microsoft.Extensions.DependencyInjection.IServiceCollection。
    // 此方法不会注册用于页面的服务。
    builder.Services.AddControllersWithViews(options =>   
    {
        options.Filters.Add(typeof(MyCustomFilter));   //
    });

2.10 使用过滤器来保护操作方法

您可能希望确保控制器类的一种特定操作方法只能由特定安全角色的成员调用。您可以通过使用 [Authorize] 属性 装饰方法来实现这一点,如下表所述:

• [Authorize]:只允许经过身份验证(非匿名、登录)的访问者访问此操作方法
• [Authorize(Roles = "Sales,Marketing")]:只允许属于指定角色成员的访问者访问此操作方法

让我们看一个例子:

  1. 在 HomeController.cs 中,导入 Microsoft.AspNetCore.Authorization (授权) 命名空间。

  2. 向 Privacy 方法添加一个属性,以仅允许属于名为 Administrators 的组/角色成员的登录用户访问,如以下代码中突出显示的那样:

    [Authorize(Roles = "Administrators")] 
    public IActionResult Privacy()
  3. 启动网站。

  4. 单击 Privacy 并注意您将被重定向到登录页面

  5. 输入您的电子邮件和密码。

  6. 单击登录并注意您被拒绝访问

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


2.11 启用角色管理并以编程方式创建角色

默认情况下,在 ASP.NET Core MVC 项目中不启用角色管理,因此我们必须在创建角色之前首先启用它,然后我们将创建一个控制器,该控制器将以编程方式创建管理员角色(如果它尚不存在)和 将测试用户分配给该角色

  1. 在 Program.cs 中,在 ASP.NET Core Identity 及其数据库的设置中,添加对AddRoles 的调用以启用角色管理,如以下代码中突出显示的那样:

    services.AddDefaultIdentity<IdentityUser>(   
        options => options.SignIn.RequireConfirmedAccount = true)   
        .AddRoles<IdentityRole>() //  enable role management   
        .AddEntityFrameworkStores<ApplicationDbContext>();
  2. 在Controllers中,添加一个名为 RolesController.cs 的空控制器类,并修改其内容,如下代码所示:

    using Microsoft.AspNetCore.Identity; // RoleManager, UserManager
    using Microsoft.AspNetCore.Mvc; // Controller, IActionResult
    
    using static System.Console;
    
    namespace Northwind.Mvc.Controllers;
    
    public class RolesController : Controller
    {
        private string AdminRole = "Administrators";
        private string UserEmail = "test@example.com";
    
        private readonly RoleManager<IdentityRole> roleManager;//角色管理器
        private readonly UserManager<IdentityUser> userManager;//用户管理器
        // 获取并存储注册用户和角色管理器依赖服务
        public RolesController(RoleManager<IdentityRole> roleManager,
            UserManager<IdentityUser> userManager)
        {
            this.roleManager = roleManager;
            this.userManager = userManager;
        }
    
        public async Task<IActionResult> Index()
        {   //获取一个标志,该标志指示指定的 roleName=AdminRole 是否存在。
            if (!(await roleManager.RoleExistsAsync(AdminRole)))
            {   //在持久性存储中创建指定角色。
                await roleManager.CreateAsync(new IdentityRole(AdminRole));
            }
            //获取与指定电子邮件地址的规范化值关联的用户(如果有)。
            /// 注意:建议在使用该方法时将identityOptions.User.RequireUniqueEmail设置为true,否则如果有重复邮箱的用户,store可能会抛出。
            IdentityUser user = await userManager.FindByEmailAsync(UserEmail);
           
            if (user == null)
            { //如果管理员角色不存在,我们使用角色管理器来创建它。
                user = new();
                user.UserName = UserEmail;
                user.Email = UserEmail;
                //作为异步操作,使用给定密码在后台存储中创建指定用户。
                IdentityResult result = await userManager.CreateAsync(
                    user, "Pa$$w0rd");
    
                if (result.Succeeded)
                {
                    WriteLine($"User {user.UserName} created successfully.");
                }
                else
                {
                    foreach (IdentityError error in result.Errors)
                    {
                        WriteLine(error.Description);
                    }
                }
            }
            //我们尝试通过电子邮件查找测试用户,如果不存在则创建它,然后将用户分配给管理员角色。
            if (!user.EmailConfirmed)
            {   //为指定用户生成电子邮件确认令牌。
                string token = await userManager.GenerateEmailConfirmationTokenAsync(user);
                //验证电子邮件确认令牌是否与指定的匹配
                IdentityResult result = await userManager.ConfirmEmailAsync(user, token);
    
                if (result.Succeeded)
                {
                    WriteLine($"User {user.UserName} email confirmed successfully.");
                }
                else
                {
                    foreach (IdentityError error in result.Errors)
                    {
                        WriteLine(error.Description);
                    }
                }
            }
            // //用户是给定命名角色的成员?
            if (!(await userManager.IsInRoleAsync(user, AdminRole)))
            {   //将指定用户添加到命名角色
                IdentityResult result = await userManager.AddToRoleAsync(user, AdminRole);
    
                if (result.Succeeded)
                {
                    WriteLine($"User {user.UserName} added to {AdminRole} successfully.");
                }
                else
                {
                    foreach (IdentityError error in result.Errors)
                    {
                        WriteLine(error.Description);
                    }
                }
            }
    
            return Redirect("/");
        }
    }

    请注意以下事项 :
    • 角色名称和用户电子邮件的两个字段。
    • 构造函数获取并存储 注册用户和角色管理器依赖服务。
    • 如果管理员角色不存在,我们使用角色管理器来创建它。
    • 我们尝试通过电子邮件查找测试用户,如果不存在则创建它,然后将用户分配给管理员角色。
    • 由于网站使用DOI,我们必须生成一个电子邮件确认令牌并用它来确认新用户的电子邮件地址。
    • 成功消息和任何错误都写到控制台。
    • 您将被自动重定向到主页。

  3. 启动网站。

  4. 单击隐私并注意您将被重定向到登录页面。

  5. 输入您的电子邮件和密码。(我使用的是 mark@example.com。)

  6. 单击登录并注意您像以前一样被拒绝访问。

  7. 单击主页。

  8. 在地址栏中,手动输入roles 作为相对URL 路径,如下链接所示:https://localhost:5001/roles。

  9. 查看写入控制台的成功消息,如下输出所示:

    User test@example.com created successfully. 
    User test@example.com email confirmed successfully. 
    User test@example.com added to Administrators successfully.
  10. 单击注销Logout,因为您必须注销并重新登录才能在您登录后创建角色成员时加载它们。

  11. 再次尝试访问隐私页面,输入以编程方式创建的新用户的电子邮件地址,例如 test@example.com 及其密码,然后单击登录,您现在应该可以访问了。

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


2.12 使用过滤器缓存响应

为了缩短响应时间和提高可伸缩性,您可能希望通过使用 [ResponseCache] 属性修饰操作方法来缓存操作方法生成的 HTTP 响应。您可以通过设置参数来控制响应的缓存位置和缓存时间,如下表所示:

• Duration:以秒为单位。这将设置以秒为单位的 max-age HTTP 响应标头。常见的选择是一小时(3600 秒)和一天(86400 秒)。
• Location:ResponseCacheLocation 值之一,Any、Client 或 None。这将设置缓存控制 HTTP 响应标头。
• NoStore:如果为真,则忽略持续时间和位置并将缓存控制 HTTP 响应标头设置为无存储。

让我们看一个例子:

  1. 在 HomeController.cs 中,为 Index 方法添加一个属性(在浏览器 或 服务器和浏览器之间的任何代理上缓存响应 10 秒,如以下代码中突出显示的那样:

    [ResponseCache(Duration = 10, Location = ResponseCacheLocation.Any)] 
    public IActionResult Index()
  2. 在 Views 的 Home 中,打开 Index.cshtml,并添加一段以长格式输出包括秒的当前时间,如以下标记所示:

    <p class="alert alert-primary">@DateTime.Now.ToLongTimeString()</p>
  3. 启动网站。

  4. 注意主页上的时间。

  5. 单击注册。

  6. 单击主页并注意主页上的时间是相同的,因为使用了页面的缓存版本

  7. 单击注册。至少等待十秒钟。

  8. 单击“主页”并注意时间现已更新

  9. 单击登录,输入您的电子邮件和密码,然后单击登录。

  10. 注意主页上的时间。

  11. 单击隐私。

  12. 单击“主页”并注意该页面未缓存。

  13. 查看控制台并注意警告消息,说明您的缓存已被覆盖因为访问者已登录,在这种情况下,ASP.NET Core 使用防伪令牌,不应缓存它们,如下所示 输出:

    warn: Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery[8]       
        The 'Cache-Control' and 'Pragma' headers have been overridden and  
    set to 'no-cache, no-store' and 'no-cache' respectively to prevent caching  of this response. Any response that uses antiforgery should not be cached.
  14. 关闭 Chrome 并关闭网络服务器。


2.13 使用过滤器定义自定义路由

您可能希望为操作方法定义一个简化的路由,而不是使用默认路由。
例如,要显示隐私页面,当前需要以下 URL 路径,该路径同时指定了控制器和操作:
https://localhost:5001/home/privacy

我们可以使路由route 更简单,如以下链接所示:
https://localhost:5001/private

让我们看看如何做到这一点:

  1. 在HomeController.cs中,在 Privacy 方法中添加一个属性,定义一个简化路由,如下代码高亮显示:

    [Route("private")]
    [Authorize(Roles = "Administrators")]
    public IActionResult Privacy()
    {
        return View();
    }
  2. 启动网站。

  3. 在地址栏中,输入以下网址路径:https://localhost:5001/private

  4. 输入您的电子邮件和密码,单击登录,注意简化路径显示隐私页面。

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


2.14 了解实体和视图模型

在 MVC 中,M 代表模型。 模型表示响应请求所需的数据。常用的模型有两种:实体模型视图模型。实体模型表示数据库(如 SQL Server 或 SQLite)中的实体。根据请求,可能需要从数据存储中检索一个或多个实体。

Entity models (实体模型)是使用类定义的,因为它们可能需要更改,然后用于更新底层数据存储

我们想要显示响应请求的所有数据都是 MVC 模型(MVC model),有时称为视图模型(view model),因为它是传递到视图以呈现为响应格式(如 HTML 或 JSON)的模型。 视图模型应该是不可变的,因此它们通常使用记录来定义。

例如,以下 HTTP GET 请求可能意味着浏览器正在请求产品编号 3 的产品详细信息页面
http://www.example.com/products/details/3

控制器需要使用 ID 路由值 3 来检索该产品的实体将其传递给视图,然后该视图可以将模型转换为 HTML以便在浏览器中显示。
想象一下,当用户访问我们的网站时,我们希望向他们展示一个类别轮播(a carousel of categories)、产品列表以及本月访问者数量的计数。

我们将引用您在第 13 章“介绍 C# 和 .NET 的实际应用”中创建的 Northwind 数据库的 Entity Framework Core 实体数据模型:

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

    <ItemGroup>
    <!-- change Sqlite to SqlServer if you prefer -->   
    <ProjectReference Include=
    "..\Northwind.Common.DataContext.Sqlite\Northwind.Common.DataContext. Sqlite.csproj" /> 
    </ItemGroup>
  2. 构建 Northwind.Mvc 项目以编译其依赖项。

  3. 如果您使用的是 SQL Server,或者可能想要在 SQL Server 和 SQLite 之间切换,则在 appsettings.json 中,为使用 SQL Server 的 Northwind 数据库添加一个连接字符串,如以下标记中突出显示的那样:

    "ConnectionStrings": {
        //身份验证数据库的连接字符串
        "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-Northwind.Mvc-DC9C4FAF-DD84-4FC9-B925-69A61240EDA7;Trusted_Connection=True;MultipleActiveResultSets=true",
        "NorthwindConnection": "Server=.;Database=Northwind;Trusted_Connection=True;MultipleActiveResultSets=true"
    },
  4. 在 Program.cs 中,导入命名空间以使用您的实体模型类型,如以下代码所示:

    using Packt.Shared; // AddNorthwindContext extension method
  5. 调用 builder.Build 方法之前,添加语句以加载适当的连接字符串,然后注册 Northwind 数据库上下文,如以下代码所示:

    // 如果您使用的是 SQL Serverif you are using SQL Server
    string sqlServerConnection = builder.Configuration
    .GetConnectionString("NorthwindConnection");
    builder.Services.AddNorthwindContext(sqlServerConnection);
    
    //如果你使用的是 SQLite 默认是 ..\Northwind.db  if you are using SQLite default is ..\Northwind.db
    builder.Services.AddNorthwindContext();
  6. 在Models文件夹中添加一个class文件,命名为HomeIndexViewModel.cs。

    良好实践:虽然 MVC 项目模板创建的 ErrorViewModel 类不遵循此约定,但我建议您对视图模型类使用命名约定 {Controller}{Action} ViewModel。

  7. 修改语句,定义一条记录,记录有访问人数统计、分类和商品列表三个属性,如下代码所示:

    using Packt.Shared; // Category, Product
    
    namespace Northwind.Mvc.Models;
    
    public record HomeIndexViewModel
    (
        int VisitorCount,
        IList<Category> Categories,
        IList<Product> Products
    );
  8. 在HomeController.cs中,导入Packt.Shared命名空间,如下代码所示:

    using Packt.Shared; // NorthwindContext
  9. 添加一个字段来存储对 Northwind 实例的引用,并在构造函数中对其进行初始化,如以下代码中突出显示的所示:

    private readonly ILogger<HomeController> _logger;
    private readonly NorthwindContext db;
    private readonly IHttpClientFactory clientFactory;
    
    public HomeController(ILogger<HomeController> logger,
        NorthwindContext injectedContext,
        IHttpClientFactory httpClientFactory)
    {
        _logger = logger;
        db = injectedContext;
        clientFactory = httpClientFactory;
    }

    ASP.NET Core 将使用构造函数参数注入,使用您在 Program.cs 中指定的连接字符串传递 NorthwindContext 数据库上下文的实例

  10. 修改 Index 动作方法中的语句,为该方法创建视图模型的实例,使用 Random 类模拟访问者计数生成 1 到 1000 之间的数字,并使用 Northwind 数据库获取类别列表和 产品,然后将模型传递给视图,如以下代码中突出显示的那样:

    [ResponseCache(Duration = 10, Location = ResponseCacheLocation.Any)]
    public async Task<IActionResult> Index()
    {
        _logger.LogError("This is a serious error (not really!)");
        _logger.LogWarning("This is your first warning!");
        _logger.LogWarning("Second warning!");
        _logger.LogInformation("I am in the Index method of the HomeController.");
    
        HomeIndexViewModel model = new
        (
            VisitorCount: (new Random()).Next(1, 1001),
            Categories: await db.Categories.ToListAsync(),
            Products: await db.Products.ToListAsync()
        );
        return View(model); // pass model to view
    }

记住视图搜索约定:当在控制器的操作方法中调用 View 方法时,ASP.NET Core MVC 在 Views 文件夹中查找与当前控制器同名的子文件夹,即 Home。 然后它会查找与当前操作同名的文件,即 Index.cshtml。它还将在 Shared 文件夹中搜索与操作方法名称匹配的视图,并在 Pages 文件夹中搜索 Razor Pages


2.15 理解视图

在 MVC 中,V 代表视图。 视图的职责是将模型转换为 HTML 或其他格式
多个视图引擎可用于执行此操作。 默认的视图引擎称为 Razor,它使用 @ 符号来指示服务器端代码执行。ASP.NET Core 2.0 引入的 Razor Pages 功能使用相同的视图引擎,因此可以使用相同的 Razor 语法。
让我们修改主页视图以呈现类别和产品列表:

  1. 展开 Views 文件夹,然后展开 Home 文件夹

  2. 打开 Index.cshtml 文件并注意包裹在@{} 中的C# 代码块。这将首先执行,可用于 存储需要传递到共享布局文件中的数据,如网页标题,如下代码所示:

    @{
    ViewData["Title"] = "Home Page";  
    }
  3. 注意**<div>** 元素中的静态 HTML 内容使用Bootstrap 进行样式设置

    良好做法:除了定义您自己的样式外,您还可以将样式基于实现响应式设计的通用库,例如 Bootstrap。

    与 Razor Pages 一样,有一个名为 _ViewStart.cshtml 的文件由 View 方法执行。它用于设置适用于所有视图的默认值。
    例如,它将所有视图的 Layout 属性设置为共享布局文件,如以下标记所示:

    @{
        Layout = "_Layout";
    }
  4. 在Views文件夹中,打开**_ViewImports.cshtml文件,注意它导入了一些命名空间**,然后添加了ASP.NET Core tag helpers标签助手,如下代码所示:

    @using Northwind.Mvc
    @using Northwind.Mvc.Models
    @addTagHelper *, **Microsoft.AspNetCore.Mvc.TagHelpers**
  5. 在 Shared 文件夹中,打开 _Layout.cshtml 文件。

  6. 请注意,标题是从先前在 Index.cshtml 视图中设置的 ViewData 字典中读取的,如以下标记所示:

    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Northwind.Mvc</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    </head>
  7. 注意支持 Bootstrap 和 站点样式表 的渲染链接,其中 ~ 表示 wwwroot 文件夹,如以下标记所示:

  8. 注意标题中导航栏的渲染,如以下标记所示:

    <body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
    </header>
    </body>
  9. 注意可折叠 <div> 的渲染,其中包含用于登录的部分视图和超链接,以允许用户使用具有 asp-controller 和 asp-action 等属性的 ASP.NET Core 标记助手在页面之间导航,如下所示 标记:

    <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
        <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>
            <li class="nav-item">
            <a class="nav-link text-dark" asp-area=""
                asp-controller="Home" asp-action="Services">Services</a>
            </li>
            <li class="nav-item">
            <a class="nav-link text-dark" asp-area=""
                asp-controller="Home" asp-action="Chat">Chat</a>
            </li>
        </ul>
        <partial name="_LoginPartial" />
    </div>

    HTML 标签:
    <a> 定义超文本链接
    <area> 定义图像映射内部的区域
    <b> 定义文本粗体
    <code> 定义计算机代码文本
    <button> 定义一个点击按钮
    <col> 定义表格中一个或多个列的属性值
    <h1> to <h6> 定义 HTML 标题
    <hr> 定义水平线
    <head> 定义关于文档的信息
    <header> 定义了文档的头部区域
    <input> 定义输入控件
    <label> 定义 input 元素的标注
    <li> 定义列表的项目 (listitem)
    <link> 定义文档与外部资源的关系
    <main> 定义文档的主体部分
    <mark> 定义带有记号的文本。请在需要突出显示文本时使用 <em> 标签。
    <menu> 不赞成使用。定义菜单列表。
    <meta> 定义关于 HTML 文档的元信息。
    <nav> 定义导航链接的部分
    <p> 定义段落
    <s> 定义加删除线的文本
    <script> 定义客户端脚本
    <section> <section> 标签定义文档中的节(section、区段)。比如章节、页眉、页脚或文档中的其他部分
    <select> 定义选择列表(下拉列表)
    <small> 定义小号文本
    <source> <source> 标签为媒介元素(比如 <video> 和 <audio>)定义媒介资源。
    <span> 定义文档中的节。
    <strong> 定义强调文本。
    <style> 定义文档的样式信息
    <sub> 定义下标文本
    <sup> 定义上标文本
    <table> 定义表格
    <tbody> 定义表格中的主体内容
    <td> 定义表格中的单元
    <textarea> 定义多行的文本输入控件
    <tfoot> 定义表格中的表注内容(脚注)
    <th> 定义表格中的表头单元格
    <thead> 定义表格中的表头内容
    <time> 定义日期或时间,或者两者
    <title> 定义文档的标题
    <tr> 定义表格中的行
    <u> 定义下划线文本
    <ul> 定义无序列表
    <var> 定义文本的变量部分
    <video> <video> 标签定义视频,比如电影片段或其他视频流
    <wbr> 规定在文本中的何处适合添加换行符
    <!-- --> 定义注释

    <a> 元素使用名为 asp-controller 和 asp-action 的tag helper 属性来指定单击链接时将执行的控制器名称和操作名称。如果你想导航到 Razor 类库中的一个功能,比如你在上一章中创建的员工组件,那么你可以使用 asp-area 来指定功能名称。

  10. 注意 <main> 元素内body的渲染,如以下标记所示:

    <div class="container">
        <main role="main" class="pb-3">
        @RenderBody()
        </main>
    </div>

    RenderBody 方法在共享布局中的那个点 为页面注入特定 Razor 视图的内容,例如 Index.cshtml 文件。

  11. 注意页面底部 <script> 元素的渲染,这样不会减慢页面的显示速度,并且可以将自己的脚本块添加到名为scripts的可选定义部分中,如下标记所示:

    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @await RenderSectionAsync("Scripts", required: false)

    当 asp-append-version 在任何元素(如 <img> 或 <script> 以及 src 属性中指定为true值时,将调用图像标记帮助(Image Tag Helper)程序(此帮助程序的名称很糟糕,因为它不仅影响图像!).
    它的工作原理是自动附加一个名为 v 的查询字符串值,该值是从引用的源文件的哈希生成的,如以下示例生成的输出所示:

    <script src="~/js/site.js? v=Kl_dqr9NVtnMdsM2MUg4qthUnWZm5T1fCEimBPWDNgM"></script>

    即使 site.js 文件中的单个字节发生变化,那么它的哈希值也会不同,因此如果浏览器或 CDN 正在缓存脚本文件,那么它将破坏缓存的副本并将其替换为新版本。

The End

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/600244.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何设计和使用文档模板 | 技术写作什么鬼

今天看到叶伟民老师的一篇文章&#xff0c;瞬间泪目&#xff1a;叶老师&#xff0c;您是懂人性的啊。在我整天鞭策自己“不能再拖了”的关键时刻&#xff0c;及时分享经验&#xff1a; 是的&#xff0c;这篇文章实在是拖了太久&#xff0c;了太久&#xff0c;太久&#xff0c;久…

web前端 --- javascript(03) -- 函数、内置对象

函数&#xff08;function&#xff09; 具有名称的&#xff0c;为了实现特定功能的代码集合体 &#xff08;1&#xff09;javascript如何定义函数&#xff1a;function关键字定义 function 函数名称 &#xff08;[ 参数列表 ]&#xff09;{ // 函数体 // [return 返回值]…

【Springboot】发送QQ邮件

系列文章目录 文章目录 系列文章目录前言添加Maven依赖QQ邮箱开启POP服务配置application.properties文件Controller层编写 vue前端&#xff08;也可以直接省略&#xff09; 前言 这篇博客用于简单实现SpringBoot中使用Controller发送邮件请求&#xff0c;用户可以收到邮件。 …

Python读写access数据库的实战代码

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

chatgpt赋能python:使用Python关闭端口的方法

使用Python关闭端口的方法 在网络安全中&#xff0c;关闭端口是非常重要的一项任务。一旦一个端口被打开并暴露给互联网&#xff0c;恶意用户就可能通过它们的攻击进入您的服务器或计算机系统。Python是一种流行的编程语言&#xff0c;也可以用来关闭端口。下面介绍一些常用的…

JSONSQL:使用SQL过滤JSON类型数据(支持多种数据库常用查询、统计、平均值、最大值、最小值、求和语法)...

1. 简介 在开发中&#xff0c;经常需要根据条件过滤大批量的JSON类型数据。如果仅需要过滤这一种类型&#xff0c;将JSON转为List后过滤即可&#xff1b;如果相同的条件既想过滤数据库表中的数据、也想过滤内存中JSON数据&#xff0c;甚至想过滤Elasticsearch中的数据&#xff…

chatgpt赋能python:Python内置变量:掌握这些变量,让你的编程更高效

Python内置变量&#xff1a;掌握这些变量&#xff0c;让你的编程更高效 Python作为一门优秀的编程语言&#xff0c;自然不会缺少重要的内置变量。这些内置变量可以帮助程序员轻松地实现各种编程功能&#xff0c;提高编程效率。在本文中&#xff0c;我们将介绍Python内置变量的…

chatgpt赋能python:Python内置函数使用指南

Python内置函数使用指南 Python是一种高级编程语言&#xff0c;得益于其简单易学的语法、强大的标准库和丰富的第三方模块&#xff0c;现已成为全球最受欢迎的编程语言之一。其中&#xff0c;Python内置函数是Python编程的重要组成部分&#xff0c;本文将为您介绍这些内置函数…

四种主要的IO模型

基本概念 基本概念阻塞IO指的是需要内核IO操作彻底完成后&#xff0c;才返回到用户空间执行用户的操作。阻塞指的是用户空间程序的执行状态。传统的IO模型都是同步阻塞IO。在Java中&#xff0c;默认创建的socket都是阻塞的。简单来说&#xff1a;阻塞是指用户空间&#xff08…

尚硅谷-云尚办公-项目复盘

尚硅谷-云尚办公-项目复盘 资料地址本文介绍问题汇总问题1.knife4j无法下载 视频4问题2.dev等含义 视频5问题3.wrapper继承/实现图 视频8问题4.修改统一返回结果 视频11问题5.修改后新增也变修改 视频29问题6.redis中key值乱码 视频55-60问题7.RangeError: Maximum call stack …

高完整性系统工程(六):INTRODUCING ADA

目录 1. ADA的历史 2. ADA的特点 2.1 Strong, Static Typing 强语言、强静态类型语言 2.1.1 ADA is Strong, Static Typing 2.1.2 C is Weak, Static Typing 2.2 Module System 2.3 Portable 2.3.1 ADA 2.3.2 C 2.3.3 Cost of Runtime Checking 2.4 Readability …

IPython使用学习笔记

学习《利用python进行数据分析》第三章 IPython:一种交互式计算和开发环境的笔记&#xff0c;共享给大家&#xff0c;同时为自己作为备忘用。 安装ipython用pip即可。ps.博主用的是win7系统&#xff0c;所以接下来的都是在windows系统下操作的。 一.Ipython基础 启动&#xff…

chatgpt赋能python:Python关闭程序代码的实现方法

Python 关闭程序代码的实现方法 如果你是一个经验丰富的 Python 工程师&#xff0c;你应该知道如何在程序中实现正常关闭。不过&#xff0c;如果你是一个新手&#xff0c;这可能会变得有点棘手&#xff0c;特别是当你需要在程序中添加一些特定的关闭功能的时候。本文将为您介绍…

【javaEE】计算机网络原理初始

目录 1、网络发展史 1.1、独立模式 1.2、网络互连 1.2.1、局域网&#xff08;LAN&#xff09; 1.2.2、广域网&#xff08;WAN&#xff09; 1.2.3、广域网和局域网的区别 1.2.4、局域网组建网络的方式 &#xff08;了解&#xff09; &#xff12;、网络通信基础 2.1、I…

前端开发技术栈(工具篇):2023最新版nvm的Win/Linux安装和使用(详细) 27.8k stars

目录 nvm是什么 nvm下载 nvm安装 Windows nvm的使用 安装Node.js 切换Node.js版本 卸载Node.js 其他使用方法 Linux nvm的使用 安装NVM 使用NVM 总结 Node.js是一个非常流行的JavaScript运行时环境&#xff0c;可以帮助开发人员构建高性能的网络应用程序, 它被用于…

SpringCloud:分布式锁和线程安全

这篇文章是一个初步了解分布式应用的线程安全和锁的文章&#xff0c;所有截图及代码全部来自亲身实践 1.对于单机应用我们可以把锁加在方法维度&#xff08;有用&#xff0c;不推荐&#xff09; 像这样 但是我们应该缩小锁的范围&#xff0c;我们这里是在派单&#xff0c;避免…

手撕希尔排序

什么是希尔排序&#xff1f;他的效率怎摸样&#xff0c;如何去实现希尔排序呢&#xff1f;在这之前可能我们已经了解了希尔排序&#xff0c;作为排序中的老大哥一员&#xff0c;希尔排序的效率也是屈指可数的。 想要知道希尔排序如何实现我们就的先了解插入排序。 目录 1.何…

Flutter 笔记 | Flutter 核心原理(六)Embedder 启动流程(Android)

Embedder是Flutter接入原生平台的关键&#xff0c;其位于整个Flutter架构的底层&#xff0c;负责Engine的创建、管理与销毁&#xff0c;同时也为Engine提供绘制UI的接口&#xff0c;那么底层的实现细节如何&#xff1f;本文将详细分析。 Embedder关键类分析 在正式分析Embedd…

chatgpt赋能python:Python知识|关联两个列表

Python 知识 | 关联两个列表 Python 是一种高效的编程语言&#xff0c;它能够很好地进行数据处理&#xff0c;因此在 SEO 领域得到广泛的应用。关联两个列表是一种基础的数据处理方法&#xff0c;本文将为读者详细介绍如何使用 Python 关联两个列表&#xff0c;并给出一些实例…

Rust每日一练(Leetday0018) N皇后II、最大子数组和、螺旋矩阵

目录 52. N皇后 II N Queens II &#x1f31f;&#x1f31f;&#x1f31f; 53. 最大子数组和 Maximum Subarray &#x1f31f;&#x1f31f; 54. 螺旋矩阵 Spiral Matrix &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏…