How to get the .resx file strings in asp.net core

.NET Core changed how resource files work in a way I feel is sub-par and confusing (took me days to figure out), but this is what you need to do:

  1. Add the following code to Startup.cs - Note: Change what languages you support and the ResourcePath of "Resources" will just be the folder you store the .resx files later.

As JustAMartin said in comments: If you are planning to put your SharedResource file inside Resources folder and set its namespace to end with Resources then do not set o.ResourcesPath = "Resources" just use services.AddLocalization(), otherwise it will start looking in Resources.Resources folder, which doesn't exist.

        services.AddLocalization(o => o.ResourcesPath = "Resources");
        services.Configure<RequestLocalizationOptions>(options =>
        {
            var supportedCultures = new[]
            {
                new CultureInfo("en-US"),
                new CultureInfo("en-GB"),
                new CultureInfo("de-DE")
            };
            options.DefaultRequestCulture = new RequestCulture("en-US", "en-US");

            // You must explicitly state which cultures your application supports.
            // These are the cultures the app supports for formatting 
            // numbers, dates, etc.

            options.SupportedCultures = supportedCultures;

            // These are the cultures the app supports for UI strings, 
            // i.e. we have localized resources for.

            options.SupportedUICultures = supportedCultures;
        });
  1. Create a folder in whatever project you want to store the resx files in - default, call it "Resources".

  2. Create a new resx file with the specific culture and the file name you'll look up later: If you had a shared one, you could do: SharedResource.en-US.resx. Then turn off auto-code generation as it is useless now.

  3. Create a class called "SharedResource" in the same location as your resx file. It can be blank, it just needs to be there so you can reference it later.

  4. Wherever you want to use your resource, IoC inject (in this example) IStringLocalizer< SharedResource > with name "_localizer" or something.

  5. Finally, you can reference an entry in the Resource file by doing _localizer["My_Resource_Name"]

  6. Add another language by creating a new resx file named "SharedResource.de-DE.resx" or whatever, in that same folder.

The "Resource" folder will be used across all assemblies to look all of them up. Thus, this folder could end up pretty cluttered, especially if you start getting view specific stuff in here.

I see what the devs were trying to do here, but they gave up too much to get there. People can code and add translation stuff without actually translating anything. They made it easier for devs to have translation in mind from the start, but they end up making it way more work for the devs that actually use translations. Now we can't auto generate anything. We have to IoC inject a reference to the translations in order to access them (no more static unless you want to use the ServiceLocater anti-pattern). All the names are hard-coded strings, so now if you spell a translation wrong it'll just spit back the string you gave it, defeating the purpose of having a translation in the first place, meaning you'll probably need a wrapper around this so you don't rely on constants everywhere.

I can't believe anyone thought this was a good idea, to be honest. Why bend over backwards for devs that don't care about translations, anyway?

I ended up creating a wrapper around this style. The only good thing about this is that if you decide you want to get resources from the database, no code change above will be necessary, but now you have to add the resource entry, add it to the interface, and then implement it to pull it back out again. I used nameof() so I didn't need to use constants, but this is still brittle as if the property name or resx file name changes, it'll break the translation without any sort of crash - I will probably need an integration test to ensure I don't get the same value I send in:

public interface ICommonResource
{
    string ErrorUnexpectedNumberOfRowsSaved { get; }
    string ErrorNoRecordsSaved { get; }
    string ErrorConcurrency { get; }
    string ErrorGeneric { get; }

    string RuleAlreadyInUse { get; }
    string RuleDoesNotExist { get; }
    string RuleInvalid { get; }
    string RuleMaxLength { get; }
    string RuleRequired { get; }
}

public class CommonResource : ICommonResource
{
    private readonly IStringLocalizer<CommonResource> _localizer;

    public CommonResource(IStringLocalizer<CommonResource> localizer) =>
        _localizer = localizer;

    public string ErrorUnexpectedNumberOfRowsSaved => GetString(nameof(ErrorUnexpectedNumberOfRowsSaved));
    public string ErrorNoRecordsSaved => GetString(nameof(ErrorNoRecordsSaved));
    public string ErrorConcurrency => GetString(nameof(ErrorConcurrency));
    public string ErrorGeneric => GetString(nameof(ErrorGeneric));

    public string RuleAlreadyInUse => GetString(nameof(RuleAlreadyInUse));
    public string RuleDoesNotExist => GetString(nameof(RuleDoesNotExist));
    public string RuleInvalid => GetString(nameof(RuleInvalid));
    public string RuleMaxLength => GetString(nameof(RuleMaxLength));
    public string RuleRequired => GetString(nameof(RuleRequired));

    private string GetString(string name) =>
        _localizer[name];
}

For a more direct replacement, I have created ResXResourceReader.NetStandard, which repackages ResXResourceReader and ResXResourceWriter for .NET Standard (which means .NET Core too).


The old way version (like in asp.net) is to create a default resource file like MyResources.resx and other files for different cultures MyResources.fr.resx, ... and retrieve the values from it with MyResources.MyValue1 . Creating MyResources.resx will generate a .cs file with all your resource values as static properties.

.Net Core recommends to work with IStringLocalizer<T> where T is a class created by you that match with the name of your resource files. You can start development without any resource file and add them later. You have to inject (IStringLocalizer< MyResources > localizer) on your controller and than get the value with _localizer["MyValue1"];

You can take a look over the .net core official documentation here