7. مكونات خادم ASP - 1
7.1. مقدمة
في هذا الفصل، نصف التقنية الموصى بها في ASP.NET لإنشاء واجهة المستخدم. نعلم أن هناك مرحلتين متميزتين في معالجة خادم الويب لصفحة .aspx:
- أولاً، يتم تنفيذ وحدة التحكم في الصفحة. وتتكون هذه من كود موجود إما داخل صفحة .aspx نفسها (حل WebMatrix) أو في ملف منفصل (حل Visual Studio.NET).
- ثم يتم تنفيذ كود العرض لصفحة .aspx وتحويله إلى كود HTML يتم إرساله إلى العميل.

يوفر ASP.NET ثلاث مكتبات علامات لكتابة كود عرض الصفحة:
- علامات HTML الكلاسيكية. وهذا ما كنا نستخدمه حتى الآن.
- علامات HTML من جانب الخادم
- علامات نماذج الويب
بغض النظر عن مكتبة العلامات المستخدمة، يظل دور وحدة التحكم في الصفحة كما هو. يجب أن تحسب قيمة المعلمات الديناميكية التي تظهر في كود العرض. حتى الآن، كانت هذه المعلمات الديناميكية بسيطة: كانت كائنات من النوع [String]. لذا، إذا كان لدينا في كود العرض علامة <%=name%>:
- يعلن وحدة التحكم في الصفحة متغير [name] من النوع [String] ويحسب قيمته
- عندما تنتهي وحدة التحكم في الصفحة من عملها ويتم تنفيذ كود العرض لإنشاء استجابة HTML، يتم استبدال علامة <%=name%> بالقيمة التي حسبها كود وحدة التحكم
نحن نعلم أن الفصل بين وحدة التحكم والعرض أمر تعسفي وأن كود وحدة التحكم وكود العرض يمكن خلطهما في نفس الصفحة. وقد أوضحنا سبب عدم تشجيع هذا النهج، وسنستمر في الالتزام بالفصل بين وحدة التحكم والعرض.
تسمح لك علامات [server HTML] و [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>
محتوى القائمة والخيار المراد تحديده هما عنصران ديناميكيان، ولذلك يجب أن يتم إنشاؤهما بواسطة وحدة التحكم في الصفحة. لقد واجهنا هذه المشكلة من قبل وحللناها عن طريق تضمين العلامة
سيتم استبدال هذه العلامة بقيمة [String] للمتغير [uneListeHTML]. ويجب أن تكون هذه القيمة، التي يحسبها وحدة التحكم، هي كود HTML الخاص بالقائمة، أي "<select name=..>...</select>". وهذا الأمر ليس صعبًا بشكل خاص، ويبدو حلاً أنيقًا يتجنب وضع كود التوليد مباشرةً في طبقة العرض الخاصة بالصفحة. هنا، سيكون هناك حلقة مع اختبارات يجب إدراجها فيها، مما سيؤدي إلى "ازدحامها" بشكل كبير. ومع ذلك، فإن هذه الطريقة لها عيب. فالفصل بين التحكم والعرض في الصفحة يساعد أيضًا على تحديد مجالين للمسؤولية:
- مسؤولية مطور .NET، الذي يتولى التحكم في الصفحة،
- ومجال مسؤولية مصمم الجرافيك، الذي يتولى جزء العرض في الصفحة
هنا، نرى أن إنشاء كود HTML للقائمة قد تم نقله إلى وحدة التحكم. قد يرغب مصمم الجرافيك في تعديل كود HTML هذا لتغيير المظهر "المرئي" للقائمة. سيضطرون إلى العمل في قسم [وحدة التحكم] وبالتالي الخروج عن مجال خبرتهم، مع المخاطر المرتبطة بإدخال أخطاء في الكود عن غير قصد.
تحل مكتبات علامات الخادم هذه المشكلة. فهي توفر كائنًا يمثل قائمة HTML. على سبيل المثال، توفر مكتبة [WebForms] العلامة التالية:
تمثل هذه العلامة كائن [ListBox] يمكن التحكم فيه بواسطة وحدة التحكم في الصفحة. يحتوي هذا الكائن على خصائص تمثل الخيارات المختلفة في قائمة HTML وتحدد الخيار المحدد. وبالتالي، ستقوم وحدة التحكم في الصفحة بتعيين القيم المناسبة لهذه الخصائص. عند تنفيذ طبقة العرض، يتم عرض العلامة
بكود HTML الذي يمثل كائن [uneListe]، أي الكود "<select ..>...</select>". في الوقت الحالي، لا يوجد فرق جوهري عن الطريقة السابقة سوى نهج البرمجة الموجهة للكائنات، وهو أمر مثير للاهتمام. لنعد إلى مصمم الجرافيك الذي يحتاج إلى تعديل "مظهر" القائمة. تحتوي علامات الخادم على سمات نمط (BackColor، Bordercolor، BorderWidth، ...) تسمح لك بتعيين المظهر المرئي للكائن HTML المقابل. لذا يمكننا كتابة:
<asp:ListBox id="ListBox1" runat="server" BackColor="#ffff99"></asp:ListBox></P>
الميزة هي أن مصمم الجرافيك يعمل مباشرةً داخل كود العرض لإجراء هذه التغييرات. وهذه ميزة واضحة مقارنة بالطريقة السابقة. وبالتالي، فإن مكتبات العلامات من جانب الخادم تبسط بناء طبقة العرض لصفحات الويب — وهو ما أشرنا إليه في الفصول السابقة باسم واجهة المستخدم. والغرض من هذا الفصل هو تقديمها. سنرى أنها توفر أحيانًا كائنات معقدة مثل التقويمات أو الجداول المرتبطة بمصادر البيانات. وهي قابلة للتوسيع، مما يعني أنه يمكن للمستخدم إنشاء مكتبة العلامات الخاصة به. وبالتالي يمكنه إنشاء علامة تولد لافتة على الصفحة. وستحتوي جميع الصفحات التي تستخدم هذه العلامة على نفس اللافتة.
يتكيف HTML الذي يتم إنشاؤه لعلامة ما مع نوع متصفح العميل. عندما يرسل المتصفح طلبًا إلى خادم الويب، فإنه يضم رأس [User-Agent: xx] ضمن رؤوس HTTP الخاصة به، حيث يحدد [xx] هوية العميل. وإليك مثال على ذلك:
باستخدام هذه المعلومات، يمكن لخادم الويب تحديد إمكانيات العميل، ولا سيما نوع كود HTML الذي يمكنه التعامل معه. بمرور الوقت، ظهرت بالفعل عدة إصدارات من لغة HTML. تدعم المتصفحات الحديثة أحدث إصدارات اللغة، في حين أن المتصفحات القديمة لا تدعمها. استنادًا إلى رأس HTTP [User-Agent:] الذي يرسله العميل، سيرسل الخادم إصدار HTML يمكن للعميل فهمه. هذه طريقة مفيدة وعملية لأنها تعني أن المطور لا يحتاج إلى القلق بشأن النوع المحدد لمتصفح العميل الذي يستخدمه تطبيقه.
أخيرًا، تسمح بيئات التطوير المتقدمة مثل Visual Studio.NET وWebMatrix وغيرها بتصميم واجهة الويب "بأسلوب Windows". ورغم أن هذه الأدوات ليست أساسية، إلا أنها توفر مساعدة كبيرة للمطور. يقوم المطور بتصميم واجهة الويب باستخدام مكونات رسومية يضعها على الواجهة. ولديه وصول مباشر إلى خصائص كل مكون من مكونات الواجهة، والتي يمكنه تكوينها حسب الرغبة. وتُترجم هذه الخصائص إلى كود عرض HTML للواجهة كسمات لعلامة <asp:> الخاصة بالمكون. وتتمثل الفائدة للمطور في أنه لا يتعين عليه حفظ قائمة سمات كل علامة أو صيغتها. وتعد هذه ميزة كبيرة عندما لا يكون المرء على دراية كاملة بمكتبات علامات الخادم التي يوفرها ASP.NET. وبمجرد إتقان هذه الصيغة، قد يفضل بعض المطورين كتابة العلامات مباشرةً في كود عرض الصفحة دون المرور بمرحلة التصميم الرسومي. وبالتالي، لم يعد هناك حاجة إلى بيئة تطوير متكاملة (IDE)؛ يكفي استخدام محرر نصوص بسيط. اعتمادًا على سير عملك، ينصب التركيز بعد ذلك على المكونات (باستخدام بيئة تطوير متكاملة) أو العلامات (باستخدام محرر نصوص). هذان المصطلحان متكافئان. المكون هو الكائن الذي سيتم التعامل معه بواسطة كود التحكم في الصفحة. تتيح لنا بيئة التطوير المتكاملة الوصول إلى خصائصه خلال مرحلة التصميم. يتم ترجمة القيم المخصصة لهذه الخصائص على الفور إلى سمات علامة المكون في كود العرض. خلال مرحلة وقت التشغيل، سيتحكم كود التحكم في الصفحة في المكون ويخصص قيمًا لبعض خصائصه. سيقوم كود العرض بإنشاء كود HTML الخاص بالمكون باستخدام، من ناحية، السمات المحددة أثناء التصميم لعلامة الخادم المقابلة، ومن ناحية أخرى، قيم خصائص المكون المحسوبة بواسطة كود التحكم.
7.2. سياق تنفيذ الأمثلة
سنوضح تصميم واجهات الويب استنادًا إلى مكونات الخادم باستخدام برامج سيكون سياق تنفيذها في الغالب كما يلي:
- سيتألف تطبيق الويب من صفحة واحدة P تحتوي على نموذج F،
- سيقوم العميل بتقديم طلبه الأول مباشرة إلى هذه الصفحة P. وسيتضمن ذلك طلب عنوان URL للصفحة P باستخدام متصفح. وبالتالي، سيتم إرسال طلب GET إلى عنوان URL P هذا. سيقوم الخادم بتسليم الصفحة P وبالتالي النموذج F الذي تحتوي عليه،
- سيقوم المستخدم بملئه وإرساله، أي أنه سيقوم بإجراء يفرض على المتصفح إرسال النموذج F إلى الخادم. ستوجه عملية POST للمتصفح دائمًا إلى الصفحة P. سيقوم الخادم مرة أخرى بتسليم الصفحة P مع النموذج F، الذي قد يكون محتواه قد تم تعديله بفعل إجراء المستخدم.
- ثم ستستأنف الخطوتان 2 و 3.
هذه عملية تنفيذ محددة للغاية، وخارجها لا تعمل بعض المفاهيم التي سنناقشها أدناه. لم نعد في سياق بنية MVC حيث يتم التحكم في تطبيق متعدد الصفحات بواسطة صفحة محددة أطلقنا عليها اسم "وحدة التحكم في التطبيق". في هذا النوع من البنية، تستهدف عمليات إرسال النماذج (POST) وحدة التحكم وليس النماذج نفسها. ومع ذلك، سنرى أن إنشاء نموذج بمكونات من جانب الخادم يعني أن هذا النموذج يتم إرساله إلى نفسه.
7.3. مكون التسمية
7.3.1. الاستخدام
تسمح لك علامة <asp:label> بإدراج نص ديناميكي في كود العرض للصفحة. وبالتالي، فهي لا تفعل أكثر من علامة <%=variable%> المستخدمة حتى الآن. ستساعدنا دراسة هذه العلامة الأولى على فهم كيفية عمل علامات الخادم. سننشئ صفحة تحتوي على قسم تحكم [form1.aspx.vb] وقسم عرض [form1.aspx]. الهدف هو عرض الوقت:

تمت تغطية هذه المسألة بالفعل في الفصل 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
' saves the current query in request.txt of the page folder
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
' sets time in 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]. نحصل على النتيجة التالية:

فيما يلي كود 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 التالي:
إنها الخاصية [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] يدويًا. هذه الطريقة مفيدة إذا كنت تعرف العلامات. يكفي عندئذٍ محرر نصوص بسيط لإنشاء واجهة المستخدم. للبدء، غالبًا ما تكون أداة تصميم رسومية مقترنة بإنشاء تلقائي للكود ضرورية لأنك لا تعرف صيغة العلامات التي تحتاجها. سنقوم الآن بإنشاء نفس التطبيق باستخدام أداة WebMatrix. بمجرد تشغيل WebMatrix، نختار خيار [File/New File]:

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


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

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

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

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

في علامة التبويب [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] لإظهار نافذة خصائصه في الزاوية اليمنى السفلية:

ندعو القارئ إلى مراجعة خصائص كائن [Label]. هناك خاصيتان مهمتان هنا:
- Text: هذا هو النص الذي يجب أن تعرضه التسمية — قمنا بتعيين السلسلة على فارغة (أي لا شيء)
- ID: هذا هو معرفه — قمنا بتعيينه على lblHeure
تبدو علامة التبويب [تصميم] الآن كما يلي:

ويصبح كود [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
' sets time in 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 تلقائيًا بالمعلمات التالية:

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

من الآن فصاعدًا، سنستخدم أداة WebMatrix بشكل أساسي لتسهيل تطوير واختبار البرامج القصيرة التي سنكتبها.
7.4. المكون الحرفي
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] في نموذج، مما يجلب معه معالجة أحداث مشابهة لتلك الموجودة في تطبيقات Windows. هذه هي النقطة التي نريد استكشافها أكثر هنا. نقوم بإنشاء الصفحة [form3.aspx] التالية:

تحتوي هذه الصفحة، التي تم إنشاؤها باستخدام WebMatrix، على ثلاثة مكونات:
رقم | الاسم | النوع | الخصائص | الدور |
1 | زر | text=Button1 | زر الإرسال | |
2 | زر | text=زر 2 | زر الإرسال | |
3 | تسمية | 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 من جانب الخادم—أي علامة HTML قياسية يمثلها كائن يمكن للوحدة التحكمية التعامل معه. سيتم إنشاء كود HTML لعلامة <form> بناءً على القيمة التي تخصصها الوحدة التحكمية لهذا الكائن.
دعونا نضيف الإجراء [Page_Init] إلى قسم وحدة التحكم في الكود، الذي يتعامل مع حدث [Init] للصفحة. سنضع الكود هناك الذي يحفظ طلب العميل في ملف [request.txt]. سنحتاج إلى هذا لفهم كيفية عمل الأزرار.
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' saves the current request in request.txt of the page folder
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]. نحصل على الصفحة التالية:

