Asp.Net MVC 概述

原文链接:http://www.asp.net/learn/mvc/

1. ASP.NET MVC 概览

模型-视图-控制器(Model-View-Contoller, MVC)架构模式将应用程序分为了三个主要的组件:模型,视图和控制器。ASP.NET MVC框架为ASP.NET Web表单模式提供了另一种开发模式――基于MVC的Web应用程序。ASP.NET MVC框架是一个轻量的、高度可测试的表现层框架(与基于表单的Web应用程序相同),它继承在了现有的ASP.NET功能之中,例如模板页和基于Membership的验证。MVC框架定义在了System.Web.Mvc命名空间中,并且是必不可少的,由部分System.Web命名空间支持。

MVC是一个许多开发者都熟悉的、标准的设计模式。一些类型的Web应用程序将从MVC框架中受益。其他一些应用程序将会继续使用基于Web表单和Postback的传统ASP.NET应用程序模式。还有一些Web应用程序将会将这两种方法结合;这两种方式互不排斥。

MVC框架包含下面的组件:

图1:调用一个需要参数值的控制器动作

模型:模型对象是应用程序中实现了数据领域(data domain)逻辑的部分。通常,模型对象从数据库中获取模型状态,并且将模型状态保存至数据库。举个例子,一个Product对象可能从数据库中获取信息,对它进行操作,然后将更新后的信息写回SQL Server中的Products表。

在小型的应用程序中,模型通常是一个概念上的划分而非一个实际的划分。举个例子,如果应用程序只是读取DataSet然后发送给视图,应用程序没有一个实际的模型层和相关的类。在这种情况下,DataSet就承担了模型对象的角色。

视图:视图是应用程序中显示用户界面(UI)的组件。典型地,这个UI基于模型数据创建。举个例子,Products表的编辑视图基于Products对象的状态显示了文本框、下拉列表,以及复选框。

控制器:控制器是处理用户交互的组件,它与模型协作,并且最终选择一个要呈现的视图来显示用户界面。在MVC应用程序中,视图仅仅显示信息;控制器处理并且响应用户输入和交互。举个例子,控制器处理查询字符串值,并且将这些值传递给模型,模型再使用这些参数查询数据库。

MVC模型帮助创建这样的应用程序,它能够将应用程序的各个方面区分开(输入逻辑、业务逻辑,以及UI逻辑),同时提供这些元素之间的松耦合。这个模式指定了每一种逻辑应该位于应用程序的哪个位置。UI逻辑属于视图。输入逻辑属于控制器。业务逻辑属于模型。这种分隔有助于你在创建应用程序时管理复杂性,因为它能够让你在一次将精力集中于实现的某一方面。举个例子,你可以集中在视图,而不依赖于业务逻辑。

除了管理复杂性以外,测试应用程序时MVC模式比基于Web表单的ASP.NET应用程序要简单得多。举个例子,在一个基于Web表单的ASP.NET应用程序中,一个类既用于显示输出,也用于响应用户输入。为基于Web表单的ASP.NET应用程序编写自动测试程序是很复杂的,因为要测试每个页面,你必须初始化页面类,它的所有子控件,以及应用程序中其他有所依赖的类。因为为了运行页面初始化了这么多的类,所以编写专用于应用程序单独部分的测试就变得很困难了。测试基于Web表单的ASP.NET应用程序因此比测试MVC应用程序更加难以实施。除此以外,基于Web表单的ASP.NET应用程序需要一个Web服务器。MVC框架将组件进行了解耦,并且大量使用了接口,使得测试独立于框架其他部分的组件成为可能。

MVC应用程序三个主要组件之间的松耦合也提升了并行开发的程度。举个例子,一个开发者可以开发视图,第二个开发者可以开发控制器逻辑,而第三个开发者可以将精力集中于模型中的业务逻辑。

1.1 决定如何创建MVC应用程序

你必须认真考虑是使用ASP.NET MVC框架实现Web应用程序,还是使用ASP.NET Web表单模型来实现Web应用程序。MVC框架并没有取代Web表单模型;你可以选择使用MVC框架(如果你已经有基于Web表单的应用程序,它们可以继续像往常一样工作)。

对于一个特定的Web站点,在你决定使用MVC框架或者Web窗体模型之前,权衡一下每一种方式的优势。

