Prise en charge multilingue des messages par défaut affichés lors de la validation des entrées

Date de création de la page :

environnement

ASP.NET noyau
  • 5,0 MVC

Au début

La prise en charge multilingue des messages de validation d’entrée par défaut avec ces conseils peut ne pas être complète. S’il vous plaît traiter avec ceux manquants.

En outre, la modification de la langue en fonction de l’état de session ou de l’état du cache peut toujours apparaître dans le texte de la langue précédente.

Il existe plusieurs façons de modifier le message de validation d’entrée par défaut, chacune pouvant être combinée. Ce n’est pas l’un d’entre eux, donc si vous voulez le changer de manière fiable, vous devez correspondre à tous les modèles.

prémisse

Ces conseils sont écrits comme la compréhension des conseils suivants:

En outre, si vous créez un nouveau projet, vous devez avoir ajouté les fichiers et le code suivants en fonction des conseils ci-dessus.

  • Créez un fichier SharedResource.resx (+en, es). (Étant donné que seul le message de ces conseils est traduit, le contenu peut être vide.)
  • Créer un fichier SharedResource.cs
  • Ajouter du code de localisation à Startup.ConfigureServices
  • Ajouter du code de localisation à Startup.Configure
  • Ajout de UserViewModel (cette fois, nous ne saisissez pas explicitement les messages par défaut dans plusieurs langues)
  • Ajout d’actions et de vues d’écran créées par l’utilisateur (Create.cshtml)

Code de départ

Sur la base des hypothèses ci-dessus, chaque code doit être le suivant:

Démarrage.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();

      // 省略
    }
  }
}
HomeController.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>
Créer.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,
  }
}
Ressource partagée.cs
namespace LocalizationDefaultValidation
{
  // クラス名は作成した .resx のファイル名と同じにする必要がある
  public class SharedResource { }
}

Messages de liaison de modèle

Si vous définissez le type de propriété de modèle sur int ou et que vous le liez à la vue et que vous DateTime essayez de l’enregistrer non typé, vous verrez un message tel que « La valeur '' n’est pas valide. ». Ces messages s’affichent lorsqu’une chaîne vide int ne peut pas être liée, par exemple, dans le modèle. Comme il s’agit d’un timing qui ne peut pas être lié, il ne se produit qu’avant la validation des autres valeurs et le traitement côté serveur.

Ces messages sont DefaultModelBindingMessageProvider définis comme .

Startup.cs Ajoutez aux arguments de la méthode qui ont été définis depuis le début services.AddControllersWithViews dans , puis ajoutez Action . Vous pouvez être multilingue en définissant la passe dans options ModelBindingMessageProvider l’argument.

Il existe 11 types de messages que vous pouvez définir, vous devez donc d’abord définir le message comme suit : SharedResource.resx Le nom de la clé est facultatif. Le message anglais par défaut dans la colonne des commentaires est (les commentaires n’ont pas besoin d’être inclus). Vous n’aurez pas à traduire chaque langue, vous devez donc faire votre propre traduction (l’exemple de code inclut également le fichier SharedResource.resx traduit).

sur
Commentairela valeur du nom
ModelBinding_AttemptedValueIsInvalid '{0}' est une valeur non valide dans le {1}. La valeur '{0}' n’est pas valide pour {1}.
ModelBinding_MissingBindRequiredValue La valeur du {0} n’est pas spécifiée. Aucune valeur pour le paramètre ou la propriété '{0}' n’a pas été fournie.
ModelBinding_MissingKeyOrValue Obligatoire. Une valeur est requise.
ModelBinding_MissingRequestBodyRequiredValue La demande doit avoir un corps. Un corps de demande non vide est requis.
ModelBinding_NonPropertyAttemptedValueIsInvalid « {0} » n’est pas valide. La valeur '{0}' n’est pas valide.
ModelBinding_NonPropertyUnknownValueIsInvalid La valeur n’est pas valide. La valeur fournie n’est pas valide.
ModelBinding_NonPropertyValueMustBeANumber Le numéro doit être spécifié. Le champ doit être un nombre.
ModelBinding_UnknownValueIsInvalid La valeur du {0} n’est pas valide. La valeur fournie n’est pas valide pour {0}.
ModelBinding_ValueIsInvalid « {0} » n’est pas valide. La valeur '{0}' n’est pas valide.
ModelBinding_ValueMustBeANumber {0} doit être un nombre. Le champ {0} doit être un nombre.
ModelBinding_ValueMustNotBeNull Entrée requise. La valeur '{0}' n’est pas valide.

