Ansichtskomponenten in ASP.NET Core

Von Rick Anderson

Ansichtskomponenten

Ansichtskomponenten ähneln zwar den Teilansichten, sind aber wesentlich leistungsstärker. Ansichtskomponenten verwenden keine Modellbindung, sondern sie sind von den beim Aufrufen der Ansichtskomponente übergebenen Daten abhängig. Beim Schreiben dieses Artikels wurden Controller und Ansichten verwendet, aber Ansichtskomponenten funktionieren auch zusammen mit Razor Pages.

Eine Ansichtskomponente:

  • Rendert nur einen Block statt einer gesamten Antwort.
  • Umfasst die gleiche Trennung von Belangen und Vorzüge der Testbarkeit, die auch zwischen einem Controller und einer Ansicht bestehen.
  • Kann Parameter und Geschäftslogik aufweisen.
  • Wird normalerweise von einer Layoutseite aus aufgerufen.

Ansichtskomponenten wurden für wiederverwendbare Renderinglogik entwickelt, die für eine Teilansicht zu komplex ist. Dazu gehören:

  • Dynamische Navigationsmenüs
  • Tagcloud (dort, wo die Datenbank abgefragt wird)
  • Anmeldebereich
  • Einkaufswagen
  • vor Kurzem veröffentlichte Artikel
  • Inhalt in einer Randleiste auf einem Blog
  • Ein Anmeldebereich, der auf jeder Seite gerendert wird und die Links zum Ab- oder Anmelden anzeigt, je nachdem, ob der/die Benutzer*in an- oder abgemeldet ist

Eine Ansichtskomponente besteht aus zwei Teilen:

  • Die Klasse, die in der Regel von ViewComponent abgeleitet ist
  • Das zurückgegebene Ergebnis, in der Regel eine Ansicht

Eine Ansichtskomponente kann, ähnlich wie Controller, ein POCO sein. Die meisten Entwickelnden sollten jedoch von den durch Ableiten von ViewComponent verfügbaren Methoden und Eigenschaften Gebrauch machen.

Bei der Überlegung, ob Ansichtskomponenten den Spezifikationen einer App entsprechen, sollten Sie stattdessen Razor-Komponenten in Betracht ziehen. Razor-Komponenten kombinieren zudem Markup mit C#-Code, um wiederverwendbare UI-Einheiten zu erzeugen. RazorKomponenten wurden für Entwicklerproduktivität bei der Bereitstellung von clientseitiger Benutzeroberflächenlogik und -gestaltung konzipiert. Weitere Informationen finden Sie unter Razor-Komponenten in ASP.NET Core. Informationen zum Integrieren von Razor Komponenten in eine MVC- oder Razor Pages-App finden Sie unter Integrieren ASP.NET KernkomponentenRazor.

Erstellen einer Ansichtskomponente

In diesem Abschnitt werden die allgemeinen Anforderungen zum Erstellen einer Ansichtskomponente beschrieben. Im Folgenden wird jeder Schritt ausführlich betrachtet, und Sie erstellen im Zuge dessen eine Ansichtskomponente.

Die Ansichtskomponentenklasse

Eine Ansichtskomponentenklasse kann durch folgende Aktionen erstellt werden:

  • Ableiten von ViewComponent
  • durch Ergänzen der Klasse mit dem Attribut [ViewComponent] oder durch das Ableiten von einer Klasse mit dem Attribut [ViewComponent]
  • Erstellen einer Klasse, deren Name mit dem Suffix ViewComponent endet

Ansichtskomponenten müssen, genauso wie Controller, öffentliche, unverschachtelt und nicht abstrakte Klassen sein. Der Name der Ansichtskomponente ist der Klassenname ohne den Suffix ViewComponent. Er kann zudem explizit mit der Eigenschaft Name angegeben werden.

Eine Ansichtskomponentenklasse:

  • Unterstützt Dependency Injection für Konstruktoren
  • Nimmt nicht am Controllerlebenszyklus teil, daher können Filter in Ansichtskomponenten nicht verwendet werden

Um zu verhindern, dass eine Klasse mit einem Suffix ohne Beachtung der Groß-/Kleinschreibung ViewComponent als Ansichtskomponente behandelt wird, versehen Sie die Klasse mit dem [NonViewComponent]-Attribut:

using Microsoft.AspNetCore.Mvc;

[NonViewComponent]
public class ReviewComponent
{
    public string Status(string name) => JobStatus.GetCurrentStatus(name);
}

Ansichtskomponentenmethoden

Ansichtskomponenten definieren ihre Logik in folgenden Methoden:

  • InvokeAsync-Methode, die Task<IViewComponentResult> zurückgibt
  • Synchrone Invoke-Methode, die ein IViewComponentResult zurückgibt

Parameter stammen direkt vom Aufruf der Ansichtskomponente und nicht von der Modellbindung. Eine Ansichtskomponente behandelt nie direkt eine Anfrage. Normalerweise initialisiert eine Ansichtskomponente ein Modell und übergibt dieses an eine Ansicht, indem sie die View-Methode aufruft. Zusammengefasst bedeutet dies für Komponentenmethoden Folgendes:

  • Es wird eine InvokeAsync-Methode definiert, die ein Task<IViewComponentResult> zurückgibt, oder eine synchrone Invoke-Methode, die ein IViewComponentResult zurückgibt.
  • Normalerweise wird ein Modell initialisiert und an eine Ansicht übergeben, indem die ViewComponent.View-Methode aufgerufen wird.
  • Parameter stammen aus der aufrufenden Methode, nicht aus HTTP. Es gibt keine Modellbindung.
  • Die Methoden sind nicht direkt als HTTP-Endpunkte erreichbar. Sie werden in der Regel in einer Ansicht aufgerufen. Eine Ansichtskomponente verarbeitet nie eine Anforderung.
  • Methoden werden in der Signatur überladen, nicht in Details der aktuellen HTTP-Anforderung.

Anzeigen des Suchpfads

Die Runtime sucht in den folgenden Pfaden nach der Ansicht:

  • /Views/{Controllername} /Components/{Ansichtskomponentenname}/{Ansichtsname}
  • /Views/Shared/Components/{Ansichtskomponentenname}/{Ansichtsname}
  • /Pages/Shared/Components/{Ansichtskomponentenname}/{Ansichtsname}
  • /Areas/{Bereichsname}/Views/Shared/Components/{Name der Ansichtskomponente}/{Name der Ansicht}

Der Suchpfad betrifft Projekte mit Controllern und Ansichten sowie Razor Pages.

Der Standardansichtsname für die Ansichtskomponente ist Default. Dies bedeutet, dass Ansichtsdateien in der Regel den Namen Default.cshtml aufweisen. Bei Erstellen der Ansichtskomponentenergebnisse oder beim Aufrufen der View-Methode kann ein anderer Ansichtsname angegeben werden.

Es wird empfohlen, die Ansichtsdatei Default.cshtml zu nennen und den Pfad View/Shared/Components/{Name der Ansichtskomponente}/{Name der Ansicht} zu verwenden. In der in diesem Beispiel verwendeten Ansichtskomponente PriorityList wird Views/Shared/Components/PriorityList/Default.cshtml als Ansicht der Ansichtskomponente verwendet.

Anpassen des Ansichtssuchpfads

Um den Suchpfad für die Ansicht anzupassen, ändern Sie die Sammlung Razor von ViewLocationFormats. Um z. B. nach Ansichten im Pfad /Components/{View Component Name}/{View Name} zu suchen, fügen Sie der Sammlung ein neues Element hinzu:

using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews()
    .AddRazorOptions(options =>
    {
        options.ViewLocationFormats.Add("/{0}.cshtml");
    });

builder.Services.AddDbContext<ToDoContext>(options =>
        options.UseInMemoryDatabase("db"));

var app = builder.Build();

// Remaining code removed for brevity.

Im vorstehenden Code entspricht der Platzhalter {0} dem Pfad Components/{View Component Name}/{View Name}.

Aufrufen einer Ansichtskomponente

Rufen Sie Folgendes in der Ansicht auf, wenn Sie die Ansichtskomponente verwenden möchten:

@await Component.InvokeAsync("Name of view component",
                             {Anonymous Type Containing Parameters})

Die Parameter werden an die InvokeAsync-Methode übergeben. Die im Artikel entwickelte Ansichtskomponente PriorityList wird über die Ansichtsdatei Views/ToDo/Index.cshtml aufgerufen. Im folgenden Code wird die InvokeAsync-Methode mit zwei Parametern aufgerufen:

</table>

