使用 ASP.NET Core MVC 创建 Web 应用(笔记)

1.创建项目

(1)、在 Visual Studio 中,选择“文件”>“新建”>“项目”

(2)、在左侧窗格中选择“.NET Core”>在中间窗格中选择“ASP.NET Core Web 应用程序(.NET Core)”>填写项目名称(例:“MvcMovie”)>选择“确定”

(3)、在版本选择器下拉框中选择“ASP.NET Core 2.2”>选择“Web 应用(模型-视图-控制器)”>选择“确定”。

 

2.运行项目

(1)、Visual Studio 创建 Web 项目时,Web 服务器使用的是随机端口。

(2)、使用 Ctrl+F5 启动应用(非调试模式)后,可执行代码更改、保存文件、刷新浏览器和查看代码更改等操作。 使用非调试模式可以快速启动应用并查看更改。

(3)、可以从“调试”菜单项中以调试或非调试模式启动应用。

 

3.主要项目文件夹以及文件的概述

(1)、Controllers文件夹包含控制器 (C),处理浏览器请求的类。 它们检索模型数据并调用返回响应的视图模板。 在 MVC 应用中,视图仅显示信息;控制器处理并响应用户输入和交互。 例如,控制器处理路由数据和查询字符串值,并将这些值传递给模型。 该模型可使用这些值查询数据库。

(2)、Models文件夹包含模型 (M),表示应用数据的类。模型类使用验证逻辑来对该数据强制实施业务规则。 通常,模型对象检索模型状态并将其存储在数据库中。

(3)、Views文件夹包含视图 (V),视图是显示应用用户界面 (UI) 的组件。 此 UI 通常会显示模型数据。其中:

 Views/Shared/_Layout.cshtml 文件实现了菜单布局

 

Views/_ViewStart.cshtml 文件将 Views/Shared/_Layout.cshtml 文件引入到每个视图中。

 

(4)、wwwroot 文件夹:包含静态文件,如 HTML 文件、JavaScript 文件和 CSS 文件。

 

(5)、appSettings.json:包含配置数据,如连接字符串。

 

(6)、Program.cs:包含程序的入口点。

 

(7)、Startup.cs:包含配置应用行为的代码,例如,是否需要同意 cookie。

 

4.将控制器添加到 ASP.NET Core MVC 应用

(1)、MVC 模式可帮助创建分隔不同应用特性(输入逻辑、业务逻辑和 UI 逻辑)的应用,同时让这些元素之间实现松散耦合。 该模式可指定应用中每种逻辑的位置。 UI 逻辑位于视图中。 输入逻辑位于控制器中。 业务逻辑位于模型中。这种隔离有助于控制构建应用时的复杂程度,因为它可用于一次处理一个实现特性,而不影响其他特性的代码。 例如,处理视图代码时不必依赖业务逻辑代码。

(2)、添加控制器

A.在“解决方案资源管理器”中,右键单击“控制器”,然后单击“添加”>“控制器”

B.在“添加基架”对话框中,选择“MVC 控制器 - 空”

C.在“添加空 MVC 控制器”对话框中,输入名称,如 HelloWorldController 并选择“ADD”。

 

(3)、HTTP 终结点、URL、URI

控制器中的每个 public 方法均可作为 HTTP 终结点调用。 上述示例中,两种方法均返回一个字符串。 请注意每个方法前面的注释。

