Click here to Skip to main content
15,867,306 members
Articles / Web Development / HTML
Article

ASP.NET MVC Localization using a Database

Rate me:
Please Sign up or sign in to vote.
5.00/5 (15 votes)
13 Apr 2016CPOL2 min read 26.8K   642   18   4
How to localize an ASP.NET MVC application and read all text from a database while still using DataAnnotations.

Introduction

ASP.NET MVC already offers support for localization through ressource files. However, if you need to read texts from a database things get more complicated. Especially if you still want to use DataAnnotations in your model for display text and validation messages.

Background

In a model, DataAnnotations can be used to define labels and validation messages for your model properties. Let's take a simple model that is used on a login page:

public class LoginModel
{
    [Required(ErrorMessage = "User name is required!")]
    [Display(Name = "User name")]
    public string UserName { get; set; }

    [Required(ErrorMessage = "Passwort is required!")]
    [Display(Name = "Password")]
    public string Password { get; set; }
}

In the corresponding view the following code can be used to automatically render a label, a textbox and a (initially invisible) validation message for the "UserName" property in your model:

@Html.LabelFor(m => m.UserName)
@Html.TextBoxFor(m => m.UserName)
@Html.ValidationMessageFor(m => m.UserName)

The view might look like this in the browser:

Image 1

Localization from the database

Now as we want to read the localized texts from a database, we simple write the text-id (or whatever unique key you use in your database) into the annotation attributes:

[Required(ErrorMessage = "27")]
[Display(Name = "42")]
public string UserName { get; set; }

To replace the text-id of the Display attribute with the text from the database, we need to create our own MetadataProvider:

public class MetadataProvider : AssociatedMetadataProvider
{
    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes,
        Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
        var metadata = new ModelMetadata(this, containerType, modelAccessor, modelType, propertyName);
        if (propertyName != null)
        {
            var displayAttribute = attributes.OfType<DisplayAttribute>().FirstOrDefault();
            if (displayAttribute != null)
            {
                int textId;
                if (Int32.TryParse(displayAttribute.Name, out textId))
                {
                    // TODO: get text from database
                    metadata.DisplayName = "DB Text with id " + textId;
                }
            }
        }
        return metadata;
    }
}

This class must then be registered in the Application_Start() method of Global.asax.cs:

ModelMetadataProviders.Current = new MetadataProvider();

For the validation attributes it is a bit more complicated. The first class we need is a ValidatorProvider, which tells the MVC validation system which class to use to perform model validation. Our implementation in the class LocalizableModelValidatorProvider returns an instance of LocalizableModelValidator.

public class LocalizableModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
    protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
    {
        var validators = base.GetValidators(metadata, context, attributes);
        return validators.Select(validator => new LocalizableModelValidator(validator, metadata, context)).ToList();
    }
}

Our validation provider also needs to be registered in the Application_Start() method of Global.asax.cs:

var provider = ModelValidatorProviders.Providers.FirstOrDefault(p => p.GetType() == typeof(DataAnnotationsModelValidatorProvider));
if (provider != null)
{
    ModelValidatorProviders.Providers.Remove(provider);
}
ModelValidatorProviders.Providers.Add(new LocalizableModelValidatorProvider());

As you can see, we remove the existing validator provider that is of type DataAnnotationsModelValidatorProvider and replace it with our own implementation in the LocalizableModelValidatorProvider class.

The second class we need is the actual ModelValidatorProvider, which we implement in the class LocalizableModelValidatorProvider:

public class LocalizableModelValidator : ModelValidator
{
    private readonly ModelValidator innerValidator;

    public LocalizableModelValidator(ModelValidator innerValidator, ModelMetadata metadata, ControllerContext controllerContext)
        : base(metadata, controllerContext)
    {
        this.innerValidator = innerValidator;
    }

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
    {
        var rules = innerValidator.GetClientValidationRules();
        var modelClientValidationRules = rules as ModelClientValidationRule[] ?? rules.ToArray();
        foreach (var rule in modelClientValidationRules)
        {
            int textId;
            if (Int32.TryParse(rule.ErrorMessage, out textId))
            {
                // TODO: read text from database
                rule.ErrorMessage = "DB_Text_" + textId;
            }
        }
        return modelClientValidationRules;
    }

    public override IEnumerable<ModelValidationResult> Validate(object container)
    {
        // execute the inner validation which doesn't have localization
        var results = innerValidator.Validate(container);
        // convert the error message (text id) to the localized value
        return results.Select(result =>
        {
            int textId;
            if (Int32.TryParse(result.Message, out textId))
            {
                // TODO: read text from database
                result.Message = "DB text with id " + textId;
            }
            return new ModelValidationResult() { Message = result.Message };
        });
    }
}

The first method GetClientValidationRules() is responsible for client side validation. It will be called for each property in your model that has a validation attribute each time a view renders your model. However if you disable client side validation in Web.config this method is never called.

The second method ModelValidationResult() is responsible for server side validation. It will be called for each property in your model that has a validation attribute AFTER the form has been posted back to the server.

Using the code

My examples do not show how to read a text from a database. You will find plenty of examples of that using ADO.NET or Entity Framework. For best performance do not hit the database every time you need to read a single text. Better build your own caching, for example read all texts into a dictionary once and then get them from there.

History

2016-04-13 Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Sevitec Informatik AG
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralAlternative for ASP.NET Core Pin
Rudi Breedenraedt1-Dec-22 22:03
Rudi Breedenraedt1-Dec-22 22:03 
QuestionCache this Solution Pin
behnam Rajabi31-Jul-18 2:30
professionalbehnam Rajabi31-Jul-18 2:30 
GeneralMy vote of 5 Pin
gicalle7519-Apr-16 10:35
professionalgicalle7519-Apr-16 10:35 
GeneralMy vote of 5 Pin
Rakesh Kohale13-Apr-16 20:54
professionalRakesh Kohale13-Apr-16 20:54 

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.