Skip to content

7. مكونات الخادم ASP - 1

7.1. Introduction

نصف في هذا الفصل التقنية الموصى بها في ASP.NET لإنشاء واجهة المستخدم. نعلم أن هناك مرحلتين متميزتين تمامًا في معالجة صفحة .aspx بواسطة خادم الويب:

  1. أولاً، يتم تنفيذ وحدة التحكم في الصفحة. وتتكون هذه الوحدة من كود موجود إما في صفحة .aspx نفسها (الحل WebMatrix) أو في ملف منفصل (الحل Visual Studio.NET).
  2. ثم يتم تنفيذ كود عرض الصفحة .aspx لتحويله إلى كود HTML الذي يتم إرساله إلى العميل.

Image

يقدم ASP.NET ثلاث مكتبات من العلامات لكتابة كود عرض الصفحة:

  1. علامات HTML التقليدية. وهذا ما استخدمناه حتى الآن.
  2. علامات الخادم HTML
  3. علامات webforms

بغض النظر عن مكتبة العلامات المستخدمة، لا يتغير دور وحدة التحكم في الصفحة. يجب أن تحسب قيمة المعلمات الديناميكية التي تظهر في كود العرض. حتى الآن، كانت هذه المعلمات الديناميكية بسيطة: كانت كائنات من النوع [String]. وبالتالي، إذا كان لدينا في كود العرض علامة <%=name%>:

  • يقوم وحدة التحكم في الصفحة بإعلان متغير [nom] من النوع [String] ويحسب قيمته
  • عندما ينتهي وحدة التحكم في الصفحة من عملها ويتم تنفيذ كود العرض لتوليد الاستجابة HTML، يتم استبدال العلامة <%=name%> بالقيمة التي تم حسابها بواسطة كود وحدة التحكم

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

تسمح العلامات [HTML serveur] و [WebForms] بإدخال كائنات أكثر تعقيدًا من الكائن البسيط [String] في كود العرض. وهذا يمثل أحيانًا فائدة حقيقية. لنأخذ مثالاً على نموذج يحتوي على قائمة. يجب عرض هذه القائمة للعميل برمز html يشبه ما يلي:

<select name="uneListe" size="3">
    <option value="opt1">option1</option>
    <option value="opt2">option2</option>
    <option value="opt3" selected>option3</option>
</select>

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

<%=uneListeHTML%>

سيتم استبدال هذه العلامة بالقيمة [String] للمتغير [uneListeHTML]. يجب أن تكون هذه القيمة التي يحسبها وحدة التحكم هي الرمز HTML من القائمة، c.a.d. "<select name=..>...</select>". هذا ليس صعبًا بشكل خاص ويبدو حلاً أنيقًا يتجنب وضع كود التوليد مباشرةً في جزء العرض من الصفحة. هنا سيكون هناك حلقة مع اختبارات يجب إدراجها فيها، مما سيؤدي إلى "تلوثها" بشدة. ومع ذلك، فإن هذه الطريقة تنطوي على عيب. إن تقسيم الصفحة إلى وحدة التحكم/العرض يُستخدم أيضًا لتحديد مجالين من الاختصاص:

  • مجال اختصاص المطور .NET الذي يتولى وحدة التحكم في الصفحة
  • مجال اختصاص مصمم الجرافيك الذي يتولى جزء العرض الخاص بها

هنا، نرى أنه تم نقل إنشاء الكود HTML الخاص بالقائمة إلى وحدة التحكم. قد يرغب مصمم الجرافيك في التعامل مع هذا الكود HTML لتعديل المظهر "المرئي" للقائمة. سيضطر إلى العمل في الجزء [contrôleur] وبالتالي الخروج من مجال اختصاصه مع ما ينطوي عليه ذلك من مخاطر إدخال أخطاء عن غير قصد في الكود.

تحل مكتبات علامات الخادم هذه المشكلة. فهي توفر كائنًا يمثل قائمة HTML. وهكذا توفر مكتبة [WebForms] العلامة التالية:

<asp:ListBox id="uneListe" runat="server"></asp:ListBox>

يمثل هذا العلامة كائنًا من النوع [ListBox] يمكن معالجته بواسطة وحدة التحكم في الصفحة. يحتوي هذا الكائن على خصائص لتمثيل الخيارات المختلفة في القائمة HTML وتحديد الخيار المحدد. وبالتالي، سيقوم وحدة التحكم في الصفحة بإعطاء القيم المناسبة لهذه الخصائص. وعندما يتم تنفيذ جزء العرض، فإن العلامة

<asp:ListBox id="uneListe" runat="server"></asp:ListBox>

