I am new to asp.net core localization, and trying to use resource files. There are multiple ways of doing it, so I started with IStringLocalizer and IHtmlLocalizer.
We can specify the type while injecting the Localizer into the view, and most of the tutorial recommend to create an Empty SharedResource class file with root namespace.
I tried to find the reason behind it but didn't find, Could anyone please help me out about the reason of having the empty SharedResource class?
#inject IHtmlLocalizer<SharedResources> Localizer
namespace Root.Namespace
{
public class SharedResources
{
}
}
empty SharedResource class is use to group your resource file in Visual studio like this
Also it need for IStringLocalizeFactory
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddDataAnnotationsLocalization(options =>
{
options.DataAnnotationLocalizerProvider = (type, factory) =>
{
var assemblyName = new AssemblyName(typeof(SharedResource).GetTypeInfo().Assembly.FullName);
return factory.Create("SharedResource", assemblyName.Name);
};
});
You can read my blog and source code here to understand it better
Related
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.
W'm working on a migration project. I need to use my appsettings in other class libraries. so after googling and stackoverflowing, I load my appsettings.json inside static class as follows:
public static class ReadAppConfig
{
private static readonly IConfiguration Root;
private static readonly ConfigurationBuilder ConfigurationBuilder;
static ReadAppConfig()
{
if (ConfigurationBuilder == null)
{
ConfigurationBuilder = new ConfigurationBuilder();
ConfigurationBuilder.SetBasePath(Directory.GetCurrentDirectory());
ConfigurationBuilder.AddJsonFile("appsettings.json", optional: true);
ConfigurationBuilder.AddJsonFile("appsettings.QA.json", optional: true);
ConfigurationBuilder.AddJsonFile("appsettings.Dev.json", optional: true);
ConfigurationBuilder.AddJsonFile("appsettings.Staging.json", optional: true);
if (Root == null)
Root = ConfigurationBuilder.Build();
}
}
public static string UserManualFile => Root.GetSection("AppSettings:SomeKey").Value;
}
So now I can get UserManualFile like ReadAppConfig.UserManualFile in other libraries.
This works fine. But it always reads from appsettings.Staging.json only. How to make this read based on deploy environment.
I cannot get IHostingEnvironment here as this is static class.
Please assist / suggest me with proper way to do this.
Thanks
There's two problems here. First, don't use a static class. Configuration is designed to be dependency injected and dependency injection is fundamentally incompatible with statics. In truth, statics are almost always the wrong approach, dependency injection or not. Second, libraries should depend only on abstractions, not concrete data/implementations.
Honestly, there's three problems and the last one is the killer here: you need IHostingEnvironment for your use case, and there's absolutely know way to get that in a static class. Game over.
There's multiple ways you could go here, but I'm going to be opinionated with what I feel is the best option. Ultimately, your libraries just need UserManualFile, it seems. As such, that is all they should depend on: a string that corresponds to the location of a user manual, presumably. So, you'll do something like:
public class SomeLibraryClass
{
private readonly string _userManualFie;
public SomeLibraryClass(string userManualFile)
{
_userManualFile = userManualFile;
}
}
This requires the least amount of knowledge and provides the greatest amount of abstraction for your library. It no longer cares where or how it gets the file location, just that it gets it.
Then, in your actual app, you'll use strongly-typed config to provide this value:
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
...
services.AddScoped(p =>
{
var appSettings = p.GetRequiredService<IOptions<AppSettings>>();
return new SomeLibraryClass(appSettings.Value.UserManualFile);
});
Done. Now, if there's actually other stuff the library needs, you might choose to pass a custom "settings" class to the library. This class should come from the library, so that it documents what it needs. For example, in your library, you'd create a class like:
public class SomeLibrarySettings
{
public string Foo { get; set; }
public string Bar { get; set; }
// etc.
}
Then, your library class(es) would inject this:
public SomeLibraryClass(SomeLibrarySettings settings)
Finally, in your app, you can either manually compose this settings class instance or inject it. Injecting it will still require you to manually compose it, so it only makes sense to do it that way if you're going to share it between multiple classes.
Manually compose
services.AddScoped(p =>
{
var appSettings = p.GetRequiredService<IOptions<AppSettings>>();
var someLibrarySettings = new SomeLibrarySettings
{
Foo = appSettings.Value.Foo,
Bar = appSettings.Value.Bar,
// etc.
};
return SomeLibraryClass(someLibrarySettings);
});
Inject
services.AddSingleton(p =>
{
var appSettings = p.GetRequiredService<IOptions<AppSettings>>();
return new SomeLibrarySettings
{
Foo = appSettings.Value.Foo,
Bar = appSettings.Value.Bar,
// etc.
};
});
services.AddScoped<SomeLibraryClass1>();
services.AddScoped<SomeLibraryClass2>();
// etc.
Because SomeLibrarySettings is registered in the service collection, it will be automatically injected into the library classes that depend on it.
Finally, it's worth noting that because you're moving the configuration logic to where it actually belongs, you no longer need to even worry about the environment. ASP.NET Core is already set up to load the appropriate environment settings, so it just works.
I updated my project from 1.0.0-rc1-final to 1.0.0-rc2-final which is called ASP.NET Core 2 now. This is how I initialize the configuration builder:
var builder = new ConfigurationBuilder().SetBasePath(Environment.GetEnvironmentVariable("ASPNETCORE_CONTENTROOT")).AddJsonFile(file).AddEnvironmentVariables();
IConfiguration configuration = builder.Build();
I know for sure that the initialization is ok because I can do
configuration.AsEnumerable()
in the debugger and see all the values in the configuration files in there.
However, if I try to get a whole configuration section like this
configuration.GetSection(section.Name);
it doesn't work. It returns an object no matter what I pass to GetSection. However, the Value field of this object is always null, regardless whether the section exists or not.
Note that this used to work perfectly fine before.
Any clues?
It turns out that one can no longer do something like:
var allSettingsInSection = configuration.Get(typeof(StronglyTypedConfigSection), sectionName);
Instead, it has to be done like this now:
IConfigurationSection sectionData = configuration.GetSection(sectionName);
var section = new StronglyTypedConfigSection();
sectionData.Bind(section);
Note that it's necessary to include Microsoft.Extensions.Configuration.Binder in project.json.
Just a cleaner version of the accepted answer:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MySettings>(options => Configuration.GetSection("MySettings").Bind(options));
}
Source
In dot net core 2.1 you can do this:
I used nameof here to get the name of the class as a string, rather than use an actual string. This is based on Uwe Kleins reply, it's cleaner.
var myConfigClass = Configuration.GetSection(nameof(MyConfigClass)).Get<MyConfigClass>();
Easily inject your strongly typed configuration as follows:
services.Configure<MyConfigClass>(myConfigClass);
I am using the GetSection allot and thus I have created an extension method to help me get sections using generics
public static class ConfigurationExtensions
{
public static T GetConfig<T>(this IConfiguration config) where T : new()
{
var settings = new T();
config.Bind(settings);
return settings;
}
public static T GetConfig<T>(this IConfiguration config, string section) where T : new()
{
var settings = new T();
config.GetSection(section).Bind(settings);
return settings;
}
}
I want to get a link to image resource in a MVC view that is part of an Orchard module.
Googling a bit resulted in the following approach:
https://stackoverflow.com/a/9256515/3936440
and its using #Html.ResourceUrl() in views to get the resource URL.
I wonder where the ResourceUrl() comes from as its not documented in MSDN and i cannot use it in my projects either.
Did someone used that approach already and can shed some light on whats missing here?
Update:
I figured it out. The following code works in connection with Orchard modules.
First you need to add a resource manifest to the Orchard module like so
public class ResourceManifest : Orchard.UI.Resources.IResourceManifestProvider
{
public void BuildManifests(Orchard.UI.Resources.ResourceManifestBuilder aBuilder)
{
Orchard.UI.Resources.ResourceManifest lManifest = aBuilder.Add();
string lModulePath = "~/Modules/YourModuleName";
lManifest.DefineResource("ProfilePicture", "User1").SetUrl(lModulePath + "/Images/User1.png");
}
}
Then you extend the Html object:
// This class adds so called "extension methods" to class System.Web.Mvc.HtmlHelper
public static class HtmlHelperExtensions
{
// This method retrieves the URL of a resource defined in ResourceManifest.cs via the Orchard resource management system
public static string ResourceUrl(this System.Web.Mvc.HtmlHelper aHtmlHelper, string aResourceType, string aResourceName)
{
// note:
// resolving Orchard.UI.Resources.IResourceManager via work context of orchard because
// - calling System.Web.Mvc.DependencyResolver.Current.GetService() does not work as it always returns null at this point
// - constructor parameter injection is not allowed in static classes
// - setting the resource manager from another class that uses constructor parameter injection does not work as it causes a "circular component dependency "
Orchard.WorkContext lWorkContext = Orchard.Mvc.Html.HtmlHelperExtensions.GetWorkContext(aHtmlHelper);
Orchard.UI.Resources.IResourceManager lResourceManager = (Orchard.UI.Resources.IResourceManager)lWorkContext.Resolve<Orchard.UI.Resources.IResourceManager>();
if (lResourceManager != null)
{
Orchard.UI.Resources.RequireSettings lSettings = new Orchard.UI.Resources.RequireSettings { Type = aResourceType, Name = aResourceName, BasePath = aResourceType };
Orchard.UI.Resources.ResourceDefinition lResource = lResourceManager.FindResource(lSettings);
if (lResource != null)
{
Orchard.UI.Resources.ResourceRequiredContext lContext = new Orchard.UI.Resources.ResourceRequiredContext { Resource = lResource, Settings = lSettings };
string lAppBasePath = System.Web.HttpRuntime.AppDomainAppVirtualPath;
return lContext.GetResourceUrl(lSettings, lAppBasePath);
}
}
return null;
}
}
and then you can write:
<img src="#Html.ResourceUrl("ProfilePicture", "User1")" />
in an Orchard module view to get appropriate image link for User1.
I hope this helps.
ResourceUrl() is a custom HtmlHelper extension.
The code that you need to implement it is included in the answer you have linked.
You simply need to create a static class that contains the method code.
Asp.net article on how to create custom html helpers
PS: Make sure you import your namespace into the view with #using YourNamespace or add it to the System.Web.Mvc.HtmlHelper class.
I've converted my project from .Net framework 4 to 4.5.
I have done this to make use of the Nuget package MvcMailer.
All is good except in the UserMailer class the following code exists:
public virtual MvcMailMessage Welcome()
{
//ViewBag.Data = someObject;
return Populate(x =>
{
x.Subject = "Welcome";
x.ViewName = "Welcome";
x.To.Add("some-email#example.com");
});
}
The Populate word throws an error:
The name 'Populate' does not exist in the current context
To what Namespace does the word Populate belong to?
Or is it an extension?
I can't find anything on the net.
It's a class method of MailerBase controller with this signature (from source code on GitHub):
public virtual MvcMailMessage Populate(Action<MvcMailMessage> action)
To use it you must derive your controller from MailerBase (it's the base class for Mailers. Your mailer should subclass MailerBase).
For example, supposing your controller is named Home, from:
public class Home : Controller {
To:
public class Home : MailerBase {
It's in Mvc.Mailer namespace (same of MvcMailerMessage class) anyway it's not an extension method so you don't even need to worry about it.
Put the cursor on the keyword and hit Alt+Shift+F10. It will show you it's source, and you'll be able to include the whole namespace, or use the keyword just once. It will only work if you have a correct .dll reference in your project.
I think you got the code from this GitHub repository.
https://github.com/smsohan/MvcMailer/wiki/MvcMailer-Step-by-Step-Guide
public virtual MvcMailMessage Welcome()
{
ViewBag.Name = "Sohan";
return Populate(x =>{
x.viewName = "Welcome";
x.To.Add("sohan39#example.com");
});
}
This code was provided by the Developer of the Package and he showed how to use it to edit the MvcMailer.
If so, the guy there used these namespaces in the top of his C# file.
using Mvc.Mailer;
So, I guess it would be a part of this Namespace. Include it to your project and you're done!