开发者社区> zting科技> 正文

白话学习MVC(十)View的呈现二

简介:
+关注继续查看

本节将接着《白话学习MVC(九)View的呈现一》来继续对ViewResult的详细执行过程进行分析!

9、ViewResult

  ViewResult将视图页的内容响应给客户端!

  由于ViewResult的View呈现过程比较复杂,在此先大致描述一下整个流程:

  • 获取视图引擎,默认有两个:ASPX引擎、Razor引擎。
  • 根据视图页名称,通过视图引擎去检查是否存在对应的视图页,如果存在,则创建视图对象。如果不存在,则将所有视图引擎寻找过的路径作为异常返回。
  • 创建视图对象之后,处理视图页中的内容(先处理_ViewStart.cshtml,之后再处理相应的试图页)。例如:TempData、Html.XXX等。
  • 视图页内容处理完毕之后,就将视图内容作为响应返回给客户端。

注:由于对于ASP.NET MVC来说,xxx.cshtml文件称为视图,而对于视图引擎来说,实现IView接口的类称为视图,为了避免混淆,有如下定义:
【视图引擎】泛指那些实现了IViewEngine接口的类,如:WebFormViewEngine、RazorViewEngine。
【视图】泛指那些实现了IView接口的类,如:WebViewPage、RazorView。
【视图页】例如:xxx.cshtml文件或xxx.aspx文件。
【视图页的名称】指视图页的文件名。

对于ViewResult,它有一个父类ViewResultBase,其中定义着一些必要的属性和ExecuteResult方法!

复制代码
public abstract class ViewResultBase : ActionResult
{
    private DynamicViewDataDictionary _dynamicViewData;
    private TempDataDictionary _tempData;
    private ViewDataDictionary _viewData;
    private ViewEngineCollection _viewEngineCollection;
    private string _viewName;
 
    public object Model
    {
        get { return ViewData.Model; }
    }
 
    public TempDataDictionary TempData
    {
        get
        {
            if (_tempData == null)
            {
                _tempData = new TempDataDictionary();
            }
            return _tempData;
        }
        set { _tempData = value; }
    }
 
    public IView View { get; set; }
 
    public dynamic ViewBag
    {
        get
        {
            if (_dynamicViewData == null)
            {
                _dynamicViewData = new DynamicViewDataDictionary(() => ViewData);
            }
            return _dynamicViewData;
        }
    }
    public ViewDataDictionary ViewData
    {
        get
        {
            if (_viewData == null)
            {
                _viewData = new ViewDataDictionary();
            }
            return _viewData;
        }
        set { _viewData = value; }
    }
 
    //获取或设置视图引擎,ASP.NET有两个视图引擎,分别是:WebFormViewEngine、RazorViewEngine。
    public ViewEngineCollection ViewEngineCollection
    {
        get { return _viewEngineCollection ?? ViewEngines.Engines; }
        set { _viewEngineCollection = value; }
    }
 
    public string ViewName
    {
        get { return _viewName ?? String.Empty; }
        set { _viewName = value; }
    }
 
    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        //如果没有指定视图页名称将当前Action作为视图页的名称
        if (String.IsNullOrEmpty(ViewName))
        {
            ViewName = context.RouteData.GetRequiredString("action");
        }
 
        ViewEngineResult result = null;
 
        if (View == null)
        {
            //通过视图引擎去创建视图对象(实现了IView接口的类)
            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);
        }
    }
复制代码
复制代码
public class ViewResult : ViewResultBase
{
    private string _masterName;
  
    public string MasterName
    {
        get { return _masterName ?? String.Empty; }
        set { _masterName = value; }
    }
  
    protected override ViewEngineResult FindView(ControllerContext context)
    {
          //遍历执行视图引擎集合中每个视图引擎的FindView方法,如果检查到存在指定的视图页(.cshtml),则创建视图对象(实现IView接口),否则不创建视图对象。
        //此处ViewEngineCollection是ViewResultBase类中的一个属性,表示视图引擎集合。
        ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName);
       //如果创建了视图对象,即:指定路径中存在相匹配的视图页(.cshtml文件)。
        if (result.View != null)
        {
            return result;
        }
       //没有创建视图对象,即:指定路径中不存在相匹配的视图页(.cshtml文件)。
        StringBuilder locationsText = new StringBuilder();
        foreach (string location in result.SearchedLocations)
        {
            locationsText.AppendLine();
            locationsText.Append(location);
        }
        throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
                                                          MvcResources.Common_ViewNotFound, ViewName, locationsText));
    }
}
复制代码

下面就来以ExecuteResult方法为入口,并根据以上ViewResult及继承的成员来逐步分析下执行流程:

 当创建ViewResult对象时,如果没有指定【视图页的名称】,则从路由中获取Action的名称并作为【视图页的名称】。

复制代码
public abstract class ViewResultBase : ActionResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        //如果没有指定视图页名称将当前Action作为视图页的名称
        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);
        }
    }
}
复制代码

② 通过视图引擎去创建视图对象。(视图对象用于处理视图页(.cshtml文件)的内容)

  大致过程:通过视图引擎,在指定的目录下({controller}/{action})寻找与上一步中的【视图页的名称】相匹配的视图页,如果能找到,则创建视图对象(实现IView接口的类),该对象会在之后的步骤3时,执行Render方法来处理视图页的内容,例如:TempData、Html.xxx(...)等。否则,不创建视图对象,而是将视图引擎所有寻找的路径通过异常输出。

复制代码
public abstract class ViewResultBase : ActionResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        //如果没有指定视图页的名称将当前Action作为视图页的名称
        if (String.IsNullOrEmpty(ViewName))
        {
            ViewName = context.RouteData.GetRequiredString("action");
        }
      ViewEngineResult result = null;
        if (View == null)
        {
            //通过视图引擎去创建视图对象,并将视图对象和该视图相关的信息封装在ViewEngineResult对象中。
            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);
        }
    }
}
复制代码
复制代码
public class ViewResult : ViewResultBase
{
    private string _masterName;
 
    public string MasterName
    {
        get { return _masterName ?? String.Empty; }
        set { _masterName = value; }
    }
 
