.NET 4.6.2 中的多语言数据注释

Mulitlingual Data Annotation in .NET 4.6.2

我有以下视图模型:

public class LoginViewModel
    {
        [DisplayName("Email Address")]
        [Required(ErrorMessage = "PleaseEnterYourEmail")]
        public string EmailAddress { get; set; }

    }

我将以下资源文件命名为:DataAnnotation.Localization.de-DE.resx,它位于 App_LocalResources 文件夹

具有以下属性:

现在根据 blog post announcing.net 4.6.2 这应该可以正常工作,因为我应该将消息的本地化版本返回到视图。

然而它只是显示:

我检查了我当前的文化并将其设置为:de-DE 因此该应用知道它需要显示的语言。 Target 框架也是 4.6.2。

我在这里遗漏了什么吗?

终于找到了解决这个问题的方法。

在这里,我将我的解决方案分享给遇到同样问题的其他人。

将此 class 添加到您的 asp.net 核心应用程序:

using System;
using Microsoft.Extensions.Localization;

namespace App.Utilities
{
    public static class StringLocalizerFactoryExtensions
    {
        public static IStringLocalizer CreateConventional<T>(this IStringLocalizerFactory factory)
        {
            return factory.CreateConventional(typeof(T));
        }

        public static IStringLocalizer CreateConventional(this IStringLocalizerFactory factory, Type type)
        {
            if (type.Module.ScopeName != "CommonLanguageRuntimeLibrary")
            {
                string[] parts = type.FullName.Split(new[] { type.Assembly.FullName.Split(',')[0] }, StringSplitOptions.None);

                string name = parts[parts.Length - 1].Trim('.');

                return factory.CreateConventional(name);
            }
            else
            {
                return factory.Create(type);
            }
        }

        public static IStringLocalizer CreateConventional(this IStringLocalizerFactory factory, string resourceName)
        {
            return factory.Create(resourceName, null);
        }

        public static IStringLocalizer CreateDataAnnotation(this IStringLocalizerFactory factory)
        {
            if (type.Module.ScopeName != "CommonLanguageRuntimeLibrary")
            {
                return factory.Create("DataAnnotation.Localization", "App_LocalResources");
            }
            else
            {
                return factory.Create(type);
            }
        }
    }
}

... 并在您的 Startup.cs 文件中替换以下部分:

services.AddLocalization(options => options.ResourcesPath = "Resources");

services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();

...使用此代码:

services.AddLocalization(options => options.ResourcesPath = "Resources");

services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
//The following part includes the change:
.AddDataAnnotationsLocalization(options => options.DataAnnotationLocalizerProvider = (type, factory) => factory.CreateConventional(type));

代码将您的视图模型本地化资源视为用于视图或任何其他可以使用默认 IStringLocalizerFactory 的地方。

因此,不再需要 DataAnnotation.Localization.de-DE.resx 资源和 App_LocalResources 文件夹。

  • 只是,Resources 文件夹,这是通过调用 services.AddLocalization(options => options.ResourcesPath = "Resources")) 设置的,您就可以开始了。 TagHelpers 和 HtmlHelpers 将开始工作并翻译错误消息。

  • 另外, 这对 DisplayAttribute.Name 开箱即用。 (v1.1.0-preview1-final + .net v4.6.2)

更新 1: 这是我的 project.json:

{
  "userSecretsId": "...",

  "dependencies": {
    "Microsoft.NETCore.Platforms": "1.1.0-preview1-*",
    "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0-preview1-final",
    "Microsoft.AspNetCore.Diagnostics": "1.1.0-preview1-final",
    "Microsoft.AspNetCore.DataProtection": "1.1.0-preview1-final",
    "Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.1.0-preview1-final",
    "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.1.0-preview1-final",
    "Microsoft.AspNetCore.Mvc": "1.1.0-preview1-final",
    "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview3-final",
    "Microsoft.ApplicationInsights.AspNetCore": "1.0.2",
    "Microsoft.AspNetCore.Mvc.Localization": "1.1.0-preview1-final",
    "Microsoft.AspNetCore.Mvc.Razor": "1.1.0-preview1-final",
    "Microsoft.AspNetCore.Mvc.TagHelpers": "1.1.0-preview1-final",
    "Microsoft.AspNetCore.Mvc.DataAnnotations": "1.1.0-preview1-final",
    "Microsoft.Extensions.Configuration.CommandLine": "1.1.0-preview1-final",
    "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0-preview1-final",
    "Microsoft.AspNet.WebApi.Client": "5.2.3",
    "Microsoft.AspNetCore.Routing": "1.1.0-preview1-final",
    "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0-preview1-final",
    "Microsoft.AspNetCore.Server.Kestrel": "1.1.0-preview1-final",
    "Microsoft.AspNetCore.StaticFiles": "1.1.0-preview1-final",
    "Microsoft.EntityFrameworkCore": "1.1.0-preview1-final",
    "Microsoft.EntityFrameworkCore.SqlServer.Design": "1.1.0-preview1-final",
    "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0-preview1-final",
    "Microsoft.Extensions.Configuration.Json": "1.1.0-preview1-final",
    "Microsoft.Extensions.Configuration.UserSecrets": "1.1.0-preview1-final",
    "Microsoft.Extensions.Logging": "1.1.0-preview1-final",
    "Microsoft.Extensions.Logging.Console": "1.1.0-preview1-final",
    "Microsoft.Extensions.Logging.Debug": "1.1.0-preview1-final",
    "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0-preview1-final",
    "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0",
    "Microsoft.VisualStudio.Web.CodeGeneration.Tools": "1.0.0-preview3-final",
    "Microsoft.VisualStudio.Web.CodeGenerators.Mvc": "1.0.0-preview3-final",
    "Microsoft.AspNetCore.Hosting": "1.1.0-preview1-final",
    "Microsoft.AspNetCore.Hosting.WindowsServices": "1.1.0-preview1-final",
    "Loggr.Extensions.Logging": "1.0.0",
    "Microsoft.EntityFrameworkCore.SqlServer": "1.1.0-preview1-final",
    "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview3-final",
    "BundlerMinifier.Core": "2.2.296"
  },
  "tools": {
    "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview3-final",
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview3-final",
    "Microsoft.EntityFrameworkCore.Tools.DotNet": "1.0.0-preview3-final",
    "Microsoft.Extensions.SecretManager.Tools": "1.0.0-preview3-final",
    "Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
      "version": "1.0.0-preview3-final",
      "imports": [
        "portable-net45+win8"
      ]
    }
  },
  "frameworks": {
    "net462": {}
  },
  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },
  "runtimeOptions": {
    "configProperties": {
      "System.GC.Server": true
    }
  },
  "publishOptions": {
    "include": [
      "wwwroot",
      "**/*.cshtml",
      "appsettings.json",
      "web.config"
    ]
  },
  "scripts": {
    "prepublish": [ "bower install", "dotnet bundle" ],
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  }
}