بالرمز HTML الذي يمثل الكائن [uneListec.a.d. الرمز "<select ..>...</select>". في الوقت الحالي، لا يوجد فرق جوهري مع الطريقة السابقة سوى طريقة البرمجة الموجهة للكائنات، وهو أمر مثير للاهتمام. لنعد إلى مصمم الجرافيك الذي يحتاج إلى تعديل "مظهر" القائمة. تحتوي علامات الخادم على سمات نمطية (BackColor، Bordercolor، BorderWidth، ...) تسمح بتحديد المظهر البصري للكائن HTML المقابل. وبالتالي يمكننا كتابة:


                <asp:ListBox id="ListBox1" runat="server" BackColor="#ffff99"></asp:ListBox></P>

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

يتكيف الإنشاء HTML الخاص بعلامة ما مع نوع متصفح العميل. عندما يرسل المتصفح طلبًا إلى خادم الويب، فإنه يرسل ضمن رؤوسه HTTP، رأسًا [User-Agent: xx] حيث يحدد [xx] هوية العميل. فيما يلي مثال:

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

باستخدام هذه المعلومات، يمكن لخادم الويب معرفة قدرات العميل، ولا سيما نوع الرمز HTML الذي يمكنه إدارته. فقد ظهرت بالفعل، بمرور الوقت، عدة إصدارات من لغة HTML. تدعم المتصفحات الحديثة أحدث إصدارات اللغة، وهو ما لا تستطيع المتصفحات القديمة القيام به. اعتمادًا على الرأس HTTP [User-Agent:] الذي أرسله العميل إليه، سيرسل الخادم إليه إصدارًا HTML يستطيع فهمه. هذه فكرة مثيرة للاهتمام ومفيدة لأن المطور لا يضطر عندئذٍ إلى القلق بشأن نوع متصفح العميل الذي يستخدمه لتشغيل تطبيقه.

وأخيرًا، تتيح الأدوات المتطورة مثل Visual Studio.NET وWebMatrix... تصميم واجهة الويب "على غرار Windows". ورغم أن هذه الأدوات ليست ضرورية، إلا أنها توفر مساعدة حاسمة للمطور. يقوم المطور برسم واجهة الويب باستخدام مكونات رسومية يضعها على هذه الواجهة. ولديه وصول مباشر إلى خصائص كل مكون من مكونات الواجهة، مما يتيح له ضبطها حسب رغبته. سيتم ترجمة هذه الخصائص في كود HTML الخاص بعرض الواجهة إلى سمات لعلامة <asp:> الخاصة بالمكون. وتكمن الفائدة للمطور في أنه لا يحتاج إلى تذكر قائمة سمات كل علامة ولا صيغتها. وهذه ميزة كبيرة عندما لا تكون على دراية تامة بمكتبات علامات الخادم التي يوفرها ASP.NET. وعندما يتم إتقان هذه الصيغة، قد يفضل بعض المطورين كتابة العلامات مباشرة في كود عرض الصفحة دون المرور بمرحلة التصميم الجرافيكي. في هذه الحالة، لا يكون IDE مفيدًا بعد الآن. يكفي استخدام محرر نصوص بسيط. ووفقًا لطريقة العمل، يتم التركيز عندئذٍ على المكونات (استخدام IDE) أو العلامات (استخدام محرر نصوص). هناك تكافؤ بين هذين المصطلحين. المكون هو الكائن الذي سيتم التعامل معه بواسطة كود التحكم في الصفحة. يتيح لنا IDE الوصول إلى خصائصه في مرحلة التصميم. يتم ترجمة القيم المحددة لهذه الخصائص على الفور إلى سمات علامة المكون في كود العرض. في مرحلة التنفيذ، سيتعامل كود التحكم في الصفحة مع المكون ويخصص قيمًا لبعض خصائصه. سيقوم كود العرض بإنشاء كود المكون HTML باستخدام، من ناحية، السمات المحددة في التصميم لعلامة الخادم المقابلة، ومن ناحية أخرى، قيم خصائص المكون المحسوبة بواسطة كود التحكم.

7.2. سياق تنفيذ الأمثلة

سنقوم بتوضيح تصميم واجهات الويب القائمة على مكونات الخادم باستخدام برامج يكون سياق تنفيذها في معظم الأحيان كما يلي:

  1. ستتألف تطبيق الويب من صفحة P واحدة تحتوي على نموذج F،
  2. سيقوم العميل بإرسال طلبه الأول مباشرة إلى هذه الصفحة P. وسيتضمن ذلك طلب عنوان URL للصفحة P باستخدام متصفح. وبالتالي، سيتم إرسال طلب GET إلى عنوان URL P هذا. سيقوم الخادم بتسليم الصفحة P وبالتالي النموذج F الذي تحتوي عليه،
  3. سيقوم المستخدم بملء النموذج وإرساله، c.a.d. حيث سيقوم بإجراء يفرض على المتصفح إرسال النموذج F إلى الخادم. وستظل عملية POST للمتصفح موجهة إلى الصفحة P. وسيقوم الخادم بتسليم الصفحة P مرة أخرى مع النموذج F، حيث قد يكون محتوى هذا الأخير قد تم تعديله بفعل المستخدم.
  4. ثم ستتكرر الخطوتان 2 و 3.

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

7.3. مكون Label

7.3.1. الاستخدام

تسمح العلامة <asp:label> بإدراج نص ديناميكي في كود عرض الصفحة. وبالتالي، فهي لا تفعل أكثر مما تفعله العلامة <%=variable%> المستخدمة حتى الآن. سيسمح لنا دراسة هذه العلامة الأولى باكتشاف آلية عمل علامات الخادم. نقوم بإنشاء صفحة تحتوي على جزء تحكم [form1.aspx.vb] وجزء عرض [form1.aspx] . يتعلق الأمر بعرض الوقت:

Image

تمت معالجة هذه المشكلة بالفعل في الفصل 2، وندعو القارئ إلى الرجوع إليه إذا أراد معرفة كيفية معالجتها. رمز العرض [form1.aspx] هو كما يلي:


<%@ page src="form1.aspx.vb" inherits="form1" AutoEventWireup="false" %>
<HTML>
    <HEAD>
        <title>Webforms</title>
    </HEAD>
    <body>
        <asp:Label Runat="server" ID="lblHeure" />
    </body>
</HTML>

نقوم بإدخال العلامة <asp:label>. في مكتبة العلامات، يعد السمة [runat="server"] إلزامية. تحدد السمة ID المكون. يجب على وحدة التحكم الإشارة إليه باستخدام هذا المعرف. رمز وحدة التحكم [form1.aspx.vb] هو كما يلي:


Imports System.Web.UI.WebControls

Public Class form1
    Inherits System.Web.UI.Page

    Protected lblHeure As Label

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
        ' حفظ الاستعلام الحالي في request.txt في مجلد الصفحة
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' يضع الوقت في lblHeure
        lblHeure.Text = "Il est " + Date.Now.ToString("T")
    End Sub

End Class

يجب أن يعطي المتحكم قيمة للكائن [lblHeure] من النوع [System.Web.UI.WebControls.Label]. جميع الكائنات المعروضة بواسطة العلامات <asp:> تنتمي إلى مساحة الأسماء [System.Web.UI.WebControls]. وبالتالي يمكن استيراد مساحة الأسماء هذه بشكل منهجي:


Imports System.Web.UI.WebControls

يحتوي الكائن [Label] على خصائص مختلفة، منها الخاصية [Text] التي تمثل النص الذي سيتم عرضه بواسطة العلامة <asp:label> المقابلة. هنا، نضع في هذه الخاصية الوقت الحالي. ونقوم بذلك في الإجراء [Form_Load] الخاص بوحدة التحكم والذي يتم تنفيذه بشكل منهجي. في الإجراء [Form_Init]، الذي يتم تنفيذه أيضًا بشكل دائم ولكن قبل الإجراء [Form_Load]، نقوم بتخزين طلب العميل في ملف [request.txt] الموجود في مجلد التطبيق. ستتاح لنا الفرصة لفحص هذا الملف لفهم بعض جوانب عمل الصفحات التي تستخدم علامات الخادم.

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

7.3.2. الاختبارات

نضع الملفات (form1.aspx، form1.aspx.vb) في مجلد <application-path> ونشغل Cassini بالمعلمات (<application-path>,/form1). ثم نطلب عنوان URL [http://localhost/form1/form1.aspx]. ونحصل على النتيجة التالية:

Image

الرمز HTML الذي يتلقاه المتصفح هو التالي:

<HTML>
    <HEAD>
        <title>Webforms</title>
    </HEAD>
    <body>
        <span id="lblHeure">Il est 19:39:37</span>
    </body>
</HTML>

يمكننا أن نرى أن علامة الخادم


        <asp:Label Runat="server" ID="lblHeure" />

قد تم تحويلها إلى الكود HTML التالي:

        <span id="lblHeure">Il est 19:39:37</span>

إنها الخاصية [Text] للكائن [lblHeure] التي تم وضعها بين العلامتين <span> و </span>. الطلب الذي قدمه العميل وتم تخزينه في [request.txt] هو التالي:

GET /form1/form1.aspx HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

كل شيء طبيعي تمامًا.

7.3.3. إنشاء التطبيق باستخدام WebMatrix

لقد قمنا بإنشاء كود عرض الصفحة [form1.aspx] يدويًا. يمكن استخدام هذه الطريقة إذا كنت على دراية بعلامات HTML. في هذه الحالة، يكفي استخدام محرر نصوص بسيط لإنشاء واجهة المستخدم. في البداية، غالبًا ما تكون هناك حاجة إلى أداة تصميم جرافيكي مقترنة بإنشاء كود تلقائي لأنك لا تعرف صيغة العلامات التي تحتاجها. نقوم الآن بإنشاء نفس التطبيق باستخدام أداة WebMatrix. بمجرد تشغيل WebMatrix، نختار الخيار [File/New File]:

Image

نقوم بإنشاء صفحة ASP.NET تسمى [form2.aspx]. بمجرد التحقق من صحة المساعد السابق، نحصل على نافذة تصميم الصفحة [form2.aspx]:

Image

Image

نذكر أن WebMatrix يضع كود التحكم في الصفحة وكود العرض في ملف واحد، وهو هنا [form2.aspx]. تعرض علامة التبويب [All] محتوى هذا الملف النصي. يمكننا أن نلاحظ الآن أنه ليس فارغًا:

Image

عيب هذا النوع من الأدوات هو أنها غالبًا ما تولد كودًا غير ضروري. وهذا هو الحال هنا، حيث أن WebMatrix قد ولّد علامة <form> بينما لن نقوم بإنشاء نموذج... بالإضافة إلى ذلك، يمكن ملاحظة أن المستند لا يحتوي على علامة <title>. نقوم بمعالجة هاتين المشكلتين على الفور للحصول على النسخة الجديدة التالية:

Image

سيتم إدراج ما نسميه كود التحكم بين العلامتين <script> و </script>، مما يضمن فصلًا بصريًا على الأقل بين نوعي الكود: التحكم والعرض. نعود إلى علامة التبويب [Design] لرسم واجهتنا. تتوفر قائمة بالمكونات في نافذة أدوات على يسار نافذة التصميم:

Image

توفر نافذة الأدوات الوصول إلى نوعين من المكونات:

  • مكونات [WebControls] التي تترجم إلى علامات <asp:>
  • مكونات [HTML Elements] التي تُترجم إلى علامات HTML التقليدية. ومع ذلك، يمكن إضافة السمة [runat="server"] إلى سمات علامة HTML. في هذه الحالة، يمكن للوحدة التحكم الوصول إلى العلامة HTML وسماتها عبر كائن له خصائص العلامة HTML التي يمثلها. وقد أطلقنا سابقًا على هذه العلامات اسم علامات الخادم HTML.

لنضغط مرتين على المكون [Label] في قائمة عناصر التحكم [WebControls]. في علامة التبويب [Design] نحصل على النتيجة التالية:

Image

في علامة التبويب [All]، أصبح الرمز كما يلي:

<%@ Page Language="VB" %>
<html>
<head>
    <title>webforms</title>
</head>
<body>
    <asp:Label id="Label1" runat="server">Label</asp:Label>
</body>
</html>

يمكن ملاحظة أولاً أننا فقدنا العلامة <script>. تم إنشاء علامة <asp:label>. اسمها هو [Label1] وقيمتها هي [Label]. لنعد إلى علامة التبويب [Design] لتعديل هاتين القيمتين. نضغط مرة واحدة على المكون [Label] لإظهار نافذة خصائص هذا المكون، في أسفل اليمين:

Image

يُطلب من القارئ الاطلاع على خصائص الكائن [Label]. هناك خاصيتان تهماننا هنا:

  • Text: هو النص الذي يجب أن يعرضه الملصق - نضع السلسلة فارغة (c.a.d. nothing)
  • ID: هو معرفه - نضع lblHeure

تصبح علامة التبويب [Design] كما يلي:

Image

ويصبح رمز [All] كما يلي:

<%@ Page Language="VB" %>
<html>
<head>
    <title>webforms</title>
</head>
<body>
    <asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>

انتهى الجزء الخاص بعرض الصفحة. يبقى لنا كتابة كود التحكم المسؤول عن وضع الوقت في الخاصية [Text] لـ [lblHeure]. نضيف في علامة التبويب [All] الكود التالي:


<%@ Page Language="VB" %>

<script runat="server">
    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' يضع الوقت في lblHeure
        lblHeure.Text = "Il est " + Date.Now.ToString("T")
    End Sub
</script>

<html>
<head>
    <title>webforms</title>
</head>
<body>
    <asp:Label id="lblHeure" runat="server"></asp:Label>
</body>
</html>

يُلاحظ أنه في كود وحدة التحكم، لم يتم إعلان الكائن [lblHeure] كما تم سابقًا:


    Protected lblHeure As New System.Web.UI.WebControls.Label

في الواقع، يتم الإعلان ضمناً عن جميع مكونات الخادم <asp:> في جزء العرض في جزء كود التحكم. كما أن الإعلان عنها يؤدي إلى حدوث خطأ في التحويل البرمجي، يشير إلى أن الكائن قد تم الإعلان عنه بالفعل. نحن جاهزون للتنفيذ. نختار الخيار [View/Start] أو الاختصار [F5]. يتم تشغيل Cassini تلقائيًا بالمعلمات التالية:

Image

نقبل هذه القيم. يتم تشغيل متصفح النظام الافتراضي تلقائيًا لطلب عنوان URL [http://localhost/form2.aspx]. نحصل على النتيجة التالية:

Image

بعد ذلك، سنستخدم بشكل أساسي الأداة WebMatrix لتسهيل إنشاء واختبار البرامج القصيرة التي سنكتبها.

7.4. المكون Literal

7.4.1. الاستخدام

تسمح العلامة <asp:literal> بإدراج نص ديناميكي في كود عرض الصفحة مثل العلامة <asp:label>. السمة الرئيسية لها هي [Text] التي تمثل النص الذي سيتم إدراجه كما هو في تدفق HTML للصفحة. هذه العلامة كافية إذا لم تكن هناك نية لتنسيق النص الذي نريد إدراجه في تدفق HTML. ففي الواقع، إذا كانت الفئة [Label] تسمح بالتنسيق بفضل سمات مثل [BorderColor, BorderWidth, Font, ...]، فإن الفئة [Literal] لا تحتوي على أي من هذه السمات. يمكن للقارئ تكرار المثال السابق بالكامل باستبدال المكون [Label] بمكون [Literal].

7.5. مكون Button

7.5.1. الاستخدام

تسمح العلامة <asp:Button> بإدراج زر من النوع [Submit] في نموذج، مما يوفر إدارة للأحداث مشابهة لتلك الموجودة في تطبيقات ويندوز. وهذا هو الموضوع الذي نريد التعمق فيه هنا. نقوم بإنشاء الصفحة [form3.aspx] التالية:

Image

تتكون هذه الصفحة، التي تم إنشاؤها باستخدام WebMatrix، من ثلاثة مكونات:

رقم
الاسم
النوع
الخصائص
الدور
1
Button1
زر
text=زر 1
زر الإرسال
2
Button2
زر
text=زر 2
زر الإرسال
3
lblInfo
تسمية
text=
رسالة إعلامية

الرمز الذي تم إنشاؤه بواسطة WebMatrix لهذا الجزء هو التالي:

<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form runat="server">
        <p>
            <asp:Button id="Button1" runat="server" Text="Bouton1"></asp:Button>
            <asp:Button id="Button2" runat="server" Text="Bouton2"></asp:Button>
        </p>
        <p>
            <asp:Label id="lblInfo" runat="server"></asp:Label>
        </p>
    </form>
</body>
</html>

نجد المكونات [Button] و [Label] المستخدمة أثناء التصميم الجرافيكي للصفحة في علامات <asp:>. لاحظ علامة <form runat="server"> التي تم إنشاؤها تلقائيًا. إنها علامة خادم HTML، c.a.d. علامة HTML كلاسيكية ممثلة مع ذلك بكائن يمكن التحكم فيه بواسطة وحدة التحكم. سيتم إنشاء كود العلامة <form> استنادًا إلى القيمة التي سيمنحها وحدة التحكم لهذا الكائن.

لنضف في جزء وحدة التحكم من الكود الإجراء [Page_Init] الذي يدير الحدث [Init] للصفحة. نضع فيه الكود الذي يحفظ طلب العميل في الملف [request.txt]. سنحتاج إلى ذلك لفهم آلية عمل الأزرار.

<script runat="server">
    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) 
        ' يحفظ الطلب الحالي في request.txt من ملف الصفحة
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub
</script>

يُلاحظ أعلاه أننا لم نضع البند [Handles MyBase.Init] بعد إعلان الإجراء [Page_Init]. في الواقع، يحتوي الحدث [Init] الخاص بالكائن [Page] على معالج افتراضي يسمى [Page_Init]. إذا استخدمنا اسم المعالج هذا، تصبح الجملة [Handles Page.Init] غير ضرورية. ومع ذلك، فإن إدراجها لا يتسبب في حدوث أي خطأ.

7.5.2. الاختبارات

نقوم بتشغيل التطبيق تحت WebMatrix بواسطة [F5]. نحصل على الصفحة التالية:

Image

الرمز HTML الذي يتلقاه المتصفح هو التالي:

<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />

        <p>
            <input type="submit" name="Button1" value="Bouton1" id="Button1" />
            <input type="submit" name="Button2" value="Bouton2" id="Button2" />

        </p>
        <p>
            <span id="lblInfo"></span>
        </p>
    </form>
</body>
</html>

تجدر الإشارة إلى النقاط التالية:

  • تحولت العلامة <form runat="server"> إلى العلامة HTML
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">

تم تعيين سمتين هما [method="post"] و [action="form3.aspx"]. هل يمكن إعطاء قيم مختلفة لهاتين السمتين؟ سنحاول توضيح هذه النقطة لاحقًا. سنحتفظ هنا بأن النموذج سيتم إرساله إلى عنوان URL [form3.aspx]. تم أيضًا تعيين سمتين أخريين هما [name, id]. في معظم الأحيان، يتم تجاهلهما. ومع ذلك، إذا كانت الصفحة تحتوي على كود جافا سكريبت يتم تنفيذه من جانب المتصفح، فإن السمة [name] لعلامة <form> تكون مفيدة.

  • أصبحت علامات <asp:button> علامات HTML لأزرار [submit]. وبالتالي، فإن النقر على أي من هذه الأزرار سيؤدي إلى "إرسال" النموذج [_ctl10] إلى عنوان URL [form3.aspx].
  • أصبحت العلامة <asp:label> علامة HTML <span>
  • تم إنشاء حقل مخفي [__VIEWSTATE] بقيمة غريبة:
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />

يمثل هذا الحقل بشكل مشفر حالة النموذج المرسلة إلى العميل. تمثل هذه الحالة قيمة جميع مكونات النموذج. نظرًا لأن [__VIEWSTATE] جزء من النموذج، فسيتم إرسال قيمته مع الباقي إلى الخادم. سيسمح ذلك للخادم بمعرفة أي مكون من مكونات النموذج قد تغيرت قيمته واتخاذ القرارات اللازمة. ستتخذ هذه القرارات شكل أحداث من نوع "تغيرت قيمة TextBox كذا وكذا".

7.5.3. طلبات العميل

بمجرد الحصول على الصفحة [form3.aspx] في المتصفح، نطلبها مرة أخرى ثم ننظر إلى الطلب الذي أرسله المتصفح للحصول عليها. نتذكر أن تطبيقنا يخزنها في الملف [request.txt] في مجلد التطبيق:

GET /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

لدينا هنا رمز GET كلاسيكي. لنضغط الآن على زر [Bouton1] في الصفحة في المتصفح. يبدو أنه لم يحدث شيء. ومع ذلك، نحن نعلم أن النموذج قد تم إرساله. هذا ما يشير إليه رمز HTML للصفحة. ويتم تأكيد ذلك من خلال المحتوى الجديد لـ [request.txt]:

POST /form3.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 80
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form3.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

__VIEWSTATE=dDwxNTY0NjIwMjUwOzs%2B2mcnJczeuvF2PEfvmtv7uiUhWUw%3D&Button1=Bouton1

يشير العنوان الأول HTTP بوضوح إلى أن العميل أرسل POST إلى عنوان URL [/form3.aspx]. ويُظهر السطر الأخير القيم المرسلة:

  • قيمة الحقل المخفي __VIEWSTATE
  • قيمة الزر الذي تم النقر عليه

إذا نقرنا على [Bouton2]، فإن القيم التي أرسلها المتصفح هي التالية:

__VIEWSTATE=dDwxNTY0NjIwMjUwOzs%2B2mcnJczeuvF2PEfvmtv7uiUhWUw%3D&Button2=Bouton2

تظل قيمة الحقل المخفي كما هي، ولكن القيمة التي تم إرسالها هي قيمة [Button2]. وبالتالي، يمكن للخادم معرفة الزر الذي تم استخدامه. وسيستخدم هذه المعلومة لتشغيل حدث يمكن للصفحة معالجته بمجرد تحميلها.

7.5.4. إدارة حدث النقر (Click) لكائن Button

لنتذكر كيفية عمل صفحة .aspx. هذه الصفحة هي كائن مشتق من الفئة [Page]. لنسمي الفئة المشتقة [unePage]. عندما يتلقى الخادم طلبًا لصفحة من هذا النوع، يتم إنشاء مثيل لكائن من النوع [unePage] بواسطة عملية new unePage(...). ثم يقوم الخادم بعد ذلك بإنشاء حدثين يُسميان [Init] و [Load] بهذا الترتيب. يمكن للكائن [unePage] إدارتها من خلال توفير معالجات الأحداث [Page_Init] و [Page_Load]. بعد ذلك، سيتم إنشاء أحداث أخرى. سيكون لدينا فرصة للعودة إلى هذا الموضوع لاحقًا. إذا كان طلب العميل هو POST، فسيقوم الخادم بتوليد الحدث [Click] للزر الذي تسبب في هذا POST. إذا كانت الفئة [unePage] قد وفرت معالجًا لهذا الحدث، فسيتم استدعاؤه. لنرى هذه الآلية باستخدام WebMatrix. في علامة التبويب [Design] الخاصة بـ [form3.aspx]، انقر نقرًا مزدوجًا على الزر [Bouton1]. يتم توجيهنا تلقائيًا إلى علامة التبويب [Code]، في نص إجراء يسمى [Button1_Click]. لفهم الأمر بشكل أفضل، لننتقل إلى علامة التبويب [All] ونلقي نظرة على الكود بالكامل. تم إجراء التعديلات التالية:

<%@ Page Language="VB" %>
<script runat="server">

...
    Sub Button1_Click(sender As Object, e As EventArgs)

    End Sub

</script>
<html>
...
<body>
    <form runat="server">
...
            <asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
    </form>
</body>
</html>

تمت إضافة سمة جديدة [onclick="Button1_Click"] إلى العلامة <asp:Button> الخاصة بـ [Button1]. تشير هذه السمة إلى الإجراء المسؤول عن معالجة الحدث [Click] على الكائن [Button1]، وهو في هذه الحالة الإجراء [Button1_Click]. لم يتبق لنا سوى كتابة هذا الإجراء:

    Sub Button1_Click(sender As Object, e As EventArgs)
        ' النقر على الزر 1
        lblInfo.Text="Vous avez cliqué sur [Bouton1]"
    End Sub

تضع الإجراء في التسمية [lblInfo] رسالة إعلامية. نتبع نفس الطريقة بالنسبة للزر [Bouton2] للحصول على الصفحة الجديدة التالية [form3.aspx]:

<%@ Page Language="VB" %>
<script runat="server">

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
        ' حفظ الطلب الحالي في request.txt من مجلد الصفحة
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

    Sub Button1_Click(sender As Object, e As EventArgs)
        ' انقر على الزر 1
        lblInfo.Text="Vous avez cliqué sur [Bouton1]"
    End Sub

    Sub Button2_Click(sender As Object, e As EventArgs)
        ' انقر على الزر 2
        lblInfo.Text="Vous avez cliqué sur [Bouton2]"
    End Sub

</script>
<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form runat="server">
        <p>
            <asp:Button id="Button1" onclick="Button1_Click" runat="server" Text="Bouton1"></asp:Button>
            <asp:Button id="Button2" onclick="Button2_Click" runat="server" Text="Bouton2" BorderStyle="None"></asp:Button>
        </p>
        <p>
            <asp:Label id="lblInfo" runat="server"></asp:Label>
        </p>
    </form>
</body>
</html>

نقوم بتشغيل الأمر [F5] للحصول على الصفحة التالية:

Image

إذا نظرنا إلى الرمز HTML الذي تم استلامه، فسنلاحظ أنه لم يتغير مقارنةً بالرمز الخاص بالإصدار السابق للصفحة:

<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwOzs+2mcnJczeuvF2PEfvmtv7uiUhWUw=" />

        <p>
            <input type="submit" name="Button1" value="Bouton1" id="Button1" />
            <input type="submit" name="Button2" value="Bouton2" id="Button2" />
        </p>
        <p>
            <span id="lblInfo"></span>
        </p>
    </form>
</body>
</html>

إذا نقرنا على [Bouton1]، نحصل على الإجابة التالية:

Image

الرمز HTML الذي تم استلامه لهذا الرد هو التالي:

<html>
<head>
    <title>asp:button</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form3.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNTY0NjIwMjUwO3Q8O2w8aTwxPjs+O2w8dDw7bDxpPDU+Oz47bDx0PHA8cDxsPFRleHQ7PjtsPFZvdXMgYXZleiBjbGlxdcOpIHN1ciBbQm91dG9uMV07Pj47Pjs7Pjs+Pjs+Pjs+4oO98Vd244kj0lPMXReWOwJ1WW0=" />

        <p>
            <input type="submit" name="Button1" value="Bouton1" id="Button1" />
            <input type="submit" name="Button2" value="Bouton2" id="Button2" />
        </p>
        <p>
            <span id="lblInfo">Vous avez cliqué sur [Bouton1]</span>
        </p>
    </form>
</body>
</html>

يمكننا ملاحظة أن الحقل المخفي [__VIEWSTATE] قد تغيرت قيمته. وهذا يعكس تغير قيمة المكون [lblInfo].

7.5.5. أحداث دورة حياة تطبيق ASP.NET

تقدم الوثائق ASP.NET قائمة بالأحداث التي يولدها الخادم خلال دورة حياة التطبيق:

  • عند استلام الطلب الأول للتطبيق، سيتم إنشاء الحدث [Start] على كائن [HttpApplication] الخاص بالتطبيق. يمكن إدارة هذا الحدث بواسطة الإجراء [Application_Start] في ملف [global.asax] الخاص بالتطبيق.

بعد ذلك، ستتكرر سلسلة من الأحداث لكل طلب يتم استلامه:

  • إذا لم يرسل الطلب رمز جلسة عمل، يتم بدء جلسة عمل جديدة ويتم إنشاء حدث [Start] على الكائن [Session] المرتبط بالطلب. يمكن إدارة هذا الحدث بواسطة الإجراء [Session_Start] في الملف [global.asax] الخاص بالتطبيق.
  • يقوم الخادم بإنشاء الحدث [BeginRequest] على الكائن [HttpApplication]. ويمكن إدارته بواسطة الإجراء [Application_BeginRequest] الموجود في ملف [global.asax] الخاص بالتطبيق.
  • يقوم الخادم بتحميل الصفحة المطلوبة في الطلب. وسيقوم بإنشاء مثيل لكائن [Page] ثم إنشاء حدثين على هذا الكائن: [Init] ثم [Load]. يمكن إدارة هذين الحدثين بواسطة الإجراءين [Page_Init] و [Page_Load] في الصفحة.
  • استنادًا إلى القيم المرسلة المستلمة، سيقوم الخادم بإنشاء أحداث أخرى: [TextChanged] لمكون [TextBox] الذي تغيرت قيمته، و[CheckedChanged] لزر الاختيار الذي تغيرت قيمته، و[SelectedIndexChanged] لقائمة تغير العنصر المحدد فيها، ... ستتاح لنا الفرصة لذكر الأحداث الرئيسية لكل مكون من مكونات الخادم التي سنقدمها. يمكن إدارة كل حدث E على كائن يسمى O بواسطة إجراء يحمل الاسم O_E.
  • ترتيب معالجة الأحداث السابقة غير مضمون. لذلك يجب ألا تفترض برامج المعالجة أي شيء بشأن هذا الترتيب. ومع ذلك، من المؤكد أن الحدث [Click] الخاص بالزر الذي تسبب في الحدث POST يتم معالجته أخيرًا.
  • بمجرد أن تصبح الصفحة جاهزة، سيقوم الخادم بإرسالها إلى العميل. قبل ذلك، يقوم بتوليد الحدث [PreRender] الذي يمكن معالجته بواسطة الإجراء [Page_PreRender] للصفحة.
  • بمجرد إرسال الاستجابة HTML إلى العميل، سيتم تفريغ الصفحة من الذاكرة. سيتم إنشاء حدثين في هذه الحالة هما [Unload] و [Disposed]. يمكن للصفحة استخدام هذين الحدثين لتحرير الموارد.

يمكن للتطبيق أيضًا تلقي أحداث خارج نطاق طلب العميل:

  • يحدث الحدث [End] على كائن [Session] في التطبيق عند انتهاء الجلسة. وقد يحدث ذلك بناءً على طلب صريح من كود الصفحة أو لأن مدة صلاحية الجلسة قد انتهت. تدير الإجراء [Session_End] في الملف [global.asax] هذا الحدث. وعادةً ما يتم فيه تحرير الموارد التي تم الحصول عليها في [Session_Start].
  • يحدث الحدث [End] على الكائن [HttpApplication] للتطبيق عند انتهاء التطبيق. ويحدث هذا بشكل خاص عند إيقاف تشغيل خادم الويب. تدير الإجراء [Application_End] في الملف [global.asax] هذا الحدث. وعادةً ما يتم فيه تحرير الموارد التي تم الحصول عليها في [Application_Start].

يجب ملاحظة النقاط التالية:

  • يعتمد نموذج الأحداث السابق على التبادلات التقليدية بين العميل والخادم HTTP. ويمكن ملاحظة ذلك بوضوح عند فحص الرؤوس HTTP المتبادلة.
  • تتم معالجة الأحداث السابقة دائمًا على جانب الخادم. يمكن بالطبع معالجة النقر على زر بواسطة برنامج نصي جافا سكريبت على جانب الخادم. ولكن لا يتعلق الأمر في هذه الحالة بحدث خادم، ونحن هنا أمام تقنية مستقلة عن ASP.NET.

متى تتم معالجة الأحداث، سواء تمت معالجتها من جانب الخادم (الأحداث المرتبطة بمكونات الخادم) أو من جانب المتصفح بواسطة نصوص جافا سكريبت؟

لنأخذ مثال قائمة منسدلة. عندما يغير المستخدم العنصر المحدد فيها، يمكن معالجة الحدث (تغيير العنصر المحدد) أو عدم معالجته، وإذا تمت معالجته، فيمكن أن تتم في أوقات مختلفة.

  • إذا أردنا معالجته على الفور، فلدينا حلان:
    • يمكن معالجته بواسطة المتصفح باستخدام برنامج نصي جافا سكريبت. ولا يتدخل الخادم في هذه الحالة. ولتحقيق ذلك، يجب أن تكون الصفحة قابلة لإعادة البناء باستخدام القيم الموجودة في الصفحة.
    • يمكن معالجته بواسطة الخادم. ولهذا، لا يوجد سوى حل واحد: يجب إرسال النموذج إلى الخادم للمعالجة. لدينا إذن عملية [submit]. سنرى أنه في هذه الحالة، نستخدم مكون خادم يسمى [DropDownList] ونحدد سمة [AutoPostBack] الخاصة به على [true]. وهذا يعني أنه في حالة تغيير العنصر المحدد في القائمة المنسدلة، يجب إرسال النموذج على الفور إلى الخادم. في هذه الحالة، يقوم الخادم بإنشاء رمز HTML للكائن [DropDownList] مرتبط بوظيفة جافا سكريبت مكلفة بإنشاء [submit] بمجرد حدوث حدث "تغيير العنصر المحدد". سيقوم هذا الرمز [submit] بإرسال النموذج إلى الخادم، وستوضع فيه حقول مخفية للإشارة إلى أن الرمز [post] ناتج عن تغيير في التحديد في القائمة المنسدلة. ثم سيقوم الخادم بإنشاء الحدث [SelectedIndexChanged] الذي يمكن للصفحة معالجته.
    • إذا كنا نرغب في معالجته ولكن ليس على الفور، فإننا نحدد السمة [AutoPostBack] لمكون الخادم [DropDownList] إلى [false]. في هذه الحالة، يقوم الخادم بإنشاء الرمز HTML التقليدي لقائمة <select> بدون وظيفة جافا سكريبت مرتبطة بها، وذلك للكائن [DropDownList]. ولا يحدث أي شيء عند قيام المستخدم بتغيير التحديد في القائمة المنسدلة. ومع ذلك، عندما يقوم بتأكيد النموذج عن طريق زر [submit] على سبيل المثال، سيتمكن الخادم من التعرف على حدوث تغيير في التحديد. فقد رأينا بالفعل أن النموذج المرسل إلى الخادم يحتوي على حقل مخفي [__VIEWSTATE] يمثل بشكل مشفر حالة جميع عناصر النموذج المرسل. عندما يتلقى الخادم النموذج الجديد الذي أرسله العميل، سيتمكن من التحقق مما إذا كان العنصر المحدد في القائمة المنسدلة قد تغير أم لا. إذا كان الأمر كذلك، فسيقوم بتوليد الحدث [SelectedIndexChanged] الذي ستتمكن الصفحة من إدارته. للتمييز بين هذه الآلية والآلية السابقة، يقول بعض المؤلفين إن حدث "تغيير التحديد" تم "تخزينه مؤقتًا" عندما حدث على المتصفح. ولن يعالجه الخادم إلا عندما يرسل المتصفح النموذج إليه، وغالبًا ما يكون ذلك نتيجة النقر على زر [submit].
    • وأخيرًا، إذا لم نرغب في معالجة الحدث، نقوم بتعيين السمة [AutoPostBack] لمكون الخادم [DropDownList] إلى [false] ولا نكتب معالج الحدث الخاص به [SelectedIndexChanged].

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

  • تحتوي مكونات الخادم في النموذج على خاصية [AutoPostBack] إلى [false]
  • يحتوي النموذج على زر أو أزرار مسؤولة عن تنفيذ [POST] للنموذج
  • يتم كتابة معالجات الأحداث التي نريد إدارتها فقط في كود وحدة التحكم للصفحة، وغالبًا ما يكون الحدث [Click] على أحد الأزرار.

7.6. المكون TextBox

7.6.1. الاستخدام

تسمح العلامة <asp:TextBox> بإدراج حقل إدخال في كود عرض الصفحة. نقوم بإنشاء صفحة [form4.aspx] للحصول على العرض التالي:

Image

4321تحتوي هذه الصفحة، التي تم إنشاؤها باستخدام WebMatrix، على المكونات التالية:

رقم
الاسم
النوع
الخصائص
الدور
1
TextBox1
TextBox
AutoPostback=true
Text=
حقل الإدخال
2
TextBox2
TextBox
AutoPostback=false
Text=
حقل الإدخال
3
lblInfo1
التسمية
text=
رسالة إعلامية حول محتوى [TextBox1]
3
lblInfo2
التسمية
text=
رسالة إعلامية حول محتوى [TextBox2]

الرمز الذي تم إنشاؤه بواسطة WebMatrix لهذا الجزء هو التالي:

<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
    <title>asp:textbox</title>
</head>
<body>
    <form runat="server">
        <p>
            Texte 1 :
            <asp:TextBox id="TextBox1" runat="server" AutoPostBack="True"></asp:TextBox>
        </p>
        <p>
            Texte 2 :
            <asp:TextBox id="TextBox2" runat="server"></asp:TextBox>
        </p>
        <p>
            <asp:Label id="lblInfo1" runat="server"></asp:Label>
        </p>
        <p>
            <asp:Label id="lblInfo2" runat="server"></asp:Label>
        </p>
    </form>
</body>
</html>

في علامة التبويب [Design]، انقر نقرًا مزدوجًا على المكون [TextBox1]. يتم بعد ذلك إنشاء الهيكل الأساسي لمدير الحدث [TextChanged] لهذا الكائن (علامة التبويب [All]):

<%@ Page Language="VB" %>
<script runat="server">

    Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
    End Sub
</script>
<html>
...
<body>
...
            <asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
        </p>
....
    </form>
</body>
</html>

تمت إضافة السمة [OnTextChanged="TextBox1_TextChanged"] إلى العلامة <asp:TextBox id="TextBox1"> لتعيين معالج الحدث [TextChanged] على [TextBox1]. هذه هي الإجراء [TextBox1_Changed] الذي نكتبه الآن.

    Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
      ' تغيير النص
      lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
    End Sub

في الإجراء، نكتب في التسمية [lblInfo1] رسالة تشير إلى الحدث وتوضح محتوى [TextBox1]. ونفعل الشيء نفسه بالنسبة لـ [TextBox2]. كما نحدد الوقت لتتبع معالجة الأحداث بشكل أفضل. الرمز النهائي لـ [form4.aspx] هو التالي:

<%@ Page Language="VB" %>
<script runat="server">

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
        ' حفظ الطلب الحالي في request.txt من مجلد الصفحة
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

    Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
      ' تغيير النص
      lblInfo1.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox1]. Texte 1=["+textbox1.Text+"]"
    End Sub

    Sub TextBox2_TextChanged(sender As Object, e As EventArgs)
      ' تغيير النص
      lblInfo2.text=Date.now.Tostring("T") + ": evt [TextChanged] sur [TextBox2]. Texte 2=["+textbox2.Text+"]"
    End Sub

</script>
<html>
<head>
    <title>asp:textbox</title>
</head>
<body>
    <form runat="server">
        <p>
            Texte 1 :
            <asp:TextBox id="TextBox1" runat="server" AutoPostBack="True" OnTextChanged="TextBox1_TextChanged"></asp:TextBox>
        </p>
        <p>
            Texte 2 :
            <asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
        </p>
        <p>
            <asp:Label id="lblInfo1" runat="server"></asp:Label>
        </p>
        <p>
            <asp:Label id="lblInfo2" runat="server"></asp:Label>
        </p>
    </form>
</body>
</html>

لقد أضفنا الإجراء [Page_Init] لتخزين طلب العميل كما في المثال السابق.

7.6.2. الاختبارات

نقوم بتشغيل التطبيق تحت WebMatrix بواسطة [F5]. نحصل على الصفحة التالية:

Image

الرمز HTML الذي يتلقاه المتصفح هو التالي:

<html>
<head>
    <title>asp:textbox</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />

<script language="javascript">
<!--
    function __doPostBack(eventTarget, eventArgument) {
        var theform = document._ctl0;
        theform.__EVENTTARGET.value = eventTarget;
        theform.__EVENTARGUMENT.value = eventArgument;
        theform.submit();
    }
// -->
</script>

        <p>
            Texte 1 :
            <input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1"></span>

        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>
    </form>
</body>
</html>

هناك الكثير من العناصر في هذا الرمز التي تم إنشاؤها تلقائيًا بواسطة الخادم. لاحظ النقاط التالية:

  • هناك ثلاثة حقول مخفية: [__VIEWSTATE] التي سبق أن صادفناها، و [__EventTarget] و [__EventArgument]. يُستخدم الحقلان الأخيران لإدارة حدث "التغيير" في المتصفح في حقل الإدخال [TextBox1]
  • وقد أدت علامات الخادم <asp:textbox> إلى إنشاء علامات HTML <input type="text" ...> التي تتوافق مع حقول الإدخال
  • أنتجت علامة الخادم <asp:textbox id="TextBox1" AutoPostBack="true" ...> علامة <input type="text" ...> ذات سمة [onchange="__doPostBack('TextBox1','')"]. تشير هذه السمة إلى أنه في حالة تغيير محتوى [TextBox1]، يجب تنفيذ وظيفة جافا سكريبت [_doPostBack(...)]. وهي كما يلي:
<script language="javascript">
<!--
    function __doPostBack(eventTarget, eventArgument) {
        var theform = document._ctl0;
        theform.__EVENTTARGET.value = eventTarget;
        theform.__EVENTARGUMENT.value = eventArgument;
        theform.submit();
    }
// -->
</script>

ماذا تفعل الوظيفة أعلاه؟ إنها تعين قيمة لكل من الحقلين المخفيين [__EventTarget] و [__EventArgument] ثم ترسل النموذج. وبالتالي يتم إرسال النموذج إلى الخادم. هذا هو تأثير [AutoPostBack]. يؤدي حدث المتصفح "change" إلى تنفيذ الكود "__doPostBack('TextBox1','')". ونستنتج من ذلك أنه في النموذج المرسل، سيكون للحقل المخفي [__EventTarget] القيمة 'TextBox1' وللحقل المخفي [__EventArgument] القيمة ''. وهذا سيسمح للخادم بمعرفة المكون الذي تسبب في POST.

  • أدى عنصر الخادم <asp:textbox id="TextBox2"...> إلى إنشاء عنصر <input type="text" ...> تقليدي لأن سمة [AutoPostBack] الخاصة به لم تكن مضبوطة على [true].
  • تشير العلامة <form> إلى أن النموذج سيتم إرساله إلى [form4.aspx]:
    <form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">

لنقم باختبارنا الأول. لنكتب نصًا في حقل الإدخال الأول:

Image

ثم نضع المؤشر في حقل الإدخال الثاني. على الفور، نحصل على صفحة جديدة:

Image

ماذا حدث؟ عندما غادر المؤشر حقل الإدخال الأول، تحقق المتصفح مما إذا كان محتوى هذا الحقل قد تغير. وكان الأمر كذلك. لذا قام المتصفح بتوليد الحدث [change] على الحقل HTML [TextBox1]. لقد رأينا بعد ذلك أن وظيفة جافا سكريبت تم تنفيذها وأرسلت النموذج إلى [form4.aspx]. وبالتالي، تم إعادة تحميل هذه الصفحة بواسطة الخادم. سمحت القيم التي أرسلها النموذج للخادم بدوره بمعرفة أن محتوى علامة الخادم [TextBox1] قد تغير. وبالتالي، تم تنفيذ الإجراء [TextBox1_Changed] على جانب الخادم. وقد وضع رسالة في العلامة [lblInfo1]. بمجرد انتهاء هذا الإجراء، تم إرسال [form4.aspx] إلى المتصفح. ولهذا السبب لدينا الآن نص في [lblInfo1]. ومع ذلك، قد نستغرب وجود شيء ما في حقل الإدخال [TextBox1]. في الواقع، لا توجد أي عملية يتم تنفيذها على جانب الخادم تُعيّن قيمة لهذا الحقل. لدينا هنا آلية عامة لنماذج الويب ASP.NET: يقوم الخادم بإعادة إرسال النموذج في الحالة التي استلمه بها. وللقيام بذلك، يعيد تعيين القيمة التي تم إرسالها من قبل العميل إلى المكونات. بالنسبة لبعض المكونات، لا يرسل العميل أي قيمة. هذا هو الحال، على سبيل المثال، مع مكونات <asp:label> التي تُترجم إلى علامات <span>. نتذكر أن النموذج يحتوي على حقل مخفي يمثل حالة النموذج عند إرساله إلى العميل. هذه الحالة هي مجموع حالات جميع مكونات النموذج بما في ذلك مكونات <asp:label> إن وجدت. وبما أن الحقل المخفي [__VIEWSTATE] يتم إرساله بواسطة متصفح العميل، فإن الخادم قادر على استعادة الحالة السابقة لجميع مكونات النموذج. ولم يتبق له سوى تعديل تلك التي تغيرت قيمتها بواسطة POST.

لنلقِ نظرة الآن في [request.txt] على الطلب الذي تم إرساله بواسطة المتصفح:

POST /form4.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 137
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/form4.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=

يمكننا رؤية POST بوضوح بالإضافة إلى المعلمات التي تم إرسالها. لنعد إلى متصفحنا ونكتب نصًا في حقل الإدخال الثاني:

Image

لنعد إلى حقل الإدخال رقم 1 لإدخال نص جديد: لم يحدث شيء هذه المرة. لماذا؟ لأن حقل الإدخال [TextBox2] لا يحتوي على الخاصية [AutoPostBack] إلى [true]، فإن العلامة <input type="text"...> التي تم إنشاؤها له لا تدعم الحدث [Change] كما يوضح كودها HTML:

            <input name="TextBox2" type="text" id="TextBox2" />

لذلك لا يحدث أي حدث عند الخروج من حقل الإدخال رقم 2. الآن أدخل نصًا جديدًا في الحقل رقم 1:

Image

لنغادر حقل الإدخال رقم 1. فورًا يتم الكشف عن الحدث [Change] في هذا الحقل ويتم إرسال النموذج إلى الخادم الذي يرد بالصفحة التالية:

Image

ماذا حدث؟ أولاً، أرسل المتصفح النموذج. نجد هذا في طلب العميل المخزن في [request.txt]:

POST /form4.aspx HTTP/1.1
....
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTt0PDtsPGk8MT47PjtsPHQ8O2w8aTwxPjtpPDU%2BOz47bDx0PHA8cDxsPFRleHQ7PjtsPHByZW1pZXIgdGV4dGU7Pj47Pjs7Pjt0PHA8cDxsPFRleHQ7PjtsPDE4OjQyOjI5OiBldnQgW1RleHRDaGFuZ2VkXSBzdXIgW1RleHRCb3gxXS4gVGV4dGUgMT1bcHJlbWllciB0ZXh0ZV07Pj47Pjs7Pjs%2BPjs%2BPjs%2BxLOermpUUUz5rTAa%2FFsjda6lVmo%3D&TextBox1=troisi%C3%A8me+texte&TextBox2=second+texte

يبدأ الخادم بإعادة القيم السابقة إلى المكونات بفضل الحقل المخفي [__VIEWSTATE] الذي أرسله العميل إليه. وبفضل الحقول المرسلة [TextBox1] و [TextBox2]، سيقوم بتعيين القيم التي تم إرسالها إليه للمكونات [TextBox1] و [TextBox2]. من خلال هذه الآلية، سيستعيد العميل النموذج كما تم إرساله. ثم، وبفضل الحقول المرسلة [__VIEWSTATE]، [TextBox1] و [TextBox2]، سيكتشف الخادم أن قيم حقول الإدخال [TextBox1] و [TextBox2] قد تغيرت. وبالتالي، سيقوم بإنشاء الأحداث [TextChanged] لهذين الكائنين. سيتم تنفيذ الإجراءات [TextBox1_TextChanged] و [TextBox2_TextChanged] وستتلقى العلامات [labelInfo1] و [labelInfo2] قيمة جديدة. ثم يتم إرجاع الصفحة [form4.aspx] المعدلة إلى العميل.

الآن نقوم بتعديل حقل الإدخال رقم 1 مرة أخرى:

Image

عندما نخرج مؤشر الإدخال من الحقل 1، يحدث الحدث [Change] على المتصفح. بعد ذلك، تجري سلسلة الأحداث التي تم شرحها سابقًا (إرسال من المتصفح إلى الخادم، ...، إرسال استجابة الخادم). نحصل على الاستجابة التالية:

Image

نظرًا للوقت المعروض لكل رسالة، نرى أن الإجراء [TextBox1_Changed] هو الوحيد الذي تم تنفيذه على الخادم. لم يتم تنفيذ الإجراء [TextBox2_TextChanged] لأن قيمة [TextBox2] لم تتغير. أخيرًا، لنضع نصًا جديدًا في الحقل رقم 2:

Image

ثم نضع المؤشر على الحقل رقم 1 ثم نعيده إلى الحقل رقم 2. لا تتغير الصفحة. لماذا؟ لأننا لا نغير قيمة الحقل رقم 1، لا يحدث حدث المتصفح [Change] عندما نغادر هذا الحقل. وبالتالي، لا يتم إرسال النموذج إلى الخادم. لذا لا يتغير شيء على الصفحة. إن عدم تغير محتوى [lblInfo2] هو ما يوضح لنا عدم وجود POST. وإذا كان هناك واحد، فسيكتشف الخادم أن محتوى [TextBox2] قد تغير ويجب أن يعكس هذا الأمر في [lblInfo2].

نستخلص من هذا المثال أنه لا فائدة من نقل الخاصية [AutoPostBack] من [TextBox] إلى [true]. فهذا يتسبب في تبادل بيانات غير ضروري بين العميل والخادم في معظم الأحيان.

7.6.3. دور الحقل __VIEWSTATE

لقد رأينا أن الخادم يضع بشكل منهجي حقلًا مخفيًا يسمى __VIEWSTATE في النموذج الذي يقوم بإنشائه. وقد ذكرنا أن هذا الحقل يمثل حالة النموذج وأنه إذا تم إرجاع هذا الحقل المخفي إليه، فإن الخادم قادر على إعادة تكوين القيمة السابقة للنموذج. حالة النموذج هي مجموع حالات مكوناته. لكل مكون خاصية [EnableViewState] ذات قيمة منطقية تشير إلى ما إذا كان يجب وضع حالة المكون في الحقل المخفي [__VIEWSATE] أم لا. بشكل افتراضي، تكون قيمة هذه الخاصية هي [true]، مما يؤدي إلى وضع حالة جميع مكونات النموذج في [__VIEWSTATE]. في بعض الأحيان، قد لا يكون ذلك مرغوبًا.

لنقم ببعض التجارب لفهم دور الخاصية [EnableViewState] بشكل أفضل. لنضبط هذه الخاصية على [false] لحقلَي الإدخال:

...
            <asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True" EnableViewState="False"></asp:TextBox>
...
            <asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged" EnableViewState="False"></asp:TextBox>
...

لنقم الآن بتشغيل التطبيق ونكتب نصًا أول في الحقل رقم 1 قبل الانتقال إلى الحقل رقم 2. عندئذٍ يتم إرسال طلب POST إلى الخادم ونحصل على الرد التالي:

Image

نكتب نصًا في الحقل رقم 2، ونعدل نص الحقل رقم 1 ثم نعود إلى الحقل رقم 2 (بهذا الترتيب). يتم إرسال طلب POST جديد إلى الخادم ونحصل على الرد التالي:

Image

في الوقت الحالي، كل شيء كما كان من قبل. الآن، لنقوم بتعديل محتوى الحقل رقم 1 ثم ننتقل إلى الحقل رقم 2. يتم إرسال طلب جديد POST. رد الخادم هو التالي:

Image

هذه المرة، هناك تغيير. اكتشف الخادم حدث [TextChanged] في الحقل رقم 2 لأن وقت [lblInfo2] قد تم تعديله. لكن لم يكن هناك أي تغيير. ويُفسر ذلك بخاصية [EnableViewState=false] الخاصة بـ [TextBox2]. فهي تجعل الخادم لا يضع في الحقل [__VIEWSTATE] في النموذج الحالة السابقة لـ [TextBox2]. وهذا يعادل تعيين السلسلة الفارغة كحالة سابقة. عندما حدث POST بسبب تغيير محتوى [TextBox1]، قارن الخادم القيمة الحالية لـ [TextBox2]، والتي كانت [deux]، بقيمتها السابقة (السلسلة الفارغة). وخلص إلى أن قيمة [TextBox2] قد تغيرت، وقام بإنشاء الحدث [TextChanged] لـ [TextBox2]. يمكننا التحقق من صحة هذا الأداء عن طريق وضع السلسلة الفارغة في [TextBox2]. وفقًا لما تم شرحه للتو، لا ينبغي أن يقوم الخادم بإنشاء الحدث [TextChanged] لـ [TextBox2]. دعونا نجرب:

Image

هذا ما حدث بالفعل. يُظهر وقت ومحتوى [lblInfo2] أن الإجراء [TextBox2_TextChanged] لم يتم تنفيذه. وبناءً على ذلك، دعونا نفحص الخاصية [EnableViewState] للمكونات الأربعة للنموذج:

TextBox1
نرغب في الحفاظ على حالة هذا المكون حتى يعرف الخادم ما إذا كان قد تغير أم لا
TextBox2
نفس الشيء
lblInfo1
لا نرغب في الحفاظ على حالة هذا المكون. نريد إعادة حساب النص عند كل POST جديد. إذا لم يتم إعادة حسابه، فيجب أن يكون فارغًا. يتم تحقيق كل ذلك باستخدام [EnableViewState=false]
lblInfo2 
نفس الشيء

تصبح صفحة العرض الخاصة بنا كما يلي:

...
            <asp:TextBox id="TextBox1" runat="server" OnTextChanged="TextBox1_TextChanged" AutoPostBack="True"></asp:TextBox>
...
            <asp:TextBox id="TextBox2" runat="server" OnTextChanged="TextBox2_TextChanged"></asp:TextBox>
....
            <asp:Label id="lblInfo1" runat="server" enableviewstate="False"></asp:Label>
...
            <asp:Label id="lblInfo2" runat="server" enableviewstate="False"></asp:Label>
...

لنكرر نفس سلسلة الاختبارات التي أجريناها سابقًا. حيث حصلنا على الشاشة

version 1

Image

نحصل الآن على:

version 2

Image

في هذه الخطوة، قمنا بتغيير محتوى الحقل 1 دون تغيير محتوى الحقل 2، مما أدى إلى عدم تنفيذ الإجراء [TextBox2_TextChanged] على جانب الخادم، مما يعني أن الحقل [lblInfo2] لم يتلقَ قيمة جديدة. لذلك يتم عرضه بقيمته السابقة. في الإصدار 1 [EnableViewState=true]، كانت هذه القيمة السابقة هي القيمة التي تم إدخالها. في الإصدار 2 [EnableViewState=false]، تكون هذه القيمة السابقة هي السلسلة الفارغة.

في بعض الأحيان، لا يكون من الضروري الاحتفاظ بالحالة السابقة للمكونات. بدلاً من وضع [EnableViewState=false] لكل منها، يمكننا تحديد أن الصفحة لا يجب أن تحتفظ بحالتها. يتم ذلك في التوجيه [Page] في كود العرض:

<%@ Page Language="VB" EnableViewState="False" %>

في هذه الحالة، بغض النظر عن قيمة خاصية [EnableViewState]، لا يتم وضع حالة المكون في الحقل المخفي [__VIEWSTATE]. ويحدث كل شيء كما لو كانت حالته السابقة هي السلسلة الفارغة.

لنستخدم الآن العميل [curl] لتسليط الضوء على آليات أخرى. نطلب أولاً عنوان URL [http://localhost/form4.aspx]:

dos>curl --include --url http://localhost/form4.aspx

HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Sun, 04 Apr 2004 17:51:14 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1077
Connection: Close


<html>
<head>
    <title>asp:textbox</title>
</head>
<body>
    <form name="_ctl0" method="post" action="form4.aspx" id="_ctl0">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u+/OrTD" />

<script language="javascript">
<!--
    function __doPostBack(eventTarget, eventArgument) {
        var theform = document._ctl0;
        theform.__EVENTTARGET.value = eventTarget;
        theform.__EVENTARGUMENT.value = eventArgument;
        theform.submit();
    }
// -->
</script>

        <p>
            Texte 1 :
            <input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1"></span>
        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>
    </form>
</body>
</html>

نتلقى من الخادم الرمز HTML من [form4.aspx]. وهو لا يختلف عن الرمز الذي تلقّاه المتصفح. ولنتذكر هنا الطلب الذي أرسله المتصفح عند إرسال النموذج:

POST /form4.aspx HTTP/1.1
Connection: keep-alive
...
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

__EVENTTARGET=TextBox1&__EVENTARGUMENT=&__VIEWSTATE=dDwtMTY4MDc0MTUxOTs7PoqpeSYSCX7lCiWZvw5p7u%2B%2FOrTD&TextBox1=premier+texte&TextBox2=

لنقم بنفس العملية POST ولكن دون إرسال الحقل [__VIEWSTATE]:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=النص الأول --data TextBox2=

...................
        <p>
            Texte 1 :
            <input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1">19:57:48: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>
..............

تجدر الإشارة إلى النقاط التالية:

  • اكتشف الخادم حدث [TextChanged] على [TextBox1] لأنه أنشأ النص [lblInfo1]. ولم يعيقه غياب [__VIEWSTATE]. في حالة عدم وجوده، يفترض أن القيمة السابقة لحقل الإدخال هي سلسلة فارغة.
  • وقد تمكن من إعادة النص الذي تم نشره لـ [TextBox1] في السمة [value] لعلامة [TextBox1] حتى يظهر الحقل [TextBox1] مرة أخرى بالقيمة التي تم إدخالها. وللقيام بذلك، لا يحتاج إلى [__VIEWSTATE] بل يحتاج فقط إلى القيمة التي تم إرسالها لـ [TextBox1]

الآن، لنكرر نفس الاستعلام دون تغيير أي شيء. نحصل على الرد الجديد التالي:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox1=النص الأول --data TextBox2=


        <p>
            Texte 1 :
            <input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1">20:05:47: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>

في حالة عدم وجود [__VIEWSTATE]، لم يتمكن الخادم من رؤية أن قيمة الحقل [TextBox1] لم تتغير. لذا، يتصرف الخادم كما لو أن القيمة السابقة كانت سلسلة فارغة. لذلك قام هنا بإنشاء الحدث [TextChanged] على [TextBox1]. لنعيد تشغيل نفس الاستعلام مرة أخرى، ولكن هذه المرة مع الحقل [TextBox1] فارغًا و [TextBox2] غير فارغ:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET=TextBox1 --data __EVENTARGUMENT= --data TextBox2=second+texte --data TextBox1=
......
        <p>
            Texte 1 :
            <input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" value="second texte" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1"></span>
        </p>
        <p>
            <span id="lblInfo2">20:11:54: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
        </p>
......

في حالة عدم وجود [__VIEWSTATE]، تم اعتبار القيمة السابقة لـ [TextBox1] سلسلة فارغة. ونظرًا لأن القيمة التي تم إرسالها لـ [TextBox1] هي أيضًا سلسلة فارغة، لم يتم إنشاء الحدث [TextChanged] على [TextBox1]. لم يتم تنفيذ الإجراء [TextBox1_TextChanged]، وبالتالي لم يتلق الحقل [lblInfo1] أي قيمة جديدة. من المعروف أنه في هذه الحالة، يحتفظ المكون بقيمته القديمة. ولكن هنا، ليس هذا هو الحال، فقد فقدت [lblInfo1] قيمتها السابقة. وذلك لأن هذه القيمة يتم البحث عنها في [__VIEWSTATE]. ونظرًا لعدم وجود هذا الحقل، تم تعيين السلسلة الفارغة إلى [lblInfo1]. بالنسبة لـ [TextBox2]، قارن الخادم قيمته المنشورة [second texte] بقيمته السابقة. في غياب [__VIEWSTATE]، تكون هذه القيمة السابقة مساوية للسلسلة الفارغة. ونظرًا لأن القيمة المنشورة لـ [TextBox2] تختلف عن السلسلة الفارغة، فقد تم إنشاء الحدث [TextChanged] على [TextBox2]. تم تنفيذ الإجراء [TextBox2_TextChanged] وتلقى الحقل [lblInfo2] قيمة جديدة.

قد يتساءل المرء عما إذا كانت المعلمات [__EVENTTARGET] و [__EVENTARGUMENT] مفيدة بالفعل. عند عدم إرسال هذه المعلمات، لن يعرف الخادم الحدث الذي تسبب في [submit]. لنجرب:

dos>curl --include --url http://localhost/form4.aspx --data TextBox2=النص+الثاني --data TextBox1=النص+الأول
..............................
        <p>
            Texte 1 :
            <input name="TextBox1" type="text" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1"></span>
        </p>
        <p>
            <span id="lblInfo2"></span>
        </p>
    </form>
.....................

نلاحظ أنه لم تتم معالجة أي حدث [TextChanged]. علاوة على ذلك، لا تستعيد الحقول التي تم إرسالها [TextBox1] و [TextBox2] قيمها التي تم إرسالها. في الواقع، يبدو الأمر كما لو تم إجراء GET. تعود الأمور إلى طبيعتها إذا كان الحقل [__EVENTTARGET] موجودًا في الحقول المنشورة، حتى لو لم يكن له قيمة:

dos>curl --include --url http://localhost/form4.aspx --data __EVENTTARGET= --data TextBox2=النص+الثاني --data TextBox1=النص+الأول
.......
        <p>
            Texte 1 :
            <input name="TextBox1" type="text" value="premier texte" id="TextBox1" onchange="__doPostBack('TextBox1','')" language="javascript" />
        </p>
        <p>
            Texte 2 :
            <input name="TextBox2" type="text" value="second texte" id="TextBox2" />
        </p>
        <p>
            <span id="lblInfo1">20:34:14: evt [TextChanged] sur [TextBox1]. Texte 1=[premier texte]</span>
        </p>
        <p>
            <span id="lblInfo2">20:34:14: evt [TextChanged] sur [TextBox2]. Texte 2=[second texte]</span>
        </p>
........

7.6.4. خصائص أخرى لمكون TextBox

يسمح مكون الخادم [TextBox] أيضًا بإنشاء العلامات <input type="password"..> و <texarea>..</textareac.a.d. العلامات التي تتوافق على التوالي مع حقل إدخال محمي وحقل إدخال متعدد الأسطر. يتم التحكم في هذا الإنشاء بواسطة الخاصية [TextMode] للمكون [TextBox]. ولها ثلاث قيم ممكنة:

قيمة
العلامة HTML التي تم إنشاؤها
SingleLine
<input type="text" ...>
MultiLine
<textarea>...</textarea>
Password
<input type="password ...>

نحن ندرس استخدام هذه الخصائص باستخدام المثال التالي [form5.aspx]:

Image

رقم
الاسم
النوع
الخصائص
الدور
1
btnAjouter
زر
 
زر [submit] - يُستخدم لإضافة محتوى [TextBox1] إلى محتوى [TextBox2]
2
TextBox1
TextBox
TextMode=Password
Text=
حقل إدخال محمي
3
TextBox2
TextBox
TextMode=متعدد الأسطر
Text=
يجمع الإدخالات التي تمت في [TextBox1]

يتم تعيين الخاصية [EnableViewState] للصفحة إلى [false]. على جانب الخادم، نقوم بإدارة حدث النقر على الزر [btnAjouter]:

Sub btnAjouter_Click(sender As Object, e As EventArgs)
  ' يتم إضافة محتوى [textBox1] إلى محتوى [TextBox2]
  textbox2.text=textbox2.text + textbox1.text+controlchars.crlf
End Sub

لفهم هذا الرمز، يجب تذكر طريقة معالجة POST لنموذج ما. يتم تنفيذ الإجراءات [Page_Init] و [Page_Load] أولاً. ثم تأتي جميع إجراءات الأحداث المخزنة مؤقتًا. وأخيرًا، يتم تنفيذ الإجراء الذي يدير الحدث الذي تسبب في [POST]، وهو هنا الإجراء [btnAjouter_Click]. عند تنفيذ معالجات الأحداث، تكون جميع مكونات الصفحة التي لها قيمة في POST قد أخذت هذه القيمة. أما المكونات الأخرى، فقد استعادت قيمتها السابقة إذا كانت خاصية [EnableViewState] الخاصة بها تساوي [true] أو قيمتها التصميمية إذا كانت خاصية [EnableViewState] الخاصة بها تساوي [false]. هنا، ستصبح قيم الحقول [TextBox1] و [TextBox2] جزءًا من POST الذي أنشأه العميل. وكذلك في الكود السابق، ستكون قيمة [textbox1.text] هي القيمة التي أرسلها العميل، وكذلك بالنسبة لـ [textbox2.text]. تضع الإجراء [btnAjouter_Click] في الحقل [TextBox2] القيمة التي تم إرسالها لـ [TextBox2] مضافًا إليها تلك التي تم إرسالها لـ [TextBox1] مضافًا إليها علامة نهاية السطر [ControlChars.CrLf] المحددة في مساحة الأسماء [Microsoft.VisualBasic]. لا يلزم استيراد هذه المساحة، حيث يقوم خادم الويب باستيرادها افتراضيًا.

الرمز النهائي لـ [form5.aspx] هو التالي:

<%@ Page Language="VB" EnableViewState="False" %>
<script runat="server">

    Sub btnAjouter_Click(sender As Object, e As EventArgs)
      ' يتم إضافة محتوى [textBox1] إلى محتوى [TextBox2]
      textbox2.text+=textbox1.text+controlchars.crlf
    End Sub

</script>
<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            <asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter" EnableViewState="False"></asp:Button>
            <asp:TextBox id="TextBox1" runat="server" TextMode="Password" Width="353px" EnableViewState="False"></asp:TextBox>
        </p>
        <p>
            <asp:TextBox id="TextBox2" runat="server" TextMode="MultiLine" Width="419px" Height="121px" EnableViewState="False"></asp:TextBox>
        </p>
    </form>
</body>
</html>

أعلاه بقليل، تم عرض لقطة شاشة لعملية تنفيذ.

7.7. المكون DropDownList

تسمح العلامة <asp:DropDownList> بإدراج قائمة منسدلة في كود عرض الصفحة. نقوم بإنشاء صفحة [form6.aspx] للحصول على العرض التالي:

Image

رقم
الاسم
النوع
الخصائص
الدور
1
DropDownList1
DropDownList
AutoPostback=true
EnableViewState=true
قائمة منسدلة
2
lblInfo
التسمية
EnableViewState=false
رسالة إعلامية

رمز العرض الذي تم إنشاؤه هو التالي:

Page Language="VB" %>
<script runat="server">

</script>
<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            <asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
        </p>
        <p>
            <asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
        </p>
    </form>
</body>
</html>

في الوقت الحالي، لا تحتوي القائمة المنسدلة على أي عناصر. سنقوم بملئها في الإجراء [Page_Load]. وللقيام بذلك، يجب أن نعرف بعض خصائص وأساليب الفئة [DropDownList]:

Items
مجموعة من النوع [ListItemCollection] لعناصر القائمة المنسدلة. أعضاء هذه المجموعة من النوع [ListItem].
Items.Count
عدد عناصر المجموعة [Items]
Items(i)
العنصر رقم i من القائمة - من النوع [ListItem]
Items.Add
لإضافة عنصر جديد [ListItem] إلى المجموعة [Items]
Items.Clear
لحذف جميع العناصر من المجموعة [Items]
Items.RemoveAt(i)
لحذف العنصر رقم i من المجموعة [Items]
SelectedItem
العنصر الأول [ListItem] من المجموعة [Items] الذي تكون خاصيته [Selected] صحيحة
SelectedIndex
رقم العنصر [SelectedItem] في المجموعة [Items]

العناصر في المجموعة [Items] من الفئة [DropDownList] هي من النوع [ListItem]. يولد كل عنصر [ListItem] علامة HTML <option>:

<option value="V" [selected="selected"]>T</option>

نصف بعض خصائص وأساليب الفئة [ListItem]:

ListItem(String texte, String value)
المنشئ - ينشئ عنصر [ListItem] بخصائص [texte] و [value]. سيؤدي عنصر ListItem(T,V) إلى إنشاء العلامة HTML <option value="V">T</option>. وبالتالي، تسمح الفئة [ListITem] بوصف عناصر قائمة HTML
Selected
من نوع بولياني. إذا كان صحيحًا، فإن الخيار المقابل في القائمة HTML سيحمل السمة [selected="selected"]. تشير هذه السمة إلى المتصفح بأن العنصر المقابل يجب أن يظهر محددًا في القائمة HTML
Text
النص T للخيار HTML <option value="V" [selected="selected"]>T</option>
Value
القيمة V للسمة [Value] للخيار HTML <option value="V" [selected="selected"]>T</option>

لدينا معلومات كافية لكتابة رمز ملء القائمة المنسدلة [DropDownList1] في الإجراء [Page_Load] للصفحة:

    Sub Page_Load(sender As Object, e As EventArgs)
        ' يتم ملء القائمة المنسدلة إذا كانت هذه هي المرة الأولى التي يتم فيها استدعاء الدالة
        if not IsPostBack then
          dim valeurs() as String = {"1","2","3","4"}
            dim textes() as  String = {"un","deux","trois","quatre"}

            dim i as integer
            for i=0 to valeurs.length-1
                DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
            next
        end if
    end sub

بعد تهيئة المكون [DropDownList1] بهذه الطريقة، ستكون ترجمته HTML كما يلي:

<select name="DropDownList1" id="DropDownList1" onchange="__doPostBack('DropDownList1','')" language="javascript">

    <option value="1">un</option>
    <option value="2">deux</option>
    <option value="3">trois</option>
    <option value="4">quatre</option>

</select>

نحن نعلم أن الإجراء [Page_Load] يتم تنفيذه عند كل تشغيل للصفحة [form6.aspx]. يتم استدعاء هذه الصفحة للمرة الأولى بواسطة GET، ثم بواسطة POST في كل مرة يختار فيها المستخدم عنصرًا جديدًا من القائمة المنسدلة. هل يجب تنفيذ كود ملء هذه القائمة في [Page_Load] في كل مرة؟ تعتمد الإجابة على السمة [EnableViewState] للمكون [DropDownList1]. إذا كان هذا السمة صحيحًا، فمن المعروف أن حالة المكون [DropDownList1] ستُحفظ عبر الطلبات في الحقل المخفي [__VIEWSTATE]. تتضمن هذه الحالة أمرين:

  • قائمة بجميع قيم القائمة المنسدلة
  • قيمة العنصر المحدد في هذه القائمة

يبدو من المغري إذن تعيين الخاصية [EnableViewState] للمكون [DropDownList1] إلى [true] حتى لا نضطر إلى إعادة حساب القيم التي يجب وضعها في القائمة. المشكلة هنا هي أن الإجراء [Page_Load] يتم تنفيذه في كل مرة يتم فيها طلب الصفحة [form6.aspx]، وبالتالي سيتم حساب هذه القيم على أي حال. يحتوي الكائن [Page]، الذي يمثل [form6.aspx] مثيلًا له، على سمة [IsPostBack] ذات قيمة منطقية. إذا كانت هذه السمة صحيحة، فهذا يعني أن الصفحة تم استدعاؤها بواسطة POST. وإذا كانت القيمة "خطأ"، فهذا يعني أن الصفحة تم استدعاؤها بواسطة GET. في نظامنا ذي الاتجاهين بين العميل والخادم، يطلب العميل دائمًا نفس الصفحة [form6.aspx] من الخادم. في المرة الأولى، يطلبها باستخدام GET، وفي المرات التالية باستخدام POST. نستنتج من ذلك أن الخاصية [IsPostBack] يمكن أن تساعدنا في اكتشاف الاستدعاء الأول GET من العميل. لا يتم إنشاء قيم القائمة المنسدلة إلا عند هذا الطلب الأول. في الطلبات التالية، سيتم إنشاء هذه القيم بواسطة آلية [VIEWSTATE]. في حالات أخرى، قد يختلف محتوى القائمة من طلب لآخر ويجب إعادة حسابه في كل طلب. في هذه الحالة، سيتم تعيين السمة [EnableViewState] الخاصة بها إلى [false] لتجنب الحساب المزدوج غير الضروري لمحتوى القائمة، إلا إذا كانت هناك حاجة لمعرفة العناصر المحددة مسبقًا في القائمة، حيث يتم الاحتفاظ بهذه المعلومات في [VIEWSTATE].

تم تعيين السمة [AutoPostBack] للقائمة [DropDownList1] على "صحيح". وهذا يعني أن المتصفح سيرسل النموذج بمجرد اكتشافه لحدث "تغيير العنصر المحدد" في القائمة المنسدلة. بدوره، سيكتشف الخادم، بفضل [VIEWSTATE] والقيم المرسلة، أن العنصر المحدد في المكون [DropDownList1] قد تغير. ثم سيطلق الحدث [SelectedIndexChanged] على هذا المكون. وسنتعامل معه بالإجراء التالي:

    Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
      ' تغيير التحديد
      lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
        " valeur=" + dropdownlist1.selecteditem.value + _
        " index="+ dropdownlist1.selectedindex.tostring
    End Sub

عند تنفيذ هذا الإجراء، استعاد الكائن [DropDownList1] عناصره من النوع [ListItem] بفضل [VIEWSTATE]. علاوة على ذلك، فإن أحد هذه العناصر من النوع [ListItem] له السمة [Selected] بقيمة "صحيح"، وهي السمة التي تم إرسال قيمتها بواسطة المتصفح. يمكن الوصول إلى هذا العنصر بعدة طرق:

DropDownList1.SelectedItem
هو العنصر الأول [ListItem] في القائمة الذي يكون سمة [Selected] الخاصة به "صحيح"
DropDownList1.SelectedItem.Text
يتوافق مع الجزء [texte] من العلامة HTML للعنصر <option value="...">نص</option> الذي اختاره المستخدم
DropDownList1.SelectedItem.Value
يتوافق مع الجزء [value] من العلامة HTML للعنصر <option value="...">نص</option> الذي اختاره المستخدم
DropdDownList1.SelectedIndex
رقم في المجموعة [DropDownList1.Items] للعنصر الأول [ListItem] الذي يكون سمة [Selected] الخاصة به صحيحة

الرمز النهائي لـ [form6.aspx] هو التالي:

<%@ Page Language="VB" %>
<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs)
        ' يتم ملء القائمة المنسدلة إذا كانت هذه هي المرة الأولى التي يتم فيها استدعاء الوظيفة
        if not IsPostBack then
          dim valeurs() as String = {"1","2","3","4"}
            dim textes() as  String = {"un","deux","trois","quatre"}

            dim i as integer
            for i=0 to valeurs.length-1
                DropDownList1.Items.Add(new ListItem(textes(i),valeurs(i)))
            next
        end if
    end sub

    Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
      ' تغيير الاختيار
      lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
        " valeur=" + dropdownlist1.selecteditem.value + _
        " index="+ dropdownlist1.selectedindex.tostring
    End Sub

</script>
<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            <asp:DropDownList id="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"></asp:DropDownList>
        </p>
        <p>
            <asp:Label id="lblInfo" runat="server" enableviewstate="False"></asp:Label>
        </p>
    </form>
</body>
</html>

7.8. المكون ListBox

تسمح العلامة <asp:ListBox> بإدراج قائمة في كود عرض الصفحة. نقوم بإنشاء [form7.aspx] للحصول على العرض التالي:

Image

رقم
الاسم
النوع
الخصائص
الدور
1
txtSaisie
TextBox
EnableViewState=false
حقل الإدخال
2
btnAjouter
زر
 
زر [submit] الذي ينقل إلى القائمة 1 محتوى txtSaisie إذا كان غير فارغ.
3
ListBox1
ListBox
EnableViewState=true
SelectionMode=Single
قائمة القيم ذات الاختيار الفردي
4
ListBox2
ListBox
EnableViewState=true
SelectionMode=Multiple
قائمة القيم المتعددة
5
btn1vers2
زر
 
زر [submit] الذي ينقل العنصر المحدد من [liste 1] إلى [liste 2]
6
btn2vers1
زر
 
زر [submit] الذي ينقل العناصر المحددة من [liste 2] إلى [liste 1]

رمز العرض الذي تم إنشاؤه هو التالي:

<%@ Page Language="VB" %>
<script runat="server">
</script>
<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            Tapez un texte pour l'inclure dans Liste 1 :
            <asp:TextBox id="txtSaisie" runat="server" EnableViewState="False"></asp:TextBox>
        </p>
        <p>
            <asp:Button id="btnAjouter" onclick="btnAjouter_Click" runat="server" Text="Ajouter"></asp:Button>
        </p>
        <p>
            <table>
                <tbody>
                    <tr>
                        <td>
                            <p align="center">
                                Liste 1
                            </p>
                        </td>
                        <td>
                        </td>
                        <td>
                            <p align="center">
                                Liste 2
                            </p>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <asp:ListBox id="ListBox1" runat="server"></asp:ListBox>
                        </td>
                        <td>
                            <p>
                                <asp:Button id="btn1vers2" onclick="btn1vers2_Click" runat="server" Text="-->"></asp:Button>
                            </p>
                            <p>
                                <asp:Button id="btn2vers1" onclick="btn2vers1_Click" runat="server" Text="<--"></asp:Button>
                            </p>
                        </td>
                        <td>
                            <p>
                                <asp:ListBox id="ListBox2" runat="server" SelectionMode="Multiple"></asp:ListBox>
                            </p>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <p align="center">
                                <asp:Button id="btnRaz1" onclick="btnRaz1_Click" runat="server" Text="Effacer"></asp:Button>
                            </p>
                        </td>
                        <td>
                        </td>
                        <td>
                            <p align="center">
                                <asp:Button id="btnRaz2" onclick="btnRaz2_Click" runat="server" Text="Effacer"></asp:Button>
                            </p>
                        </td>
                    </tr>
                </tbody>
            </table>
        </p>
    </form>
</body>
</html>

الفئة [ListBox] مشتقة من نفس الفئة [ListControl] التي تنتمي إليها الفئة [DropDownList] التي تمت دراستها سابقًا. نجد فيها جميع الخصائص والأساليب التي تمت دراستها في [DropDownList] لأنها كانت في الواقع تنتمي إلى [ListControl]. تظهر خاصية جديدة:

SelectionMode
تحدد وضع التحديد لقائمة HTML <select> التي سيتم إنشاؤها من المكون. إذا كانت SelectionMode=Single، فيمكن تحديد عنصر واحد فقط. إذا كانت SelectionMode=Multiple، فيمكن تحديد عدة عناصر. لهذا الغرض، سيتم إنشاء السمة [multiple="multiple"] في العلامة <select> الخاصة بقائمة HTML.

دعونا نعالج الأحداث. سيتم معالجة النقر على الزر [Ajouter] بواسطة الإجراء [btnAjouter_Click] التالي:

    Sub btnAjouter_Click(sender As Object, e As EventArgs)
      ' إضافة إلى القائمة 1
      dim texte as string=txtSaisie.text.trim
      if texte<> "" then ListBox1.Items.Add(New ListItem(texte))
      ' مسح txtSaisie
      txtSaisie.text=""
    End Sub

إذا لم يكن النص الذي تم إدخاله في [txtSaisie] عبارة عن سلسلة فارغة أو بيضاء، يتم إضافة عنصر جديد إلى قائمة [ListBox1]. نحن نعلم أنه يتعين علينا إضافة عنصر من النوع [ListItem]. في السابق، استخدمنا المنشئ [ListItem(T as String, V as String)] للقيام بمهمة مماثلة. ينتج عن هذا العنصر العلامة HTML [<option value="V">T</option>]. هنا، نستخدم المنشئ [ListItem(T as String)] الذي ينتج العلامة HTML [<option value="T">T</option>]، c.a.d. يتم أخذ النص [T] الخاص بالخيار لتشكيل قيمة الخيار. بمجرد إضافة محتوى [txtSaisie] إلى القائمة [ListBox1]، يتم إفراغ الحقل [txTSaisie].

سيتم معالجة النقرات على الأزرار [Effacer] من خلال الإجراءات التالية:

    Sub btnRaz1_Click(sender As Object, e As EventArgs)
      ' مسح القائمة 1
      ListBox1.Items.Clear
    End Sub

    Sub btnRaz2_Click(sender As Object, e As EventArgs)
      ' مسح القائمة 2
      ListBox2.Items.Clear
    End Sub

يتم معالجة النقرات على أزرار النقل بين القوائم من خلال الإجراءات التالية:

    Sub btn1vers2_Click(sender As Object, e As EventArgs)
      ' نقل العنصر المحدد من القائمة 1 إلى القائمة 2
      transfert(ListBox1,ListBox2)
    End Sub

    Sub btn2vers1_Click(sender As Object, e As EventArgs)
      ' نقل العنصر المحدد من القائمة 2 إلى القائمة 1
      transfert(ListBox2,ListBox1)
    End Sub

    sub transfert(l1 as listbox, l2 as listbox)
      ' نقل العناصر المحددة من l1 إلى l2
       ' هل هناك شيء يجب فعله؟
      if l1.selectedindex=-1 then return
      dim i as integer
      ' نبدأ بالنهاية
      for i=l1.items.count-1 to 0 step -1
        ' محدد؟
        if l1.items(i).selected then
            ' لم يعد محددًا
            l1.items(i).selected=false
          ' نقل إلى l2
          l2.items.add(l1.items(i))
          ' حذف في l1
          l1.items.removeAt(i)
        end if
      next
    end sub

نظرًا لأن الزرين يؤديان نفس الوظيفة، وهي نقل العناصر من قائمة إلى أخرى، يمكننا اختصار ذلك إلى إجراء نقل واحد له معلمتان:

  • l1 من النوع [ListBox] وهي القائمة المصدر
  • l2 من النوع [ListBox] وهي القائمة الوجهة

أولاً، نتحقق مما إذا كان هناك عنصر واحد على الأقل محدد في القائمة l1، وإلا فلا يوجد ما يمكن فعله. وللقيام بذلك، نفحص الخاصية [l1.selectedindex] التي تمثل رقم العنصر الأول المحدد في القائمة. إذا لم يكن هناك أي عنصر، تكون قيمتها -1. إذا كان هناك عنصر واحد على الأقل محدد في l1، يتم إجراء النقل إلى l2. للقيام بذلك، يتم استعراض قائمة عناصر l1 بالكامل والتحقق من كل عنصر لمعرفة ما إذا كانت سمة [selected] الخاصة به صحيحة. إذا كان الأمر كذلك، يتم تعيين سمة [selected] إلى [false]، ثم يتم نسخها إلى القائمة l2 وأخيرًا يتم حذفها من القائمة l1. يؤدي هذا الحذف إلى إعادة ترقيم عناصر القائمة l1. لهذا السبب يتم تصفح قائمة عناصر l1 في الاتجاه المعاكس. إذا تم تصفحها في الاتجاه الصحيح وحذف العنصر رقم 10، يصبح العنصر رقم 11 هو رقم 10، ويصبح رقم 12 هو العنصر رقم 11. بعد معالجة العنصر رقم 10، ستعالج حلقتنا في الاتجاه الصحيح العنصر رقم 11 الذي، وفقًا لما أوضحناه للتو، هو العنصر رقم 12 السابق. أما العنصر الذي كان يحمل الرقم 11 ويحمل الآن الرقم 10، فيفوتنا. وبتصفح عناصر القائمة l1 في الاتجاه المعاكس، نتجنب هذه المشكلة.

7.9. المكونات CheckBox و RadioButton

تسمح العلامات <asp:RadioButton> و <asp:CheckBox> بإدراج زر اختيار ومربع اختيار على التوالي في كود عرض الصفحة. نقوم بإنشاء صفحة [form8.aspx] للحصول على العرض التالي:

Image

رقم
الاسم
النوع
الخصائص
الدور
1
RadioButton1
RadioButton2
RadioButton3
RadioButton
RadioButton1.Checked=true
RadioButton1.Text=1
RadioButton2.Checked=false
RadioButton2.Text=2
RadioButton3.Checked=false
RadioButton3.Text=3
للأزرار الثلاثة: GroupName=radio
أزرار الاختيار
2
CheckBoxA
CheckBoxB
CheckBoxC
CheckBox
Checked=false للجميع
CheckBoxA.Text=A
CheckBoxB.Text=B CheckBoxC.Text=C
مربعات الاختيار
3
btnEnvoyer
زر
 
زر [submit]
4
lstInfos
ListBox
 
قائمة المعلومات

لكي يعالج المتصفح الأزرار الثلاثة على أنها أزرار حصرية لبعضها البعض، يجب تجميعها في مجموعة أزرار. ويتم ذلك باستخدام السمة [GroupName] من الفئة [RadioButton]. لا يلزم الحفاظ على حالة الصفحة في هذا التطبيق. لذلك نضع السمة [EnableViewState="false"] في الصفحة. رمز العرض هو كما يلي:

<html>
<head>
</head>
<body>
    <form id="frmControls" runat="server">
        <h3>Cases à cocher 
        </h3>
        <p>
            <asp:RadioButton id="RadioButton1" runat="server" Checked="True" EnableViewState="False" GroupName="radio" Text="1"></asp:RadioButton>
            <asp:RadioButton id="RadioButton2" runat="server" EnableViewState="False" GroupName="radio" Text="2"></asp:RadioButton>
            &nbsp;<asp:RadioButton id="RadioButton3" runat="server" EnableViewState="False" GroupName="radio" Text="3"></asp:RadioButton>
        </p>
        <p>
            <asp:CheckBox id="CheckBoxA" runat="server" EnableViewState="False" Text="A"></asp:CheckBox>
            <asp:CheckBox id="CheckBoxB" runat="server" EnableViewState="False" Text="B"></asp:CheckBox>
            <asp:CheckBox id="CheckBoxC" runat="server" EnableViewState="False" Text="C"></asp:CheckBox>
        </p>
        <p>
            <asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
            <asp:Button id="btnTree" onclick="btnTree_Click" runat="server" Text="Contrôles"></asp:Button>
        </p>
        <p>
            <asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6" Height="131px"></asp:ListBox>
        </p>
    </form>
</body>
</html>

يجب علينا كتابة الإجراء [btnEnvoyer_Click] لمعالجة الحدث [Click] على هذا الزر. يتم تحديد حالة زر الاختيار أو مربع الاختيار من خلال سمة [Checked]، وهي قيمة منطقية تكون صحيحة إذا كان المربع محددًا، وخاطئة في حالة عدم تحديده. لذلك يكفي أن نكتب في القائمة [lstInfos] قيمة السمة [Checked] للأزرار الستة ومربعات الاختيار. وبما أنه لا توجد صعوبة خاصة في ذلك، فإننا نبتكر قليلاً:

<script runat="server">

    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' نضع المعلومات في مربع القائمة
      for each c as control in FindControl("frmControls").controls
          ' هل العنصر مستمد من CheckBox
          if TypeOf(c) is CheckBox then
              lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
          end if
      next
    End Sub

</script>

يمكن النظر إلى الصفحة على أنها بنية شجرية من عناصر التحكم. في مثالنا، تحتوي صفحتنا على نصوص وعناصر تحكم خادم. يُنظر إلى النصوص على أنها عنصر تحكم خاص يُسمى [LiteralControl]. أي نص، حتى لو كان مجرد سلسلة من المسافات بين عنصرين من عناصر التحكم، يؤدي إلى إنشاء هذا العنصر. يحتوي كل عنصر تحكم على سمة ID تحدد هويته. السمة ID هي التي تظهر في العلامات:

            <asp:CheckBox id="CheckBoxA" runat="server" ></asp:CheckBox>

إذا تجاهلنا عناصر التحكم [LiteralControl]، فإن الصفحة قيد الدراسة تحتوي على عناصر التحكم التالية:

  • [HtmlForm] وهو النموذج [ID=frmControls]. وهذا بدوره عبارة عن حاوية لعناصر التحكم. ويحتوي على عناصر التحكم التالية:

-- [ID=RadioButton1] من النوع [RadioButton]

-- [ID=RadioButton2] من النوع [RadioButton]

-- [ID=RadioButton3] من النوع [RadioButton]

-- [ID=CheckBoxA] من النوع [CheckBox]

-- [ID= CheckBoxA] من النوع [CheckBox]

-- [ID= CheckBoxA] من النوع [CheckBox]

-- [ID=btnEnvoyer] من النوع [Button]

يحتوي عنصر التحكم على الخصائص التالية:

[Control].Controls
تُرجع مجموعة عناصر التحكم الفرعية لـ [Control] إن وجدت
[Control].FindControl(ID)
يعرض عنصر التحكم المحدد بواسطة ID الموجود في جذر شجرة عناصر التحكم الفرعية لـ [Control]. في المثال أعلاه: Page.FindControl("frmControls") يشير إلى الحاوية [HtmlForm]. للوصول إلى زر الاختيار [RadioButton1]، يجب كتابة
Page.FindControl("frmControls").FindControl("RadioButton1")
[Control].ID
معرف [Control]

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

    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' يتم وضع المعلومات في مربع القائمة
      for each c as control in FindControl("frmControls").controls
          ' هل العنصر مشتق من CheckBox؟
          if TypeOf(c) is CheckBox then
              lstInfos.Items.Add(c.ID + " : " + Ctype(c,CheckBox).Checked.ToString)
          end if
      next
    End Sub

نريد عرض حالة أزرار الاختيار ومربعات الاختيار الموجودة في النموذج. نقوم بتصفح جميع عناصر التحكم فيه. إذا كان عنصر التحكم الحالي من نوع مشتق من [CheckBox]، فإننا نعرض خاصيته [Checked]. نظرًا لأن الفئة [RadioButton] مشتقة من الفئة [CheckBox]، فإن الاختبار صالح لكلا النوعين من عناصر التحكم. تظهر لقطة الشاشة المعروضة أعلاه مثالاً على التنفيذ.

7.10. المكونات CheckBoxList، RadioButtonList

في بعض الأحيان، نرغب في حث المستخدم على الاختيار بين قيم لا نعرفها في وقت تصميم الصفحة. تأتي هذه الخيارات من ملف تكوين أو قاعدة بيانات أو غير ذلك، ولا تُعرف إلا في وقت التنفيذ. هناك حلول لهذه المشكلة وقد صادفناها. تناسب قائمة الاختيار الفردي الحالات التي لا يمكن للمستخدم فيها سوى اختيار خيار واحد، بينما تناسب قائمة الاختيار المتعدد الحالات التي يمكنه فيها اختيار عدة خيارات. من الناحية الجمالية، وإذا لم يكن عدد الخيارات كبيرًا، فقد نرغب في استخدام أزرار الاختيار بدلاً من قائمة الاختيار الفردي أو مربعات الاختيار بدلاً من قائمة الاختيار المتعدد. وهذا ممكن باستخدام المكونات [CheckBoxList] و [RadioButtonList].

تُشتق الفئتان [CheckBoxList] و [RadioButtonList] من نفس الفئة [ListControl] التي تُشتق منها الفئتان [DropDownList] و [ListBox] اللتان تمت دراستهما سابقًا. لذلك سنجد بعض الخصائص والطرق التي تمت دراستها لهذه الفئات، وهي تلك التي كانت في الواقع تابعة لـ [ListControl].

Items
مجموعة من النوع [ListItemCollection] لعناصر القائمة المنسدلة. أعضاء هذه المجموعة من النوع [ListItem].
Items.Count
عدد عناصر المجموعة [Items]
Items(i)
العنصر رقم i من القائمة - من النوع [ListItem]
Items.Add
لإضافة عنصر جديد [ListItem] إلى المجموعة [Items]
Items.Clear
لحذف جميع العناصر من المجموعة [Items]
Items.RemoveAt(i)
لحذف العنصر رقم i من المجموعة [Items]
SelectedItem
العنصر الأول [ListItem] من المجموعة [Items] الذي تكون خاصيته [Selected] صحيحة
SelectedIndex
رقم العنصر السابق في المجموعة [Items]

بعض الخصائص خاصة بالفئتين [CheckBoxList] و [RadioButtonList]:

RepeatDirection
[horizontal] أو [vertical] للقوائم الأفقية أو الرأسية.

العناصر في المجموعة [Items] هي من النوع [ListItem]. سيؤدي كل عنصر من عناصر [ListItem] إلى إنشاء علامة مختلفة حسب ما إذا كان الكائن من نوع [CheckBoxList] أو [RadioButtonList]:

<input type="checkbox" [selected="selected"] value="V">Texte

أو

<input type="radio" [selected="selected"] value="V">Texte

نصف بعض خصائص وأساليب الفئة [ListItem]:

ListItem(String texte, String value)
المنشئ - ينشئ عنصر [ListItem] مع خصائص النص والقيمة. سيؤدي عنصر ListItem(T,V) إلى إنشاء العلامة HTML <input type="checkbox" value="V">T أو <input type="radio" value="V">T حسب الحالة.
Selected
منطقية. إذا كانت صحيحة، فإن الخيار المقابل في القائمة HTML سيحمل السمة [selected="selected"]. تشير هذه السمة إلى المتصفح بأن العنصر المقابل يجب أن يظهر محددًا في القائمة HTML
Text
النص T للخيار HTML <input type=".." value="V" [selected="selected"]>T
Value
قيمة السمة Value للخيار HTML <input type=".." value="V" [selected="selected"]>T

نقترح إنشاء الصفحة التالية:

Image

رقم
الاسم
النوع
الخصائص
الدور
1
RadioButtonList1
RadioButtonList
EnableViewState=true
اتجاه التكرار=أفقي
قائمة أزرار الاختيار
2
CheckBoxList1
CheckBoxList
EnableViewState=true
RepeatDirection=horizontal
قائمة مربعات الاختيار
3
btnEnvoyer
زر
 
زر [submit] الذي يعرض في [4] قائمة العناصر المحددة في القائمتين
4
lstInfos
ListBox
EnableViewState=false
قائمة القيم

رمز عرض الصفحة هو التالي:

<%@ Page Language="VB" autoeventwireup="false" %>
<script runat="server">
...
</script>
<html>
<head>
</head>
<body>
    <form id="frmControls" runat="server">
        <h3>Listes de cases à cocher
        </h3>
        <p>
            <asp:RadioButtonList id="RadioButtonList1" runat="server" RepeatDirection="Horizontal"></asp:RadioButtonList>
        </p>
        <p>
            <asp:CheckBoxList id="CheckBoxList1" runat="server" RepeatDirection="Horizontal"></asp:CheckBoxList>
        </p>
        <p>
            <asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
        </p>
        <p>
            <asp:ListBox id="lstInfos" runat="server" EnableViewState="False" Rows="6"></asp:ListBox>
        </p>
    </form>
</body>
</html>

رمز التحقق هو:

<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs) handles MyBase.Load
        ' يتم ملء القوائم إذا كانت هذه هي المرة الأولى التي يتم فيها استدعاء
        if not IsPostBack then
            ' نصوص لقائمة RadioButton
          dim textesRadio() as String = {"1","2","3","4"}
          ' نصوص القائمة CheckBox
          dim textesCheckBox() as  String = {"un","deux","trois","quatre"}
                 ' ملء قائمة الاختيار
            dim i as integer
            for i=0 to textesRadio.length-1
                RadioButtonList1.Items.Add(new ListItem(textesRadio(i)))
            next
             ' تحديد العنصر رقم 1
            RadioButtonList1.SelectedIndex=1
            ' ملء قائمة مربعات الاختيار
            for i=0 to textesCheckBox.length-1
                CheckBoxList1.Items.Add(new ListItem(textesCheckBox(i)))
            next
        end if
    end sub


    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
      ' نضع المعلومات في مربع القائمة lstinfos
      affiche(RadioButtonList1)
      affiche(CheckBoxList1)
    End Sub

    sub affiche(l1 as ListControl)
      ' عرض قيم العناصر المحددة من l1
       ' هل هناك شيء يجب فعله؟
      if l1.selectedindex=-1 then return
      dim i as integer
      ' نبدأ بالنهاية
      for i= 0 to l1.items.count-1
        ' محدد؟
        if l1.items(i).selected then
          lstInfos.Items.Add("["+TypeName(l1)+"] ["+l1.items(i).text+"] sélectionné")
        end if
      next
    end sub

