Files
deepdrft/product-notes/team-brief-authblocks-modelview-di.md

12 KiB

Team Brief — AuthBlocks: Register ModelView's Missing Dependency via ConfigureAuthServices

Audience: an orchestrator (and its implementers) working only in the AuthBlocks repository at C:\Development\AuthBlocks. You do not need, and should not assume, any knowledge of the products that consume AuthBlocks. Everything you need is in this brief or in that one repo (plus a single new BlazorBlocks package version — see §2, the blocking prerequisite).

Status: RESOLVED — shipped in Cerebellum.BlazorBlocks.Web 10.3.33 + Cerebellum.AuthBlocks.Web 10.3.36 (2026-06-20). scoped request, blocked on a BlazorBlocks publish (see §2). Confirmed at runtime against Cerebellum.AuthBlocks.Web 10.3.33 / Cerebellum.BlazorBlocks.Web 10.3.32. Author: product-designer (for a downstream consumer team). Date: 2026-06-19.

Resolution (2026-06-20): AddBlazorBlocksWeb() landed in Cerebellum.BlazorBlocks.Web 10.3.33 and ConfigureAuthServices calls it in Cerebellum.AuthBlocks.Web 10.3.36; DeepDrftManager picked up 10.3.36 and removed its local EditModalSaveContextHolder stopgap. This brief is retained as historical record — no further action required.

This is one half of a two-team, ordered fix. AuthBlocks ships second — see §2 and §9.


1. The defect in one sentence

AuthBlocksWeb ships Users.razor and Registrations.razor, both of which render BlazorBlocks' <ModelView> component — but ConfigureAuthServices (the single DI entry point consumers call to light up the AuthBlocks Web surface) does not ensure ModelView's required service Web.Maintenance.Entities.EditModalSaveContextHolder is registered. So a consumer that wires up AuthBlocks the normal way gets an unhandled InvalidOperationException that terminates the Blazor circuit on navigation to either page, unless that consumer manually hand-registers an internal BlazorBlocks service in its own Program.cs.

The fix: have ConfigureAuthServices call BlazorBlocks' new AddBlazorBlocksWeb() extension (which registers the holder), so AuthBlocks stays self-contained for its consumers.


2. Blocking prerequisite — BlazorBlocks must ship first

This fix cannot land until BlazorBlocks publishes a new Cerebellum.BlazorBlocks.Web version that exposes a Web-side registration extension (working name AddBlazorBlocksWeb()). That extension is what actually registers EditModalSaveContextHolder; AuthBlocks' job is only to call it.

  • AuthBlocks is currently on Cerebellum.BlazorBlocks.Web 10.3.32, which has no such extension.
  • The BlazorBlocks team is shipping the extension and bumping the package as the first half of this fix.
  • Reference the specific new version BlazorBlocks publishes for this fix — fill in the exact version number once the BlazorBlocks team reports it. Do not proceed against 10.3.32; the method will not exist.

If you reach this work before the BlazorBlocks version is available, stop and wait for the published version number. The AuthBlocks change is small once the prerequisite is in hand.


3. The confirmed failure

Stack trace (captured from a consuming app on navigation to the Users admin page)

System.InvalidOperationException: Cannot provide a value for property 'SaveContextHolder' on type
'Web.Maintenance.Entities.ModelView`5[[AuthBlocksModels.InputModels.UserInputModel, ...],
[AuthBlocksModels.Models.UserModel, ...],[AuthBlocksWeb.Components.Pages.UserAdmin.Users.UserEditModal, ...],
[AuthBlocksWeb.Components.Pages.UserAdmin.Users.UsersViewModel, ...],
[AuthBlocksModels.Converters.UserModelToInputConverter, ...]]'.
There is no registered service of type 'Web.Maintenance.Entities.EditModalSaveContextHolder'.
   at Microsoft.AspNetCore.Components.ComponentFactory...CreatePropertyInjector...

ModelView declares SaveContextHolder as required with [Inject], so Blazor's component factory throws during component activation. There is no try/catch around component instantiation in the render path, so the exception propagates and tears down the circuit. The user sees a dead page / "An unhandled error has occurred" and must reload.

Which AuthBlocks pages trigger it

AuthBlocksWeb ships two user-admin pages that render <ModelView>:

  • AuthBlocksWeb/Components/Pages/UserAdmin/Users/Users.razor
  • AuthBlocksWeb/Components/Pages/UserAdmin/Registrations/Registrations.razor

Any consumer that routes to either page hits the defect on first navigation.


4. Why this is AuthBlocks' gap to close (not the consumer's)

AuthBlocksWeb/Startup.cs exposes ConfigureAuthServices(IServiceCollection, string apiBaseUrl) — the single DI entry point consumers call to light up the AuthBlocks Web surface. It already registers all the user-admin viewmodels and clients (UsersViewModel, RegistrationsViewModel, PermissionsViewModel, their clients, auth state, hierarchical authorization, etc.). It does not register EditModalSaveContextHolder and does not call any BlazorBlocks Web extension.

So AuthBlocks ships pages that depend on ModelView while leaving one of ModelView's required services unregistered. The consumer is silently expected to fill the gap — and that is exactly what happened: two independent downstream products each had to hand-register the internal BlazorBlocks service (AddScoped<Web.Maintenance.Entities.EditModalSaveContextHolder>()) in their own Program.cs to make AuthBlocks' shipped pages work. The whole value of a single ConfigureAuthServices entry point is that a consumer calling it (plus the already-required AddMudServices) gets a working surface with zero manual registrations. Today they don't. Closing this gap inside ConfigureAuthServices restores that promise.