Correction du démarrage.cs comme suit :

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

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

Le point clé est services.AddControllersWithViews d’ajouter a à la méthode qui reçoit le option Action , options.ModelBindingMessageProvider Je définis du texte localisé pour chaque méthode Set dans .

Le texte traduit est IStringLocalizer extrait de . Il n’y a aucun moyen de recevoir IStringLocalizer du code direct, qui est un peu un code rond. SharedResource.resx Si vous générez du code à partir de celui-ci, vous obtiendrez des valeurs localisées directement à partir de celui-ci.

IStringLocalizer La clé que vous spécifiez dans SharedResource.resx spécifie la clé que vous avez ajoutée à . Définissez-le pour qu’il corresponde au contenu de chaque méthode Set.

De plus, comme chaque message a un argument de chaîne de format, tel que , {0}{1} j’utilise Simple avec pour que vous puissiez remplacer les string.Format Func valeurs reçues.

Localiser les messages de validation par défaut

Le message d’erreur par défaut lorsque les attributs des Required propriétés d’un modèle sont définis sur environ est déterminé par le framework et est StringLength essentiellement en anglais.

Ceux-ci peuvent être IValidationAttributeAdapterProvider localisés en définissant une classe dérivée de l’interface.

Tout SharedResource.resx d’abord, enregistrez le texte multilingue. Le nom de la clé est arbitraire, mais pour simplifier le programme, enregistrez-le sous la forme de « Validator_< nom de l’attribut de validation> ».

sur
Commentairela valeur du nom
Validator_CompareAttribute Les {0} et {1} ne correspondent pas. « {0} » et « {1} » ne correspondent pas.
Validator_CreditCardAttribute {0} n’est pas un numéro de carte valide. Le champ {0} n’est pas un numéro de carte de crédit valide.
Validator_DataTypeAttribute_Date Entrez une date valide. Veuillez entrer une date valide.
Validator_EmailAddressAttribute {0} n’est pas une adresse e-mail valide. Le champ {0} n’est pas une adresse e-mail valide.
Validator_FileExtensionsAttribute {0} accepte uniquement les fichiers avec les extensions suivantes : : {1} Le champ {0} n’accepte que les fichiers portant les extensions suivantes : {1}
Validator_MaxLengthAttribute {0} doit être un type de chaîne ou de tableau d’une longueur maximale de '{1}'. Le champ {0} doit être de type chaîne ou tableau d’une longueur maximale de '{1}'.
Validator_MinLengthAttribute {0} doit être un type de chaîne ou de tableau d’une longueur minimale de '{1}'. Le champ {0} doit être de type chaîne ou tableau d’une longueur minimale de '{1}'.
Validator_PhoneAttribute {0} n’est pas un numéro de téléphone valide. Le champ {0} n’est pas un numéro de téléphone valide.
Validator_RangeAttribute {0} doit aller de {1} à {2}. Le champ {0} doit se situer entre {1} et {2}.
Validator_RegularExpressionAttribute {0} doit correspondre à l’expression régulière « {1} ». Le champ {0} doit correspondre à l’expression régulière '{1}'.
Validator_RequiredAttribute {0} est requise. Le champ {0} est obligatoire.
Validator_StringLengthAttribute {0} doit être à {1} chiffres. Le champ {0} doit être une chaîne d’une longueur maximale de {1}.
Validator_UrlAttribute {0} n’est pas une URL valide. Le champ {0} n’est pas une URL http, https ou ftp complète valide.
Validator_StringLengthAttributeWithMin {0} doit comporter au moins {2} {1} chiffres. Le champ {0} doit être une chaîne d’une longueur maximale de {1} et d’une longueur minimale de {2}.

Ensuite, créez la classe AdapterProvider suivante :

Le nom de la classe est arbitraire, mais cette CustomValidationAttributeAdapterProvider fois, je vais écrire une classe appelée et écrire le code comme suit : L’emplacement du fichier de code est arbitraire, mais l’exemple est placé dans un dossier appelé Adapter.

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 vous définissez les propriétés du modèle sur Required ou , la méthode est appelée pour chaque StringLength IValidationAttributeAdapterProvider.GetAttributeAdapter validation.

ErrorMessageResourceName Si la propriété contient une valeur, le code côté modèle possède déjà un message ou une clé de localisation, il retourne donc tel qu’il est.

