From 460046ae99adbf6fd76d2e1108aae211a2a1cfd8 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 29 Jan 2026 13:21:28 +0100 Subject: [PATCH 1/4] Fix IdAttributeValue generation in interactive mode --- src/Components/Web/src/Forms/InputBase.cs | 22 ++++++---- .../Web/test/Forms/InputTextTest.cs | 42 +++++++++++++++++++ 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/Components/Web/src/Forms/InputBase.cs b/src/Components/Web/src/Forms/InputBase.cs index cd8d7ee8ac50..344286c0f595 100644 --- a/src/Components/Web/src/Forms/InputBase.cs +++ b/src/Components/Web/src/Forms/InputBase.cs @@ -207,13 +207,7 @@ protected string NameAttributeValue if (_shouldGenerateFieldNames) { - if (_formattedValueExpression is null && ValueExpression is not null) - { - _formattedValueExpression = FieldPrefix != null ? FieldPrefix.GetFieldName(ValueExpression) : - ExpressionFormatter.FormatLambda(ValueExpression); - } - - return _formattedValueExpression ?? string.Empty; + return GetFieldName(); } return string.Empty; @@ -225,7 +219,7 @@ protected string NameAttributeValue /// /// /// If an explicit "id" is provided via , that value takes precedence. - /// Otherwise, the id is derived from with invalid characters sanitized. + /// Otherwise, the id is sanitized version of in SSR mode; generated independently in interactive mode. /// protected string IdAttributeValue { @@ -236,10 +230,20 @@ protected string IdAttributeValue return Convert.ToString(idAttributeValue, CultureInfo.InvariantCulture) ?? string.Empty; } - return FieldIdGenerator.SanitizeHtmlId(NameAttributeValue); + var fieldName = NameAttributeValue; + if (string.IsNullOrEmpty(fieldName)) + { + fieldName = GetFieldName(); + } + + return FieldIdGenerator.SanitizeHtmlId(fieldName); } } + private string GetFieldName() + => _formattedValueExpression ??= FieldPrefix?.GetFieldName(ValueExpression!) + ?? ExpressionFormatter.FormatLambda(ValueExpression!); + /// public override Task SetParametersAsync(ParameterView parameters) { diff --git a/src/Components/Web/test/Forms/InputTextTest.cs b/src/Components/Web/test/Forms/InputTextTest.cs index f5850029183f..21ea2393eadb 100644 --- a/src/Components/Web/test/Forms/InputTextTest.cs +++ b/src/Components/Web/test/Forms/InputTextTest.cs @@ -63,6 +63,48 @@ public async Task ExplicitIdOverridesGenerated() Assert.Equal("custom-id", idAttribute.AttributeValue); } + [Fact] + public async Task RendersIdAttribute_WhenShouldUseFieldIdentifiersIsFalse_InteractiveMode() + { + // simulate interactive mode where ShouldUseFieldIdentifiers is false + var model = new TestModel(); + var editContext = new EditContext(model) { ShouldUseFieldIdentifiers = false }; + var rootComponent = new TestInputHostComponent + { + EditContext = editContext, + ValueExpression = () => model.StringProperty, + }; + + var componentId = await RenderAndGetInputTextComponentIdAsync(rootComponent); + var frames = _testRenderer.GetCurrentRenderTreeFrames(componentId); + + // id should still be generated for Label/Input association to work in interactive mode + var idAttribute = frames.Array.SingleOrDefault(f => f.FrameType == RenderTreeFrameType.Attribute && f.AttributeName == "id"); + Assert.Equal("model_StringProperty", idAttribute.AttributeValue); + } + + [Fact] + public async Task RendersIdAttribute_WhenNoEditContext_InteractiveMode() + { + // This test simulates the scenario where InputBase falls back to !OperatingSystem.IsBrowser() + // In tests, this is true (not browser), so _shouldGenerateFieldNames would be true. + // However, if running in a browser, id would not be generated without this fix. + var model = new TestModel(); + var editContext = new EditContext(model) { ShouldUseFieldIdentifiers = false }; + var rootComponent = new TestInputHostComponent + { + EditContext = editContext, + ValueExpression = () => model.StringProperty, + }; + + var componentId = await RenderAndGetInputTextComponentIdAsync(rootComponent); + var frames = _testRenderer.GetCurrentRenderTreeFrames(componentId); + + // id should be present for Label/Input coordination even in interactive mode + var idAttribute = frames.Array.SingleOrDefault(f => f.FrameType == RenderTreeFrameType.Attribute && f.AttributeName == "id"); + Assert.Equal("model_StringProperty", idAttribute.AttributeValue); + } + private async Task RenderAndGetInputTextComponentIdAsync(TestInputHostComponent hostComponent) { var hostComponentId = _testRenderer.AssignRootComponentId(hostComponent); From c07d687ab0472ed09fac86d56a75f0513526dbd2 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 29 Jan 2026 13:48:19 +0100 Subject: [PATCH 2/4] Clean test. --- .../Web/test/Forms/InputTextTest.cs | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/src/Components/Web/test/Forms/InputTextTest.cs b/src/Components/Web/test/Forms/InputTextTest.cs index 21ea2393eadb..956e6a92e570 100644 --- a/src/Components/Web/test/Forms/InputTextTest.cs +++ b/src/Components/Web/test/Forms/InputTextTest.cs @@ -83,28 +83,6 @@ public async Task RendersIdAttribute_WhenShouldUseFieldIdentifiersIsFalse_Intera Assert.Equal("model_StringProperty", idAttribute.AttributeValue); } - [Fact] - public async Task RendersIdAttribute_WhenNoEditContext_InteractiveMode() - { - // This test simulates the scenario where InputBase falls back to !OperatingSystem.IsBrowser() - // In tests, this is true (not browser), so _shouldGenerateFieldNames would be true. - // However, if running in a browser, id would not be generated without this fix. - var model = new TestModel(); - var editContext = new EditContext(model) { ShouldUseFieldIdentifiers = false }; - var rootComponent = new TestInputHostComponent - { - EditContext = editContext, - ValueExpression = () => model.StringProperty, - }; - - var componentId = await RenderAndGetInputTextComponentIdAsync(rootComponent); - var frames = _testRenderer.GetCurrentRenderTreeFrames(componentId); - - // id should be present for Label/Input coordination even in interactive mode - var idAttribute = frames.Array.SingleOrDefault(f => f.FrameType == RenderTreeFrameType.Attribute && f.AttributeName == "id"); - Assert.Equal("model_StringProperty", idAttribute.AttributeValue); - } - private async Task RenderAndGetInputTextComponentIdAsync(TestInputHostComponent hostComponent) { var hostComponentId = _testRenderer.AssignRootComponentId(hostComponent); From 0171df0d99bbb762897f558340b71375b001c999 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Thu, 29 Jan 2026 15:04:45 +0100 Subject: [PATCH 3/4] Update src/Components/Web/src/Forms/InputBase.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Components/Web/src/Forms/InputBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Web/src/Forms/InputBase.cs b/src/Components/Web/src/Forms/InputBase.cs index 344286c0f595..9b2a62e39021 100644 --- a/src/Components/Web/src/Forms/InputBase.cs +++ b/src/Components/Web/src/Forms/InputBase.cs @@ -219,7 +219,7 @@ protected string NameAttributeValue /// /// /// If an explicit "id" is provided via , that value takes precedence. - /// Otherwise, the id is sanitized version of in SSR mode; generated independently in interactive mode. + /// Otherwise, the id is a sanitized version of in SSR mode; generated independently in interactive mode. /// protected string IdAttributeValue { From 59e9f70b443d69456d3051345eff189adc5beb98 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Thu, 29 Jan 2026 15:05:27 +0100 Subject: [PATCH 4/4] Update src/Components/Web/test/Forms/InputTextTest.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Components/Web/test/Forms/InputTextTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Web/test/Forms/InputTextTest.cs b/src/Components/Web/test/Forms/InputTextTest.cs index 956e6a92e570..695ab5da3180 100644 --- a/src/Components/Web/test/Forms/InputTextTest.cs +++ b/src/Components/Web/test/Forms/InputTextTest.cs @@ -79,7 +79,7 @@ public async Task RendersIdAttribute_WhenShouldUseFieldIdentifiersIsFalse_Intera var frames = _testRenderer.GetCurrentRenderTreeFrames(componentId); // id should still be generated for Label/Input association to work in interactive mode - var idAttribute = frames.Array.SingleOrDefault(f => f.FrameType == RenderTreeFrameType.Attribute && f.AttributeName == "id"); + var idAttribute = frames.Array.Single(f => f.FrameType == RenderTreeFrameType.Attribute && f.AttributeName == "id"); Assert.Equal("model_StringProperty", idAttribute.AttributeValue); }