Sokongan berbilang bahasa untuk mesej lalai dipaparkan semasa pengesahihan input

Tarikh penciptaan halaman :

Persekitaran

Teras ASP.NET
  • 5.0 MVC

Pada mulanya

Sokongan berbilang bahasa bagi mesej pengesahihan input lalai dengan Petua ini mungkin tidak lengkap. Tolong berurusan dengan yang hilang.

Juga, menukar bahasa bergantung pada keadaan sesi atau keadaan cache mungkin masih muncul dalam teks dari bahasa sebelumnya.

Terdapat beberapa cara untuk menukar mesej pengesahan input lalai, masing-masing boleh digabungkan. Ia bukan salah seorang daripada mereka, jadi jika anda ingin mengubahnya dengan pasti, anda perlu sesuai dengan semua corak.

Premis

Petua ini ditulis sebagai memahami petua berikut:

Juga, jika anda membuat projek baru, anda mesti telah menambah fail dan kod berikut berdasarkan petua di atas.

  • Cipta fail SharedResource.resx (+en, es). (Oleh kerana hanya mesej Petua ini diterjemahkan, kandungannya mungkin kosong.)
  • Cipta fail SharedResource.cs
  • Tambah kod penyetempatan ke Startup.ConfigureServices
  • Tambah kod penyetempatan ke Startup.Configure
  • Tambah UserViewModel (kali ini, kami tidak secara jelas memasukkan mesej lalai dalam pelbagai bahasa)
  • Tambah tindakan dan pandangan skrin yang dicipta pengguna (Create.cshtml)

Memulakan kod

Berdasarkan andaian di atas, setiap kod adalah seperti berikut:

Permulaan.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();

      // 省略
    }
  }
}
UtamaMuaf.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 { }
}

Mesej pengikatan model

Jika anda mengesetkan jenis sifat model int kepada atau dan mengikatnya ke DateTime paparan, dan cuba mendaftarkannya tidak ditaip, anda akan melihat mesej seperti "Nilai'' tidak sah.". Mesej ini muncul apabila rentetan kosong int tidak boleh diikat, contohnya, dalam model. Kerana ia adalah masa yang tidak boleh terikat, ia berlaku hanya sebelum pengesahan nilai lain dan pemprosesan bahagian pelayan.

Mesej ini DefaultModelBindingMessageProvider ditakrifkan sebagai .

Startup.cs Tambah kepada hujah kaedah yang telah ditakrifkan sejak permulaan services.AddControllersWithViews dalam , dan kemudian tambah Action . Anda boleh berbilang bahasa dengan menetapkan yang diluluskan dalam options ModelBindingMessageProvider argumen.

Terdapat 11 jenis mesej yang boleh anda tetapkan, jadi anda mesti terlebih dahulu menentukan mesej seperti berikut: SharedResource.resx Nama kekunci adalah pilihan. Mesej Bahasa Inggeris lalai dalam lajur komen adalah (komen tidak perlu disertakan). Anda tidak perlu menterjemahkan setiap bahasa, jadi anda harus melakukan terjemahan anda sendiri (kod sampel juga termasuk SharedResource.resx yang diterjemahkan).

Komen Nilai Nama
ModelBinding_AttemptedValueIsInvalid '{0}' ialah nilai tidak sah dalam {1}. Nilai '{0}' tidak sah untuk {1}.
ModelBinding_MissingBindRequiredValue Nilai untuk {0} tidak dinyatakan. Nilai untuk parameter atau sifat '{0}' tidak disediakan.
ModelBinding_MissingKeyOrValue Diperlukan. Nilai diperlukan.
ModelBinding_MissingRequestBodyRequiredValue Permintaan mesti mempunyai badan. Badan permintaan bukan kosong diperlukan.
ModelBinding_NonPropertyAttemptedValueIsInvalid '{0}' tidak sah. Nilai '{0}' tidak sah.
ModelBinding_NonPropertyUnknownValueIsInvalid Nilai tidak sah. Nilai yang dibekalkan tidak sah.
ModelBinding_NonPropertyValueMustBeANumber Nombor mesti dinyatakan. Medan mestilah nombor.
ModelBinding_UnknownValueIsInvalid Nilai {0} tidak sah. Nilai yang dibekalkan tidak sah untuk {0}.
ModelBinding_ValueIsInvalid '{0}' tidak sah. Nilai '{0}' tidak sah.
ModelBinding_ValueMustBeANumber {0} mesti menjadi nombor. Medan {0} mestilah nombor.
ModelBinding_ValueMustNotBeNull Input yang diperlukan. Nilai '{0}' tidak sah.