更新 2: 如果有人希望代码能够像 App_LocalResourses 文件夹中 DataAnnotation.Localization 所承诺的那样工作,我已经更新了 StringLocalizerFactoryExtensions代码。使用更新后的 class 和 Startup.cs class 中的以下代码,它应该可以工作。

services.AddLocalization(options => options.ResourcesPath = "Resources");

services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
//The following part includes the change:
.AddDataAnnotationsLocalization(options => options.DataAnnotationLocalizerProvider = (type, factory) => factory.CreateDataAnnotation());

新的 DataAnnotations 本地化功能,如 blog post announcing .NET Framework 4.6.2 中所述,开箱即用 仅适用于 ASP.NET WebForms

网络表单...

.NET Framework 4.6.2 中的新本地化功能在 class System.Web.ModelBinding.DataAnnotationsModelValidator 中实现,它使用 StringLocalizerProviders.DataAnnotationStringLocalizerProvider.GetLocalizedString 解析本地化字符串。

System.Web.Mvc.DataAnnotationsModelValidator 默认设置为 System.Web.Globalization.ResourceFileStringLocalizerProvider。此提供商尝试在指定网站的 Temporary ASP.NET Files 文件夹中查找“App_LocalResources.root”dll。

但是,有一个预先检查功能是否应该被使用:

private bool UseStringLocalizerProvider {
    get {
        // if developer already uses existing localization feature,
        // then we don't opt in the new localization feature.
        return (!string.IsNullOrEmpty(Attribute.ErrorMessage) &&
            string.IsNullOrEmpty(Attribute.ErrorMessageResourceName) &&
            Attribute.ErrorMessageResourceType == null);
    }
}

以上检查意味着新的本地化功能仅在以下情况下有效:

[Required(ErrorMessage = "FirstName is required")]
public string FirstName { get; set; }

其中设置了 ErrorMessage 值。对于最常见的用例:

[Required]
public string FirstName { get; set; }

它回退到旧名称解析。

在 MVC 5 中功能不起作用,因为...

MVC 5 使用它自己的特定实现,System.Web.Mvc.DataAnnotationsModelValidator。此实现源自 Microsoft.AspNet.Mvc 版本 5.x.x,早于 .NET Framework 4.6.2。它没有实现新的本地化功能。

此外,ASP.NET MVC 和 WebForms 的动态编译输出不同,因此 App_LocalResources 甚至不存在 WebForms 使用的编译资源(例如:de\App_LocalResources.root.q_wjw-ce.resources.dll") ASP.NET MVC 应用程序。

MVC 和 WebForms 之间编译输出的这种差异排除了围绕 WebForms 实现编写包装器并在 MVC 应用程序中“按原样”使用它的可能性。

MVC 6 有效,但略有不同...

MVC 6 使用第 3 种实现,Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.DataAnnotationsModelValidator。此实现接受 IStringLocalizer stringLocalizer 作为构造函数参数。

可以在Startup.cs中添加默认本地化配置为:

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc()
        .AddDataAnnotationsLocalization();
}

并且需要在Startup.csConfigure(...)方法中请求本地化,例如:

// Configure the localization options
app.UseRequestLocalization(new RequestLocalizationOptions
{
    DefaultRequestCulture = new RequestCulture(new CultureInfo("de-AT")),
    SupportedCultures = new List<CultureInfo>
    {
        new CultureInfo("de")
    },
    SupportedUICultures = new List<CultureInfo>
    {
        new CultureInfo("de-AT")
    }
});

如果我们创建 ViewModel:

using System.ComponentModel.DataAnnotations;

namespace WebApplication1.Models
{
    public class User
    {
        [Required(ErrorMessage = "First name is required.")]
        public string FirstName { get; set; }
    }
}

我们必须在 WebApplication 的根目录中添加 Models.User.{culture}.resx 文件,键为“需要名字”。和本地化的验证错误消息。

即使 MVC 6 有不同的实现,相同的条件适用于 ValidationAttribute 与在 WebForms 中一样。 ErrorMessage 必须定义,而 ErrorMessageResourceNameErrorMessageResourceType 不应使用。