🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)
🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)

🔧 Programmierung 🕛 kürzlich 15 Min Lesezeit
0

ASP.NET 8 – Multilingual Application with single Resx file – Part 3 – Forms Validation Strings

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht




ASP.NET 8 – Multilingual Application with single Resx file – Part 3 – Forms Validation Strings



A practical guide to building a multi-language Asp.Net 8 MVC application.



Abstract:. A practical guide to building a multi-language Asp.Net 8 MVC application where all language resource strings are kept in a single shared file, as opposed to having separate resource files for each controller/view. In this part, we focus on the localization of form validation error strings.






1 Multilingual form validation error strings



A separate task is how to handle in Asp.Net MVC application a localization of form validation error strings, or so-called Data Annotation Localization. That is the focus of this article.






2 Other articles in this series



Articles in this series are:


ASP.NET 8 – Multilingual Application with single Resx file – Part 1


ASP.NET 8 – Multilingual Application with single Resx file – Part 2 – Alternative Approach


ASP.NET 8 – Multilingual Application with single Resx file – Part 3 – Form Validation Strings


ASP.NET 8 – Multilingual Application with single Resx file – Part 4 – Resource Manager





3 Shared Resources approach



By default, Asp.Net Core 8 MVC technology envisions separate resource file .resx for each controller and the view. But most people do not like it, since most multilanguage strings are the same in different places in the application, we would like it to be all in the same place. Literature [1] calls that approach the “Shared Resources” approach.


In order to implement it, we will create a marker class SharedResoureces.cs to group all the resources. Then in our application, we will invoke Dependency Injection (DI) for that particular class/type instead of a specific controller/view. That is a little trick mentioned in Microsoft documentation [1] that has been a source of confusion in StackOverflow articles [6]. We plan to demystify it here. While everything is explained in [1], what is needed are some practical examples, like the one we provide here.





4 Steps to Multilingual Application





4.1 Configuring Localization Services and Middleware



Localization services are configured in Program.cs:




