From 374f9c53613b15ad3195e918e5122e09c2c46b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ey=C3=BCpcan=20=C3=87ak=C4=B1r?= <69207222+eypcnckr@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:05:30 +0300 Subject: [PATCH] Add an attribute adapter for EnumDataTypeAttribute ASP.NET Core's ValidationAttributeAdapterProvider has no adapter for EnumDataTypeAttribute, so its ErrorMessage is never passed through the IStringLocalizer and the raw localization key is returned to the client, unlike every other common validation attribute. Add EnumDataTypeAttributeAdapter and return it from AbpValidationAttributeAdapterProvider. --- .../AbpValidationAttributeAdapterProvider.cs | 5 ++ .../EnumDataTypeAttributeAdapter.cs | 34 +++++++++ ...alidationAttributeAdapterProvider_Tests.cs | 72 +++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/EnumDataTypeAttributeAdapter.cs create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider_Tests.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs index 0ba54c5448..5b253b53fe 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs @@ -33,6 +33,11 @@ public class AbpValidationAttributeAdapterProvider : IValidationAttributeAdapter return new DynamicRangeAttributeAdapter((DynamicRangeAttribute)attribute, stringLocalizer); } + if (type == typeof(EnumDataTypeAttribute)) + { + return new EnumDataTypeAttributeAdapter((EnumDataTypeAttribute)attribute, stringLocalizer); + } + return _defaultAdapter.GetAttributeAdapter(attribute, stringLocalizer); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/EnumDataTypeAttributeAdapter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/EnumDataTypeAttributeAdapter.cs new file mode 100644 index 0000000000..29ad39097d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/EnumDataTypeAttributeAdapter.cs @@ -0,0 +1,34 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; + +namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations; + +public class EnumDataTypeAttributeAdapter : AttributeAdapterBase +{ + public EnumDataTypeAttributeAdapter( + EnumDataTypeAttribute attribute, + IStringLocalizer? stringLocalizer) + : base(attribute, stringLocalizer) + { + } + + public override void AddValidation(ClientModelValidationContext context) + { + Check.NotNull(context, nameof(context)); + + //There is no built-in client side validation rule for enum values. + //This adapter is used to localize the error message on the server side. + } + + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + Check.NotNull(validationContext, nameof(validationContext)); + + return GetErrorMessage( + validationContext.ModelMetadata, + validationContext.ModelMetadata.GetDisplayName() + ); + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider_Tests.cs new file mode 100644 index 0000000000..d5bb122939 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider_Tests.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; +using Shouldly; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations; + +public class AbpValidationAttributeAdapterProvider_Tests +{ + private readonly AbpValidationAttributeAdapterProvider _provider = new(new ValidationAttributeAdapterProvider()); + + [Fact] + public void Should_Return_An_Adapter_For_The_EnumDataTypeAttribute() + { + //ASP.NET Core does not provide an adapter for the EnumDataTypeAttribute. + new ValidationAttributeAdapterProvider() + .GetAttributeAdapter(new EnumDataTypeAttribute(typeof(MyEnum)), null) + .ShouldBeNull(); + + _provider.GetAttributeAdapter(new EnumDataTypeAttribute(typeof(MyEnum)), null) + .ShouldBeOfType(); + } + + [Fact] + public void Should_Localize_The_Error_Message_Of_The_EnumDataTypeAttribute() + { + var attribute = new EnumDataTypeAttribute(typeof(MyEnum)) { ErrorMessage = "MyEnumIsInvalid" }; + + var adapter = _provider.GetAttributeAdapter(attribute, new TestStringLocalizer())!; + + adapter.GetErrorMessage(CreateValidationContext()).ShouldBe("Localized:MyEnumIsInvalid"); + } + + private static ModelValidationContextBase CreateValidationContext() + { + var metadataProvider = new EmptyModelMetadataProvider(); + + return new ClientModelValidationContext( + new ActionContext(), + metadataProvider.GetMetadataForProperty(typeof(MyModel), nameof(MyModel.Value)), + metadataProvider, + new Dictionary() + ); + } + + public enum MyEnum + { + Value1 = 1 + } + + public class MyModel + { + public MyEnum Value { get; set; } + } + + private class TestStringLocalizer : IStringLocalizer + { + public LocalizedString this[string name] => new(name, "Localized:" + name); + + public LocalizedString this[string name, params object[] arguments] => new(name, "Localized:" + name); + + public IEnumerable GetAllStrings(bool includeParentCultures) + { + return new List(); + } + } +}