Friday, September 21, 2012

Localized Data Annotations without Resource Files

Data annotations are a simple, out-of-the-box way to get validation messages into your web application. And on a small scale, there's a minimal amount of coding to get this to work. For an asp.net MVC 4 application, this mean including jquery.validate.unobtrusive.js and adding the data annotation attributes to your class.  For example:
public class Thingy{
    
    [Required]
    public string Name{ get; set; }

    [Required]
    public string Description{ get; set; }

}
So when the user goes to input a new Thingy, if they leave out the Name or Description, we would like the application to not allow the postback and show them a message indicating that those fields are required. In this case, a default message 'The Name field is required' will be displayed.
However things start to get complicated when localization comes into play. Suppose we want this message displayed in Spanish, or Italian. The common way to handle this is with resource files. And again, there is out-of-the-box support for this. Simply provide a resource name as follows, and it just works.
[Required(ErrorMessageResourceName = "LocalizedResource")]
[StringLength(30, ErrorMessageResourceName = "AnotherResource")]
public string Name { get; set; }
A quick search on "data annotations multilingual error message" will provide more than enough details on how to get that to work.
What happens, though, if resource files aren't an option? What if you get your multilingual content from a database? One way to solve this problem is with a custom ModelValidatorProvider. Below is some sample code to get this to work.
public class LocalizedModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
    protected override IEnumerable GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable attributes)
    {
        var dependencyResolver = DependencyResolver.Current;

        var _contentProvider = dependencyResolver.GetService();
        var translatedMessages = _contentProvider.GetContent();

        foreach (RequiredAttribute attribute in attributes.Where(a => a is RequiredAttribute).ToList())
        {
            string myMessage = //Get your content here

            attribute.ErrorMessage = myMessage;
        }

        IEnumerable validators = base.GetValidators(metadata, context, attributes);

        return validators;
    }
}

And to get all this to work, you need to register your provider in global.asax:
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);

            ModelValidatorProviders.Providers.Clear();
            ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider());

        }

Hope this is helpful.