CheckBox には、例えばユーザーが条件を許諾したという意思表示のため、チェックを入れるのが必須というケースがあると思います。その検証をクライアント側とサーバー側の両方で行うためのカスタム検証属性を考えてみました。
CheckBox の場合 RequiredAttribute ではクライアント側でもサーバー側でも検証はかかりません。RangeAttribute を使って [Range(typeof(bool), "true", "true")] というように設定するという手段がありますが、クライアント側での検証がかかりません。
というわけで、先の記事「ASP.NET Core MVC 検証属性の自作」とか「ファイルアップロード時の検証 (CORE)」で書いたようなカスタム検証属性を作って利用するのが良さそうです。
DropDownList にもそのようなカスタム検証属性を作ってみようと思いましたが、未選択で検証 NG とする場合は一番最初の option 要素が value="" となっていれば RequiredAttribute でクライアント/サーバー両方で検証がかかること、特定の項目を選ばなければならないというのは他の条件も絡むであろうから先の記事「CustomValidation 属性」で書いたようにすべきで、意味がなさそうなので止めました。
以下にそのコードをアップしておきます。上の画像を表示したものです。DropDownList 関係のコードもせっかく書いたので一緒に載せておきます。
Model とカスタム検証属性
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Globalization;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace MvcCoreApp.Models
{
// ビューモデル
public class Partner
{
[Required(ErrorMessage = "{0} is required.")]
public string Name { get; set; }
// Required 属性では検証はかからない
[Required(ErrorMessage = "{0} is required.")]
[CheckBoxValidate(true)]
public bool PartnerAccepted { get; set; }
[Required(ErrorMessage = "{0} is required.")]
public string Area { get; set; }
public SelectList AreaList { get; set; }
}
// DropDownList に渡す SelectList 生成用のクラス
// AreaId は int? または string 型にしておけば、SelectList を
// 生成する際、下の Controller のコード例のように null を設定
// できる。結果 option value="" となり未選択で検証 NG とできる
public class Area
{
public int? AreaId { set; get; }
public string AreaName { set; get; }
}
// CheckBox 用の検証属性
// true/false を引数で指定(要チェックなら true とする)
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class CheckBoxValidateAttribute : ValidationAttribute,
IClientModelValidator
{
private readonly bool option;
public CheckBoxValidateAttribute(bool option)
{
this.option = option;
this.ErrorMessage = "{0} must be set to {1}.";
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentCulture,
ErrorMessageString,
name,
option);
}
public override bool IsValid(object value)
{
// テキストボックスに入力された文字列が検証対象の場合は
// ここで value が空白か否かをチェックして空白の場合は
// true を返すようにしていた(空白の検証は Required だけで
// 行い、その他の検証属性は空白の場合は true を返すのが
// 原則なので)。チェックボックスの場合は ASP.NET MVC では
// 必ず true/false のいずれかが返されるようになっている。
// なので null が帰ってきた場合は何か良からぬことが起こって
// いるかもしれないということで例外をスローすることにした
if (value == null)
{
throw new ArgumentNullException("CheckBoxValidate");
}
if ((bool)value == option)
{
return true;
}
return false;
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
var errorMessage =
FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes,
"data-val-checkboxvalidate",
errorMessage);
MergeAttribute(context.Attributes,
"data-val-checkboxvalidate-option",
this.option.ToString());
}
// 上の AddValidation メソッドで使うヘルパーメソッド
private bool MergeAttribute(IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
}
Controller / Action Method
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using MvcCoreApp.Models;
using Microsoft.AspNetCore.Http;
using System.IO;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace MvcCoreApp.Controllers
{
public class ValidationController : Controller
{
public IActionResult CheckBoxDropDown()
{
// DropDwonList に渡す SelectList の生成
// AreaId を null に設定すると結果 option value="" となり
// 未選択では RequiredAttribute による検証は NG となる
var list = new List<Area>
{
new Area { AreaId = null, AreaName = "--- Select One ---"},
new Area { AreaId = 1, AreaName = "North" },
new Area { AreaId = 2, AreaName = "South"},
new Area { AreaId = 3, AreaName = "Eastt" },
new Area { AreaId = 4, AreaName = "West" }
};
var partner = new Partner
{
AreaList = new SelectList(list, "AreaId", "AreaName")
};
return View(partner);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult CheckBoxDropDown(Partner partner)
{
if (!ModelState.IsValid)
{
// 検証 NG で再表示する場合は SelectList も再生成要
var list = new List<Area>
{
new Area { AreaId = null, AreaName = "--- Select One ---"},
new Area { AreaId = 1, AreaName = "North" },
new Area { AreaId = 2, AreaName = "South"},
new Area { AreaId = 3, AreaName = "Eastt" },
new Area { AreaId = 4, AreaName = "West" }
};
partner.AreaList = new SelectList(list, "AreaId", "AreaName");
return View(partner);
}
return RedirectToAction("Create", "Validation");
}
}
}
View
@model MvcCoreApp.Models.Partner
@{
ViewData["Title"] = "CheckBoxDropDown";
}
<h1>CheckBoxDropDown</h1>
<h4>Pertner</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="CheckBoxDropDown">
<div asp-validation-summary="ModelOnly" class="text-danger">
</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 form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="PartnerAccepted" />
@Html.DisplayNameFor(model => model.PartnerAccepted)
</label>
<br />
<span asp-validation-for="PartnerAccepted" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Area" class="control-label"></label>
<select asp-for="Area" asp-items="Model.AreaList"
class="form-control">
</select>
<span asp-validation-for="Area" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script type="text/javascript">
//<![CDATA[
$.validator.addMethod("checkboxvalidate",
function (value, element, parameters) {
// チェックされてないと value は undefined となり
// value では判定できないので注意
var result = element.checked;
var isTrue = (parameters.toUpperCase() == 'TRUE');
if (result == isTrue) {
return true;
}
return false;
});
$.validator.unobtrusive.adapters.
addSingleVal('checkboxvalidate', 'option');
//]]>
</script>
}