Dukungan multibahasa untuk pesan default ditampilkan selama validasi input

Tanggal pembuatan halaman :

lingkungan

inti ASP.NET
  • 5.0 MVC

Pada awalnya

Dukungan multibahasa dari pesan validasi input default dengan Tips ini mungkin tidak lengkap. Tolong berurusan dengan yang hilang.

Juga, mengubah bahasa tergantung pada keadaan sesi atau status cache mungkin masih muncul dalam teks dari bahasa sebelumnya.

Ada beberapa cara untuk mengubah pesan validasi input default, yang masing-masing dapat digabungkan. Ini bukan salah satu dari mereka, jadi jika Anda ingin mengubahnya dengan andal, Anda harus sesuai dengan semua pola.

Premis

Tips ini ditulis sebagai memahami tips berikut:

Juga, jika Anda membuat proyek baru, Anda harus menambahkan file dan kode berikut berdasarkan tips di atas.

  • Buat file SharedResource.resx (+en, es). (Karena hanya pesan dari Tips ini yang diterjemahkan, isinya mungkin kosong.)
  • Membuat file SharedResource.cs
  • Menambahkan kode lokalisasi ke Startup.ConfigureServices
  • Menambahkan kode lokalisasi ke Startup.Configure
  • Ditambahkan UserViewModel (kali ini, kami tidak secara eksplisit memasukkan pesan default dalam berbagai bahasa)
  • Menambahkan tindakan dan tampilan layar buatan pengguna (Create.cshtml)

Kode awal

Berdasarkan asumsi di atas, setiap kode harus sebagai berikut:

Startup.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>
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 { }
}

Pesan pengikatan model

Jika Anda mengatur jenis properti model int ke atau dan mengikatnya ke DateTime tampilan, dan mencoba mendaftarkannya tanpa tipe, Anda akan melihat pesan seperti "Nilai '' tidak valid.". Pesan-pesan ini muncul ketika string kosong int tidak dapat terikat, misalnya, dalam model. Karena ini adalah waktu yang tidak dapat terikat, itu terjadi hanya sebelum validasi nilai lainnya dan pemrosesan sisi server.

Pesan-pesan ini DefaultModelBindingMessageProvider didefinisikan sebagai .

Startup.cs Tambahkan ke argumen metode yang telah didefinisikan sejak awal services.AddControllersWithViews tahun 2000, dan kemudian Action tambahkan. Anda dapat multibahasa dengan mengatur lulus dalam options ModelBindingMessageProvider argumen.

Ada 11 jenis pesan yang dapat Anda atur, jadi Anda harus terlebih dahulu mendefinisikan pesan sebagai berikut: SharedResource.resx Nama kuncinya opsional. Pesan bahasa Inggris default di kolom komentar adalah (komentar tidak harus disertakan). Anda tidak perlu menerjemahkan setiap bahasa, jadi Anda harus melakukan terjemahan sendiri (kode sampel juga menyertakan SharedResource.resx yang diterjemahkan).

Komentar Nilai Nama
ModelBinding_AttemptedValueIsInvalid '{0}' adalah nilai yang tidak valid dalam {1}. Nilai '{0}' tidak berlaku untuk {1}.
ModelBinding_MissingBindRequiredValue Nilai untuk {0} tidak ditentukan. Nilai untuk parameter atau properti '{0}' tidak disediakan.
ModelBinding_MissingKeyOrValue Diperlukan. Nilai diperlukan.
ModelBinding_MissingRequestBodyRequiredValue Permintaan harus memiliki tubuh. Badan permintaan yang tidak kosong diperlukan.
ModelBinding_NonPropertyAttemptedValueIsInvalid '{0}' tidak valid. Nilai '{0}' tidak valid.
ModelBinding_NonPropertyUnknownValueIsInvalid Nilainya tidak valid. Nilai yang disediakan tidak valid.
ModelBinding_NonPropertyValueMustBeANumber Nomor harus ditentukan. Bidang harus menjadi angka.
ModelBinding_UnknownValueIsInvalid Nilai {0} tidak valid. Nilai yang disediakan tidak valid untuk {0}.
ModelBinding_ValueIsInvalid '{0}' tidak valid. Nilai '{0}' tidak valid.
ModelBinding_ValueMustBeANumber {0} harus menjadi angka. Bidang {0} harus menjadi angka.
ModelBinding_ValueMustNotBeNull Input yang diperlukan. Nilai '{0}' tidak valid.