فيما يلي كود 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
تم تعيين سمتين: [method="post"] و [action="form3.aspx"]. هل يمكننا تعيين قيم مختلفة لهذه السمات؟ سنحاول توضيح هذه النقطة لاحقًا. لاحظ هنا أن النموذج سيتم إرساله إلى عنوان URL [form3.aspx]. كما تم تعيين سمتين أخريين [name، id]. في معظم الأحيان، يتم تجاهلهما. ومع ذلك، إذا كانت الصفحة تحتوي على كود JavaScript من جانب المتصفح، فإن السمة [name] لعلامة <form> تكون مفيدة.
- أصبحت علامات <asp:button> علامات زر [submit] في HTML. وبالتالي، سيؤدي النقر فوق أي من هذه الأزرار إلى تشغيل "إرسال" النموذج [_ctl10] إلى عنوان URL [form3.aspx].
- أصبحت علامة <asp:label> علامة HTML <span>
- تم إنشاء حقل مخفي [__VIEWSTATE] بقيمة غريبة:
يمثل هذا الحقل، في شكل مشفر، حالة النموذج المرسلة إلى العميل. تمثل هذه الحالة قيمة جميع مكوناته. نظرًا لأن [__VIEWSTATE] جزء من النموذج، فسيتم إرسال قيمته مع الباقي إلى الخادم. سيسمح هذا للخادم بمعرفة مكون النموذج الذي تغيرت قيمته، واتخاذ القرارات اللازمة إذا لزم الأمر. ستتخذ هذه القرارات شكل أحداث مثل "تغيرت قيمة مربع النص الذي يحمل الاسم الفلاني".
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 قياسي. الآن دعونا نضغط على زر [Button1] في الصفحة في المتصفح. يبدو أنه لم يحدث شيء. ومع ذلك، نحن نعلم أن النموذج قد تم إرساله. ويؤكد ذلك كود 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
- قيمة الزر الذي تم النقر عليه
إذا نقرنا على [Button2]، فإن القيم التي أرسلها المتصفح هي كما يلي:
تكون قيمة الحقل المخفي هي نفسها دائمًا، ولكن قيمة [Button2] هي التي تم إرسالها. وبالتالي، يمكن للخادم تحديد الزر الذي تم استخدامه. وسيستخدم هذا لتشغيل حدث يمكن للصفحة معالجته بمجرد تحميلها.
7.5.4. معالجة حدث النقر على كائن زر
دعونا نستعرض كيفية عمل صفحة .aspx. إنها كائن مشتق من فئة [Page]. دعونا نسمي الفئة المشتقة [aPage]. عندما يتلقى الخادم طلبًا لمثل هذه الصفحة، يتم إنشاء مثيل لكائن من النوع [unePage] عبر العملية new unePage(...). ثم يقوم الخادم بإنشاء حدثين يسميان [Init] و [Load] بهذا الترتيب. يمكن لكائن [unePage] معالجة هذين الحدثين من خلال توفير معالجي الأحداث [Page_Init] و [Page_Load]. سيتم إنشاء أحداث أخرى لاحقًا. ستتاح لنا الفرصة لمراجعتها لاحقًا. إذا كان طلب العميل هو POST، فسيقوم الخادم بإنشاء حدث [Click] للزر الذي أطلق هذا الطلب POST. إذا كانت فئة [unePage] قد وفرت معالجًا لهذا الحدث، فسيتم استدعاؤه. دعونا نستكشف هذه الآلية باستخدام WebMatrix. في علامة التبويب [Design] في [form3.aspx]، انقر نقرًا مزدوجًا على الزر [Button1]. سننتقل بعد ذلك تلقائيًا إلى علامة التبويب [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)
' click on button 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
يعرض الإجراء رسالة إعلامية في التسمية [lblInfo]. نواصل بنفس الطريقة بالنسبة للزر [Button2] للحصول على الصفحة الجديدة التالية [form3.aspx]:
<%@ Page Language="VB" %>
<script runat="server">
Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs)
' sauve la requte courante dans request.txt du dossier de la page
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)
' clic sur bouton 1
lblInfo.Text="Vous avez cliqué sur [Bouton1]"
End Sub
Sub Button2_Click(sender As Object, e As EventArgs)
' clic sur bouton 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] لتشغيل الكود ونحصل على الصفحة التالية:

إذا نظرنا إلى كود 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>
إذا نقرنا على [Button1]، فسنحصل على الاستجابة التالية:

رمز 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 المتبادلة.
- تتم معالجة الأحداث السابقة دائمًا على جانب الخادم. يمكن بالطبع معالجة النقر فوق زر بواسطة برنامج نصي JavaScript على جانب الخادم. ومع ذلك، لا يعد هذا حدثًا للخادم، ونحن نتعامل مع تقنية مستقلة عن ASP.NET.
متى تتم معالجة الأحداث، سواء على جانب الخادم (الأحداث المتعلقة بمكونات الخادم) أو على جانب المتصفح بواسطة نصوص JavaScript؟
لنأخذ مثال قائمة منسدلة. عندما يغير المستخدم العنصر المحدد فيها، قد تتم معالجة الحدث (تغيير العنصر المحدد) أو لا تتم، وإذا تمت معالجته، فيمكن معالجته في أوقات مختلفة.
- إذا كنت ترغب في معالجته على الفور، فهناك حلان:
- يمكن معالجته بواسطة المتصفح باستخدام نصوص JavaScript. لا يتدخل الخادم في هذه الحالة. لكي يكون ذلك ممكنًا، يجب أن تكون الصفحة قابلة لإعادة البناء باستخدام القيم الموجودة على الصفحة.
- يمكن معالجته بواسطة الخادم. ولهذا، هناك حل واحد فقط: يجب إرسال النموذج إلى الخادم للمعالجة. لذلك لدينا عملية [submit]. سنرى أنه في هذه الحالة، نستخدم مكون خادم يسمى [DropDownList] ونضبط سمة [AutoPostBack] الخاصة به على [true]. وهذا يعني أنه إذا تغير العنصر المحدد في القائمة المنسدلة، يجب إرسال النموذج على الفور إلى الخادم. في هذه الحالة، يقوم الخادم بإنشاء كود HTML لكائن [DropDownList] المرتبط بوظيفة JavaScript مسؤولة عن تنفيذ [submit] بمجرد حدوث حدث "تغيير العنصر المحدد". سيقوم هذا [submit] بإرسال النموذج إلى الخادم، وسيتم تضمين الحقول المخفية في الإرسال للإشارة إلى أن [post] نتج عن تغيير في التحديد في القائمة المنسدلة. ثم سيقوم الخادم بإنشاء حدث [SelectedIndexChanged]، الذي يمكن للصفحة معالجته.
- إذا كنت ترغب في معالجة الأمر ولكن ليس على الفور، فقم بتعيين السمة [AutoPostBack] لمكون الخادم [DropDownList] على [false]. في هذه الحالة، يقوم الخادم بإنشاء كود HTML القياسي لقائمة <select> الخاصة بكائن [DropDownList]، دون وظيفة JavaScript مرتبطة بها. لا يحدث شيء عندما يغير المستخدم التحديد في القائمة المنسدلة. ومع ذلك، عندما يرسل المستخدم النموذج باستخدام زر [submit]، على سبيل المثال، سيتمكن الخادم من التعرف على حدوث تغيير في التحديد. لقد رأينا بالفعل أن النموذج المرسل إلى الخادم يحتوي على حقل مخفي [__VIEWSTATE] يمثل، في شكل مشفر، حالة جميع العناصر في النموذج المرسل. عندما يتلقى الخادم النموذج الجديد الذي أرسله العميل، يمكنه التحقق مما إذا كان العنصر المحدد في القائمة المنسدلة قد تغير. إذا كان الأمر كذلك، فسيؤدي ذلك إلى تشغيل حدث [SelectedIndexChanged]، والذي يمكن للصفحة معالجته بعد ذلك. ولتمييز هذه الآلية عن الآلية السابقة، يقول بعض المؤلفين إن حدث "تغيير التحديد" يتم "تخزينه مؤقتًا" عندما يحدث في المتصفح. ولن تتم معالجته بواسطة الخادم إلا عندما يرسل المتصفح النموذج إليه، وغالبًا ما يكون ذلك بعد النقر على زر [إرسال].
- أخيرًا، إذا كنت لا ترغب في معالجة الحدث، فقم بتعيين السمة [AutoPostBack] لمكون الخادم [DropDownList] على [false] ولا تكتب معالجًا لحدث [SelectedIndexChanged] الخاص به.
بمجرد فهم آلية معالجة الأحداث، لن يقوم المطور بتصميم تطبيق ويب مثل تطبيق Windows. في الواقع، في حين أن تغيير التحديد في مربع القائمة المنسدلة داخل تطبيق Windows يمكن استخدامه لتحديث مظهر النموذج الذي يحتوي عليه على الفور، فمن المرجح أن يتردد المرء في معالجة هذا الحدث على الفور في تطبيق ويب إذا كان ينطوي على "إرسال" نموذج إلى الخادم — وبالتالي رحلة ذهاب وإياب بين العميل والخادم. لهذا السبب يتم تعيين الخاصية [AutoPostBack] لمكونات جانب الخادم على [false] بشكل افتراضي. علاوة على ذلك، لا يمكن استخدام آلية [AutoPostBack]، التي تعتمد على نصوص JavaScript التي يتم إنشاؤها تلقائيًا بواسطة خادم الويب في النموذج المرسل إلى العميل، إلا إذا كان المرء متأكدًا من أن متصفح العميل قد قام بتمكين تنفيذ نصوص JavaScript. لذلك غالبًا ما يتم إنشاء النماذج على النحو التالي:
- يتم تعيين خاصية [AutoPostBack] لمكونات النموذج من جانب الخادم على [false]
- يحتوي النموذج على زر واحد أو أكثر مسؤول عن تنفيذ عملية [POST]
- في كود وحدة التحكم بالصفحة، تكتب معالجات للأحداث التي تريد معالجتها فقط، وغالبًا ما يكون ذلك حدث [Click] على أحد الأزرار.
7.6. مكون TextBox
7.6.1. الاستخدام
تسمح لك العلامة <asp:TextBox> بإدراج حقل إدخال في كود عرض الصفحة. نقوم بإنشاء صفحة [form4.aspx] لتحقيق التخطيط التالي:

1234تحتوي هذه الصفحة، التي تم إنشاؤها باستخدام WebMatrix، على المكونات التالية:
رقم | الاسم | النوع | الخصائص | الدور |
1 | مربع النص | AutoPostback=true Text= | حقل الإدخال | |
2 | مربع نص | إعادة الإرسال التلقائي=false Text= | حقل الإدخال | |
3 | تسمية | text= | رسالة معلومات حول محتويات [TextBox1] | |
3 | تسمية | 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>
في علامة التبويب [تصميم]، انقر نقرًا مزدوجًا فوق المكون [TextBox1]. ثم يتم إنشاء الهيكل الأساسي لمعالج الحدث [TextChanged] لهذا الكائن (في علامة التبويب [الكل]):
<%@ 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)
' text change
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)
' sauve la requte courante dans request.txt du dossier de la page
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)
' changement de texte
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)
' changement de texte
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]. نحصل على الصفحة التالية:

فيما يلي كود 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]، يجب تنفيذ دالة JavaScript [_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]. يؤدي حدث "التغيير" في المتصفح إلى تشغيل الكود "__doPostBack('TextBox1','')". يمكننا استنتاج أنه في النموذج المرسَل، سيحتوي الحقل المخفي [__EventTarget] على القيمة 'TextBox1' وسيحتوي الحقل المخفي [__EventArgument] على القيمة ''. وهذا يسمح للخادم بتحديد المكون الذي أطلق POST.
- أنشأت علامة الخادم <asp:textbox id="TextBox2"...> علامة <input type="text" ...> قياسية لأن سمة [AutoPostBack] الخاصة بها لم تكن مضبوطة على [true].
- تشير علامة <form> إلى أن النموذج سيتم إرساله إلى [form4.aspx]:
دعونا نجري اختبارنا الأول. اكتب بعض النص في حقل الإدخال الأول:

ثم انقل المؤشر إلى حقل الإدخال الثاني. ستظهر صفحة جديدة على الفور:

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

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

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

