面向 Web Forms 开发人员的 ASP.NET MVC 入门教程






4.87/5 (93投票s)
在本文中,我们将从初学者的角度来了解 ASP.NET MVC 架构。
引言
在本文中,我们将尝试从初学者的角度来了解 ASP.NET MVC 架构。关于同一主题的文章比比皆是。本文是我用自己的话再次尝试解释 MVC。
背景
ASP.NET MVC 是基于 ASP.NET 框架的架构模式,它提供了一种清晰优雅的 Web 应用程序开发方式。在理解 MVC 的流行原因之前,我们需要先了解 Web Forms 的一些局限性。
简要讨论 Web Forms
让我们讨论一下 Web Forms 中被视为缺点的几点局限性。我相信许多资深开发人员会同意,通过遵循最佳实践和指南可以控制这些缺点,但尽管如此,在了解 ASP.NET MVC 架构之前讨论它们仍然是个好主意。
- ViewState:当 ASP.NET 能够为无状态 HTTP 协议提供有状态抽象时,ViewState 是 ASP.NET 的核心。但强大的功能也伴随着巨大的责任。如果 ViewState 管理不当,它会大大增加渲染的网页大小,从而导致额外的负载。
- 生成的 HTML:服务器端控件是 Web Forms 的另一个强大卖点。它们促进了快速的应用程序开发,而无需过多关注客户端 HTML 代码。但随着客户端技术(JavaScript、jQuery 等)的使用不断增加,对生成标记的控制越来越少正成为一个问题。
- URL:在 ASP.NET Web Forms 中,请求 URL 指向物理文件,并且 URL 结构对 SEO 不友好。此外,现在 URL 结构非常重要,并且非常需要干净的 URL 和对 URL 的控制。
- 后台代码:在我看来,后台代码模型是 ASP.NET 最好的部分。它提供了清晰的关注点分离。从开发人员的角度来看,拥有后台代码文件提供了一种编写服务器端逻辑的好方法,而无需在 HTML 标记中编写代码。
- 页面生命周期:尽管后台代码模型为提供关注点分离提供了一种好方法,但页面生命周期有些复杂,需要 fully understood。如果开发人员未能理解页面生命周期,最终应用程序中可能会出现一些未预料到的不良问题。
- 测试驱动开发支持:现在是敏捷的时代。当我们遵循敏捷开发方法(特别是 Scrum)时,定义 Scrum 中的“完成”非常重要。TDD 有助于此。如果所有测试用例都通过了,我们就可以确定“完成”已经完成。ASP.NET 由于很难模拟 HTTP 请求和 HTTP 上下文,因此不对 TDD 提供本地支持。
说了(以上所有观点)之后,我还想指出,随着 ASP.NET Web Forms 的每个新版本发布,Web Forms 的局限性问题正在变得模糊。许多这些局限性正在 Web Forms 中得到解决。ASP.NET 4.0
增加了 URL 路由,减少了 ViewState
,并对 HTML 标记有了更好的控制。通过遵循最佳实践和设计指南,可以解决 Web Forms 中与泄漏抽象相关的许多其他问题。并且可以通过一些工具来执行 TDD。
所以这不是哪个更好的问题。ASP.NET Web Forms 和 MVC 是两种不同的架构风格。Forms 专注于快速应用程序开发(并且随着每个新版本的发布,现在变得越来越好),而 MVC 用于设计 Web 应用程序,摆脱 Forms 带来的负担并遵循良好的模式。
许多开发人员认为 MVC 是开发 Web 应用程序的新方式,而 Web Forms 已经过时。但严格来说,在我个人看来,它们都是两种不同的架构风格,没有一种能凌驾于另一种之上。我们应该根据我们的需求和可用资源来选择一种。事实上,能够混合这两种风格的可能性是最好的。我们可以在单个应用程序中使用这两种风格,从而获得两全其美。
了解 MVC
MVC 框架接受 HTTP 是无状态的事实,因此框架不会提供有状态抽象。由开发人员自己负责管理 MVC 应用程序的状态。
在 MVC 架构中有三个主要参与者:Model
、View
和 Controller
。我们需要致力于创建这些组件才能使我们的 Web 应用程序正常工作。现在,在详细介绍这三者之前,让我们尝试思考如何将服务器端内容放入 HTML 页面。我们将采取一种倒置的方法来理解这一点。
我们需要在渲染之前将一些服务器端数据放入 HTML 标记。现在,这在 Web Forms 中也可以轻松完成,我们过去会在 aspx 标记中放置 C# 代码。唯一的先决条件是,我们应该有一个包含服务器端值的对象,从中可以提取数据。这就是 MVC 中的视图所做的。它们只是运行,在渲染标记之前,它们使用一些服务器端对象来提取数据,创建完整的 HTML 标记并将其渲染到客户端浏览器。
现在让我们再次退一步。现在我们需要一个服务器端对象。在 MVC 世界中,这就是 Model。我们需要一个已实例化的 Model,并将其传递给 View,以便 View 可以从中提取数据并创建最终的 HTML 标记。
现在的问题是,谁会实例化这些 Model?这些 Model 将在 Controller 中实例化。Controller 将实例化 Model,然后将 Model 传递给 View,以便 View 可以使用它们来生成标记。
但现在更大的问题是,Controller 将如何被调用?Controller 将在用户请求时被调用。所有用户请求都将被某个模块拦截,然后解析请求 URL,并根据 URL 结构调用相应的 Controller。
所以,既然我们知道了基本流程,让我们正式定义 Model、View 和 Controller。
-
Model
:这些是包含数据的类。它们实际上可以是任何可以实例化并提供数据的类。这些可以是实体框架生成的实体、集合、泛型,甚至是泛型集合。 -
Controllers
:这些将在用户请求时被调用的类。它们的主要任务是生成 Model 类对象,将它们传递给某个 View,并告诉 View 生成标记并将其渲染到用户浏览器。 -
Views
:这些是包含 HTML 和 C# 代码的简单页面,它们将使用服务器端对象(即 Model)来提取数据,定制 HTML 标记,然后将其渲染到客户端浏览器。
了解 MVC 请求响应过程
我们已经以相反的方向看到了 MVC 的请求生命周期。让我们尝试正式地看一下。
- 用户以 URL 的形式发送请求。
-
UrlRoutingModule
拦截请求并开始解析它。 - 通过查看
RouteTable
集合,将从 URL 中识别出相应的 Controller 和 handler。此外,请求附带的任何数据都保存在RouteData
中。 - 将执行已识别 Controller 中的相应操作。
- 此操作将基于数据创建 Model 类。
- 然后,操作会将此创建的 Model 类传递给某个 View,并告知 View 继续执行。
- 现在,View 将执行,并基于逻辑和 Model 的数据创建标记,然后将 HTML 推送回浏览器。
上述生命周期是为了解释而定义的,并且省略了一些涉及更多对象的(如 Controller 基类)技术细节。一旦理解了 MVC 的概念,我建议深入研究请求生命周期。
一瞥路由
现在我们已经说过 Controller 和 Action 将由 URL 决定。所以,也许看看这个 RouteTable
条目的一个非常简单的例子是个好主意,这样我们就知道哪种 URL 模式会调用哪个 Controller 和哪个 Action。
这是 RouteTable
中的默认条目,它在 global.asax
的 application_start 事件中创建。
routes.MapRoute
(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Blog", action = "Index", id = UrlParameter.Optional
);
在这里,上面的路由中,路由的名称指定为 Default。第二个参数指定了将此路由用于的 URL 模式,即模式为 "SiteAddress/{controller}/{action}/{id}"
的任何 URL 都将使用此路由。最后一个参数指定如果使用此路由,将调用名为 Blog 的 Controller 和 Index 操作。id 是可选的,表示可以存在也可以不存在。
因此,以下 URL 将调用相应的操作和 Controller。
URL:www.siteaddress.com
Controller:Blog
Action:Index
URL:www.siteaddress.com/Blog
Controller:Blog
Action:Index
URL:www.siteaddress.com/Blog/Create
Controller:Blog
Action:Create
URL:www.siteaddress.com/Blog/Delete/1
Controller:Blog
Action:Delete
ID: 1
URL:www.siteaddress.com/Category/Edit/1
Controller:Category
Action:Edit
ID: 1
因此,上面的例子也许能清楚地说明哪个 URL 将调用哪个 Controller 和哪个 Action。我们也可以定义自定义路由。本文不讨论创建自定义路由,但一旦理解了路由,创建自定义路由就相当直接了。
使用代码
现在我们已经看到了一些 MVC 架构背后的理论。现在让我们创建一个简单的应用程序,它将在特定的 URL 上调用。将调用 Controller 并创建一个 Model 类。然后,这个 Model 将被传递给 View,最终的 HTML 将显示在浏览器上。
现在,创建 Controllers
、Models
和 Views
需要开发人员编写一些代码。Visual Studio 还提供了一些标准模板,可以开箱即用地提供各种脚手架。现在,由于本教程主要是为了了解 MVC 的整体架构以及如何创建我们的第一个 MVC 应用程序,我们将使用这些模板和脚手架。但强烈建议参考一些好的 MVC 书籍来全面理解它们。
注意:我们将创建一个 MVC 3.0 应用程序并为此练习使用 Razor 视图引擎。
让我们继续创建一个新的 MVC 3 Web 应用程序。

