Wielojęzyczna obsługa komunikatów domyślnych wyświetlanych podczas sprawdzania poprawności danych wejściowych

Data utworzenia strony :

środowisko

Rdzeń ASP.NET
  • 5,0 MVC

Na początku

Wielojęzyczna obsługa domyślnych komunikatów sprawdzania poprawności danych wejściowych za pomocą tych porad może nie być zakończona. Proszę zająć się brakującymi.

Ponadto zmiana języka w zależności od stanu sesji lub stanu pamięci podręcznej może nadal pojawiać się w tekście z poprzedniego języka.

Istnieje kilka sposobów zmiany domyślnego komunikatu sprawdzania poprawności danych wejściowych, z których każdy można połączyć. Nie jest to jeden z nich, więc jeśli chcesz go niezawodnie zmienić, musisz korespondować ze wszystkimi wzorcami.

przesłanka

Ta wskazówka jest napisana jako zrozumienie następujących wskazówek:

Ponadto, jeśli tworzysz nowy projekt, musisz dodać następujące pliki i kod na podstawie powyższych wskazówek.

  • Utwórz plik SharedResource.resx (+en, es). (Ponieważ przetłumaczona jest tylko wiadomość z tych wskazówek, zawartość może być pusta).
  • Tworzenie pliku SharedResource.cs
  • Dodawanie kodu lokalizacji do startup.ConfigureServices
  • Dodaj kod lokalizacji do Startup.Configure
  • Dodano UserViewModel (tym razem nie kluczujemy jawnie domyślnych komunikatów w wielu językach)
  • Dodano utworzone przez użytkownika akcje i widoki ekranu (Create.cshtml)

Kod startowy

W oparciu o powyższe założenia każdy kod jest następujący:

Uruchamianie.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()));
  }
}
Indeks.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>
Utwórz.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 { }
}

Modelowanie komunikatów wiążących

Jeśli ustawisz typ właściwości modelu int na lub i powiążesz go z DateTime widokiem i spróbujesz zarejestrować go bez wpisu, zobaczysz komunikat taki jak "Wartość '' jest nieprawidłowa.". Komunikaty te pojawiają się, gdy pusty ciąg int nie może być powiązany, na przykład w modelu. Ponieważ jest to czas, którego nie można wiązać, występuje tylko przed sprawdzaniem poprawności innych wartości i przetwarzaniem po stronie serwera.

Te komunikaty są DefaultModelBindingMessageProvider zdefiniowane jako .

Startup.cs Dodaj do argumentów metody, które były zdefiniowane od początku services.AddControllersWithViews w programie , a następnie dodaj Action . Możesz być wielojęzyczny, ustawiając przekazany w options ModelBindingMessageProvider argumencie.

Istnieje 11 typów wiadomości, które można ustawić, więc najpierw należy zdefiniować wiadomość w następujący sposób: SharedResource.resx Nazwa klucza jest opcjonalna. Domyślna wiadomość w języku angielskim w kolumnie komentarzy to (komentarze nie muszą być uwzględniane). Nie będziesz musiał tłumaczyć każdego języka, więc powinieneś wykonać własne tłumaczenie (przykładowy kod zawiera również przetłumaczony SharedResource.resx).

Komentarz
do wartości nazwy
ModelBinding_AttemptedValueIsInvalid "{0}" jest nieprawidłową wartością w {1}. Wartość "{0}" nie jest prawidłowa dla {1}.
ModelBinding_MissingBindRequiredValue Wartość {0} nie jest określona. Wartość parametru lub właściwości "{0}" nie została podana.
ModelBinding_MissingKeyOrValue Wymagane. Wymagana jest wartość.
ModelBinding_MissingRequestBodyRequiredValue Wniosek musi mieć organ. Wymagana jest niepusty korpus żądania.
ModelBinding_NonPropertyAttemptedValueIsInvalid "{0}" jest nieprawidłowe. Wartość "{0}" jest nieprawidłowa.
ModelBinding_NonPropertyUnknownValueIsInvalid Wartość jest nieprawidłowa. Podawana wartość jest nieprawidłowa.
ModelBinding_NonPropertyValueMustBeANumber Należy podano numer. Pole musi być liczbą.
ModelBinding_UnknownValueIsInvalid Wartość {0} jest nieprawidłowa. Podawana wartość jest nieprawidłowa dla {0}.
ModelBinding_ValueIsInvalid "{0}" jest nieprawidłowe. Wartość "{0}" jest nieprawidłowa.
ModelBinding_ValueMustBeANumber {0} musi być liczba. Pole {0} musi być liczbą.
ModelBinding_ValueMustNotBeNull Wymagane dane wejściowe. Wartość "{0}" jest nieprawidłowa.