<div>
    Maximum Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync("PriorityList",
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

Aufrufen einer Ansichtskomponente als Taghilfsprogramm

Eine Ansichtskomponente kann als Taghilfsprogramm aufgerufen werden:

<div>
       Maxium Priority: @ViewData["maxPriority"] <br />
       Is Complete:  @ViewData["isDone"]
    @{
        int maxPriority = Convert.ToInt32(ViewData["maxPriority"]);
        bool isDone = Convert.ToBoolean(ViewData["isDone"]);
    }
    <vc:priority-list max-priority=maxPriority is-done=isDone>
    </vc:priority-list>
</div>

Namen von Klassen und Methodenparameter für Taghilfsprogramme, die in Pascal-Schreibweise angegeben sind, werden in Kebab-Schreibweise übersetzt. Das Taghilfsprogramm zum Aufrufen einer Ansichtskomponente verwendet das <vc></vc>-Element. Die Ansichtskomponente wird wie folgt angegeben:

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

Damit Sie eine Ansichtskomponente als Taghilfsprogramm verwenden können, registrieren Sie die Assembly, die die Ansichtskomponente enthält, mit der @addTagHelper-Anweisung. Wenn sich die Ansichtskomponente in einer Assembly mit dem Namen MyWebApp befindet, fügen Sie der Datei _ViewImports.cshtml die folgende Anweisung hinzu:

@addTagHelper *, MyWebApp

Ansichtskomponenten können als Taghilfsprogramme für jede Datei registriert werden, die auf die Ansichtskomponente verweist. Weitere Informationen zum Registrieren eines Taghilfsprogramms finden Sie unter Managing Tag Helper Scope (Verwalten des Bereichs des Taghilfsprogramms).

Die InvokeAsync-Methode, die in diesem Tutorial verwendet wird:

@await Component.InvokeAsync("PriorityList",
                 new { 
                     maxPriority =  ViewData["maxPriority"],
                     isDone = ViewData["isDone"]  }
                 )

Im vorstehenden Markup wird aus der Ansichtskomponente PriorityListpriority-list. Die Parameter der Ansichtskomponente werden als Attribute in Kebab-Schreibweise übergeben.

Direkter Aufruf einer Ansichtskomponente aus einem Controller

Ansichtskomponenten werden normalerweise aus einer Ansicht aufgerufen, sie können jedoch auch direkt aus einer Controllermethode aufgerufen werden. Obwohl Ansichtskomponenten nicht wie Controller Endpunkte definieren, kann eine Controlleraktion implementiert werden, die den Inhalt von ViewComponentResult zurückgibt.

Im folgenden Beispiel wird die Ansichtskomponente direkt von einem Controller aus aufgerufen:

public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
    return ViewComponent("PriorityList",
        new { 
           maxPriority = maxPriority,
           isDone = isDone
        });
}

Erstellen einer grundlegenden Ansichtskomponente

Laden Sie den Startercode herunter, und erstellen und testen Sie diesen. Hierbei handelt es sich um ein grundlegendes Projekt mit einem ToDo-Controller, in dem eine Liste von ToDo-Elementen angezeigt wird.

Liste mit ToDo-Elementen

Aktualisieren des Controllers zum Übergeben der Priorität und des Abschlussstatus

Aktualisieren Sie die Index-Methode so, dass Prioritäts- und Abschlussstatusparameter verwendet werden:

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.Controllers;
public class ToDoController : Controller
{
    private readonly ToDoContext _ToDoContext;

    public ToDoController(ToDoContext context)
    {
        _ToDoContext = context;
        _ToDoContext.Database.EnsureCreated();
    }

    public IActionResult Index(int maxPriority = 2, bool isDone = false)
    {
        var model = _ToDoContext!.ToDo!.ToList();
        ViewData["maxPriority"] = maxPriority;
        ViewData["isDone"] = isDone;
        return View(model);
    }

Hinzufügen einer ViewComponent-Klasse

Hinzufügen einer ViewComponent-Klasse zu ViewComponents/PriorityListViewComponent.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents;

public class PriorityListViewComponent : ViewComponent
{
    private readonly ToDoContext db;

    public PriorityListViewComponent(ToDoContext context) => db = context;

    public async Task<IViewComponentResult> InvokeAsync(
                                            int maxPriority, bool isDone)
    {
        var items = await GetItemsAsync(maxPriority, isDone);
        return View(items);
    }

    private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
    {
        return db!.ToDo!.Where(x => x.IsDone == isDone &&
                             x.Priority <= maxPriority).ToListAsync();
    }
}

Bemerkungen zum Code:

  • Ansichtskomponentenklassen können sich in jedem Ordner im Projekt befinden.

  • Da der Klassenname „PriorityListViewComponent“ mit dem Suffix ViewComponent endet, verwendet die Runtime für Verweise auf die Klassenkomponente aus einer Ansicht die Zeichenfolge PriorityList.

  • Das [ViewComponent]-Attribut kann den Namen ändern, der zum Verweis auf eine Ansichtskomponente verwendet wird. Die Klasse könnte beispielsweise mit dem folgenden [ViewComponent]-Attribut den Namen XYZ erhalten:

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • Das [ViewComponent]-Attribut im vorstehenden Code teilt dem Ansichtskomponentenselektor mit, Folgendes zu verwenden:

    • Den Namen PriorityList für die Suche nach den Ansichten, die der Komponente zugeordnet sind
    • Die Zeichenfolge „PriorityList“, wenn aus einer Sicht auf die Klassenkomponente verwiesen wird
  • Die Komponente verwendet Dependency Injection, um den Datenkontext verfügbar zu machen.

  • InvokeAsync macht eine Methode verfügbar, die von einer Ansicht aus aufgerufen werden kann, und akzeptiert eine beliebige Anzahl von Argumenten.

  • Die InvokeAsync-Methode gibt mehrere ToDo-Elemente zurück, die die Bedingungen der Parameter isDone und maxPriority erfüllen.

Erstellen der Razor-Ansicht der Ansichtskomponente

  • Erstellen Sie den Ordner Views/Shared/Components. Diese Ordner muss den Namen Components besitzen.

  • Erstellen Sie den Ordner Views/Shared/Components/PriorityList. Der Ordnername muss mit dem Namen der Ansichtskomponentenklasse oder dem Namen der Klasse ohne Suffix übereinstimmen. Bei Verwendung des ViewComponent-Attributs muss der Klassenname mit der Attributbezeichnung übereinstimmen.

  • Erstellen einer Views/Shared/Components/PriorityList/Default.cshtmlRazor-Ansicht:

    @model IEnumerable<ViewComponentSample.Models.TodoItem>
    
    <h3>Priority Items</h3>
    <ul>
        @foreach (var todo in Model)
        {
            <li>@todo.Name</li>
        }
    </ul>
    

    Die Razor-Ansicht nimmt eine Liste von TodoItem entgegen und zeigt diese an. Wenn die InvokeAsync-Methode der Ansichtskomponente nicht den Namen der Ansicht übergibt, wird Default per Konvention als Ansichtsname verwendet. Fügen Sie eine Ansicht einem controllerspezifischen Ansichtsordner hinzu, um das Standardformat für einen spezifischen Controller zu überschreiben (z.B. Views/ToDo/Components/PriorityList/Default.cshtml).

    Wenn die Ansichtskomponente controllerspezifisch ist, kann sie dem controllerspezifischen Ordner hinzugefügt werden. Beispielsweise ist Views/ToDo/Components/PriorityList/Default.cshtml controllerspezifisch.

  • Fügen Sie ein div-Element, das einen Aufruf an eine Prioritätslistenkomponente enthält, am Ende der Datei Views/ToDo/index.cshtml hinzu:

    </table>
    
    <div>
        Maximum Priority: @ViewData["maxPriority"] <br />
        Is Complete:  @ViewData["isDone"]
        @await Component.InvokeAsync("PriorityList",
                         new { 
                             maxPriority =  ViewData["maxPriority"],
                             isDone = ViewData["isDone"]  }
                         )
    </div>
    

Das Markup @await Component.InvokeAsync zeigt die Syntax für die aufrufenden Ansichtskomponenten an. Das erste Argument ist der Name der aufzurufenden Komponente. Darauffolgende Parameter werden an die Komponente übergeben. InvokeAsync kann eine arbiträre Anzahl von Argumenten annehmen.

Testen Sie die App. In der folgenden Abbildung werden die ToDo-Liste und die Elemente mit Priorität angezeigt:

ToDo-Liste und Prioritätselemente

Die Ansichtskomponente kann direkt aus dem Controller aufgerufen werden:

public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
    return ViewComponent("PriorityList",
        new { 
           maxPriority = maxPriority,
           isDone = isDone
        });
}

Prioritätselemente der IndexVC-Aktion

