\
:::info This part 4 of a 4-part series. Read part 1 here, part 2 here, and part 3 here.
:::
1 Resource Manager is still working in Asp.Net 8 MVCFor those who like the old-fashioned approach, the good news is that the Resource Manager is still working in Asp.Net 8 MVC. You can use it together at the same time as IStringLocalizer, or even as the only localization mechanism if that is what you like.
1.1 How Resource Manager worksSo, a typical solution is to use expressions in code like “Resources.SharedResource.Wellcome”. That is really a property that evaluates to the string. Evaluation to the string is done dynamically in run-time, and the string is chosen from SharedResource resx files, based on the current thread culture.
2 Other articles in this seriesArticles 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 approachBy 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.
4 Steps to Multilingual Application 4.1 Configuring Localization Services and MiddlewareLocalization services are configured in Program.cs:
\
```csharp
private static void AddingMultiLanguageSupportServices(WebApplicationBuilder? builder)
{
if (builder == null) { throw new Exception("builder==null"); };
}
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: \ `IStringLocalizercsharp
//SharedResource.cs=================================================== namespace SharedResources04 { /* * 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: * IStringLocalizer 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 * * 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. \ ![](https://cdn.hackernoon.com/images/eUn59ClVtWU6R4iSzcVC45bTj4f1-2024-11-30T18:00:09.162Z-nmn4jyt4xahmrps9bpwq2ct1) ![](https://cdn.hackernoon.com/images/eUn59ClVtWU6R4iSzcVC45bTj4f1-2024-11-30T18:00:09.210Z-h0muqt7v0moxqk5mheah28cs) In Visual Studio Resource editor you need to set Access Modifier to Public for all resource files. \ ![](https://cdn.hackernoon.com/images/eUn59ClVtWU6R4iSzcVC45bTj4f1-2024-11-30T18:00:09.212Z-mbn3gn52o9td2hor5re94atg) ### 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: \csharp
private void ChangeLanguage_SetCookie(HttpContext myContext, string? culture)
{
if(culture == null) { throw new Exception("culture == null"); };
}
\ Cookie can be easily seen with Chrome DevTools: \ ![](https://cdn.hackernoon.com/images/eUn59ClVtWU6R4iSzcVC45bTj4f1-2024-11-30T18:00:09.214Z-p79aeampl43lj5hvij79jsah) \ I built a small application to demo it, and here is the screen where I can change the language: \ ![](https://cdn.hackernoon.com/images/eUn59ClVtWU6R4iSzcVC45bTj4f1-2024-11-30T18:00:10.051Z-nxgpl0cynhmw4a9cr94l99oj) 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 Localization Services in the Controller Here we show how both approaches, IStringLocalizer and Resource Manager are used to localize strings in the Controller codeHere is the code snippet: \csharp
public class HomeController : Controller
{
private readonly ILogger _logger;
private readonly IStringLocalizer _stringLocalizer;
{ string text = "Thread CurrentUICulture is [" + @Thread.CurrentThread.CurrentUICulture.ToString() + "] ; "; text += "Thread CurrentCulture is [" + @Thread.CurrentThread.CurrentCulture.ToString() + "]"; model.ThreadCultureInController = text; //here we test localization by Resource Manager model.LocalizedInControllerByResourceManager1 = Resources.SharedResource.Wellcome; model.LocalizedInControllerByResourceManager2 = Resources.SharedResource.Hello_World; //here we test localization by IStringLocalizer model.LocalizedInControllerByIStringLocalizer1 = _stringLocalizer["Wellcome"]; model.LocalizedInControllerByIStringLocalizer2 = _stringLocalizer["Hello World"];
return View(model);}
### 4.6 Using Localization Services in the View \ Here we show how both approaches, IStringLocalizer and Resource Manager are used to localize strings in the View codeHere is the code snippet: \razor-cshtml
@* LocalizationExample.cshtml ====================================================*@
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Localization
@using SharedResources04
@model LocalizationExampleViewModel
@* Here is of course the Dependency Injection (DI) coming in and filling all the dependencies. The key thing is we are asking for a specific type=SharedResource. If it doesn't work for you, you can try to use full class name in your DI instruction, like this one: @inject IStringLocalizer StringLocalizer *@
@inject IStringLocalizer StringLocalizer
@{
Controller Thread Culture:
@Model.ThreadCultureInController
Localized In Controller By ResourceManager:
@Model.LocalizedInControllerByResourceManager1
Localized In Controller By ResourceManager:
@Model.LocalizedInControllerByResourceManager2
Localized In Controller By IStringLocalizer:
@Model.LocalizedInControllerByIStringLocalizer1
Localized In Controller By IStringLocalizer:
@Model.LocalizedInControllerByIStringLocalizer2
@{
string text = "Thread CurrentUICulture is [" +
@Thread.CurrentThread.CurrentUICulture.ToString() + "] ; ";
text += "Thread CurrentCulture is [" +
@Thread.CurrentThread.CurrentCulture.ToString() + "]";
}
View Thread Culture:
@text
Localized In View By ResourceManager:
@SharedResources04.Resources.SharedResource.Wellcome
Localized In View By ResourceManager:
@SharedResources04.Resources.SharedResource.Hello_World
Localized In View By IStringLocalizer:
@StringLocalizer["Wellcome"]
Localized In View By IStringLocalizer:
@StringLocalizer["Hello World"]
}
### 4.7 Execution result Here is what the execution result looks like: \ ![](https://cdn.hackernoon.com/images/eUn59ClVtWU6R4iSzcVC45bTj4f1-2024-11-30T18:00:10.019Z-v8fe0mgejki6t3fq2tuae6mm) 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\]. \csharp
//Program.cs===========================================================================
namespace SharedResources04
{
public class Program
{
public static void Main(string[] args)
{
//=====Middleware and Services=============================================
var builder = WebApplication.CreateBuilder(args);
}
//SharedResource.cs=================================================== namespace SharedResources04 { /* * 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: * IStringLocalizer 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 * * 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 { }}
//HomeController.cs================================================================ namespace SharedResources04.Controllers { public class HomeController : Controller { private readonly ILogger _logger; private readonly IStringLocalizer _stringLocalizer;
/* Here is, of course, the Dependency Injection (DI) coming in and filling * all the dependencies. The key thing is we are asking for a specific * type=SharedResource. * If it doesn't work for you, you can try to use full class name * in your DI instruction, like this one: * IStringLocalizer}
//ChangeLanguageViewModel.cs===================================================== namespace SharedResources04.Models.Home { public class ChangeLanguageViewModel { //model public string? SelectedLanguage { get; set; } = "en";
public bool IsSubmit { get; set; } = false; //view model public List}
//LocalizationExampleViewModel.cs=============================================== namespace SharedResources04.Models.Home { public class LocalizationExampleViewModel { public string? LocalizedInControllerByResourceManager1 { get; set; } public string? LocalizedInControllerByResourceManager2 { get; set; }
public string? LocalizedInControllerByIStringLocalizer1 { get; set; } public string? LocalizedInControllerByIStringLocalizer2 { get; set; } public string? ThreadCultureInController { get; set; } }}
razor-cshtml
@* ChangeLanguage.cshtml ===================================================*@
@model ChangeLanguageViewModel
@{
}
@* LocalizationExample.cshtml ====================================================*@ @using Microsoft.AspNetCore.Mvc.Localization @using Microsoft.Extensions.Localization @using SharedResources04
@model LocalizationExampleViewModel
@* Here is of course the Dependency Injection (DI) coming in and filling all the dependencies. The key thing is we are asking for a specific type=SharedResource. If it doesn't work for you, you can try to use full class name in your DI instruction, like this one: @inject IStringLocalizer StringLocalizer *@
@inject IStringLocalizer StringLocalizer
@{
Controller Thread Culture:
@Model.ThreadCultureInController
Localized In Controller By ResourceManager:
@Model.LocalizedInControllerByResourceManager1
Localized In Controller By ResourceManager:
@Model.LocalizedInControllerByResourceManager2
Localized In Controller By IStringLocalizer:
@Model.LocalizedInControllerByIStringLocalizer1
Localized In Controller By IStringLocalizer:
@Model.LocalizedInControllerByIStringLocalizer2
@{
string text = "Thread CurrentUICulture is [" +
@Thread.CurrentThread.CurrentUICulture.ToString() + "] ; ";
text += "Thread CurrentCulture is [" +
@Thread.CurrentThread.CurrentCulture.ToString() + "]";
}
View Thread Culture:
@text
Localized In View By ResourceManager:
@SharedResources04.Resources.SharedResource.Wellcome
Localized In View By ResourceManager:
@SharedResources04.Resources.SharedResource.Hello_World
Localized In View By IStringLocalizer:
@StringLocalizer["Wellcome"]
Localized In View By IStringLocalizer:
@StringLocalizer["Hello World"]
} ```
6 References[1] Make an ASP.NET Core app's content localizable
[2] Provide localized resources for languages and cultures in an ASP.NET Core app
[3] Implement a strategy to select the language/culture for each request in a localized ASP.NET Core app
[4] Globalization and localization in ASP.NET Core
[5] Troubleshoot ASP.NET Core Localization
[6] ASP.NET Core Localization with help of SharedResources
[99] https://github.com/MarkPelf/AspNet8MultilingualApplicationWithSingleResxFile
All Rights Reserved. Copyright , Central Coast Communications, Inc.