• 0
عبد الكريم

تعرف إلى الواصفة ValidationAttribute وطريقة استخدامها

سؤال

هناك عدة طرق للتحقق من قيم نموذج model منها

 

 

في هذه المقالة سنتعرف إلى كيفية استخدام الفئة ValidationAttribute

 

باختصار التطبيق الذي سنقوم به هو تعريف واصفة UnlikeAttribute مهمتها هي التحقق من عدم التطابق (الإختلاف) بين حقلين أو خاصيتين داخل فئة. اذا كنت قد استخدمت الواصفة CompareAttribute ستدرك أن ما سنقوم به الآن هو العكس تماما لمبدأ عمل Compare. حيث أن الفئة السابقة مهمتها التحقق من التطابق وبالتالي حدوث اختلاف بين قيمتين فهذا يعني أن النموذج غير صحيح وعليه يترتب إجراء مناسب لضمان سلامة وصحة البيانات. ولكن الواصفة UnlikeAttribute التي سنقوم بإنشائها ستكون مخالفة تماما. قد تتساءل ما الفائدة من ذلك؟ ببساطة في حال كنت قد قررت إضافة إمكانية تغير كلمة المرور للمستخدم داخل أحد تطبيقاتك فربما ستقرر بعدم السماح للمستخدم باستخدام نفس كلمة المرور الحالية ككلمة مرور جديدة!

 

تجهيز مشروع

 

الشيفرة التالية تظهر الفئة الجديدة مع التوابع التي سنحتاج إلى العمل عليها لإتمام المهمة

 

C#

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]public class UnlikeAttribute : ValidationAttribute
{
    private string otherPropertyName;
    private const string DefaultErrorMessage = "{0} must be different than {1}";
    public UnlikeAttribute(string otherProperty) : base(DefaultErrorMessage)
    {
        otherPropertyName = otherProperty;
    }
    public override string FormatErrorMessage(string name)
    {

    }

    protected override ValidationResult IsValid(object value,                ValidationContext validationContext)
    {

    }
}

 

 

 لاحظ أن كلا التابعين FormatErrorMessage و IsValid عبارة عن توابع معرفة داخل الفئة ValidationAttribute والآن نقوم بإعادة تعريفها داخل الفئة الجديدة

 

وظيفة التابع FormatErrorMessage هو تهيئة رسالة الخطأ وذلك باستخدام الخاصية ErrorMessageString التابعة للفئة ValidationAttribute [1] ( سنقوم باستخدام هذه الخاصية لاحقا عند تعرف التابع)

 

والآن لنقوم بتجهيز التابع IsValid على النحو التالي:

 

C# (UnlikeAttribute)

