ASP.NET MVC 中的 DefaultControllerFactory





4.00/5 (5投票s)
本文完整描述了 DefaultControllerFactory 创建控制器类实例以生成响应所遵循的过程。
每当 MVC 收到请求时,路由引擎的工作就是将请求 URL 与注册的路由进行匹配。找到匹配的路由后,会调用 MvcRouteHandler
来为请求提供一个合适的处理程序。MvcHandler
负责为正在处理的当前请求生成响应。在其 ProcessRequestInit()
方法中,会调用获取控制器工厂的方法,该方法会返回一个 DefaultControllerFactory
类的实例。使用此 DefaultControllerFactory
类的实例,调用 CreateController()
方法来返回一个控制器。您可以在我的上一篇文章中了解有关 MvcRouteHandler
和 MvcHandler
的更多信息。
在这篇文章中,让我们探索 DefaultControllerFactory
类实例化控制器的过程。DefaultControllerFactory
类实现了 IControllerFactory
接口。控制器工厂的职责是创建需要调用以生成响应的控制器类实例。
IControllerFactory 接口
IControllerFactory
接口公开了方法,当任何类实现这些方法时,该类将充当控制器工厂。我们可以通过实现 IControllerFactory
并在 Application_Start()
事件中调用 SetControllerFactory()
方法来注册我们的自定义控制器工厂。IControllerFactory
的样子如下:
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.
namespace System.Web.Mvc {
using System.Web.Routing;
using System.Web.SessionState;
public interface IControllerFactory {
IController CreateController(RequestContext requestContext, string controllerName);
SessionStateBehavior GetControllerSessionBehavior(
RequestContext requestContext, string controllerName);
void ReleaseController(IController controller);
}
}
是 CreateController()
方法,实现类为其提供主体以创建匹配控制器的实例。ReleaseController()
方法用于释放控制器实例。
DefaultControllerFactory 类
DefaultControllerFactory
以虚函数形式实现 IControllerFactory
的方法。默认情况下,MVC 在 ControllerBuilder
类的构造函数中将 DefaultControllerFactory
注册为创建控制器的工厂。
DefaultControllerFactory() 构造函数
在 DefaultControllerFactory
类中,除了默认构造函数,它还有一个接受 IControllerActivator
类型的参数化构造函数。这允许使用 DefaultControllerFactory
类创建自定义控制器激活器。
注意:此类的完整代码可在 codeplex 中找到。
// Copyright (c) Microsoft Open Technologies, Inc.
// All rights reserved. See License.txt in the project root for license information.
public DefaultControllerFactory()
: this(null, null, null) {
}
public DefaultControllerFactory(IControllerActivator controllerActivator)
: this(controllerActivator, null, null) {
}
internal DefaultControllerFactory(IControllerActivator controllerActivator,
IResolver<IControllerActivator> activatorResolver,
IDependencyResolver dependencyResolver) {
if (controllerActivator != null) {
_controllerActivator = controllerActivator;
}
else {
_activatorResolver = activatorResolver ??
new SingleServiceResolver<IControllerActivator>(
() => null,
new DefaultControllerActivator(dependencyResolver),
"DefaultControllerFactory contstructor"
);
}
}
默认构造函数和参数化构造函数都调用另一个内部构造函数。如果它没有收到控制器激活器,则该内部控制器负责创建控制器激活器实例。现在,如果它收到了控制器激活器,这意味着用户提供了一个自定义实现的控制器激活器;如果收到了空值,它会检查是否有任何服务使用 activatorResolver
参数解析控制器激活器。activatorResolver
的类型是 IResolver<IControllerActivator>
,它指定了一个知道如何定位 IControllerActivator
类型的解析器。如果 controllerActivator
和 activatorResolver
值都为空,则会创建一个 DefaultControllerActivator
实例。DefaultControllerActivator
是 DefaultControllerFactory
类内部的一个内部类。内部类如下所示:
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.
private class DefaultControllerActivator : IControllerActivator {
Func<IDependencyResolver> _resolverThunk;
public DefaultControllerActivator()
: this(null) {
}
public DefaultControllerActivator(IDependencyResolver resolver) {
if (resolver == null) {
_resolverThunk = () => DependencyResolver.Current;
}
else {
_resolverThunk = () => resolver;
}
}
public IController Create(RequestContext requestContext, Type controllerType) {
try {
return (IController)(_resolverThunk().GetService(controllerType) ??
Activator.CreateInstance(controllerType));
}
catch (Exception ex) {
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.DefaultControllerFactory_ErrorCreatingController,
controllerType),
ex);
}
}
}
为了创建 DefaultControllerActivator
类的实例,会调用 SingleServiceResolver()
方法,该方法将调用委托给 DependencyResolver
类中的 GetSingleService()
方法,该方法使用其默认构造函数创建 IControllerActivator
类型的实例。
CreateController() 方法
DefaultControllerFactory
类是这样实现 CreateController()
方法及其所需的内部方法的。
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.
private IControllerActivator ControllerActivator {
get {
if (_controllerActivator != null) {
return _controllerActivator;
}
_controllerActivator = _activatorResolver.Current;
return _controllerActivator;
}
}
public virtual IController CreateController(RequestContext requestContext, string controllerName) {
if (requestContext == null) {
throw new ArgumentNullException("requestContext");
}
if (String.IsNullOrEmpty(controllerName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
}
Type controllerType = GetControllerType(requestContext, controllerName);
IController controller = GetControllerInstance(requestContext, controllerType);
return controller;
}
protected internal virtual IController GetControllerInstance(
RequestContext requestContext, Type controllerType) {
if (controllerType == null) {
throw new HttpException(404,
String.Format(
CultureInfo.CurrentCulture,
MvcResources.DefaultControllerFactory_NoControllerFound,
requestContext.HttpContext.Request.Path));
}
if (!typeof(IController).IsAssignableFrom(controllerType)) {
throw new ArgumentException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.DefaultControllerFactory_TypeDoesNotSubclassControllerBase,
controllerType),
"controllerType");
}
return ControllerActivator.Create(requestContext, controllerType);
}
protected internal virtual Type GetControllerType(
RequestContext requestContext, string controllerName) {
if (String.IsNullOrEmpty(controllerName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
}
// first search in the current route's namespace collection
object routeNamespacesObj;
Type match;
if (requestContext != null &&
requestContext.RouteData.DataTokens.TryGetValue(
"Namespaces", out routeNamespacesObj)) {
IEnumerable<string> routeNamespaces = routeNamespacesObj as IEnumerable<string>;
if (routeNamespaces != null && routeNamespaces.Any()) {
HashSet<string> nsHash =
new HashSet<string>(routeNamespaces, StringComparer.OrdinalIgnoreCase);
match = GetControllerTypeWithinNamespaces(
requestContext.RouteData.Route, controllerName, nsHash);
// the UseNamespaceFallback key might not exist,
// in which case its value is implicitly "true"
if (match != null || false.Equals(
requestContext.RouteData.DataTokens["UseNamespaceFallback"])) {
// got a match or the route requested we stop looking
return match;
}
}
}
// then search in the application's default namespace collection
if (ControllerBuilder.DefaultNamespaces.Count > 0) {
HashSet<string> nsDefaults = new HashSet<string>(
ControllerBuilder.DefaultNamespaces, StringComparer.OrdinalIgnoreCase);
match = GetControllerTypeWithinNamespaces(
requestContext.RouteData.Route, controllerName, nsDefaults);
if (match != null) {
return match;
}
}
// if all else fails, search every namespace
return GetControllerTypeWithinNamespaces(
requestContext.RouteData.Route, controllerName, null /* namespaces */);
}
在 MvcHandler
类的 ProcessRequestInit()
方法中,创建了 DefaultControllerFactory
类的实例。创建实例后,调用 CreateController()
方法。CreateController()
方法的职责如下:
- 搜索与控制器名称匹配的控制器类型。
- 如果找到一个与名称匹配的控制器类型,则实例化该类型并返回其实例。
- 如果找到多个与名称匹配的控制器类型,则抛出歧义异常。
- 如果未找到任何控制器类型,则返回 http 404 响应。
CreateController()
方法要完成的上述工作被分成两个虚拟方法,即 GetControllerType()
和 GetControllerInstance()
方法。
仔细查看 GetControllerType()
方法,我们可以得出结论,工厂查找控制器类型的第一步是在 RouteData
的 DataTokens
属性中分配的命名空间。当我们创建路由时,我们可以传递将处理请求的控制器的命名空间。例如,
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { "MyMvcApp.Controllers" }
);
为了检查是否存在并获取命名空间,会调用 DataTokens
的 TryGetValue()
方法。然后从 TryGetValue()
方法的结果中创建一个哈希集,以获取唯一的命名空间。使用控制器、哈希集和控制器名称的匹配路由,调用 GetControllerTypeWithinNamespaces()
方法。它看起来是这样的:
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.
private Type GetControllerTypeWithinNamespaces(RouteBase route,
string controllerName, HashSet<string> namespaces) {
// Once the master list of controllers has been created we can quickly index into it
ControllerTypeCache.EnsureInitialized(BuildManager);
ICollection<Type> matchingTypes =
ControllerTypeCache.GetControllerTypes(controllerName, namespaces);
switch (matchingTypes.Count) {
case 0:
// no matching types
return null;
case 1:
// single matching type
return matchingTypes.First();
default:
// multiple matching types
throw CreateAmbiguousControllerException(
route, controllerName, matchingTypes);
}
}
DefaultControllerFactory
类使用反射来发现程序集中的控制器类型。反射本身是一个昂贵的操作。因此,为了避免每次都搜索控制器,工厂会将发现的控制器类型进行缓存。ControllerTypeCache
类用于此目的。所有找到的控制器类型都存储为名为 MVC-ControllerTypeCache.xml
的 XML 文件。GetControllerTypeWithinNamespaces()
方法做的第一件事是,它调用 ControllerTypeCache
类的 EnsureInitialized()
方法。结果是,所有存储的控制器类型都从 XML 文件中读取并存储在字典中。之后,调用 ControllerTypeCache
类的 GetControllerTypes()
方法,传入控制器名称和命名空间哈希集。输出是命名空间哈希集中所有与名称匹配的控制器类型。
根据 matchingTypes
的计数,决定返回类型。如果没有与发送的命名空间哈希集匹配的控制器类型,则返回 null 值。如果只有一个条目,则返回匹配的控制器类型。否则,如果有多个匹配的控制器类型,则调用 CreateAmbiguousControllerException()
方法,该方法会引发 InvalidOperationException
异常。CreateAmbiguousControllerException()
方法如下所示:
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.
internal static InvalidOperationException CreateAmbiguousControllerException(
RouteBase route, string controllerName, ICollection<Type> matchingTypes) {
// we need to generate an exception containing all the controller types
StringBuilder typeList = new StringBuilder();
foreach (Type matchedType in matchingTypes) {
typeList.AppendLine();
typeList.Append(matchedType.FullName);
}
string errorText;
Route castRoute = route as Route;
if (castRoute != null) {
errorText = String.Format(CultureInfo.CurrentCulture,
MvcResources.DefaultControllerFactory_ControllerNameAmbiguous_WithRouteUrl,
controllerName, castRoute.Url, typeList);
}
else {
errorText = String.Format(CultureInfo.CurrentCulture,
MvcResources.DefaultControllerFactory_ControllerNameAmbiguous_WithoutRouteUrl,
controllerName, typeList);
}
return new InvalidOperationException(errorText);
}
获取匹配的控制器类型后,控制权回到 DefaultConrollerFactory
类的 GetControllerType()
方法。
现在问题是,如果在 RouteData
的 DataToken
属性中没有分配命名空间,或者无法找到与命名空间匹配的单个控制器类型;那么它会转到第二个位置,即在 ControllerBuilder
的 DefaultNamespaces
属性中分配的命名空间。例如,我们可以在路由中传递控制器的命名空间,也可以通过 ControllerBuilder
类在全局级别设置控制器的命名空间。例如,
ControllerBuilder.Current.DefaultNamespaces.Add("MyMvcApp.Controllers");
对于这些定义的命名空间,将再次遵循相同的上述过程,即创建命名空间的哈希集,调用 GetControllerTypeWithinNamespaces()
方法来确定控制器类型,并返回匹配的控制器类型(如果存在)。
如果上述情况再次失败,也就是说,RouteData
的 DataToken
属性中没有分配命名空间(或者有命名空间但无法找到与 RouteData
的 DataToken
属性中分配的命名空间匹配的单个控制器类型),并且 ControllerBuilder
的 DefaultNamespaces
属性中在全局级别也没有分配命名空间(或者有命名空间但无法找到与 ControllerBuilder
的 DefaultNamespaces
属性中分配的命名空间匹配的单个控制器类型)。在这种情况下,它会在当前执行程序集以及引用的程序集的所有命名空间中进行搜索。为此,它会调用 GetControllerTypeWithinNamespaces()
方法,其中命名空间参数为 null 值,该方法会将此 null 值转发给 ControllerTypeCache
类,即 GetControllerTypes()
方法。null 值告诉 GetControllerTypes()
方法在所有当前执行程序集以及引用的程序集中进行搜索。
注意:现在假设我们希望阻止在其他命名空间中搜索控制器,如果未在指定命名空间中找到控制器,默认情况下会发生这种情况。我们可以通过将 DataToken
属性的 UseNamespaceFallback
值设置为 false 来实现。这将停止检查其他命名空间中的控制器类型,并返回控制器类型的 null 值。
最后,在获得匹配的控制器类型后,控制权返回到 CreateController()
方法。使用此控制器类型,会调用 GetControllerInstance()
方法来创建匹配类型的实例。如果返回的控制器类型为 null,则会引发带有 http 状态码 404 的 HttpException
异常。否则,会进行另一次检查,以确保匹配的控制器类型可分配给 IController
接口。如果一切顺利,使用 ControllerActivator
属性,我们会获得对 DefaultControllerActivator
类的引用(该类由 DefaultControllerFactory
类构造函数创建)。使用 DefaultControllerActivator
引用,会调用其 Create()
方法,该方法使用控制器类型的默认构造函数创建控制器实例。
ReleaseController()
DefaultControllerFactory
类中 ReleaseController()
方法的实现如下:
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.
public virtual void ReleaseController(IController controller) {
IDisposable disposable = controller as IDisposable;
if (disposable != null) {
disposable.Dispose();
}
}
名称本身清楚地表明该方法用于释放控制器实例。ReleaseController()
方法在 MvcHandler
类的 ProcessRequest()
方法中,在控制器的 Execute()
方法调用完成后被调用。它标志着响应生成过程的结束,伴随着控制器的释放。为了释放控制器,会调用 Controller
类的 Dispose()
方法(因为 ASP.NET MVC 中的每个控制器都派生自 Controller
类)。Dispose()
方法会为其正在释放的对象调用 GC
类的 SuppressFinalize()
方法。SuppressFinalize 告诉垃圾收集器对象已正确清理,不需要进入终结器队列。
GetControllerSessionBehavior()
IControllerFactory
接口的第三个也是最后一个实现是 GetControllerSessionBehavior()
方法。它在 MvcRouteHandler
类的 GetSessionStateBehavior()
方法中调用。方法体如下:
// Copyright (c) Microsoft Open Technologies, Inc.
// All rights reserved. See License.txt in the project root for license information.
private static readonly ConcurrentDictionary<Type, SessionStateBehavior>
_sessionStateCache = new ConcurrentDictionary<Type, SessionStateBehavior>();
SessionStateBehavior IControllerFactory.GetControllerSessionBehavior(
RequestContext requestContext, string controllerName) {
if (requestContext == null) {
throw new ArgumentNullException("requestContext");
}
if (String.IsNullOrEmpty(controllerName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
}
Type controllerType = GetControllerType(requestContext, controllerName);
return GetControllerSessionBehavior(requestContext, controllerType);
}
protected internal virtual SessionStateBehavior GetControllerSessionBehavior(
RequestContext requestContext, Type controllerType) {
if (controllerType == null) {
return SessionStateBehavior.Default;
}
return _sessionStateCache.GetOrAdd(
controllerType,
type =>
{
var attr = type.GetCustomAttributes(typeof(SessionStateAttribute), inherit: true)
.OfType<SessionStateAttribute>()
.FirstOrDefault();
return (attr != null) ? attr.Behavior : SessionStateBehavior.Default;
}
);
}
此方法用于管理控制器的会话状态行为。默认情况下,ASP.NET MVC 支持会话状态。会话用于在请求之间存储数据值。无论您是否在会话中存储一些数据值,ASP.NET MVC 都会管理应用程序中所有控制器的会话状态。为了管理控制器类型的会话状态,它会检查给定控制器类型上是否存在 SessionStateAttribute
,如果找到,则返回 SessionStateAttribute
中设置的 SessionStateBehavior
枚举值,否则返回默认会话状态,即 SessionStateBehavior.Default
。SessionStateBehavior.Default
枚举值指定使用默认 ASP.NET 行为,即从 HttpContext
确定会话状态配置。
控制器类型的 SessionStateBehavior
存储在 ConcurrentDictionary
类的静态对象中,这使得多个请求线程可以访问它。它充当所有匹配控制器类型及其相应 SessionStateBehavior
值的缓存。
至此,我们完成了对 DefaultControllerFactory
类的目的和工作原理的完整解释。