2016-04-20 5 views
11

Lassen Sie uns sagen, dass ich einen Controller haben, der Attribut-based Routing verwendet, wie so eine angeforderte URL/admin/Produkt zu handhaben:Wie Sie den Speicherort der Ansicht in asp.net Core Mvc angeben, wenn Sie benutzerdefinierte Speicherorte verwenden?

[Route("admin/[controller]")]   
public class ProductController: Controller { 

    // GET: /admin/product 
    [Route("")] 
    public IActionResult Index() { 

     return View(); 
    } 
} 

die wir nun sagen, dass ich meine Ansichten in einer Ordnerstruktur organisiert halten möchten, das entspricht ungefähr den URL-Pfaden, auf die sie sich beziehen. Also würde ich die Ansicht mag für diesen Controller hier lokalisiert werden:

/Views/Admin/Product.cshtml 

weiter zu gehen, wenn ich einen Controller wie folgt habe:

[Route("admin/marketing/[controller]")]   
public class PromoCodeListController: Controller { 

    // GET: /admin/marketing/promocodelist 
    [Route("")] 
    public IActionResult Index() { 

     return View(); 
    } 
} 

ich den Rahmen möchte automatisch suchen es ist Blick hier:

Views/Admin/Marketing/PromoCodeList.cshtml 

Ideal des Ansatz für den Rahmen der Aussichtslage zu informieren würde auf der Grundlage des Informationsattribut basierend Route in allgemeiner Art und Weise arbeitet, unabhängig davon, wie viele url seg sind involviert (d. h. wie tief es verschachtelt ist).

Wie kann ich das Core MVC-Framework (ich verwende derzeit RC1) anweisen, an einem solchen Ort nach der Ansicht des Controllers zu suchen?

Antwort

26

Eine andere Lösung dieses Problems die Standorte zu erweitern ist, dass die Ansicht Motor für Ansichten sieht, ohne dass eine benutzerdefinierte Ansicht Motor zu schaffen. Für diesen Ansatz tun Sie Folgendes:

public class ViewLocationExpander: IViewLocationExpander { 

    /// <summary> 
    /// Used to specify the locations that the view engine should search to 
    /// locate views. 
    /// </summary> 
    /// <param name="context"></param> 
    /// <param name="viewLocations"></param> 
    /// <returns></returns> 
    public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations) { 
     //{2} is area, {1} is controller,{0} is the action 
     string[] locations = new string[] { "/Views/{2}/{1}/{0}.cshtml"}; 
     return locations.Union(viewLocations);   //Add mvc default locations after ours 
    } 


    public void PopulateValues(ViewLocationExpanderContext context) { 
     context.Values["customviewlocation"] = nameof(ViewLocationExpander); 
    } 
} 

Dann in der ConfigureServices(IServiceCollection services) Methode in den startup.cs den folgenden Code-Datei fügen Sie es mit dem IoC-Container zu registrieren. Mach das direkt nach services.AddMvc();

services.Configure<RazorViewEngineOptions>(options => { 
     options.ViewLocationExpanders.Add(new ViewLocationExpander()); 
    }); 

Bam! Jetzt haben Sie die Möglichkeit, eine beliebige benutzerdefinierte Verzeichnisstruktur zu der Liste der Stellen hinzuzufügen, an denen die Ansichtsengine nach Ansichten und Teilansichten sucht. Fügen Sie es einfach zu locationsstring[] hinzu.Sie können auch eine _ViewImports.cshtml Datei in dasselbe Verzeichnis oder ein beliebiges übergeordnetes Verzeichnis stellen und es wird gefunden und mit Ihren Ansichten in dieser neuen Verzeichnisstruktur zusammengeführt. Ziemlich cool.

+0

Dies ist eine gute Lösung, dies löst jedoch nicht das Problem, eine Ansicht mit einem Routenattribut für die Aktion oder den Controller zu finden. Die View-Methode scheint weiterhin den Namen des Controllers und nicht den Routennamen zu verwenden, um die Ansicht zu finden. – Xipooo

+0

@ Xipooo, guter Punkt. Das von mir bereitgestellte Beispiel ist ein guter Anfang, aber um die Route zu verwenden, können Sie 'locations' Array so einstellen, dass'/Views' + 'context.ActionContext.HttpContext.Request.Path' + entweder' index.cshtml' oder '.cshtml' enthalten . –

+0

Dadurch konnte ich eine Ansicht verwenden, die über einen .net-Standard 1.6 zum Ordner bin hinzugefügt wurde. Danke großartige Lösung. – DeadlyChambers

2

Sie werden eine benutzerdefinierte RazorviewEngine für diesen benötigen.

Zuerst wird der Motor:

public class CustomEngine : RazorViewEngine 
{ 
    private readonly string[] _customAreaFormats = new string[] 
    { 
     "/Views/{2}/{1}/{0}.cshtml" 
    }; 

    public CustomEngine(
     IRazorPageFactory pageFactory, 
     IRazorViewFactory viewFactory, 
     IOptions<RazorViewEngineOptions> optionsAccessor, 
     IViewLocationCache viewLocationCache) 
     : base(pageFactory, viewFactory, optionsAccessor, viewLocationCache) 
    { 
    } 

    public override IEnumerable<string> AreaViewLocationFormats => 
     _customAreaFormats.Concat(base.AreaViewLocationFormats); 
} 

Dies wird eine zusätzliche Fläche Format erstellen, die den Anwendungsfall von {areaName}/{controller}/{view} übereinstimmt.

