• 通过源代码研究ASP.NET MVC中的Controller和View(一)(转载)


    因为Jumony计划提供ASP.NET MVC的视图模型支持,以取代ASP.NET MVC默认的视图模型(事实上,我觉得很糟糕)。所以,需要先对ASP.NET MVC的视图模型有一个了解。

    ASP.NET MVC是一个很年轻的项目,代码并不多,这很好,但麻烦的是文档和资料也不够多,看来要想了解其视图模型只能靠自己。

    不过幸运的是,MVC模型已经决定了其大体的框架,所以我打算直接用Reflector来看看里面的结构(不直接用源代码的原因仅仅是因为Reflector导航功能实在是太强了,但当我贴代码的时候,贴的是MVC源代码),配合名称和设计思想反析就能够很轻松的了解其架构。

    那么这一次研究的对象是.NET Framework 4中的ASP.NET MVC 2。

    我先来看一下ASP.NET MVC中的类型,那么我很轻松的发现,这两个类型会是我的切入点:

    IView

    IController

    这两个接口再明白不过的告诉了我它们就是View和Controller的抽象。那么他们俩都只有一个方法,所以职责也很容易就能推导出来:

    IView只有一个方法:void Render(ViewContext viewContext, TextWriter writer);

    依据视图上下文(ViewContext)呈现HTML。

    IController也只有一个方法:void Execute(RequestContext requestContext);

    根据请求上下文执行相应的操作。

    简单的来说,视图的职责就是呈现(Render),控制器的职责就是执行(Execute),还真简单,哈。

    下面我想了解一下视图和控制器是怎么联系到一起的,先来看看MVC的范例网站,我发现Controller里面所有的方法的最后都是这样的:

    return View();

    而所有的方法的返回值类型都是ActionResult,通过源代码我发现View方法的返回结果是ViewResult,一个ActionResult的子类:

        protected internal ViewResult View()

    看来先得弄明白这俩东西是干什么的,从名称我不能发现更多的信息,那么先从抽象的ActionResult来研究。

    这个类型只有一个方法:

      public abstract class ActionResult
      {

        public abstract void ExecuteResult( ControllerContext context );

      }

     

    根据控制器上下文来执行结果,这个方法是抽象的,应该是由具体的类型(如ViewResult)来决定到底要执行什么操作了。

    看来这个东西的职责就是执行结果了。

    根据上面的观察可以得到推论:

    Controller里面的方法都需要返回一个ActionResult(猜测?),这个ActionResult负责下一步的执行操作。不过我有点奇怪为什么要把操作放在这个ActionResult而不是直接在方法里面执行,那么看看ActionResult有些什么子类型:

    image

    我不知道您看出来了什么,不过通过子类我已经找到刚才问题的答案了。

    ActionResult执行的操作都是与用户交互相关的,例如JavaScriptResult,又或者是FileResult,当然还有我们的ViewResult。利用ActionResult可以隔离在Controller里面的的业务逻辑和在ActionResult里面的交互逻辑。这是一个很经典的设计。呃,顺带说说我为什么讨厌《设计模式》,你经常会发现一些很经典的设计手法,然后,你没办法从那个天杀的《设计模式》中找到一个很NB的名词来描述(我一会儿思考下这是不是那啥模式),所以。。。。

    为了证实我的观点,来看看ActionResult.Execute方法的那个参数ControllerContext里面有什么。是的,里面有一大堆与用户界面相关的东西,例如HttpContext、RequestContext(尽管我现在还不太清楚这个干什么用的,但单从名称看就知道与业务没关系)、RouteData等。

    那么我们可以得到ActionResult的定义,这是一个用户界面交互动作的抽象,用户界面交互包括,呈现一个视图(ViewResult)、或是执行一段脚本(JavaScriptResult)、或是下载一个文件(FileResult)。如果我们将用户交互全部定义为MVC中的View部分的话,那么ActionResult就是Controller通向View的入口。也就是MVC结构图中Controller指向View的那个箭头。

    或者说,ActionResult的职责就是,改变和呈现View(广义的View,泛指用户界面,非IView实例)。

    或者说我现在可以得到结论,ViewResult的职责就是呈现一个HTML视图(因为还有FileResult、JavaScriptResult,所以ViewResult多半只负责呈现一个动态的HTML视图)?

    所以ActionResult之前的事情在这一次研究中我就不太关心了,因为ActionResult是视图的控制入口,我现在只关心视图模型。

    来看看ViewResult(ViewResultBase)对于ExecuteResult的实现:

        public override void ExecuteResult( ControllerContext context )
        {
          if ( context == null )
          {
            throw new ArgumentNullException( "context" );
          }
          if ( String.IsNullOrEmpty( ViewName ) )
          {
            ViewName = context.RouteData.GetRequiredString( "action" );
          }

          ViewEngineResult result = null;

          if ( View == null )
          {
            result = FindView( context );
            View = result.View;
          }

          TextWriter writer = context.HttpContext.Response.Output;
          ViewContext viewContext = new ViewContext( context, View, ViewData, TempData, writer );
          View.Render( viewContext, writer );

          if ( result != null )
          {
            result.ViewEngine.ReleaseView( context, View );
          }
        }

    这是ViewResultBase的源代码,前面两个if是例行的入口检查,从第三个if开始干活。首先判断自己的View属性(IView类型)是不是为空,为空的话执行FindView方法,得到一个result(类型是ViewEngineResult),再把result.View赋给自己的View属性。我将这些步骤称为查找视图。

    然后创建一个writer和一个ViewContext,调用View的Render方法来呈现视图。最后如果result不为空,则调用ViewEngine的ReleaseView方法。简单的说就是:

    • 查找视图(this.View : IView)
    • 呈现视图(IView.Render)
    • 释放试图(IViewEngine.ReleaseView)

    查找视图的主要方法是 FindView,这是ViewResultBase唯一的一个抽象方法,由子类ViewResult来实现。那么ViewResultBase和ViewResult的分工也很明确了。ViewResult封装了查找视图的逻辑(因为只有这么一个抽象方法),呈现视图和其他工作则是由ViewResultBase来完成。

    看看ViewResult.FindView的实现:

        protected override ViewEngineResult FindView( ControllerContext context )
        {
          ViewEngineResult result = ViewEngineCollection.FindView( context, ViewName, MasterName );
          if ( result.View != null )
          {
            return result;
          }

          // we need to generate an exception containing all the locations we searched
          StringBuilder locationsText = new StringBuilder();
          foreach ( string location in result.SearchedLocations )
          {
            locationsText.AppendLine();
            locationsText.Append( location );
          }
          throw new InvalidOperationException( String.Format( CultureInfo.CurrentUICulture,
              MvcResources.Common_ViewNotFound, ViewName, locationsText ) );
        }

    首先调用了ViewResultBase.ViewEngineCollection.FindView方法。先不管这个方法的实现,我们看到这个方法的返回值result在后面被原封不动的return了。所以,我们得出结论,ViewResult的FindView方法干了什么事:

    1. 调用ViewResultBase.ViewEngineCollection.FindView方法
    2. 如果刚才的结果里面存在一个View(result.View != null),那么返回这个结果。
    3. 否则抛个InvalidOperationException的异常。

    那么这里没有查找视图的逻辑,我需要进一步研究ViewResultBase.ViewEngineCollection.FindView方法。先看看ViewResultBase.ViewEngineCollection这个属性,其值默认就是ViewEngines.Engines:

        public ViewEngineCollection ViewEngineCollection
        {
          get
          {
            return _viewEngineCollection ?? ViewEngines.Engines;
          }
          set
          {
            _viewEngineCollection = value;
          }
        }

    换言之ViewResultBase.ViewEngineCollection.FindView等于ViewEngines.Engines.FindView,多按几下F12找到这个方法的实现:

        public virtual ViewEngineResult FindView( ControllerContext controllerContext, string viewName, string masterName )
        {
          if ( controllerContext == null )
          {
            throw new ArgumentNullException( "controllerContext" );
          }
          if ( string.IsNullOrEmpty( viewName ) )
          {
            throw new ArgumentException( MvcResources.Common_NullOrEmpty, "viewName" );
          }
          Func<IViewEngine, ViewEngineResult> cacheLocator = e => e.FindView( controllerContext, viewName, masterName, true );
          Func<IViewEngine, ViewEngineResult> locator = e => e.FindView( controllerContext, viewName, masterName, false );
          return Find( cacheLocator, locator );
        }
        private ViewEngineResult Find( Func<IViewEngine, ViewEngineResult> cacheLocator, Func<IViewEngine, ViewEngineResult> locator )
        {
          ViewEngineResult result;

          foreach ( IViewEngine engine in Items )
          {
            if ( engine != null )
            {
              result = cacheLocator( engine );

              if ( result.View != null )
              {
                return result;
              }
            }
          }

          List<string> searched = new List<string>();

          foreach ( IViewEngine engine in Items )
          {
            if ( engine != null )
            {
              result = locator( engine );

              if ( result.View != null )
              {
                return result;
              }

              searched.AddRange( result.SearchedLocations );
            }
          }

          return new ViewEngineResult( searched );
        }

    这里的cacheLocator和locator是两个匿名方法,其实就是封装了IViewEngine.FindView的调用,利用闭包把controllerContext、viewName和masterName包了进去。

    这个手法很好玩,但实际上如果把Find方法代入展开就是这样:

        public virtual ViewEngineResult FindView( ControllerContext controllerContext, string viewName, string masterName )
        {
          if ( controllerContext == null )
          {
            throw new ArgumentNullException( "controllerContext" );
          }
          if ( string.IsNullOrEmpty( viewName ) )
          {
            throw new ArgumentException( MvcResources.Common_NullOrEmpty, "viewName" );
          }

          ViewEngineResult result;

          foreach ( IViewEngine engine in Items )
          {
            if ( engine != null )
            {
              result = engine.FindView( controllerContext, viewName, masterName, true );//Func<IViewEngine, ViewEngineResult> cacheLocator = e => e.FindView( controllerContext, viewName, masterName, true );

              if ( result.View != null )
              {
                return result;
              }
            }
          }

          List<string> searched = new List<string>();

          foreach ( IViewEngine engine in Items )
          {
            if ( engine != null )
            {
              result = engine.FindView( controllerContext, viewName, masterName, false );//Func<IViewEngine, ViewEngineResult> locator = e => e.FindView( controllerContext, viewName, masterName, false );

              if ( result.View != null )
              {
                return result;
              }

              searched.AddRange( result.SearchedLocations );
            }
          }

          return new ViewEngineResult( searched );
        }

    方法虽然复杂点,但逻辑很简单,首先遍历Items里面所有的IViewEngine,调用FindView方法(最后一个参数为true),如果有任何一个IViewEngine返回的结果的View不是null,就返回。

    如果遍历了一次没有找到任何View,那么就进行二次遍历(最后一个参数为false),同样的,一旦发现有一个IViewEngine返回的结果存在有View,就返回。两次遍历都没结果的话,就返回一个new ViewEngineResult( searched )。searched是已经查找过的位置(根据result.SearchedLocations这个名字直接就知道了)。

    最后查查MSDN可知,ViewEngines.Engines是所有可用的视图引擎(IViewEngine的实例)的集合,这个Items是其基类Collection<IViewEngine>的属性,简单的说就是一个容器,存放所有视图引擎。

    结合之前的结论可以得到ViewResult的ExecuteResult全部执行过程:

    • 查找视图(IView)
      • 调用ViewResultBase.ViewEngineCollection.FindView
        • 调用ViewEngines.Engines.FindView
          1. 遍历所有的视图引擎(IViewEngine实例),调用FindView方法(最后一个参数为true)
          2. 若找到任何一个视图引擎返回的结果中View不为null,则返回。
          3. 第二次遍历,最后一个参数用false来调用FindView
          4. 若找到View则返回
          5. 否则返回一个没有View的ViewEngineResult,在ViewResult.FindView中将会抛出异常。
    • 呈现视图(IView.Render)
    • 释放试图(IViewEngine.ReleaseView)

    最后来看看IViewEngine接口长啥样:

      public interface IViewEngine
      {
        ViewEngineResult FindPartialView( ControllerContext controllerContext, string partialViewName, bool useCache );
        ViewEngineResult FindView( ControllerContext controllerContext, string viewName, string masterName, bool useCache );
        void ReleaseView( ControllerContext controllerContext, IView view );
      }

    我们发现这个IViewEngine的实例主要就干两件事情:FindView和ReleaseView。

    综合上面的结论,我们可以搞清楚ViewResult、IView和IViewEngine的分工和职责了:

    ViewResult:提供ActionResult的实现,查找视图(FindView)并且呈现它(IView.Render),最后释放(IViewEngine.Release)。很明显,ViewResult是视图(IView)的使用者。几乎也是唯一的使用者:

    image

    IViewEngine:提供查找视图(FindView)和释放视图(ReleaseView)的功能,从FindView的最后一个参数(useCache)来看,它还可以缓存视图实例以便下次使用。所以,他是视图(IView)的管理者。

    IView:负责呈现HTML视图。

  • 相关阅读:
    【Spring__ApplicationContext】SpringContextHolder
    【Js__ztree】ztree使用
    【Docker__镜像】自定义镜像
    【Docker__容器命令】
    【Docker__安装】
    【Redis__安装】Redis简单安装和使用
    【Redis__集群】Redis集群的搭建
    RCWL0801激光测距传感器模块驱动(基于传感器管理组件)
    RCWL0516微波雷达传感器(基于传感器管理组件)
    热敏电阻温度传感器(基于传感器管理组件)
  • 原文地址:https://www.cnblogs.com/zhwl/p/1989241.html
Copyright © 2020-2023  润新知