    protected override ViewEngineResult FindView(ControllerContext context)
    {
        //遍历执行视图引擎集合中每个视图引擎的FindView方法,如果检查到存在指定的视图页(.cshtml),则创建视图对象(实现IView接口),否则不创建视图对象。
        //此处ViewEngineCollection是ViewResultBase类中的一个属性,表示视图引擎集合。
        ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName);
        //如果创建了视图对象,即:指定路径中存在相匹配的视图页(.cshtml文件)。
        if (result.View != null)
        {
            return result;
        }
        //没有创建视图对象,即:指定路径中不存在相匹配的视图页(.cshtml文件)。
        StringBuilder locationsText = new StringBuilder();
        foreach (string location in result.SearchedLocations)
        {
            locationsText.AppendLine();
            locationsText.Append(location);
        }
        throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
                                                          MvcResources.Common_ViewNotFound, ViewName, locationsText));
    }
}
复制代码

  创建视图对象的任务实现在ViewResult了的FindView方法中,该方法的返回值类型为ViewEngineResult!由此可知,ViewEngineCollection.FindView(context,ViewName,MasterName)就是创建视图对象的切入点,此处的这个ViewEngineCollection其实是ViewResultBase类中的一个属性,该属性的返回值是ViewEngineCollection类型的,表示视图引擎集合,在没有设置视图引擎集合的情况下,默认该视图引擎集合中只有 ASPX引擎和Razor引擎。此处的这个FindView方法其实就是遍历各个视图引擎,并让每个视图引擎都可以按照自己定义的方式去检查是否存在指定的视图页,从而来决定是否创建视图对象。上代码!!!

复制代码
public abstract class ViewResultBase : ActionResult
{
    private ViewEngineCollection _viewEngineCollection;
    
    //获取或设置视图引擎集合,默认情况下该集合两个视图引擎,分别是:WebFormViewEngine、RazorViewEngine。
    public ViewEngineCollection ViewEngineCollection
    {
        get { return _viewEngineCollection ?? ViewEngines.Engines; }
        set { _viewEngineCollection = value; }
    }
}

public static class ViewEngines
{
    //初始化Collection<T>,执行Collection<T>索引器将视图引擎实例放入集合中
    //同时执行ViewEngineCollection无参数的构造函数,将视图引擎也保存在其变量中,用于之后执行FineView方法遍历视图引擎去检查指定路径中否存在想匹配的视图页(.cshtml)
    private static readonly ViewEngineCollection _engines = new ViewEngineCollection
    {
        new WebFormViewEngine(),
        new RazorViewEngine(),
    };

    public static ViewEngineCollection Engines
    {
        get { return _engines; }
    }
}

public class ViewEngineCollection : Collection<IViewEngine>
{
    private IResolver<IEnumerable<IViewEngine>> _serviceResolver;

    public ViewEngineCollection()
    {
        //将所有视图引擎保存在变量_serviceResolver中,也是视图引擎进行缓存的处理!
        _serviceResolver = new MultiServiceResolver<IViewEngine>(() => Items);
    }
}
复制代码
复制代码
public class ViewEngineCollection : Collection<IViewEngine>
{
    private IResolver<IEnumerable<IViewEngine>> _serviceResolver;

    public ViewEngineCollection()
    {
        //将所有视图引擎保存在变量_serviceResolver中,也是视图引擎进行缓存的处理!
        _serviceResolver = new MultiServiceResolver<IViewEngine>(() => Items);
    }

    public ViewEngineCollection(IList<IViewEngine> list)
        : base(list)
    {
        _serviceResolver = new MultiServiceResolver<IViewEngine>(() => Items);
    }

    internal ViewEngineCollection(IResolver<IEnumerable<IViewEngine>> serviceResolver, params IViewEngine[] engines)
        : base(engines)
    {
        _serviceResolver = serviceResolver ?? new MultiServiceResolver<IViewEngine>(() => Items);
    }

    //视图引擎集合
    private IEnumerable<IViewEngine> CombinedItems
    {
        get { return _serviceResolver.Current; }
    }

    protected override void InsertItem(int index, IViewEngine item)
    {
        if (item == null)
        {
            throw new ArgumentNullException("item");
        }
        base.InsertItem(index, item);
    }

    protected override void SetItem(int index, IViewEngine item)
    {
        if (item == null)
        {
            throw new ArgumentNullException("item");
        }
        base.SetItem(index, item);
    }
    
    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");
        }
        //执行Find方法,该方法有两个委托类型的参数。
        //每个委托类型都是以 视图引擎 为参数,并执行视图引擎的FindView方法。
        return Find(e => e.FindView(controllerContext, viewName, masterName, true),
                    e => e.FindView(controllerContext, viewName, masterName, false));
    }
    //参数是泛型委托
    private ViewEngineResult Find(Func<IViewEngine, ViewEngineResult> cacheLocator, Func<IViewEngine, ViewEngineResult> locator)
    {
        return Find(cacheLocator, trackSearchedPaths: false)
               ?? Find(locator, trackSearchedPaths: true);
    }
    //trackSearchedPaths表示是否记录寻找路径
    private ViewEngineResult Find(Func<IViewEngine, ViewEngineResult> lookup, bool trackSearchedPaths)
    {
        // Returns
        //    1st result
        // OR list of searched paths (if trackSearchedPaths == true)
        // OR null
        ViewEngineResult result;

        List<string> searched = null;
        if (trackSearchedPaths)
        {
            searched = new List<string>();
        }
        //迭代视图引擎,执行委托也就是执行每个视图引擎的FindView方法去检查在指定的路径中是否存在想匹配的视图页(.cshtml文件)。
        //如果存在,则创建视图对象(实现IView接口)。
        foreach (IViewEngine engine in CombinedItems)
        {
            if (engine != null)
            {
                result = lookup(engine);

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

                if (trackSearchedPaths)
                {
                    searched.AddRange(result.SearchedLocations);
                }
            }
        }

        if (trackSearchedPaths)
        {
            // Remove duplicate search paths since multiple view engines could have potentially looked at the same path
            return new ViewEngineResult(searched.Distinct().ToList());
        }
        else
        {
            return null;
        }
    }

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

        return Find(e => e.FindPartialView(controllerContext, partialViewName, true),
                    e => e.FindPartialView(controllerContext, partialViewName, false));
    }
}
复制代码

  在FindView中,调用了参数为两个泛型委托 的Find方法。泛型委托的参数是IViewEngine(视图引擎),其内容是实行当前参数(视图引擎)的FindView方法,正式因为泛型委托,所以在之后遍历视图引擎集合中的每个视图引擎,并将视图引擎作为参数去执行这个泛型委托,从而实现调用每个视图引擎的FindView方法去寻找指定的视图!上述<FindView方法的执行代码>中可以看出,最终实现 遍历并执行泛型委托 落点在Find(Func<IViewEngine, ViewEngineResult> lookup, bool trackSearchedPaths)中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//trackSearchedPaths表示是否记录寻找路径,从而实现当没有寻找到View时,将所有的寻找路径通过异常返回。