1.1.1 基于MVC的Web应用程序的优势

ASP.NET MVC框架提供了下面的优点:

  • 通过将应用程序分为了模型、视图和控制器,它在管理复杂性方面更加的简单。
  • 它不适用视图状态或者基于服务器的表单。对于那些想要完全控制应用程序行为的开发者来说,MVC框架是很理想的。
  • 通过一个控制器,它使用了Front Controller模式来处理对Web应用程序的请求。这允许你设计出一种可以支持丰富的路由结构的应用程序。关于Front Controller的更多信息,可以查看MSDN网站。
  • 它为测试驱动开发(test-driven development, TDD)提供了更好的支持。
  • 对于那些由大型开发者和设计者所支持的Web应用程序来说,它运作得很好,能够对应用程序的行为提供更高层次的控制。

1.1.2 基于Web窗体的Web应用程序的优势

基于Web窗体的框架提供了下面的优势:

  • 它支持在HTTP上保存状态的事件模型,这有益于line-of-business的Web应用程序开发。基于Web窗体的应用程序提供了大量的事件,它们被数以百计的服务器控件所支持。
  • 它使用了一个Page Controller模式来为单个页面添加功能。关于Page Controller的更多信息,可以参考MSDN网站。
  • 它使用了视图状态或者基于服务器的表单,这使得管理状态信息更加容易。
  • 对于那些想要利用大量的用于快速应用程序开发组件的小型Web开发者和设计者团队来说,它工作得很好。
  • 总的来说,对于应用程序开发来说,它更加的简单,因为组件(Page类,控件等)已经紧密地集成了,并且通常比MVC需要编写更少的代码。

1.2 ASP.NET MVC框架的功能

ASP.NET MVC框架提供了下面的功能:

  • 应用程序任务的分离(输入逻辑、业务逻辑和用户界面逻辑),可测试性,以及默认的测试驱动开发。MVC框架中的所有核心契约都是基于接口的,并且可以使用mock对象进行测试,mock对象是模拟的对象,它们可以模仿应用程序中实际对象的行为。你可以对应用程序进行单元测试,而不需要在ASP.NET进程中运行控制器,这使得单元测试更快速也更灵活。
  • 它是一个可扩展和可插入的框架。ASP.NET MVC框架的组件设计为可以很容易地替换或者定制。你可以嵌入你自己的视图引擎、URL路由策略、动作方法参数序列化,以及其他组件。ASP.NET MVC框架也支持使用依赖注入(Dependency Injection,DI)和控件倒置(Inversion of Control,IOC)容器模型。DI允许你将对象注入到类中,而不是基于类来创建对象本身。IOC制定了如果一个对象需要另一个对象,第一个对象应该由外部源,例如配置文件,获得第二个对象。这使得测试更加容易。
  • 一个强大的URL映射组件,允许你创建拥有着易于理解和便于搜索的URL的应用程序。URL并不需要包含文件名扩展,并且设计为有利于搜索引擎优化(SEO)和表现状态传输(representational state transfer)的URL命名模式。
  • 支持在现有的ASP.NET页面(.aspx文件)、用户控件(.ascx文件)和模板页(.master文件)文件中使用标记来作为视图模板。你可以与ASP.NET MVC框架一起使用现有的ASP.NET功能,例如嵌套模板页,in-line表达式(<%%>),声明式服务器控件、模板、数据绑定、本地化等等。
  • 支持现有的ASP.NET功能。ASP.NET MVC允许你使用例如表单验证和Windows验证、URL授权、成员资格和角色、输出和数据缓存、会话和档案状态管理、健康监视器、配置系统和Provider架构这些ASP.NET的功能。

2. 理解MVC应用程序执行过程

对基于ASP.NET MVC的Web应用程序发出的请求首先通过UrlRoutingModule对象,它是一个Http模块。这个模块对请求进行解析,并且执行路由选择。UrlRoutingModule对象选择与当前请求所匹配的第一个路由对象(路由对象十一哥实现了RouteBase的类,典型地是是一个Route类的实例)。如果没有路由匹配,UrlRoutingModule对象什么也不做,并且让请求回退到通常的Asp.Net或者IIS请求处理。