Napraw uruchamianie.cs w następujący sposób:

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

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

Kluczowym punktem jest services.AddControllersWithViews dodanie a do metody, która odbiera option Action , options.ModelBindingMessageProvider Ustawiam zlokalizowany tekst dla każdej metody Set w programie .

Przetłumaczony tekst jest IStringLocalizer pobierany z pliku . Nie ma możliwości otrzymania bezpośredniego IStringLocalizer kodu, który jest trochę okrągłym kodem. SharedResource.resx Jeśli generujesz kod, otrzymasz zlokalizowane wartości bezpośrednio z niego.

IStringLocalizer Klucz określony w SharedResource.resx polu Określa klucz dodany do pliku . Ustaw go tak, aby pasował do zawartości każdej metody Set.

Ponadto, ponieważ każda wiadomość ma argument ciągu formatu, taki jak , {0}{1} używam Simple z, aby można było zastąpić string.Format Func odebrane wartości.

Lokalizuj domyślne komunikaty sprawdzania poprawności

Domyślny komunikat o błędzie, gdy atrybuty Required właściwości modelu są ustawione na lub tak, jest określany przez strukturę i jest StringLength zasadniczo w języku angielskim.

Można IValidationAttributeAdapterProvider je zlokalizować, definiując klasę pochodzącą z interfejsu.

Przede SharedResource.resx wszystkim zarejestruj tekst, który jest wielojęzyczny. Nazwa klucza jest dowolna, ale aby uprościć program, zarejestruj go w postaci "Validator_< nazwa atrybutu sprawdzania poprawności>".

Komentarz
do wartości nazwy
Validator_CompareAttribute {0} i {1} nie pasują do siebie. "{0}" i "{1}" nie pasują do siebie.
Validator_CreditCardAttribute {0} nie jest prawidłowy numer karty. Pole {0} nie jest prawidłowym numerem karty kredytowej.
Validator_DataTypeAttribute_Date Wprowadź prawidłową datę. Podaj prawidłową datę.
Validator_EmailAddressAttribute {0} jest nieprawidłowy adres e-mail. Pole {0} nie jest prawidłowym adresem e-mail.
Validator_FileExtensionsAttribute {0} akceptuje tylko pliki z następującymi rozszerzeniami: : {1} Pole {0} akceptuje tylko pliki z następującymi rozszerzeniami: {1}
Validator_MaxLengthAttribute {0} musi być typu ciągu lub tablicy o maksymalnej długości "{1}". Pole {0} musi być typem ciągu lub tablicy o maksymalnej długości "{1}".
Validator_MinLengthAttribute {0} musi być typu ciągu lub tablicy o minimalnej długości "{1}". Pole {0} musi być typem ciągu lub tablicy o minimalnej długości "{1}".
Validator_PhoneAttribute {0} nie jest prawidłowym numerem telefonu. Pole {0} nie jest prawidłowym numerem telefonu.
Validator_RangeAttribute {0} muszą wynosić od {1} do {2}. {0} pola musi znajdować się w zakresie od {1} do {2}.
Validator_RegularExpressionAttribute {0} musi być zgodny z wyrażeniem regularnym "{1}". Pole {0} musi być zgodne z wyrażeniem regularnym "{1}".
Validator_RequiredAttribute {0} jest wymagane. Pole {0} jest wymagane.
Validator_StringLengthAttribute {0} musi mieścić się w {1} cyfrach. Pole {0} musi być ciągiem o maksymalnej długości {1}.
Validator_UrlAttribute {0} nie jest prawidłowym adresem URL. Pole {0} nie jest prawidłowym w pełni kwalifikowanym adresem URL http, https lub ftp.
Validator_StringLengthAttributeWithMin {0} musi być co najmniej {2} {1} cyfr. Pole {0} musi być ciągiem o maksymalnej długości {1} i minimalnej długości {2}.

Następnie utwórz następującą klasę AdapterProvider:

Nazwa klasy jest dowolna, ale tym razem CustomValidationAttributeAdapterProvider napiszę klasę o nazwie i napiszę kod w następujący sposób: Lokalizacja pliku kodu jest dowolna, ale próbka jest umieszczana w folderze o nazwie 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);
    }
  }
}

Jeśli ustawisz właściwości modelu na Required lub , metoda jest wywoływana dla każdej StringLength IValidationAttributeAdapterProvider.GetAttributeAdapter walidacji.

ErrorMessageResourceName Jeśli właściwość zawiera wartość, kod po stronie modelu ma już komunikat lub klucz lokalizacji, więc zwraca się tak, jak jest.

