Skip to content

4. نموذج الإجراء

لنعد إلى بنية تطبيق ASP.NET MVC:

في الفصل السابق، تناولنا العملية التي توجه الطلب [1] إلى وحدة التحكم والإجراء [2a] اللذين سيتوليان معالجته، وهي آلية تُعرف باسم التوجيه. كما عرضنا الاستجابات المختلفة التي يمكن أن ترسلها الإجراء إلى المتصفح. حتى الآن، عرضنا إجراءات لم تقم بمعالجة الطلب المقدم إليها. يحمل الطلب [1] أجزاء مختلفة من المعلومات التي يقدمها ASP.NET MVC [2a] إلى الإجراء في شكل نموذج. لا ينبغي الخلط بين هذا المصطلح ونموذج M لعرض V [2c] الذي تنتجه الإجراء:

  • يصل طلب HTTP الخاص بالعميل إلى [1]؛
  • في [2]، يتم تحويل المعلومات الواردة في الطلب إلى نموذج إجراء [3]، غالبًا ما يكون فئة، ولكنه ليس بالضرورة كذلك، والذي يعمل كمدخل للإجراء [4]؛
  • في [4]، تقوم الإجراء، بناءً على هذا النموذج، بإنشاء استجابة. تتكون هذه الاستجابة من مكونين: عرض V [6] ونموذج M لهذا العرض [5]؛
  • ستستخدم طريقة العرض V [6] نموذجها M [5] لتوليد استجابة HTTP الموجهة للعميل.

في نموذج MVC، الإجراء [4] هو جزء من C (وحدة التحكم)، ونموذج العرض [5] هو M، والعرض [6] هو V.

يتناول هذا الفصل آليات ربط المعلومات التي يحملها الطلب — وهي سلاسل نصية بطبيعتها — بنموذج الإجراء، الذي يمكن أن يكون فئة ذات خصائص من أنواع مختلفة.

4.1. تهيئة معلمات الإجراء

نقوم بإضافة [1] مشروع ASP.NET MVC أساسي جديد إلى الحل الحالي:

  • في [2]، اسم المشروع الجديد؛
  • في [3، 4]، نختار مشروع ASP.NET MVC أساسي؛
  • في [5]، المشروع الجديد.

سنجعل المشروع الجديد هو مشروع بدء التشغيل للحل.

كما فعلنا في القسم 3.1، نقوم بإنشاء وحدة تحكم باسم [First] [1]:

Image

في وحدة التحكم هذه، نقوم بإنشاء الإجراء التالي [Action01]:


using System.Web.Mvc;
 
namespace Exemple_02.Controllers
{
  public class FirstController : Controller
  {
    // Action01
    public ContentResult Action01(string nom)
    {
      return Content(string.Format("Contrôleur=First, Action=Action01, nom={0}", nom));
    }
 
  }
}

الميزة الجديدة موجودة في السطر 8: تحتوي طريقة [Action01] على معلمة. في هذا الفصل، سنستكشف الطرق المختلفة لتهيئة معلمات الإجراء. يتم تهيئة المعلمة [name] أعلاه بالترتيب بالقيم التالية:

Request.Form["name"]
معلمة باسم [name] مرسلة بواسطة طلب POST
RouteData.Values["name"]
عنصر URL باسم [name]
Request.QueryString["name"]
معلمة باسم [name] مرسلة بواسطة طلب GET
Request.Files["name"]
ملف تم تحميله باسم [name]

دعونا ندرس هذه الحالات المختلفة. دعونا ندخل عنوان URL [/First/Action01?name=someone] مباشرة في المتصفح. نحصل على الاستجابة التالية:

Image

كان طلب HTTP للمتصفح كما يلي:

1
2
3
GET /First/Action01?nom=someone HTTP/1.1
Host: localhost:55483
...
  • السطر 1: الطلب هو GET. يتضمن عنوان URL المطلوب المعلمة [name]. على جانب الخادم، يتم توجيه الطلب إلى الإجراء [Action01]، الذي له التوقيع التالي:

public ContentResult Action01(string nom)

لتعيين قيمة للمعلمة name، يتحقق ASP.NET MVC من القيم التالية بالترتيب: *Request.Form[&quot;name&quot;], RouteData.Values[&quot;name&quot;],* *<span style="color: #2323dc">Request.QueryString[&quot;name&quot;]</span>**, Request.Files[&quot;name&quot;]*. ويتوقف فور العثور على قيمة. تم وضع المعلمة [name] المضمنة في عنوان URL GET بواسطة إطار العمل في Request.QueryString["name"]. وبهذه القيمة [someone] سيتم تهيئة المعلمة [name] لـ [Action01]. ثم يتم تنفيذ كود [Action01]:


return Content(string.Format("Contrôleur=First, Action=Action01, nom={0}", nom), "text/plain", Encoding.UTF8);

يوفر هذا الكود الاستجابة المرسلة إلى العميل:

Image

ملاحظة: آلية ربط المعلمات لا تميز بين الأحرف الكبيرة والصغيرة. لذا، إذا تم تعريف الإجراء على النحو التالي:


public ContentResult Action01(string NOM)

وتم تمرير المعلمة [?Name=zébulon]، فسيتم الربط. وستتلقى المعلمة [Name] في [Action01] القيمة [zébulon].

الآن، دعونا نطلب نفس عنوان URL باستخدام POST. للقيام بذلك، سنستخدم تطبيق [Advanced Rest Client]:

  • في [1]، عنوان URL المطلوب؛
  • في [2]، سيتم استخدام طريقة POST؛
  • في [3]، معلمات POST.

دعونا نرسل هذا الطلب ونلقي نظرة على سجلات HTTP. طلب HTTP هو كما يلي:

  • في [1]، POST؛
  • في [2]، معلمات POST. من الناحية الفنية، تم إرسالها بعد رؤوس HTTP، بعد السطر الفارغ الذي يشير إلى نهاية تلك الرؤوس؛
  • في [3]، الاستجابة المستلمة. نجحنا في استرداد معلمة [name] من POST. من بين القيم التي تم اختبارها لمعلمة name Request.Form["name"], RouteData.Values["name"], Request.QueryString["name"], Request.Files["name"] — نجحت القيمة الأولى.

الآن، دعونا نعدل المسار الافتراضي في [App_Start/RouteConfig]. حاليًا، هذا المسار كما يلي:


      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