从所选择的Route对象中,UrlRoutingModule对象获取与Route对象相关的IRouteHandler对象。典型地,在一个MVC应用程序中,这是一个MvcRouteHandler的实例。IRouteHandler实例创建了一个IHttpHandler对象,并且向它传递IHttpContext对象。默认情况下,MVC的IHttpHandler实例是一个MvcHandler对象。MvcHandler对象然后选择控制器,该控制器最终处理请求。

NOTE:当ASP.NET MVC应用程序运行在IIS7.0中时,MVC项目对文件的扩展名没有要求。然而,在IIS6.0中,handler要求你将.mvc文件名后缀映射到ASP.NET ISAPI DLL。

模块和处理器(handler)是ASP.NET MVC框架的入口点。它们执行下面的动作:

  • 在MVC Web应用程序中选择合适的控制器。
  • 获取一个特定的控制器实例。
  • 调用控制器的Execute方法。

下面的表列出了MVC Web项目的执行阶段。

阶段 细节
收到应用程序的第一个请求 在Global.asax文件中,Route对象添加到RouteTable对象中。
执行路由 UrlRoutingModule模块使用RouteTable集合中的第一个匹配的Route对象来创建RouteData对象,该对象然后被用来创建RequestContext(IHttpContext)对象。
创建MVC请求处理器 MvcRouteHandler对象创建一个MvcHandler类的实例,并且将RequestContext实例传递给它。
创建控制器 MvcHandler对象使用RequestContext实例来识别出IControllerFactory对象(典型地,是一个DefaultControllerFactory类的实例),来创建控制器实例。
调用动作 大多数控制器都继承自Controller基类。对于这些控制器来说,与控制器相关的ControllerActionInvoker对象决定调用控制器类的哪一个动作方法,然后调用它。
执行结果 一个典型的动作方法可能会接收用户输入,准备合适的响应数据,然后通过返回一个结果类型来执行结果。内置的、可以执行的结果包括下面这些:ViewResult(呈现一个视图,是最常使用的结果类型),RedirectToRouteResult、RedirectResult、ContentResult、JsonResult和EmptyResult。

3. 理解模型、视图和控制器

这篇教程将为你提供ASP.NET MVC模型、视图、控制器的高层次概览。换言之,它解释了ASP.NET MVC中的“M”、“V”和“C”。

在读完本教程之后,你应该能够理解ASP.NET MVC应用程序中的各个不同部分是如何一起工作的。你也应该能够理解ASP.NET MVC应用程序与ASP.NET Web窗体应用程序和动态服务器页面应用程序在构架上有何不同。

3.1 范例ASP.NET MVC应用程序

Visual Studio用于创建ASP.NET MVC Web应用程序的默认模板包括了一个极其简单的范例应用程序,可以使用它来理解ASP.NET MVC应用程序的各个部分。我们将会在这篇教程中利用这个简单的应用程序。

