Click here to Skip to main content
15,879,474 members
Articles / Programming Languages / C#
Tip/Trick

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

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
22 Dec 2012CPOL2 min read 70.1K   13   6
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:

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

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

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

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

HTML
<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:

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

And then define the resource type like this:

C#
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.

C#
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:

C#
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:

C#
[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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
PraiseVery good post. Pin
Olivier Voutat3-Mar-16 1:48
Olivier Voutat3-Mar-16 1:48 
GeneralGreat Post Pin
Shajahan KK7-Apr-15 21:50
Shajahan KK7-Apr-15 21:50 
QuestionGreat post!! Pin
Prakash Balasubramanian4-Jun-14 2:51
Prakash Balasubramanian4-Jun-14 2:51 
QuestionMore hits to DB Pin
TAMIZH199012-Dec-13 22:08
TAMIZH199012-Dec-13 22:08 
AnswerRe: More hits to DB Pin
Abu Ali Muhammad Sharjeel22-Jan-14 1:03
professionalAbu Ali Muhammad Sharjeel22-Jan-14 1:03 
GeneralMy vote of 5 Pin
mohamed sobhey mahmoud27-Jun-13 3:26
mohamed sobhey mahmoud27-Jun-13 3:26 

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.