ASP.NET Core custom validation attribute localization
Implement an adapter for localization:
public class RequiredIfAttributeAdapter : AttributeAdapterBase<RequiredIfAttribute>
{
public RequiredIfAttributeAdapter(RequiredIfAttribute 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());
}
}
Implement a provider for the adapter(s):
public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
{
private readonly IValidationAttributeAdapterProvider _baseProvider = new ValidationAttributeAdapterProvider();
public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
{
if (attribute is RequiredIfAttribute)
return new RequiredIfAttributeAdapter(attribute as RequiredIfAttribute, stringLocalizer);
else
return _baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
}
}
Register the provider in Startup.cs:
services.AddSingleton<IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
Credits to this blog: https://blogs.msdn.microsoft.com/mvpawardprogram/2017/01/03/asp-net-core-mvc/
The answer from Ramin is the correct answer. But I decided to take another path, so I don't have to write adapters and adapter providers for many cases.
The idea is to wrap your specific string localizer in a service interface, and get it from the validation attribute itself.
public class CPFAttribute: ValidationAttribute
{
public CPFAttribute()
{
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string cpf;
try
{
cpf = (string)value;
}
catch (Exception)
{
return new ValidationResult(GetErrorMessage(validationContext));
}
if (string.IsNullOrEmpty(cpf) || cpf.Length != 11 || !StringUtil.IsDigitsOnly(cpf))
{
return new ValidationResult(GetErrorMessage(validationContext));
}
return ValidationResult.Success;
}
private string GetErrorMessage(ValidationContext validationContext)
{
if (string.IsNullOrEmpty(ErrorMessage))
{
return "Invalid CPF";
}
ErrorMessageTranslationService errorTranslation = validationContext.GetService(typeof(ErrorMessageTranslationService)) as ErrorMessageTranslationService;
return errorTranslation.GetLocalizedError(ErrorMessage);
}
}
Then the service can be created as:
public class ErrorMessageTranslationService
{
private readonly IStringLocalizer<SharedResource> _sharedLocalizer;
public ErrorMessageTranslationService(IStringLocalizer<SharedResource> sharedLocalizer)
{
_sharedLocalizer = sharedLocalizer;
}
public string GetLocalizedError(string errorKey)
{
return _sharedLocalizer[errorKey];
}
}
The service can be registered as a singleton, in the Startup class.
services.AddSingleton<ErrorMessageTranslationService>();
If these validation attributes need to be factored to another assembly, just create an interface for this translation service that can be referenced by all validation attributes you create.