Localizing Data Annotation Attributes in ASP.NET Core

Posted on

In one of my previous articles, ASP.NET Core Localization from the Database, I demonstrated various techniques for retrieving localized content from the database and displaying that content in ASP.NET controllers and views. Core MVC. Many people have asked me about implementing a similar type of localization with data annotations so they can decorate their model properties with data annotations and link those models with the localized Razor views or forms.

This article is based on my previous article ASP.NET Core Localization from Database, so I won’t start it from scratch and show you how to implement location services to retrieve content from localization from the database and display a drop-down list at the top to switch content from one language to another language. You can read ASP.NET Core Localization from the Database to learn basic knowledge about localization in ASP.NET Core.

Implementing a simple ASP.NET Core MVC form

Let’s start by creating a Customer model class in the Models folder. It is a simple model class with only two properties.

Client.cs

public class Customer
{
    public int Id { get; set; }

    public string? FirstName { get; set; }

    public string? LastName { get; set; }
}

Next, bind the above model class with a Razor view and create a simple ASP.NET Core MVC form as shown below.

@model Customer;

@{
    ViewData["Title"] = Localize("customer.page.create.title");
}

<div class="myform">
    <h3>@Localize("customer.page.create.title")</h3>

    <form asp-action="form" method="post">

        <div class="form-group">
            <label asp-for="FirstName"></label>
            <input asp-for="FirstName" class="form-control" />
        </div>

        <div class="form-group">
            <label asp-for="LastName"></label>
            <input asp-for="LastName" class="form-control" />
        </div>

        <button type="submit" class="btn btn-primary">
            @Localize("general.button.create")
        </button>

    </form>

</div>

The code snippet above uses a Locate method I implemented in my previous post, ASP.NET Core Localization from Database to show localized strings from the database. Run this project now and you should see the following form where all contents are rendered in English.

ASP.NET Core MVC form without localized data annotations
ASP.NET Core MVC form without localized data annotations

Try changing the language from English to French in the top dropdown and you will notice that the content rendered using the Locate The method displays strings in French but the form labels First name and Last name are always displayed in English. Indeed, we have not yet implemented our custom localized data annotation.

ASP.NET Core MVC form without French localized data annotations
ASP.NET Core MVC form without French localized data annotations

Implementing a Custom Localized DisplayNameAttribute

We want to implement a custom DisplayNameAttribute which can automatically detect the current language and retrieve localized content from the database. Once this attribute is implemented, we will be able to use this attribute with First name and Last name properties as follows.

[LocalizedDisplayName("customer.page.create.firstname")]
public string? FirstName { get; set; }

[LocalizedDisplayName("customer.page.create.lastname")]
public string? LastName { get; set; }

Let’s add a new class LocalizedDisplayNameAttributeLocalizedDisplayNameAttribute to the project and inherit this class from DisplayNameAttribute to classify.

LocalizedDisplayNameAttribute.cs

public class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
    private string _resourceKey = string.Empty;
    public HttpContext _httpContext => new HttpContextAccessor().HttpContext;

    public LocalizedDisplayNameAttribute(string resourceKey) : base(resourceKey)
    {
        _resourceKey = resourceKey;
    }

    public override string DisplayName
    {
        get
        {
            // Resolve Services 
            ILanguageService _languageService = (ILanguageService)_httpContext.RequestServices.GetService(typeof(ILanguageService));
            ILocalizationService _localizationService = (ILocalizationService)_httpContext.RequestServices.GetService(typeof(ILocalizationService));

            // Get Language Information from Database based on Current Culture
            var currentCulture = Thread.CurrentThread.CurrentUICulture.Name;
            var language = _languageService.GetLanguageByCulture(currentCulture);

            if (language != null)
            {
                // Get String Resource Value from Database
                var stringResource = _localizationService.GetStringResource(_resourceKey, language.Id);
                if (stringResource != null && !string.IsNullOrEmpty(stringResource.Value))
                {
                    return stringResource?.Value ?? _resourceKey;
                }
            } 

            return _resourceKey;
        }
    } 
}

The above class constructor accepts a resourceKey parameter that will be used to query the localized content of the database.

public LocalizedDisplayNameAttribute(string resourceKey) : base(resourceKey)
{
    _resourceKey = resourceKey;
}

The main functionality is implemented in the replacement Display a name property where we first solve our custom localization and language services that we implemented in the previous post, ASP.NET Core localization from the database.

ILanguageService _languageService = (ILanguageService)_httpContext.RequestServices.GetService(typeof(ILanguageService));
ILocalizationService _localizationService = (ILocalizationService)_httpContext.RequestServices.GetService(typeof(ILocalizationService));

You also need to add the following line in the Startup.cs file in order to access the current HttpContext in the custom attribute.

services.AddHttpContextAccessor();

Next, we detect the current UI culture and retrieve the language information from the database.

var currentCulture = Thread.CurrentThread.CurrentUICulture.Name;
var language = _languageService.GetLanguageByCulture(currentCulture);

Finally, we read the resource string value from the database using the resource key and language.

var stringResource = _localizationService.GetStringResource(_resourceKey, language.Id);
if (stringResource != null && !string.IsNullOrEmpty(stringResource.Value))
{
    return stringResource?.Value ?? _resourceKey;
}

We can now update the Client class code and LocalizedDisplayName with the properties we want to locate.

public class Customer
{
    public int Id { get; set; }

    [LocalizedDisplayName("customer.page.create.firstname")]
    public string? FirstName { get; set; }

    [LocalizedDisplayName("customer.page.create.lastname")]
    public string? LastName { get; set; }
}

Run the project again and you will notice that the form labels now show the strings from the database.

English localized display name attribute
English localized display name attribute

If you select French language, the form labels will automatically change to display French language channels.

Display name attribute localized to French language
Display name attribute localized to French language

Selecting the German language will change the form labels again to display the German language strings from the database.

German localized display name attribute
German localized display name attribute

Summary

I hope you found this article useful. If you have any comments or suggestions, please leave your comments below. Don’t forget to share this tutorial with your friends or community. You can also download the full source code for this article using the Download Source Code button shown at the beginning of this article.

Leave a Reply

Your email address will not be published.