ASP.NET MVC 4 (四) 控制器扩展

MVC的标准流程是请求传递给控制器,由控制器action方法操作数据模型,最后交由视图渲染输出,这里忽略了两个细节,就是MVC是如何创建相应控制器实例,又是如何调用控制器action方法的,这就必须讲到控制器工厂和action调用器。

控制器工厂

Controller factory负责创建并初始化控制器,控制器工厂实现IControllerFactory接口:

namespace System.Web.Mvc {
public interface IControllerFactory {
  IController CreateController(RequestContext requestContext, string controllerName);
  SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName);
  void ReleaseController(IController controller);
  }
} 

我们可以从IControllerFactory接口实现自定义的控制器工厂:

public class CustomControllerFactory: IControllerFactory {

        public IController CreateController(RequestContext requestContext, string controllerName) {

            Type targetType = null;
            switch (controllerName) {
                case "Product":
                    targetType = typeof(ProductController);
                    break;
                case "Customer":
                    targetType = typeof(CustomerController);
                    break;
                default:
                    requestContext.RouteData.Values["controller"] = "Product";
                    targetType = typeof(ProductController);
                    break;
            }

            return targetType == null ? null :
                (IController)DependencyResolver.Current.GetService(targetType);
        }

        public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName) {

            switch (controllerName) {
                case "Home":
                    return SessionStateBehavior.ReadOnly;
                case "Product":
                    return SessionStateBehavior.Required;
                default:
                    return SessionStateBehavior.Default;
            }
        }

        public void ReleaseController(IController controller) {
            IDisposable disposable = controller as IDisposable;
            if (disposable != null) {
                disposable.Dispose();
            }
        }
    }

这里最重要的方法就是CreateController(),由它根据请求的控制器的名称直接创建所需控制器的实例,这里是直接硬编码控制器名称,当然实际的应用中众多不应该是这样操作。CreateController()必须返回一个实现IController的对象,上面例子中最后调用DependencyResolver.Current.GetService()来负责创建相应控制器的实例。

方法GetControllerSessionBehavior()是用于MVC确定是否需要维护会话信息,稍后详述。方法ReleaseController()用于释放需要的资源,这里只是单纯调用控制器实例的Dispose()方法释放资源。

要使用自定义的控制器工厂我们还必须在应用启动时注册它为当前控制器工厂:

    public class MvcApplication : System.Web.HttpApplication {
        protected void Application_Start() {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());
...

MVC内建控制器工厂DefaultControllerFactory

我们一般不需要自定义控制器工厂而使用MVC默认的DefaultControllerFactory,它根据路径映射在应用程序中搜索符合这些要求的控制器类:

  • 类必须是public
  • 必须是实类,不是abstract
  • 不能带泛型参数
  • 类名必须以Controller结尾
  • 类必须实现IController接口

DefaultControllerFactory维护一个应用程序内符合要求的控制器列表,在请求到达时从列表中搜索相应的控制器。在路径映射一文中讲到搜索控制器时的命名空间优先级,除了在路径映射中指定命名空间,还可以这样添加默认优先搜索的命名空间:

ControllerBuilder.Current.DefaultNamespaces.Add("MyControllerNamespace");
ControllerBuilder.Current.DefaultNamespaces.Add("MyProject.*"); 

通过ControllerBuilder.Current.DefaultNamespaces.Add添加的命名空间比未在此添加的命名空间有更高的优先级,但是用此方法添加的所有命名空间是同等对待的,它们之间不分优先级。

DefaultControllerFactory使用DependencyResolver初始化控制器类,我们可以通过controller activator以DI(Dependency injection)的方式调整DefaultControllerFactory创建控制器类,controller activator用到IControllerActivator接口:

namespace System.Web.Mvc {
using System.Web.Routing;
public interface IControllerActivator {
IController Create(RequestContext requestContext, Type controllerType);
}
} 

创建一个controller activator的实现:

public class CustomControllerActivator : IControllerActivator
    {

        public IController Create(RequestContext requestContext,
            Type controllerType)
        {
            if (controllerType == typeof(ProductController))
            {
                controllerType = typeof(CustomerController);
            }
            return (IController)DependencyResolver.Current.GetService(controllerType);
        }
    }

和自定义控制器工厂类似,我们需要注册使用它:

ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory(new CustomControllerActivator()));

以这种方式可以调整DefaultControllerFactory如何创建控制器类,比完全自定义类工厂更加简单。实际上我们还可以直接从DefaultControllerFactory扩展自定义的控制器工厂,可以重载DefaultControllerFactory的方法CreateController、

