تعلم ASP.NET MVC


الدرس: التحقق من صحة البيانات


الصفحة السابقة

التحقق من صحة النموذج


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

 [Table("Restos")]
public class Resto
{
    public int Id { get; set; }
    [Required]
    public string Nom { get; set; }
    [Display(Name="Téléphone")]
    public string Telephone { get; set; }
}
إنه هو ، السمة الموجودة Required لذلك. أخبرني ، يمكننا التفكير جيدًا في التحكم قليلاً في عملنا ، مثل:

 [HttpPost]
public ActionResult ModifierRestaurant(Resto resto)
{
    if (string.IsNullOrWhiteSpace(resto.Nom))
    {
        ViewBag.MessageErreur = "Le nom du restaurant doit être rempli";
        return View(resto);
    }
    using (IDal dal = new Dal())
    {
        dal.ModifierRestaurant(resto.Id, resto.Nom, resto.Telephone);
        return RedirectToAction("Index");
    }
}
ثم أضف رسالة صغيرة مثل:

<div>
    @Html.LabelFor(model => model.Nom)
    @Html.TextBoxFor(model => model.Nom)
    <span style="color:red">@ViewBag.MessageErreur</span>
</div>
مما سيعطينا:
ASP.NET framework
التحقق من الشكل البسيط
ولما لا ... ستعمل ...
ما عدا ذلك !!!! لا حاجة للقيام بكل ذلك ، يمكن لـ ASP.NET MVC القيام بذلك لنا.
مجرد إلقاء نظرة على محتوى IsValid   خاصية الكائن ModelState  . على سبيل المثال ، إذا لم أدخل قيمة في حقل الاسم ، IsValid   فستكون قيمتها false  . لذا يمكننا تغيير الكود السابق عن طريق:

 [HttpPost]
public ActionResult ModifierRestaurant(Resto resto)
{
    if (!ModelState.IsValid)
    {
        ViewBag.MessageErreur = ModelState["Nom"].Errors[0].ErrorMessage;
        return View(resto);
    }
    using (IDal dal = new Dal())
    {
        dal.ModifierRestaurant(resto.Id, resto.Nom, resto.Telephone);
        return RedirectToAction("Index");
    }
}
أنا أستخدم اختراقًا هنا لعرض رسالة الخطأ ، ولكن سترى أنه فقط للمثال ؛ سيكون لدينا حل أكثر أناقة في وقت قصير.
في الواقع ، كل نتيجة التحقق في الكائن  ModelState  . يحتوي هذا الكائن أيضا القاموس مع كل ثلاثة من خاصيات Resto  :   Id  ،  Nom   و Téléphone  . فقط الخطأ المقابل للاسم به خطأ ونجد رسالة الخطأ المرتبطة به في مجموعة الأخطاء. وبالتالي يتم عرض هذه الرسالة في رأينا:
ASP.NET framework
التحقق من صحة النموذج بواسطة خاصية ModelState
يتم إنشاء رسالة الخطأ هذه تلقائيًا من خلال إطار عمل التحقق مما لدينا في النموذج:

 [Required]
public string Nom { get; set; }
إذا أردنا تعديل رسالة الخطأ ، يمكننا الإشارة إليها في السمة:

 [Required(ErrorMessage = "Le nom du restaurant doit être saisi")]
public string Nom { get; set; }
ومع ذلك ، من التقييد تمامًا أن تضطر إلى العثور على رسالة الخطأ في ModelState ، وتمريرها في ViewBag   العلامة وإنشاء علامة لعرضها في رأينا. لحسن الحظ ، هناك حل أكثر أناقة للقيام بذلك: استخدم المساعد Html.ValidationMessage   ولا سيما مكافئه المكتوب. ما عليك سوى استبدال <span>   هذا الاستدعاء لـ helper :

@Html.ValidationMessageFor(model => model.Nom)
وكل شيء آلي. لا حاجة للذهاب للحصول على رسالة الخطأ ، أو ملء ViewBag ... باختصار ، ليس هناك ما يمكن فعله في وحدة التحكم بخلاف التحقق من صلاحية النموذج:

 [HttpPost]
public ActionResult ModifierRestaurant(Resto resto)
{
    if (!ModelState.IsValid)
        return View(resto);
    using (IDal dal = new Dal())
    {
        dal.ModifierRestaurant(resto.Id, resto.Nom, resto.Telephone);
        return RedirectToAction("Index");
    }
}
وفي النهاية ، لدينا في النهاية:

@using (Html.BeginForm())
{
    <fieldset>
        <legend>Modifier un restaurant</legend>

        <div>
            @Html.LabelFor(model => model.Nom)
            @Html.TextBoxFor(model => model.Nom)
            @Html.ValidationMessageFor(model => model.Nom)
        </div>
        <div>
            @Html.LabelFor(model => model.Telephone)
            @Html.TextBoxFor(model => model.Telephone)
        </div>
        <br />
        <input type="submit" value="Modifier" />
    </fieldset>
}
والنتيجة هي نفسها ، باستثناء أن اللون ليس أحمر:
ASP.NET framework
التحقق من خلال إطار التحقق
إذا نظرنا إلى HTML الذي تم إنشاؤه لرسالة الخطأ هذه ، فلدينا:

<span class="field-validation-error" data-valmsg-for="Nom" data-valmsg-replace="true">Le nom du restaurant doit être saisi</span>
اسم السمة class هو السمة المستخدمة افتراضيًا. من الممكن استخدام هذه السمة لإضافة اللون الأحمر. إلى جانب ذلك ، يتم إنشاء هذا النمط بشكل افتراضي عند إنشاء مشروع ASP.NET MVC ، وهو موجود في هذا الموقع /Content/Site.css .
إنها ورقة أنماط بسيطة تم إنشاؤها بواسطة Visual Studio . يمكننا إضافتها إلى العرض في القسم <head>   :

<link type="text/css" href="../../Content/Site.css" rel="stylesheet" />
يمكننا الآن أن نرى أن صفحتنا لها مظهر جديد ، وذلك بفضل ورقة الأنماط:
ASP.NET framework
التحقق من الصحة مع النمط
وها هو الأمر ، تحقق جيد قليل. 
وهذه هي المصادقة الوحيدة. هذا يعني أنه هو الشيء الأساسي والذي يتم على جانب الخادم ، بواسطة وحدة التحكم ، والذي يجعل من الممكن التحقق الكامل من قواعد العمل الخاصة بتطبيقنا. نتحدث عن التحقق من جانب الخادم لأنه خادم الويب (وبالتالي ASP.NET MVC )الذي يتلقى طلبًا مع إرسال النموذج والذي يمكنه التحقق من المحتوى قبل تحديد ما إذا كان صحيحًا أم لا.
إذا كانت رسالتك لا تزال غير حمراء أو إذا كان ملف Site.css لا يحتوي على النمط التالي ، فأضفه إلى ورقة الأنماط الخاصة بك:

<style type="text/css">
    .field-validation-error { color : #f00;}
    .validation-summary-errors { color : #f00; font-weight: bold};
    .input-validation-error { border: 2px solide #f00;background-color : #fee}
    input[type="checkbox"].input-validation-error { outline: 2px solid #f00;}
</style>

التحقق من جانب العميل


ولكن هناك عملية تحقق أخرى ، وهي جميلة وتوفر الوقت: التحقق من جانب العميل. تذكر في مقدمتنا إلى HTML ، تحدثنا عن JavaScript وأظهرنا كيفية إجراء التحقق المصغر من جانب العميل. إنها في الواقع طريقة JavaScript يتم تنفيذها من قبل المتصفح عند إرسال النموذج والتي تمنع هذا الإرسال إذا كانت حقولنا غير صالحة.
هذا التحقق ذو أهمية كبيرة لأنه يمنع إرسال الصفحة إلى الخادم إذا كانت هناك مشكلة ، مما يوفر وقت مستخدمينا. ومع ذلك ، لا يمكن أن يكون كافيًا بمفرده لأنه يمكن للمستخدم إلغاء تنشيط JavaScript في متصفحه.
أؤكد لك على الفور ، أننا لن نكتب نفس نوع جافا سكريبت الذي كتبناه في الجزء الأول لأن ... ASP.NET MVC لديه بالفعل كل شيء مخطط له.  ملك البساطة. تحتاج فقط إلى تضمين نصوص جاهزة تم إنشاؤها في حلنا. لذا أضف في نص <head> النصوص التالية:

<script type="text/javascript" src="../../Scripts/jquery-1.10.2.js"></script>
<script type="text/javascript" src="../../Scripts/jquery.validate-vsdoc.js"></script>
<script type="text/javascript" src="../../Scripts/jquery.validate.unobtrusive.js"></script>
توجد هذه البرامج النصية أيضًا في إصدار مصغر ، تليها. Min . هذه النسخ أصغر (وغير مقروءة!) إصدارات مخصصة للاستخدام في الإنتاج لأنها أقل تحميلًا. أشجعك على استخدام الإصدارات العادية أثناء التطوير والإصدارات المصغرة عند نشر التطورات الخاصة بك.
تذكر للتحقق من رقم إصدار الملفات ، فقد تكون مختلفة قليلاً في منطقتك اعتمادًا على التحديثات المختلفة.
أعِد عرض صفحة التعديل في أحد المطاعم لمحاولة التحقق من النموذج دون ملء الاسم. مستحيل! يمكنك أن ترى أنه لا يوجد خادم ذهابا وإيابا وأنه لم يتم نشر الصفحة.
إنه بالطبع بفضل النصوص jQuery التي قمنا بتضمينها ولكن أيضًا بفضل حقيقة أن مقاطع HTML تولد سمات إضافية على علاماتنا ، كما رأينا أعلاه.
jQuery هي مكتبة JavaScript شائعة تهدف إلى تبسيط التطوير من جانب العميل. هناك أنواع أخرى من نفس النوع ولكنها اختارت Microsoft إكمال إطار عمل MVC .
بفضل اصطلاحات التسمية هذه ، تستطيع البرامج النصية للتحقق من jQuery العثور على العناصر الصحيحة للتحقق ، بالإضافة إلى رسائل الخطأ الصحيحة لعرضها.
نصيحة: سوف توافق معي على أنه لتضمين ملف نصي أو CSS ، فمن غير اللائق أن تستخدم عنوان URL النسبي مع
../../Scripts/etc
. من الممكن استبدال: <script type="text/javascript" src="../../Scripts/jquery-1.10.2.js"></script> 
بواسطة:
<script type="text/javascript" src="~/Scripts/jquery-1.10.2.js"></script> 
يشبه إلى حد ما استخدام المساعد Url.Content ، المسؤول عن إنشاء عنوان URL الصحيح.
لاحظ أن مبدأ التحقق هذا يعمل مع السمة Required   التي استخدمناها على اسم المطعم ، ولكن أيضًا مع العديد من السمات الأخرى التي تسمح لنا بالتحقق من العديد من الأشياء المختلفة. للحد من الحجم الأقصى لاسم المطعم ، يمكننا على سبيل المثال إضافة السمة StringLength   :

 [StringLength(80)]
public string Nom { get; set; }
وبالتالي ، لا يمكن أن يكون لديك اسم يزيد حجمه عن 80 حرفًا. شاهد ما يولده المساعد:

<input data-val="true" data-val-length="Le champ Nom doit être une chaîne dont la longueur maximale est de 80." data-val-length-max="80" data-val-required="Le nom du restaurant doit être saisi" id="Nom" name="Nom" type="text" value="Resto pinambour" />
لا تزال السمات الإضافية التي يتم إنشاؤها بواسطة المساعد والتي يستخدمها jQuery لإجراء التحقق من جانبنا من جانب العميل. تذكر أنه يجب أيضًا إجراء نفس التحقق من جانب الخادم ، في حالة تعطيل JavaScript ، عن طريق التحقق من الخاصية ModelState.IsValid  .
يمكنك حذف StringLength الذي لن نعيد استخدامه.
يمكنك إجراء عمليات تحقق أكثر تعقيدًا باستخدام التعبيرات العادية. يمكن استخدام هذا ، على سبيل المثال ، للتحقق من تكوين عنوان بريد إلكتروني بشكل صحيح. ليس لدينا رقم في نموذجنا ، ولكن لدينا رقم هاتف. بفضل التعبيرات العادية ، يمكننا التحقق من أن هذا رقم هاتف صحيح. لتبسيط حياتنا ، سنقول أن رقم الهاتف يبدأ بـ 0 ويتبعه 9 أرقام (بدون مسافات ، لا / أو شرطات):

 [Table("Restos")]
public class Resto
{
    […]
    [RegularExpression(@"^0[0-9]{9}$")]
    public string Telephone { get; set; }
}
لا تنس أن تضيف رسالة التحقق من صحة الهاتف في طريقة العرض:

@Html.ValidationMessageFor(model => model.Telephone)
وها ، عندما ندخل رقم هاتف غير صحيح ، نتلقى رسالة خطأ:
ASP.NET framework
التحقق من التعبير العادي
حسنًا ، حسنًا ، رسالة الخطأ ليست جيدة جدًا حقًا ... لكنك تعرف كيف تغيرها. ما عليك سوى ملء الخاصية ErrorMessage   :

 [RegularExpression(@"^0[0-9]{9}$", ErrorMessage="Le numéro de téléphone est incorrect")]
يعطي :
ASP.NET framework
باستخدام رسالة خطأ مخصصة
هذا التعبير العادي بسيط طوعيًا ولا يدير إشارات النداء من النوع + 33 ... لا تتردد في تحسينه .
يرجى ملاحظة أن هذا التعبير العادي قد يعني تأثيرًا جانبيًا على اختبارات DAL (ما لم تكن قد أجريت الاختبارات التي أجريتها أثناء المختبر) وعلى مُهيئ قاعدة البيانات. في الواقع ، في الاختبارات الأولى التي كتبناها لـ DAL ، استخدمنا أرقام الهواتف في النموذج 05 02 03 04 05. لم تعد صالحة. سيكون عليك إما تحسين التعبير العادي أو تغيير الاختبارات للتسجيل على سبيل المثال 0102030405. وبالمثل ، في مُهيئ قاعدة البيانات ، سيكون عليك تغيير أرقام الهاتف بحيث تكون صحيحة:

protected override void Seed(BddContext context)
{
    context.Restos.Add(new Resto { Id = 1, Nom = "Resto pinambour", Telephone = "0102030405" });
    context.Restos.Add(new Resto { Id = 2, Nom = "Resto pinière", Telephone = "0102030405" });
    context.Restos.Add(new Resto { Id = 3, Nom = "Resto toro", Telephone = "0102030405" });

    base.Seed(context);
}
لا تزال هناك سمات أخرى للتحقق ، دعنا نرى واحدة أخرى. على سبيل المثال ، إذا أردنا أن يكون الحقل صالحًا فقط إذا كانت قيمته بين اثنين آخرين ، فيمكننا استخدام السمة Range  . مثالية لعمر مستخدمنا على سبيل المثال ... حسنًا ، حسنًا ، ليس لدينا هذه الخاصية ، ولكن يمكننا امتلاكها. 

public class Utilisateur
{
    […]
    [Range(18, 120)]
    public int Age { get; set; }
}
سأقتبس منك مرة أخرى دون توضيح السمة Compare   التي تجعل من الممكن ضمان تطابق حقلين. يمكن استخدام هذا ، على سبيل المثال ، في نموذج تسجيل ، للتحقق من أن المستخدم لم يرتكب خطأ في عنوان بريده الإلكتروني من خلال إدخاله مرتين.
سنرى في درس على Ajax كيفية إجراء التحقق من جانب العميل الذي يتطلب تشغيل التعليمات البرمجية على الخادم.

التحقق المتقدم


إذا كنت غير قادر على تغطية كل سيناريوهات التحقق التي تريدها ، فلديك خيارات أخرى. الأول ، البسيط ، هو إجراء عمليات التحقق من جانب الخادم ، في وحدة التحكم الخاصة بك ؛ يشبه إلى حد ما ما فعلناه في البداية ، قبل معرفة آلية التحقق.
تخيل على سبيل المثال أنك قمت بعمل نموذج يسمح بإنشاء مطعم جديد ... خيال نسبي تمامًا لأننا سنقوم بذلك.  قبل إضافة هذا المطعم الجديد ، قد يكون من الجيد التحقق من عدم وجوده بالفعل. لهذا ، يمكننا استخدام الطريقة في DAL لدينا والتي ستتحقق من وجود مطعم:

public interface IDal : IDisposable
{
    bool RestaurantExiste(string nom);
    […]
}
بعد ذلك ، نحتاج إلى إنشاء طريقتين في وحدة التحكم ( GET و POST ) :

public ActionResult CreerRestaurant()
{
    return View();
}

[HttpPost]
public ActionResult CreerRestaurant(Resto resto)
{
    using (IDal dal = new Dal())
    {
        if (dal.RestaurantExiste(resto.Nom))
        {
            ModelState.AddModelError("Nom", "Ce nom de restaurant existe déjà");
            return View(resto);
        }
        if (!ModelState.IsValid)
            return View(resto);
        dal.CreerRestaurant(resto.Nom, resto.Telephone);
        return RedirectToAction("Index");
    }
}
بالإضافة إلى العرض المرتبط CreerRestaurant.cshtml :

@model  ChoixResto.Models.Resto

[...]

@using (Html.BeginForm())
{
    <fieldset>
        <legend>Ajouter un restaurant</legend>

        <div>
            @Html.LabelFor(model => model.Nom)
            @Html.TextBoxFor(model => model.Nom)
            @Html.ValidationMessageFor(model => model.Nom)
        </div>
        <div>
            @Html.LabelFor(model => model.Telephone)
            @Html.TextBoxFor(model => model.Telephone)
            @Html.ValidationMessageFor(model => model.Telephone)
        </div>
        <br />
        <input type="submit" value="Ajouter" />
    </fieldset>}
نستخدم الطريقة AddModelError للإشارة إلى وجود مشكلة في ملكية Nom المطعم. بالطبع لا ننسى أن نتحقق من صحة جميع الحقول ModelState.IsValid . لذلك ، عندما يكون المطعم موجودًا ، يمكننا الحصول على النتيجة التالية:
ASP.NET framework
التحقق المخصص
إنه حل مثير للاهتمام يتيح لنا القيام بأشياء لا تسمح لنا السيناريوهات الأساسية التي تقدمها ASP.NET MVC والتي قد تسمح لنا باختبار تناسق العديد من المجالات ، وهو أمر لا يمكننا فعله خلاف ذلك. على سبيل المثال ، تخيل أن لديك نموذج عميل يتعين عليك فيه تقديم خيار إدخال رقم هاتف أرضي أو رقم هاتف محمول أو كليهما ، وأن رقم هاتف واحد على الأقل مطلوب. لا يمكننا التفكير في استخدام السمة Required   على الحقول لأن النتيجة مشروطة بما هو موجود في الحقلين. هذا مثال على سيناريو نموذجي حيث يمكننا استخدام هذا النوع من التحقق.
باستثناء أنه إذا كان علينا القيام بهذا الاختبار في الإجراء الذي يجعل من الممكن الإنشاء ، ثم في الإجراء الذي يجعل من الممكن التعديل ، وربما في مكان آخر ، سنقوم بتكرار الكثير من التعليمات البرمجية ...
هناك العديد من الحلول لحل هذه المشكلة. الأول هو جعل النموذج يحمل عملية التحقق الخاصة به ، والتي يجب أن تكون متوافقة مع آلية التحقق من إطار عمل ASP.NET MVC . هذا الحل هو تنفيذ الواجهة IValidatableObject  . يتطلب تنفيذ طريقة التحقق:

public class Resto : IValidatableObject
{
    public int Id { get; set; }
    public string Nom { get; set; }
    public string Telephone { get; set; }
    public string Email { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (string.IsNullOrWhiteSpace(Telephone) && string.IsNullOrWhiteSpace(Email))
            yield return new ValidationResult("Vous devez saisir au moins un moyen de contacter le restaurant", new [] {"Telephone", "Email"});
        // etc.
    }
}
للتوضيح ، قمت بإزالة سمات الصف لتسهيل قراءته. وبدلاً من الاختيار بين هاتف أرضي أو هاتف محمول ، سيكون لديك الاختيار بين الهاتف والبريد الإلكتروني. لذا أضفت خاصية Email   (يمكنك حذفها لاحقًا) في فئة Resto للنموذج. المبدأ هو إجراء عمليات التحقق من صحة النموذج وإرجاع أي أخطاء قد تحدث.
في المقابل ، هذه الفئة تتناسب تمامًا مع آلية التحقق ، وإذا كان هناك قلق ، فسيكون ModelState.IsValid   الأمر يساوى false  . وسيكون لدينا:
ASP.NET framework
التحقق من الصحة الشخصية مع واجهة IValidatableObject
تتميز هذه الطريقة بكونها قابلة للمشاركة بين وحدات التحكم التي يجب عليها التحقق من صحة النموذج ، وهو أمر عملي إلى حد ما. بالطبع ، يتم التحقق من الصحة هذا من جانب الخادم ، يجب عليك أيضًا تحويله من جانب العميل إذا كنت تريد أن يقوم المتصفح بالتحكم نفسه قبل نشر الصفحة ...
للقيام بذلك ، يجب علينا أولاً الاهتمام بهذا التحقق من صحة العملاء الشهيرة. دعونا ننظر بالفعل في كيفية هيكلة الحقل الذي يجب التحقق من صحته ، لنأخذ حقل الاسم الإلزامي الذي رأيناه بالفعل:

<input data-val="true" data-val-length="Le champ Nom doit être une chaîne dont la longueur maximale est de 80." data-val-length-max="80" data-val-required="Le nom du restaurant doit être saisi" id="Nom" name="Nom" type="text" value="" />
في هذا المثال ، هناك عناصر تم وضعها بواسطة السمة StringLength   التي جعلتك تحذفها منذ قليل. هذا حتى تفهم بشكل أفضل كيفية عمل التحقق من صحة العميل.
كما يمكنك أن تتخيل ، يعمل إطار التحقق من صحة ASP.NET MVC بفضل الاتفاقيات. ويتم التعبير عن هذه الاصطلاحات من خلال سمات العلامات. يمكننا أن نرى على وجه الخصوص سمة data-val   ل true  . هذا يعني أن الحقل يخضع للمصادقة. ثم نرى واحدة data-val-length   وواحدة data-val-required  . في الواقع ، يشير هذا إلى أنه يجب التحقق من صحة الحقل بواسطة الطريقة length   والطريقة required  . محتوى هذه السمة هو بالضبط رسالة الخطأ التي يتم عرضها بعد ذلك.
وبالمثل ، يمكننا أن نشك في أنه data-val-length-max   يشير إلى القيمة القصوى لطول السلسلة وأن هذه القيمة ، 80 ، يتم تمريرها كمُدخل للطريقة length  .
إذا أردنا إنشاء طريقة مخصصة جديدة للتحقق من صحة حقل ، فيجب علينا استخدام هذا النوع من الشكليات من أجل إدخال أنفسنا في آلية التحقق. يتم التحقق من جانب العميل من خلال إطار عمل التحقق من صحة jQuery ، لذلك سيتعين علينا كتابة طريقة نمرر فيها اسمين للحقول كمُدخلات والتي يجب تنفيذها من خلال إطار التحقق. يتم ذلك على النحو التالي:

<script type="text/javascript">
    jQuery.validator.unobtrusive.adapters.add
        ("verifcontact", ["parametre1", "parametre2"], function (options) {
            options.rules["verifcontact"] = options.params;
            options.messages["verifcontact"] = options.message;
        });
</script>
وهكذا أضيف إلى المصدقين طريقة verifcontact   تقبل مصفوفة من مُدخلين ، مسماة parametre1   و parametre2  . يستخرج إطار التحقق من قيم سمات علامة HTML ويضعها في المُدخل options . Options.params   لذلك يحتوي على جدول المُدخلات والخيارات. تحتوي الرسالة على رسالة الخطأ. بعد ذلك ، يتعين علي فقط تمرير المُدخلات إلى آلية التحقق حتى أتمكن من العثور عليها في طريقة التحقق التالية:

<script type="text/javascript">
    jQuery.validator.addMethod("verifcontact",
    function (value, element, params) {
        var tel = $("#" + params.parametre1).val();
        var email = $("#" + params.parametre2).val();
        return tel != '' || email != '';
    });
</script>
إذا كنت تعرف القليل عن بناء جملة jQuery ، فلا يجب أن يمثل ذلك مشكلة لك. المبدأ هو إضافة الطريقة verifcontact   إلى آلية التحقق. ثم أستعيد قيمة الحقلين اللذين أمررهما في المُدخلات وأتأكد مما إذا كان هناك حقل واحد على الأقل مملوء. سيسمح لي إرجاع false إلى الإشارة إلى فشل التحقق من الصحة ، بالطبع تشير الإرادة الحقيقية إلى أن التحقق لا بأس به.
جافا سكريبت ليست آمنة أو محسنة حقًا ، ولكن الهدف هو أنك تفهم بسهولة ما أفعله.
يبقى فقط لكتابة HTML مع السمات التي هي على ما يرام. يجب أن تنجح في الحصول على HTML التالي:

<input data-val="true" data-val-regex="Le numéro de téléphone est incorrect" data-val-regex-pattern="^0[0-9]{9}$" data-val-verifcontact="Vous devez saisir au moins un moyen de contacter le restaurant" data-val-verifcontact-parametre1="Telephone" data-val-verifcontact-parametre2="Email" id="Telephone" name="Telephone" type="text" value="" />

<input data-val="true" data-val-verifcontact="Vous devez saisir au moins un moyen de contacter le restaurant" data-val-verifcontact-parametre1="Telephone" data-val-verifcontact-parametre2="Email" id="Email" name="Email" type="text" value="" />
نظرًا لأن حقل الهاتف لدينا يخضع بالفعل للتحقق ، فإنه يحتوي بالفعل على سمة data-val تساوي true . لا يزال لدي السمات الأخرى التي يجب إنشاؤها. لدينا السمة:

data-val-verifcontact="Vous devez saisir au moins un moyen de contacter le restaurant"
ثم المُدخلان:

data-val-verifcontact-parametre1="Telephone" data-val-verifcontact-parametre2="Email"
يحترم HTML هذا جيدًا الاصطلاحات التي رأيناها ، أي من verifcontact   هو اسم الأسلوب ، مسبوقًا به data-val  . المُدخلات تتبع نفس المبدأ. لقد قمت بالطبع بتعيين قيمة هذه السمات كأسماء الحقول التي أريد التحقق منها.
لقد رأينا بالفعل أنه لإضافة السمات ، كان عليك بناء كائن مجهول وتمريره كمُدخل مساعد. يعطي :

@Html.TextBoxFor(model => model.Telephone, new { data_val_verifcontact = "Vous devez saisir au moins un moyen de contacter le restaurant", data_val_verifcontact_parametre1 = "Telephone", data_val_verifcontact_parametre2 = "Email" })

@Html.TextBoxFor(model => model.Email, new { data_val = "true", data_val_verifcontact = "Vous devez saisir au moins un moyen de contacter le restaurant", data_val_verifcontact_parametre1 = "Telephone", data_val_verifcontact_parametre2 = "Email"})
ملاحظة: نظرًا لأنه من غير الممكن إنشاء خصائص C# التي تحتوي على شرطة (-) في أسمائها ، فإن ASP.NET MVC لديه حل بديل. ما عليك سوى استخدام تسطير سفلي (_) بدلاً من ذلك ، ويقوم الإطار تلقائيًا بتحويله إلى شرطة. لذلك data_val_verifcontact   سيتم إنشاؤها في data-val-verifcontact  .
دعنا لا ننسى أن حقل البريد الإلكتروني يضيف قيمة بيانات إلى true لأن هذا الحقل ليس موجودًا بالفعل.
أقدم لك JavaScript الكامل ، والذي يجب بالطبع وضعه في العلامة <head>   :

<script type="text/javascript">
    jQuery.validator.addMethod("verifcontact",
    function (value, element, params) {
        var tel = $("#" + params.parametre1).val();
        var email = $("#" + params.parametre2).val();
        return tel != '' || email != '';
    });

    jQuery.validator.unobtrusive.adapters.add
        ("verifcontact", ["parametre1", "parametre2"], function (options) {
            options.rules["verifcontact"] = options.params;
            options.messages["verifcontact"] = options.message;
        });
</script>
هذا كل شيء ، التحقق من صحة العميل في مكانه. الآن نحن قادرون على التحقق من صحة هذه الحقول دون رحلة ذهابا وإيابا الخادم.
لا تنس أنه من الضروري الحفاظ على التحقق من صحة الخادم في حالة قيام المستخدم بإلغاء تنشيط JavaScript على متصفحه.
هذه هي التقنية ، ولكن من المؤلم إنشاء الحقول في HTML . لحسن الحظ ، هناك حل آخر لإجراء عمليات التحقق الشخصية. هذا الحل يجعل من الممكن أن تكون قابلة لإعادة الاستخدام ، لكنه يريد أن يكون أكثر عمومية وربما حتى يمكن نقله في العديد من التطبيقات.
في الواقع ، سنقوم بإنشاء سمة جديدة ، بنفس الطريقة مثل تلك الموجودة بالفعل مثل Required ... مثالية لتمديد عمليات التحقق القليلة الموجودة لإطار التحقق.
للقيام بذلك ، يجب عليك إنشاء فئة جديدة ستستمد منها ValidationAttribute  . هذه هي أنواع الفئات التي يجب إنشاؤها في تجميع منفصل. هناك ، نظرًا لأن هذا مثال سنقوم بإزالته قريبًا ، يمكنك إنشاؤه في الحل الخاص بك:

public class AuMoinsUnDesDeuxAttribute : ValidationAttribute
{
    public string Parametre1 { get; set; }
    public string Parametre2 { get; set; }

    public AuMoinsUnDesDeuxAttribute() : base("Vous devez saisir au moins un moyen de contacter le restaurant")
    {
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        PropertyInfo[] proprietes = validationContext.ObjectType.GetProperties();
        PropertyInfo info1 = proprietes.FirstOrDefault(p => p.Name == Parametre1);
        PropertyInfo info2 = proprietes.FirstOrDefault(p => p.Name == Parametre2);

        string valeur1 = info1.GetValue(validationContext.ObjectInstance) as string;
        string valeur2 = info2.GetValue(validationContext.ObjectInstance) as string;

        if (string.IsNullOrWhiteSpace(valeur1) && string.IsNullOrWhiteSpace(valeur2))
            return new ValidationResult(ErrorMessage);
        return ValidationResult.Success;
    }
}
أوه هناك ... ما هذا الشيء المعقد ... يبدو مثل التفكير ؟
نعم ، هذا صحيح.
المبدأ هو أنه يجب علينا استبدال الطريقة من IsValid   أجل توفير منطق التحقق الخاص بنا. التظاهر الوحيد هو عرض إمكانية تمرير المُدخلات إلى هذه السمة (يشبه إلى حد ما رسالة الخطأ ، أو حجم السلسلة). هنا يجب علينا تمرير سلسلتين تمثل الحقول التي نريد التحقق منها. في هذه الحالة، سيكون من الحقول Telephone   و Email   بالطبع. يتدخل الانعكاس لمعرفة قيمة هاتين الخاصيتين في كائن السياق. نسعى لمعرفة قيمة الخاصية Resto.Telephone  وكذلك قيمة الخاصيةResto.Email  . هذا هو بالضبط ما يفعله هذا الكود. بمجرد الحصول على القيمتين ، علينا فقط التحقق من إدخال واحدة على الأقل من القيمتين. 
يمكننا أن نرى أننا نمرر قيمة افتراضية إلى رسالة الخطأ باستخدام مُنشئ الفئة الأساسية وأننا نستخدمها لإرجاع رسالة الخطأ في حالة التحقق من صحة غير صحيحة.
بعد ذلك ، علينا فقط استخدام السمة ، كما فعلنا مع تلك التي نعرفها بالفعل:

public class Resto
{
    public int Id { get; set; }
    public string Nom { get; set; }
    [AuMoinsUnDesDeux(Parametre1 = "Telephone", Parametre2 = "Email", ErrorMessage = "Vous devez saisir au moins un moyen de contacter le restaurant")]
    public string Telephone { get; set; }
    [AuMoinsUnDesDeux(Parametre1 = "Telephone", Parametre2 = "Email", ErrorMessage = "Vous devez saisir au moins un moyen de contacter le restaurant")]
    public string Email { get; set; }
}
لقد قمت بالطبع بإزالة Resto طريقة التحقق التي قمنا بها من قبل. جانب HTML ، إنه نفس الشيء الذي حذفت فيه السمات الإضافية التي قمت بوضعها في الحقول. الهاتف والبريد الإلكتروني هما ببساطة:

<div>
    @Html.LabelFor(model => model.Telephone)
    @Html.TextBoxFor(model => model.Telephone)
    @Html.ValidationMessageFor(model => model.Telephone)
</div>
<div>
    @Html.LabelFor(model => model.Email)
    @Html.TextBoxFor(model => model.Email)
    @Html.ValidationMessageFor(model => model.Email)
</div>
والآن ، هناك المزيد للاختبار.
ASP.NET framework
التحقق من صحة شخصية بفضل سمة
الكمال كل ذلك ، ما عدا ذلك ... أنها تفتقر إلى التحقق من جانب العميل! مرة أخرى. لكن هذا لا ينقصنا كثيرًا لأننا كتبنا كل جافا سكريبت من قبل. أنها تفتقر فقط إلى ما يكفي لإنشاء HTML بشكل صحيح. ولجعلها قابلة لإعادة الاستخدام قدر الإمكان ، سنحرص على أن تكون السمة هي التي تنشئ HTML الصحيح. لهذا ، يجب أن تنفذ صفتنا الواجهة IClientValidatable   :

public class AuMoinsUnDesDeuxAttribute : ValidationAttribute, IClientValidatable
{
    public string Parametre1 { get; set; }
    public string Parametre2 { get; set; }

    public AuMoinsUnDesDeuxAttribute() : base("Vous devez saisir au moins un moyen de contacter le restaurant")
    {
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        PropertyInfo[] proprietes = validationContext.ObjectType.GetProperties();
        PropertyInfo info1 = proprietes.FirstOrDefault(p => p.Name == Parametre1);
        PropertyInfo info2 = proprietes.FirstOrDefault(p => p.Name == Parametre2);

        string valeur1 = info1.GetValue(validationContext.ObjectInstance) as string;
        string valeur2 = info2.GetValue(validationContext.ObjectInstance) as string;

        if (string.IsNullOrWhiteSpace(valeur1) && string.IsNullOrWhiteSpace(valeur2))
            return new ValidationResult(ErrorMessage);
        return ValidationResult.Success;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        ModelClientValidationRule regle = new ModelClientValidationRule();
        regle.ValidationType = "verifcontact";
        regle.ErrorMessage = ErrorMessage;
        regle.ValidationParameters.Add("parametre1", Parametre1);
        regle.ValidationParameters.Add("parametre2", Parametre2);
        return new List<ModelClientValidationRule> { regle };
    }
}
تتطلب منا هذه الواجهة تنفيذ الطريقة GetClientValidationRules   وتعطينا الفرصة لتحديد العناصر التي سنحتاجها لقواعد التحقق الخاصة بنا. هنا، في هذه الحالة أنا أنشأت قاعدة جديدة تحتوي على اسم الأسلوب جافا سكريبت إطار التحقق من صحة Jquery لدعوة verifcontact  . ننتهز هذه الفرصة للإشارة إلى رسالة الخطأ والمُدخلين لسمة لدينا.
كل هذا سيجعل من الممكن إنشاء HTML الصحيح بشكل صحيح ، وذلك ببساطة بفضل صفتنا. إذا قمت بإعادة ترجمة العرض وإعادة عرضه ، فالحقيقة البسيطة الآن هي تعيين السمة على خصائص الهاتف والبريد الإلكتروني للنموذج ، جنبًا إلى جنب مع المساعدين

@Html.TextBoxFor(model => model.Telephone)
@Html.TextBoxFor(model => model.Email)
سينتج HTML التالي:

<input data-val="true" data-val-regex="Le numéro de téléphone est incorrect" data-val-regex-pattern="^0[0-9]{9}$" data-val-verifcontact="Vous devez saisir au moins un moyen de contacter le restaurant" data-val-verifcontact-parametre1="Telephone" data-val-verifcontact-parametre2="Email" id="Telephone" name="Telephone" type="text" value="" />
<input data-val="true" data-val-verifcontact="Vous devez saisir au moins un moyen de contacter le restaurant" data-val-verifcontact-parametre1="Telephone" data-val-verifcontact-parametre2="Email" id="Email" name="Email" type="text" value="" />
وهو مثالي لطريقة التحقق من جافا سكريبت التي كتبناها. 
الكثير لهذين الحلين لإنشاء عمليات التحقق الشخصية. الأمر متروك لك لاختيار أفضل ما يناسب احتياجاتك.

اعرض العرض الصحيح


لقد رأينا بالفعل أنها كانت الطريقة View()   والطريقة PartialView()   المسؤولة عن اختيار العرض الذي ستعرضه وحدة التحكم. بدون مُدخلات ، تعرض طرق العرض هذه طريقة العرض الافتراضية ، وفقًا للاتفاقيات. من الممكن أيضًا اختيار طريقة عرض أخرى عن طريق تحديد اسمها أو موقعها كمُدخل عرض.
لقد رأينا أيضًا استخدام الطريقة RedirectToAction . كما يوحي الاسم ، تقوم هذه الطريقة بإعادة التوجيه ثم تنفيذ الإجراء الذي تم تمريره كمُدخل. استخدمناه عندما أنشأنا المطعم:

 [HttpPost]
public ActionResult CreerRestaurant(Resto resto)
{
    […]
    if (!ModelState.IsValid)
        return View(resto);
    dal.CreerRestaurant(resto.Nom, resto.Telephone);
    return RedirectToAction("Index");
}
بشكل ملموس ، على مستوى HTTP ، بعد POST للصفحة ، يقوم الخادم بإرجاع كود HTTP 302 الذي يشير إلى إعادة توجيه مؤقتة ، ثم ينفذ Index الإجراء ، كما لو كنا قد انتقلنا إلى عنوان URL /Restaurant/Index .
هذه الطريقة لها أخت صغيرة تسمى RedirectToActionPermanent . يفعل نفس الشيء باستثناء أنه بدلاً من إرجاع رمز HTTP 302 ، فإنه يُرجع 301 مما يشير إلى إعادة توجيه دائمة. بدلاً من ذلك ، يتم استخدام هذه الطريقة عندما لا يكون للمورد سببًا يدعو إلى التواجد في هذا الموقع.
وفي السياق نفسه، لدينا في الطرق المتوفرة لدينا Redirect   و RedirectPermanent   التي على التوالي تعود ويمكن أن تشمل رمز 302 ورمز 301. هذه الطريقة العودة إلى موقع خارجي (ولكن أيضا إلى صفحة داخلية):

public ActionResult AfficheOpenClassRooms(string id) 
{
    return Redirect("http://fr.openclassrooms.com/");
}
ما زلنا في نفس النوع ، لدينا طريقتان RedirectToRoute  و RedirectToRoutePermanent  . إنهم يشبهون إلى حد كبير RedirectToAction   ولكنهم أكثر مرونة قليلاً من حيث أننا نعبر الطرق مباشرة إليها:

public ActionResult RetourAccueil(string id) 
{
    return RedirectToRoute(new { controller = "Accueil", action = "index" });
}
من الممكن أيضًا إرجاع رمز HTTP 404 ، مشيرًا إلى أنه لا يمكن العثور على المورد باستخدام الطريقة HttpNotFound . تتذكر طريقة عرض مطعم لتعديله:

public ActionResult ModifierRestaurant(int? id)
{
    if (id.HasValue)
    {
        Resto restaurant = dal.ObtientTousLesRestaurants().FirstOrDefault(r => r.Id == id.Value);
        if (restaurant == null)
            return View("Error");
        return View(restaurant);
    }
    else
        return View("Error");
}
هناك العديد من الاختبارات التي تسمح لك بالتحقق من وجود المعرّف الذي تم تمريره كمُدخل ( نعم ، لا شيء يمنعك من إدخال أي شيء في عنوان URL ، أو معرف غير موجود أو حتى سلسلة) . إذا لم يتم العثور عليه ، فسنعود إلى عرض الخطأ المشترك. قد يكون من المناسب إعادة رمز 404 بدلاً من ذلك إذا لم يكن للمعرف قيمة ، للإشارة إلى أن الصفحة غير موجودة. في هذه الحالة ، سنستخدم الطريقة HttpNotFound :

public ActionResult ModifierRestaurant(int? id)
{
    if (id.HasValue)
    {
        Resto restaurant = dal.ObtientTousLesRestaurants().FirstOrDefault(r => r.Id == id.Value);
        if (restaurant == null)
            return View("Error");
        return View(restaurant);
    }
    else
        return HttpNotFound();
}
يتم استخدام طرق أخرى لإنشاء المحتوى. هذه هي الحالة ، على سبيل المثال ، للطريقة Content() التي تُرجع سلسلة أحرف:

public ActionResult AfficheChaine()
{
    return Content("Pas de HTML, juste une chaine");
}
هذا يعادل ما قمنا به في الجزء الأمامي في بداية دراسة وحدات التحكم. لقد قمنا بتغيير نوع النتيجة التي ترجع من ActionResult  الى سلسلة .   تسمح الطريقة Content() بإرجاع سلسلة مع الاحتفاظ بنوع مشتق منه ActionResult  .
في نفس النوع ، يمكننا أيضًا إرجاع JavaScript أو JSON .
يشير JSON إلى JavaScript Object Notation وهو تنسيق بيانات شائع جدًا لتمثيل المعلومات المنظمة ، مثل XML إلى حد كبير. ميزة JSON هي أنه يعمل بشكل أصلي مع JavaScript.
بشكل عام ، نستخدم JSON لتسلسل كائن أو إلغاء تسلسله لجعله ينتقل بين نظامين غير متجانسين. على سبيل المثال ، يمكنني تقديم تمثيل JSON لمطعم بهذه الطريقة:

public ActionResult AfficheJson()
{
    Resto resto = new Resto { Id = 1, Nom = "Resto pinambour" };
    return Json(resto, JsonRequestBehavior.AllowGet);
}
والتي سترسل لي JSON التالي:

{"Id":1,"Nom":"Resto pinambour","Telephone":null}
كن حذرًا ، إذا كانت البيانات حساسة ، يجب عليك دائمًا إعادة JSON أثناء POST (وليس GET ) لتجنب JSON Hijacking . هذا هو السبب في أن الطريقة تأخذ مُدخلا للإشارة إلى أنها تسمح JSON من خلال GET ( AllowGet ) . دون الخوض في التفاصيل ، JSON Hijacking هو هجوم يهدف إلى سرقة المعلومات من موقع إلى آخر.
ترى أنه بهذه الطريقة ، يمكننا إرجاع شيء آخر غير HTML . من الممكن أيضًا الترتيب لإرسال الملفات مرة أخرى حتى يتمكن المستخدم من تنزيل المحتوى. يحدث هذا مع الطريقة File()  . على سبيل المثال ، إذا قمت بإضافة ملف إلى دليل App_Data ، فيمكنني أن أحمل المستخدم على تنزيله عن طريق القيام بما يلي:

public ActionResult ObtientFichier()
{
    string fichier = Server.MapPath("~/App_Data/MonFichier.txt" );
    return File(fichier, "application/octet-stream", "MonFichier.txt");
}
كل هذه الأساليب هي مساعدين يهدفون إلى إرجاع فئة مشتقة ActionResult   بهدف إرجاع المحتوى المطلوب.
بنفس الطريقة ، يمكنك إرجاع مثيل من الفصل EmptyResult   لعرض صفحة فارغة ، وهي غير مجدية :

public ActionResult RenvoiDuVide()
{
    return EmptyResult();
}
ولكن من الممكن أيضًا إعادة الكائنات مباشرة ، دون المرور بطرق المساعدين ، من أجل إرجاع محتوى مختلف.
على سبيل المثال ، يمكننا إرجاع رمز خطأ HTTP 401 ، للإشارة إلى أن الوصول إلى المورد غير مصرح به ، وذلك بفضل الفئة HttpUnauthorizedResult   :

public ActionResult AccesAuthentifie()
{
    if (HttpContext.User.Identity.IsAuthenticated)
        return View();
    return new HttpUnauthorizedResult();
}
أو حتى إرجاع أي رمز HTTP مع HttpStatusCodeResult :

public ActionResult AccesAuthentifie()
{
    if (HttpContext.User.Identity.IsAuthenticated)
        return View();
    return new HttpStatusCodeResult(401);
}
مع كل ذلك ، يجب أن تكون قادرًا على إعادة المحتوى الذي تريده إلى المستخدم الخاص بك.

اختبار وحدة تحكم


أستطيع أن أرى ذلك في عينيك: كنت حزينًا لأنه لا يمكنك كتابة اختبارات آلية للعرض. اطمئن ، مع رنين وحدات التحكم لعودة الاختبار ... Ahhh ، لطيف.  بفضل تقسيم MVC ، يصبح من الأسهل كتابة اختبارات آلية. وهذا هو الحال أيضًا مع وحدات التحكم.
فماذا يجب أن يفعل اختبار تحكم؟
عدة أشياء يجب عليه:
  • تحقق من أن العرض (أو الإجراء) الصحيح قد تم إرجاعه بواسطة إجراء وحدة التحكم ؛
  • تحقق من أن نموذج العرض هو النموذج الذي نتوقعه ؛
  • تحقق من وجود البيانات التي تم تمريرها إلى العرض.
هذا ما أقترح أن توضحه. خذ على سبيل المثال وحدة تحكم المنزل البسيطة الخاصة بنا:

public class AccueilController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}
ما نريد التحقق منه هنا هو أنه من خلال إنشاء وحدة تحكم واستدعاء طريقتها index ، نحصل على طريقة العرض الافتراضية. للقيام بذلك ، لنقم بإنشاء فئة اختبار جديدة نسميها على سبيل المثال AccueilControllerTests  . المبدأ هو إنشاء وحدة تحكم ، لاستدعاء الأسلوب Index   ومقارنة النتيجة. تذكر ، عملنا يُرجع ActionResult  .
في الواقع ، View()   ترجع الطريقة ViewResult   فئة مشتقة منها ActionResult   وهذه الفئة لها خاصية ViewName   اسم العرض. إليك الاختبار للتحقق من ذلك:

 [TestMethod]
public void AccueilController_Index_RenvoiVueParDefaut()
{
    AccueilController controller = new AccueilController();

    ViewResult resultat = (ViewResult)controller.Index();

    Assert.AreEqual(string.Empty, resultat.ViewName);
}
ملاحظة: ستحتاج إلى إضافة مرجع إلى تجميع System.Web.Mvc للعثور على الفئة ViewResult  .
تحتوي طريقة العرض الافتراضية على اسم فارغ لأننا لا نعرض أي شيء كمُدخل أسلوب View()  .
يمكننا أيضًا التحقق من عرض المعلومات الصحيحة بشكل صحيح. تخيل أن لدينا إجراء يعرض التاريخ ويعرض رسالة صغيرة:

public ActionResult AfficheDate(string id)
{
    ViewBag.Message = "Bonjour " + id + " !";
    ViewData["Date"] = new DateTime(2012, 4, 28);
    return View("Index");
}
يمكننا اختبار هذا الإجراء مع الاختبار التالي:

 [TestMethod]
public void AccueilController_AfficheDate_RenvoiVueIndexEtViewData()
{
    AccueilController controller = new AccueilController();

    ViewResult resultat = (ViewResult)controller.AfficheDate("Nicolas");

    Assert.AreEqual("Index", resultat.ViewName);
    Assert.AreEqual(new DateTime(2012, 4, 28), resultat.ViewData["date"]);
    Assert.AreEqual("Bonjour Nicolas !", resultat.ViewBag.Message);
}
نلاحظ أنه هذه المرة ، نحصل على اسم العرض في خاصية ViewName . وبالمثل ، يمكن الوصول إلى الرسائل المختلفة عبر الكائنات ViewData   أو ViewBag  .
يمكنك حذف هذا الاختبار والإجراء الذي يعرض التاريخ ، ولن نستخدمه بعد الآن.
حسنًا ، بالنسبة إلى وحدة التحكم Accueil  ، فهي بسيطة إلى حد ما لأنها لا تحتوي على تبعية. حيث يصبح الأمر أصعب بالنسبة لوحدة التحكم Restaurant   لأنها تستخدم قاعدة البيانات.
هنا ، نريد فقط اختبار وحدة التحكم لأننا نكتب اختبار وحدة. لقد كتبنا بالفعل اختبارات للتحقق من أن النموذج كان جيدًا ، لذلك لا نحتاج إلى إعادة اختباره. بالإضافة إلى ذلك ، لا نريد أن نضطر إلى استبدال قاعدة البيانات في كل مرة. لذلك علينا توصيل DAL .
ستقول لي: Moq ! وسأجيبك لما لا ... باستثناء أنه يمكننا استخدام تقنيات أخرى لتوصيل DAL . يمكننا على سبيل المثال كتابة DAL خاطئة من شأنها تنفيذ نفس الواجهة ولكن التي تستخدم البيانات في الذاكرة بدلاً من استمرار البيانات في الأساس. من بين أمور أخرى ، هذا سوف يسرع إلى حد كبير من وقت تنفيذ اختباراتنا.
لذا دعنا نضيف الفئة DalEnDur   إلى مشروعنا التجريبي. يجب على هذا الفصل بالطبع تنفيذ الواجهة IDal   :

public class DalEnDur : IDal
{
    private List<Resto> listeDesRestaurants;
    private List<Utilisateur> listeDesUtilisateurs;
    private List<Sondage> listeDessondages;

    public DalEnDur()
    {
        listeDesRestaurants = new List<Resto>
        {
            new Resto { Id = 1, Nom = "Resto pinambour", Telephone = "0102030405"},
            new Resto { Id = 2, Nom = "Resto pinière", Telephone = "0102030405"},
            new Resto { Id = 3, Nom = "Resto toro", Telephone = "0102030405"},
        };
        listeDesUtilisateurs = new List<Utilisateur>();
        listeDessondages = new List<Sondage>();
    }

    public List<Resto> ObtientTousLesRestaurants()
    {
        return listeDesRestaurants;
    }

    public void CreerRestaurant(string nom, string telephone)
    {
        int id = listeDesRestaurants.Count == 0 ? 1 : listeDesRestaurants.Max(r => r.Id) + 1;
        listeDesRestaurants.Add(new Resto { Id = id, Nom = nom, Telephone = telephone });
    }

    public void ModifierRestaurant(int id, string nom, string telephone)
    {
        Resto resto = listeDesRestaurants.FirstOrDefault(r => r.Id == id);
        if (resto != null)
        {
            resto.Nom = nom;
            resto.Telephone = telephone;
        }
    }

    public bool RestaurantExiste(string nom)
    {
        return listeDesRestaurants.Any(resto => string.Compare(resto.Nom, nom, StringComparison.CurrentCultureIgnoreCase) == 0);
    }

    public int AjouterUtilisateur(string nom, string motDePasse)
    {
        int id = listeDesUtilisateurs.Count == 0 ? 1 : listeDesUtilisateurs.Max(u => u.Id) + 1;
        listeDesUtilisateurs.Add(new Utilisateur { Id = id, Prenom = nom, MotDePasse = motDePasse });
        return id;
    }

    public Utilisateur Authentifier(string nom, string motDePasse)
    {
        return listeDesUtilisateurs.FirstOrDefault(u => u.Prenom == nom && u.MotDePasse == motDePasse);
    }

    public Utilisateur ObtenirUtilisateur(int id)
    {
        return listeDesUtilisateurs.FirstOrDefault(u => u.Id == id);
    }

    public Utilisateur ObtenirUtilisateur(string idStr)
    {
        int id;
        if (int.TryParse(idStr, out id))
            return ObtenirUtilisateur(id);
        return null;
    }

    public int CreerUnSondage()
    {
        int id = listeDessondages.Count == 0 ? 1 : listeDessondages.Max(s => s.Id) + 1;
        listeDessondages.Add(new Sondage { Id = id, Date = DateTime.Now, Votes = new List() });
        return id;
    }

    public void AjouterVote(int idSondage, int idResto, int idUtilisateur)
    {
        Vote vote = new Vote
        {
            Resto = listeDesRestaurants.First(r => r.Id == idResto),
            Utilisateur = listeDesUtilisateurs.First(u => u.Id == idUtilisateur)
        };
        Sondage sondage = listeDessondages.First(s => s.Id == idSondage);
        sondage.Votes.Add(vote);
    }

    public bool ADejaVote(int idSondage, string idStr)
    {
        Utilisateur utilisateur = ObtenirUtilisateur(idStr);
        if (utilisateur == null)
            return false;
        Sondage sondage = listeDessondages.First(s => s.Id == idSondage);
        return sondage.Votes.Any(v => v.Utilisateur.Id == utilisateur.Id);
    }

    public List<Resultats> ObtenirLesResultats(int idSondage)
    {
        List<Resto> restaurants = ObtientTousLesRestaurants();
        List<Resultats> resultats = new List<Resultats>();
        Sondage sondage = listeDessondages.First(s => s.Id == idSondage);
        foreach (IGrouping<int, Vote> grouping in sondage.Votes.GroupBy(v => v.Resto.Id))
        {
            int idRestaurant = grouping.Key;
            Resto resto = restaurants.First(r => r.Id == idRestaurant);
            int nombreDeVotes = grouping.Count();
            resultats.Add(new Resultats { Nom = resto.Nom, Telephone = resto.Telephone, NombreDeVotes = nombreDeVotes });
        }
        return resultats;
    }

    public void Dispose()
    {
        listeDesRestaurants = new List<Resto>();
        listeDesUtilisateurs = new List<Utilisateur>();
        listeDessondages = new List<Sondage>();
    }
}
الفئة طويلة بعض الشيء نظرًا لوجود العديد من الطرق ، ولكنها في الواقع تشبه إلى حد كبير DAL التي لدينا بالفعل ، باستثناء أنه لا يوجد استخدام لإطار العمل على الإطلاق وأن كل شيء مخزن في قوائم الذاكرة.
باستثناء ذلك ... كيفية استخدام هذا DalEnDur  دون تعديل كود وحدة التحكم الخاصة بنا وإزعاج عمل تطبيق الويب لدينا؟ لأنه سيظل جيدًا إذا تمكنا من استخدامه DalEnDur   في اختباراتنا الآلية وأن DAL العادي هو الذي يتم استخدامه عند التنقل في تطبيقنا. ولكن في الوقت الحالي من المستحيل. انظر إلى كود التحكم:

public class RestaurantController : Controller
{
    public ActionResult Index()
    {
        using (IDal dal = new Dal())
        {
            List<Resto> listeDesRestaurants = dal.ObtientTousLesRestaurants();
            return View(listeDesRestaurants);
        }
    }

    […]
}
يتم إنشاء DAL مباشرة في نص الإجراء. والأمر نفسه بالنسبة إلى الإجراءات الأخرى التي أخفيتها هنا.
لذلك سيتعين علينا إعادة صياغة هذا الكود بحيث يكون الاعتماد على DAL أقل قوة. لهذا ، يمكننا استخدام مبدأ حقن التبعية المفرطة في التبسيط ولكن هذا سيساعدنا هنا على الحصول على اقتران ضعيف بين وحدة التحكم والنموذج.
يكفي تقديم إمكانية إنشاء DAL خارج وحدة التحكم وتمرير هذا المثيل إلى مُنشئ وحدة التحكم الخاصة بنا. 

public class RestaurantController : Controller
{
    private IDal dal;

    public RestaurantController(IDal dalIoc)
    {
        dal = dalIoc;
    }

    public ActionResult Index()
    {
        List<Resto> listeDesRestaurants = dal.ObtientTousLesRestaurants();
        return View(listeDesRestaurants);
    }

    […]
}
لاحظ أنه لم يعد هناك الآن using   في جسم العمل Index   ؛ لذلك يجب القيام بنفس الشيء بالنسبة للإجراءات الأخرى.
وبالتالي ، سنكون قادرين على كتابة اختبار سينسخ DAL الكاذب ويمررها إلى وحدة التحكم عبر منشئها. لإظهار ذلك ، قم بإنشاء فئة جديدة من الاختبارات ، مخصصة لجهاز التحكم Restaurant ، والتي يمكننا تسميتها RestaurantControllerTests   :

 [TestMethod]
public void RestaurantController_Index_LeControleurEstOk()
{
    using (IDal dal = new DalEnDur())
    {
        RestaurantController controller = new RestaurantController(dal);

        ViewResult resultat = (ViewResult)controller.Index();

        List<Resto> modele = (List<Resto>)resultat.Model;
        Assert.AreEqual("Resto pinambour", modele[0].Nom);
    }
}
ترى مدى بساطة استخدامه هنا DalEnDur للاختبار. لا يزال هناك قلق. لم يعد تطبيق MVC يعمل لأن وحدة التحكم تحتاج إلى مُنشئ افتراضي ويجب أن توفر أيضًا تطبيقًا لـ DAL العادي. يمكن حل هذا ببساطة بهذه الطريقة:

public class RestaurantController : Controller
{
    private IDal dal;

    public RestaurantController() : this(new Dal())
    {
    }

    public RestaurantController(IDal dalIoc)
    {
        dal = dalIoc;
    }

    public ActionResult Index()
    {
        List<Resto> listeDesRestaurants = dal.ObtientTousLesRestaurants();
        return View(listeDesRestaurants);
    }

    […]
}
لذلك ، عندما يتم إنشاء وحدة التحكم بواسطة ASP.NET MVC ، فإننا نستخدم فئة DAL . عندما يتم نسخها بواسطة الاختبارات ، فإنها تستخدم تلك DalEnDur   التي نمررها في مُدخلات المنشئ.
ملاحظة: هناك أنظمة عكس تبعية أكثر تقدمًا تعمل جيدًا مع ASP.NET MVC . المبدأ هو إنشاء factory مسؤول عن إنشاء وحدات التحكم باستخدام IOC . لن أوضح كيف تعمل هنا ، ولكن بشكل عام من السهل القيام بذلك.
وها هو الأمر ، تم حل مشكلة التبعية. يمكننا الانغماس مرة أخرى في اختباراتنا والتحقق من أن الإجراء الذي يجعل من الممكن تعديل مطعم يعمل بشكل كامل. اتخاذ الإجراء الذي يستجيب لطلب POST ، وهي:

 [HttpPost]
public ActionResult ModifierRestaurant(Resto resto)
{
    if (!ModelState.IsValid)
        return View(resto);
    dal.ModifierRestaurant(resto.Id, resto.Nom, resto.Telephone);
    return RedirectToAction("Index");
}
ما نريد التحقق منه هنا هو أنه بتمرير Resto  غير صحيح نحصل على العرض الذي يعيد عرض المطعم لتغييره. وعندما يكون هذا Resto   صحيحًا ، يتم إعادة توجيهنا إلى العمل بشكل جيد Index  . ولكن هناك القليل من الدقة ... في الواقع ، يتم التحقق من النموذج أثناء ربط النموذج الذي يتم قبل إنشاء عمل وحدة التحكم ، وهو أمر لا يمكننا التحكم فيه. فجأة ، هذا يعني أنه ModelState.IsValid   سيكون دائمًا ذا قيمة true  .
هذا أمر مزعج لأننا نود أن نكون قادرين على التحقق من أنه عندما يكون النموذج غير صالح ، فإننا نحصل على العرض الافتراضي مع النموذج الصحيح. لذلك ، سيتعين علينا محاكاة حقيقة أن النموذج غير صالح عن طريق الإضافة إلى الخاصية ModelState   خطأ. يتم ذلك في بداية طريقة الاختبار ، والتي تستخدم لإعداد البيانات:

 [TestMethod]
public void RestaurantController_ModifierRestaurantAvecRestoInvalide_RenvoiVueParDefaut()
{
    using (IDal dal = new DalEnDur())
    {
        RestaurantController controller = new RestaurantController(dal);
        controller.ModelState.AddModelError("Nom", "Le nom du restaurant doit être saisi");

        ViewResult resultat = (ViewResult)controller.ModifierRestaurant(new Resto { Id = 1, Nom = null, Telephone = "0102030405" });

        Assert.AreEqual(string.Empty, resultat.ViewName);
        Assert.IsFalse(resultat.ViewData.ModelState.IsValid);
    }
}
لذا ، من خلال استدعاء الطريقة AddModelError  ، نعيد الخاصية IsValid   إلى false ، مما يسمح لنا بالتحقق من أننا نحصل على ما نريد.
ستقول لي بالتأكيد أنها ليست رهيبة. نقوم بتمرير Resto   مُدخل وحدة تحكم باسم غير محدد عندما يكون إلزاميًا ونضطر لمحاكاة أنفسنا بحقيقة أن النموذج غير صالح.
في الواقع ، إذا فكرت في الأمر ، فسوف تسأل نفسك السؤال التالي:
ما الذي يحاول الاختبار الخاص بي التحقق منه؟ هذا النموذج ملزم والتحقق يعمل؟
هذا غير مفيد للاختبار لأن تجليد(binding) النموذج يعمل! إنه أحد مكونات إطار عمل ASP.NET MVC ، لذا فهو يعمل. هناك بالتأكيد اختبارات وحدة كتبها مهندسو Microsoft للتحقق من صحة عملها.
لا ، يجب أن يتحقق اختبارنا من أنه عندما يكون النموذج غير صالح ، يكون العرض الذي تم إرجاعه هو الافتراضي. هذا بالضبط ما يفعله هذا الاختبار.
أدرك من ناحية أخرى أنه ليس من العملي جدًا أن نعدد الأخطاء المختلفة التي سنواجهها وأن بناء Resto   غير صالح (Invalid) أسهل . يمكننا تبسيط مهمتنا من خلال محاكاة هذا التحقق من ربط النموذج. دعنا ننشئ طريقة تمديد صغيرة تسمح لنا بالتحقق من صحة نموذجنا. للقيام بذلك ، يمكنك إضافة فئة ثابتة إلى مشروع الاختبار الخاص بك ، والذي سوف نسميه ControllerExtensions   :

public static class ControllerExtensions
{
    public static void ValideLeModele(this Controller controller, T modele)
    {
        controller.ModelState.Clear();

        ValidationContext context = new ValidationContext(modele, null, null);
        List<ValidationResult> validationResults = new List<ValidationResult>();
        Validator.TryValidateObject(modele, context, validationResults, true);

        foreach (ValidationResult result in validationResults)
        {
            foreach (string memberName in result.MemberNames)
            {
                controller.ModelState.AddModelError(memberName, result.ErrorMessage);
            }
        }
    }
}
وقالت إنها تحرص على التحقق من صحة النموذج عن طريق استدعاء إطار طريقة التحقق من الصحة :  TryValidateObject  . إذا كانت هناك أخطاء ، فسيتم إضافتها تلقائيًا إلى الخاصية ModelState  .
وبالتالي ، يمكننا إنشاء اختبار للتحقق من صحة النموذج والتحقق مما يحدث عندما يكون غير صالح:

 [TestMethod]
public void RestaurantController_ModifierRestaurantAvecRestoInvalideEtBindingDeModele_RenvoiVueParDefaut()
{
    RestaurantController controller = new RestaurantController(new DalEnDur());
    Resto resto = new Resto { Id = 1, Nom = null, Telephone = "0102030405" };
    controller.ValideLeModele(resto);

    ViewResult resultat = (ViewResult)controller.ModifierRestaurant(resto);

    Assert.AreEqual(string.Empty, resultat.ViewName);
    Assert.IsFalse(resultat.ViewData.ModelState.IsValid);
}
ما عليك سوى استدعاء طريقة التحقق قبل استدعاء إجراء وحدة التحكم التي نريد اختبارها.
ها أنت ذا.
وفقًا للمبدأ نفسه ، يمكننا التحقق مما يحدث عندما يكون النموذج صالحًا:

 [TestMethod]
public void RestaurantController_ModifierRestaurantAvecRestoValide_CreerRestaurantEtRenvoiVueIndex()
{
    using (IDal dal = new DalEnDur())
    {
        RestaurantController controller = new RestaurantController(dal);
        Resto resto = new Resto { Id = 1, Nom = "Resto mate", Telephone = "0102030405" };
        controller.ValideLeModele(resto);

        RedirectToRouteResult resultat = (RedirectToRouteResult)controller.ModifierRestaurant(resto);

        Assert.AreEqual("Index", resultat.RouteValues["action"]);
        Resto restoTrouve = dal.ObtientTousLesRestaurants().First();
        Assert.AreEqual("Resto mate", restoTrouve.Nom);
    }
}
يمكننا أن نرى أن RedirectToAction   الإحالة واحدة RedirectToRouteResult   وأنه يمكننا الوصول إلى الإجراء باستخدام الخاصية RouteValues . لاحظ أن هذا القاموس يحتوي أيضًا على اسم وحدة التحكم ، ولكن نظرًا لعدم استخدامه هنا ، نظرًا لأنه مماثل لإجراء الاستدعاء ، فلن يكون موجودًا في القاموس.
أتوقف عند اختبارات التحكم لأنني أعلم أنك قد فهمت بالفعل أهمية اختبار كل ما يمكن للمرء تلقائيًا. لديك الآن المفاتيح لاختبار وحدات التحكم الخاصة بك والتأكد من أنها تعمل وأنه مع تقدمك ، لا تقوم بإنشاء تراجع.
لاحظ أنه لا يزال بإمكاننا تبسيط الاختبارات باستخدام المساعدين الذين يمكن العثور عليهم على الشبكة. هناك أيضًا مشروع كامل يهدف إلى تسهيل تطوير ASP.NET MVC ، والذي يحتوي بشكل خاص على مساعدين للاختبار. يمكنك الذهاب لإلقاء نظرة إذا كنت متحدثًا باللغة الإنجليزية:   http://mvccontrib.codeplex.com/wikipage?title=TestHelper

باختصار


  • يتم استخدام وحدات التحكم لمعالجة إجراءات المستخدم وتحديد أي طريقة عرض للعودة إلى المتصفح.
  • من السهل جدًا تمرير المُدخلات إلى وحدة تحكم بفضل الآلية التي تطابق عناصر عنوان URL ومُدخلات الطريقة.
  • ربط النموذج هو عنصر قوي للغاية يسمح لك بتحويل عناصر نموذج HTML المقدمة إلى وحدة التحكم إلى كائنات معقدة في النموذج.
  • يمكن (ويجب)! التحقق من صحة النماذج المقدمة إلى وحدة التحكم قبل معالجتها ، وهذا هو دور إطار عمل التحقق من صحة ASP.NET MVC .
  • يسمح الإطار أيضًا بالتحقق من جانب العميل بمساعدة مكتبة jQuery .
  • لاختبار وحدة تحكم تحتوي على تبعيات بسهولة ، يمكنك استخدام آلية عكس التحكم لتوصيل هذه التبعية.