تعلم ASP.NET MVC


الدرس: مرشحات (Filters)


الصفحة السابقة
نحن نعرف الآن تصرفات المتحكمين على أطراف الأصابع ، مع كل ما رأيناه وكل ما وضعناه موضع التنفيذ. ولكن هل تعلم أنه من الممكن إنشاء مرشحات للاجراءات؟ أو حتى مرشحات وحدات التحكم؟

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

مرشحات ASP.NET MVC


هل تتذكر السمة [Authorize]   التي تجعل من الممكن تأمين إجراءاتنا كوحدات تحكم عن طريق منع استدعاء هذه الإجراءات إذا لم تتم مصادقة المستخدم؟

حسنا هو مرشح. في هذه الحالة ، يتم تنفيذ هذا المرشح قبل إجراء وحدة التحكم ويضع شروط تنفيذه المحتمل. هذا عامل تصفية التخويل لأنه ينفذ IAuthorizationFilter  . هذا هو الحال أيضًا بالنسبة للفلتر RequireHttpsAttribute   الذي يتطلب تقديم طلب في HTTPS أو الفلتر ChildActionOnly   الذي تعرفه بالفعل.
هناك فلاتر أخرى ، مثل فلاتر الاستثناء التي تنفذ IExceptionFilter   مثل الفلتر HandleErrorAttribute   الذي يسمح بعرض طريقة عرض الخطأ في حالة حدوث استثناء أثناء تنفيذ الإجراء.

لاحظ أيضًا الفلتر OutputCacheAttribute   الذي يشير إلى ما إذا كان الإجراء يجب أن يتم تخزينه مؤقتًا وكيف ومتى. هو عبارة عن مرشح نتائج يقوم بتنفيذ IResultFilter  غرضه تعديل محتوى الذي تم ActionResult   ارجاعه بواسطة الطريقة. في هذه الحالة ، يضيف هذا الفلتر الرؤوس الخاصة بذاكرة التخزين المؤقت في العرض المعروض.
أخيرًا ، هناك فلاتر إجراءات تنفيذ IActionFilter   تسمح لك بالقيام بالأشياء قبل بدء الإجراء وبعد تنفيذه. مفيد جدًا على سبيل المثال لإضافة سجلات في تطبيقك أو لقياس المدة التي تستغرقها إجراءاتك المختلفة.
يمكنك دمج الفلاتر المختلفة في إجراءاتك أو وحدات التحكم الخاصة بك ، ولكن احذر من الترتيب الذي يتم تنفيذه بها. أولاً ، هناك فلاتر التفويض ، ثم فلاتر الإجراء ، ثم فلاتر النتائج وأخيرًا فلاتر الاستثناء.

إنشاء عامل التصفية الخاص بك


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

سنقوم بإنشاء مرشح عمل خاص بنا والذي سيسمح لنا بحل مشكلة واجهناها عندما قمنا بعمل Ajax . في الواقع ، لا أعرف ما إذا كنت تتذكر ، ولكن عندما أردنا تحديث جدولنا ، قمنا باستدعاء عرض جزئي مرتبط بعمل AfficheTableau   وحدة التحكم Vote  . ومع ذلك ، يمكن أيضًا عرض هذا العرض الجزئي عبر URL /Vote/AfficheTableau/1 (لرقم الاستطلاع 1) الذي ينتج عنه نتيجة غير مناسبة لأنه يتم عرضه خارج طريقة العرض الرئيسية الخاصة به. ومع ذلك ، كان من المستحيل تزيينها بالسمة ChildActionOnly   لأنها تسببت في حدوث خطأ.
سنكتب مرشحًا لحل هذه المشكلة. للقيام بذلك ، من الضروري الكشف عن طلب Ajax . لذا خذ محلل الاستعلام المفضل لديك ويمكنك أن ترى أن هناك رأسًا يحتوي على هذا الرأس: X-Requested-With:XMLHttpRequest
ASP.NET framework
header Ajax
إنه header غير قياسي يستخدم لتحديد طلبات Ajax . ترسله معظم أطر عمل JavaScript بالقيمة ، XMLHttpRequest   وهذا هو بالضبط الحال مع jQuery .
سنقوم بالتالي بكتابة عامل تصفية الإجراء الذي سيتحقق مما إذا كان هذا العنوان موجودًا في الطلب. لذا قم بإنشاء دليل عوامل التصفية وأضف الفئة التالية:

public class AjaxFilterAttribute : ActionFilterAttribute
{
}
يمكنك استبدال طريقة OnActionExecuting التي سيتم استدعاؤها قبل الإجراء مباشرة والتي تحتوي على سياق الطلب. انظر فقط إذا كان هناك هذا الرأس ، وإذا لم يكن موجودًا ، فعندئذٍ نعود إلى أن الطلب غير موجود:

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    if (filterContext.HttpContext.Request.Headers != null && filterContext.HttpContext.Request.Headers["X-Requested-With"] != "XMLHttpRequest")
        filterContext.Result = new HttpNotFoundResult();
    base.OnActionExecuting(filterContext);
}
ها أنت ذا. بسيط جدا. يبقى فقط لتزيين طريقة AfficheTableau التحكم باستخدام هذا الفلتر:

 [AjaxFilter]
public ActionResult AfficheTableau(int id)
{
    List<Resultats> resultats = dal.ObtenirLesResultats(id);
    return PartialView(resultats.OrderByDescending(r => r.NombreDeVotes).ToList());
}
وبالتالي ، لن يُرجع الإجراء أي شيء إذا لم يتم استدعاؤه في Ajax . ليس سيئا أليس كذلك؟
يمكننا أيضًا تبسيط هذه الطريقة باستخدام طريقة تمديد تتحقق مما إذا كان هذا الرأس موجودًا. هذه هي الطريقة IsAjaxRequest   وتقوم بنفس الوظيفة تقريبًا:

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    if (!filterContext.HttpContext.Request.IsAjaxRequest())
        filterContext.Result = new HttpNotFoundResult();
    base.OnActionExecuting(filterContext);
}
ومع ذلك ، يشكل هذا المرشح مشكلة صغيرة. في الواقع ، AfficheTableau يتم استدعاء العمل من قبل المساعد RenderAction الموجود في عرض PosterResult :

<p>Résultats du sondage :</p>
<div id="tableauResultat">
@{
Html.RenderAction("AfficheTableau", new { id = ViewBag.Id });
}
</div>
<p>Vue normale : @DateTime.Now.ToLongTimeString()</p>
ما سيحدث هو أنه عندما RenderAction يتم استدعاء الطريقة AfficheTableau ، فلن يكون استدعاء Ajax . وبالتالي أصبحت هذه المكالمة عديمة الفائدة ويمكنك بعد ذلك إفراغ علامة <div> :

<div id="tableauResultat">
</div>
<p>Vue normale : @DateTime.Now.ToLongTimeString()</p>
من ناحية أخرى ، سيظهر الجدول فقط بعد 10 ثوانٍ ، وهو الوقت الذي يعمل فيه تحديث Ajax . يمكنك ببساطة فرض مكالمة Ajax في المرة الأولى ، عند بدء تشغيل المؤقت:

<script type="text/javascript">
    var timer;
    function ChargeVuePartielle() {
        $.ajax({
            url: '@Url.Action("AfficheTableau", new {id = ViewBag.Id })',
            type: 'GET',
            dataType: 'html',
            success: function (result) {
                $('#tableauResultat').html(result);
            }
        });
    }

    $(function () {
        timer = window.setInterval("ChargeVuePartielle()", 10000);
        ChargeVuePartielle();
    });
</script>
ولكن مع كل ذلك ، ما هو السحر ، هو أن الاتصال المباشر بـ /Vote/PosterArray/1 لن يعرض أي شيء. 

اختبر الفلتر


Ahhh ... لقد مر وقت طويل منذ أن اختبرنا. 
مشكلة الفلتر هي أنها تُنفذ بواسطة محرك ASP.NET MVC ، لذلك عند اختبار إجراء من وحدة تحكم ، لا يمكنك الاستفادة من معالجة المرشح. ولكن في الوقت نفسه ، ليس الأمر خطيرًا للغاية لأن ما نريد اختباره هو المرشح وفقط هو.
ولهذا ، نحتاج إلى توصيل سياق التنفيذ. من قال Moq؟ 
حسنًا!
هذا لم يعد يحمل أي أسرار لك. في ما يلي الاختباران اللذان يتحققان من تشغيل الفلتر:

 [TestClass]
