در
قسمت قبل با نحوهی پیاده سازی اعتبارسنجیهای سفارشی سمت کلاینت مخصوص کتابخانهی Fluent Validation آشنا شدیم. در این قسمت، یک حالت خاص همان نوع اعتبارسنجیهای سمت کلاینت را که
remote validation نام دارد، بررسی میکنیم. در این حالت خاص، نیازی به کدنویسی جاوااسکریپتی خاصی نیست. چون زیرساخت آن به همراه
unobtrusive jQuery Ajax خود ASP.NET Core ارائه میشود. در اینجا فقط نیاز است تا متادیتای خاص آنرا تولید کنیم. به عبارتی اینبار هدف ما تنها تولید یک چنین تگ HTML ای است:
<input dir="ltr" class="form-control input-validation-error"
type="email"
data-val="true"
data-val-email="'آدرس ایمیل' is not a valid email address."
data-val-remote="این آدرس ایمیل هم اکنون مورد استفادهاست"
data-val-remote-url="/Home/ValidateUniqueEmail"
data-val-required="'آدرس ایمیل' must not be empty."
id="Email"
name="Email"
>
که به همراه ویژگیهای data-val ، data-val-remote و data-val-remote-url است. همینقدر که این سه ویژگی وجود داشته باشند، مابقی منطق اعتبارسنجی سمت کلاینت آن توسط unobtrusive jQuery Ajax (ارسال خودکار Ajax ای مقدار ایمیل، به سمت سرور و دریافت پاسخ) و unobtrusive java script validation مدیریت خواهند شد.
افزودن آدرس ایمیل به مدل کاربران
به همان مدل
قسمت قبل، قصد داریم خاصیت آدرس ایمیل را هم اضافه کنیم:
using System.ComponentModel.DataAnnotations;
namespace FluentValidationSample.Models
{
public class UserModel
{
[Display(Name = "نام کاربری")]
public string Username { get; set; }
[Display(Name = "سن")]
public int Age { get; set; }
[Display(Name = "سابقه کار")]
public int Experience { get; set; }
[DataType(DataType.EmailAddress)]
[Display(Name = "آدرس ایمیل")]
public string Email { get; set; }
}
}
ایجاد سرویسی برای بررسی منحصربفرد بودن آدرس ایمیل
در ادامه قصد داریم سرویسی را ایجاد کنیم که برای مثال با بانک اطلاعاتی ارتباط برقرار کرده (در اینجا جهت سهولت ارائه، از یک آرایه استفاده شدهاست) و مشخص میکند که آیا ایمیل دریافتی پیشتر استفاده شدهاست یا خیر:
using System.Linq;
namespace FluentValidationSample.Services
{
public interface IUsersService
{
bool IsUniqueEmail(string emailAddress);
}
public class UsersService : IUsersService
{
public bool IsUniqueEmail(string emailAddress)
{
string[] registedEmails = {
"email@site.com",
"test@gmail.com"
};
return !registedEmails.Contains(emailAddress);
}
}
}
این سرویس را هم با طول عمر Scoped به برنامه معرفی میکنیم؛ چون طول عمر سرویسهایی که با بانک اطلاعاتی و DbContext کار میکنند، به همین نحو تعیین میشوند:
namespace FluentValidationSample.Web
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IUsersService, UsersService>();
ایجاد اعتبارسنج سمت سرور بررسی منحصربفرد بودن آدرس ایمیل
در ادامه یک PropertyValidator جدید را ایجاد میکنیم تا بتوان توسط آن مقدار ایمیل دریافتی را در سمت سرور، تعیین اعتبار کرد:
using FluentValidation.Validators;
using FluentValidationSample.Services;
namespace FluentValidationSample.ModelsValidations
{
public class UniqueEmailValidator : PropertyValidator
{
private readonly IUsersService _usersService;
public UniqueEmailValidator(IUsersService usersService)
: base("این آدرس ایمیل هم اکنون مورد استفادهاست")
{
_usersService = usersService;
}
protected override bool IsValid(PropertyValidatorContext context)
{
return context.PropertyValue != null &&
_usersService.IsUniqueEmail((string)context.PropertyValue);
}
}
}
همانطور که مشاهده میکنید، در اینجا تزریق سرویس سفارشی IUsersService به سازندهی کلاس اعتبارسنج، مجاز است و بدون مشکل کار میکند.
پس از آن جهت سهولت استفادهی از آن، یک متد الحاقی جدید را نیز به نام UniqueEmail به نحو زیر تعریف میکنیم:
using FluentValidation;
using FluentValidationSample.Services;
namespace FluentValidationSample.ModelsValidations
{
public static class CustomValidatorExtensions
{
public static IRuleBuilderOptions<T, string> UniqueEmail<T>(
this IRuleBuilder<T, string> ruleBuilder, IUsersService usersService)
{
return ruleBuilder.SetValidator(new UniqueEmailValidator(usersService));
}
}
}
یک نکته: اگر دقت کرده باشید، فضای نام این اعتبارسنج در این قسمت FluentValidationSample.
ModelsValidations شدهاست:
علت اینجا است که اعتبارسنج تعریف شده نیاز دارد هم از مدلها استفاده کند و هم از سرویس کاربران. سرویس کاربران هم از مدلها استفاده میکند. به همین جهت اگر تعاریف اعتبارسنجی را داخل پروژهی مدلها قرار دهیم، یک وابستگی حلقوی رخ خواهد داد (وابستگی مدلها به سرویسها و برعکس). بنابراین بهتر است اعتبارسنجها را به یک پروژهی مجزا منتقل کنیم تا از بروز این cyclic dependency جلوگیری شود.
اعمال اعتبارسنجی منحصربفرد بودن ایمیل دریافتی به اعتبارسنج UserModel
پس از تهیهی متد الحاقی UniqueEmail، آنرا به RuleFor مخصوص خاصیت ایمیل اضافه میکنیم. در اینجا نیز تزریق وابستگی سرویس سفارشی IUsersService به سازندهی کلاس اعتبارسنج مجاز است:
using FluentValidation;
using FluentValidationSample.Models;
using FluentValidationSample.Services;
namespace FluentValidationSample.ModelsValidations
{
public class UserModelValidator : AbstractValidator<UserModel>
{
public UserModelValidator(IUsersService usersService)
{
RuleFor(x => x.Username).NotNull();
RuleFor(x => x.Age).NotNull();
RuleFor(x => x.Experience).LowerThan(nameof(UserModel.Age)).NotNull();
RuleFor(x => x.Email).EmailAddress().NotNull().UniqueEmail(usersService);
}
}
}
ایجاد متادیتای مورد نیاز جهت unobtrusive java script validation در سمت سرور
در ادامه نیاز است ویژگیهای data-val خاص unobtrusive java script validation را توسط FluentValidation ایجاد کنیم:
using FluentValidation;
using FluentValidation.AspNetCore;
using FluentValidation.Internal;
using FluentValidation.Resources;
using FluentValidation.Validators;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace FluentValidationSample.ModelsValidations
{
public class RemoteClientValidator : ClientValidatorBase
{
public string RemoteUrl { set; get; }
public RemoteClientValidator(PropertyRule rule, IPropertyValidator validator) :
base(rule, validator)
{
}
public override void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-remote", GetErrorMessage(context));
MergeAttribute(context.Attributes, "data-val-remote-url", RemoteUrl);
}
private string GetErrorMessage(ClientModelValidationContext context)
{
var formatter = ValidatorOptions.MessageFormatterFactory().AppendPropertyName(Rule.GetDisplayName());
string messageTemplate;
try
{
messageTemplate = Validator.Options.ErrorMessageSource.GetString(null);
}
catch (FluentValidationMessageFormatException)
{
messageTemplate = ValidatorOptions.LanguageManager.GetStringForValidator<NotEmptyValidator>();
}
return formatter.BuildMessage(messageTemplate);
}
}
}
در این کدها، تنها قسمت مهم آن، متد AddValidation است که کار تعریف و افزودن متادیتاهای unobtrusive java script validation را انجام میدهد و برای مثال سبب رندر تگ HTML ای زیر میشود:
<input dir="ltr" class="form-control input-validation-error"
type="email"
data-val="true"
data-val-email="'آدرس ایمیل' is not a valid email address."
data-val-remote="این آدرس ایمیل هم اکنون مورد استفادهاست"
data-val-remote-url="/Home/ValidateUniqueEmail"
data-val-required="'آدرس ایمیل' must not be empty."
id="Email"
name="Email"
>
که به همراه ویژگیهای data-val، data-val-remote و data-val-remote-url است تا unobtrusive jQuery Ajax validation را فعال کند.
افزودن اعتبارسنجهای تعریف شده به تنظیمات برنامه
پس از تعریف UniqueEmailValidator و RemoteClientValidator، روش افزودن آنها به تنظیمات FluentValidation به صورت زیر است:
namespace FluentValidationSample.Web
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IUsersService, UsersService>();
services.AddControllersWithViews().AddFluentValidation(
fv =>
{
fv.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly());
fv.RegisterValidatorsFromAssemblyContaining<RegisterModelValidator>();
fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
fv.ConfigureClientsideValidation(clientSideValidation =>
{
// ...
clientSideValidation.Add(
validatorType: typeof(UniqueEmailValidator),
factory: (context, rule, validator) =>
new RemoteClientValidator(rule, validator)
{
RemoteUrl = "/Home/ValidateUniqueEmail"
});
});
}
);
}
در اینجا یک RemoteUrl را هم مشاهده میکنید که به صورت زیر باید تعریف شود:
namespace FluentValidationSample.Web.Controllers
{
public class HomeController : Controller
{
private readonly IUsersService _usersService;
public HomeController(IUsersService usersService)
{
_usersService = usersService;
}
// ...
public IActionResult ValidateUniqueEmail(string email)
{
return Ok(_usersService.IsUniqueEmail(email));
}
}
}
زمانیکه اعتبارسنجی سمت کلاینت رخ میدهد، آدرس ایمیل، به اکشن متد فوق ارسال شده و یک true و یا false را دریافت میکند که بیانگر موفقیت آمیز بودن و یا شکست اعتبارسنجی از راه دور است.
تعریف کدهای جاوا اسکریپتی مورد نیاز
پیش از هرکاری، اسکریپتهای فایل layout برنامه باید چنین تعریفی را داشته باشند:
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
در اینجا مداخل jquery، سپس jquery.validate و بعد از آن jquery.validate.unobtrusive را مشاهده میکنید. در ادامه نیازی به تکمیل فایل js/site.js جهت افزودن کدهای remote client validation نیست و این کدها جزئی از کتابخانهی jquery.validate.unobtrusive هستند.
آزمایش برنامه
View این قسمت نیز همانند
قسمت قبل است که فقط یک آدرس ایمیل به آن اضافه شدهاست:
برای آزمایش آن اگر برای مثال یکی از آدرسهای ایمیل از پیش تعریف شدهی در متد IsUniqueEmail سرویس کاربران را وارد کنیم، با خطای اعتبارسنجی سمت کلاینت فوق روبرو خواهیم شد.
کدهای کامل این سری را تا این قسمت از اینجا میتوانید دریافت کنید: FluentValidationSample-part04.zip