روش مرسوم اعتبارسنجی اطلاعات مدلهای ASP.NET Core، با استفاده از data annotations توکار آن است که در بسیاری از موارد هم به خوبی کار میکند. اما اگر به دنبال ویژگیهای دیگری مانند نوشتن آزمونهای واحد برای اعتبارسنجی اطلاعات، جداسازی شرطهای اعتبارسنجی از تعاریف مدلها، نوشتن اعتبارسنجیهای پیچیده به همراه تزریق وابستگیها هستید، کتابخانهی FluentValidation میتواند جایگزین بهتر و بسیار کاملتری باشد.
نصب کتابخانهی FluentValidation در پروژه
فرض کنید پروژهی ما از سه پوشهی FluentValidationSample.Web، FluentValidationSample.Models و FluentValidationSample.Services تشکیل شدهاست که اولی یک پروژهی MVC است و دو مورد دیگر classlib هستند.
در پروژهی FluentValidationSample.Models، بستهی نیوگت کتابخانهی FluentValidation را به صورت زیر نصب میکنیم:
جایگزین کردن سیستم اعتبارسنجی مبتنی بر DataAnnotations با FluentValidation
اکنون فرض کنید در پروژهی Models، مدل ثبتنام زیر را اضافه کردهایم که از همان data annotations توکار و استاندارد ASP.NET Core برای اعتبارسنجی اطلاعات استفاده میکند:
برای جایگزین کردن data annotations اعتبارسنجی اطلاعات با روش FluentValidation، میتوان به صورت زیر عمل کرد:
برای این منظور ابتدا یک کلاس Validator را با ارث بری از AbstractValidator از نوع مدلی که میخواهیم قواعد اعتبارسنجی آنرا مشخص کنیم، ایجاد میکنیم. سپس در سازندهی آن، میتوان به متدهای تعریف شدهی در این کلاس پایه دسترسی یافت.
در اینجا در ابتدا به ازای هر خاصیت کلاس مدل مدنظر، یک RuleFor تعریف میشود که با استفاده از static reflection، امکان تعریف strongly typed آنها وجود دارد. سپس ویژگی Required به متد NotNull تبدیل میشود و ویژگی StringLength توسط متد Length قابل تعریف خواهد بود و یا ویژگی Compare توسط متد Equal به صورت strongly typed به خاصیت دیگری متصل میشود.
پس از این تعاریف، میتوان ویژگیهای اعتبارسنجی اطلاعات را از مدل ثبت نام حذف کرد و تنها ویژگیهای خاص Viewهای MVC را در صورت نیاز باقی گذاشت:
تعریف پیامهای سفارشی اعتبارسنجی
روش تعریف پیامهای سفارشی شکست اعتبارسنجی اطلاعات را توسط متد WithMessage در ادامه مشاهده میکنید:
به ازای هر متد تعریف یک قاعدهی اعتبارسنجی جدید، بلافاصله میتوان از متد WithMessage نیز استفاده کرد. همچنین این متد میتواند به اطلاعات اصل model دریافتی نیز همانند پیام سفارشی مرتبط با MinimumLength نام کاربری، دسترسی پیدا کند.
روش تعریف اعتبارسنجیهای سفارشی خواص مدل
فرض کنید میخواهیم یک کلمهی عبور وارد شدهی معتبر، حتما از جمع حروف کوچک، بزرگ، اعداد و symbols تشکیل شده باشد. برای این منظور میتوان از متد Must استفاده کرد:
متد Must، میتواند مقدار خاصیت متناظر را نیز در اختیار ما قرار دهد و بر اساس آن مقدار میتوان خروجی true/false ای را بازگشت داد تا نشان شکست و یا موفقیت آمیز بودن اعتبارسنجی اطلاعات باشد.
البته lambda expression نوشته شده را میتوان توسط method groups، به صورت زیر نیز خلاصه نوشت:
انتقال تعاریف اعتبارسنجهای سفارشی خواص به کلاسهای مجزا
اگر نیاز به استفادهی از متد hasValidPassword در کلاسهای دیگری نیز وجود دارد، میتوان اینگونه اعتبارسنجیهای سفارشی را به کلاسهای مجزایی نیز تبدیل کرد. برای مثال فرض کنید که میخواهیم ایمیل دریافت شده، فقط از یک دومین خاص قابل قبول باشد.
برای این منظور یک کلاس جدید را با ارثبری از PropertyValidator تعریف شدهی در فضای نام FluentValidation.Validators، ایجاد میکنیم. سپس متد IsValid آنرا بازنویسی میکنیم تا برای مثال ایمیلها را صرفا از دومین خاصی بپذیرد.
PropertyValidatorContext امکان دسترسی به نام و مقدار خاصیت در حال اعتبارسنجی را میسر میکند. همچنین مقدار کل model جاری را نیز به صورت یک object در اختیار ما قرار میدهد.
اکنون برای استفادهی از آن میتوان از متد SetValidator استفاده کرد:
و یا حتی میتوان یک متد الحاقی fluent را نیز برای آن طراحی کرد تا SetValidator را به صورت خودکار فراخوانی کند:
سپس تعریف قاعدهی اعتبارسنجی ایمیلها به صورت زیر تغییر میکند:
تعریف قواعد اعتبارسنجی خواص تو در تو و لیستی
فرض کنید به RegisterModel این قسمت، دو خاصیت آدرس و شماره تلفنها نیز اضافه شدهاست که یکی به شیء آدرس و دیگری به مجموعهای از آدرسها اشاره میکند:
در یک چنین حالتی، ابتدا به صورت متداول، قواعد اعتبارسنجی Phone و Address را جداگانه تعریف میکنیم:
سپس برای تعریف اعتبارسنجی دو خاصیت پیچیدهی اضافه شده، میتوان از همان متد SetValidator استفاده کرد که اینبار پارامتر ورودی آن، نمونهای از AbstractValidatorهای هرکدام است. البته برای خاصیت مجموعهای اینبار باید با متد RuleForEach شروع کرد:
در قسمت بعد، روشهای مختلف استفادهی از قواعد اعتبارسنجی تعریف شده را در یک برنامهی ASP.NET Core بررسی میکنیم.
برای مطالعهی بیشتر
- «FluentValidation #1»
نصب کتابخانهی FluentValidation در پروژه
فرض کنید پروژهی ما از سه پوشهی FluentValidationSample.Web، FluentValidationSample.Models و FluentValidationSample.Services تشکیل شدهاست که اولی یک پروژهی MVC است و دو مورد دیگر classlib هستند.
در پروژهی FluentValidationSample.Models، بستهی نیوگت کتابخانهی FluentValidation را به صورت زیر نصب میکنیم:
dotnet add package FluentValidation.AspNetCore
جایگزین کردن سیستم اعتبارسنجی مبتنی بر DataAnnotations با FluentValidation
اکنون فرض کنید در پروژهی Models، مدل ثبتنام زیر را اضافه کردهایم که از همان data annotations توکار و استاندارد ASP.NET Core برای اعتبارسنجی اطلاعات استفاده میکند:
using System.ComponentModel.DataAnnotations; namespace FluentValidationSample.Models { public class RegisterModel { [Required] [Display(Name = "User name")] public string UserName { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } [DataType(DataType.EmailAddress)] [Display(Name = "Email")] [EmailAddress] public string Email { get; set; } [Range(18, 60)] [Display(Name = "Age")] public int Age { get; set; } } }
using FluentValidation; namespace FluentValidationSample.Models { public class RegisterModelValidator : AbstractValidator<RegisterModel> { public RegisterModelValidator() { RuleFor(x => x.UserName).NotNull(); RuleFor(x => x.Password).NotNull().Length(6, 100); RuleFor(x => x.ConfirmPassword).Equal(x => x.Password); RuleFor(x => x.Email).EmailAddress(); RuleFor(x => x.Age).InclusiveBetween(18, 60); } } }
در اینجا در ابتدا به ازای هر خاصیت کلاس مدل مدنظر، یک RuleFor تعریف میشود که با استفاده از static reflection، امکان تعریف strongly typed آنها وجود دارد. سپس ویژگی Required به متد NotNull تبدیل میشود و ویژگی StringLength توسط متد Length قابل تعریف خواهد بود و یا ویژگی Compare توسط متد Equal به صورت strongly typed به خاصیت دیگری متصل میشود.
پس از این تعاریف، میتوان ویژگیهای اعتبارسنجی اطلاعات را از مدل ثبت نام حذف کرد و تنها ویژگیهای خاص Viewهای MVC را در صورت نیاز باقی گذاشت:
using System.ComponentModel.DataAnnotations; namespace FluentValidationSample.Models { public class RegisterModel { [Display(Name = "User name")] public string UserName { get; set; } [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm password")] public string ConfirmPassword { get; set; } [DataType(DataType.EmailAddress)] [Display(Name = "Email")] public string Email { get; set; } [Display(Name = "Age")] public int Age { get; set; } } }
تعریف پیامهای سفارشی اعتبارسنجی
روش تعریف پیامهای سفارشی شکست اعتبارسنجی اطلاعات را توسط متد WithMessage در ادامه مشاهده میکنید:
using FluentValidation; namespace FluentValidationSample.Models { public class RegisterModelValidator : AbstractValidator<RegisterModel> { public RegisterModelValidator() { RuleFor(x => x.UserName) .NotNull() .WithMessage("Your first name is required.") .MaximumLength(20) .WithMessage("Your first name is too long!") .MinimumLength(3) .WithMessage(registerModel => $"Your first name `{registerModel.UserName}` is too short!"); RuleFor(x => x.Password) .NotNull() .WithMessage("Your password is required.") .Length(6, 100); RuleFor(x => x.ConfirmPassword) .NotNull() .WithMessage("Your confirmation password is required.") .Equal(x => x.Password) .WithMessage("The password and confirmation password do not match."); RuleFor(x => x.Email).EmailAddress(); RuleFor(x => x.Age).InclusiveBetween(18, 60); } } }
روش تعریف اعتبارسنجیهای سفارشی خواص مدل
فرض کنید میخواهیم یک کلمهی عبور وارد شدهی معتبر، حتما از جمع حروف کوچک، بزرگ، اعداد و symbols تشکیل شده باشد. برای این منظور میتوان از متد Must استفاده کرد:
using System.Text.RegularExpressions; using FluentValidation; namespace FluentValidationSample.Models { public class RegisterModelValidator : AbstractValidator<RegisterModel> { public RegisterModelValidator() { RuleFor(x => x.Password) .NotNull() .WithMessage("Your password is required.") .Length(6, 100) .Must(password => hasValidPassword(password)); //... } private static bool hasValidPassword(string password) { var lowercase = new Regex("[a-z]+"); var uppercase = new Regex("[A-Z]+"); var digit = new Regex("(\\d)+"); var symbol = new Regex("(\\W)+"); return lowercase.IsMatch(password) && uppercase.IsMatch(password) && digit.IsMatch(password) && symbol.IsMatch(password); } } }
البته lambda expression نوشته شده را میتوان توسط method groups، به صورت زیر نیز خلاصه نوشت:
RuleFor(x => x.Password) .NotNull() .WithMessage("Your password is required.") .Length(6, 100) .Must(hasValidPassword);
اگر نیاز به استفادهی از متد hasValidPassword در کلاسهای دیگری نیز وجود دارد، میتوان اینگونه اعتبارسنجیهای سفارشی را به کلاسهای مجزایی نیز تبدیل کرد. برای مثال فرض کنید که میخواهیم ایمیل دریافت شده، فقط از یک دومین خاص قابل قبول باشد.
using System; using FluentValidation; using FluentValidation.Validators; namespace FluentValidationSample.Models { public class EmailFromDomainValidator : PropertyValidator { private readonly string _domain; public EmailFromDomainValidator(string domain) : base("Email address {PropertyValue} is not from domain {domain}") { _domain = domain; } protected override bool IsValid(PropertyValidatorContext context) { if (context.PropertyValue == null) return false; var split = context.PropertyValue.ToString().Split('@'); return split.Length == 2 && split[1].Equals(_domain, StringComparison.OrdinalIgnoreCase); } } }
PropertyValidatorContext امکان دسترسی به نام و مقدار خاصیت در حال اعتبارسنجی را میسر میکند. همچنین مقدار کل model جاری را نیز به صورت یک object در اختیار ما قرار میدهد.
اکنون برای استفادهی از آن میتوان از متد SetValidator استفاده کرد:
RuleFor(x => x.Email) .SetValidator(new EmailFromDomainValidator("gmail.com"));
public static class CustomValidatorExtensions { public static IRuleBuilderOptions<T, string> EmailAddressFromDomain<T>( this IRuleBuilder<T, string> ruleBuilder, string domain) { return ruleBuilder.SetValidator(new EmailFromDomainValidator(domain)); } }
RuleFor(x => x.Email).EmailAddressFromDomain("gmail.com");
فرض کنید به RegisterModel این قسمت، دو خاصیت آدرس و شماره تلفنها نیز اضافه شدهاست که یکی به شیء آدرس و دیگری به مجموعهای از آدرسها اشاره میکند:
public class RegisterModel { // ... public Address Address { get; set; } public ICollection<Phone> Phones { get; set; } } public class Phone { public string Number { get; set; } public string Description { get; set; } } public class Address { public string Location { get; set; } public string PostalCode { get; set; } }
public class PhoneValidator : AbstractValidator<Phone> { public PhoneValidator() { RuleFor(x => x.Number).NotNull(); } } public class AddressValidator : AbstractValidator<Address> { public AddressValidator() { RuleFor(x => x.PostalCode).NotNull(); RuleFor(x => x.Location).NotNull(); } }
public class RegisterModelValidator : AbstractValidator<RegisterModel> { public RegisterModelValidator() { // ... RuleFor(x => x.Address).SetValidator(new AddressValidator()); RuleForEach(x => x.Phones).SetValidator(new PhoneValidator()); }
در قسمت بعد، روشهای مختلف استفادهی از قواعد اعتبارسنجی تعریف شده را در یک برنامهی ASP.NET Core بررسی میکنیم.
برای مطالعهی بیشتر
- «FluentValidation #1»