被要求时,让我们选择 Razor 视图引擎。

现在,这个模板带有许多预定义的 Controller、Model 和 View。让我们删除所有这些,只在此应用程序中保留三个名为 Models、Views 和 Controllers 的文件夹。

现在,让我们开始在 global.asax
中定义我们自己的路由。现在,我们默认要访问的 Controller 将是 DemoController
。我们希望此 Controller 的 Index 操作默认执行。我们还将传递一个 ID,如果存在,也将显示在页面上。因此,在 global.asax
中为此定义的路由将如下所示:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Demo", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
现在,让我们向此应用程序添加一个名为 Demo 的 Controller。可以通过右键单击 Controller 文件夹并选择“添加 Controller”来完成。将此 Controller 命名为 DemoController
。
现在,此 Controller 带有一个默认操作 Index。如果未为该 Controller 指定操作,将调用此操作。如果调用此 Controller,我们将显示一条简单消息。我们将为此使用 ViewBag
。Viewbag
是一个在 View 页面中可访问的对象,并且应该在 Controller 中填充。
public ActionResult Index()
{
ViewBag.UsageSample =
@"Please Use the Url 'Demo/Test' or 'Demo/Test/1' to see the Book Details";
return View();
}
注意:本文不讨论 ViewBag
、ViewData
和 TempData
等数据传输技术。该主题需要详细讨论。我们以后可能会讨论它们。
现在,这段代码将简单地将一个字符串放入 view bag 并调用 View。return 语句将期望在名为 Demo 的文件夹中有一个名为 index 的视图。我们现在还没有,所以让我们通过右键单击方法名来创建这个视图:

