终极 ASP.NET
生命和一个 ASP.NET MVC 控制器的时间
Scott Allen
本文基于 ASP.NET MVC 框架的预发布版本。详细信息可能会有变动。
内容
所有路由都导致工厂
工厂可扩展性
执行是仅有开始
选择器属性
筛选器属性
自定义操作的筛选器
获得结果
操作是通过
控制器是模型视图控制器 (MVC) 设计模式的 lynchpins。它们是在前面行第一次接收客户端的请求和然后将请求转换成应用程序的域的逻辑和数据驻留在模型的指令。控制器负责还选择用于向用户展示信息的视图。
本文,我将就可以深入了解 ASP.NET MVC 框架并查看控制器的工作方式。我将介绍与您的控制器在 Framework 的交互方式和可以影响这些交互的方式。我会查看控制器工厂、 控制器操作,操作的筛选器,以及操作结果。
我会相当深研究中,因此,如果您在寻找在 ASP.NET MVC 框架的更多常规简介看到 Chris Tavares 文章"构建 Web 应用程序没有 Web 窗体."
所有路由都导致工厂
很难讨论在控制器的生存期内不讨论路由。在 ASP.NET 应用程序中的路由表包含所需 ASP.NET 路由模块从传入的 URL 提取信息,并直接请求到正确的软件组件的信息。我看 1 月的列中使用 Web 窗体中使用 ASP.NET 路由模块 ("路由使用 ASP.NET Web 窗体".) 在该列,我为了执行 Web 窗体构建自己路由的处理程序,但 ASP.NET MVC 框架提供路由将最终直接我们控制器之一的请求的处理程序。
处理此 MVC 路由程序来处理请求,您需要应用程序启动过程中配置路由表。默认路由配置由 MVC 项目模板提供 Global.asax 文件中,并且在 图 1 显示。
图 1 </a0>-默认路由配置
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);
}
图 1 中路由的配置条目之一是 URL 模板名为默认路由"{控制器} / {操作} / {id}"。 此 URL 模板是路由引擎将使用第一个,请参阅,如果一个传入的 URL 是该路由匹配图案。 将匹配这样的路由的 URL 将 http://localhost/home/Index/。 当路由引擎发现有匹配项时它将再次使用 URL 模板用作图案提升从传入的 URL 的参数。 在此示例,字符串"主页"成为控制器参数,因为它是在 {控制器} 位置,字符串"索引"成为 Action 参数。
在匿名类型的对象,作为第三个参数为 MapRoute 构造表示路由引擎以如果找不到给定的参数在 URL 中使用默认值。 万一的 http://localhost/home/Index/,路由引擎不会"ID"中找到信息在的 URL,但它仍将传递默认值为空字符串的 ID 参数。 路由引擎将所有这些参数传递给路由处理程序,通过 RouteData 对象。
值得注意路由引擎一无所知 ASP.NET MVC。 引擎的唯一作业是分析 URL,并将控制权交给路由处理程序。 MapRoute 方法中的 图 1 的 RegisterRoutes 方法调用是一种扩展方法,提供由 MVC 框架。 注册 MapRoute 每个路由被配置为使用 MvcRouteHandler 对象,这是由 MVC Framework 提供路由处理程序。 如您看到一月列中,是要查找请求的 HTTP 处理程序的工艺路线处理程序的作业) 的实现 IHttpHandler 接口的对象。 MVC 应用程序中, 此对象将类型 MvcHandler 的对象,并且在 MvcHandler 有处理变得有趣。
图 2 显示了典型的 MVC 请求在处理流。 控制到达该 MvcHandler,在 MvcHandler 时能够从由路由模块前面在处理的 RouteData 提取控制器参数。 该处理程序将最终发送到控制器工厂的此控制器为参数是一个字符串。 然后是构建,并返回一个控制器的工厂的责任。 MVC 应用程序中的所有控制器都实现 IController 接口。
图 2 典型的 MVC 请求的控制流
MVC Framework,提供了默认控制器工厂 (适当命名 DefaultControllerFactory) 将搜索在一个查找所有类型的实现 IController 和其名称结尾"控制器"所有程序集的。 因此,如果您知道,工厂以查找在主"控制器,工厂可以返回 HomeController 类无论命名空间或它所在的程序集的新实例化的实例只要它实现 IController。 此行为是 MVC 的"约定通过配置"样式的一部分。 是工厂文字部分的更多,但是让我们首先完成 MvcHandler 处理。
一旦在 MvcHandler 工厂中有一个 IController 引用,它调用控制器和等待工作的神奇功能在控制器上执行。 执行完成时,将检查该 MvcHandler 如果控制器实现该 IDisposable 接口,如果是,将调用 Dispose 以清理非托管资源在控制器上。
工厂可扩展性
控制器工厂是 ASP.NET MVC Framework 中的一个关键扩展点。 尽管 Framework 所提供的默认工厂可以在您的解决方案中找到任何一个 HomeController,它可以实例仅化在 HomeController 如果提供无参数构造函数。 此限制是按照依赖项反转原则,并将通过其构造函数的控制器的依赖项的团队的问题。 作为示例,请考虑 EmployeeController (图 3 所示),需要某人将日志记录组件传递到其唯一的构造函数。
图 3 EmployeeController
public class EmployeeController : IController
{
public EmployeeController(ILogger logger)
{
_logger = logger;
}
public void Execute(RequestContext requestContext)
{
// ...
}
ILogger _logger;
}
幸运的是,您可以创建自定义工厂。 实现 IControllerFactory 接口的任何类是一个的例子,您只需要实现 CreateController 和 ReleaseController 方法。 但是,控件容器,如 StructureMap、 Unity、 Ninject 和城墙项目的 Windsor 的反转会是易于使用,并且是这种情况下的完全适合。 实际上,在 CodePlex 上的 MVC contrib 项目 包含上面列出的容器的所有 IControllerFactory 实现。
如果想要用作控件容器的将反转的 StructureMap,您可以引用 StructureMap 和 MvcContrib.StructureMap 程序集,然后编写代码 图 4 所示。 在此列表 InitializeContainer 方法将第一次通知 StructureMap 的 ILogger 需要时使用 SqlServerLogger 类型。 该代码然后设置控制器工厂整个应用程序使用的当前 ControllerBuilder SetControllerFactory 方法。 请求处理,过程中在 MvcHandler 将当前配置工厂询问此同一 ControllerBuilder 并使用而不是默认 Framework 工厂的 StructureMapControllerFactory。
图 4 初始化容器
protected void Application_Start()
{
InitializeContainer();
RegisterRoutes(RouteTable.Routes);
}
private void InitializeContainer()
{
StructureMapConfiguration
.ForRequestedType<ILogger>()
.TheDefaultIsConcreteType<SqlServerLogger>();
ControllerBuilder.Current.SetControllerFactory(
new StructureMapControllerFactory());
}
从 MVC contrib 项目 StructureMapControllerFactory 会继承 MVC Framework 的默认控制器工厂,并将仍使用我前面介绍查找要实例化的控制器类型时的约定。 但是,工厂将用于 StructureMap 实例化该的控制器以及 StructureMap 知道如何使用参数化构造函数。 图 4 所示的配置将所有需要处理一个请求 http://localhost/Employee/。 StructureMap 通过 SqlServerLogger 引用中的传递,将实例化从 图 3 EmployeeController。
图 5 类层次结构
执行是仅有开始
前面我声明在 MvcHandler 调用等待的控制器的 Execute 方法,然后清除。 这是因为在 MvcHandler 只知道通过 IController 接口的控制器。 如果想要编写应用程序在此级别,可以只是派生 IController 界面中的所有控制器并提供 Execute 方法的实现。 但是,ASP.NET MVC 框架提供了更丰富的执行模型通过 图 5 所示的类的层次结构的控制器。
默认,您将添加到 ASP.NET MVC 的项目的控制器将派生自 System.Web.mvc.controller 类。 一个方法添加一个新的控制器是右键单击在解决方案资源管理器该控制器文件夹并选择添加 | 控制器可以 图 6 所示的对话框。 记住控制器工厂以查找该名称必须以"控制器"结束您控制器。
控制器基类引入了操作的概念。 操作是在控制器上作为最终请求目标 MVC 应用程序中的方法。 前面,我指出 ASP.NET 路由模块将提升从"开始"作为控制器参数从 URL 的 http://localhost/home/Index/。 这是将请求路由到正确的控制器的足够信息。 此外选择"索引"操作参数的形式出该路由模块。 当在 MvcHandler 告诉执行 HomeController 时,有达组成基本的控制器类来检查此操作参数和调用正确的控制器方法内部的逻辑。 此操作的路由逻辑的大部分驻留公用 ControllerActionInvoker 类的内部。
图 6 添加控制器
图 7 是由 ASP.NET MVC 项目模板提供的 HomeController。 这些公共实例方法的索引和大约,代表客户端请求住宅 / 索引时,将调用框架的操作 / 和主页将 /,分别。 任何 public 实例方法可以作为一个控制器操作,只要在 Framework 可以确定特定的操作是调用该正确操作 (即,谨慎方法重载)。 其他规则在播放时,有了 Framework 正在搜索要调用的操作。 可以影响的操作的框架的选择,建立为操作选择,附加规则并处理您的操作使用属性的行为。
图 7 HomeController
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
return View();
}
}
选择器属性
您可以修饰一个控制器操作,要提供 Framework 其他信息,选择要调用的操作时要考虑的选择器属性。是例如将 [NonAction] 属性添加到控制器方法将导致方法从可用操作列表中排除。您还可以为一个方法特定操作命名。默认情况下, 一个方法的名称也是它的操作名称但如果置于 [ActionName("Help")] 您 HomeController 有关方法的则"关于"是不再对您的控制器的操作无效。相反,在有关方法的操作名称是"帮助",在 Framework 将调用该方法的请求如 / 家庭帮助 /。
一个特别重要的选择器属性是 AcceptVerbs 属性。此属性只允许在 Framework 属性中列出的谓词的一个匹配当前的 HTTP 请求的谓词时选择操作。对于示例,修饰方法 with[AcceptVerbs(HttpVerbs.post)] 意味着只能为一个操作为 HTTP POST 操作调用该方法。很重要,选择您控制器操作的正确谓词尤其是当操作修改服务器上的状态。有关详细信息,请参阅Stephen Walther ASP.NET MVC 提示 #46.
筛选器属性
操作的筛选器是属性的另一种类型的可放置的操作。使用操作筛选器可以添加缓存、 验证和错误处理您的操作使用声明性编程模型的行为。筛选器属性的示例是 [HandleError] 属性在 图 7 中 HomeController。可以将此属性应用于单独的操作或将该属性添加到控制器类将行为应用于控制器中的所有操作。
当一个 HandleError 属性的操作上存在,并且操作引发异常时,MVC 框架将查找名称"错误"的视图第一个控制器的视图文件夹中,然后还在共享的视图文件夹。视图时出错,可以为用户提供友好错误页。还可以将异常映射到使用更明确的 HandleError 属性的特定视图。是例如 [HandleError(ExceptionType=typeof(SqlException),视图 ="DatabaseError")] 将映射到名为"DatabaseError"视图的未处理的 SqlException。 图 8 中概述操作筛选器 MVC Framework 所提供的其余部分。
图 8 的操作筛选器 |
名称 | 说明 |
OutputCacheAttribute | 类似于 ASP.NET Web 窗体中在 OutputCache 指令。OutputCache 属性允许在 MVC Framework 缓存控制器的输出。 |
ValidateInputAttribute | 类似于 Web 表单中 ValidateRequest 属性。默认,MVC 框架将为 HTML 或其他危险输入检查传入的 HTTP 请求。如果检测到,将引发异常。使用此属性可以禁用请求验证。 |
AuthorizeAttribute | authorize 属性,可以将控制器操作的声明性的授权检查。该属性可以限制特定角色中的用户的操作。当您创建只应可供管理员角色中的用户的操作时,您可以使用此属性。 |
ValidateAntiForgeryTokenAttribute | 此属性是一个解决方案以帮助防止跨站点的一半请求 forgeries (CSRF)。它允许验证的 HTTP POST 为特定于用户的标记在 Framework。有关详细信息 CSRFs,请参阅"防止跨站点请求伪造 (CSFR) 使用 ASP.NET MVC AntiForgeryToken() 帮助器." |
自定义操作的筛选器
您可以创建自己的操作筛选以将具有自定义逻辑的操作。图 9 中的代码是为简单的日志记录操作筛选可以写入输出窗口的 Visual Studio 调试过程中的代码。我们可以在一个单独的操作应用此属性,或我们可以将所有操作的日志记录控制器中控制器类上放置此属性。
图 9 A 日志记录操作筛选器
public class LogAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Log("Action Executing", filterContext.RouteData);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
Log("Action Executed", filterContext.RouteData);
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
Log("Result Executing", filterContext.RouteData);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
Log("Result Executed", filterContext.RouteData);
}
void Log(string stageName, RouteData routeData)
{
Debug.WriteLine(
String.Format("{0}::{1} - {2}",
routeData.Values["controller"],
routeData.Values["action"],
stageName));
}
}
正如您所看到则有四个文章 ActionFilter 类提供的虚拟方法。您可以重写一个或多个不仅预生成和 post-process 控制器操作但还预生成这些方法并 post-process 控制器操作的结果。操作执行、 OnActionExecuting 方法将触发,和操作何时完成之前,OnActionExecuted 方法将触发 (并将会触发,即使该操作会引发未处理的异常)。同样,OnResultExecuting 方法将触发结果执行和 OnResultExecuted 方法将触发后之前。
上下文参数传递给操作的筛选器方法将允许您检查 HTTP 请求、 HTTP 上下文、 工艺路线的数据等。引发异常从这些方法之一将中止请求处理流。如果在编写一个 ActionFilter 检查环境中的前提条件,是有用的工具一个例外。
获得结果
成功执行 MVC 控制器操作会产生从 ActionResult 派生的对象。呈现视图和重定向到新的 URL 的浏览器则是可能需要从您的控制器结果两个有效类型。图 10 显示 ActionResult 派生类型的完整列表。
图 10 ActionResult 派生类型 |
名称 | Framework 行为 | 生成方法 |
ContentResult | 写入一个字符串值直接 HTTP 响应。 | 内容 |
EmptyResult | 不写入 HTTP 响应。 | |
FileContentResult | 采用一个文件 (表示为一个字节数组) 的内容,并将内容写入到 HTTP 响应。 | 文件 |
FilePathResult | 将文件的内容在给定位置,并将内容写入到 HTTP 响应。 | 文件 |
FileStreamResult | 采用由该控制器的文件流,并写入 HTTP 响应的流。 | 文件 |
HttpUnauthorizedResult | 授权检查失败时使用的授权筛选器的特殊结果。 | |
JavaScriptResult | 响应客户端执行客户端脚本。 | JavaScript |
JsonResult | 响应客户端 JavaScript Object Notation (JSON) 中的数据。 | Json |
RedirectResult | 将客户重定向到新的 URL。 | 重定向 |
RedirectToRouteResult | 呈现 HTML 片段 (通常用于 AJAX 方案) 的响应指定的视图。 | RedirectToRoute / RedirectToAction |
PartialViewResult | 呈现 HTML 片段 (通常用于 AJAX 方案) 的响应指定的视图。 | PartialView |
ViewResult | 呈现在指定的视图,并响应客户端使用 HTML。 | 视图 |
请注意一个控制器操作不需要直接实例化这些类型之一。相反,控制器操作可以调用 图 10 所示以产生结果的方法名称。这些方法是从 MVC 控制器的基类继承的。也的值得注意的一个控制器操作不需要返回 ActionResult 对象。如果该控制器返回以外的 ActionResult,框架将在将对象转换为字符串,并且封装一个 ContentResult 只是将字符串写入 HTTP 响应) 的字符串。一个返回 void 控制器将生成一个 EmptyResult。
ActionResult 类定义了一个 ExecuteResult 方法将覆盖每个 图 10 中的类型。此方法调用由该 ControllerActionInvoker,调用该控制器的操作的同一个对象。当调用,每个结果将负责成功地将结果传递到客户端所需的所有小细节。是例如 JavaScript 结果将在 HttpUnauthorizedResult 将集到一个 401 (未经授权) 响应的 HTTP 状态代码时设置响应"应用程序 / x-javascript",内容类型标的头。
从控制器操作常见响应将一个 ViewResult。所有您操作已调用控制器的该 View 方法并返回结果,您已经看到在以前的代码列表的此结果。这是另一个示例"约定通过配置,",因为在 ViewResult 将此的默认方式中使用时查找视图在该控制器的视图文件夹和使用与操作匹配的文件名。是例如 views\home\about.aspx 是在常规视图在有关主控制器的操作。View 方法的重载的版本,可以明确说明视图的名称。
操作是通过
本月我已获得深入研究,抽象,行为周围 ASP.NET MVC 控制器。yo u 现在应该有的方式在 MVC Framework 查找一个很好理解,创建,并使用控制器,以及如何挂接到扩展点 MVC Framework 的都围绕控制器。在本专栏在下一版中,我会看看准则和将这些控制器在实际的应用程序中工作的最佳做法。
见解: 帮助器方法
如果您觉得奇怪帮助器方法用于操作的结果,它们在 Framework 中第一次出现时 (view()、 content()),您可能想知道其有关的附带,并且内容是该特定的设计决策后面。
嗯,文章帮助器方法返回操作结果后面是 99%的 MVC 开发人员编写 MVC 应用程序的时间要花费编写控制器的操作。我们想要确保在典型的方法是干净、 读尽可能为声明。
是例如可以仍编写的操作方法如下:
public ActionResult Foo {
// ... do stuff ...
ViewData.Model = myModel;
return new ViewResult {ViewName = "Foo", ViewData = this.ViewData};
}
我们想要此清理有点,以便可以看到,我们做出一些的调整:
public ActionResult Foo {
// ... do stuff ...
return new View(myModel);
}
这是语言的一种更声明性的方法 (尽管很强制性的使用)。 读取的操作方法时, 它反映了您的需要。 " 我想返回一个包含此模型的视图"。
--Phil Haak,高级项目经理 Microsoft
将您的问题和提出的意见发送至 xtrmasp@Microsoft.com.
K Scott Allen 是 Pluralsight 技术人员的成员和 OdeToCode 的创始人。 您可以访问在 Scott Scott@OdeToCode.com 或读取在他的博客 odetocode.com/blogs/Scott.