ماذا حدث؟ أولاً، أرسل المتصفح النموذج. وينعكس ذلك في طلب العميل المخزن في [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 مرة أخرى:

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

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

ثم ضع المؤشر على الحقل رقم 1 وحركه مرة أخرى إلى الحقل رقم 2. لا تتغير الصفحة. لماذا؟ نظرًا لأننا لا نغير قيمة الحقل رقم 1، لا يحدث حدث [Change] للمتصفح عندما نغادر هذا الحقل. وبالتالي، لا يتم إرسال النموذج إلى الخادم. وبالتالي، لا يتغير شيء في الصفحة. إن حقيقة أن محتوى [lblInfo2] لا يتغير هي التي توضح لنا أنه لا يوجد POST. لو كان هناك POST، لكان الخادم قد اكتشف أن محتوى [TextBox2] قد تغير وكان يجب أن يعكس ذلك في [lblInfo2].
الدرس المستفاد من هذا المثال هو أنه لا فائدة من تعيين خاصية [AutoPostBack] لـ [TextBox] إلى [true]. فهذا يتسبب في رحلة ذهاب وإياب غير ضرورية بين العميل والخادم في معظم الأحيان.
7.6.3. دور حقل __VIEWSTATE
لقد رأينا أن الخادم يضع بشكل منهجي حقلًا مخفيًا يسمى __VIEWSTATE في النموذج الذي ينشئه. وقد لاحظنا أن هذا الحقل يمثل حالة النموذج وأنه إذا تم إرجاع هذا الحقل المخفي إلى الخادم، فيمكنه إعادة بناء القيمة السابقة للنموذج. حالة النموذج هي مجموع حالات مكوناته. يحتوي كل مكون على خاصية [EnableViewState] ذات قيمة منطقية تشير إلى ما إذا كان يجب وضع حالة المكون في الحقل المخفي [__VIEWSTATE] أم لا. بشكل افتراضي، يتم تعيين هذه الخاصية على [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 إلى الخادم، ونحصل على الاستجابة التالية:

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

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

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

هذا هو بالضبط ما حدث. يُظهر الوقت ومحتوى [lblInfo2] أن الإجراء [TextBox2_TextChanged] لم يتم تنفيذه. مع أخذ ذلك في الاعتبار، دعونا نفحص الخاصية [EnableViewState] لمكونات النموذج الأربعة:
نريد الحفاظ على حالة هذا المكون حتى يعرف الخادم ما إذا كان قد تغير أم لا | |
نفس | |
لا نريد الحفاظ على حالة هذا المكون. نريد إعادة حساب النص مع كل طلب POST جديد. إذا لم يتم إعادة حسابه، فيجب أن يكون فارغًا. يتم تحقيق كل هذا باستخدام [EnableViewState=false] | |
نفس |
تبدو صفحتنا الرئيسية الآن كما يلي:
...
<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>
...
دعونا نجري نفس سلسلة الاختبارات كما فعلنا من قبل. حيث حصلنا على الشاشة
الإصدار 1

، نحصل الآن على:
الإصدار 2

في هذه الخطوة، قمنا بتغيير محتوى الحقل 1 دون تغيير محتوى الحقل 2، مما يضمن عدم تنفيذ الإجراء [TextBox2_TextChanged] على جانب الخادم، مما يعني أن الحقل [lblInfo2] لم يتلقَ قيمة جديدة. ولذلك يتم عرضه بقيمته السابقة. في الإصدار 1 [EnableViewState=true]، كانت هذه القيمة السابقة هي القيمة التي تم إدخالها. في الإصدار 2 [EnableViewState=false]، هذه القيمة السابقة هي سلسلة فارغة.
في بعض الأحيان، لا يكون من الضروري الحفاظ على الحالة السابقة للمكونات. بدلاً من تعيين [EnableViewState=false] لكل منها، يمكنك تحديد أن الصفحة يجب ألا تحافظ على حالتها. يتم ذلك في توجيه [Page] في كود العرض:
في هذه الحالة، بغض النظر عن قيمة خاصية [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=premier+texte --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=premier+texte --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]، قارن الخادم قيمته المنشورة [النص الثاني] بقيمته السابقة. في حالة عدم وجود [__VIEWSTATE]، تكون هذه القيمة السابقة مساوية للسلسلة الفارغة. ونظرًا لأن القيمة المنشورة لـ [TextBox2] تختلف عن السلسلة الفارغة، فقد تم تشغيل حدث [TextChanged] في [TextBox2]. تم تنفيذ الإجراء [TextBox2_TextChanged]، وتلقى الحقل [lblInfo2] قيمة جديدة.
قد يتساءل المرء عما إذا كانت المعلمات [__EVENTTARGET] و [__EVENTARGUMENT] مفيدة بالفعل. من خلال عدم إرسال هذه المعلمات، لن يعرف الخادم الحدث الذي أدى إلى تشغيل [submit]. دعونا نجرب:
dos>curl --include --url http://localhost/form4.aspx --data TextBox2=second+texte --data TextBox1=premier+texte
..............................
<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=second+texte --data TextBox1=premier+texte
.......
<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] أيضًا إنشاء علامات HTML <input type="password"..> و<textarea>..</textarea>، أي العلامات التي تتوافق على التوالي مع حقل كلمة المرور وحقل الإدخال متعدد الأسطر. يتم التحكم في هذا الإنشاء بواسطة خاصية [TextMode] لمكون [TextBox]. ولها ثلاث قيم ممكنة:
القيمة | علامة HTML التي تم إنشاؤها |
<input type="text" ...> | |
<textarea>...</textarea> | |
<input type="password ...> |
سنقوم بفحص استخدام هذه الخصائص من خلال المثال التالي [form5.aspx]:

رقم | name | النوع | الخصائص | الدور |
1 | زر | زر [submit] - يُستخدم لإضافة محتويات [TextBox1] إلى محتويات [TextBox2] | ||
2 | TextBox | TextMode=Password Text= | حقل إدخال محمي بكلمة مرور | |
3 | TextBox | TextMode=Multi-line Text= | يجمع الإدخالات التي تمت في [TextBox1] |
تم تعيين خاصية [EnableViewState] للصفحة على [false]. على جانب الخادم، نتعامل مع حدث النقر على الزر [btnAjouter]:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' the contents of [textBox1] are added to those of [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)
' on ajoute le contenu de [textBox1] à celui de [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] لإنتاج التخطيط التالي:

رقم | الاسم | نوع | الخصائص | الدور |
1 | قائمة منسدلة | إعادة الإرسال التلقائي=صحيح تمكين حالة العرض=صحيح | قائمة منسدلة | |
2 | تسمية | 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]:
مجموعة من النوع [ListItemCollection] تحتوي على العناصر الموجودة في القائمة المنسدلة. أعضاء هذه المجموعة من النوع [ListItem]. | |
عدد العناصر في مجموعة [Items] | |
العنصر رقم i في القائمة - من النوع [ListItem] | |
لإضافة عنصر جديد [ListItem] إلى مجموعة [Items] | |
لإزالة جميع العناصر من مجموعة [Items] | |
لإزالة العنصر رقم i من مجموعة [Items] | |
أول [ListItem] في مجموعة [Items] التي تكون خاصية [Selected] الخاصة بها صحيحة | |
فهرس عنصر [SelectedItem] في مجموعة [Items] |
العناصر الموجودة في مجموعة [Items] لفئة [DropDownList] هي من النوع [ListItem]. يولد كل عنصر [ListItem] علامة HTML <option>:
نصف بعض الخصائص والأساليب لفئة [ListItem]:
منشئ - ينشئ عنصر [ListItem] بخصائص [text] و [value]. سيقوم عنصر ListItem(T,V) بإنشاء علامة HTML <option value="V">T</option>. وبالتالي، تسمح لنا فئة [ListItem] بوصف عناصر قائمة HTML | |
من نوع boolean. إذا كانت القيمة true، فسيكون للخيار المقابل في قائمة HTML السمة [selected="selected"]. تخبر هذه السمة المتصفح بأن العنصر المقابل يجب أن يظهر محددًا في قائمة HTML | |
النص T للخيار HTML <option value="V" [selected="selected"]>T</option> | |
القيمة V لخاصية [Value] لخيار HTML <option value="V" [selected="selected"]>T</option> |
لدينا معلومات كافية لكتابة الكود لملء القائمة المنسدلة [DropDownList1] في إجراء [Page_Load] للصفحة:
Sub Page_Load(sender As Object, e As EventArgs)
' fill in the combo if it's the 1st time you've been called
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]. إذا تم تعيين هذه السمة على true، فإننا نعلم أن حالة مكون [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] على true. وهذا يعني أن المتصفح سيرسل النموذج بمجرد اكتشافه لحدث "تغيير العنصر المحدد" في القائمة المنسدلة. وبدوره، سيكتشف الخادم — باستخدام [VIEWSTATE] والقيم المرسلة — أن العنصر المحدد في مكون [DropDownList1] قد تغير. ثم سيقوم بتشغيل حدث [SelectedIndexChanged] على هذا المكون. وسنتعامل مع ذلك من خلال الإجراء التالي:
Sub DropDownList1_SelectedIndexChanged(sender As Object, e As EventArgs)
' selection change
lblInfo.text="Elément sélectionné : texte="+dropdownlist1.selecteditem.text+ _
" valeur=" + dropdownlist1.selecteditem.value + _
" index="+ dropdownlist1.selectedindex.tostring
End Sub
عند تشغيل هذا الإجراء، يكون الكائن [DropDownList1] قد استرد عناصر [ListItem] الخاصة به عبر [VIEWSTATE]. علاوة على ذلك، يتم تعيين السمة [Selected] لأحد عناصر [ListItem] هذه على "true" — وهو العنصر الذي تم إرسال قيمته بواسطة المتصفح. هناك عدة طرق للوصول إلى هذا العنصر:
هو أول عنصر [ListItem] في القائمة الذي تم تعيين سمة [Selected] الخاصة به على true | |
يتوافق مع جزء [text] من علامة HTML لعنصر <option value="...">text</option> الذي حدده المستخدم | |
يتوافق مع جزء [value] من علامة HTML لعنصر <option value="...">text</option> الذي حدده المستخدم | |
الفهرس في مجموعة [DropDownList1.Items] للعنصر [ListItem] الأول الذي تكون سمة [Selected] الخاصة به صحيحة |
الرمز النهائي لـ [form6.aspx] هو كما يلي:
<%@ Page Language="VB" %>
<script runat="server">
Sub Page_Load(sender As Object, e As EventArgs)
' on remplit le combo si c'est la 1ère fois qu'on est appelé
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)
' changement de sélection
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] للحصول على التخطيط التالي:

رقم | الاسم | النوع | الخصائص | الدور |
1 | مربع نص | EnableViewState=false | حقل الإدخال | |
2 | زر | زر [submit] الذي ينقل محتويات txtSaisie إلى القائمة 1 إذا لم تكن فارغة. | ||
3 | ListBox | EnableViewState=true SelectionMode=Single | قائمة القيم للاختيار الفردي | |
4 | ListBox | EnableViewState=true SelectionMode=Multiple | قائمة القيم للاختيار المتعدد | |
5 | زر | زر [إرسال] الذي ينقل العنصر المحدد من [القائمة 1] إلى [القائمة 2] | ||
6 | زر | زر [إرسال] الذي ينقل العناصر المحددة من [القائمة 2] إلى [القائمة 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]. تظهر خاصية جديدة:
يحدد وضع التحديد لقائمة HTML <select> التي سيتم إنشاؤها من المكون. إذا كان SelectionMode=Single، فيمكن تحديد عنصر واحد فقط. إذا كان SelectionMode=Multiple، فيمكن تحديد عناصر متعددة. لتحقيق ذلك، سيتم إنشاء السمة [multiple="multiple"] في علامة <select> لقائمة HTML. |
دعونا نتعامل مع الأحداث. سيتم التعامل مع النقر على زر [Add] بواسطة الإجراء [btnAdd_Click] التالي:
Sub btnAjouter_Click(sender As Object, e As EventArgs)
' added to list 1
dim texte as string=txtSaisie.text.trim
if texte<> "" then ListBox1.Items.Add(New ListItem(texte))
' raz 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>]، أي أن النص [T] للخيار يُستخدم لتشكيل قيمة الخيار. بمجرد إضافة محتوى [txtSaisie] إلى قائمة [ListBox1]، يتم مسح حقل [txtSaisie].
سيتم التعامل مع النقرات على أزرار [Clear] من خلال الإجراءات التالية:
Sub btnRaz1_Click(sender As Object, e As EventArgs)
' raz list 1
ListBox1.Items.Clear
End Sub
Sub btnRaz2_Click(sender As Object, e As EventArgs)
' raz list 2
ListBox2.Items.Clear
End Sub
يتم التعامل مع النقرات على الأزرار الخاصة بالنقل بين القوائم من خلال الإجراءات التالية:
Sub btn1vers2_Click(sender As Object, e As EventArgs)
' transfer the item selected in list 1 to list 2
transfert(ListBox1,ListBox2)
End Sub
Sub btn2vers1_Click(sender As Object, e As EventArgs)
' transfer of item selected in list 2 to list 1
transfert(ListBox2,ListBox1)
End Sub
sub transfert(l1 as listbox, l2 as listbox)
' transfer items selected in l1 to l2
' anything to do?
if l1.selectedindex=-1 then return
dim i as integer
' we start at the end
for i=l1.items.count-1 to 0 step -1
' selected?
if l1.items(i).selected then
' more selected
l1.items(i).selected=false
' transfer to l2
l2.items.add(l1.items(i))
' deletion in 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] للحصول على التخطيط التالي:

