Zum Inhalt

Client und Layout

Coworkee.Client.Blazor ist eine vollständige WebAssembly-Shell: Layout, Navigation, Benutzermenü, Benachrichtigungen, Admin-Seiten für Benutzer, Gruppen, Rollen, Einstellungen, Dienste, Themes, Mail-Vorlagen und Audit-Log. Die App ergänzt ihre Seiten und Menüeinträge.

MyApp.Web.Client/Program.cs
var builder = WebAssemblyHostBuilder.CreateDefault(args);
var baseAddress = new Uri(builder.HostEnvironment.BaseAddress);
builder.Services.AddCoworkeeClient(baseAddress, options =>
{
    options.AppTitle = "MyApp";
    options.AppDescription = "Clean Architecture starter for Blazor, built on Coworkee modules.";
    options.AboutLinks.Add(new AboutLink("Documentation", "https://fgilde.github.io/CoworkeeLib/"));
});
builder.Services.AddHttpClient<ICatalogApi, CatalogApi>(client => client.BaseAddress = baseAddress);
builder.Services.AddSingleton<INavigationContributor, MyAppNavigation>();
builder.Services.Configure<NavigationMenuOptions>(MyAppNavigation.Order);
var host = builder.Build();
await host.Services.InitializeCoworkeeClientAsync();
await host.RunAsync();
Routes.razor
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(CoworkeeClientOptions).Assembly }">
    <Found Context="routeData">
        <AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(CoworkeeLayout)" />
    </Found>
</Router>

Dashboard des Templates

internal sealed class MyAppNavigation : INavigationContributor
{
    private const string CatalogManagement = "Catalog Management";

    public static void Order(NavigationMenuOptions menu) => menu
        .OrderGroup("Personal", 0)
        .OrderGroup(NavigationGroups.Administration, 2)
        .OrderGroup(CatalogManagement, 3);

    public IEnumerable<CoworkeeNavItem> Items =>
    [
        new("Dashboard", "/dashboard", Icons.Material.Outlined.Dashboard, CatalogPermissions.Dashboards.View, Group: "Personal"),
        new("Products", "/catalog/products", Icons.Material.Outlined.ViewCarousel, CatalogPermissions.Products.View, Group: CatalogManagement),
        new("Brands", "/catalog/brands", Icons.Material.Outlined.Sell, CatalogPermissions.Brands.View, Group: CatalogManagement, Order: 1),
    ];
}
NavigationMenuOptions
OrderGroup(name, order) Position einer Gruppe; unbekannte Gruppen kommen zuletzt, nach Namen
Hide(hrefs) entfernt Einträge, auch die eingebauten Admin-Seiten
ShowHome, HomeTitle der Startseiten-Link oben
IconForGroup(name, icon) das Symbol eines Gruppenknotens
Place(href, group, title?, order?) verschiebt einen Link der Bibliothek in eine andere Gruppe; ein Gruppenpfad wie Administration/Localization verschachtelt

Die Admin-Seiten der Bibliothek liegen in Gruppen unter Administration: NavigationGroups.Identity (Benutzer, Gruppen, Rollen, Anwendungen, Scopes), NavigationGroups.System (Einstellungen, Dienste, Mandanten, Editionen, Themes, Sicherungen), NavigationGroups.Communication (Mail-Vorlagen, Mail-Log), NavigationGroups.Localization (Sprachen, Übersetzungen) und NavigationGroups.Monitoring (Audit-Log, KI-Werkzeugaufrufe). Verschachtelte Gruppen sortieren sich innerhalb ihrer obersten Gruppe, die ihren Platz aus OrderGroup behält.

Das Menü ist ein MudExTreeView: Gruppen sind Knoten, das Filterfeld hebt Treffer hervor und öffnet ihre Gruppen, und die Ansicht wechselt zwischen Baum, Liste und flacher Liste. Angeheftet bleibt die Leiste als Symbolstreifen unter der App-Leiste, der Menüknopf verbreitert sie; nicht angeheftet öffnet sie sich als Überlagerung und verschwindet wieder. Ob eine oder mehrere Gruppen offen bleiben, ist ein zweiter Schalter. Beides speichert der Browser.

