2009-05-26 6 views
30

Je souhaite modifier les emplacements d'affichage lors de l'exécution en fonction de la culture d'interface utilisateur actuelle. Comment puis-je y parvenir avec le moteur d'affichage Web Form par défaut?Comment modifier le schéma d'emplacement de vue par défaut dans ASP.NET MVC?

Fondamentalement, je veux savoir comment mettre en œuvre avec WebFormViewEngine quelque chose ce qui est custom IDescriptorFilter en Spark.

Existe-t-il un autre moteur d'affichage qui me donne le contrôle d'exécution sur les emplacements d'affichage?


Edit: Mes URL doivent {lang}/{controller}/{action}/{id} regards suivants. Je n'ai pas besoin de contrôleurs dépendant de la langue et les vues sont localisées avec des ressources. Cependant, peu de vues seront différentes dans certaines langues. Donc j'ai besoin de dire au moteur de vue de regarder d'abord le dossier spécifique à la langue.

Répondre

31

Une solution simple serait, dans votre Appication_Start mettre la main sur la ViewEngine appropriée de la collection ViewEngines.Engines et mettre à jour sa gamme ViewLocationFormats et PartialViewLocationFormats. Pas de piratage: c'est en lecture/écriture par défaut.

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 
} 

L'un par défaut pour Razor looks like this:

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

Remarque que vous pouvez mettre à jour PartialViewLocationFormats aussi.

+0

Cela a bien fonctionné ... à l'exécution. Cependant, je ne peux pas sembler obtenir VS 2013 (ou peut-être ReSharper) pour reconnaître le nouvel emplacement personnalisé. J'ai perdu la possibilité de F12 à la définition et l'appel est marqué comme une erreur. Vivez-vous le même problème? J'ai introduit un emplacement de vue partielle personnalisé. Merci. –

+0

"Vous rencontrez le même problème" non, mais je n'utilise pas Resharper, donc je ne suis pas familier avec ce que vous attendez de lui. –

+2

+1 pour ne pas suivre la méthode d'overkill 'CustomViewEngine' de la plupart – Brad

1

Je crois que cette solution serait de créer votre propre ViewEngine qui hérite de WebFormViewEngine. Dans le constructeur, il doit vérifier la culture actuelle de l'interface utilisateur à partir du thread actuel et ajouter les emplacements appropriés. N'oubliez pas de l'ajouter à vos moteurs de vue.

Cela devrait ressembler à ceci:

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

dans global.asax:

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

Vous pouvez également voir la mise en œuvre dans le projet http://www.codeplex.com/oxite. – pocheptsov

+2

Désolé, ce n'est pas une bonne solution car l'instance de ViewEngine est partagée entre les threads et j'ai besoin de rendre la vue différente en fonction de la culture de l'interface utilisateur du thread. –

+0

Peut-être qu'il est possible d'ajouter viewEngine pour chaque culture et remplacer les méthodes findView pour les interrompre, si le thread est différent? Juste une idée bizarre ... –

8

VirtualPathProviderViewEngine.GetPathFromGeneralName doit être modifiée pour permettre un paramètre supplémentaire de la route. Ce n'est pas public, c'est pourquoi vous devez copier GetPath, GetPathFromGeneralName, IsSpecificPath ... sur votre propre implémentation ViewEngine.

Vous avez raison: cela ressemble à une réécriture complète. Je souhaite GetPathFromGeneralName était public.

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

Il me semble comme réécriture complète de WebFormViewEngine. –

+1

Juste une note à d'autres qui utilisent le code comme ci-dessus. Vous devez également remplacer FindPartialView de la même manière que FindView est implémenté moins le code traitant des fichiers/emplacements de la page maître. – sdanna

3

1) étendre la classe de moteur de vue de rasoir

public class LocalizationWebFormViewEngine : RazorViewEngine

2) Ajouter les formats de localisation partielles

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) Insérer la méthode de remplacement pour une vue partielle rendre

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 est une méthode privée de sorte que vous ne serez pas en mesure d'y accéder. –

1

Voici un moteur de vue localisé sans la réécriture. En bref, le moteur insérera de nouveaux emplacements dans les vues à chaque fois une vue est recherchée. Le moteur utilisera le langage à deux caractères pour trouver la vue. Donc, si la langue actuelle est es (en espagnol), cela va chercher ~/Views/Home/Index.es.cshtml.

Voir les commentaires de code pour plus de détails.

Une meilleure approche consisterait à remplacer la façon dont les emplacements de vue sont analysés, mais les méthodes ne sont pas redéfinissables; peut-être dans 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 vous avez beaucoup de demandes avec des cultures différentes, n'auriez-vous pas des problèmes avec eux se marchant les uns sur les autres? –

+0

Ce que Brian a dit. Ne semble pas thread-safe. –

Questions connexes