Betulkan permulaan.cs seperti 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
      });

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

Perkara utama adalah services.AddControllersWithViews untuk menambah a kepada kaedah yang menerima option Action , options.ModelBindingMessageProvider Saya menetapkan teks setempat untuk setiap kaedah Set dalam .

Teks yang diterjemahkan IStringLocalizer diambil dari . Tidak ada cara untuk menerima IStringLocalizer kod langsung, yang merupakan sedikit kod bulat. SharedResource.resx Jika anda menjana kod daripada, anda akan mendapat nilai setempat terus daripadanya.

IStringLocalizer Kekunci yang anda tentukan menentukan SharedResource.resx kekunci yang anda tambah . Tetapkannya agar sepadan dengan kandungan setiap kaedah Set.

Juga, kerana setiap mesej mempunyai hujah rentetan format, seperti , saya {0}{1} menggunakan Mudah dengan supaya anda boleh menggantikan nilai yang string.Format Func diterima.

Menyetempatkan mesej pengesahihan lalai

Mesej ralat lalai apabila atribut Required sifat model disetkan kepada atau lebih ditentukan oleh rangka kerja dan StringLength pada dasarnya dalam bahasa Inggeris.

Ini boleh IValidationAttributeAdapterProvider disetempatkan dengan menentukan kelas yang berasal dari antara muka.

Pertama SharedResource.resx sekali, daftarkan teks yang berbilang bahasa. Nama kekunci adalah sewenang-wenangnya, tetapi untuk memudahkan program, mendaftarkannya dalam bentuk "Validator_< nama atribut pengesahan>".

Komen Nilai Nama
Validator_CompareAttribute {0} dan {1} tidak sepadan. '{0}' dan '{1}' tidak sepadan.
Validator_CreditCardAttribute {0} bukan nombor kad yang sah. Medan {0} bukan nombor kad kredit yang sah.
Validator_DataTypeAttribute_Date Masukkan tarikh yang sah. Sila masukkan tarikh yang sah.
Validator_EmailAddressAttribute {0} bukan alamat e-mel yang sah. Medan {0} bukan alamat e-mel yang sah.
Validator_FileExtensionsAttribute {0} hanya menerima fail dengan sambungan berikut: : {1} Medan {0} hanya menerima fail dengan sambungan berikut: {1}
Validator_MaxLengthAttribute {0} mestilah rentetan atau jenis tatasusunan dengan panjang maksimum '{1}'. Medan {0} mestilah rentetan atau jenis tatasusunan dengan panjang maksimum '{1}'.
Validator_MinLengthAttribute {0} mestilah rentetan atau jenis tatasusunan dengan panjang minimum '{1}'. Medan {0} mestilah rentetan atau jenis tatasusunan dengan panjang minimum '{1}'.
Validator_PhoneAttribute {0} bukan nombor telefon yang sah. Medan {0} bukan nombor telefon yang sah.
Validator_RangeAttribute {0} mesti berkisar dari {1} hingga {2}. Medan {0} mestilah antara {1} dan {2}.
Validator_RegularExpressionAttribute {0} mesti sepadan dengan ungkapan biasa '{1}'. Medan {0} mesti sepadan dengan ungkapan biasa '{1}'.
Validator_RequiredAttribute {0} diperlukan. Medan {0} diperlukan.
Validator_StringLengthAttribute {0} mesti berada dalam digit {1}. Medan {0} mestilah rentetan dengan panjang maksimum {1}.
Validator_UrlAttribute {0} bukan URL yang sah. Medan {0} bukan URL http, https atau ftp yang layak sepenuhnya yang sah.
Validator_StringLengthAttributeWithMin {0} mestilah sekurang-kurangnya {2} {1} digit. Medan {0} mestilah rentetan dengan panjang maksimum {1} dan panjang minimum {2}.

Seterusnya, cipta kelas AdapterProvider berikut:

Nama kelas adalah sewenang-wenangnya, tetapi kali ini CustomValidationAttributeAdapterProvider saya akan menulis kelas yang dipanggil dan menulis kod seperti berikut: Lokasi fail kod adalah sewenang-wenangnya, tetapi sampel diletakkan dalam folder yang dipanggil Penyesuai.

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 menetapkan sifat model kepada Required atau , kaedah dipanggil untuk setiap StringLength IValidationAttributeAdapterProvider.GetAttributeAdapter pengesahan.

ErrorMessageResourceName Jika sifat mengandungi nilai, kod pada sisi model sudah mempunyai mesej atau kekunci penyetempatan, jadi ia kembali seperti sedia ada.

