【问题标题】:Override a resource from standard assembly in ASP.NET覆盖 ASP.NET 中标准程序集的资源
【发布时间】:2011-02-02 00:53:14
【问题描述】:

我想为 ASP.NET 项目覆盖来自 System.ComponentModel.DataAnnotations 的字符串。我是否需要制作卫星组件、搞乱自定义构建任务、al.exe 等?即使是,我也找不到如何将.resx 转换为.resources 以将其提供给al.exe。如果没有,.resx. 应该放在哪里以及如何命名?

UPD:为了清楚起见:我想使用自定义资源字符串,而不是来自程序集的默认资源的字符串。我不想在每个使用该字符串的地方进行更改。毕竟,资源的存在只是为了覆盖它们。

【问题讨论】:

    标签: asp.net localization satellite-assembly


    【解决方案1】:

    假设您要覆盖验证属性中的默认错误消息字符串,您可以通过设置 ErrorMessageResourceNameErrorMessageResourceType 属性来实现,如下所示:

    [Required(ErrorMessageResourceName = "Required_Username", ErrorMessageResourceType = typeof(MyResourceFile)]
    public string Username { get; set; }
    

    您可以创建一个名为 MyResourceFile.resx 的资源文件,其中包含 Required_Username 以及您想要的错误消息。

    希望这会有所帮助。

    【讨论】:

    • 我知道这一点,但我不想更改所有属性。
    【解决方案2】:

    Phil Haack 有一篇出色的文章 Localizing ASP.Net MVC Validation,它专门指导您覆盖字符串。这篇文章更适用于 DataAnnotations,而不是 ASP.net MVC。因此,这将有助于您使用 DataAnnotattions

    下面我列出了在 Visual Studio 中添加本地化资源的最简单步骤。

    1. 打开 Project Properties 对话框。
    2. 选择Resources标签。
    3. 点击创建一个新的默认 资源文件
    4. 这将在您的Properties 文件夹中创建两个文件。
      • Resources.resx
      • Resources.Designer.cs
    5. Resources.resx 有 打开,改成Access Modifier Public
    6. 添加您的字符串。

    要为特定文化添加额外的资源文件,您需要这样做。

    1. 右击你的Project Solution Explorer.
    2. 选择添加 -> 新建项目 -> 资源 文件。
    3. 将其命名为 Resources.en-us.resx。 (将“en-us”替换为适当的 代码)
    4. 点击添加
    5. 将其拖到Properties 文件夹中。
    6. 打开 Resources.en-us.resx 并将其更改为 Access Modifier Public
    7. 添加您的字符串。
    8. 为您需要的每种文化重复 支持。

    在构建过程中,VS 会将 .resx 文件转换为 .resource 文件并为您构建包装类。然后您可以通过命名空间 YourAssembly.Properties.Resources 访问。

    使用这个 using 语句。

    using YourAssembly.Properties;
    

    你可以用这样的属性来装饰:

    [Required(ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = "MyStringName")]
    

    注意:为了保持一致性,我使用了 Properties 文件夹。要使用 App_GlobalResources 将您的 .resx 文件移到那里并更改您的 using 语句以匹配目录名称。像这样:

    using YourAssembly.App_GlobalResources;
    

    编辑:最接近强类型资源名称的方法是执行以下操作:

    public class ResourceNames
    {
        public const string EmailRequired = "EmailRequired";
    }
    

    然后你可以用这样的属性来装饰。

    [Required(ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = ResourceNames.EmailRequired)]
    

    要启用自动客户端文化检测,请将​​ globalizationsection 添加到 web.config 文件中。

    <configuration>
        <system.web>
            <globalization enableClientBasedCulture="true" culture="auto:en-us" uiCulture="auto:en-us"/>
        </system.web>
    <configuration>
    

    这里我启用了基于客户端的文化并将 cultureuiculture 设置为“auto”,默认为“zh-cn”。


    创建单独的附属程序集:

    MSDN Creating Satellite Assemblies 文章也会有所帮助。 如果您不熟悉附属程序集,请务必阅读 Packaging and Deploying Resources

    在过去创建附属程序集时,我发现使用 VS 构建事件很有用。这些是我将采取的步骤。

    1. 在我的解决方案中创建一个单独的 Class Library 项目。
    2. 创建或添加我的 .resx 文件到这个项目。
    3. Post-Build Event 添加到 Project Properties 对话框。 (如下图所示)

    示例 VS 构建后脚本:

    set RESGEN="C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\resgen.exe"
    set LINKER="C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\al.exe"
    set ASSEMBLY=$(TargetName)
    set SOURCEDIR=$(ProjectDir)
    Set OUTDIR=$(TargetDir)
    
    REM Build Default Culture Resources (en)
    %RESGEN% %SOURCEDIR%en\%ASSEMBLY%.en.resx  %SOURCEDIR%en\%ASSEMBLY%.resources
    
    REM Embed Default Culture
    %LINKER% /t:lib /embed:%SOURCEDIR%en\%ASSEMBLY%.resources /culture:en /out:%OUTDIR%%ASSEMBLY%.resources.dll
    REM Embed English Culture
    IF NOT EXIST %OUTDIR%en\ MKDIR $%OUTDIR%en\
    %LINKER% /t:lib /embed:%SOURCEDIR%en\%ASSEMBLY%.resources /culture:en /out:%OUTDIR%en\%ASSEMBLY%.resources.dll
    
    
    REM These are just a byproduct of using the project build event to run the resource build script
    IF EXIST %OUTDIR%%ASSEMBLY%.dll DEL %OUTDIR%%ASSEMBLY%.dll
    IF EXIST %OUTDIR%%ASSEMBLY%.pdb DEL %OUTDIR%%ASSEMBLY%.pdb
    

    如果您不想使用 ResGen.exe 来转换您的 .resx 文件,您可以这样做。

    using System;
    using System.Collections;
    using System.IO;
    using System.Resources;
    
    namespace ResXConverter
    {
        public class ResxToResource
        {
            public void Convert(string resxPath, string resourcePath)
            {
                using (ResXResourceReader resxReader = new ResXResourceReader(resxPath))
                using (IResourceWriter resWriter = new ResourceWriter(
                        new FileStream(resourcePath, FileMode.Create, FileAccess.Write)))
                {
                    foreach (DictionaryEntry entry in resxReader)
                    {
                        resWriter.AddResource(entry.Key.ToString(), entry.Value);
                    }
                    resWriter.Generate();
                    resWriter.Close();
                }
            }
        }
    }
    

    以这种方式进行转换的一个潜在缺点是需要引用System.Windows.Forms.dll。您仍然需要使用 Assembly Linker

    编辑:正如 wRAR 提醒我们的那样,如果您正在签署您的程序集,您的密钥 must match

    【讨论】:

    • 我已经阅读了所有这些链接。第一个链接建议将资源名称明确地放在属性中,这不是我想要的。至于附属程序集,我仍然无法理解(甚至在 MSDN 中找到相关位置)程序集、命名空间和资源应该如何命名以由运行时加载以及是否存在附加要求。 System.ComponentModel.DataAnnotations.resources.dll 包含 System.ComponentModel.DataAnnotations.resources 或 System.ComponentModel.DataAnnotations.Resources.DataAnnotationsResources.resources 显然没有加载。
    • @wRAR:在我们讨论命名约定之前,您是想在运行时指定显示什么样的消息,还是不想在属性中包含字符串?
    • 我只想本地化标准字符串。
    • @wRAR:我刚刚有机会将 VS 部分添加到答案中。如果这能解决您的问题,请告诉我。
    • 好的,我终于找到了。 msdn.microsoft.com/en-us/library/ff8dk041.aspx(所以答案是“不可能”)。
    【解决方案3】:

    虽然这很奇怪,尤其是对于熟悉开源本地化技术的人来说,但不能为任何系统组件构建卫星组件,甚至不能由第三方签署:

    If your main assembly uses strong naming, satellite assemblies must be signed with the same private key as the main assembly. If the public/private key pair does not match between the main and satellite assemblies, your resources will not be loaded.

    虽然我对此表示怀疑,但是否可以自动进行相同的操作,但没有卫星组件,这是未知的。

    【讨论】:

      【解决方案4】:

      如果服务器没有安装 .NET 语言包,那么无论 CurrentUICulture 设置为什么,您将始终在 DataAnnotations 验证消息中看到英语。这个史诗般的 hack 对我们有用。

      • 转到“Microsoft .NET Framework 4.6.1 语言包”下载页面https://www.microsoft.com/en-us/download/details.aspx?id=49977
      • 选择语言并下载
      • 使用 7-Zip 解压 NDP461-KB3102436-x86-x64-AllOS-{LANG}.exe
      • 使用 7-Zip 解压 CAB 文件 x64-Windows10.0-KB3102502-x64.cab
      • 找到“msil_system.componentmod..notations.resources_....”
      • ...您将在其中找到“system.componentmodel.dataannotations.resources.dll”
      • 用ILSpy打开.resources.dll,找到Resources,点击String Table上方的Save按钮保存为System.ComponentModel.DataAnnotations.Resources.DataAnnotationsResources.{LANGUAGE}.resources
      • 在“资源”下添加到您的项目中
      • 确保资源文件的文件构建操作属性设置为“嵌入式资源”

      然后在项目的 PreStart 方法中,用项目中的字段覆盖 System.ComponentModel.DataAnnotations.Resources.DataAnnotationsResources.resourceMan 私有静态字段(告诉你这是一个 hack)。

      using System;
      using System.Linq;
      using System.Reflection;
      using System.Resources;
      
      [assembly: WebActivator.PreApplicationStartMethod(typeof(ResourceManagerUtil), nameof(ResourceManagerUtil.PreStart))]
      
      class ResourceManagerUtil
      {
          public static void PreStart()
          {
              initDataAnnotationsResourceManager();
          }
      
          /// <summary>
          /// If the server doesn't have .NET language packs installed then no matter what CurrentUICulture is set to, you'll always get English in 
          /// DataAnnotations validation messages. Here we override DataAnnotationsResources to use a ResourceManager that uses language .resources 
          /// files embedded in this assembly.
          /// </summary>
          static void initDataAnnotationsResourceManager()
          {
              var embeddedResourceNamespace = "<YourProjectDefaultNamespace>.<FolderYouSavedResourcesFilesIn>";
              var dataAnnotationsResourcesName = "System.ComponentModel.DataAnnotations.Resources.DataAnnotationsResources";
              var thisAssembly = typeof(ResourceManagerUtil).Assembly;
              var dataAnnotationsAssembly = typeof(System.ComponentModel.DataAnnotations.ValidationAttribute).Assembly;
      
              var resourceManager = new ResourceManager(embeddedResourceNamespace + "." + dataAnnotationsResourcesName, thisAssembly);
      
              // Set internal field `DataAnnotationsResources.resourceMan`
              var dataAnnotationsResourcesType = dataAnnotationsAssembly.GetType(dataAnnotationsResourcesName);
              var resmanProp = dataAnnotationsResourcesType.GetField("resourceMan", BindingFlags.NonPublic | BindingFlags.Static);
              resmanProp.SetValue(null, resourceManager);
          }
      }
      

      【讨论】:

        【解决方案5】:

        我想提供与 Duncan Smart 相同想法的答案,但针对 .NET Core 2.2 而不是 .NET Framework 4.x。

        在这里。

        using System;
        using System.Linq;
        using System.Reflection;
        using System.Resources;
        
        public static class ResourceManagerHack
        {
            /// <summary>
            /// If the server doesn't have .NET language packs installed then no matter what CurrentUICulture is set to, you'll always get English in 
            /// DataAnnotations validation messages. Here we override DataAnnotationsResources to use a ResourceManager that uses language .resources 
            /// files embedded in this assembly.
            /// </summary>
            public static void OverrideComponentModelAnnotationsResourceManager()
            {
                EnsureAssemblyIsLoaded();
        
                FieldInfo resourceManagerFieldInfo = GetResourceManagerFieldInfo();
                ResourceManager resourceManager = GetNewResourceManager();
                resourceManagerFieldInfo.SetValue(null, resourceManager);
            }
        
            private static FieldInfo GetResourceManagerFieldInfo()
            {
                var srAssembly = AppDomain.CurrentDomain
                                          .GetAssemblies()
                                          .First(assembly => assembly.FullName.StartsWith("System.ComponentModel.Annotations,"));
                var srType = srAssembly.GetType("System.SR");
                return srType.GetField("s_resourceManager", BindingFlags.Static | BindingFlags.NonPublic);
            }
            internal static ResourceManager GetNewResourceManager()
            {
                return new ResourceManager($"{typeof(<YourResource>).Namespace}.Strings", typeof(<YourResource>).Assembly);
            }
            private static void EnsureAssemblyIsLoaded()
            {
                var _ = typeof(System.ComponentModel.DataAnnotations.RequiredAttribute);
            }
        }
        

        我这样称呼它:

        public static void Main(string[] args)
        {
            ResourceManagerHack.OverrideComponentModelAnnotationsResourceManager();
            CreateWebHostBuilder(args).Build().Run();
        }
        

        此外,我创建了一个~/Resources/&lt;YourResource&gt;.resx 文件并用the default values 填充它并随意更改它们。最后我创建了一个公共空类&lt;YourResource&gt;

        【讨论】:

          猜你喜欢
          • 2011-08-27
          • 1970-01-01
          • 2020-11-11
          • 1970-01-01
          • 1970-01-01
          • 2013-10-07
          • 1970-01-01
          • 1970-01-01
          • 2012-04-20
          相关资源
          最近更新 更多