</script>

في الإجراء [Page_Load] الذي يتم تنفيذه عند كل تشغيل للصفحة، يتم تهيئة القائمتين. لتجنب تهيئتهما في كل مرة، يتم استخدام الخاصية [IsPostBack] للقيام بذلك في المرة الأولى فقط. في المرات التالية، سيتم إعادة إنشاء القوائم تلقائيًا بواسطة آلية [VIEWSTATE]. بمجرد عرض الصفحة، يقوم المستخدم بتحديد بعض المربعات واستخدام الزر [Envoyer]. ثم يتم إرسال قيم النموذج إلى النموذج نفسه. بعد تنفيذ [Page_Load]، يتم تنفيذ الإجراء [btnEnvoyer_Click]. يستدعي هذا الإجراء الإجراء [affiche] لملء القائمة [lstInfos]. وتتلقى هذه الإجراء كمعلمة كائن من النوع [ListControl]، مما يسمح بإرسال كائن [RadioButtonList] أو كائن [CheckBoxList] إليها دون تمييز، وهما فئتان مشتقتان من [ListControl]. يمكن أن يكون السمة [EnableViewState] لقائمة [lstInfos] هي [false] نظرًا لأن حالتها لا تحتاج إلى الحفاظ عليها بين الطلبات المختلفة.

