asp.net mvc源碼分析-ActionResult篇 FindView

來源:互聯網
上載者:User

接著上篇asp.net mvc源碼分析-ActionResult篇 ViewResult 中有ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName)這麼一句,它究竟是怎麼找到View的了?首先放我們看看你ViewEngineCollection中的FindView方法吧,其實就一句

return Find(e => e.FindView(controllerContext, viewName, masterName, true),
                        e => e.FindView(controllerContext, viewName, masterName, false));

不過這句乾的事情可不少啊,調用內部的一個Find方法,

    private ViewEngineResult Find(Func<IViewEngine, ViewEngineResult> cacheLocator, Func<IViewEngine, ViewEngineResult> locator) {
            // First, look up using the cacheLocator and do not track the searched paths in non-matching view engines
            // Then, look up using the normal locator and track the searched paths so that an error view engine can be returned
            return Find(cacheLocator, trackSearchedPaths: false)
                ?? Find(locator, trackSearchedPaths: true);
        }

這裡的cacheLocator=e.FindView(controllerContext, viewName, masterName, true),locator=e.FindView(controllerContext, viewName, masterName, false),它也是在調用一個內部的find方法,

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

 其中 trackSearchedPaths表示是否記錄收索路徑,首先不記錄收索路徑看能否找到view,如果找不到就記錄收索路徑在此尋找view。順便提一下ViewEngineCollection中有一個FindPartialView方法和FindView方法邏輯一致。

這裡 的CombinedItems其實就是ViewEngines中的Engines屬性,預設只有WebFormViewEngine、RazorViewEngine這2個執行個體。所以它會遍曆所有的IViewEngine來尋找view。這裡為了提高效能,我們可以移除一個我們不需要的IViewEngine。,例如我在用Razor開發mvc時可以把WebFormViewEngine給移除以提高性。Application_Start方法中添加  ViewEngines.Engines.RemoveAt(0);

trackSearchedPaths這個參數是記錄尋找路徑的,究竟有什麼效果了?如果我們在IViewEngine中沒有找到相應的view則記錄尋找路徑。 

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

如果 所有的IViewEngine都找完了,都沒有找到尋找到view,trackSearchedPaths為false值直接返回null,否者返回一個沒有view的ViewEngineResult。

給我們帶來效果如下:

現在 我們來看看你真正尋找view的方法是在VirtualPathProviderViewEngine類的FindView方法:

        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");            string viewPath = GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, "ViewLocationFormats", viewName, controllerName, _cacheKeyPrefix_View, useCache, out viewLocationsSearched);            string masterPath = GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, "MasterLocationFormats", masterName, controllerName, _cacheKeyPrefix_Master, useCache, out masterLocationsSearched);            if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName))) {                return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));            }            return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);        }

 這裡面有一句很重要  string viewPath = GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, "ViewLocationFormats", viewName, controllerName, _cacheKeyPrefix_View, useCache, out viewLocationsSearched);,用GetPath來找viewpath、masterPath。

 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) {                return ViewLocationCache.GetViewLocation(controllerContext.HttpContext, cacheKey);            }            return (nameRepresentsPath) ?                GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations) :                GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, cacheKey, ref searchedLocations);        }

  

 string areaName = AreaHelpers.GetAreaName(controllerContext.RouteData);
            bool usingAreas = !String.IsNullOrEmpty(areaName);
預設情況下usingAreas 為false。GetViewLocations方法就是返回一個ViewLocation集合,裡面的代碼簡單,舉個例子吧,在RazorViewEngine中重設了

  ViewLocationFormats = new[] {
                "~/Views/{1}/{0}.cshtml",
                "~/Views/{1}/{0}.vbhtml",
                "~/Views/Shared/{0}.cshtml",
                "~/Views/Shared/{0}.vbhtml"
            };

加入當前的Controller為Home,Action為Index,

那麼 後面實際 尋找路徑就會一次是

"~/Views/Home/Index.cshtml",
"~/Views/Home/Index.vbhtml",
"~/Views/Shared/Index.cshtml",
"~/Views/Shared/Index.vbhtml"

其實我們一般的項目要麼就是C#和VB一種語言開發,所以ViewLocationFormats 可以移除2個元素以提高效能。

 bool nameRepresentsPath = IsSpecificPath(name)這句是看我們的viewname是否以~和/開頭.

   string cacheKey = CreateCacheKey(cacheKeyPrefix, name, (nameRepresentsPath) ? String.Empty : controllerName, areaName); 建立一個緩衝key。

 if (useCache) {
                return ViewLocationCache.GetViewLocation(controllerContext.HttpContext, cacheKey);
            }

從 緩衝中返回viewpath, 預設情況相愛ViewLocationCache = new DefaultViewLocationCache()

DefaultViewLocationCache的主要方法如下:

 public string GetViewLocation(HttpContextBase httpContext, string key) {
            if (httpContext == null) {
                throw new ArgumentNullException("httpContext");
            }
            return (string)httpContext.Cache[key];
        }
        public void InsertViewLocation(HttpContextBase httpContext, string key, string virtualPath) {
            if (httpContext == null) {
                throw new ArgumentNullException("httpContext");
            }
            httpContext.Cache.Insert(key, virtualPath, null /* dependencies */, Cache.NoAbsoluteExpiration, TimeSpan);
        }

很簡單的。

現在 我們回到GetPath方法中,如果不用緩衝就 只剩下最後一句了

 return (nameRepresentsPath) ?
                GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations) :
                GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, cacheKey, ref searchedLocations);

這 2個方法實現如下:

    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);                if (FileExists(controllerContext, virtualPath)) {                    searchedLocations = _emptyLocations;                    result = virtualPath;                    ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);                    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;        }

  這2個方法都調用一個共同的方法FileExists,FileExists是個內嵌函式直接調用VirtualPathProvider.FileExists(virtualPath),預設情況下VirtualPathProvider=HostingEnvironment.VirtualPathProvider。但是實際上調用的是 BuildManager.GetObjectFactory(virtualPath, false) != null它是如何檢查路徑的我們這裡就忽略它吧。在GetPathFromSpecificName方法中調用一個FilePathIsSupported方法,其實就是檢查viewname的副檔名。
這裡我們就完成可viewpath的尋找。

 

現在 我們回到FindView中來,

  if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName))) {
                return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
            }

這句 沒什麼說的了簡單, return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);這句返回一個ViewEngineResult,ViewEngineResult的建構函式也很簡單,多說了。現在我們來看看這個CreateView方法。RazorViewEngine和WebFormViewEngine的具體實現如下:

 protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) {
            var view = new RazorView(controllerContext, viewPath,
                                     layoutPath: masterPath, runViewStartPages: true, viewStartFileExtensions: FileExtensions, viewPageActivator: ViewPageActivator);
            return view;
        }
  protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) {
            return new WebFormView(controllerContext, viewPath, masterPath, ViewPageActivator);
        }

至此 我們已經成功找到了View。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.