Note: IDialogService / ISnackbar (also injected by ModelView) come from MudBlazor's AddMudServices(), which every AuthBlocks Web consumer already calls as a documented prerequisite — those are not the gap. The single library-owned gap is EditModalSaveContextHolder.


5. The fix

5.1 Bump the BlazorBlocks reference

Update the Cerebellum.BlazorBlocks.Web package reference (currently 10.3.32) to the new version BlazorBlocks publishes for this fix (see §2 — fill in the exact version). This is what makes AddBlazorBlocksWeb() available.

5.2 Call the extension from ConfigureAuthServices

// AuthBlocksWeb/Startup.cs, inside ConfigureAuthServices(...)
services.AddBlazorBlocksWeb();   // registers EditModalSaveContextHolder for the ModelView-based pages

Order does not matter for this scoped service; place it near the other registrations. The AddBlazorBlocksWeb() extension registers EditModalSaveContextHolder as scoped per circuit (its correct lifetime — the holder is per-circuit mutable state that ModelView writes and EditModelModal reads).

5.3 Why this belongs in ConfigureAuthServices, not pushed onto consumers

  • ConfigureAuthServices is AuthBlocks' single DI entry point. A consumer calling only AddMudServices() + ConfigureAuthServices(...) should get a fully working user-admin surface. Folding the BlazorBlocks call into the existing entry point keeps that promise; introducing a second method the consumer must remember to call (or expecting them to call AddBlazorBlocksWeb() themselves) just relocates the leaked registration one layer up.
  • AuthBlocks must not register EditModalSaveContextHolder directly (reaching into BlazorBlocks' internal Web.Maintenance.Entities namespace) — that is the same smell the two consumers exhibited, merely relocated. Compose BlazorBlocks' own Add* extension instead; the registration lives with its owner, and AuthBlocks stays self-contained by calling it. This is the standard ASP.NET Core layering: each library exposes an Add* for its own services, and a higher-level library's Add* calls the lower one's.

6. Constraints

  • Do not register EditModalSaveContextHolder directly in AuthBlocks. Call AddBlazorBlocksWeb(); let BlazorBlocks own its type (§5.3).
  • Keep ConfigureAuthServices the single AuthBlocks entry point. Do not introduce a second method consumers must remember to call; fold the BlazorBlocks call into the existing one.
  • MudBlazor remains a caller-owned prerequisite. AuthBlocks already relies on consumers calling AddMudServices() for all its MudBlazor-based pages; do not absorb it into ConfigureAuthServices.
  • Versioning: AuthBlocks Web packs/pushes via its pack.ps1 / packaging script. AuthBlocksWeb is currently Cerebellum.AuthBlocks.Web 10.3.33; bump to the next version after referencing the new BlazorBlocks version and adding the AddBlazorBlocksWeb() call. Record the new version so consumers can pin.

7. Acceptance criteria

  1. The Cerebellum.BlazorBlocks.Web package reference is bumped to the new version BlazorBlocks published for this fix, and ConfigureAuthServices calls AddBlazorBlocksWeb().
  2. A fresh consumer that calls only AddMudServices() and AuthBlocksWeb.Startup.ConfigureAuthServices(...) — and registers nothing else by hand — can navigate to the Users admin page and the Registrations admin page with no InvalidOperationException and no circuit teardown.
  3. Working behavior means not just page load: opening the edit dialog on a user, saving a valid change, succeeds — i.e. the save bridge actually works end-to-end through AuthBlocks' pages.
  4. No AuthBlocks consumer needs to touch Web.Maintenance.Entities directly; no manual registrations are required beyond the documented AddMudServices.
  5. AuthBlocksWeb is published as a version bump from 10.3.33, and the new version number is recorded.

8. Open questions for the implementing team / its sponsor

  1. Exact BlazorBlocks version to reference. Pending the BlazorBlocks team's publish (§2). Confirm the published version number and the exact extension method name (proposed AddBlazorBlocksWeb()) before landing the call.
  2. Placement within ConfigureAuthServices. Anywhere in the method works (scoped service, order- independent). Confirm there is no existing convention in Startup.cs for grouping third-party Add* calls that this should follow.
  3. Any other AuthBlocks pages built on ModelView/NewModelView? This brief identified Users and Registrations. If the team expects to add more maintenance pages, note that calling AddBlazorBlocksWeb() once covers them all (it is the single home for the maintenance-component deps).

9. Suggested reading order in the repo

  1. AuthBlocksWeb/Startup.csConfigureAuthServices, the single entry point; add the AddBlazorBlocksWeb() call here.
  2. AuthBlocksWeb/Components/Pages/UserAdmin/Users/Users.razor — renders <ModelView>; triggers the defect on navigation.
  3. AuthBlocksWeb/Components/Pages/UserAdmin/Registrations/Registrations.razor — the second page that renders <ModelView>.
  4. The AuthBlocks Web .csproj — the Cerebellum.BlazorBlocks.Web package reference to bump.
  5. The AuthBlocks pack.ps1 / packaging script — to bump and publish AuthBlocksWeb after referencing the new BlazorBlocks version.

10. Cross-team ordering (important — you ship second)

This fix is layered across two repos and must land in order:

  1. BlazorBlocks ships first. It adds AddBlazorBlocksWeb(), bumps Cerebellum.BlazorBlocks.Web from 10.3.32, packs/pushes, and reports the new version number.
  2. Then AuthBlocks (this team) bumps its Cerebellum.BlazorBlocks.Web reference to that published version, calls AddBlazorBlocksWeb() from ConfigureAuthServices, bumps Cerebellum.AuthBlocks.Web from 10.3.33, and publishes.

This team cannot complete its part until the BlazorBlocks version is published (§2). Confirm that version number is in hand before starting.