Angeben eines Namens für eine Ansichtskomponente

Eine komplexe Ansichtskomponente erfordert möglicherweise, dass unter bestimmten Umständen eine Ansicht angegeben wird, die nicht dem Standard entspricht. Der folgende Code zeigt, wie die Ansicht „PVC“ in der InvokeAsync-Methode angegeben wird. Aktualisieren Sie die InvokeAsync-Methode in der PriorityListViewComponent-Klasse.

public async Task<IViewComponentResult> InvokeAsync(
                                           int maxPriority, bool isDone)
{
    string MyView = "Default";
    // If asking for all completed tasks, render with the "PVC" view.
    if (maxPriority > 3 && isDone == true)
    {
        MyView = "PVC";
    }
    var items = await GetItemsAsync(maxPriority, isDone);
    return View(MyView, items);
}

Kopieren Sie die Datei Views/Shared/Components/PriorityList/Default.cshtml in eine Ansicht mit dem Namen Views/Shared/Components/PriorityList/PVC.cshtml. Fügen Sie eine Überschrift hinzu, um anzugeben, dass die PVC-Ansicht verwendet wird.

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h2> PVC Named Priority Component View</h2>
<h4>@ViewBag.PriorityMessage</h4>
<ul>
    @foreach (var todo in Model)
    {
        <li>@todo.Name</li>
    }
</ul>

Führen Sie die App aus und überprüfen Sie die PVC-Ansicht.

Ansichtskomponente mit Priorität

Wenn die Ansicht „PVC“ nicht gerendert wird, stellen Sie sicher, dass die Ansichtskomponente mit einer Priorität von mindestens 4 aufgerufen wird.

Untersuchen des Ansichtspfads

  • Ändern Sie den Prioritätsparameter in drei oder weniger, damit die Prioritätsansicht nicht zurückgegeben wird.

  • Benennen Sie vorübergehend Views/ToDo/Components/PriorityList/Default.cshtml in 1Default.cshtml um.

  • Wenn Sie die App testen, tritt der folgende Fehler auf:

    An unhandled exception occurred while processing the request.
    InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
    /Views/ToDo/Components/PriorityList/Default.cshtml
    /Views/Shared/Components/PriorityList/Default.cshtml
    
  • Kopieren Sie Views/ToDo/Components/PriorityList/1Default.cshtml nach Views/Shared/Components/PriorityList/Default.cshtml.

  • Fügen Sie der ToDo-Ansichtskomponentenansicht Shared (Freigegeben) Markup hinzu, um anzugeben, dass die Ansicht aus dem Ordner Shared stammt.

  • Testen Sie die Komponentenansicht Shared (Freigegeben).

ToDo-Ausgabe mit Komponentenansicht „Shared“

Vermeiden von hartcodierten Zeichenfolgen

Aus Gründen der Sicherheit zur Kompilierzeit sollten Sie den hartcodierten Komponentennamen durch den Klassennamen ersetzen. Aktualisieren Sie die Datei PriorityListViewComponent.cs so, dass nicht das Suffix „ViewComponent“ verwendet wird:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents;

public class PriorityList : ViewComponent
{
    private readonly ToDoContext db;

    public PriorityList(ToDoContext context)
    {
        db = context;
    }

    public async Task<IViewComponentResult> InvokeAsync(
                                               int maxPriority, bool isDone)
    {
        var items = await GetItemsAsync(maxPriority, isDone);
        return View(items);
    }

    private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
    {
        return db!.ToDo!.Where(x => x.IsDone == isDone &&
                             x.Priority <= maxPriority).ToListAsync();
    }
}

Hier ist die Ansichtsdatei:

</table>

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

In einer Überladung der Component.InvokeAsync-Methode mit einem CLR-Typ wird der Operator typeof verwendet:

</table>

<div>
    Testing typeof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(typeof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

Ausführen synchroner Verarbeitung

Das Framework verarbeitet das Aufrufen einer synchronen Invoke-Methode, wenn keine asynchrone Arbeit erforderlich ist. Die folgende Methode erstellt eine synchrone Invoke-Ansichtskomponente:

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityListSync : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityListSync(ToDoContext context)
        {
            db = context;
        }

        public IViewComponentResult Invoke(int maxPriority, bool isDone)
        {
 
            var x = db!.ToDo!.Where(x => x.IsDone == isDone &&
                                  x.Priority <= maxPriority).ToList();
            return View(x);
        }
    }
}

Die Razor-Datei der Ansichtskomponente:

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityListSync),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

Die Ansichtskomponente wird in einer Razor-Datei (z. B. Views/Home/Index.cshtml) mit einem der folgenden Verfahren aufgerufen:

Rufen Sie Component.InvokeAsync auf, um das IViewComponentHelper-Verfahren zu verwenden:

@await Component.InvokeAsync(nameof(PriorityList),
                             new { maxPriority = 4, isDone = true })

Um das Taghilfsprogramm zu verwenden, registrieren Sie die Assembly, die die Ansichtskomponente enthält, mit der Anweisung @addTagHelper (die Ansichtskomponente befindet sich in einer Assembly namens MyWebApp):

@addTagHelper *, MyWebApp

Verwenden des Taghilfsprogramms der Ansichtskomponente in der Razor-Markupdatei:

<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>

Die Methodensignatur von PriorityList.Invoke ist synchron, aber Razor sucht nach der Methode und ruft sie mit Component.InvokeAsync in der Markupdatei auf.

Zusätzliche Ressourcen

Ansichtskomponenten

Ansichtskomponenten ähneln zwar den Teilansichten, sind aber wesentlich leistungsstärker. Ansichtskomponenten verwenden keine Modellbindung, sondern sie sind von den beim Aufrufen der Ansichtskomponente übergebenen Daten abhängig. Beim Schreiben dieses Artikels wurden Controller und Ansichten verwendet, aber Ansichtskomponenten funktionieren auch zusammen mit Razor Pages.

Eine Ansichtskomponente:

  • Rendert nur einen Block statt einer gesamten Antwort.
  • Umfasst die gleiche Trennung von Belangen und Vorzüge der Testbarkeit, die auch zwischen einem Controller und einer Ansicht bestehen.
  • Kann Parameter und Geschäftslogik aufweisen.
  • Wird normalerweise von einer Layoutseite aus aufgerufen.

Ansichtskomponenten wurden für wiederverwendbare Renderinglogik entwickelt, die für eine Teilansicht zu komplex ist. Dazu gehören:

  • Dynamische Navigationsmenüs
  • Tagcloud (dort, wo die Datenbank abgefragt wird)
  • Anmeldebereich
  • Einkaufswagen
  • vor Kurzem veröffentlichte Artikel
  • Inhalt in einer Randleiste auf einem Blog
  • Ein Anmeldebereich, der auf jeder Seite gerendert wird und die Links zum Ab- oder Anmelden anzeigt, je nachdem, ob der/die Benutzer*in an- oder abgemeldet ist

Eine Ansichtskomponente besteht aus zwei Teilen:

  • Die Klasse, die in der Regel von ViewComponent abgeleitet ist
  • Das zurückgegebene Ergebnis, in der Regel eine Ansicht

Eine Ansichtskomponente kann, ähnlich wie Controller, ein POCO sein. Die meisten Entwickelnden sollten jedoch von den durch Ableiten von ViewComponent verfügbaren Methoden und Eigenschaften Gebrauch machen.

Bei der Überlegung, ob Ansichtskomponenten den Spezifikationen einer App entsprechen, sollten Sie stattdessen Razor-Komponenten in Betracht ziehen. Razor-Komponenten kombinieren zudem Markup mit C#-Code, um wiederverwendbare UI-Einheiten zu erzeugen. RazorKomponenten wurden für Entwicklerproduktivität bei der Bereitstellung von clientseitiger Benutzeroberflächenlogik und -gestaltung konzipiert. Weitere Informationen finden Sie unter Razor-Komponenten in ASP.NET Core. Informationen zum Integrieren von Razor Komponenten in eine MVC- oder Razor Pages-App finden Sie unter Integrieren ASP.NET KernkomponentenRazor.

Erstellen einer Ansichtskomponente

In diesem Abschnitt werden die allgemeinen Anforderungen zum Erstellen einer Ansichtskomponente beschrieben. Im Folgenden wird jeder Schritt ausführlich betrachtet, und Sie erstellen im Zuge dessen eine Ansichtskomponente.

Die Ansichtskomponentenklasse

Eine Ansichtskomponentenklasse kann durch folgende Aktionen erstellt werden:

  • Ableiten von ViewComponent
  • durch Ergänzen der Klasse mit dem Attribut [ViewComponent] oder durch das Ableiten von einer Klasse mit dem Attribut [ViewComponent]
  • Erstellen einer Klasse, deren Name mit dem Suffix ViewComponent endet

