Kann ich einen benutzerdefinierten Speicherort für die Suche nach Ansichten in ASP.NET MVC angeben?

105

Ich habe das folgende Layout für mein MVC-Projekt:

  • / Controller
    • /Demo
    • / Demo / DemoArea1Controller
    • / Demo / DemoArea2Controller
    • etc...
  • / Ansichten
    • /Demo
    • /Demo/DemoArea1/Index.aspx
    • /Demo/DemoArea2/Index.aspx

Wenn ich dies jedoch habe für DemoArea1Controller:

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

Ich erhalte den Fehler "Der Ansichtsindex oder sein Master konnte nicht gefunden werden" mit den üblichen Suchpositionen.

Wie kann ich festlegen, dass Controller in der Namespace-Suche "Demo" im Unterordner der Ansicht "Demo" angezeigt werden?

Daniel Schaffer
quelle
Hier ist ein weiteres Beispiel einer einfachen ViewEngine aus der MVC Commerce-App von Rob Connery: View Engine Code und der Global.asax.cs-Code zum Festlegen der ViewEngine: Global.asax.cs Hoffe, dies hilft.
Robert Dean

Antworten:

121

Sie können die WebFormViewEngine einfach erweitern, um alle Speicherorte anzugeben, an denen Sie suchen möchten:

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

Stellen Sie sicher, dass Sie daran denken, die View Engine zu registrieren, indem Sie die Application_Start-Methode in Ihrer Global.asax.cs ändern

protected void Application_Start()
{
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new CustomViewEngine());
}
Sam Wessel
quelle
Wie können Sie von einer verschachtelten Masterseite aus auf den Pfad einer Masterseite zugreifen? Wie beim Festlegen des verschachtelten Masterseitenlayouts für die Suche in den Pfaden der CustomViewEngine
Drahcir
6
Ist es nicht besser, wenn wir das Löschen der bereits registrierten Engines überspringen und nur die neue hinzufügen und viewLocations nur die neuen haben soll?
Prasanna
3
Implementiert ohne ViewEngines.Engines.Clear (); Alle funktionieren gut. Wenn Sie * .cshtml verwenden möchten, müssen Sie von RazorViewEngine
KregHEk
Gibt es eine Möglichkeit, die Optionen "Ansicht hinzufügen" und "Zur Ansicht wechseln" von den Controllern mit den neuen Ansichtspositionen zu verknüpfen? Ich benutze Visual Studio 2012
Neville Nazerane
Wie von @Prasanna erwähnt, müssen die vorhandenen Engines nicht gelöscht werden, um neue Standorte hinzuzufügen. Weitere Informationen finden Sie in dieser Antwort .
Hooman Bahreini
44

Jetzt können Sie in MVC 6 die IViewLocationExpanderSchnittstelle implementieren , ohne mit View Engines herumzuspielen:

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

Wo {0}ist der Name der Zielansicht, {1}- Name des Controllers und {2}- Name des Bereichs.

Sie können Ihre eigene Liste von Speicherorten zurückgeben, mit default viewLocations( .Union(viewLocations)) zusammenführen oder einfach ändern ( viewLocations.Select(path => "/AnotherPath" + path)).

Fügen Sie der ConfigureServicesMethode in der Startup.csDatei die folgenden Zeilen hinzu, um Ihren benutzerdefinierten Ansichtsstandort-Expander in MVC zu registrieren :

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.ViewLocationExpanders.Add(new MyViewLocationExpander());
    });
}
Whyleee
quelle
3
Ich wünschte, ich könnte dies mit 10 Stimmen abstimmen. Ist genau das, was in Asp.net 5 / MVC 6 benötigt wird. Schön. Sehr nützlich in meinem Fall (und anderen), wenn Sie Bereiche für größere Sites oder logische Gruppierungen in Superbereiche gruppieren möchten.
Drawid
Der Startup.cs-Teil sollte sein: services.Configure <RazorViewEngineOptions> Er geht in diese Methode: public void ConfigureServices (IServiceCollection-Dienste)
OrangeKing89
42