7.11. مكونات Panel، LinkButton

تسمح العلامة <asp:panel> بإدراج حاوية عناصر تحكم في صفحة. وتكمن فائدة الحاوية في أن بعض خصائصها تنطبق على جميع عناصر التحكم التي تحتوي عليها. وينطبق ذلك على خاصيتها [Visible]. وتوجد هذه الخاصية في كل عنصر تحكم خادم. إذا كان للحاوية الخاصية [Visible=false]، فسيتم التحكم في كل عنصر من عناصرها من خلال خاصيتها الخاصة [Visible]. وإذا كان يحتوي على الخاصية [Visible=false]، فلن يتم عرض الحاوية وكل ما تحتويه. وقد يكون هذا أسهل من إدارة الخاصية [Visible] لكل عنصر تحكم في الحاوية.

تسمح العلامة <asp:LinkButton> بإدراج رابط في كود عرض الصفحة. وهي تؤدي دورًا مشابهًا للزر [Button]. فهي تسبب في الواقع POST على جانب العميل بفضل وظيفة Javascript المرتبطة بها. نقوم بإنشاء صفحة [form9.aspx] للحصول على العرض التالي:

Image

رقم
الاسم
النوع
الخصائص
الدور
1
Panel1
لوحة
EnableViewState=true
حاوية عناصر التحكم
2
ListBox1
ListBox
EnableViewState=true
قائمة من ثلاث قيم
3
lnkCacher
LinkButton
EnableViewState=false
رابط لإخفاء الحاوية

