Localizing validation messages in asp.net core : fallback on shared resources if not found

Standard

Right now in asp.net core, when you want to localize your validation messages, you have to create a resource file with the exact same name as your viewmodel, as explained here : https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization#dataannotations-localization

What if you want to keep this smart mechanism but still get a fallback to a shared resource file ? For example, let’s say you want to have the message “This field is absolutely required please !” instead of the standard message, for all required fields in all your viewmodels. You obviously don’t want to specify this message in every localized resource file named like your viewmodel.

Here’s a basic solution. In the startup.cs file, add something like :

services.AddMvc().AddDataAnnotationsLocalization(o =>
o.DataAnnotationLocalizerProvider = (modelType, stringLocalizerFactory) =>
{
   var sl = stringLocalizerFactory.Create(modelType);
   var shared = stringLocalizerFactory.Create(typeof(SharedResources));
   var ezSl = new MyDataAnnotations.ResourceManagerStringLocalizer(sl, shared);
   return ezSl;
})

And this is how your class MyDataAnnotations.ResourceManagerStringLocalizer could look like :


public class ResourceManagerStringLocalizer : IStringLocalizer
    {
        private readonly IStringLocalizer _resourceManagerStringLocalizer;
        private readonly IStringLocalizer _sharedLocalizer;

        /// <summary>
        ///
        /// </summary>

        /// <param name="resourceManagerStringLocalizerByType">type of Microsoft.Extensions.Localization.ResourceManagerStringLocalizer</param>
        /// <param name="sharedLocalizer"></param>
        public ResourceManagerStringLocalizer(IStringLocalizer resourceManagerStringLocalizerByType, IStringLocalizer sharedLocalizer)
        {
            _resourceManagerStringLocalizer = resourceManagerStringLocalizerByType;
            _sharedLocalizer = sharedLocalizer;

        }

        public LocalizedString this[string name]
        {
            get
            {
                var result = _resourceManagerStringLocalizer.GetString(name);
                if (result.ResourceNotFound)
                    result = _sharedLocalizer.GetString(name);
                return result;
            }
        }

        public LocalizedString this[string name, params object[] arguments]
        {
            get
            {
                var result = _resourceManagerStringLocalizer.GetString(name, arguments);
                if (result.ResourceNotFound)
                    result = _sharedLocalizer.GetString(name, arguments);
                return result;
            }
        }

        public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
        {
            var result = _resourceManagerStringLocalizer.GetAllStrings(includeParentCultures);
            foreach (var item in result)
            {
                if (item.ResourceNotFound)
                {
                    var i = _sharedLocalizer.GetString(item.Name);
                    yield return i;
                }
                yield return item;
            }
        }

        public IStringLocalizer WithCulture(CultureInfo culture)
        {
            return _resourceManagerStringLocalizer.WithCulture(culture);
        }
    }

 

Advertisements

2 responses »

  1. Thank you. This is an Interesting and usefull post. Instead of create string localizer from the extension method I create a FallBackStringLocalizerFactory so that you can use the new StringLocalizer everywhere.

    FYI

    public class FallBackStringLocalizerFactory : IStringLocalizerFactory
    {
    private readonly Type sharedBaseType = typeof(BizWork.FleetWork2.Web.SharedResources);
    private ResourceManagerStringLocalizerFactory _stdFactory;
    public FallBackStringLocalizerFactory(IHostingEnvironment hostingEnvironment, IOptions localizationOptions)
    {
    _stdFactory = new ResourceManagerStringLocalizerFactory(hostingEnvironment, localizationOptions);
    }

    public IStringLocalizer Create(Type resourceSource)
    {
    IStringLocalizer sharedLocalizer = _stdFactory.Create(sharedBaseType);
    IStringLocalizer typeLocalizer = _stdFactory.Create(resourceSource);
    return new FallbackStringLocalizer(typeLocalizer, sharedLocalizer);
    }

    public IStringLocalizer Create(string baseName, string location)
    {
    IStringLocalizer sharedLocalizer = _stdFactory.Create(sharedBaseType.FullName, location);
    IStringLocalizer typeLocalizer = _stdFactory.Create(baseName, location);
    return new FallbackStringLocalizer(typeLocalizer, sharedLocalizer);
    }

    }

    • Great yes if you want to use it everywhere (I just needed it for data annotations in my case. ) I wonder though, you will have two instances of your SharedResources when you inject an IStringLocalizer, you may wanna check that case where you don’t need any fallback.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s