Si elle est vide, faites correspondre les noms de classe du préfixe et de l’attribut validation pour en faire la clé de localisation, puis récupérez le texte localisé de la clé et définissez-le sur ErrorMessage . Cela permet de localiser la plupart des messages par défaut.

Ensuite, enregistrez cette classe dans la .cs de démarrage. Fondamentalement, vous pouvez l’ajouter comme suit.

// 省略

using Microsoft.AspNetCore.Mvc.DataAnnotations;

namespace LocalizationDefaultValidation
{
  public class Startup
  {
    // 省略

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

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

Exécutez-le pour vous assurer qu’il fonctionne correctement.

Modifier un message existant par paramètres

Par exemple, StringLength le message d’attribut est localisé, tel que « {0} doit être à {1} chiffres ». MinimumLength Si la propriété est définie, vous pouvez modifier la {0} comme « Spécifier {2} chiffres ou plusieurs {1} dans un certain nombre de chiffres ».

Dans ce CustomValidationAttributeAdapterProvider cas, étendez la classe comme suit :

// 省略

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

      // 省略
    }
  }
}

Étant donné que la méthode est appelée pour chaque GetAttributeAdapter validation, si la variable transmise attribute était StringLengthAttribute un MinimumLength attribut, vérifiez la propriété, Si cette option est définie, obtenez un message qui prend également en compte le nombre minimum de chiffres et ErrorMessage remplace .

Si vous l’exécutez, vérifiez et que le message change, c’est OK.

Autres messages génériques

Certains messages ne sont pas localisés après que vous l’ayez fait jusqu’à présent. Par exemple, les messages qui sont déterminés uniquement en Javascript.

La cible est jquery.validate.js le message répertorié dans le fichier.

Étant donné que ce fichier ne doit pas être modifié directement, pour remplacer ces messages par du texte localisé, vous input devez ajouter un attribut similaire à la balise et y définir du data-val-XXXX texte localisé. La partie XXXX contient la clé de la figure jquery.validate.js ci-dessus. (p. ex. data-val-number etc.)

data-val-XXXX Pour définir du texte localisé sur des attributs dans

Localisons le message lorsque des caractères non numériques sont inclus dans le champ de saisie Age à titre d’exemple. UserViewModel.Age a Range un attribut et le code HTML de sortie ajoute data-val-range l’attribut d’entrée.

<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>

Toutefois, data-val-number le message de vérification numérique n’est pas localisé car il n’y a pas d’attribut. Vous allez ajouter un autre attribut du côté du modèle pour imprimer également l’attribut localisé ajouté au data-val-number message.

La première étape consiste à créer une classe d’attributs qui vérifie s’il s’agit IntAttribute d’un nombre : Il est limité à ici, mais vous int êtes libre de le changer en fonction de la situation. Le fichier de code peut être n’importe où, mais cette fois, il se trouve dans le dossier Adaptateur.

using System.ComponentModel.DataAnnotations;

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

Si la valeur définie est int un type, il s’agit d’un attribut qui n’est qu’un verdict normal, mais en pratique, il est 100% normal s’il est défini sur une propriété de type int, de sorte que le traitement de cette classe elle-même n’a aucun sens. Le but de cette période est d’afficher des messages d’erreur localisés dans le client.

Ensuite, créez la classe Adapter suivante IntAttributeAdapter : Il se trouve également dans le dossier Adaptateur.

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

Le point ici est AddValidation la méthode décrite dans la MergeAttribute méthode.

Fusion d’attributs en tant qu’attributs de la balise d’entrée data-val-number pour les renvoyer au client. La valeur à définir dans l’attribut définit un message d’erreur localisé.

Retournez cet adaptateur dans la classe que vous avez créée CustomValidationAttributeAdapterProvider précédemment.

// 省略

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 la valeur à valider IntAttribute est , renvoyez celle que vous avez créée IntAttributeAdapter précédemment. La propriété avec l’attribut Int est maintenant ajoutée lorsqu’elle est affichée data-val-number côté client.

Enfin, UserViewModel.Age IntAttribute ajoutez à .

Une fois que vous avez corrigé votre code, essayez-le pour voir si vous souhaitez le vérifier.

<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>

Il existe d’autres messages non localisés, mais vous pouvez les localiser dans les méthodes décrites ci-dessus. Ajoutez du code lorsque vous en avez besoin.