Einträge mit HostOnly: true dienen der ganzen Installation und erscheinen nur Benutzern der Systemorganisation; die Bibliothek markiert so Mandanten, Editionen, Anwendungen, Scopes, Dienste und Sicherungen, und die Einstellungsseite zeigt ihre typisierten Reiter und den Reiter System nur dort. Die Organisation kennt der Client aus /bff/user (SystemTenant, aus dem Claim system_tenant des Auth-Servers); Anmeldungen von vor diesem Claim zeigen alles wie bisher.

Eingeklappte Navigation

App-Leiste

Die App-Leiste zeigt Theme-Menü, Rechts-nach-links-Schalter, Dunkelmodus, Benachrichtigungen, Benutzermenü und den Info-Dialog. Eigene Komponenten kommen so dazu:

internal sealed class LanguageBar : IAppBarContributor
{
    public IEnumerable<AppBarItem> Items => [new AppBarItem(typeof(LanguageSwitch), Order: 10)];
}

Info-Dialog

Die eigene API aufrufen

Typisierte Clients erben von ApiClientBase. Sie senden den CSRF-Header, den der BFF verlangt, und machen aus Problem Details eine ApiException:

internal sealed class CatalogApi(HttpClient http) : ApiClientBase(http), ICatalogApi
{
    public Task<DashboardDto> GetDashboardAsync(CancellationToken cancellationToken = default) => GetAsync<DashboardDto>("api/v1/dashboard", cancellationToken);

    public Task DeleteBrandsAsync(IReadOnlyList<Guid> ids, CancellationToken cancellationToken = default) =>
        SendAsync(HttpMethod.Post, "api/v1/brands/delete", new IdsRequest(ids), cancellationToken);
}

Snackbar.RunAsync(() => api.SaveAsync(...), "Saved") führt einen Aufruf aus und zeigt Erfolg oder die Validierungsmeldungen eines Fehlers.

Personen

UserAvatar zeigt das Bild eines Benutzers oder seine Initialen, UserCard Bild und Name mit optionaler zweiter Zeile. Beide laden gebündelt und aktualisieren sich für alle, sobald jemand Name oder Bild auf der Profilseite ändert (Realtime-Topic tenant:users).

Profilseite

<UserCard UserId="entry.ActorId">@entry.CreatedAt.ToString("g")</UserCard>
<UserAvatar UserId="message.FromUserId" Size="Size.Small" ShowTooltip="true" />

Kontoseite

/profile enthält die Tabs Profil (Bild, Name, Telefon, Adresse), Sicherheit, Benachrichtigungen und Einstellungen; /profile/{key} öffnet einen direkt. Mit einem ProfileTab fügen Sie eigene hinzu:

builder.Services.AddSingleton(new ProfileTab("Documents", "documents", typeof(MyDocuments), DocumentPermissions.Documents.View));

Bausteine

Komponente Zweck
PageHeader Titel, Beschreibung, Aktionen rechts; setzt den Browsertitel
PermissionGate, PermissionView rendern Inhalt nur mit einer Berechtigung
RealtimeSubscription ruft zurück, wenn der Server auf einem Thema veröffentlicht, z. B. type:Brand
EntityWatch hält eine Detailseite mit ihrem Eintrag gleich, geht zurück, wenn er gelöscht wird
NotAllowed was der Router statt einer verbotenen Seite zeigt, mit Wechsel zu einer Rolle, die sie erlaubt
ActiveRoleMenu, ChatNotifier im Benutzermenü: die aktive Rolle und Chat-Snackbars
CoworkeeDataTable<T> Datentabellen
ShowEditAsync aus einem Modell erzeugter Bearbeitungsdialog
AuditTimeline, VersionHistory Änderungshistorie einer Entity
ResourcePermissionsPanel Berechtigungen auf einer einzelnen Ressource