Ansichtskomponenten müssen, genauso wie Controller, öffentliche, unverschachtelt und nicht abstrakte Klassen sein. Der Name der Ansichtskomponente ist der Klassenname ohne den Suffix ViewComponent. Er kann zudem explizit mit der Eigenschaft Name angegeben werden.

Eine Ansichtskomponentenklasse:

  • Unterstützt Dependency Injection für Konstruktoren
  • Nimmt nicht am Controllerlebenszyklus teil, daher können Filter in Ansichtskomponenten nicht verwendet werden

Um zu verhindern, dass eine Klasse mit einem Suffix ohne Beachtung der Groß-/Kleinschreibung ViewComponent als Ansichtskomponente behandelt wird, versehen Sie die Klasse mit dem [NonViewComponent]-Attribut:

using Microsoft.AspNetCore.Mvc;

[NonViewComponent]
public class ReviewComponent
{
    public string Status(string name) => JobStatus.GetCurrentStatus(name);
}

Ansichtskomponentenmethoden

Ansichtskomponenten definieren ihre Logik in folgenden Methoden:

  • InvokeAsync-Methode, die Task<IViewComponentResult> zurückgibt
  • Synchrone Invoke-Methode, die ein IViewComponentResult zurückgibt

Parameter stammen direkt vom Aufruf der Ansichtskomponente und nicht von der Modellbindung. Eine Ansichtskomponente behandelt nie direkt eine Anfrage. Normalerweise initialisiert eine Ansichtskomponente ein Modell und übergibt dieses an eine Ansicht, indem sie die View-Methode aufruft. Zusammengefasst bedeutet dies für Komponentenmethoden Folgendes:

  • Es wird eine InvokeAsync-Methode definiert, die ein Task<IViewComponentResult> zurückgibt, oder eine synchrone Invoke-Methode, die ein IViewComponentResult zurückgibt.
  • Normalerweise wird ein Modell initialisiert und an eine Ansicht übergeben, indem die ViewComponent.View-Methode aufgerufen wird.
  • Parameter stammen aus der aufrufenden Methode, nicht aus HTTP. Es gibt keine Modellbindung.
  • Die Methoden sind nicht direkt als HTTP-Endpunkte erreichbar. Sie werden in der Regel in einer Ansicht aufgerufen. Eine Ansichtskomponente verarbeitet nie eine Anforderung.
  • Methoden werden in der Signatur überladen, nicht in Details der aktuellen HTTP-Anforderung.

Anzeigen des Suchpfads

Die Runtime sucht in den folgenden Pfaden nach der Ansicht:

  • /Views/{Controllername} /Components/{Ansichtskomponentenname}/{Ansichtsname}
  • /Views/Shared/Components/{Ansichtskomponentenname}/{Ansichtsname}
  • /Pages/Shared/Components/{Ansichtskomponentenname}/{Ansichtsname}
  • /Areas/{Bereichsname}/Views/Shared/Components/{Name der Ansichtskomponente}/{Name der Ansicht}

Der Suchpfad betrifft Projekte mit Controllern und Ansichten sowie Razor Pages.

Der Standardansichtsname für die Ansichtskomponente ist Default. Dies bedeutet, dass Ansichtsdateien in der Regel den Namen Default.cshtml aufweisen. Bei Erstellen der Ansichtskomponentenergebnisse oder beim Aufrufen der View-Methode kann ein anderer Ansichtsname angegeben werden.

Es wird empfohlen, die Ansichtsdatei Default.cshtml zu nennen und den Pfad View/Shared/Components/{Name der Ansichtskomponente}/{Name der Ansicht} zu verwenden. In der in diesem Beispiel verwendeten Ansichtskomponente PriorityList wird Views/Shared/Components/PriorityList/Default.cshtml als Ansicht der Ansichtskomponente verwendet.

Anpassen des Ansichtssuchpfads

Um den Suchpfad für die Ansicht anzupassen, ändern Sie die Sammlung Razor von ViewLocationFormats. Um z. B. nach Ansichten im Pfad /Components/{View Component Name}/{View Name} zu suchen, fügen Sie der Sammlung ein neues Element hinzu:

using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews()
    .AddRazorOptions(options =>
    {
        options.ViewLocationFormats.Add("/{0}.cshtml");
    });

builder.Services.AddDbContext<ToDoContext>(options =>
        options.UseInMemoryDatabase("db"));

var app = builder.Build();

// Remaining code removed for brevity.

Im vorstehenden Code entspricht der Platzhalter {0} dem Pfad Components/{View Component Name}/{View Name}.

Aufrufen einer Ansichtskomponente

Rufen Sie Folgendes in der Ansicht auf, wenn Sie die Ansichtskomponente verwenden möchten:

@await Component.InvokeAsync("Name of view component",
                             {Anonymous Type Containing Parameters})

Die Parameter werden an die InvokeAsync-Methode übergeben. Die im Artikel entwickelte Ansichtskomponente PriorityList wird über die Ansichtsdatei Views/ToDo/Index.cshtml aufgerufen. Im folgenden Code wird die InvokeAsync-Methode mit zwei Parametern aufgerufen:

</table>

<div>
    Maximum Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync("PriorityList",
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

Aufrufen einer Ansichtskomponente als Taghilfsprogramm

Eine Ansichtskomponente kann als Taghilfsprogramm aufgerufen werden:

<div>
       Maxium Priority: @ViewData["maxPriority"] <br />
       Is Complete:  @ViewData["isDone"]
    @{
        int maxPriority = Convert.ToInt32(ViewData["maxPriority"]);
        bool isDone = Convert.ToBoolean(ViewData["isDone"]);
    }
    <vc:priority-list max-priority=maxPriority is-done=isDone>
    </vc:priority-list>
</div>

Namen von Klassen und Methodenparameter für Taghilfsprogramme, die in Pascal-Schreibweise angegeben sind, werden in Kebab-Schreibweise übersetzt. Das Taghilfsprogramm zum Aufrufen einer Ansichtskomponente verwendet das <vc></vc>-Element. Die Ansichtskomponente wird wie folgt angegeben:

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

Damit Sie eine Ansichtskomponente als Taghilfsprogramm verwenden können, registrieren Sie die Assembly, die die Ansichtskomponente enthält, mit der @addTagHelper-Anweisung. Wenn sich die Ansichtskomponente in einer Assembly mit dem Namen MyWebApp befindet, fügen Sie der Datei _ViewImports.cshtml die folgende Anweisung hinzu:

@addTagHelper *, MyWebApp

Ansichtskomponenten können als Taghilfsprogramme für jede Datei registriert werden, die auf die Ansichtskomponente verweist. Weitere Informationen zum Registrieren eines Taghilfsprogramms finden Sie unter Managing Tag Helper Scope (Verwalten des Bereichs des Taghilfsprogramms).

Die InvokeAsync-Methode, die in diesem Tutorial verwendet wird:

@await Component.InvokeAsync("PriorityList",
                 new { 
                     maxPriority =  ViewData["maxPriority"],
                     isDone = ViewData["isDone"]  }
                 )

Im vorstehenden Markup wird aus der Ansichtskomponente PriorityListpriority-list. Die Parameter der Ansichtskomponente werden als Attribute in Kebab-Schreibweise übergeben.

Direkter Aufruf einer Ansichtskomponente aus einem Controller

Ansichtskomponenten werden normalerweise aus einer Ansicht aufgerufen, sie können jedoch auch direkt aus einer Controllermethode aufgerufen werden. Obwohl Ansichtskomponenten nicht wie Controller Endpunkte definieren, kann eine Controlleraktion implementiert werden, die den Inhalt von ViewComponentResult zurückgibt.

Im folgenden Beispiel wird die Ansichtskomponente direkt von einem Controller aus aufgerufen:

public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
    return ViewComponent("PriorityList",
        new { 
           maxPriority = maxPriority,
           isDone = isDone
        });
}

Erstellen einer grundlegenden Ansichtskomponente

Laden Sie den Startercode herunter, und erstellen und testen Sie diesen. Hierbei handelt es sich um ein grundlegendes Projekt mit einem ToDo-Controller, in dem eine Liste von ToDo-Elementen angezeigt wird.

Liste mit ToDo-Elementen

Aktualisieren des Controllers zum Übergeben der Priorität und des Abschlussstatus

Aktualisieren Sie die Index-Methode so, dass Prioritäts- und Abschlussstatusparameter verwendet werden:

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.Controllers;
public class ToDoController : Controller
{
    private readonly ToDoContext _ToDoContext;