CODE
private static void AddingMultiLanguageSupportServices(WebApplicationBuilder? builder)
{
if (builder == null) { throw new Exception("builder==null"); };

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 AddingMultiLanguageSupport(WebApplication? app)
{
app?.UseRequestLocalization();
}










4.2 Create marker class SharedResources.cs



This is just a dummy marker class to group shared resources. We need it for its name and type.


It seems the namespace needs to be the same as the app root namespace, which needs to be the same as the assembly name. I had some problems when changing the namespace, it would not work. If it doesn't work for you, you can try to use the full class name in your DI instruction, like this one:


IStringLocalizer<SharedResources01.SharedResource> StringLocalizer



There is no magic in the name "SharedResource", you can name it "MyResources" and change all references in the code to "MyResources" and all will still work.



The location seems can be any folder, although some articles ([6] claim it needs to be the root project folder I do not see such problems in this example. To me looks like it can be any folder, just keep your namespace tidy.




CODE

//SharedResource.cs===================================================
namespace SharedResources03
{
/*
* This is just a dummy marker class to group shared resources
* We need it for its name and type
*
* It seems the namespace needs to be the same as app root namespace
* which needs to be the same as the assembly name.
* I had some problems when changing the namespace, it would not work.
* If it doesn't work for you, you can try to use full class name
* in your DI instruction, like this one: SharedResources03.SharedResource
*
* There is no magic in the name "SharedResource", you can
* name it "MyResources" and change all references in the code
* to "MyResources" and all will still work
*
* Location seems can be any folder, although some
* articles claim it needs to be the root project folder
* I do not see such problems in this example.
* To me looks it can be any folder, just keep your
* namespace tidy.
*/


public class SharedResource
{
}
}










4.3 Create language resources files



In the folder “Resources” create your language resources files, and make sure you name them SharedResources.xx.resx.








4.4 Selecting Language/Culture



Based on [5], the Localization service has three default providers:





  1. QueryStringRequestCultureProvider


  2. CookieRequestCultureProvider


  3. AcceptLanguageHeaderRequestCultureProvider



Since most apps will often provide a mechanism to set the culture with the ASP.NET Core culture cookie, we will focus only on that approach in our example.


This is the code to set .AspNetCore.Culture cookie:




CODE
private void ChangeLanguage_SetCookie(HttpContext myContext, string? culture)
{
if (culture == null) { throw new Exception("culture == null"); };

//this code sets .AspNetCore.Culture cookie
CookieOptions cookieOptions=new CookieOptions();
cookieOptions.Expires = DateTimeOffset.UtcNow.AddMonths(1);
cookieOptions.IsEssential = true;

myContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
cookieOptions
);
}






Cookie can be easily seen with Chrome DevTools:





Note that I added some debugging info into the footer, to show the value of the Request language cookie, to see if the app is working as desired.






4.5 Using Data Annotation – Field Validation



In your model class, you set up validation attributes with proper strings that need to be localized.




CODE
//LocalizationExampleViewModel.cs===============================================
namespace SharedResources03.Models.Home
{ public class LocalizationExampleViewModel
{
/* It is these field validation error messages
* that are focus of this example. We want to
* be able to present them in multiple languages
*/

//model
[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;
}
}







For model-level validation in the controller, we will use classical IStringLocalizer.




CODE
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IStringLocalizer<SharedResource> _stringLocalizer;

public HomeController(ILogger<HomeController> logger,
IStringLocalizer<SharedResource> stringLocalizer)
{
_logger = logger;
_stringLocalizer = stringLocalizer;
}

//--------------------------

public IActionResult LocalizationExample(LocalizationExampleViewModel model)
{
if(model.IsSubmit)
{
if (!ModelState.IsValid)
{
ModelState.AddModelError("", _stringLocalizer["Please correct all errors and submit again"]);
}
}
else
{
ModelState.Clear();
}

return View(model);
}









4.6 Synchronization Asp.Net CSS error classes with Bootstrap CSS classes



Asp.Net will add CSS class .input-validation-error to form a field with an error. But, Bootstrap does not know what to do with that CSS class, so that class needs to be mapped to CSS class that Bootstrap understands, and that is CSS class .is-invalid.


That is the purpose of this JavaScript code that is here. Of course, we hook to the DOMContentLoaded event and do the mapping of CSS classes.


All this is to sync Asp.Net CSS error classes with Bootstrap CSS classes to mark error input elements border to red by Bootstrap.


The final result is that the red line on the form control marks an invalid field.




CODE
@* _ValidationClassesSyncBetweenAspNetAndBootstrap.cshtml===================== *@
@*
All this is to sync Asp.Net CSS error classes with Bootstrap CSS classes to
mark error input elements border to red by Bootstrap.

Asp.Net will add CSS class .input-validation-error to form a field with an error.
But, Bootstrap does not know what to do with that CSS class, so that class
needs to be mapped to CSS class that Bootstrap understands, and that is
CSS class .is-invalid.

That is the purpose of this JavaScript code that is here. Of course, we hook
to DOMContentLoaded event and do the mapping of CSS classes.

The final result is that the red line on the form control marking an invalid field.
*@


<script type="text/javascript">
window.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll("input.input-validation-error")
.forEach((elem) => { elem.classList.add("is-invalid"); }
);
});
</script>









4.7 Sample view with field and model validation messages



Here is our sample view.




CODE
@* LocalizationExample.cshtml ====================================================*@
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Localization

@model LocalizationExampleViewModel

@{
<partial name="_ValidationClassesSyncBetweenAspNetAndBootstrap" />

<div style="width:500px ">
<fieldset class="border rounded-3 p-3 bg-info">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<form id="formlogin" novalidate>
<div class="form-group">
<label asp-for="UserName"></label>
<div>
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
<input class="form-control" asp-for="UserName" />
</div>
<div class="form-group">
<label asp-for="Email"></label>
<div>
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<input class="form-control" asp-for="Email" />
</div>
<input type="hidden" name="IsSubmit" value="true">
<button type="submit" class="btn btn-primary mt-3 float-end"> Submit</button>
</form>
</fieldset>
</div>
}