private ViewEngineResult Find(Func<IViewEngine, ViewEngineResult> lookup, bool trackSearchedPaths)
{
    ViewEngineResult result;
  
    List<string> searched = null;
    if (trackSearchedPaths)
    {
        searched = new List<string>();
    }
    //迭代视图引擎,执行委托也就是执行每个视图引擎的FindView方法去检查指定路径是否存在相匹配的视图页,并在内部决定是否创建视图对象!
    foreach (IViewEngine engine in CombinedItems)
    {
        if (engine != null)
        {
            result = lookup(engine);//重要:执行委托,调用视图引擎的FindView方法!
  
            if (result.View != null)
            {
                return result;
            }
  
            if (trackSearchedPaths)
            {  //检查到没有相匹配的视图页,那么就将视图引擎寻找的所有路径添加到集合。
                searched.AddRange(result.SearchedLocations);
            }
        }
    }
  
    if (trackSearchedPaths)
    {
        //将所有视图引擎寻找的所有路径返回,
        return new ViewEngineResult(searched.Distinct().ToList());
    }
    else
    {
        return null;
    }
}

  上述代码中,result=lookup(engine)就是执行视图引擎的FindView方法,而默认情况下的视图引擎有:.ASPX引擎(WebFormViewEngine)、Razor引擎(RazorViewEngine),视图引擎的FindView方法定义在其父类VirtualPathProviderViewEngine中,FindView方法内部会调用CreateView、CreatePartialView等方法,从而使不同的视图引擎能按照自己的方式来进行检查是否存在指定的视图页。对于视图引擎,其相关的类和接口的关系如下图:

下面是Razor引擎(RazorViewEngine)的详细执行过程:

复制代码
public abstract class VirtualPathProviderViewEngine : IViewEngine
{
    // format is ":ViewCacheEntry:{cacheType}:{prefix}:{name}:{controllerName}:{areaName}:"
    private const string CacheKeyFormat = ":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}:";
    private const string CacheKeyPrefixMaster = "Master";
    private const string CacheKeyPrefixPartial = "Partial";
    private const string CacheKeyPrefixView = "View";
    private static readonly string[] _emptyLocations = new string[0];
    private DisplayModeProvider _displayModeProvider;

    private VirtualPathProvider _vpp;
    internal Func<string, string> GetExtensionThunk = VirtualPathUtility.GetExtension;

    protected VirtualPathProviderViewEngine()
    {
        if (HttpContext.Current == null || HttpContext.Current.IsDebuggingEnabled)
        {
            ViewLocationCache = DefaultViewLocationCache.Null;
        }
        else
        {
            ViewLocationCache = new DefaultViewLocationCache();
        }
    }

