Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core custom validation attribute localization

I'm trying to implement localization in a custom validation attribute in asp.net core 1.0. This is my simplified viewmodel:

public class EditPasswordViewModel
{
    [Required(ErrorMessage = "OldPasswordRequired")]
    [DataType(DataType.Password)]
    [CheckOldPassword(ErrorMessage = "OldPasswordWrong")]
    public string OldPassword { get; set; }
}

The localization of "OldPasswordRequired" is working fine. However the localization of my custom attribute is not working and returns always "OldPasswordWrong" message. This is the code:

public class CheckOldPasswordAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object classInstance, ValidationContext validationContext)
    {                   
        if (oldPasswordSaved == oldPasswordTyped) //simplified
        {
            return ValidationResult.Success;
        }
        else
        {
            string errorMessage = FormatErrorMessage(ErrorMessageString);
            return new ValidationResult(errorMessage);
        }
    }

}

ErrorMessageString is always "OldPasswordWrong" and FormatErrorMessage returns always "OldPasswordWrong". What am I doing wrong? I'm using the new asp.net core data annotations localizations, so I'm not using ErrorMessageResourceName and ErrorMessageResourceType attributes (I don't have any ViewModel.Designer.cs).

like image 926
rickyalbert Avatar asked Mar 14 '17 11:03

rickyalbert


People also ask

What is Modelstate in asp net core?

Model state represents errors that come from two subsystems: model binding and model validation. Errors that originate from model binding are generally data conversion errors. For example, an "x" is entered in an integer field.

How to add validation in ASP net Core MVC?

Add validation rules to the movie model DataAnnotations also contains formatting attributes like DataType that help with formatting and don't provide any validation. Update the Movie class to take advantage of the built-in Required , StringLength , RegularExpression , and Range validation attributes.

How can use client side validation in asp net core?

The ASP.NET core includes unobtrusive client-side validation libraries, which makes it easier to add client side validation code, without writing a single line of code. In the previous tutorial on server side validation, we looked at how data annotations attributes are used by the Model Validator to validate the Model.


2 Answers

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/

like image 120
Ramin Avatar answered Oct 14 '22 16:10

Ramin


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.

like image 40
Marcos de Aguiar Avatar answered Oct 14 '22 18:10

Marcos de Aguiar