refactor(di): shared AddSharpClientViewModels registration to prevent host drift#24
Merged
Merged
Conversation
… host drift The Web host (Web/Program.cs) and the MAUI host (App/MauiProgram.cs) each registered the six presentation view models independently, and they drifted: TriggerAliasEditorViewModel was registered on Web but not MAUI, which crashed the Rules page on Android with "No registered service of type 'TriggerAliasEditorViewModel'". Extract a single AddSharpClientViewModels(ServiceLifetime perViewLifetime) extension in SharpClient.UI (referenced by both hosts). It registers the three shared VMs (Sessions, ProtocolPanel, Settings) as Singleton and the three per-view VMs (WorldManager, HistorySearch, TriggerAliasEditor) with the caller-supplied lifetime. MAUI passes Transient, Web passes Scoped, preserving each host's previous lifetimes exactly. Both hosts now call the shared method, so the two can no longer diverge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHCRc5CJ6595iMzEgYYdQp
There was a problem hiding this comment.
Pull request overview
This PR eliminates DI registration drift between the MAUI and Web hosts by moving the shared SharpClient.Core.Presentation view model registrations into a single SharpClient.UI IServiceCollection extension method, ensuring both hosts stay aligned while preserving their existing lifetimes.
Changes:
- Added
AddSharpClientViewModels(IServiceCollection, ServiceLifetime)inSharpClient.UIto centrally register the six view models. - Updated both hosts to use the shared registration method (MAUI:
Transientper-view; Web:Scopedper-view) while keeping shared VMs asSingleton. - Added an explicit
Microsoft.Extensions.DependencyInjection.Abstractionspackage reference toSharpClient.UI.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/SharpClient.Web/Program.cs | Replaces per-VM registrations with a single shared AddSharpClientViewModels(ServiceLifetime.Scoped) call. |
| src/SharpClient.App/MauiProgram.cs | Replaces per-VM registrations with AddSharpClientViewModels(ServiceLifetime.Transient) to match MAUI lifetimes. |
| src/SharpClient.UI/ServiceCollectionExtensions.cs | Introduces the shared view model DI registration extension method used by both hosts. |
| src/SharpClient.UI/SharpClient.UI.csproj | Adds DI abstractions package reference needed for ServiceDescriptor usage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| sp.GetRequiredService<ISessionHistory>(), | ||
| sp.GetRequiredService<IWorldStore>())); | ||
| // Registered via the shared extension so MAUI and Web stay in lockstep (no host drift). | ||
| // Per-view view models are Scoped here to match the Web's per-request scope. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem: host-DI drift
The two app hosts each registered the six
SharpClient.Core.Presentationview models independently:src/SharpClient.App/MauiProgram.cssrc/SharpClient.Web/Program.csBecause the two lists were maintained separately, they drifted.
TriggerAliasEditorViewModelwas registered on Web but never on MAUI, so the Rules page crashed on Android withNo registered service of type 'TriggerAliasEditorViewModel'.Fix
Add a single shared extension
AddSharpClientViewModels(this IServiceCollection, ServiceLifetime perViewLifetime)inSharpClient.UI(a Razor class library already referenced by both hosts) —src/SharpClient.UI/ServiceCollectionExtensions.cs.SessionsViewModel,ProtocolPanelViewModel,SettingsViewModel) are always registered as Singleton.WorldManagerViewModel,HistorySearchViewModel,TriggerAliasEditorViewModel) use the caller-suppliedperViewLifetimevia factory-basedServiceDescriptors, preserving the constructor-injection lambdas.Both hosts now call the shared method — MAUI with
ServiceLifetime.Transient, Web withServiceLifetime.Scoped— so each host keeps its exact previous lifetimes and the two can no longer diverge. Added theMicrosoft.Extensions.DependencyInjection.Abstractionspackage reference toSharpClient.UI.Supersedes #22
PR #22 hot-fixed the single missing
TriggerAliasEditorViewModelregistration inMauiProgram. This refactor moves all VM registrations into the shared method (which registersTriggerAliasEditorViewModelfor MAUI too), so it fixes the same crash structurally. #22 can be closed.Verified
dotnet build src/SharpClient.Web/SharpClient.Web.csproj— clean (0 warnings, 0 errors).dotnet build src/SharpClient.App/SharpClient.App.csproj -f net10.0-android— clean (0 warnings, 0 errors).dotnet run --project tests/SharpClient.Tests— 198 passed, failed: 0.dotnet run --project tests/SharpClient.UI.Tests— 47 passed, failed: 0.dotnet run --project tests/SharpClient.Data.Tests— 17 passed, failed: 0.grep -rhoE "@inject [A-Za-z]+ViewModel" src/SharpClient.UI→ Sessions, ProtocolPanel, Settings, WorldManager, HistorySearch, TriggerAliasEditor — all six).🤖 Generated with Claude Code