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

105

J'ai la disposition suivante pour mon projet mvc:

  • / Contrôleurs
    • / Démo
    • / Démo / 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 l'espace de noms "Demo" recherchent dans le sous-dossier de la vue "Demo"?

Daniel Schaffer
la source
Voici un autre exemple d'un simple ViewEngine de l'application MVC Commerce de Rob Connery: Afficher le code du moteur et le code Global.asax.cs pour définir le ViewEngine: Global.asax.cs J'espère que cela vous aidera.
Robert Dean

Réponses:

121

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());
}
Sam Wessel
la source
Comment accéder au chemin d'une page maître à partir d'une page maître imbriquée? Comme pour définir la mise en page de
gabarit
6
N'est-ce pas mieux si nous ignorons la suppression des moteurs déjà enregistrés et ajoutons simplement le nouveau et viewLocations n'aura que les nouveaux?
Prasanna
3
Implémente sans ViewEngines.Engines.Clear (); Tout fonctionne bien. Si vous souhaitez utiliser * .cshtml, vous devez hériter de RazorViewEngine
KregHEk
existe-t-il un moyen de lier les options «ajouter une vue» et «accéder à la vue» des contrôleurs aux nouveaux emplacements de vue?
J'utilise
Comme mentionné par @Prasanna, il n'est pas nécessaire d'effacer les moteurs existants pour ajouter de nouveaux emplacements, voir cette réponse pour plus de détails.
Hooman Bahreini le
45

Désormais, dans MVC 6, vous pouvez implémenter l' IViewLocationExpanderinterface sans vous soucier des 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 default viewLocations( .Union(viewLocations)) ou simplement les modifier ( viewLocations.Select(path => "/AnotherPath" + path)).

Pour enregistrer votre expanseur d'emplacement de vue personnalisé dans MVC, ajoutez les lignes suivantes à la ConfigureServicesméthode dans le Startup.csfichier:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.ViewLocationExpanders.Add(new MyViewLocationExpander());
    });
}
whyleee
la source
3
J'aurais aimé pouvoir voter pour 10 voix supplémentaires. Est exactement ce qu'il faut dans Asp.net 5 / MVC 6. Belle. Très utile dans mon cas (et dans d'autres) lorsque vous souhaitez regrouper des zones en super zones pour des sites plus grands ou des regroupements logiques.
attiré
La partie Startup.cs doit être: services.Configure <RazorViewEngineOptions> Il va dans cette méthode: public void ConfigureServices (services IServiceCollection)
OrangeKing89
42

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.

Chris S
la source
23

Si vous voulez 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);

de même pour WebFormEngine

Marcelo De Zen
la source
2
Pour les vues: utilisez razorEngine.ViewLocationFormats.
Aldentev
13

Au lieu de sous-classer le RazorViewEngine, ou de le remplacer carrément, 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();
}
Simon Giles
la source
2
Cela a fonctionné pour moi, à l'exception du fait que le type de moteur de rasoir était «FixedRazorViewEngine» au lieu de «RazorViewEngine». Aussi, je lance une exception si le moteur n'a pas été trouvé car cela empêche mon application de s'initialiser avec succès.
Rob
3

La dernière fois que j'ai vérifié, cela vous oblige à créer votre propre ViewEngine. Je ne sais pas s'ils ont rendu les choses plus faciles en 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 des dossiers correspondant aux parties.

ÉDITER:

Je suis retourné 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;
}
Joël
la source
1
C'est en fait beaucoup plus facile. Sous-classe WebFormsViewEngine, puis ajoutez simplement au tableau de chemins qu'il recherche déjà dans votre constructeur.
Craig Stuntz
Bon à savoir. La dernière fois que j'ai eu besoin de modifier cette collection, ce n'était pas possible de cette manière.
Joel
Il convient de mentionner que vous devez définir la variable "baseControllerNamespace" sur votre espace de noms de contrôleur de base (par exemple, "Project.Controllers"), mais sinon, j'ai fait exactement ce dont j'avais besoin, 7 ans après avoir été publié.
prototype14
3

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);
}
Vitaliy Ulantikov
la source
3

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: Ceci est pour la version préliminaire 1 et est donc sujet à changement.

Simon_Weaver
la source
1

La plupart des réponses ici, effacez les emplacements existants en appelantViewEngines.Engines.Clear() , puis ajoutez-les à nouveau ... il n'est pas nécessaire de le faire.

Nous pouvons simplement ajouter les nouveaux emplacements aux emplacements existants, comme indiqué ci-dessous:

// note that the base class is RazorViewEngine, NOT WebFormViewEngine
public class ExpandedViewEngine : RazorViewEngine
{
    public ExpandedViewEngine()
    {
        var customViewSubfolders = new[] 
        {
            // {1} is conroller name, {0} is action name
            "~/Areas/AreaName/Views/Subfolder1/{1}/{0}.cshtml",
            "~/Areas/AreaName/Views/Subfolder1/Shared/{0}.cshtml"
        };

        var customPartialViewSubfolders = new[] 
        {
            "~/Areas/MyAreaName/Views/Subfolder1/{1}/Partials/{0}.cshtml",
            "~/Areas/MyAreaName/Views/Subfolder1/Shared/Partials/{0}.cshtml"
        };

        ViewLocationFormats = ViewLocationFormats.Union(customViewSubfolders).ToArray();
        PartialViewLocationFormats = PartialViewLocationFormats.Union(customPartialViewSubfolders).ToArray();

        // use the following if you want to extend the master locations
        // MasterLocationFormats = MasterLocationFormats.Union(new[] { "new master location" }).ToArray();   
    }
}

Vous pouvez maintenant configurer votre projet pour utiliser ce qui précède RazorViewEnginedans Global.asax:

protected void Application_Start()
{
    ViewEngines.Engines.Add(new ExpandedViewEngine());
    // more configurations
}

Voir ce tutoriel pour plus d'informations.

Hooman Bahreini
la source