Click here to Skip to main content
15,881,248 members
Articles / Web Development / ASP.NET

Simplified localization for DataAnnotations

Rate me:
Please Sign up or sign in to vote.
4.50/5 (7 votes)
26 Sep 2011LGPL31 min read 47.7K   4   4
Simplified localization for DataAnnotations

Update 

I've come up with a better approach

Original article 

The built in localization support in DataAnnotations is a bit hard to work with. You are supposed to do add the ResourceManager to each attribute that you add. The code is looking like something like this: 

C#
public class User
{
    [Required(ErrorMessageResourceName = "Validation_Required", 
    	ErrorMessageResourceType = typeof(ModelTranslations))]
    public int Id { get; set; }
    
    [Required(ErrorMessageResourceName = "Validation_Required", 
    	ErrorMessageResourceType = typeof(ModelTranslations))]
    [StringLength(40, ErrorMessageResourceName = "Validation_StringLength", 
    	ErrorMessageResourceType = typeof(ModelTranslations))]
    public string FirstName { get; set; }
    
    [Required(ErrorMessageResourceName = "Validation_Required", 
    	ErrorMessageResourceType = typeof(ModelTranslations))]
    [StringLength(40, ErrorMessageResourceName = "Validation_StringLength", 
    	ErrorMessageResourceType = typeof(ModelTranslations))]
    public string LastName { get; set; }
}

You should of course move the strings to a static class or something like that.

The problem is that you have just added translation for the validation messages and not the actual model. There are actually no built in solutions for that problem. If you Google, you find some solutions using a custom LocalizedDisplayNameAttribute that uses the same approach as the DataAnnotationAttribute’s: Pointing on a resource type in the attribute constructor.

Updated source code for this solution:

C#
public class User
{
    [Required(ErrorMessageResourceName = "Validation_Required", 
    	ErrorMessageResourceType = typeof(ModelTranslations))]
    [LocalizedDisplayNameAttribute("User_Id", 
    	NameResourceType=typeof(ModelTranslations))]
    public int Id { get; set; }
    
    [Required(ErrorMessageResourceName = "Validation_Required", 
    	ErrorMessageResourceType = typeof(ModelTranslations))]
    [StringLength(40, ErrorMessageResourceName = "Validation_StringLength", 
    	ErrorMessageResourceType = typeof(ModelTranslations))]
    [LocalizedDisplayNameAttribute("User_FirstName", 
    	NameResourceType=typeof(ModelTranslations))]
    public string FirstName { get; set; }
    
    [Required(ErrorMessageResourceName = "Validation_Required", 
    	ErrorMessageResourceType = typeof(ModelTranslations))]
    [StringLength(40, ErrorMessageResourceName = "Validation_StringLength", 
    	ErrorMessageResourceType = typeof(ModelTranslations))]
    [LocalizedDisplayNameAttribute("User_LastName", 
    	NameResourceType=typeof(ModelTranslations))]
    public string LastName { get; set; }
}

Not very readable, is it? As you can see, I use the pattern ClassName_PropertyName for all strings definitions. It’s a safe way to avoid a name collision. The same word might not mean the same thing in different models.

The Solution

What we need is a way to provide strings to all attributes WITHOUT specifying the resource in the attribute constructors. In this way, we can easily move resources without having to change each attribute. Changing every attribute is not very DRY is it?

My solution is to create a singleton class that you can fetch strings from. Want a more flexible solution? Check my ShureValidation library (shameless advertising) that I’ve posted about earlier in this blog.

End result for the model:

C#
public class User
{
    [Required]
    [LocalizedDisplayNameAttribute("User_Id")]
    public int Id { get; set; }
    
    [Required]
    [StringLength(40)]
    [LocalizedDisplayNameAttribute("User_FirstName")]
    public string FirstName { get; set; }
    
    [Required]
    [StringLength(40)]
    [LocalizedDisplayNameAttribute("User_LastName")]
    public string LastName { get; set; }
}

A bit more readable? oooooh yes *uhu*.

Classes making it possible:

You have to derive all standard DataAnnotation attribute classes like this:

C#
public class RequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute
{
    private string _displayName;
    
    public RequiredAttribute()
    {
        ErrorMessageResourceName = "Validation_Required";
    }
    
    protected override ValidationResult IsValid
	(object value, ValidationContext validationContext)
    {
        _displayName = validationContext.DisplayName;
        return base.IsValid(value, validationContext);
    }
    
    public override string FormatErrorMessage(string name)
    {
        var msg = LanguageService.Instance.Translate(ErrorMessageResourceName);
        return string.Format(msg, _displayName);
    }
}

Next step is to add a custom DisplayName class:

C#
public class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
    private PropertyInfo _nameProperty;
    private Type _resourceType;
    
    public LocalizedDisplayNameAttribute(string className, string propertyName)
        : base(className + (propertyName == null ? "_Class" : ("_" + propertyName)))
    {
    
    }
    
    public override string DisplayName
    {
        get
        {
            return LanguageService.Instance.Translate(base.DisplayName) ?? 
		"**" + base.DisplayName + "**";
        }
    }
}

And finally create the language service:

C#
public class LanguageService
{
    private static LanguageService _instance = new LanguageService();
    private List<ResourceManager> _resourceManagers = new List<ResourceManager>();
    
    private LanguageService()
    {
    }
    
    public static LanguageService Instance { get { return _instance; } }
    
    public void Add(ResourceManager mgr)
    {
        _resourceManagers.Add(mgr);
    }
    
    public string Translate(string key)
    {
        foreach (var item in _resourceManagers)
        {
            var value = item.GetString(key);
            if (value != null)
                return value;
        }
        
        return null;
    }
}

You can easily switch to a database or something like that if you need to.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Founder 1TCompany AB
Sweden Sweden

Comments and Discussions

 
QuestionNice article for Multi-language websites using MVC Pin
Snehal P. Thube19-Aug-13 23:16
Snehal P. Thube19-Aug-13 23:16 
QuestionDoes your solution require that the resource files should be embedded? Pin
jerryoz21-May-13 21:39
jerryoz21-May-13 21:39 
GeneralMy vote of 1 Pin
DESTAIR17-Feb-11 22:13
DESTAIR17-Feb-11 22:13 
It is not correct that Microsoft has not a solution for multi-lingual resources on data annotation. There is a solution called embedded resource files. See here: http://ryanrivest.com/blog/archive/2010/01/15/reusable-validation-error-message-resource-strings-for-dataannotations.aspx
GeneralRe: My vote of 1 Pin
jgauffin9-Jun-11 8:36
jgauffin9-Jun-11 8:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.