Implementing Multilingual Form Validation with a Single Resx File in ASP.NET 8
This guide details the integration of multilingual form validation error messages using a centralized resource file in an ASP.NET Core 8 application. The approach leverages shared resources to consolidate localization strings across controllers and views, eliminating redundancy.
The core setup begins in Program.cs by registering localization services and configuring request culture handling:
private static void ConfigureLocalizationServices(WebApplicationBuilder builder)
{
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
builder.Services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) =>
factory.Create(typeof(SharedResource));
});
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[] { "en", "fr", "de", "it" };
options.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
});
}
private static void ApplyLocalizationMiddleware(WebApplication app)
{
app.UseRequestLocalization();
}
A marker class named SharedResource is defined to act as a reference point for the localization system. This class must reside in the root namespace and is used to resolve resource files dynamicallly:
namespace SharedResources03
{
public class SharedResource
{
// Marker class for shared localization resources
}
}
Language-specific resource files are created under a Resources folder with names like SharedResource.en.resx, SharedResource.fr.resx, etc., containing localized validation messages.
Model validation is handled via data annotations with placeholder strings that will be replaced at runtime based on the current culture:
public class LocalizationExampleViewModel
{
[Required(ErrorMessage = "The UserName field is required.")]
[Display(Name = "UserName")]
public string? UserName { get; set; }
[EmailAddress(ErrorMessage = "The Email field is not a valid email address.")]
[Display(Name = "Email")]
public string? Email { get; set; }
public bool IsSubmit { get; set; } = false;
}
In the controller, IStringLocalizer<SharedResource> is injected to retrieve localized messages for custom error displays:
public IActionResult LocalizationExample(LocalizationExampleViewModel model)
{
if (model.IsSubmit && !ModelState.IsValid)
{
ModelState.AddModelError("", _stringLocalizer["Please correct all errors and submit again"]);
}
else
{
ModelState.Clear();
}
return View(model);
}
To ensure compatibility with Bootstrap’s styling, client-side JavaScript synchronizes ASP.NET’s default .input-validation-error class with Bootstrap’s .is-invalid class:
<script type="text/javascript">
window.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll("input.input-validation-error")
.forEach(elem => elem.classList.add("is-invalid"));
});
</script>
Views use standard tag helpers for rendering validation messages and incorporate the script partial to maintain visual consistency. The novalidate attribute is applied to prevent browser-native validation, ensuring only server-side, localized validation is shown.
Language selection is managed through a cookie-based mechanism, where user preference is stored in the .AspNetCore.Culture cookie, allowing persistent language settnigs across sessions.