دعونا نغيره إلى:


      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{nom}",
          defaults: new { controller = "Home", action = "Index", nom = UrlParameter.Optional }
);

  • في السطر 3، قمنا بتسمية العنصر الثالث للمسار [name
  • في السطر 4، تم تعريف هذا العنصر على أنه اختياري.

الآن، دعونا نعيد ترجمة التطبيق ونطلب عنوان URL [/First/Action01/zébulon] مباشرة في المتصفح. نحصل على الاستجابة التالية:

Image

من بين القيم التي تم اختبارها للمعلمة "name" Request.Form["name"], RouteData.Values["name"], Request.QueryString["name"], Request.Files["name"] — نجحت القيمة الثانية.

دعونا نرسل الطلب نفسه باستخدام طلب POST و [Advanced Rest Client]:

  • في [1]، قمنا بتعيين قيمة لعنصر {name} في المسار؛
  • في [2]، نضيف معلمة [name] إلى الطلب المرسل؛
  • الاستجابة التي تم الحصول عليها موجودة في [3].

من بين القيم التي تم اختبارها لمعلمة [name] Request.Form["name"], RouteData.Values["name"], Request.QueryString["name"], Request.Files["name"] — كانت اثنتان صالحتين: أول اثنتين. تم استخدام الأولى.

4.2. التحقق من صحة معلمات الإجراء

إذا احتوت إحدى الإجراءات على معلمة باسم [p]، فسيحاول ASP.NET MVC تعيين إحدى القيم التالية لها: Request.Form["p"]، أو RouteData.Values["p"]، أو Request.QueryString["p"]، أو Request.Files["p"]. القيم الثلاث الأولى هي سلاسل نصية. وإذا لم تكن المعلمة [p] من النوع [string]، فقد تنشأ مشكلات.

لنقم بإنشاء الإجراء الجديد التالي:


    // Action02
    public ContentResult Action02(int age)
    {
      string texte = string.Format("Contrôleur={0}, Action={1}, âge={2}", RouteData.Values["controller"], RouteData.Values["action"],age);
      return Content(texte, "text/plain", Encoding.UTF8);
}

  • السطر 2: تقبل الإجراء [Action02] معلمة باسم [age] من النوع int. يجب أن تكون السلسلة المستردة قابلة للتحويل إلى int.

دعونا نطلب عنوان URL [http://localhost:55483/First/Action02?age=21]. نحصل على الصفحة التالية:

Image

دعونا نطلب عنوان URL [http://localhost:55483/First/Action02?age=21x]. نحصل على الصفحة التالية:

Image

هذه المرة، تلقينا صفحة خطأ. من المثير للاهتمام إلقاء نظرة على رؤوس HTTP التي أرسلها الخادم في هذه الحالة:

1
2
3
4
5
HTTP/1.1 500 Internal Server Error
...
Content-Type: text/html; charset=utf-8
...
Content-Length: 12438

  • السطر 1: استجاب الخادم برمز [500 Internal Server Error] وأرسل صفحة HTML (السطر 3) بحجم 12,438 بايت (السطر 5) لشرح الأسباب المحتملة لهذا الخطأ.

الآن دعونا ننشئ الإجراء [Action03] التالي:


    // Action03
    public ContentResult Action03(int? age)
    {
      ...
}

[Action03] مطابق لـ [Action02] باستثناء أننا قمنا بتغيير نوع المعلمة [age] إلى int?، مما يعني عدد صحيح أو قيمة فارغة.

دعونا نطلب عنوان URL [http://localhost:55483/First/Action03?age=21x]. نحصل على الصفحة التالية:

Image

لم يتمكن ASP.NET MVC من تحويل [21x] إلى نوع int. ولذلك، قام بتعيين القيمة null للمتغير [age]، كما يسمح به نوع int؟ الخاص به. ومع ذلك، من الممكن تحديد ما إذا كان المتغير قد تلقى قيمة من الطلب أم لا.

نقوم بإنشاء الإجراء الجديد التالي [Action04]:


    // Action04
    public ContentResult Action04(int? age)
    {
      bool valide = ModelState.IsValid;
      string texte = string.Format("Contrôleur={0}, Action={1}, âge={2}, valide={3}", RouteData.Values["controller"], RouteData.Values["action"], age, valide);
      return Content(texte, "text/plain", Encoding.UTF8);
}

  • السطر 2: احتفظنا بنوع [int?]. وهذا يسمح على وجه التحديد للطلب بحذف المعلمة [age]، التي تتلقى عندئذ القيمة null؛
  • السطر 4: نتحقق من صحة نموذج الإجراء. يتكون نموذج الإجراء من جميع معلماته، وفي هذه الحالة [age]. يكون النموذج صحيحًا إذا تمكنت جميع المعلمات من الحصول على قيمة من الطلب أو القيمة null إذا كان نوع المعلمة يسمح بذلك؛
  • السطر 5: نضيف قيمة المتغير [valid] إلى النص المرسل إلى العميل.

دعونا نطلب عنوان URL [http://localhost:55483/First/Action04?age=21x]. نحصل على الصفحة التالية:

Image

لم يتمكن ASP.NET MVC من تحويل [21x] إلى النوع int. ولذلك، قام بتعيين القيمة null للمعلمة [age]، كما يسمح بذلك نوعها int؟. ومع ذلك، كانت هناك أخطاء في التحويل، كما هو موضح بقيمة [valid].

من الممكن الحصول على رسالة الخطأ المرتبطة بفشل التحويل. دعونا نفحص الإجراء الجديد التالي:


    // Action05
    public ContentResult Action05(int? age)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("Contrôleur={0}, Action={1}, âge={2}, valide={3}, erreurs={4}", RouteData.Values["controller"], RouteData.Values["action"], age, ModelState.IsValid, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

الميزة الجديدة موجودة في السطر 4. هنا، نستدعي طريقة خاصة [getErrorMessagesFor] ونمرر إليها حالة نموذج الإجراء. وهي تُرجع سلسلة تحتوي على جميع رسائل الخطأ التي حدثت. هذه الطريقة هي كما يلي:


private string getErrorMessagesFor(ModelStateDictionary état)
    {
      List<String> erreurs = new List<String>();
      string messages = string.Empty;
      if (!état.IsValid)
      {
        foreach (ModelState modelState in état.Values)
        {
          foreach (ModelError error in modelState.Errors)
          {
            erreurs.Add(getErrorMessageFor(error));
          }
        }
        foreach (string message in erreurs)
        {
          messages += string.Format("[{0}]", message);
        }
      }
      return messages;
    }

  • السطر 1: المعلمة [ModelState] الفعلية التي تم تمريرها إلى الأسلوب هي من النوع [ModelStateDictionary
  • السطر 3: قائمة برسائل الخطأ، فارغة في البداية؛
  • السطر 5: نتحقق مما إذا كانت الحالة التي تم تمريرها كمعلمة صالحة أم لا. إذا لم تكن صالحة، فسنجمع جميع رسائل الخطأ في سلسلة واحدة؛
  • السطر 7: يحتوي النوع [ModelStateDictionary] على خاصية [Values]، وهي مجموعة من أنواع [ModelState]. يوجد [ModelState] واحد لكل عنصر نموذج. على سبيل المثال:

    • ModelState["age"]: حالة النموذج للإجراء الخاص بالمعلمة [age]،
    • ModelState["age"].Errors: مجموعة الأخطاء الخاصة بهذا المعامل. الأخطاء من النوع [ModelError
    • ModelState["age"].Errors[i].ErrorMessage: رسالة الخطأ (إن وجدت) للمعلمة [age] في النموذج
    • ModelState["age"].Errors[i].Exception: الاستثناء الخاص بالخطأ رقم i في مجموعة الأخطاء للمعلمة [age]،
    • ModelState["age"].Errors[i].Exception.InnerException: سبب هذا الاستثناء،
    • ModelState["age"].Errors[i].Exception.InnerException.Message: رسالة سبب الاستثناء؛
  • السطر 9: نكرر عبر مجموعة [Errors] الخاصة بـ [ModelState] محدد؛
  • السطر 11: نسترد رسالة الخطأ من [ModelError] محدد ونضيفها إلى قائمة رسائل الخطأ من السطر 3؛
  • الأسطر 14–17: يتم ربط عناصر قائمة رسائل الخطأ في سلسلة واحدة.

طريقة [getErrorMessageFor] في السطر 11 هي كما يلي:


    private string getErrorMessageFor(ModelError error)
    {
      if (error.ErrorMessage != null && error.ErrorMessage.Trim() != string.Empty)
      {
        return error.ErrorMessage;
      }
      if (error.Exception != null && error.Exception.InnerException == null && error.Exception.Message != string.Empty)
      {
        return error.Exception.Message;
      }
      if (error.Exception != null && error.Exception.InnerException != null && error.Exception.InnerException.Message != string.Empty)
      {
        return error.Exception.InnerException.Message;
      }
      return string.Empty;
}

  • السطر 1: نتلقى نوع [ModelError] الذي يغلف خطأً في أحد عناصر نموذج الإجراء. نسترد رسالة الخطأ من ثلاثة مواقع مختلفة:

    • في [ModelError].ErrorMessage، الأسطر 3–6؛
    • في [ModelError].Exception.Message، الأسطر 7–10؛
    • في [ModelError].Exception.InnerException.Message، الأسطر 11–14؛

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

دعونا نطلب عنوان URL [http://localhost:55483/First/Action05?age=21x]. نحصل على الصفحة التالية:

Image

4.3. إجراء مع معلمات متعددة

لننظر إلى الإجراء الجديد التالي:


    // Action06
    public ContentResult Action06(double? poids, int? age)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"], poids, age, ModelState.IsValid, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

  • السطر 2: لدينا معلمتان [الوزن] و[العمر].

تنطبق القواعد الموضحة أعلاه الآن على كلا المعلمتين. فيما يلي بعض أمثلة التنفيذ:

Image

Image

4.4. استخدام فئة كنموذج لعمل

دعونا نحدد فئة ستكون بمثابة نموذج لعمل ما. سنضعها في مجلد [Models] [1].

وسيكون كودها كما يلي:


namespace Exemple_02.Models
{
  public class ActionModel01
  {
    public double? Poids { get; set; }
    public int? Age { get; set; }
  }
}

تحتوي فئتنا على الخاصيتين التلقائيتين [Weight] و [Age] اللتين تمت مناقشتهما سابقًا. ستكون هذه الفئة هي معلمة الإدخال للإجراء [Action07]:


    // Action07
    public ContentResult Action07(ActionModel01 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"], modèle.Poids, modèle.Age, ModelState.IsValid, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

  • السطر 2: نموذج الإجراء هو مثيل من النوع [ActionModel01].

دعونا نراجع نفس المثالين السابقين:

Image

Image

لاحظ أن ربط المعلمات لا يميز بين الأحرف الكبيرة والصغيرة. كانت معلمات الطلب هي [age] و [weight]. وقد قامت بتعبئة خصائص [Age] و [Weight] في فئة [ModelAction01].

علاوة على ذلك، استخدمنا حتى الآن طلبات HTTP من نوع [GET]. دعونا نوضح أن طلبات [POST] تتصرف بنفس الطريقة. للقيام بذلك، دعونا نستخدم تطبيق [Advanced Rest Client] مرة أخرى:

  • في [1]، عنوان URL المطلوب؛
  • في [2]، سيتم إرساله عبر طلب POST؛
  • في [3]، معلمات POST.

نحصل على نفس الاستجابة كما في طلب GET:

Image

4.5. نموذج الإجراء مع قيود التحقق من الصحة - 1

باستخدام النموذج السابق:


namespace Exemple_02.Models
{
  public class ActionModel01
  {
    public double? Poids { get; set; }
    public int? Age { get; set; }
  }
}

يمكن حذف المعلمتين [weight] و [age] من الطلب. في هذه الحالة، يتم تعيين الخاصيتين [Weight] و [Age] إلى [null]، ولا يتم الإبلاغ عن أي خطأ. قد ترغب في تحويل النموذج على النحو التالي:


namespace Exemple_02.Models
{
  public class ActionModel01
  {
    public double Poids { get; set; }
    public int Age { get; set; }
  }
}

السطران 5 و 6: لم يعد بإمكان الخاصيتين [Weight] و [Age] أن تأخذا القيمة [null]. لنرى ما يحدث مع هذا النموذج الجديد عندما تكون المعلمتان [weight] و [age] مفقودتين من الطلب.

Image

لم تحدث أية أخطاء، واحتفظت خصائص [Weight] و [Age] بقيمتهما الأولية: 0. ASP.NET MVC:

  • أنشأ مثيلًا للنموذج باستخدام `new ActionModel01`. وهنا تم تعيين القيمة 0 لخاصيتي [Weight] و[Age
  • لم يتم تعيين أي قيم لهاتين الخاصيتين لأنه لم تكن هناك معلمات تحمل هذين الاسمين.

يسمح لنا النموذج الأول بالتحقق من عدم وجود معلمة: عندئذٍ تكون للخاصية المقابلة القيمة [null]. أما النموذج الثاني فلا يسمح بذلك. من الممكن إضافة قيود التحقق من الصحة التي تتجاوز النوع البسيط للمعلمات. سنعرضها الآن.

لننظر إلى نموذج الإجراء الجديد التالي:

Image


using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
  public class ActionModel02
  {
    [Required]
    [Range(1, 200)]
    public double? Poids { get; set; }
    [Required]
    [Range(1, 150)]
    public int? Age { get; set; }
  }
}

  • السطر 6: يشير إلى أن حقل [Weight] مطلوب؛
  • السطر 7: يشير إلى أن حقل [Weight] يجب أن يكون في النطاق [1,200]؛
  • السطر 9: يشير إلى أن حقل [Age] مطلوب؛
  • السطر 7: يشير إلى أن حقل [Age] يجب أن يكون ضمن النطاق [1,150]؛

سيكون الإجراء الذي يستخدم هذا النموذج هو [Action08] التالي:


    // Action08
    public ContentResult Action08(ActionModel02 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"], modèle.Poids, modèle.Age, ModelState.IsValid, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

  • السطر 2: تتلقى الإجراء مثيلًا للنموذج [ActionModel02

دعونا نجري بعض الاختبارات:

Image

Image

Image

Image

تم الكشف عن الأخطاء بشكل صحيح. الآن، دعونا نقوم بتحديث النموذج على النحو التالي:


using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
  public class ActionModel02
  {
    [Required]
    [Range(1, 200)]
    public double Poids { get; set; }
    [Required]
    [Range(1, 150)]
    public int Age { get; set; }
  }
}

السطران 8 و 11: لم يعد بإمكان الخصائص أن تأخذ القيمة [null]. دعونا نجمع البرنامج ونشغل الاختبار مرة أخرى بدون معلمات:

Image

أدى غياب المعلمات إلى احتفاظ الخصائص [Weight] و [Age] بالقيمة التي اكتسبتها عند إنشاء مثيل النموذج: 0. ثم يتم التحقق من الصحة. وبذلك يتم استيفاء السمة [Required]. يمكننا أن نرى أن رسالة الخطأ أعلاه تتعلق بالسمة [Range]. لذلك، للتحقق من وجود معلمة، يجب أن تكون الخاصية المرتبطة قابلة للقيمة null، أي يجب أن تكون قادرة على قبول القيمة null.

لنعد إلى النموذج الأولي [ActionModel02] وننظر في إجراء يتكون نموذجه من مثيل [ActionModel02] ونوع [DateTime] قابل للقيمة null:


    // Action09
    public ContentResult Action09(ActionModel02 modèle, DateTime? date)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, date={4}, valide={5}, erreurs={6}", RouteData.Values["controller"], RouteData.Values["action"], modèle.Poids, modèle.Age, date, ModelState.IsValid, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

دعونا نجري بعض الاختبارات:

Image

لم نمرر أي معلمات إلى الإجراء. قامت سمات [Required] في خصائص [Weight] و [Age] بوظيفتها. ومع ذلك، تلقى التاريخ القيمة null ولم يتم الإبلاغ عن أي أخطاء.

الآن دعونا نمرر معلمات غير صالحة:

Image

الآن دعونا نمرر قيمًا صالحة:

Image

دعونا نفحص قيود التحقق من الصحة الأخرى. نموذج الإجراء الجديد هو كما يلي:

Image


using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
  public class ActionModel03
  {
    [Required(ErrorMessage = "Le paramètre email est requis")]
    [EmailAddress(ErrorMessage = "Le paramètre email n'a pas un format valide")]
    public string Email { get; set; }
 
    [Required(ErrorMessage = "Le paramètre jour est requis")]
    [RegularExpression(@"^\d{1,2}$", ErrorMessage = "Le paramètre jour doit avoir 1 ou 2 chiffres")]
    public string Jour { get; set; }
 
    [Required(ErrorMessage = "Le paramètre info1 est requis")]
    [MaxLength(4, ErrorMessage = "Le paramètre info1 ne peut avoir plus de 4 caractères")]
    public string Info1 { get; set; }
 
    [Required(ErrorMessage = "Le paramètre info2 est requis")]
    [MinLength(2, ErrorMessage = "Le paramètre info2 ne peut avoir moins de 2 caractères")]
    public string Info2 { get; set; }
 
    [Required(ErrorMessage = "Le paramètre info3 est requis")]
    [MinLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
    [MaxLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
    public string Info3 { get; set; }
  }
}

  • السطر 6: السمة [Required]، هذه المرة مع رسالة خطأ نحددها بأنفسنا؛
  • السطر 7: تتطلب السمة [EMailAddress] أن يحتوي الحقل [Email] على عنوان بريد إلكتروني صالح؛
  • السطر 11: تتطلب السمة [RegularExpression] أن يحتوي الحقل [Day] على سلسلة مكونة من رقم واحد أو رقمين. المعلمة الأولى هي التعبير العادي الذي يجب أن يتحقق الحقل من صحته؛
  • السطر 15: تتطلب السمة [MaxLength] ألا يحتوي الحقل [Info1] على أكثر من 4 أحرف؛
  • السطر 19: تتطلب السمة [MinLength] أن يحتوي الحقل [Info2] على حرفين على الأقل؛
  • السطران 23-24: تتطلب السمتان [MaxLength] و[MinLength] مجتمعتين أن يحتوي الحقل [Info3] على 4 أحرف بالضبط؛

ستستخدم الإجراء [Action10] هذا النموذج:


// Action10
    public ContentResult Action10(ActionModel03 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("email={0}, jour={1}, info1={2}, info2={3}, info3={4}, erreurs={5}",
        modèle.Email, modèle.Jour, modèle.Info1, modèle.Info2, modèle.Info3, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
    }

دعونا نجري بعض الاختبارات باستخدام هذا الإجراء.

أولاً، بدون معلمات:

Image

ثم باستخدام معلمات غير صالحة:

Image

ثم مع معلمات صالحة:

Image

4.6. نموذج الإجراء مع قيود الصحة - 2

نقدم قيودًا إضافية على السلامة. سيكون نموذج الإجراء الجديد هو الفئة [ActionModel04] التالية:


using System.ComponentModel.DataAnnotations;
 
namespace Exemple_02.Models
{
  public class ActionModel04
  {
    [Required(ErrorMessage="Le paramètre url est requis")]
    [Url(ErrorMessage="URL invalide")]
    public string Url { get; set; }
    [Required(ErrorMessage = "Le paramètre info1 est requis")]
    public string Info1 { get; set; }
    [Required(ErrorMessage = "Le paramètre info2 est requis")]
    [Compare("Info1",ErrorMessage="Les paramètres info1 et info2 doivent être identiques")]
    public string Info2 { get; set; }
    [Required(ErrorMessage = "Le paramètre cc est requis")]
    [CreditCard(ErrorMessage = "Le paramètre cc n'est pas un n° de carte de crédit valide")]
    public string Cc { get; set; }
  }
}
  • السطر 8: يشترط أن يكون الحقل المُعلَّق عليه عنوان URL صالحًا؛
  • السطر 13: يتطلب أن تكون خصائص [Info1] و[Info2] لها نفس القيمة؛
  • السطر 16: يتطلب أن يكون الحقل المُعلَّق عليه رقم بطاقة ائتمان صالحًا.

سيكون الإجراء الذي يستخدم هذا النموذج كما يلي:


    // Action11
    public ContentResult Action11(ActionModel04 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("URL={0}, Info1={1}, Info2={2}, CC={3},erreurs={4}",
        modèle.Url, modèle.Info1, modèle.Info2, modèle.Cc, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

لاختبار الإجراء [Action11]، نستخدم تطبيق [Advanced Rest Client]:

  • في [1]، عنوان URL لإجراء [Action11
  • في [2]، سيتم طلب عنوان URL هذا باستخدام POST؛
  • في [3]، حدد علامة التبويب [Form
  • في [4]، قيم المعلمات الأربعة المتوقعة. هذه التهيئة هي ميزة يوفرها [ARC]. يمكن عرض المعلمات المرسلة فعليًا في علامة التبويب [Raw] [5]؛
  • في [6]، معلمات POST.

بالنسبة لهذا الطلب، نتلقى الاستجابة التالية:

Image

دعونا نمرر معلمات غير صالحة:

Image

ثم نتلقى الرد التالي:

Image

4.7. نموذج الإجراء مع قيود الصحة - 3

في بعض الأحيان، لا تكون قيود التكامل المتاحة كافية. في هذه الحالة، يمكنك إنشاء قيودك الخاصة. على وجه الخصوص، يمكنك استخدام نموذج ينفذ واجهة [IValidatableObject]. في هذه الحالة، تضيف عمليات التحقق من صحة النموذج الخاصة بك إلى طريقة [Validate] لهذه الواجهة. لنلقِ نظرة على مثال. سيكون نموذج الإجراء الجديد هو الفئة [ActionModel05] التالية:


using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
 
namespace Exemple_02.Models
{
  public class ActionModel05 : IValidatableObject
  {
    [Required(ErrorMessage = "Le paramètre taux est requis")]
    public double? Taux { get; set; }
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      List<ValidationResult> résultats = new List<ValidationResult>();
      bool ok = Taux < 4.2 || Taux > 6.7;
      if (!ok)
      {
        résultats.Add(new ValidationResult("Le paramètre taux doit être < 4.2 ou > 6.7", new string[] { "Taux" }));
      }
      return résultats;
    }
  }
}

  • السطر 6: ينفذ النموذج واجهة [IValidatableObject
  • السطر 10: طريقة [Validate] لهذه الواجهة. وهي تُرجع مجموعة من العناصر من النوع [ValidationResult]. ويغلف هذا النوع الأخطاء التي سيتم الإبلاغ عنها؛
  • السطر 9: السعر الصالح هو سعر <4.2 أو > 6.7؛
  • السطر 12: نقوم بإنشاء قائمة فارغة من العناصر من النوع [ValidationResult
  • السطر 13: نتحقق من صحة الخاصية [Rate
  • الأسطر 14-17: إذا كانت الخاصية [Rate] غير صالحة، يتم إضافة عنصر من النوع [ValidationResult] إلى قائمة النتائج. المعلمة الأولى هي رسالة خطأ. المعلمة الثانية، وهي اختيارية، هي مجموعة من الخصائص المتأثرة بهذا الخطأ.

سيكون الإجراء الذي يستخدم هذا النموذج كما يلي:


    // Action12
    public ContentResult Action12(ActionModel05 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("taux={0}, erreurs={1}", modèle.Taux, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

فيما يلي مثال على التنفيذ:

Image

4.8. نموذج الإجراء من النوع Table أو List

لنأخذ الإجراء التالي [Action13]:


// Action13
    public ContentResult Action13(string[] data)
    {
      string strData = "";
      if (data != null && data.Length != 0)
      {
        strData = string.Join(",", data);
      }
      string texte = string.Format("data=[{0}]", strData);
      return Content(texte, "text/plain", Encoding.UTF8);
    }

  • السطر 2: يتكون نموذج الإجراء من مصفوفة من نوع [string]. وهو يسمح لنا باسترداد معلمة باسم [data]، والتي قد تظهر عدة مرات في معلمات الطلب، كما في [?data=data1&data=data2&data=data3]. ستملأ معلمات [data] المختلفة في الطلب مصفوفة [data] في نموذج الإجراء. يحدث هذا السيناريو مع القوائم المنسدلة. ثم يرسل المتصفح القيم المختلفة التي اختارها المستخدم، وكلها تحمل نفس اسم المعلمة.

فيما يلي مثال على ذلك:

Image

يمكن أن يكون النموذج أيضًا قائمة:


    // Action14
    public ContentResult Action14(List<int> data)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string strData = "";
      if (data != null && data.Count != 0)
      {
        strData = string.Join(",", data);
      }
      string texte = string.Format("data=[{0}], erreurs=[{1}]", strData, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

النموذج هنا عبارة عن قائمة من الأعداد الصحيحة (السطر 2). وإليك التشغيل الأول:

Image

والتشغيل الثاني:

Image

4.9. تصفية نموذج الإجراء

في بعض الأحيان يكون لدينا نموذج ولكننا نريد فقط أن يتم تهيئة عناصر معينة من النموذج بواسطة طلب HTTP. لنفكر في نموذج الإجراء التالي [ActionModel06]:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
 
namespace Exemple_02.Models
{
  [Bind(Exclude = "Info2")]
  public class ActionModel06
  {
    [Required(ErrorMessage = "Le paramètre [info1] est requis")]
    public string Info1 { get; set; }
 
    public string Info2 { get; set; }
  }
}
  • السطران 9 و10: المعلمة [info1] مطلوبة؛
  • السطر 6: تم استبعاد المعلمة [info2] الموجودة في السطر 12 من ربط طلب HTTP بنموذجه.

سيكون الإجراء كما يلي [Action15]:


    // Action15
    public ContentResult Action15(ActionModel06 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("valide={0}, info1={1}, info2={2}, erreurs={3}", ModelState.IsValid, modèle.Info1, modèle.Info2, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

فيما يلي مثال على التنفيذ:

  • في [1]: نمرر المعلمة [info2] في عنوان URL؛
  • في [2]: تظل خاصية [Info2] لنموذج الإجراء فارغة.

4.10. توسيع نموذج ربط البيانات

دعونا نعيد النظر في بنية تنفيذ الإجراء:

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

  • البيانات المشتركة بين جميع مستخدمي تطبيق الويب. وعادةً ما تكون هذه البيانات للقراءة فقط. تُستخدم ثلاثة ملفات لتنفيذ مشاركة البيانات هذه:
    • [Web.Config]: ملف تكوين التطبيق
    • [Global.asax، Global.asax.cs]: يسمحان لك بتعريف فئة، تسمى فئة التطبيق العالمية، والتي تستمر طوال عمر التطبيق، بالإضافة إلى معالجات لأحداث معينة في نفس التطبيق.

تسمح لك فئة التطبيق العالمية بتعريف البيانات التي ستكون متاحة لجميع الطلبات من جميع المستخدمين.

  • البيانات المشتركة عبر الطلبات من نفس العميل. يتم تخزين هذه البيانات في كائن يسمى "الجلسة" (Session). نشير إلى هذا باسم "جلسة العميل" للإشارة إلى ذاكرة العميل. جميع الطلبات الواردة من العميل لها حق الوصول إلى هذه الجلسة. يمكنها تخزين المعلومات وقراءتها هناك.

أعلاه، نعرض أنواع الذاكرة التي يمكن للإجراء الوصول إليها:

  • ذاكرة التطبيق، التي تحتوي في الغالب على بيانات للقراءة فقط ويمكن لجميع المستخدمين الوصول إليها؛
  • ذاكرة مستخدم معين، أو جلسة عمل، والتي تحتوي على بيانات للقراءة والكتابة ويمكن الوصول إليها من خلال الطلبات المتتالية من نفس المستخدم؛
  • غير موضح أعلاه، هناك ذاكرة الطلب، أو سياق الطلب. قد تتم معالجة طلب المستخدم من خلال عدة إجراءات متتالية. يسمح سياق الطلب للإجراء 1 بتمرير المعلومات إلى الإجراء 2.

لنلقِ نظرة على المثال الأول الذي يوضح هذه الأنواع المختلفة من الذاكرة:

أولاً، نقوم بتعديل ملف [Web.config] لمشروع [Example-02] على النحو التالي:


  <appSettings>
    <add key="webpages:Version" value="2.0.0.0" />
    ...
    <add key="infoAppli1" value="infoAppli1"/>
</appSettings>

نضيف السطر 4، الذي يربط القيمة [infoAppli1] بالمفتاح [infoAppli1]. ستكون هذه بيانات نطاق [Application] لدينا: وستكون متاحة لجميع الطلبات من جميع المستخدمين.

بعد ذلك، نقوم بتعديل الأسلوب [Application_Start] في ملف [Global.asax]. يتم تشغيل هذا الأسلوب مرة واحدة عند بدء تشغيل التطبيق. وهنا نحتاج إلى استخدام ملف [Web.config]:


    protected void Application_Start()
    {
      AreaRegistration.RegisterAllAreas();
 
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // intialisation application
      Application["infoAppli1"] = ConfigurationManager.AppSettings["infoAppli1"];
}

نضيف السطر 10. وهو يقوم بأمرين:

  • يسترد قيمة مفتاح [infoAppli1] من ملف [Web.config] باستخدام فئة [System.Configuration.ConfigurationManager
  • ويخزنها في قاموس [HttpApplication.Application]، المرتبط بمفتاح [infoAppli1]. جميع الإجراءات لها حق الوصول إلى هذا القاموس.

في نفس ملف [Global.asax]، نضيف طريقة [Session_Start] التالية:


    protected void Session_Start()
    {
      // initialisation compteur
      Session["compteur"] = 0;
}

يتم تنفيذ طريقة [Session_Start] لكل مستخدم جديد. ما هو المستخدم الجديد؟ يتم "تتبع" المستخدم بواسطة رمز الجلسة. هذا الرمز هو:

  • يتم إنشاؤه بواسطة خادم الويب وإرساله إلى المستخدم الجديد في رؤوس HTTP للاستجابة الأولى المرسلة إليه؛
  • يتم إرساله مرة أخرى بواسطة متصفح المستخدم مع كل طلب جديد يقوم به. وهذا يسمح للخادم بالتعرف على المستخدم وإدارة مساحة ذاكرة له تسمى جلسة عمل المستخدم.

يتعرف خادم الويب على أنه يتعامل مع مستخدم جديد عندما لا يرسل المستخدم رمز الجلسة. ثم يقوم الخادم بإنشاء رمز له.

في السطر 4 أعلاه، نضع عدادًا في جلسة عمل المستخدم سيتم زيادته مع كل طلب من هذا المستخدم. وهذا يوضح الذاكرة المرتبطة بالمستخدم. تُستخدم فئة [Session] كالقاموس (السطر 4).

وبعد الانتهاء من ذلك، نكتب الإجراء [Action16] التالي:


// Action16
    public ContentResult Action16()
    {
      // on récupère le contexte de la requête HTTP
      HttpContextBase contexte = ControllerContext.HttpContext;
      // on récupère les infos de portée Application
      string infoAppli1 = contexte.Application["infoAppli1"] as string;
      // et celles de portée Session
      int? compteur = contexte.Session["compteur"] as int?;
      compteur++;
      contexte.Session["compteur"] = compteur;
      // la réponse au client
      string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
      return Content(texte, "text/plain", Encoding.UTF8);
}

  • السطر 5: نسترد سياق طلب HTTP الذي يجري معالجته حاليًا. سيمنحنا هذا السياق إمكانية الوصول إلى البيانات في نطاقي [Application] و[Session
  • السطر 7: نسترد معلومات نطاق [Application
  • السطر 9: نسترد العداد من الجلسة؛
  • السطران 10-11: يتم زيادته ثم تخزينه مرة أخرى في الجلسة؛
  • السطران 13-14: يتم إرسال كلتا المعلومتين إلى العميل.

فيما يلي بعض أمثلة التنفيذ:

يتم طلب [Action16] مرة واحدة [1]، ثم يتم تحديث الصفحة [F5] مرتين [2]:

في [2]، أرسل العميل ما مجموعه ثلاثة طلبات. وفي كل مرة، تمكن من استرداد العداد الذي تم تحديثه بواسطة الطلب السابق.

لمحاكاة مستخدم ثانٍ، نستخدم متصفحًا ثانيًا لطلب نفس عنوان URL:

في [3]، يسترد المستخدم الثاني بنجاح نفس معلومات نطاق [التطبيق]، ولكنه يمتلك عداد نطاق [الجلسة] الخاص به.

لنعد إلى كود الإجراء [Action16]:


// Action16
    public ContentResult Action16()
    {
      // on récupère le contexte de la requête HTTP
      HttpContextBase contexte = ControllerContext.HttpContext;
      // on récupère les infos de portée Application
      string infoAppli1 = contexte.Application["infoAppli1"] as string;
      // et celles de portée Session
      int? compteur = contexte.Session["compteur"] as int?;
      compteur++;
      contexte.Session["compteur"] = compteur;
      // la réponse au client
      string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
      return Content(texte, "text/plain", Encoding.UTF8);
}

أحد أهداف إطار عمل ASP.NET MVC هو جعل وحدات التحكم والإجراءات قابلة للاختبار بشكل منفصل دون الحاجة إلى خادم ويب. ومع ذلك، كما هو موضح في السطر 5، فإن سياق طلب HTTP مطلوب لاسترداد البيانات من نطاقي [Application] و[Session]. نقترح إنشاء إجراء جديد [Action17] يستقبل بيانات نطاقي [Application] و[Session] كمعلمات:


    // Action17
    public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)
    {
      // retrieve range info Application
      string infoAppli1 = applicationData.InfoAppli1;
      // and Session range
      int compteur = sessionData.Compteur++;
      // customer response
      string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
      return Content(texte, "text/plain", Encoding.UTF8);
}

لم يعد الكود يعتمد على طلب HTTP. وبالتالي، يمكن اختباره بشكل مستقل عن خادم الويب.

لنرى كيفية القيام بذلك. أولاً، نحتاج إلى إنشاء فئتي [ApplicationModel] و [SessionModel]، اللتين ستقومان بتغليف بيانات نطاق [Application] و [Session]، على التوالي. وهما كالتالي:


namespace Exemple_02.Models
{
  public class ApplicationModel
  {
    public string InfoAppli1 { get; set; }
  }
}


namespace Exemple_02.Models
{
  public class SessionModel
  {
    public int Compteur { get; set; }
    public SessionModel()
    {
      Compteur = 0;
    }
  }
}

بعد ذلك، نحتاج إلى تعديل طريقتي [Application_Start] و [Session_Start] في ملف [Global.asax]:


public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
      AreaRegistration.RegisterAllAreas();
 
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // intialisation application - case 1
      Application["infoAppli1"] = ConfigurationManager.AppSettings["infoAppli1"];
      // intialisation application - case 2
      ApplicationModel data=new ApplicationModel();
      data.InfoAppli1=ConfigurationManager.AppSettings["infoAppli1"];
      Application["data"] = data;
    }
 
    protected void Session_Start()
    {
      // counter initialization - case 1
      Session["compteur"] = 0;
      // counter initialization - case 2
      Session["data"] = new SessionModel();
    }
  }

  • السطر 14: يتم إنشاء مثيل لـ [ApplicationModel
  • السطر 15: يتم تهيئتها؛
  • السطر 16: ووضعها في قاموس [Application]، مرتبطة بالمفتاح [data]. [Application] هي خاصية لفئة [HttpApplication] من السطر 1؛
  • السطر 24: يتم إنشاء مثيل لـ [SessionModel] ووضعه في قاموس [Session]، مرتبطًا بالمفتاح [data]. [Session] هي خاصية لفئة [HttpApplication] من السطر 1؛

بناءً على ما رأيناه حتى الآن، فإن التوقيع


    public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)

أن طلب HTTP الذي تعالجه الإجراء يجب أن يتضمن معلمات باسم [applicationData] و [sessionData]. لن يكون هذا هو الحال. يجب علينا إنشاء نموذج ربط بيانات جديد بحيث عندما يتلقى إجراء نوعًا كمعلمة:

  • [ApplicationModel]، يتم تزويده بالبيانات ذات النطاق [Application] والمفتاح [data
  • [SessionModel]، يتم توفير البيانات ذات النطاق [Session] والمفتاح [data] له.

للقيام بذلك، نحتاج إلى إنشاء فئات تنفذ واجهة [IModelBinder].

نبدأ بإنشاء مجلد [Infrastructure] في مشروع [Example-02]:

Image

داخله، نقوم بإنشاء فئة [ApplicationModelBinder] التالية:


using System.Web.Mvc;
 
namespace Exemple_02.Infrastructure
{
  public class ApplicationModelBinder : IModelBinder
  {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      // render scope data [Application]
      return controllerContext.RequestContext.HttpContext.Application["data"];
    }
  }
}

  • السطر 5: تنفذ الفئة واجهة [IModelBinder]. لفهم كودها، عليك أن تعلم أنها ستُستدعى في كل مرة يكون فيها لإجراء ما معلمة من نوع [ApplicationModel]. سيتم إنشاء هذا الربط [ApplicationModel] --> [ApplicationModelBinder] عند بدء تشغيل التطبيق، في طريقة [Application_Start] في [Global.asax
  • السطر 7: الطريقة الوحيدة لواجهة [IModelBinder
  • السطر 7: تتيح لنا المعلمة [ControllerContext] الوصول إلى طلب HTTP الذي يجري معالجته حاليًا؛
  • السطر 7: تتيح لنا المعلمة من النوع [ModelBindingContext] الوصول إلى معلومات حول النموذج المراد إنشاؤه، وهو في هذه الحالة النوع [ApplicationModel
  • السطر 7: نتيجة [BindModel] هي الكائن الذي سيتم تعيينه إلى المعلمة المرتبطة، وهنا معلمة من النوع [ApplicationModel
  • السطر 10: نعيد ببساطة الكائن ذي النطاق [Application] والمفتاح [data].

تتبع فئة [ SessionModelBinder] نفس النمط:


using System.Web.Mvc;
 
namespace Exemple_02.Infrastructure
{
  public class SessionModelBinder : IModelBinder
  {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      // render scope data [Session]
      return controllerContext.HttpContext.Session["data"];
    }
  }
}

كل ما تبقى هو ربط كل نموذج [XModel] بـ [XModelBinder] الخاص به. ويتم ذلك في الأسلوب [Application_Start] في [Global.asax]:


    protected void Application_Start()
    {
....
      // intialisation application - case 2
      ApplicationModel data=new ApplicationModel();
      data.InfoAppli1=ConfigurationManager.AppSettings["infoAppli1"];
      Application["data"] = data;
      // model binders
      ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
}

  • السطر 9: عندما تحتوي إحدى الإجراءات على معلمة من النوع [ApplicationModel]، سيتم استدعاء الأسلوب [ApplicationModelBinder.Bind]. ونعلم أنه يعيد البيانات الموجودة في نطاق [Application] المرتبطة بالمفتاح [data
  • السطر 10: الأمر نفسه بالنسبة لنوع [SessionModel].

لنعد إلى إجراء [Action17] الخاص بنا:


    // Action17
    public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)
    {
      // retrieve range info Application
      string infoAppli1 = applicationData.InfoAppli1;
      // and Session range
      sessionData.Compteur++;
      int compteur = sessionData.Compteur;
      // customer response
      string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
      return Content(texte, "text/plain", Encoding.UTF8);
}

  • السطر 2: عند استدعاء [Action17]، ستتلقى
    • المعلمة الأولى: بيانات نطاق [Application] المرتبطة بمفتاح [data
    • المعلمة الثانية: بيانات نطاق [Session] المرتبطة بمفتاح [data

يمكن أن تكون هاتان المجموعتان من البيانات معقدتين بقدر ما تريد، ويمكن أن تتضمن إحداهما جميع البيانات من نطاق [Application]، والأخرى جميع البيانات من نطاق [Session].

فيما يلي مثال على تنفيذ الإجراء [Action17]:

Image

4.11. الربط المتأخر لنموذج الإجراء

لقد كتبنا [Action12] التالي:


// Action12
    public ContentResult Action12(ActionModel05 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("taux={0}, erreurs={1}", modèle.Taux, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

في الخلفية، يقوم ASP.NET MVC بما يلي:

  • ينشئ مثيلًا من النوع [ActionModel05] باستخدام منشئه الذي لا يحتوي على معلمات؛
  • يقوم بتهيئتها بمعلومات الطلب التي تحمل نفس الاسم (بغض النظر عن حالة الأحرف) كواحدة من خصائص [ActionModel05].

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


    // Action18
    public ContentResult Action18()
    {
      ActionModel05 modèle = new ActionModel05();
      TryUpdateModel(modèle);
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("taux={0}, erreurs={1}", modèle.Taux, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

  • السطر 2: لم تعد الإجراء تتلقى معلمات. وبالتالي، لم يعد هناك أي ربط تلقائي للبيانات؛
  • السطر 4: نقوم بأنفسنا بإنشاء مثيل لنموذج الإجراء. وهنا يمكننا استخدام منشئ مختلف؛
  • السطر 5: نقوم بتهيئة النموذج باستخدام معلومات الطلب. يقوم ASP.NET MVC بهذه المهمة. ويقوم بها بنفس الطريقة التي كان سيقوم بها لو كان النموذج معلمة؛
  • السطر 6: نحن الآن في نفس الموقف كما في الإجراء [Action12].

فيما يلي مثال على التنفيذ:

Image

4.12. خاتمة

لنعد إلى بنية تطبيق ASP.NET MVC:

يحمل الطلب [1] أجزاء مختلفة من المعلومات التي يقدمها ASP.NET MVC [2a] إلى الإجراء في شكل نموذج، وهو ما أطلقنا عليه اسم نموذج الإجراء.

  • يصل طلب HTTP الخاص بالعميل إلى [1]؛
  • في [2]، يتم تحويل المعلومات الواردة في الطلب إلى نموذج إجراء [3]؛
  • في [4]، سيقوم الإجراء، بناءً على هذا النموذج، بإنشاء استجابة. ستتألف هذه الاستجابة من مكونين: عرض V [6] ونموذج M لهذا العرض [5]؛
  • ستستخدم طريقة العرض V [6] نموذجها M [5] لتوليد استجابة HTTP الموجهة للعميل.

في نموذج MVC، الإجراء [4] هو جزء من C (وحدة التحكم)، ونموذج العرض [5] هو M، والعرض [6] هو V.

تناول هذا الفصل الآليات التي تربط المعلومات التي يحملها الطلب — وهي سلاسل نصية بطبيعتها — بنموذج الإجراء، الذي يمكن أن يكون فئة ذات خصائص من أنواع مختلفة. كما رأينا أنه من الممكن التحقق من صحة النموذج المقدم إلى الإجراء. وأخيرًا، رأينا كيفية توسيع هذا النموذج ليشمل البيانات من نطاقي [Session] و[Application].

سنركز الآن على نهاية سلسلة معالجة الطلب [1]: إنشاء العرض [6] ونموذجه [5]. يتم إنتاج هذين العنصرين بواسطة الإجراء [4].