GetControllerType、GetControllerInstance来满足自己的需要。

自定义action invoker

如果我们的控制器类是直接从IController接口创建而来的,我们需要自己调用action方法,而如果控制器类是从Controller类扩展的,Controller则已经内建支持如何调用action方法。调用action方法用到接口IActionInvoker:

namespace System.Web.Mvc {
public interface IActionInvoker {
bool InvokeAction(ControllerContext controllerContext, string actionName);
}
} 

从该接口我们可以创建自定义的action invoker:

public class CustomActionInvoker : IActionInvoker {

        public bool InvokeAction(ControllerContext controllerContext, string actionName) {
            if (actionName == "Index") {
                controllerContext.HttpContext.
                    Response.Write("This is output from the Index action");
                return true;
            } else {
                return false;
            }
        }
    }

这里只是简单的匹配action的名称,如果是index则直接输出结果到响应。我们还必须将它和控制器联系起来才能使用:

public class ActionInvokerController : Controller {
  public ActionInvokerController() {
    this.ActionInvoker = new CustomActionInvoker();
    }
} 

这里在ActionInvokerController的构造函数中指定其ActionInvoker为自定义的ActionInvoker,由它负责处理action的调用。

内建Action invoker

和控制器工厂一样,MVC提供内建的默认action invoker:ControllerActionInvoker。它负责在控制器内搜索匹配action方法,只有符合以下要求的控制器方法才被认为是action方法:

  • 方法必须是public
  • 方法必须不是static
  • 方法必须不在System.Web.Mvc.Controller或它的任何子类中出现
  • 方法名必须不是特殊名,比如不是构造函数、不是属性、不是事件访问方法,简单的说就是不带IsSpecialName(System.Reflection.MethodBase)标志。

虽然泛型函数比如MyMethod<T>()满足上面的要求,但是MVC在调用这样的方法时会报出异常。

默认情况下根据请求查找同名的action方法,但是我们可以这样自定义action名称:

[ActionName("Enumerate")]
        public ViewResult List() {
            return View("Result", new Result {
                ControllerName = "Customer",
                ActionName = "List"
            });
        }

这里通过ActionName特性将Enumerate action映射到List方法,访问原有的list会得到错误。通过这种方式可以实现不符合c#方法名称的action,比如[ActionName("User-Registration")],还可以将同一个action名称结合其他特性比如[HttpGet]、[HttpPost]将它们映射到不同的控制器方法上。

除了ActionName特性我们还可以使用NonAction特性指示一个控制器方法不能作为action调用:

[NonAction]
        public ActionResult MyAction() {
            return View();
        }

这里MyAction虽然是合法的action方法,但是通过[NonAction]将其标识为非action,对它的请求将得到“Resource not found”错误。

Action方法选择器

ActionName、NoActionAction、HttpPost、HttpGet这些特性都是从MethodSelectorAttribute扩展而来,它们统一称为action方法选择器,当然我们也可以创建自定义的action方法选择器:

public class LocalAttribute : ActionMethodSelectorAttribute {

        public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) {
                return controllerContext.HttpContext.Request.IsLocal;
        }
    }

这里LocalAttribute确认所标识的action方法针对请求来自于本地是可以处理的:

public class HomeController : Controller {

        public ActionResult Index() {
            return View("Result", new Result {
                ControllerName = "Home", ActionName = "Index"
            });
        }

        [Local]
        [ActionName("Index")]
        public ActionResult LocalIndex()
        {
            return View("Result", new Result
            {
                ControllerName = "Home",
                ActionName = "LocalIndex"
            });
        }
...

比如这里的Home控制器的Index方法和通过ActionName("Index")指定的LocalIndex方法都对应到Index请求,如果LocalIndex不附加[Local]就会产生重名的action错误,通过[Local]就可以将来自于本地的请求映射到LocalIndex上,其他请求则调用Index方法。

未知Action的处理

如果没有找到对应的Action方法,Controller类调用其HandleUnknownAction方法,默认该方法报404-Not found错误。我们可以重载Controller的HandleUnknownAction方法执行其他操作:

protected override void HandleUnknownAction(string actionName) {
            Response.Write(string.Format("You requested the {0} action",actionName));
        }

如果没有发现相应的action方法,这里直接输出一条信息到响应。

Sessionless控制器

控制器默认支持会话,在多个客户端请求间保存会话数据,如果客户端同时发出多个请求,这些请求必须排队依次处理,以先后顺序修改会话数据,这会影响服务器的并发性能。对于那些不需要会话的场合,我们可以使用sessionless控制器。

IControllerFactory的GetControllerSessionBehavior方法返回一个SessionStateBehavior枚举,它包含以下枚举值:

