关于最近学习MVC的一些感触
一个好的程序员,每次在面对一些新知识的时候,他们总会在想,如何去抓住深层次的东西。今天我们就来研究研究MVC的流程。
因为在使用ASP.NET MVC开发的时候,我们首先接触到的是路由,亦传说中的 System.Web.Routing.RouteTable,Reflection该类的代码如下:
private static RouteCollection _instance = new RouteCollection(); public static RouteCollection Routes { [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get { return RouteTable._instance; } }
好吧,这么的单纯简单,那就去看看RouteCollection的代码吧,发现该类中并无常用的那个MapRoute方法,原来该方法是MVC框架扩展的,具体代码位于MVC项目源码中的System.Web.Mvc.RouteCollectionExtensions 类,下面是其代码:
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
{ if (routes == null) { throw new ArgumentNullException("routes"); } if (url == null) { throw new ArgumentNullException("url"); } Route route = new Route(url, new MvcRouteHandler()) { Defaults = new RouteValueDictionary(defaults), Constraints = new RouteValueDictionary(constraints), DataTokens = new RouteValueDictionary() }; if ((namespaces != null) && (namespaces.Length > 0)) { route.DataTokens["Namespaces"] = namespaces; } routes.Add(name, route); return route; }
以上代码虽简单但有两处重点:
- 创建了一个Route对象,在其构造函数中传入了MvcRouteHandler对象;
- 设置了新建Route对象的Default、Constraints、DataTokens这三个RouteValueDictionary类型的属性,其中默认值(Defaults)和约束(Constraints)是通过object的对象进行初始化的,关于RouteValueDictionary类型是以string为键object为值的字典,具体代码这里就不贴了,大家可以反射看看。
我们只要看看RouteValueDictionary这个构造函数以及私有的AddValues方法即可:
public RouteValueDictionary(object values) { this._dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); this.AddValues(values); }
private void AddValues(object values) { if (values != null) { PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(values); foreach (PropertyDescriptor propertyDescriptor in properties) { object value = propertyDescriptor.GetValue(values); this.Add(propertyDescriptor.Name, value); } } }
上面的AddValues方法利用了TypeDescriptor来获取values参数指定的任意对象的公共属性集,并将这些属性值以名称/值的方式加入到内部字典中,这对于使用匿名类进行设置Route对象中的相关属性非常方便。在MapRoute方法中,使用的是MvcRouteHandler的默认构造函数来创建它的实例,那么就顺藤摸瓜吧:
public class MvcRouteHandler : IRouteHandler { private IControllerFactory _controllerFactory; public MvcRouteHandler() { } public MvcRouteHandler(IControllerFactory controllerFactory) { _controllerFactory = controllerFactory; } protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) { requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext)); return new MvcHandler(requestContext); } protected virtual SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext) { string controllerName = (string)requestContext.RouteData.Values["controller"]; IControllerFactory controllerFactory = _controllerFactory ?? ControllerBuilder.Current.GetControllerFactory(); return controllerFactory.GetControllerSessionBehavior(requestContext, controllerName); } #region IRouteHandler Members IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) { return GetHttpHandler(requestContext); } #endregion }
在GetHttpHandler方法最后返回了一个新建的MvcHandler对象,这个类先按着不表。先看看关于控制器工厂的问题,由于默认构造函数保留了_controllerFactory成员变量为空(null),因此GetSessionStateBehavior方法中是通过ControllerBuilder来获取控制器工厂对象的,且看看它是如何获取的:
public class ControllerBuilder { private Func<IControllerFactory> _factoryThunk = () => null; private static ControllerBuilder _instance = new ControllerBuilder(); private HashSet<string> _namespaces = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private IResolver<IControllerFactory> _serviceResolver; public ControllerBuilder() : this(null) { } internal ControllerBuilder(IResolver<IControllerFactory> serviceResolver) { _serviceResolver = serviceResolver ?? new SingleServiceResolver<IControllerFactory>( () => _factoryThunk(), new DefaultControllerFactory { ControllerBuilder = this }, "ControllerBuilder.GetControllerFactory" ); } public static ControllerBuilder Current { get { return _instance; } } public HashSet<string> DefaultNamespaces { get { return _namespaces; } } public IControllerFactory GetControllerFactory() { return _serviceResolver.Current; } public void SetControllerFactory(IControllerFactory controllerFactory) { if(controllerFactory == null) { throw new ArgumentNullException("controllerFactory"); } _factoryThunk = () => controllerFactory; } public void SetControllerFactory(Type controllerFactoryType) { if(controllerFactoryType == null) { throw new ArgumentNullException("controllerFactoryType"); } if(!typeof(IControllerFactory).IsAssignableFrom(controllerFactoryType)) { throw new ArgumentException( String.Format( CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_MissingIControllerFactory, controllerFactoryType), "controllerFactoryType"); } _factoryThunk = delegate() { try { return (IControllerFactory)Activator.CreateInstance(controllerFactoryType); } catch(Exception ex) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_ErrorCreatingControllerFactory, controllerFactoryType), ex); } }; } }
大致看看,这个类主要就是用来获取IControllerFactory对象的,其中GetControllerFactory方法很简单,通过内部的IResolver<IControllerFactory>来解析得到想要的IControllerFactory服务,其中两个重载的SetControllerFactory注入方法的实现比较简单,只是刚开始有点奇怪为什么不直接通过将注入的IControllerFactory类型的controllerFactory参数保存起来作为GetControllerFactory的返回之用,还非得绕个弯弯通过_factoryThunk 这个委托进行返回?没事,看到后面就知道为什么了,据说这也是MVC 3.0中为了扩展性加入的新处理方式(至于听谁说的…)。既然ControllerBuilder把获取IControllerFactory的职责都交给了默认的SingleServiceResolver类,那么就看看它是咋么一回事吧!
internal class SingleServiceResolver<TService> : IResolver<TService> where TService : class { private TService _currentValueFromResolver; private Func<TService> _currentValueThunk; private TService _defaultValue; private Func<IDependencyResolver> _resolverThunk; private string _callerMethodName; public SingleServiceResolver(Func<TService> currentValueThunk, TService defaultValue, string callerMethodName) { if(currentValueThunk == null) throw new ArgumentNullException("currentValueThunk"); if(defaultValue == null) throw new ArgumentNullException("defaultValue"); _resolverThunk = () => DependencyResolver.Current; _currentValueThunk = currentValueThunk; _defaultValue = defaultValue; _callerMethodName = callerMethodName; } public TService Current { get { if(_resolverThunk != null) { lock(_currentValueThunk) { if(_resolverThunk != null) { _currentValueFromResolver = _resolverThunk().GetService<TService>(); _resolverThunk = null; if(_currentValueFromResolver != null && _currentValueThunk() != null) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.SingleServiceResolver_CannotRegisterTwoInstances, typeof(TService).Name.ToString(), _callerMethodName)); } } } } return _currentValueFromResolver ?? _currentValueThunk() ?? _defaultValue; } } }
在SingleServiceResolver类的Current属性中,首先是判断_resolverThunk成员变量是否为空,而这个变量是返回IDependencyResolver接口的一个委托,在构造函数中可以看到它指向DependencyResolver类的静态属性Current(这个东东先且放放),紧接着就是标准的双检锁的处理,具体工作代码是通过这个委托获取到的IDependcencyResolver的GetService方法来获取IControllerFactory,获取到之后马上将该委托置空,然后判断获取到的控制器工厂对象是否不为空并且通过构造函数传入的那个“绕弯弯”的返回IControllerFactory的委托返回的控制器工厂是否也不为空,如果都不为空则表示获取方式有冲突,变抛出一个无效操作异常,该异常表示不能注册两种获取服务实例的方式。
最后一行是返回代码,这里有蹊跷,它表明了对几种获取服务方式的优先级:
- 优先使用IDependencyResolver的GetService方法的结果;
- 其次使用构造函数传入的Func<IControllerFactory>类型参数的委托返回结果;
- 最后才使用构造函数传入的具体的IControllerFactory类型的控制器工厂对象。
看到这里,是不是有些眼熟?貌似跟很多IoC框架中的那个ServiceLocator有点神似呢?不扯这个了,既然弄清楚了优先级别,那么根据构造函数的代码,这个优先级别最高的依赖解析器(IDependencyResolver)指向了DependencyResolver的Current静态属性,那么就去围观下它吧:
public class DependencyResolver { private static DependencyResolver _instance = new DependencyResolver(); public static IDependencyResolver Current { get { return _instance.InnerCurrent; } } private IDependencyResolver _current = new DefaultDependencyResolver(); public IDependencyResolver InnerCurrent { get { return _current; } } public void InnerSetResolver(IDependencyResolver resolver) { if(resolver == null) throw new ArgumentNullException("resolver"); _current = resolver; } public void InnerSetResolver(object commonServiceLocator) { if(commonServiceLocator == null) throw new ArgumentNullException("commonServiceLocator"); Type locatorType = commonServiceLocator.GetType(); MethodInfo getInstance = locatorType.GetMethod("GetInstance", new[] { typeof(Type) }); MethodInfo getInstances = locatorType.GetMethod("GetAllInstances", new[] { typeof(Type) }); if(getInstance == null || getInstance.ReturnType != typeof(object) || getInstances == null || getInstances.ReturnType != typeof(IEnumerable<object>)) { throw new ArgumentException( String.Format( CultureInfo.CurrentCulture, MvcResources.DependencyResolver_DoesNotImplementICommonServiceLocator, locatorType.FullName ), "commonServiceLocator" ); } var getService = (Func<Type, object>)Delegate.CreateDelegate(typeof(Func<Type, object>), commonServiceLocator, getInstance); var getServices = (Func<Type, IEnumerable<object>>)Delegate.CreateDelegate(typeof(Func<Type, IEnumerable<object>>), commonServiceLocator, getInstances); _current = new DelegateBasedDependencyResolver(getService, getServices); } public void InnerSetResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices) { if(getService == null) throw new ArgumentNullException("getService"); if(getServices == null) throw new ArgumentNullException("getServices"); _current = new DelegateBasedDependencyResolver(getService, getServices); }
private class DelegateBasedDependencyResolver : IDependencyResolver { Func<Type, object> _getService; Func<Type, IEnumerable<object>> _getServices; public DelegateBasedDependencyResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices) { _getService = getService; _getServices = getServices; } public object GetService(Type type) { try { return _getService.Invoke(type); } catch { return null; } } public IEnumerable<object> GetServices(Type type) { return _getServices(type); } }
private class DefaultDependencyResolver : IDependencyResolver { public object GetService(Type serviceType) { try { return Activator.CreateInstance(serviceType); } catch { return null; } } public IEnumerable<object> GetServices(Type serviceType) { return Enumerable.Empty<object>(); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架