    public ToDoController(ToDoContext context)
    {
        _ToDoContext = context;
        _ToDoContext.Database.EnsureCreated();
    }

    public IActionResult Index(int maxPriority = 2, bool isDone = false)
    {
        var model = _ToDoContext!.ToDo!.ToList();
        ViewData["maxPriority"] = maxPriority;
        ViewData["isDone"] = isDone;
        return View(model);
    }

Hinzufügen einer ViewComponent-Klasse

Hinzufügen einer ViewComponent-Klasse zu ViewComponents/PriorityListViewComponent.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents;

public class PriorityListViewComponent : ViewComponent
{
    private readonly ToDoContext db;

    public PriorityListViewComponent(ToDoContext context) => db = context;

    public async Task<IViewComponentResult> InvokeAsync(
                                            int maxPriority, bool isDone)
    {
        var items = await GetItemsAsync(maxPriority, isDone);
        return View(items);
    }

    private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
    {
        return db!.ToDo!.Where(x => x.IsDone == isDone &&
                             x.Priority <= maxPriority).ToListAsync();
    }
}

Bemerkungen zum Code:

  • Ansichtskomponentenklassen können sich in jedem Ordner im Projekt befinden.

  • Da der Klassenname „PriorityListViewComponent“ mit dem Suffix ViewComponent endet, verwendet die Runtime für Verweise auf die Klassenkomponente aus einer Ansicht die Zeichenfolge PriorityList.

  • Das [ViewComponent]-Attribut kann den Namen ändern, der zum Verweis auf eine Ansichtskomponente verwendet wird. Die Klasse könnte beispielsweise mit dem folgenden [ViewComponent]-Attribut den Namen XYZ erhalten:

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • Das [ViewComponent]-Attribut im vorstehenden Code teilt dem Ansichtskomponentenselektor mit, Folgendes zu verwenden:

    • Den Namen PriorityList für die Suche nach den Ansichten, die der Komponente zugeordnet sind
    • Die Zeichenfolge „PriorityList“, wenn aus einer Sicht auf die Klassenkomponente verwiesen wird
  • Die Komponente verwendet Dependency Injection, um den Datenkontext verfügbar zu machen.

  • InvokeAsync macht eine Methode verfügbar, die von einer Ansicht aus aufgerufen werden kann, und akzeptiert eine beliebige Anzahl von Argumenten.

  • Die InvokeAsync-Methode gibt mehrere ToDo-Elemente zurück, die die Bedingungen der Parameter isDone und maxPriority erfüllen.

Erstellen der Razor-Ansicht der Ansichtskomponente

  • Erstellen Sie den Ordner Views/Shared/Components. Diese Ordner muss den Namen Components besitzen.

  • Erstellen Sie den Ordner Views/Shared/Components/PriorityList. Der Ordnername muss mit dem Namen der Ansichtskomponentenklasse oder dem Namen der Klasse ohne Suffix übereinstimmen. Bei Verwendung des ViewComponent-Attributs muss der Klassenname mit der Attributbezeichnung übereinstimmen.

  • Erstellen einer Views/Shared/Components/PriorityList/Default.cshtmlRazor-Ansicht:

    @model IEnumerable<ViewComponentSample.Models.TodoItem>
    
    <h3>Priority Items</h3>
    <ul>
        @foreach (var todo in Model)
        {
            <li>@todo.Name</li>
        }
    </ul>
    

    Die Razor-Ansicht nimmt eine Liste von TodoItem entgegen und zeigt diese an. Wenn die InvokeAsync-Methode der Ansichtskomponente nicht den Namen der Ansicht übergibt, wird Default per Konvention als Ansichtsname verwendet. Fügen Sie eine Ansicht einem controllerspezifischen Ansichtsordner hinzu, um das Standardformat für einen spezifischen Controller zu überschreiben (z.B. Views/ToDo/Components/PriorityList/Default.cshtml).

    Wenn die Ansichtskomponente controllerspezifisch ist, kann sie dem controllerspezifischen Ordner hinzugefügt werden. Beispielsweise ist Views/ToDo/Components/PriorityList/Default.cshtml controllerspezifisch.

  • Fügen Sie ein div-Element, das einen Aufruf an eine Prioritätslistenkomponente enthält, am Ende der Datei Views/ToDo/index.cshtml hinzu:

    </table>
    
    <div>
        Maximum Priority: @ViewData["maxPriority"] <br />
        Is Complete:  @ViewData["isDone"]
        @await Component.InvokeAsync("PriorityList",
                         new { 
                             maxPriority =  ViewData["maxPriority"],
                             isDone = ViewData["isDone"]  }
                         )
    </div>
    

Das Markup @await Component.InvokeAsync zeigt die Syntax für die aufrufenden Ansichtskomponenten an. Das erste Argument ist der Name der aufzurufenden Komponente. Darauffolgende Parameter werden an die Komponente übergeben. InvokeAsync kann eine arbiträre Anzahl von Argumenten annehmen.

Testen Sie die App. In der folgenden Abbildung werden die ToDo-Liste und die Elemente mit Priorität angezeigt:

ToDo-Liste und Prioritätselemente

Die Ansichtskomponente kann direkt aus dem Controller aufgerufen werden:

public IActionResult IndexVC(int maxPriority = 2, bool isDone = false)
{
    return ViewComponent("PriorityList",
        new { 
           maxPriority = maxPriority,
           isDone = isDone
        });
}

Prioritätselemente der IndexVC-Aktion

Angeben eines Namens für eine Ansichtskomponente

Eine komplexe Ansichtskomponente erfordert möglicherweise, dass unter bestimmten Umständen eine Ansicht angegeben wird, die nicht dem Standard entspricht. Der folgende Code zeigt, wie die Ansicht „PVC“ in der InvokeAsync-Methode angegeben wird. Aktualisieren Sie die InvokeAsync-Methode in der PriorityListViewComponent-Klasse.

public async Task<IViewComponentResult> InvokeAsync(
                                           int maxPriority, bool isDone)
{
    string MyView = "Default";
    // If asking for all completed tasks, render with the "PVC" view.
    if (maxPriority > 3 && isDone == true)
    {
        MyView = "PVC";
    }
    var items = await GetItemsAsync(maxPriority, isDone);
    return View(MyView, items);
}

Kopieren Sie die Datei Views/Shared/Components/PriorityList/Default.cshtml in eine Ansicht mit dem Namen Views/Shared/Components/PriorityList/PVC.cshtml. Fügen Sie eine Überschrift hinzu, um anzugeben, dass die PVC-Ansicht verwendet wird.

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h2> PVC Named Priority Component View</h2>
<h4>@ViewBag.PriorityMessage</h4>
<ul>
    @foreach (var todo in Model)
    {
        <li>@todo.Name</li>
    }
</ul>

Führen Sie die App aus und überprüfen Sie die PVC-Ansicht.

Ansichtskomponente mit Priorität

Wenn die Ansicht „PVC“ nicht gerendert wird, stellen Sie sicher, dass die Ansichtskomponente mit einer Priorität von mindestens 4 aufgerufen wird.

Untersuchen des Ansichtspfads

  • Ändern Sie den Prioritätsparameter in drei oder weniger, damit die Prioritätsansicht nicht zurückgegeben wird.

  • Benennen Sie vorübergehend Views/ToDo/Components/PriorityList/Default.cshtml in 1Default.cshtml um.

  • Wenn Sie die App testen, tritt der folgende Fehler auf:

    An unhandled exception occurred while processing the request.
    InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
    /Views/ToDo/Components/PriorityList/Default.cshtml
    /Views/Shared/Components/PriorityList/Default.cshtml
    
  • Kopieren Sie Views/ToDo/Components/PriorityList/1Default.cshtml nach Views/Shared/Components/PriorityList/Default.cshtml.

  • Fügen Sie der ToDo-Ansichtskomponentenansicht Shared (Freigegeben) Markup hinzu, um anzugeben, dass die Ansicht aus dem Ordner Shared stammt.

  • Testen Sie die Komponentenansicht Shared (Freigegeben).

ToDo-Ausgabe mit Komponentenansicht „Shared“

Vermeiden von hartcodierten Zeichenfolgen

Aus Gründen der Sicherheit zur Kompilierzeit sollten Sie den hartcodierten Komponentennamen durch den Klassennamen ersetzen. Aktualisieren Sie die Datei PriorityListViewComponent.cs so, dass nicht das Suffix „ViewComponent“ verwendet wird:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents;

public class PriorityList : ViewComponent
{
    private readonly ToDoContext db;

    public PriorityList(ToDoContext context)
    {
        db = context;
    }

