매개 변수 이름, 입력 유효성 검사 메시지 등에 사용되는 DataAnnotations에 대한 다국어 지원

페이지 생성 날짜 :

환경

ASP.NET 코어
  • 5.0 MVC

전제

이 팁은 다음 팁을 이해하는 것으로 작성되었습니다.

또한 새 프로젝트를 만드는 경우 위의 팁을 기반으로 다음 파일과 코드를 추가해야 합니다.

  • 공유리소스.resx(+en, es) 파일을 만듭니다. (내용이 비어 있을 수 있습니다.)
  • 공유 리소스.cs 파일 만들기
  • 시작에 지역화 코드 추가.ConfigureServices
  • 시작에 지역화 코드 추가.구성

시작 .cs 수정 사항

DataAnnotations와 관련된 추가 다국어 설정이 필요하므로 Startup.ConfigureServices 다음과 같이 메서드를 수정합니다.

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

입력 모델링

입력 화면에 바인딩할 모델을 만듭니다. 이 샘플에서는 다양한 입력 컨트롤을 수용할 수 있는 여러 속성을 만들었습니다. 만드는 데 문제가 있는 경우 두 가지를 만들 수 있습니다.

모델 폴더에서 사용자뷰모델 .cs 만듭니다.

코드는 다음과 같아야 합니다. 네임스페이스가 적절해야 합니다. 설정한 특성은 여러 언어에만 할당되므로 크게 관련이 없습니다. 다국어 지원은 나중에 수행됩니다.

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace LocalizationDataAnnotation.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]*$", ErrorMessage = "Password")]
    public string Password { get; set; }

    // 省略
  }

  // 省略
}

작업 만들기

사용자의 등록 화면을 표시하고 등록 작업을 수행하는 작업을 HomeController 만듭니다.

화면과 작업을 만드는 것이 이 팁의 주요 주제가 아니므로 적절합니다. 또한 실제 등록 절차는 수행되지 않습니다.

// 初期コードは省略

namespace LocalizationDataAnnotation.Controllers
{
  public class HomeController : Controller
  {
    // 初期コードは省略

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

