Better support for localization in ASP.NET.

If you’ve tried the built in localization features in ASP.NET you’ll probably written cluttered view models like:

 public class UserViewModel
{
    [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))]
    [LocalizedDisplayName(ErrorMessageResourceName = "UserId", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))]
    [LocalizedDescription(ErrorMessageResourceName = "UserIdDescription", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))]
    public int Id { get; set; }

    [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))]
    [LocalizedDisplayName(ErrorMessageResourceName = "UserFirstName", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))]
    [LocalizedDescription(ErrorMessageResourceName = "UserFirstNameDescription", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))]
    public string FirstName { get; set; }

    [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))]
    [LocalizedDisplayName(ErrorMessageResourceName = "UserLastName", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))]
    [LocalizedDescription(ErrorMessageResourceName = "UserLastNameDescription", ErrorMessageResourceType = typeof(Resources.LocalizedStrings))]
    public string LastName { get; set; }
}

I’ve solved that with my Griffin.MvcContrib project. But the solution is more of an hack than a solid approach.

The problem is really that there is no way to inject your own custom strings into the localization process of the DataAnnotation attributes. They use their own string tables (resource files) and that’s it. Well, unless you use attribute properties as shown above.

A much cleaner approach would be if the attributes asked a central class about the strings. Something like:

public static class LocalizationStringProvider
{
    public static string GetValidationMessage(Type attributeType, string text)
    {
        // scan registered providers after the text
    }
    
    public static string GetModelText(Type model, string propertyName, string metadataName)
    {
        // scan registered providers after the text
    }
    
    // add a new one
    public void Register(ILocalizationStringProvider provider)
    {
    }
    
    // remove all providers
    public void Clear()
    {}
}

Which in turn will get it’s text from a registered provider:

public interface ILocalizationStringProvider
{
    string GetValidationMessage(Type attributeType, string text);
    string GetModelText(Type model, string propertyName, string metadataName);
}

In this way we can by ourself choose if we want to use string tables, a SQL database, flat files or any other source. It will also work for attributes like the ASP.NET MVC CompareAttribute which requires to get a localized version of the property name which it’s compared to.

Hence to customize the localization of model and validation strings would simply be:

LocalizationStringProvider.Clear();
LocalizationStringProvider.Add(new MySqlProvider());

Summary

Vote at uservoice.com if you like this idea and want it implemented in ASP.NET.

Disclaimer: The decision of using a static class in this example was chosen since it’s how the other extension points of ASP.NET is made.


5 Comments

  • Reply Apostol Apostolov |

    If you’r looking for a decent library for localization to get ideas from you can take a look at https://github.com/danielcrenna/i18n . It’s a little hard to comprehend but once you start using it – it’s a pleasure to work with. And it’s approach is similar to the one you describe in here – with central place to get the localized string from.

    • Reply JonasGauffin |

      Thank you for the comment. This post is however just an elaboration of the suggestion for the ASP.NET user voice site. The link is at the bottom of the post.

  • Reply Yustme |

    Hi,

    I just voted you with 3. Can you update your request with support for localization for enums? That one is a pain in the ass!

  • Reply Rick Strahl |

    The issues here are much more complicated than just the DataAnnotations though. While you might be able to inject the resources for annotations, the real solution to this IMHO is to overhaul the whole .NET ResourceManager architecture which is incredibly limited by not having any hookpoints. While you can use other resource managers, getting them to be used by the .NET infrastructure is difficult. ASP.NET’s resource providers make extensibility a little easier, but it’s still a red hot mess and difficult to build these providers because you have to implement 4 or 5 different interfaces to make it happen.

    The problem with localization in general is that an application can have many pieces and components and somehow each should be able to have its own rules to determine where resources are coming from. Right now, .NET just decides for you – Resx. You can cheat this by using custom ResourceManagers but that starts falling apart with strongly typed resources because those are hardcoded to the Resx ResourceManager.

    Unfortunately it doesn’t look like Microsoft is going to be doing anything about this in vNext either so I doubt there are going to be major improvements in this arena. So we’re going to end up with specialized solutions like my Westwind.Globalization ( https://github.com/RickStrahl/Westwind.Globalization ) or i18n mentioned earlier.

So, what do you think ?