Why does not work ASP.NET Core Localization - c#

I created an empty project.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(s => s.ResourcesPath = "Resources");
var supportedCultures = new CultureInfo[]
{
new CultureInfo("de-CH"),
new CultureInfo("en-GB"),
};
services.Configure<RequestLocalizationOptions>(s =>
{
s.SupportedCultures = supportedCultures;
s.SupportedUICultures = supportedCultures;
s.DefaultRequestCulture = new RequestCulture(culture: "de-CH", uiCulture: "de-CH");
});
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseStaticFiles();
// Using localization
var locOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(locOptions.Value);
app.UseMvc();
}
Folder Structure
Resources
|
|--Controllers
| HomeController.de.resx
| HomeController.en.resx
| HomeController.resx
Controller
public class HomeController : Controller
{
private readonly IStringLocalizer<HomeController> _stringLocalizer;
public HomeController(IStringLocalizer<HomeController> stringLocalizer)
{
_stringLocalizer = stringLocalizer;
}
public IActionResult Index()
{
string testValue = _stringLocalizer["Test"];
return View();
}
}
I'm new about asp.net core, I'm just trying to understand, Why testValue always return Test, it's a bit confusing. I'm doing something wrong? i will be happy if u help me.

Just add the package Microsoft.Extensions.Localization
After do that, it works.
The ResourcePath is optional, and if you leave it null, the resource files organization style is the same that classic Asp.Net application (in the same location of the target classes).

Two different errors here prevent correct loading of localized resources.
You set incorrect ResourcesPath in AddLocalization() call. Since your resx files are placed in Resources/Controllers directory, you should replace call
services.AddLocalization(s => s.ResourcesPath = "Resources");
with
services.AddLocalization(s => s.ResourcesPath = "Resources/Controllers");
You use incorrect name for resx files. Check Resource file naming section in Globalization and localization in ASP.NET Core article:
Resources are named for the full type name of their class minus the
assembly name. For example, a French resource in a project whose main
assembly is LocalizationWebsite.Web.dll for the class
LocalizationWebsite.Web.Startup would be named Startup.fr.resx. A
resource for the class
LocalizationWebsite.Web.Controllers.HomeController would be named
Controllers.HomeController.fr.resx. If your targeted class's namespace
isn't the same as the assembly name you will need the full type name.
For example, in the sample project a resource for the type
ExtraNamespace.Tools would be named ExtraNamespace.Tools.fr.resx.
So if your assembly is called TestMvcApplication and HomeController resides in namespace TestMvcApplication.Controllers, then you should call your resx files in the following way:
Resources
|
|--Controllers
| Controllers.HomeController.de.resx
| Controllers.HomeController.en.resx
| Controllers.HomeController.resx
I believe after you make above changes to your project, localization will work ok.

I fixed it, by making sure that my folder structure reflected the namespace I used when working with resource designer files !!

Related

Using IStringLocalizer using resources files in WebApi

In a WEB API in .NET 6.0, I'd like access resources based on a language. I do this :
In Startup.cs :
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddControllersWithViews()
.AddViewLocalization
(LanguageViewLocationExpanderFormat.SubFolder)
.AddDataAnnotationsLocalization();
services.Configure<RequestLocalizationOptions>(options => {
var supportedCultures = new[] { "fr-BE", "nl-BE" };
options.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
});
The resource files are in Resources\Controllers\ with 2 files MyController.fr-BE.resx and MyController.nl-BE.resx
In the controller :
private readonly IStringLocalizer<MyController> _localizer;
public MyController(IStringLocalizer<MyController> localizer)
{
_localizer = localizer;
}
In one of the entry point I do this :
public ActionResult Get()
{
var article = _localizer["Article"];
return Ok();
}
The article variable has these values :
Name = "Article"
ResourceNotFound = true
article.SearchedLocation = API.Resources.Controllers.MyController
Value = "Article"
In the resource file, I have for "Article" in MyController.fr-BE : "Article FR" and in MyController.nl-BE : "Article NL"
The request call from postman has in the header :
Accept-Language = fr-BE
Am I missed something ?
Thanks,
In your startup you misconfigured LanguageViewLocaionExpanderFormat to SubFolder. It should be Suffix, see the docs.
LanguageViewLocaionExpanderFormat has two options:
SubFolder: Locale is a subfolder under which the view exists.
Suffix: Locale is part of the view name as a suffix.
The sub folder would have a file structure: Resources/controllers/fr-BE/filename.resx
Whilst The suffix would have the following structure: Resources/controllers/filename.fr-BE.resx. Where filename should be the name of the controller.
services.AddControllersWithViews()
.AddViewLocalization
(LanguageViewLocationExpanderFormat.SubFolder)
.AddDataAnnotationsLocalization();
Your resources should look something like this:

ASP.NET Core 3.1 move localization resource files to a separate project

I'm trying to move my localization resource files to a separate project, since in my solution I have my logic split in different projects, and I'd like to share the same resource files on all of them without adding references between each other. I've got familiar with the new .NET Core way of localization and I tested it out in my MVC project and everything worked, however when I created a new .NET Core class library for the resources I couldn't manage to use them. I've read pretty much everything and couldn't manage to find a solution.
Here's my Resources project hierarchy:
I've tried moving the "resx" files to Resources folder, moving them next to Resources.cs without any folders, moving Resources.cs to the Resources folder, nothing works.
Here's my code:
Resources.cs:
using Microsoft.Extensions.Localization;
namespace Resources
{
public interface IResources
{
}
public class Resources : IResources
{
private readonly IStringLocalizer _localizer;
public Resources(IStringLocalizer<Resources> localizer)
{
_localizer = localizer;
}
public string this[string index]
{
get
{
return _localizer[index];
}
}
}
}
Startup.cs:
Configureservices method:
services.AddLocalization(options => { options.ResourcesPath = "Resources"; });
services
.AddMvc()
.AddDataAnnotationsLocalization(o =>
{
o.DataAnnotationLocalizerProvider = (type, factory) =>
{
return factory.Create(typeof(Resources.SharedResources));
};
})
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);
services.Configure<RequestLocalizationOptions>(options =>
{
CultureInfo[] supportedCultures = new[]
{
new CultureInfo("en"),
new CultureInfo("bg")
};
options.DefaultRequestCulture = new RequestCulture("en");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
Configure method:
app.UseRequestLocalization();
Index.cshtml where I'm trying to load my localization resources:
#using Microsoft.AspNetCore.Mvc.Localization;
#inject IHtmlLocalizer<Resources.Resources> Localizer
#{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">#Localizer["Welcome"]</h1>
<p>#Localizer["LearnAbout"] #Localizer["Build"].</p>
</div>
Instead of getting the resource values I just get the name of the resource as written inside the #Localizer.
Is there any way to have the resources separated in a different project in .NET Core 3.1? I've seen a lot of answers about older versions, but they don't work in 3.1.
Any help will be appreciated!
Edit: After changing the names of the resource files to SharedResources I can use them in the views, but I can't understand how to use them in ViewModels.
Whenever I call [Display(ResourceType = typeof(SharedResources), Name = "lblFromDate")] on my attribute I get an error:
InvalidOperationException: Cannot retrieve property 'Name' because
localization failed. Type 'Resources.SharedResources' is not public or
does not contain a public static string property with the name
'lblFromDate'.
I've made the resource files public, but for some reason they don't generate .cs files. Something that I've noticed is that I actually can't create resource files in .Net 3.1 Class Libraries, even searching for a resource file doesn't show anything up.

How to get localized string from ASP.NET Core Controller using IStringLocalizer?

Kinda confused here, super simple hello-world example of localization in ASP.Net Core 2.0. My About page is set up to render two localized strings:
From the view (using IViewLocalizer)
From code (using IStringLocalizer<HomeController> via the controller)
The code in the controller refuses to get the loc string appropriately. This is not complicated, what obvious things am I missing?
About.cshtml
#using Microsoft.AspNetCore.Mvc.Localization
#inject IViewLocalizer Localizer
#{
ViewData["Title"] = "About";
}
<h2>#ViewData["Title"]</h2>
<h3>#ViewData["Message"]</h3>
<p>#Localizer["Use this area to provide additional information."]</p>
^ Note the two strings: "Message" will be localized from code using IStringLocalizer (see HomeController below), and the #Localizer will use the IViewLocalizer class.
HomeController.cs
public class HomeController : Controller
{
private readonly IStringLocalizer _localizer;
public HomeController(IStringLocalizer<HomeController> localizer)
{
_localizer = localizer;
}
public IActionResult Index()
{
return View();
}
public IActionResult About()
{
ViewData["Message"] = _localizer["Your application description page."];
return View();
}
}
Startup.cs (relevant parts)
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("fr-CH"),
};
options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var locOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(locOptions.Value);
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Resources:
Views.Home.About.fr-CH.resx
^ with two values in it:
"Use this area to provide additional information." = "Use this
area... success for fr-CH!"
"Your application description page." =
"Your app descript... success for fr-CH!"
My Results:
localhost:56073/Home/About
^ This renders the strings as expected in en-US (default finds nothing, uses the strings actually hard coded)
localhost:56073/Home/About?culture=fr-CH
^ This renders ONLY the 2nd string: "Use this area... success for fr-CH!", which clearly means all the code wired up is working and finding the fr-CH.resx as expected.
BUT, the first string (set in code as ViewData["Message"]) does NOT get the fr-CH version! It's like the IStringLocalizer<HomeController> failed to realize there was a lang specified, or failed to find the fr-CH.resx that is clearly available.
Why???
Also BTW, I tried using the ShareResource example too (see link below), and passed in the factory to the HomeController ctor as IStringLocalizerFactory factory, also with no love, still not getting the fr-CH resource. Sigh.
Other notes:
Using this as my primary reference:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/localization
Using VS 2017, latest updates, with ASP.Net Core 2.0
The issue is that ASP .NET Core creates wrong RESX namespace for localization using IStringLocalizer. If you have in the code
services.AddLocalization(options => options.ResourcesPath = "Resources");
then the instance of injected service IStringLocalizer has in the namespace twice "Resources", namespace looking "Resources.Resources" . This is the root cause why the RESX cannot be found.
You are using IStringLocalizer<HomeController> as localizer in the controller to find the localized string. The localizer will look in Resources folder to find YouControllerNameSpace.HomeController resource file and since it doesn't find it, it will return the original key which you passed to the localizer.
To solve the problem, you can use either of following options:
Inject IStringLocalizer<T>
Inject IStringLocalizerFactory
For more information about resource file names, take a look at Resource file naming section in documentations.
Inject IStringLocalizer<T>
Using this option, you should have a resource file with the same name as full name of T, in your case, the controller code should be the same as it is:
IStringLocalizer _localizer;
public HomeController(IStringLocalizer<HomeController> localizer)
{
_localizer = localizer;
}
For the resource file:
Make sure you have YouControllerNameSpace.HomeController resource file. (YouControllerNameSpace is just a placeholder, use your controller namespace.)
Make sure you have the specified string in resource file.
Make sure you have resource files for different cultures.
Inject IStringLocalizerFactory
Using this option you can use any file as resource file. For example if you want to read resources from Views.Home.About resource file, you should change the controller code to this:
IStringLocalizer _localizer;
public HomeController(IStringLocalizerFactory factory)
{
_localizer = factory.Create("Views.Home.About",
System.Reflection.Assembly.GetExecutingAssembly().GetName().Name);
}
For the resource file:
Make sure you have Views.Home.About resource file.
Make sure you have the specified string in resource file.
Make sure you have resource files for different cultures.
Try the technique described by tmg in this answer.
Specifically, try adding the lines
options.RequestCultureProviders = new List<IRequestCultureProvider>
{
new QueryStringRequestCultureProvider(),
new CookieRequestCultureProvider()
};
to your ConfigureServices() function

