Using IStringLocalizer using resources files in WebApi - c#

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:

Related

Get name by translated string from resx file

I want to get the name of a translated string by its value from RESX file in .NET Core 3.1.
I Know the below code is answer but how do I access to RESX file correctly?
System.Resources.ResourceManager rm =
new System.Resources.ResourceManager("Resources.Views.Shared._Layout.de.resx", this.GetType().Assembly);
return await Task.Run(() => rm.GetResourceSet(System.Threading.Thread.CurrentThread.CurrentCulture, true, true)
.OfType<DictionaryEntry>()
.FirstOrDefault(e => e.Value.ToString() == value).Key.ToString());
My resource files are scaffolded as below.
And the error message is as below.
What is the right addressing to the RESX files in this case?
If you will look at the documentation, you will see that ASP.NET Core has introduced IStringLocalizer and IStringLocalizer.
Under the cover, IStringLocalizer use ResourceManager and ResourceReader. Basic usage from the documentation:
public class StringLocalizerController : Controller
{
private readonly IStringLocalizer localizer;
public StringLocalizerController(IStringLocalizerFactory factory)
{
var assemblyName = new AssemblyName(this.GetType().Assembly.FullName);
localizer = factory.Create("SharedResource", assemblyName.Name);
}
public IActionResult SharedResource()
{
return Content(localizer["AAA"]);
}
}
Start up:
services.AddLocalization(options => options.ResourcesPath = "Resources/View/Shared");
Result:

How to explicitly define API Controllers Path for Swagger documentation in Asp.Net Core MVC Project

I am developing a Asp.Net core 3.1 MVC web app with the web API project inside it.
Now I want to configure Swagger Documentation for the API project only, So how can I specify in the configurations to use only the web API controllers for documentation?
Configuration for swagger in startup class inside ConfigureServices method is as follows:-
services.AddSwaggerGen(option =>
{
option.SwaggerDoc("v1.0",
new OpenApiInfo
{
Title = "ProjName OpenApi",
Version = "1.0",
//Description = //get from appsettings.json
});
var xmlCommentFileName = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlCommentFilePath = Path.Combine(AppContext.BaseDirectory, xmlCommentFileName);
option.IncludeXmlComments(xmlCommentFilePath);
});
and the configuration in Configure method is as follows:-
app.UseSwagger(option =>
{
option.RouteTemplate = "docs/{documentname}/swagger.json";
});
app.UseSwaggerUI(option =>
{
option.SwaggerEndpoint("/docs/v1.0/swagger.json", "ProjName OpenApi v1.0");
option.RoutePrefix = "docs/v1.0";
option.DocumentTitle = "ProjName OpenAPI Docs";
});
The issue is that the swagger gen is looking in controllers folder, Admin & Identity Areas for generating docs, but I would rather like to configure it to Use only the controllers in WebApi Folder.
All the Controllers or Action methods which has been specified Route Attribute in those controllers also gets listed in the API Docs. How can I exclude those?
Can some one please help me out with this? I'm really stuck here.
PS: I would like to mention that I can not move the API layer into its separate project.
According to you description, I suggest you could try to create a custom filter to check if the controller name is mvc controller and then remove its route.
More details, you could refer to below codes:
Startup.cs ConfigureServices method:
services.AddSwaggerGen(option =>
{
option.SwaggerDoc("v1.0",
new OpenApiInfo
{
Title = "ProjName OpenApi",
Version = "1.0"});
option.DocumentFilter<HideInDocsFilter>();
});
HideInDocsFilter
public class HideInDocsFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
foreach (var apiDescription in context.ApiDescriptions)
{
// replace the data to your controller name
if (apiDescription.ActionDescriptor.DisplayName.Contains("Data"))
{
var route = "/" + apiDescription.RelativePath.TrimEnd('/');
swaggerDoc.Paths.Remove(route);
}
}
}
}
Result:
Only contains WeatherForecast controller method

How to solve "Task could not find al.exe using the SdkToolPath" error while creating .NET Core API?

i want to add a resource file to my application. The problem is when i'm trying to build my solution, i get an error MSB3086 with the description "Task could not find "al.exe" using the SdkTollsPath "" or registry key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\NETFXSDK\4.7.2\WinSDK-NetFx40Tools-x86". Make sure the SdkToolsPath is set and the tool exists in the correct processor specific location under the SdkTollsPath and that the Microsoft Windows SDK is installed." The problem occurs only when i'm trying to name my resource file including culture info as shown below:
If i changed a name to UsersController.resx everything compiles, but i cannot get any value from the resource file. Here is snippet responsible for the configuration from my Startup class:
services.AddLocalization(opts => { opts.ResourcesPath = "Tabler.WebApi/Resources"; }); //ConfigureServices
app.UseRequestLocalization(options => //Configure method
{
IList<CultureInfo> supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
};
options.DefaultRequestCulture = new RequestCulture("en-US");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
Here I inject IStringLocalizer (I cannot retrieve data from it, if i build an application with the "UsersController.resx" file name):
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly ITokenGenerator _tokenGenerator;
private readonly IStringLocalizer<UsersController> _stringLocalizer;
public UsersController(IUserService userService, ITokenGenerator tokenGenerator, IStringLocalizer<UsersController> stringLocalizer) : base()
{
this._userService = userService;
this._tokenGenerator = tokenGenerator;
this._stringLocalizer = stringLocalizer;
}
What am i doing wrong?

Why does not work ASP.NET Core Localization

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 !!

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

Categories

Resources