رقم | الاسم | النوع | الخصائص | الدور |
1 | زر الاختيار | RadioButton1.Checked = true RadioButton1.Text = 1 RadioButton2.Checked = false RadioButton2.Text = 2 RadioButton3.Checked = false RadioButton3.Text = 3 بالنسبة لجميع الأزرار الثلاثة: GroupName = radio | أزرار الاختيار | |
2 | مربع الاختيار | Checked=false للجميع نص مربع الاختيار A = A نص مربع الاختيار B = B نص مربع الاختيار C = C | مربعات الاختيار | |
3 | زر | زر [إرسال] | ||
4 | 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>
<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]، التي تكون قيمتها "صحيح" (true) إذا كان المربع محددًا، و"خطأ" (false) في الحالات الأخرى. لذلك، نحتاج ببساطة إلى كتابة قيمة السمة [Checked] للأزرار الستة ومربعات الاختيار في قائمة [lstInfos]. وبما أنه لا توجد صعوبة خاصة في القيام بذلك، فلنجرب شيئًا مختلفًا قليلاً:
<script runat="server">
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' place information in the listbox
for each c as control in FindControl("frmControls").controls
' is the control derived from 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 هي التي تظهر في العلامات:
إذا تجاهلنا عناصر التحكم [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]، إن وجدت | |
تُرجع عنصر التحكم المحدد بواسطة ID الموجود في جذر شجرة عناصر التحكم الفرعية لـ [Control]. في المثال أعلاه: تشير Page.FindControl("frmControls") إلى الحاوية [HtmlForm]. للوصول إلى زر الاختيار [RadioButton1]، يجب كتابة Page.FindControl("frmControls").FindControl("RadioButton1") | |
معرف [Control] |
لنعد إلى كود الإجراء [btnEnvoyer_Click]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' place information in the listbox
for each c as control in FindControl("frmControls").controls
' is the control derived from 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].
مجموعة من النوع [ListItemCollection] تحتوي على العناصر الموجودة في القائمة المنسدلة. أعضاء هذه المجموعة من النوع [ListItem]. | |
عدد العناصر في مجموعة [Items] | |
العنصر رقم i في القائمة - من النوع [ListItem] | |
لإضافة عنصر جديد [ListItem] إلى مجموعة [Items] | |
لإزالة جميع العناصر من مجموعة [Items] | |
لإزالة العنصر رقم i من مجموعة [Items] | |
أول [ListItem] في مجموعة [Items] التي تكون خاصية [Selected] الخاصة بها صحيحة | |
فهرس العنصر السابق في مجموعة [Items] |
بعض الخصائص خاصة بفئتي [CheckBoxList] و [RadioButtonList]:
[horizontal] أو [vertical] للقوائم الأفقية أو الرأسية. |
العناصر الموجودة في مجموعة [Items] هي من النوع [ListItem]. سيقوم كل عنصر [ListItem] بإنشاء علامة مختلفة اعتمادًا على ما إذا كان كائن [CheckBoxList] أو [RadioButtonList]:
أو
نصف بعض خصائص وأساليب فئة [ListItem]:
المنشئ - ينشئ عنصر [ListItem] بخصائص النص والقيمة. سيقوم عنصر ListItem(T,V) بإنشاء علامة HTML <input type="checkbox" value="V">T أو <input type="radio" value="V">T، حسب الاقتضاء. | |
Boolean. إذا كانت القيمة true، فسيكون للخيار المقابل في قائمة HTML السمة [selected="selected"]. تخبر هذه السمة المتصفح بأن العنصر المقابل يجب أن يظهر محددًا في قائمة HTML | |
النص T لخيار HTML <input type=".." value="V" [selected="selected"]>T | |
قيمة السمة Value للخيار HTML <input type=".." value="V" [selected="selected"]>T |
نقترح إنشاء الصفحة التالية [form8b.aspx]:

رقم | الاسم | النوع | الخصائص | الدور |
1 | قائمة أزرار الاختيار | EnableViewState=true اتجاه التكرار=أفقي | قائمة أزرار الاختيار | |
2 | CheckBoxList | تمكين حالة العرض=صحيح اتجاه التكرار=أفقي | قائمة مربعات الاختيار | |
3 | زر | زر [إرسال] يعرض في [4] قائمة العناصر المحددة من كلا القائمتين | ||
4 | مربع القائمة | 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
' fill in the lists if it's the 1st time you're called
if not IsPostBack then
' texts for the RadioButton list
dim textesRadio() as String = {"1","2","3","4"}
' texts for the CheckBox list
dim textesCheckBox() as String = {"un","deux","trois","quatre"}
' radio list filling
dim i as integer
for i=0 to textesRadio.length-1
RadioButtonList1.Items.Add(new ListItem(textesRadio(i)))
next
' selection item no. 1
RadioButtonList1.SelectedIndex=1
' checkbox list filling
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)
' place information in the lstinfos listbox
affiche(RadioButtonList1)
affiche(CheckBoxList1)
End Sub
sub affiche(l1 as ListControl)
' displays the values of selected elements of l1
' anything to do?
if l1.selectedindex=-1 then return
dim i as integer
' we start at the end
for i= 0 to l1.items.count-1
' selected?
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]. وبمجرد عرض الصفحة، يقوم المستخدم بتحديد مربعات معينة والنقر فوق الزر [Submit]. ثم يتم إرسال قيم النموذج إلى النموذج نفسه. بعد تنفيذ [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] لإنتاج التخطيط التالي:

لا. | الاسم | النوع | الخصائص | الدور |
1 | لوحة | EnableViewState=true | حاوية التحكم | |
2 | ListBox | EnableViewState=true | قائمة من ثلاث قيم | |
3 | زر الارتباط | EnableViewState=false | رابط لإخفاء الحاوية |
عندما يتم إخفاء الحاوية، يظهر رابط جديد:

لا | الاسم | النوع | الخصائص | الدور |
4 | زر الارتباط | 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] بثلاث قيم. ومعالجات أحداث [Click] للرابطين هي كما يلي:
<%@ 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)
' displays container 1
panel1.Visible=true
' changing links
lnkVoir.visible=false
lnkCacher.visible=true
End Sub
Sub lnkCacher_Click(sender As Object, e As EventArgs)
' hides container 1
panel1.Visible=false
' changing links
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)
' the 1st time
if not IsPostBack then
' the container is shown
lnkVoir_Click(nothing,nothing)
end if
end sub
.....
</script>
7.12. لمتابعة...
قدمت الفقرات السابقة عددًا من المكونات من جانب الخادم. ولم يتم تناول سوى عدد قليل من خصائصها في كل قسم. لاستكشاف هذه المكونات بمزيد من التعمق، يمكن للقارئ المضي قدمًا بعدة طرق:
- استكشاف خصائص المكون باستخدام بيئة تطوير متكاملة (IDE) مثل WebMatrix. تعرض هذه الأداة الخصائص الرئيسية للمكونات المستخدمة في النموذج
- الرجوع إلى وثائق .NET لاستكشاف جميع الفئات المطابقة لكل مكون من مكونات الخادم. هذه هي الطريقة المفضلة لاكتساب إتقان كامل للمكون. هناك، ستجد التسلسل الهرمي للفئات المؤدي إلى المكونات، بالإضافة إلى الخصائص والأساليب والمُنشِئات والأحداث لكل منها. بالإضافة إلى ذلك، توفر الوثائق أحيانًا أمثلة.
في هذا الفصل، استخدمنا نهج [WebMatrix] الشامل، مما يعني أننا وضعنا كود العرض وكود التحكم لصفحة ما في نفس الملف. بشكل عام، لا نوصي بهذه الطريقة بل نوصي بنهج [codebehind] المستخدم سابقًا، والذي يضع هذين النوعين من الكود في ملفين منفصلين. تجدر الإشارة إلى أن ميزة هذا الفصل تكمن في حقيقة أنه يمكن ترجمة كود التحكم دون الحاجة إلى تشغيل تطبيق الويب. علاوة على ذلك، كما أوضحنا في بداية هذا الفصل، كانت أمثلةنا ذات بنية محددة للغاية: فقد كانت تتألف من صفحة واحدة تعمل كنموذج يتم تبادله بين العميل والخادم في دورات متتالية من الطلب والاستجابة، حيث يكون طلب العميل الأول هو طلب GET والطلبات اللاحقة هي طلبات POST.
7.13. مكونات الخادم ووحدة التحكم في التطبيق
في الفصول السابقة، قمنا بإنشاء العديد من تطبيقات الويب. وقد تم إنشاؤها جميعًا باستخدام بنية MVC (النموذج-العرض-وحدة التحكم)، التي تقسم التطبيق إلى كتل منفصلة وتسهل عملية الصيانة. كنا في السابق نبني واجهات المستخدم الخاصة بنا باستخدام علامات HTML القياسية. وبالنظر إلى ما تناولناه للتو، فمن الطبيعي أن نرغب الآن في استخدام مكونات الخادم. دعونا نعود إلى مشكلة سبق أن درسناها باستفاضة: حساب الضرائب. وكانت بنية MVC الخاصة بها على النحو التالي:

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

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