custom keys in appSettings.json not working in ASP.NET Core 2.0

I added a CustomSettings section keys in appSettings.json in ASP.NET Core project:
{
"ConnectionStrings": {
"DefaultConnectionString": "Data Source=..."
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"CustomSettings": {
"Culture": "es-CO"
}
}
I've not been able to load Culture key in following controller:
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IEmailSender emailSender,
ILogger<AccountController> logger,
IConfiguration configuration)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(configuration.GetSection("CustomSettings")["Culture"])),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
);
}
No matter if I do following, always they return NULL:
configuration.GetSection("CustomSettings")["Culture"];
configuration.GetSection("CustomSettings").GetValue("Culture");
I tried help based in ASP.NET Core: Step by Step Guide to Access appsettings.json in web project and class library and I've created CustomSettings class with string Culture property and injecting in Startup as follows:
// Load Custom Configuration from AppSettings.json
services.Configure<Models.CustomSettings>(Configuration.GetSection("CustomSettings"));
Accesing by inject IOptions customSettings, the value of
customSettings.Value.Culture returns NULL.
First Question: ¿What am I doing wrong or what is missing?
Second Question: ¿Why doing following in Index of HomeController throws an exception?
public class HomeController : Controller
{
public IActionResult Index(IConfiguration configuration)
{
}
}
Exception:
An unhandled exception occurred while processing the request.
InvalidOperationException: Could not create an instance of type 'Microsoft.Extensions.Options.IOptions`1[[OmniMerchant.Models.CustomSettings, OmniMerchant, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. Model bound complex types must not be abstract or value types and must have a parameterless constructor.
Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder.CreateModel(ModelBindingContext bindingContext)
Third Question: I need to set Culture from Starting for all the app in background based on Culture property on appSettings.json, I read MSDN documentation, but I've not been able to achieve that, ¿How can I achieve this?
Thanks
First create the modal that matches the appsetting section
public class CustomSettings
{
public string Culture { get; set; }
}
Then register it in the ConfigureServices method in Startup.cs
services.Configure<CustomSettings>(Configuration.GetSection("CustomSettings"));
Then inject it using IOptions where its needed
AccountController(IOptions<CustomSettings> settings)
{
_settings = settings.Value;
}
Why configuration section values are null?
By default there are two config files. For Release build and one for Debug. Have you checked that you actually editing the correct one (probably appsettings.Development.json)
Why DI is not working.
In .NET Core basically you can use DI in two ways. By injecting it in constructor or directly in method. In the second option you have to use special attribute [FromServices]
In your application properties -> Debug section -> Environment variables
If this is set
ASPNETCORE_ENVIRONMENT: Development
It will use appsettings.Development.json
TL:DR; Your appsettings.json file needs to be in the same working directory as your dll file.
You need to make sure that you are running the app from it's working directory.
For example, if your dll's are built to the bin folder, the following won't work:
cd bin
dotnet run app.dll
Because the appsettings.json file is not in the same folder as the dll that you are running from.
However if you are in the directory that the appsettings.json file is in, the current working directory will be set and that file will be read.
dotnet run bin/app.dll
If using VS Code launch.json you can set the cwd property to achieve this.
I had problems reading from different configuration files until I added each of them specifically in the Startup constructor like below:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json") //***
.AddJsonFile("appsettings.development.json") //***
.AddEnvironmentVariables();
Configuration = builder.Build();
}
This is a bug I think but probably you can solve it by setting "HostingEnvironment.ContentRootPath" manualy.
Try to add the following code in the Startup method in Startup.cs:
if (env.EnvironmentName== "Production")
{
env.ContentRootPath = System.IO.Directory.GetCurrentDirectory();
}
or hardcode path like this:
if (env.EnvironmentName == "Production")
{
env.ContentRootPath = "/var/aspnetcore/...";
}
For example if your files located in "/var/aspnetcore/my_ASP_app", the startup method should be something like this:
public Startup(IHostingEnvironment env)
{
if (env.EnvironmentName== "Production")
{
//env.ContentRootPath = "/var/aspnetcore/my_ASP_app";
env.ContentRootPath = System.IO.Directory.GetCurrentDirectory();
}
//env.ContentRootPath = System.IO.Directory.GetCurrentDirectory();//you can write this line outside of IF block.
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true);
Configuration = builder.Build();
}
It is better to use Directory.GetCurrentDirectory() instead of hardcoding.
This worked for me in Linux Ubuntu with nginx, but I don't know if it is applicable for your enviornment.

