Data Annotations
ライブラリ
Data Annotations
概要
Data Annotationsは、.NET Frameworkの標準ライブラリの一部として提供される属性ベースのバリデーションシステムです。System.ComponentModel.DataAnnotations名前空間に含まれ、クラスのプロパティに属性を付与することで、宣言的にバリデーションルールを定義できます。ASP.NET MVC、Entity Framework、Blazorなど.NETエコシステム全体で広く使用されており、シンプルで直感的な構文により、データモデルの検証を効率的に実装できます。
詳細
Data Annotationsは、.NET標準ライブラリの一部として、クラスのプロパティやフィールドに属性(Attribute)を付与することで、バリデーションルールを定義する仕組みです。Required、StringLength、Range、RegularExpressionなどの豊富なビルトイン属性を提供し、カスタム属性の作成も可能です。ASP.NET MVCではModel Bindingと連携して自動的にバリデーションを実行し、Entity Frameworkではデータベース制約と連携します。属性ベースのアプローチにより、ビジネスロジックとバリデーションルールを分離し、保守性の高いコードを実現できます。
主な特徴
- 標準ライブラリ: .NET Frameworkの標準機能として提供
- 宣言的構文: 属性による直感的なバリデーション定義
- フレームワーク統合: MVC、Entity Framework、Blazorとの深い連携
- 拡張性: カスタム属性の作成が容易
- 国際化対応: リソースファイルを使用した多言語エラーメッセージ
- クライアントサイド対応: JavaScript生成による自動クライアント検証
メリット・デメリット
メリット
- .NET標準ライブラリとして追加設定不要
- 属性による宣言的で読みやすいコード
- ASP.NET MVCとEntity Frameworkの完全統合
- 豊富なビルトイン属性によるカバレッジ
- カスタム属性の作成が簡単
- クライアントサイド検証の自動生成
デメリット
- 複雑な条件付きバリデーションが困難
- 属性によるモデルクラスの汚染
- 実行時バリデーションのパフォーマンス制約
- 属性の組み合わせによる柔軟性の限界
- テストが困難な場合がある
- 動的なバリデーションルールの実装が困難
参考ページ
書き方の例
基本的な属性とバリデーション
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
// 基本的なデータモデル
public class User
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage = "名前は必須項目です")]
[StringLength(50, MinimumLength = 2, ErrorMessage = "名前は2文字以上50文字以下で入力してください")]
[Display(Name = "名前")]
public string Name { get; set; }
[Required(ErrorMessage = "メールアドレスは必須項目です")]
[EmailAddress(ErrorMessage = "有効なメールアドレスを入力してください")]
[Display(Name = "メールアドレス")]
public string Email { get; set; }
[Range(0, 120, ErrorMessage = "年齢は0歳から120歳の間で入力してください")]
[Display(Name = "年齢")]
public int Age { get; set; }
[Phone(ErrorMessage = "有効な電話番号を入力してください")]
[Display(Name = "電話番号")]
public string PhoneNumber { get; set; }
[Url(ErrorMessage = "有効なURLを入力してください")]
[Display(Name = "ウェブサイト")]
public string Website { get; set; }
[DataType(DataType.Date)]
[Display(Name = "生年月日")]
public DateTime? BirthDate { get; set; }
[DataType(DataType.Password)]
[StringLength(100, MinimumLength = 8, ErrorMessage = "パスワードは8文字以上100文字以下で入力してください")]
[Display(Name = "パスワード")]
public string Password { get; set; }
[Compare("Password", ErrorMessage = "パスワードが一致しません")]
[Display(Name = "パスワード確認")]
[NotMapped] // Entity Frameworkで永続化しない
public string ConfirmPassword { get; set; }
[RegularExpression(@"^[0-9]{3}-[0-9]{4}$", ErrorMessage = "郵便番号は000-0000の形式で入力してください")]
[Display(Name = "郵便番号")]
public string PostalCode { get; set; }
[CreditCard(ErrorMessage = "有効なクレジットカード番号を入力してください")]
[Display(Name = "クレジットカード番号")]
public string CreditCardNumber { get; set; }
}
// 製品モデル
public class Product
{
[Key]
public int Id { get; set; }
[Required(ErrorMessage = "製品名は必須項目です")]
[StringLength(200, ErrorMessage = "製品名は200文字以下で入力してください")]
[Display(Name = "製品名")]
public string Name { get; set; }
[Required(ErrorMessage = "価格は必須項目です")]
[Range(0.01, double.MaxValue, ErrorMessage = "価格は0.01以上で入力してください")]
[DataType(DataType.Currency)]
[Display(Name = "価格")]
public decimal Price { get; set; }
[StringLength(1000, ErrorMessage = "説明は1000文字以下で入力してください")]
[Display(Name = "説明")]
public string Description { get; set; }
[Range(0, int.MaxValue, ErrorMessage = "在庫数は0以上で入力してください")]
[Display(Name = "在庫数")]
public int Stock { get; set; }
[Required(ErrorMessage = "カテゴリは必須項目です")]
[Display(Name = "カテゴリ")]
public string Category { get; set; }
[Display(Name = "有効")]
public bool IsActive { get; set; } = true;
[DataType(DataType.DateTime)]
[Display(Name = "作成日時")]
public DateTime CreatedAt { get; set; } = DateTime.Now;
[DataType(DataType.DateTime)]
[Display(Name = "更新日時")]
public DateTime UpdatedAt { get; set; } = DateTime.Now;
}
// バリデーション実行クラス
public class ValidationHelper
{
public static ValidationResult ValidateModel(object model)
{
var validationResults = new List<ValidationResult>();
var validationContext = new ValidationContext(model);
bool isValid = Validator.TryValidateObject(model, validationContext, validationResults, true);
return new ValidationResult
{
IsValid = isValid,
Errors = validationResults.Select(vr => new ValidationError
{
PropertyName = vr.MemberNames.FirstOrDefault(),
ErrorMessage = vr.ErrorMessage
}).ToList()
};
}
public static ValidationResult ValidateProperty(object model, string propertyName, object value)
{
var validationResults = new List<ValidationResult>();
var validationContext = new ValidationContext(model) { MemberName = propertyName };
bool isValid = Validator.TryValidateProperty(value, validationContext, validationResults);
return new ValidationResult
{
IsValid = isValid,
Errors = validationResults.Select(vr => new ValidationError
{
PropertyName = propertyName,
ErrorMessage = vr.ErrorMessage
}).ToList()
};
}
}
// バリデーション結果クラス
public class ValidationResult
{
public bool IsValid { get; set; }
public List<ValidationError> Errors { get; set; } = new List<ValidationError>();
}
public class ValidationError
{
public string PropertyName { get; set; }
public string ErrorMessage { get; set; }
}
// 使用例
class Program
{
static void Main(string[] args)
{
// 有効なユーザーデータ
var validUser = new User
{
Name = "田中太郎",
Email = "[email protected]",
Age = 30,
PhoneNumber = "090-1234-5678",
Website = "https://example.com",
BirthDate = new DateTime(1993, 5, 15),
Password = "SecurePassword123",
ConfirmPassword = "SecurePassword123",
PostalCode = "100-0001",
CreditCardNumber = "4111111111111111"
};
// バリデーション実行
var result = ValidationHelper.ValidateModel(validUser);
if (result.IsValid)
{
Console.WriteLine("✓ ユーザーデータは有効です");
}
else
{
Console.WriteLine("✗ バリデーションエラー:");
foreach (var error in result.Errors)
{
Console.WriteLine($" {error.PropertyName}: {error.ErrorMessage}");
}
}
// 無効なユーザーデータ
var invalidUser = new User
{
Name = "A", // 短すぎる
Email = "invalid-email", // 無効なメール
Age = 150, // 範囲外
PhoneNumber = "123", // 無効な電話番号
Website = "not-a-url", // 無効なURL
Password = "123", // 短すぎる
ConfirmPassword = "456", // 一致しない
PostalCode = "invalid", // 無効な郵便番号
CreditCardNumber = "invalid" // 無効なクレジットカード番号
};
var invalidResult = ValidationHelper.ValidateModel(invalidUser);
if (!invalidResult.IsValid)
{
Console.WriteLine("\n無効なユーザーデータのバリデーションエラー:");
foreach (var error in invalidResult.Errors)
{
Console.WriteLine($" {error.PropertyName}: {error.ErrorMessage}");
}
}
// 製品データのバリデーション
var product = new Product
{
Name = "サンプル製品",
Price = 1000.50m,
Description = "これはサンプル製品です",
Stock = 100,
Category = "電子機器"
};
var productResult = ValidationHelper.ValidateModel(product);
if (productResult.IsValid)
{
Console.WriteLine("\n✓ 製品データは有効です");
}
else
{
Console.WriteLine("\n✗ 製品データのバリデーションエラー:");
foreach (var error in productResult.Errors)
{
Console.WriteLine($" {error.PropertyName}: {error.ErrorMessage}");
}
}
}
}
カスタム属性とバリデーション
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
// カスタム属性:年齢制限
public class MinimumAgeAttribute : ValidationAttribute
{
private readonly int _minimumAge;
public MinimumAgeAttribute(int minimumAge)
{
_minimumAge = minimumAge;
ErrorMessage = $"年齢は{_minimumAge}歳以上である必要があります";
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null) return ValidationResult.Success;
if (value is DateTime birthDate)
{
var age = DateTime.Today.Year - birthDate.Year;
if (birthDate.Date > DateTime.Today.AddYears(-age))
{
age--;
}
if (age < _minimumAge)
{
return new ValidationResult(ErrorMessage, new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
}
// カスタム属性:日本の郵便番号
public class JapanesePostalCodeAttribute : ValidationAttribute
{
public JapanesePostalCodeAttribute()
{
ErrorMessage = "日本の郵便番号形式(000-0000)で入力してください";
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null || string.IsNullOrEmpty(value.ToString()))
{
return ValidationResult.Success; // 空値は他の属性で検証
}
var postalCode = value.ToString();
var pattern = @"^\d{3}-\d{4}$";
if (!System.Text.RegularExpressions.Regex.IsMatch(postalCode, pattern))
{
return new ValidationResult(ErrorMessage, new[] { validationContext.MemberName });
}
return ValidationResult.Success;
}
}
// カスタム属性:日本の電話番号
public class JapanesePhoneNumberAttribute : ValidationAttribute
{
public JapanesePhoneNumberAttribute()
{
ErrorMessage = "日本の電話番号形式で入力してください";
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null || string.IsNullOrEmpty(value.ToString()))
{
return ValidationResult.Success;
}
var phoneNumber = value.ToString();
var patterns = new[]
{
@"^0\d{1,4}-\d{1,4}-\d{4}$", // 固定電話
@"^0\d{2,3}-\d{3,4}-\d{4}$", // 固定電話(市外局番)
@"^0[7-9]0-\d{4}-\d{4}$", // 携帯電話
@"^050-\d{4}-\d{4}$" // IP電話
};
bool isValid = patterns.Any(pattern =>
System.Text.RegularExpressions.Regex.IsMatch(phoneNumber, pattern));
if (!isValid)
{
return new ValidationResult(ErrorMessage, new[] { validationContext.MemberName });
}
return ValidationResult.Success;
}
}
// カスタム属性:禁止文字列
public class ForbiddenWordsAttribute : ValidationAttribute
{
private readonly string[] _forbiddenWords;
public ForbiddenWordsAttribute(params string[] forbiddenWords)
{
_forbiddenWords = forbiddenWords;
ErrorMessage = "禁止されている文字が含まれています";
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null) return ValidationResult.Success;
var text = value.ToString().ToLowerInvariant();
foreach (var word in _forbiddenWords)
{
if (text.Contains(word.ToLowerInvariant()))
{
return new ValidationResult(
$"'{word}' は禁止されている文字です",
new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
}
// カスタム属性:条件付き必須
public class RequiredIfAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
private readonly object _comparisonValue;
public RequiredIfAttribute(string comparisonProperty, object comparisonValue)
{
_comparisonProperty = comparisonProperty;
_comparisonValue = comparisonValue;
ErrorMessage = "この項目は必須です";
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
if (property == null)
{
return new ValidationResult($"プロパティ '{_comparisonProperty}' が見つかりません");
}
var comparisonValue = property.GetValue(validationContext.ObjectInstance);
if (Equals(comparisonValue, _comparisonValue))
{
if (value == null || string.IsNullOrEmpty(value.ToString()))
{
return new ValidationResult(ErrorMessage, new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
}
// カスタム属性:ファイルサイズ制限
public class FileSizeAttribute : ValidationAttribute
{
private readonly int _maxSizeInMB;
public FileSizeAttribute(int maxSizeInMB)
{
_maxSizeInMB = maxSizeInMB;
ErrorMessage = $"ファイルサイズは{_maxSizeInMB}MB以下である必要があります";
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null) return ValidationResult.Success;
if (value is Microsoft.AspNetCore.Http.IFormFile file)
{
var maxSizeInBytes = _maxSizeInMB * 1024 * 1024;
if (file.Length > maxSizeInBytes)
{
return new ValidationResult(ErrorMessage, new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
}
// カスタム属性を使用したモデル
public class AdvancedUser
{
[Required(ErrorMessage = "名前は必須項目です")]
[StringLength(100, MinimumLength = 2, ErrorMessage = "名前は2文字以上100文字以下で入力してください")]
[ForbiddenWords("admin", "test", "spam", ErrorMessage = "禁止されている名前です")]
public string Name { get; set; }
[Required(ErrorMessage = "メールアドレスは必須項目です")]
[EmailAddress(ErrorMessage = "有効なメールアドレスを入力してください")]
public string Email { get; set; }
[Required(ErrorMessage = "生年月日は必須項目です")]
[MinimumAge(18, ErrorMessage = "18歳以上である必要があります")]
public DateTime BirthDate { get; set; }
[JapanesePostalCode]
public string PostalCode { get; set; }
[JapanesePhoneNumber]
public string PhoneNumber { get; set; }
[Display(Name = "運転免許証を持っている")]
public bool HasDriverLicense { get; set; }
[RequiredIf("HasDriverLicense", true, ErrorMessage = "運転免許証を持っている場合は免許証番号が必要です")]
[StringLength(20, ErrorMessage = "免許証番号は20文字以下で入力してください")]
public string DriverLicenseNumber { get; set; }
[Display(Name = "プロフィール画像")]
[FileSize(5, ErrorMessage = "プロフィール画像は5MB以下で選択してください")]
public Microsoft.AspNetCore.Http.IFormFile ProfileImage { get; set; }
[Display(Name = "年収")]
[Range(0, 100000000, ErrorMessage = "年収は0円以上1億円以下で入力してください")]
public decimal? AnnualIncome { get; set; }
[Display(Name = "趣味")]
[StringLength(500, ErrorMessage = "趣味は500文字以下で入力してください")]
public string Hobbies { get; set; }
[Display(Name = "自己紹介")]
[StringLength(1000, ErrorMessage = "自己紹介は1000文字以下で入力してください")]
[ForbiddenWords("広告", "宣伝", "spam", ErrorMessage = "不適切な内容が含まれています")]
public string SelfIntroduction { get; set; }
}
// 会社情報モデル
public class Company
{
[Required(ErrorMessage = "会社名は必須項目です")]
[StringLength(200, ErrorMessage = "会社名は200文字以下で入力してください")]
public string Name { get; set; }
[Required(ErrorMessage = "郵便番号は必須項目です")]
[JapanesePostalCode]
public string PostalCode { get; set; }
[Required(ErrorMessage = "住所は必須項目です")]
[StringLength(500, ErrorMessage = "住所は500文字以下で入力してください")]
public string Address { get; set; }
[Required(ErrorMessage = "電話番号は必須項目です")]
[JapanesePhoneNumber]
public string PhoneNumber { get; set; }
[EmailAddress(ErrorMessage = "有効なメールアドレスを入力してください")]
public string Email { get; set; }
[Url(ErrorMessage = "有効なURLを入力してください")]
public string Website { get; set; }
[Range(1, 10000, ErrorMessage = "従業員数は1人以上10,000人以下で入力してください")]
public int EmployeeCount { get; set; }
[Required(ErrorMessage = "設立年月日は必須項目です")]
[DataType(DataType.Date)]
public DateTime EstablishedDate { get; set; }
[StringLength(1000, ErrorMessage = "事業内容は1000文字以下で入力してください")]
public string BusinessDescription { get; set; }
}
// 高度なバリデーション実行クラス
public class AdvancedValidationHelper
{
public static ValidationResult ValidateModel(object model)
{
var validationResults = new List<System.ComponentModel.DataAnnotations.ValidationResult>();
var validationContext = new ValidationContext(model);
bool isValid = Validator.TryValidateObject(model, validationContext, validationResults, true);
return new ValidationResult
{
IsValid = isValid,
Errors = validationResults.Select(vr => new ValidationError
{
PropertyName = vr.MemberNames.FirstOrDefault(),
ErrorMessage = vr.ErrorMessage
}).ToList()
};
}
public static ValidationResult ValidateModelWithGroups(object model, params string[] groups)
{
var validationResults = new List<System.ComponentModel.DataAnnotations.ValidationResult>();
var validationContext = new ValidationContext(model);
// グループ指定されたプロパティのみ検証
var properties = model.GetType().GetProperties();
var errors = new List<ValidationError>();
foreach (var property in properties)
{
var value = property.GetValue(model);
var propertyValidationResults = new List<System.ComponentModel.DataAnnotations.ValidationResult>();
var propertyValidationContext = new ValidationContext(model) { MemberName = property.Name };
bool propertyIsValid = Validator.TryValidateProperty(value, propertyValidationContext, propertyValidationResults);
if (!propertyIsValid)
{
errors.AddRange(propertyValidationResults.Select(vr => new ValidationError
{
PropertyName = property.Name,
ErrorMessage = vr.ErrorMessage
}));
}
}
return new ValidationResult
{
IsValid = errors.Count == 0,
Errors = errors
};
}
}
// 使用例
class Program
{
static void Main(string[] args)
{
// 高度なユーザーデータのバリデーション
var advancedUser = new AdvancedUser
{
Name = "田中太郎",
Email = "[email protected]",
BirthDate = new DateTime(1990, 5, 15),
PostalCode = "100-0001",
PhoneNumber = "03-1234-5678",
HasDriverLicense = true,
DriverLicenseNumber = "123456789012",
AnnualIncome = 5000000,
Hobbies = "読書、映画鑑賞、プログラミング",
SelfIntroduction = "こんにちは、田中太郎です。よろしくお願いします。"
};
var result = AdvancedValidationHelper.ValidateModel(advancedUser);
if (result.IsValid)
{
Console.WriteLine("✓ 高度なユーザーデータは有効です");
}
else
{
Console.WriteLine("✗ 高度なユーザーデータのバリデーションエラー:");
foreach (var error in result.Errors)
{
Console.WriteLine($" {error.PropertyName}: {error.ErrorMessage}");
}
}
// 無効なデータの例
var invalidUser = new AdvancedUser
{
Name = "admin", // 禁止文字
Email = "invalid-email",
BirthDate = new DateTime(2010, 1, 1), // 18歳未満
PostalCode = "invalid", // 無効な郵便番号
PhoneNumber = "invalid", // 無効な電話番号
HasDriverLicense = true,
DriverLicenseNumber = "", // 必須だが空
AnnualIncome = -1000000, // 負の値
SelfIntroduction = "これは広告です" // 禁止文字
};
var invalidResult = AdvancedValidationHelper.ValidateModel(invalidUser);
if (!invalidResult.IsValid)
{
Console.WriteLine("\n無効なユーザーデータのバリデーションエラー:");
foreach (var error in invalidResult.Errors)
{
Console.WriteLine($" {error.PropertyName}: {error.ErrorMessage}");
}
}
// 会社情報のバリデーション
var company = new Company
{
Name = "株式会社サンプル",
PostalCode = "100-0001",
Address = "東京都千代田区千代田1-1-1",
PhoneNumber = "03-1234-5678",
Email = "[email protected]",
Website = "https://sample.com",
EmployeeCount = 100,
EstablishedDate = new DateTime(2000, 4, 1),
BusinessDescription = "ソフトウェア開発およびITコンサルティング"
};
var companyResult = AdvancedValidationHelper.ValidateModel(company);
if (companyResult.IsValid)
{
Console.WriteLine("\n✓ 会社情報は有効です");
}
else
{
Console.WriteLine("\n✗ 会社情報のバリデーションエラー:");
foreach (var error in companyResult.Errors)
{
Console.WriteLine($" {error.PropertyName}: {error.ErrorMessage}");
}
}
}
}
ASP.NET Core統合とクライアントサイド検証
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.ComponentModel.DataAnnotations;
// ASP.NET Core MVCでの使用例
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
[HttpPost]
public IActionResult CreateUser([FromBody] User user)
{
// モデルバリデーションの確認
if (!ModelState.IsValid)
{
var errors = ModelState
.Where(x => x.Value.Errors.Count > 0)
.Select(x => new ValidationError
{
PropertyName = x.Key,
ErrorMessage = string.Join(", ", x.Value.Errors.Select(e => e.ErrorMessage))
})
.ToList();
return BadRequest(new
{
Message = "バリデーションエラーが発生しました",
Errors = errors
});
}
// バリデーション成功時の処理
return Ok(new { Message = "ユーザーが正常に作成されました", User = user });
}
[HttpPut("{id}")]
public IActionResult UpdateUser(int id, [FromBody] User user)
{
// 部分バリデーション(特定のプロパティのみ)
var validationResults = new List<ValidationResult>();
var validationContext = new ValidationContext(user);
// 特定のプロパティのみバリデーション
var propertiesToValidate = new[] { "Name", "Email", "Age" };
foreach (var propertyName in propertiesToValidate)
{
var property = user.GetType().GetProperty(propertyName);
if (property != null)
{
var value = property.GetValue(user);
var propertyValidationContext = new ValidationContext(user) { MemberName = propertyName };
var propertyValidationResults = new List<ValidationResult>();
Validator.TryValidateProperty(value, propertyValidationContext, propertyValidationResults);
validationResults.AddRange(propertyValidationResults);
}
}
if (validationResults.Any())
{
var errors = validationResults.Select(vr => new ValidationError
{
PropertyName = vr.MemberNames.FirstOrDefault(),
ErrorMessage = vr.ErrorMessage
}).ToList();
return BadRequest(new
{
Message = "部分バリデーションエラーが発生しました",
Errors = errors
});
}
return Ok(new { Message = "ユーザーが正常に更新されました", User = user });
}
[HttpPost("validate")]
public IActionResult ValidateUserData([FromBody] User user)
{
// カスタムバリデーションロジック
var customValidationResults = new List<ValidationError>();
// 重複チェック(例:データベース確認)
if (IsEmailDuplicate(user.Email))
{
customValidationResults.Add(new ValidationError
{
PropertyName = nameof(user.Email),
ErrorMessage = "このメールアドレスは既に使用されています"
});
}
// 年齢と生年月日の整合性チェック
if (user.BirthDate.HasValue && user.Age > 0)
{
var calculatedAge = DateTime.Today.Year - user.BirthDate.Value.Year;
if (user.BirthDate.Value.Date > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}
if (Math.Abs(user.Age - calculatedAge) > 1)
{
customValidationResults.Add(new ValidationError
{
PropertyName = nameof(user.Age),
ErrorMessage = "年齢と生年月日が一致しません"
});
}
}
// Data Annotationsバリデーション
var validationResults = new List<ValidationResult>();
var validationContext = new ValidationContext(user);
bool isValid = Validator.TryValidateObject(user, validationContext, validationResults, true);
var allErrors = validationResults.Select(vr => new ValidationError
{
PropertyName = vr.MemberNames.FirstOrDefault(),
ErrorMessage = vr.ErrorMessage
}).ToList();
allErrors.AddRange(customValidationResults);
if (allErrors.Any())
{
return BadRequest(new
{
Message = "バリデーションエラーが発生しました",
Errors = allErrors
});
}
return Ok(new { Message = "バリデーションが成功しました" });
}
private bool IsEmailDuplicate(string email)
{
// 実際の実装ではデータベース確認
var existingEmails = new[] { "[email protected]", "[email protected]" };
return existingEmails.Contains(email);
}
}
// カスタムバリデーション属性(ASP.NET Core固有)
public class UniqueEmailAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null) return ValidationResult.Success;
var email = value.ToString();
// 実際の実装ではDIコンテナからサービスを取得
// var userService = validationContext.GetService<IUserService>();
// bool isDuplicate = userService.IsEmailDuplicate(email);
// 簡略化した例
var existingEmails = new[] { "[email protected]", "[email protected]" };
bool isDuplicate = existingEmails.Contains(email);
if (isDuplicate)
{
return new ValidationResult(
"このメールアドレスは既に使用されています",
new[] { validationContext.MemberName });
}
return ValidationResult.Success;
}
}
// 国際化対応のバリデーション
public class LocalizedUser
{
[Required(ErrorMessageResourceType = typeof(Resources.ValidationMessages),
ErrorMessageResourceName = "NameRequired")]
[StringLength(50, MinimumLength = 2,
ErrorMessageResourceType = typeof(Resources.ValidationMessages),
ErrorMessageResourceName = "NameLength")]
public string Name { get; set; }
[Required(ErrorMessageResourceType = typeof(Resources.ValidationMessages),
ErrorMessageResourceName = "EmailRequired")]
[EmailAddress(ErrorMessageResourceType = typeof(Resources.ValidationMessages),
ErrorMessageResourceName = "EmailFormat")]
[UniqueEmail(ErrorMessageResourceType = typeof(Resources.ValidationMessages),
ErrorMessageResourceName = "EmailDuplicate")]
public string Email { get; set; }
[Range(0, 120, ErrorMessageResourceType = typeof(Resources.ValidationMessages),
ErrorMessageResourceName = "AgeRange")]
public int Age { get; set; }
}
// リソースファイル(Resources/ValidationMessages.resx)の例
public class ValidationMessages
{
public static string NameRequired => "名前は必須項目です";
public static string NameLength => "名前は2文字以上50文字以下で入力してください";
public static string EmailRequired => "メールアドレスは必須項目です";
public static string EmailFormat => "有効なメールアドレスを入力してください";
public static string EmailDuplicate => "このメールアドレスは既に使用されています";
public static string AgeRange => "年齢は0歳から120歳の間で入力してください";
}
// Blazor Serverでの使用例
@page "/user-form"
@using System.ComponentModel.DataAnnotations
<EditForm Model="@user" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group">
<label for="name">名前:</label>
<InputText id="name" @bind-Value="user.Name" class="form-control" />
<ValidationMessage For="@(() => user.Name)" />
</div>
<div class="form-group">
<label for="email">メールアドレス:</label>
<InputText id="email" @bind-Value="user.Email" class="form-control" />
<ValidationMessage For="@(() => user.Email)" />
</div>
<div class="form-group">
<label for="age">年齢:</label>
<InputNumber id="age" @bind-Value="user.Age" class="form-control" />
<ValidationMessage For="@(() => user.Age)" />
</div>
<button type="submit" class="btn btn-primary">送信</button>
</EditForm>
@code {
private User user = new User();
private async Task HandleValidSubmit()
{
// バリデーション済みデータの処理
Console.WriteLine($"ユーザー作成: {user.Name}, {user.Email}, {user.Age}");
// 実際の実装では保存処理など
await Task.Delay(1000);
// 成功メッセージ表示
user = new User(); // フォームリセット
}
}