LoginForm: IStringLocalizer<LoginForm> injection is required in practice, breaks apps without AddLocalization() #35

Closed
opened 2026-06-10 22:46:50 +00:00 by Grandiras · 2 comments
Owner

v0.4.0 release notes describe the new IStringLocalizer<LoginForm> support as optional ("when registered … default copy is resolved by parameter name"). In practice, rendering <LoginForm /> in an app that has not called services.AddLocalization() throws:

System.InvalidOperationException: Cannot provide a value for property 'Localizer' on type 'ATProtoNet.Blazor.Components.LoginForm'. There is no registered service of type 'Microsoft.Extensions.Localization.IStringLocalizer`1[ATProtoNet.Blazor.Components.LoginForm]'.

Hit while upgrading Updraft.Kanban from 0.3.0 → 0.4.0: the /login page 500s. Explicit ButtonText/HandlePlaceholder values don't avoid it — the failure is at property-injection time, before parameters apply.

Expected: the localizer should be optional as documented. Suggested fix: resolve via IServiceProvider.GetService<IStringLocalizer<LoginForm>>() or an optional injection.

Workaround for consumers: builder.Services.AddLocalization();

v0.4.0 release notes describe the new `IStringLocalizer<LoginForm>` support as optional ("when registered … default copy is resolved by parameter name"). In practice, rendering `<LoginForm />` in an app that has not called `services.AddLocalization()` throws: ``` System.InvalidOperationException: Cannot provide a value for property 'Localizer' on type 'ATProtoNet.Blazor.Components.LoginForm'. There is no registered service of type 'Microsoft.Extensions.Localization.IStringLocalizer`1[ATProtoNet.Blazor.Components.LoginForm]'. ``` Hit while upgrading Updraft.Kanban from 0.3.0 → 0.4.0: the /login page 500s. Explicit `ButtonText`/`HandlePlaceholder` values don't avoid it — the failure is at property-injection time, before parameters apply. Expected: the localizer should be optional as documented. Suggested fix: resolve via `IServiceProvider.GetService<IStringLocalizer<LoginForm>>()` or an optional injection. Workaround for consumers: `builder.Services.AddLocalization();`
Collaborator

🤖 On it — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 On it — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/54); the reply will link the full session transcript. 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/ATProto.NET/issue-35/run-54.md) — updating every ~5 minutes while the task runs.
Collaborator

Fixed on branch claude/issue-35.

Cause

LoginForm declared the localizer as [Inject] private IStringLocalizer<LoginForm>? Localizer { get; set; }. Blazor's property injection resolves [Inject] members with GetRequiredService-style semantics — the ? annotation has no effect on it — so the component demanded the service at activation time, before parameters were applied. That's why explicit ButtonText/HandlePlaceholder values didn't help. The docs and release notes were correct about the intent; the wiring wasn't.

Change

src/ATProtoNet.Blazor/Components/LoginForm.razor now injects IServiceProvider and resolves the localizer lazily via Services.GetService<IStringLocalizer<LoginForm>>(), cached after first lookup. Behaviour:

  • No localization registered → built-in English defaults render (previously: 500).
  • Localizer registered → copy resolved by parameter name, unchanged.
  • Explicit parameter values → still take precedence over the localizer, unchanged.

No public API change; the Localizer member was and remains private.

Verification

Added tests/ATProtoNet.Tests/Blazor/LoginFormTests.cs — three tests rendering the component through HtmlRenderer against a real service provider: no-localization, localizer-registered (including fallback for keys the localizer doesn't know), and explicit-parameter-wins.

  • The no-localization test reproduces the reported failure on the unpatched component: I reverted the .razor file and it failed with exactly System.InvalidOperationException : Cannot provide a value for property 'Localizer' … There is no registered service of type 'Microsoft.Extensions.Localization.IStringLocalizer\1[ATProtoNet.Blazor.Components.LoginForm]'.`
  • With the fix: all 3 pass.
  • Full unit suite: 1036 passed, 0 failed (dotnet test tests/ATProtoNet.Tests/ -p:EnableSourceControlManagerQueries=false).

Also updated CHANGELOG.md (under [Unreleased]Fixed) and added a line to docs/blazor.md stating the localizer is optional and that apps without AddLocalization() need no setup.

The builder.Services.AddLocalization(); workaround stays valid for anyone on 0.4.0 until this ships.

→ Opened PR: #63


⏱ 3m 5s · 23 turns · 📜 Full session transcript

(reply recovered from the transcript — the posting step in runs before v12.3 was broken by a shell-quoting bug)

Fixed on branch **`claude/issue-35`**. ## Cause `LoginForm` declared the localizer as `[Inject] private IStringLocalizer<LoginForm>? Localizer { get; set; }`. Blazor's property injection resolves `[Inject]` members with `GetRequiredService`-style semantics — the `?` annotation has no effect on it — so the component demanded the service at activation time, before parameters were applied. That's why explicit `ButtonText`/`HandlePlaceholder` values didn't help. The docs and release notes were correct about the intent; the wiring wasn't. ## Change `src/ATProtoNet.Blazor/Components/LoginForm.razor` now injects `IServiceProvider` and resolves the localizer lazily via `Services.GetService<IStringLocalizer<LoginForm>>()`, cached after first lookup. Behaviour: - **No localization registered** → built-in English defaults render (previously: 500). - **Localizer registered** → copy resolved by parameter name, unchanged. - **Explicit parameter values** → still take precedence over the localizer, unchanged. No public API change; the `Localizer` member was and remains private. ## Verification Added `tests/ATProtoNet.Tests/Blazor/LoginFormTests.cs` — three tests rendering the component through `HtmlRenderer` against a real service provider: no-localization, localizer-registered (including fallback for keys the localizer doesn't know), and explicit-parameter-wins. - The no-localization test **reproduces the reported failure** on the unpatched component: I reverted the `.razor` file and it failed with exactly `System.InvalidOperationException : Cannot provide a value for property 'Localizer' … There is no registered service of type 'Microsoft.Extensions.Localization.IStringLocalizer\`1[ATProtoNet.Blazor.Components.LoginForm]'.` - With the fix: all 3 pass. - Full unit suite: **1036 passed, 0 failed** (`dotnet test tests/ATProtoNet.Tests/ -p:EnableSourceControlManagerQueries=false`). Also updated `CHANGELOG.md` (under `[Unreleased]` → `Fixed`) and added a line to `docs/blazor.md` stating the localizer is optional and that apps without `AddLocalization()` need no setup. The `builder.Services.AddLocalization();` workaround stays valid for anyone on 0.4.0 until this ships. → Opened PR: https://git.grandiras.net/Grandiras/ATProto.NET/pulls/63 --- ⏱ 3m 5s · 23 turns · 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/ATProto.NET/issue-35/run-54.md) _(reply recovered from the transcript — the posting step in runs before v12.3 was broken by a shell-quoting bug)_
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/ATProto.NET#35
No description provided.