    [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")]
    public string[] AreaMasterLocationFormats { get; set; }

    [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")]
    public string[] AreaPartialViewLocationFormats { get; set; }

    [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")]
    public string[] AreaViewLocationFormats { get; set; }

    [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")]
    public string[] FileExtensions { get; set; }

    [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")]
    public string[] MasterLocationFormats { get; set; }

    [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")]
    public string[] PartialViewLocationFormats { get; set; }

    public IViewLocationCache ViewLocationCache { get; set; }

    [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "This is a shipped API")]
    public string[] ViewLocationFormats { get; set; }

    protected VirtualPathProvider VirtualPathProvider
    {
        get
        {
            if (_vpp == null)
            {
                _vpp = HostingEnvironment.VirtualPathProvider;
            }
            return _vpp;
        }
        set { _vpp = value; }
    }

    protected internal DisplayModeProvider DisplayModeProvider
    {
        get { return _displayModeProvider ?? DisplayModeProvider.Instance; }
        set { _displayModeProvider = value; }
    }

    private string CreateCacheKey(string prefix, string name, string controllerName, string areaName)
    {
        return String.Format(CultureInfo.InvariantCulture, CacheKeyFormat,
                             GetType().AssemblyQualifiedName, prefix, name, controllerName, areaName);
    }

    internal static string AppendDisplayModeToCacheKey(string cacheKey, string displayMode)
    {
        // key format is ":ViewCacheEntry:{cacheType}:{prefix}:{name}:{controllerName}:{areaName}:"
        // so append "{displayMode}:" to the key
        return cacheKey + displayMode + ":";
    }

    protected abstract IView CreatePartialView(ControllerContext controllerContext, string partialPath);

    protected abstract IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath);

    protected virtual bool FileExists(ControllerContext controllerContext, string virtualPath)
    {
        return VirtualPathProvider.FileExists(virtualPath);
    }

    public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (String.IsNullOrEmpty(partialViewName))
        {
            throw new ArgumentException(MvcResources.Common_NullOrEmpty, "partialViewName");
        }

        string[] searched;
        string controllerName = controllerContext.RouteData.GetRequiredString("controller");
        string partialPath = GetPath(controllerContext, PartialViewLocationFormats, AreaPartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, controllerName, CacheKeyPrefixPartial, useCache, out searched);

        if (String.IsNullOrEmpty(partialPath))
        {
            return new ViewEngineResult(searched);
        }

        return new ViewEngineResult(CreatePartialView(controllerContext, partialPath), this);
    }
    
    public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (String.IsNullOrEmpty(viewName))
        {
            throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
        }

        string[] viewLocationsSearched;
        string[] masterLocationsSearched;
        
        string controllerName = controllerContext.RouteData.GetRequiredString("controller");
        //根据定义的路径去检查是否存在想匹配的视图页(.cshtml)。
        string viewPath = GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, "ViewLocationFormats", viewName, controllerName, CacheKeyPrefixView, useCache, out viewLocationsSearched);
        string masterPath = GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, "MasterLocationFormats", masterName, controllerName, CacheKeyPrefixMaster, useCache, out masterLocationsSearched);
        //检查到不存在相匹配的视图页(.cshtml文件)
        if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName)))
        {
            //将检查过的所有路径封装到ViewEngineResult对象中,以便之后通过异常的方式输出。
            return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
        }
        //检测存在相匹配的视图页,创建视图对象(实现IView接口的类)。
        return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
    }

    private string GetPath(ControllerContext controllerContext, string[] locations, string[] areaLocations, string locationsPropertyName, string name, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
    {
        searchedLocations = _emptyLocations;

        if (String.IsNullOrEmpty(name))
        {
            return String.Empty;
        }

        string areaName = AreaHelpers.GetAreaName(controllerContext.RouteData);
        bool usingAreas = !String.IsNullOrEmpty(areaName);
        List<ViewLocation> viewLocations = GetViewLocations(locations, (usingAreas) ? areaLocations : null);

        if (viewLocations.Count == 0)
        {
            throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
                                                              MvcResources.Common_PropertyCannotBeNullOrEmpty, locationsPropertyName));
        }

        bool nameRepresentsPath = IsSpecificPath(name);
        string cacheKey = CreateCacheKey(cacheKeyPrefix, name, (nameRepresentsPath) ? String.Empty : controllerName, areaName);

        if (useCache)
        {
            // Only look at cached display modes that can handle the context.
            IEnumerable<IDisplayMode> possibleDisplayModes = DisplayModeProvider.GetAvailableDisplayModesForContext(controllerContext.HttpContext, controllerContext.DisplayMode);
            foreach (IDisplayMode displayMode in possibleDisplayModes)
            {
                string cachedLocation = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayMode.DisplayModeId));

                if (cachedLocation != null)
                {
                    if (controllerContext.DisplayMode == null)
                    {
                        controllerContext.DisplayMode = displayMode;
                    }

                    return cachedLocation;
                }
            }

            // GetPath is called again without using the cache.
            return null;
        }
        else
        {
            return nameRepresentsPath
                ? GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations)
                : GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, cacheKey, ref searchedLocations);
        }
    }

    private string GetPathFromGeneralName(ControllerContext controllerContext, List<ViewLocation> locations, string name, string controllerName, string areaName, string cacheKey, ref string[] searchedLocations)
    {
        string result = String.Empty;
        searchedLocations = new string[locations.Count];

        for (int i = 0; i < locations.Count; i++)
        {
            ViewLocation location = locations[i];
            string virtualPath = location.Format(name, controllerName, areaName);
            DisplayInfo virtualPathDisplayInfo = DisplayModeProvider.GetDisplayInfoForVirtualPath(virtualPath, controllerContext.HttpContext, path => FileExists(controllerContext, path), controllerContext.DisplayMode);

            if (virtualPathDisplayInfo != null)
            {
                string resolvedVirtualPath = virtualPathDisplayInfo.FilePath;

                searchedLocations = _emptyLocations;
                result = resolvedVirtualPath;
                ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, virtualPathDisplayInfo.DisplayMode.DisplayModeId), result);

                if (controllerContext.DisplayMode == null)
                {
                    controllerContext.DisplayMode = virtualPathDisplayInfo.DisplayMode;
                }

                // Populate the cache with the existing paths returned by all display modes.
                // Since we currently don't keep track of cache misses, if we cache view.aspx on a request from a standard browser
                // we don't want a cache hit for view.aspx from a mobile browser so we populate the cache with view.Mobile.aspx.
                IEnumerable<IDisplayMode> allDisplayModes = DisplayModeProvider.Modes;
                foreach (IDisplayMode displayMode in allDisplayModes)
                {
                    if (displayMode.DisplayModeId != virtualPathDisplayInfo.DisplayMode.DisplayModeId)
                    {
                        DisplayInfo displayInfoToCache = displayMode.GetDisplayInfo(controllerContext.HttpContext, virtualPath, virtualPathExists: path => FileExists(controllerContext, path));

                        if (displayInfoToCache != null && displayInfoToCache.FilePath != null)
                        {
                            ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayInfoToCache.DisplayMode.DisplayModeId), displayInfoToCache.FilePath);
                        }
                    }
                }
                break;
            }

            searchedLocations[i] = virtualPath;
        }

        return result;
    }

    private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
    {
        string result = name;

        if (!(FilePathIsSupported(name) && FileExists(controllerContext, name)))
        {
            result = String.Empty;
            searchedLocations = new[] { name };
        }

        ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
        return result;
    }

    private bool FilePathIsSupported(string virtualPath)
    {
        if (FileExtensions == null)
        {
            // legacy behavior for custom ViewEngine that might not set the FileExtensions property
            return true;
        }
        else
        {
            // get rid of the '.' because the FileExtensions property expects extensions withouth a dot.
            string extension = GetExtensionThunk(virtualPath).TrimStart('.');
            return FileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
        }
    }

    private static List<ViewLocation> GetViewLocations(string[] viewLocationFormats, string[] areaViewLocationFormats)
    {
        List<ViewLocation> allLocations = new List<ViewLocation>();

        if (areaViewLocationFormats != null)
        {
            foreach (string areaViewLocationFormat in areaViewLocationFormats)
            {
                allLocations.Add(new AreaAwareViewLocation(areaViewLocationFormat));
            }
        }

        if (viewLocationFormats != null)
        {
            foreach (string viewLocationFormat in viewLocationFormats)
            {
                allLocations.Add(new ViewLocation(viewLocationFormat));
            }
        }

        return allLocations;
    }

    private static bool IsSpecificPath(string name)
    {
        char c = name[0];
        return (c == '~' || c == '/');
    }

    public virtual void ReleaseView(ControllerContext controllerContext, IView view)
    {
        IDisposable disposable = view as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }

    private class AreaAwareViewLocation : ViewLocation
    {
        public AreaAwareViewLocation(string virtualPathFormatString)
            : base(virtualPathFormatString)
        {
        }

        public override string Format(string viewName, string controllerName, string areaName)
        {
            return String.Format(CultureInfo.InvariantCulture, _virtualPathFormatString, viewName, controllerName, areaName);
        }
    }

    private class ViewLocation
    {
        protected string _virtualPathFormatString;

        public ViewLocation(string virtualPathFormatString)
        {
            _virtualPathFormatString = virtualPathFormatString;
        }

        public virtual string Format(string viewName, string controllerName, string areaName)
        {
            return String.Format(CultureInfo.InvariantCulture, _virtualPathFormatString, viewName, controllerName);
        }
    }
}

VirtualPathProviderViewEngine
复制代码
 BuildManagerViewEngine
 RazorViewEngine

  上述代码中,GetPath方法检查指定路径下的.cshtml文件是否存在;如果不存在,则将视图引擎检查过的所有路径封装到ViewEngineResult对象,并在之后通过异常返回;否则,会通过RazorViewEngine类的CreateView方法中创建了一个 RazorView 对象,即:视图对象。注意:之后在步骤3中通过执行该对象的Render方法,来对【视图页】(.cshtml文件)的内容进行处理。
  上述中通过var view = new RazorView(controllerContext, viewPath,layoutPath: masterPath, runViewStartPages: true, viewStartFileExtensions:FileExtensions, viewPageActivator: ViewPageActivator){DisplayModeProvider = DisplayModeProvider};来创建RazorView对象,5个参数和1个属性分别表示:

  • controllerContext           :控制器上下文
  • viewPath                      :视图页的路径
  • runViewStartPages         :true,该值指示视图启动文件是否应在视图之前执行。
  • viewStartFileExtensions   :视图启动文件时将使用的扩展名集(.cshtml和.vbhtml)
  • viewPageActivator          :用来创建视图页对象实例(反射、缓存)
  • DisplayModeProvider        :默认情况值为一个DisplayModeProvider对象,通过DisplayModeProvider.Instance获取的。

接下来,着重看一下参数viewPageActivator的值和属性DisplayModeProvider值的获取方式:
  1、ViewPageActivator是类BuildManagerViewEngine中的一个属性,它在之后创建的实例的过程类似于ControllerBuilder创建DefaultControllerFactory。由于在Controller激活中已经对SingleServiceResolver<TService>和DependencyResolver分析过,这里就不在敖述,仅贴出源码!