  • Default:使用配置文件中HttpContext节定义的默认ASP.NET会话状态
  • Requried:会话状态可写可读
  • ReadOnly:会话状态只可写
  • Disabled:禁用会话状态

如果我们是直接从IControllerFactory自定义控制器工厂,我们可以针对不同的请求设置不同的会话行为:

public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName) {

            switch (controllerName) {
                case "Home":
                    return SessionStateBehavior.ReadOnly;
                case "Product":
                    return SessionStateBehavior.Required;
                default:
                    return SessionStateBehavior.Default;
            }
        }

如果使用的是默认DefaultControllerFactory控制器工厂,我们可以使用SessionState标识控制器会话行为:

[SessionState(SessionStateBehavior.Disabled)]
    public class FastController : Controller {

        public ActionResult Index() {
            return View("Result", new Result {
                ControllerName = "Fast ",ActionName = "Index"
            });
        }
    }

这里将FastController标识未不启用会话状态,如果我们在视图中试图访问会话数据比如@Session["Message"]将抛出异常。

异步控制器

ASP.NET维护一个.NET线程池用于处理客户请求,这些工作线程在完成工作后返回到线程池等待为下一个请求服务。使用线程池可以节约为每个请求创建线程的开销,通过固定线程数量也避免同时请求数超过服务器的负载能力的情况。一种极端的情况是如果我们请求的资源位于远程服务器上,线程等待远程资源被阻塞,造成服务器不能响应新的客户端请求。我们用下面的例子模仿这种情况:

public class RemoteDataController : Controller {
  public ActionResult Data() {
    RemoteService service = new RemoteService();
    string data = service.GetRemoteData();
  return View((object)data);
  }
} 

RemoteData控制器方法Data请求的数据来自于RemoteService:

public class RemoteService {
  public string GetRemoteData() {
    Thread.Sleep(2000);
    return "Hello from the other side of the world";
  }
}

我们暂停RemoteService的线程2秒模仿请求远程数据阻塞,如果同时多个请求都访问/RemoteData/Data,比如造成服务器的响应延迟。

创建异步控制器可以直接从System.Web.Mvc.Async.IAsyncController实现,也可以从 System.Web.Mvc.AsyncController扩展,后者内部实现了IAsyncController接口。这里使用AsyncController为例:

public class RemoteDataController : AsyncController{
  public async Task<ActionResult>Data() {
    string data = await Task<string>.Factory.StartNew(() => {
      return new RemoteService().GetRemoteData(); });
    return View((object)data);
  }
} 

异步控制器action方法返回一个Task<ActionResult>对象,方法内部使用await等待访问远程数据完成,结果就是请求该action时工作线程不会被阻塞停止,而是返回到线程池相应其他其他请求,在获取到远程数据完成后再启动线程继续后续工作。

以上为对《Apress Pro ASP.NET MVC 4》第四版相关内容的总结,不详之处参见原版 http://www.apress.com/9781430242369

时间: 2024-12-09 20:35:30

ASP.NET MVC 4 (四) 控制器扩展的相关文章

ASP.NET MVC 过滤器(四)

ASP.NET MVC 过滤器(四) 前言 前一篇对IActionFilter方法执行过滤器在框架中的执行过程做了大概的描述,本篇将会对IActionFilter类型的过滤器使用来做一些介绍. ASP.NET MVC过滤器 过滤器在系统框架中的整体对象模型 IAuthorizationFilter授权认证过滤器的执行过程 使用IAuthorizationFilter过滤器 IActionFilter行为过滤器的执行过程 自定义实现IActionFilter行为过滤器 异常过滤器的使用 自定义实现

ASP.NET MVC 路由(四)

ASP.NET MVC 路由(四) 前言 在前面的篇幅中我们讲解路由系统在MVC中的运行过程以及粗略的原理,想必看过前面篇幅的朋友应该对路由有个概念性的了解了,本篇来讲解区域,在读完本篇后不会肯定的让你对区域有彻底的了解,但是会让你在以后对这部分知识掌握的路上夯上厚实的基础. 区域 在路由的起初在按照VS环境新建的MVC项目来讲的,今天区域也是按照这样的套路来讲.MSDN上说,在大型项目使用中被MVC划分为较小单元也就是区域,我没有使用过区域的经验,所以这篇只能讲解它的对象模型以及粗略的原理.