public void ConfigureServices(IServiceCollection services) 
{ 
    // Add custom engine (must be BEFORE services.AddMvc() call) 
    services.AddSingleton<IRazorViewEngine, CustomEngine>(); 

    // Add framework services. 
    services.AddMvc(); 
} 

Drittens fügt Bereich Routing zu Ihren Routen MVC, in der Configure Methode:

Zweitens den Motor im ConfigureServices Verfahren der Startup.cs Klasse registriert

app.UseMvc(routes => 
{ 
    // add area routes 
    routes.MapRoute(name: "areaRoute", 
     template: "{area:exists}/{controller}/{action}", 
     defaults: new { controller = "Home", action = "Index" }); 

    routes.MapRoute(
     name: "default", 
     template: "{controller=Home}/{action=Index}/{id?}"); 
}); 

Schließlich ändern Ihre ProductController Klasse zur Verwendung der AreaAttribute:

Jetzt

, Ihre Anwendungsstruktur kann wie folgt aussehen:

sample project structure

+0

Will - danke für diese Lösung. Ich kann bestätigen, dass es funktioniert. Nach weiteren Recherchen habe ich einen anderen Weg gefunden, der meiner Ansicht nach noch einfacher ist, wenn man die ExpandViewLocations-Klasse injiziert. Ich werde Ihre Lösung als die Antwort anerkennen, weil es funktioniert und Sie verdienen den Kredit für Ihre gründliche Antwort. –

+0

Diese Antwort ist nicht mehr gültig für aktuelle ASP.Net Core-Projekte, da 'AreaViewLocationFormats' nicht mehr existiert. Ich denke, das wurde jetzt zu "RazorViewEngineOptions" verschoben. –

+1

@RonC Ich denke, es wäre eine gute Idee, die akzeptierte Antwort zu dieser Frage zu ändern. –

8

In .net-Kern können Sie den gesamten Pfad zu der Ansicht angeben.

return View("~/Views/booking/checkout.cshtml", checkoutRequest);

+3

Völlig wahr, aber ich suchte nach einer Lösung, damit das Framework die Ansicht automatisch an einem benutzerdefinierten Ort findet, ohne es auf diese Weise angeben zu müssen. Dies ist jedoch eine gute Lösung für Personen, die den Standort der Ansicht manuell angeben möchten. –

+3

Ja, ich mag deine Antwort viel mehr als die akzeptierte. Ich wollte es benutzen, bis ich merkte, dass es für das, was ich brauchte, übertrieben war. Ich habe dies gepostet, weil ich mein Problem lösen konnte, ohne benutzerdefinierten Code hinzufügen zu müssen. Vielleicht wird jemand das nützlich finden. Danke für deine Antwort!Grüße –

16

Gute Nachrichten ... In ASP.NET Core 2, brauchen Sie nicht eine benutzerdefinierte Viewengine oder sogar ExpandViewLocations mehr.

In startup.cs, in ConfigureServices, können Sie so etwas wie dieses hinzufügen, die ich Feature-Ordnerstruktur (I stark bevorzugen es über die Standard-Konventionen) zu implementieren verwendet:

services.Configure<RazorViewEngineOptions>(o => 
{ 
    // {2} is area, {1} is controller,{0} is the action  
    o.ViewLocationFormats.Clear(); 
    o.ViewLocationFormats.Add("/Controllers/{1}/Views/{0}" + RazorViewEngine.ViewExtension); 
    o.ViewLocationFormats.Add("/Controllers/Shared/Views/{0}" + RazorViewEngine.ViewExtension); 

    // Untested. You could remove this if you don't care about areas. 
    o.AreaViewLocationFormats.Clear(); 
    o.AreaViewLocationFormats.Add("/Areas/{2}/Controllers/{1}/Views/{0}" + RazorViewEngine.ViewExtension); 
    o.AreaViewLocationFormats.Add("/Areas/{2}/Controllers/Shared/Views/{0}" + RazorViewEngine.ViewExtension); 
    o.AreaViewLocationFormats.Add("/Areas/Shared/Views/{0}" + RazorViewEngine.ViewExtension); 
}); 

Und das ist es! Keine speziellen Klassen erforderlich.

Bonus Tipp: Wenn Sie Resharper verwenden, stellen Sie möglicherweise fest, dass Resharper an einigen Stellen Ihre Ansichten nicht finden kann und Ihnen lästige Warnungen gibt. um das zu umgehen, ziehen im Resharper.Annotations Paket und in Ihrem startup.cs (oder anderswo wirklich) für jedes Ihrer Ansicht Standorte eines dieser Attribute hinzufügen:

[assembly: AspMvcViewLocationFormat("/Controllers/{1}/Views/{0}.cshtml")] 
[assembly: AspMvcViewLocationFormat("/Controllers/Shared/Views/{0}.cshtml")] 

[assembly: AspMvcViewLocationFormat("/Areas/{2}/Controllers/{1}/Views/{0}.cshtml")] 
[assembly: AspMvcViewLocationFormat("/Controllers/Shared/Views/{0}.cshtml")] 

Hoffentlich erspart dies einige Leute die Stunden von Frustration, die ich gerade durchgemacht habe. :)

+1

Du bist mein Held. Ich bin auf diese Frage gekommen, weil ich die Feature-basierte Ordnerstruktur implementieren wollte, und Sie haben es bereits getan. Fantastisch! –

+2

@JohnHargrove Danke für die freundlichen Worte, Sie haben einen schwierigen Tag aufgehellt :) –

+2

Ich liebe den _Bonus tip_ über den ReSharper! Ich habe alle meine Aufrufe von 'View' und' PartialView' rot hervorgehoben. – t3chb0t

Verwandte Themen