复制代码
public abstract class BuildManagerViewEngine : VirtualPathProviderViewEngine
{
    private IViewPageActivator _viewPageActivator;
    private IResolver<IViewPageActivator> _activatorResolver;

    protected BuildManagerViewEngine()
        : this(null, null, null)
    {
    }

    protected BuildManagerViewEngine(IViewPageActivator viewPageActivator)
        : this(viewPageActivator, null, null)
    {
    }

    internal BuildManagerViewEngine(IViewPageActivator viewPageActivator, IResolver<IViewPageActivator> activatorResolver, IDependencyResolver dependencyResolver)
    {
        if (viewPageActivator != null)
        {
            _viewPageActivator = viewPageActivator;
        }
        else
        {
            _activatorResolver = activatorResolver ?? new SingleServiceResolver<IViewPageActivator>(
                                                          () => null,
                                                          new DefaultViewPageActivator(dependencyResolver),
                                                          "BuildManagerViewEngine constructor");
        }
    }
    protected IViewPageActivator ViewPageActivator
    {
        get
        {
            if (_viewPageActivator != null)
            {
                return _viewPageActivator;
            }
            _viewPageActivator = _activatorResolver.Current;
            return _viewPageActivator;
        }
    }

    //内部类
    internal class DefaultViewPageActivator : IViewPageActivator
    {
        private Func<IDependencyResolver> _resolverThunk;

        public DefaultViewPageActivator()
            : this(null)
        {
        }

        public DefaultViewPageActivator(IDependencyResolver resolver)
        {
            if (resolver == null)
            {
                _resolverThunk = () => DependencyResolver.Current;
            }
            else
            {
                _resolverThunk = () => resolver;
            }
        }

        public object Create(ControllerContext controllerContext, Type type)
        {
            return _resolverThunk().GetService(type) ?? Activator.CreateInstance(type);
        }
    }
}
复制代码
复制代码
internal class SingleServiceResolver<TService> : IResolver<TService>
    where TService : class
{
    private Lazy<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;
        _currentValueFromResolver = new Lazy<TService>(GetValueFromResolver);
        _currentValueThunk = currentValueThunk;
        _defaultValue = defaultValue;
        _callerMethodName = callerMethodName;
    }

    internal SingleServiceResolver(Func<TService> staticAccessor, TService defaultValue, IDependencyResolver resolver, string callerMethodName)
        : this(staticAccessor, defaultValue, callerMethodName)
    {
        if (resolver != null)
        {
            _resolverThunk = () => resolver;
        }
    }

    public TService Current
    {
        get { return _currentValueFromResolver.Value ?? _currentValueThunk() ?? _defaultValue; }
    }

    private TService GetValueFromResolver()
    {
        TService result = _resolverThunk().GetService<TService>();

        if (result != null && _currentValueThunk() != null)
        {
            throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.SingleServiceResolver_CannotRegisterTwoInstances, typeof(TService).Name.ToString(), _callerMethodName));
        }

        return result;
    }
}
复制代码
复制代码
public class DependencyResolver
{
    private static DependencyResolver _instance = new DependencyResolver();

    private IDependencyResolver _current;

    /// <summary>
    /// Cache should always be a new CacheDependencyResolver(_current).
    /// </summary>
    private CacheDependencyResolver _currentCache;

    public DependencyResolver()
    {
        InnerSetResolver(new DefaultDependencyResolver());
    }

    public static IDependencyResolver Current
    {
        get { return _instance.InnerCurrent; }
    }

    internal static IDependencyResolver CurrentCache
    {
        get { return _instance.InnerCurrentCache; }
    }

    public IDependencyResolver InnerCurrent
    {
        get { return _current; }
    }

    /// <summary>
    /// Provides caching over results returned by Current.
    /// </summary>
    internal IDependencyResolver InnerCurrentCache
    {
        get { return _currentCache; }
    }

    public static void SetResolver(IDependencyResolver resolver)
    {
        _instance.InnerSetResolver(resolver);
    }

    public static void SetResolver(object commonServiceLocator)
    {
        _instance.InnerSetResolver(commonServiceLocator);
    }

    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types.")]
    public static void SetResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices)
    {
        _instance.InnerSetResolver(getService, getServices);
    }

    public void InnerSetResolver(IDependencyResolver resolver)
    {
        if (resolver == null)
        {
            throw new ArgumentNullException("resolver");
        }

        _current = resolver;
        _currentCache = new CacheDependencyResolver(_current);
    }

    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);

        InnerSetResolver(new DelegateBasedDependencyResolver(getService, getServices));
    }

    [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types.")]
    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");
        }

        InnerSetResolver(new DelegateBasedDependencyResolver(getService, getServices));
    }

    /// <summary>
    /// Wraps an IDependencyResolver and ensures single instance per-type.
    /// </summary>
    /// <remarks>
    /// Note it's possible for multiple threads to race and call the _resolver service multiple times.
    /// We'll pick one winner and ignore the others and still guarantee a unique instance.
    /// </remarks>
    private sealed class CacheDependencyResolver : IDependencyResolver
    {
        private readonly ConcurrentDictionary<Type, object> _cache = new ConcurrentDictionary<Type, object>();
        private readonly ConcurrentDictionary<Type, IEnumerable<object>> _cacheMultiple = new ConcurrentDictionary<Type, IEnumerable<object>>();

        private readonly IDependencyResolver _resolver;

        public CacheDependencyResolver(IDependencyResolver resolver)
        {
            _resolver = resolver;
        }

        public object GetService(Type serviceType)
        {
            return _cache.GetOrAdd(serviceType, _resolver.GetService);
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            return _cacheMultiple.GetOrAdd(serviceType, _resolver.GetServices);
        }
    }

    private class DefaultDependencyResolver : IDependencyResolver
    {
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method might throw exceptions whose type we cannot strongly link against; namely, ActivationException from common service locator")]
        public object GetService(Type serviceType)
        {
            // Since attempting to create an instance of an interface or an abstract type results in an exception, immediately return null
            // to improve performance and the debugging experience with first-chance exceptions enabled.
            if (serviceType.IsInterface || serviceType.IsAbstract)
            {
                return null;
            }

            try
            {
                return Activator.CreateInstance(serviceType);
            }
            catch
            {
                return null;
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            return Enumerable.Empty<object>();
        }
    }

    private class DelegateBasedDependencyResolver : IDependencyResolver
    {
        private Func<Type, object> _getService;
        private Func<Type, IEnumerable<object>> _getServices;

        public DelegateBasedDependencyResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices)
        {
            _getService = getService;
            _getServices = getServices;
        }

        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method might throw exceptions whose type we cannot strongly link against; namely, ActivationException from common service locator")]
        public object GetService(Type type)
        {
            try
            {
                return _getService.Invoke(type);
            }
            catch
            {
                return null;
            }
        }

        public IEnumerable<object> GetServices(Type type)
        {
            return _getServices(type);
        }
    }
}
复制代码

  2、DisplayModeProvider是VirtualPathProviderViewEngine类的一个属性。

