MVC 模式
模型-视图-控制器 (MVC) 体系结构模式将应用程序分成 3 个主要组件组:模型、视图和控制器。 此模式有助于实现关注点分离。 使用此模式,用户请求被路由到控制器,后者负责使用模型来执行用户操作和/或检索查询结果。 控制器选择要显示给用户的视图,并为其提供所需的任何模型数据。
下图显示 3 个主要组件及其相互引用关系:
基础介绍
ASP.NET Core MVC 采用 ASP.NET Core 作为基础,因此享有内建的相依注入能力 (Dependency Injection),ASP.NET Core MVC 本身也是 ASP.NET Core 的服务之一,因此必须要在 ASP.NET Core 的起始类别中注册并使用 MVC,才可以享有 MVC 的功能。下列例子即为在一个 ASP.NET Core 的程式的起始类别 (通常被命名为 Startup) 中注册并启用 ASP.NET Core MVC 的程式码:
public void ConfigureServices(IServiceCollection services)
{
// 加入 ASP.NET Core MVC 服務
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// ...
// 啟用 ASP.NET Core MVC
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
注册 ASP.NET Core MVC 服务后,ASP.NET Core 会自动将 MVC 的执行引擎加入 ASP.NET Core 的管线式相依注入 (Pipeline-based Dependency Injection) 的服务清单内,以开始提供 MVC 的相关服务。
路由
ASP.NET Core MVC 强化了 ASP.NET Routing 技术,使其更具弹性,除了原有的由起始类别加入的路由外,亦全面整合了之前在 ASP.NET MVC 5.2 / Web API 2.1 起支援的属性路由能力 (Attribute Routing),这表示开发人员不一定需要在起始类别注册 MVC 时定义路由,只需要在 Controller 内加入路由设定即可,但官方还是建议至少加入预设路由 (default routes),例如:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Controller
ASP.NET Core MVC 可同时支援 MVC 本身的功能以及 Web API 的功能,它们都源自相同的 Controller 基底类别,此类别已被重新实作,以支援一般的 View 以及 RESTful API 的回传值,微软亦重新定义了 ActionResult 类别,提出新的 IActionResult 界面,但开发人员不一定要回传 IActionResult 界面,也可以直接回传 .NET 内建的资料型态,Controller 会自动将它对应到 Content Result。虽然微软建议以 IActionResult 为传回型别,但原本的 ActionResult 型别仍然适用。
下列程式是一个标准的 ASP.NET Core MVC Controller 的实作,和 ASP.NET MVC 差异相当小。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace WebApplication18.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult About()
{
ViewData["Message"] = "你的应用程序描述页。";
return View();
}
public IActionResult Contact()
{
ViewData["Message"] = "你的相联页面。";
return View();
}
public IActionResult Error()
{
return View();
}
}
}
Model
在 ASP.NET MVC 中,Model 相对不设限,可以使用内置的数据结构以及自定义的资料类别,也可以是一个商业物件,因此 Model 的弹性相当大,除了前述的数据结构外,微软新发展的一些资料存取方式也可以应用在 Model 中,像是ADO.NET Entity Framework与LINQ to SQL等技术。
另外,MVC在服务端资料验证中,提供了ViewDataDictionary类别,这个类别中有一个ModelState属性,内含了ModelStateDictionary类别,开发人员可以利用这个类别来控制资料验证的结果,而View中输出验证消息的部分会和此类别有关系,例如下列的程序:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Person person)
{
if (person.Name.Trim().Length == 0)
{
ModelState.AddModelError("Name", "姓名为必填项。");
}
if (person.Age < 1 || person.Age > 200)
{
ModelState.AddModelError("Age", "年龄必须在 1 到 200 之间。");
}
if ((person.Zipcode.Trim().Length > 0) && (!Regex.IsMatch(person.Zipcode, @"^\d{5}$|^\d{5}-\d{4}$")))
{
ModelState.AddModelError("Zipcode", "邮政编码无效。");
}
if (!Regex.IsMatch(person.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
{
ModelState.AddModelError("Phone", "电话号码无效。");
}
if (!Regex.IsMatch(person.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
{
ModelState.AddModelError("Email", "电子邮件格式无效。");
}
if (!ModelState.IsValid)
{
return View("Create", person);
}
people.Add(person);
return RedirectToAction("Index");
}
在 ASP.NET MVC 2.0 中,新增了一个可以直接让 MVC Framework 针对资料字段进行验证控制的模型,称为 Model Validation,它融合了在 .NET Framework 3.5 SP1 发表的 ASP.NET Dynamic Data Framework 中 Data Annotations (资料记号) 的特性,让开发人员可以只利用标记的方式来执行验证,或是利用自定义的代码来扩展资料记号的验证行为。
using System.ComponentModel.DataAnnotations;
namespace MvcDA {
[MetadataType(typeof(ProductMD))]
public partial class Product {
public class ProductMD {
[StringLength(50),Required]
public object Name { get; set; }
[StringLength(15)]
public object Color { get; set; }
[Range(0, 9999)]
public object Weight { get; set; }
}
}
}
View
由于View是直接呈现给用户,因此与用户交互的部分都要由此层处理,包含资料的输出以及以客户端操作为主的回应(例如脚本)等。
HTML 工具类别
HTML工具类别在View中是很重要的输出工具,它内置了辅助产生HTML标签的工具方法,多数的HTML语法都可以利用它来产生,包含像链接(<a>)、窗体(<form>)以及窗体控件等等。HTML工具是以HtmlHelper类别为核心,并配合System.Web.Mvc.Html命名空间的方法,以延伸方法(Extension Method)的方式,让产生HTML的程序就有如调用方法般简单:
<h2>Index</h2>
<table>
<tr>
<th></th>
<th>
Id
</th>
<th>
Name
</th>
</tr>
<% foreach (var person in Model) { %>
<tr>
<td>
<%= Html.ActionLink("Details", "Details", person )%>
</td>
<td>
<%= Html.Encode(person.Id) %>
</td>
<td>
<%= Html.Encode(person.Name) %>
</td>
</tr>
<% } %>
</table>
<p>
<%= Html.ActionLink("Create New", "Create") %>
</p>
资料验证
View的HTML工具可以配合Model处理资料验证的结果,在ASP.NET中常用的ValidationSummary在这里也支持,而且MVC的架构让验证信息的输出也更加弹性:
<h2>Create</h2>
<%= Html.ValidationSummary("Create was unsuccessful. Please correct the errors and try again.") %>
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>Fields</legend>
<p>
<label for="Name">Name:</label>
<%= Html.TextBox("Name") %> Required
<%= Html.ValidationMessage("Name", "*") %>
</p>
<p>
<label for="Age">Age:</label>
<%= Html.TextBox("Age") %> Required
<%= Html.ValidationMessage("Age", "*") %>
</p>
<p>
<label for="Street">Street:</label>
<%= Html.TextBox("Street") %>
<%= Html.ValidationMessage("Street", "*") %>
</p>
<p>
<label for="City">City:</label>
<%= Html.TextBox("City") %>
<%= Html.ValidationMessage("City", "*") %>
</p>
<p>
<label for="State">State:</label>
<%= Html.TextBox("State") %>
<%= Html.ValidationMessage("State", "*") %>
</p>
<p>
<label for="Zipcode">Zipcode:</label>
<%= Html.TextBox("Zipcode") %>
<%= Html.ValidationMessage("Zipcode", "*") %>
</p>
<p>
<label for="Phone">Phone:</label>
<%= Html.TextBox("Phone") %> Required
<%= Html.ValidationMessage("Phone", "*") %>
</p>
<p>
<label for="Email">Email:</label>
<%= Html.TextBox("Email") %> Required
<%= Html.ValidationMessage("Email", "*") %>
</p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
<div>
<%=Html.ActionLink("Back to List", "Index") %>
</div>
不同类型的输出
每一个Controller中负责回应的方法,都会回传一个ActionResult物件的信息,ActionResult是一个执行结果物件的封装体,当MvcHandler执行完指令接到ActionResult时,就会依照它的内容来输出资料。目前MVC Framework支持的ActionResult有下列几种:
- ViewResult物件,这个物件内装载了IView接口的信息,以及IViewEngine的信息,实际产生输出资料的会是
IViewEngine,以及其指示的 View 物件。 - PartialViewResult物件,与ViewResult相似,但它回传的是"部分展示",即用户控件的View。
- ContentResult物件,装载由用户自定义的 Content-Type 以及资料。
- EmptyResult物件,表示不回传任何东西。
- HttpUnauthorizedReuslt物件,表示动作没有被授权(即 HTTP 401)的错误消息。
- JavaScriptResult物件,表示回传的是JavaScript脚本。
- JsonResult物件,表示回传的是JSON资料。
- FileResult物件,表示回传的是一个文件资料。
- RedirectResult物件,表示回传的是一个重导向 (HTTP Redirect) 指令。
- RedirectToRouteResult物件,与 RedirectResult 类似,但是它是重导向给一个 Route 的路径。
透过多类型的ActionResult,开发人员可以自由决定要回传的资料的类型与格式。