然后,它会询问我们需要添加的视图的更多选项。现在,让我们不选择任何其他选项,只创建视图。

现在,在视图中,我们将编写从 ViewBag
中提取数据并将其显示在页面上的逻辑。
<div>
@ViewBag.UsageSample;
</div>
现在,如果我们尝试运行此应用程序

这样,我们就看到了 Controller 如何从用户请求中被调用,然后 Controller 将一些数据传递给 View,View 在处理后渲染它。但我们还没有添加任何 Model。让我们在此应用程序中添加一个简单的 Model。让我们在 models 文件夹中定义一个简单的 Book
类 Model。然后,我们将看到如何实例化这个 Model 并在我们的 View 中使用它。
public class Book
{
public int ID { get; set; }
public string BookName { get; set; }
public string AuthorName { get; set; }
public string ISBN { get; set; }
}
现在,让我们在 DemoController
中添加一个名为 Test 的操作,并创建这个 Book
Model 的一个对象。创建对象后,我们需要将其作为以下方式传递给 View:
public ActionResult Test()
{
Book book = new Book
{
ID = 1,
BookName = "Design Pattern by GoF",
AuthorName = "GoF",
ISBN = "NA"
};
return View(book);
}
上面的代码将创建 Model 并将其传递给 View。现在,让我们创建一个能够从此 Model 提取数据的 View。这可以通过创建强类型 View 来完成,如下所示:

我还选择了“Details
”作为脚手架模板,以便 View 页面中用于显示这些详细信息的样板代码已经生成。

现在,当我们运行代码时,我们可以看到 Book 的详细信息,这些信息是从 Controller 中创建的 Model 中提取的,而 Controller 是在解析用户请求 URL 后执行的。

这样,我们就有了第一个工作的 MVC 3.0 应用程序,使用了 Razor 视图引擎。
关注点
本文是为正在使用 Web Forms 并且是 MVC 新手的开发人员创建的教程。有经验的 MVC 开发人员可能会觉得本文很平凡。本文实际上更像是我对 MVC 初学者进行的第一次培训的转录。希望本文对初学者有所帮助。
历史
- 2013 年 4 月 10 日:第一个版本。