وينقر على الزر [Calculate] للحصول على الاستجابة التالية:

في تطبيق MVC، يجب أن يمر كل طلب عبر وحدة التحكم، وهي في هذه الحالة [main.aspx]. وهذا يعني أنه بمجرد أن يملأ المستخدم النموذج [form.aspx]، يجب إرساله إلى [main.aspx] وليس إلى [form.aspx]. وهذا ببساطة غير ممكن إذا قمنا ببناء واجهة المستخدم [form.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>. دعونا نشغل هذا التطبيق. تظهر صفحة العرض زرًا واحدًا فقط:

دعونا نلقي نظرة على كود 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"]، في حين أننا كتبنا علامة HTML من جانب الخادم في [formtest.aspx]:
يُعد السمة [runat="server"] لعلامة <form> مطلوبة عند استخدام مكونات جانب الخادم. يحدث خطأ في التجميع إذا لم نقم بتضمين هذه السمة. وعندما نقوم بتضمينها، يتم تجاهل السمة [action] لعلامة <form>. يقوم الخادم دائمًا بإنشاء سمة [action] تشير إلى النموذج نفسه. يمكننا أن نستنتج أنه في تطبيق MVC، لا يمكننا استخدام النماذج التي تم إنشاؤها باستخدام علامة <form ... runat="server">. ومع ذلك، فإن هذه العلامة ضرورية لجميع مكونات خادم ASP التي تسترد مدخلات المستخدم. بعبارة أخرى، لا يمكننا استخدام نماذج خادم ASP في تطبيق MVC. وهذا اكتشاف مهم. في الواقع، تتمثل إحدى الميزات الرئيسية لـ ASP.NET في أنه يمكنك إنشاء تطبيق ويب مثل تطبيق Windows. وهذا صحيح إذا كان تطبيقك لا يتبع بنية MVC، ولكنه أقل صحة في الحالات الأخرى. ومع ذلك، تبدو بنية MVC مفهومًا أساسيًا في تطوير الويب الحديث يصعب تجاهله.
من الممكن استخدام بنية MVC بالاقتران مع نماذج مكونات ASP للتطبيقات التي تحتوي على عدد قليل من طرق العرض المختلفة باستخدام الحل البديل التالي:
- يتكون التطبيق من صفحة واحدة تعمل كوحدة تحكم
- يتم تمثيل طرق العرض على هذه الصفحة بواسطة حاويات مختلفة، حاوية واحدة لكل طريقة عرض. لعرض طريقة عرض، نجعل الحاوية الخاصة بها مرئية ونخفي الحاويات الأخرى
هذا حل أنيق سنقوم الآن بتطبيقه في بعض الأمثلة
7.14. أمثلة على تطبيقات MVC مع مكونات خادم ASP
7.14.1. المثال 1
في هذا المثال الأول، نقوم بتنفيذ مكونات الخادم التي قدمناها. ستبدو صفحة [form10.aspx] كما يلي:
![]() | ![]() |
تُظهر لقطة الشاشة الموجودة أعلى اليسار النموذج كما يظهر للمستخدم. يقوم المستخدم بملئه وإرساله بالنقر فوق [Submit]. يعرض الخادم عرضًا يظهر قائمة بالقيم التي تم إدخالها (لقطة الشاشة اليمنى). يتيح رابط للمستخدم العودة إلى النموذج. ويراه تمامًا كما أرسله. فيما يلي كود العرض لـ [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] هي كما يلي:
name | النوع | الخصائص | الدور |
لوحة | EnableViewState=true | عرض النموذج | |
زر الاختيار | EnableViewState=true GroupName=rdmarie | أزرار الاختيار | |
مربع الاختيار | EnableViewState=true | مربعات الاختيار | |
مربع النص | EnableViewState=true | حقل الإدخال | |
مربع نص | EnableViewState=true | حقل إدخال محمي | |
مربع نص | EnableViewState=true | مربع نص متعدد الأسطر | |
قائمة منسدلة | EnableViewState=true | قائمة منسدلة | |
ListBox | EnableViewState=true SelectionMode=Single | قائمة الاختيار الفردي | |
زر | EnableViewState=false | إلغاء تحديد جميع العناصر في lstSimple | |
مربع القائمة | EnableViewState=true SelectionMode=Multiple | قائمة التحديد المتعدد | |
زر | EnableViewState=false | إلغاء تحديد جميع العناصر في lstMultiple | |
تسمية | EnableViewState=true مرئي=كاذب | حقل مخفي | |
HTML قياسي | يعرض تنبيهًا | ||
زر | EnableViewState=false | زر [submit] في النموذج | |
HTML قياسي | زر [reset] في النموذج |
يعد دور [VIEWSTATE] للمكونات مهمًا هنا. يجب أن تحتوي جميع المكونات باستثناء الأزرار على الخاصية [EnableViewState=true]. لفهم السبب، نحتاج إلى تذكر كيفية عمل التطبيق. لنفترض أن الحقل [txtSaisie] يحتوي على الخاصية [EnableViewState=false]:
- يطلب العميل صفحة [form10.aspx] لأول مرة. يتلقى عرض النموذج
- ، ويملؤه، ويقوم بإرساله باستخدام زر [Submit]. ثم يتم إرسال حقول الإدخال، ويقوم الخادم بتعيين القيمة المرسلة أو [VIEWSTATE] الخاصة بها إلى المكونات من جانب الخادم، إذا كان لديها واحدة. وبالتالي، يتم تعيين القيمة التي أدخلها المستخدم لحقل [txtSaisie]. لذلك، في هذه الخطوة، لا يخدم [VIEWSTATE] الخاص به أي غرض. ونتيجة لهذه العملية، يتم إرسال عرض [informations]—الذي لا يزال في الواقع هو صفحة [form10.aspx] ولكن مع حاوية عرض مختلفة.
- يقوم المستخدم بعرض هذه الصفحة الجديدة ويستخدم رابط [العودة إلى النموذج] للعودة إليها. ثم يتم إرسال طلب POST إلى [form10.aspx]. ولا يوجد سوى قيمة واحدة مرسلة على الأكثر: وهي القيمة التي اختارها المستخدم من قائمة المعلومات، والتي لا يتم استخدامها لاحقًا. وعلى أي حال، لا يوجد حقل [txtSaisie] مرسَل.
- يتلقى الخادم طلب POST ويقوم بتعيين القيمة المرسلة إلى مكونات الخادم أو [VIEWSTATE] الخاصة بها إذا كان لديها واحدة. هنا، لا تحتوي [txtNom] على أي قيمة مرسلة. إذا تم تعيين سمة [EnableViewState] الخاصة بها على [false]، فسيتم تعيين السلسلة الفارغة لها. نظرًا لأننا نريدها أن تحتوي على القيمة التي أدخلها المستخدم، فيجب أن تحتوي على الخاصية [EnableViewState=true].
يحتوي الحاوية [panelinfo] على عناصر التحكم التالية:
name | type | الخصائص | role |
لوحة | EnableViewState=false | عرض المعلومات | |
مربع القائمة | EnableViewState=false | قائمة بالمعلومات تلخص القيم التي أدخلها المستخدم | |
زر الارتباط | EnableViewState=false | رابط للعودة إلى النموذج |
أثناء الاختبار، إذا نظرنا إلى كود HTML الذي تم إنشاؤه بواسطة كود العرض أعلاه، فقد نتفاجأ بالكود الذي تم إنشاؤه للحقل المخفي [lblSecret]:
لا يتم عرض المكون [lblSecret] ككود HTML لأنه يحتوي على الخاصية [Visible=false]. ومع ذلك، نظرًا لأنه يحتوي على الخاصية [EnableViewState=true]، فسيظل قيمته مخزنة في الحقل المخفي [__VIEWSATE]. لذلك، سنتمكن من استرداده، كما ستوضح الاختبارات.
ما زلنا بحاجة إلى كتابة معالجات الأحداث. في [Page_Load]، سنقوم بتهيئة النموذج:
Sub page_Load(sender As Object, e As EventArgs)
' the 1st time, we initialize the elements
' subsequent times, they are reset to their values by VIEWSTATE
if IsPostBack then return
' init form
' panelinfo not displayed
panelinfo.visible=false
' paneform displayed
panelform.visible=true
' radio buttons
rdNon.Checked=true
' checkboxes
chk2.Checked=true
' input field
txtSaisie.Text="qqs mots"
' password field
txtMdp.Text="ceciestsecret"
' input box
txtArea.Text="ligne"+ControlChars.CrLf+"ligne2"+ControlChars.CrLf
' combo
dim i as integer
for i=1 to 4
cmbValeurs.Items.Add(new ListItem("choix"+i.ToString,i.ToString))
next
cmbValeurs.SelectedIndex=1
' simple selection list
for i=1 to 7
lstSimple.Items.Add(new ListItem("simple"+i.ToString,i.ToString))
next
lstSimple.SelectedIndex=0
' multiple selection list
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
' hidden field
lblSecret.Text="secret"
End Sub
النقر على الزرين [lstRazSimple] و [lstMultiple]:
Sub btnRazSimple_Click(sender As Object, e As EventArgs)
' raz single list
lstSimple.SelectedIndex=-1
End Sub
Sub razMultiple_Click(sender As Object, e As EventArgs)
' raz multiple list
lstMultiple.SelectedIndex=-1
End Sub
النقر على زر [إرسال]:
Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
' the info panel is made visible and the form panel is hidden
panelinfo.Visible=true
panelform.visible=false
' we retrieve the posted values and put them in lstInfos
' radio buttons
dim info as string="état marital : "+iif(rdoui.checked,"marié"," non marié")
affiche(info)
' checkboxes
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)
' input field
affiche("champ de saisie : " + txtSaisie.Text.Trim)
' password
affiche("mot de passe : " + txtMdp.Text.Trim)
' input box
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))
' combo
affiche("éléments sélectionnés dans combo : "+selection(cmbValeurs))
' simple list
affiche("éléments sélectionnés dans liste simple : "+selection(lstSimple))
' multiple list
affiche("éléments sélectionnés dans liste multiple : "+selection(lstMultiple))
' hidden field
affiche ("Champ caché : " + lblSecret.Text)
End Sub
sub affiche(msg as String)
' displays msg in lstInfos
lstInfos.Items.Add(msg)
end sub
function selection(liste as ListControl) as string
' browse list elements
' to find those selected
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
أخيرًا، النقر على رابط [العودة إلى النموذج]:
Sub LinkButton1_Click(sender As Object, e As EventArgs)
' display the form and hide the info panel
panelform.visible=true
panelinfo.visible=false
End Sub
7.14.2. المثال 2
هنا، نعيد النظر في تطبيق تمت تغطيته سابقًا يستخدم نماذج HTML قياسية. يتيح التطبيق للمستخدمين إجراء محاكاة لحساب الضرائب. يعتمد التطبيق على فئة [impot]، والتي لن نعيد النظر فيها هنا. تتطلب هذه الفئة بيانات يتم استردادها من مصدر بيانات OLEDB. في هذا المثال، سنستخدم مصدر بيانات ACCESS.
7.14.2.1. هيكل MVC للتطبيق
هيكل MVC للتطبيق هو كما يلي:

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

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