Jeśli jest pusty, dopasuj nazwy klas prefiksu i atrybutu sprawdzania poprawności, aby uczynić go kluczem lokalizacji, a następnie pobierz zlokalizowany tekst z klucza i ustaw go ErrorMessage na . Umożliwia to zlokalizowanie większości wiadomości domyślnych.

Następnie zarejestruj tę klasę w .cs startowym. Zasadniczo możesz dodać go w następujący sposób.

// 省略

using Microsoft.AspNetCore.Mvc.DataAnnotations;

namespace LocalizationDefaultValidation
{
  public class Startup
  {
    // 省略

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

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

Uruchom go, aby upewnić się, że działa poprawnie.

Zmienianie istniejącej wiadomości według parametrów

Na przykład StringLength komunikat atrybutu jest zlokalizowany, na przykład "{0} musi znajdować się w {1} cyfrach". MinimumLength Jeśli właściwość jest ustawiona, możesz zmienić {0} jako "Określ {2} cyfr lub więcej {1} w obrębie kilku cyfr".

W CustomValidationAttributeAdapterProvider takim przypadku rozszerz klasę w następujący sposób:

// 省略

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

      // 省略
    }
  }
}

Ponieważ metoda jest wywoływana dla każdej GetAttributeAdapter walidacji, jeśli attribute StringLengthAttribute przekazana zmienna była MinimumLength atrybutem, sprawdź właściwość, Jeśli jest ustawiona, otrzymasz komunikat, który uwzględnia również minimalną liczbę cyfr i ErrorMessage zastępuje .

Jeśli go uruchomisz, zaznaczysz, a komunikat umieje się, wszystko jest w porządku.

Inne wiadomości ogólne

Niektóre wiadomości nie są lokalizowane po zrobieniu tego do tej pory. Na przykład wiadomości, które są określane tylko w Javascript.

Obiektem docelowym jest jquery.validate.js komunikat wymieniony w pliku.

Ponieważ ten plik nie powinien być edytowany bezpośrednio, aby zastąpić te wiadomości zlokalizowanym input tekstem, należy dodać atrybut podobny do znacznika i ustawić data-val-XXXX tam zlokalizowany tekst. Część XXXX zawiera klucz na jquery.validate.js powyższym rysunku. (np. data-val-number itp.)

data-val-XXXX Aby ustawić zlokalizowany tekst na atrybuty w

Zlokalizujmy wiadomość, gdy znaki nieliczbowe są zawarte w polu Wejścia Wiek jako przykład. UserViewModel.Age ma Range atrybut, a wyjściowy kod HTML data-val-range dołącza atrybut input.

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

Jednak data-val-number numeryczny komunikat kontrolny nie jest zlokalizowany, ponieważ nie ma atrybutu. Dodasz kolejny atrybut po stronie modelu, aby wydrukować również zlokalizowany atrybut dodany do data-val-number wiadomości.

Pierwszym krokiem jest utworzenie klasy atrybutów, która sprawdza, czy jest to IntAttribute liczba: Jest on ograniczony do tutaj, ale możesz int go zmienić w zależności od sytuacji. Plik kodu może znajdować się w dowolnym miejscu, ale tym razem znajduje się w folderze Adapter.

using System.ComponentModel.DataAnnotations;

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

Jeśli ustawiona wartość jest int typem, jest to atrybut, który jest tylko normalnym werdyktem, ale w praktyce jest w 100% normalny, jeśli jest ustawiony na właściwość typu int, więc przetwarzanie tej klasy samo w sobie nie ma sensu. Celem tego czasu jest wyświetlenie zlokalizowanych komunikatów o błędach w kliencie.

Następnie utwórz następującą IntAttributeAdapter klasę adaptera: Znajduje się on również w folderze Adapter.

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

Chodzi tutaj AddValidation o metodę opisaną w MergeAttribute metodzie.

Scalanie atrybutów jako atrybutów znacznika wejściowego w celu data-val-number powrotu do klienta. Wartość ustawiona w atrybcie ustawia zlokalizowany komunikat o błędzie.

Zwróć tę kartę w utworzonej CustomValidationAttributeAdapterProvider wcześniej klasie.

// 省略

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

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

Jeśli wartość do sprawdzenia poprawności IntAttribute to , zwróć utworzoną wcześniej IntAttributeAdapter wartość. Właściwość z atrybutem Int jest teraz dodawana, gdy jest wyświetlana data-val-number po stronie klienta.

Na koniec UserViewModel.Age IntAttribute dodaj do .

Po naprawieniu kodu wypróbuj go, aby sprawdzić, czy chcesz go wyewidencjonować.

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

Istnieją inne niezlokalizowane wiadomości, ale można je zlokalizować w metodach opisanych powyżej. Dodawaj kod, kiedy go potrzebujesz.