1

I want to implement localization in a custom attribute to check if the property is a valid IP address or hostname. The validation is working fine so far, but my problem is that I only get the default english error message though my local is switched to german. I am working with resource files. I do not want to implement a client side validation for this. I know there is way to implement an Adapter but correct me if I'm wrong, this is only for client side validation.

My custom validation class:

public class IPAddressOrHostnameAttribute : ValidationAttribute
{
     public IPAddressOrHostnameAttribute(string propertyName, object desiredvalue, string errorMessage)
    {
        PropertyName = propertyName;
        DesiredValue = desiredvalue;
        ErrorMessage = errorMessage;
    }

    private string PropertyName { get; }
    private object DesiredValue { get; }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        var instance = context.ObjectInstance;
        var type = instance.GetType();
        var propertyValue = type.GetProperty(PropertyName).GetValue(instance, null);
        if (propertyValue.ToString() == DesiredValue.ToString() && value != null)
        {
            if (Regex.IsMatch(value.ToString(), AckConstants.VALIDIPADDRESSREGEX)
                || Regex.IsMatch(value.ToString(), AckConstants.VALIDHOSTNAMEREGEX))
            {
                return ValidationResult.Success;
            }

            return new ValidationResult(ErrorMessage);
        }

        return ValidationResult.Success;
    }
}

My model class:

  [Required(ErrorMessage = "The field {0} is required")]
        [RegularExpression(@"^\S*$", ErrorMessage = "No white spaces allowed.")]
        [IPAddressOrHostname(nameof(IsFileAdapter), true, "Please enter a valid IP address or hostname")]
        [IPAddress(nameof(IsFileAdapter), false, "Please enter a valid IP address")]
        [Display(Name = "Destination")]
        public string Destination { get; set; }

My startup class to configure the DataAnnotationLocalizerProvider:

services
                .AddMvc()
                .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix,
                    opts => { opts.ResourcesPath = "Resources"; })
                .AddDataAnnotationsLocalization(options =>
                {
                    options.DataAnnotationLocalizerProvider = (type, factory) =>
                        factory.Create(typeof(SharedResource)); // SharedResource is the class where the DataAnnotations (translations) will be stored.
                })

The localization is working for the default attributes like e.g. Required etc. but not for my custom validation attribute. I have no idea what's wrong in my code. I have read the stackoverflow post ASP.NET Core custom validation attribute localization, but I do not understand why my server side validation with localization is not working. Hopefully someone can help me or give me an example how to get it work, because this problem is driving me crazy.

Sandro
  • 123
  • 2
  • 5
  • If you are using `AddDataAnnotationsLocalization()` method in your asp.net core project `program.cs` to organize annotation's localization, then consider this answer as well: https://stackoverflow.com/a/75631961/862795 – Rzassar Mar 03 '23 at 21:25

2 Answers2

7

Creating an adapter could be a solution, but it is too expensive! you have to create the adapter, then create adapter provider then you need to register it in startup! it is too much work.

A shorter solution is to get localization service in the custom validation attribute via ValidationContext.GetService:

If you are using the built-in localization service it will be IStringLocalizer, if you are using a custom localization service e.g. (MyLocalizer) you can access it by parsing it to the GetService(typeof(MyLocalizer)) method. see below sample:

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
    var _localizationService = (IStringLocalizer)validationContext.GetService(typeof(IStringLocalizer));

    var localizedError = _localizationService[ErrorMessage];

    //
    // do your custom validation
    //
    // if validation result is wrong
        return new ValidationResult(localizedError);

    // if validation result is correct
    return ValidationResult.Success;
}

btw, ValidationAttribute already has ErrorMessage property, so you don't have to define it in your custom attribute.

public IPAddressOrHostnameAttribute(string propertyName, object desiredvalue /*, string errorMessage*/)
    {
        PropertyName = propertyName;
        DesiredValue = desiredvalue;
        // ErrorMessage = errorMessage;
    }

Use it just like other attributes:

[IPAddressOrHostname(nameof(IsFileAdapter), true, ErrorMessage = "Please enter a valid IP address or hostname")]
AGuyCalledGerald
  • 7,882
  • 17
  • 73
  • 120
LazZiya
  • 5,286
  • 2
  • 24
  • 37
  • 4
    Thx @Laz Ziya very much for your answer it helped me a lot to get it done. I am working with SharedResources. I had to extend your line a little bit, but now it's working. `var localizationService = (IStringLocalizer) validationContext.GetService(typeof(IStringLocalizer)); var localizedError = localizationService[ErrorMessage];` – Sandro Sep 04 '19 at 06:49
  • 2
    In my case I have to do this to work - var _localizationService = (IStringLocalizer)validationContext.GetService(typeof(IStringLocalizer)); – yogihosting Apr 15 '20 at 17:52
2

I do not want to implement a client side validation for this. I know there is way to implement an Adapter but correct me if I'm wrong, this is only for client side validation.

Actually, that's not the truth. Adapter doesn't means you'll have to use a client side validation. Please see Ramin's answer here.

As for your question itself, you could create an Adapter and also a AdapterProvider to provide a adapter:

public class IPAddressOrHostnameAttributeAdapter : AttributeAdapterBase<IPAddressOrHostnameAttribute>
{
    public IPAddressOrHostnameAttributeAdapter(IPAddressOrHostnameAttribute 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());
    }
}

public class  IPAddressOrHostnameAttributeAdapterProvider : IValidationAttributeAdapterProvider
{
    private readonly IValidationAttributeAdapterProvider fallback = new ValidationAttributeAdapterProvider();

    public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
    {
        var attr = attribute as IPAddressOrHostnameAttribute;
        return attr == null?
            this.fallback.GetAttributeAdapter(attribute, stringLocalizer):
            new IPAddressOrHostnameAttributeAdapter(attr, stringLocalizer);
    }
}

Also, ensure this service is registered in the DI container:

services.AddSingleton<IValidationAttributeAdapterProvider, IPAddressOrHostnameAttributeAdapterProvider>();

Finally, if you're using querystring as the culture provider, don't forget to append a culture=de in your form action:

@{ var __culture = Context.Features.Get<IRequestCultureFeature>().RequestCulture.Culture.ToString(); }

<form asp-action="Create" asp-route-culture="@__culture">
    ....
</form>

A Screenshot of the Demo

enter image description here

itminus
  • 23,772
  • 2
  • 53
  • 88
  • Thx @itminus for your quick answer. It's working too :-). Two right answers you are amazing. – Sandro Sep 04 '19 at 06:52