    [HttpPost]
    public IActionResult Create(UserViewModel model)
    {
      // エラーなら差し戻し
      if (ModelState.IsValid == false) return View(model);

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

사용자 등록 화면 링크

View\Home\Index.cshtml에서 사용자 등록 화면에 대한 링크를 만듭니다. 화면 전환을 할 수 있는 한 무엇이든 쓸 수 있습니다.

asp-route-culture 특성은 지정된 언어로 페이지를 엽니다.

<!-- 初期コード省略 -->

<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 작업을 마우스 오른쪽 단추로 클릭하고 보기 추가를 선택합니다.

면도기 뷰를 선택합니다.

템플릿을 "만들기"와 모델 클래스 "UserViewModel"로 만듭니다. 샘플 코드를 사용하는 경우 UserViewModel 코드에 주석도 포함했습니다. 속성에 특정 특성을 넣으면 비계에서 코드를 생성할 때 오류가 발생합니다. 뷰를 만들기 전에 일시적으로 댓글을 달아주세요.

실제로 오류를 만들 때 오류가 발생하면 빈 Razor 보기를 만들고 다음 코드를 포함합니다.

@model LocalizationDataAnnotation.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">
      <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">
        <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");}
}

현재로서는 준비 단계로 지금까지 코드를 성공적으로 빌드하고 화면을 올바르게 보고 실행할 수 있습니다.

DataAnnotations에 대한 다국어 텍스트 등록

모든 속성은 설명하기가 길기 ID 때문에. Name 다른 속성은 비슷한 방식으로 다국어, 그래서 그들을 밖으로 시도.

다음 텍스트는 여기에서 여러 언어로 제공됩니다.

다국어 텍스트
ID 매개 변수 표시 이름 ID_DisplayName
ID가 입력되지 않은 오류 메시지 Validate_Required
ID 입력 문자가 20자를 초과할 때 오류 메시지 Validate_StringLength
ID를 비숫자 문자로 입력할 때 오류 메시지 Validate_Alphanumeric
이름 매개 변수 표시 이름 Name_DisplayName
이름에 50자 이상이 입력된 오류 메시지 Validate_StringLength

Validate_XXXXX 보편적이어야 합니다. 키 명명 규칙이 없으므로 자유롭게 첨부할 수 있습니다.

이러한 키를 기초로 사용하여 SharedResource.resx SharedResource.en.resx 번역된 텍스트를 SharedResource.es.resx 입력합니다.

공유 리소스.resx

이름
Validate_Required "{0}"이 필요합니다.
Validate_StringLength "{0}"에서 "{1}"까지 입력할 수 있습니다.
Validate_Alphanumeric {0} 단지 유수의 일 수 있습니다.
ID_DisplayName ID(거수)
Name_DisplayName 신원

공유리소스.en.resx

이름
Validate_Required "{0}"는 필수 입력입니다.
Validate_StringLength "{0}"에 입력할 수 있는 최대 문자 수는 "{1}"입니다.
Validate_Alphanumeric "{0}"에 대해 는 거숫자 문자만 입력할 수 있습니다.
ID_DisplayName ID(거숫자 문자)
Name_DisplayName 전체 이름

공유리소스.es.resx

이름
Validate_Required "{0}" 에스 우나 엔드라다 의무.
Validate_StringLength El número máximo de caracteres que se pueden ingresar en "{0}" es "{1}".
Validate_Alphanumeric 솔로 세 푸덴 잉레사 카락터 알파누메리코 파라 "{0}".
ID_DisplayName ID (카액터 알파누메리코)
Name_DisplayName 놈브레 컴플토

입력 유효성 검사 오류 메시지는 매개 변수 이름과 숫자를 {0} 및 {1} 대체합니다. 긍정적으로 사용해 봅시다.

데이터 어노션에 대한 다국어 응용 프로그램

모든 다국어 설정은 모델의 특성(DataAnnotations)에 적용됩니다.

매개 변수의 표시 이름에 적용하려면 Display Name 속성에 공유Resource.resx 키를 연결하고 지정합니다.

입력 유효성 검사 중에 오류 메시지에 적용하려면 ErrorMessage 각 유효성 검사 특성의 속성에 공유리소스.resx 키를 지정합니다.

public class UserViewModel
{
  [Required(ErrorMessage = "Validate_Required")]
  [Display(Name = "ID_DisplayName")]
  [StringLength(20, ErrorMessage = "Validate_StringLength")]
  [RegularExpression(@"^[0-9a-zA-Z]*$", ErrorMessage = "Validate_Alphanumeric")]
  public string ID { get; set; }

  [Display(Name = "Name_DisplayName")]
  [StringLength(50, ErrorMessage = "Validate_StringLength")]
  public string Name { get; set; }

