¿Cómo cambiar el esquema de ubicación de vista predeterminado en ASP.NET MVC?

Quiero cambiar las ubicaciones de visualización en tiempo de ejecución en función de la cultura de IU actual. ¿Cómo puedo lograr esto con el motor de vista de Web Form predeterminado?

Básicamente, quiero saber cómo implementar con WebFormViewEngine algo que es IDescriptorFilter personalizado en Spark .

¿Hay otro motor de vista que me da control de tiempo de ejecución sobre las ubicaciones de visualización?


Editar: Mis URL deben verse después de {lang}/{controller}/{action}/{id} . No necesito controladores dependientes del idioma y las vistas se localizan con recursos. Sin embargo, algunas de las vistas serán diferentes en algunos idiomas. Así que primero necesito decirle a view engine que mires a la carpeta específica del idioma.

Una solución simple sería, en su Appication_Start obtener el apropiado ViewEngine de la colección ViewEngines.Engines y actualizar su ViewLocationFormats array y PartialViewLocationFormats . Sin piratería: es de lectura / escritura por defecto.

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

El predeterminado para Razor se ve así :

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

Tenga en cuenta que es posible que desee actualizar PartialViewLocationFormats también.

VirtualPathProviderViewEngine.GetPathFromGeneralName debe cambiar para permitir un parámetro adicional de la ruta. No es público, es por eso que debes copiar GetPath , GetPathFromGeneralName , IsSpecificPath … a tu propia implementación de ViewEngine .

Tienes razón: esto parece una reescritura completa. Ojalá GetPathFromGeneralName fuera público.

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

1) Extienda la clase del motor de vista de afeitar

public class LocalizationWebFormViewEngine : RazorViewEngine

2) Agregue los formatos de ubicación parcial

 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) Crear el método de anulación para render de vista parcial

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

Creo que esa solución sería crear tu propio ViewEngine que hereda de WebFormViewEngine. En el constructor, debe verificar la cultura de UI actual del hilo actual y agregar las ubicaciones apropiadas. Solo no olvides agregarlo a tus motores de visualización.

Esto debería verse más o menos así:

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

en global.asax:

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

A continuación se muestra un motor de vista localizada sin la reescritura.

En pocas palabras, el motor insertará nuevas ubicaciones en las ubicaciones de vista cada vez que se busque una vista. El motor usará el lenguaje de dos caracteres para encontrar la vista. Entonces, si el idioma actual es es (español), buscará ~/Views/Home/Index.es.cshtml .

Vea los comentarios del código para más detalles.

Un mejor enfoque sería anular la forma en que se analizan las ubicaciones de las vistas, pero los métodos no son reemplazables; tal vez en 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(); } }