اگر بخواهیم یک Attribute سفارشی را برای اعتبارسنجی ایجاد کنیم، معمولا یک کلاس را ایجاد کرده و از ValidationAttribute ارث بری میکنیم و سپس متد IsValid آنرا override میکنیم؛ با توجه به نیازی که به آن Attribute داریم. به عنوان مثال در ادامه یک Attribute را ایجاد کردهایم که عمل مقایسهی دو خاصیت را انجام میدهد و اگر مقدار خاصیتی که ویژگی LowerThan بر روی آن قرار دارد، از مقدار خاصیت دیگری که باید با آن مقایسه شود، کمتر نباشد، یک خطا را به ModelState اضافه میکنیم:
ابتدا مقدار خاصیت مورد نظر را که میخواهیم با آن مقایسه شود، با استفاده از رفلکشن گرفتهایم و آن را در متغییر dependentPropertyValue ذخیره میکنیم. در ادامه مقدار Name را با استفاده از رفلکشن از DisplayAttribute میخوانیم و سپس عمل مقایسه را انجام میدهیم که اگر مقدار خاصیتی که ویژگی LowerThan بر روی آن قرار دارد، از مقدار خاصیت مورد نظر که مقدار آن را با استفاده از رفلکشن خواندهایم، کمتر نباشد، یک خطا را به ModelState اضافه میکنیم.
اما یک مشکل! این عمل فقط در سمت سرور بررسی میشود و هنگامیکه ModelState.IsValid را در اکشن متد فراخوانی میکنیم، عمل اعتبارسنجی انجام میشود. یعنی همهی دادهها به سمت سرور ارسال میشوند و اگر خطایی در ModelState وجود داشته باشد، کاربر مجددا باید دادهها را ارسال کند.
اما میتوان با استفاده از اینترفیس IClientModelValidator، عمل اعتبارسنجی را برای این ویژگی در سمت کلاینت انجام داد. برای انجام این کار ابتدا باید از اینترفیس IClientModelValidator ارث بری کنیم و متد AddValidation آن را پیاده سازی کنیم.
اینترفیس IClientModelValidator، یک متد به نام AddValidation دارد که این امکان را فراهم میکند تا بتوانیم اعتبارسنجی را در سمت کلاینت انجام دهیم. در ادامه باید با استفاده از JQuery اعتبارسنجی مخصوص این ویژگی را در سمت کلاینت پیاده سازی کنیم. در متد AddValidation فقط اسم تابع و پارامترهای مورد نیاز در سمت کلاینت را مشخص میکنیم. به عنوان مثال در مثال بالا یک تابع را معرفی کردهایم به نام lowerthan که بعدا باید آنرا در سمت کلاینت پیاده سازی کنیم و نام خاصیتی را که باید با آن مقایسه شود، با نام data-val-dependentpropertyname معرفی کردهایم. در کد زیر، این اعتبار سنجی سمت کلاینت را پیاده سازی کرده ایم. lowerthan نام متدی است که آنرا در متد AddValidation اضافه کردیم. مقدار value همان مقدار خاصیتی است که ویژگی LowerThan بر روی آن قرار دارد و otherPropId نام خاصیتی است که باید با آن مقایسه شود که آنرا از element خواندهایم:
کدهای جاواسکریپتی بالا را در یک فایل جدید به نام LowerThan.js ذخیره کردهایم که باید آن را به صفحه خود اضافه کنیم:
سپس برای استفاده، باید ویژگی LowerThan را بر روی خاصیت مورد نظر قرار دهیم؛ مانند زیر:
و در نهایت اگر مقدار خاصیت Experience که ویژگی LowerThan بر روی آن قرار دارد، از مقدار خاصیت Age که باید با آن مقایسه شود، کمتر باشد، true برگردانده میشود؛ اما اگر بزرگتر یا مساوی باشد، متن خطایی را که در متد AddValidation اضافه کردیم، نشان داده خواهد شد.
public class LowerThanAttribute : ValidationAttribute { public LowerThanAttribute(string dependentPropertyName) { DependentPropertyName = dependentPropertyName; } public string DependentPropertyName { get; set; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { int? currentPropertyValue = value as int?; currentPropertyValue ??= 0; var typeInfo = validationContext.ObjectInstance.GetType(); var dependentPropertyValue = Convert.ToInt32(typeInfo.GetProperty(DependentPropertyName) .GetValue(validationContext.ObjectInstance, null)); var displayDependentProperyName = typeInfo.GetProperty(DependentPropertyName) .GetCustomAttributes(typeof(DisplayAttribute), false) .Cast<DisplayAttribute>() .FirstOrDefault()?.Name; if (!(currentPropertyValue.Value < dependentPropertyValue)) { return new ValidationResult("مقدار {0} باید کمتر باشد از " + displayDependentProperyName); } return ValidationResult.Success; } }
اما یک مشکل! این عمل فقط در سمت سرور بررسی میشود و هنگامیکه ModelState.IsValid را در اکشن متد فراخوانی میکنیم، عمل اعتبارسنجی انجام میشود. یعنی همهی دادهها به سمت سرور ارسال میشوند و اگر خطایی در ModelState وجود داشته باشد، کاربر مجددا باید دادهها را ارسال کند.
اما میتوان با استفاده از اینترفیس IClientModelValidator، عمل اعتبارسنجی را برای این ویژگی در سمت کلاینت انجام داد. برای انجام این کار ابتدا باید از اینترفیس IClientModelValidator ارث بری کنیم و متد AddValidation آن را پیاده سازی کنیم.
public class LowerThanAttribute : ValidationAttribute, IClientModelValidator { public LowerThanAttribute(string dependentPropertyName) { DependentPropertyName = dependentPropertyName; } public string DependentPropertyName { get; set; } public void AddValidation(ClientModelValidationContext context) { var displayCurrentProperyName = context.ModelMetadata.ContainerMetadata .ModelType.GetProperty(context.ModelMetadata.PropertyName) .GetCustomAttributes(typeof(DisplayAttribute), false) .Cast<DisplayAttribute>() .FirstOrDefault()?.Name; var displayDependentProperyName = context.ModelMetadata.ContainerMetadata .ModelType.GetProperty(DependentPropertyName) .GetCustomAttributes(typeof(DisplayAttribute), false) .Cast<DisplayAttribute>() .FirstOrDefault()?.Name; MergeAttribute(context.Attributes, "data-val", "true"); MergeAttribute(context.Attributes, "data-val-lowerthan", $"{displayCurrentProperyName} باید کمتر باشد از {displayDependentProperyName}"); MergeAttribute(context.Attributes, "data-val-dependentpropertyname", "#" + DependentPropertyName); } private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value) { if (attributes.ContainsKey(key)) { return false; } attributes.Add(key, value); return true; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { int? currentPropertyValue = value as int?; currentPropertyValue ??= 0; var typeInfo = validationContext.ObjectInstance.GetType(); var dependentPropertyValue = Convert.ToInt32(typeInfo.GetProperty(DependentPropertyName) .GetValue(validationContext.ObjectInstance, null)); var displayCurrentProperyName = typeInfo.GetProperty(DependentPropertyName) .GetCustomAttributes(typeof(DisplayAttribute), false) .Cast<DisplayAttribute>() .FirstOrDefault()?.Name; if (!(currentPropertyValue.Value < dependentPropertyValue)) { return new ValidationResult("مقدار {0} باید کمتر باشد از " + displayCurrentProperyName); } return ValidationResult.Success; } }
jQuery.validator.addMethod("lowerthan", function (value, element, param) { var otherPropId = $(element).data('val-dependentpropertyname'); if (otherPropId) { var otherProp = $(otherPropId); if (otherProp) { var otherVal = otherProp.val(); if (parseInt(otherVal) > parseInt(value)) { return true; } return false; } } return true; }); jQuery.validator.unobtrusive.adapters.addBool("lowerthan");
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script> <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script> <script src="~/js/LowerThan.js"></script>
public class User { [Required] [Display(Name ="نام کاربری")] public string Username { get; set; } [Required] [Display(Name = "سن")] public int Age { get; set; } [LowerThan(nameof(Age))] [Required] [Display(Name = "سابقه کار")] public int Experience { get; set; } }