12. خدمات الويب
12.1. Introduction
قدمنا في الفصل السابق عدة تطبيقات عميل-خادم Tcp-Ip. وبما أن العملاء والخادم يتبادلون أسطر نصية، فيمكن كتابتها بأي لغة. يجب على العميل ببساطة معرفة بروتوكول الحوار الذي يتوقعه الخادم.
خدمات الويب هي أيضًا تطبيقات خادم TCP/IP. وتتميز بالخصائص التالية:
- يتم استضافتها بواسطة خوادم الويب وبروتوكول التبادل بين العميل والخادم هو HTTP (بروتوكول النقل HyperText)، وهو بروتوكول أعلى من TCP-IP.
- تتمتع خدمة الويب ببروتوكول تواصل قياسي بغض النظر عن الخدمة المقدمة. تقدم خدمة الويب خدمات متنوعة مثل S1 و S2 و... و Sn. تتوقع كل خدمة من هذه الخدمات معلمات مقدمة من العميل وتقدم له نتيجة. بالنسبة لكل خدمة، يحتاج العميل إلى معرفة:
- الاسم الدقيق للخدمة إذا
- قائمة المعلمات التي يجب تزويدها ونوعها
- نوع النتيجة التي تعيدها الخدمة
بمجرد معرفة هذه العناصر، يتبع الحوار بين العميل والخادم نفس التنسيق، بغض النظر عن خدمة الويب التي يتم الاستعلام عنها. وبذلك يتم توحيد كتابة العملاء.
- لأسباب أمنية تتعلق بالهجمات القادمة من الإنترنت، تمتلك العديد من المؤسسات شبكات خاصة ولا تفتح على الإنترنت سوى بعض منافذ خوادمها: بشكل أساسي المنفذ 80 لخدمة الويب. يتم إغلاق جميع المنافذ الأخرى. لذلك، يتم إنشاء تطبيقات العميل-الخادم كما هو موضح في الفصل السابق داخل الشبكة الخاصة (الإنترانت) ولا يمكن الوصول إليها من الخارج بشكل عام. إن استضافة خدمة داخل خادم ويب يجعلها متاحة لجميع مستخدمي الإنترنت.
- يمكن نمذجة خدمة الويب ككائن بعيد. وتصبح الخدمات المقدمة عندئذٍ طرقًا لهذا الكائن. ويمكن للعميل الوصول إلى هذا الكائن البعيد كما لو كان محليًا. وهذا يخفي كل جزء الاتصال بالشبكة ويسمح ببناء عميل مستقل عن هذه الطبقة. وإذا تغيرت هذه الطبقة، فلن يتعين تعديل العميل.
- كما هو الحال بالنسبة لتطبيقات العميل-الخادم Tcp-Ip التي تم عرضها في الفصل السابق، يمكن كتابة العميل والخادم بأي لغة برمجة. يتبادلان سطور النص. تتكون هذه السطور من جزأين:
- الرؤوس اللازمة لبروتوكول HTTP
- نص الرسالة. بالنسبة لرد الخادم على العميل، يكون هذا الرد بتنسيق XML (لغة ترميز eXtensible). بالنسبة لطلب العميل إلى الخادم، يمكن أن يتخذ نص الرسالة عدة أشكال، منها XML. قد يكون للطلب XML المقدم من العميل تنسيق خاص يسمى SOAP (بروتوكول الوصول البسيط إلى الكائنات). في هذه الحالة، تتبع استجابة الخادم أيضًا التنسيق SOAP.
تكون بنية تطبيق العميل/الخادم القائم على خدمة الويب كما يلي:
![]() |
وهي امتداد للبنية ثلاثية الطبقات التي تضاف إليها فئات اتصال شبكي متخصصة. وقد سبق أن تناولنا بنية مشابهة مع تطبيق العميل الرسومي لـ Windows / خادم TCP للضرائب في الفقرة 11.9.1.
دعونا نوضح هذه المفاهيم العامة بمثال أول.
12.2. خدمة ويب أولى باستخدام Visual Web Developer
سنقوم بإنشاء أول تطبيق عميل/خادم له البنية المبسطة التالية:
![]() |
12.2.1. جانب الخادم
لقد ذكرنا أن خدمة الويب يتم استضافتها بواسطة خادم ويب. يندرج كتابة خدمة الويب ضمن الإطار العام لبرمجة الويب، من جانب الخادم. سبق لنا أن كتبنا عملاء ويب، وهو ما يعد أيضًا برمجة ويب ولكن من جانب العميل هذه المرة. غالبًا ما يشير مصطلح "برمجة الويب" إلى البرمجة من جانب الخادم بدلاً من البرمجة من جانب العميل. لتطوير خدمات الويب أو تطبيقات الويب بشكل عام، لا يعد Visual C# الأداة المناسبة. سنستخدم Visual Developer، وهي إحدى إصدارات Express من Visual Studio 2008 القابلة للتنزيل [2] على العنوان [1]: [http://msdn.microsoft.com/fr-fr/express/future/bb421473(en-us).aspx] (مايو 2008):
![]() |
- [1]: عنوان التنزيل
- [2]: علامة تبويب التنزيلات
- [3]: تنزيل Visual Developer 2008
لإنشاء خدمة ويب أولى، يمكن اتباع الخطوات التالية بعد تشغيل Visual Developer:
![]() |
- [1]: اختيار الخيار File / New Web Site
- [2]: اختيار تطبيق من نوع ASP.NET Web Service
- [3]: اختر لغة التطوير: C#
- [4]: حدد المجلد الذي تريد إنشاء المشروع فيه
![]() |
- [5]: المشروع الذي تم إنشاؤه في Visual Web Developer
- [6]: مجلد المشروع على القرص
يتم تنظيم تطبيق الويب بالطريقة التالية في Web Developer:
- جذر يحتوي على مستندات موقع الويب (صفحات ويب ثابتة بتنسيق HTML، صور، صفحات ويب ديناميكية بتنسيق .aspx، خدمات ويب بتنسيق .asmx، ...). يوجد فيه أيضًا الملف [web.config] وهو ملف تكوين تطبيق الويب. وهو يؤدي نفس دور الملف [App.config] في تطبيقات Windows ويتم تنظيمه بنفس الطريقة.
- مجلد [App_Code] الذي يحتوي على فئات وواجهات موقع الويب المخصصة للتجميع.
- مجلد [App_Data] الذي سيتم وضع البيانات التي تستخدمها فئات [App_Code] فيه. على سبيل المثال، يمكن العثور فيه على قاعدة بيانات SQL Server *.mdf.
[Service.asmx] هي خدمة الويب التي طلبنا إنشاؤها. وهي لا تحتوي سوى على السطر التالي:
<%@ WebService Language="C#" CodeBehind="~/App_Code/Service.cs" Class="Service" %>
الرمز المصدري أعلاه مخصص لخادم الويب الذي سيستضيف التطبيق. في وضع الإنتاج، يكون هذا الخادم عادةً IIS (Internet Information Server)، خادم الويب من Microsoft. يتضمن Visual Web Developer خادم ويب خفيف الوزن يُستخدم في وضع التطوير. توجه التوجيه السابق خادم الويب إلى:
- [Service.asmx] هو خدمة ويب (التوجيه WebService)
- مكتوبة بلغة C# (السمة Language)
- أن كود C# لخدمة الويب موجود في الملف [~/App_Code/Service.cs] (السمة CodeBehind). وهذا هو المكان الذي سيبحث فيه خادم الويب عنه لتجميعه.
- أن الفئة التي تنفذ خدمة الويب تسمى Service (السمة Class)
رمز C# [Service.cs] لخدمة الويب التي تم إنشاؤها بواسطة Visual Developer هو التالي:
using System.Web.Services;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// للسماح باستدعاء خدمة الويب هذه من البرنامج النصي، باستخدام ASP.NET AJAX، قم بإلغاء تعليق السطر التالي.
// [System.Web.Script.Services.ScriptService]
public class Service : System.Web.Services.WebService
{
public Service () {
//قم بإلغاء تعليق السطر التالي في حالة استخدام المكونات المصممة
//InitializeComponent();
}
[WebMethod]
public string HelloWorld() {
return "Hello World";
}
}
تشبه الفئة Service فئة C# تقليدية، ولكن مع بعض النقاط التي يجب ملاحظتها:
- السطر 7: الفئة مشتقة من الفئة WebService المحددة في مساحة الاسم System.Web.Services. هذا التوريث ليس إلزاميًا دائمًا. في هذا المثال على وجه الخصوص، يمكن الاستغناء عنه.
- السطر 3: تسبق الفئة نفسها سمة [WebService(Namespace="http://tempuri.org/")] تهدف إلى توفير مساحة أسماء لخدمة الويب. يخصص بائع الفئات مساحة أسماء لفئاته من أجل منحها اسمًا فريدًا وتجنب التضارب مع فئات بائعين آخرين قد تحمل الاسم نفسه. الأمر نفسه ينطبق على خدمات الويب. يجب أن يمكن تحديد كل خدمة ويب بواسطة اسم فريد، وهو هنا http://tempuri.org/. يمكن أن يكون هذا الاسم أي اسم. ولا يجب بالضرورة أن يكون على شكل عنوان Uri Http.
- السطر 15: تسبق الطريقة HelloWorld سمة [WebMethod] التي تشير للمترجم إلى أن الطريقة يجب أن تكون مرئية للعملاء البعيدين لخدمة الويب. لا تكون الطريقة التي لا يسبقها هذا السمة مرئية لعملاء خدمة الويب. قد تكون هذه طريقة داخلية تستخدمها طرق أخرى ولكنها غير مخصصة للنشر.
- السطر 9: منشئ خدمة الويب. وهو غير ضروري في تطبيقنا.
يتم تحويل الفئة [Service.cs] التي تم إنشاؤها على النحو التالي:
using System.Web.Services;
[WebService(Namespace = "http://st.istia.univ-angers.fr")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
[WebMethod]
public string DisBonjourALaDame(string nomDeLaDame) {
return string.Format("Bonjour Mme {0}", nomDeLaDame);
}
}
ملف التكوين [web.config] الذي تم إنشاؤه للتطبيق الويب هو التالي:
<?xml version="1.0"?>
<!--
Note: As an alternative to hand editing this file you can use the
web admin tool to configure settings for your application. Use
the Website->Asp.Net Configuration option in Visual Studio.
A full list of settings and comments can be found in
machine.config.comments usually located in
\Windows\Microsoft.Net\Framework\v2.x\Config
-->
<configuration>
<configSections>
<sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
...
</sectionGroup>
</configSections>
<appSettings/>
<connectionStrings/>
...
</configuration>
يحتوي الملف على 140 سطراً. وهو معقد ولن نعلق عليه. سنحتفظ به كما هو. أعلاه، نجد العلامات <configuration>، <configSections>، <sectionGroup>، <appSettings>، <connectionString> التي صادفناها في الملف [App.config] لتطبيقات ويندوز.
لدينا خدمة ويب جاهزة للعمل يمكن تشغيلها:
![]() |
- [1,2]: انقر بزر الماوس الأيمن على [Service.asmx] واطلب عرض الصفحة في متصفح
- [3]: يقوم Visual Web Developer بتشغيل خادم الويب المدمج الخاص به ويضع أيقونته في أسفل يمين شريط المهام. يتم تشغيل خادم الويب على منفذ عشوائي، وهو 1906 في هذه الحالة. عنوان URI المعروض /WsHello هو اسم موقع الويب [4].
كما أطلق Visual Web Developer متصفحًا لعرض الصفحة المطلوبة، وهي [Service.asmx]:
![]() |
- في [1]، عنوان URI للصفحة. نجد عنوان URI للموقع [http://localhost:1906/WsHello] متبوعًا بعنوان URI للصفحة /Service.asmx.
- في [2]، أشارت اللاحقة .asmx إلى خادم الويب بأنها ليست صفحة ويب عادية (اللاحقة .aspx) تؤدي إلى إنشاء صفحة HTML، بل هي صفحة خدمة ويب. ثم يقوم تلقائيًا بإنشاء صفحة ويب تعرض رابطًا لكل طريقة من طرق خدمة الويب التي تحمل السمة [WebMethod]. هذه الروابط تسمح باختبار الطرق.
يؤدي النقر على الرابط [2] أعلاه إلى الانتقال إلى الصفحة التالية:
![]() |
- في [1]، لاحظ عنوان Uri [http://localhost:1906/WsHello/Service.asmx?op=DisBonjourALaDame] للصفحة الجديدة. هذا هو عنوان Uri لخدمة الويب مع معلمة op=M، حيث M هو اسم إحدى طرق خدمة الويب.
- لنتذكر توقيع الطريقة [DisBonjourALaDame]:
public string DisBonjourALaDame(string nomDeLaDame) ;
تقبل الطريقة معلمة من النوع string وتُرجع نتيجة من النوع string أيضًا. تسمح لنا الصفحة بتنفيذ الطريقة [DisBonjourALaDame]: في [2] نضع قيمة المعلمة nomDeLaDame وفي [3]، نطلب تنفيذ الطريقة. ونحصل على النتيجة التالية:
![]() |
- في [1]، نلاحظ أن عنوان Uri للرد ليس مطابقًا لعنوان الطلب. لقد تغير.
- في [2]، رد خادم الويب. نلاحظ النقاط التالية:
- إنها استجابة XML وليست HTML
- نتيجة الطريقة [DisBonjourALaDame] مغلفة في علامة <string> تمثل نوعها.
- تحتوي العلامة <string> على سمة xmlns (مساحة اسم xml) وهي مساحة الاسم التي أطلقناها على خدمة الويب الخاصة بنا (السطر 1 أدناه).
[WebService(Namespace = "http://st.istia.univ-angers.fr")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
لمعرفة كيفية قيام متصفح الويب بإرسال طلبه، يجب الاطلاع على كود HTML الخاص بنموذج الاختبار:
- السطر 11: سيتم إرسال قيم النموذج (علامة form) (السمة method) إلى عنوان URL [ http://localhost:1906/WsHello/Service.asmx/DisBonjourALaDame] (السمة action).
- السطر 19: يُسمى حقل الإدخال nomDeLaDame (السمة name).
سمح لنا طلب تنفيذ خدمة الويب [/Service.asmx] باختبار أساليبها والحصول على الحد الأدنى من الفهم للتبادلات بين العميل والخادم.
12.2.2. الجزء الخاص بالعميل
![]() |
من الممكن تنفيذ عميل خدمة الويب البعيدة المذكورة أعلاه باستخدام عميل Tcp-Ip أساسي. فيما يلي على سبيل المثال الحوار بين العميل والخادم الذي تم إجراؤه باستخدام عميل putty متصل بخدمة الويب البعيدة (localhost,1906):
- الأسطر 1-5: الرسائل المرسلة من قبل العميل putty
- السطر 1: الأمر POST
- الأسطر 6-10: رد الخادم. وهذا يعني أن العميل يمكنه إرسال قيم POST.
- السطر 11: القيم المرسلة في شكل param1=val1¶m2=val2& .... يجب أن تكون بعض الأحرف من الأحرف المقبولة في عنوان URL. وهذا ما أطلقنا عليه سابقًا عنوان URL مشفر. هنا، يحتوي النموذج على معلمة واحدة فقط تسمى nomDeLaDame. يبلغ إجمالي عدد أحرف القيمة المرسلة 23 حرفًا. يجب الإعلان عن هذا الحجم في رأس Http في السطر 4.
- السطور 12-22: استجابة الخادم
- السطر 22: نتيجة طريقة الويب [DisBonjourALaDame].
باستخدام Visual C#، يمكن إنشاء عميل خدمة ويب عن بُعد بمساعدة معالج. وهذا ما سنراه الآن.
![]() |
يتم تنفيذ الطبقة [1] أعلاه بواسطة مشروع Visual Studio C# من نوع تطبيق Windows ويسمى ClientWsHello:
![]() |
- في [1]، مشروع ClientWsHello في Visual C#
- إلى [2]، وسيكون مساحة الاسم الافتراضية للمشروع هي Client (انقر بزر الماوس الأيمن على المشروع / Properties / Application). ستُستخدم مساحة الاسم هذه لإنشاء مساحة اسم العميل التي سيتم إنشاؤها.
- في [3]، انقر بزر الماوس الأيمن على المشروع لإضافة مرجع إلى خدمة ويب بعيدة
![]() |
- في [4]، أدخل عنوان URI لخدمة الويب التي تم إنشاؤها مسبقًا
- في [4b]، قم بتوصيل Visual C# بخدمة الويب المحددة بواسطة [4]. سيقوم Visual C# باسترداد وصف خدمة الويب وبفضل هذا الوصف سيتمكن من إنشاء عميل لها.
- في [5]، بمجرد استرداد وصف خدمة الويب، يمكن لـ Visual C# عرض الأساليب العامة الخاصة بها
- في [6]، حدد مساحة أسماء للعميل الذي سيتم إنشاؤه. ستُضاف هذه المساحة إلى مساحة الأسماء المحددة في [2]. وبذلك تكون مساحة أسماء العميل هي Client.WsHello.
- في [6b]، قم بتأكيد المساعد.
- في [7]، تظهر الإشارة إلى خدمة الويب WsHello في المشروع. علاوة على ذلك، تم إنشاء ملف تكوين [app.config].
- في [8]، اعرض جميع ملفات المشروع.
- في [9]، تحتوي الإشارة إلى خدمة الويب WsHello على ملفات متنوعة لن نوضحها هنا. لكننا سنلقي نظرة على الملف [Reference.cs] الذي يمثل كود C# للعميل الذي تم إنشاؤه:
namespace Client.WsHello {
...
public partial class ServiceSoapClient : System.ServiceModel.ClientBase<Client.WsHello.ServiceSoap>, Client.WsHello.ServiceSoap {
public ServiceSoapClient() {
}
...
public string DisBonjourALaDame(string nomDeLaDame) {
Client.WsHello.DisBonjourALaDameRequest inValue = new Client.WsHello.DisBonjourALaDameRequest();
inValue.Body = new Client.WsHello.DisBonjourALaDameRequestBody();
inValue.Body.nomDeLaDame = nomDeLaDame;
Client.WsHello.DisBonjourALaDameResponse retVal = ((Client.WsHello.ServiceSoap)(this)).DisBonjourALaDame(inValue);
return retVal.Body.DisBonjourALaDameResult;
}
}
}
- السطر 1: مساحة اسم العميل الذي تم إنشاؤه هي Client.WsHello. إذا كنت ترغب في تغيير مساحة الاسم هذه، فهذا هو المكان المناسب للقيام بذلك.
- السطر 3: الفئة ServiceSoapClient هي فئة العميل الذي تم إنشاؤه. وهي فئة وكيلة بمعنى أنها ستخفي عن تطبيق Windows حقيقة استخدام خدمة ويب بعيدة. سيستخدم تطبيق Windows الفئة البعيدة WsHello عبر الفئة المحلية Client.WsHello.ServiceSoapClient. لإنشاء مثيل للعميل، سنستخدم منشئ السطر 5:
- السطر 8: الطريقة DisBonjourALaDame هي النظير، من جانب العميل، للطريقة DisBonjourALaDame لخدمة الويب. ستستخدم تطبيق Windows الطريقة البعيدة DisBonjourALaDame عبر الطريقة المحلية Client.WsHello.ServiceSoapClient.DisBonjourALaDame بالشكل التالي:
الملف [app.config] الذي تم إنشاؤه هو التالي:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
....
</bindings>
<client>
<endpoint address="http://localhost:1906/WsHello/Service.asmx"... />
</client>
</system.serviceModel>
</configuration>
من هذا الملف، سنحتفظ فقط بالسطر 8 الذي يحتوي على عنوان Uri لخدمة الويب. إذا تغير عنوان Uri هذا، فلن يكون هناك حاجة لإعادة إنشاء عميل Windows. يكفي تغيير عنوان Uri للملف [app.config].
لنعد إلى بنية تطبيق Windows الذي نريد إنشاءه:
![]() |
لقد أنشأنا الطبقة [client] لخدمة الويب. وستكون الطبقة [ui] كما يلي:
![]() |
رقم | النوع | الاسم | الدور |
1 | TextBox | textBoxNomDame | اسم السيدة |
2 | زر | buttonSalutations | للاتصال بخدمة الويب البعيدة WsHello واستدعاء الطريقة DisBonjourALaDame. |
3 | التسمية | labelBonjour | النتيجة التي ترسلها خدمة الويب |
رمز النموذج [Form1.cs] هو التالي:
using System;
using System.Windows.Forms;
using Client.WsHello;
namespace ClientSalutations {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void buttonSalutations_Click(object sender, EventArgs e) {
// sablier
Cursor=Cursors.WaitCursor;
// استعلام خدمة الويب
labelBonjour.Text = new ServiceSoapClient().DisBonjourALaDame(textBoxNomDame.Text.Trim());
// مؤشر عادي
Cursor = Cursors.Arrow;
}
}
}
- السطر 15: يتم إنشاء مثيل لعميل خدمة الويب. وهو من النوع Client.WsHello.ServiceSoapClient. يتم إعلان مساحة الاسم Client.WsHello في السطر 3. يتم استدعاء الطريقة المحلية ServiceSoapClient().DisBonjourALaDame. ومن المعروف أنها تستدعي بدورها الطريقة البعيدة التي تحمل الاسم نفسه في خدمة الويب.
12.3. خدمة ويب للعمليات الحسابية
سنقوم بإنشاء تطبيق عميل/خادم ثانٍ له مرة أخرى البنية المبسطة التالية:
![]() |
كانت خدمة الويب السابقة توفر طريقة واحدة. سننظر في خدمة ويب ستوفر العمليات الحسابية الأربع:
- add(a,b) التي ستُرجع a+b
- طرح(a,b) التي ستعطي a-b
- الضرب(a,b) التي ستنتج a*b
- القسمة (a,b) التي ستنتج a/b
والتي سيتم استدعاؤها بواسطة واجهة المستخدم الرسومية التالية:
![]() |
- في [1]، العملية المطلوب إجراؤها
- في [2,3]: المعاملات
- في [4]، زر استدعاء خدمة الويب
- في [5]، النتيجة التي تقدمها خدمة الويب
12.3.1. جزء الخادم
نقوم بإنشاء مشروع من نوع خدمة الويب باستخدام Visual Web Developer:
![]() |
- في [1]، تطبيق الويب WsOperations الذي تم إنشاؤه
- في [2]، تم تحديث مظهر تطبيق الويب WsOperations على النحو التالي:
- تمت إعادة تسمية صفحة الويب [Service.asmx] إلى [Operations.asmx]
- تم تغيير اسم الفئة [Service.cs] إلى [Operations.cs]
- تم حذف الملف [web.config] لإظهار أنه ليس ضروريًا.
تحتوي صفحة الويب [Service.asmx] على السطر التالي:
<%@ WebService Language="C#" CodeBehind="~/App_Code/Operations.cs" Class="Operations" %>
يتم توفير خدمة الويب بواسطة الفئة [Operations.cs] التالية:
using System.Web.Services;
[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Operations : System.Web.Services.WebService
{
[WebMethod]
public double Ajouter(double a, double b)
{
return a + b;
}
[WebMethod]
public double Soustraire(double a, double b)
{
return a - b;
}
[WebMethod]
public double Multiplier(double a, double b)
{
return a * b;
}
[WebMethod]
public double Diviser(double a, double b)
{
return a / b;
}
}
لنشر الخدمة على الويب، نتبع الإجراءات الموضحة في [3]. وبذلك نحصل على صفحة اختبار الطرق الأربع للخدمة WsOperations:

يُطلب من القارئ اختبار الطرق الأربع.
12.3.2. جزء العميل
![]() |
باستخدام Visual C#، نقوم بإنشاء تطبيق Windows ClientWsOperations:
![]() |
- في [1]، مشروع ClientWsOperations في Visual C#
- في [2]، سيكون مساحة الاسم الافتراضية للمشروع هي Client (انقر بزر الماوس الأيمن على المشروع / Properties / Application). ستُستخدم مساحة الاسم هذه لإنشاء مساحة اسم العميل التي سيتم إنشاؤها.
- في [3]، انقر بزر الماوس الأيمن على المشروع لإضافة مرجع إلى خدمة ويب موجودة
![]() |
- في [4]، أدخل عنوان URI لخدمة الويب التي تم إنشاؤها مسبقًا. للقيام بذلك، يجب النظر إلى ما يظهر في حقل العنوان في المتصفح الذي يعرض صفحة اختبار خدمة الويب.
- في [4b]، قم بتوصيل Visual C# بخدمة الويب المحددة بواسطة [4]. سيقوم Visual C# باسترداد وصف خدمة الويب وبفضل هذا الوصف سيتمكن من إنشاء عميل لها.
- في [5]، بمجرد استرداد وصف خدمة الويب، يمكن لـ Visual C# عرض الأساليب العامة
- في [6]، حدد مساحة أسماء للعميل الذي سيتم إنشاؤه. سيتم إضافة هذا إلى مساحة الأسماء المحددة في [2]. وبالتالي ستكون مساحة أسماء العميل هي Client.WsOperations.
- في [6b]، قم بتأكيد المساعد.
- في [7]، تظهر الإشارة إلى خدمة الويب WsOperations في المشروع. علاوة على ذلك، تم إنشاء ملف تكوين [app.config].
تجدر الإشارة إلى أن العميل الذي تم إنشاؤه هو من النوع Client.WsOperations.OperationsSoapClient حيث
- Client.WsOperations هو مساحة اسم عميل خدمة الويب
- Operations هي فئة خدمة الويب البعيدة.
على الرغم من وجود طريقة منطقية لإنشاء هذا الاسم، غالبًا ما يكون من الأسهل العثور عليه في الملف [Reference.cs] الذي يعد ملفًا مخفيًا افتراضيًا. ومحتواه كما يلي:
namespace Client.WsOperations {
...
public partial class OperationsSoapClient : System.ServiceModel.ClientBase<Client.WsOperations.OperationsSoap>, Client.WsOperations.OperationsSoap {
public OperationsSoapClient() {
}
...
public double Ajouter(double a, double b) {
...
}
public double Soustraire(double a, double b) {
...
}
public double Multiplier(double a, double b) {
...
}
public double Diviser(double a, double b) {
...
}
}
}
سيتم الوصول إلى الطرق Ajouter و Soustraire و Multiplier و Diviser لخدمة الويب البعيدة سيتم الوصول إليها عبر طرق البروكسي التي تحمل نفس الاسم (الأسطر 8 و12 و16 و20) للعميل من النوع Client.WsOperations.OperationsSoapClient (السطر 3).
يبقى لنا إنشاء الواجهة الرسومية:
![]() |
رقم | النوع | الاسم | الدور |
1 | ComboBox | comboBoxOperations | قائمة العمليات الحسابية |
2 | TextBox | textBoxA | العدد أ |
3 | TextBox | textBoxB | الرقم ب |
4 | زر | buttonExécuter | يستعلم عن خدمة الويب البعيدة |
5 | التسمية | labelRésultat | نتيجة العملية |
رمز [Form1.cs] هو التالي:
using System;
using System.Windows.Forms;
using Client.WsOperations;
namespace ClientWsOperations {
public partial class Form1 : Form {
// جدول العمليات
private string[] opérations = { "Ajouter", "Soustraire", "Multiplier", "Diviser" };
// خدمة الويب المطلوب الاتصال بها
private OperationsSoapClient opérateur = new OperationsSoapClient();
// المنشئ
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
// ملء قائمة العمليات المنسدلة
comboBoxOperations.Items.AddRange(opérations);
comboBoxOperations.SelectedIndex = 0;
}
private void buttonExécuter_Click(object sender, EventArgs e) {
// التحقق من المعلمات a و b للعملية
textBoxMessage.Text = "";
bool erreur = false;
Double a = 0;
if (!Double.TryParse(textBoxA.Text, out a)) {
textBoxMessage.Text += "Nombre a erroné...";
}
Double b = 0;
if (!Double.TryParse(textBoxB.Text, out b)) {
textBoxMessage.Text += String.Format("{0}Nombre b erroné...", Environment.NewLine);
}
if (erreur) {
return;
}
// تنفيذ العملية
Double c=0;
try {
switch (comboBoxOperations.SelectedItem.ToString()) {
case "Ajouter":
c=opérateur.Ajouter(a, b);
break;
case "Soustraire":
c=opérateur.Soustraire(a, b);
break;
case "Multiplier":
c=opérateur.Multiplier(a, b);
break;
case "Diviser":
c=opérateur.Diviser(a, b);
break;
}
// عرض النتيجة
labelRésultat.Text = c.ToString();
} catch (Exception ex) {
textBoxMessage.Text = ex.Message;
}
}
}
}
- السطر 3: مساحة اسم عميل خدمة الويب البعيدة
- السطر 10: يتم إنشاء مثيل لعميل الخدمة الويب البعيدة في نفس وقت إنشاء النموذج
- الأسطر 17-21: يتم ملء قائمة العمليات المنسدلة عند التحميل الأولي للنموذج
- السطر 23: تنفيذ العملية التي طلبها المستخدم
- الأسطر 25-37: يتم التحقق من أن المدخلات a و b هي أعداد حقيقية
- الأسطر 41-54: مفتاح لتنفيذ العملية البعيدة التي طلبها المستخدم
- الأسطر 43 و46 و49 و52: يتم استعلام العميل المحلي. وبشكل شفاف، يقوم هذا الأخير باستعلام الخدمة الويب البعيدة.
12.4. خدمة ويب لحساب الضرائب
نعود الآن إلى تطبيق حساب الضرائب المعروف جيدًا. في المرة الأخيرة التي عملنا فيها عليه، قمنا بتحويله إلى خادم TCP بعيد يمكن استدعاؤه عبر الإنترنت. ونقوم الآن بتحويله إلى خدمة ويب.
كانت بنية الإصدار 8 كما يلي:
![]() |
وستكون بنية الإصدار 9 مشابهة:
![]() |
وهي بنية مشابهة لتلك الخاصة بالإصدار 8 التي تمت دراستها في الفقرة 11.9.1، ولكن حيث تم استبدال الخادم وعميل TCP بخدمة ويب وعميل بروكسي خاص بها. سنستعيد بالكامل الطبقات [ui] و [metier] و [dao] من الإصدار 8.
12.4.1. جزء الخادم
نقوم بإنشاء مشروع من نوع خدمة الويب باستخدام Visual Web Developer:
![]() |
- في [1]، تطبيق الويب WsImpot الذي تم إنشاؤه
- إلى [2]، وتطبيق الويب WsImpot الذي تم إعادة تصميمه على النحو التالي:
- تمت إعادة تسمية صفحة الويب [Service.asmx] إلى [ServiceImpot.asmx]
- تمت إعادة تسمية الفئة [Service.cs] إلى [ServiceImpot .cs]
تحتوي صفحة الويب [ServiceImpot.asmx] على السطر التالي:
<%@ WebService Language="C#" CodeBehind="~/App_Code/ServiceImpot.cs" Class="ServiceImpot" %>
يتم توفير خدمة الويب بواسطة الفئة [ServiceImpot.cs] التالية:
using System.Web.Services;
[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ServiceImpot : System.Web.Services.WebService
{
[WebMethod]
public int CalculerImpot(bool marié, int nbEnfants, int salaire)
{
return 0;
}
}
لن تعرض خدمة الويب سوى الطريقة CalculerImpot من السطر 9.
لنعد إلى بنية العميل/الخادم في الإصدار 8:
![]() |
كان مشروع Visual Studio للخادم [1] كما يلي:
![]() |
- في [1]، المشروع. كان يحتوي على العناصر التالية:
- [ServeurImpot.cs]: خادم TCP/IP لحساب الضريبة في شكل تطبيق وحدة التحكم.
- [dbimpots.sdf]: قاعدة البيانات SQL Server compact من الإصدار 7 الموصوف في الفقرة 9.8.5.
- [App.config]: ملف تكوين التطبيق.
- في [2]، يحتوي المجلد [lib] على ملفات DLL اللازمة للمشروع:
- [ImpotsV7-dao]: طبقة [dao] من الإصدار 7
- [ImpotsV7-metier]: الطبقة [metier] من الإصدار 7
- [antlr.runtime, CommonLogging, Spring.Core] لـ Spring
- في [3]، مراجع المشروع
الطبقات [metier] و [dao] من الإصدار موجودة بالفعل: وهي تلك المستخدمة في الإصدارين 7 و 8. وهي في شكل DLL التي ندمجها في المشروع بالطريقة التالية:
![]() |
- في [1]، تم نسخ المجلد [lib] من خادم الإصدار 8 إلى مشروع خدمة الويب للإصدار 9.
- في [2]، نقوم بتعديل خصائص الصفحة لإضافة DLL من المجلد [lib] [4] إلى مراجع مشروع [3].
بعد هذه العملية، يكون لدينا جميع الطبقات اللازمة للخادم [1] أدناه:
![]() |
إذا كانت عناصر الخادم [1] و [serveur] و [metier]، [dao]، [entites]، [spring] موجودة بالفعل في مشروع Visual Studio، فإننا نفتقد العنصر الذي سيقوم بإنشاء مثيل لها عند بدء تشغيل تطبيق الويب. في الإصدار 8، كانت فئة رئيسية تحتوي على الطريقة الثابتة [Main] تقوم بمهمة إنشاء مثيلات الطبقات بمساعدة Spring. في تطبيق ويب، الفئة القادرة على القيام بمهمة مماثلة هي الفئة المرتبطة بالملف [Global.asax] :
![]() |
- في [1]، نضيف عنصرًا جديدًا إلى مشروع الويب
- في [2]، نختار نوع Global Application Class
- في [3]، الاسم المقترح افتراضيًا لهذا العنصر
- في [4]، يتم تأكيد الإضافة
- في [5]، تم دمج العنصر الجديد في المشروع
لنلقِ نظرة على محتوى الملف [Global.asax]:
<%@ Application Language="C#" %>
<script runat="server">
void Application_Start(object sender, EventArgs e)
{
// الرمز الذي يتم تشغيله عند بدء تشغيل التطبيق
}
void Application_End(object sender, EventArgs e)
{
// الكود الذي يتم تشغيله عند إيقاف تشغيل التطبيق
}
void Application_Error(object sender, EventArgs e)
{
// الرمز الذي يتم تشغيله عند حدوث خطأ لم يتم معالجته
}
void Session_Start(object sender, EventArgs e)
{
// الرمز الذي يتم تشغيله عند بدء جلسة جديدة
}
void Session_End(object sender, EventArgs e)
{
// الرمز الذي يتم تشغيله عند انتهاء الجلسة.
}
</script>
الملف عبارة عن مزيج من العلامات الموجهة إلى خادم الويب (الأسطر 1 و3 و30) ورمز C#. كانت هذه الطريقة هي الوحيدة المستخدمة مع ASP، سلف ASP.NET التي تعد تقنية Microsoft الحالية للبرمجة على الويب. مع ASP.NET، لا تزال هذه الطريقة قابلة للاستخدام ولكنها ليست الطريقة الافتراضية. الطريقة الافتراضية هي الطريقة المعروفة باسم "CodeBehind" والتي صادفناها في صفحات خدمات الويب، على سبيل المثال هنا في [ServiceImpot.asmx]:
<%@ WebService Language="C#" CodeBehind="~/App_Code/ServiceImpot.cs" Class="ServiceImpot" %>
يحدد السمة CodeBehind مكان وجود شفرة المصدر للصفحة [ServiceImpot.asmx]. بدون هذا السمة، سيكون الكود المصدري في الصفحة [ServiceImpot.asmx] بنحو مشابه لتلك الموجودة في [Global.asax]. لن نحتفظ بالملف [Global.asax] كما تم إنشاؤه، لكن كوده يسمح لنا بفهم الغرض منه:
- يتم إنشاء مثيل للفئة المرتبطة بـ Global.asax عند بدء تشغيل التطبيق. وتستمر مدة حياتها طوال مدة حياة التطبيق بأكمله. وبشكل ملموس، لا تختفي إلا عند إيقاف تشغيل خادم الويب.
- يتم بعد ذلك تنفيذ الطريقة Application_Start. وهذه هي المرة الوحيدة التي سيتم فيها تنفيذها. ولذلك يتم استخدامها لإنشاء مثيلات لكائنات مشتركة بين جميع المستخدمين. يتم وضع هذه الكائنات:
- أو في الحقول الثابتة للفئة المرتبطة بـ Global.asax. ونظرًا لوجود هذه الفئة بشكل دائم، يمكن لأي مستخدم قراءة المعلومات الموجودة فيها عند إجراء أي استعلام.
- أو الحاوية Application. يتم إنشاء هذه الحاوية أيضًا عند بدء تشغيل التطبيق وتستمر طوال مدة تشغيل التطبيق.
- لوضع بيانات في هذا الحاوية، نكتب Application["clé"]=القيمة؛
- لاستردادها، نكتب T value=(T)Application["clé"]؛ حيث T هو نوع valeur.
- يتم تنفيذ الطريقة Session_Start في كل مرة يقوم فيها مستخدم جديد بإرسال طلب. كيف يتم التعرف على مستخدم جديد؟ يتلقى كل مستخدم (غالبًا متصفح) في نهاية طلبه الأول، رمز جلسة عمل وهو سلسلة أحرف فريدة لكل مستخدم. بعد ذلك، يعيد المستخدم رمز الجلسة الذي تلقّاه مع كل طلب جديد يقدمه. وهذا يسمح لخادم الويب بالتعرف عليه. مع تراكم الطلبات المختلفة لنفس المستخدم، يمكن تخزين البيانات الخاصة به في حاوية Session:
- لوضع بيانات في هذا الحاوية، نكتب Session["clé"]=القيمة؛
- لاستردادها، نكتب T value=(T)Session["clé"]؛ حيث T هو نوع valeur.
يتم تحديد مدة صلاحية الجلسة افتراضيًا بـ 20 دقيقة من عدم نشاط المستخدم (c.a.d. أي أنه لم يرسل رمز الجلسة الخاص به منذ 20 دقيقة).
- يتم تنفيذ الطريقة Application_Error عندما تصل استثناءات غير معالجة من قبل تطبيق الويب إلى خادم الويب.
- أما الطرق الأخرى فهي أقل استخدامًا.
بعد هذه المعلومات العامة، ما الفائدة التي يمكن أن نستفيد منها من Global.asax؟ سنستخدم طريقتها Application_Start لتهيئة الطبقات [metier]، و [dao] و [entites] الموجودة في DLL و [ImpotsV7-metier, ImpotsV7-dao]. سنستخدم Spring لإنشاء مثيلات لها. سيتم بعد ذلك تخزين مراجع الطبقات التي تم إنشاؤها في حقول ثابتة للفئة المرتبطة بـ Global.asax.
في الخطوة الأولى، ننقل كود C# من Global.asax إلى فئة منفصلة. يتطور المشروع على النحو التالي:
![]() |
في [1]، سيتم ربط الملف [Global.asax] بالفئة [Global.cs] [2] من خلال السطر الوحيد التالي:
<%@ Application Language="C#" Inherits="WsImpot.Global"%>
تشير السمة Inherits="WsImpot.Global" إلى أن الفئة المرتبطة بـ Global.asax ترث الفئة WsImpot.Global. يتم تعريف هذه الفئة في [Global.cs] بالطريقة التالية:
using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
public class Global : System.Web.HttpApplication
{
// طبقة الأعمال
public static IImpotMetier Metier;
// طريقة يتم تنفيذها عند بدء تشغيل التطبيق
private void Application_Start(object sender, EventArgs e)
{
// مثيلات الطبقات [metier] و [dao]
Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
}
}
}
- السطر 4: مساحة اسم الفئة
- السطر 6: الفئة Global. يمكن تسميتها بأي اسم نريده. المهم هو أنها مشتقة من الفئة System.Web.HttpApplication.
- السطر 9: حقل عام ثابت سيحتوي على مرجع إلى الطبقة [metier].
- السطر 12: الطريقة Application_Start التي سيتم تنفيذها عند بدء تشغيل التطبيق.
- السطر 15: يتم استخدام Spring لاستغلال الملف [web.config] الذي سيجد فيه الكائنات التي سيتم إنشاء مثيل لها لإنشاء الطبقتين [metier] و [dao]. لا يوجد أي فرق بين استخدام Spring مع [App.config] في تطبيق ويندوز، واستخدام Spring مع [web.config] في تطبيق ويب. كما أن [web.config] و [App.config] لهما نفس البنية. السطر 15 يخزن مرجع الطبقة [metier] في الحقل الثابت في السطر 9، بحيث يكون هذا المرجع متاحًا لجميع الاستعلامات لجميع المستخدمين.
سيكون ملف [web.config] كما يلي:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="dao" type="Dao.DataBaseImpot, ImpotsV7-dao">
<constructor-arg index="0" value="MySql.Data.MySqlClient"/>
<constructor-arg index="1" value="Server=localhost;Database=bdimpots;Uid=admimpots;Pwd=mdpimpots;"/>
<constructor-arg index="2" value="select limite, coeffr, coeffn from tranches"/>
</object>
<object name="metier" type="Metier.ImpotMetier, ImpotsV7-metier">
<constructor-arg index="0" ref="dao"/>
</object>
</objects>
</spring>
</configuration>
هذا هو الملف [App.config] المستخدم في الإصدار 7 من التطبيق والذي تمت دراسته في الفقرة 9.8.4.
- الأسطر 16-20: تحدد طبقة [dao] تعمل مع قاعدة بيانات MySQL5. تم وصف قاعدة البيانات هذه في الفقرة 9.8.1.
- الأسطر 21-23: تحدد الطبقة [metier]
لنعد إلى لغز الخادم:
![]() |
عند بدء تشغيل التطبيق، تم إنشاء مثيلات الطبقتين [metier] و [dao]. مدة حياة الطبقات هي نفس مدة حياة التطبيق نفسه. متى يتم إنشاء مثيل خدمة الويب؟ في الواقع، عند كل طلب يتم توجيهه إليها. عند انتهاء الطلب، يتم حذف الكائن الذي خدمه. وبالتالي، فإن خدمة الويب هي، للوهلة الأولى، خدمة عديمة الحالة. لا يمكنها تخزين المعلومات بين طلبين في الحقول الخاصة بها. يمكنها تخزينها في جلسة عمل المستخدم. وللقيام بذلك، يجب تمييز الطرق التي تعرضها بسمات خاصة:
[WebMethod(EnableSession=true)]
public int CalculerImpot(bool marié, int nbEnfants, int salaire)
....
في الأعلى، تسمح السطر 1 للطريقة CalculerImpot بالوصول إلى الحاوية Session التي تحدثنا عنها سابقًا. لن نحتاج إلى استخدام هذا السمة في تطبيقنا. وبالتالي، سيتم إنشاء مثيل لخدمة الويب WsImpot عند كل طلب وستكون بدون حالة.
يمكننا الآن كتابة الكود [ServiceImpot.cs] الذي ينفذ خدمة الويب:
using System.Web.Services;
using WsImpot;
[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ServiceImpot : System.Web.Services.WebService
{
[WebMethod]
public int CalculerImpot(bool marié, int nbEnfants, int salaire)
{
return Global.Metier.CalculerImpot(marié, nbEnfants, salaire);
}
}
- السطر 10: الطريقة الوحيدة لخدمة الويب
- السطر 12: يتم استخدام الطريقة CalculerImpot من الطبقة [metier]. يوجد مرجع لهذه الطبقة في الحقل الثابت Metier من الفئة Global. تنتمي هذه الفئة إلى مساحة الأسماء WsImpot (السطر 2).
نحن جاهزون لتشغيل خدمة الويب. قبل ذلك، يجب تشغيل SGBD و MySQL5 حتى تكون قاعدة البيانات bdimpots متاحة. بعد ذلك، نقوم بتشغيل خدمة الويب [1]:
![]() |
ثم يعرض المتصفح الصفحة [2]. نتبع الرابط:
![]() |
نقوم بتعيين قيمة لكل من المعلمات الثلاثة للطريقة CalculerImpot ونطلب تنفيذ الطريقة. نحصل على النتيجة التالية وهي صحيحة:

12.4.2. عميل رسومي يعمل بنظام Windows لخدمة الويب البعيدة
الآن بعد أن تمت كتابة خدمة الويب، ننتقل إلى العميل. لنعد إلى بنية تطبيق العميل/الخادم:
![]() |
علينا كتابة العميل [2]. ستكون الواجهة الرسومية مطابقة لتلك الموجودة في الإصدار 8:
![]() |
لكتابة الجزء [client] من الإصدار 9، سنبدأ من الجزء [client] من الإصدار 8، ثم سنقوم بإجراء التعديلات اللازمة. نقوم بنسخ مشروع Visual Studio الذي تمت دراسته في الفقرة 11.9.4.1، ونعيد تسميته ClientWsImpot ونقوم بتحميله في Visual Studio:
![]() |
كان حل Visual Studio للإصدار 8 يتكون من مشروعين:
- مشروع [metier] [1] الذي كان عميل TCP لخادم TCP لحساب الضرائب
- مشروع [ui] [2] الخاص بالواجهة الرسومية.
التعديلات المطلوب إجراؤها هي التالية:
- يجب أن يصبح المشروع [metier] الآن عميلاً لخدمة ويب
- يجب أن يشير المشروع [ui] إلى DLL من الطبقة الجديدة [metier]
- يجب تغيير تكوين الطبقة [metier] في [App.config].
12.4.2.1. الطبقة الجديدة [metier]
![]() |
- في [1]، IImpotMetier هي واجهة الطبقة [metier] و ImpotMetierTcp هي تنفيذها بواسطة عميل Tcp
- في [2]، نقوم بإزالة التنفيذ ImpotMetierTcp. يجب علينا إنشاء تنفيذ آخر للواجهة IImpotMetier التي ستكون عميلة لخدمة ويب.
- في [3]، نسمي Client مساحة الأسماء الافتراضية للمشروع [metier]. سيتم تسمية DLL الذي سيتم إنشاؤه باسم [ImpotsV9-metier.dll].
![]() |
- في [4]، نقوم بإنشاء مرجع إلى خدمة الويب WsImpot.
- في [5]، نقوم بتكوينه والتحقق من صحته.
- في [6]، تم إنشاء الإشارة إلى خدمة الويب WsImpot وتم إنشاء ملف [app.config].
في الملف المخفي [Reference.cs]:
- مساحة الاسم هي Client.WsImpot
- تسمى فئة العميل ServiceImpotSoapClient
- ولديها طريقة توقيع واحدة:
public int CalculerImpot(bool marié, int nbEnfants, int salaire) ;
يبقى علينا تنفيذ واجهة IImpotMetier:
namespace Metier {
public interface IImpotMetier {
int CalculerImpot(bool marié, int nbEnfants, int salaire);
}
}
ونقوم بتنفيذها باستخدام الفئة ImpotMetierWs التالية:
using System.Net.Sockets;
using System.IO;
using Client.WsImpot;
namespace Metier {
public class ImpotMetierWs : IImpotMetier {
// عميل خدمة الويب البعيدة
private ServiceImpotSoapClient client = new ServiceImpotSoapClient();
// حساب الضريبة
public int CalculerImpot(bool marié, int nbEnfants, int salaire) {
return client.CalculerImpot(marié, nbEnfants, salaire);
}
}
}
- السطر 6: الفئة ImpotMetierWs تنفذ الواجهة IImpotMetier.
- السطر 9: عند إنشاء مثيل ImpotMetierWs، يتم تهيئة الحقل client بمثيل لعميل خدمة الويب لحساب الضرائب.
- السطر 12: الطريقة الوحيدة التي يجب تنفيذها في واجهة IImpotMetier.
- السطر 13: يتم استخدام الطريقة CalculerImpot الخاصة بعميل خدمة الويب البعيدة لحساب الضرائب. وفي النهاية، سيتم استدعاء الطريقة CalculerImpot الخاصة بخدمة الويب البعيدة.
يمكن إنشاء DLL للمشروع:
![]() |
- إلى [1]، والمشروع [client] في حالته النهائية
- إلى [2]، وإنشاء ملف DLL للمشروع
- إلى [3]، ويوجد DLL ImpotsV9-metier.dll في مجلد /bin/Release الخاص بالمشروع.
12.4.2.2. الطبقة الجديدة [ui]
![]() |
تمت كتابة الطبقة [client] الخاصة بالعميل. يتبقى لنا كتابة الطبقة [ui]. لنعد إلى مشروع Visual Studio:
![]() |
- في [1]، تم تحويل المشروع [ui] الناتج عن الإصدار 8
- إلى [2]، DLL ImpotsV8-metier من الطبقة القديمة [metier] تم استبدالها بـ DLL ImpotsV9-metier من الطبقة الجديدة
- في [3]، تمت إضافة DLL و ImpotsV9-metier إلى مراجع المشروع.
التغيير الثاني يحدث في [App.config]. يجب تذكر أن هذا الملف يستخدمه Spring لإنشاء مثيل للطبقة [metier]. وبما أن هذه الطبقة قد تغيرت، يجب تغيير تكوين [App.config]. من ناحية أخرى، يجب أن يكون لـ [App.config] التكوين الذي يسمح بالاتصال بخدمة الويب البعيدة لحساب الضرائب. تم إنشاء هذا التكوين في الملف [app.config] التابع للمشروع [metier] عندما تمت إضافة الإشارة إلى خدمة الويب البعيدة إليه.
وبالتالي يصبح الملف [App.config] كما يلي:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
</object>
</objects>
</spring>
<!-- خدمة الويب -->
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="ServiceImpotSoap" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
</client>
</system.serviceModel>
</configuration>
- الأسطر 15-18: يقوم Spring بإنشاء مثيل لكائن واحد فقط، وهو الطبقة [metier]
- السطر 16: يتم إنشاء مثيل الطبقة [metier] بواسطة الفئة [Metier.ImpotMetierWs] الموجودة في DLL ImpotsV9-metier.
- الأسطر 22-46: تكوين عميل خدمة الويب البعيدة. يتم نسخ محتوى الملف [app.config] من المشروع [metier] ولصقه هنا.
نحن جاهزون. نقوم بتشغيل التطبيق باستخدام Ctrl-F5 (يجب تشغيل خدمة الويب، ويجب تشغيل SGBD MySQL5، ويجب أن يكون المنفذ في السطر 42 أعلاه هو المنفذ الصحيح):
![]() |
12.5. عميل ويب لخدمة الويب لحساب الضرائب
لنعد إلى بنية تطبيق العميل/الخادم التي تمت كتابتها للتو:
![]() |
تم تنفيذ الطبقة [ui] أعلاه بواسطة عميل رسومي يعمل بنظام Windows. ونقوم الآن بتنفيذها باستخدام واجهة ويب:
![]() |
هذا تغيير مهم للمستخدمين. حالياً، يمكن لتطبيق العميل/الخادم الخاص بنا، الإصدار 9، خدمة عدة عملاء في وقت واحد. وهذا تحسن مقارنة بالإصدار 8 حيث كان يخدم عميلاً واحداً فقط في كل مرة. القيد هو أن المستخدمين الراغبين في استخدام خدمة الويب لحساب الضرائب يجب أن يكون لديهم على أجهزتهم عميل Windows الذي قمنا بكتابته. في هذا الإصدار الجديد، الذي سنسميه الإصدار 10، سيتمكن المستخدمون من الوصول، باستخدام متصفحهم، إلى خدمة الويب لحساب الضرائب.
في البنية المذكورة أعلاه:
- لا يتغير جانب الخادم. فهو يبقى كما هو في الإصدار 9.
- على جانب العميل، لا تتغير الطبقة [client du service web]. فقد تم تغليفها في DLL [ImpotsV9-metier]. سنعيد استخدام هذه الطبقة DLL.
- في النهاية، يتمثل التغيير الوحيد في استبدال واجهة مستخدم رسومية لـ Windows بواجهة ويب.
سنناقش مفاهيم جديدة للبرمجة على الويب من جانب الخادم. وبما أن الهدف من هذا المستند ليس تعليم البرمجة على الويب، فسنحاول شرح النهج الذي سيتم اتباعه دون الخوض في التفاصيل. لذلك سيكون هناك جانب "سحري" بعض الشيء في هذا القسم. ومع ذلك، يبدو من المثير للاهتمام اتباع هذا النهج لإظهار مثال جديد على بنية متعددة الطبقات حيث يتم تغيير إحدى الطبقات.
وبالتالي، فإن بنية الإصدار 10 هي كما يلي:
![]() |
لدينا بالفعل جميع الطبقات، باستثناء الطبقة [web]. لفهم ما سيتم القيام به بشكل أفضل، نحتاج إلى أن نكون أكثر دقة بشأن بنية العميل. وستكون كما يلي:
![]() |
- يحتوي متصفح المستخدم على نموذج ويب
- يتم إرسال هذا النموذج إلى خادم الويب 1 الذي يقوم بمعالجته بواسطة الطبقة [web]
- ستحتاج الطبقة [web] إلى خدمات عميل الخدمة الويب البعيدة، المُغلفة في [ImpotsV9-metier.dll].
- سيتواصل عميل خدمة الويب البعيدة مع خادم الويب 2 الذي يستضيف خدمة الويب البعيدة.
- سترتد استجابة خدمة الويب البعيدة إلى طبقة الويب الخاصة بالعميل الذي سيقوم بتنسيقها في صفحة سيرسلها إلى المستخدم.
لذا فإن مهمتنا هنا هي:
- إنشاء نموذج الويب الذي سيراه المستخدم في متصفحه
- كتابة تطبيق الويب الذي سيعالج طلب المستخدم ويرسل له ردًا في شكل صفحة ويب جديدة. وستكون هذه الصفحة في الواقع هي نفس النموذج الذي أضفنا إليه مبلغ الضريبة المستحقة
- كتابة "الرابط" الذي يجعل كل هذا يعمل معًا.
سيتم تنفيذ كل هذا باستخدام موقع ويب جديد تم إنشاؤه باستخدام Visual Web Developer:
![]() |
- [1]: اختر الخيار File / New Web Site
- [2]: اختيار تطبيق من نوع ASP.NET Web Site
- [3]: اختر لغة التطوير: C#
- [4]: حدد المجلد الذي تريد إنشاء المشروع فيه
![]() |
- [5]: المشروع الذي تم إنشاؤه في Visual Web Developer
- [Default.aspx] هي صفحة ويب تُعرف بالصفحة الافتراضية. وهي الصفحة التي سيتم عرضها عند طلب عنوان URL http://.../ClientAspImpot دون تحديد مستند معين. وستحتوي هذه الصفحة على نموذج حساب الضريبة الذي سيظهر للمستخدم في متصفحه.
- [Default.aspx.cs] هي الفئة المرتبطة بالصفحة، وهي التي ستقوم بإنشاء النموذج الذي يتم إرساله إلى المستخدم ثم معالجته عندما يقوم المستخدم بملئه والتحقق من صحته.
- [web.config] هو ملف تكوين التطبيق. على عكس المرات السابقة، سنحتفظ به.
إذا عدنا إلى البنية التي يتعين علينا إنشاؤها:
![]() |
- سيتم تنفيذ [1] بواسطة [Default.aspx]
- سيتم تنفيذ [2] بواسطة [Default.aspx.cs]
- سيتم تنفيذ [3] بواسطة DLL [ImpotV9-metier]
لنبدأ بتنفيذ الطبقة [3]. هناك عدة خطوات:
![]() |
- في [1]، يتم نسخ المجلد [lib] الخاص بعميل واجهة المستخدم الرسومية لـ Windows الإصدار 9 إلى مجلد مشروع الويب [ClientAspWsImpot]. يتم ذلك باستخدام مستكشف Windows. لإظهار هذا المجلد في Web Developer، يجب تحديث الحل باستخدام الزر [2].
- ثم إضافتها إلى مراجع المشروع [3,4,5]. يتم نسخ ملفات DLL المشار إليها تلقائيًا إلى مجلد /bin الخاص بالمشروع [6].
لدينا الآن ملفات DLL اللازمة لتشغيل Spring، كما تم أيضًا تنفيذ طبقة العميل لخدمة الويب البعيدة. إذا كان كود هذه الخدمة موجودًا بالفعل، فلا يزال يتعين تكوينه. في الإصدار 9، تم تكوينه بواسطة الملف [App.config] التالي:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
</object>
</objects>
</spring>
<!-- خدمة الويب -->
<system.serviceModel>
<bindings>
<basicHttpBinding>
...
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
</client>
</system.serviceModel>
</configuration>
نعيد استخدام هذا التكوين كما هو وندمجه في الملف [web.config] بالطريقة التالية:
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.extensions"...>
...
</sectionGroup>
<!-- بداية قسم Spring -->
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
<!-- نهاية قسم Spring -->
</configSections>
<!-- بداية تكوين Spring -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
</object>
</objects>
</spring>
<!-- نهاية تكوين Spring -->
<!-- بداية تكوين عميل خدمة الويب البعيدة -->
<system.serviceModel>
<bindings>
<basicHttpBinding>
...
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
</client>
</system.serviceModel>
<!-- نهاية تكوين عميل الخدمة الويب البعيدة -->
<!-- تكوينات أخرى موجودة بالفعل في web.config الذي تم إنشاؤه -->
...
</configuration>
تجدر الإشارة إلى أن السطر 37 يشير إلى منفذ خدمة الويب البعيدة. قد يتغير هذا المنفذ لأن Visual Developer يطلق خدمة الويب على منفذ عشوائي.
لنعد إلى بنية عميل الويب التي يتعين علينا إنشاؤها:
![]() |
- سيتم تنفيذ [1] بواسطة [Default.aspx]
- سيتم تنفيذ [2] بواسطة [Default.aspx.cs]
- تم تنفيذ [3] بواسطة DLL [ImpotV9-metier]
لقد قمنا للتو بتنفيذ الطبقة [3]. ننتقل إلى واجهة الويب [1] التي تم تنفيذها بواسطة الصفحة [Default.aspx]. انقر نقرًا مزدوجًا على الصفحة [Default.aspx] للانتقال إلى وضع التصميم.
![]() |
هناك طريقتان لإنشاء صفحة ويب:
- بشكل رسومي كما في [2]. يجب عندئذ اختيار الوضع [Design] في [1]. ستجد شريط الأزرار هذا في أسفل شريط الحالة لمحرر صفحة الويب.
- باستخدام لغة العلامات كما في [3]. في هذه الحالة، يجب اختيار الوضع [Source] في [1].
الوضعان [Design] و [Source] ثنائي الاتجاه: أي تعديل يتم إجراؤه في الوضع [Design] يترجم إلى تعديل في الوضع [Source] والعكس صحيح. تجدر الإشارة إلى أن النموذج الإلكتروني الذي يجب عرضه في المتصفح هو التالي:
![]() |
- في [1]، النموذج المعروض في متصفح
- في [2]، المكونات المستخدمة لإنشائه
- في [3]، صفحة تصميم النموذج. وهي تتضمن العناصر التالية:
- السطر A، زران اختيار يُسميان RadioButtonOui و RadioButtonNon
- السطر B، منطقة إدخال تسمى TextBoxEnfants وتسمية تسمى LabelErreurEnfants
- السطر C، منطقة إدخال تسمى TextBoxSalaire وتسمية تسمى LabelErreurSalaire
- السطر D، تسمية باسم LabelImpot
- السطر E، زران باسم ButtonCalculer و ButtonEffacer
بمجرد وضع المكون على سطح التصميم، يمكن الوصول إلى خصائصه:
![]() |
- في [1]، الوصول إلى خصائص مكون
- في [2]، بطاقة خصائص المكون [LabelErreurEnfants ]
- في [3]، (ID) هو اسم المكون
- في [4]، قمنا بتعيين اللون الأحمر لأحرف التسمية.
لا يكفي وضع المكونات على النموذج ثم تحديد خصائصها. بل يجب أيضًا تنظيم ترتيبها. في واجهة Windows الرسومية، يكون هذا الترتيب مطلقًا. حيث يتم سحب المكون إلى المكان الذي نريده. أما في صفحة الويب، فالأمر مختلف، فهو أكثر تعقيدًا ولكنه أيضًا أكثر قوة. لن يتم تناول هذا الجانب هنا.
الرمز المصدري [Default.aspx] الذي تم إنشاؤه بواسطة هذا التصميم هو التالي:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Calculer votre impôt</title>
</head>
<body bgcolor="#ffff99">
<h2>
Calculer votre impôt</h2>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager2" runat="server" EnablePartialRendering="true" />
<asp:UpdatePanel runat="server" ID="UpdatePanelPam">
<ContentTemplate>
<div>
</div>
<table>
<tr>
<td>
Etes-vous marié(e)
</td>
<td>
<asp:RadioButton ID="RadioButtonOui" runat="server" GroupName="statut" Text="Oui" />
<asp:RadioButton ID="RadioButtonNon" runat="server" GroupName="statut" Text="Non"
Checked="True" />
</td>
</tr>
<tr>
<td>
Nombre d'الأطفال
</td>
<td>
<asp:TextBox ID="TextBoxEnfants" runat="server" Columns="3"></asp:TextBox>
</td>
<td>
<asp:Label ID="LabelErreurEnfants" runat="server" ForeColor="#FF3300"></asp:Label>
</td>
</tr>
<tr>
<td>
Salaire annuel
</td>
<td>
<asp:TextBox ID="TextBoxSalaire" runat="server" Columns="8"></asp:TextBox>
</td>
<td>
<asp:Label ID="LabelErreurSalaire" runat="server" ForeColor="#FF3300"></asp:Label>
</td>
</tr>
<tr>
<td>
Impôt à payer
</td>
<td>
<asp:Label ID="LabelImpot" runat="server" BackColor="#99CCFF"></asp:Label>
</td>
</tr>
</table>
<br />
<table>
<tr>
<td>
<asp:Button ID="ButtonCalculer" runat="server" Text="Calculer" OnClick="ButtonCalculer_Click" />
</td>
<td>
<asp:Button ID="ButtonEffacer" runat="server" Text="Effacer" OnClick="ButtonEffacer_Click" />
</td>
<td>
</td>
</tr>
</table>
</div>
</ContentTemplate>
</asp:UpdatePanel>
</form>
</body>
</html>
يمكن التعرف على مكونات النموذج في الأسطر 23 و24 و33 و36 و44 و47 و55 و63 و66. أما الباقي فهو أساسًا تنسيق.
لنعد إلى البنية التي يتعين علينا إنشاؤها:
![]() |
- تم تنفيذ [1] بواسطة [Default.aspx]
- سيتم تنفيذ [2] بواسطة [Default.aspx.cs]
- تم تنفيذ [3] بواسطة DLL [ImpotV9-metier]
تم الآن تنفيذ الطبقات [1] و [3]. يبقى لنا كتابة الطبقة [2]، وهي الطبقة التي تقوم بإنشاء النموذج، وإرساله إلى المستخدم، ومعالجته عندما يعيده المستخدم بعد ملئه، واستخدام الطبقة [3] لحساب الضريبة، وإنشاء صفحة الويب الخاصة بالرد على المستخدم وإرسالها إليه. الرمز [Default.aspx.cs] هو الذي يقوم بكل هذا العمل:
using System;
using WsImpot;
public partial class _Default : System.Web.UI.Page
{
protected void ButtonCalculer_Click(object sender, EventArgs e)
{
...
}
protected void ButtonEffacer_Click(object sender, EventArgs e)
{
...
}
}
وهو رمز قريب جدًا من رمز نموذج Windows التقليدي. وهذه هي الميزة الرئيسية لتقنية ASP.NET: لا يوجد فاصل بين نموذج برمجة Windows ونموذج برمجة الويب ASP.NET. يجب فقط تذكر المخطط التالي دائمًا:
![]() |
عندما ينقر المستخدم في [1] على الزر [Calculer]، سيتم تنفيذ الإجراء ButtonCalculer_Click الموجود في السطر 6 من [Default.aspx.cs]. ولكن في غضون ذلك:
- ستنتقل قيم النموذج المملوء من المتصفح إلى خادم الويب عبر بروتوكول Http
- سيقوم الخادم ASP.NET بتحليل الطلب ونقله إلى الصفحة [Default.aspx]
- سيتم إنشاء مثيل للصفحة [Default.aspx].
- سيتم تهيئة مكوناتها (RadioButtonOui، RadioButtonNon، TextBoxEnfants، TextBoxSalaire، LabelErreurEnfants، LabelErreurSalaire، LabelImpot) سيتم تهيئتها بالقيمة التي كانت عليها عندما تم إرسال النموذج في البداية إلى المتصفح بفضل آلية تسمى "ViewState".
- سيتم تعيين القيم المرسلة إلى مكوناتها (RadioButtonOui، RadioButtonNon، TextBoxEnfants، TextBoxSalaire). وبالتالي، إذا أدخل المستخدم 2 كعدد للأطفال، فسيكون لدينا TextBoxEnfants.Text="2".
- إذا كانت الصفحة [Default.aspx] تحتوي على طريقة [Page_Load]، فسيتم تنفيذها
- سيتم تنفيذ الطريقة [ButtonCalculer_Click] في السطر 6 إذا تم النقر على الزر [Calculer]
- سيتم تنفيذ الطريقة [ButtonEffacer_Click] في السطر 10 إذا تم النقر على الزر [Effacer]
بين اللحظة التي ينشئ فيها المستخدم حدثًا في متصفحه واللحظة التي تتم معالجته في [Default.aspx.cs]، هناك تعقيد كبير. هذا التعقيد مخفي ويمكننا التظاهر بأنه غير موجود عند كتابة معالجات الأحداث لصفحة الويب. ولكن يجب ألا ننسى أبدًا أن هناك شبكة بين الحدث ومديره، وبالتالي لا يمكن إدارة أحداث الماوس مثل Mouse_Move التي قد تتسبب في عمليات ذهاب وإياب مكلفة بين العميل والخادم ...
رمز معالجات النقرات على الأزرار [Calculer] و [Effacer] هو الرمز الذي كنا سنكتبه لتطبيق ويندوز كلاسيكي:
protected void ButtonCalculer_Click(object sender, EventArgs e)
{
// التحقق من البيانات
int nbEnfants;
bool erreur = false;
if (!int.TryParse(TextBoxEnfants.Text.Trim(), out nbEnfants) || nbEnfants < 0)
{
LabelErreurEnfants.Text = "Valeur incorrecte...";
erreur = true;
}
int salaire;
if (!int.TryParse(TextBoxSalaire.Text.Trim(), out salaire) || salaire < 0)
{
LabelErreurSalaire.Text = "Valeur incorrecte...";
erreur = true;
}
// خطأ؟
if (erreur) return;
// يتم مسح الأخطاء المحتملة
LabelErreurEnfants.Text = "";
LabelErreurSalaire.Text = "";
// الحالة الاجتماعية
bool marié = RadioButtonOui.Checked;
// حساب الضريبة
try
{
LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));
}
catch (Exception ex)
{
LabelImpot.Text = ex.Message;
}
}
- لفهم هذا الكود، يجب معرفة
- أنه عند بدء تنفيذه، يكون النموذج [Default.aspx] بالشكل الذي ملأه به المستخدم. وبالتالي، تحتوي الحقول (RadioButtonOui، RadioButtonNon، TextBoxEnfants، TextBoxSalaire) على القيم التي أدخلها المستخدم.
- وبعد انتهاء التنفيذ، سيتم إرجاع نفس الصفحة [Default.aspx] إلى المستخدم. ويتم ذلك تلقائيًا.
لذلك يجب على الإجراء ButtonCalculer_Click أن يقوم، انطلاقًا من القيم الحالية للحقول (RadioButtonOui، RadioButtonNon، TextBoxEnfants، TextBoxSalaire) تحديد قيمة جميع الحقول (RadioButtonOui، RadioButtonNon، TextBoxEnfants، TextBoxSalaire، LabelErreurEnfants، LabelErreurSalaire، LabelImpot) للصفحة الجديدة [Default.aspx] التي سيتم إرجاعها إلى المستخدم.
لا توجد صعوبة خاصة في هذا الكود. فقط السطر 27 يستحق الشرح. فهو يستخدم الطريقة CalculerImpot لحقل Global.Metier الذي لم يتم ذكره. سنعود إلى هذا الموضوع قريبًا.
الطريقة ButtonEffacer_Click هي كما يلي:
protected void ButtonEffacer_Click(object sender, EventArgs e)
{
// إعادة تعيين النموذج
TextBoxEnfants.Text = "";
TextBoxSalaire.Text = "";
LabelImpot.Text = "";
LabelErreurEnfants.Text = "";
LabelErreurSalaire.Text = "";
}
لنعد إلى البنية التي يتعين علينا إنشاؤها:
![]() |
- تم تنفيذ [1] بواسطة [Default.aspx]
- تم تنفيذ [2] بواسطة [Default.aspx.cs]
- تم تنفيذ [3] بواسطة DLL [ImpotV9-metier]
يبقى لنا وضع "الرابط" حول هذه الطبقات الثلاث. يتعلق الأمر أساسًا بـ:
- إنشاء مثيل للطبقة [3] عند بدء تشغيل التطبيق
- وضع مرجع لها في مكان يمكن لصفحة الويب [Default.aspx.cs] الوصول إليه كلما تم إنشاء مثيل لها وطلب منها حساب الضريبة.
هذه ليست مشكلة جديدة. فقد تمت مواجهتها بالفعل عند إنشاء خدمة الويب البعيدة وتم دراستها في الفقرة 12.4.1. ومن المعروف أن الحل يتمثل في:
- في إنشاء ملف [Global.asax] مرتبط بفئة [Global.cs]
- إنشاء مثيل للطبقة [3] في الطريقة Application_Start التابعة للطبقة [Global.cs]
- وضع مرجع الطبقة [3] في حقل ثابت من الفئة [Global.cs]، لأن مدة حياة هذه الفئة هي مدة حياة التطبيق.
وبالتالي، يتطور مشروع الويب الخاص بنا على النحو التالي:
![]() |
- إلى [1]، والملف [Global.asax].
- إلى [2]، والرمز [Global.cs] المرتبط به. المجلد [App_Code] الذي يوجد فيه هذا الملف غير موجود افتراضيًا في الحل الويب. استخدم [3] لإنشائه.
الملف Global.asax هو التالي:
<%@ Application Language="C#" Inherits="WsImpot.Global"%>
الرمز [Global.cs] هو التالي:
using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
public class Global : System.Web.HttpApplication
{
// طبقة الأعمال
public static IImpotMetier Metier;
// الطريقة التي يتم تنفيذها عند بدء تشغيل التطبيق
private void Application_Start(object sender, EventArgs e)
{
// مثيلات الطبقات [metier] و [dao]
Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
}
}
}
- السطر 6: تسمى الفئة Global وهي جزء من مساحة الأسماء WsImpot (السطر 4). وبالتالي، فإن اسمها الكامل هو WsImpot.Global، وهذا هو الاسم الذي يجب إدخاله في السمة Inherits لـ Global.asax.
- السطر 6: نعلم أن الفئة المرتبطة بـ Global.asax يجب أن تكون مشتقة بالضرورة من الفئة System.Web.HttpApplication.
- السطر 12: الطريقة Application_Start التي يتم تنفيذها عند بدء تشغيل تطبيق الويب.
- السطر 15: يتم إنشاء مثيل للطبقة [metier] (الطبقة [3] للتطبيق قيد الإنشاء) باستخدام Spring والتكوين التالي في [web.config]:
<!-- كائنات Spring -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="metier" type="Metier.ImpotMetierWS, ImpotsV9-metier">
</object>
</objects>
</spring>
توجد الفئة [Metier.ImpotMetierWS] في السطر (g) أعلاه في [ImpotsV9-metier.dll].
يتم وضع مرجع الطبقة [metier] التي تم إنشاؤها في الحقل الثابت في السطر 9. وهذا الحقل هو الذي يتم استخدامه في السطر 27 من الإجراء ButtonCalculer_Click:
LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));
نحن جاهزون لإجراء الاختبار. يجب تشغيل SGBD MySQL5، خدمة الويب البعيدة، وتدوين المنفذ الذي تعمل عليه:
![]() |
بعد ذلك، يجب التحقق من أن المنفذ الخاص بخدمة الويب البعيدة صحيح في ملف [web.config] الخاص بعميل الويب:
![]() |
بعد ذلك، يمكن تشغيل عميل الويب الخاص بالخدمة الويب البعيدة بالضغط على Ctrl-F5:
![]() |
12.6. عميل Java لخدمة الويب لحساب الضرائب
لإثبات أن خدمات الويب يمكن الوصول إليها بواسطة عملاء مكتوبين بأي لغة، نقوم بكتابة عميل Java بسيط للوحدة. ستكون بنية تطبيق العميل/الخادم كما يلي:
![]() |
- سيتم كتابة العميل [1] بلغة Java
- الخادم [2] هو الذي تمت كتابته بلغة C#
أولاً، سنقوم بتغيير تفصيل واحد في خدمة الويب الخاصة بنا لحساب الضرائب. تعريفه الحالي في [ServiceImpot.cs] هو كما يلي:
...
public class ServiceImpot : System.Web.Services.WebService
{
[WebMethod]
public int CalculerImpot(bool marié, int nbEnfants, int salaire)
{
return Global.Metier.CalculerImpot(marié, nbEnfants, salaire);
}
}
أظهرت الاختبارات أن التشديد في المعلمة marié في السطرين 6 و 8 قد يشكل مشكلة في التوافق بين Java و C#. سنعتمد التعريف الجديد التالي:
...
public class ServiceImpot : System.Web.Services.WebService
{
[WebMethod]
public int CalculerImpot(bool marie, int nbEnfants, int salaire)
{
return Global.Metier.CalculerImpot(marie, nbEnfants, salaire);
}
}
سيتم وضع هذه الخدمة في مشروع Web Developer جديد يسمى WsImpotsSansAccents. وسيكون عنوان URL لخدمة الويب هو [/WsImpotSansAccents/ServiceImpot.asmx].