Perbaiki startup.cs sebagai berikut:

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

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

Poin utamanya adalah services.AddControllersWithViews menambahkan a ke metode yang menerima option Action , options.ModelBindingMessageProvider Saya menetapkan teks lokal untuk setiap metode Set di .

Teks yang diterjemahkan IStringLocalizer diambil dari . Tidak ada cara untuk menerima IStringLocalizer kode langsung, yang merupakan sedikit kode bulat. SharedResource.resx Jika Anda menghasilkan kode dari, Anda akan mendapatkan nilai lokal langsung dari itu.

IStringLocalizer Kunci yang Anda tentukan SharedResource.resx menentukan kunci yang Anda tambahkan ke . Atur agar sesuai dengan isi setiap metode Set.

Juga, karena setiap pesan memiliki argumen string format, seperti , saya {0}{1} menggunakan Simple dengan sehingga Anda dapat mengganti nilai yang string.Format Func diterima.

Melokalisasi pesan validasi default

Pesan kesalahan default ketika atribut Required properti model diatur ke atau lebih ditentukan oleh kerangka kerja dan pada dasarnya dalam bahasa StringLength Inggris.

Ini dapat IValidationAttributeAdapterProvider dilokalisasi dengan mendefinisikan kelas yang berasal dari antarmuka.

SharedResource.resxPertama-tama, daftarkan teks yang multibahasa. Nama kuncinya sewenang-wenang, tetapi untuk menyederhanakan program, daftarkan dalam bentuk "Validator_< nama atribut validasi>".

Komentar Nilai Nama
Validator_CompareAttribute {0} dan {1} tidak cocok. '{0}' dan '{1}' tidak cocok.
Validator_CreditCardAttribute {0} bukan nomor kartu yang valid. Bidang {0} bukanlah nomor kartu kredit yang valid.
Validator_DataTypeAttribute_Date Masukkan tanggal yang valid. Silakan masukkan tanggal yang valid.
Validator_EmailAddressAttribute {0} bukan alamat email yang valid. Bidang {0} bukanlah alamat email yang valid.
Validator_FileExtensionsAttribute {0} hanya menerima file dengan ekstensi berikut: : {1} Bidang {0} hanya menerima file dengan ekstensi berikut: {1}
Validator_MaxLengthAttribute {0} harus berupa tipe string atau array dengan panjang maksimum '{1}'. Bidang {0} harus berupa tipe string atau array dengan panjang maksimum '{1}'.
Validator_MinLengthAttribute {0} harus berupa tipe string atau array dengan panjang minimum '{1}'. Bidang {0} harus berupa tipe string atau array dengan panjang minimum '{1}'.
Validator_PhoneAttribute {0} bukan nomor telepon yang valid. Bidang {0} bukanlah nomor telepon yang valid.
Validator_RangeAttribute {0} harus berkisar dari {1} hingga {2}. Lapangan {0} harus antara {1} dan {2}.
Validator_RegularExpressionAttribute {0} harus mencocokkan ekspresi reguler '{1}'. Bidang {0} harus sesuai dengan ekspresi reguler '{1}'.
Validator_RequiredAttribute {0} diperlukan. Bidang {0} diperlukan.
Validator_StringLengthAttribute {0} harus berada dalam {1} digit. Bidang {0} harus berupa string dengan panjang maksimum {1}.
Validator_UrlAttribute {0} bukan URL yang valid. Bidang {0} bukanlah URL http, https, atau ftp yang memenuhi syarat.
Validator_StringLengthAttributeWithMin {0} harus setidaknya {2} {1} digit. Bidang {0} harus berupa string dengan panjang maksimum {1} dan panjang minimum {2}.

Selanjutnya, buat kelas AdapterProvider berikut:

Nama kelasnya sewenang-wenang, tapi kali ini CustomValidationAttributeAdapterProvider saya akan menulis kelas yang disebut dan menulis kode sebagai berikut: Lokasi file kode sewenang-wenang, tetapi sampel ditempatkan dalam folder yang disebut Adaptor.

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

Jika Anda mengatur properti model ke Required atau, metode ini diperlukan untuk setiap StringLength IValidationAttributeAdapterProvider.GetAttributeAdapter validasi.

ErrorMessageResourceName Jika properti berisi nilai, kode di sisi model sudah memiliki pesan atau kunci lokalisasi, sehingga kembali seperti apa pun.