Static Content in NancyFX with ASP.Net Core

I'm using Nancy 2.0.0 with ASP.Net Core 2.0.0 and I can't get my application to return static content (in this case, a zip file) from a route defined in a Nancy module.
The Nancy convention is to store static content in /Content and the ASP.Net Core convention is to store it in /wwwroot, but I can't get my app to recognize either.
My Startup.Configure method looks like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStaticFiles();
app.UseOwin(b => b.UseNancy());
}
And my module route looks like this:
Get("/big_file", _ => {
return Response.AsFile("wwwroot/test.zip");
});
But Nancy always returns a 404 when I hit this route. I've also tried directing ASP.Net Core to the static directory expected by Nancy, like this:
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), #"Content")),
RequestPath = new PathString("/Content")
});
But this didn't work either. I've tried putting the file in /Content and in /wwwroot with the same result. And I've tried different casing of Content, but nothing seems to work. What am I missing?
I figured it out. The issue was that I needed to let Nancy know what I wanted to use as the root path for the application. I did this by creating a class that inherits from IRootPathProvider. Nancy will discover any class that inherits from this on Startup, so you can put it wherever you want.
public class DemoRootPathProvider : IRootPathProvider
{
public string GetRootPath()
{
return Directory.GetCurrentDirectory();
}
}
Once this was in place I was able to access static content in /Content. Additionally, I was able to add additional static directories (for example, if I wanted to stick with /wwwroot) by adding a class that inherits from DefaultNancyBootstrapper. Again, Nancy will find this on Startup, so you can put it anywhere.
public class DemoBootstrapper : DefaultNancyBootstrapper
{
protected override void ConfigureConventions(NancyConventions conventions)
{
base.ConfigureConventions(conventions);
conventions.StaticContentsConventions.Add(
StaticContentConventionBuilder.AddDirectory("wwwroot")
);
}
}

Categories

Resources