How to provide localized validation messages for validation attributes

前端 未结 4 582
北海茫月
北海茫月 2020-12-09 21:46

I am working on an ASP.NET Core application and I would like to override the default validation error messages for data-annotations, like Required,

相关标签:
4条回答
  • 2020-12-09 22:05

    If you want to change the complete text, you should use resource files to localize it.

    Every ValidationAttribute has properties for ErrorMessageResourceType and ErrorMessageResourceName (see source here).

    [Required(ErrorMessageResourceName = "BoxLengthRequired", ErrorMessageResourceType = typeof(SharedResource))]
    

    Edit

    Okay there seems to be a way to use the localization provider to localize it, but it's still a bit hacky and requires at least one property on the attribute (from this blog post - Word of warning though, it was initially for an old rc1 or rc2 version, should work but some of the API in that article may not work):

    In startup:

    services.AddMvc()
       .AddViewLocalization()
       .AddDataAnnotationsLocalization();
    

    On your model:

    [Required(ErrorMessage = "ViewModelPropertyRequired"), MinLength(10, ErrorMessage = "ViewModelMinLength")]
    public string RequiredProperty { get; set; }
    

    and implement/use an localization provider that uses DB (i.e. https://github.com/damienbod/AspNet5Localization).

    0 讨论(0)
  • 2020-12-09 22:07

    For those that end up here, in search of a general solution, the best way to solve it is using a Validation Metadata Provider. I based my solution on this article: AspNetCore MVC Error Message, I usted the .net framework style localization, and simplified it to use the designed provider.

    1. Add a Resource file for example ValidationsMessages.resx to your project, and set the Access Modifier as Internal or Public, so that the code behind is generated, that will provide you with the ResourceManager static instance.
    2. Add a custom localization for each language ValidationsMessages.es.resx. Remember NOT to set Access Modifier for this files, the code is created on step 1.
    3. Add an implementation of IValidationMetadataProvider
    4. Add the localizations based on the Attributes Type Name like "RequiredAtrribute".
    5. Setup your app on the Startup file.

    Sample ValidationsMessages.es.resx

    Sample for IValidatioMetadaProvider:

    using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
    using System.ComponentModel.DataAnnotations;
    using System.Reflection;
    
    public class LocalizedValidationMetadataProvider : IValidationMetadataProvider
    {
        public LocalizedValidationMetadataProvider()
        {
        }
    
        public void CreateValidationMetadata(ValidationMetadataProviderContext context)
        {
            if (context.Key.ModelType.GetTypeInfo().IsValueType && Nullable.GetUnderlyingType(context.Key.ModelType.GetTypeInfo()) == null && context.ValidationMetadata.ValidatorMetadata.Where(m => m.GetType() == typeof(RequiredAttribute)).Count() == 0)
                context.ValidationMetadata.ValidatorMetadata.Add(new RequiredAttribute());
            foreach (var attribute in context.ValidationMetadata.ValidatorMetadata)
            {
                var tAttr = attribute as ValidationAttribute;
                if (tAttr?.ErrorMessage == null && tAttr?.ErrorMessageResourceName == null)
                {
                    var name = tAttr.GetType().Name;
                    if (Resources.ValidationsMessages.ResourceManager.GetString(name) != null)
                    {
                        tAttr.ErrorMessageResourceType = typeof(Resources.ValidationsMessages);
                        tAttr.ErrorMessageResourceName = name;
                        tAttr.ErrorMessage = null;
                    }
                }
            }
        }
    }
    

    Add the provider to the ConfigureServices method on the Startup class:

    services.AddMvc(options =>
    {
         options.ModelMetadataDetailsProviders.Add(new LocalizedValidationMetadataProvider());
    })
    
    0 讨论(0)
  • 2020-12-09 22:07

    So, I landed here because of creating my own custom IStringLocalizer and wanted to share my solution because @jlchavez helped me out.

    I created a MongoDb IStringLocalizer and wanted to use the resources via the DataAnnotations. Problem is that DataAnnotations Attributes expect localizations via a static class exposing the resources.

    One enhancement over @jlchavez is that this will fix the resource messages for all ValidationAttribute(s)

    services.AddTransient<IValidationMetadataProvider, Models.LocalizedValidationMetadataProvider>();
    services.AddOptions<MvcOptions>()
            .Configure<IValidationMetadataProvider>((options, provider) =>
            {
                options.ModelMetadataDetailsProviders.Add(provider);
            });
    
    
    
    public class Resource
    {
        public string Id => Culture + "." + Name;
        public string Culture { get; set; }
        public string Name { get; set; }
        public string Text { get; set; }
    }
    public class MongoLocalizerFactory : IStringLocalizerFactory
    {
        private readonly IMongoCollection<Resource> _resources;
        public MongoLocalizerFactory(IMongoCollection<Resource> resources)
        {
            _resources = resources;
        }
    
        public IStringLocalizer Create(Type resourceSource)
        {
            return new MongoLocalizer(_resources);
        }
    
        public IStringLocalizer Create(string baseName, string location)
        {
            return new MongoLocalizer(_resources);
        }
    }
    public class MongoLocalizer : IStringLocalizer
    {
        private readonly IMongoCollection<Resource> _resources;
    
        public MongoLocalizer(IMongoCollection<Resource> resources)
        {
            _resources = resources;
        }
    
        public LocalizedString this[string name]
        {
            get
            {
                var value = GetString(name);
                return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
            }
        }
    
        public LocalizedString this[string name, params object[] arguments]
        {
            get
            {
                var format = GetString(name);
                var value = string.Format(format ?? name, arguments);
                return new LocalizedString(name, value, resourceNotFound: format == null);
            }
        }
    
        public IStringLocalizer WithCulture(CultureInfo culture)
        {
            CultureInfo.DefaultThreadCurrentCulture = culture;
    
            return new MongoLocalizer(_resources);
        }
    
        public IEnumerable<LocalizedString> GetAllStrings(bool includeAncestorCultures)
        {
            var resources = _resources.Find(r => r.Culture == CultureInfo.CurrentCulture.Parent.Name).ToList();
            return resources.Select(r => new LocalizedString(r.Name, r.Text, false));
    
        }
    
        private string GetString(string name)
        {
            var resource = _resources.Find(r => r.Culture == CultureInfo.CurrentCulture.Parent.Name && r.Name == name).SingleOrDefault();
            if (resource != null)
            {
                return new LocalizedString(resource.Name, resource.Text, false);
            }
            return new LocalizedString(name, name, true);
        }
    }
    
    public class LocalizedValidationMetadataProvider : IValidationMetadataProvider
    {
        private IStringLocalizer _localizer;
        public LocalizedValidationMetadataProvider(IStringLocalizer localizer)
        {
            _localizer = localizer;
        }
    
        public void CreateValidationMetadata(ValidationMetadataProviderContext context)
        {
            foreach(var metadata in context.ValidationMetadata.ValidatorMetadata)
            {
                if (metadata is ValidationAttribute attribute)
                {
                    attribute.ErrorMessage = _localizer[attribute.ErrorMessage].Value;
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-09 22:15

    I encountered the same problem and the solution I used was to create a subclass of the validation attribute to provide the localized error message.

    To prevent programmers from accidentally using the non-localized version, I just left out the using statement for the non-localized library.

    0 讨论(0)
提交回复
热议问题