Erben von Komponenten in Blazor
Ich habe ein Projekt mit einer Reihe von Formularseiten erstellt, und jede einzelne hatte dieselbe Ladestatuslogik, dieselbe Fehlerbehandlung und dieselben Toastbenachrichtigungen. Das Kopieren und Einfügen fühlte sich alles falsch an, also habe ich mich mit der Komponentenvererbung in Blazor befasst. Es stellt sich heraus, dass es ziemlich einfach ist, da Blazor-Komponenten nur C#-Klassen sind.
Die Grundlagen
Jede Blazor-Komponente erbt standardmäßig von ComponentBase. Sie können Ihre eigene Basisklasse erstellen, die ComponentBase erweitert, und dann Ihre Komponenten davon erben lassen.
Nehmen wir an, die meisten unserer Seiten benötigen eine Ladestatus- und Fehlerbehandlung. Wir können eine Basisklasse erstellen:
using Microsoft.AspNetCore.Components;
public abstract class PageBase : ComponentBase
{
protected bool IsLoading { get; set; } = true;
protected string? ErrorMessage { get; set; }
protected async Task LoadDataAsync(Func<Task> action)
{
try
{
IsLoading = true;
ErrorMessage = null;
await action();
}
catch (Exception ex)
{
ErrorMessage = ex.Message;
}
finally
{
IsLoading = false;
StateHasChanged();
}
}
}
Verwendung der Basisklasse
Jetzt erben wir in jeder Seitenkomponente nicht mehr von ComponentBase, sondern von unserem PageBase:
@page "/users"
@inherits PageBase
@if (IsLoading)
{
<div class="spinner"></div>
}
else if (ErrorMessage is not null)
{
<div class="alert alert-danger">@ErrorMessage</div>
}
else
{
<ul>
@foreach (var user in users)
{
<li>@user.Name</li>
}
</ul>
}
@code {
private List<User> users = new();
protected override async Task OnInitializedAsync()
{
await LoadDataAsync(async () =>
{
users = await Http.GetFromJsonAsync<List<User>>("api/users");
});
}
}
Die @inherits PageBase-Direktive ist der Schlüssel. Es weist Blazor an, unsere Basisklasse anstelle der Standardklasse ComponentBase zu verwenden. Jetzt erhalten wir IsLoading, ErrorMessage und LoadDataAsync() kostenlos auf jeder Seite, die davon erbt.
Injizieren von Diensten in die Basisklasse
Sie können auch Dienste in die Basisklasse einfügen, damit sie für alle untergeordneten Komponenten verfügbar sind:
public abstract class PageBase : ComponentBase
{
[Inject]
protected NavigationManager Navigation { get; set; } = default!;
[Inject]
protected IToastService Toast { get; set; } = default!;
protected bool IsLoading { get; set; } = true;
protected string? ErrorMessage { get; set; }
protected void NavigateBack() => Navigation.NavigateTo("javascript:history.back()");
protected void ShowSuccess(string message) => Toast.ShowSuccess(message);
}
Jede Komponente, die von PageBase erbt, hat jetzt Zugriff auf Navigation, Toast, NavigateBack() und ShowSuccess(), ohne dass etwas eingefügt werden muss.
Mit generischen Basisklassen tiefer gehen
Sie können sogar generische Basisklassen für gängige CRUD-Muster erstellen:
public abstract class CrudPageBase<T> : PageBase
{
protected List<T> Items { get; set; } = new();
protected T? SelectedItem { get; set; }
protected abstract Task<List<T>> FetchItems();
protected abstract Task DeleteItem(T item);
protected override async Task OnInitializedAsync()
{
await LoadDataAsync(async () =>
{
Items = await FetchItems();
});
}
protected async Task OnDelete(T item)
{
await LoadDataAsync(async () =>
{
await DeleteItem(item);
Items = await FetchItems();
ShowSuccess("Item deleted.");
});
}
}
Dann wird Ihre eigentliche Seite supersauber:
@page "/products"
@inherits CrudPageBase<Product>
@* just the markup, all logic lives in the base class *@
@code {
protected override Task<List<Product>> FetchItems()
=> Http.GetFromJsonAsync<List<Product>>("api/products");
protected override Task DeleteItem(Product item)
=> Http.DeleteAsync($"api/products/{item.Id}");
}
Wann man es verwendet und wann nicht
Die Komponentenvererbung eignet sich hervorragend für gemeinsames Verhalten wie Ladezustände, Fehlerbehandlung, Authentifizierungsprüfungen oder CRUD-Muster. Übertreiben Sie es aber nicht mit tiefen Vererbungshierarchien – wenn Sie feststellen, dass Sie mehr als zwei Ebenen tief gehen, ist die Komposition wahrscheinlich besser dran (wie der LoadingComponent-Ansatz aus einem früheren Beitrag).
Normalerweise beschränke ich mich auf eine Basisklasse pro „Seitentyp“: PageBase für normale Seiten, FormPageBase für Formulare, und das war’s auch schon.
Ich hoffe, Ihnen hat der Beitrag gefallen! Sie können mich gerne in den sozialen Medien unter @emimontesdeoca kontaktieren.