探索ASP.NET MVC框架之控制器的查找与激活机制
引言
前面一篇博文我们介绍了MVC框架的路由机制,我们知道一个URL请求如何从ASP.NET处理管线到达了IHttpHandler实例(MvcHandler)。今天我们从MvcHandler来进行下一步骤的分析,看看MVC框架是如何找到指定的控制器并且激活(创建)控制器实例的。
一切从MvcHandler的ProcessRequest方法开始(获取控制器工厂实例)
我们知道Http请求到达服务端,找到相应的IHttpHandler后,会执行ProcessRequest方法来处理请求,现在我们就来看看MvcHandler是怎么处理请求的。上源码:
1 IController controller;
2 IControllerFactory factory;
3 ProcessRequestInit(httpContext, out controller, out factory);
4 try
5 {
6 controller.Execute(RequestContext);
7 }
8 finally
9 {
10 factory.ReleaseController(controller);
11 }
从这段代码我们看到,在获取控制器实例时,是通过控制器工厂来创建的,下面我们来看一下ProcessRequestInit方法。看源代码:
1 private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) 2 { 3 // If request validation has already been enabled, make it lazy. This allows attributes like [HttpPost] (which looks 4 // at Request.Form) to work correctly without triggering full validation. 5 // Tolerate null HttpContext for testing. 6 HttpContext currentContext = HttpContext.Current; 7 if (currentContext != null) 8 { 9 bool? isRequestValidationEnabled = ValidationUtility.IsValidationEnabled(currentContext); 10 if (isRequestValidationEnabled == true) 11 { 12 ValidationUtility.EnableDynamicValidation(currentContext); 13 } 14 } 15 16 AddVersionHeader(httpContext); 17 RemoveOptionalRoutingParameters(); 18 19 // Get the controller type 20 string controllerName = RequestContext.RouteData.GetRequiredString("controller"); 21 22 // Instantiate the controller and call Execute 23 factory = ControllerBuilder.GetControllerFactory(); 24 controller = factory.CreateController(RequestContext, controllerName); 25 if (controller == null) 26 { 27 throw new InvalidOperationException( 28 String.Format( 29 CultureInfo.CurrentCulture, 30 MvcResources.ControllerBuilder_FactoryReturnedNull, 31 factory.GetType(), 32 controllerName)); 33 } 34 }
我们看这个方法从20行开始,该方法先获取控制器的名称,然后获取控制器工厂实例(代码23行)。然后使用控制器工厂实例来创建控制器实例。下面我们就来看一下ControllerBuilder来创建控制器工厂实例的过程。
下面我们来看一下GetControllerFactory方法的内部实现,源码很简单,就一行代码:
1 public IControllerFactory GetControllerFactory() 2 { 3 return _serviceResolver.Current; 4 }
很明显,这是读取了_serviceResolver实例的Current属性值,那么我们需要仔细查看下_serviceResolver实例的创建及其Current属性的赋值,通过看源代码,我们看到了以下代码:
1 private static ControllerBuilder _instance = new ControllerBuilder(); 2 private Func<IControllerFactory> _factoryThunk = () => null; 3 private HashSet<string> _namespaces = new HashSet<string>(StringComparer.OrdinalIgnoreCase); 4 private IResolver<IControllerFactory> _serviceResolver; 5 6 public ControllerBuilder() : this(null) { } 7 8 internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver) 9 { 10 _serviceResolver = serviceResolver ?? new SingleServiceResolver<IControllerFactory>( 11 () => _factoryThunk(), 12 new DefaultControllerFactory { ControllerBuilder = this }, 13 "ControllerBuilder.GetControllerFactory"); 14 } 15 16 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Calling method multiple times might return different objects.")] 17 public IControllerFactory GetControllerFactory() 18 { 19 return _serviceResolver.Current; 20 }
我们看到ControllerBuilder类中有一个静态字段 _instance,静态字段是在程序执行前加载的,那么由代码可知,就需要执行该类的构造函数,即执行public ControllerBuilder(): this(null),而其后的: this(null)则表示要去执执行带一个参数的构造函数 ControllerBuilder(IResolver<IControllerFactory> serviceResolver),这个构造函数中首先判断传入的参数是否为空,如果为空的话就实例化一个 SingleServiceResolver<IControllerFactory>对象(暂且理解为封装 ControllerFactory的一个类),并赋值给私有变量_serviceResolver,通过调用_serviceResolver的 Current属性来获取当前封装的ControllerFactory实例。
通过上面的分析,我们知道了大致的流程,下面我们深入SingleServiceResolve内部看看其实例的Current到底是怎么计算的。默认情况下_factoryThunk委托传递的值是null。看源码:
1 internal class SingleServiceResolver<TService> : IResolver<TService> 2 where TService : class 3 { 4 private Lazy<TService> _currentValueFromResolver; 5 private Func<TService> _currentValueThunk; 6 private TService _defaultValue; 7 private Func<IDependencyResolver> _resolverThunk; 8 private string _callerMethodName; 9 10 public SingleServiceResolver(Func<TService> currentValueThunk, TService defaultValue, string callerMethodName) 11 { 12 if (currentValueThunk == null) 13 { 14 throw new ArgumentNullException("currentValueThunk"); 15 } 16 if (defaultValue == null) 17 { 18 throw new ArgumentNullException("defaultValue"); 19 } 20 //默认情况下是DefaultDependencyResolver 21 _resolverThunk = () => DependencyResolver.Current; 22 _currentValueFromResolver = new Lazy<TService>(GetValueFromResolver); 23 _currentValueThunk = currentValueThunk; 24 _defaultValue = defaultValue; 25 _callerMethodName = callerMethodName; 26 } 27 28 internal SingleServiceResolver(Func<TService> staticAccessor, TService defaultValue, IDependencyResolver resolver, string callerMethodName) 29 : this(staticAccessor, defaultValue, callerMethodName) 30 { 31 if (resolver != null) 32 { 33 _resolverThunk = () => resolver; 34 } 35 } 36 37 public TService Current 38 { 39 get { return _currentValueFromResolver.Value ?? _currentValueThunk() ?? _defaultValue; } 40 } 41 42 private TService GetValueFromResolver() 43 { 44 TService result = _resolverThunk().GetService<TService>(); 45 46 if (result != null && _currentValueThunk() != null) 47 { 48 throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.SingleServiceResolver_CannotRegisterTwoInstances, typeof(TService).Name.ToString(), _callerMethodName)); 49 } 50 51 return result; 52 } 53 }
我们看到Current属性的求值涉及到3个过程,看代码的红色部分。下面我们来看这3个步骤。
第一个步骤我们看_currentValueFromResolver.Value的值。我们知道_currentValueFromResolver绑定了GetValueFromResolver方法。该方法内部使用了resolverThunk委托,该委托返回DependencyResolver.Current。我在以上的代码中已经进行了注释。DependencyResolver.Current实际使用的是DefaultDependencyResolver类。使用DefaultDependencyResolver类的GetService方法。我们深入该方法内部发现其逻辑是这样的:
1 public object GetService(Type serviceType) 2 { 3 // Since attempting to create an instance of an interface or an abstract type results in an exception, immediately return null 4 // to improve performance and the debugging experience with first-chance exceptions enabled. 5 if (serviceType.IsInterface || serviceType.IsAbstract) 6 { 7 return null; 8 } 9 10 try 11 { 12 return Activator.CreateInstance(serviceType); 13 } 14 catch 15 { 16 return null; 17 } 18 }
我们看到,如果传递的Type类型是接口或者抽象类的话就返回null。很明显我们传递的serviceType是一个接口。第一步骤返回时null。接下来我们继续分析第二个步骤。
第二个步骤我们看到调用的是_currentValueThunk委托。这个委托默认值是返回null的。当然我们在自定义控制器工厂的时候其实就是重新设置这个委托值。这是MVC框架的一个扩展点。我们来看一下设置自定义控制器工厂内部发生的事,看源代码:
1 public void SetControllerFactory(IControllerFactory controllerFactory) 2 { 3 if (controllerFactory == null) 4 { 5 throw new ArgumentNullException("controllerFactory"); 6 } 7 8 _factoryThunk = () => controllerFactory; 9 }
看到了把,通过SetControllerFactory方法,我们相当于让_factoryThunk委托返回我们自定义的控制器工厂实例。这样在获取Current属性值的时候,第二个步骤就是调用这个委托返回控制器工厂实例,只是默认值是null而已。如果我们设置了自定义的值,就会返回自定义控制器工厂值。
第三个步骤我们看到返回的是_defaultValue的值。很明显MVC框架默认值是DefaultControllerFactory。所以MVC框架默认的控制器工厂是DefaultControllerFactory。到此为止我们已经分析出该开始ProcessRequest中获取的控制器工厂实例就是DefaultControllerFactory的实例了。我们继续往下看。
激活(创建)控制器实例
通过上面的分析我们知道控制器工厂是DefaultControllerFactory,下面我们深入其内部看看控制器实例到底是怎么创建出来的。通过最开始部分代码的逻辑(即ProcessRequest方法),我们知道创建控制器的方法是CreateController方法,那么我们就从这个方法入手。
1 public virtual IController CreateController(RequestContext requestContext, string controllerName) 2 { 3 if (requestContext == null) 4 { 5 throw new ArgumentNullException("requestContext"); 6 } 7 if (String.IsNullOrEmpty(controllerName)) 8 { 9 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName"); 10 } 11 Type controllerType = GetControllerType(requestContext, controllerName); 12 IController controller = GetControllerInstance(requestContext, controllerType); 13 return controller; 14 }
我们看到整个过程主要分成两个部分。第一步:获取控制器类型。第二步:根据控制器类型获取控制器实例。下面我们就分两步来介绍这个过程。
第一步获取控制器类型:
1 protected internal virtual Type GetControllerType(RequestContext requestContext, string controllerName) 2 { 3 if (String.IsNullOrEmpty(controllerName)) 4 { 5 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName"); 6 } 7 8 // first search in the current route's namespace collection 9 object routeNamespacesObj; 10 Type match; 11 if (requestContext != null && requestContext.RouteData.DataTokens.TryGetValue("Namespaces", out routeNamespacesObj)) 12 { 13 IEnumerable<string> routeNamespaces = routeNamespacesObj as IEnumerable<string>; 14 if (routeNamespaces != null && routeNamespaces.Any()) 15 { 16 HashSet<string> namespaceHash = new HashSet<string>(routeNamespaces, StringComparer.OrdinalIgnoreCase); 17 match = GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, namespaceHash); 18 19 // the UseNamespaceFallback key might not exist, in which case its value is implicitly "true" 20 if (match != null || false.Equals(requestContext.RouteData.DataTokens["UseNamespaceFallback"])) 21 { 22 // got a match or the route requested we stop looking 23 return match; 24 } 25 } 26 } 27 28 // then search in the application's default namespace collection 29 if (ControllerBuilder.DefaultNamespaces.Count > 0) 30 { 31 HashSet<string> namespaceDefaults = new HashSet<string>(ControllerBuilder.DefaultNamespaces, StringComparer.OrdinalIgnoreCase); 32 match = GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, namespaceDefaults); 33 if (match != null) 34 { 35 return match; 36 } 37 } 38 39 // if all else fails, search every namespace 40 return GetControllerTypeWithinNamespaces(requestContext.RouteData.Route, controllerName, null /* namespaces */); 41 }
我们看到这段代码的结构很清晰。第一步先在当前路由设置的命名空间中寻找符合条件的控制器。如果找不到就从工程中设置的命名空间中寻找。如果还是找不到就一个一个命名空间的遍历寻找。
第二步根据控制器类型创建其实例
我们接下来看第二个步骤,创建控制器实例,逻辑中也是封装在另一个方法中的(GetControllerInstance)。下面我们来看看其内部实现。
1 /// <summary> 2 /// 通过控制器类型来创建控制器实例 3 /// </summary> 4 /// <param name="requestContext"></param> 5 /// <param name="controllerType"></param> 6 /// <returns></returns> 7 protected internal virtual IController GetControllerInstance(RequestContext requestContext, Type controllerType) 8 { 9 if (controllerType == null) 10 { 11 throw new HttpException(404, 12 String.Format( 13 CultureInfo.CurrentCulture, 14 MvcResources.DefaultControllerFactory_NoControllerFound, 15 requestContext.HttpContext.Request.Path)); 16 } 17 if (!typeof(IController).IsAssignableFrom(controllerType)) 18 { 19 throw new ArgumentException( 20 String.Format( 21 CultureInfo.CurrentCulture, 22 MvcResources.DefaultControllerFactory_TypeDoesNotSubclassControllerBase, 23 controllerType), 24 "controllerType"); 25 } 26 return ControllerActivator.Create(requestContext, controllerType); 27 }
我们看到创建控制器本质上是交给控制器激活器来创建的。看来我们需要好好看看这个控制器激活器是如何工作的了。首先我们应该先找到ControllerActivator的值。我们看到了如下代码:
1 private IControllerActivator ControllerActivator 2 { 3 get 4 { 5 if (_controllerActivator != null) 6 { 7 return _controllerActivator; 8 } 9 _controllerActivator = _activatorResolver.Current; 10 return _controllerActivator; 11 } 12 }
我们接着往下看:
1 public DefaultControllerFactory() 2 : this(null, null, null) 3 { 4 } 5 6 public DefaultControllerFactory(IControllerActivator controllerActivator) 7 : this(controllerActivator, null, null) 8 { 9 } 10 11 internal DefaultControllerFactory(IControllerActivator controllerActivator, IResolver<IControllerActivator> activatorResolver, IDependencyResolver dependencyResolver) 12 { 13 //如果不存在自定义的控制器激活器,就使用默认的DefaultControllerActivator控制器激活器 14 if (controllerActivator != null) 15 { 16 _controllerActivator = controllerActivator; 17 } 18 else 19 { 20 _activatorResolver = activatorResolver ?? new SingleServiceResolver<IControllerActivator>( 21 () => null, 22 new DefaultControllerActivator(dependencyResolver), 23 "DefaultControllerFactory constructor"); 24 } 25 }
我们在这里又看到了熟悉的代码(红色部分),相信大家对于SingleServiceResolver泛型类比较熟悉了。我们看到MVC框架默认的控制器激活器是DefaultControllerActivator。我们接下来来看DefaultControllerActivator类内部逻辑。
1 /// <summary> 2 /// MVC框架默认的自定义控制器激活器 3 /// </summary> 4 private class DefaultControllerActivator : IControllerActivator 5 { 6 private Func<IDependencyResolver> _resolverThunk; 7 8 public DefaultControllerActivator() 9 : this(null) 10 { 11 } 12 13 public DefaultControllerActivator(IDependencyResolver resolver) 14 { 15 if (resolver == null) 16 { 17 //DependencyResolver.Current默认是DefaultDependencyResolver 18 _resolverThunk = () => DependencyResolver.Current; 19 } 20 else 21 { 22 _resolverThunk = () => resolver; 23 } 24 } 25 26 public IController Create(RequestContext requestContext, Type controllerType) 27 { 28 try 29 { 30 return (IController)(_resolverThunk().GetService(controllerType) ?? Activator.CreateInstance(controllerType)); 31 } 32 catch (Exception ex) 33 { 34 throw new InvalidOperationException( 35 String.Format( 36 CultureInfo.CurrentCulture, 37 MvcResources.DefaultControllerFactory_ErrorCreatingController, 38 controllerType), 39 ex); 40 } 41 } 42 }
我们来看激活器的Create方法,控制器的实例就是由这个方法来创建。我们看到控制器的实例就是根据控制器类型来创建的。到此为止,我们终于知道了如何激活控制器实例。下面一篇文章我们还会沿着请求的步骤继续往下探索。