翔如菲菲

其实天很蓝,阴云总会散;其实海不宽,此岸连彼岸.

导航

ASP.NET MVC控制器 PART1

 

以下内容摘自:

http://www.cnblogs.com/r01cn/archive/2011/11/17/2252003.html

http://www.cnblogs.com/r01cn/archive/2011/11/17/2252010.html

http://www.cnblogs.com/r01cn/archive/2011/11/18/2253455.html

感谢作者的翻译,这里只是译文。原书名:Pro ASP.NET MVC 3 Framework

 

第十二章 控制器与动作PART1

到达你应用程序的每一个请求都是由控制器处理的。控制器可以以它认为合适的方式自由地处理请求,只要它不偏离到属于模型和视图职责的领域。意即,我们不要把事务或数据存储逻辑放到控制器中来,也不要生成用户界面。

ASP.NET MVC框架中,控制器是.NET类,它含有处理请求所需要的逻辑。在第4章中我们解释过,控制器的作用是封装应用程序逻辑。这意味着控制器要负责处理输入请求、执行域模型上的操作、并选择渲染给用户的视图。

在本章,我们将向你演示控制器是如何实现的,以及你能够把控制器用来接收并生成输出的不同方法。MVC框架并未限制你只有通过视图才可以生成HTML,而且我们也将讨论其它可用的选择。我们也将演示动作方法如何使单元测试容易,并演示如何测试一个动作方法能够产生的各种结果。

控制器介绍

到目前为止的几乎所有章节中,你都已经看到了控制器的使用。现在到了回过头来探究场景背后的东西的时候了。一开始,我们需要创建一个例子项目。

准备项目

为了做好本章的准备,我们用空模板生成一个新的MVC 3项目,并将之称为ControllersAndActions。我们选择空模板是因为,随着本章的进行,我们打算生成所有的控制器和视图。

IController生成一个控制器

MVC框架中,控制器类必须实现System.Web.Mvc命名空间的IController接口,如清单12-1所示。

清单12-1. System.Web.Mvc.IController 接口

public interface IController {
    void Execute(RequestContext requestContext);
}

这是一个很简单的接口。当一个请求以这个控制器类为目标时,单一的方法Execute被调用。MVC框架通过读取由路由数据所生成的controller属性的值知道一个请求的目标是哪个控制器。

通过实现IController,你可以选择自己去生成控制器类,但这是一个相当低级的接口,因此你必须做很多工作,才能让事情变得有用。清单12-2显示了一个名为BasicController的简单控制器提供了一个演示。

清单 12-2. BasicController 

using System.Web.Mvc;
using System.Web.Routing;
namespace ControllersAndActions.Controllers {
    public class BasicController : IController {
        public void Execute(RequestContext requestContext) {
            string controller = (string)requestContext.RouteData.Values["controller"];
            string action = (string)requestContext.RouteData.Values["action"];
            requestContext.HttpContext.Response.Write(
                    string.Format("Controller: {0}, Action: {1}", controller, action));
        }
    }
}

为了生成这个类,我们右击例子项目中的Controllers文件夹,并选择“Add”“New Class”。然后以BasicController命名控制器,并输入你在清单12-2中所看到的代码。

Execute方法中,我们从与这个请求相关联的RouteData对象读取了controlleraction变量的值,并把它们写到结果。如果你运行这个应用程序,并导航到Basic/Index,你可以看到该控制器所生成的输出,如图12-1所示。

图12-1

 12-1. BasicController 类生成的结果

实现IController接口允许你生成一个类,MVC框架把这个类看作是一个控制器,并把请求发送给它,但编写一个复杂的应用程序是相当困难的。MVC框架并未指定一个控制器如何处理请求,这就意味着你可以采用任何你想采取办法来处理请求。

通过从Controller类派生的办法生成一个控制器

MVC框架是无限可定制和可扩展的。你可以实现IController接口来生成你需要的各种请求处理和结果生成。不喜欢动作方法?不关心渲染的视图?那么,你可以把事情掌握在自己的手中,并编更好、更快、以及更雅致的方式处理请求。或者,你可以使用MVC框架团队已经提供的特性(features),这是通过从System.Web.Mvc.Controller类派生你的控制器来获得的。

System.Web.Mvc.Controller是大多数MVC开发人员需要熟悉的用来提供请求处理支持的类。这是我们前面章节中所有例子一直在使用的一个类。

这个Controller类提供了三个关键特性:

·         动作方法:一个控制器的行为被分解成多个方法(而不是只有一个单一的Execute()方法)。每个动作方法被暴露给不同的URL,并通过从输入请求提取的参数进行调用。

·         动作结果:你可以返回一个描述动作结果的对象(例如,渲染一个视图、或重定向到一个不同的URL或动作方法),然后通过该对象实现你的目的。这种指定结果和执行它们之间的分离简化了单元测试。

