diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs index f40ff1030b..25fb1aff3e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs @@ -88,10 +88,10 @@ public class AbpInputTagHelperService : AbpTagHelperService var (inputTag, isCheckBox) = await GetInputTagHelperOutputAsync(context, output); context.Items[nameof(IsOutputHidden)] = IsOutputHidden(inputTag); - var inputHtml = inputTag.Render(_encoder); var label = await GetLabelAsHtmlAsync(context, output, inputTag, isCheckBox); var info = GetInfoAsHtml(context, output, inputTag, isCheckBox); var validation = isCheckBox ? "" : await GetValidationAsHtmlAsync(context, output, inputTag); + var inputHtml = inputTag.Render(_encoder); return (GetContent(context, output, label, inputHtml, validation, info, isCheckBox), isCheckBox); } @@ -267,7 +267,7 @@ public class AbpInputTagHelperService : AbpTagHelperService var infoText = _tagHelperLocalizer.GetLocalizedText(idAttr.Value + "InfoText", TagHelper.AspFor.ModelExplorer); - inputTagHelperOutput.Attributes.Add("aria-describedby", infoText); + inputTagHelperOutput.Attributes.SetAttribute("aria-describedby", infoText); } protected virtual bool IsInputCheckbox(TagHelperContext context, TagHelperOutput output, TagHelperAttributeList attributes) @@ -363,7 +363,7 @@ public class AbpInputTagHelperService : AbpTagHelperService div.AddCssClass("form-text"); div.InnerHtml.Append(localizedText); - inputTag.Attributes.Add("aria-describedby", idAttr?.Value + "InfoText"); + inputTag.Attributes.SetAttribute("aria-describedby", idAttr?.Value + "InfoText"); return div.ToHtmlString(); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs index e1ef97386e..854415f8c1 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs @@ -73,10 +73,10 @@ public class AbpSelectTagHelperService : AbpTagHelperService protected virtual async Task GetFormInputGroupAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperContent childContent) { var selectTag = await GetSelectTagAsync(context, output, childContent); - var selectAsHtml = selectTag.Render(_encoder); var label = await GetLabelAsHtmlAsync(context, output, selectTag); var validation = await GetValidationAsHtmlAsync(context, output, selectTag); var infoText = GetInfoAsHtml(context, output, selectTag); + var selectAsHtml = selectTag.Render(_encoder); return TagHelper.FloatingLabel ? selectAsHtml + Environment.NewLine + label + Environment.NewLine + infoText + Environment.NewLine + validation : label + Environment.NewLine + selectAsHtml + Environment.NewLine + infoText + Environment.NewLine + validation; @@ -224,7 +224,7 @@ public class AbpSelectTagHelperService : AbpTagHelperService var infoText = _tagHelperLocalizer.GetLocalizedText(idAttr.Value + "InfoText", TagHelper.AspFor.ModelExplorer); - inputTagHelperOutput.Attributes.Add("aria-describedby", infoText); + inputTagHelperOutput.Attributes.SetAttribute("aria-describedby", infoText); } protected virtual string GetInfoAsHtml(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag) @@ -256,7 +256,7 @@ public class AbpSelectTagHelperService : AbpTagHelperService div.AddCssClass("form-text"); div.InnerHtml.Append(localizedText); - inputTag.Attributes.Add("aria-describedby", idAttr?.Value + "InfoText"); + inputTag.Attributes.SetAttribute("aria-describedby", idAttr?.Value + "InfoText"); return div.ToHtmlString(); } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo/Abp/AspNetCore/Mvc/UI/Bootstrap/TagHelpers/Form/AbpSelectTagHelperService_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo/Abp/AspNetCore/Mvc/UI/Bootstrap/TagHelpers/Form/AbpSelectTagHelperService_Tests.cs index 240562f929..79d61ff74d 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo/Abp/AspNetCore/Mvc/UI/Bootstrap/TagHelpers/Form/AbpSelectTagHelperService_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo/Abp/AspNetCore/Mvc/UI/Bootstrap/TagHelpers/Form/AbpSelectTagHelperService_Tests.cs @@ -1,6 +1,7 @@ #nullable enable using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Text.Encodings.Web; using System.Threading.Tasks; @@ -51,6 +52,30 @@ public class AbpSelectTagHelperService_Tests service.LastSelectTag.ShouldNotBeNull(); service.LastSelectTag!.Attributes.ContainsName("aria-describedby").ShouldBeTrue(); service.LastSelectTag.Attributes["aria-describedby"].Value.ToString().ShouldBe("TestSelectInfoText"); + service.LastGroupHtml.ShouldContain("aria-describedby=\"TestSelectInfoText\""); + } + + [Fact] + public async Task InputInfoText_attribute_should_render_info_text_with_single_aria_describedby() + { + var service = new TestAbpSelectTagHelperService(); + var tagHelper = new AbpSelectTagHelper(service) + { + AspFor = CreateModelExpressionWithInputInfoText() + }; + + var output = CreateOutput(); + + await tagHelper.ProcessAsync(CreateContext(), output); + + service.LastGroupHtml.ShouldContain("
a.Name == "aria-describedby").ToList(); + ariaDescribedby.Count.ShouldBe(1); + ariaDescribedby[0].Value.ToString().ShouldBe("TestSelectInfoText"); } private static TagHelperContext CreateContext() @@ -77,6 +102,21 @@ public class AbpSelectTagHelperService_Tests metadataProvider.GetModelExplorerForType(typeof(string), null)); } + private static ModelExpression CreateModelExpressionWithInputInfoText() + { + var metadataProvider = new EmptyModelMetadataProvider(); + var modelExplorer = metadataProvider + .GetModelExplorerForType(typeof(TestModelWithInputInfoText), null) + .GetExplorerForProperty(nameof(TestModelWithInputInfoText.TestSelect)); + return new ModelExpression(nameof(TestModelWithInputInfoText.TestSelect), modelExplorer); + } + + private class TestModelWithInputInfoText + { + [InputInfoText("Description from attribute")] + public string TestSelect { get; set; } = string.Empty; + } + private sealed class TestAbpSelectTagHelperService : AbpSelectTagHelperService { public string LastGroupHtml { get; private set; } = string.Empty; @@ -98,6 +138,8 @@ public class AbpSelectTagHelperService_Tests TagMode = TagMode.StartTagAndEndTag }; + AddInfoTextId(LastSelectTag); + return Task.FromResult(LastSelectTag); }