三、理解 MVC

在前一章中,我们快速回顾了 ASP.NET 中的 MVC 及其组件。 在本章中,我们将深入探讨 ASP 中的 MVC.NET Core,它是 beta 版的 MVC 6。

开发人员常见的两个问题是:如何将旧的 MVC 项目升级到 ASP.NET Core MVC ? 是否存在自动迁移过程? 从 MVC 5 迁移到 asp.net 中新的 MVC.NET Core 涉及一些手动步骤,因为没有自动迁移过程。 您可以将所有静态客户端文件复制到wwwroot位置,并调整对这些文件的任何引用以指向正确的位置。

对于服务器端代码,您可以在您的模型、视图和控制器上进行迁移,而无需进行很多更改。 本章将涵盖以下各方面的知识:

  • 控制器
  • 的观点
  • 模型

对于已经使用过以前版本的 ASP 的开发人员来说,本章的部分内容会比较熟悉。 净 MVC。 我们将浏览一些熟悉的材料,同时介绍对 MVC 的更新添加。

建筑控制器

您的 MVC 控制器是奇迹发生的地方。 请求来自最终用户,然后返回内容和数据。 在这两者之间会发生什么取决于你——开发者。

控制器方法

默认情况下,控制器方法可以使用[HttpGet]属性用于 HTTPGET请求,或者完全忽略这个动作谓词属性,因为它是默认行为。 很可能,您已经使用了以下 HTTPGETPOST动词:

  • HttpGet:使用 HTTPGET方法,带有可选的querystring参数
  • HttpPut:使用 HTTPPOST方法提交表单,创建实体

除了上述内容,您还应该注意可以用作控制器属性的其他 HTTP 谓词; 它们如下:

  • HttpPut:使用 HTTPPUT方法编辑现有实体
  • HttpDelete:使用 HTTPDELETE方法删除已有实体
  • HttpPatch:允许部分模型更新,而不是完整的PUT请求
  • AcceptVerbs:允许指定多个动作动词

基本控制器

使用 Visual Studio,您可以添加一个基本控制器,并逐步向其添加代码。 您还可以使用 scaffolding 添加一个更完整的控制器,其中包括模型绑定和操作方法来操作模型。 但我们会在本章的后面讲到。

首先,让我们创建一个示例项目,我们将在其中添加一个基本控制器,如下所示:

  1. 创建一个新的 ASP。 通过点击文件|新建|项目
  2. 选择ASP.NET Web 应用(.NET Core)
  3. 命名您的项目患者记录
  4. ASP. net 列表下选择Web 应用.NET Core 模板
  5. 点击更改认证切换为个人用户账号
  6. 点击OK创建您的 web 项目。

我们也可以从一个空的模板开始,但是这样我们就必须添加更多的依赖项和配置才能启动和运行。 让我们继续添加基本控制器。

**要添加基本控制器,请执行以下步骤:

  1. 在“解决方案资源管理器”中右键单击“Controllers”文件夹。
  2. 在上下文菜单中,单击添加|新项目
  3. 在。net Core 和 ASP.NET 下的Add New Item对话框中,选择MVC Controller Class
  4. 将控制器命名为PatientController,单击添加继续操作。

以下截图显示了添加新项目窗口:

Basic controllers

此时,您应该拥有一个带有一个Index()方法的基本控制器,该方法返回一个简单的View。 这对于有经验的 ASP 应该已经很熟悉了。 净 MVC 开发人员。 如果我们现在运行项目,web 应用应该在 web 浏览器中启动,使用任意端口号,比如12345。 您的端口号会有所不同。 您的 web 应用的链接类似于http://localhost:12345

在浏览器仍然打开的情况下,您可以尝试通过将控制器名称Patient添加到 URL:http://localhost:12345/Patient的末尾来访问PatientController

这将导致未处理的InvalidOperationException,因为我们还没有为Patient控制器添加Index视图。 与以前版本的 MVC 一样,该视图通常位于Views文件夹中名为Patient的子文件夹中。 如果没有,回退视图将位于Shared子文件夹和Views文件夹中。 当这两个常规位置都不包含预期的视图时,就会发生异常。