ASP.NET MVC路由(四)

ASP.NET MVC路由(四) 前言 在前面的篇幅中我们讲解路由系统在MVC中的运行过程以及粗略的原理,想必看过前面篇幅的朋友应该对路由有个概念性的了解了,本篇来讲解区域,在读完本篇后不会肯定的让你对区域有彻底的了解,但是会让你在以后对这部分知识掌握的路上夯上厚实的基础. 区域 在路由的起初在按照VS环境新建的MVC项目来讲的,今天区域也是按照这样的套路来讲.MSDN上说,在大型项目使用中被MVC划分为较小单元也就是区域,我没有使用过区域的经验,所以这篇只能讲解它的对象模型以及粗略的原理. (

ASP.NET MVC学习之控制器篇扩展性

原文:ASP.NET MVC学习之控制器篇扩展性 一.前言 在之前的一篇随笔中已经讲述过控制器,而今天的随笔是作为之前的扩展. 二.正文 1.自定义动作方法 相信大家在开发过程一定会遇到动作方法的重名问题,虽然方法的名称和参数一样,但是里面的逻辑是不一样的,因为你设置了对应的注解属性可以确定调用哪个动作方法.这个时候你就需要将动作的名称与方法的名称区别开来,那么你就可以使用ActionName注解属性.比如我们要求一个页面在本地访问与非本地访问时呈现不同的页面,但是你又想用不同的方法区分开来写,

ASP.NET MVC中有四种过滤器类型

在ASP.NET MVC中有四种过滤器类型 Action 1.在ASP.NET MVC项目中,新建文件夹Filter,然后新建类MyCustormFilter,继承自ActionFilterAttribute类,我们来看下ActionFilterAttribute类有如下四个方法,从命名我应该就可以看出他们的执行时机. public class MyCustormFilter:ActionFilterAttribute { public override void OnActionExecuti

ASP.NET MVC学前篇之扩展方法、链式编程

前言 目的没有别的,就是介绍几点在ASP.NETMVC 用到C#语言特性,还有一些其他琐碎的知识点,强行的划分一个范围的话,只能说都跟MVC有关,有的是外围的知识,有的是包含在框架内的. MVC学前篇字样?有噱头的成分也有真实的成分,所以工欲善其事,必先利其器.器是什么?基础嘛,虽然说MVC框架中涉及到的知识很多很多也不是我一篇两篇能说完的,我能做的就是知道多少就跟大家分享多少,当然了随着时间的推移会完善这个系列. 1扩展方法 扩展方法是C# 3.0特性里的知识,它用在最多的地方是在Linq中,

ASP.NET MVC 5 -从控制器访问数据模型

原文:ASP.NET MVC 5 -从控制器访问数据模型 在本节中,您将创建一个新的MoviesController类,并在这个Controller类里编写代码来取得电影数据,并使用视图模板将数据展示在浏览器里. 在开始下一步前,先Build一下应用程序(生成应用程序)(确保应用程序编译没有问题) 在解决方案上,用鼠标右键单击Controller文件夹,点击新增,再选择Controller. 在Scaffold新增对话框,选择MVC 5  Controller with views, using

ASP.NET MVC 视图(四)

ASP.NET MVC 视图(四) 前言 上篇对于利用IoC框架对视图的实现进行依赖注入,最后还简单的介绍一下自定义的视图辅助器是怎么定义和使用的,对于Razor语法的细节和辅助器的使用下篇会说讲到,本篇来讲解一下视图中的分段概念.和分部视图的使用. ASP.NET MVC 视图 自定义视图引擎 Razor视图引擎执行过程 Razor视图的依赖注入.自定义视图辅助器 分段.分部视图的使用 Razor语法.视图辅助器 分段.分部视图的使用 分段的使用 在ASP.NET MVC框架的Razor引擎中

[转]ASP.NET MVC 5 -从控制器访问数据模型

在本节中,您将创建一个新的MoviesController类,并在这个Controller类里编写代码来取得电影数据,并使用视图模板将数据展示在浏览器里. 在开始下一步前,先Build一下应用程序(生成应用程序)(确保应用程序编译没有问题) 在解决方案上,用鼠标右键单击Controller文件夹,点击新增,再选择Controller. 在Scaffold新增对话框,选择MVC 5  Controller with views, using Entity Framework, 点击新增. · 控制