Suport multilingüe per als missatges predeterminats que es mostren durant la validació d'entrada

Data de creació de la pàgina :

entorn

Nucli ASP.NET
  • 5,0 MVC

Al principi

Pot ser que el suport multilingüe dels missatges de validació d'entrada per defecte amb aquest Consell no s'hagi completat. Si us plau, tracteu amb els desapareguts.

A més, pot ser que el canvi d'idioma en funció de l'estat de sessió o de l'estat de la memòria cau encara aparegui al text de l'idioma anterior.

Hi ha diverses maneres de canviar el missatge de validació d'entrada per defecte, cadascuna de les quals es pot combinar. No és un d'ells, de manera que si voleu canviar-lo de manera fiable, heu de correspondre amb tots els patrons.

premissa

Aquests consells s'escriuen com a comprensió dels següents consells:

A més, si esteu creant un projecte nou, heu d'haver afegit els fitxers i el codi següents basats en els consells anteriors.

  • Crea un fitxer SharedResource.resx (+ca, es). (Com que només es tradueix el missatge d'aquests consells, el contingut pot estar buit.)
  • Crea un fitxer sharedResource.cs
  • Afegeix un codi de localització a Startup.ConfigureServices
  • Afegeix un codi de localització a Startup.Configura
  • S'ha afegit UserViewModel (aquesta vegada, no teclegem explícitament els missatges per defecte en diversos idiomes)
  • S'han afegit accions i visualitzacions de pantalla creades per l'usuari (Create.cshtml)

S'està iniciant el codi

Sobre la base dels supòsits anteriors, cada codi serà el següent:

Inici.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Globalization;

namespace LocalizationDefaultValidation
{
  public class Startup
  {
    // 省略

    public void ConfigureServices(IServiceCollection services)
    {
      services.AddControllersWithViews();

      services.AddMvc()
        // ローカライズに必要。Resx ファイルのフォルダパスを指定
        .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, opts => { opts.ResourcesPath = "Resources"; })
        // DataAnnotations のローカライズに必要
        .AddDataAnnotationsLocalization(options =>
        {
          // DataAnnotation を使ったときのメッセージは SharedResource に集約する
          options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(SharedResource));
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
      if (env.IsDevelopment())
      {
        app.UseDeveloperExceptionPage();
      }
      else
      {
        app.UseExceptionHandler("/Home/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
      }

      // 標準の機能で切り替えたい言語を定義します。
      var supportedCultures = new[]
      {
        new CultureInfo("ja"),
        new CultureInfo("en"),
        new CultureInfo("es"),
      };

      // 標準の言語切り替え機能を有効にします。対応しているのは「クエリ文字列」「Cookie」「Accept-Language HTTP ヘッダー」です。
      app.UseRequestLocalization(new RequestLocalizationOptions
      {
        DefaultRequestCulture = new RequestCulture("ja"),
        SupportedCultures = supportedCultures,
        SupportedUICultures = supportedCultures
      });

      app.UseHttpsRedirection();
      app.UseStaticFiles();

      // 省略
    }
  }
}
HomeControlador.cs
using LocalizationDefaultValidation.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace LocalizationDefaultValidation.Controllers
{
  public class HomeController : Controller
  {
    // 省略

    public IActionResult Create()
    {
      SetDayOfWeeksViewData();
      return View();
    }

    [HttpPost]
    public IActionResult Create(UserViewModel model)
    {
      SetDayOfWeeksViewData();

      // エラーなら差し戻し
      if (ModelState.IsValid == false) return View(model);

      // 正常に登録できた場合は Index に戻る
      return RedirectToAction(nameof(Index));
    }

    /// <summary>曜日の一覧を ViewData に設定します。</summary>
    private void SetDayOfWeeksViewData()
      => ViewData["DayOfWeeks"] = ((DayOfWeek[])Enum.GetValues(typeof(DayOfWeek))).Select(x => new SelectListItem(x.ToString(), ((int)x).ToString()));
  }
}
Index.cshtml
@{
  ViewData["Title"] = "Home Page";
}

<div class="text-center">
  <h1 class="display-4">Welcome</h1>
  <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

<p>ユーザー作成画面に遷移します。リンクごとに選択した言語で表示できます。</p>
<ul>
  <li><a asp-action="Create">Create</a></li>
  <li><a asp-action="Create" asp-route-culture="ja">Create (ja)</a></li>
  <li><a asp-action="Create" asp-route-culture="en">Create (en)</a></li>
  <li><a asp-action="Create" asp-route-culture="es">Create (es)</a></li>
</ul>
Create.cshtml
@model LocalizationDefaultValidation.Models.UserViewModel
@using System.Globalization;
@{
  ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>UserViewModel</h4>
<hr />
<div class="row">
  <div class="col-md-4">
    <form asp-action="Create" enctype="multipart/form-data" >
      <div asp-validation-summary="ModelOnly" class="text-danger"></div>
      <div class="form-group">
        <label asp-for="ID" class="control-label"></label>
        <input asp-for="ID" class="form-control" />
        <span asp-validation-for="ID" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Name" class="control-label"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Password" class="control-label"></label>
        <input asp-for="Password" class="form-control" />
        <span asp-validation-for="Password" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="ConfirmPassword" class="control-label"></label>
        <input asp-for="ConfirmPassword" class="form-control" />
        <span asp-validation-for="ConfirmPassword" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Email" class="control-label"></label>
        <input asp-for="Email" class="form-control" />
        <span asp-validation-for="Email" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Age" class="control-label"></label>
        <input asp-for="Age" class="form-control" />
        <span asp-validation-for="Age" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Gender"></label>
        <div>
          <label><input type="radio" asp-for="Gender" value="@(GenderType.None)" />@(GenderType.None)</label>
          <label><input type="radio" asp-for="Gender" value="@(GenderType.Man)" />@(GenderType.Man)</label>
          <label><input type="radio" asp-for="Gender" value="@(GenderType.Woman)" />@(GenderType.Woman)</label>
        </div>
        <span asp-validation-for="Gender" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Birthday" class="control-label"></label>
        <input asp-for="Birthday" class="form-control" />
        <span asp-validation-for="Birthday" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Phone" class="control-label"></label>
        <input asp-for="Phone" class="form-control" />
        <span asp-validation-for="Phone" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="PostalCode" class="control-label"></label>
        <input asp-for="PostalCode" class="form-control" />
        <span asp-validation-for="PostalCode" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="CreditCard" class="control-label"></label>
        <input asp-for="CreditCard" class="form-control" />
        <span asp-validation-for="CreditCard" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Money" class="control-label"></label>
        <input asp-for="Money" class="form-control" />
        <span asp-validation-for="Money" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="StartDateTime" class="control-label"></label>
        <input asp-for="StartDateTime" class="form-control" />
        <span asp-validation-for="StartDateTime" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="WakeUpTime" class="control-label"></label>
        <input asp-for="WakeUpTime" class="form-control" />
        <span asp-validation-for="WakeUpTime" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Homepage" class="control-label"></label>
        <input asp-for="Homepage" class="form-control" />
        <span asp-validation-for="Homepage" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="MyImage" class="control-label"></label>
        <input asp-for="MyImage" class="form-control" />
        <span asp-validation-for="MyImage" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="MyColor" class="control-label"></label>
        <input asp-for="MyColor" class="form-control" type="color" />
        <span asp-validation-for="MyColor" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="WorkingDays" class="control-label"></label>
        <select asp-for="WorkingDays" class="form-control" asp-items="@((IEnumerable<SelectListItem>)ViewData["DayOfWeeks"])" multiple></select>
        <span asp-validation-for="WorkingDays" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="VacationDay" class="control-label"></label>
        <select asp-for="VacationDay" class="form-control" asp-items="@((IEnumerable<SelectListItem>)ViewData["DayOfWeeks"])" multiple></select>
        <span asp-validation-for="VacationDay" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Comment" class="control-label"></label>
        <textarea asp-for="Comment" class="form-control"></textarea>
        <span asp-validation-for="Comment" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="FileName" class="control-label"></label>
        <input asp-for="FileName" class="form-control" />
        <span asp-validation-for="FileName" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="UploadFile" class="control-label"></label>
        <input asp-for="UploadFile" type="file" multiple />
        <span asp-validation-for="UploadFile" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Month" class="control-label"></label>
        <input asp-for="Month" class="form-control" type="month" />
        <span asp-validation-for="Month" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Search" class="control-label"></label>
        <input asp-for="Search" class="form-control" type="search" />
        <span asp-validation-for="Search" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Range" class="control-label"></label>
        <input asp-for="Range" class="form-control" type="range" min="10" max="100" />
        <span asp-validation-for="Range" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="Week" class="control-label"></label>
        <input asp-for="Week" class="form-control" type="week" />
        <span asp-validation-for="Week" class="text-danger"></span>
      </div>
      <div class="form-group form-check">
        <label class="form-check-label">
          <input class="form-check-input" asp-for="IsAccepted" /> @Html.DisplayNameFor(model => model.IsAccepted)
        </label>
      </div>
      <div class="form-group">
        <input type="submit" value="Create" class="btn btn-primary" asp-route-culture="@CultureInfo.CurrentCulture.Name" />
      </div>
    </form>
  </div>
</div>

<div>
  <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
  @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
UserViewModel.cs
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace LocalizationDefaultValidation.Models
{
  public class UserViewModel
  {
    [Required]
    [Display(Name = "ID (半角英数字)")]
    [StringLength(20)]
    [RegularExpression(@"^[0-9a-zA-Z]*$")]
    public string ID { get; set; }

    [StringLength(50)]
    public string Name { get; set; }

    [StringLength(50, MinimumLength = 8)]
    [DataType(DataType.Password)]
    [RegularExpression(@"^[0-9a-zA-Z]*$")]
    public string Password { get; set; }

    [StringLength(50, MinimumLength = 8)]
    [DataType(DataType.Password)]
    [Compare(nameof(Password))]
    public string ConfirmPassword { get; set; }

    [EmailAddress]
    [DataType(DataType.EmailAddress)] // スキャフォールディングするときはコメントアウトする
    public string Email { get; set; }

    //[Int]
    [Range(0, 150)]
    public int Age { get; set; }

    [Required]
    [EnumDataType(typeof(GenderType))]
    public GenderType Gender { get; set; }

    [DataType(DataType.Date)]
    public DateTime Birthday { get; set; }

    [Phone()]
    [DataType(DataType.PhoneNumber)] // スキャフォールディングするときはコメントアウトする
    public string Phone { get; set; }

    [DataType(DataType.PostalCode)]
    public string PostalCode { get; set; }

    [CreditCard()]
    [DataType(DataType.CreditCard)] // スキャフォールディングするときはコメントアウトする
    public string CreditCard { get; set; }

    [DataType(DataType.Currency)]
    public decimal Money { get; set; }

    [DataType(DataType.DateTime)]
    public DateTime StartDateTime { get; set; }

    [DataType(DataType.Time)]
    public TimeSpan WakeUpTime { get; set; }

    [Url]
    [DataType(DataType.Url)] // スキャフォールディングするときはコメントアウトする
    public string Homepage { get; set; }

    [Url]
    [DataType(DataType.ImageUrl)] // スキャフォールディングするときはコメントアウトする
    public string MyImage { get; set; }

    public string MyColor { get; set; }

    [MaxLength(5)]
    public DayOfWeek[] WorkingDays { get; set; }

    [MinLength(3)]
    public DayOfWeek[] VacationDay { get; set; }

    [StringLength(200)]
    [DataType(DataType.MultilineText)]
    public string Comment { get; set; }

    [Display(Name = "ファイル名 (.png)")]
    [FileExtensions(Extensions = "png")]
    public string FileName { get; set; }

    [DataType(DataType.Upload)]
    public List<IFormFile> UploadFile { get; set; }

    public DateTime Month { get; set; }

    public string Search { get; set; }

    [Range(10, 100)]
    public int Range { get; set; }

    public string Week { get; set; }

    [Required]
    public bool IsAccepted { get; set; }
  }

  public enum GenderType
  {
    None,
    Man,
    Woman,
    Other,
  }
}
SharedResource.cs
namespace LocalizationDefaultValidation
{
  // クラス名は作成した .resx のファイル名と同じにする必要がある
  public class SharedResource { }
}

Missatges d'enquadernació del model

Si definiu el tipus de propietat del model int a o l'vinculeu a la DateTime visualització i intenteu registrar-lo sense escriure, veureu un missatge com ara "El valor '' no és vàlid". Aquests missatges apareixen quan una cadena buida int no es pot vincular, per exemple, al model. Com que és un temps que no es pot vincular, només es produeix abans d'una altra validació de valor i processament del servidor.

Aquests missatges DefaultModelBindingMessageProvider es defineixen com a .

Startup.cs Afegiu als arguments del mètode que s'han definit des del principi services.AddControllersWithViews a i, a continuació, afegiu Action . Podeu ser multilingüe establint el passat a options ModelBindingMessageProvider l'argument.

Hi ha 11 tipus de missatges que podeu establir, de manera que primer heu de definir el missatge de la manera següent: SharedResource.resx El nom de la clau és opcional. El missatge en anglès per defecte de la columna de comentaris és (els comentaris no s'han d'incloure). No haureu de traduir cada idioma, de manera que hauríeu de fer la vostra pròpia traducció (el codi d'exemple també inclou el sharedresource.resx traduït).

Comentari del valor del nom
ModelBinding_AttemptedValueIsInvalid "{0}" és un valor no vàlid a la {1}. El valor "{0}" no és vàlid per a {1}.
ModelBinding_MissingBindRequiredValue No s'especifica el valor del {0}. No s'ha proporcionat cap valor per al paràmetre o la propietat "{0}".
ModelBinding_MissingKeyOrValue Obligatori. Es requereix un valor.
ModelBinding_MissingRequestBodyRequiredValue La sol·licitud ha de tenir un cadàver. Es requereix un cos de sol·licitud no buit.
ModelBinding_NonPropertyAttemptedValueIsInvalid "{0}" no és vàlida. El valor "{0}" no és vàlid.
ModelBinding_NonPropertyUnknownValueIsInvalid El valor no és vàlid. El valor proporcionat no és vàlid.
ModelBinding_NonPropertyValueMustBeANumber S'ha d'especificar el número. El camp ha de ser un número.
ModelBinding_UnknownValueIsInvalid El valor de la {0} no és vàlid. El valor proporcionat no és vàlid per a {0}.
ModelBinding_ValueIsInvalid "{0}" no és vàlida. El valor "{0}" no és vàlid.
ModelBinding_ValueMustBeANumber {0} ha de ser un número. El camp {0} ha de ser un número.
ModelBinding_ValueMustNotBeNull Entrada necessària. El valor "{0}" no és vàlid.

Esmena l'inici.cs de la manera següent:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using System;
using System.Globalization;
using System.Reflection;

namespace LocalizationDefaultValidation
{
  public class Startup
  {
    // 省略 (初期コード)

    /// <summary>
    /// 検証メッセージローカライズで使用。
    /// </summary>
    private IServiceProvider ServiceProvider { get; set; }

    private IStringLocalizer _localizer = null;
    /// <summary>
    /// 検証メッセージローカライズで使用。
    /// </summary>
    private IStringLocalizer Localizer
      => _localizer ?? (_localizer = ServiceProvider.GetService<IStringLocalizerFactory>()
               .Create(nameof(SharedResource), new AssemblyName(typeof(SharedResource).Assembly.FullName).Name));

    // このメソッドはランタイムによって呼び出されます。 このメソッドを使用して、コンテナーにサービスを追加します。
    public void ConfigureServices(IServiceCollection services)
    {
      services.AddControllersWithViews(options =>
      {
        // 検証メッセージのローカライズで使用
        // モデルバインディング失敗時のエラーメッセージをカスタマイズ
        // サーバ側でモデルに値を格納する際に発生する可能性がある
        
        // メッセージの {0} や {1} を置換するための関数を定義
        static string f1(string f, string a1) => string.Format(f, a1);
        static string f2(string f, string a1, string a2) => string.Format(f, a1, a2);

        // 各メソッドを読んでメッセージを置き換えます
        var mp = options.ModelBindingMessageProvider;
        mp.SetAttemptedValueIsInvalidAccessor((x, y) => f2(Localizer["ModelBinding_AttemptedValueIsInvalid"], x, y));
        mp.SetMissingBindRequiredValueAccessor((x) => f1(Localizer["ModelBinding_MissingBindRequiredValue"], x));
        mp.SetMissingKeyOrValueAccessor(() => Localizer["ModelBinding_MissingKeyOrValue"]);
        mp.SetMissingRequestBodyRequiredValueAccessor(() => Localizer["ModelBinding_MissingRequestBodyRequiredValue"]);
        mp.SetNonPropertyAttemptedValueIsInvalidAccessor((x) => f1(Localizer["ModelBinding_NonPropertyAttemptedValueIsInvalid"], x));
        mp.SetNonPropertyUnknownValueIsInvalidAccessor(() => Localizer["ModelBinding_NonPropertyUnknownValueIsInvalid"]);
        mp.SetNonPropertyValueMustBeANumberAccessor(() => Localizer["ModelBinding_NonPropertyValueMustBeANumber"]);
        mp.SetUnknownValueIsInvalidAccessor((x) => f1(Localizer["ModelBinding_UnknownValueIsInvalid"], x));
        mp.SetValueIsInvalidAccessor((x) => f1(Localizer["ModelBinding_ValueIsInvalid"], x));
        mp.SetValueMustBeANumberAccessor((x) => f1(Localizer["ModelBinding_ValueMustBeANumber"], x));
        mp.SetValueMustNotBeNullAccessor((x) => f1(Localizer["ModelBinding_ValueMustNotBeNull"], x));
      });

      services.AddMvc()
        // ローカライズに必要。Resx ファイルのフォルダパスを指定
        .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, opts => { opts.ResourcesPath = "Resources"; })
        // DataAnnotations のローカライズに必要
        .AddDataAnnotationsLocalization(options =>
        {
          // DataAnnotation を使ったときのメッセージは SharedResource に集約する
          options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(SharedResource));
        });
    }

    // このメソッドはランタイムによって呼び出されます。 このメソッドを使用して、HTTP要求パイプラインを構成します。
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
      // ローカライズで使用するため IServiceProvider をプロパティに保持しておきます。
      ServiceProvider = app.ApplicationServices;

      // 標準の機能で切り替えたい言語を定義します。
      var supportedCultures = new[]
      {
        new CultureInfo("ja"),
        new CultureInfo("en"),
        new CultureInfo("es"),
      };

      // 標準の言語切り替え機能を有効にします。対応しているのは「クエリ文字列」「Cookie」「Accept-Language HTTP ヘッダー」です。
      app.UseRequestLocalization(new RequestLocalizationOptions
      {
        DefaultRequestCulture = new RequestCulture("ja"),
        SupportedCultures = supportedCultures,
        SupportedUICultures = supportedCultures
      });

      // 省略 (初期コード)
    }
  }
}

El punt clau és services.AddControllersWithViews afegir a al mètode que rep el option Action , options.ModelBindingMessageProvider Estic establint text localitzat per a cada mètode Descció a .

El text traduït IStringLocalizer es recupera de . No hi ha manera de rebre IStringLocalizer codi directe, que és una mica d'un codi rodó. SharedResource.resx Si esteu generant codi des de, obtindreu valors localitzats directament.

IStringLocalizer La clau que especifiqueu SharedResource.resx especifica especifica la clau a la que heu afegit a . Establiu-lo perquè coincideixi amb el contingut de cada mètode de conjunt.

A més, perquè cada missatge té un argument de cadena de format, com ara , {0}{1} utilitzo Simple amb perquè pugueu reemplaçar els string.Format Func valors rebuts.

Localitzar els missatges de validació per defecte

El missatge d'error per defecte quan els atributs de les Required propietats d'un model estan definits en o així està determinat pel framework i és StringLength bàsicament en anglès.

Aquests es poden IValidationAttributeAdapterProvider localitzar definint una classe derivada de la interfície.

En primer SharedResource.resx lloc, registreu el text multilingüe. El nom de la clau és arbitrari, però per simplificar el programa, registreu-lo en forma de "nom de l'atribut de validació Validator_<>".

Comentari del valor del nom
Validator_CompareAttribute Els {0} i {1} no coincideixen. "{0}" i "{1}" no coincideixen.
Validator_CreditCardAttribute {0} no és un número de targeta vàlid. El camp {0} no és un número de targeta de crèdit vàlid.
Validator_DataTypeAttribute_Date Introduïu una data vàlida. Introduïu una data vàlida.
Validator_EmailAddressAttribute {0} no és una adreça de correu electrònic vàlida. El camp {0} no és una adreça electrònica vàlida.
Validator_FileExtensionsAttribute {0} només accepta fitxers amb les extensions següents: : {1} El camp {0} només accepta fitxers amb les extensions següents: {1}
Validator_MaxLengthAttribute {0} ha de ser un tipus de cadena o matriu amb una longitud màxima de "{1}". El camp {0} ha de ser un tipus de cadena o matriu amb una longitud màxima de "{1}".
Validator_MinLengthAttribute {0} ha de ser un tipus de cadena o matriu amb una longitud mínima de "{1}". El camp {0} ha de ser un tipus de cadena o matriu amb una longitud mínima de "{1}".
Validator_PhoneAttribute {0} no és un número de telèfon vàlid. El camp {0} no és un número de telèfon vàlid.
Validator_RangeAttribute {0} ha d'anar des de {1} fins a {2}. El camp {0} ha d'estar entre {1} i {2}.
Validator_RegularExpressionAttribute {0} ha de coincidir amb l'expressió regular "{1}". El camp {0} ha de coincidir amb l'expressió regular "{1}".
Validator_RequiredAttribute {0} és necessària. Es requereix el camp {0}.
Validator_StringLengthAttribute {0} ha d'estar dins {1} dígits. El camp {0} ha de ser una cadena amb una longitud màxima de {1}.
Validator_UrlAttribute {0} no és una adreça URL vàlida. El camp {0} no és un URL http, https o ftp totalment qualificat vàlid.
Validator_StringLengthAttributeWithMin {0} ha de tenir com a mínim {2} {1} dígits. El camp {0} ha de ser una cadena amb una longitud màxima de {1} i una longitud mínima de {2}.

A continuació, creeu la següent classe AdapterProvider:

El nom de la classe és arbitrari, però aquesta vegada CustomValidationAttributeAdapterProvider escriuré una classe anomenada i escriuré el codi de la següent manera: La ubicació del fitxer de codi és arbitrària, però la mostra es col·loca en una carpeta anomenada Adaptador.

using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.Extensions.Localization;
using System;
using System.ComponentModel.DataAnnotations;

namespace LocalizationDefaultValidation
{
  public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
  {
    /// <summary>コード簡略化のためのリソースキー命名規則プリフィックス</summary>
    private const string RESOURCE_KEY_PREFIX = "Validator_";

    private readonly IValidationAttributeAdapterProvider _fallback = new ValidationAttributeAdapterProvider();

    /// <summary>
    /// 指定された ValidationAttribute の IAttributeAdapter を返します。
    /// </summary>
    /// <param name="attribute">IAttributeAdapter を作成するための ValidationAttribute。</param>
    /// <param name="stringLocalizer">メッセージの作成に使用される IStringLocalizer。</param>
    /// <returns>指定された属性の IAttributeAdapter。</returns>
    IAttributeAdapter IValidationAttributeAdapterProvider.GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
    {
      // すでにエラーメッセージが設定されている場合はそれを使用するのでここでは何も設定しない
      if (attribute.ErrorMessageResourceName != null) return _fallback.GetAttributeAdapter(attribute, stringLocalizer);

      // attribute には「Required」や「StringLength」などが設定されています
      Type attrType = attribute.GetType();
      
      // プリフィックスと属性のクラス名からローカライズ用のキーを生成します
      var key = RESOURCE_KEY_PREFIX + attrType.Name;

      // IStringLocalizer から指定したキーでローカライズされたテキストを取得します
      // ない場合は getString にそのまま key の値が入ります
      var getString = stringLocalizer[key];

      // 正しくローカライズされたテキストが取得できた場合は ErrorMessage に値をセットします。
      if (key != getString && attribute.ErrorMessage != getString)
      {
        attribute.ErrorMessage = getString;
      }

      // 設定した attribute を渡します
      return _fallback.GetAttributeAdapter(attribute, stringLocalizer);
    }
  }
}

Si definiu les propietats del model a Required o , el mètode s'anomena per a cada StringLength IValidationAttributeAdapterProvider.GetAttributeAdapter validació.

ErrorMessageResourceName Si la propietat conté un valor, el codi del costat del model ja té un missatge o una clau de localització, de manera que torna tal com és.

Si està buit, feu coincidir els noms de classe del prefix i l'atribut de validació per convertir-lo en la clau de localització i, a continuació, recupereu el text localitzat de la clau i establiu-lo ErrorMessage a . Això permet localitzar la majoria dels missatges per defecte.

A continuació, registreu aquesta classe a .cs d'inici. Bàsicament, podeu afegir-lo de la manera següent.

// 省略

using Microsoft.AspNetCore.Mvc.DataAnnotations;

namespace LocalizationDefaultValidation
{
  public class Startup
  {
    // 省略

    // このメソッドはランタイムによって呼び出されます。 このメソッドを使用して、コンテナーにサービスを追加します。
    public void ConfigureServices(IServiceCollection services)
    {
      // 省略

      // 作成した CustomValidationAttributeAdapterProvider をシングルトンとして登録します
      services.AddSingleton<IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    }
    
    // 省略
  }
}

Executeu-lo per assegurar-vos que funciona correctament.

Canvia un missatge existent per paràmetres

Per exemple, StringLength el missatge d'atribut està localitzat, com ara "{0} ha d'estar dins {1} dígits". MinimumLength Si la propietat està establerta, és possible que vulgueu canviar la {0} com a "Especifiqueu {2} dígits o més {1} dins d'un nombre de dígits".

En aquest CustomValidationAttributeAdapterProvider cas, amplieu la classe de la manera següent:

// 省略

namespace LocalizationDefaultValidation
{
  public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
  {
    // 省略

    IAttributeAdapter IValidationAttributeAdapterProvider.GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
    {
      if (attribute is StringLengthAttribute slAttribute)
      {
        // attribute が StringLengthAttribute で MinimumLength が設定されている場合はメッセージを変える
        if (slAttribute.MinimumLength >= 1)
        {
          attribute.ErrorMessage = stringLocalizer["Validator_StringLengthAttributeWithMin"];
          return _fallback.GetAttributeAdapter(slAttribute, stringLocalizer);
        }
      }

      // 省略
    }
  }
}

Com que el mètode s'anomena per a cada GetAttributeAdapter validació, si la variable passada attribute era un StringLengthAttribute MinimumLength atribut, comproveu la propietat, Si s'estableix, reberu un missatge que també tingui en compte el nombre mínim de dígits i ErrorMessage substitueixi .

Si l'executeu, comproveu i el missatge canvia, no passa res.

Altres missatges genèrics

Alguns missatges no es localitzen després de fer-ho fins ara. Per exemple, missatges que només es determinen en Javascript.

L'objectiu és jquery.validate.js el missatge que apareix al fitxer.

Com que aquest fitxer no s'ha d'editar directament, per substituir aquests missatges per text localitzat, input heu d'afegir un atribut com ara a l'etiqueta i data-val-XXXX establir-hi text localitzat. La part XXXX conté la clau de la figura jquery.validate.js anterior. (per exemple, data-val-number etc.)

data-val-XXXX Per establir el text localitzat als atributs de

Localitzem el missatge quan s'incloguin caràcters no numèrics al camp d'entrada Edat com a exemple. UserViewModel.AgeRange un atribut, i l'HTML de sortida data-val-range afegeix l'atribut d'entrada.

<div class="form-group">
  <label class="control-label" for="Age">Age</label>
  <input class="form-control" type="number" data-val="true" data-val-range="Ageは0から150の範囲で指定してください。" data-val-range-max="150" data-val-range-min="0" data-val-required="Ageは必須です。" id="Age" name="Age" value="" />
  <span class="text-danger field-validation-valid" data-valmsg-for="Age" data-valmsg-replace="true"></span>
</div>

Tanmateix, data-val-number el missatge de comprovació numèric no està localitzat perquè no hi ha cap atribut. També afegireu un altre atribut al costat del model per imprimir l'atribut localitzat afegit al data-val-number missatge.

El primer pas és crear una classe d'atribut que comprovi si és IntAttribute un número: Es limita a aquí, però vostè és int lliure de canviar-lo en funció de la situació. El fitxer de codi pot ser en qualsevol lloc, però aquesta vegada és a la carpeta Adaptador.

using System.ComponentModel.DataAnnotations;

namespace LocalizationDefaultValidation
{
  public class IntAttribute : ValidationAttribute
  {
    public override bool IsValid(object value)
    {
      // 渡された値が int であれば有効な値とする
      return value is int;
    }
  }
}

Si el valor establert és int un tipus, és un atribut que només és un veredicte normal, però a la pràctica és 100% normal si s'estableix a una propietat de tipus int, de manera que el processament d'aquesta classe en si no té cap sentit. L'objectiu d'aquest temps és mostrar missatges d'error localitzats al client.

A continuació, creeu la classe d'adaptador IntAttributeAdapter següent: Això també es troba a la carpeta Adaptador.

using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Localization;

namespace LocalizationDefaultValidation
{
  /// <summary>Int 属性のアダプター。</summary>
  public class IntAttributeAdapter : AttributeAdapterBase<IntAttribute>
  {
    /// <summary>受け取った IStringLocalizer を保持しておく</summary>
    IStringLocalizer _stringLocalizer;

    public IntAttributeAdapter(IntAttribute attribute, IStringLocalizer stringLocalizer)
        : base(attribute, stringLocalizer)
    {
      _stringLocalizer = stringLocalizer;
    }

    /// <summary>バリデーションの追加処理として呼ばれる。</summary>
    public override void AddValidation(ClientModelValidationContext context)
    {
      // 新たに data-val-number 属性をマージして追加します。値はローカライズしたテキストをセットします。
      MergeAttribute(context.Attributes, "data-val-number", _stringLocalizer["ModelBinding_NonPropertyValueMustBeANumber"]);
    }

    /// <summary>サーバーのエラーメッセージはそのまま返します。</summary>
    public override string GetErrorMessage(ModelValidationContextBase validationContext) => Attribute.ErrorMessage;
  }
}

El punt aquí és AddValidation el mètode descrit en el MergeAttribute mètode.

S'estan fusionant els atributs com a atributs de l'etiqueta d'entrada per tornar data-val-number al client. El valor que s'ha d'establir a l'atribut estableix un missatge d'error localitzat.

Retorna aquest adaptador a la classe que has creat CustomValidationAttributeAdapterProvider abans.

// 省略

namespace LocalizationDefaultValidation
{
  public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
  {
    // 省略
    
    IAttributeAdapter IValidationAttributeAdapterProvider.GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
    {
      // IntAttribute の場合は IntAttributeAdapter 経由で返す
      if (attribute is IntAttribute intAttribute)
      {
        return new IntAttributeAdapter(intAttribute, stringLocalizer);
      }

      // 省略 (前に追加したコード)
    }
  }
}

Si el valor a validar IntAttribute és , torneu el que heu creat IntAttributeAdapter anteriorment. La propietat amb l'atribut Int s'afegeix ara quan es mostra al costat del data-val-number client.

Finalment, UserViewModel.Age IntAttribute afegiu-hi .

Un cop hàgiu corregit el codi, proveu-lo per veure si voleu comprovar-lo.

<div class="form-group">
  <label class="control-label" for="Age">Age</label>
  <input class="form-control" type="number" data-val="true" data-val-number="数字を指定してください。" data-val-range="Ageは0から150の範囲で指定してください。" data-val-range-max="150" data-val-range-min="0" data-val-required="Ageは必須です。" id="Age" name="Age" value="" />
  <span class="text-danger field-validation-valid" data-valmsg-for="Age" data-valmsg-replace="true"></span>
</div>

Hi ha altres missatges no localitzats, però podeu localitzar-los en els mètodes descrits anteriorment. Afegiu codi quan ho necessiteu.