Es gibt tatsächlich eine viel einfachere Methode, als die Pfade in Ihren Konstruktor fest zu codieren. Unten finden Sie ein Beispiel für die Erweiterung der Razor-Engine, um neue Pfade hinzuzufügen. Ich bin mir nicht ganz sicher, ob die hier hinzugefügten Pfade zwischengespeichert werden:

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

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

Beachten Sie Folgendes: Ihr benutzerdefinierter Speicherort benötigt die Datei ViewStart.cshtml im Stammverzeichnis.

Chris S.
quelle
23

Wenn Sie nur neue Pfade hinzufügen möchten, können Sie die Standard-Ansichtsmodule hinzufügen und einige Codezeilen sparen:

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

Gleiches gilt für WebFormEngine

Marcelo De Zen
quelle
2
Für Ansichten: Verwenden Sie razorEngine.ViewLocationFormats.
Aldentev
13

Anstatt die RazorViewEngine in Unterklassen zu unterteilen oder sie vollständig zu ersetzen, können Sie einfach die PartialViewLocationFormats-Eigenschaft der vorhandenen RazorViewEngine ändern. Dieser Code geht in 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
quelle
2
Dies funktionierte für mich mit der Ausnahme, dass der Typ der Rasiermaschine "FixedRazorViewEngine" anstelle von "RazorViewEngine" war. Außerdem löst ich eine Ausnahme aus, wenn die Engine nicht gefunden wurde, da dadurch verhindert wird, dass meine Anwendung erfolgreich initialisiert wird.
Rob
3

Zuletzt habe ich überprüft, dass Sie Ihre eigene ViewEngine erstellen müssen. Ich weiß nicht, ob sie es in RC1 einfacher gemacht haben.

Der grundlegende Ansatz, den ich vor dem ersten RC verwendet habe, bestand darin, in meiner eigenen ViewEngine den Namespace des Controllers aufzuteilen und nach Ordnern zu suchen, die den Teilen entsprechen.

BEARBEITEN:

Ging zurück und fand den Code. Hier ist die allgemeine Idee.

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;
}
Joel
quelle
1
Es ist eigentlich viel einfacher. Unterklasse WebFormsViewEngine und fügen Sie dann einfach das Array von Pfaden hinzu, die bereits in Ihrem Konstruktor gesucht werden.
Craig Stuntz
Gut zu wissen. Das letzte Mal, als ich diese Sammlung ändern musste, war dies nicht möglich.
Joel
Erwähnenswert ist, dass Sie die Variable "baseControllerNamespace" auf Ihren Basiscontroller-Namespace (z. B. "Project.Controllers") setzen müssen, aber ansonsten genau das getan haben, was ich 7 Jahre nach der Veröffentlichung benötigt habe.
Prototyp
3

Versuchen Sie so etwas:

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
quelle
3

Hinweis: Für ASP.NET MVC 2 verfügen sie über zusätzliche Standortpfade, die Sie für Ansichten in "Bereiche" festlegen müssen.

 AreaViewLocationFormats
 AreaPartialViewLocationFormats
 AreaMasterLocationFormats

Das Erstellen einer Ansichts-Engine für einen Bereich wird in Phils Blog beschrieben .

Hinweis: Dies ist für die Vorschau-Version 1 vorgesehen und kann sich ändern.

Simon_Weaver
quelle
1

Bei den meisten Antworten hier löschen Sie die vorhandenen Standorte, indem Sie anrufen, ViewEngines.Engines.Clear()und fügen Sie sie dann wieder hinzu. Dies ist nicht erforderlich.

Wir können die neuen Standorte einfach zu den vorhandenen hinzufügen, wie unten gezeigt:

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

Jetzt können Sie Ihr Projekt so konfigurieren, dass es RazorViewEnginein Global.asax Folgendes verwendet:

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

Weitere Informationen finden Sie in diesem Tutorial .

Hooman Bahreini
quelle