任何对应用程序的请求都是由controller处理的。我们不需要把业务逻辑或者数据存储路基放在controller,也不需要生成用户接口。在ASP.NET MVC Framework中,controller是处理请求逻辑的.NET类。controller对处理请求负责,在domain模型上执行操作,选取view呈现给用户。这里我们会展示如果实现controller,以及使用controller接收和生成输出的多种方法。

项目准备

先创建一个新的MVC3项目,使用空模板,命名为ControllersAndActions。之所以使用空模板是因为我们要创建自己需要的controller和view。

在MVC Framework中,controller类必须是些IController接口,该接口在命名空间System.Web.Mvc中,如下:

public interface IController {

void Execute(RequestContext requestContext);

}

这是一个很简单的接口,只含有一个方法Execute,在请求到达该controller类的时候调用。MVC Framework通过读取路由数据生成的controller属性知道哪个controller类被请求了。

你可以通过实现IController接口选择创建controller,但是这是一个很底层的接口,你必须要做很多工作。如下显示了一个简单的controller——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));

    }

}

}

要创建上面的类,在controller文件夹上右键,选择Add New Class。然后命名为BasicController,输入如上的代码。

在Execute方法中,我们从和请求相关的RouteData对象中读取controller和action变量的值,让输出。如果你输入如下地址/Basic/Index,你会看到我们的controller会生成如下的输出:

clipboard

实现IController接口允许你创建类,MVC Framework将它之别为controller,把请求发送给它处理。但是写一个复杂的应用却很难。MVC Framework不指定controller如何处理request,这意味着你可以创建人和你想要的方法。

从Controller类继承的方式创建Controller

MVC Framework是可定制和可扩展。你可以实现IController接口来创建任何你需要的类型请求处理和输出结果。不喜欢action方法?不关心views?那么你只需要自己写一个更优雅的方式来处理请求。或者你可以使用MVC Framework团队提供的现成的功能,由继承System.Web.Mvc.Controller class实现。

System.Web.Mvc.Controller 类用来支持处理请求,大多数程序员都非常熟悉此类。在之前的例子中我们也曾经使用过。此类有3个特征:

action方法:controller的行为被分割到多个方法中,而不是只有一个Execute()方法。每个action方法对不同的URL开放,从request中的中获取参数并调用。

action result:你可以返回一个描述动作结果的对象(比如:呈现View,重定向到不同的URL或者action方法)。这种分离result和执行简化了单元测试

过滤:你可以封装一个可以重复使用的行为,(比如认证)作为过滤,然后通过设置 Attribute特性,标记每个行为到controller或者action上。

除非你有非常特殊的需求,否则创建controller的最好方法就是从Controller类继承。正如你希望的那样,这也是Visual Studio在Add Controller菜单创建新类的默认方式。下面例举了一种简单的创建controller的方法,命名为DerivedController。

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基类实现了Execute方法,并且负责调用action方法,只要action的名字匹配于路由数据里的action的值。

Controller类也同样衔接了view系统。在下面,我们返回view方法的一个值,通过传递我们想要呈现的视图的名字作为参数。如下代码,展示了一个视图,命名为MyView.cshtml,位于 Views/Derived文件夹下。

MyView.cshtml File

@{

ViewBag.Title = "MyView";

}

<h2>MyView</h2>

Message: @ViewBag.Message

If you start the application and navigate to /Derived/Index, the action method we have defined will

be executed, and the view we named will be rendered, as shown in Figure 12-2.

如果你启动应用程序,并且导向到/Derived/Index,我们定义的action方法将会执行,然后呈现我们命名的view,如下图:

clipboard[1]

作为Controller类的子类,实现了action方法,获取我们需要处理的请求,生成合适响应。在后续,我们还有很多方法能完成这种任务。

接受输入

控制器经常需要通过路由系统从请求的URL中访问传输来的数据,比如query string的值。有3个方法访问这些数据:

