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

ASP.NET MVC Localization using a Database

0.00/5 (No votes)
13 Apr 2016 1  
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 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