[ASP.NET MVC 小牛之路]09 - Controller 和 Action (1)
[ASP.NET
MVC 小牛之路]09 - Controller 和 Action (1)
我们知道,在 MVC 中每个请求都会提交到 Controller 进行处理。Controller 是和请求密切相关的,它包含了对请求的逻辑处理,能对 Model 进行操作并选择 View 呈现给用户,对于业务和数据的逻辑代码以及接口和辅助类库等一般都不放到 Controller 中。
Controller 和 Action 的内容较多,我把它分成了两篇,也可能会分成三篇。本篇介绍 Controller 的实现、Controller 对状态数据的获取、ActionResult 和 Action 的数据传递,后续将介绍 Controller 工厂、Action Invoker 和暂时还没想好或正在学习的一些较高级的特性。
本文目录
继承 IController 接口
在本系列前面的文章中,我们添加的 Controller 都是一个继承自抽象类 System.Web.Mvc.Controller 的普通类(请注意:controller(或Controller) 和 Controller 类在本文是两个意思,请在阅读本文时根据上下文理解)。Controller 抽象类封装了很多很实用的功能,让开发人员不用自己去写那些重复烦琐的处理代码。
如果不使用封装的 Controller 抽象类,我们也可以通过实现 IController 接口来创建自己的 controller。IController 接口中只有一个 Exctute 方法:
public interface IController { void Execute(RequestContext requestContext); }
Controller 接口在 System.Web.Mvc 命名空间下,一个结构非常简单的接口。
当请求送到一个实现了 IController 接口的 controller 类时(路由系统通过请求的URL能够找到controller),Execute 方法被调用。
下面我们创建一个空的 MVC 应用程序,在 Controllers 文件夹下添加一个实现了 IController 的类,并做一些简单的操作,如下:
using System.Web.Mvc; using System.Web.Routing; namespace MvcApplication1.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)); } } }
运行应用程序,URL 定位到 /Basic/Index(你可以把 Index 改成其他任意片段名称),结果如下:
实现了 IController 的类,MVC就会辨识它为一个 controller 类,根据 controller 名把对应的请求交给这个类处理。
但对于一个稍稍复杂的应用程序,自己实现 IController 接口是要做很多工作的,我们很少会这么做。通过这我们更好地理解了 controller 的运行,controller 中一切对请求的处理都是从 Execute 方法开始的。
继承 Controller 抽象类
MVC 允许我们*地进行自定义和扩展,比如像上面讲的你可以实现 IController 接口来创建对各类请求的各种处理并生成结果。不喜欢 Action 方法或不关心 View,那么你可以自己动手写一个更好更快更优雅的 controller 来处理请求。但像前面说的,自己实现 IController 接口要做很多工作,最重要的是没有经过长期实践测试,代码的健壮性得不到保证, 一般不建议你这么做。MVC 框架的 System.Web.Mvc.Controller 类,提供了足够实用的特性来方便我们对请求的处理和返回结果。
继承 Controller 类的 controller 我们已经使用过很多次了,对它已经有一定的了解,它提供了如下几个关键的特性:
Action方法:一个 Controller,它的行为被分为多个方法,通常一个方法对应着一个请求,并且可以通过方法参数来取得请求传递过来的数据。
ActionResult:可以返回一个描述了 Action 方法执行结果的对象,这样的好处是想返回什么结果就指定对应的返回对象就行,不用关心怎么去执行并生成结果。
Filters:通过C#特性,对某一种行为的处理(比如授权和验证)进行封装,方便了在多个 Controller 和 Action 方法之间进行重用。
所以,如果你不是因为特殊的需求或闲得蛋疼,创建一个满足要求的 Controller 最好的途径是继承 Controller 抽象类。由于之前我们已经使用过多次,这里就不再进行具体的演示了,但我们还是来看一下它的代码结构。
在 Controllers 文件夹下添加一个 Controller,VS 已经把类的结构帮我们生成好了,如果你喜欢整洁,会习惯性地把不需要用到的引用删掉,代码如下:
using System.Web.Mvc; namespace MvcApplication1.Controllers { public class DerivedController : Controller { public ActionResult Index() { ViewBag.Message = "Hello from the DerivedController Index method"; return View("MyView"); } } }
我们可以查看 Controller 抽象类的定义,发现它是继承 ControllerBase 类的,在 ControllerBase 类中实现了 IController 接口的 Execute 方法,这个方法是MVC对请求进行处理的各个组件的入口,其中包括通过路由系统找到 Action 方法并调用。
Controller 类内部使用 Razor 视图系统来呈现 View,这里通过 View 方法,指定 View 的名称参数来告诉 MVC 选择 MyView 视图来返回给用户结果。
在 Controller 中获取状态数据
我们经常需要访问客户端提交过来的数据,比如 QueryString 值、表单值和通过路由系统来自 URL 的参数值,这些值都可称为状态数据。下面是 Controller 中获取状态数据的三个主要来源:
一系列的上下文对象。
传递给 Action 方法的参数。
显式的调用框架的模型绑定(Model Binding)特性。
从上下文对象中获取状态数据
获取状态数据最直接的方法就是从上下文对象中提取。当你创建了一个继承自 Controller 类的 Controller 时,可以通过一系列的属性可以方便的访问到和请求相关的数据,这些属性包括 Request、Response、RouteData、HttpContext 和 Server,每一个都提供了请求相关的不同类型的信息。下面列出了最常的上下文对象:
在 Action 方法中可以使用任意上下文对象来获取请求相关的信息,如下面在 Action 方法中所演示的:
... public ActionResult RenameProduct() { //访问不同的上下文对象 string userName = User.Identity.Name; string serverName = Server.MachineName; string clientIP = Request.UserHostAddress; DateTime dateStamp = HttpContext.Timestamp; AuditRequest(userName, serverName, clientIP, dateStamp, "Renaming product"); //从POST请求提交的表单中获取数据 string oldProductName = Request.Form["OldName"]; string newProductName = Request.Form["NewName"]; bool result = AttemptProductRename(oldProductName, newProductName); ViewData["RenameResult"] = result; return View("ProductRenamed"); } ...
这些上下对象不用特意去记,用的时候,你可以通过VS的智能提示来了解这些上下文对象。
使用 Action 方法参数获取状态数据
在本系列的前面的文章中,我们已经知识如何通过 Action 参数来接收数据,这种方法和上面的从上下文对象中获取相比,它更为简洁明了。比如,我们有下面这样一个使用上下文对象的 Action 方法:
public ActionResult ShowWeatherForecast() { string city = (string)RouteData.Values["city"]; DateTime forDate = DateTime.Parse(Request.Form["forDate"]); // do something ... return View(); }
我们可以像下面这样使用 Action 方法参数来重写它:
public ActionResult ShowWeatherForecast(string city, DateTime forDate) { // do something ... return View(); }
它不仅易读性强,也方便进行单元测试。
Action 方法的参数不允许使用 ref 和 out 参数,这是没有意义的。
MVC 框架通过检查上下文对象来为 Action 方法的参数提供值,它的名称是不区分大小写的,比如 Action 方法的 city 参数的值可以是通过 Request.Form["City"] 来获取的。
理解 Action 方法的参数是如何被赋值的
Controller 类通过 MVC 框架的 value provider 和 model binder 组件来为 Action 方法获取参数的值。
value provider 提供了一系列Controller中可以访问到的值,在内部它通过从 Request.Form、Request.QueryString、Request.Files 和 RouteData.Values 等上下文对象中提取数据(键值集合),然后把数据传递给 model binder,model binder 试图将这些数据与Action方法的参数进行匹配。默认的 model binder 可以创建和赋值给任何.NET类型对象参数(即 Action 方法的参数),包括集合和自定义的类型。
在这不对 model binder 进行介绍,我将在本系列的后续博文中对其进行专门的介绍。
理解 ActionResult
ActionResult 是描述 Action 方法执行结果的对象,它的好处是想返回什么结果就指定对应的返回对象就行,不用关心如何使用Response对象来组织和生成结果。ActionResult 是一个命令模式的例子,这种模式通过存储和传递对象来描述操作。
当 MVC 框架从 Action 方法中接收到一个 ActionResult 对象,它调用这个对象的 ExecuteResult 方法,其内部是通过 Response 对象来返回我们想要的输出结果。
为了更好的理解,我们通过继承 ActionResult 类来自定义一个 ActionResult。在MVC工程中添加一个Infrastructure文件夹,在里面创建一个名为 CustomRedirectResult 的类文件,代码如下:
using System.Web.Mvc; namespace MvcApplication1.Infrastructure { public class CustomRedirectResult : ActionResult { public string Url { get; set; } public override void ExecuteResult(ControllerContext context) { string fullUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext); context.HttpContext.Response.Redirect(fullUrl); } } }
当我们创建一个 CustomRedirectResult 类的实例时,我们可以传递想要跳转的 URL。当 Action 方法执行结束时,MVC 框架调用 ExecuteResult 方法,ExecuteResult 方法通过 ControllerContext 对象获得 Response 对象,然后调用 Redirect 方法。
下面我们在 Controller 中使用自定义的 CustomRedirectResult:
public class DerivedController : Controller { ... public ActionResult ProduceOutput() { if (Server.MachineName == "WL-PC") { return new CustomRedirectResult { Url = "/Basic/Index" }; } else { Response.Write("Controller: Derived, Action: ProduceOutput"); return null; } }
运行后我们看到如下结果:
当运行在本机(WL-PC)时直接重定向到了指定的/Basic/Index。
上面我们通过自定义 CustomRedirectResult 来实现重定向,我们可以用 MVC 框架提供的方法,如下:
... public ActionResult ProduceOutput() { return new RedirectResult("/Basic/Index"); }
为了使用方便,Controller 类中为大部分类型的 ActionResult 提供简便的方法,如上面的可像下面这样简写:
... public ActionResult ProduceOutput() { return Redirect("/Basic/Index"); }
MVC框架包含了许多 ActionResult 类型,这些类型都继承自 ActionResult 类,大部分在 Controller 类中都有简便的方法,下面列举了一些:
除了该表列出来的,还有ContentResult、FileResult、JsonResult 和 JavaScriptResult。具体每种ActionResult类型的用法这里就不讲了,大家可以看看蒋老师的了解ASP.NET
MVC几种ActionResult的本质系列的文章。
几种从 Action 传递数据到 View 的方式
我们经常需要在 Action 方法中传递数据到一个 View 中,MVC 框架为此提供了一些很方便的操作。下面简单简介几种常用的方式。
View Model 对象
通过 View Model 对象传递数据给View,这是最常用的一种,在 Acton 方法执行结束时通过 View 方法传递 View Model 对象给 View,如下代码所示:
... public ViewResult Index() { DateTime date = DateTime.Now; return View(date); }
在 View 中我们通过 Model 属性来使用传递过来的 View Model 对象,如下:
@model DateTime @{ ViewBag.Title = "Index"; } <h2>Index</h2> The day is: @Model.DayOfWeek
在 Razor 视图引擎中,@model 的作用是声明 odel 属性的类型,省去了类型转换的麻烦,而 @Model 是V iew Model 对象的引用。
ViewBag、ViewData 和 TempData 属性
ViewBag、ViewData 和 TempData 都是 Controller 和 View 中能访问到的属性,都是用来存储小量的数据,他们的区别如下:
ViewBag,是一个动态(dynamic)的弱类型,在程序运行的时候解析,是 MVC3 中新增的特性,只在当前View有效。
ViewData,是一个字典集合,也是只在当前View有效,性能比 ViewBag 高,但是使用的时候需要类型转换。
TempData,也是字典集合,一般用于两个请求之间临时缓存内容或页面间传递消息,保存在 Session 中,使用完以后则从 Session 中被清除。
下面是三者使用的例子,先在 Controller 中分别用三者存储小数据:
public class DerivedController : Controller { public ActionResult Index() { ViewBag.DayOfWeek = DateTime.Now.DayOfWeek; ViewData["DayOfMonth"] = DateTime.Now.Day; return View(); } public ActionResult ProduceOutput() { TempData["Message"] = "Warning message from Derived Controller."; return Redirect("/Home/Index"); } }
在 Views/Derived 目录下的 Index.cshtml 中,取出 ViewBag 和 ViewData 中的存储的数据:
... Day of week from ViewBag: @ViewBag.DayOfWeek <p /> Day of month from ViewData: @ViewData["DayOfMonth"]
在 Views/Home 目录下的 Index.cshtml 中,取 TempData 中的数据如下:
... @TempData["Message"]
当请求 /Derived/ProduceOutput 时,ProduceOutput 方法将一条消息存到 TempData 中,并跳转到 /Home/Index。
下面是分别是将URL定位到 /Derived/Index 和 /Derived/ProduceOutput 时的结果:
一般在当前 View 中使用 ViewBag 或 ViewData,在两个请求之间传递临时数据用 TempData。由于 TempData 被使用后即被释放,所以如果要二次使用 TempData 中的数据就需要将其存到其他变量中。
以上就是[ASP.NET MVC 小牛之路]09 - Controller 和 Action (1)的内容,更多相关内容请关注PHP中文网(www.php.cn)!
上一篇: php计算多维数组中所有值总和的方法