·         过滤器:你可以把可重用的行为(例如,认证,正如你在第9章所看到的那样)封装成过滤器,然后通过在你的源代码中放置一个[Attribute]的办法,把各个行为连接在一个或多个控制或动作方法上。

除非你在头脑中有一个非常明确的需求,生成控制器最好的办法是通过Controller类进行派生,而且,正如你可能希望的,这就是Visual Studio在响应Add  Controller menu菜单项生成一个新类时为你所做的事情。清单12-3显示了用这种方式创建一个简单的控制器。我们称之为DerivedController

清单 12-3. 一个继承自System.Web.Mvc.Controller Class的简单控制器

using System.Web.Mvc;
namespace ControllersAndActions.Controllers {
    public class DerivedController : Controller {
        public ActionResult Index() {
            ViewBag.Message = "Hello from the DerivedController Index method";
            return View("MyView");
        }
    }
}

Controller类也使我们与视图系统连接。在上述清单中,我们返回了View方法的结果,在其中作为参数传递了我们想渲染给客户端的视图名。清单12-4显示了名为MyView.cshtml并位于项目的Views/Derived文件夹的这个视图。

Listing 12-4. The MyView.cshtml File

@{
    ViewBag.Title = "MyView";
}
<h2>MyView</h2>
Message: @ViewBag.Message

如果你启动这个应用程序,并导航到/Derived/Index,我们已经定义的这个动作方法将被执行,我们所命名的视图将被渲染,如图12-2所示。

图12-2

Figure 12-2. A result generated from the DerviedController class

作为Controller类的一个派生,我们所要做的事情是实现动作方法,获取我们需要处理一个请求的任何输入,并生成一个适当的响应。本章的其余部分涉及我们能够做这种事情的种种办法。

接收输入

控制器经常需要访问输入数据,如查询字串值、表单值、以及通过路由系统从输入URL解析所得到的参数。有三个主要的办法来访问这些数据:

·         通过一组上下文对象进行提取。

·         作为参数被传递到动作方法的数据。

·         明确地调用框架的模型绑定特性(feature)。

这里,我们将考查为动作方法获取输入的途径,关注于使用上下文对象以及动作方法参数。在第17章中,我们将深入地涉及模型绑定。

通过上下文获取数据

抓取数据最直接的办法是你自己去拿。当你的控制器是通过Controller基类派生而来的时候,你便得到了一组便利属性来访问与请求相关的信息。这些便利属性包括RequestResponseRouteDataHttpContext、以及Server。每一个都包含了请求的不同方面的信息。我们把这些称为便利属性,是因为它们每一个都从请求的ControllerContext实例(可以通过Controller.ControllerContext属性对它进行访问)接受了不同类型的数据。我们在表12-1中描述了一些最常用的上下文对象。

表12-1. 常用的上下文对象

属性

类型

描述

Request.QueryString

NameValueCollection

请求的GET变量

Request.Form

NameValueCollection

请求POST变量

Request.Cookies

HttpCookieCollection

由浏览器用这个请求发送的Cookies

Request.HttpMethod

string

用于这个请求的HTTP方法(动词,如GET或POST)

Request.Headers

NameValueCollection

请求的整个HTTP头

Request.Url

Uri

请求的URL

Request.UserHostAddress

string

发送请求的用户的IP地址

RouteData.Route

RouteBase

为这个请求所选择的RouteTable.Routes条目

RouteData.Values

RouteValueDictionary


当前路由的参数(或是从URL提取的,或是默认值)

HttpContext.Application

HttpApplicationStateBase

应用程序状态库

HttpContext.Cache

Cache

应用程序缓存库

HttpContext.Items

IDictionary

当前请求的状态库

HttpContext.Session

HttpSessionStateBase

访问者的会话状态库

User

IPrincipal

已登录用户的认证信息

TempData

TempDataDictionary

为当前用户存储的临时数据项

一个动作方法可以用任意一个这些上下文对象来获取与请求相关的信息,如清单12-5所示(有些未在表12-1中说明,比如Server — 译者注)。

清单12-5. 使用上下文对象获取请求相关信息的一个动作方法

public ActionResult RenameProduct() {
    // Access various properties from context objects
    string userName = User.Identity.Name;
    string serverName = Server.MachineName;
    string clientIP = Request.UserHostAddress;
    DateTime dateStamp = HttpContext.Timestamp;
    AuditRequest(userName, serverName, clientIP, dateStamp, "Renaming product");
    // Retrieve posted data from Request.Form
    string oldProductName = Request.Form["OldName"];
    string newProductName = Request.Form["NewName"];
    bool result =AttemptProductRename(oldProductName, newProductName);
    ViewData["RenameResult"] = result;
    return View("ProductRenamed");
}

 