Jika kosong, cocokkan nama kelas awalan dan atribut validasi untuk menjadikannya kunci lokalisasi, lalu ambil teks lokal dari kunci dan atur ErrorMessage ke . Hal ini memungkinkan sebagian besar pesan default untuk dilokalisasi.

Selanjutnya, daftarkan kelas ini di startup .cs. Pada dasarnya, Anda dapat menambahkannya sebagai berikut.

// 省略

using Microsoft.AspNetCore.Mvc.DataAnnotations;

namespace LocalizationDefaultValidation
{
  public class Startup
  {
    // 省略

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

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

Jalankan untuk memastikan itu bekerja dengan baik.

Mengubah pesan yang sudah ada menurut parameter

Misalnya, StringLength pesan atribut dilokalisasi, seperti "{0} harus dalam {1} digit." MinimumLength Jika properti diatur, Anda mungkin ingin mengubah {0} sebagai "Tentukan {2} digit atau lebih {1} dalam sejumlah digit".

Dalam CustomValidationAttributeAdapterProvider hal ini, memperpanjang kelas sebagai berikut:

// 省略

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

      // 省略
    }
  }
}

Karena metode ini diperlukan untuk setiap GetAttributeAdapter validasi, jika variabel yang dilewati attribute adalah StringLengthAttribute MinimumLength atribut, periksa properti, Jika diatur, dapatkan pesan yang juga memperhitungkan jumlah minimum digit dan ErrorMessage ganti.

Jika Anda menjalankannya, periksa dan pesan berubah, tidak apa-apa.

Pesan generik lainnya

Beberapa pesan tidak terlokalisasi setelah Anda melakukannya sejauh ini. Misalnya, pesan yang ditentukan hanya di Javascript.

Targetnya adalah jquery.validate.js pesan yang tercantum dalam file.

Karena file ini tidak boleh diedit secara langsung, untuk mengganti pesan ini dengan teks lokal, Anda input harus menambahkan atribut seperti tag dan mengatur teks lokal di data-val-XXXX sana. Bagian XXXX berisi kunci pada gambar di jquery.validate.js atas. data-val-number (Misalnya, dll.)

data-val-XXXX Untuk mengatur teks lokal ke atribut dalam

Mari kita lokalkan pesan saat karakter nonnumerik disertakan dalam bidang input Usia sebagai contoh. UserViewModel.Age memiliki Range atribut, dan output HTML data-val-range menambahkan atribut 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>

Namun, data-val-number pesan pemeriksaan numerik tidak terlokalisasi karena tidak ada atribut. Anda akan menambahkan atribut lain di sisi model untuk mencetak atribut pesan-ditambahkan lokal data-val-number juga.

Langkah pertama adalah membuat kelas atribut yang memeriksa apakah itu IntAttribute angka: Ini terbatas di sini, tetapi Anda int bebas untuk mengubahnya tergantung pada situasinya. File kode bisa di mana saja, tapi kali ini ada di folder Adaptor.

using System.ComponentModel.DataAnnotations;

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

Jika nilai yang ditetapkan adalah int jenis, itu adalah atribut yang hanya putusan normal, tetapi dalam praktiknya itu 100% normal jika diatur ke properti jenis int, sehingga pemrosesan kelas ini sendiri tidak masuk akal. Tujuan kali ini adalah untuk menampilkan pesan kesalahan lokal di klien.

Selanjutnya, buat kelas Adaptor IntAttributeAdapter berikut: Ini juga ada di folder Adaptor.

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

Intinya di sini adalah AddValidation metode yang dijelaskan dalam MergeAttribute metode.

Menggabungkan atribut sebagai atribut tag input untuk kembali data-val-number ke klien. Nilai yang akan diatur dalam atribut mengatur pesan kesalahan lokal.

Kembalikan adaptor ini di kelas yang Anda buat CustomValidationAttributeAdapterProvider sebelumnya.

// 省略

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

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

Jika nilai yang akan divalidasi IntAttribute adalah, kembalikan yang Anda buat IntAttributeAdapter sebelumnya. Properti dengan atribut Int sekarang ditambahkan ketika ditampilkan di data-val-number sisi klien.

Akhirnya, UserViewModel.Age IntAttribute tambahkan ke .

Setelah Anda memperbaiki kode Anda, cobalah untuk melihat apakah Anda ingin memeriksanya.

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

Ada pesan non-lokal lainnya, tetapi Anda dapat melokalisasinya dalam metode yang dijelaskan di atas. Tambahkan kode saat Anda membutuhkannya.