Pro ASP.NET Core MVC 第6版 第二章(后半章)

增长动态输出

整个web应用平台的关注点在于构建并显示动态输出内容。在MVC里,控制器负责构建一些数据并将其传给视图。视图负责渲染成HTML。 从控制器向视图传递数据的一种方式是使用ViewBag 对象,它是一个控制器基类的成员。ViewBag是一个动态对象,你能够给他赋值任意属性给视图来渲染用。代码2-5 演示了如何在HomeController里传递简单对象。 Listing 2-5. 设置视图数据css

using System; using Microsoft.AspNetCore.Mvc; namespace PartyInvites.Controllers { public class HomeController : Controller { public ViewResult Index() { int hour = DateTime.Now.Hour; ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon"; return View("MyView"); } } } html

我向ViewBag.Greeting属性赋值,以给视图提供数据。Greeting属性在赋值以前是不存在的,这容许我以任意流畅的方式从控制器向视图传递数据而没必要在赋值以前定义类。我在视图中引用了ViewBag.Greeting属性以得到他的值。如同代码2-6,这是修改后的MyView.cshtml。 web

Listing 2-6. 在视图里获取传递过来的值bootstrap

@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Index</title> </head> <body> <div> @ViewBag.Greeting World (from the view) </div> </body> </html> 设计模式

上面代码增长的部分是Razor表达式,它在MVC 使用视图生成相应的时候求值。当我在控制器内调用View方法的时候,MVC找到MyView.cshtml文件并请求Razor 视图引擎解析文件的内容。Razor 会查找象上面代码中的表达式。在本例中,处理表达式的意思是将ViewBag.Greeting属性插入到视图中。 浏览器

Greeting这个属性名字没有什么特殊的东西,你可使用任何其余的名字,而且同样好用。只要你在控制器中的名字与视图中的名字相同便可。你可使用多个属性来传递多个数据。而后你运行一下看一下效果,如图2-13。 服务器

fig.2-13

图2-13 一个MVC的动态响应 架构

建立一个简单的数据录入应用

在本章的剩下的部分,我将经过构建一个简单的数据录入应用来探索更多的基本MVC特征。这一节,我将会加快点速度。个人目标是用action来演示MVC,因此我将略过去一些讲解有些东西的内部原理。可是不要担忧,我将会在之后的章节中讨论那些内容的。 app

设置场景

想像一下,一个朋友决定了要举行一个新年晚会,他请我创建一个web 应用来跟踪它经过电子邀请函邀请的朋友。她须要如下四个关键功能: 框架

  • 一个关于这场晚会的信息的主页。
  • 一个能够填写邀请函的窗体。
  • 填写邀请函的时候须要验证,还要显示感谢页。
  • 一个总结页面,用来显示谁将会参加这个晚会。

在接下来的段落里,我将在前面MVC工程的基础上逐渐增长内容,增长那些功能。第一步,我将立刻就实现列表中的第一项,由于前面已经作了一些工做,只须要向现有的视图增长一些HTML来给出晚会的信息便可。代码2-7 显示了我向Views/Home/MyView.cshtml文件中增长的内容。

Listing 2-7. 显示晚会明细

@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Index</title> </head> <body> <div> @ViewBag.Greeting World (from the view) <p>We're going to have an exciting party.<br /> (To do: sell it better. Add pictures or something.) </p> </div> </body> </html>

我继续。 若是你运行,你会看见晚会的信息,(额,仍是一些占位符,可是你已经明白了吧)如图2-14.

fig.2-14 图 2-14 向视图里增长HTML

增长一个数据模型

在MVC里,M 表明模型, 他是应用程序里最重要的部分。模型是真实世界对象的表明,处理,定义领域的规则。模型常常被称为是领域模型,包含C# 对象(领域对象)构成应用程序的世界和操纵他们的方法。视图和控制器会将领域用一致的方式暴露给客户端,而且,一个设计良好的MVC应用程序应该从一个设计良好的模型开始。而后控制器和视图才加入。

在PartyInvites工程里,我不须要复杂的模型,由于这是一个很是简单的应用,我只须要创建一个领域类,而后我将会调用GuestResponse.这个对象将负责保存,验证和确认一个邀请函。

MVC的约定通常把模型放在Models文件夹里,要创建这个文件夹,右击PartyInvites工程,从菜单中选择Add->New Folder ,而后设置名字为Models。

注意: 当程序运行时,你不能设置一个文件夹的名字。你能够从Debug菜单里选择Stop Debugging,右击你已经加入的NewFolder项,而后从弹出菜单中选择Rename,而后改为Models。

要创建一个类文件,右击Models文件夹,而后在弹出菜单里选择Add->Class。 设置新的类名字为GuestResponse.cs ,而后单击Add按钮,编辑新类的内容为代码2-8.

Listing 2-8 GuestResponse 领域类定义

namespace PartyInvites.Models { public class GuestResponse { public string Name { get; set; } public string Email { get; set; } public string Phone { get; set; } public bool? WillAttend { get; set; } } }

提示: 你可能已经注意到了 WillAttend 属性是一个可空的bool型,意思是它能够是true,false,或null。我将解释她的基本原理在本章的"增长校验"节。

创建第二个Action和强类型的视图

个人应用程序的一个目标是包含一个邀请函窗体,也就是我将要定义一个行动(action)方法能够为它接收请求。一个单独的控制器类能够定义多个行动方法,默认约定是将相关的行动放到同一个控制器内。代码2-9展示了Home 控制器中新增长的行动方法。

Listing 2-9. Adding an Action Method in the HomeController.cs File using System; using Microsoft.AspNetCore.Mvc; namespace PartyInvites.Controllers { public class HomeController : Controller { public ViewResult Index() { int hour = DateTime.Now.Hour; ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon"; return View("MyView"); } public ViewResult RsvpForm() { return View(); } } }

RsvpForm 行动方法调用View方法,不带参数,这将会告诉MVC取渲染链接于该行动方法的默认的视图,与行动方法的名字相同,在这里是RsvpForm.cshtml。 右击Views->Home 文件夹并在弹出菜单中选择Add->New Item。从ASP.NET 分类里选择MVC View Page模板,设置新的名字为RsvpForm.cshtml,并点击Add 按钮来建立文件。修改该文件的内容,让他变成代码 2-10那样。

Listing 2-10. 设置视图文件

@model PartyInvites.Models.GuestResponse @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>RsvpForm</title> </head> <body> <div> This is the RsvpForm.cshtml View </div> </body> </html>

上面内容大部分为HTML,中间夹杂有@model Razor表达式,用来建立一个强类型的视图。强类型的视图用来渲染特定类型的模型,若是我指定一个类型(本例中,GuestResponse类),MVC能够建立一些有用的快捷方式并使它更容易。一下子我将利用强类型的特征。 要测试新的行动方法和它的视图,启动应用程序,并使用浏览器浏览/Home/RsvpForm 。 MVC将使用命名约定来重定向请求到Home控制器中的RsvpForm行动方法。这个行动方法告诉MVC去渲染默认的视图,这里又使用了另外一个命名规范,渲染RsvpForm.cshtml。图2-15展现告终果。

fig.2-15

图2-15 渲染第二个视图

链接行动方法

我想要从MyView视图内创建一个链接,以便个人客人可以看见RsvpForm视图而没必要知道URL。如代码2-11。

Listing 2-11. 在MyView.cshtml里增长一个链接

@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Index</title> </head> <body> <div> @ViewBag.Greeting World (from the view) <p>We're going to have an exciting party.<br /> (To do: sell it better. Add pictures or something.) </p> <a asp-action="RsvpForm">RSVP Now</a> </div> </body> </html>

代码中增长的部分是一个具备asp-action属性的<a>标记。这是一个标记帮助器(tag helper)属性,他是一个给Razor的指令,在视图渲染的时候执行。这里的asp-action属性是用来给<a>标记增长一个href属性,包含指向一个行动方法的URL。我将会在第24,25和26章结束适合使用标记帮助器。可是这里是一个<a>标记的简单的标记帮助器,他告诉Razor 插入定义在与本视图相同的控制器中定义的一个行动方法的URL。程序运行的时候你会看到这个连接。如图2-16所示。

fig.2-16

图 2-16 在行动方法之间加连接

启动应用程序并将鼠标放在RSVP Now链接的上面,你会看到链接指向的是下面的URL(端口可能会有不一样): http://localhost:57628/Home/RsvpForm

这里有一个重要的原则,即你应该使用MVC生成URL的功能,而不是在你的视图里硬编码。当标记帮助器给<a标记创建href属性时,他会检查当前应用程序的配置并计算出URL是什么样子。这容许改变应用程序配置来支持不一样的URL格式而无需更新视图。我将会在第15章解释其原理。

创建窗体

如今我已经创建了强类型的视图,而且可以在Index视图中链接到它,我将在RsvpForm.cshtml文件里增长一些内容,并使他们变成一个HTML表单,用来编辑GuestResponse对象,如代码2-12。

Listing 2-12. 创建一个输入表单视图

@model PartyInvites.Models.GuestResponse @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>RsvpForm</title> </head> <body> <form asp-action="RsvpForm" method="post"> <p> <label asp-for="Name">Your name:</label> <input asp-for="Name" /> </p> <p> <label asp-for="Email">Your email:</label> <input asp-for="Email" /> </p> <p> <label asp-for="Phone">Your phone:</label> <input asp-for="Phone" /></p> <p> <label>Will you attend?</label> <select asp-for="WillAttend"> <option value="">Choose an option</option> <option value="true">Yes, I'll be there</option> <option value="false">No, I can't come</option> </select> </p> <button type="submit">Submit RSVP</button> </form> </body> </html>

我已经为GuestResponse模型类的每个属性定义了一个标签和输入元素。每个元素都使用asp-for属性链接一个模型的属性。这里的asp-for是另外一个标签帮助器,他能配置将元素链接到模型对象。下面是一个例子,由标记帮助器生成,发给浏览器的HTML:

<p> <label for="Name">Your name:</label> <input type="text" id="Name" name="Name" value=""> </p>

这里Label 上的asp-for 帮助器能够给for属性设置值。input 元素上的asp-for 能够设置元素的id 和name。这些都不是什么特殊的用途,可是你将会看到将元素链接到模型的属性会带来更多的好处。 立刻你就会看到更多的asp-action属性应用到了form元素, 它使用了应用程序的URL路由配置来设置form的action属性为一个指向到一个特殊的行动方法的URL,以下面这样:

<form method="post" action="/Home/RsvpForm"> 与我应用到元素的助手属性同样,这种方法的好处是更改应用程序使用的URL系统,标签助手生成的内容将反映自动变化。 运行该应用程序并点击RSVP Now 链接,能够看到窗体,如图2-17。

fig.2-17

图 2-17 在行动方法之间加连接

接收数据

我尚未告诉MVC当表单被发送到服务器时我想作什么。目前来看,点击Submit RSVP按钮只是清除您输入到表单中的值。那是由于表单将数据发回Home控制器的RsvpForm行动方法后只告诉MVC再次渲染这个视图,没作别的事情。 要接收和处理提交的表单数据,我将使用核心控制器功能。我会加第二个RsvpForm动做方法来创建下面这些内容:

  • 一个响应HTTP GET请求的方法:浏览器每次单击连接时,一般会引起一个GET请求。这个版本的行动方法将在用户第一次访问Home/RsvpForm时显示一个空白的表单。
  • 一个响应HTTP POST请求的方法: 默认的状况下,使用Html.BeginForm()的表单会被浏览器使用POST请求提交。这个版本的行动,将会负责接受提交的数据并决定下一步对这些数据做什么处理。 用不一样的C#方法分别处理Get和POST请求会让你的代码看起来更简洁。由于两个方法应该行使不一样的职责。两个方法会被同一个URL引起,可是MVC可以确根据GET请求或POST请求来调用适当的方法。代码2-13 能够看到HomeController类所作的更改。

Listing 2-13. 增长一个行动方法来支持POST请求

using System; using Microsoft.AspNetCore.Mvc; using PartyInvites.Models; namespace PartyInvites.Controllers { public class HomeController : Controller { public ViewResult Index() { int hour = DateTime.Now.Hour; ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon"; return View("MyView"); } [HttpGet] public ViewResult RsvpForm() { return View(); } [HttpPost] public ViewResult RsvpForm(GuestResponse guestResponse) { // TODO: store repsonse from guest return View(); } } }

我已经给已经存在的RsvpForm行动方法加上了一个HttpGet的特性(attribute),这回告诉MVC此方法仅可以被Get请求调用。而后我给RsvpForm方法增长了一个重载的版本,他能够接受GuestResponse 对象。 对这个方法我应用了HttpPost特性。它会告诉MVC 新的方法将处理POST请求。在后面的段落里我将会介绍这些代码是如何工做的。我也引入了一个叫作PartyInvites.Models的命名空间。加入它是为了我可以使用GuestResponse 模型类型。

使用模型绑定

第一个重载的RsvpForm行动方法渲染的视图同前面的同样--RsvpForm.cshtml--是用来生成图2-17那样的表单的。第二个重载的是个更有意思的事,可是考虑到响应于HTTP POST请求将调用行动方法,GuestResponse类型是一个C#类,二者是如何链接的?

答案是模型绑定,一个有用的MVC功能,其中输入数据被解析成HTTP请求中的键/值对,用于填充领域模型类型的属性。  模型绑定是一种功能强大且可自定义的功能,能够消除磨合而直接处理HTTP请求,并让您使用C#对象,而不是处理由浏览器发送的单个数据值。做为参数传递给Action方法的GuestResponse对象是自动填充表单字段中的数据的。 在第26章我将会介绍更多关于模型绑定的细节,包括如何定制。

应用程序中另外一个目标是提供一个摘要页面,其中包含参加人的详细信息,这须要跟踪我收到的回复。 我将经过建立一个内存中的Collection对象来作到这一点。 这在实际应用中是没有什么用途的,由于应用程序中止或从新启动的话,数据将丢失,但这种方法容许我将重点放在MVC上并建立一个能够轻松地重置为初始状态的应用程序。

提示:第8章中,我演示了一个更实际的示例应用程序,将演示如何在MVC中永久地存储和访问数据。

我经过右键单击Models文件夹并从弹出窗口中选择Add->Class,将文件添加到项目中。 我将文件的名称设置为Repository.cs,并使用它来定义一个类,如清单2-14所示。

Listing 2-14. Repository.cs 文件的内容

using System.Collections.Generic; namespace PartyInvites.Models { public static class Repository { private static List<GuestResponse> responses = new List<GuestResponse>(); public static IEnumerable<GuestResponse> Responses { get { return responses; } } public static void AddResponse(GuestResponse response) { responses.Add(response); } } }

Repository类及其成员是静态的,这将使我很容易从应用程序中的不一样位置存储和检索数据。 MVC提供了一种更为复杂的方法来定义常见的功能,称为依赖注入,我在第18章中描述,但静态类是一个简单的应用程序入门的好方法。

保存响应

如今我有一个存储数据的地方,我能够更新接收HTTP POST请求的操做方法,如清单2-15所示。

Listing 2-15. 更新行动方法

using System; using Microsoft.AspNetCore.Mvc; using PartyInvites.Models; namespace PartyInvites.Controllers { public class HomeController : Controller { public ViewResult Index() { int hour = DateTime.Now.Hour; ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon"; return View("MyView"); } [HttpGet] public ViewResult RsvpForm() { return View(); } [HttpPost] public ViewResult RsvpForm(GuestResponse guestResponse) { Repository.AddResponse(guestResponse); return View("Thanks", guestResponse); } } }

处理请求中发送的表单数据的全部操做都是与传递给行动方法的GuestResponse对象一块儿使用 - 在这种状况下,将其做为参数传递给Repository.AddResponse方法,以便保存响应。

为何模型绑定不像Web Forms?

在第一章中,我解释说传统ASP.NET Web窗体的一个缺点是它隐藏了开发人员的HTTP和HTML的细节。 您可能会想知道用于从代码2-15中的HTTP POST请求建立GuestResponse对象的MVC模型绑定是否会作一样的事情。 他没有这样作。模型绑定使我免除无聊且容易出错的任务,由于咱们必须检查HTTP请求并提取我须要的全部数据值,可是(这是重要的部分),若是我想手动处理请求,那也能够,由于MVC能够方便地访问全部的请求数据。MVC没有对开发人员隐藏任何东西,可是有一些有用的功能可使HTTP和HTML更简单。对于这些功能你能够用,也能够不用,随便。

这彷佛是一个微妙的区别,可是当您了解有关MVC的更多信息时,您将看到开发体验与传统Web窗体彻底不一样,而且您能够始终可以了解到应用程序收到的请求是如何处理的。

在RsvpForm操做方法中对View方法的调用告诉MVC渲染一个名为Thanks的视图,并将GuestResponse对象传递给视图。 要建立视图,请右键单击解决方案资源管理器中的“视图/主页”文件夹,而后从弹出菜单中选择“添加”->“新建项目”。 在ASP.NET类别中选择MVC视图页面模板,将名称设置为Thanks.cshtml,而后单击添加按钮。 Visual Studio将建立Views / Home / Thanks.cshtml文件并打开它进行编辑。 用代码2-16替换文件的内容。

Listing 2-16. Thanks.cshtml 文件的内容

@model PartyInvites.Models.GuestResponse @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Thanks</title> </head> <body> <p> <h1>Thank you, @Model.Name!</h1> @if (Model.WillAttend == true) { @:It's great that you're coming. The drinks are already in the fridge! } else { @:Sorry to hear that you can't make it, but thanks for letting us know. } </p> <p>Click <a asp-action="ListResponses">here</a> to see who is coming.</p> </body> </html>

Thanks.cshtml视图使用Razor来显示RsvpForm操做方法中传递给View方法的GuestResponse属性的值。 Razor中,@model表达式指定强制键入视图的领域模型类型。   要访问域对象中的属性的值,我使用Model.PropertyName。 例如,要获取Name属性的值,我调用Model.Name。 若是不了解Razor的语法,不要担忧,我在第5章更详细地解释它。   如今我已经建立了Thanks视图,我已经有了一个基本的工做示例-使用MVC一个表单。 经过从Debug菜单中选择Start Debugging来启动Visual Studio中的应用程序,单击“RSVP Now”连接,在表单中添加一些数据,而后单击“Submit RSVP”按钮, 你会看到如图2-18所示的结果(尽管若是你的名字不是Joe)。

fig.2-18

图2-18 感谢视图

显示响应

在Thanks.cshtml视图的结尾,我添加了一个元素来建立一个连接来显示参加派对的人员列表。 我使用asp-action标签帮助器属性来建立一个目标名为ListResponses的动做方法的URL,像这样:

... <p>Click <a asp-action="ListResponses">here</a> to see who is coming.</p> ...

若是您将鼠标悬停在浏览器显示的连接上,您将看到它的URL是/Home/ListResponses。 这与Home控制器中的任何操做方法不对应,若是单击连接,您将看到一个空页面。 打开浏览器的开发工具并查看服务器发送的响应会显示服务器发回404 - 未找到错误(Chrome有一点奇怪的是它不会向用户显示错误消息,可是 我将在第14章解释如何产生有意义的错误消息)。   我将经过建立Home控制器中URL定位的操做方法来解决问题,如清单2-17所示。

Listing 2-17. 在控制器里增长一个行动方法

using System; using Microsoft.AspNetCore.Mvc; using PartyInvites.Models; using System.Linq; namespace PartyInvites.Controllers { public class HomeController : Controller { public ViewResult Index() { int hour = DateTime.Now.Hour; ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon"; return View("MyView"); } [HttpGet] public ViewResult RsvpForm() { return View(); } [HttpPost] public ViewResult RsvpForm(GuestResponse guestResponse) { Repository.AddResponse(guestResponse); return View("Thanks", guestResponse); } public ViewResult ListResponses() { return View(Repository.Responses.Where(r => r.WillAttend == true)); } } }

新的Action方法称为ListResponses,它使用Repository调用View方法。 响应属性做为参数。 这是向强类型视图提供数据的一种操做方法。 使用LINQ过滤GuestResponse对象的集合,以便获得正确响应。 ListResponses行动方法没有指定应该用于显示GuestResponse对象的集合的视图的名称,这意味着将使用默认的命名约定,MVC将在Views/Home和Views/Shared文件夹中查找名为ListResponses.cshtml的视图。要建立视图,请右键单击解决方案资源管理器中的“视图/主页”文件夹,而后从弹出菜单中选择“添加”->“新建项目”。 在ASP.NET类别中选择MVC视图页面模板,将名称设置为ListResponses.cshtml,而后单击添加按钮。 编辑新视图的内容如代码2-18。

Listing 2-18. 显示接收

@model IEnumerable<PartyInvites.Models.GuestResponse> @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Responses</title> </head> <body> <h2>Here is the list of people attending the party</h2> <table> <thead> <tr> <th>Name</th> <th>Email</th> <th>Phone</th> </tr> </thead> <tbody> @foreach (PartyInvites.Models.GuestResponse r in Model) { <tr> <td>@r.Name</td> <td>@r.Email</td> <td>@r.Phone</td> </tr> } </tbody> </table> </body> </html>

Razor视图文件具备cshtml文件扩展名,由于它们是C#代码和HTML元素的组合。 您能够在清单2-18中看到这一点,其中我使用foreach循环来处理使用View方法将action方法传递给视图的每一个GuestResponse对象。与正常的C#foreach循环不一样,Razor foreach循环的主体包含添加到将被发送回浏览器的响应中的HTML元素。在此视图中,每一个GuestResponse对象都生成一个tr元素,其中包含用对象属性的值填充的td元素。  要查看工做中的列表,请经过从开始菜单中选择启动调试来运行应用程序,提交一些表单数据,而后单击连接以查看响应列表。您将看到从应用程序启动后输入的数据摘要,如图2-19所示。该视图呈现数据的方式不太美观,但还能够了,本章稍后将介绍应用程序的样式。

fig.2-19

图2-18 显示参加人员列表

加入数据验证

我如今能够向个人应用程序添加数据验证。 没有验证,用户能够瞎输入数据,甚至提交一个空的表单。 在MVC应用程序中,一般将验证应用于领域模型,而不是在用户界面中。 您能够在一个位置定义验证,可是它将在使用模型类的应用程序中的任何位置生效。 MVC支持使用system.ComponentModel.DataAnnotations 命名空间中的属性定义的声明性验证规则,这意味着可使用标准C#属性特征表示验证约束。 清单2-19显示了如何将这些属性应用于GuestResponse模型类。

Listing 2-19. 应用数据验证

using System.ComponentModel.DataAnnotations; namespace PartyInvites.Models { public class GuestResponse { [Required(ErrorMessage = "Please enter your name")] public string Name { get; set; } [Required(ErrorMessage = "Please enter your email address")] [RegularExpression(".+\\@.+\\..+", ErrorMessage = "Please enter a valid email address")] public string Email { get; set; } [Required(ErrorMessage = "Please enter your phone number")] public string Phone { get; set; } [Required(ErrorMessage = "Please specify whether you'll attend")] public bool? WillAttend { get; set; } } }

MVC会自动检测属性,并在模型绑定过程当中使用它们来验证数据。 我导入了包含验证属性的命名空间,因此我能够引用它们,而不须要限定他们的名字。

提示: 如前所述,我为WillAttend属性使用了可空的bool类型。 这样作可让我应用必需的验证属性。 若是我使用了常规bool类型,我经过模型绑定收到的值可能只是真或假,我没法判断用户是否选择了一个值。 可空的bool有三个可能的值:true,false和null。 若是用户没有选择值,浏览器会发送一个空值,这会致使Required属性报告验证错误。 这是一个很好的例子,演示MVC如何优雅地将C#功能与HTML和HTTP混合在一块儿。

我使用Controller类中的ModelState.IsValid属性来检查是否存在验证问题。 清单2-20显示了如何在Home控制器类的POST对应的RsvpForm行动方法中完成此操做。

Listing 2-20. 校验表单中的错误

using System; using Microsoft.AspNetCore.Mvc; using PartyInvites.Models; using System.Linq; namespace PartyInvites.Controllers { public class HomeController : Controller { public ViewResult Index() { int hour = DateTime.Now.Hour; ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon"; return View("MyView"); } [HttpGet] public ViewResult RsvpForm() { return View(); } [HttpPost] public ViewResult RsvpForm(GuestResponse guestResponse) { if (ModelState.IsValid) { Repository.AddResponse(guestResponse); return View("Thanks", guestResponse); } else { // there is a validation error return View(); } } public ViewResult ListResponses() { return View(Repository.Responses.Where(r => r.WillAttend == true)); } } }

Controller基类提供了一个名为ModelState的属性,它提供有关将HTTP请求数据转换为C#对象的信息。若是ModelState.IsValue属性返回true,那么我知道MVC已经可以知足经过GuestResponse类中的属性指定的验证约束。当这种状况发生时,我就像之前同样渲染了Thanks视图。 若是ModelState.IsValue属性返回false,那么我知道有验证错误。由ModelState属性返回的对象提供了错误的详细信息,可是我不须要进入该级别的详细信息,由于我可使用一个有用的功能,自动请求用户解决任何经过调用View方法没有任何参数的问题。当MVC呈现视图时,Razor能够访问与请求相关联的任何验证错误的详细信息,标签助手能够访问详细信息以向用户显示验证错误。清单2-21显示了向RsvpForm视图添加验证标签助手属性。

Listing 2-21. 增长验证汇总

@model PartyInvites.Models.GuestResponse @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>RsvpForm</title> </head> <body> <form asp-action="RsvpForm" method="post"> <div asp-validation-summary="All"></div> <p> <label asp-for="Name">Your name:</label> <input asp-for="Name" /> </p> <p> <label asp-for="Email">Your email:</label> <input asp-for="Email" /> </p> <p> <label asp-for="Phone">Your phone:</label> <input asp-for="Phone" /></p> <p> <label>Will you attend?</label> <select asp-for="WillAttend"> <option value="">Choose an option</option> <option value="true">Yes, I'll be there</option> <option value="false">No, I can't come</option> </select> </p> <button type="submit">Submit RSVP</button> </form> </body> </html>

将asp-validation-summary属性应用于div元素,并在显示视图时显示验证错误列表。 asp-validation-summary属性的值是一个名为ValidationSummary的枚举的值,它指定了摘要将包含哪些类型的验证错误。 我指定了All,这对于大多数应用程序来讲都很适用,我将在第27章描述其余值并解释它们中的工做原理。 要了解验证摘要的工做原理,请运行应用程序,填写“名称”字段,并提交表单而不输入任何其余数据。 您将看到验证错误的摘要,如图2-20所示。

fig.2-20

图2-20 显示验证错误

若是GuestResponse类的约束得没有获得验证,RsvpForm操做方法将不会呈现“感谢”视图。 请注意,当Razor使用验证摘要呈现视图时,它会保留并显示输入到“名称”字段中的数据。 这是模型绑定的另外一个好处,它简化了处理表单数据的工做。

注意: 若是你使用过ASP.NET Web Forms,你会知道Web Forms会将值序列化为名为__VIEWSTATE的隐藏表单字段来保留服务器控件的状态。 MVC将模型绑定到Web窗体服务器控件与View State的概念无关。 MVC不会在您呈现的HTML页面中注入隐藏的__VIEWSTATE字段。 而是经过设置输入元素的值属性来包含数据。

高亮显示无效字段

将模型属性与元素相关联的标签助手属性具备模型绑定的便捷功能。 当模型类属性验证失败时,帮助器属性将生成稍微不一样的HTML。 如下是当没有验证错误时的为“电话”字段生成的输入元素:

<input type="text" data-val="true" data-val-required="Please enter your phone number" id="Phone" name="Phone" value="">

相对的,这是在用户在文本字段中不输入任何数据并提交表单产生的HTML元素,(这是一个验证错误,我将Required的验证属性应用到了GuestResponse类的Phone属性上):

<input type="text" class="input-validation-error" data-val="true" data-val-required="Please enter your phone number" id="Phone" name="Phone" value="">

我已经高亮显示了两者的区别:asp-for标签帮助器属性将输入元素添加到一个名为input-validation-error的类中。 我建立一个包含此类的CSS样式的样式表,不一样的HTML助手属性使用的其余样式表来实现这个效果。  MVC项目中的约定是将传递给客户端的静态内容放入wwwroot文件夹中,按内容类型进行组织,以便CSS样式表进入wwwroot/css文件夹,JavaScript文件进入wwwroot/js文件夹,依此类推。   要建立样式表,请右键单击Visual Studio解决方案资源管理器中的wwwroot/css文件夹,选择添加->新项,导航到客户端部分,而后从模板列表中选择样式表,如图2-21。

fig.2-21

图2-21 创建CSS样式表

提示:当使用Web应用程序模板建立项目时,Visual Studio会在wwwroot / css文件夹中建立一个style.css文件。 你能够忽略这个文件,我在本章不使用它。

将文件的名称设置为styles.css,单击添加按钮建立样式表,而后编辑新文件,把他改为代码2-22所示的样式。

Listing 2-22. styles.css 文件的内容

.field-validation-error {color: #f00;} .field-validation-valid { display: none;} .input-validation-error { border: 1px solid #f00; background-color: #fee; } .validation-summary-errors { font-weight: bold; color: #f00;} .validation-summary-valid { display: none;} To apply this stylesheet, I have added a link element to the head section of the RsvpForm view, as shown in Listing 2-23 . Listing 2-23. Applying a Stylesheet in the RsvpForm.cshtml File ... <head> <meta name="viewport" content="width=device-width" /> <title>RsvpForm</title> <link rel="stylesheet" href="/css/styles.css" /> </head> ...

link 元素使用href属性来指定样式表的位置。 请注意,URL中省略了wwwroot文件夹。 ASP.NET的默认配置包括支持静态内容,如图像,CSS样式表和JavaScript文件,并将请求自动映射到wwwroot文件夹。 我在第14章中描述了ASP.NET和MVC配置过程。

提示:有一个特殊的标签帮助器来处理样式表,若是你有不少文件要管理,这能够颇有用。 详见第25章。

随着样式表的应用,当提交致使验证错误的数据时,会显示更明显的验证错误,如图2-22所示。

fig.2-22

图2-22 自动高亮错误

设置样式

应用程序的全部功能目标都完成了,但应用程序的总体外观还须要调整一下。 当您使用Web应用程序模板建立项目时,如本章中的示例所示,Visual Studio安装了一些常见的客户端开发包。 虽然我不是使用模板的粉丝,但我喜欢Microsoft选择的客户端库。 其中一个被称为Bootstrap,它是一个很好的CSS框架,最初由Twitter开发,已经成为一个主要的开源项目,它已经成为Web应用程序开发的支柱。

注意:Bootstrap 3是我写的当前版本,可是第4版正在开发中。 Microsoft可能会选择在Visual Studio的更高版本中更新Web应用程序模板使用的Bootstrap版本,这可能会致使内容显示不一样。 这对于本书中的其余章节来讲不会是一个问题,由于我向您展现如何明确指定包版本,以便得到预期的结果。

设置Welcome视图的样式

wwwroot/lib/bootstrap文件夹的文件中定义了一些CSS选择器,基本的Bootstrap功能经过将类应用到与添加到CSS选择器相对应的元素来工做。 您能够从http://getbootstrap.com 获取Bootstrap定义的类的详细信息,代码2-24演示了如何将一些基本样式应用于MyView.cshtml视图。

Listing 2-24. 给MyView.cshtml添加Bootstrap

@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Index</title> <link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" /> </head> <body> <div class="text-center"> <h3>We're going to have an exciting party!</h3> <h4>And you are invited</h4> <a class="btn btn-primary" asp-action="RsvpForm">RSVP Now</a> </div> </body> </html>

我添加了其中link元素,他的href属性是wwwroot/lib/bootstrap/dist/css/bootstrap.css。 第三方CSS和JavaScript包安装在wwwroot/lib文件夹中是一个经常使用约定,我将在第6章中描述用于管理这些包的工具。   导入Bootstrap样式表后,我须要为个人元素设置样式。 这是一个简单的例子,因此我只须要使用少许的Bootstrap CSS类:text-center,btn和btn-primary。   text-center class将元素及其子元素的内容置于中心位置。 btn类将按钮、input元素变得更漂亮,btn-primary指定我想要的按钮的颜色范围。 运行应用程序能够看到效果,如图2-23所示。

fig.2-23

图2-23 设置视图样式

很明显,我不是网页设计师。 事实上,做为一个孩子,我根本就没有任何才华,所以我被排除在艺术课以外。 这使得我有更多时间来上数学课,但意味着个人艺术能力并无超过10岁的平均水平。 对于一个真正的项目,我会寻求一位专业人士来帮助设计和设计内容,可是在这个例子中,我一我的就这样作了,也就是应用Bootstrap能够像我同样有一些限制和一致性。

设置RsvpForm视图的样式

Bootstrap定义了能够用于样式表单的类。 我不会详细介绍他们,可是您能够看到我已经在清单2-25中应用了这些类。

Listing 2-25. 给RsvpForm.cshtml添加Bootstrap

@model PartyInvites.Models.GuestResponse @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>RsvpForm</title> <link rel="stylesheet" href="/css/styles.css" /> <link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" /> </head> <body> <div class="panel panel-success"> <div class="panel-heading text-center"><h4>RSVP</h4></div> <div class="panel-body"> <form class="p-a-1" asp-action="RsvpForm" method="post"> <div asp-validation-summary="All"></div> <div class="form-group"> <label asp-for="Name">Your name:</label> <input class="form-control" asp-for="Name" /> </div> <div class="form-group"> <label asp-for="Email">Your email:</label> <input class="form-control" asp-for="Email" /> </div> <div class="form-group"> <label asp-for="Phone">Your phone:</label> <input class="form-control" asp-for="Phone" /> </div> <div class="form-group"> <label>Will you attend?</label> <select class="form-control" asp-for="WillAttend"> <option value="">Choose an option</option> <option value="true">Yes, I'll be there</option> <option value="false">No, I can't come</option> </select> </div> <div class="text-center"> <button class="btn btn-primary" type="submit"> Submit RSVP </button> </div> </form> </div> </div> </body> </html>

此示例中的Bootstrap类建立一个标题,只给了布局的结构。 要设置样式,我使用了form-group类,用于给标签元素和相关的input或select元素设置样式。 您能够在图2-24中看到效果。

fig.2-24

图2-24 设置RsvpForm视图的样式

设置Thanks视图的样式

下一个要设置样式的视图是Thanks.cshtml,您可使用相似于我用于其余视图的CSS类来看清楚如何在代码2-26中完成此操做。 为了使应用程序更易于管理,尽量避免重复代码和标记,这一个很好的原则。 MVC提供了几个功能来帮助减小重复,我将在后面的章节中介绍。 这些功能包括Razor布局(第5章),部分视图(第21章)和视图组件(第22章)。

Listing 2-26. 设置Thanks.cshtml样式

@model PartyInvites.Models.GuestResponse @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Thanks</title> <link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" /> </head> <body class="text-center"> <p> <h1>Thank you, @Model.Name!</h1> @if (Model.WillAttend == true) { @:It's great that you're coming. The drinks are already in the fridge! } else { @:Sorry to hear that you can't make it, but thanks for letting us know. } </p> Click <a class="nav-link" asp-action="ListResponses">here</a> to see who is coming. </body> </html>

图2-25 显示了样式的效果。

fig.2-25

图2-25 Thanks视图的样式

设置列表视图的样式

最后的风格是ListResponses,它列出了与会者的列表。 对内容设置样式遵循与全部Bootstrap样式相同的基本方法,如代码2-27所示。

Listing 2-27. ListResponses.cshtml 加 Bootstrap

@model IEnumerable<PartyInvites.Models.GuestResponse> @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" /> <title>Responses</title> </head> <body> <div class="panel-body"> <h2>Here is the list of people attending the party</h2> <table class="table table-sm table-striped table-bordered"> <thead> <tr> <th>Name</th> <th>Email</th> <th>Phone</th> </tr> </thead> <tbody> @foreach (PartyInvites.Models.GuestResponse r in Model) { <tr> <td>@r.Name</td> <td>@r.Email</td> <td>@r.Phone</td> </tr> } </tbody> </table> </div> </body> </html>

图2-26显示了与会者的表格的呈现方式。 将这些样式添加到视图中完成了示例应用程序,该应用程序如今实现了全部的开发目标,而且外观也已经改的很好了。

fig.2-26

图2-26 ListResponses视图的样式

小结

在本章中,我建立了一个新的MVC项目,并使用它来构建一个简单的数据录入应用程序,让您首先了解ASP.NET Core MVC架构和方法。 我跳过了一些关键的东西(包括Razor语法,路由和测试),但我将在后面的章节深刻介绍这些内容。 在下一章中,我将描述MVC设计模式,这是开发ASP.NET Core MVC应用程序的基础。

相关文章
相关标签/搜索