Подтвердить что ты не робот

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

Я хочу изменить места просмотра во время выполнения на основе текущей культуры пользовательского интерфейса. Как я могу добиться этого с помощью механизма просмотра веб-форм по умолчанию?

В принципе, я хочу знать, как реализовать с WebFormViewEngine что-то, что custom IDescriptorFilter в Spark.

Есть ли другой механизм просмотра, который дает мне контроль над просмотром местоположений?


Изменить: Мои URL-адреса должны выглядеть следующим образом {lang}/{controller}/{action}/{id}. Мне не нужны языковые контроллеры, и представления локализованы с помощью ресурсов. Однако некоторые из них будут отличаться на некоторых языках. Поэтому мне нужно сказать, что движок просмотра сначала смотрит на папку с конкретным языком.

4b9b3361

Ответ 1

Простое решение состоит в том, чтобы в Appication_Start получить соответствующий ViewEngine из коллекции ViewEngines.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
}

Значение по умолчанию для Razor выглядит следующим образом:

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

Примечание, которое вы также можете обновить PartialViewLocationFormats.

Ответ 2

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 == '/');
        }

    }
}

Ответ 3

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

Ответ 4

Я считаю, что решением было бы создать собственный 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());

Ответ 5

Ниже представлен локализованный механизм просмотра без перезаписи.

Вкратце, двигатель будет вставлять новые местоположения в места просмотра каждый раз при просмотре вида. Для поиска просмотра движок использует два символьных языка. Поэтому, если текущий язык 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();
    }
}