Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Simple and Effective Way to Localize ASP.NET MVC Data Annotation Texts and Error Messages

0.00/5 (No votes)
22 Dec 2012 3  
By use of a cutomized metadata provider, MVC model data annotation validation messages can be localized in a simple and elegant way.

Introduction

Recently, I was working on an ASP.NET MVC 4 project which needs language localization. Generally speaking, it can be done in this way...

Design a class file which connects the database (my project uses a database to store localization languages. Of course, one can use resource files), and get the localized text by the resource key.

Suppose we have a Notes view model:

[StringLength(100, ErrorMessage="NotesError"]
[Display(Name = "Notes")]
public string Notes { get; set; }

In the page .cshtml file, we can use the class like this:

@using MyUIResources
{
    var ui = new UiResources();
}

In places where localization is needed, call the class method:

<td>
    @ui.GetResourceValueFromDatabase( "Notes" ) 
<!-- Text "Notes" does not come from the model display name. --></span />
    @Html.ValidationMessageFor( m => m.Notes, @ui.GetResourceValueFromDatabase( "NotesError" ) )
<!-- Text "NotesError" does not come from the model ErrorMessage. --></span />
</td>

Then the localized text and validation message (if there is validation error) will be displayed in the table cell.

Though it works, it is not how I want it to work due to comments as above.

There are quite a few samples of how to localize validation messages. But they may be either too heavy weighted, or do not fit into my case.

At first, I wanted to use ErrorMessageResourceName as the resource key, and create a localization provider. So define a view model like this:

[StringLength(100, ErrorMessageResourceName="NotesError", 
	ErrorMessageResourceType = typeof(MyLocalizationProvider)]
[Display(Name = "Notes")]
public string Notes { get; set; }

And then define the resource type like this:

using MyUIResources
{
    public class MyLocalizationProvider
    {
        static readonly UiResources ui = new UiResources();
        public static string NotesError
        {
            get { return ui.GetResourceValueFromDatabase( 'NotesError' ); }
        }
        public static string OtherError
        {
            get { return ui.GetResourceValueFromDatabase( 'OtherError' ); }
        }
        
        ...
    }
}

It works fine with validation error message, but not with display name. The worst with this approach is that it needs every message to be generated by such a GETTER method, which is not practical even for a middle size web site. It is said that this issue can be addressed by T4 Text Template. But I did not know how to use it and it needs approval from the architect.

So I investigated and experimented. Finally I made it by changing the model data annotation metadata and using the display name and error message as resource keys.

using (some other namespaces);
using System.ComponentModel.DataAnnotations;

namespace MyUIResources
{
    public class MyLocalizationProvider : DataAnnotationsModelMetadataProvider
    {
        private static UiResources ui = new UiResources();
        protected override ModelMetadata CreateMetadata(
                             IEnumerable<attribute> attributes ,
                             Type containerType ,
                             Func<object> modelAccessor ,
                             Type modelType ,
                             string propertyName )
        {

            string sKey = string.Empty;
            string sLocalizedText = string.Empty;

            HttpContext.Current.Application.Lock();          
            foreach ( var attr in attributes )
            {
                if ( attr != null )
                {
                    string typeName = attr.GetType().Name;
                    string attrAppKey = string.Empty;

                    if ( typeName.Equals( "DisplayAttribute" ) )
                    {
                        sKey = ( ( DisplayAttribute ) attr ).Name;

                        if ( !string.IsNullOrEmpty( sKey ) )
                        {
                            attrAppKey = string.Format( "{0}-{1}-{2}" , 
                            containerType.Name , propertyName , typeName );
                            if ( HttpContext.Current.Application [ attrAppKey ] == null )
                            {
                                HttpContext.Current.Application [ attrAppKey ] = sKey;
                            }
                            else
                            {
                                sKey = HttpContext.Current.Application [ attrAppKey ].ToString();
                            }

                            sLocalizedText = ui.GetResourceValueFromDb( sKey );
                            if ( string.IsNullOrEmpty( sLocalizedText ) )
                            {
                                sLocalizedText = sKey;
                            }

                            ( ( DisplayAttribute ) attr ).Name = sLocalizedText;
                        }
                    }
                    else if ( attr is ValidationAttribute )
                    {
                        sKey = ( ( ValidationAttribute ) attr ).ErrorMessage;

                        if ( !string.IsNullOrEmpty( sKey ) )
                        {
                            attrAppKey = string.Format( "{0}-{1}-{2}" , 
                            containerType.Name , propertyName , typeName );
                            if ( HttpContext.Current.Application [ attrAppKey ] == null )
                            {
                                HttpContext.Current.Application [ attrAppKey ] = sKey;
                            }
                            else
                            {
                                sKey = HttpContext.Current.Application [ attrAppKey ].ToString();
                            }

                            sLocalizedText = ui.GetResourceValueFromDb( sKey );
                            if ( string.IsNullOrEmpty( sLocalizedText ) )
                            {
                                sLocalizedText = sKey;
                            }

                            ( ( ValidationAttribute ) attr ).ErrorMessage = sLocalizedText;
                        }
                    }
                }
            }
            HttpContext.Current.Application.UnLock();

            return base.CreateMetadata
              (attributes, containerType, modelAccessor, modelType, propertyName);
        }
    }
}

The main point here is to use the Application object as a resource key container to hold the key as the display name and error message will be changed to the corresponding localized texts. In the Application object, string.Format( "{0}-{1}-{2}" , containerType.Name , propertyName , typeName ) will effectively make a unique key for each display name or error message.

This localization provider should be registered in Application_Start() in Global.asax.cs:

protected void Application_Start()
{
    ...... 
    ModelMetadataProviders.Current = new MyUIResources.MyLocalizationProvider();
}

With this provider in place, we can show Notes property in the view in a centralized and kinda view-model coupled way:

@Html.DisplayNameFor( m => m.Notes )
@Html.TextBoxFor( m => m.Notes )
@Html.ValidationMessageFor( m => m.Notes )

If we need to add RequiredAttribute, just simply add the annotation to Notes property:

[Required(ErrorMessage = "RequiredMsg")]
[StringLength(100, ErrorMessage="NotesError")]
[Display(Name = "Notes")]
public string Notes { get; set; }

And @Html.ValidationMessageFor( m => m.Notes ) will also take the responsibility to show Required localized message in addition to StringLength error message, provided that there is the "RequiredMsg" resource key.

Sounds great, eh?

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here