复制代码
public abstract class VirtualPathProviderViewEngine : IViewEngine
{
    //省略其他代码
    private DisplayModeProvider _displayModeProvider;
    protected internal DisplayModeProvider DisplayModeProvider
    {
        get { return _displayModeProvider ?? DisplayModeProvider.Instance; }
        set { _displayModeProvider = value; }
    }
}
复制代码
复制代码
public sealed class DisplayModeProvider
{
    public static readonly string MobileDisplayModeId = "Mobile";
    public static readonly string DefaultDisplayModeId = string.Empty;
    private static readonly object _displayModeKey = new object();
    private static readonly DisplayModeProvider _instance = new DisplayModeProvider();
    private readonly List<IDisplayMode> _displayModes;
    public bool RequireConsistentDisplayMode
    {
        get;
        set;
    }
    public static DisplayModeProvider Instance
    {
        get
        {
            return DisplayModeProvider._instance;
        }
    }
    public IList<IDisplayMode> Modes
    {
        get
        {
            return this._displayModes;
        }
    }
    internal DisplayModeProvider()
    {
        List<IDisplayMode> list = new List<IDisplayMode>();
        List<IDisplayMode> arg_37_0 = list;
        DefaultDisplayMode defaultDisplayMode = new DefaultDisplayMode(DisplayModeProvider.MobileDisplayModeId);
        defaultDisplayMode.ContextCondition = ((HttpContextBase context) => context.GetOverriddenBrowser().IsMobileDevice);
        arg_37_0.Add(defaultDisplayMode);
        list.Add(new DefaultDisplayMode());
        this._displayModes = list;
        base..ctor();
    }
    public IEnumerable<IDisplayMode> GetAvailableDisplayModesForContext(HttpContextBase httpContext, IDisplayMode currentDisplayMode)
    {
        return this.GetAvailableDisplayModesForContext(httpContext, currentDisplayMode, this.RequireConsistentDisplayMode).ToList<IDisplayMode>();
    }
    internal IEnumerable<IDisplayMode> GetAvailableDisplayModesForContext(HttpContextBase httpContext, IDisplayMode currentDisplayMode, bool requireConsistentDisplayMode)
    {
        IEnumerable<IDisplayMode> source = (requireConsistentDisplayMode && currentDisplayMode != null) ? this.Modes.SkipWhile((IDisplayMode mode) => mode != currentDisplayMode) : this.Modes;
        return 
            from mode in source
            where mode.CanHandleContext(httpContext)
            select mode;
    }
    public DisplayInfo GetDisplayInfoForVirtualPath(string virtualPath, HttpContextBase httpContext, Func<string, bool> virtualPathExists, IDisplayMode currentDisplayMode)
    {
        return this.GetDisplayInfoForVirtualPath(virtualPath, httpContext, virtualPathExists, currentDisplayMode, this.RequireConsistentDisplayMode);
    }
    internal DisplayInfo GetDisplayInfoForVirtualPath(string virtualPath, HttpContextBase httpContext, Func<string, bool> virtualPathExists, IDisplayMode currentDisplayMode, bool requireConsistentDisplayMode)
    {
        IEnumerable<IDisplayMode> availableDisplayModesForContext = this.GetAvailableDisplayModesForContext(httpContext, currentDisplayMode, requireConsistentDisplayMode);
        return (
            from mode in availableDisplayModesForContext
            select mode.GetDisplayInfo(httpContext, virtualPath, virtualPathExists)).FirstOrDefault((DisplayInfo info) => info != null);
    }
    internal static IDisplayMode GetDisplayMode(HttpContextBase context)
    {
        if (context == null)
        {
            return null;
        }
        return context.Items[DisplayModeProvider._displayModeKey] as IDisplayMode;
    }
    internal static void SetDisplayMode(HttpContextBase context, IDisplayMode displayMode)
    {
        if (context != null)
        {
            context.Items[DisplayModeProvider._displayModeKey] = displayMode;
        }
    }
}
复制代码

③ 处理视图页中的内容,并将处理后的内容响应给客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public abstract class ViewResultBase : ActionResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        //如果没有指定视图页的名称将当前Action作为视图页的名称
        if (String.IsNullOrEmpty(ViewName))
        {
            ViewName = context.RouteData.GetRequiredString("action");
        }
      ViewEngineResult result = null;
        if (View == null)
        {
            //通过视图引擎去创建视图对象,并将视图对象和该视图相关的信息封装在ViewEngineResult对象中。
            result = FindView(context);
            View = result.View;
        }
        TextWriter writer = context.HttpContext.Response.Output;
        //将上下文、视图对象、之前定义的ViewData、之前定义的TempData、writer封装到一个ViewContext对象中。
        //注意:ViewData对象中包含了我们定义的ViewData键值对,还有我们创建ActionResult对象时的参数值(也就是要在Razor中用的模型)。
        ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, writer);
        //使用指定的编写器对象来呈现指定的视图上下文,即:处理视图页中的内容,再通过writer将处理后的内容响应给客户端。
        View.Render(viewContext, writer);
 
        if (result != null)
        {
            //释放视图页资源
            result.ViewEngine.ReleaseView(context, View);
        }
    }
} 

  上述代码可以看出,View.Render(viewContext,writer)方法是处理视图页面的入口,对于视图页(.cshtml文件),会将其编译成继承自WebViewPage<object>的类(如:~/Views/Foo/Action1.cshtml被编译后的类名称为:_Page_Views_foo_Action1_cshtml),被编译之后的文件以 .dll 的形式保存在临时文件夹中。关于这个临时文件夹,如果以Visual Studio Development Server为宿主,则存放临时文件夹目录为:“%WinDir%\Microsoft.NET\Framework\{VersionNo}\Temporary ASP.NET Files\root\目录下;如果是以IIS为宿主,则临时文件存放在目录“%WinDir%\Microsoft.NET\Framework\{VersionNo}\Temporary ASP.NET Files\Web应用名称\xxx\xxx”下,例如:名称为MVCAppTest的Web应用,其临时文件存放在路径:C:\Windows\Microsoft.NET\Framework\{VersionNo}\Temporary ASP.NET Files\mvcapptest\c4ea0afa\a83bd407下的App_Web_xxxxx.dll,这个临时目录地址还可以通过WebConfig文件来配置,如下:更多详细介绍请猛击这里

1
2
3
4
5
<configuration>
  <system.web>
    <compilation tempDirectory="D;\Temporary\"></compilation>
  </system.web>
</configuration>