    public async Task<IViewComponentResult> InvokeAsync(
                                               int maxPriority, bool isDone)
    {
        var items = await GetItemsAsync(maxPriority, isDone);
        return View(items);
    }

    private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
    {
        return db!.ToDo!.Where(x => x.IsDone == isDone &&
                             x.Priority <= maxPriority).ToListAsync();
    }
}

Hier ist die Ansichtsdatei:

</table>

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

In einer Überladung der Component.InvokeAsync-Methode mit einem CLR-Typ wird der Operator typeof verwendet:

</table>

<div>
    Testing typeof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(typeof(PriorityList),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

Ausführen synchroner Verarbeitung

Das Framework verarbeitet das Aufrufen einer synchronen Invoke-Methode, wenn keine asynchrone Arbeit erforderlich ist. Die folgende Methode erstellt eine synchrone Invoke-Ansichtskomponente:

using Microsoft.AspNetCore.Mvc;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityListSync : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityListSync(ToDoContext context)
        {
            db = context;
        }

        public IViewComponentResult Invoke(int maxPriority, bool isDone)
        {
 
            var x = db!.ToDo!.Where(x => x.IsDone == isDone &&
                                  x.Priority <= maxPriority).ToList();
            return View(x);
        }
    }
}

Die Razor-Datei der Ansichtskomponente:

<div>
    Testing nameof(PriorityList) <br />

    Maxium Priority: @ViewData["maxPriority"] <br />
    Is Complete:  @ViewData["isDone"]
    @await Component.InvokeAsync(nameof(PriorityListSync),
                     new { 
                         maxPriority =  ViewData["maxPriority"],
                         isDone = ViewData["isDone"]  }
                     )
</div>

Die Ansichtskomponente wird in einer Razor-Datei (z. B. Views/Home/Index.cshtml) mit einem der folgenden Verfahren aufgerufen:

Rufen Sie Component.InvokeAsync auf, um das IViewComponentHelper-Verfahren zu verwenden:

@await Component.InvokeAsync(nameof(PriorityList),
                             new { maxPriority = 4, isDone = true })

Um das Taghilfsprogramm zu verwenden, registrieren Sie die Assembly, die die Ansichtskomponente enthält, mit der Anweisung @addTagHelper (die Ansichtskomponente befindet sich in einer Assembly namens MyWebApp):

@addTagHelper *, MyWebApp

Verwenden des Taghilfsprogramms der Ansichtskomponente in der Razor-Markupdatei:

<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>

Die Methodensignatur von PriorityList.Invoke ist synchron, aber Razor sucht nach der Methode und ruft sie mit Component.InvokeAsync in der Markupdatei auf.

Zusätzliche Ressourcen

Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)

Ansichtskomponenten

Ansichtskomponenten ähneln zwar den Teilansichten, sind aber wesentlich leistungsstärker. Ansichtskomponenten verwenden keine Modellbindungen und sind nur von den Daten abhängig, die bei ihrem Aufruf bereitgestellt werden. Beim Schreiben dieses Artikels wurden Controller und Ansichten verwendet, aber Ansichtskomponenten funktionieren auch mit Razor Pages.

Eine Ansichtskomponente:

  • Rendert nur einen Block statt einer gesamten Antwort.
  • Umfasst die gleiche Trennung von Belangen und Vorzüge der Testbarkeit, die auch zwischen einem Controller und einer Ansicht bestehen.
  • Kann Parameter und Geschäftslogik aufweisen.
  • Wird normalerweise von einer Layoutseite aus aufgerufen.

Ansichtskomponenten wurden für wiederverwendbare Renderinglogik entwickelt, die für eine Teilansicht zu komplex ist. Dazu gehören:

  • Dynamische Navigationsmenüs
  • Tag Cloud (dort, wo die Datenbank abgefragt wird)
  • ein Anmeldebereich
  • Einkaufswagen
  • vor Kurzem veröffentlichte Artikel
  • Inhalt in einer Seitenleiste auf einem klassischen Blog
  • Ein Anmeldebereich, der auf jeder Seite gerendert wird und der die Links zum Abmelden bzw. Anmelden anzeigt, je nachdem, ob der Benutzer an- oder abgemeldet ist

Eine Ansichtskomponente umfasst zwei Teile: die Klasse (normalerweise von ViewComponent abgeleitet) und das von ihr zurückgegebene Ergebnis (normalerweise eine Ansicht). Eine Ansichtskomponente kann, ähnlich wie Controller, ein POCO sein. Die meisten Entwickelnden sollten jedoch von den durch Ableiten von ViewComponent verfügbaren Methoden und Eigenschaften Gebrauch machen.

Bei der Überlegung, ob Ansichtskomponenten den Spezifikationen einer App entsprechen, sollten Sie stattdessen Razor-Komponenten in Betracht ziehen. Razor-Komponenten kombinieren zudem Markup mit C#-Code, um wiederverwendbare UI-Einheiten zu erzeugen. RazorKomponenten wurden für Entwicklerproduktivität bei der Bereitstellung von clientseitiger Benutzeroberflächenlogik und -gestaltung konzipiert. Weitere Informationen finden Sie unter Razor-Komponenten in ASP.NET Core. Informationen zum Integrieren von Razor Komponenten in eine MVC- oder Razor Pages-App finden Sie unter Integrieren ASP.NET KernkomponentenRazor.

Erstellen einer Ansichtskomponente

In diesem Abschnitt werden die allgemeinen Anforderungen zum Erstellen einer Ansichtskomponente beschrieben. Im Folgenden wird jeder Schritt ausführlich betrachtet, und Sie erstellen im Zuge dessen eine Ansichtskomponente.

Die Ansichtskomponentenklasse

Eine Ansichtskomponentenklasse kann durch folgende Aktionen erstellt werden:

  • durch die Ableitung von ViewComponent
  • durch Ergänzen der Klasse mit dem Attribut [ViewComponent] oder durch das Ableiten von einer Klasse mit dem Attribut [ViewComponent]
  • durch das Erstellen einer Klasse, deren Name mit dem Suffix ViewComponent endet

Ansichtskomponenten müssen, genauso wie Controller, öffentliche, unverschachtelt und nicht abstrakte Klassen sein. Der Ansichtskomponentenname ist der Klassenname ohne den Suffix „ViewComponent“. Er kann zudem explizit mit der Eigenschaft ViewComponentAttribute.Name angegeben werden.

Eine Ansichtskomponentenklasse:

Um zu verhindern, dass eine Klasse mit einem ViewComponent-Suffix ohne Beachtung der Groß-/Kleinschreibung als Ansichtskomponente behandelt wird, versehen Sie die Klasse mit dem [NonViewComponent]-Attribut:

[NonViewComponent]
public class ReviewComponent
{
    // ...

Ansichtskomponentenmethoden

Eine Ansichtskomponente definiert ihre Logik in einer InvokeAsync-Methode, die ein Task<IViewComponentResult> zurückgibt, oder in einer synchronen Invoke-Methode, die ein IViewComponentResult zurückgibt. Parameter stammen direkt vom Aufruf der Ansichtskomponente und nicht von der Modellbindung. Eine Ansichtskomponente behandelt nie direkt eine Anfrage. Normalerweise initialisiert eine Ansichtskomponente ein Modell und übergibt dieses an eine Ansicht, indem sie die View-Methode aufruft. Zusammengefasst bedeutet dies für Komponentenmethoden Folgendes:

  • Es wird eine InvokeAsync-Methode definiert, die ein Task<IViewComponentResult> zurückgibt, oder eine synchrone Invoke-Methode, die ein IViewComponentResult zurückgibt.
  • Normalerweise initialisieren die Methoden ein Modell und übergeben dieses durch Aufrufen der ViewComponent-Methode View an eine Ansicht.
  • Parameter stammen aus der aufrufenden Methode, nicht aus HTTP. Es gibt keine Modellbindung.
  • Die Methoden sind nicht direkt als HTTP-Endpunkt erreichbar. Sie werden von Ihrem Code aufgerufen (üblicherweise in einer Ansicht). Eine Ansichtskomponente verarbeitet nie eine Anforderung.
  • Methoden werden in der Signatur überladen, nicht in Details der aktuellen HTTP-Anforderung.

Anzeigen des Suchpfads

Die Runtime sucht in den folgenden Pfaden nach der Ansicht:

  • /Views/{Controllername} /Components/{Ansichtskomponentenname}/{Ansichtsname}
  • /Views/Shared/Components/{Ansichtskomponentenname}/{Ansichtsname}
  • /Pages/Shared/Components/{Ansichtskomponentenname}/{Ansichtsname}
  • /Areas/{Bereichsname}/Views/Shared/Components/{Name der Ansichtskomponente}/{Name der Ansicht}

Der Suchpfad betrifft Projekte mit Controllern und Ansichten sowie Razor Pages.

Der Standardansichtsname für die Ansichtskomponente lautet Default. Dies bedeutet, dass Ihre Ansichtsdatei normalerweise den Namen Default.cshtml aufweist. Sie können einen anderen Ansichtsnamen angeben, wenn Sie die Ansichtskomponentenergebnisse erstellen oder die View-Methode aufrufen.

Es wird empfohlen, der Ansichtsdatei den Namen Default.cshtml zu geben und den Pfad View/Shared/Components/{Name der Ansichtskomponente}/{Name der Ansicht} zu verwenden. In der in diesem Beispiel verwendeten Ansichtskomponente PriorityList wird Views/Shared/Components/PriorityList/Default.cshtml als Ansicht der Ansichtskomponente verwendet.

Anpassen des Ansichtssuchpfads

Um den Suchpfad für die Ansicht anzupassen, ändern Sie die Sammlung Razor von ViewLocationFormats. Um z. B. nach Ansichten im Pfad „/Components/{Name der Ansichtskomponente}/{Name der Ansicht}“ zu suchen, fügen Sie der Auflistung ein neues Element hinzu:

services.AddMvc()
    .AddRazorOptions(options =>
    {
        options.ViewLocationFormats.Add("/{0}.cshtml");
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Im vorangehenden Code stellt der Platzhalter „{0}“ den Pfad „Components/{Name der Ansichtskomponente}/{Name der Ansicht}“ dar.

Aufrufen einer Ansichtskomponente

Rufen Sie Folgendes in der Ansicht auf, wenn Sie die Ansichtskomponente verwenden möchten:

@await Component.InvokeAsync("Name of view component", {Anonymous Type Containing Parameters})

Die Parameter werden an die InvokeAsync-Methode übergeben. Die im Artikel entwickelte Ansichtskomponente PriorityList wird über die Ansichtsdatei Views/ToDo/Index.cshtml aufgerufen. Im folgenden Code wird die InvokeAsync-Methode mit zwei Parametern aufgerufen:

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

Aufrufen einer Ansichtskomponente als Taghilfsprogramm

In ASP.NET Core 1.1 und höher können Sie eine Ansichtskomponente als Taghilfsprogramm aufrufen.

<vc:priority-list max-priority="2" is-done="false">
</vc:priority-list>

Namen von Klassen und Methodenparameter für Taghilfsprogramme, die in Pascal-Schreibweise angegeben sind, werden in Kebab-Schreibweise übersetzt. Das Taghilfsprogramm zum Aufrufen einer Ansichtskomponente verwendet das <vc></vc>-Element. Die Ansichtskomponente wird wie folgt angegeben:

<vc:[view-component-name]
  parameter1="parameter1 value"
  parameter2="parameter2 value">
</vc:[view-component-name]>

Damit Sie eine Ansichtskomponente als Taghilfsprogramm verwenden können, registrieren Sie die Assembly, die die Ansichtskomponente enthält, mit der @addTagHelper-Anweisung. Wenn sich die Ansichtskomponente in einer Assembly mit dem Namen MyWebApp befindet, fügen Sie der Datei _ViewImports.cshtml die folgende Anweisung hinzu:

@addTagHelper *, MyWebApp

Sie können eine Ansichtskomponente als Taghilfsprogramm für jede Datei registrieren, die auf die Ansichtskomponente verweist. Weitere Informationen zum Registrieren eines Taghilfsprogramms finden Sie unter Managing Tag Helper Scope (Verwalten des Bereichs des Taghilfsprogramms).

Die InvokeAsync-Methode, die in diesem Tutorial verwendet wird:

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

In Taghilfsprogramm-Markup:

<vc:priority-list max-priority="2" is-done="false">
</vc:priority-list>

Im Beispiel oben stehenden Beispiel wird die Ansichtskomponente PriorityList zu priority-list. Die Parameter der Ansichtskomponente werden als Attribute in Kebab-Schreibweise übergeben.

Direkter Aufrufe einer Ansichtskomponente von einem Controller

Ansichtskomponenten werden normalerweise von einer Ansicht aus aufgerufen, aber Sie können sie auch direkt von einer Controllermethode aus aufrufen. Obwohl Ansichtskomponenten keine Endpunkte so wie Controller definieren, können Sie trotzdem eine Controlleraktion implementieren, die den Inhalt von ViewComponentResult zurückgibt.

In diesem Beispiel wird die Ansichtskomponente direkt von einem Controller aus aufgerufen:

public IActionResult IndexVC()
{
    return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}

Exemplarische Vorgehensweise: Erstellen einer einfachen Ansichtskomponente

Laden Sie den Startercode herunter, und erstellen und testen Sie diesen. Dabei handelt es sich um ein einfaches Projekt mit einem ToDo-Controller, in dem eine Liste von ToDo-Elementen angezeigt wird.

Liste mit ToDo-Elementen

Hinzufügen einer ViewComponent-Klasse

Erstellen Sie einen ViewComponents-Ordner, und fügen Sie die folgende PriorityListViewComponent-Klasse hinzu:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityListViewComponent : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityListViewComponent(ToDoContext context)
        {
            db = context;
        }

        public async Task<IViewComponentResult> InvokeAsync(
        int maxPriority, bool isDone)
        {
            var items = await GetItemsAsync(maxPriority, isDone);
            return View(items);
        }
        private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
        {
            return db.ToDo.Where(x => x.IsDone == isDone &&
                                 x.Priority <= maxPriority).ToListAsync();
        }
    }
}

Bemerkungen zum Code:

  • Ansichtskomponentenklassen können sich in jedem Ordner im Projekt befinden.

  • Da der Klassenname „PriorityListViewComponent“ mit dem Suffix ViewComponent endet, verwendet die Runtime für Verweise auf die Klassenkomponente aus einer Ansicht die Zeichenfolge PriorityList.

  • Das [ViewComponent]-Attribut kann den Namen ändern, der zum Verweis auf eine Ansichtskomponente verwendet wird. Die Klasse könnte beispielsweise mit dem ViewComponent-Attribut den Namen XYZ erhalten:

    [ViewComponent(Name = "PriorityList")]
       public class XYZ : ViewComponent
    
  • Das [ViewComponent]-Attribut im vorstehenden Code teilt dem Ansichtskomponentenselektor mit, Folgendes zu verwenden:

    • Den Namen PriorityList für die Suche nach den Ansichten, die der Komponente zugeordnet sind
    • Die Zeichenfolge „PriorityList“, wenn aus einer Sicht auf die Klassenkomponente verwiesen wird
  • Die Komponente verwendet Dependency Injection, um den Datenkontext verfügbar zu machen.

  • InvokeAsync macht eine Methode verfügbar, die von einer Ansicht aus aufgerufen werden kann, und akzeptiert eine arbiträre Anzahl von Argumenten.

  • Die InvokeAsync-Methode gibt mehrere ToDo-Elemente zurück, die die Bedingungen der Parameter isDone und maxPriority erfüllen.

Erstellen der Razor-Ansicht der Ansichtskomponente

  • Erstellen Sie den Ordner Views/Shared/Components. Dieser Ordner muss den Namen Components aufweisen.

  • Erstellen Sie den Ordner Views/Shared/Components/PriorityList. Der Ordnername muss mit dem Namen der Ansichtskomponentenklasse oder mit dem Namen der Klasse ohne Suffix (wenn wir uns an die Konvention gehalten und ViewComponent als Suffix im Klassennamen verwendet haben) übereinstimmen. Wenn Sie das Attribut ViewComponent verwenden, muss der Klassenname mit der Attributbezeichnung übereinstimmen.

  • Erstellen einer Views/Shared/Components/PriorityList/Default.cshtmlRazor-Ansicht:

    @model IEnumerable<ViewComponentSample.Models.TodoItem>
    
    <h3>Priority Items</h3>
    <ul>
        @foreach (var todo in Model)
        {
            <li>@todo.Name</li>
        }
    </ul>
    

    Die Razor-Ansicht nimmt eine Liste von TodoItem entgegen und zeigt diese an. Wenn die InvokeAsync-Methode der Ansichtskomponente nicht den Namen der Ansicht übergibt (wie in unserem Beispiel), wird Default per Konvention für den Ansichtsnamen verwendet. Später in diesem Tutorial erfahren Sie, wie Sie den Namen der Ansicht übergeben. Fügen Sie eine Ansicht einem controllerspezifischen Ansichtsordner hinzu, um das Standardformat für einen spezifischen Controller zu überschreiben (z.B. Views/ToDo/Components/PriorityList/Default.cshtml).