你可以用智能感应(在一个动作方法中,输入this.,并浏览弹出的内容)、和微软开发者网络(查看System.Web.Mvc.Controller及其基类,或System.Web.Mvc.ControllerContext)来浏览这些大量可用的请求上下文信息。

用动作方法参数

正如在前面一些章节你已经看到的,动作方法可以有参数。这是一种比通过上下文对象提取数据更灵活的接收输入数据的办法,而且这使你的动作方法更易于阅读。例如,假设我们有一个像下面这样使用上下文对象的动作方法:

public ActionResult ShowWeatherForecast(){
    string city = RouteData.Values["city"];
    DateTime forDate = DateTime.Parse(Request.Form["forDate"]);
    // ...implement weather forecast here ...
}

 

我们可以把它重写成使用参数的形式,像这样:

public ActionResult ShowWeatherForecast(string city, DateTime forDate){
    // ...implement weather forecast here ...
}

 

这不仅更易于阅读,而且它也有助于单元测试 — 我们不需要模仿控制器类的便利属性就能够单元测试这个动作方法。

出于完整性,值得注意的是,动作方法不允许有outref参数。如果这么做,没有任何意义。ASP.NET MVC如果看到这种参数会简单地弹出一个异常。

MVC框架将代表我们通过检查上下文对象的办法自动地给我们的参数提供值,包括Request.QueryStringRequest.Form、以及RouteData.Values。对参数名的处理不分大小写,以便一个名为city的动作方法参数能够被Request.Form["City"]的值所填充。

理解参数对象是如何被实例化的

底层Controller类使用叫做值提供器模型绑定器MVC框架组件,为你的动作方法参数获取值。

值提供器代表一组可用于控制器的数据项。有内建的值提供器,它们从Request.FormRequest.QueryStringRequest.Files以及RouteData.Values获得各数据项。这些值然后被传递给模型绑定器,它试图把它们映射到你的动作方法参数的数据类型。

默认的模型绑定器能够生成并填充任何.NET类型的对象,包括集合以及项目专用的自定义类型。你在第9章看到过它的一个例子,在管理员递交表单时,作为一个单一的Product对象呈现给了我们的动作方法,即使各个值被分散在HTML表单的各个元素之中也没问题。

我们将在第17章深度涉及值提供器和模型绑定器。

理解可选参数和强制参数

如果MVC框架找不到一个引用类型参数(如一个stringobject)的值,动作方法仍然会被调用,但对该参数会使用一个null值。如果找不到一个值类型参数(如intdouble)的值,那么会throw个异常,该动作方法不会被调用。以下是考虑了这一情况的另一种办法。

· 值类型参数是被强制的。为了使它们成为可选的,或者指定一个默认值(参见下一小节),或是把参数类型改为可空(nullable)类型(如int? DateTiem?),如果没有可用的值,MVC框架可以传递null

·      引用类型参数是可选的。为了使它们成为强制的(以保证传递一个非空值),把一些代码添加到该动作方法的顶部,以拒绝null值。例如,如果参数值为null,则throw一个ArgumentNullException异常。

注意:我们并不是在指UI校验。如果你的目的是给用户提供所需表单字段的反馈,请参阅第18章。
指定默认参数值

如果你想处理不含动作方法参数值的请求,但你又不想在代码中检查null值或thrown异常,你可以使用C#的可选参数特性来代替。清单12-6提供了一个演示。

Listing 12-6. Using the C# Optional Parameter Feature in an Action Method

public ActionResult Search(string query = "all"int page = 1) {
    // ...
}

 

我们在定义参数时,通过对参数赋值的办法把参数标记为可选的。在上述清单中,我们给querypage参数提供了默认值。MVC框架将试图通过请求为这些参数获取值,但如果无值可用,那么将用我们所指定的默认值来替代。

对于字符串型参数query,这意味着我们不需要检查null值。如果我们所处理的请求没有查询字串,那么,我们的动作方法将用字符串all进行调用。对于int型参建,在没有page值时,我们不需要担心请求会导致错误。我们的方法将以默认值1进行调用。

可选参数可以被用于基元类型(literal types,所谓基元类型是不需要用new关键词定义的任何类型,如stringint、以及double等。

警告:如果一个请求包含了一个参数的值,但又不能把它转换成正确的类型(例如,如果用户对一个int参数给了一个非数值字符串),那么框架将给这个参数类型传递默认值(例如,对int参数给0值),并在一个名为ModelState的特殊的上下文对象中把这个未遂值(the attempted value)注册为一个校验错误。除非你检查ModelState中的校验错误,否则当用户在表单中输入了错误数据,你可能会陷入奇怪的境况,但该请求仍然被处理了,就好像用户还没有输入任何数据,或输入了默认数据。参阅第18章的校验和ModelState细节,可以用来避免这类问题。

 未完待续。。。

posted on 2012-03-17 17:29  翔如飞飞  阅读(581)  评论(0编辑  收藏  举报