Note that we used novalidate attribute in the form element to suppress browser-level integrated validation that is popping out and is not localized.


So, there are 3 possible levels of validation:





  1. Server-side validation – used in this example, and is localized (multilingual)


  2. Client-side validation – in Asp.Net it can be enabled by using jquery.validate.unobtrusive.min.js, but we are not using it in this example.


  3. Browser-integrated validation – disabled in this example by the usage of novalidate attribute, because it is not localized, and is always in English.



If you do not set novalidate attribute, the browser will pop up its validation dialog and you will see messages as on the following screen. It can be confusing to the user to multiple different messages.





Note that I added some debugging info into the footer, to show the value of the Request language cookie, to see if the app is working as desired.





5 Full Code



Since most people like code they can copy-paste, here is the full code of the application. Code can be downladed at GitHub [99].




CODE

//Program.cs===========================================================================
namespace SharedResources03
{
public class Program
{
public static void Main(string[] args)
{
//=====Middleware and Services=============================================
var builder = WebApplication.CreateBuilder(args);

//adding multi-language support
AddingMultiLanguageSupportServices(builder);

// Add services to the container.
builder.Services.AddControllersWithViews();

//====App===================================================================
var app = builder.Build();

//adding multi-language support
AddingMultiLanguageSupport(app);

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=ChangeLanguage}/{id?}");

app.Run();
}

private static void AddingMultiLanguageSupportServices(WebApplicationBuilder? builder)
{
if (builder == null) { throw new Exception("builder==null"); };

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 AddingMultiLanguageSupport(WebApplication? app)
{
app?.UseRequestLocalization();
}
}
}


//SharedResource.cs===================================================
namespace SharedResources03
{
/*
* This is just a dummy marker class to group shared resources
* We need it for its name and type
*
* It seems the namespace needs to be the same as app root namespace
* which needs to be the same as the assembly name.
* I had some problems when changing the namespace, it would not work.
* If it doesn't work for you, you can try to use full class name
* in your DI instruction, like this one: SharedResources03.SharedResource
*
* There is no magic in the name "SharedResource", you can
* name it "MyResources" and change all references in the code
* to "MyResources" and all will still work
*
* Location seems can be any folder, although some
* articles claim it needs to be the root project folder
* I do not see such problems in this example.
* To me looks it can be any folder, just keep your
* namespace tidy.
*/


public class SharedResource
{
}
}

//ChangeLanguageViewModel.cs=====================================================
namespace SharedResources03.Models.Home
{
public class ChangeLanguageViewModel
{
//model
public string? SelectedLanguage { get; set; } = "en";

public bool IsSubmit { get; set; } = false;

//view model
public List<SelectListItem>? ListOfLanguages { get; set; }
}
}

//LocalizationExampleViewModel.cs===============================================
namespace SharedResources03.Models.Home
{ public class LocalizationExampleViewModel
{
/* It is these field validation error messages
* that are focus of this example. We want to
* be able to present them in multiple languages
*/

//model
[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;
}
}

//HomeController.cs================================================================
namespace SharedResources03.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IStringLocalizer<SharedResource> _stringLocalizer;

public HomeController(ILogger<HomeController> logger,
IStringLocalizer<SharedResource> stringLocalizer)
{
_logger = logger;
_stringLocalizer = stringLocalizer;
}

public IActionResult ChangeLanguage(ChangeLanguageViewModel model)
{
if (model.IsSubmit)
{
HttpContext myContext = this.HttpContext;
ChangeLanguage_SetCookie(myContext, model.SelectedLanguage);
//doing funny redirect to get new Request Cookie
//for presentation
return LocalRedirect("/Home/ChangeLanguage");
}

//prepare presentation
ChangeLanguage_PreparePresentation(model);
return View(model);
}

private void ChangeLanguage_PreparePresentation(ChangeLanguageViewModel model)
{
model.ListOfLanguages = new List<SelectListItem>
{
new SelectListItem
{
Text = "English",
Value = "en"
},

new SelectListItem
{
Text = "German",
Value = "de",
},

new SelectListItem
{
Text = "French",
Value = "fr"
},

new SelectListItem
{
Text = "Italian",
Value = "it"
}
};
}

private void ChangeLanguage_SetCookie(HttpContext myContext, string? culture)
{
if (culture == null) { throw new Exception("culture == null"); };

//this code sets .AspNetCore.Culture cookie
CookieOptions cookieOptions=new CookieOptions();
cookieOptions.Expires = DateTimeOffset.UtcNow.AddMonths(1);
cookieOptions.IsEssential = true;

myContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
cookieOptions
);
}