除此之外,应该了解在【View呈现】的部分,对视图页的编译顺序为:【_ViewStart.cshtml】-->【母板页】-->【被请求的视图页】。如果是利用WebMatrix开发的话,则对视图页的编译顺序为:【_PageStart.cshtml】-->【母板页】-->【被请求的视图页】。
编译视图页时,@{ ... }会被编译成方法,@Html.xxx也被编译成xxx方法。

  了解上述内容之后,我们再来看看在整个过程在源码中的体现,如上代码所知,View.Render(viewContext,writer)是处理视图页面的入口,此处的View就是在第二部分中我们创建的RazorView对象,那么接下来我们就来看看它们的源码,查看到底是如何实现处理视图页面并返回给客户端的!

 主要执行代码有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public abstract class BuildManagerCompiledView : IView
{
    public void Render(ViewContext viewContext, TextWriter writer)
    {
        if (viewContext == null)
        {
            throw new ArgumentNullException("viewContext");
        }
 
        object instance = null;
        //获取被编译的视图页(cshtml文件)的类型                                                                
        Type type = BuildManager.GetCompiledType(ViewPath);
        if (type != null)
        {
            //通过SingleServiceResolver利用反射和缓存的原理创建视图页的实例,过程类似SingleServiceResolver类对Controller的激活。
            instance = ViewPageActivator.Create(_controllerContext, type);
        }
 
        if (instance == null)
        {
            throw new InvalidOperationException(
                String.Format(
                    CultureInfo.CurrentCulture,
                    MvcResources.CshtmlView_ViewCouldNotBeCreated,
                    ViewPath));
        }
        //执行RenderView方法
        RenderView(viewContext, writer, instance);
    }
}
public class RazorView : BuildManagerCompiledView
{
    protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)
    {
        if (writer == null)
        {
            throw new ArgumentNullException("writer");
        }
        //将视图页实例转换为WebViewPage对象。
        WebViewPage webViewPage = instance as WebViewPage;
        if (webViewPage == null)
        {
            throw new InvalidOperationException(
                String.Format(
                    CultureInfo.CurrentCulture,
                    MvcResources.CshtmlView_WrongViewBase,
                    ViewPath));
        }
        webViewPage.OverridenLayoutPath = LayoutPath;//母板路径(在第二步中,创建RazorView对象时,由构造函数导入)
        webViewPage.VirtualPath = ViewPath;          //该视图路径
        webViewPage.ViewContext = viewContext;       //视图上下文,其中有TempData、controllerContext、ViewData等
        webViewPage.ViewData = viewContext.ViewData;
        //WebViewPage对象初始化,执行创建3个xxHelper对象AjaxHelper、HtmlHelper、UrlHelper。
        webViewPage.InitHelpers();
 
        if (VirtualPathFactory != null)
        {
            webViewPage.VirtualPathFactory = VirtualPathFactory;
        }
        if (DisplayModeProvider != null)
        {
            webViewPage.DisplayModeProvider = DisplayModeProvider;
        }
 
        WebPageRenderingBase startPage = null;
        //true,该值指示视图启动文件是否应在视图之前执行(Razor视图:true;WebForm视图:false)
        if (RunViewStartPages)
        {
            //编译_ViewStart.cshtml并通过反射创建一个StartPage类型实例,然后再将webViewPage赋值到其ChildPage属性中,最后再转为WebPageRenderingBase类型。
            //目的:将多个视图对象按照层次封装在一起,以便之后进行内容的处理。
            //StartPageLookup是一个委托,执行的StartPage类的静态方法GetStartPage
            //参数:ViewStartFileName是RazorViewEngine类中定义的一个只读字符串“_ViewStart”
            //参数:ViewStartFileExtensions是个数组,包含“cshtml”和“vbhtml”
            startPage = StartPageLookup(webViewPage, RazorViewEngine.ViewStartFileName, ViewStartFileExtensions);
        }
        //按层次结构处理视图页的内容。
        //先处理_ViewStart.cshtml内容,然后将其中设置的PageData["key"]等值封装到视图上下文中,再去处理被请求页的内容。
        //参数:页的上下文数据、要用于编写执行 HTML 的编写器、在页层次结构中开始执行的页
        webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage);
    }
}

上述代码中,有两处需要重点分析:

  1、startPage=StartPageLookup(webViewPage,RazorViewEngine.ViewStartFileName,ViewStartFileExtensions)

  StartPageLookup是一个委托,执行该委托就是调用StartPage类的静态方法GetStartPage,该方法内编译_ViewPage.cshtml并通过反射将其创建为一个StartPage实例(继承自WebPageRenderingBase),之后将之前创建的被请求的视图页对象赋值到StartPage实例的ChildPage属性中,最后将StartPage实例转换为WebPageRenderingBase类型。

复制代码
public abstract class StartPage : WebPageRenderingBase
{
    //page:被请求的视图页对象;fileName:“_ViewStart”;supportedExtensions:数组“cshtml”和“vbhtml”
    public static WebPageRenderingBase GetStartPage(WebPageRenderingBase page, string fileName, IEnumerable<string> supportedExtensions)
    {
        if (page == null)
        {
            throw new ArgumentNullException("page");
        }
        if (string.IsNullOrEmpty(fileName))
        {
            throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, CommonResources.Argument_Cannot_Be_Null_Or_Empty, new object[]
            {
                "fileName"
            }), "fileName");
        }
        if (supportedExtensions == null)
        {
            throw new ArgumentNullException("supportedExtensions");
        }
        return StartPage.GetStartPage(page, page.VirtualPathFactory ?? VirtualPathFactoryManager.Instance, HttpRuntime.AppDomainAppVirtualPath, fileName, supportedExtensions);
    }
    internal static WebPageRenderingBase GetStartPage(WebPageRenderingBase page, IVirtualPathFactory virtualPathFactory, string appDomainAppVirtualPath, string fileName, IEnumerable<string> supportedExtensions)
    {
        WebPageRenderingBase webPageRenderingBase = page;
        //得到视图路径的目录,page.VirtualPath是被请求的视图页的路径。
        string directory = VirtualPathUtility.GetDirectory(page.VirtualPath);
        while (!string.IsNullOrEmpty(directory) && directory != "/" && PathUtil.IsWithinAppRoot(appDomainAppVirtualPath, directory))
        {
            //循环“cshtml”和“vbhtml”
            foreach (string current in supportedExtensions)
            {
                string virtualPath = VirtualPathUtility.Combine(directory, fileName + "." + current);
                if (virtualPathFactory.Exists(virtualPath))
                {
                    //创建“_ViewStart.cshtml”的实例。
                    StartPage startPage = virtualPathFactory.CreateInstance(virtualPath);
                    startPage.VirtualPath = virtualPath;
                    //将被请求的视图页对象保存到StartPage对象的ChildPage属性
                    startPage.ChildPage = webPageRenderingBase;
                    startPage.VirtualPathFactory = virtualPathFactory;
                    //将webPageRenderingBase的值设置为当前StartPage对象。
                    webPageRenderingBase = startPage;
                    break;
                }
            }
            directory = webPageRenderingBase.GetDirectory(directory);
        }
        return webPageRenderingBase;
    }
}
复制代码

