asp.net - 为什么 ASP.NET View 引擎检查 .Mobile.cshtml View 路径?

标签 asp.net asp.net-mvc asp.net-mvc-4 razor-2

对于我的 ASP.NET MVC 4 项目,我正在尝试实现自定义 View 引擎以查找“Index.cshtml” View 文件(如果文件夹中存在)。此外,我为所有未找到的 View 路径抛出 404。

当 View 文件不存在时,404 有效。当 View 文件确实存在时, View 引擎将尝试使用 FileExists() 函数查找 .Mobile.cshtml 文件。没有 .mobile.cshtml 文件,所以会抛出异常。为什么 View 引擎在已经找到非移动文件时仍然寻找 .mobile.cshtml 文件?

例如,当 View 引擎能够在“~/Views/About/History/Index.cshtml”找到一个 View 路径时,它会尝试找到文件“~/Views/About/History/Index.cshtml”。移动.cshtml”。下面是我的自定义 View 引擎的完整代码。

namespace System.Web.Mvc
{
  // Extend where RazorViewEngine looks for view files.
  // This looks for path/index.ext file if no path.ext file is found
  // Ex:  looks for "about/history/index.chstml" if "about/history.cshtml" is not found.
  public class CustomViewEngine : RazorViewEngine
  {
    public BeckmanViewEngine()
    {
      AreaViewLocationFormats = new[] 
      {
        "~/Areas/{2}/Views/{1}/{0}/Index.cshtml",
      };

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

    // Return 404 Exception if viewpath file in existing path is not found
    protected override bool FileExists(ControllerContext context, string path)
    {
      if (!base.FileExists(context, path))
      {
        throw new HttpException(404, "HTTP/1.1 404 Not Found");
      }

      return true;
    }
  }
}

最佳答案

我在 MVC 4 source code 中挖掘了一下后找到了答案.

RazorViewEngine 派生自 BuildManagerViewEngine,而这个又派生自 VirtualPathProviderViewEngine。 它是 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, CacheKeyPrefixView, useCache, out viewLocationsSearched);
        string masterPath = GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, "MasterLocationFormats", masterName, controllerName, CacheKeyPrefixMaster, 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);
    }

GetPath 方法在 View 路径还没有被缓存时会做这样的事情:

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

到达那里!有趣的方法是 GetPathFromGeneralName,它试图为 View 构建整个路径并检查该路径是否存在。该方法循环遍历在 View 引擎中注册的每个 View 位置,使用显示模式更新 View 路径对当前 HttpContext 有效,然后检查解析的路径是否存在。如果是,则 View 已找到,分配给结果,缓存并返回结果路径。

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 for all other display modes. We want to cache both file system hits and misses so that we can distinguish
            // in future requests whether a file's status was evicted from the cache (null value) or if the file doesn't exist (empty string).
            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));

                    string cacheValue = String.Empty;
                    if (displayInfoToCache != null && displayInfoToCache.FilePath != null)
                    {
                        cacheValue = displayInfoToCache.FilePath;
                    }
                    ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayMode.DisplayModeId), cacheValue);
                }
            }
            break;
        }

        searchedLocations[i] = virtualPath;
    }

    return result;
}

您可能已经注意到我没有谈到一段带有以下注释的代码(为清楚起见重新格式化):

// Populate the cache for all other display modes. 
// We want to cache both file system hits and misses so that we can distinguish
// in future requests whether a file's status was evicted from the cache 
// (null value) or if the file doesn't exist (empty string).

那(以及评论下方的代码 :))意味着一旦 MVC 4 从在 View 引擎中注册的 View 位置找到第一个有效路径,它还将检查 View 文件是否适用于所有存在未测试的其他显示模式,因此信息可以包含在缓存中(尽管只是针对该 View 位置,而不是 View 引擎中可用的所有位置)。 另请注意,它如何将 lambda 传递给每个测试的显示模式以检查该模式的文件是否存在:

DisplayInfo displayInfoToCache = displayMode.GetDisplayInfo(
                      controllerContext.HttpContext, 
                      virtualPath, 
                      virtualPathExists: path => FileExists(controllerContext, path));

这就解释了为什么当您重写 FileExists 时,即使它已经找到了非移动 View ,也会为移动 View 调用它。

在任何情况下,都可以删除显示模式,方法与添加它们的方法相同:通过在应用程序启动时更新 DisplayModes 集合。例如,删除移动显示模式并仅保留默认和非特定模式(您无法清除集合,否则将永远找不到 View ):

...
using System.Web.WebPages;
...

protected void Application_Start()
{
    DisplayModeProvider.Instance.Modes.Remove(
              DisplayModeProvider.Instance.Modes
                                 .Single(m => m.DisplayModeId == "Mobile"));

相当长的答案,但希望它有意义!

关于asp.net - 为什么 ASP.NET View 引擎检查 .Mobile.cshtml View 路径?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16987797/

相关文章:

javascript - event.preventDefault() 和重定向 asp mvc/jQuery

asp.net - 在 ASP.Net MVC 中使用 @Html.AntiForgeryToken() 提供 CSRF token 的替代方法

c# - 返回部分 View 并将其附加到 index.cshtml View Mvc4 中的 div

c# - 如何配置 WebMethods 以使用 DataContractJsonSerializer

asp.net - 使用 ServiceStack 检查请求 header

c# - ASP.NET MVC如何从应用程序中删除ViewState?

javascript - .NET MVC Razor - 作用域样式

javascript - 自定义控件的 HTMLDocument getElementById 返回 null,为什么,是否有不同的方法来访问它?

asp.net - 如何跟踪应用程序中的异常

c# - 如何在 MVC Controller 之外返回 StatusCode 对象