从context 对象充抽取。

让数据作为参数传递给你的action方法。

显式的调用framework的模型绑定特性。

这里我们,我们先介绍使用context对象和action方法参数,剩下的后续会讲到。

从Context对象中获取数据

获取数据最直接的方法就是自己获取。当你通过集成controller基类创建一个controller,你就能通过这个request信息的活的一些属性。这些属性包括Request, Response, RouteData, HttpContext, 和Server。每个提供了request各个方面的信息。我们认为这些是很便捷的属性,因为他们可以从request的 ControllerContext(可以通过Controller.ControllerContext属性获得) 实例中获取不同类型的数据。下面是一些最常用的context对象。

ba35bf9065534ed9aa71ee4f6aeab034

action方法可以使用这些context对象获取request的信息,如下的演示代码:

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");

}

你可以通过visual studio的智能提示浏览可以使用的request context信息。或者MSDN上也可以搜索到。

使用action方法参数

正如你之前看到的那样,action方法可以接受参数。使用这种方法获取数据比手动的使用context对象更简洁,也让代码可读性更好。比如,加上我们有一个action方法使用context 对象,如下:

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 ...

}

不仅可读性提高了,而且也能帮助单元测试,我们可以单元测试这个方法,不需要模拟一些controller类的属性。

值得注意的是,由于完整性,action方法不允许有out或者ref参数,如果非要这样写,ASP.NET MVC会抛出一个异常。

MVC Framework通过检查context对象为我们的参数提供值,包括Request.QueryString, Request.Form, 和RouteData.Values。参数的名字不对大小写敏感,因此,action方法中名为city的参数可以从 Request.Form["City"]获得值。

理解参数对象是如何被初始化的

基类Controller 采用名为value providers和model binders 的MVC Framework 组件,为action方法提供值。Value providers 表示一组能从controller得到的数据项,这些是内建的provider,从Request.Form, Request.QueryString, Request.Files和RouteData.Values中获取指。这些值然后传递到model binder,尝试匹配action方法需要的参数。默认的model binder可以创建和植入任何.NET类型,包括collection,自定义类。后续还会说到。

理解可选参数和必选参数

如果MVC Framework不能为应用类型参数(比如string或者object)找到一个值,action方法调用的时候会使用null值。如果是值类型参数,比如int或者double,那么会抛出异常。action方法也不会被调用,可以这样认为:

值类型是强制的。如果要改为可选,那么必须指定默认值,或者改变参数类型为可空类型,比如int?或者DateTime?,这样,如果没有值的时候,MVC Framework可以传递一个null。

应用类型是可选的。如果要让他们强制输入,确保传递的值是一个非null的值,只需要加一写代码,比如值等于null的时候,抛出ArgumentNullException异常。

指定参数默认值

如果要处理请求,该请求的action方法没有参数值,但是你又想检测null值,那么你可以使用C#的可选参数。如下代码:

public ActionResult Search(string query= "all", int page = 1) {

// ...

}

通过给参数赋值来标记参数为可选参数,在上例代码中,我们为query和page参数提供了默认值。MVCFramework会尝试从request请求中获取参数值,但是如果没有残是指的话,就会使用默认值。

对于string类型参数,query,意味着我们u需要检查是否是null值。如果处理的request没有指定query,那么我们的方法会赋值为all。对于page,如果request中没有page值,那么就会使用默认值1.

如果一个请求包含一个参数的值,但是该值不能被转换为正确的类型,你入用户给了一个非数字的字符串,但是要转换成int。那么framework会传递这个参数类型的默认值。比如int参数的默认值0,同时,会在指定的context ModelState对象中注册这个值作为非法错误。除非你对ModelState做了非法性校验,否则你会得到一个奇怪问题:用户输入的是无效的数据,但是请求处理似乎用户输入的是合法的数据或者输入了默认值。后续还会说到对于ModelState的验证细节。