Как да променя схемата за местоположение на изгледа по подразбиране в ASP.NET MVC?

Искам да променя местоположенията на изгледа по време на изпълнение въз основа на текущата култура на потребителския интерфейс. Как мога да постигна това с машината за преглед на уеб формуляри по подразбиране?

По принцип искам да знам как да внедря с WebFormViewEngine нещо какво е персонализиран IDescriptorFilter в Spark.

Има ли друга машина за изгледи, която ми дава контрол по време на изпълнение върху местоположенията на изгледите?


Редактиране: URL адресите ми трябва да изглеждат след {lang}/{controller}/{action}/{id}. Нямам нужда от зависими от езика контролери и изгледите са локализирани с ресурси. Малко от изгледите обаче ще се различават на някои езици. Така че трябва да кажа на двигателя за преглед първо да търси папката за специфичния език.


person Jakub Šturc    schedule 26.05.2009    source източник


Отговори (5)


Едно просто решение би било във вашия Appication_Start да вземете подходящия ViewEngine от колекцията ViewEngines.Engines и да актуализирате неговия ViewLocationFormats масив и PartialViewLocationFormats< /a>. Без хакерство: чете/записва по подразбиране.

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 изглежда така:

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

Имайте предвид, че може да искате да актуализирате PartialViewLocationFormats< /a> също.

person Duncan Smart    schedule 29.09.2013
comment
Това работи добре... по време на изпълнение. Изглежда обаче не мога да накарам VS 2013 (или вероятно да е ReSharper) да разпознае новото персонализирано местоположение. Загубих способността за F12 към дефиницията и извикването е маркирано като грешка. Изпитвате ли същия проблем? Въведох персонализирано местоположение за частичен изглед. Благодаря. - person Vinney Kelly; 31.10.2013
comment
Имате ли същия проблем, не, но аз не използвам Resharper, така че не съм запознат с това, което очаквате да направи. - person Duncan Smart; 31.10.2013
comment
+1 за неспазване на CustomViewEngine метода на прекомерно убиване на повечето - person Brad; 10.05.2014
comment
Сблъсках се със следващия проблем - оформленията се игнорират за изгледи от отделни dll. Ако посоча Layout за такъв изглед, той ще търси изглед под следващия път - ~/Features/Views/Controller/_MyLayout.cshtml, но не и под ~/Features/Views/Shared/_MyLayout.cshtml, дори добавям следващ път за всички LocationFormats - ~/Features/Views/Shared/{0}.cshtml - person Anton Putov; 26.07.2014

VirtualPathProviderViewEngine.GetPathFromGeneralName трябва да се промени, за да позволи допълнителен параметър от маршрута. Не е публичен, затова трябва да копирате GetPath, GetPathFromGeneralName, IsSpecificPath ...във собствената си реализация 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 == '/');
        }

    }
}
person Mathias F    schedule 26.05.2009
comment
Изглежда ми като пълно пренаписване на WebFormViewEngine. - person Jakub Šturc; 27.05.2009
comment
Само бележка за другите, които използват код като горния. Трябва също така да замените FindPartialView по подобен начин, по който FindView е имплементиран, минус кода, който се занимава с главния файл/местоположения на страницата. - person sdanna; 07.07.2010

1) Разширете класа от машината за изглед на бръснач

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);}
}
person Fellipe    schedule 28.12.2010
comment
GetPath е частен метод, така че няма да имате достъп до него. - person Morten Christiansen; 31.03.2011

Вярвам, че това решение би било да създадете свой собствен ViewEngine, който наследява от WebFormViewEngine. В конструктора трябва да провери текущата култура на потребителския интерфейс от текущата нишка и да добави подходящи местоположения. Само не забравяйте да го добавите към вашите машини за преглед.

Това трябва да изглежда нещо подобно:

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());
person Arnis Lapsa    schedule 26.05.2009
comment
Съжалявам, това не е добро решение, тъй като екземплярът на ViewEngine се споделя между нишките и трябва да изобразя различен изглед въз основа на културата на потребителския интерфейс на нишката. - person Jakub Šturc; 26.05.2009
comment
Може би е възможно да добавите viewEngine за всяка култура и да замените методите findView, за да ги прекъснете, ако нишката е различна? Просто странна идея... - person Arnis Lapsa; 26.05.2009
comment
@Арнис Л.: Странно е. Но ще го пробвам. Може би ще открия, че ми харесва. - person Jakub Šturc; 26.05.2009
comment
@pocheptsov: В момента разглеждам изходния код на Oxite. Там има много страхотни идеи. Въпреки това не мога да намеря такъв, който да ми помогне с този конкретен проблем. - person Jakub Šturc; 26.05.2009

По-долу е локализиран механизъм за преглед без пренаписване.

Накратко, двигателят ще вмъква нови местоположения в местоположенията на изгледа всеки път, когато се търси изглед. Машината ще използва езика с два знака, за да намери изгледа. Така че, ако текущият език е es (испански), той ще търси ~/Views/Home/Index.es.cshtml.

Вижте коментарите на кода за повече подробности.

По-добър подход би бил да се замени начинът, по който местоположенията на изгледите се анализират, но методите не могат да се заменят; може би в 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();
    }
}
person Omar    schedule 23.01.2013
comment
Ако имате много заявки с различни култури, няма ли да имате проблеми с това, че те се настъпват една върху друга? - person Brian Reischl; 15.03.2013