عندما يتم إخفاء الحاوية، يظهر رابط جديد:

Image

رقم
الاسم
النوع
الخصائص
الدور
4
lnkVoir
LinkButton
EnableViewState=false
رابط لعرض الحاوية

رمز عرض الصفحة هو التالي:

<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            <asp:Panel id="Panel1" runat="server" BorderStyle="Ridge" BorderWidth="1px">
                <p>
                    Conteneur 
                </p>
                <p>
                    <asp:ListBox id="ListBox1" runat="server">
                        <asp:ListItem Value="1">un</asp:ListItem>
                        <asp:ListItem Value="2">deux</asp:ListItem>
                        <asp:ListItem Value="3" Selected="True">trois</asp:ListItem>
                    </asp:ListBox>
                </p>
            </asp:Panel>
        </p>
        <p>
            <asp:LinkButton id="lnkVoir" onclick="lnkVoir_Click" runat="server">Voir le conteneur</asp:LinkButton>
        </p>
        <p>
            <asp:LinkButton id="lnkCacher" onclick="lnkCacher_Click" runat="server">Cacher le conteneur</asp:LinkButton>
        </p>
    </form>
</body>
</html>

لاحظ أن هذا الرمز يقوم بتهيئة القائمة [ListBox1] بثلاث قيم. فيما يلي معالجات الأحداث [Clic] على الرابطين:

<%@ Page Language="VB" %>
<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs)
...
    end sub

    Sub lnkVoir_Click(sender As Object, e As EventArgs)
        ' يعرض الحاوية 1
        panel1.Visible=true
         ' تغيير الروابط
        lnkVoir.visible=false
        lnkCacher.visible=true
    End Sub

    Sub lnkCacher_Click(sender As Object, e As EventArgs)
        ' إخفاء الحاوية 1
        panel1.Visible=false
         ' تغيير الروابط
        lnkVoir.visible=true
        lnkCacher.visible=false
    End Sub
</script>

سنستخدم الإجراء [Page_Load] لتهيئة النموذج. سنقوم بذلك عند الطلب الأول (IsPostBack=false):

<%@ Page Language="VB" %>
<script runat="server">

    Sub Page_Load(sender As Object, e As EventArgs)
        ' المرة الأولى
        if not IsPostBack then
            ' يتم عرض الحاوية
            lnkVoir_Click(nothing,nothing)
        end if
    end sub
.....
</script>

7.12. لمواصلة...

عرضت الفقرات السابقة عددًا من مكونات الخادم. ولم يتم عرض سوى بعض خصائصها في كل مرة. لتعميق دراسة هذه المكونات، يمكن للقارئ اتباع طرق مختلفة:

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

في هذا الفصل، استخدمنا تقنية [WebMatrix] "الكل في واحد"، c.a.d، حيث وضعنا كود العرض وكود التحكم لصفحة واحدة في نفس الملف. بشكل عام، لا نوصي بهذه الطريقة بل بالطريقة المعروفة باسم [codebehind] المستخدمة سابقًا والتي تضع هذين الكودين في ملفين منفصلين. نذكر أن فائدة هذا الفصل تكمن في حقيقة أن كود التحكم يمكن تجميعه دون الحاجة إلى تشغيل تطبيق الويب. علاوة على ذلك، كانت أمثلةنا، كما أوضحنا منذ بداية الفصل، ذات طابع خاص جدًا: فقد كانت تتكون من صفحة واحدة عبارة عن نموذج يتم تبادله بين العميل والخادم في دورات متتالية من الطلب والاستجابة، حيث كان الطلب الأول للعميل هو GET، والطلبات التالية هي POST.

7.13. مكونات الخادم ووحدة التحكم في التطبيق

في الفصول السابقة، قمنا بإنشاء العديد من تطبيقات الويب. وقد تم إنشاؤها جميعًا وفقًا لهيكل MVC (النموذج-العرض-المتحكم) الذي يقسم التطبيق إلى كتل متميزة تمامًا ويسهل صيانته. كنا نبني واجهات المستخدم الخاصة بنا باستخدام علامات HTML القياسية. وبناءً على ما سبق، من الطبيعي أن نرغب الآن في استخدام مكونات الخادم. لنعد إلى مشكلة سبق دراستها باستفاضة وهي حساب الضريبة. كانت بنية MVC الخاصة بها كما يلي:

Image

يحتوي التطبيق على عرضين: [formulaire.aspx] و [erreurs.aspx]. يتم عرض [formulaire.aspx] عند طلب عنوان URL [main.aspx] لأول مرة:

Image

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

Image

ويستخدم الزر [Calculer] للحصول على الرد التالي:

Image

في تطبيق MVC، يجب أن تمر جميع الطلبات عبر وحدة التحكم، وهي هنا [main.aspx]. وهذا يعني أنه عندما يتم ملء النموذج [formulaire.aspx] من قبل المستخدم، يجب إرساله إلى [main.aspx] وليس إلى [formulaire.aspx]. وهذا ببساطة غير ممكن إذا قمنا بإنشاء واجهة المستخدم [formulaire.aspx] باستخدام مكونات الخادم ASP. ولتوضيح ذلك، لنقم بإنشاء نموذج [formtest.aspx] باستخدام مكون <asp:button>:

<%@ Page Language="VB" EnableViewState="false"%>
<html>
<head>
    <title>test</title>
</head>
<body>
    <form action="main.aspx" runat="server">
        <p>
            <asp:Button id="btnTest" runat="server" EnableViewState="false" Text="Test"></asp:Button>
        </p>
    </form>
</body>
</html>

لاحظ السمة [action="main.aspx"] لعلامة <form..>. لنقم بتشغيل هذا التطبيق. لا تحتوي صفحة العرض إلا على زر واحد:

Image

لنلقِ نظرة على الكود HTML الذي أرسله الخادم:

<html>
<head>
    <title>test</title>
</head>
<body>
    <form name="_ctl0" method="post" action="formtest.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwtNTMwNzcxMzI0Ozs+" />

        <p>
            <input type="submit" name="btnTest" value="Test" id="btnTest" />
        </p>

    </form>
</body>
</html>

نرى أن POST في النموذج يستهدف النموذج نفسه [action="formtest.aspx"] بينما كنا قد كتبنا في [formtest.aspx] العلامة HTML الخادم:

    <form action="main.aspx" runat="server">

يتم فرض السمة [runat="server"] لعلامة <form> علينا من خلال استخدام مكونات الخادم. تحدث خطأ في التجميع إذا لم نضع هذه السمة. وعندما نضعها، يتم تجاهل السمة [action] لعلامة <form>. يقوم الخادم دائمًا بإنشاء سمة [action] تشير إلى النموذج نفسه. ونستنتج من ذلك أنه في تطبيق MVC، لا يمكن استخدام النماذج التي تم إنشاؤها باستخدام العلامة <form ... runat="server">. لكن هذه العلامة ضرورية لجميع مكونات الخادم ASP التي تسترد إدخالات المستخدم. بمعنى آخر، لا يمكن استخدام نماذج خادم ASP في تطبيق MVC. هذا اكتشاف مهم. في الواقع، تتمثل إحدى نقاط القوة في تسويق ASP.NET في إمكانية إنشاء تطبيق ويب مثل تطبيق ويندوز. هذا صحيح إذا كان تطبيقنا لا يتوافق مع بنية MVC، ولكنه صحيح أكثر في حالة العكس. ومع ذلك، تبدو بنية MVC مفهومًا أساسيًا في تطوير الويب الحالي يصعب تجاهله.

من الممكن استخدام بنية MVC جنبًا إلى جنب مع نماذج مكونات ASP للتطبيقات التي تحتوي على عدد قليل من العروض المختلفة بفضل الحيلة التالية:

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

إنها حل أنيق نطبقه الآن في بعض الأمثلة

7.14. أمثلة على تطبيقات MVC مع مكونات الخادم ASP

7.14.1. المثال 1

نطبق في هذا المثال الأول مكونات الخادم التي قدمناها. ستكون الصفحة [form10.aspx] كما يلي:

الصورة اليسرى أعلاه هي النموذج كما يظهر للعميل. يقوم العميل بملئه وتأكيده باستخدام [Envoyer]. يُرسل له الخادم عرضًا يقدّم له قائمة بالقيم التي تم إدخالها (لقطة الشاشة اليمنى). يتيح رابط للمستخدم العودة إلى النموذج. ويجد النموذج كما قام بتأكيده. رمز العرض لـ [form10.aspx] هو التالي:

<html>
<head>
    <title>Exemple</title> <script language="javascript">
        function effacer(){
            alert("Vous avez cliqué sur [Effacer]")
        }
    </script>
</head>
<body>
    <p>
        Gestion d'un formulaire
    </p>
    <p>
        <hr />
    </p>
    <form runat="server">
        <p>
            <asp:Panel id="panelinfo" runat="server" EnableViewState="False">
                <p>
                    Liste des valeurs obtenues
                </p>
                <p>
                    <asp:ListBox id="lstInfos" runat="server" EnableViewState="False"></asp:ListBox>
                </p>
                <p>
                    <asp:LinkButton id="LinkButton1" onclick="LinkButton1_Click" runat="server">Retour au formulaire</asp:LinkButton>
                </p>
                <p>
                    <hr />
                </p>
            </asp:Panel>
        </p>
        <p>
            <asp:Panel id="panelform" runat="server" >
                <table>
                    <tbody>
                        <tr>
                            <td>
                                Etes-vous marié(e)</td>
                            <td>
                                <asp:RadioButton id="rdOui" runat="server"  GroupName="rdmarie"></asp:RadioButton>
                                Oui<asp:RadioButton id="rdNon" runat="server"  GroupName="rdmarie" Checked="True"></asp:RadioButton>
                                Non</td>
                        </tr>
                        <tr>
                            <td>
                                Cases à cocher</td>
                            <td>
                                <asp:CheckBox id="chk1" runat="server"></asp:CheckBox>
                                1<asp:CheckBox id="chk2" runat="server"></asp:CheckBox>
                                2<asp:CheckBox id="chk3" runat="server"></asp:CheckBox>
                                3</td>
                        </tr>
                        <tr>
                            <td>
                                Champ de saisie</td>
                            <td>
                                <asp:TextBox id="txtSaisie" runat="server"  MaxLength="20" Columns="20"></asp:TextBox>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Mot de passe</td>
                            <td>
                                <asp:TextBox id="txtmdp" runat="server"  MaxLength="10" Columns="10" TextMode="Password"></asp:TextBox>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Boîte de saisie</td>
                            <td>
                                <asp:TextBox id="txtArea" runat="server"  Columns="20" TextMode="MultiLine" Rows="3"></asp:TextBox>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Liste déroulante</td>
                            <td>
                                <asp:DropDownList id="cmbValeurs" runat="server"></asp:DropDownList>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Liste à choix unique</td>
                            <td>
                                <asp:ListBox id="lstSimple" runat="server"></asp:ListBox>
                                <asp:Button id="btnRazSimple" onclick="btnRazSimple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Liste à choix multiple</td>
                            <td>
                                <asp:ListBox id="lstMultiple" runat="server" SelectionMode="Multiple"></asp:ListBox>
                                <asp:Button id="razMultiple" onclick="razMultiple_Click" runat="server" EnableViewState="False" Text="Raz"></asp:Button>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                Champ caché</td>
                            <td>
                                <asp:Label id="lblSecret" runat="server" visible="False"></asp:Label></td>
                        </tr>
                        <tr>
                            <td>
                                Bouton simple</td>
                            <td>
                                <input id="btnEffacer" onclick="effacer()" type="button" value="Effacer" /></td>
                        </tr>
                        <tr>
                            <td>
                                Bouton [reset]</td>
                            <td>
                                <input id="btnReset" type="reset" value="Rétablir" /></td>
                        </tr>
                        <tr>
                            <td>
                                Bouton [submit]</td>
                            <td>
                                <asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" EnableViewState="False" Text="Envoyer"></asp:Button>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </asp:Panel>
        </p>
    </form>