يستخدم زر [إرسال] لطلب حساب الضريبة. يظهر له عرض [المحاكاة] التالي:

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

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

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

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

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

7.14.2.3. كود عرض التطبيق
تذكر أن صفحة [main.aspx] تجمع جميع طرق العرض. وهي عبارة عن نموذج واحد يحتوي على ثلاثة حاويات:
- [panelform] لعرض [form]
- [panelerrors] لعرض [الأخطاء]
- [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]:
name | النوع | الخصائص | الدور |
لوحة | EnableViewState=true | عرض النموذج | |
زر الاختيار | EnableViewState=true GroupName=rdmarie | أزرار الاختيار | |
مربع النص | EnableViewState=true | عدد الأطفال | |
مربع نص | EnableViewState=true | الراتب السنوي | |
زر | زر [submit] في النموذج - يبدأ حساب الضريبة | ||
زر | زر [submit] في النموذج - يمسح النموذج |
حاوية [errorPanel]:
الاسم | النوع | الخصائص | الدور |
لوحة | EnableViewState=true | عرض الخطأ | |
زر الارتباط | EnableViewState=true | رابط النموذج | |
نص | كود HTML لقائمة الأخطاء |
[panelsimulations] الحاوية:
الاسم | النوع | الخصائص | الدور |
لوحة | EnableViewState=true | عرض المحاكاة | |
زر الارتباط | EnableViewState=true | رابط إلى النموذج | |
نص | كود HTML لقائمة المحاكاة في جدول HTML |
7.14.2.4. رمز التحكم في التطبيق
يتم توزيع كود التحكم في التطبيق عبر ملفات [global.asax.vb] و [main.aspx.vb]. يتم تعريف ملف [global.asax] على النحو التالي:
ملف [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)
' create an impot object
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' put the object in the application
Application("objImpot") = objImpot
' no error
Application("erreur") = False
Catch ex As Exception
'there has been an error, we note it in the application
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' start of session - create a list of empty simulations
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>
يحدد المفتاح [connectionString] سلسلة الاتصال بمصدر 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
' local variables
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
' first, we look at the state of the application
If CType(Application("erreur"), Boolean) Then
' the application failed to initialize
' the error view is displayed
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
afficheErreurs(erreurs, "")
Exit Sub
End If
' no errors - on the 1st request, the form is presented
If Not IsPostBack Then afficheFormulaire()
End Sub
تذكر أنه عند تشغيل الإجراء [Page_Load] على طلب POST من العميل، يكون لجميع مكونات النموذج قيمة: إما القيمة التي أرسلها العميل، إن وجدت، أو القيمة السابقة للمكون عبر [VIEWSTATE]. في هذا النموذج، تتمتع جميع المكونات بخاصية [EnableViewState=true]. قبل معالجة الطلب، نتأكد من أن التطبيق قد تم تهيئته بشكل صحيح. إذا لم يكن الأمر كذلك، نعرض عرض [errors] باستخدام الإجراء [displayErrors]. إذا كان هذا هو الطلب الأول (IsPostBack=false)، نعرض عرض [form] باستخدام [displayForm].
الإجراء الذي يعرض عرض [errors] هو كما يلي:
Private Sub afficheErreurs(ByRef erreurs As ArrayList, ByRef lien As String)
' displays the error container
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
' the other containers are hidden
panelform.Visible = False
panelsimulations.Visible = False
End Sub
يحتوي الإجراء على معلمتين:
- قائمة برسائل الخطأ في [errors]
- نص رابط في [link]
يتم وضع كود HTML المراد إنشاؤه لقائمة الأخطاء في المتغير الثابت [errorsHTML]. ويتم وضع نص الرابط في الخاصية [Text] للكائن [LinkButton] في العرض.
الإجراء الذي يعرض عرض [form] هو كما يلي:
Private Sub afficheFormulaire()
' displays the form
panelform.Visible = True
' the other containers are hidden
panelerreurs.Visible = False
panelsimulations.Visible = False
End Sub
هذا الإجراء يجعل حاوية [panelform] مرئية ببساطة. يتم عرض المكونات بقيمتها المنشورة أو السابقة (VIEWSTATE).
عندما ينقر المستخدم على الزر [Calculate] في عرض [form]، يتم إرسال طلب POST إلى [main.aspx]. يتم تنفيذ الإجراء [Page_Load]، يليه الإجراء [btnCalculate_Click]:
Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' check the validity of the data entered
Dim erreurs As ArrayList = checkData()
' if there are errors, we report them
If erreurs.Count <> 0 Then
' the error page is displayed
afficheErreurs(erreurs, "Retour au formulaire")
Exit Sub
End If
' no errors - tax is calculated
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
' the result is added to existing simulations
Dim simulation() As String = New String() {CType(IIf(rdOui.Checked, "oui", "non"), String), _
txtEnfants.Text.Trim, txtSalaire.Text.Trim, impot.ToString}
' the result is added to existing simulations
Dim simulations As ArrayList = CType(Session.Item("simulations"), ArrayList)
simulations.Add(simulation)
' put simulations in session and context
Session.Item("simulations") = simulations
' the result page is displayed
afficheSimulations(simulations, "Retour au formulaire")
End Sub
يبدأ الإجراء بالتحقق من صحة حقول النموذج باستخدام الإجراء [checkData]، الذي يُرجع [ArrayList] من رسائل الخطأ. إذا لم تكن القائمة فارغة، يتم عرض عرض [errors] وينتهي الإجراء. إذا كانت البيانات المدخلة صحيحة، يتم حساب مبلغ الضريبة باستخدام كائن [tax] الذي تم تخزينه في التطبيق عند بدء التشغيل. تُضاف هذه المحاكاة الجديدة إلى قائمة المحاكاة التي تم إجراؤها بالفعل وتخزينها في الجلسة.
تتحقق الدالة [CheckData] من صحة البيانات. وتُرجع قائمة [ArrayList] من رسائل الخطأ، والتي تكون فارغة إذا كانت البيانات صالحة:
Private Function checkData() As ArrayList
' initially no errors
Dim erreurs As New ArrayList
' no. of children
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
' salary
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 the list of errors
Return erreurs
End Function
أخيرًا، يتم عرض طريقة العرض [simulations] بواسطة الإجراء [displaySimulations] التالي:
Private Sub afficheSimulations(ByRef simulations As ArrayList, ByRef lien As String)
' displays the simulations view
panelsimulations.Visible = True
' the other containers are hidden
panelerreurs.Visible = False
panelform.Visible = False
' contents of simulations view
' each simulation is an array of 4 string elements
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
' link
lnkForm2.Text = lien
End Sub
يحتوي الإجراء على معلمتين:
- قائمة بالمحاكاة في [simulations]
- نص رابط في [link]
يتم تخزين كود HTML المراد إنشاؤه لقائمة عمليات المحاكاة في المتغير الثابت [simulationsHTML]. يتم تخزين نص الرابط في الخاصية [Text] للكائن [LinkButton] في طريقة العرض.
عندما ينقر المستخدم على الزر [Clear] في عرض [form]، يتم تنفيذ الإجراء [btnClear_click] (دائمًا بعد [Page_Load]):
Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
' displays the empty form
razForm()
afficheFormulaire()
End Sub
Private Sub razForm()
' empty the form
rdOui.Checked = False
rdNon.Checked = True
txtEnfants.Text = ""
txtSalaire.Text = ""
End Sub
الرمز أعلاه بسيط بما يكفي بحيث لا يحتاج إلى تعليق. ما زلنا بحاجة إلى التعامل مع النقرات على روابط عرض [errors] و [simulations]:
Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
' displays the form
afficheFormulaire()
End Sub
Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
' displays the form
afficheFormulaire()
End Sub
يعرض كلا الإجراءين ببساطة عرض [النموذج]. نحن نعلم أن حقول النموذج ستتلقى قيمة هي إما القيمة التي تم إرسالها لها أو قيمتها السابقة. وبما أن طلب POST الخاص بالعميل في هذه الحالة لا يرسل أي قيم لحقول النموذج، فإنها ستعود إلى قيمها السابقة. وبالتالي، يتم عرض النموذج بالقيم التي أدخلها المستخدم. نتذكر أنه في الإصدار الذي لا يحتوي على مكونات الخادم، قمنا بإجراء هذه الاستعادة بأنفسنا.
7.14.2.5. الاختبارات
يتم وضع جميع الملفات المطلوبة للتطبيق في مجلد باسم <application-path>: ![]() | يحتوي المجلد [bin] على ملف DLL الذي يحتوي على فئات [impot] و[impotsData] و[impotsOLEDB] التي يتطلبها التطبيق: |
يمكن للقارئ، إن رغب في ذلك، الرجوع إلى الفصل 5 الذي يشرح كيفية إنشاء ملف [impot.dll] المذكور أعلاه. وبمجرد الانتهاء من ذلك، يتم تشغيل خادم Cassini باستخدام المعلمات (<application-path>,/impots5). نطلب عنوان URL [http://impots5/main.aspx] باستخدام متصفح:

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

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

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

يعمل وحدة التحكم [main.aspx] كوحدة التحكم الرئيسية. وهي التي يتم استدعاؤها بواسطة الروابط الموجودة على الصفحة الرئيسية للتطبيق. ستقوم بتنفيذ العمليات المشتركة بين جميع الإجراءات الممكنة ثم تنفيذ الإجراء المحدد المرتبط بالرابط المستخدم. بعد ذلك، ستقوم بتسليم المهمة إلى إحدى وحدات التحكم الثانوية، وهي المسؤولة عن تنفيذ الإجراء. من هذه النقطة فصاعدًا، يتم الاتصال بين العميل ووحدة التحكم المحددة هذه. لم نعد نمر عبر وحدة التحكم الرئيسية [main.aspx]. وبالتالي، لم نعد في إطار عمل MVC الذي يحتوي على وحدة تحكم واحدة تقوم بتصفية جميع الطلبات. يمكن لكل وحدة من وحدات التحكم المذكورة أعلاه تقديم طرق عرض متعددة باستخدام آلية الحاوية داخل صفحة واحدة، كما وصفنا.
إن حقيقة أننا لم نعد نمتلك وحدة تحكم واحدة تختار العروض التي سيتم إرسالها إلى العميل لها عيوبها. خذ معالجة الأخطاء على سبيل المثال. قد تحتاج كل إجراء يعرضه التطبيق إلى عرض عرض خطأ. سيكون لكل وحدة تحكم [applix.aspx] عرض [errors] خاص بها لأن هذا ببساطة عبارة عن حاوية محددة داخل صفحة وحدة التحكم. لا توجد طريقة للحصول على عرض [errors] واحد يستخدمه جميع التطبيقات الفردية. في الواقع، يتضمن هذا العرض عادةً رابطًا للعودة إلى النموذج الذي يحتوي على الأخطاء، ويجب استعادة هذا النموذج إلى الحالة التي تم التحقق من صحتها فيها للسماح للمستخدم بتصحيح أخطائه. تتم هذه الاستعادة عبر آلية [VIEWSTATE]، التي لا تعمل عبر وحدات التحكم المختلفة. إذا تم تطوير التطبيقات بواسطة أشخاص مختلفين، فهناك خطر وجود صفحات أخطاء تبدو مختلفة اعتمادًا على الإجراء الذي يختاره المستخدم، مما يقوض اتساق التطبيق بشكل عام. سنرى لاحقًا أن ASP.NET يقدم حلاً لهذه المشكلة المحددة المتعلقة بمشاركة طرق العرض. يمكن تنفيذ ذلك كمكون خادم جديد نقوم ببنائه بأنفسنا. يضمن مجرد استخدام هذا المكون عبر التطبيقات المختلفة اتساق التطبيق ككل. أما المسألة الأكثر صعوبة في الإدارة فهي ترتيب الإجراءات. عندما تمر جميع الطلبات عبر وحدة تحكم واحدة، يمكنها التحقق من أن الإجراء المطلوب متوافق مع الإجراء السابق. يقع كود التحقق هذا في مكان واحد. هنا، سيتعين توزيعه عبر وحدات التحكم المختلفة، مما يعقد صيانة التطبيق ككل.
لنعد إلى تطبيقنا أعلاه. صفحة الدخول الخاصة به هي صفحة HTML قياسية [home.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] هي كما يلي:

وإليك كود 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]. وهي عبارة عن مجموعة من الروابط:
![]() | |
تشير جميع الروابط المختلفة إلى وحدة التحكم الرئيسية [main.aspx] مع معلمة [action] تشير إلى الإجراء المطلوب تنفيذه. نحدد أن هدف الرابط يجب أن يُعرض في الإطار [main] (target="main").
الصفحة الأولى التي يتم عرضها في الإطار [main] هي [main.htm]:
|
وحدة التحكم الرئيسية [main.aspx، main.aspx.vb] هي كما يلي:
[main.aspx]
[main.aspx.vb]
فئة عامة main
تورث <bdi dir="ltr" class="odt-ltr-term">System.Web.UI.Page</bdi>
<bdi dir="ltr" class="odt-ltr-term">Private Sub Page</bdi>\_Load(<bdi dir="ltr" class="odt-ltr-term">ByVal sender As System.Object, ByVal e As System.EventArgs</bdi>) <bdi dir="ltr" class="odt-ltr-term">Handles MyBase.Load</bdi>
' استرداد الإجراء المطلوب تنفيذه
<bdi dir="ltr" class="odt-ltr-term">Dim action As String</bdi>
إذا كان <bdi dir="ltr" class="odt-ltr-term">Request.QueryString</bdi>("<bdi dir="ltr" class="odt-ltr-term">action</bdi>") يساوي <bdi dir="ltr" class="odt-ltr-term">Nothing</bdi>
<bdi dir="ltr" class="odt-ltr-term">action</bdi> = "<bdi dir="ltr" class="odt-ltr-term">label</bdi>"
<bdi dir="ltr" class="odt-ltr-term">Else</bdi>
<bdi dir="ltr" class="odt-ltr-term">action</bdi> = <bdi dir="ltr" class="odt-ltr-term">Request.QueryString</bdi>("<bdi dir="ltr" class="odt-ltr-term">action</bdi>").<bdi dir="ltr" class="odt-ltr-term">ToString.ToLower</bdi>
<bdi dir="ltr" class="odt-ltr-term">End If</bdi>
' تنفيذ الإجراء
اختر الحالة <bdi dir="ltr" class="odt-ltr-term">action</bdi>
<bdi dir="ltr" class="odt-ltr-term">Case</bdi> "<bdi dir="ltr" class="odt-ltr-term">label</bdi>"
<bdi dir="ltr" class="odt-ltr-term">Server.Transfer</bdi>("<bdi dir="ltr" class="odt-ltr-term">form2.aspx</bdi>")
الحالة "زر"
<bdi dir="ltr" class="odt-ltr-term">Server.Transfer</bdi>("<bdi dir="ltr" class="odt-ltr-term">form3.aspx</bdi>")
الحالة "<bdi dir="ltr" class="odt-ltr-term">textbox1</bdi>"
<bdi dir="ltr" class="odt-ltr-term">Server.Transfer</bdi>("<bdi dir="ltr" class="odt-ltr-term">form4.aspx</bdi>")
حقل "<bdi dir="ltr" class="odt-ltr-term">textbox2</bdi>"
<bdi dir="ltr" class="odt-ltr-term">Server.Transfer</bdi>("<bdi dir="ltr" class="odt-ltr-term">form5.aspx</bdi>")
الحالة "<bdi dir="ltr" class="odt-ltr-term">dropdownlist</bdi>"
<bdi dir="ltr" class="odt-ltr-term">Server.Transfer</bdi>("<bdi dir="ltr" class="odt-ltr-term">form6.aspx</bdi>")
الحالة "<bdi dir="ltr" class="odt-ltr-term">listbox</bdi>"
<bdi dir="ltr" class="odt-ltr-term">Server.Transfer</bdi>("<bdi dir="ltr" class="odt-ltr-term">form7.aspx</bdi>")
الحالة "<bdi dir="ltr" class="odt-ltr-term">checkbox</bdi>"
<bdi dir="ltr" class="odt-ltr-term">Server.Transfer</bdi>("<bdi dir="ltr" class="odt-ltr-term">form8.aspx</bdi>")
الحالة "<bdi dir="ltr" class="odt-ltr-term">checkboxlist</bdi>"
<bdi dir="ltr" class="odt-ltr-term">Server.Transfer</bdi>("<bdi dir="ltr" class="odt-ltr-term">form8b.aspx</bdi>")
الحالة "<bdi dir="ltr" class="odt-ltr-term">panel</bdi>"
<bdi dir="ltr" class="odt-ltr-term">Server.Transfer</bdi>("<bdi dir="ltr" class="odt-ltr-term">form9.aspx</bdi>")
الحالة الأخرى
<bdi dir="ltr" class="odt-ltr-term">Server.Transfer</bdi>("<bdi dir="ltr" class="odt-ltr-term">form2.aspx</bdi>")
نهاية الاختيار
نهاية <bdi dir="ltr" class="odt-ltr-term">Sub</bdi>
نهاية الفئة
وحدة التحكم لدينا بسيطة. اعتمادًا على قيمة المعلمة [action]، تقوم بنقل معالجة الطلب إلى الصفحة المناسبة. وهي لا تقدم أي قيمة مضافة مقارنة بصفحة HTML تحتوي على روابط. ومع ذلك، فإن مجرد إضافة صفحة مصادقة من شأنه أن يثبت فائدتها. إذا كان على المستخدم المصادقة (اسم المستخدم، كلمة المرور) للوصول إلى التطبيقات، فإن وحدة التحكم [main.aspx] ستكون مكانًا جيدًا للتحقق من إتمام هذه المصادقة.