  // 省略
}

이렇게 하면 설정이 완료됩니다. 그 후, 어떻게 작동하는지 확인하기 위해 실행합니다.

그런데 입력 문자의 수는 입력 태그의 특성에 의해 maxlength 제한됩니다. 메시지를 보려면 웹 브라우저의 개발자 도구와 같은 제한을 제거해야 합니다.

그런데 나이, 날짜 등을 비우면 몇 가지 메시지가 표시됩니다. 프레임워크 측에 설정된 메시지이지만 다른 팁에서는 여러 언어로 지원하는 방법을 보여 줍니다.

모든 코드의 경우

이 팁에는 일부 속성만 포함되어 있지만 샘플 코드는 html5 지원 입력 필드를 모두 다룹니다. 확인하려면 페이지 위의 링크를 참조하십시오.

아래 부분 으로 나열됨

사용자뷰모델.cs

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace LocalizationDataAnnotation.Models
{
  public class UserViewModel
  {
    [Required(ErrorMessage = "Validate_Required")]
    [Display(Name = "ID_DisplayName")]
    [StringLength(20, ErrorMessage = "Validate_StringLength")]
    [RegularExpression(@"^[0-9a-zA-Z]*$", ErrorMessage = "Validate_Alphanumeric")]
    public string ID { get; set; }

    [Display(Name = "Name_DisplayName")]
    [StringLength(50, ErrorMessage = "Validate_StringLength")]
    public string Name { get; set; }

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

    [Display(Name = "ConfirmPassword_DisplayName")]
    [StringLength(50, MinimumLength = 8, ErrorMessage = "Validate_StringLengthRange")]
    [DataType(DataType.Password)]
    [Compare(nameof(Password), ErrorMessage = "Validate_Compare")]
    public string ConfirmPassword { get; set; }

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

    [Display(Name = "Age_DisplayName")]
    [Required(ErrorMessage = "Validate_Required")]
    [Range(0, 150, ErrorMessage = "Validate_Range")]
    public int Age { get; set; }

    [Display(Name = "Gender_DisplayName")]
    [Required(ErrorMessage = "Validate_Required")]
    [EnumDataType(typeof(GenderType))]
    public GenderType Gender { get; set; }

    [Display(Name = "Birthday_DisplayName")]
    [DataType(DataType.Date)]
    public DateTime Birthday { get; set; }

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

    [Display(Name = "PostalCode_DisplayName")]
    [DataType(DataType.PostalCode)]
    public string PostalCode { get; set; }

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

    [Display(Name = "Money_DisplayName")]
    [DataType(DataType.Currency)]
    public decimal Money { get; set; }

    [Display(Name = "StartDateTime_DisplayName")]
    [DataType(DataType.DateTime)]
    public DateTime StartDateTime { get; set; }

    [Display(Name = "WakeUpTime_DisplayName")]
    [DataType(DataType.Time)]
    public TimeSpan WakeUpTime { get; set; }

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

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

    [Display(Name = "MyColor_DisplayName")]
    public string MyColor { get; set; }

    [Display(Name = "WorkingDays_DisplayName")]
    [MaxLength(5, ErrorMessage = "Validate_MaxLength")]
    [EnumDataType(typeof(DayOfWeek))]
    public DayOfWeek[] WorkingDays { get; set; }

    [Display(Name = "VacationDay_DisplayName")]
    [MinLength(3, ErrorMessage = "Validate_MinLength")]
    [EnumDataType(typeof(DayOfWeek))]
    public DayOfWeek[] VacationDay { get; set; }

    [Display(Name = "Comment_DisplayName")]
    [StringLength(200, ErrorMessage = "Validate_StringLength")]
    [DataType(DataType.MultilineText)]
    public string Comment { get; set; }

    [Display(Name = "FileName_DisplayName")]
    [FileExtensions(Extensions = "png", ErrorMessage = "Validate_FileExtensions")]
    public string FileName { get; set; }

    [Display(Name = "UploadFile_DisplayName")]
    [DataType(DataType.Upload)]
    public List<IFormFile> UploadFile { get; set; }

    [Display(Name = "Month_DisplayName")]
    public DateTime Month { get; set; }

    [Display(Name = "Search_DisplayName")]
    public string Search { get; set; }

    [Display(Name = "Range_DisplayName")]
    [Range(10, 100, ErrorMessage = "Validate_Range")]
    public int Range { get; set; }

    [Display(Name = "Week_DisplayName")]
    public string Week { get; set; }

    [Display(Name = "IsAccepted_DisplayName")]
    [Required(ErrorMessage = "Validate_Required")]
    public bool IsAccepted { get; set; }
  }

  public enum GenderType
  {
    None,
    Man,
    Woman,
    Other,
  }
}

홈 컨트롤러.cs

using LocalizationDataAnnotation.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 LocalizationDataAnnotation.Controllers
{
  public class HomeController : Controller
  {
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
      _logger = logger;
    }

    public IActionResult Index()
    {
      return View();
    }

    public IActionResult Privacy()
    {
      return View();
    }

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
      return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    }

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

만들기.cshtml

@model LocalizationDataAnnotation.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");}
}