如何更改 ASP.NET MVC 中的默认视图位置方案?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/909794/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 12:45:37  来源:igfitidea点击:

How to change default view location scheme in ASP.NET MVC?

.netasp.net-mvcviewengine

提问by Jakub ?turc

I want to change view locations at runtime based on current UI culture. How can I achieve this with default Web Form view engine?

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

Basically I want to know how implement with WebFormViewEnginesomething what is custom IDescriptorFilterin Spark.

基本上我想知道如何与实现WebFormViewEngine的东西是什么风俗IDescriptorFilter星火

Is there other view engine which gives me runtime control over view locations?

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



Edit:My URLs should looks following {lang}/{controller}/{action}/{id}. I don't need language dependent controllers and views are localized with resources. However few of the views will be different in some languages. So I need to tell view engine to looks to the language specific folder first.

编辑:我的网址应该如下所示{lang}/{controller}/{action}/{id}。我不需要依赖于语言的控制器,并且视图是用资源本地化的。然而,在某些语言中,很少有视图会有所不同。所以我需要告诉视图引擎首先查看语言特定的文件夹。

回答by Duncan Smart

A simple solution would be to, in your Appication_Startget hold of the appropriate ViewEnginefrom the ViewEngines.Enginescollection and update its ViewLocationFormatsarray and PartialViewLocationFormats. No hackery: it's read/write by default.

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

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
}

The default one for Razor looks like this:

Razor 的默认设置如下所示

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

Notethat you may want to update PartialViewLocationFormatsalso.

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

回答by Mathias F

VirtualPathProviderViewEngine.GetPathFromGeneralNamemust be changed to allow an additional parameter from the route. Its not public, that's why you have to copy GetPath, GetPathFromGeneralName, IsSpecificPath...over to your own ViewEngineimplementation.

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

You are right: this looks like a complete rewrite. I wished GetPathFromGeneralNamewas public.

你是对的:这看起来像是完全重写。我希望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 == '/');
        }

    }
}

回答by Fellipe

1) Extend the class from razor view engine

1)从剃刀视图引擎扩展类

public class LocalizationWebFormViewEngine : RazorViewEngine

public class LocalizationWebFormViewEngine : RazorViewEngine

2) Add the partial location formats

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) Create the override method for partial view render

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

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

回答by Omar

Below is a localized view engine without the rewrite.

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

In a nutshell, the engine will insert new locations into the view locations everytimea view is looked up. The engine will use the two character language to find the view. So if the current language is es(Spanish), it'll look for ~/Views/Home/Index.es.cshtml.

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

See code comments for more details.

有关更多详细信息,请参阅代码注释。

A better approach would be to override the way view locations are parsed, but the methods are not overridable; maybe in ASP.NET MVC 5?

更好的方法是覆盖解析视图位置的方式,但这些方法不可覆盖;也许在 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();
    }
}

回答by Arnis Lapsa

I believe that solution would be to create your own ViewEngine which inherits from WebFormViewEngine. In constructor, it should check current UI culture from current thread and add appropriate locations. Just don't forget to add it to your view engines.

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

This should look something like this:

这应该是这样的:

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

in global.asax:

在 global.asax 中:

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