2009-05-26 10 views
30

Quiero cambiar las ubicaciones de visualización en el tiempo de ejecución según la cultura de IU actual. ¿Cómo puedo lograr esto con el motor de vista de Web Form predeterminado?¿Cómo cambiar el esquema de ubicación de vista predeterminado en ASP.NET MVC?

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

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


Editar: Mis direcciones URL deben miradas siguiente {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.

Respuesta

31

Una solución sencilla sería, en su bodega Appication_Start get de la adecuada ViewEngine de la colección ViewEngines.Engines y actualizar su gama ViewLocationFormats 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<RazorViewEngine>().First(); 
    razorEngine.ViewLocationFormats = razorEngine.ViewLocationFormats.Concat(new string[] 
    { 
     "~/Features/{1}/{0}.cshtml" 
    }).ToArray(); 
    ... 
    // also: razorEngine.PartialViewLocationFormats if required 
} 

El único defecto para la maquinilla de afeitar looks like this:

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

Nota que es posible que desee actualizar PartialViewLocationFormats también.

+0

Esto funcionó bien ... en tiempo de ejecución. Sin embargo, parece que no puedo obtener VS 2013 (o posiblemente sea ReSharper) para reconocer la nueva ubicación personalizada. Perdí la capacidad de F12 en la definición y la llamada se marcó como un error. ¿Estás experimentando el mismo problema? Introduje una ubicación de vista parcial personalizada. Gracias. –

+0

"¿Tiene el mismo problema?" No, pero no uso Resharper, así que no estoy familiarizado con lo que espera que haga. –

+2

+1 por no seguir el método de sobrecarga 'CustomView Engine' de la mayoría – Brad

1

Creo que la solución sería crear su 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 debe ser algo como esto:

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()); 
+1

También podría ver la implementación en el proyecto http://www.codeplex.com/oxite. – pocheptsov

+2

Disculpe, esta no es una buena solución, ya que la instancia de ViewEngine se comparte entre los hilos y necesito renderizar una vista diferente en función de la cultura de UI del hilo. –

+0

Quizás es posible agregar viewEngine para cada cultura y reemplazar los métodos de findView para interrumpirlos, si thread es diferente? Solo una idea extraña ... –

8

VirtualPathProviderViewEngine.GetPathFromGeneralName debe ser cambiado para permitir que un parámetro adicional de la ruta. No es público, es por eso que tiene que copiar GetPath, GetPathFromGeneralName, IsSpecificPath ... en su propia implementación ViewEngine.

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

    } 
} 
+0

Me parece una reescritura completa de WebFormViewEngine. –

+1

Solo una nota para otros que utilizan código como el anterior. También debe reemplazar FindPartialView de manera similar que FindView se implementa menos el código relacionado con el archivo/ubicaciones de la página maestra. – sdanna

3

1) Ampliación de la clase de motor de vista de afeitar

public class LocalizationWebFormViewEngine : RazorViewEngine

2) Añadir los formatos de localización parciales

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 reemplazo para la vista parcial rendir

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

GetPath es un método privado, por lo que no podrá acceder a él. –

1

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

En pocas palabras, el motor insertará nuevas ubicaciones en las ubicaciones de vista siempre se busca 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.

Consulte los comentarios del código para obtener más información.

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(); 
    } 
} 
+1

Si tiene muchas solicitudes con culturas diferentes, ¿no tendrá problemas con que se pisen entre sí? –

+0

Lo que dijo Brian. No se ve seguro para subprocesos –

Cuestiones relacionadas