لكتابة عميل Java، سنستخدم IDE Netbeans [http://www.netbeans.org/]:
![]() |
- في [1]، قم بإنشاء مشروع جديد
- في [2,3]، اختر مشروعًا من نوع Java Application
- في [4]، انتقل إلى الخطوة التالية
- في [5]، قم بتسمية المشروع
- في [6]، حدد المجلد الذي سيتم فيه إنشاء مجلد فرعي يحمل اسم المشروع
- في [7]، قم بتسمية الفئة التي ستحتوي على الطريقة main التي يتم تنفيذها عند بدء تشغيل التطبيق
- في [8]، إنهاء المساعد
![]() |
- في [9]: مشروع Java الذي تم إنشاؤه
- في [10]: انقر بزر الماوس الأيمن على المشروع لإنشاء عميل خدمة الويب لحساب الضرائب
![]() |
- إلى [11]، عنوان URL للملف الذي يصف خدمة الويب لحساب الضرائب:
http://localhost:1089/WsImpotSansAccents/ServiceImpot.asmx?WSDL
هذا الرابط هو رابط الخدمة [ServiceImpot.asmx] الذي نضيف إليه المعلمة ?WSDL. يصف المستند الموجود في هذا الرابط بلغة Xml ما يمكن أن تقوم به الخدمة [15]. وهو عنصر قياسي في خدمة الويب.
- في [12]، الحزمة (المكافئة لمساحة أسماء C#) التي ستوضع فيها الفئات التي سيتم إنشاؤها
- في [13]، اترك القيمة الافتراضية
- في [14]، قم بإنهاء المعالج
![]() |
- في [16]، تم دمج خدمة الويب المستوردة في مشروع Java. وهي تدعم بروتوكولي اتصال هما Soap و Soap12.
- في [17]، الفئة [Main] التي سنستخدم فيها العميل الذي تم إنشاؤه
![]() |
- في [18]، سنقوم بإدراج كود في الطريقة [main]. ضع المؤشر في المكان الذي يجب إدراج الكود فيه، وانقر بزر الماوس الأيمن واختر الخيار [19]
- في [20]، حدد أنك تريد إنشاء كود استدعاء الدالة CalculerImpot لخدمة حساب الضريبة عن بُعد، ثم انقر فوق "موافق".
الرمز الذي تم إنشاؤه في [Main] هو التالي:
يوضح الرمز الذي تم إنشاؤه كيفية استدعاء الدالة CalculerImpot الخاصة بخدمة حساب الضرائب عن بُعد. إذا قارنا ذلك بما رأيناه في C#، فإن المتغير port في السطر 7 هو ما يعادل العميل المستخدم في C#. لن نعلق أكثر على هذا الرمز. سنقوم بإعادة تنظيمه على النحو التالي:
- السطر 1: نستورد الفئة ServiceImpot التي تمثل العميل الذي تم إنشاؤه بواسطة المساعد.
- السطر 6: نستدعي الطريقة البعيدة CalculerImpot باتباع الإجراء الموضح في الكود الذي تم إنشاؤه في main.
النتائج التي تم الحصول عليها في وحدة التحكم عند التنفيذ (F6) هي كما يلي:



































