HTTP 终结点是 Web 应用程序中可定向的 URL(例如https://localhost:5001/HelloWorld),其中结合了所用的协议 HTTPS、TCP 端口等 Web 服务器的网络位置 localhost:5001,以及目标 URI HelloWorld。

 

(4)、路由格式

MVC 根据入站 URL 调用控制器类(及其中的操作方法)。 MVC 所用的默认 URL 路由逻辑使用如下格式来确定调用的代码:

/[Controller]/[ActionName]/[Parameters]

 

在 Startup.cs 文件的 Configure 方法中设置路由格式。关键代码如下:

app.UseMvc(routes =>

{

    routes.MapRoute(

        name: "default",

        template: "{controller=Home}/{action=Index}/{id?}");

});

 

解释:

第一个 URL 段决定要运行的控制器类。 因此 localhost:xxxx/HelloWorld 映射到 HelloWorldController 类。

 

该 URL 段的第二部分决定类上的操作方法。 因此 localhost:xxxx/HelloWorld/Index 将触发 HelloWorldController 类的 Index 运行。

 

请注意,只需浏览到 localhost:xxxx/HelloWorld,而 Index 方法默认调用。 原因是 Index 是默认方法,如果未显式指定方法名称,则将在控制器上调用它。

 

URL 段的第三部分 (id) 针对的是路由数据。

 

(5)、将一些参数信息从 URL 传递到控制器。

例如 /HelloWorld/Welcome?name=Rick&numtimes=4。 更改 Welcome 方法以包括以下代码中显示的两个参数:

 

// GET: /HelloWorld/Welcome/

// Requires using System.Text.Encodings.Web;

public string Welcome(string name, int numTimes = 1)

{

    return HtmlEncoder.Default.Encode($"Hello {name}, NumTimes is: {numTimes}");

}

 

A.使用 C# 可选参数功能指示,没有为 numTimes 参数传递值时该参数默认为 1。

B.使用 HtmlEncoder.Default.Encode 防止恶意输入(即 JavaScript)损害应用。

C.在 $"Hello {name}, NumTimes is: {numTimes}" 中使用内插字符串。

D.MVC 模型绑定系统可将命名参数从地址栏中的查询字符串自动映射到方法中的参数。

E.上述 URL 中的 ?(问号)为分隔符,后接查询字符串。 & 字符用于分隔查询字符串。

 

(6)、路由中参数可选的表示https://localhost:xxx/HelloWorld/Welcome/3?name=Rick

此时,第三个 URL 段与路由参数 id 相匹配。 Welcome 方法包含 MapRoute 方法中匹配 URL 模板的参数 id。 后面的 ?(id? 中)表示 id 参数可选。

 

5.将视图添加到 ASP.NET Core MVC 应用

(1)、修改 HelloWorldController 类,进而使用 Razor 视图文件来顺利封装为客户端生成 HTML 响应的过程。

如:在 HelloWorldController 类中,将 Index 方法替换为以下代码:

public IActionResult Index()

{

    return View();

}

 

上面的代码调用控制器的 View 方法。 它使用视图模板来生成 HTML 响应。

 

控制器方法(亦称为“操作方法”,如上面的 Index 方法)通常返回 IActionResult(或派生自 ActionResult 的类),而不是 string 等类型。

 

原本,Index 方法返回带有在控制器类中硬编码的消息的字符串。

 

使用 Razor 创建视图模板文件。 基于 Razor 的模板具有“.cshtml”文件扩展名。 它们提供了一种巧妙的方法来使用 C# 创建 HTML 输出。

 

(2)、添加视图

右键单击“视图”文件夹,然后单击“添加”>“新文件夹”,并将文件夹命名为“HelloWorld”。

右键单击“Views/HelloWorld”文件夹,然后单击“添加”>“新项”。

在“添加新项 - MvcMovie”对话框中,在右上角的搜索框中,输入“视图”

选择“Razor 视图”

保持“名称”框的值:Index.cshtml。

选择“添加”

 

(3)、更改Razor视图文件,并测试

导航到https://localhost:xxxx/HelloWorld。 HelloWorldController 中的 Index 方法作用不大,它运行 return View() 语句,指定此方法应使用视图模板文件来呈现对浏览器的响应。 因为没有显式指定视图模板文件的名称,所以 MVC 默认使用 /Views/HelloWorld 文件夹中的 Index.cshtml 视图文件。

 

(4)、布局页面

菜单布局是在 Views/Shared/_Layout.cshtml 文件中实现的。

 

布局模板使你能够在一个位置指定网站的 HTML 容器布局,然后将它应用到网站中的多个页面。

 

其中的@RenderBody():

RenderBody 是显示创建的所有特定于视图的页面的占位符,已包装在布局页面中。 例如,如果选择“隐私”链接,Views/Home/Privacy.cshtml 视图将在 RenderBody 方法中呈现。

 

(5)、将数据从控制器传递给视图

A、控制器

控制器操作会被调用以响应传入的 URL 请求。 控制器类是编写处理传入浏览器请求的代码的地方。 控制器从数据源检索数据,并决定将哪些类型的响应发送回浏览器。可以从控制器使用视图模板来生成并格式化对浏览器的 HTML 响应。

控制器负责提供所需的数据,使视图模板能够呈现响应。最佳做法:视图模板不应该直接执行业务逻辑或与数据库进行交互。 相反,视图模板应仅使用由控制器提供给它的数据。保持此“分离关注点”有助于保持代码干净以及可测试性和可维护性。

 

 

B、视图模板

 视图模板会生成动态响应,这意味着必须将适当的数据位从控制器传递给视图以生成响应。为此,可以让控制器将视图模板所需的动态数据(参数)放置在视图模板稍后可以访问的 ViewData 字典中。

 

 

C、ViewData 字典

ViewData 字典是一个动态对象,这意味着可以使用任何类型。只有将内容放在其中后 ViewData 对象才具有定义的属性。

ViewData 字典对象包含将传递给视图的数据。

 MVC 模型绑定系统自动将命名参数(name 和 numTimes)从地址栏中的查询字符串映射到方法中的参数

 

 

6.将模型添加到 ASP.NET Core MVC 应用

(1)、EF Core

可以结合 Entity Framework Core (EF Core) 使用模型类来处理数据库。 EF Core 是对象关系映射 (ORM) 框架,可以简化需要编写的数据访问代码。

(2)、模型类

也称为 POCO 类(源自“简单传统 CLR 对象”),因为它们与 EF Core 没有任何依赖关系。 它们只定义将存储在数据库中的数据的属性。

(3)、Code first

先编写模型类,然后 EF Core 将创建数据库。

(4)、添加模型类

右键单击 Models 文件夹,然后单击“添加” > “类”。 将类命名,如“Movie”。

为模型类添加属性等。

 

(5)、为模型搭建基架

 确切地说,基架工具将生成页面,用于对“电影”模型执行创建、读取、更新和删除 (CRUD) 操作。

步骤:

在解决方案资源管理器中,右键单击“Controllers”文件夹 >“添加”>“新搭建基架的项目”。

在“添加基架”对话框中,选择“包含视图的 MVC 控制器(使用 Entity Framework)”>“添加”。

填写“添加控制器”对话框:

A.模型类:Movie (MvcMovie.Models)

B.数据上下文类:选择 + 图标并添加默认MvcMovie.Models.MvcMovieContext

C.视图:将每个选项保持为默认选中状态

D.控制器名称:保留默认的 MoviesController

E.选择“添加”

 

结果:

Visual Studio 将创建:

A.Entity Framework Core 数据库上下文类 (Data/MvcMovieContext.cs)

B.电影控制器 (Controllers/MoviesController.cs)

C.“创建”、“删除”、“详细信息”、“编辑”和“索引”页面的 Razor 视图文件 (Views/Movies/*.cshtml)

自动创建数据库上下文和 CRUD(创建、读取、更新和删除)操作方法和视图的过程称为“搭建基架”。

 

(6)、初始迁移

创建数据库,并且使用 EF Core 迁移功能来执行此操作。 通过迁移可创建与数据模型匹配的数据库,并在数据模型更改时更新数据库架构。

 

步骤:

从“工具”菜单中,选择“NuGet 包管理器” > “包管理器控制台”(PMC)。

在 PMC 中,输入以下命令:

PM>Add-Migration Initial

PM>Update-Database

 

解释:

Add-Migration 命令生成用于创建初始数据库架构的代码。

数据库架构基于在 MvcMovieContext 类中(位于 Data/MvcMovieContext.cs 文件中)中指定的模型。 Initial 参数是迁移名称。可以使用任何名称,但是按照惯例,会使用可说明迁移的名称。

 

Update-Database 命令在用于创建数据库的 Migrations/{time-stamp}_InitialCreate.cs 文件中运行 Up 方法。

 

(7)、依赖注入

 服务(例如 EF Core 数据库上下文)在应用程序启动期间通过 DI 注册。需要这些服务(如 Razor 页面)的组件通过构造函数提供相应服务。

基架工具自动创建数据库上下文并将其注册到 DI 容器。

 Startup.ConfigureServices 方法中注入上下文的代码:

services.AddDbContext<MvcMovieContext>(options =>

 options.UseSqlServer(Configuration.GetConnectionString("MvcMovieContext")));

 

(8)、数据库上下文

MvcMovieContext 为 Movie 模型协调 EF Core 功能(创建、读取、更新、删除等)。 数据上下文 (MvcMovieContext) 派生自 Microsoft.EntityFrameworkCore.DbContext。 数据上下文指定数据模型中包含哪些实体

 

(9)、实体集(如, DbSet<Movie>

 在实体框架术语中,实体集通常与数据表相对应。实体对应表中的行。

 

(10)、强类型模型和 @model 关键词

ViewData 字典是一个动态对象,提供了将信息传递给视图的方便的后期绑定方法。

通过将 @model 语句(如,@model MvcMovie.Models.Movie)包括在视图文件的顶端,可以指定视图期望的对象类型。基架自动添加的。

@model 指令使你能够使用强类型的 Model 对象访问控制器传递给视图的模型,如,电影。

 

 

 

7.在 ASP.NET Core 中使用 SQL

(1)、MvcMovieContext 对象

处理连接到数据库并将 Movie 对象映射到数据库记录的任务。 在 Startup.cs 文件的 ConfigureServices 方法中向依赖关系注入容器注册数据库上下文

 

(2)、 appsettings.json 文件ConnectionString

ASP.NET Core 配置系统会读取 ConnectionString。 为了进行本地开发,它会从 appsettings.json 文件获取连接字符串

 

(3)、设定数据库种子

在 Models 文件夹中创建一个名为 SeedData 的新类,并修改内容

 

(4)、添加种子初始值设定项

修改Program.cs 的内容

 

(5)、测试数据库种子

 

步骤:

删除 DB 中的所有记录。可以使用浏览器中的删除链接,也可从 SSOX 执行此操作。

强制应用初始化(调用 Startup 类中的方法),使种子方法能够正常运行。 若要强制进行初始化,必须先停止 IIS Express,然后再重新启动它。可以使用以下任一方法来执行此操作:

右键单击通知区域中的 IIS Express 系统任务栏图标,然后点击“退出”或“停止站点”

如果是在非调试模式下运行 VS 的,请按 F5 以在调试模式下运行

如果是在调试模式下运行 VS 的,请停止调试程序并按 F5

应用将显示设定为种子的数据。

 

 

 

8.ASP.NET Core 中的控制器方法和视图

(1)、DataAnnotations

Display 特性指定要显示的字段名称的内容

DataType 属性指定数据的类型(日期),使字段中存储的时间信息不会显示。

[Column(TypeName = "decimal(18, 2)")]能将属性正确地映射到数据库中的货币

 

(2)、定位标记帮助程序

标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素

AnchorTagHelper 从控制器操作方法和路由 ID 动态生成 HTML href 特性值。

<a asp-action="Edit" asp-route-id="@item.ID">Edit</a>

||

V

<a href="/Movies/Edit/4"> Edit </a>

 

(3)、路由格式

在 Startup.cs 文件中设置的:

app.UseMvc(routes =>

{

    routes.MapRoute(

        name: "default",

        template: "{controller=Home}/{action=Index}/{id?}");

});

 

(4)、Bind特性(controller类的方法的参数中使用)

[Bind] 特性是防止过度发布的一种方法。只应在 [Bind] 特性中包含想要更改的属性

 

(5)、HttpPost 特性和HttpGet 特性(controller类的方法使用)

HttpPost 特性指定只能为 POST 请求调用此 Edit 方法。

可将 HttpGet 特性应用于第一个编辑方法,但不是必需,因为 HttpGet 是默认设置。

以 HTTP GET 方式修改数据是一种安全隐患。 以 HTTP GET 方法修改数据也违反了 HTTP 最佳做法和架构 REST 模式,后者指定 GET 请求不应更改应用程序的状态。 换句话说,执行 GET 操作应是没有任何隐患的安全操作,也不会修改持久数据。

 

(6)、ValidateAntiForgeryToken 特性

ValidateAntiForgeryToken 特性用于防止请求伪造,并与编辑视图文件 (Views/Movies/Edit.cshtml) 中生成的防伪标记相配对。编辑视图文件使用表单标记帮助程序生成防伪标记。

表单标记帮助程序会生成隐藏的防伪标记,此标记必须与电影控制器的 Edit 方法中 [ValidateAntiForgeryToken] 生成的防伪标记相匹配。

[ValidateAntiForgeryToken] 特性验证表单标记帮助程序中的防伪标记生成器生成的隐藏的 XSRF 标记

 

(7)、模型绑定系统

模型绑定系统采用发布的表单值,并创建一个作为 movie 参数传递的 Movie 对象。

ModelState.IsValid 方法验证表单中提交的数据是否可以用于修改(编辑或更新)Movie 对象。 如果数据有效,将保存此数据。 通过调用数据库上下文的 SaveChangesAsync 方法,将更新(编辑)的电影数据保存到数据库。 保存数据后,代码将用户重定向到 MoviesController 类的 Index 操作方法,此方法显示电影集合,包括刚才所做的更改。

 

 

(8)、模型验证

在表单发布到服务器之前,客户端验证会检查字段上的任何验证规则。如果有任何验证错误,则将显示错误消息,并且不会发布表单。

如果禁用 JavaScript,则不会进行客户端验证,但服务器将检测无效的发布值,并且表单值将与错误消息一起重新显示。

 

 

 

9.将搜索添加到 ASP.NET Core MVC 应用                       

(1)、更新 Index 方法,关键代码:

public async Task<IActionResult> Index(string searchString)

{

    var movies = from m in _context.Movie

                 select m;

 

    if (!String.IsNullOrEmpty(searchString))

    {

        movies = movies.Where(s => s.Title.Contains(searchString));

    }

 

    return View(await movies.ToListAsync());

}

 

 

(2)、    Lambda 表达式和LINQ 查询

上面的 s => s.Title.Contains() 代码是 Lambda 表达式。

 Lambda 在基于方法的 LINQ 查询中用作标准查询运算符方法的参数,如 Where 方法或 Contains(上述的代码中所使用的)。

 

(3)、延迟执行查询

 

在对 LINQ 查询进行定义或通过调用方法(如 Where、Contains 或 OrderBy)进行修改后,此查询不会被执行。 相反,会延迟执行查询。这意味着表达式的计算会延迟,直到真正循环访问其实现的值或者调用 ToListAsync 方法为止。

 

              

(4)、Contains方法

Contains 方法在数据库上运行,而不是在上面显示的 C# 代码中运行。

查询是否区分大小写取决于数据库和排序规则。

在 SQL Server 上,Contains 映射到 SQL LIKE,这是不区分大小写的。

在 SQLite 中,由于使用了默认排序规则,因此需要区分大小写。

 

(5)、添加UI元素,进行筛选

更改“Views/Movies/Index.cshtml”文件,添加的代码:

<form asp-controller="Movies" asp-action="Index">

    <p>

        Title: <input type="text" name="SearchString">

        <input type="submit" value="Filter" />

    </p>

</form>

此 HTML <form> 标记使用表单标记帮助程序,因此提交表单时,筛选器字符串会发布到电影控制器的 Index 操作。

 

 

10.将新字段添加到 ASP.NET Core MVC 应用

(1)、Entity Framework Code First

 

Entity Framework Code First 迁移用于:

将新字段添加到模型。

将新字段迁移到数据库。

 

使用 EF Code First 自动创建数据库时,Code First 将:

将表添加到数据库,以跟踪数据库的架构。

验证数据库与生成它的模型类是否同步。 如果它们不同步,EF 则会引发异常。这使查找不一致的数据库/代码问题变得更加轻松。

 

(2)、步骤

修改模型,添加属性。

更新绑定允许名单,将此新属性纳入其中。

在 MoviesController.cs 中,更新 Create 和 Edit 操作方法的 [Bind] 属性,以包括 Rating 属性

更新视图模板以在浏览器视图中显示、创建和编辑新的 Rating 属性。

更新数据库,利用code first迁移数据库,命令:

PM>Add-Migration  Rating

PM>Update-Database

 

(3)SqlException: Invalid column name 'Rating'.

在 DB 更新为包括新字段之前,应用将不会正常工作。 如果它现在运行,将引发以下 SqlException:

SqlException: Invalid column name 'Rating'.

 

发生此错误是因为更新的 Movie 模型类与现有数据库的 Movie 表架构不同。 (数据库表中没有 Rating 列。)

可通过几种方法解决此错误:

让 Entity Framework 自动丢弃,并基于新的模型类架构重新创建数据库。 在测试数据库上进行开发时,此方法在开发周期早期很方便;通过它可以一起快速改进模型和数据库架构。但其缺点是会丢失数据库中的现有数据 - 因此请勿对生产数据库使用此方法! 使用初始值设定项,以使用测试数据自动设定数据库种子,这通常是开发应用程序的有效方式。对于早期开发和使用 SQLite 的情况,这是一个不错的方法。

 

对现有数据库架构进行显式修改,使它与模型类相匹配。 此方法的优点是可以保留数据。可以手动或通过创建数据库更改脚本进行此更改。

 

使用 Code First 迁移更新数据库架构。

 

 

11.将验证添加到 ASP.NET Core MVC 应用

(1)、DRY 原则

MVC 的设计原则之一是 DRY(“不要自我重复”)。 ASP.NET Core MVC 支持你仅指定一次功能或行为,然后使它应用到整个应用中。这可以减少所需编写的代码量,并使编写的代码更少出错,更易于测试和维护。

MVC 和 Entity Framework Core Code First 提供的验证支持是 DRY 原则在实际操作中的极佳示例。可以在一个位置(模型类中)以声明方式指定验证规则,并且在应用中的所有位置强制执行。

 

(2)、命名空间DataAnnotations,特性:

Required

MinimumLength

RegularExpression

 

(3)、好处

不需要在Controller 类或视图中更改单个代码行来启用此验证 UI。 控制器和视图会自动选取验证规则,这些规则是通过在模型类的属性上使用验证特性所指定的。

 

(4)、客户端验证和服务器端验证

存在客户端验证错误时,不会将表单数据发送到服务器。

服务器端相应的类的方法里会调用 ModelState.IsValid 以检查是否有任何验证错误。

 调用此方法将评估已应用于对象的任何验证特性。

如果对象有验证错误,则操作方法会重新显示此表单。 如果没有错误,此方法则将新模型保存在数据库中。

 如果在浏览器中禁用 JavaScript,客户端验证将被禁用。

 

 

12.检查 ASP.NET Core 应用的 Details 和 Delete 方法

(1)、公共语言运行时 (CLR) 需要重载方法拥有唯一的参数签名(相同的方法名称但不同的参数列表)。 但是,这里需要两个 Delete 方法 -- 一个用于 GET,另一个用于 POST -- 这两个方法拥有相同的参数签名。 (它们都需要接受单个整数作为参数。)

可通过两种方法解决此问题,一种是为方法提供不同的名称。这正是基架机制进行的操作。 但是,这会造成一个小问题:ASP.NET 按名称将 URL 段映射到操作方法,如果重命名方法,如,命名为DeleteConfirmed,则路由通常无法找到该方法。 但是存在解决方案,即向 DeleteConfirmed 方法添加 ActionName("Delete") 属性。 该属性对路由系统执行映射,以便包括 POST 请求的 /Delete/ 的 URL可找到 DeleteConfirmed 方法。

对于名称和签名相同的方法,另一个常用解决方法是手动更改 POST 方法的签名以包括额外(未使用)的参数。如,

// POST: Movies/Delete/6

[HttpPost]

[ValidateAntiForgeryToken]

public async Task<IActionResult> Delete(int id, bool notUsed)

代码交流 2021