Jika kosong, padankan nama kelas awalan dan atribut pengesahihan untuk menjadikannya kekunci penyetempatan, kemudian ambil teks setempat dari kekunci dan tetapkannya ErrorMessage kepada . Ini membolehkan kebanyakan mesej lalai disetempatkan.

Seterusnya, daftar kelas ini dalam .cs permulaan. Pada asasnya, anda boleh menambahnya seperti 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 benar.

Tukar mesej sedia ada mengikut parameter

Sebagai contoh, StringLength mesej atribut disetempatkan, seperti "{0} mesti berada dalam {1} digit." MinimumLength Jika sifat ditetapkan, anda mungkin mahu menukar {0} sebagai "Tentukan digit {2} atau lebih {1} dalam beberapa digit".

Dalam CustomValidationAttributeAdapterProvider kes itu, lanjutkan kelas seperti 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);
        }
      }

      // 省略
    }
  }
}

Oleh kerana kaedah dipanggil untuk setiap GetAttributeAdapter pengesahan, jika pemboleh ubah yang diluluskan attribute adalah StringLengthAttribute MinimumLength atribut, semak sifat, Jika ditetapkan, dapatkan mesej yang juga mengambil kira bilangan minimum digit dan ErrorMessage ganti .

Jika anda menjalankannya, semak dan mesej berubah, tidak mengapa.

Mesej generik lain

Sesetengah mesej tidak disetempatkan selepas anda berbuat demikian setakat ini. Sebagai contoh, mesej yang ditentukan hanya dalam Javascript.

Sasaran ialah jquery.validate.js mesej yang disenaraikan dalam fail.

Oleh kerana fail ini tidak boleh diedit secara langsung, untuk menggantikan mesej ini dengan teks disetempatkan, anda input mesti menambah atribut seperti tag dan menetapkan teks data-val-XXXX disetempatkan di sana. Bahagian XXXX mengandungi kunci dalam angka di jquery.validate.js atas. (cth. data-val-number dll.)

data-val-XXXX Untuk menetapkan teks disetempatkan kepada atribut dalam

Mari kita menyetempatkan mesej apabila aksara bukan angka disertakan dalam medan input Umur sebagai contoh. UserViewModel.Age mempunyai Range atribut, dan HTML output data-val-range menambah 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>

Walau bagaimanapun, data-val-number mesej semakan angka tidak disetempatkan kerana tiada atribut. Anda akan menambah atribut lain pada bahagian model untuk mencetak atribut tambah mesej data-val-number setempat juga.

Langkah pertama ialah mencipta kelas atribut yang menyemak sama ada ia adalah IntAttribute nombor: Ia terhad kepada di sini, tetapi anda int bebas untuk mengubahnya bergantung kepada keadaan. Fail kod boleh berada di mana-mana sahaja, tetapi kali ini ia berada dalam folder Penyesuai.

using System.ComponentModel.DataAnnotations;

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

Jika nilai set adalah int jenis, ia adalah atribut yang hanya keputusan biasa, tetapi dalam praktiknya ia adalah 100% normal jika ditetapkan kepada sifat jenis int, jadi pemprosesan kelas ini sendiri tidak masuk akal. Tujuan masa ini adalah untuk memaparkan mesej ralat setempat dalam pelanggan.

Seterusnya, cipta kelas Penyesuai IntAttributeAdapter berikut: Ini juga dalam folder Penyesuai.

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

Titik di sini adalah AddValidation kaedah yang diterangkan dalam kaedah MergeAttribute ini.

Menggabungkan atribut sebagai atribut tag input untuk kembali data-val-number kepada klien. Nilai untuk disetkan dalam atribut mengesetkan mesej ralat disetempatkan.

Kembalikan penyesuai ini dalam kelas yang anda cipta CustomValidationAttributeAdapterProvider sebelum ini.

// 省略

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 untuk disahkan IntAttribute adalah , kembalikan yang anda cipta IntAttributeAdapter sebelum ini. Sifat dengan atribut Int kini ditambah apabila ia dipaparkan pada data-val-number sisi klien.

Akhirnya, UserViewModel.Age IntAttribute tambah ke .

Sebaik sahaja anda telah membetulkan kod anda, cubalah untuk melihat sama ada anda mahu menyemaknya.

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

Terdapat mesej lain yang tidak disetempatkan, tetapi anda boleh menyetempatkannya dalam kaedah yang diterangkan di atas. Tambah kod apabila anda memerlukannya.