wake-up-neo.com

Wie kann ich den Speicherort der Ansicht in asp.net core mvc angeben, wenn ich benutzerdefinierte Speicherorte verwende?

Angenommen, ich habe einen Controller, der ein attributbasiertes Routing verwendet, um eine angeforderte URL von/admin/product wie folgt zu verarbeiten:

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

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

        return View();
    }
}

Angenommen, ich möchte meine Ansichten in einer Ordnerstruktur organisieren, die in etwa die URL-Pfade widerspiegelt, mit denen sie verknüpft sind. Daher möchte ich, dass sich die Ansicht für diesen Controller hier befindet:

/Views/Admin/Product.cshtml

Um noch weiter zu gehen, wenn ich einen Controller wie diesen hätte:

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

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

        return View();
    }
}

Ich möchte, dass das Framework hier automatisch nach seiner Ansicht sucht:

Views/Admin/Marketing/PromoCodeList.cshtml

Im Idealfall würde der Ansatz zum Informieren des Frameworks des Ansichtsorts allgemein auf der Grundlage der attributbasierten Routeninformationen funktionieren, unabhängig davon, wie viele URL-Segmente betroffen sind (dh wie tief verschachtelt sie sind).

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

28
Ron C

Sie können die Positionen erweitern, an denen das Ansichtsmodul nach Ansichten sucht, indem Sie eine Ansichtslokalisierungserweiterung implementieren. Hier ist ein Beispielcode, um den Ansatz zu demonstrieren:

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

Fügen Sie dann in der ConfigureServices(IServiceCollection services) -Methode in der Datei startup.cs den folgenden Code hinzu, um sie im IoC-Container zu registrieren. Mach das gleich nach services.AddMvc();

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

Jetzt haben Sie die Möglichkeit, eine beliebige benutzerdefinierte Verzeichnisstruktur zur Liste der Bereiche hinzuzufügen, in denen das Ansichtsmodul nach Ansichten und Teilansichten sucht. Fügen Sie es einfach zum locationsstring[] Hinzu. Sie können auch eine _ViewImports.cshtml - Datei im selben Verzeichnis oder in einem beliebigen übergeordneten Verzeichnis ablegen. Diese Datei wird dann gefunden und mit Ihren Ansichten in dieser neuen Verzeichnisstruktur zusammengeführt.

pdate:
Das Schöne an diesem Ansatz ist, dass er mehr Flexibilität bietet als der später in ASP.NET Core 2 eingeführte Ansatz (Vielen Dank an BrianMacKay für die Dokumentation des neuen Ansatzes). So können Sie beispielsweise mit diesem ViewLocationExpander-Ansatz nicht nur eine Hierarchie von Pfaden angeben, um nach Ansichten und Bereichen zu suchen, sondern auch nach Layouts und Ansichtskomponenten. Sie haben auch Zugriff auf das vollständige ActionContext, um festzustellen, welche Route geeignet ist. Dies bietet viel Flexibilität und Kraft. Wenn Sie beispielsweise den entsprechenden Ansichtsspeicherort ermitteln möchten, indem Sie den Pfad der aktuellen Anforderung auswerten, können Sie über context.ActionContext.HttpContext.Request.Path Auf den Pfad der aktuellen Anforderung zugreifen.

44
Ron C

Tolle Neuigkeiten ... In ASP.NET Core 2. * benötigen Sie keine benutzerdefinierte ViewEngine oder ExpandViewLocations mehr.

Verwenden des OdeToCode.AddFeatureFolders-Pakets

Dies ist der einfachste Weg ... K. Scott Allen hat ein Nuget-Paket für Sie bei OdeToCode.AddFeatureFolders, das sauber ist und optionale Unterstützung für Bereiche enthält. Github: https://github.com/OdeToCode/AddFeatureFolders

Installieren Sie das Paket und es ist so einfach wie:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc()
                .AddFeatureFolders();

        ...
    }

    ...
}  

[~ # ~] diy [~ # ~]

Verwenden Sie diese Option, wenn Sie eine sehr genaue Kontrolle über Ihre Ordnerstruktur benötigen oder wenn Sie die Abhängigkeit aus irgendeinem Grund nicht akzeptieren möchten. Dies ist auch recht einfach, obwohl es vielleicht unübersichtlicher ist als das obige Nuget-Paket:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
         ...

         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.

Umgang mit Resharper/Rider

Bonus-Tipp: Wenn Sie ReSharper verwenden, werden Sie möglicherweise bemerken, dass ReSharper an einigen Stellen Ihre Ansichten nicht findet und Ihnen ärgerliche Warnungen gibt. Um das zu umgehen, ziehen Sie das Resharper.Annotations-Paket und fügen Sie in Ihrem startup.cs (oder woanders wirklich) eines der folgenden Attribute für jeden Ihrer Ansichtsspeicherorte hinzu:

[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 einigen Leuten die Stunden der Frustration, die ich gerade erlebt habe. :)

67
Brian MacKay

In .net core können Sie den gesamten Pfad zur Ansicht angeben.

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

21
Brian Rizo

Hierfür benötigen Sie ein benutzerdefiniertes RazorviewEngine.

Erstens, 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);
}

Dadurch wird ein zusätzliches Bereichsformat erstellt, das dem Anwendungsfall von {areaName}/{controller}/{view} Entspricht.

Zweitens registrieren Sie die Engine in der ConfigureServices -Methode der Klasse Startup.cs:

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ügen Sie Ihren MVC-Routen in der Configure -Methode Bereichsrouting hinzu:

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?}");
});

Zuletzt ändern Sie Ihre Klasse ProductController, um die Klasse AreaAttribute zu verwenden:

[Area("admin")]
public class ProductController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

Nun kann Ihre Anwendungsstruktur so aussehen:

sample project structure

2
Will Ray

Nach dem Graben habe ich das Problem wahrscheinlich auf einem anderen Stapelüberlauf gefunden. Ich hatte das gleiche Problem, und beim Kopieren der ViewImports-Datei aus dem Nicht-Bereichsabschnitt funktionierten die Links wie erwartet.
Wie hier zu sehen: Asp.Net Core 2.0 MVC-Anker-Tag-Helfer funktioniert nicht
Die andere Lösung bestand darin, auf Ansichtsebene zu kopieren:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

0
greendave11