【问题标题】:How to change default view location scheme in ASP.NET MVC?如何更改 ASP.NET MVC 中的默认视图位置方案?
【发布时间】:2010-10-28 22:19:17
【问题描述】:

我想根据当前的 UI 文化在运行时更改视图位置。如何使用默认的 Web 表单视图引擎实现这一点?

基本上我想知道如何用WebFormViewEngine 实现custom IDescriptorFilter 中的Spark 是什么。

是否有其他视图引擎可以让我在运行时控制视图位置?


编辑:我的 URL 应该看起来像 {lang}/{controller}/{action}/{id}。我不需要依赖语言的控制器,并且视图已使用资源进行本地化。然而,在某些语言中,很少有观点会有所不同。所以我需要告诉视图引擎首先查看特定于语言的文件夹。

【问题讨论】:

    标签: .net asp.net-mvc viewengine


    【解决方案1】:

    我相信解决方案是创建您自己的继承自 WebFormViewEngine 的 ViewEngine。在构造函数中,它应该从当前线程检查当前 UI 文化并添加适当的位置。只是不要忘记将它添加到您的视图引擎中。

    这应该看起来像这样:

    public class ViewEngine : WebFormViewEngine
    {
        public ViewEngine()
        {
            if (CultureIsX())
                ViewLocationFormats = new string[]{"route1/controller.aspx"};
            if (CultureIsY())
                ViewLocationFormats = new string[]{"route2/controller.aspx"};
        }
    }
    

    在 global.asax 中:

    ViewEngines.Engines.Add(new ViewEngine());
    

    【讨论】:

    • 抱歉,这不是一个好的解决方案,因为 ViewEngine 的实例是跨线程共享的,我需要根据线程 UI 文化呈现不同的视图。
    • 也许可以为每种文化添加 viewEngine 并覆盖 findView 方法以中断它们,如果线程不同?只是一个奇怪的想法......
    • @Arnis L.:这很奇怪。但我会试试看。也许我会发现我喜欢它。
    • @pocheptsov:我目前正在寻找 Oxite 源代码。那里有很多很棒的想法。但是我找不到可以帮助我解决这个具体问题的方法。
    【解决方案2】:

    VirtualPathProviderViewEngine.GetPathFromGeneralName 必须更改为允许来自路由的附加参数。它不公开,这就是为什么你必须将GetPathGetPathFromGeneralNameIsSpecificPath 复制到你自己的ViewEngine 实现中。

    你是对的:这看起来像是一个完整的重写。我希望GetPathFromGeneralName 是公开的。

    using System.Web.Mvc;
    using System;
    using System.Web.Hosting;
    using System.Globalization;
    using System.Linq;
    
    namespace MvcLocalization
    {
        public class LocalizationWebFormViewEngine : WebFormViewEngine
        {
            private const string _cacheKeyFormat = ":ViewCacheEntry:{0}:{1}:{2}:{3}:";
            private const string _cacheKeyPrefix_Master = "Master";
            private const string _cacheKeyPrefix_Partial = "Partial";
            private const string _cacheKeyPrefix_View = "View";
            private static readonly string[] _emptyLocations = new string[0];
    
            public LocalizationWebFormViewEngine()
            {
                base.ViewLocationFormats = new string[] { 
                        "~/Views/{1}/{2}/{0}.aspx", 
                        "~/Views/{1}/{2}/{0}.ascx", 
                        "~/Views/Shared/{2}/{0}.aspx", 
                        "~/Views/Shared/{2}/{0}.ascx" ,
                         "~/Views/{1}/{0}.aspx", 
                        "~/Views/{1}/{0}.ascx", 
                        "~/Views/Shared/{0}.aspx", 
                        "~/Views/Shared/{0}.ascx" 
    
                };
    
            }
    
            private VirtualPathProvider _vpp;
    
            public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
            {
                if (controllerContext == null)
                    throw new ArgumentNullException("controllerContext");
    
                if (String.IsNullOrEmpty(viewName))
                    throw new ArgumentException( "viewName");
    
                string[] viewLocationsSearched;
                string[] masterLocationsSearched;
    
                string controllerName = controllerContext.RouteData.GetRequiredString("controller");
                string viewPath = GetPath(controllerContext, ViewLocationFormats, "ViewLocationFormats", viewName, controllerName, _cacheKeyPrefix_View, useCache, out viewLocationsSearched);
                string masterPath = GetPath(controllerContext, MasterLocationFormats, "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);
            }
    
            private string GetPath(ControllerContext controllerContext, string[] locations, string locationsPropertyName, string name, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
            {
                searchedLocations = _emptyLocations;
    
                if (String.IsNullOrEmpty(name))
                    return String.Empty;
    
                if (locations == null || locations.Length == 0)
                    throw new InvalidOperationException();
    
                bool nameRepresentsPath = IsSpecificPath(name);
                string cacheKey = CreateCacheKey(cacheKeyPrefix, name, (nameRepresentsPath) ? String.Empty : controllerName);
    
                if (useCache)
                {
                    string result = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, cacheKey);
                    if (result != null)
                    {
                        return result;
                    }
                }
    
                return (nameRepresentsPath) ?
                    GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations) :
                    GetPathFromGeneralName(controllerContext, locations, name, controllerName, cacheKey, ref searchedLocations);
            }
    
            private string GetPathFromGeneralName(ControllerContext controllerContext, string[] locations, string name, string controllerName, string cacheKey, ref string[] searchedLocations)
            {
                string result = String.Empty;
                searchedLocations = new string[locations.Length];
                string language = controllerContext.RouteData.Values["lang"].ToString();
    
                for (int i = 0; i < locations.Length; i++)
                {
                    string virtualPath = String.Format(CultureInfo.InvariantCulture, locations[i], name, controllerName,language);
    
                    if (FileExists(controllerContext, virtualPath))
                    {
                        searchedLocations = _emptyLocations;
                        result = virtualPath;
                        ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
                        break;
                    }
    
                    searchedLocations[i] = virtualPath;
                }
    
                return result;
            }
    
            private string CreateCacheKey(string prefix, string name, string controllerName)
            {
                return String.Format(CultureInfo.InvariantCulture, _cacheKeyFormat,
                    GetType().AssemblyQualifiedName, prefix, name, controllerName);
            }
    
            private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
            {
                string result = name;
    
                if (!FileExists(controllerContext, name))
                {
                    result = String.Empty;
                    searchedLocations = new[] { name };
                }
    
                ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, result);
                return result;
            }
    
            private static bool IsSpecificPath(string name)
            {
                char c = name[0];
                return (c == '~' || c == '/');
            }
    
        }
    }
    

    【讨论】:

    • 在我看来,这就像对 WebFormViewEngine 的完全重写。
    • 只是对使用上述代码的其他人的说明。您还应该以与实现 FindView 减去处理母版页文件/位置的代码类似的方式覆盖 FindPartialView。
    【解决方案3】:

    1) 从 razor 视图引擎扩展类

    public class LocalizationWebFormViewEngine : RazorViewEngine

    2) 添加部分位置格式

    public LocalizationWebFormViewEngine() 
    {
        base.PartialViewLocationFormats = new string[] {
            "~/Views/{2}/{1}/{0}.cshtml", 
            "~/Views/{2}/{1}/{0}.aspx", 
            "~/Views/{2}/Shared/{0}.cshtml", 
            "~/Views/{2}/Shared/{0}.aspx"
        };
    
        base.ViewLocationFormats = new string[] {
            "~/Views/{2}/{1}/{0}.cshtml", 
            "~/Views/{2}/{1}/{0}.aspx", 
            "~/Views/{2}/Shared/{0}.cshtml", 
            "~/Views/{2}/Shared/{0}.aspx"
        };
    }
    

    3) 创建局部视图渲染的覆盖方法

    public override ViewEngineResult FindPartialView(ControllerContext controllerContext, String partialViewName, Boolean useCache)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (String.IsNullOrEmpty(partialViewName))
        {
            throw new ArgumentException("partialViewName");
        }
    
        string[] partialViewLocationsSearched;
    
        string controllerName = controllerContext.RouteData.GetRequiredString("controller");
        string partialPath = GetPath(controllerContext, PartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, controllerName, _cacheKeyPrefix_Partial, useCache, out partialViewLocationsSearched);
    
        return new ViewEngineResult(CreatePartialView(controllerContext, partialPath), this);}
    }
    

    【讨论】:

    • GetPath 是私有方法,因此您将无法访问它。
    【解决方案4】:

    下面是一个没有重写的本地化视图引擎。

    简而言之,引擎会将新位置插入到视图位置每次查找视图。引擎将使用两种字符语言来查找视图。所以如果当前语言是es(西班牙语),它会寻找~/Views/Home/Index.es.cshtml

    查看代码 cmets 了解更多详情。

    更好的方法是覆盖视图位置的解析方式,但这些方法是不可覆盖的;也许在 ASP.NET MVC 5 中?

    public class LocalizedViewEngine : RazorViewEngine
    {
        private string[] _defaultViewLocationFormats;
    
        public LocalizedViewEngine()
            : base()
        {
            // Store the default locations which will be used to append
            // the localized view locations based on the thread Culture
            _defaultViewLocationFormats = base.ViewLocationFormats;
        }
    
        public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
        {
            AppendLocalizedLocations();
            return base.FindPartialView(controllerContext, partialViewName, useCache:fase);
        }
    
        public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
        {
            AppendLocalizedLocations();
            returnbase.FindView(controllerContext, viewName, masterName, useCache:false);
        }
    
        private void AppendLocalizedLocations()
        {
            // Use language two letter name to identify the localized view
            string lang = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;
    
            // Localized views will be in the format "{action}.{lang}.cshtml"
            string localizedExtension = string.Format(".{0}.cshtml", lang);
    
            // Create an entry for views and layouts using localized extension
            string view =  "~/Views/{1}/{0}.cshtml".Replace(".cshtml", localizedExtension);
            string shared =  "~/Views/{1}/Shared/{0}".Replace(".cshtml", localizedExtension);
    
            // Create a copy of the default view locations to modify
            var list = _defaultViewLocationFormats.ToList();
    
            // Insert the new locations at the top of the list of locations
            // so they're used before non-localized views.
            list.Insert(0, shared);
            list.Insert(0, view);
            base.ViewLocationFormats = list.ToArray();
        }
    }
    

    【讨论】:

    • 如果你有很多不同文化的请求,你会不会有他们互相踩踏的问题?
    【解决方案5】:

    一个简单的解决方案是,在您的Appication_Start 中,从ViewEngines.Engines 集合中获取适当的ViewEngine,并更新其ViewLocationFormats 数组和PartialViewLocationFormats。没有hackery:默认情况下它是读/写的。

    protected void Application_Start()
    {
        ...
        // Allow looking up views in ~/Features/ directory
        var razorEngine = ViewEngines.Engines.OfType<RazorViewEngine>().First();
        razorEngine.ViewLocationFormats = razorEngine.ViewLocationFormats.Concat(new string[] 
        { 
            "~/Features/{1}/{0}.cshtml"
        }).ToArray();
        ...
        // also: razorEngine.PartialViewLocationFormats if required
    }
    

    Razor 的默认looks like this

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

    请注意,您可能还想更新PartialViewLocationFormats

    【讨论】:

    • 这很好用......在运行时。但是,我似乎无法让 VS 2013(或者可能是 ReSharper)识别新的自定义位置。我失去了按 F12 进行定义的能力,并且该调用被标记为错误。您是否遇到同样的问题?我已经介绍了一个自定义的局部视图位置。谢谢。
    • “你是否遇到同样的问题”不,但我不使用 Resharper,所以我不熟悉你的工作;期待它做。
    • +1 表示不遵循大多数人的CustomViewEngine overkill 方法
    • 我遇到了下一个问题 - 对于来自单独 dll 的视图,布局会被忽略。如果我为此类视图指定布局,它将在下一个路径下寻找视图 - “~/Features/Views/Controller/_MyLayout.cshtml”,但不在“~/Features/Views/Shared/_MyLayout.cshtml”下,即使我为所有 LocationFormats 添加下一个路径 - “~/Features/Views/Shared/{0}.cshtml”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-18
    • 1970-01-01
    • 2011-01-01
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    相关资源
    最近更新 更多