web-dev-qa-db-fra.com

Puis-je spécifier un emplacement personnalisé pour "rechercher des vues" dans ASP.NET MVC?

J'ai la disposition suivante pour mon projet mvc:

  • /Controllers
    • / Démo
    • / Demo/DemoArea1Controller
    • / Démo/DemoArea2Controller
    • etc...
  • /Vues
    • / Démo
    • /Demo/DemoArea1/Index.aspx
    • /Demo/DemoArea2/Index.aspx

Cependant, quand j'ai ceci pour DemoArea1Controller:

public class DemoArea1Controller : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

J'obtiens l'erreur "La vue 'index' ou son maître est introuvable", avec les emplacements de recherche habituels.

Comment puis-je spécifier que les contrôleurs dans la recherche d'espace de noms "Demo" dans le sous-dossier de vue "Demo"?

102
Daniel Schaffer

Vous pouvez facilement étendre WebFormViewEngine pour spécifier tous les emplacements dans lesquels vous souhaitez rechercher:

public class CustomViewEngine : WebFormViewEngine
{
    public CustomViewEngine()
    {
        var viewLocations =  new[] {  
            "~/Views/{1}/{0}.aspx",  
            "~/Views/{1}/{0}.ascx",  
            "~/Views/Shared/{0}.aspx",  
            "~/Views/Shared/{0}.ascx",  
            "~/AnotherPath/Views/{0}.ascx"
            // etc
        };

        this.PartialViewLocationFormats = viewLocations;
        this.ViewLocationFormats = viewLocations;
    }
}

N'oubliez pas d'enregistrer le moteur de vue en modifiant la méthode Application_Start dans votre Global.asax.cs

protected void Application_Start()
{
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new CustomViewEngine());
}
119
Sam Wessel

Maintenant, dans MVC 6, vous pouvez implémenter l'interface IViewLocationExpander sans déconner avec les moteurs de vue:

public class MyViewLocationExpander : IViewLocationExpander
{
    public void PopulateValues(ViewLocationExpanderContext context) {}

    public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
    {
        return new[]
        {
            "/AnotherPath/Views/{1}/{0}.cshtml",
            "/AnotherPath/Views/Shared/{0}.cshtml"
        }; // add `.Union(viewLocations)` to add default locations
    }
}

{0} est le nom de la vue cible, {1} - le nom du contrôleur et {2} - le nom de la zone.

Vous pouvez renvoyer votre propre liste d'emplacements, la fusionner avec la valeur par défaut viewLocations (.Union(viewLocations)) ou simplement les modifier (viewLocations.Select(path => "/AnotherPath" + path)).

Pour enregistrer votre expanseur d'emplacement d'affichage personnalisé dans MVC, ajoutez les lignes suivantes à la méthode ConfigureServices dans le fichier Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.ViewLocationExpanders.Add(new MyViewLocationExpander());
    });
}
43
whyleee

Il existe en fait une méthode beaucoup plus simple que de coder en dur les chemins dans votre constructeur. Vous trouverez ci-dessous un exemple d'extension du moteur Razor pour ajouter de nouveaux chemins. Une chose dont je ne suis pas tout à fait sûr est de savoir si les chemins que vous ajoutez ici seront mis en cache:

public class ExtendedRazorViewEngine : RazorViewEngine
{
    public void AddViewLocationFormat(string paths)
    {
        List<string> existingPaths = new List<string>(ViewLocationFormats);
        existingPaths.Add(paths);

        ViewLocationFormats = existingPaths.ToArray();
    }

    public void AddPartialViewLocationFormat(string paths)
    {
        List<string> existingPaths = new List<string>(PartialViewLocationFormats);
        existingPaths.Add(paths);

        PartialViewLocationFormats = existingPaths.ToArray();
    }
}

Et votre Global.asax.cs

protected void Application_Start()
{
    ViewEngines.Engines.Clear();

    ExtendedRazorViewEngine engine = new ExtendedRazorViewEngine();
    engine.AddViewLocationFormat("~/MyThemes/{1}/{0}.cshtml");
    engine.AddViewLocationFormat("~/MyThemes/{1}/{0}.vbhtml");

    // Add a shared location too, as the lines above are controller specific
    engine.AddPartialViewLocationFormat("~/MyThemes/{0}.cshtml");
    engine.AddPartialViewLocationFormat("~/MyThemes/{0}.vbhtml");

    ViewEngines.Engines.Add(engine);

    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
}

Une chose à noter: votre emplacement personnalisé aura besoin du fichier ViewStart.cshtml à sa racine.

40
Chris S

Si vous souhaitez simplement ajouter de nouveaux chemins, vous pouvez ajouter aux moteurs de vue par défaut et épargner quelques lignes de code:

ViewEngines.Engines.Clear();
var razorEngine = new RazorViewEngine();
razorEngine.MasterLocationFormats = razorEngine.MasterLocationFormats
      .Concat(new[] { 
          "~/custom/path/{0}.cshtml" 
      }).ToArray();

razorEngine.PartialViewLocationFormats = razorEngine.PartialViewLocationFormats
      .Concat(new[] { 
          "~/custom/path/{1}/{0}.cshtml",   // {1} = controller name
          "~/custom/path/Shared/{0}.cshtml" 
      }).ToArray();

ViewEngines.Engines.Add(razorEngine);

La même chose s'applique à WebFormEngine

22
Marcelo De Zen

Au lieu de sous-classer RazorViewEngine ou de le remplacer purement et simplement, vous pouvez simplement modifier la propriété PartialViewLocationFormats de RazorViewEngine existante. Ce code va dans Application_Start:

System.Web.Mvc.RazorViewEngine rve = (RazorViewEngine)ViewEngines.Engines
  .Where(e=>e.GetType()==typeof(RazorViewEngine))
  .FirstOrDefault();

string[] additionalPartialViewLocations = new[] { 
  "~/Views/[YourCustomPathHere]"
};

if(rve!=null)
{
  rve.PartialViewLocationFormats = rve.PartialViewLocationFormats
    .Union( additionalPartialViewLocations )
    .ToArray();
}
13
Simon Giles

Remarque: pour ASP.NET MVC 2, ils ont des chemins d'emplacement supplémentaires que vous devrez définir pour les vues dans "Zones".

 AreaViewLocationFormats
 AreaPartialViewLocationFormats
 AreaMasterLocationFormats

La création d'un moteur de vue pour une zone est décrite sur le blog de Phil .

Remarque: il s'agit de la version 1 de l'aperçu. Elle peut donc être modifiée.

3
Simon_Weaver

Essayez quelque chose comme ceci:

private static void RegisterViewEngines(ICollection<IViewEngine> engines)
{
    engines.Add(new WebFormViewEngine
    {
        MasterLocationFormats = new[] {"~/App/Views/Admin/{0}.master"},
        PartialViewLocationFormats = new[] {"~/App/Views/Admin//{1}/{0}.ascx"},
        ViewLocationFormats = new[] {"~/App/Views/Admin//{1}/{0}.aspx"}
    });
}

protected void Application_Start()
{
    RegisterViewEngines(ViewEngines.Engines);
}
3
Vitaliy Ulantikov

La dernière fois que j'ai vérifié, cela vous oblige à créer votre propre ViewEngine. Je ne sais pas s'ils ont facilité les choses dans RC1.

L'approche de base que j'ai utilisée avant le premier RC était, dans mon propre ViewEngine, de diviser l'espace de noms du contrôleur et de rechercher les dossiers qui correspondaient aux parties.

MODIFIER:

Je suis revenu et j'ai trouvé le code. Voici l'idée générale.

public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName)
{
    string ns = controllerContext.Controller.GetType().Namespace;
    string controller = controllerContext.Controller.GetType().Name.Replace("Controller", "");

    //try to find the view
    string rel = "~/Views/" +
        (
            ns == baseControllerNamespace ? "" :
            ns.Substring(baseControllerNamespace.Length + 1).Replace(".", "/") + "/"
        )
        + controller;
    string[] pathsToSearch = new string[]{
        rel+"/"+viewName+".aspx",
        rel+"/"+viewName+".ascx"
    };

    string viewPath = null;
    foreach (var path in pathsToSearch)
    {
        if (this.VirtualPathProvider.FileExists(path))
        {
            viewPath = path;
            break;
        }
    }

    if (viewPath != null)
    {
        string masterPath = null;

        //try find the master
        if (!string.IsNullOrEmpty(masterName))
        {

            string[] masterPathsToSearch = new string[]{
                rel+"/"+masterName+".master",
                "~/Views/"+ controller +"/"+ masterName+".master",
                "~/Views/Shared/"+ masterName+".master"
            };


            foreach (var path in masterPathsToSearch)
            {
                if (this.VirtualPathProvider.FileExists(path))
                {
                    masterPath = path;
                    break;
                }
            }
        }

        if (string.IsNullOrEmpty(masterName) || masterPath != null)
        {
            return new ViewEngineResult(
                this.CreateView(controllerContext, viewPath, masterPath), this);
        }
    }

    //try default implementation
    var result = base.FindView(controllerContext, viewName, masterName);
    if (result.View == null)
    {
        //add the location searched
        return new ViewEngineResult(pathsToSearch);
    }
    return result;
}
3
Joel