你可以通过启动Visual Studio2008,并且在菜单中选择“File(文件)”、“New Project(新建项目)”来使用MVC模板创建一个新的ASP.NET MVC应用程序(如图1)。在“新建项目”对话框,在项目类型下选择你喜欢的编程语言(Visual Basic或者C#),并且在模板下选择ASP.NET MVC Web应用程序。点击“确定”按钮。

图1 - 新建项目对话框

当你创建一个新的ASP.NET MVC应用程序时,将会出现“创建单元测试项目(Create Unit Test Project)”对话框(如图2)。这个对话框允许你在解决方案下创建一个独立的项目用于测试你的ASP.NET MVC应用程序。选择选项“No, do not create a unit test project(否,不要创建单元测试项目)”,并且点击“OK(确定)”按钮。

图2 - 创建单元测试对话框

在新的ASP.NET MVC应用程序创建好之后。你将会在解决方案浏览器窗口中看到几个文件夹和文件。特别地,你将会看到三个文件夹,名字是Models、Views和Controllers。正如你从文件夹的名称可以推测出的,这三个文件夹包含了实现了模型、视图和控制器的文件。

如果你展开Controller文件夹,你应该会看到一个命名为HomeController.cs的文件。如果你展开Views文件夹,你应该看到两个子文件夹,名称是Home和Shared。如果你展开Home文件夹,你将会再次看到两个文件,名称是About.aspx和Home.aspx(见图3)。这些文件组成了这个包含在默认ASP.NET MVC模板中的范例应用程序。

图3 - 解决方案浏览器窗口

你可以通过选择菜单项“Debug(调试)”、“Start Debugging(启动调试)”来运行这个范例应用程序。除此以外,你可以点击F5键。

当你第一次运行ASP.NET应用程序,将会出现图4中的对话框,建议你开启调试模式。点击“确定”按钮将会运行应用程序。

图4 - 未开启调试对话框

当你运行一个ASP.NET MVC应用程序,Visual Studio会在你的web浏览器中运行应用程序。这个范例应用程序只含有两个页面:Index页面和About页面。当应用程序第一次启动时,将会显示Index页面(见图5)。你可以通过点击应用程序右上角的菜单链接导航到About页面。

图5 - Index页面

注意一下你浏览器地址栏中的URL。当你点击Home菜单链接,浏览器地址栏的URL将变为/Home。当你点击About菜单链接,浏览器地址栏的URL变为/About

3.2 一个URL并不等同于一个页面

当你创建一个传统的ASP.NET Web窗体应用程序,或者是一个动态服务器页面(ASP)应用程序,在URL与页面之间存在一对一的对应。如果你请求服务器上的一个名为SomePage.aspx的页面,那么最好在磁盘上有一个SomePage.aspx页面。如果SomePage.aspx文件并不存在,你将会获得一个丑陋的404-页面不存在错误

当创建一个ASP.NET MVC应用程序时则大不相同,你键入到浏览器地址栏的URL与你应用程序中的文件之间并没有一个对应关系。在一个ASP.NET MVC应用程序中,一个URL与一个控制器的动作相对应,而不是磁盘上的页面。

对于一个传统的ASP.NET或者ASP应用程序,浏览器请求被映射到了页面。在ASP.NET MVC应用程序中,浏览器请求被映射到了控制器动作。一个ASP.NET Web窗体应用程序是以内容为中心的。而一个ASP.NET MVC应用程序则以是应用程序逻辑为中心的。

3.3 理解URL路由

一个浏览器请求通过名叫URL路由(URL Routing)的ASP.NET MVC功能被映射到了一个控制器动作。URL路由将即将到来的请求发送到了控制器动作。

URL路由使用一张路由表来处理来到的请求。这个路由表在你的Web应用程序首次启动时创建。这个路由表建立在Global.asax文件中。代码清单1包含了这个默认的MVC Global.asax文件。

代码清单1 - Glabal.asax

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace MvcApplication1
{
     public class GlobalApplication : System.Web.HttpApplication
     {
          public static void RegisterRoutes(RouteCollection routes)
          {
               routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
               routes.MapRoute(
               "Default",
               "{controller}/{action}/{id}",
               new { controller = "Home", action = "Index", id = ""}
               );
          }

          protected void Application_Start()
          {
               RegisterRoutes(RouteTable.Routes);
          }
     }
}

当ASP.NET应用程序第一次启动时,将调用Application_Start()方法。在代码清单1中,这个方法调用了RegisterRoutes()方法,并且RegisterRoutes()方法创建了默认的路由表。

默认的路由表只含有一个路由。这个默认的路由将所有到来的请求分为了三个分段(一个URL分段是正斜杠之间的任何东西)。第一个分段映射到了控制器名称,第二个分段映射到了动作名称,最后一个分段映射到了传递给动作的名为Id的参数。

例如,考虑下面的URL:

/Product/Details/3

这个URL将会被解析为像这样的三个部分:

Controller = ProductController

Action = Details

Id = 3

注意到Controller后缀被添加到了控制器参数的末尾。这只是MVC的一个怪癖而已。

默认的路由包含所有三个分段的默认值。默认的控制器是HomeController,默认的动作是Index,默认的Id是一个空字符串。脑子里记下这三个默认值,考虑下面的URL是如何被解析的:

/Employee

这个URL将被解析为像这样的三个参数:

Controller = EmployeeController

Action = Index

Id = ""

最后,如果你打开一个ASP.NET MVC应用程序而不提供任何的URL(例如,http://localhost),然后这个URL将会被解析成这样:

Controller = HomeController

Action = Index

Id = ""

这个请求被发送到了HomeController类的Index()动作。

3.4 理解控制器

控制器负责用户与MVC应用程序交互的方式。当用户发出浏览器请求时,控制器决定向用户发回什么样的响应。

控制器不过是一个类(例如,一个Visual Basic或者C#类)。这个范例ASP.NET MVC应用程序包含一个名为HomeController.cs的控制器,它位于Controllers文件夹下。HomeController.cs的内容再次显示在了代码清单2中。

代码清单2 - HomeConroller.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
   
namespace MvcApplication1.Controllers
{
     public class HomeController : Controller
     {
          public ActionResult Index()
          {
               ViewData["Title"] = "Home Page";
               ViewData["Message"] = "Welcome to ASP.NET MVC!";
               return View();
          }

          public ActionResult About()
          {
               ViewData["Title"] = "About Page";
               return View();
          }
     }
}

注意到HomeController拥有两个方法,名为Index()和About()。这两个方法对应于控制器暴露出的两个动作。URL /Home/Index将会调用HomeController.Index()方法,而URL /Home/About会调用HomeController.About()方法。

控制器中的任何公共方法都被暴露为一个控制器动作。对此你需要多加小心。这就意味着包含在控制器中的任何公共方法都可以由任何可以访问互联网的人调用,通过在浏览器中输入正确的URL。

3.5 理解视图

由HomeController类暴露出的两个控制器动作,Index()和About(),都返回了一个视图。视图含有将会发送给浏览器HTML标记和内容。当使用ASP.NET MVC应用程序时,一个视图等同于一个页面。

你必须在正确的位置创建你的视图。HomeController.Index()动作返回了一个位于以下路径的视图:

\Views\Home\Index.aspx

HomeController.About()动作返回一个位于以下路径的视图:

\Views\Home\About.aspx

一般而言,如果你想为一个控制器动作返回一个视图,那么你需要在Views文件夹下创建一个子文件夹,这个文件夹与你的控制器同名。在子文件夹下,你必须创建一个.aspx文件,它的名字与控制器动作相同。

代码清单3中的文件含有About.aspx视图。

代码清单3 - About.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
AutoEventWireup="true" CodeBehind="About.aspx.cs" Inherits="MvcApplication1.Views.Home.About"%>

<asp:Content ID="aboutContent" ContentPlaceHolderID="MainContent" runat="server">
     <h2>About Us</h2>
     <p>
          TODO: Put <em>about</em> content here.
     </p>
</asp:Content>

如果你忽略代码清单中的第一行,视图剩余的大部分是由标准的HTML构成的。你可以在这里输入任何你想要的HTML来修改视图的内容。

视图非常类似于动态服务器页面(ASP)中的页面,或者ASP.NET的Web窗体。视图可以含有HTML内容和脚本。你可以使用你所喜爱的.NET语言(例如,C#或者Visual Basic .Net)编写脚本。你使用脚本显示动态的内容,例如数据库数据。

3.6 理解模型

我们已经讨论了控制器,我们也讨论了视图。我们需要讨论的最后一个话题是模型。MVC模型是什么呢?

MVC的模型包含了你应用程序中的所有逻辑,这些逻辑没有包含在视图或者控制器中。模型应该包含你的应用程序中的所有业务逻辑和数据库访问逻辑。例如,如果你使用LINQ to SQL访问数据库,那么你可以在Models文件夹中创建你的LINQ to SQL类(你的dbml文件)。

视图应该仅包含与生成用户界面有关的逻辑。控制器应该仅仅只包含最少量的逻辑,用于返回正确的视图或者将用户重定向到另一个动作。任何其他的事情都应该包含在模型中。

通常,你应该竭尽全力创建一个丰富的模型以及一个瘦小的控制器。你的控制器方法应该只包含几行代码。如果一个控制器动作变得太丰富,那么你应该考虑将这些逻辑抽取出来放置到Models文件夹的新类中。

3.7 总结

这篇教程为你提供了ASP.NET MVC Web应用程序各个不同部分的一个高层次的概览。你学习了URL路由如何将即将到来的浏览器请求发往特定的控制器动作。你还学习了控制器是如何协调视图怎样返回浏览器的。最后,你学习了模型是怎样包含应用程序的业务和数据库访问逻辑的。