</body>
</html>

تحتوي الصفحة على حاويتين، واحدة لكل عرض: [panelform] لعرض النموذج، و[panelinfo] لعرض المعلومات. قائمة مكونات الحاوية [panelForm] هي كما يلي:

الاسم
النوع
الخصائص
الدور
panelform
لوحة
EnableViewState=true
عرض النموذج
rdOui
rdNon
RadioButton
EnableViewState=true
GroupName=rdmarie
أزرار الاختيار
chk1
chk2
chk3
CheckBox
EnableViewState=true
مربعات الاختيار
txtSaisie
TextBox
EnableViewState=true
حقل الإدخال
txtMdp
TextBox
EnableViewState=true
حقل إدخال محمي
txtArea
TextBox
EnableViewState=true
مربع إدخال متعدد الأسطر
cmbValeurs
DropDownList
EnableViewState=true
قائمة منسدلة
lstSimple
ListBox
EnableViewState=true
SelectionMode=Single
قائمة الاختيار الفردي
btnRazSimple
زر
EnableViewState=false
إلغاء تحديد جميع العناصر في lstSimple
lstMultiple
ListBox
EnableViewState=true
SelectionMode=Multiple
قائمة متعددة الاختيار
btnRazMultiple
زر
EnableViewState=false
إلغاء تحديد جميع العناصر في lstMultiple
lblSecret
تسمية
EnableViewState=true
Visible=false
حقل مخفي
btnEffacer
HTML قياسي
 
يعرض تنبيهًا
btnEnvoyer
زر
EnableViewState=false
زر [submit] في النموذج
btnReset
HTML قياسي
 
زر [reset] في النموذج

يلعب [VIEWSTATE] دورًا مهمًا بالنسبة للمكونات هنا. يجب أن تحتوي جميع المكونات باستثناء الأزرار على الخاصية [EnableViewState=true]. لفهم السبب، يجب تذكر كيفية عمل التطبيق. لنفترض أن الحقل [txtSaisie] له الخاصية [EnableViewState=false]:

  1. يطلب العميل الصفحة [form10.aspx] للمرة الأولى. يحصل على عرض النموذج
  2. ويملؤها ويرسلها باستخدام الزر [Envoyer]. يتم عندئذٍ إرسال حقول الإدخال ويقوم الخادم بتعيين القيمة المرسلة لمكونات الخادم أو حالتها [VIEWSTATE] إذا كانت لها حالة. وبالتالي، يتم تعيين القيمة التي أدخلها المستخدم للحقل [txtSaisie]. وكذلك بالنسبة لهذه الخطوة، فإن حالته [VIEWSTATE] لا فائدة منها. ونتيجة لهذه العملية، يتم إرسال العرض [informations]، وهو في الواقع لا يزال الصفحة [form10.aspx] ولكن مع حاوية معروضة مختلفة.
  3. يقوم المستخدم بالاطلاع على هذا العرض الجديد ويستخدم الرابط [Retour au formulaire] للعودة إلى هذا العرض. ثم يتم إجراء POST إلى [form10.aspx]. وبالتالي، لا يوجد سوى قيمة واحدة مرسلة: وهي القيمة التي حددها المستخدم في قائمة المعلومات، وهي معلومات لم يتم استخدامها لاحقًا. ومع ذلك، لا يوجد حقل [txtSaisie] مرسَل.
  4. يتلقى الخادم POST ويخصص للمكونات الخادمية القيمة المرسلة أو حالتها [VIEWSTATE] إذا كانت لها حالة. هنا، لا تحتوي [txtNom] على قيمة مرسلة. إذا كانت سمة [EnableViewState] الخاصة به هي [false]، فسيتم تعيين السلسلة الفارغة له. ونظرًا لأننا نرغب في أن يكون له القيمة التي أدخلها المستخدم، فيجب أن يكون له الخاصية [EnableViewState=true].

يحتوي الحاوية [panelinfo] على عناصر التحكم التالية:

الاسم
النوع
الخصائص
الدور
panelinfo
لوحة
EnableViewState=false
عرض المعلومات
lstInfos
ListBox
EnableViewState=false
قائمة المعلومات التي تلخص القيم التي أدخلها المستخدم
LinkButton1
LinkButton
EnableViewState=false
رابط للعودة إلى النموذج

أثناء الاختبارات، إذا نظرنا إلى الرمز HTML الذي تم إنشاؤه بواسطة رمز العرض أعلاه، فقد نتفاجأ بالرمز الذي تم إنشاؤه للحقل المخفي [lblSecret]:

                    <tr>
                        <td>
                            Champ caché</td>
                        <td></td>
                    </tr>

لا يتم ترجمة المكون [lblSecret] إلى الرمز HTML لأنه يحتوي على الخاصية [Visible=false]. ولكن نظرًا لامتلاكه الخاصية [EnableViewState=true]، فسيتم الاحتفاظ بقيمته في الحقل المخفي [__VIEWSATE]. وبالتالي، سيكون بإمكاننا استردادها كما ستظهر الاختبارات.

يبقى لنا كتابة معالجات الأحداث. في [Page_Load] سنقوم بتهيئة النموذج:

Sub page_Load(sender As Object, e As EventArgs)
         ' في المرة الأولى، يتم تهيئة العناصر
          ' في المرات التالية، تستعيد هذه العناصر قيمها بواسطة VIEWSTATE
         if IsPostBack then return
         ' تهيئة النموذج
          ' لم يتم عرض لوحة المعلومات
         panelinfo.visible=false
         ' لوحة النموذج معروضة
         panelform.visible=true
         ' أزرار الاختيار
         rdNon.Checked=true
         ' مربعات الاختيار
         chk2.Checked=true
          ' حقل الإدخال
         txtSaisie.Text="qqs mots"
          ' حقل كلمة المرور
         txtMdp.Text="ceciestsecret"
         ' مربع الإدخال
         txtArea.Text="ligne"+ControlChars.CrLf+"ligne2"+ControlChars.CrLf
         ' قائمة منسدلة
         dim i as integer
         for i=1 to 4
             cmbValeurs.Items.Add(new ListItem("choix"+i.ToString,i.ToString))
         next
         cmbValeurs.SelectedIndex=1
         ' قائمة اختيار أحادية
         for i=1 to 7
             lstSimple.Items.Add(new ListItem("simple"+i.ToString,i.ToString))
         next
         lstSimple.SelectedIndex=0
         ' قائمة اختيار متعددة
         for i=1 to 10
             lstMultiple.Items.Add(new ListItem("multiple"+i.ToString,i.ToString))
         next
         lstMultiple.Items(0).Selected=true
         lstMultiple.Items(2).Selected=true
         ' حقل مخفي
         lblSecret.Text="secret"
End Sub

النقر على الأزرار [lstRazSimple] و [lstMultiple]:

Sub btnRazSimple_Click(sender As Object, e As EventArgs)
    ' مسح قائمة أحادية
    lstSimple.SelectedIndex=-1
End Sub

Sub razMultiple_Click(sender As Object, e As EventArgs)
    ' إعادة تعيين قائمة متعددة
    lstMultiple.SelectedIndex=-1
End Sub

النقر على الزر [Envoyer] :

Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
    ' يتم إظهار لوحة المعلومات وإخفاء لوحة النموذج
  panelinfo.Visible=true
  panelform.visible=false
  ' يتم استرداد القيم المرسلة ووضعها في lstInfos
   ' أزرار الاختيار
  dim info as string="état marital : "+iif(rdoui.checked,"marié"," non marié")
  affiche(info)
   ' مربعات الاختيار
  info=" cases cochées : "+iif(chk1.checked,"1 oui","1 non")+","+ _
      iif(chk2.checked,"2 oui","2 non")+","+iif(chk3.checked,"3 oui","3 non")
  affiche(info)
   ' حقل الإدخال
  affiche("champ de saisie : " + txtSaisie.Text.Trim)
   ' كلمة المرور
  affiche("mot de passe : " + txtMdp.Text.Trim)
   ' مربع الإدخال
  dim lignes() as String
  lignes=new Regex("\r\n").Split(txtArea.Text.Trim)
  dim i as integer
  for i=0 to lignes.length-1
      lignes(i)="["+lignes(i).Trim+"]"
  next
  affiche("Boîte de saisie : " + String.Join(",",lignes))
   ' قائمة منسدلة
  affiche("éléments sélectionnés dans combo : "+selection(cmbValeurs))
   ' قائمة بسيطة
  affiche("éléments sélectionnés dans liste simple : "+selection(lstSimple))
   ' قائمة متعددة
  affiche("éléments sélectionnés dans liste multiple : "+selection(lstMultiple))
   ' حقل مخفي
  affiche ("Champ caché : " + lblSecret.Text)
End Sub

sub affiche(msg as String)
    ' يعرض رسالة في lstInfos
    lstInfos.Items.Add(msg)
end sub

       function selection(liste as ListControl) as string
           ' يتم تصفح عناصر القائمة
            ' للعثور على العناصر المحددة
           dim i as integer
           dim info as string=""
           for i=0 to liste.Items.Count-1
               if liste.Items(i).Selected then info+="[" + liste.Items(i).Text + "]"
           next
           return info
       end function

وأخيرًا، النقر على الرابط [Retour vers le formulaire ]:

Sub LinkButton1_Click(sender As Object, e As EventArgs)
    ' يتم عرض النموذج وإخفاء لوحة المعلومات
    panelform.visible=true
    panelinfo.visible=false
End Sub

7.14.2. مثال 2

نستأنف هنا تطبيقًا تمت معالجته بالفعل باستخدام نماذج HTML القياسية. يتيح التطبيق إجراء محاكاة لحسابات الضرائب. وهو يعتمد على فئة [impot] التي لن نذكرها هنا. تحتاج هذه الفئة إلى بيانات تجدها في مصدر بيانات OLEDB. في هذا المثال، سيكون المصدر هو ACCESS.

7.14.2.1. هيكل MVC للتطبيق

هيكل MVC للتطبيق هو كما يلي:

Image

سيتم دمج العروض الثلاثة في كود العرض الخاص بوحدة التحكم [main.aspx] في شكل حاويات. ولذلك، فإن هذا التطبيق يحتوي على صفحة واحدة [main.aspx].

7.14.2.2. طرق عرض تطبيق الويب

الطريقة [formulaire] هي نموذج إدخال المعلومات الذي يسمح بحساب ضريبة المستخدم:

Image

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

Image

ويستخدم الزر [Envoyer] لطلب حساب ضريبته. ويحصل على العرض التالي [simulations]:

Image

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

Image

يتم الإبلاغ عن هذه الأخطاء من خلال العرض [erreurs]:

Image

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

Image

ثم يحصل على العرض [simulations] مع محاكاة إضافية:

Image

أخيرًا، إذا لم يكن مصدر البيانات متاحًا، يتم إخطار المستخدم بذلك في العرض [erreurs]:

Image

7.14.2.3. كود عرض التطبيق

تجدر الإشارة إلى أن الصفحة [main.aspx] تجمع جميع العروض. وهي عبارة عن نموذج واحد يحتوي على ثلاثة حاويات:

  • [panelform] لعرض [formulaire]
  • [panelerreurs] للعرض [erreurs]
  • [panelsimulations] للعرض [simulations]

نعود إلى فصل كود العرض عن كود التحكم في ملفين منفصلين. سيكون الأول في [main.aspx] والثاني في [main.aspx.vb]. كود [main.aspx] هو التالي:


<%@ page codebehind="main.aspx.vb" inherits="vs.main" AutoEventWireUp="false" %>
<HTML>
    <HEAD>
        <title>Calcul d'impôt </title>
    </HEAD>
    <body>
        <P>Calcul de votre impôt</P>
        <HR width="100%" SIZE="1">
        <FORM id="Form1" runat="server">
            <asp:panel id="panelform" Runat="server">
                <TABLE id="Table1" cellSpacing="1" cellPadding="1" border="0">
                    <TR>
                        <TD height="19">Etes-vous marié(e)</TD>
                        <TD height="19">
                            <asp:RadioButton id="rdOui" runat="server" GroupName="rdMarie"></asp:RadioButton>Oui
                            <asp:RadioButton id="rdNon" runat="server" GroupName="rdMarie" Checked="True"></asp:RadioButton>Non</TD>
                    </TR>
                    <TR>
                        <TD>Nombre d'enfants</TD>
                        <TD>
                            <asp:TextBox id="txtEnfants" runat="server" MaxLength="3" Columns="3"></asp:TextBox></TD>
                    </TR>
                    <TR>
                        <TD>Salaire annuel (euro)</TD>
                        <TD>
                            <asp:TextBox id="txtSalaire" runat="server" MaxLength="10" Columns="10"></asp:TextBox></TD>
                    </TR>
                </TABLE>
                <P>
                    <asp:Button id="btnCalculer" runat="server" Text="Calculer"></asp:Button>
                    <asp:Button id="btnEffacer" runat="server" Text="Effacer"></asp:Button></P>
            </asp:panel>
          <asp:panel id="panelerreurs" runat="server">
                <P>Les erreurs suivantes se sont produites :</P>
                <P>
                    <asp:Literal id="erreursHTML" runat="server"></asp:Literal></P>
                <P></P>
                <asp:LinkButton id="lnkForm1" runat="server">Retour au formulaire</asp:LinkButton>
            </asp:panel>
          <asp:panel id="panelsimulations" runat="server">
                <P>
                    <TABLE>
                        <TR>
                            <TH>
                                Marié</TH>
                            <TH>
                                Enfants</TH>
                            <TH>
                                Salaire annuel</TH>
                            <TH>
                                Impôt à payer (euro)</TH></TR>
                        <asp:Literal id="simulationsHTML" runat="server"></asp:Literal></TABLE>
                    <asp:LinkButton id="lnkForm2" runat="server">Retour au formulaire</asp:LinkButton></P>
            </asp:panel>
      </FORM>
    </body>
</HTML>

لقد حددنا الحاويات الثلاث. تجدر الإشارة إلى أنها جميعها موجودة داخل العلامة <form runat="server">. وهذا أمر إلزامي، لأنه إذا أردنا الاستفادة من مزايا مكونات الخادم، فيجب وضعها داخل هذه العلامة. النقطة المهمة التي يجب فهمها هي أن لدينا هنا نموذجًا واحدًا سيتبادل بينه العميل وخادم الويب. لذلك، نحن بالفعل في التكوين المستخدم في هذا الفصل بأكمله حول مكونات الخادم. دعونا نوضح مكونات كل حاوية:

الحاوية [panelform]:

الاسم
النوع
الخصائص
الدور
panelform
لوحة
EnableViewState=true
عرض النموذج
rdOui
rdNon
RadioButton
EnableViewState=true
GroupName=rdmarie
أزرار الاختيار
txtEnfants
TextBox
EnableViewState=true
عدد الأطفال
txtSalaire
TextBox
EnableViewState=true
الراتب السنوي
btnCalculer
زر
 
زر [submit] في النموذج - يبدأ حساب الضريبة
btnEffacer
زر
 
زر [submit] في النموذج - يفرغ النموذج

حاوية [panelerreurs]:

الاسم
النوع
الخصائص
الدور
panelerreurs
لوحة
EnableViewState=true
عرض الأخطاء
lnkForm1
LinkButton
EnableViewState=true
رابط إلى النموذج
erreursHTML
حرف
 
رمز HTML من قائمة الأخطاء

حاوية [panelsimulations]:

الاسم
النوع
الخصائص
الدور
panelsimulations
لوحة
EnableViewState=true
عرض المحاكاة
lnkForm2
LinkButton
EnableViewState=true
رابط إلى النموذج
simulationsHTML
حرف
 
رمز HTML لقائمة المحاكاة في جدول HTML

7.14.2.4. رمز التحكم في التطبيق

يوجد رمز التحكم في التطبيق موزعًا في الملفين [global.asax.vb] و [main.aspx.vb]. يتم تعريف الملف [global.asax] على النحو التالي:

<%@ Application src="Global.asax.vb" Inherits="Global" %>

الملف [global.asax.vb] هو التالي:


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Collections

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' يتم إنشاء كائن استيراد
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
            ' يتم وضع الكائن في التطبيق
            Application("objImpot") = objImpot
            ' لا توجد أخطاء
            Application("erreur") = False
        Catch ex As Exception
            'حدث خطأ، يتم تسجيله في التطبيق
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' بداية الجلسة - يتم إنشاء قائمة محاكاة فارغة
        Session.Item("simulations") = New ArrayList
    End Sub
End Class

عند بدء تشغيل التطبيق (الطلب الأول المقدم إلى التطبيق)، يتم تنفيذ الإجراء [Application_Start]. ويهدف هذا الإجراء إلى إنشاء كائن من النوع [impot] يستمد بياناته من مصدر OLEDB. يُطلب من القارئ قراءة الفصل 5 حيث تم تعريف هذه الفئة، إذا كان قد نسيها. قد يفشل إنشاء الكائن [impot] إذا لم يكن مصدر البيانات متاحًا. في هذه الحالة، يتم تخزين الخطأ في التطبيق حتى تعرف جميع الاستعلامات اللاحقة أن هذا الكائن لم يتم تهيئته بشكل صحيح. إذا سارت عملية الإنشاء بشكل جيد، يتم أيضًا تخزين الكائن [impot] الذي تم إنشاؤه في التطبيق. وسيتم استخدامه من قبل جميع استعلامات حساب الضريبة. عندما يقوم العميل بطلبه الأول، يتم إنشاء جلسة له بواسطة الإجراء [Application_Start]. تهدف هذه الجلسة إلى تخزين مختلف عمليات محاكاة حساب الضريبة التي سيقوم بها. سيتم تخزين هذه العمليات في كائن [ArrayList] مرتبط بمفتاح الجلسة "simulations". عند بدء الجلسة، يتم ربط هذا المفتاح بكائن [ArrayList] فارغ. يتم وضع المعلومات اللازمة للتطبيق في ملف التكوين الخاص به [wenConfig]:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="chaineConnexion" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\webforms\vs\impots5\impots.mdb" />
    </appSettings>
</configuration>

