如何在asp.net core中获取.resx文件的字符串

43

如何在ASP.NET Core中获取Resx文件的字符串? 在MVC中,我使用ResxResourceReader获取字符串。但是在ASP.NET Core中我无法做到相同。


2
我想以键值对的形式访问 resx 文件中的条目。在 dotnet core 中如何实现? - Ilakkiya.B
3个回答

59

.NET Core在资源文件方面的改变我认为有些不足和令人困惑(花了我几天时间才弄明白),但这是你需要做的:

  1. 将以下代码添加到Startup.cs中 - 注意:更改您支持的语言和“Resources”的ResourcePath只是您稍后存储.resx文件的文件夹。

JustAMartin在评论中所说:如果您计划将您的SharedResource文件放在Resources文件夹中并将其命名空间设置为以Resources结尾,则不要设置o.ResourcesPath = "Resources",只需使用services.AddLocalization(),否则它将开始搜索Resources.Resources文件夹,但此文件夹不存在。

        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. 在任何你想要存储resx文件的项目中创建一个文件夹 - 默认情况下,称之为"Resources".

  2. 使用特定的区域设置和稍后要查找的文件名创建一个新的resx文件:如果你有一个共享的resx文件,你可以这样做:SharedResource.en-US.resx。然后关闭自动生成代码,因为现在它没用了。

  3. 在与resx文件相同的位置创建一个名为"SharedResource"的类。它可以是空的,只需要存在,以便稍后引用它。

  4. 无论在哪里使用你的资源,在这个例子中使用IoC注入IStringLocalizer< SharedResource >并命名为"_localizer"或其他名称。

  5. 最后,你可以通过_localizer["My_Resource_Name"]引用Resource文件中的条目。

  6. 通过在同一文件夹中创建名为"SharedResource.de-DE.resx"(或其他名称)的新的resx文件来添加另一种语言。

"Resource"文件夹将被用于查找所有程序集。因此,这个文件夹可能会变得非常混乱,特别是如果你开始在这里添加特定于视图的内容。

我理解开发人员的初衷,但他们放弃了过多的内容。人们可以编写并添加翻译的内容,而不必实际翻译任何东西。他们让开发人员更容易从一开始就考虑翻译,但最终却让实际使用翻译的开发人员工作量增加了很多。现在我们不能自动生成任何东西。我们必须IoC注入引用以访问它们(除非你想要使用ServiceLocater反模式中的静态方法)。所有名称都是硬编码字符串,因此现在如果你拼错了一个翻译,它会直接返回你给它的字符串,从而打败了首次具有翻译目的的初衷,并且可能需要在这个上面包装一层,以便你不会过度依赖常量。

说实话,我无法相信有人认为这是一个好主意。为什么要为那些不关心翻译的开发人员努力呢?

最终,我创建了一个基于这种风格的包装器。唯一好的一点是,如果你决定从数据库获取资源,以上代码不需要做任何更改,但现在你必须添加资源条目、将其添加到接口中,然后实现它以将其重新提取。我使用nameof(),所以我不需要使用常量,但这仍然很脆弱,因为如果属性名称或resx文件名更改,它将打破翻译而没有任何崩溃——我可能需要一个集成测试来确保我不会得到相同的值。

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];
}

2
我相信你仍然可以使用ResXFileCodeGenerator生成编译时属性(例如在此处看到:https://christianspecht.de/2017/11/29/fixing-resourcedesignercs-generation-in-net-core/)。 - Dejan
1
我找到了一个解决方法。我使用了一个“BaseController”和两个resx文件:“Controllers.BaseController.resx”和“Controllers.BaseController.fr.resx”。这个类被我的“真正”的控制器继承,并且我传递了一个“Text”变量,其中包含了翻译后的字符串的“IStringLocalizer”对象到模型中(同样,我也为此使用了一个“BaseModel”)。这很丑陋,但这是我想要的最接近的方式。我还使用了一个常量类来避免处理字符串键,我讨厌那个,我们总是会打错字。也许有一天我会写一篇博客文章来以适当的方式解释这个。 - Aelys
3
看起来你需要在ASP.NET Core 2中调用services.AddLocalization();而不是之前的方法。我知道这对你来说可能有些晚了,但这可能会帮助其他人 :) - ProgrammingLlama
它甚至可以帮助我,因为我仍然在使用我的“丑陋”解决方法!谢谢 :) - Aelys
1
要注意两个问题:1)IStringLocalizer不喜欢短的文化名称。尽管当前线程文化和资源名称为“lv”,但它并没有起作用。当我将它们全部重命名为“lv-LV”时,它开始工作了。2)如果您将SharedResource文件放在Resources文件夹中,并将其命名空间设置为以“Resources”结尾,则不要设置“o.ResourcesPath =“ Resources””,因为它会开始查找“Resources.Resources”文件夹,而这个文件夹不存在。因此,要么是命名空间,要么是ResourcesPath,但不能同时存在。 - JustAMartin
显示剩余6条评论

7

旧的做法(比如在asp.net中)是创建一个默认资源文件,例如MyResources.resx,以及其他针对不同文化的文件,例如MyResources.fr.resx等,并通过MyResources.MyValue1从中检索值。创建MyResources.resx将生成一个包含所有资源值作为静态属性的.cs文件。

.Net Core建议使用 IStringLocalizer<T>,其中T是由您创建的与资源文件名称匹配的类。您可以开始开发而不需要任何资源文件,稍后再添加它们。您必须在控制器上注入 (IStringLocalizer< MyResources > localizer),然后使用 _localizer["MyValue1"]获取值。

您可以查看 .net core 官方文档这里


6
为什么他们要这样做?现在我们必须使用会导致运行时崩溃的字符串...... - Daniel Lorenz
3
@DanielLorenz: 我同意你的观点;我更喜欢设计师的方法(在 .Net Core 中仍然可用)。静态定义每个资源字符串所付出的代价是,您的资源类与您的应用程序紧密耦合。这种代价与微软试图避免所有依赖关系、支持模块化、可注入组件的努力不相容。对于第三方库,避免耦合具有重要的优点。对于特定于应用程序的本地化,您的翻译与您的应用程序天生就是耦合的,因此紧密耦合几乎没有缺点。 - Brian
@DanielLorenz:不,那是行不通的。从根本上说,任何提供生成属性智能提示/类型安全性的方法都不能用于正确的依赖注入。如果您注入了一个泛型接口,就会失去智能提示/类型安全性(这正是您和我不喜欢Microsoft方法的具体原因)。如果您注入一个实现(在语法上是合法的),那么您可能根本不使用依赖注入,因为您只是将自己绑定到特定的实现。 - Brian
@Brian 微软可以更改resx设计器,不仅生成包含所有项的类,还可以生成一个实现resx文件中所有属性以及本地化程序部分的接口。如果他们自动生成了这个,我们就可以简单地IoC注入生成的IMyResources类,然后直接在那里使用属性。如果有人更改了某些东西,该方法将从接口中移除,因此它不再能进行编译。这就是我想说的。 :) - Daniel Lorenz
1
我对微软的实现有问题,因为它对需要国际化的人来说更糟糕,而对不需要国际化的人则没有用处。即使对字符串进行轻微的更改也会导致翻译丢失。使用紧密耦合的资源文件要好得多。 - Stephen
显示剩余8条评论

5

如果需要更直接的替换,我已经创建了ResXResourceReader.NetStandard,它重新封装了ResXResourceReaderResXResourceWriter用于 .NET Standard(这也适用于 .NET Core)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接