For EnumDataType and all of our self-implemented ValidationAttributes it looks like the DataAnnotationLocalizerProvider is not called when generating the error message. Insteaed it just outputs the ErrorMessage as is.
Is this a bug? Do I have to specifically configure additional validation attributes somewhere?
Hi @drauch can you show a bit more about your scenario? Is this about localizing the display names of enum values? Any code you can show regarding this would be helpful.
Some example code:
public class MyModel
{
[EnumDataType(typeof(MyEnum), ErrorMessage="TextWhichShouldRunThroughDataAnnotationLocalizer"]
public MyEnum EnumProp { get; set; }
[MustBeTrue, ErrorMessage="TextWhichShouldBeLocalizedAsWell"] // one of our very own ValidationAttribute implementations
public bool LegalTermsConfirmation { get; set; }
}
Unfortunately, both ErrorMessages are not running through localization, i.e., through the DataAnnotationLocalizer which we set using the .AddDataAnnotationsLocalization(o => o.DataAnnotationLocalizerProvider = ...) configuration option.
(it works only for Required, StringLength and some other "default" validation attributes)
Ok yeah that sounds weird to me, I think I'd have expected it to work. We will investigate.
FYI: it looks like the ValidationAttributeAdapterProvider is to blame. It contains a taxative enumeration of all supported validation attributes, it should probably provide a useful default for all other validation attributes - if that's not possible, it should at least provide a good extension point for us to enable localization of further (self-implemented) validation attributes.
As a workaround we are going to decorate the ValidationAttributeAdapterProvider for now ... feels clumsy (even more so, as it resides in the Internal namespace) but it works.
Reassigning to @SteveSandersonMS because @ryanbrandenburg is out.
@Eilon It looks like the closest extensibility point for this today is IValidationAttributeAdapterProvider. If you register one of those, you can control how the messages are supplied. But it's awkward because there's no collection of such providers, nor even any clean way of getting the default instance, so appending custom logic to the default logic is messy.
For the stated use case, the simplest thing might be if we checked if the attribute itself implemented IAttributeAdapter (or maybe IValidationAttributeAdapterProvider), and if so, used that. It would still be up to the developer to make the messages get localized correctly, which they could do by calling their own DataAnnotationLocalizer if they wanted, or any other localization mechanism.
A more heavyweight solution would be introducing a new IValidationAttributeAdapterProvider collection somewhere so you could add your own implementations alongside the default one. But the points above about still having to call your own localizer would still apply.
We would be fine with the "lightweight" solution.
Would be good to discuss a proposal with @dougb, @rynowak , and me. We used to have more extensibility here in MVC 5.x I think, but it didn't make it into ASP.NET Core. I wonder if there's a past interface/design we should bring here?
We just hit the same problem so we are going to reimplement IValidationAttributeAdapterProvider to include our custom attributes. It would be nice to be able to register new providers.
For instance if I develop a library/Nuget with new useful validation attributes, it would be nice to be able to register it in Startup with services.AddMyCustomValidationAttributes().
@Eilon wrong doug.
Oops, adding correct @dougbu .
I wonder if there's a past interface/design we should bring here?
The past approach to adding / removing providers was static methods in the DataAnnotationsModelValidatorProvider e.g.
c#
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MyValidationAttribute), typeof(MyValidationAttributeAdapter));
I suspect we purposely punted similar a feature in ASP.NET Core MVC because we didn't want the old approach, hadn't designed something better, and weren't sure the feature was required (see #475).
See also #5553. That issue includes some useful information about workarounds. I haven't checked whether the suggestions or examples are up-to-date.
Thanks for reporting this issue. We agree that this seems to be a bug and we've assigned one of our team members to look into this based on its priority. /cc: @ryanbrandenburg
@mkArtakMSFT the bug label seems misleading. This is a feature or at least an enhancement -- adding an extensibility point supporting additional validation attributes.
Moving out to the Backlog as this is not a priority for the upcoming release.
:-(
This is my solution to this problem on AspNetCore. I think the differences will be small :)
ViewModels/Model.cs
[EnumDataType(typeof(UserTemplateTypes), ErrorMessage = "TemplateTypeIncorrect")]
public byte TemplateType { get; set; }
CustomValidations/EnumDataTypeAttributeAdapter.cs
public class EnumDataTypeAttributeAdapter : AttributeAdapterBase<EnumDataTypeAttribute> {
public EnumDataTypeAttributeAdapter (EnumDataTypeAttribute attribute, IStringLocalizer stringLocalizer) : base (attribute, stringLocalizer) { }
public override void AddValidation (ClientModelValidationContext context) { }
public override string GetErrorMessage (ModelValidationContextBase validationContext) {
return GetErrorMessage (validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName ());
}
}
CustomValidations/CustomValidationAttributeAdapterProvider.cs
public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider {
private readonly IValidationAttributeAdapterProvider _baseProvider = new ValidationAttributeAdapterProvider ();
public IAttributeAdapter GetAttributeAdapter (ValidationAttribute attribute, IStringLocalizer stringLocalizer) {
if (attribute is EnumDataTypeAttribute)
return new EnumDataTypeAttributeAdapter (attribute as EnumDataTypeAttribute, stringLocalizer);
else
return _baseProvider.GetAttributeAdapter (attribute, stringLocalizer);
}
}
Startup.cs
services.AddSingleton<IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();