为了绕过预期的路径,让我们用以下代码替换PatientController类的Index方法:

public string Index() 
{ 
    return "Patient Info"; 
} 

注意,我们已经将返回类型改为string,这样我们就可以返回一个字符串字面值来测试控制器。 如果你再次运行应用,当你在 http://localhost:12345/Patient访问控制器Patient时,你现在应该能够在浏览器中看到占位符文本:

Basic controllers

URL 路由和约定

如果你看一下Startup.cs中的Configure()方法,你可以看到你的 web 应用的默认路由:

app.UseMvc(routes => 
{ 
    routes.MapRoute( 
        name: "default", 
        template: "{controller=Home}/{action=Index}/{id?}"); 
}); 

你可能还记得第二章构建你的第一个 ASP。 asp.NET Core 应用,即在 asp.net 中采用Configure()方法.NET Core 用于应用在ConfigureServices()方法中添加的组件和服务。 从示例代码中可以看到,控制器和操作值分别默认为HomeIndex

可以选择将id参数添加到 URL 的末尾斜杠之后。 注意,这与向QueryString添加id参数不同,但两种方法都可以通过方法参数将值传递给控制器方法。

为了使用Encode()方法,请确保您已经添加了以下using语句以在代码中包含WebEncoders命名空间:

using System.Text.Encodings.Web; 

为了使用默认约定,将PatientController类中的Index方法更新如下以接受参数:

public string Index(int id, string name = "Unknown") 
{ 
    return "Patient Info: " + HtmlEncoder.Default.Encode( 
        "ID: " + id + ", " + 
        "Name: " + name); 
} 

参数idname分别表示患者的 ID 和姓名。 如果没有指定名称,则name参数默认为"Unknown"。 传递的值被封装在对Encode()的调用中,以确保没有恶意标记或脚本通过QueryString传递。

您现在应该能够使用http://localhost:12345/Patient?id=1&name=John作为QueryString参数。 下面的截图是相同的输出:

URL routes and conventions

实施视图

MVC 中的视图构成了表示层,这是一个用户界面,它保存着供用户交互的屏幕元素。 任何 web 应用的 UI 都可能很快变得非常繁忙,要么同时充满了太多的条目,要么承载了业务逻辑和不必要的代码。 MVC 的关注点分离的目标是帮助开发人员创建架构良好的应用,其中视图是轻量级的,验证在模型层中执行,模型层通过绑定与视图连接。

基本观点

与添加基本控制器的方式类似,我们将创建一个基本视图并逐步向其添加代码。 正如您可以使用 scaffolding 创建一个更完整的控制器一样,您也可以创建一个附加到模板和模型的更完整的视图。 但是同样的,我们会在本章的后面讲到。

首先,让我们为Patient控制器使用的视图创建一个子文件夹,这样我们就可以将我们的新视图添加到其中。 遵循以下步骤:

  1. 在“解决方案资源管理器”中,右键单击Views文件夹,并向其中添加一个新文件夹。
  2. 将子文件夹命名为Patient,以利用命名约定。
  3. 右键单击Patient子文件夹,然后单击添加|新建项目
  4. 添加新项目对话框中,选择ASP 下的MVC 视图页面
  5. 将视图命名为Index.cshtml,然后单击添加继续。

我们使用默认视图名Index.cshtml,这样这个视图将由Patient控制器中的Index()控制器方法返回。 此时,视图没有实际的内容,只有一些占位符符号用于注释和服务器端代码。 代码如下:

@* server-side comments *@ 
@{ // server-side code } 

用以下内容替换视图的内容:

@{ 
    ViewData["Title"] = "Patient Index"; 
} 
<h2>Patient Index</h2> 

"Title"ViewData值由默认的_Layout.cshtml布局文件使用,该文件可以在Views文件夹中的Shared子文件夹中找到。 以下代码适用于Patient控制器的Index视图:

<title>@ViewData["Title"] - PatientRecordsWebApp</title> 

用以下代码替换控制器的Patient视图:

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

要浏览此页面,请运行应用并将Patient控制器名称添加到 URL 末尾的斜杠后面,如http://localhost:12345/Patient

您现在应该在浏览器中看到视图的内容,包括标题和页眉文本。 如下截图所示:

Basic views

视图中的标签助手

为了更容易地浏览到这个页面,让我们更新它的布局文件,以包含一个到controller方法的链接,该方法将返回这个视图。 首先,在布局文件中找到一组可点击链接,<li>列出<ul>无序列表中的项目。

请注意,<a>标记每个都有两个属性作为标记助手:

  • asp-controller
  • asp-action

标签助手是 ASP 中新引入的.NET Core MVC,并且类似于您过去可能使用过的HtmlHelpers。 这两个属性的值将决定控制器名称以及单击链接时将触发的后续操作方法。

让我们为Patient控制器添加一个附加的可点击链接,就在Contact链接之后,使用以下代码:

<li><a asp-controller="Patient" asp-action="Index">Patients</a></li> 

运行 web 应用来查看顶部标题区域的新链接,这将允许您更快地跳转到患者的索引视图:

Tag helpers in views

在本章的后面,我们将学习如何在视图中使用标记助手来进行验证。 我们还将向模型中添加特定的属性,以帮助在提交表单时验证特定的字段。

ViewData、ViewBag 和 TempData

通过在控制器中使用ViewData项设置值,然后在视图中检索值,可以将数据从控制器传递到视图。 这些值被存储为具有基于字符串的键的键值字典项。 与使用ViewData相比,ViewBag是一种更动态的选择,允许您在不进行类型转换的情况下使用复杂数据类型。 赋值将在重定向时重置。

下面是如何在控制器中使用ViewDataViewBag的两个例子:

ViewData["PatientId"] = id; 
ViewBag.PatientData = "someData"; 

TempData略有不同,因为它在重定向期间保留其数据,这允许您在重定向操作期间在控制器之间传递数据。 它的语法类似于ViewData,所以它可以作为一个字典项使用,具有以下基于字符串的键:

TempData["UserToken"] = userTokenData; 

让我们在Patient控制器中添加以下Details()方法,为ViewData对象赋值:

public IActionResult Details(int id, string name = "Unknown") 
{ 
    ViewData["PatientId"] = id; 
    ViewData["PatientName"] = name;  
    return View(); 
} 

这里,我们分别为患者的 ID 和名称设置了两个ViewData值。 为了在视图中显示数据,我们将添加一个新的Details视图,如下所示:

  1. 在“解决方案资源管理器”中展开Views文件夹。
  2. 右键单击Patient子文件夹,然后单击添加|新建项目
  3. 添加新项目对话框中,选择ASP 下的MVC 视图页面
  4. 命名您的视图Details.cshtml以便继续。

用以下代码替换Details视图的内容:

@{ 
    ViewData["Title"] = "Patient Details"; 
} 
<h2>Patient Details</h2> 
<ul> 
    <li>ID: @ViewData["PatientId"]</li> 
    <li>Name: @ViewData["PatientName"]</li> 
</ul> 

为了提供Index视图和Details视图之间的简单链接,我们还将更新位于Views文件夹中Patient子文件夹中的Index视图。

用以下代码替换控制器Patient``Index视图的内容:

@{ 
    ViewData["Title"] = "Patient Index"; 
} 
<h2>Patient Index, with Tag Helpers</h2> 

<ul> 
    @for (int i = 0; i < 10; i++) 
    { 
        <li><a asp-controller="Patient" asp-action="Details"  
               asp-route-id="@i" asp-route-name="Patient @i"> 
               Patient # @i</a></li> 
    } 

</ul> 

在前面的代码中,for循环用于生成可单击链接的列表。 点击每个链接可以显示特定患者的详细信息。 同样,您可以在<a>标记中看到以下标记帮助器属性的使用:

  • asp-controller
  • asp-action
  • asp-route-id
  • asp-route-name

最后两个属性足够开放,可以引用 ID 和 name 等命名参数。 id属性将自动遵循 URL 路由约定,而name属性将作为 QueryString 参数添加。

连结将以下列格式显示:

  • http://localhost:12345/Patient/Details/0?name=Patient%200
  • http://localhost:12345/Patient/Details/1?name=Patient%201
  • http://localhost:12345/Patient/Details/2?name=Patient%202

问号前面的数字值是由为应用定义的 URL 路由定义的 ID 值。 注意,参数值中的空格是 url 编码为%20的。

运行 web 应用,单击顶部工具栏中的Patients链接,导航到Patient索引页。 在下面的截图中,你会看到一个包含 10 个项目的列表,每个项目都有自己的链接。 单击其中的一个链接进入该特定患者的Details页面。 详情页面将显示选定患者的 ID 和姓名:

ViewData, ViewBag, and TempData

设计模型和 ViewModels

您可以使用模型来存储一组数据,而不是将单个值从控制器逐个传递到视图。 控制器负责更新模型,模型可以与视图关联以获取其数据。

创建模型

模型只是一个文件扩展名为.cs的类文件。 在解决方案资源管理器面板中,您可以将新的模型类文件添加到Models文件夹中。 您可能已经注意到,对于 MVCController类和 MVCView页有一些选择,但是对于Model类没有选择。

要创建一个模型类,我们可以在添加新项目时选择class选项,如下所示:

  1. 在“解决方案资源管理器”中,右键单击Models文件夹,然后单击添加|新项目
  2. Add New Item对话框中,选择Code下的Class
  3. 将文件命名为Human.cs以创建一个新模型。
  4. 验证您有一个名为Human的空类。

Human类中添加以下字段:

public class Human 
{ 
    public int ID { get; set; } 
    public string SocialSecurityNumber { get; set; } 
    public DateTime DateOfBirth { get; set; } 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
} 

您可能还注意到在下面的截图中,在Models文件夹中有ViewModels子文件夹。 当您将视图绑定到模型时,模型中定义的字段表示关联视图中的 UI 元素和表单字段。 这些子文件夹是组织这些模型的好地方,它们的名称与你的控制器名称相匹配:

Creating models

绑定模型到视图

要将视图绑定到模型,可以在视图的.cshtml文件中看到以下语法:

@model MyModelName 

视图的其余部分应该包含一个服务器端表单,以及与模型字段关联的表单元素。 虽然在一个视图中可能有多个表单,但如果可以避免多个表单,MVC 视图通常倾向于使用一个表单。 在任何情况下,用于视图的模型都应该包含所有必要的字段,以覆盖视图中的所有表单。

在以前的版本中.NET MVC,你可能已经为服务器端表单使用了以下语法:

@using (Html.BeginForm("action", "controller", FormMethod.Post, new {})) 
{ 
    @Html.LabelFor(m => m.Field1) 
    @Html.TextBoxFor(m => m.Field1) 

    @Html.LabelFor(m => m.Field2) 
    @Html.TextBoxFor(m => m.Field2) 

    <input type='Submit' value='Submit' /> 
} 

在 ASP.NET Core MVC,下面介绍了使用标记助手的语法:

<form asp-controller="controller" asp-action="action" method="post"> 
    <label asp-for="Field1"></label> 
    <input asp-for="Field1" /> 
    <label asp-for="Field2"></label> 
    <input asp-for="Field2" /> 
    <input type="submit" value="Submit" /> 
</form> 

这种新方法也使代码更干净、更高效。 当表单被提交时,控制器方法可以接受模型的数据类型作为参数来接收它的所有字段:

[HttpPost] 
public IActionResult MyAction(MyModelName model) 
{ 
    return View(model); 
} 

ViewModels 和映射

当您在它们自己的文件夹中组织您的ViewModels时,您通常会将每个视图绑定到一个特定的ViewModel。 但是,数据库设计可能需要在代码中定义实体作为与数据库对象匹配的模型文件。 您需要弄清楚如何将特定于数据库的模型映射到您的ViewModels

有几种不同的方法可以映射你的ViewModels和数据库实体模型:

  • 当您的控制器方法接受一个ViewModel时,您可以逐个将每个字段映射到它们对应的实体模型字段
  • 您可以使用像AutoMapper这样的库,它可以在 GitHub/NuGet 上免费获得,并在http://automapper.org中描述为基于约定的基于对象的映射器

为了更深入地了解实体模型,我们将在第 6 章中介绍对象关系映射和实体框架,在代码中使用实体框架与数据库交互。

把它结合在一起

为了把它们结合在一起,让我们利用 Visual Studio 内置的代码生成特性来利用脚手架和绑定。 在本节中,我们将讨论帮助模型验证的字段属性。

在结束本章之前,我们还将学习如何使用异常处理来捕获代码中的错误。

脚手架、验证和模型绑定

使用到目前为止构建的PatientRecords项目,让我们按照以下步骤向其添加一个新控制器,以利用 scaffolding。 而不是点击添加项目,我们将选择控制器选项:

  1. 在解决方案资源管理器中,右键单击Controllers文件夹。
  2. 在上下文菜单中,单击添加|控制器
  3. 选择使用 EF 添加带有视图的新 MVC 控制器的选项。

在添加控制器时,系统将提示您输入其他信息。 选择/输入以下信息:

  • 模型类:Human,来自您的模型名称空间
  • 数据上下文类:ApplicationDbContext
  • 控制器名称:HumanController

对于以下所有的复选框,你可以保持默认值不变:

  • 生成视图:默认选中,用于为每个动作生成相应的视图。
  • 引用脚本库:默认选中,包含对客户端代码中的脚本库的引用。
  • 使用布局页面:默认选中,允许输入布局名称,或者可以为空以允许_viewstart.cshtml被使用。 这个页面通常包含一个对项目默认布局页面的引用,默认设置为_Layout

您可能已经注意到,控制器名称默认为HumenController,其中有一个e。 这是因为脚手架工具试图将代码中的实体多元化,所以它错误地假设Human的复数形式是Humen。 要修复此问题,请确保在添加控制器之前用a将其重命名为HumanController

在添加了搭建的控制器之后,您可以检查HumanController的内容,以验证它是否为您自动生成了一些代码。 它应该从一个数据库上下文对象开始,然后是一个利用该上下文的构造函数。

类的其余部分应该有一系列用于基本 CRUD 操作的操作方法,即用于创建、读取、更新和删除条目的操作:

  • 索引:Human对象列表
  • Details(id):具体Human对象的详细信息
  • Create:GET/POST 方法显示创建表单并创建新实体
  • Edit:GET/POST 方法来显示编辑表单并编辑现有实体
  • Delete:GET/POST 方法,显示确认界面,执行删除操作

查看Views文件夹,并验证是否有一个Human子文件夹,其中包含前面所有操作方法的单独视图:创建、删除、详细信息、编辑和索引。 如果您打开这些.cshtml文件中的任何一个,您将在每个视图的第一行中找到对每个对应模型的引用。

这些视图中包含的验证使用新的标记助手语法。 这些标记可以用于验证,我们在前一节中看到了这一点。 在此基础上,我们可以编辑Human模型,以包含一些属性来帮助验证。

你需要的属性定义在DataAnnotations命名空间中,所以你应该确保以下 using 语句被添加到你的Human.cs文件中:

using System.ComponentModel.DataAnnotations; 

编辑Models目录下的Human.cs文件,以包含以下字段的属性:

public class Human 
{ 
    public int ID { get; set; } 
    [Required] 
    [StringLength(11)] 
    [Display(Name = "SSN")] 
    public string SocialSecurityNumber { get; set; } 
    [Display(Name = "DOB")] 
    [DataType(DataType.Date)] 
    public DateTime DateOfBirth { get; set; } 
    [Display(Name = "First Name")] 
    public string FirstName { get; set; } 
    [Display(Name = "Last Name")] 
    public string LastName { get; set; } 
} 

这里使用了以下属性:

  • Required:必填项
  • StringLength(n):限制字段值为 n 个字符
  • Display(Name ="Alt Name"):在用户界面中显示的备用名称
  • :限制字段为特定的数据类型,例如 date

注意事项

如果您想试验其他属性,请参考 ASP.NET 文档来查找此名称空间中的其他验证属性。

在再次启动 web 应用之前,让我们添加一个到Human控制器Index视图的新链接。 我们可以在Views/Shared目录下_Layout.cshtml的顶部菜单中添加另一个可点击的链接:

<li><a asp-controller="Human" asp-action="Index">Humans</a></li> 

前面的行可以添加到前面添加的Patients链接之后。 如果此时单击顶部工具栏中的Humans链接,您将得到一个错误,因为我们的数据库还不存在。

数据库设置和数据录入

要设置初始数据库,我们必须从命令提示符运行初始迁移。 当前文件夹必须设置为项目的根位置,即Startup.cs所在的位置。 这与您的ModelsViewsControllers文件夹所在的位置相同。

打开文件夹所在位置的命令提示符后,可以运行以下命令。 您的 DNVM 版本可能会有所不同:

dotnet restore
dotnet build
dotnet ef migrations add Initial
dotnet ef database update

以上命令的作用如下:

  • restore:为你的项目恢复包
  • :编译并构建您的项目
  • :创建数据库模型的初始快照
  • 创建/更新您的数据库

Visual Studio 应该为你安装dotnet工具,所以你可以参考第一章ASP 入门.NET Core,用于初始设置说明,如果您遗漏了任何内容。 关于实体框架的更深入的介绍,请参见第 6 章在代码中使用实体框架与数据库交互

命令运行完成后,您应该拥有数据库的初始版本,以及与实体模型相匹配的必要数据库表,例如Human表。 Human表中的列应该匹配Human.cs模型文件中的字段。

在 Visual Studio 中查看 SQL Server 对象资源管理器,在顶部菜单中选择view|SQL Server 对象资源管理器,如下图所示:

Database setup and data entry

现在,您可以再次运行应用,并单击顶部工具栏上的Humans链接。 这应该会把你带到Index视图,显示你还没有Humans视图,如下截图所示:

Database setup and data entry

单击Create New链接创建一个新的Human表项。 单击此链接将带您进入Human控制器的Create视图。 输入一些值,然后点击Create按钮,如下截图所示:

Database setup and data entry

一旦创建了新的Human条目,您的提交将由后版本的Create方法处理。 如果您遗漏了任何必需的值或输入了错误的字符数,验证消息将自动显示在 UI 中。 正确提交后,你会被带回到Index视图,在那里你会看到你刚刚创建的新Human条目,如下截图所示:

Database setup and data entry

异常处理

在 ASP.NET Core MVC 中,异常处理已经得到了发展,并且出现了处理异常的新方法。 UseDeveloperExceptionPage()UseExceptionHandler()的使用可以在 ASP 中看到。 净 web 应用。 要查找这些方法调用的位置,只需查看Startup.cs文件即可。 在Configure()方法内部,你可以找到对以下方法的调用:

app.UseDeveloperExceptionPage(); 
app.UseExceptionHandler("/Home/Error"); 

当您在开发中运行应用时,第一个方法调用是相关的,因此您可以获得额外的调试信息。 在生产环境中,实际的异常处理程序将用于重定向到适当的错误页面。 上述代码触发控制器的HomeError动作方法,然后触发控制器的Error视图。 在我们的样例应用中,在Shared文件夹中有一个Error.cshtml文件,在生产环境中出现异常时将使用该文件。

为了模拟开发和生产环境,可以将环境变量ASPNETCORE_ENVIRONMENT的值更改为DevelopmentProduction。 此更改可以在您的launchSettings.json文件中更改,也可以在项目属性屏幕的Debug标签中更改。

与以前的版本一样.NET,您应该使用 try/catch 块来预测在运行时可能发生的各种异常,并适当地处理它们。 在某些情况下,您可能不得不在应用代码中抛出异常,以便让调用方法处理异常。

总结

在本章中,我们已经了解了模型、视图和控制器如何结合在一起形成一个可工作的 ASP.NET Core MVC web 应用。 我们接触了数据库设置,并以异常处理结束。

在下一章中,我们将学习 Web api,以及如何使用 ASP.NET 超越传统的 Web 应用。 我们将看看可以利用 Web API 后端的各种客户机。**