public class AjaxFilterAttributeTests
{
    [TestMethod]
    public void AjaxFilterOnActionExecuting_AvecAjaxHeader_LaissePasserLaRequete()
    {
        NameValueCollection fausseCollection = new NameValueCollection { { "X-Requested-With", "XMLHttpRequest" } };
        Mock<ActionExecutingContext> context = new Mock<ActionExecutingContext>();
        context.Setup(r => r.HttpContext.Request.Headers).Returns(fausseCollection);

        AjaxFilterAttribute filtre = new AjaxFilterAttribute();
        filtre.OnActionExecuting(context.Object);
            
        Assert.IsNull(context.Object.Result);
    }

    [TestMethod]
    public void AjaxFilterOnActionExecuting_SansAjaxHeader_RenvoiHttpNotFoundResult()
    {
        NameValueCollection fausseCollection = new NameValueCollection();
        Mock<ActionExecutingContext> context = new Mock<ActionExecutingContext>();
        context.Setup(r => r.HttpContext.Request.Headers).Returns(fausseCollection);

        AjaxFilterAttribute filtre = new AjaxFilterAttribute();
        filtre.OnActionExecuting(context.Object);

        Assert.IsInstanceOfType(context.Object.Result, typeof(HttpNotFoundResult));
    }
}
المبدأ هو توصيل سياق المرشح ووضع (أو لا) في طلب رأس Ajax . عندما لا يكون الرأس موجودًا ، يجب أن أجد واحدًا HttpNotFoundResult   في كائن النتيجة في السياق.

مرشح Global


من الممكن أيضًا تحديد عوامل التصفية Global . تخيل ، على سبيل المثال ، أنك قمت بإنشاء موقع ضخم به عدد كبير من وحدات التحكم وأنك قررت تأمين كل شيء والسماح بالوصول إلى الأشخاص المصادق عليهم فقط.
هل حقا ستضيف واحد [Authorize]   على جميع وحدات التحكم؟ حقاً؟
حسنًا لا ... ستقوم بإنشاء عامل تصفية عام ينطبق على كافة الاستعلامات. إذا قمت بإنشاء تطبيق غير فارغ ، فلديك بالفعل ملف FilterConfig.cs في دليل App_Start في مشروعك (إذا لم يكن كذلك ، فقم بإنشائه). في الداخل ، يوجد بالفعل الكود التالي:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
    }
}
نكتشف هناك أن الفلتر HandleErrorAttribute   الذي تحدثنا عنه أعلاه معرّف Global في موقعنا. كتذكير ، يسمح لك هذا المرشح بعرض طريقة عرض الخطأ (المعرفة في الدليل المشترك) إذا تم رفع استثناء في طريقة. في الواقع ، لنكون أكثر دقة قليلاً ، هذه هي الحالة فقط إذا كان هناك تكوين معين في web.config ، وهو:

<customErrors mode="On" defaultRedirect="Error" />
التي نضعها في القسم <system.web>  .
يمنع هذا المستخدمين النهائيين من رؤية مجموعة استدعاءاتك بالكامل من التعليمات البرمجية الخاصة بك إذا كان هناك خطأ ، وهو ليس أنيقًا للغاية وقبل كل شيء يمكن أن يُعلم المستخدمين بمحتوى التعليمات البرمجية ولماذا لا على الأخطاء المحتملة ...
العودة إلى حاجتنا لتأمين جميع وحدات التحكم. تحتاج فقط إلى إضافة السطر التالي في هذه الطريقة التي تحدد المرشحات العامة:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        filters.Add(new AuthorizeAttribute());
    }
}.
سيكون عليك بالطبع السماح للمستخدم غير المصادق على الأقل بالتمكن من المصادقة ، من خلال تزيين الإجراءات المناسبة بالسمة AllowAnonymous  .
إذا قمت بإنشاء نموذج تطبيق فارغ ، فستحتاج إلى إضافة السطر التالي في global.asax ، من أجل إخبار ASP.NET MVC بإدارة عوامل التصفية.

FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

باختصار


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