Zonit.Extensions.Projects
10.0.0-preview.19
See the version list below for details.
dotnet add package Zonit.Extensions.Projects --version 10.0.0-preview.19
NuGet\Install-Package Zonit.Extensions.Projects -Version 10.0.0-preview.19
<PackageReference Include="Zonit.Extensions.Projects" Version="10.0.0-preview.19" />
<PackageVersion Include="Zonit.Extensions.Projects" Version="10.0.0-preview.19" />
<PackageReference Include="Zonit.Extensions.Projects" />
paket add Zonit.Extensions.Projects --version 10.0.0-preview.19
#r "nuget: Zonit.Extensions.Projects, 10.0.0-preview.19"
#:package Zonit.Extensions.Projects@10.0.0-preview.19
#addin nuget:?package=Zonit.Extensions.Projects&version=10.0.0-preview.19&prerelease
#tool nuget:?package=Zonit.Extensions.Projects&version=10.0.0-preview.19&prerelease
Zonit.Extensions.Projects
Per-scope "which project is the user working in?" context. Framework-agnostic — no HttpContext, no
middleware here; the ASP.NET Core / Blazor wiring lives in
Zonit.Extensions.Website.
dotnet add package Zonit.Extensions.Projects
A catalog is a project. One value object, Project, two names: the
services around it are called ICatalogProvider / ICatalogManager / CatalogModel. There is no
separate catalog entity.
What you get
ICatalogProvider(read-only) —Project Project(Project.Emptywhen nothing is selected),ImmutableArray<Project> Visible,event Action? OnChange. Both members are cached snapshots, rebuilt when the manager changes.ICatalogManager(write / raw models) —Initialize(StateModel),Task<StateModel> InitializeAsync(ct),Task<bool> SwitchProjectAsync(Guid, ct), plusCatalogModel? Catalog,IReadOnlyCollection<ProjectModel>? Projects,StateModel? State(null= this scope was never hydrated) andOnChange.IProjectSource— the one contract your app implements.AddProjectsExtension()— the package's only DI entry point. All registrations are scoped.
Setup
builder.Services.AddWebsite(); // already calls AddProjectsExtension()
builder.Services.AddScoped<IProjectSource, AppProjectSource>(); // AddScoped, never TryAdd
AddProjectsExtension() TryAdds an internal null source as a safety net, so registering yours with
TryAddScoped loses to it and the app silently shows no project at all.
In a Zonit.Extensions.Website host, UseWebsite<TApp>(...) hydrates each authenticated request and
<WebsiteHydrator /> carries the snapshot across the prerender → interactive boundary. Elsewhere, call
InitializeAsync yourself when State is null.
Implementing the source
public interface IProjectSource
{
// Never returns null — an empty CatalogModel means "nothing selected".
Task<CatalogModel> InitializeAsync(CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<ProjectModel>?> GetProjectsAsync(CancellationToken cancellationToken = default);
// null == no access. The switch is refused and the current project is kept.
Task<CatalogModel?> SwitchProjectAsync(Guid projectId, CancellationToken cancellationToken = default);
}
InitializeAsync and GetProjectsAsync are invoked concurrently on the same instance, so do not
share a DbContext between them. Scoping the query to the active organization is your job — inject
IWorkspaceProvider from Zonit.Extensions.Organizations.
Reading and switching
@inject ICatalogProvider Catalog
@inject ICatalogManager Manager
@if (Catalog.Project.HasValue) @* readonly struct — never test != null *@
{
<h2>@Catalog.Project.Name</h2>
}
@foreach (var project in Catalog.Visible)
{
<button @onclick="() => SwitchAsync(project.Id)">@project.Name</button>
}
@code {
private async Task SwitchAsync(Guid id)
{
if (!await Manager.SwitchProjectAsync(id))
{
// Denied: nothing changed, no OnChange fired, the previous project is still active.
}
}
}
Malformed source rows degrade rather than throw: a row with Id == Guid.Empty is dropped from
Visible, a blank name becomes Title.Empty, and a name over 60 graphemes is cut. Visible.Length can
therefore be smaller than Projects.Count — join on Id, never by index.
The full guide, including the traps, ships inside the package and is installed into consuming repos at
.zonit/extensions/projects/projects.md.
License
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.10)
- Zonit.Extensions (>= 10.0.0-preview.19)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Zonit.Extensions.Projects:
| Package | Downloads |
|---|---|
|
Zonit.Extensions.Website
ASP.NET Core and Blazor web extensions providing base components (PageBase, PageEditBase, PageViewBase), navigation services, breadcrumbs management, toast notifications, cookie handling, and data protection utilities for building modern web applications. |
|
|
Zonit.Services.Dashboard
Package Description |
|
|
Zonit.Services.Manager
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.0-preview.22 | 0 | 8/11/2026 |
| 10.0.0-preview.21 | 0 | 8/11/2026 |
| 10.0.0-preview.20 | 0 | 8/10/2026 |
| 10.0.0-preview.19 | 0 | 8/10/2026 |
| 10.0.0-preview.18 | 35 | 8/9/2026 |
| 10.0.0-preview.17 | 38 | 8/9/2026 |
| 10.0.0-preview.16 | 35 | 8/9/2026 |
| 10.0.0-preview.15 | 47 | 8/7/2026 |
| 10.0.0-preview.14 | 43 | 8/6/2026 |
| 10.0.0-preview.13 | 49 | 8/6/2026 |
| 10.0.0-preview.12 | 42 | 8/6/2026 |
| 10.0.0-preview.11 | 69 | 8/4/2026 |
| 10.0.0-preview.10 | 58 | 8/3/2026 |
| 10.0.0-preview.9 | 69 | 5/16/2026 |
| 10.0.0-preview.6 | 77 | 5/15/2026 |
| 10.0.0-preview.2 | 70 | 5/12/2026 |
| 0.1.51 | 147 | 1/15/2026 |
| 0.1.50 | 506 | 11/18/2025 |
| 0.1.4 | 577 | 1/29/2025 |
| 0.1.3 | 228 | 6/13/2024 |