    Wenn die Ansichtskomponente controllerspezifisch ist, können Sie sie dem controllerspezifischen Ordner (Views/ToDo/Components/PriorityList/Default.cshtml) hinzufügen.

  • Fügen Sie ein div-Element, das einen Aufruf an eine Prioritätslistenkomponente enthält, am Ende der Datei Views/ToDo/index.cshtml hinzu:

    </table>
    <div>
        @await Component.InvokeAsync("PriorityList", new { maxPriority = 2, isDone = false })
    </div>
    

Das Markup @await Component.InvokeAsync zeigt die Syntax für die aufrufenden Ansichtskomponenten an. Das erste Argument ist der Name der aufzurufenden Komponente. Darauffolgende Parameter werden an die Komponente übergeben. InvokeAsync kann eine arbiträre Anzahl von Argumenten annehmen.

Testen Sie die App. In der folgenden Abbildung werden die ToDo-Liste und die Elemente mit Priorität angezeigt:

ToDo-Liste und Prioritätselemente

Sie können Sie Ansichtskomponente auch direkt vom Controller aus aufrufen:

public IActionResult IndexVC()
{
    return ViewComponent("PriorityList", new { maxPriority = 3, isDone = false });
}

Prioritätselemente der IndexVC-Aktion

Angeben eines Ansichtsnamens

Eine komplexe Ansichtskomponente erfordert möglicherweise, dass unter bestimmten Umständen eine Ansicht angegeben wird, die nicht dem Standard entspricht. Der folgende Code zeigt, wie die Ansicht „PVC“ in der InvokeAsync-Methode angegeben wird. Aktualisieren Sie die InvokeAsync-Methode in der PriorityListViewComponent-Klasse.

public async Task<IViewComponentResult> InvokeAsync(
    int maxPriority, bool isDone)
{
    string MyView = "Default";
    // If asking for all completed tasks, render with the "PVC" view.
    if (maxPriority > 3 && isDone == true)
    {
        MyView = "PVC";
    }
    var items = await GetItemsAsync(maxPriority, isDone);
    return View(MyView, items);
}

Kopieren Sie die Datei Views/Shared/Components/PriorityList/Default.cshtml in eine Ansicht mit dem Namen Views/Shared/Components/PriorityList/PVC.cshtml. Fügen Sie eine Überschrift hinzu, um anzugeben, dass die PVC-Ansicht verwendet wird.

@model IEnumerable<ViewComponentSample.Models.TodoItem>

<h2> PVC Named Priority Component View</h2>
<h4>@ViewBag.PriorityMessage</h4>
<ul>
    @foreach (var todo in Model)
    {
        <li>@todo.Name</li>
    }
</ul>

Aktualisieren Sie Views/ToDo/Index.cshtml:

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

Führen Sie die App aus und überprüfen Sie die PVC-Ansicht.

Ansichtskomponente mit Priorität

Wenn die PVC-Ansicht nicht gerendert wird, stellen Sie sicher, dass Sie die Ansichtskomponente mit einer Priorität von 4 oder höher aufrufen.

Untersuchen des Ansichtspfads

  • Ändern Sie den Prioritätsparameter in drei oder weniger, damit die Prioritätsansicht nicht zurückgegeben wird.

  • Benennen Sie vorübergehend Views/ToDo/Components/PriorityList/Default.cshtml in 1Default.cshtml um.

  • Wenn Sie die App testen, erhalten Sie die folgende Fehlermeldung:

    An unhandled exception occurred while processing the request.
    InvalidOperationException: The view 'Components/PriorityList/Default' wasn't found. The following locations were searched:
    /Views/ToDo/Components/PriorityList/Default.cshtml
    /Views/Shared/Components/PriorityList/Default.cshtml
    EnsureSuccessful
    
  • Kopieren Sie Views/ToDo/Components/PriorityList/1Default.cshtml nach Views/Shared/Components/PriorityList/Default.cshtml.

  • Fügen Sie der ToDo-Ansichtskomponentenansicht Shared (Freigegeben) Markup hinzu, um anzugeben, dass die Ansicht aus dem Ordner Shared stammt.

  • Testen Sie die Komponentenansicht Shared (Freigegeben).

ToDo-Ausgabe mit Komponentenansicht „Shared“

Vermeiden hart codierter Zeichenfolgen

Wenn Sie Sicherheit zu Kompilierzeit haben möchten, können Sie den hart codierten Komponentennamen durch den Klassennamen ersetzen. Erstellen Sie die Ansichtskomponente ohne den Suffix „ViewComponent“:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityList : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityList(ToDoContext context)
        {
            db = context;
        }

        public async Task<IViewComponentResult> InvokeAsync(
        int maxPriority, bool isDone)
        {
            var items = await GetItemsAsync(maxPriority, isDone);
            return View(items);
        }
        private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
        {
            return db.ToDo.Where(x => x.IsDone == isDone &&
                                 x.Priority <= maxPriority).ToListAsync();
        }
    }
}

Fügen Sie Ihrer Razor-Ansichtsdatei eine using-Anweisung hinzu, und verwenden Sie den nameof-Operator:

@using ViewComponentSample.Models
@using ViewComponentSample.ViewComponents
@model IEnumerable<TodoItem>

    <h2>ToDo nameof</h2>
    <!-- Markup removed for brevity.  -->

    <div>

        @*
            Note: 
            To use the below line, you need to #define no_suffix in ViewComponents/PriorityList.cs or it won't compile.
            By doing so it will cause a problem to index as there will be multiple viewcomponents 
            with the same name after the compiler removes the suffix "ViewComponent"
        *@

        @*@await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })*@
    </div>

Sie können eine Überladung der Component.InvokeAsync-Methode verwenden, die einen CLR-Typ annimmt. Denken Sie daran, in diesem Fall den typeof-Operator zu verwenden:

@using ViewComponentSample.Models
@using ViewComponentSample.ViewComponents
@model IEnumerable<TodoItem>

<h2>ToDo typeof</h2>
<!-- Markup removed for brevity.  -->

<div>
    @await Component.InvokeAsync(typeof(PriorityListViewComponent), new { maxPriority = 4, isDone = true })
</div>

Ausführen synchroner Verarbeitung

Das Framework verarbeitet den Aufruf einer synchronen Invoke-Methode, wenn Sie keine asynchrone Verarbeitung durchführen müssen. Die folgende Methode erstellt eine synchrone Invoke-Ansichtskomponente:

public class PriorityList : ViewComponent
{
    public IViewComponentResult Invoke(int maxPriority, bool isDone)
    {
        var items = new List<string> { $"maxPriority: {maxPriority}", $"isDone: {isDone}" };
        return View(items);
    }
}

In der Razor-Datei der Ansichtskomponente werden die Zeichenfolgen aufgelistet, die an die Invoke-Methode (Views/Home/Components/PriorityList/Default.cshtml) übergeben werden:

@model List<string>

<h3>Priority Items</h3>
<ul>
    @foreach (var item in Model)
    {
        <li>@item</li>
    }
</ul>

Die Ansichtskomponente wird in einer Razor-Datei (z. B. Views/Home/Index.cshtml) mit einem der folgenden Verfahren aufgerufen:

Rufen Sie Component.InvokeAsync auf, um das IViewComponentHelper-Verfahren zu verwenden:

@await Component.InvokeAsync(nameof(PriorityList), new { maxPriority = 4, isDone = true })

Um das Taghilfsprogramm zu verwenden, registrieren Sie die Assembly, die die Ansichtskomponente enthält, mit der Anweisung @addTagHelper (die Ansichtskomponente befindet sich in einer Assembly namens MyWebApp):

@addTagHelper *, MyWebApp

Verwenden des Taghilfsprogramms der Ansichtskomponente in der Razor-Markupdatei:

<vc:priority-list max-priority="999" is-done="false">
</vc:priority-list>

Die Methodensignatur von PriorityList.Invoke ist synchron, aber Razor sucht nach der Methode und ruft sie mit Component.InvokeAsync in der Markupdatei auf.

Alle Parameter einer Ansichtskomponente sind erforderlich

Jeder Parameter in einer Ansichtskomponente ist ein erforderliches Attribut. Weitere Informationen finden Sie im entsprechenden GitHub-Issue. Wenn ein Parameter ausgelassen wird, geschieht Folgendes:

  • Für die Signatur der InvokeAsync-Methode kann keine Übereinstimmung ermittelt werden. Die Methode wird daher nicht ausgeführt.
  • Das Markup des ViewComponent-Elements wird nicht gerendert.
  • Es werden keine Fehler ausgelöst.

Zusätzliche Ressourcen