public IActionResult LocalizationExample(LocalizationExampleViewModel model)
{
if(model.IsSubmit)
{
if (!ModelState.IsValid)
{
ModelState.AddModelError("", _stringLocalizer["Please correct all errors and submit again"]);
}
}
else
{
ModelState.Clear();
}

return View(model);
}


public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}











CODE
@* _ValidationClassesSyncBetweenAspNetAndBootstrap.cshtml===================== *@
@*
All this is to sync Asp.Net CSS error classes with Bootstrap CSS classes to
mark error input elements border to red by Bootstrap.

Asp.Net will add CSS class .input-validation-error to form a field with an error.
But, Bootstrap does not know what to do with that CSS class, so that class
needs to be mapped to CSS class that Bootstrap understands, and that is
CSS class .is-invalid.

That is the purpose of this JavaScript code that is here. Of course, we hook
to DOMContentLoaded event and do the mapping of CSS classes.

The final result is that the red line on the form control marking an invalid field.
*@


<script type="text/javascript">
window.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll("input.input-validation-error")
.forEach((elem) => { elem.classList.add("is-invalid"); }
);
});
</script>

@* ChangeLanguage.cshtml ===================================================*@
@model ChangeLanguageViewModel

@{
<div style="width:500px">
<p class="bg-info">
<partial name="_Debug.AspNetCore.CultureCookie" /><br />
</p>

<form id="form1" >
<fieldset class="border rounded-3 p-3">
<legend class="float-none w-auto px-3">Change Language</legend>
<div class="form-group">
<label asp-for="SelectedLanguage">Select Language</label>
<select class="form-select" asp-for="SelectedLanguage"
asp-items="@Model.ListOfLanguages">
</select>
<input type="hidden" name="IsSubmit" value="true">
<button type="submit" form="form1" class="btn btn-primary mt-3 float-end"
asp-area="" asp-controller="Home" asp-action="ChangeLanguage">
Submit
</button>
</div>
</fieldset>
</form>
</div>
}

@* LocalizationExample.cshtml ====================================================*@
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Localization

@model LocalizationExampleViewModel

@{
<partial name="_ValidationClassesSyncBetweenAspNetAndBootstrap" />

<div style="width:500px ">
<fieldset class="border rounded-3 p-3 bg-info">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<form id="formlogin" novalidate>
<div class="form-group">
<label asp-for="UserName"></label>
<div>
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
<input class="form-control" asp-for="UserName" />
</div>
<div class="form-group">
<label asp-for="Email"></label>
<div>
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<input class="form-control" asp-for="Email" />
</div>
<input type="hidden" name="IsSubmit" value="true">
<button type="submit" class="btn btn-primary mt-3 float-end"> Submit</button>
</form>
</fieldset>
</div>
}










6 References



[1]


Provide localized resources for languages and cultures in an ASP.NET Core app



[3]


Globalization and localization in ASP.NET Core



[5]


ASP.NET Core Localization with help of SharedResources



[99] https://github.com/MarkPelf/AspNet8MultilingualApplicationWithSingleResxFile

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
3 Quellen
GPT-6 Astra Release Today? OpenAI’s Next Major AI Model Is Almost Here
1 Quelle
Apple accuses OpenAI of destroying evidence as trade-secrets fight intensifies
1 Quelle
Major AI platforms go down in unprecedented simultaneous outage
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten ASP.NET 8 – Multilingual Application with single Resx file – Part 3 – Forms Validation Strings

Thematisch verwandte Begriffe: ASPNET, Multilingual, Application, with · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...