اگر بخواهیم یک Attribute را برای اعتبارسنجی ایجاد کنیم معمولا یک کلاس ایجاد میکنیم و از ValidationAttribute ارث بری میکنیم. و سپس متد IsValid را override میکنیم با توجه به نیازی که به آن Attribute داریم. به عنوان مثال در ادامه یک Attribute ایجاد کرده ایم که عمل مقایسه دو پراپرتی را انجام میدهد. و اگر پراپرتی که اتربیوت LowerThan بر روی آن قرار دارد از پراپرتی دیگری که باید با آن مقایسه شود کمتر نباشد یک خطا به ModelState اضافه میکنیم.

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;
    }
}

ابتدا مقدار پراپرتی مورد نظر را که میخواهیم با آن مقایسه شود را با استفاده از رفلکشن گرفته ایم و آن را در متغییر dependentPropertyValue ذخیره میکنیم. در ادامه مقدار Name را با استفاده از رفلکشن از DisplayAttribute میخوانیم و سپس عمل مقایسه را انجام میدهیم که اگر مقدار پراپرتی که اتربیوت LowerThan بر روی آن قرار دارد از پراپرتی مورد نظر که مقدار آن را با استفاده از رفلکشن خواندیم کمتر نباشد یک خطا به ModelState اضافه میکنیم در غیراین صورت عمل اعتبارسنجی با موفقیت انجام میشود.

اما یک مشکل. این عمل فقط در سمت سرور بررسی میشود و هنگامی که 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;
    }
}

اینترفیس IClientModelValidator یک متد به نام AddValidation دارد که این امکان را فراهم میکند تا بتوانیم اعتبارسنجی را در سمت کلاینت انجام دهیم. در ادامه باید با استفاده از JQuery اعتبارسنجی برای این اتربیوت را در سمت کلاینت پیاده سازی کنیم. در متد AddValidation فقط اسم تابع و پارامتر های مورد نیاز در سمت کلاینت را مشخص میکنیم. به عنوان مثال در مثال بالا یک تایع معرفی کرده ایم به نام lowerthan که بعدا باید آنرا در سمت کلاینت پیاده سازی کنیم و نام پراپرتی که باید با آن مقایسه شود را با نام data-val-dependentpropertyname معرفی کرده ایم. در کد زیر اعتبار سنجی سمت کلاینت را پیاده سازی کرده ایم. مقدار value همان مقدار پراپرتی است که اتربیوت LowerThan بر روی آن قرار دارد و otherPropId نام پراپرتی است که باید با آن مقایسه شود که آنرا از element خوانده ایم.

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");

کدهای جاواسکریپت بالا را در یک فایل به نام LowerThan.js ذخیره کرده ایم که باید آن را به صفحه خود اضافه کنیم.

برای استفاده از این اینترفیس باید فایل های مربوط به اعتبارسنجی سمت کلاینت  و فایل LowerThan.js را به صفحه خود اضافه کنید.

<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>

سپس برای استفاده باید اتربیوت LowerThan را بر روی پراپرتی مورد نظر قرار دهیم مانند زیر.

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; }
}

و در نهایت اگر مقدار پراپرتی Experience که اتربیوت LowerThan بر روی آن قرار دارد از مقدار پراپرتی Age که باید با آن مقایسه شود کمتر باشد true برگردانده میشود اما اگر بزرگتر یا مساوی باشد متن خطایی را که در متد AddValidation اضافه کردیم نشان داده خواهد شد.

Powered by Froala Editor