If you have Googled a bit, you’ll read that you should derive DescriptionAttribute
and DisplayNameAttribute
to create localized versions which will result in a View Model that looks something like this:
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; }
}
Don’t do that.
The solution to get cleaner DataAnnotation
localization is to derive the attributes and do some magic in the derived classes. The problem with that solution is that client-side validation stops working unless you create new adapters for your derived attributes.
Don’t do that.
The Easy Solution
I found an excellent post by Brad Wilson that goes through most of the new stuff in MVC3. It’s a must read. When reading it, I came up with the idea to use the new Meta Data providers to do the localization. The solution works quite well and your models become clean again:
public class UserViewModel
{
[Required]
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
You need to specify the new providers (created by me) in your global.asax:
protected void Application_Start()
{
var stringProvider =
new ResourceStringProvider(Resources.LocalizedStrings.ResourceManager);
ModelMetadataProviders.Current = new LocalizedModelMetadataProvider(stringProvider);
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(
new LocalizedModelValidatorProvider(stringProvider));
}
That’s it. No more messy attributes.
Defining the Localization Resources
I created an interface called ILocalizedStringProvider
which is used by both providers. This means that you do not have to use String
Tables. Just create a new class and implement that interface
to get support for your database, XML file, or whatever you choose.
In my example, I used a string
table, and the string
s in it look like this:
As you can see, you should name your string
s “ModelName_PropertyName
” and “ModelName_PropertyName_MetadataName
” to include meta data like Watermark
and NullDisplayText
. Refer to the documentation to see which kind of meta data there is.
As for validation attributes, the string
table names should simply be the attribute name without the “Attribute
” suffix.
Code and Documentation
The code is available at github. Documentation is available here.