يشير المفتاح [chaineConnexion] إلى سلسلة الاتصال بالمصدر OLEDB. ويوجد الجزء الآخر من رمز التحكم في [main.aspx.vb]:


Imports System.Collections
Imports Microsoft.VisualBasic
Imports st.istia.univangers.fr
Imports System

Public Class main
    Inherits System.Web.UI.Page

    Protected WithEvents rdOui As System.Web.UI.WebControls.RadioButton
    Protected WithEvents rdNon As System.Web.UI.WebControls.RadioButton
    Protected WithEvents txtEnfants As System.Web.UI.WebControls.TextBox
    Protected WithEvents txtSalaire As System.Web.UI.WebControls.TextBox
    Protected WithEvents btnCalculer As System.Web.UI.WebControls.Button
    Protected WithEvents btnEffacer As System.Web.UI.WebControls.Button
    Protected WithEvents panelform As System.Web.UI.WebControls.Panel
    Protected WithEvents lnkForm1 As System.Web.UI.WebControls.LinkButton
    Protected WithEvents lnkForm2 As System.Web.UI.WebControls.LinkButton
    Protected WithEvents panelerreurs As System.Web.UI.WebControls.Panel
    Protected WithEvents panelsimulations As System.Web.UI.WebControls.Panel
    Protected WithEvents simulationsHTML As System.Web.UI.WebControls.Literal
    Protected WithEvents erreursHTML As System.Web.UI.WebControls.Literal

    ' المتغيرات المحلية

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
    End Sub

    Private Sub afficheFormulaire()
...
    End Sub

    Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
...
    End Sub

    Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
...
    End Sub

    Private Function checkData() As ArrayList
...
    End Function

    Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
....
    End Sub

    Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
...
    End Sub

    Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
...
    End Sub

    Private Sub razForm()
...
    End Sub
End Class

Le premier événement traité par le code est [Page_Load] :

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' أولاً، ننظر إلى حالة التطبيق
        If CType(Application("erreur"), Boolean) Then
            ' لم يتمكن التطبيق من التهيئة
            ' يتم عرض شاشة الأخطاء
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
            afficheErreurs(erreurs, "")
            Exit Sub
        End If
        ' لا توجد أخطاء - عند الطلب الأول، نعرض النموذج
        If Not IsPostBack Then afficheFormulaire()
    End Sub

تجدر الإشارة إلى أنه عند تنفيذ الإجراء [Page_Load] على عميل POST، فإن جميع مكونات النموذج لها قيمة: إما القيمة التي أرسلها العميل إن وجدت، أو القيمة السابقة للمكون بفضل [VIEWSTATE]. في هذا النموذج، تتمتع جميع المكونات بخاصية [EnableViewState=true]. قبل البدء في معالجة الطلب، نتأكد من أن التطبيق قد تم تهيئته بشكل صحيح. إذا لم يكن الأمر كذلك، يتم عرض العرض [erreurs] باستخدام الإجراء [afficheErreurs]. إذا كان هذا هو الطلب الأول (IsPostBack=false)، فإننا نعرض طريقة العرض [formulaire] باستخدام [afficheFormulaire].

الإجراء الذي يعرض العرض [erreurs] هو التالي:


    Private Sub afficheErreurs(ByRef erreurs As ArrayList, ByRef lien As String)
        ' عرض حاوية الأخطاء
        panelerreurs.Visible = True
        Dim i As Integer
        erreursHTML.Text = ""
        For i = 0 To erreurs.Count - 1
            erreursHTML.Text += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
        Next
        lnkForm1.Text = lien
        ' يتم إخفاء الحاويات الأخرى
        panelform.Visible = False
        panelsimulations.Visible = False
    End Sub

تحتوي الإجراء على معلمتين:

  • قائمة برسائل الأخطاء في [erreurs]
  • نص رابط في [lien]

يتم وضع الرمز HTML المراد إنشاؤه لقائمة الأخطاء في القيمة الثابتة [erreursHTML]. ويتم وضع نص الرابط في الخاصية [Text] للكائن [LinkButton] في العرض.

الإجراء الذي يعرض العرض [formulaire] هو كما يلي:


    Private Sub afficheFormulaire()
        ' يتم عرض النموذج
        panelform.Visible = True
        ' يتم إخفاء الحاويات الأخرى
        panelerreurs.Visible = False
        panelsimulations.Visible = False
    End Sub

يقتصر هذا الإجراء على إظهار الحاوية [panelform]. يتم عرض المكونات بقيمتها المرسلة أو السابقة (VIEWSTATE).

عندما ينقر المستخدم على الزر [Calculer] في العرض [formulaire]، يتم إجراء POST إلى [main.aspx]. يتم تنفيذ الإجراء [Page_Load] ثم الإجراء [btnCalculer_Click]:


    Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
        ' يتم التحقق من صحة البيانات المدخلة
        Dim erreurs As ArrayList = checkData()
        ' في حالة وجود أخطاء، يتم الإبلاغ عنها
        If erreurs.Count <> 0 Then
            ' يتم عرض صفحة الأخطاء
            afficheErreurs(erreurs, "Retour au formulaire")
            Exit Sub
        End If
        ' لا توجد أخطاء - يتم حساب الضريبة
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
        ' يتم إضافة النتيجة إلى المحاكاة الحالية
        Dim simulation() As String = New String() {CType(IIf(rdOui.Checked, "oui", "non"), String), _
         txtEnfants.Text.Trim, txtSalaire.Text.Trim, impot.ToString}
        ' يتم إضافة النتيجة إلى المحاكاة الحالية
        Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
        simulations.Add(simulation)
        ' يتم وضع المحاكاة في الجلسة والسياق
        Session.Item("simulations") = simulations
        ' عرض صفحة النتائج
        afficheSimulations(simulations, "Retour au formulaire")
    End Sub

تبدأ العملية بالتحقق من صحة حقول النموذج باستخدام الإجراء [checkData] الذي يُرجع قائمة [ArrayList] من رسائل الخطأ. إذا لم تكن القائمة فارغة، يتم عرض العرض [erreurs] وتنتهي العملية. إذا كانت البيانات المدخلة صالحة، يتم حساب مبلغ الضريبة باستخدام الكائن من النوع [impot] الذي تم تخزينه في التطبيق عند بدء تشغيله. تضاف هذه المحاكاة الجديدة إلى قائمة المحاكاة التي تم إجراؤها بالفعل وتخزينها في الجلسة.

تتحقق الوظيفة [CheckData] من صحة البيانات. وتُرجع قائمة [ArrayList] من رسائل الخطأ، وتكون فارغة إذا كانت البيانات صحيحة:


    Private Function checkData() As ArrayList
        ' في البداية لا توجد أخطاء
        Dim erreurs As New ArrayList
        ' عدد الأطفال
        Try
            Dim nbEnfants As Integer = CType(txtEnfants.Text, Integer)
            If nbEnfants < 0 Then Throw New Exception
        Catch
            erreurs.Add("Le nombre d'enfants est incorrect")
        End Try
        ' الراتب
        Try
            Dim salaire As Long = CType(txtSalaire.Text, Long)
            If salaire < 0 Then Throw New Exception
        Catch
            erreurs.Add("Le salaire annuel est incorrect")
        End Try
        ' يتم عرض قائمة الأخطاء
        Return erreurs
    End Function

وأخيرًا، يتم عرض العرض [simulations] بواسطة الإجراء [afficheSimulations] التالي:


    Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
        ' عرض عرض المحاكاة
        panelsimulations.Visible = True
        ' الحاويات الأخرى مخفية
        panelerreurs.Visible = False
        panelform.Visible = False
        ' محتوى عرض المحاكاة
        ' كل محاكاة عبارة عن مصفوفة مكونة من 4 عناصر من نوع string
        Dim simulation() As String
        Dim i, j As Integer
        simulationsHTML.Text = ""
        For i = 0 To simulations.Count - 1
            simulation = CType(simulations(i), String())
            simulationsHTML.Text += "<tr>"
            For j = 0 To simulation.Length - 1
                simulationsHTML.Text += "<td>" + simulation(j) + "</td>"
            Next
            simulationsHTML.Text += "</tr>" + ControlChars.CrLf
        Next
        ' رابط
        lnkForm2.Text = lien
    End Sub

تحتوي الإجراء على معلمتين:

  • قائمة محاكاة في [simulations]
  • نص رابط في [lien]

يتم وضع الرمز HTML المراد إنشاؤه لقائمة المحاكاة في القيمة الثابتة [simulationsHTML]. أما نص الرابط فيتم وضعه في الخاصية [Text] للكائن [LinkButton] في العرض.

عندما ينقر المستخدم على الزر [Effacer] في العرض [formulaire]، يتم تنفيذ الإجراء [btnEffacer_click] (دائمًا بعد [Page_Load]):


    Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
        ' يعرض النموذج فارغًا
        razForm()
        afficheFormulaire()
    End Sub

    Private Sub razForm()
        ' إفراغ النموذج
        rdOui.Checked = False
        rdNon.Checked = True
        txtEnfants.Text = ""
        txtSalaire.Text = ""
    End Sub

الرمز أعلاه بسيط بما يكفي بحيث لا يحتاج إلى تعليق. يبقى لنا التعامل مع النقر على روابط العرض [erreurs] و [simulations]:


    Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
        ' يعرض النموذج
        afficheFormulaire()
    End Sub

    Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
        ' يعرض النموذج
        afficheFormulaire()
    End Sub

تكتفي الإجراءان بعرض العرض [formulaire]. ونعلم أن الحقول الموجودة فيه ستحصل على قيمة هي إما القيمة التي تم إرسالها لها أو قيمتها السابقة. وكما هو الحال هنا، لا يرسل POST الخاص بالعميل أي قيمة لحقول النموذج، لذا ستستعيد هذه الحقول قيمتها السابقة. وبالتالي، يتم عرض النموذج بالقيم التي أدخلها المستخدم. نتذكر أنه في الإصدار الذي لا يحتوي على مكونات الخادم، كنا نقوم بعملية الاستعادة هذه بأنفسنا.

7.14.2.5. الاختبارات

يتم وضع جميع الملفات اللازمة للتطبيق في مجلد <application-path>:
يحتوي المجلد [bin] على ملف dll الذي يحتوي على الفئات [impot] و [impotsData] و [impotsOLEDB] اللازمة للتطبيق:

يمكن للقارئ، إذا رغب في ذلك، إعادة قراءة الفصل 5 الذي يشرح كيفية إنشاء الملف [impot.dll] أعلاه. بعد ذلك، يتم تشغيل خادم Cassini باستخدام المعلمات (<application-path>,/impots5). يتم طلب عنوان URL [http://impots5/main.aspx] باستخدام متصفح:

Image

إذا قمنا بتغيير اسم الملف ACCESS [impots.mdb] إلى [impots1.mdb]، فسنحصل على الصفحة التالية:

Image

7.14.3. المثال 3

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

Image

لقد جمعنا في صفحة واحدة روابط للتطبيقات التي كتبناها حتى الآن. هذا النوع من التطبيقات مناسب تمامًا لهيكل MVC. ببساطة، لم يعد هناك وحدة تحكم واحدة بل عدة وحدات تحكم.

Image

يلعب وحدة التحكم [main.aspx] دور وحدة التحكم الرئيسية. وهي التي يتم استدعاؤها بواسطة الروابط الموجودة في الصفحة الرئيسية للتطبيق. وستتمكن من إجراء عمليات مشتركة بين جميع الإجراءات الممكنة، ثم ستقوم بتنفيذ الإجراء المحدد المرتبط بالرابط المستخدم. بعد ذلك، ستقوم بتسليم زمام الأمور إلى أحد وحدات التحكم الثانوية، وهي الوحدة المسؤولة عن تنفيذ الإجراء. ومنذ تلك اللحظة، تتم التبادلات بين العميل ووحدة التحكم المحددة هذه. ولم يعد يتم المرور عبر وحدة التحكم الرئيسية [main.aspx]. لذلك لم نعد في إطار MVC مع وحدة تحكم واحدة تقوم بتصفية جميع الطلبات. يمكن لكل وحدة من وحدات التحكم المذكورة أعلاه عرض عدة طرق عرض باستخدام آلية الحاويات داخل صفحة واحدة كما عرضنا.

إن عدم وجود وحدة تحكم واحدة تختار العروض التي سيتم إرسالها إلى العميل ينطوي على عيوب. لنأخذ إدارة الأخطاء كمثال. قد يتعين على كل إجراء من الإجراءات التي تعرضها التطبيق عرض عرض للأخطاء. سيكون لكل وحدة تحكم [applix.aspx] عرضها الخاص [erreurs] لأن هذا العرض هو ببساطة حاوية معينة لصفحة وحدة التحكم. لا توجد طريقة للحصول على عرض [erreurs] واحد تستخدمه جميع التطبيقات الفردية. في الواقع، عادةً ما تحتوي مثل هذه الواجهة على رابط للعودة إلى النموذج الذي يحتوي على الأخطاء، ويجب استعادة هذا النموذج إلى الحالة التي تم فيها التحقق من صحته لتمكين المستخدم من تصحيح أخطائه. تتم هذه الاستعادة من خلال آلية [VIEWSTATE] التي لا تعمل بين وحدات التحكم المختلفة. إذا تم تطوير التطبيقات بواسطة أشخاص مختلفين، فهناك خطر ظهور صفحات أخطاء بمظهر مختلف حسب الإجراء الذي يختاره المستخدم، مما يضر بتجانس التطبيق ككل. سنرى لاحقًا أن ASP.NET يقدم حلاً لهذه المشكلة الخاصة المتعلقة بالعرض المشترك. يمكن أن يكون هذا موضوع مكون خادم جديد نقوم ببنائه بأنفسنا. يكفي استخدام هذا المكون في التطبيقات المختلفة لضمان تجانس التطبيق ككل. أما مشكلة ترتيب الإجراءات فهي أصعب في إدارتها. عندما تمر جميع الطلبات عبر وحدة تحكم واحدة، يمكن لهذه الوحدة التحقق من أن الإجراء المطلوب متوافق مع الإجراء السابق. يوجد رمز التحكم هذا في مكان واحد. هنا، سيتعين توزيعه على وحدات التحكم المختلفة، مما يعقد صيانة التطبيق الشامل.

لنعد إلى تطبيقنا أعلاه. صفحة الدخول الخاصة به هي صفحة HTML كلاسيكية [accueil.htm]:

<html>
    <head>
        <TITLE>Composants ASP Serveur</TITLE>
        <meta name="pragma" content="no-cache">
    </head>
    <frameset rows="130,*" frameborder="0">
        <frame name="banner" src="bandeau.htm" scrolling="no">
        <frameset cols="200,*">
            <frame name="contents" src="options.htm">
            <frame name="main" src="main.htm">
        </frameset>
        <noframes>
            <p id="p1">
                Ce jeu de frames HTML affiche plusieurs pages Web. Pour afficher ce jeu de 
                frames, utilisez un navigateur Web qui prend en charge HTML 4.0 et version 
                ultérieure.
            </p>
        </noframes>
    </frameset>
</html>

تتكون صفحة البداية هذه من ثلاثة إطارات تسمى banner و contents و main:

الصفحة [bandeau.htm] الموضوعة في الإطار [banner] هي التالية:

Image

ورمزها HTML هو كما يلي:


<html>
    <head>
        <META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE" />
        <title>bandeau</title>
    </head>
    <body>
        <P>
            <TABLE>
                <TR>
                    <TD><IMG alt="logo université d'angers" src="univ01.gif"></TD>
                    <TD>Composants serveurs ASP</TD>
                </TR>
            </TABLE>
        </P>
        <HR>
    </body>
</html>

الصفحة [options.htm] موجودة في الشريط [contents]. وهي عبارة عن مجموعة من الروابط:


<html>
    <head>
        <meta http-equiv="pragma" content="no-cache" />
        <title>options</title>
    </head>
    <body bgcolor="Gold">
        <ul>
            <li>
                <a href="main.aspx?action=label" 
                 target="main">Label</a>
            <li>
                <a href="main.aspx?action=button" 
                 target="main">Button</a>
            <li>
                <a href="main.aspx?action=textbox1" 
                 target="main">TextBox-1</a></li>
            <li>
                <a href="main.aspx?action=textbox2" 
                 target="main">TextBox-2</a></li>
            <li>
                <a href="main.aspx?action=dropdownlist" 
                 target="main">DropDownList</a></li>
            <li>
                <a href="main.aspx?action=listbox" 
                 target="main">ListBox</a></li>
            <li>
                <a href="main.aspx?action=casesacocher" 
                  target="main">CheckBox</a> et<br>
                RadioButton</li>
            <li>
                <a href="main.aspx?action=listecasesacocher" 
                  target="main">CheckBoxList</a> et<br>
                RadioButtonList</a></li>
            <li>
                <a href="main.aspx?action=panel" 
                  target="main">Panel</a></li>
        </ul>
    </body>
</html>

تشير جميع الروابط المختلفة إلى وحدة التحكم الرئيسية [main.aspx] مع معلمة [action] تشير إلى الإجراء المطلوب. يُطلب عرض هدف الروابط في الإطار [main] (target="main").

الصفحة الأولى المعروضة في الإطار [main] هي [main.htm]:


<html>
    <head>
        <title>main</title>
    </head>
    <body>
        <P>Choisissez une option pour tester découvrir un type de composant serveur...</P>
    </body>
</html>

وحدة التحكم الرئيسية [main.aspx, main.aspx.vb] هي كما يلي:

[main.aspx]

<%@ page src="main.aspx.vb" inherits="main" autoeventwireup="false" %>

[main.aspx.vb]

فئة عامة main

يرث System.Web.UI.Page

Private Sub Page_Load(ByVal المرسل As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

' يتم استرداد الإجراء المطلوب

Dim action As String

إذا كان Request.QueryString("action") يساوي Nothing

action = "label"

Else

action = Request.QueryString("action").ToString.ToLower

End If

' يتم تنفيذ الإجراء

اختر الحالة action

Case "label"

Server.Transfer("form2.aspx")

Case "button"

Server.Transfer("form3.aspx")

حالة "textbox1"

Server.Transfer("form4.aspx")

حالة "textbox2"

Server.Transfer("form5.aspx")

حالة "dropdownlist"

Server.Transfer("form6.aspx")

حقل "listbox"

Server.Transfer("form7.aspx")

حالة "casesacocher"

Server.Transfer("form8.aspx")

حالة "listecasesacocher"

Server.Transfer("form8b.aspx")

حالة "panel"

Server.Transfer("form9.aspx")

حالة أخرى

Server.Transfer("form2.aspx")

End Select

نهاية الروتين

نهاية الفئة

وحدة التحكم لدينا بسيطة. وفقًا لقيمة المعلمة [action]، فإنها تنقل معالجة الطلب إلى الصفحة المناسبة. وهي لا تضيف أي قيمة مضافة مقارنة بصفحة HTML التي تحتوي على روابط. ومع ذلك، يكفي إضافة صفحة مصادقة لرؤية فائدته. إذا كان على المستخدم المصادقة (اسم المستخدم، كلمة المرور) للوصول إلى التطبيقات، فإن وحدة التحكم [main.aspx] ستكون مكانًا جيدًا للتحقق من إجراء هذه المصادقة.