protected override ValidationResult IsValid(object value         , ValidationContext validationConte
{
    PropertyInfo otherProperty = validationContext.ObjectInstance
        .GetType()
        .GetProperty(otherPropertyName);
    
    // in case no property where found 
    if (otherProperty == null) throw new 
                IndexOutOfRangeException($"{otherPropertyName} does not exist");
    
    // getting the value of the other property
    var otherPropertyValue = 
	      otherProperty.GetValue(validationContext.ObjectInstance);
	
    // if they are different, then the case is successful
    if (!Equals(value, otherPropertyValue)) return ValidationResult.Success;
        
    return new ValidationResult(           FormatErrorMessage(validationContext.MemberName));
}

 

بإختصار كما تلاحظ فإنه قد تم استخدام ميزة الإنعكاس في الدوت نت للوصول إلى قيمة الخاصية otherPropertyName

 

 ValidationContext هي عبارة عن فئة تستخدم لوصف البيئة التي قد تم فيها إجراء عملية الفحص. بمعنى آخر أن ValidationContext.ObjectInstance هو عبارة عن الكائن الذي يتم إجراء عليه عملية التحقق

 

والآن لنجهز التابع FormatErrorMessage

C#

public override string FormatErrorMessage(string name)
{
    return string.Format(ErrorMessageString, name, otherPropertyName);
}

 

 

والآن قم بإنشاء نموذج يمثل تغير كلمة المرور PasswordChangeModel ، وقم بإضافة الواصفة إلى الخاصية NewPassword ومرر قيمة الخاصية OldPassword لتقوم الواصفة لاحقا بالتحقق من إختلاف كلا قيمتهما

 

public class PasswordChangeModel
{
    public string OldPassword { get; set; }

    [Unlike("OldPassword")]
    public string NewPassword { get; set; }
}

 

 

 كيفة استخدام الواصفة

بعد إضافة الواصفة إلى الخاصية NewPassword لنجري عملية التحقق في أن كلمة المرور الجديدة لا تطابق القديمة كما يلي. لنذهب إلى التابع Main واكتب الأسطر التالية

 

static void Main(string[] args)
{
    var model = new PasswordChangeModel();
    model.OldPassword = "1234";
    model.NewPassword = "1234";
    var list = new List<ValidationResult>();
    if(!Validator.TryValidateObject(model, 
        new ValidationContext(model, null, null), list, true))
    {
        Console.WriteLine("Some errors found");
        foreach (var item in list) Console.WriteLine(item.ErrorMessage);
    }
}

 

تم استخدام الفئة Validator واستدعاء التابع TryValidateObject لتقوم بدورها وباستدعاء التابع IsValid للواصفات المرتبطة بجميع الخصائص.

 

والآن شغل البرنامج

 

ناتج تنقيذ البرنامج يظهر تطابق قيمتي OldPassword و  NewPassword في حين يتوجب إختلافهما

 

كما تلاحظ رسالة الخطأ قد ظهرت وهي تظهر أن كلمة المرور متطابقة ويستلزم من المستخدم تغير كلمة المرور الجديدة بحيث تكون مختلفة عن القديمة ، وبهذا الشكل نكون قد تعرفنا إلى كيفية استخدام الفئة ValidationAttribute لتعريف واصفة خاصة بك.

 

 أمور إضافية قد تود تطبيقها

 

 لنظهر أسماء الخصائص الظاهرة في رسالة الخطأ

ربما تود إظهار أسماء الخصائص الظاهر بالرسالة بطريقة أفضل للمستخدم يمكننا استخدام الواصفة DisplayNameAttribute أو DisplayAttribute ، سنقوم باستخدام الواصفة DisplayAttribute إذ أن الأولى توفر إمكانية الربط مع ملف resx في حين الأخيرة لا توفر هذه الخاصية والآن لنجري التعديلات على الفئتين PasswordChangeModel و UnlikeAttribute على النحو التالي:

C#

public class PasswordChangeModel
{
    [Display(Name = "Old Password")]
    public string OldPassword { get; set; }
    [Display(Name = "New Password")]
    [Unlike("OldPassword")]
    public string NewPassword { get; set; }
}

 

 

والآن

C#

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class UnlikeAttribute : ValidationAttribute
{
    private string otherPropertyName;
    private string otherPropertyDisplay;
    private const string DefaultErrorMessage = "{0} must be different than {1}";
    public UnlikeAttribute(string otherProperty) : base(DefaultErrorMessage)
    {
        otherPropertyName = otherProperty;
    }
    public override string FormatErrorMessage(string name)
    {
        return string.Format(ErrorMessageString, name, otherPropertyDisplay);
    }
    protected override ValidationResult IsValid(object value, 
                               ValidationContext validationContext
    {
        PropertyInfo otherProperty = validationContext.ObjectInstance
            .GetType()
            .GetProperty(otherPropertyName);
        
        // in case no property where found 
        if (otherProperty == null)
            throw new IndexOutOfRangeException($"{otherPropertyName} does not exist");
        
        // getting the value of the other property
        var otherPropertyValue = 
                          otherProperty.GetValue(validationContext.ObjectInstance);
        // if they are different, then the case is successful
        if (!Equals(value, otherPropertyValue)) return ValidationResult.Success;
        // checks if DisplayAttribute has been used
        if (!IsDefined(otherProperty, typeof(DisplayAttribute)))
            return new ValidationResult(
                                                   FormatErrorMessage(validationContext.MemberName));

        // gets the DisplayAttribute.Name
        var displayAttribute =  (DisplayAttribute)  
                           otherProperty.GetCustomAttribute(typeof(DisplayAttribute));
        
        return new ValidationResult(FormatErrorMessage(validationContext.MemberName));
    }
}

 

 

تم تظليل التغيرات التي طرأت على الشيفرة

 

 

لاحظ كيف ظهرت الأسماء بناء على الواصفة NameAttribute

 

 ماذا لو أردت استخدام ملف resources.resx

الأمر سهل جدا قم بإضافة نوع المصدر ResourceType

 

محتوى ملف AppResource.resx

 

والآن عد إلى الفئة PasswordChangeModel وأضف المصدر الذي تود للواصفة Display الربط معه

C#

public class PasswordChangeModel {
    [Display(Name = "OldPassword", ResourceType = typeof(AppResource))]
    public string OldPassword { get; set; }

    [Display(Name = "NewPassword", ResourceType = typeof(AppResource))]
    [Unlike("OldPassword")]
    public string NewPassword { get; set; }}

 

 

عدل UnlikeAttribute على النحو التالي:

C#

protected override ValidationResult IsValid(object value, 
                      ValidationContext validationConte
{
    PropertyInfo otherProperty = validationContext.ObjectInstance
        .GetType()
        .GetProperty(otherPropertyName);
    
    // in case no property where found 
    if (otherProperty == null)
        throw new IndexOutOfRangeException($"{otherPropertyName} does not exist");
    
    // getting the value of the other property
    var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance);
    // if they are different, then the case is successful
    if (!Equals(value, otherPropertyValue)) return ValidationResult.Success;
    // checks if DisplayAttribute has been used
    if (!IsDefined(otherProperty, typeof(DisplayAttribute)))
        return new ValidationResult(FormatErrorMessage(validationContext.MemberName));
    // gets the DisplayAttribute.Name
    var displayAttribute = 
        (DisplayAttribute)otherProperty.GetCustomAttribute(typeof(DisplayAttribute));

    // checks if Resource is bound 
    if (displayAttribute.ResourceType != null)
    {
        var manager = new ResourceManager(displayAttribute.ResourceType);
        otherPropertyDisplay = manager.GetString(otherPropertyName);
        return new ValidationResult(
            FormatErrorMessage(manager.GetString(validationContext.MemberName)));
    }
    
    return new ValidationResult(FormatErrorMessage(validationContext.MemberName));
}

 

 

 

لاحظ قد ظهرت الأسماء كما هي مخزنة في AppResources.resx

 

الشيفرة المصدرية: https://github.com/a-kanaan/SharedWorkspace/tree/master/UnlikeAttributeConsoleApp

 

المصادر:

 

المدونة

تم تعديل بواسطه عبد الكريم
0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

0 إجابة على هذا السؤال .

لاتوجد إجابات على هذا السؤال حتى الآن .

من فضلك سجل دخول لتتمكن من التعليق

ستتمكن من اضافه تعليقات بعد التسجيل



سجل دخولك الان

  • يستعرض القسم حالياً   0 members

    لا يوجد أعضاء مسجلين يشاهدون هذه الصفحة .