注:如下图所示,被请求的视图页对象继承WebViewPage<TModel>,而“_ViewStart.cshtml”经编译后创建的对象继承自StartPage。

 

  2、webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage)

  此步骤的功能有:处理_StartView.cshtml的内容--->处理视图页的内容--->处理母板页的内容。将前两个内容处理完成之后,如果存在母板页,则处理母板页,之后再将前两个内容添加到母板页内容中,最后执行输出。
此句代码执行视图页对象的ExecutePageHierarchy方法,由上图中的关系所示,此方法被定义在基类WebPageBase中。(参数startPage是“_ViewStart.cshtml”经过编号后创建的对象,且又将视图页对象webViewPage赋值给其ChildPage属性)。

处理“_ViewStart.cshtml”和被请求视图页的内容:

  如上代码所示,当Web应用中使用了"_ViewStart.cshtml"时,下一步就是执行StartPage类中定义的ExecutePageHierarchy(),否则,就要执行WebViewPage类中定义的ExecutePageHiererchy()方法。下面就来分析下Web应用中使用了"_ViewStart.cshtml"情况下,代码的执行过程。(由于其中包含了对 被请求视图页对象的处理,即:会执行WebViewPage类的ExecutePageHiererchy方法,所以对于Web应用中不包含“_ViewStart.cshtml”情况不再单独介绍!)

 StartPage

  上面代码StartPage类的ExecutePageHierarchy方法中调用了【this.Execute()】,该方法是“_ViewStart.cshtml”文件经编译后生成的!该方法的内部会执行StartPage类的Write和WriteLiteral方法。而有上面的StartPage类的源码可知,这些方法又会调用This.ChildPage.Write和This.ChildPage.WriteLiteral,即:被请求的视图页对象对应的方法,以实现将“_ViewStart.cshtml”中的内容输出到 被请求的视图页 中。

如下图所示的“_ViewStart.cshtml”经编译后的结果为:

  上面代码StartPage类的ExecutePageHierarchy方法中接着又要调用【this.RunPage()】方法,该方法内部又调用【this.ChildPage.ExecutePageHierarchy()】,也就是被请求视图页对象的ExecutePageHierarchy()方法,实现在WebViewPage类中。

 WebViewPage
 WebPageBase

  在WebPageBase的ExecutePageHierarchy()方法中又调用了【this.Execute()】,也就是执行 被请求视图页对象的Execute()方法!然后在Execute方法中也会调用This.Write或This.WriteLiteral方法将视图页中的内容输出。

如下图所示被请求的视图页或母板页经编译后的结果为:

  以上的这些WriteLiteral方法或Write方法,都是使用HttpWriter对象的Write方法将内容写入到Http输出流中。(之后再通过IIS将响应流中的数据返回给客户端)

处理母板页内容:

  也就是执行this.PopContext();

public abstract class WebPageBase : WebPageRenderingBase
{
    public void PopContext()
    {
        //获取"_ViewStart.cshtml"和视图页的内容
        string renderedContent = this._tempWriter.ToString();
        this.OutputStack.Pop();
        //如果有母板页
        if (!string.IsNullOrEmpty(this.Layout))
        {
            //母板页名称
            string partialViewName = base.NormalizeLayoutPagePath(this.Layout);
            this.OutputStack.Push(this._currentWriter);
            //处理母板页
            this.RenderSurrounding(partialViewName, delegate(TextWriter w)
            {
                w.Write(renderedContent);
            });
            this.OutputStack.Pop();
        }
        //没有母板页
        else
        {
            this._currentWriter.Write(renderedContent);
        }
        this.VerifyRenderedBodyOrSections();
        this.SectionWritersStack.Pop();
    }
    private void RenderSurrounding(string partialViewName, Action<TextWriter> body)
    {
        Action<TextWriter> bodyAction = base.PageContext.BodyAction;
        base.PageContext.BodyAction = body;
        bool isLayoutPage = true;
        object[] data = new object[0];
        this.Write(this.RenderPageCore(partialViewName, isLayoutPage, data));
        base.PageContext.BodyAction = bodyAction;
    }
    private HelperResult RenderPageCore(string path, bool isLayoutPage, object[] data)
    {
        if (string.IsNullOrEmpty(path))
        {
            throw ExceptionHelper.CreateArgumentNullOrEmptyException("path");
        }
        return new HelperResult(delegate(TextWriter writer)
        {
            path = this.NormalizePath(path);
            WebPageBase webPageBase = this.CreatePageFromVirtualPath(path, this.Context, new Func<string, bool>(this.VirtualPathFactory.Exists), this.DisplayModeProvider, this.DisplayMode);
            WebPageContext pageContext = this.CreatePageContextFromParameters(isLayoutPage, data);
            webPageBase.ConfigurePage(this);
            //熟悉吧,这个就是去处理母板页的入口!(和视图页的处理相同)
            webPageBase.ExecutePageHierarchy(pageContext, writer);
        });
    }
}

  母板页和视图页相同,被编译后都是继承自 WebViewPage<object>的类,所以其具体的处理过程和视图页是相同的!

下图是母板页的编译结果:

 


以上就是ViewResult进行View呈现时的全部内容,如果感兴趣的话, 自己可以分析下PartialView的执行过程,对于上述不适之处,请指正!!!


本文转自武沛齐博客园博客,原文链接:http://www.cnblogs.com/wupeiqi/p/3499690.html,如需转载请自行联系原作者


版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
如何设置阿里云服务器安全组?阿里云安全组规则详细解说
阿里云安全组设置详细图文教程(收藏起来) 阿里云服务器安全组设置规则分享,阿里云服务器安全组如何放行端口设置教程。阿里云会要求客户设置安全组,如果不设置,阿里云会指定默认的安全组。那么,这个安全组是什么呢?顾名思义,就是为了服务器安全设置的。安全组其实就是一个虚拟的防火墙,可以让用户从端口、IP的维度来筛选对应服务器的访问者,从而形成一个云上的安全域。
18638 0
阿里云服务器ECS登录用户名是什么?系统不同默认账号也不同
阿里云服务器Windows系统默认用户名administrator,Linux镜像服务器用户名root
15334 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
27778 0
阿里云服务器安全组设置内网互通的方法
虽然0.0.0.0/0使用非常方便,但是发现很多同学使用它来做内网互通,这是有安全风险的,实例有可能会在经典网络被内网IP访问到。下面介绍一下四种安全的内网互联设置方法。 购买前请先:领取阿里云幸运券,有很多优惠,可到下文中领取。
21963 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
16589 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,大概有三种登录方式:
12990 0
+关注
3550
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载