10. خدمات الويب
10.1. مقدمة
في الفصل السابق، عرضنا عدة تطبيقات عميل-خادم تعمل بنظام TCP/IP. ونظرًا لأن العملاء والخادم يتبادلون أسطر النص، فيمكن كتابة هذه التطبيقات بأي لغة برمجية. ويكتفي العميل بمعرفة بروتوكول الاتصال الذي يتوقعه الخادم. خدمات الويب هي تطبيقات خادم تعمل بنظام TCP/IP وتتميز بالخصائص التالية:
- يتم استضافتها بواسطة خوادم الويب، وبالتالي فإن بروتوكول الاتصال بين العميل والخادم هو HTTP (بروتوكول نقل النص التشعبي)، وهو بروتوكول يعمل عبر TCP/IP.
- تتمتع خدمة الويب ببروتوكول اتصال قياسي بغض النظر عن الخدمة المقدمة. تقدم خدمة الويب خدمات متنوعة S1، S2، ..، Sn. يتوقع كل منها معلمات مقدمة من العميل ويعيد نتيجة إلى العميل. بالنسبة لكل خدمة، يحتاج العميل إلى معرفة:
- الاسم الدقيق للخدمة Si
- قائمة المعلمات المطلوب توفيرها وأنواعها
- نوع النتيجة التي تعيدها الخدمة
بمجرد معرفة هذه العناصر، يتبع التفاعل بين العميل والخادم نفس التنسيق بغض النظر عن خدمة الويب التي يتم الاستعلام عنها. وبالتالي، يتم توحيد كود العميل.
- لأسباب أمنية تتعلق بالهجمات التي تنشأ من الإنترنت، تحتفظ العديد من المؤسسات بشبكات خاصة وتفتح فقط منافذ معينة على خوادمها للإنترنت: بشكل أساسي المنفذ 80 لخدمة الويب. يتم قفل جميع المنافذ الأخرى. وبالتالي، يتم إنشاء تطبيقات العميل-الخادم مثل تلك المعروضة في الفصل السابق داخل الشبكة الخاصة (الإنترانت) ولا يمكن الوصول إليها عمومًا من الخارج. استضافة خدمة على خادم ويب تجعلها متاحة لمجتمع الإنترنت بأكمله.
- يمكن نمذجة خدمة الويب ككائن بعيد. ثم تصبح الخدمات المقدمة طرقًا لهذا الكائن. يمكن للعميل الوصول إلى هذا الكائن البعيد كما لو كان محليًا. هذا يخفي طبقة اتصال الشبكة بالكامل ويسمح بتطوير عميل مستقل عن تلك الطبقة. إذا تغيرت الطبقة، فلن يحتاج العميل إلى التعديل. هذه ميزة كبيرة وربما الفائدة الرئيسية لخدمات الويب.
- كما هو الحال مع تطبيقات خادم-عميل TCP/IP التي تم عرضها في الفصل السابق، يمكن كتابة العميل والخادم بأي لغة. يتبادلان أسطر النص. تتكون هذه من جزأين:
- الرؤوس المطلوبة بواسطة بروتوكول HTTP
- نص الرسالة. بالنسبة لاستجابة الخادم للعميل، يكون نص الرسالة بتنسيق XML (لغة الترميز القابلة للتوسيع). بالنسبة لطلب العميل إلى الخادم، يمكن أن يتخذ نص الرسالة عدة أشكال، بما في ذلك XML. قد يستخدم طلب XML الخاص بالعميل تنسيقًا محددًا يسمى SOAP (بروتوكول الوصول البسيط إلى الكائنات). في هذه الحالة، تتبع استجابة الخادم أيضًا تنسيق SOAP.
10.2. المتصفحات و XML
ترسل خدمات الويب XML إلى عملائها. قد تتفاعل المتصفحات بشكل مختلف عند تلقي دفق XML هذا. يحتوي Internet Explorer على ورقة أنماط محددة مسبقًا تسمح له بعرض XML. ومع ذلك، لا يحتوي Netscape Communicator على ورقة الأنماط هذه ولا يعرض كود XML المستلم. يجب عليك عرض كود المصدر للصفحة المستلمة للوصول إلى XML. فيما يلي مثال. بالنسبة لكود XML التالي:
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="st.istia.univ-angers.fr">bonjour de nouveau !</string>
سيعرض Internet Explorer الصفحة التالية:

بينما سيعرض متصفح Netscape Navigator:

إذا قمنا بعرض شفرة المصدر للصفحة التي استقبلها Netscape، فسنحصل على:

لقد تلقى Netscape بالفعل نفس المحتوى الذي تلقّاه Internet Explorer، لكنه عرضه بشكل مختلف. من الآن فصاعدًا، سنستخدم Internet Explorer لالتقاط لقطات الشاشة.
10.3. أول خدمة ويب
سنستكشف خدمات الويب من خلال مثال بسيط للغاية متوفر في ثلاث نسخ.
10.3.1. الإصدار 1
بالنسبة لهذا الإصدار الأول، سنستخدم VS.NET، الذي يتميز بقدرته على إنشاء هيكل خدمة ويب جاهز للتشغيل على الفور. بمجرد فهمنا لهذه البنية، سنتمكن من البدء في العمل بشكل مستقل. وسيكون هذا هو محور التركيز في الإصدارات التالية.
باستخدام VS.NET، لنقم بإنشاء مشروع جديد عبر خيار [File/New/Project]:

لاحظ النقاط التالية:
- نوع المشروع هو Visual Basic (الجزء الأيسر)
- قالب المشروع هو ASP.NET Web Service (الجزء الأيمن)
- الموقع مرن. هنا، سيتم استضافة خدمة الويب بواسطة خادم ويب IIS محلي. وبالتالي، سيكون عنوان URL الخاص بها هو http://localhost/[path] حيث يجب تحديد [path]. هنا، نختار المسار http://localhost/polyvbnet/demo. سيقوم VS.NET بعد ذلك بإنشاء مجلد لهذا المشروع. أين؟ يحتوي خادم IIS على دليل جذر لشجرة مستندات الويب التي يقدمها. دعونا نسمي هذا الجذر <IISroot>. وهو يتوافق مع عنوان URL http://localhost. يمكننا استنتاج أن عنوان URL http://localhost/polyvbnet/demo سيكون مرتبطًا بالمجلد <IISroot>/polyvbnet/demo. عادةً ما يكون <IISroot> هو المجلد \inetpub\wwwroot على محرك الأقراص الذي تم تثبيت IIS عليه. في مثالنا، هذا هو محرك الأقراص E. وبالتالي، فإن المجلد الذي أنشأه VS.NET هو المجلد e:\inetpub\wwwroot\polyvbnet\demo:

كما هو الحال دائمًا، هناك عدد كبير من المجلدات التي تم إنشاؤها. وهي ليست مفيدة دائمًا. سنشرح فقط تلك التي نحتاجها في وقت معين. قام VS.NET بإنشاء مشروع:

نجد بعض الملفات الموجودة في المجلد الفعلي للمشروع. الملف الأكثر إثارة للاهتمام بالنسبة لنا هو الملف الذي يحمل الامتداد .asmx. هذا هو الامتداد الخاص بخدمات الويب. تتم إدارة خدمة الويب بواسطة VS.NET كتطبيق Windows، أي تطبيق يحتوي على واجهة مستخدم رسومية ورمز لإدارته. لهذا السبب لدينا نافذة تصميم:

لا تحتوي خدمة الويب عادةً على واجهة مستخدم رسومية. فهي تمثل كائنًا يمكن استدعاؤه عن بُعد. ولديها طرق، وتستدعي التطبيقات هذه الطرق. لذلك سننظر إليها ككائن كلاسيكي يتميز بخاصية فريدة وهي إمكانية إنشاء مثيل له عن بُعد عبر الشبكة. ولذلك، لن نستخدم نافذة التصميم التي يوفرها VS.NET. بدلاً من ذلك، دعونا نركز على كود الخدمة باستخدام خيار "عرض/كود":

هناك عدة نقاط جديرة بالملاحظة:
- اسم الملف هو Service1.asmx.vb، وليس Service1.asmx. سنعود إلى محتويات ملف Service1.asmx لاحقًا.
- نرى نافذة كود مشابهة لتلك التي كانت لدينا عند تطوير تطبيقات Windows باستخدام VS.NET
الرمز الذي تم إنشاؤه بواسطة VS.NET هو كما يلي:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
Inherits System.Web.Services.WebService
#Region " Code généré par le Concepteur des services Web "
Public Sub New()
MyBase.New()
'This call is required by the Web Services Designer.
InitializeComponent()
'Add your initialization code after the InitializeComponent() call
End Sub
'Required by the Web Services Designer
Private components As System.ComponentModel.IContainer
'REMARQUE: the following procedure is required by the Web Services Designer
'It can be modified using the Web Services Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
components = New System.ComponentModel.Container()
End Sub
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
'CODEGEN: this procedure is required by the Web Services Designer
'Do not modify it using the code editor.
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
#End Region
' EXEMPLE DE SERVICE WEB
' The HelloWorld() service example returns the string Hello World.
' To generate, leave the following lines uncommented, then save and generate the project.
' To test this Web service, make sure that the .asmx file is the start page
' and press F5.
'
'<WebMethod()> Public Function HelloWorld() As String
' HelloWorld = "Hello World
' End Function
End Class
أولاً، لاحظ أن لدينا هنا فئة، وهي فئة Service1، التي تنبثق من فئة WebService:
وهذا يقودنا إلى استيراد مساحة أسماء System.Web.Services:
Imports System.Web.Services
يسبق إعلان الفئة سمة تجميع:
<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
Inherits System.Web.Services.WebService
تشير السمة System.Web.Services.WebService() إلى أن الفئة التالية هي خدمة ويب. تقبل هذه السمة معلمات متنوعة، بما في ذلك معلمة تسمى NameSpace. تُستخدم لوضع خدمة الويب في مساحة أسماء. في الواقع، يمكن للمرء أن يتخيل وجود عدة خدمات ويب باسم "weather" في العالم. نحتاج إلى طريقة للتمييز بينها. ويجعل مساحة الاسم ذلك ممكنًا. يمكن تسمية إحداها [namespace1].weather والأخرى [namespace2].weather. وهذا مفهوم مشابه لمساحات أسماء الفئات. قام VS.NET بإنشاء الكود تلقائيًا ووضعه في منطقة من المصدر:
#Region " Code généré par le Concepteur des services Web "
إذا نظرنا إلى هذا الكود، فسنجد أنه نفس الكود الذي أنشأه المصمم عندما قمنا بإنشاء تطبيقات Windows. هذا كود يمكننا حذفه ببساطة إذا لم يكن لدينا واجهة مستخدم رسومية، وهو ما سيكون الحال بالنسبة لخدمات الويب.
تختتم الفصل بمثال على الشكل الذي قد تبدو عليه خدمة الويب:
#End Region
' EXEMPLE DE SERVICE WEB
' L'exemple de service HelloWorld() retourne la chaîne Hello World.
' Pour générer, ne commentez pas les lignes suivantes, puis enregistrez et générez le projet.
' Pour tester ce service Web, assurez-vous que le fichier .asmx est la page de démarrage
' et appuyez sur F5.
'
'<WebMethod()> Public Function HelloWorld() As String
' HelloWorld = "Hello World"
' End Function
بناءً على ما قيل للتو، نقوم بتنظيف الكود ليصبح كما يلي:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class Bonjour
Inherits System.Web.Services.WebService
<WebMethod()> Public Function Bonjour() As String
Return "bonjour !"
End Function
End Class
الآن أصبح لدينا صورة أوضح.
- خدمة الويب هي فئة مشتقة من فئة WebService
- يتم تحديد الفئة بواسطة السمة <System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>. لذلك نضع خدمتنا في مساحة الاسم st.istia.univ-angers.fr.
- يتم تحديد أساليب الفئة بواسطة سمة <WebMethod()> تشير إلى أننا نتعامل مع أسلوب يمكن استدعاؤه عن بُعد عبر الشبكة
وبالتالي، فإن الفئة التي توفر خدمة الويب لدينا تسمى Bonjour وتحتوي على طريقة واحدة، تسمى أيضًا Bonjour، والتي تُرجع سلسلة. نحن جاهزون لإجراء اختبار أولي.
- قم بتشغيل خادم الويب IIS إذا لم تكن قد قمت بذلك بالفعل
- استخدم خيار Debug/Run Without Debugging (تصحيح/تشغيل بدون تصحيح). VS.NET
سيقوم VS.NET بعد ذلك بترجمة التطبيق بالكامل، وتشغيل متصفح (غالبًا Internet Explorer إذا كان متاحًا)، وعرض عنوان URL http://localhost/polyvbnet/demo/Service1.asmx:

لماذا عنوان URL http://localhost/polyvbnet/demo/Service1.asmx؟ لأنه كان الملف .asmx الوحيد في المشروع:

لو كان هناك عدة ملفات .asmx، لكان علينا تحديد أي منها يجب تنفيذه أولاً. ويتم ذلك بالنقر بزر الماوس الأيمن على ملف .asmx ذي الصلة واختيار الخيار [Set as Start Page] (تعيين كصفحة بدء).

قد تكون مهتمًا بمعرفة ما يحتوي عليه ملف service1.asmx. في الواقع، باستخدام VS.NET، عملنا على ملف service1.asmx.vb وليس على ملف service1.asmx. يقع هذا الملف في مجلد المشروع:

لنفتحه باستخدام محرر نصوص (Notepad أو غيره). نحصل على المحتوى التالي:
يحتوي الملف على توجيه بسيط لخادم IIS يشير إلى:
- أن هذه خدمة ويب (الكلمة الرئيسية WebService)
- أن لغة فئة هذه الخدمة هي Visual Basic (Language="vb")
- أن شفرة المصدر لهذه الفئة موجودة في الملف Service1.asmx.vb (Codebehind="Service1.asmx.vb")
- أن الفئة التي تنفذ الخدمة تسمى demo.Bonjour (Class="demo.Bonjour"). لاحظ أن VS.NET قد وضع فئة Bonjour في مساحة اسم demo، والتي هي أيضًا اسم المشروع.
لنعد إلى الصفحة التي تم الوصول إليها على عنوان URL http://localhost/polyvbnet/demo/Service1.asmx:

من كتب كود HTML للصفحة أعلاه؟ ليس نحن — نحن نعلم ذلك. إنه IIS، الذي يقدم خدمات الويب بطريقة قياسية. توفر لنا هذه الصفحة رابطين. دعونا نتبع الرابط الأول [وصف الخدمة]:

أوه... هذا XML غامض للغاية. لاحظ، مع ذلك، عنوان URL
http://localhost/polyvbnet/demo/Service1.asmx?WSDL. افتح متصفحًا واكتب عنوان URL هذا مباشرةً. ستحصل على نفس النتيجة السابقة. لذا، تذكر أن عنوان URL http://serviceweb?WSDL يتيح الوصول إلى الوصف XML لخدمة الويب. لنعد إلى الصفحة الرئيسية ونضغط على رابط [Hello]. تذكر أن Hello هي طريقة من طرق خدمة الويب. لو كانت هناك طرق متعددة، لكانت جميعها مدرجة هنا. نحصل على الصفحة الجديدة التالية:

لقد قمنا عمدًا باقتطاع الصفحة الناتجة للحفاظ على إيجاز العرض التوضيحي. لاحظ عنوان URL مرة أخرى:
إذا كتبنا عنوان URL هذا مباشرةً في المتصفح، فسنحصل على نفس النتيجة المذكورة أعلاه. يُطلب منا استخدام زر [Call]. لنفعل ذلك. تظهر لنا صفحة جديدة:

هذا XML مرة أخرى. يحتوي على معلومتين كانتا موجودتين في خدمة الويب الخاصة بنا:
- مساحة الاسم st.istia.univ-angers.fr لخدمتنا
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>
- القيمة التي تعيدها طريقة Bonjour:
Return "bonjour !"
ماذا تعلمنا؟
- كيفية كتابة خدمة ويب S
- كيفية استدعائها
سننظر الآن في كتابة خدمة ويب دون استخدام VS.NET.
10.3.2. الإصدار 2
في المثال السابق، قام VS.NET بالعديد من المهام بنفسه. هل من الممكن إنشاء خدمة ويب بدون هذه الأداة؟ الإجابة هي نعم، وسنوضح لك كيفية القيام بذلك الآن. باستخدام محرر نصوص، سنقوم بإنشاء خدمة الويب التالية:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class Bonjour2
Inherits System.Web.Services.WebService
<WebMethod()> Public Function getBonjour() As String
Return "bonjour de nouveau !"
End Function
End Class
تسمى الفئة Bonjour2 وتحتوي على طريقة تسمى getBonjour. وهي موجودة في ملف demo2.vb، الذي يقع بدوره في بنية دليل خادم IIS في المجلد E:\Inetpub\wwwroot\polyvbnet\demo2. وهي فئة VB.NET قياسية يمكن تجميعها:
dos>vbc /out:demo2 /t:library /r:system.dll /r:system.web.services.dll demo2.vb
dos>dir
02/03/2004 18:04 286 demo2.vb
02/03/2004 18:10 77 demo2.asmx
02/03/2004 18:12 3 072 demo2.dll
نضع ملف التجميع demo2.dll في مجلد bin (هذا الاسم مطلوب):
نقوم الآن بإنشاء ملف demo2.asmx. هذا هو الملف الذي سيتم استدعاؤه بواسطة عملاء الويب. ومحتوياته هي كما يلي:
لقد سبق أن صادفنا هذا التوجيه. وهو يشير إلى أن:
- فئة خدمة الويب تسمى Bonjour2 وتقع في تجميع demo2.dll. سيبحث IIS عن هذا التجميع في مواقع مختلفة، بما في ذلك مجلد bin الخاص بخدمة الويب. ولهذا السبب وضعنا تجميع demo2.dll هناك.
الآن يمكننا إجراء اختبارات مختلفة. نتأكد من تشغيل IIS ونطلب عنوان URL http://localhost/polyvbnet/demo2/demo2.asmx في متصفح:

ثم عنوان URL http://localhost/polyvbnet/demo2/demo2.asmx?WSDL

ثم عنوان URL http://localhost/polyvbnet/demo2/demo2.asmx?op=getBonjour، حيث getBonjour هو اسم الطريقة الوحيدة في خدمة الويب الخاصة بنا:

نستخدم زر [Call] أعلاه:

لقد نجحنا في الحصول على نتيجة استدعاء طريقة getBonjour الخاصة بخدمة الويب. أصبحنا الآن نعرف كيفية إنشاء خدمة ويب بدون Visual Studio .NET. من الآن فصاعدًا، سنتجاهل التفاصيل الخاصة بكيفية إنشاء خدمة الويب وسنركز فقط على الملفات الأساسية.
10.3.3. الإصدار 3
استخدمت النسختان السابقتان من خدمة الويب [Hello] ملفين:
- ملف .asmx، وهو نقطة دخول خدمة الويب
- ملف .vb، وهو شفرة مصدر خدمة الويب
هنا، نوضح أن ملف .asmx واحد كافٍ. فيما يلي كود خدمة demo3.asmx:
يمكننا أن نرى أن كود مصدر الخدمة موجود الآن مباشرة في ملف المصدر demo3.asmx. التوجيه
لم تعد تشير إلى فئة في تجميع خارجي، بل إلى فئة موجودة في نفس ملف المصدر. لنضع هذا الملف في المجلد <IISroot>\polyvbnet\demo3:

لنقم بتشغيل IIS ونطلب عنوان URL http://localhost/polyvbnet/demo3/demo3.asmx:

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

10.3.4. الإصدار 4
نركز هنا على تكوين خادم IIS. حتى الآن، كنا نضع خدمات الويب الخاصة بنا دائمًا في الدليل الجذري <IISroot> لخادم IIS، هنا [e:\inetpub\wwwroot]. نوضح هنا أنه يمكننا وضع خدمة الويب في أي مكان. يتم ذلك باستخدام الدلائل الافتراضية لـ IIS. لنضع خدمتنا في الدليل التالي:

المجلد [D:\data\devel\vbnet\poly\chap9\demo3] غير موجود في شجرة دليل خادم IIS. يجب علينا تحديد هذا المجلد لـ IIS عن طريق إنشاء مجلد افتراضي في IIS. دعونا نُشغّل IIS ونختار الخيار [متقدم] أدناه:

تظهر قائمة بالدلائل الافتراضية. لن نتطرق إلى هذه القائمة. نقوم بإنشاء دليل افتراضي جديد باستخدام الزر [Add] أعلاه:

باستخدام الزر [Browse]، نختار المجلد الفعلي الذي يحتوي على خدمة الويب، وهو في هذه الحالة المجلد [D:\data\devel\vbnet\poly\chap9\demo3]. نسمي هذا المجلد باسم منطقي (افتراضي): [virdemo3]. وهذا يعني أن المستندات الموجودة داخل المجلد الفعلي [D:\data\devel\vbnet\poly\chap9\demo3] ستكون متاحة على الشبكة عبر عنوان URL [http://<machine>/virdemo3]. يحتوي مربع الحوار أعلاه على إعدادات أخرى نتركها كما هي. نضغط على "موافق". يظهر المجلد الافتراضي الجديد في قائمة المجلدات الافتراضية في IIS:

الآن، نفتح متصفحًا ونطلب عنوان URL [http://localhost/virdemo3/demo3.asmx]. نحصل على نفس النتيجة كما في السابق:

10.3.5. الخلاصة
لقد أوضحنا عدة طرق لإنشاء خدمة ويب. في المستقبل، سنستخدم الطريقة الواردة في الإصدار 3 لإنشاء الخدمة والطريقة 4 لنشرها. بهذه الطريقة، لن نحتاج إلى VS.NET. ومع ذلك، تجدر الإشارة إلى فوائد استخدام VS.NET للمساعدة في تصحيح الأخطاء التي يوفرها. تتوفر أدوات مجانية لتطوير تطبيقات الويب، ولا سيما منتج WebMatrix الذي ترعاه Microsoft، والذي يمكن العثور عليه على الرابط [http://www.asp.net/webmatrix]. إنه أداة ممتازة للبدء في برمجة الويب دون أي استثمار مسبق.
10.4. خدمة ويب للعمليات
لنفترض وجود خدمة ويب تقدم خمس وظائف:
- add(a,b)، التي تُرجع a+b
- subtract(a,b)، التي تُرجع a-b
- multiply(a,b)، التي تُرجع a*b
- divide(a,b)، التي تُرجع a/b
- doAll(a,b)، والتي تُرجع المصفوفة [a+b, a-b, a*b, a/b]
فيما يلي كود VB.NET لهذه الخدمة:
<%@ WebService language="VB" class=operations %>
imports system.web.services
<WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class operations
Inherits WebService
<WebMethod> _
Function ajouter(a As Double, b As Double) As Double
Return a + b
End Function
<WebMethod> _
Function soustraire(a As Double, b As Double) As Double
Return a - b
End Function
<WebMethod> _
Function multiplier(a As Double, b As Double) As Double
Return a * b
End Function
<WebMethod> _
Function diviser(a As Double, b As Double) As Double
Return a / b
End Function
<WebMethod> _
Function toutfaire(a As Double, b As Double) As Double()
Return New Double() {a + b, a - b, a * b, a / b}
End Function
End Class
نكرر هنا بعض التفسيرات التي سبق تقديمها، ولكنها تستحق إعادة النظر أو التوسع فيها. تشبه فئة العمليات فئة VB.NET، مع بعض النقاط التي يجب ملاحظتها:
- تسبق الطرق سمة <WebMethod()> التي تخبر المُجمِّع بالطرق التي يجب "نشرها"، أي إتاحتها للعميل. الطريقة التي لا تسبقها هذه السمة ستكون غير مرئية للعملاء البعيدين. قد تكون هذه طريقة داخلية تستخدمها طرق أخرى ولكنها غير مخصصة للنشر.
- تستمد الفئة من فئة WebService المحددة في مساحة اسم System.Web.Services. هذا التوريث ليس إلزاميًا دائمًا. في هذا المثال على وجه الخصوص، يمكننا الاستغناء عنه.
- تسبق الفئة نفسها سمة <WebService(Namespace="st.istia.univ-angers.fr")> تهدف إلى توفير مساحة أسماء لخدمة الويب. يقوم مورد الفئة بتعيين مساحة أسماء لفئاته لمنحها اسمًا فريدًا وبالتالي تجنب التعارض مع فئات من موردين آخرين قد تحمل الاسم نفسه. وينطبق الأمر نفسه على خدمات الويب. يجب أن تكون كل خدمة ويب قابلة للتعريف بواسطة اسم فريد، وهو في هذه الحالة st.istia.univ-angers.fr.
- لم نقم بتعريف منشئ. لذلك، سيتم استخدام منشئ الفئة الأصلية ضمناً.
لا يُقصد بالكود المصدري أعلاه أن يُستخدم مباشرةً مع مُركب VB.NET، بل مع خادم الويب IIS. يجب أن يحمل امتداد .asmx وأن يُحفظ في بنية دليل خادم الويب. هنا، نحفظه باسم operations.asmx في المجلد <IISroot>\polyvbnet\operations:

نربط الدليل الظاهري لـ IIS [operations] بهذا الدليل الفعلي:
![]() | ![]() |
دعونا نصل إلى الخدمة باستخدام متصفح. عنوان URL المطلوب هو [http://localhost/operations/operations.asmx]:

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

تدعونا الصفحة التي تظهر إلى اختبار الدالة add من خلال تزويدها بالمعلمتين a وb اللتين تتطلبهما. لنتذكر تعريف الدالة *add*:
لاحظ أن الصفحة استخدمت أسماء الحجج a و b من تعريف الدالة. انقر فوق الزر "Call"، وستظهر الاستجابة التالية في نافذة متصفح منفصلة:

إذا حددت [عرض/المصدر] أعلاه، فستحصل على الكود التالي:

دعونا نكرر العملية بالنسبة لطريقة [toutfaire]:

نحصل على الصفحة التالية:

لنستخدم زر [اتصال] أعلاه:

في جميع الحالات، يكون رد الخادم بالصيغة التالية:
- الرد بتنسيق XML
- السطر 1 قياسي ويكون موجودًا دائمًا في الرد
- الأسطر التالية تعتمد على نوع النتيجة (double، ArrayOfDouble)، وعدد النتائج، ومساحة اسم خدمة الويب (st.istia.univ-angers.fr في هذه الحالة).
هناك عدة طرق للاستعلام عن خدمة ويب والحصول على ردها. لنعد إلى عنوان URL الخاص بالخدمة:

ونتبع الرابط [Add]. في الصفحة التي تظهر، يتم عرض طريقتين للاستعلام عن وظيفة [Add] لخدمة الويب:



تسمى هاتان الطريقتان للوصول إلى وظائف خدمة الويب، على التوالي: HTTP-POST و SOAP. سنقوم الآن بفحصهما واحدة تلو الأخرى.
ملاحظة: في الإصدارات المبكرة من VS.NET، كانت هناك طريقة ثالثة تسمى HTTP-GET. اعتبارًا من تاريخ هذا المستند (مارس 2004)، يبدو أن هذه الطريقة لم تعد متاحة. وهذا يعني أن خدمة الويب التي تم إنشاؤها بواسطة VS.NET لا تقبل طلبات GET. وهذا لا يعني أنه لا يمكنك كتابة خدمات ويب تقبل طلبات GET، خاصةً باستخدام أدوات أخرى غير VS.NET أو ببساطة يدويًا.
10.5. عميل HTTP-POST
سنتبع الطريقة التي تقترحها خدمة الويب:

دعونا نحلل ما هو مكتوب. أولاً، يجب على عميل الويب إرسال رؤوس HTTP التالية:
يقوم عميل الويب بإرسال طلب POST إلى عنوان URL /operations/operations.asmx/add وفقًا لبروتوكول HTTP الإصدار 1.1 | |
نحدد الجهاز المستهدف للطلب. هنا، localhost. أصبح هذا الرأس إلزامياً في الإصدار 1.1 من بروتوكول HTTP | |
يحدد هذا أنه بعد رؤوس HTTP، سيتم إرسال معلمات إضافية بتنسيق urlencoded. يستبدل هذا التنسيق أحرفًا معينة برموزها السداسية العشرية. | |
هذا هو عدد أحرف سلسلة المعلمة التي سيتم إرسالها بعد رؤوس HTTP. |
تلي رؤوس HTTP سطر فارغ، ثم سلسلة معلمات POST المكونة من [Content-Length] حرفًا بالصيغة a=XX&b=YY، حيث XX وYY هما السلسلتان "المشفرتان بـ URL" لقيم المعلمتين a وb. لدينا ما يكفي من المعرفة لإعادة إنتاج ما سبق باستخدام عميل TCP العام الذي استخدمناه بالفعل في الفصل الخاص ببرمجة TCP/IP:
- نقوم بتشغيل IIS
- الخدمة متاحة على عنوان URL [http://localhost/operations/operations.asmx]
- نستخدم عميل TCP العام في نافذة DOS
dos>clttcpgenerique localhost 80
Commandes :
POST /operations/operations.asmx/ajouter HTTP/1.1
HOST: localhost
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-length: 7
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--
a=2&b=3
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:26 GMT
<-- X-Powered-By: ASP.NET
<-- Connection: close
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 90
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">5</double>
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
أولاً، لاحظ أننا أضفنا رأس [Connection: close] لإرشاد الخادم بإغلاق الاتصال بعد إرسال الرد. وهذا ضروري هنا. إذا لم نحدد ذلك، فسيبقي الخادم الاتصال مفتوحًا بشكل افتراضي. ومع ذلك، فإن ردّه عبارة عن سلسلة من الأسطر النصية، لا ينتهي آخرها بحرف نهاية السطر. اتضح أن عميل TCP العام الخاص بنا يقرأ الأسطر النصية التي تنتهي بحرف نهاية السطر باستخدام طريقة ReadLine. إذا لم يقم الخادم بإغلاق الاتصال بعد إرسال السطر الأخير، فسيتم حظر العميل لأنه ينتظر حرف نهاية السطر الذي لا يصل أبدًا. إذا أغلق الخادم الاتصال، فستكتمل طريقة ReadLine الخاصة بالعميل، ولن يتم حظر العميل.
فور استلام السطر الفارغ الذي يشير إلى نهاية رؤوس HTTP، يرسل خادم IIS استجابة أولية:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--
هذا الرد، الذي يتكون فقط من رؤوس HTTP، يُخبر العميل أنه يمكنه إرسال الأحرف السبعة التي قال إنه يريد إرسالها. ما نفعله:
لاحظ أن عميل TCP الخاص بنا يرسل أكثر من 7 أحرف هنا، لأنه يرسلها مع علامة نهاية السطر (WriteLine). لا يؤثر هذا على الخادم، الذي سيأخذ فقط أول 7 أحرف من تلك التي تم استلامها، ولأن الاتصال يتم إغلاقه بعد ذلك (Connection: close). كانت هذه الأحرف الإضافية ستشكل مشكلة لو بقي الاتصال مفتوحًا، حيث كان سيتم تفسيرها على أنها قادمة من الأمر التالي للعميل. بمجرد استلام المعلمات، يرسل الخادم استجابته:
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:26 GMT
<-- X-Powered-By: ASP.NET
<-- Connection: close
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 90
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">5</double>
لدينا الآن العناصر اللازمة لكتابة برنامج عميل لخدمة الويب الخاصة بنا. سيكون هذا البرنامج عميل وحدة تحكم يُسمى httpPost2 ويُستخدم على النحو التالي:
dos>httpPost2 http://localhost/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 6 7
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=6&b=7
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
[résultat=13]
soustraire 8 9
--> POST /operations/operations.asmx/soustraire HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=8&b=9
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">-1</double>
[résultat=-1]
fin
dos>
يتم استدعاء العميل عن طريق تمرير عنوان URL لخدمة الويب إليه:
بعد ذلك، يقرأ العميل الأوامر المكتوبة على لوحة المفاتيح ويقوم بتنفيذها. وتكون هذه الأوامر بالصيغة التالية:
حيث function هي وظيفة خدمة الويب التي يتم استدعاؤها (الجمع، الطرح، الضرب، القسمة) و a و b هما القيمتان اللتان ستعمل عليهما هذه الوظيفة. على سبيل المثال:
من هناك، سيرسل العميل طلب HTTP الضروري إلى خادم الويب ويتلقى استجابة. يتم عرض التبادلات بين العميل والخادم على الشاشة لمساعدتك على فهم العملية بشكل أفضل:
ajouter 6 7
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=6&b=7
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
[résultat=13]
التبادل الموضح أعلاه هو نفسه الذي رأيناه مع عميل TCP العام، مع اختلاف واحد: رأس HTTP **Connection: Keep-Alive يوجه الخادم بعدم إغلاق الاتصال. وبالتالي يبقى الاتصال مفتوحًا للعملية التالية للعميل، بحيث لا يحتاج العميل إلى إعادة الاتصال بالخادم. ومع ذلك، يتطلب هذا من العميل استخدام طريقة أخرى غير ReadLine() لقراءة استجابة الخادم، حيث إننا نعلم أن الاستجابة تتكون من سلسلة من الأسطر، لا ينتهي آخرها بحرف سطر جديد. بمجرد استلام استجابة الخادم بالكامل، يقوم العميل بتحليلها للعثور على نتيجة العملية المطلوبة وعرضها:
دعونا نفحص كود العميل لدينا:
' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Web
' web operations client
Public Module clientPOST
Public Sub Main(ByVal args() As String)
' syntax
Const syntaxe As String = "pg URI"
Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' number of arguments
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' note the URI required
Dim URIstring As String = args(0)
' connect to the server
Dim uri As Uri = Nothing ' the URI of the web service
Dim client As TcpClient = Nothing ' the client's tcp link with the server
Dim [IN] As StreamReader = Nothing ' the customer's reading flow
Dim OUT As StreamWriter = Nothing ' the customer's writing flow
Try
' server connection
uri = New Uri(URIstring)
client = New TcpClient(uri.Host, uri.Port)
' create customer input/output flows TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
Catch ex As Exception
' URI incorrect or other problem
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
' creation of a dictionary of web service functions
Dim dicoFonctions As New Hashtable
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
' user requests are typed on the keyboard
' as function a b
' they are terminated with the command fin
Dim commande As String = Nothing ' keyboard command
Dim champs As String() = Nothing ' command line fields
Dim fonction As String = Nothing ' name of a web service function
Dim a, b As String ' web service function arguments
' invites the user
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b")
' error management
Dim erreurCommande As Boolean
Try
' keyboard command input loop
While True
' no error at start
erreurCommande = False
' read command
commande = Console.In.ReadLine().Trim().ToLower()
' finished?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' breaking down the order into fields
champs = Regex.Split(commande, "\s+")
Try
' three fields are required
If champs.Length <> 3 Then
Throw New Exception
End If
' field 0 must be a recognized function
fonction = champs(0)
If Not dicoFonctions.ContainsKey(fonction) Then
Throw New Exception
End If
' field 1 must be a valid number
a = champs(1)
Double.Parse(a)
' field 2 must be a valid number
b = champs(2)
Double.Parse(b)
Catch
' invalid order
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
erreurCommande = True
End Try
' request the web service
If Not erreurCommande Then executeFonction([IN], OUT, uri, fonction, a, b)
End While
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
' end of client-server link
Try
[IN].Close()
OUT.Close()
client.Close()
Catch
End Try
End Sub
...........
' error display
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Module
لقد رأينا هذه العناصر عدة مرات من قبل، ولا تحتاج إلى أي تعليقات خاصة. دعونا الآن نفحص كود طريقة executeFonction، حيث توجد العناصر الجديدة:
' executeFonction
Public Sub executeFonction(ByVal [IN] As StreamReader, ByVal OUT As StreamWriter, ByVal uri As Uri, ByVal fonction As String, ByVal a As String, ByVal b As String)
' executes function(a,b) on the URI uri web service
' client-server exchanges take place via IN and OUT flows
' the result of the function is in the line
' <double xmlns="st.istia.univ-angers.fr">double</double>
' sent by the server
' query chain construction
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
' construction of header table HTTP to be sent
Dim entetes(5) As String
entetes(0) = "POST " + uri.AbsolutePath + "/" + fonction + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: application/x-www-form-urlencoded"
entetes(3) = "Content-Length: " & nbChars
entetes(4) = "Connection: Keep-Alive"
entetes(5) = ""
' send HTTP headers to the server
Dim i As Integer
For i = 0 To entetes.Length - 1
' send to server
OUT.WriteLine(entetes(i))
' screen echo
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
' we read the 1st web server response HTTP/1.1 100
Dim ligne As String = Nothing
' a line in the read stream
ligne = [IN].ReadLine()
While ligne <> ""
'echo
Console.Out.WriteLine(("<-- " + ligne))
' next line
ligne = [IN].ReadLine()
End While
'last line echo
Console.Out.WriteLine(("<-- " + ligne))
' send request parameters
OUT.Write(requête)
' echo
Console.Out.WriteLine(("--> " + requête))
' construction of the regular expression to find the response size XML
' in the web server response stream
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' read the second response from the web server after sending the request
' the value of the Content-Length line is stored
ligne = [IN].ReadLine()
While ligne <> ""
' screen echo
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length ?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' next line
ligne = [IN].ReadLine()
End While
' last line echo
Console.Out.WriteLine("<--")
' build the regular expression to retrieve the result
' in the web server response stream
Dim modèle As String = "<double xmlns=""st.istia.univ-angers.fr"">(.+?)</double>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' we read the rest of the web server response
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' the answer is broken down into lines of text
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' scroll through the lines of text looking for the result
Dim strRésultat As String = "?" ' function result
For i = 0 To lignes.Length - 1
' follow-up
Console.Out.WriteLine(("<-- " + lignes(i)))
' compare current line to model
MatchRésultat = ModèleRésultat.Match(lignes(i))
' have we found?
If MatchRésultat.Success Then
' we note the result
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' the result is displayed
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
أولاً، يرسل عميل HTTP-POST طلبه بتنسيق POST:
' query chain construction
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
' construction of header table HTTP to be sent
Dim entetes(5) As String
entetes(0) = "POST " + uri.AbsolutePath + "/" + fonction + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: application/x-www-form-urlencoded"
entetes(3) = "Content-Length: " & nbChars
entetes(4) = "Connection: Keep-Alive"
entetes(5) = ""
' send HTTP headers to the server
Dim i As Integer
For i = 0 To entetes.Length - 1
' send to server
OUT.WriteLine(entetes(i))
' screen echo
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
في الرأس
يجب تحديد حجم المعلمات التي سيقوم العميل بإرسالها خلف رؤوس HTTP:
للقيام بذلك، استخدم الكود التالي:
' query chain construction
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
تقوم طريقة HttpUtility.UrlEncode(string string) بتحويل أحرف معينة في السلسلة إلى %n1n2، حيث n1n2 هو رمز ASCII للحرف المحول. الأحرف المستهدفة بهذا التحويل هي جميع الأحرف التي لها معنى محدد في طلب POST (مسافة، =، &، إلخ). هنا، عادةً ما تكون طريقة HttpUtility.UrlEncode غير ضرورية لأن a و b أرقام لا تحتوي على أي من هذه الأحرف الخاصة. يتم استخدامها هنا كمثال. تتطلب مساحة اسم System.Web. بمجرد أن يرسل العميل رؤوس HTTP الخاصة به:
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
يستجيب الخادم برأس HTTP 100 Continue:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<--
يقوم الكود ببساطة بقراءة هذا الرد الأول وعرضه على الشاشة:
' we read the 1st web server response HTTP/1.1 100
Dim ligne As String = Nothing
' a line in the read stream
ligne = [IN].ReadLine()
While ligne <> ""
'echo
Console.Out.WriteLine(("<-- " + ligne))
' next line
ligne = [IN].ReadLine()
End While
'last line echo
Console.Out.WriteLine(("<-- " + ligne))
بمجرد قراءة هذا الرد الأولي، يجب على العميل إرسال معلماته:
ويتم ذلك باستخدام الكود التالي:
' envoi paramètres de la requête
OUT.Write(requête)
' echo
Console.Out.WriteLine(("--> " + requête))
ثم يرسل الخادم استجابته. تتكون هذه الاستجابة من جزأين:
- رؤوس HTTP متبوعة بسطر فارغ
- الرد بتنسيق XML
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
أولاً، يقرأ العميل رؤوس HTTP للعثور على سطر Content-Length واسترداد حجم استجابة XML (هنا، 90). يتم استرداد هذا باستخدام تعبير عادي. كان بإمكاننا القيام بذلك بطريقة مختلفة وربما أكثر كفاءة.
' construction of the regular expression to find the response size XML
' in the web server response stream
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' read the second response from the web server after sending the request
' the value of the Content-Length line is stored
ligne = [IN].ReadLine()
While ligne <> ""
' screen echo
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length ?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' next line
ligne = [IN].ReadLine()
End While
' last line echo
Console.Out.WriteLine("<--")
بمجرد حصولنا على طول N لاستجابة XML، نحتاج ببساطة إلى قراءة N حرفًا من دفق IN لاستجابة الخادم. يتم تقسيم هذه السلسلة المكونة من N حرفًا إلى أسطر نصية لأغراض مراقبة الشاشة. من بين هذه الأسطر، نبحث عن السطر الذي يحتوي على النتيجة:
مرة أخرى باستخدام تعبير عادي. بمجرد العثور على النتيجة، يتم عرضها.
فيما يلي نهاية كود العميل:
' build the regular expression to retrieve the result
' in the web server response stream
Dim modèle As String = "<double xmlns=""st.istia.univ-angers.fr"">(.+?)</double>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' we read the rest of the web server response
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' the answer is broken down into lines of text
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' scroll through the lines of text looking for the result
Dim strRésultat As String = "?" ' function result
For i = 0 To lignes.Length - 1
' follow-up
Console.Out.WriteLine(("<-- " + lignes(i)))
' compare current line to model
MatchRésultat = ModèleRésultat.Match(lignes(i))
' have we found?
If MatchRésultat.Success Then
' we note the result
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' the result is displayed
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
10.6. عميل SOAP
هنا ندرس عميلاً ثانياً سيستخدم حواراً بين العميل والخادم عبر بروتوكول SOAP (بروتوكول الوصول البسيط إلى الكائنات). ويُعرض مثال على هذا الحوار لوظيفة الإضافة:


طلب العميل هو طلب POST. لذلك سنرى بعض الآليات نفسها الموجودة في العميل السابق. والفرق الرئيسي هو أنه بينما أرسل عميل HTTP-POST المعلمتين a و b في النموذج
، فإن عميل SOAP يرسلها بتنسيق XML أكثر تعقيدًا:
POST /operations/operations.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "st.istia.univ-angers.fr/ajouter"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>double</a>
<b>double</b>
</ajouter>
</soap:Body>
</soap:Envelope>
ويستقبل استجابة XML في المقابل وهي أيضًا أكثر تعقيدًا من الاستجابات التي شوهدت سابقًا:
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouterResponse xmlns="st.istia.univ-angers.fr">
<ajouterResult>double</ajouterResult>
</ajouterResponse>
</soap:Body>
</soap:Envelope>
على الرغم من أن الطلب والاستجابة أكثر تعقيدًا، إلا أن هذه هي بالفعل نفس آلية HTTP المستخدمة في عميل HTTP-POST. وبالتالي، يمكن تصميم كود عميل SOAP على غرار كود عميل HTTP-POST. فيما يلي مثال على التنفيذ:
dos>clientsoap1 http://localhost/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 3 4
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>3</a>
<b>4</b>
</ajouter>
</soap:Body>
</soap:Envelope>
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:33 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 345
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>7</ajouterResult></ajouterResponse
></soap:Body></soap:Envelope>
[résultat=7]
تتغير طريقة executeFonction فقط. يرسل عميل SOAP رؤوس HTTP لطلبه. وهي ببساطة أكثر تعقيدًا قليلاً من تلك الخاصة بـ HTTP-POST:
ajouter 3 4
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
الرمز الذي يقوم بإنشائها:
' executeFonction
Public Sub executeFonction(ByVal [IN] As StreamReader, ByVal OUT As StreamWriter, ByVal uri As Uri, ByVal fonction As String, ByVal a As String, ByVal b As String)
' executes function(a,b) on the URI uri web service
' client-server exchanges take place via IN and OUT flows
' the result of the function is in the line
' <double xmlns="st.istia.univ-angers.fr">double</double>
' sent by the server
' construction of query string SOAP
Dim requêteSOAP As String = "<?xml version=" + """1.0"" encoding=""utf-8""?>" + ControlChars.Lf
requêteSOAP += "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" + ControlChars.Lf
requêteSOAP += "<soap:Body>" + ControlChars.Lf
requêteSOAP += "<" + fonction + " xmlns=""st.istia.univ-angers.fr"">" + ControlChars.Lf
requêteSOAP += "<a>" + a + "</a>" + ControlChars.Lf
requêteSOAP += "<b>" + b + "</b>" + ControlChars.Lf
requêteSOAP += "</" + fonction + ">" + ControlChars.Lf
requêteSOAP += "</soap:Body>" + ControlChars.Lf
requêteSOAP += "</soap:Envelope>"
Dim nbCharsSOAP As Integer = requêteSOAP.Length
' construction of header table HTTP to be sent
Dim entetes(6) As String
entetes(0) = "POST " + uri.AbsolutePath + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: text/xml; charset=utf-8"
entetes(3) = "Content-Length: " & nbCharsSOAP
entetes(4) = "Connection: Keep-Alive"
entetes(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + fonction + """"
entetes(6) = ""
' send HTTP headers to the server
Dim i As Integer
For i = 0 To entetes.Length - 1
' send to server
OUT.WriteLine(entetes(i))
' screen echo
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
عند استلام هذا الطلب، يرسل الخادم استجابته الأولى، والتي يعرضها العميل:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--
فيما يلي الكود الخاص بقراءة هذا الرد الأول:
' we read the 1st web server response HTTP/1.1 100
Dim ligne As String = Nothing
' a line in the read stream
ligne = [IN].ReadLine()
While ligne <> ""
'echo
Console.Out.WriteLine(("<-- " + ligne))
' next line
ligne = [IN].ReadLine()
End While 'while
'last line echo
Console.Out.WriteLine(("<-- " + ligne))
سيقوم العميل الآن بإرسال معلماته بتنسيق XML في ما يُسمى مغلف SOAP:
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>3</a>
<b>4</b>
</ajouter>
</soap:Body>
</soap:Envelope>
الرمز:
' envoi paramètres de la requête
OUT.Write(requêteSOAP)
' echo
Console.Out.WriteLine(("--> " + requêteSOAP))
ثم يرسل الخادم ردّه النهائي:
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:33 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 345
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>7</ajouterResult></ajouterResponse
></soap:Body></soap:Envelope>
يعرض العميل رؤوس HTTP المستلمة على الشاشة أثناء البحث عن سطر Content-Length:
' construction of the regular expression to find the response size XML
' in the web server response stream
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' read the second response from the web server after sending the request
' the value of the Content-Length line is stored
ligne = [IN].ReadLine()
While ligne <> ""
' screen echo
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length ?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' next line
ligne = [IN].ReadLine()
End While 'while
' last line echo
Console.Out.WriteLine("<--")
بمجرد معرفة حجم N لاستجابة XML، يقرأ العميل N حرفًا من دفق استجابة الخادم، ويقسم السلسلة المسترجعة إلى أسطر نصية لعرضها على الشاشة، ويبحث عن علامة XML للنتيجة: <ajouterResult>7</ajouterResult> ويعرضها:
' build the regular expression to retrieve the result
' in the web server response stream
Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' we read the rest of the web server response
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' the answer is broken down into lines of text
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' scroll through the lines of text looking for the result
Dim strRésultat As String = "?" ' function result
For i = 0 To lignes.Length - 1
' follow-up
Console.Out.WriteLine(("<-- " + lignes(i)))
' compare current line to model
MatchRésultat = ModèleRésultat.Match(lignes(i))
' have we found?
If MatchRésultat.Success Then
' we note the result
strRésultat = MatchRésultat.Groups(1).Value
End If
'next line
Next i
' the result is displayed
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
10.7. تغليف اتصال العميل بالخادم
لنتخيل أن عمليات خدمة الويب الخاصة بنا تُستخدم من قبل تطبيقات متنوعة. سيكون من المفيد تزويد هذه التطبيقات بفئة تعمل كواجهة بين تطبيق العميل وخدمة الويب، تخفي معظم الاتصالات الشبكية، وهو أمر ليس بالهين بالنسبة لمعظم المطورين. سيؤدي ذلك إلى البنية التالية:
![]() |
سيتواصل تطبيق العميل مع واجهة العميل-الخادم لتقديم طلباته إلى خدمة الويب. وستتولى الواجهة معالجة جميع الاتصالات الشبكية اللازمة مع الخادم وإرجاع النتيجة إلى تطبيق العميل. ولن يضطر تطبيق العميل بعد ذلك إلى التعامل مع الاتصالات مع الخادم، مما سيبسط عملية تطويره بشكل كبير.
10.7.1. فئة التغليف
بناءً على ما تناولناه في الأقسام السابقة، أصبح لدينا الآن فهم جيد للاتصال الشبكي بين العميل والخادم. حتى أننا استعرضنا ثلاث طرق. وقد اخترنا تغليف طريقة SOAP. وفيما يلي الفئة:
' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports System.Web
Imports Microsoft.VisualBasic
' clientSOAP of the Web operations service
Public Class clientSOAP
' instance variables
Private uri As uri = Nothing ' the URI of the web service
Private client As TcpClient = Nothing ' the client's tcp link with the server
Private [IN] As StreamReader = Nothing ' the customer's reading flow
Private OUT As StreamWriter = Nothing ' the customer's writing flow
' function dictionary
Private dicoFonctions As New Hashtable
' function list
Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' verbose
Private verbose As Boolean = False ' to true, displays client-server exchanges on screen
' manufacturer
Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)
' we note verbose
Me.verbose = verbose
' server connection
uri = New Uri(uriString)
client = New TcpClient(uri.Host, uri.Port)
' create customer input/output flows TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' create a dictionary of web service functions
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
End Sub
' close server connection
Public Sub Close()
' end of client-server link
[IN].Close()
OUT.Close()
client.Close()
End Sub
' executeFonction
Public Function executeFonction(ByVal fonction As String, ByVal a As String, ByVal b As String) As String
' executes function(a,b) on the URI uri web service
' client-server exchanges take place via IN and OUT flows
' the result of the function is in the line
' <double xmlns="st.istia.univ-angers.fr">double</double>
' sent by the server
' valid function?
fonction = fonction.Trim().ToLower()
If Not dicoFonctions.ContainsKey(fonction) Then
Return "[fonction [" + fonction + "] indisponible : (ajouter, soustraire,multiplier,diviser)]"
End If
' valid arguments a and b?
Dim doubleA As Double = 0
Try
doubleA = Double.Parse(a)
Catch
Return "[argument [" + a + "] incorrect (double)]"
End Try
Dim doubleB As Double = 0
Try
doubleB = Double.Parse(b)
Catch
Return "[argument [" + b + "] incorrect (double)]"
End Try
' division by zero?
If fonction = "diviser" And doubleB = 0 Then
Return "[division par zéro]"
End If
' construction of query string SOAP
Dim requêteSOAP As String = "<?xml version=" + """1.0"" encoding=""utf-8""?>" + ControlChars.Lf
requêteSOAP += "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" + ControlChars.Lf
requêteSOAP += "<soap:Body>" + ControlChars.Lf
requêteSOAP += "<" + fonction + " xmlns=""st.istia.univ-angers.fr"">" + ControlChars.Lf
requêteSOAP += "<a>" + a + "</a>" + ControlChars.Lf
requêteSOAP += "<b>" + b + "</b>" + ControlChars.Lf
requêteSOAP += "</" + fonction + ">" + ControlChars.Lf
requêteSOAP += "</soap:Body>" + ControlChars.Lf
requêteSOAP += "</soap:Envelope>"
Dim nbCharsSOAP As Integer = requêteSOAP.Length
' construction of header table HTTP to be sent
Dim entetes(6) As String
entetes(0) = "POST " + uri.AbsolutePath + " HTTP/1.1"
entetes(1) = "Host: " + uri.Host + ":" + uri.Port.ToString
entetes(2) = "Content-Type: text/xml; charset=utf-8"
entetes(3) = "Content-Length: " + nbCharsSOAP.ToString
entetes(4) = "Connection: Keep-Alive"
entetes(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + fonction + """"
entetes(6) = ""
' send HTTP headers to the server
Dim i As Integer
For i = 0 To entetes.Length - 1
' send to server
OUT.WriteLine(entetes(i))
' screen echo
If verbose Then
Console.Out.WriteLine(("--> " + entetes(i)))
End If
Next i
' we read the 1st web server response HTTP/1.1 100
Dim ligne As String = Nothing
' a line in the read stream
ligne = [IN].ReadLine()
While ligne <> ""
'echo
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' next line
ligne = [IN].ReadLine()
End While
'last line echo
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' send request parameters
OUT.Write(requêteSOAP)
' echo
If verbose Then
Console.Out.WriteLine(("--> " + requêteSOAP))
End If
' construction of the regular expression to find the response size XML
' in the web server response stream
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' read the second response from the web server after sending the request
' the value of the Content-Length line is stored
ligne = [IN].ReadLine()
While ligne <> ""
' screen echo
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' Content-Length ?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' next line
ligne = [IN].ReadLine()
End While
' last line echo
If verbose Then
Console.Out.WriteLine("<--")
End If
' build the regular expression to retrieve the result
' in the web server response stream
Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' we read the rest of the web server response
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' the answer is broken down into lines of text
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' scroll through the lines of text looking for the result
Dim strRésultat As String = "?" ' function result
For i = 0 To lignes.Length - 1
' follow-up
If verbose Then
Console.Out.WriteLine(("<-- " + lignes(i)))
End If ' compare current line to model
MatchRésultat = ModèleRésultat.Match(lignes(i))
' have we found?
If MatchRésultat.Success Then
' we note the result
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' return the result
Return strRésultat
End Function
End Class
لا يوجد شيء جديد هنا مقارنة بما رأيناه سابقًا. لقد أخذنا ببساطة الكود من عميل SOAP الذي درسناه وأعدنا ترتيبه قليلاً لتحويله إلى فئة. تحتوي هذه الفئة على منشئ وطريقتين:
' manufacturer
Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)
' executeFonction
Public Function executeFonction(ByVal fonction As String, ByVal a As String, ByVal b As String) As String
' close server connection
Public Sub Close()
ولها السمات التالية:
' variables d'instance
Private uri As Uri = Nothing ' l'URI du service web
Private client As TcpClient = Nothing ' la liaison tcp du client avec le serveur
Private [IN] As StreamReader = Nothing ' le flux de lecture du client
Private OUT As StreamWriter = Nothing ' le flux d'écriture du client
' dictionnaire des fonctions
Private dicoFonctions As New Hashtable
' liste des fonctions
Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' verbose
Private verbose As Boolean = False ' à vrai, affiche à l'écran les échanges client-serveur
نمرر معلمتين إلى المنشئ:
- عنوان URI لخدمة الويب التي يجب أن يتصل بها
- معلمة منطقية من نوع boolean، والتي عند تعيينها بالقيمة true، تطلب عرض التبادلات الشبكية على الشاشة؛ وإلا، فلن يتم عرضها.
أثناء الإنشاء، يتم إنشاء دفق IN لقراءة الشبكة، ودفق OUT لكتابة الشبكة، وقاموس الوظائف التي تديرها الخدمة. بمجرد إنشاء الكائن، يتم فتح اتصال العميل-الخادم ويصبح دفقا IN و OUT جاهزين للاستخدام.
تقوم طريقة Close بإغلاق الاتصال بالخادم.
طريقة ExecuteFonction هي الطريقة التي كتبناها لعميل SOAP الذي درسناه، مع بعض الاختلافات الطفيفة:
- لم يعد من الضروري تمرير المعلمات `uri` و`IN` و`OUT`، التي كانت تمرر سابقًا كمعلمات إلى الطريقة، لأنها أصبحت الآن سمات مثيل يمكن الوصول إليها من جميع طرق المثيل
- طريقة ExecuteFonction، التي كانت تُرجع سابقًا نوع void وتعرض نتيجة الدالة على الشاشة، تُرجع الآن تلك النتيجة—وبالتالي نوع string.
عادةً، يستخدم العميل فئة clientSOAP على النحو التالي:
- إنشاء كائن clientSOAP الذي سيقوم بإنشاء الاتصال بخدمة الويب
- استدعاء طريقة executeFonction بشكل متكرر
- إغلاق الاتصال بخدمة الويب باستخدام طريقة Close.
دعونا نلقي نظرة على عميل أول.
10.7.2. عميل وحدة التحكم
هنا نعيد النظر في عميل SOAP الذي درسناه عندما لم تكن فئة clientSOAP موجودة بعد، ونعيد تصميمه بحيث يستخدم هذه الفئة الآن:
' namespaces
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic
Public Module testClientSoap
' requests the URI of the operations web service
' interactively executes keyboard commands
Public Sub Main(ByVal args() As String)
' syntax
Const syntaxe As String = "pg URI [verbose]"
' number of arguments
If args.Length <> 1 And args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' verbose?
Dim verbose As Boolean = False
If args.Length = 2 Then
verbose = args(1).ToLower() = "verbose"
End If
' connect to the web service
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' connection error
erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
End Try
' user requests are typed on the keyboard
' in the form function a b - they end with the command fin
Dim commande As String = Nothing ' keyboard command
Dim champs As String() = Nothing ' command line fields
' invites the user
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b" + ControlChars.Lf)
' error management
Dim erreurCommande As Boolean
Try
' keyboard command input loop
While True
' initially no error
erreurCommande = False
' read command
commande = Console.In.ReadLine().Trim().ToLower()
' finished?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' breaking down the order into fields
champs = Regex.Split(commande, "\s+")
' three fields are required
If champs.Length <> 3 Then
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
' we note the error
erreurCommande = True
End If
' make a request to the web service
If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
' following request
End While
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
' end of client-server link
Try
client.Close()
Catch
End Try
End Sub
' error display
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Module
أصبح العميل الآن أبسط بكثير ولا يحتوي على أي اتصال شبكي. يقبل العميل معلمتين:
- عنوان URI لعمليات خدمة الويب
- الكلمة الرئيسية التفصيلية الاختيارية. في حالة وجودها، سيتم عرض التبادلات الشبكية على الشاشة.
يتم استخدام هذين المعلمتين لإنشاء كائن clientSOAP الذي سيتولى الاتصال بخدمة الويب.
' connect to the web service
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' connection error
erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
End Try
بمجرد إقامة الاتصال بخدمة الويب، يمكن للعميل إرسال طلباته. يتم كتابة هذه الطلبات على لوحة المفاتيح، ثم يتم تحليلها، ثم إرسالها إلى الخادم عن طريق استدعاء طريقة executeFonction الخاصة بكائن clientSOAP.
' on fait la demande au service web
If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
يتم ترجمة فئة clientSOAP إلى "تجميع":
dos>vbc /r:clientSOAP.dll testClientSOAP.vb
dos>dir
04/03/2004 08:46 6 913 clientSOAP.vb
04/03/2004 09:07 7 168 clientSOAP.dll
ثم يتم ترجمة تطبيق العميل testClientSoap باستخدام:
dos>vbc /r:clientSOAP.dll /r:system.dll testClientSOAP.vb
dos>dir
04/03/2004 09:08 2 711 testClientSOAP.vb
04/03/2004 09:08 4 608 testClientSOAP.exe
فيما يلي مثال على التنفيذ غير التفصيلي:
dos>testclientsoap http://localhost/st/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 1 3
résultat=4
soustraire 6 7
résultat=-1
multiplier 4 5
résultat=20
diviser 1 2
résultat=0.5
x
syntaxe : [ajouter|soustraire|multiplier|diviser] a b
x 1 2
résultat=[fonction [x] indisponible : (ajouter, soustraire,multiplier,diviser)]
ajouter a b
résultat=[argument [a] incorrect (double)]
ajouter 1 b
résultat=[argument [b] incorrect (double)]
diviser 1 0
résultat=[division par zéro]
fin
يمكنك مراقبة حركة مرور الشبكة عن طريق طلب تنفيذ "مفصل":
dos>testClientSOAP http://localhost/operations/operations.asmx verbose
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 4 8
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 08:15:25 GMT
<-- X-Powered-By: ASP.NET
<--
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>4</a>
<b>8</b>
</ajouter>
</soap:Body>
</soap:Envelope>
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 08:15:25 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 346
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>12</ajouterResult></ajouterResponse></soap:Body></soap:Envelope>
résultat=12
fin
الآن لنقم بإنشاء عميل رسومي.
10.7.3. عميل رسومي لنظام Windows
سنقوم الآن بالاستعلام عن خدمة الويب الخاصة بنا باستخدام عميل رسومي سيستخدم أيضًا فئة clientSOAP. ستكون الواجهة الرسومية كما يلي:
![]() |
العناصر التحكمية هي كما يلي:
رقم | النوع | اسم | الدور |
TextBox | txtURI | عنوان URI لعمليات خدمة الويب | |
زر | btnOpen | يفتح الاتصال بخدمة الويب | |
زر | btnClose | يغلق الاتصال بخدمة الويب | |
ComboBox | cmbFunctions | قائمة الوظائف (الجمع، الطرح، الضرب، القسمة) | |
TextBox | txtA | حجة الدالة | |
مربع نص | txtB | الحجة b للدوال | |
مربع النص | txtResult | نتيجة الدالة(a,b) | |
زر | btnCalculate | يبدأ حساب الدالة (a,b) | |
مربع النص | txtError | يعرض رسالة حول حالة الاتصال |
هناك بعض القيود التشغيلية:
- لا يكون الزر btnOpen نشطًا إلا إذا كان حقل txtURI غير فارغ ولم يكن هناك اتصال مفتوح بالفعل
- لا يكون زر btnClose نشطًا إلا عند فتح اتصال بخدمة الويب
- لا يكون زر btnCalculate نشطًا إلا عندما يكون الاتصال مفتوحًا وحقول txtA وtxtB غير فارغة
- يتم تعيين السمة ReadOnly للحقول txtResult وtxtError على القيمة true
يبدأ العميل بفتح الاتصال بخدمة الويب باستخدام الزر [Open]:

بعد ذلك، يمكن للمستخدم تحديد دالة وقيمتي a و b:





فيما يلي كود التطبيق. وقد حذفنا كود النموذج، لأنه غير ذي صلة بالموضوع هنا.
'namespaces
Imports System
Imports System.Windows.Forms
' the form class
Public Class FormClientSOAP
Inherits System.Windows.Forms.Form
' instance attributes
Dim client As clientSOAP ' client SOAP of the web operations service
#Region " Code généré par le Concepteur Windows Form "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
' other initializations
myInit()
End Sub
'The substituted method Disposes of the form to clean up the list of components.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
....
End Sub
...
Private Sub InitializeComponent()
....
End Sub
#End Region
Private Sub myInit()
' init form
cmbFonctions.SelectedIndex = 0
btnOuvrir.Enabled = False
btnFermer.Enabled = True
btnCalculer.Enabled = False
End Sub
Private Sub txtURI_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtURI.TextChanged
' the content of the input field has changed - set the state of the open button
btnOuvrir.Enabled = txtURI.Text.Trim <> ""
End Sub
Private Sub btnOuvrir_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnOuvrir.Click
' request to open a connection with the web service
Try
' creation of an object of type [clientSOAP]
client = New clientSOAP(txtURI.Text.Trim, False)
' button status
btnOuvrir.Enabled = False
btnFermer.Enabled = True
' the URI can no longer be modified
txtURI.ReadOnly = True
' customer status
txtErreur.Text = "Liaison au service web ouverte"
Catch ex As Exception
' there has been an error - it is displayed
txtErreur.Text = ex.Message
End Try
End Sub
Private Sub btnFermer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnFermer.Click
' closing the web service connection
client.Close()
' button states
btnOuvrir.Enabled = True
btnFermer.Enabled = False
' URI
txtURI.ReadOnly = False
' customer status
txtErreur.Text = "Liaison au service web fermée"
End Sub
Private Sub btnCalculer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' calculating a function f(a,b)
' delete the previous result
txtRésultat.Text = ""
Try
txtRésultat.Text = client.executeFonction(cmbFonctions.Text, txtA.Text.Trim, txtB.Text.Trim)
Catch ex As Exception
' there has been a network error
txtErreur.Text = ex.Message
' we close the link
btnFermer_Click(Nothing, Nothing)
End Try
End Sub
Private Sub txtA_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtA.TextChanged
' change in the value of A
btnCalculer.Enabled = txtA.Text.Trim <> "" And txtB.Text.Trim <> ""
End Sub
Private Sub txtB_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtB.TextChanged
' change in the value of B
txtA_TextChanged(Nothing, Nothing)
End Sub
' main method
Public Shared Sub main()
Application.Run(New FormClientSOAP)
End Sub
End Class
مرة أخرى، تخفي فئة clientSOAP جميع الجوانب المتعلقة بالشبكة في التطبيق. تم إنشاء التطبيق على النحو التالي:
- تم وضع تجميع clientSOAP.dll الذي يحتوي على فئة clientSOAP في مجلد المشروع
- تم إنشاء واجهة المستخدم الرسومية clientsoapgui.vb باستخدام VS.NET ثم تم ترجمتها في نافذة DOS:
dos>vbc /r:system.dll /r:system.windows.forms.dll /r:system.drawing.dll /r:clientSOAP.dll clientsoapgui.vb
dos>dir
04/03/2004 09:13 7 168 clientSOAP.dll
04/03/2004 16:44 9 866 clientsoapgui.vb
04/03/2004 16:44 11 264 clientsoapgui.exe
ثم تم تشغيل الواجهة الرسومية بواسطة:
10.8. عميل وكيل
دعونا نلخص ما قمنا به للتو. لقد أنشأنا فئة وسيطة تغلف التبادلات الشبكية بين عميل وخدمة ويب وفقًا للرسم التخطيطي أدناه:
![]() |
تأخذ منصة .NET هذه المنطقية خطوة إلى الأمام. بمجرد أن نعرف خدمة الويب التي نريد الوصول إليها، يمكننا إنشاء الفئة التي ستعمل كوسيط للوصول إلى وظائف خدمة الويب وإخفاء طبقة الشبكة بالكامل. تسمى هذه الفئة بالوكيل لخدمة الويب التي تم إنشاؤها من أجلها.
كيف يمكنك إنشاء فئة وكيل لخدمة الويب؟ ترافق خدمة الويب دائمًا ملف وصف بتنسيق XML. إذا كان عنوان URI لعمليات خدمة الويب لدينا هو http://localhost/operations/operations.asmx، فإن ملف الوصف الخاص بها متاح على عنوان URL http://localhost/operations/operations.asmx?wsdl، كما هو موضح في لقطة الشاشة التالية:

هذا ملف XML يصف بدقة جميع وظائف خدمة الويب، بما في ذلك نوع وعدد المعلمات لكل وظيفة، ونوع النتيجة. يُطلق على هذا الملف اسم ملف WSDL الخاص بالخدمة لأنه يستخدم لغة WSDL (لغة وصف خدمات الويب). ومن هذا الملف، يمكن إنشاء فئة وكيلة باستخدام أداة wsdl:
dos>wsdl http://localhost/operations/operations.asmx?wsdl /language=vb
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Écriture du fichier 'D:\data\devel\vbnet\poly\chap9\clientproxy\operations.vb'.
dos>dir
04/03/2004 17:17 6 663 operations.vb
تقوم أداة wsdl بإنشاء ملف مصدر VB.NET (الخيار /language=vb) يحمل اسم الفئة التي تنفذ خدمة الويب، وهي في هذه الحالة operations. دعونا نلقي نظرة على جزء من الكود الذي تم إنشاؤه:
'------------------------------------------------------------------------------
' <autogenerated>
' This code was generated by a tool.
' Runtime Version: 1.1.4322.573
'
' Changes to this file may cause incorrect behavior and will be lost if
' the code is regenerated.
' </autogenerated>
'------------------------------------------------------------------------------
Option Strict Off
Option Explicit On
Imports System
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Xml.Serialization
'
'This source code has been té automatically généré by wsdl, Version=1.1.4322.573.
'
'<remarks/>
<System.Diagnostics.DebuggerStepThroughAttribute(), _
System.ComponentModel.DesignerCategoryAttribute("code"), _
System.Web.Services.WebServiceBindingAttribute(Name:="operationsSoap", [Namespace]:="st.istia.univ-angers.fr")> _
Public Class operations
Inherits System.Web.Services.Protocols.SoapHttpClientProtocol
'<remarks/>
Public Sub New()
MyBase.New
Me.Url = "http://localhost/operations/operations.asmx"
End Sub
'<remarks/>
<System.Web.Services.Protocols.SoapDocumentMethodAttribute("st.istia.univ-angers.fr/ajouter", RequestNamespace:="st.istia.univ-angers.fr", ResponseNamespace:="st.istia.univ-angers.fr", Use:=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle:=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)> _
Public Function ajouter(ByVal a As Double, ByVal b As Double) As Double
Dim results() As Object = Me.Invoke("ajouter", New Object() {a, b})
Return CType(results(0),Double)
End Function
'<remarks/>
Public Function Beginajouter(ByVal a As Double, ByVal b As Double, ByVal callback As System.AsyncCallback, ByVal asyncState As Object) As System.IAsyncResult
Return Me.BeginInvoke("ajouter", New Object() {a, b}, callback, asyncState)
End Function
'<remarks/>
Public Function Endajouter(ByVal asyncResult As System.IAsyncResult) As Double
Dim results() As Object = Me.EndInvoke(asyncResult)
Return CType(results(0),Double)
End Function
....
قد يبدو هذا الكود معقدًا بعض الشيء للوهلة الأولى. لكننا لسنا بحاجة إلى فهم التفاصيل لاستخدامه. دعونا أولاً ندرس إعلان الفئة:
تحمل الفئة اسم عمليات خدمة الويب التي تم إنشاؤها من أجلها. وهي مشتقة من فئة SoapHttpClientProtocol:

تحتوي فئة الوكيل الخاصة بنا على منشئ واحد:
يقوم المنشئ بتعيين عنوان URL لخدمة الويب المرتبطة بالوكيل إلى السمة url. لا تحدد فئة العمليات أعلاه السمة url نفسها. فهي موروثة من الفئة التي ينحدر منها الوكيل: System.Web.Services.Protocols.SoapHttpClientProtocol. دعونا الآن ندرس ما يتعلق بالطريقة add:
Public Function ajouter(ByVal a As Double, ByVal b As Double) As Double
Dim results() As Object = Me.Invoke("ajouter", New Object() {a, b})
Return CType(results(0),Double)
End Function
يمكننا أن نرى أن لها نفس التوقيع الموجود في خدمة الويب للعمليات، حيث تم تعريفها على النحو التالي:
لا تظهر هنا الطريقة التي تتواصل بها هذه الفئة مع خدمة الويب. يتم التعامل مع هذا التواصل بالكامل بواسطة الفئة الأصلية System.Web.Services.Protocols.SoapHttpClientProtocol. يحتوي الوكيل فقط على ما يميزه عن الوكلاء الآخرين:
- عنوان URL لخدمة الويب المرتبطة
- تعريف أساليب الخدمة المرتبطة.
لاستخدام أساليب خدمة الويب الخاصة بالعمليات، يحتاج العميل فقط إلى فئة الوكيل الخاصة بالعمليات التي تم إنشاؤها سابقًا. دعونا نجمع هذه الفئة في ملف تجميع:
الآن دعونا نكتب عميلًا يعمل على واجهة الأوامر. يتم استدعاؤه بدون معلمات ويقوم بتنفيذ الطلبات التي يتم كتابتها على لوحة المفاتيح:
dos>testclientproxy
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b
ajouter 4 5
résultat=9
soustraire 9 8
résultat=1
multiplier 10 4
résultat=40
diviser 6 7
résultat=0,857142857142857
toutfaire 10 20
résultats=[30,-10,200,0,5]
diviser 5 0
résultat=+Infini
fin
رمز العميل هو كما يلي:
' namespaces
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Public Module testClientProxy
' interactively executes keyboard commands
' and sends them to the web operations service
Public Sub Main()
' there are no more arguments - the web service's URL is hard-coded in the proxy
' creation of a dictionary of web service functions
Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser", "toutfaire"}
Dim dicoFonctions As New Hashtable
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
' create a proxy operations object
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' connection error
erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
End Try
' user requests are typed on the keyboard
' in the form function a b - they end with the command fin
Dim commande As String = Nothing ' keyboard command
Dim champs As String() = Nothing ' command line fields
' invites the user
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b" + ControlChars.Lf)
' some local data
Dim erreurCommande As Boolean
Dim fonction As String
Dim a, b As Double
' keyboard command input loop
While True
' initially no error
erreurCommande = False
' read command
commande = Console.In.ReadLine().Trim().ToLower()
' finished?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' breaking down the order into fields
champs = Regex.Split(commande, "\s+")
Try
' three fields are required
If champs.Length <> 3 Then
Throw New Exception
End If
' field 0 must be a recognized function
fonction = champs(0)
If Not dicoFonctions.ContainsKey(fonction) Then
Throw New Exception
End If
' field 1 must be a valid number
a = Double.Parse(champs(1))
' field 2 must be a valid number
b = Double.Parse(champs(2))
Catch
' invalid order
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
erreurCommande = True
End Try
' make a request to the web service
If Not erreurCommande Then
Try
Dim résultat As Double
Dim résultats() As Double
If fonction = "ajouter" Then
résultat = myOperations.ajouter(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "soustraire" Then
résultat = myOperations.soustraire(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "multiplier" Then
résultat = myOperations.multiplier(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "diviser" Then
résultat = myOperations.diviser(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "toutfaire" Then
résultats = myOperations.toutfaire(a, b)
Console.Out.WriteLine(("résultats=[" + résultats(0).ToString + "," + résultats(1).ToString + "," + _
résultats(2).ToString + "," + résultats(3).ToString + "]"))
End If
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
End If
End While
End Sub
' error display
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Module
نحن ندرس فقط الكود الخاص باستخدام فئة الوكيل. أولاً، يتم إنشاء كائن عمليات الوكيل:
' create a proxy operations object
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' connection error
erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
End Try
يتم كتابة السطرين أ و ب على لوحة المفاتيح. وبناءً على هذه المعلومات، يتم استدعاء طرق البروكسي المناسبة:
' make a request to the web service
If Not erreurCommande Then
Try
Dim résultat As Double
Dim résultats() As Double
If fonction = "ajouter" Then
résultat = myOperations.ajouter(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "soustraire" Then
résultat = myOperations.soustraire(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "multiplier" Then
résultat = myOperations.multiplier(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "diviser" Then
résultat = myOperations.diviser(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "toutfaire" Then
résultats = myOperations.toutfaire(a, b)
Console.Out.WriteLine(("résultats=[" + résultats(0).ToString + "," + résultats(1).ToString + "," + _
résultats(2).ToString + "," + résultats(3).ToString + "]"))
End If
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
هنا، نتعامل لأول مرة مع العملية الشاملة التي تُنفِّذ جميع العمليات الأربع. وقد تم تجاهلها حتى الآن لأنها تُرجع مصفوفة من الأرقام مغلفة في غلاف XML، وهو أمر أكثر تعقيدًا في التعامل معه مقارنة بالاستجابات البسيطة بتنسيق XML الصادرة عن الوظائف الأخرى، والتي تُرجع نتيجة واحدة فقط. هنا، مع فئة الوكيل، نرى أن استخدام الطريقة الشاملة ليس أكثر تعقيدًا من استخدام الطرق الأخرى. تم ترجمة التطبيق في نافذة DOS على النحو التالي:
dos>vbc /r:operations.dll /r:system.dll /r:system.web.services.dll testClientProxy.vb
dos>dir
04/03/2004 17:17 6 663 operations.vb
04/03/2004 17:24 7 680 operations.dll
04/03/2004 17:41 4 099 testClientProxy.vb
04/03/2004 17:41 5 632 testClientProxy.exe
10.9. تكوين خدمة ويب
قد تتطلب خدمة الويب معلومات تكوين لبدء التشغيل بشكل صحيح. باستخدام IIS، يمكن وضع هذه المعلومات في ملف يسمى web.config موجود في نفس المجلد الذي توجد فيه خدمة الويب. لنفترض أننا نريد إنشاء خدمة ويب تتطلب معلومتين لبدء التشغيل: الاسم والعمر. يمكن وضع هاتين المعلومتين في ملف web.config بالتنسيق التالي:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
يتم وضع إعدادات التهيئة في حاوية XML:
سيتم إعلان معلمة تهيئة باسم P بقيمة V بالسطر:
<add key="P" value="V"/>
كيف تسترد خدمة الويب هذه المعلومات؟ عندما يقوم IIS بتحميل خدمة ويب، فإنه يتحقق من وجود ملف web.config في نفس المجلد. إذا كان موجودًا، فإنه يقرأه. يتم الحصول على القيمة V للمعلمة P باستخدام العبارة:
حيث ConfigurationSettings هي فئة في مساحة اسم System.Configuration.
دعونا نختبر هذه التقنية على خدمة الويب التالية:
<%@ WebService language="VB" class=personne %>
Imports System.Web.Services
imports System.Configuration
<WebService([Namespace] := "st.istia.univ-angers.fr")> _
Public Class personne
Inherits WebService
' attributes
Private nom As String
Private age As Integer
' manufacturer
Public Sub New()
' init attributes
nom = ConfigurationSettings.AppSettings("nom")
age = Integer.Parse(ConfigurationSettings.AppSettings("age"))
End Sub
<WebMethod> _
Function id() As String
Return "[" + nom + "," + age.ToString + "]"
End Function
End Class
تحتوي خدمة الويب Person على سمتين، هما name و age، يتم تهيئتهما في منشئها الخالي من المعلمات باستخدام قيم مقروءة من ملف التكوين web.config الخاص بخدمة Person. هذا الملف كما يلي:
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
تحتوي خدمة الويب أيضًا على <WebMethod> بدون معلمات تعيد ببساطة سمات الاسم والعمر. الخدمة مسجلة في ملف المصدر personne.asmx، الموجود مع ملف التكوين الخاص به في المجلد c:\inetpub\wwwroot\st\personne:
دعونا نربط مجلد IIS افتراضي /config بالمجلد الفعلي أعلاه. قم بتشغيل IIS، ثم استخدم متصفحًا لطلب عنوان URL http://localhost/config/personne.asmx لخدمة person:

اتبع الرابط الخاص بطريقة المعرف الفردي:

لا تحتوي طريقة id على معلمات. دعونا نستخدم زر Call:

لقد نجحنا في استرداد المعلومات المخزنة في ملف web.config الخاص بالخدمة.
10.10. خدمة الويب لحساب الضرائب
سنعود إلى تطبيق IMPOTS الذي أصبح مألوفًا الآن. في المرة الأخيرة التي عملنا فيه، حولناه إلى خادم بعيد يمكن الوصول إليه عبر الإنترنت. سنحوله الآن إلى خدمة ويب.
10.10.1. خدمة الويب
سنبدأ بفئة impôt التي تم إنشاؤها في الفصل الخاص بقواعد البيانات، والتي تم إنشاؤها من المعلومات الموجودة في قاعدة بيانات ODBC:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
Public Class impôt
' data required for tax calculation
' come from an external source
Private limites(), coeffR(), coeffN() As Decimal
' manufacturer
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' we check that the 3 tablaeux are the same size
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' it's good
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' builder 2
Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
' initializes the three limit arrays, coeffR, coeffN from
' the contents of the Timpots table in the ODBC DSNimpots database
' colLimites, colCoeffR, colCoeffN are the three columns of this table
' can throw an exception
Dim connectString As String = "DSN=" + DSNimpots + ";" ' base connection chain
Dim impotsConn As OdbcConnection = Nothing ' the connection
Dim sqlCommand As OdbcCommand = Nothing ' the SQL command
' the SELECT query
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' tables to retrieve data
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' attempt to access the database
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' create a command object
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' execute the query
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Using the recovered table
While myReader.Read()
' the data of the current line are put in the tables
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' freeing up resources
myReader.Close()
impotsConn.Close()
' dynamic tables are placed in static tables
Me.limites = New Decimal(tLimites.Count) {}
Me.coeffR = New Decimal(tLimites.Count) {}
Me.coeffN = New Decimal(tLimites.Count) {}
Dim i As Integer
For i = 0 To tLimites.Count - 1
limites(i) = Decimal.Parse(tLimites(i).ToString())
coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
Next i
End Sub
' tAX CALCULATION
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' calculating the number of shares
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' calculation of taxable income & family quota
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' tAX CALCULATION
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' return result
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
في خدمة الويب، لا يمكن استخدام سوى منشئ بدون معلمات. لذلك، سيصبح منشئ الفئة كما يلي:
' manufacturer
Public Sub New()
' initializes the three limit arrays, coeffR, coeffN from
' the contents of the Timpots table in the ODBC DSNimpots database
' colLimites, colCoeffR, colCoeffN are the three columns of this table
' can throw an exception
' retrieve the service configuration parameters
Dim DSNimpots As String = ConfigurationSettings.AppSettings("DSN")
Dim Timpots As String = ConfigurationSettings.AppSettings("TABLE")
Dim colLimites As String = ConfigurationSettings.AppSettings("COL_LIMITES")
Dim colCoeffR As String = ConfigurationSettings.AppSettings("COL_COEFFR")
Dim colCoeffN As String = ConfigurationSettings.AppSettings("COL_COEFFN")
' database operation
Dim connectString As String = "DSN=" + DSNimpots + ";" ' base connection chain
يتم الآن قراءة المعلمات الخمسة للمنشئ من الفئة السابقة من ملف web.config الخاص بالخدمة. فيما يلي الكود الموجود في ملف المصدر impots.asmx. وهو يتضمن معظم الكود السابق. لقد قمنا ببساطة بتغليف أجزاء الكود الخاصة بخدمة الويب:
<%@ WebService language="VB" class=impots %>
' creation of a tax web service
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
Imports System.Configuration
Imports System.Web.Services
<WebService([Namespace]:="st.istia.univ-angers.fr")> _
Public Class impôt
Inherits WebService
' data required for tax calculation
' come from an external source
Private limites(), coeffR(), coeffN() As Decimal
Private OK As Boolean = False
Private errMessage As String = ""
' manufacturer
Public Sub New()
' initializes the three limit arrays, coeffR, coeffN from
' the contents of the Timpots table in the ODBC DSNimpots database
' colLimites, colCoeffR, colCoeffN are the three columns of this table
' can throw an exception
' retrieve the service configuration parameters
Dim DSNimpots As String = ConfigurationSettings.AppSettings("DSN")
Dim Timpots As String = ConfigurationSettings.AppSettings("TABLE")
Dim colLimites As String = ConfigurationSettings.AppSettings("COL_LIMITES")
Dim colCoeffR As String = ConfigurationSettings.AppSettings("COL_COEFFR")
Dim colCoeffN As String = ConfigurationSettings.AppSettings("COL_COEFFN")
' database operation
Dim connectString As String = "DSN=" + DSNimpots + ";" ' base connection chain
Dim impotsConn As OdbcConnection = Nothing ' the connection
Dim sqlCommand As OdbcCommand = Nothing ' the SQL command
Dim myReader As OdbcDataReader ' odbc data reader
' the SELECT query
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' tables to retrieve data
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' attempt to access the database
Try
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' create a command object
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' execute the query
myReader = sqlCommand.ExecuteReader()
' Using the recovered table
While myReader.Read()
' the data of the current line are put in the tables
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' freeing up resources
myReader.Close()
impotsConn.Close()
' dynamic tables are placed in static tables
Me.limites = New Decimal(tLimites.Count) {}
Me.coeffR = New Decimal(tLimites.Count) {}
Me.coeffN = New Decimal(tLimites.Count) {}
Dim i As Integer
For i = 0 To tLimites.Count - 1
limites(i) = Decimal.Parse(tLimites(i).ToString())
coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
Next i
' it's good
OK = True
errMessage = ""
Catch ex As Exception
' error
OK = False
errMessage += "[" + ex.Message + "]"
End Try
End Sub
' tAX CALCULATION
<WebMethod()> _
Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' calculating the number of shares
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' calculation of taxable income & family quota
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' tAX CALCULATION
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' return result
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
' id
<WebMethod()> _
Function id() As String
' to see if everything is OK
Return "[" + OK + "," + errMessage + "]"
End Function
End Class
دعونا نوضح التغييرات القليلة التي أُجريت على فئة impots بخلاف تلك اللازمة لتحويلها إلى خدمة ويب:
- قد تفشل عملية قراءة قاعدة البيانات في المنشئ. لذلك أضفنا خاصيتين إلى فئتنا وطريقة:
- تكون القيمة المنطقية OK صحيحة إذا أمكن قراءة قاعدة البيانات، وخاطئة في حالة عدم تمكننا من ذلك
- تحتوي السلسلة `errMessage` على رسالة خطأ إذا تعذر قراءة قاعدة البيانات.
- تسترد الطريقة id التي لا تحتوي على معلمات قيم هاتين السمتين.
- للتعامل مع أي أخطاء محتملة في الوصول إلى قاعدة البيانات، تم وضع الجزء من كود المنشئ المتعلق بهذا الوصول داخل كتلة try-catch.
فيما يلي ملف web.config لتكوين الخدمة:
<configuration>
<appSettings>
<add key="DSN" value="mysql-impots" />
<add key="TABLE" value="timpots" />
<add key="COL_LIMITES" value="limites" />
<add key="COL_COEFFR" value="coeffr" />
<add key="COL_COEFFN" value="coeffn" />
</appSettings>
</configuration>
عند محاولة تحميل خدمة impots لأول مرة، أبلغ المُجمِّع أنه لم يتمكن من العثور على مساحة اسم Microsoft.Data.Odbc المستخدمة في التوجيه:
بعد الرجوع إلى الوثائق
- تمت إضافة توجيه تجميع إلى web.config لتحديد أنه يجب استخدام تجميع Microsoft.Data.odbc
- تم وضع نسخة من ملف microsoft.data.odbc.dll في مجلد bin الخاص بالمشروع. ويقوم مترجم خدمة الويب بالبحث في هذا المجلد بشكل منهجي عندما يبحث عن "تجميع".
تبدو هناك حلول أخرى ممكنة ولكن لم يتم استكشافها هنا. وبالتالي أصبح ملف التكوين كما يلي:
<configuration>
<appSettings>
<add key="DSN" value="mysql-impots" />
<add key="TABLE" value="timpots" />
<add key="COL_LIMITES" value="limites" />
<add key="COL_COEFFR" value="coeffr" />
<add key="COL_COEFFN" value="coeffn" />
</appSettings>
<system.web>
<compilation>
<assemblies>
<add assembly="Microsoft.Data.Odbc" />
</assemblies>
</compilation>
</system.web>
</configuration>
محتويات مجلد impots\bin:
تم وضع الخدمة وملف التكوين الخاص بها في impots:
تم تعيين المجلد الفعلي لخدمة الويب إلى المجلد الظاهري /impots في IIS. وتكون صفحة الخدمة عندئذٍ كما يلي:

إذا اتبعت رابط id:

إذا استخدمت زر الاتصال:

تعرض النتيجة السابقة قيم السمتين OK (true) و errMessage (""). في هذا المثال، تم تحميل قاعدة البيانات بنجاح. لم يكن هذا هو الحال دائمًا، ولهذا السبب أضفنا طريقة id للوصول إلى رسالة الخطأ. كان الخطأ هو أن اسم DSN لقاعدة البيانات قد تم تعريفه على أنه DSN مستخدم بينما كان يجب تعريفه على أنه DSN نظام. يتم التمييز بينهما في "مدير مصدر ODBC" 32 بت:
![]() |
لنعد إلى صفحة الخدمة:

لنضغط على رابط "حساب":

نحدد معلمات الاستدعاء وننفذ الاستدعاء:

النتيجة صحيحة.
10.10.2. إنشاء الوكيل لخدمة impots
الآن بعد أن أصبح لدينا خدمة ويب impots جاهزة للعمل، يمكننا إنشاء فئة الوكيل الخاصة بها. تذكر أن تطبيقات العميل ستستخدم هذه الفئة للوصول إلى خدمة الويب impots بشكل شفاف. أولاً، نستخدم الأداة المساعدة wsdl لإنشاء ملف المصدر الخاص بفئة الوكيل، والذي يتم بعد ذلك ترجمته إلى ملف DLL.
dos>wsdl /language=vb http://localhost/impots/impots.asmx
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Écriture du fichier 'D:\data\serge\devel\vbnet\poly\chap9\impots\impots.vb'.
D:\data\serge\devel\vbnet\poly\chap9\impots>dir
09/03/2004 10:20 <REP> bin
09/03/2004 10:58 4 651 impots.asmx
09/03/2004 11:05 3 364 impots.vb
09/03/2004 10:19 431 web.config
dos>vbc /t:library /r:system.dll /r:system.web.services.dll /r:system.xml.dll impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
pour Microsoft (R) .NET Framework version 1.1.4322.573
Copyright (C) Microsoft Corporation 1987-2002. Tous droits réservés.
dos>dir
09/03/2004 10:20 <REP> bin
09/03/2004 10:58 4 651 impots.asmx
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:05 3 364 impots.vb
09/03/2004 10:19 431 web.config
10.10.3. استخدام الوكيل مع عميل
في الفصل الخاص بقواعد البيانات، أنشأنا تطبيق وحدة تحكم لحساب الضرائب:
dos>dir
27/02/2004 16:56 5 120 impots.dll
27/02/2004 17:12 3 586 impots.vb
27/02/2004 17:08 6 144 testimpots.exe
27/02/2004 17:18 3 328 testimpots.vb
dos>testimpots
pg DSNimpots tabImpots colLimites colCoeffR colCoeffN
dos>testimpots odbc-mysql-dbimpots impots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
ثم استخدم برنامج testimpots فئة الضرائب القياسية الموجودة في ملف impots.dll. وكان كود برنامج testimpots.vb كما يلي:
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports Microsoft.VisualBasic
' test pg
Module testimpots
Sub Main(ByVal arguments() As String)
' interactive tax calculator
' the user enters three data points on the keyboard: married nbEnfants salary
' the program then displays the tax payable
Const syntaxe1 As String = "pg DSNimpots tabImpots colLimites colCoeffR colCoeffN"
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' checking program parameters
If arguments.Length <> 5 Then
' error msg
Console.Error.WriteLine(syntaxe1)
' end
Environment.Exit(1)
End If 'if
' retrieve the arguments
Dim DSNimpots As String = arguments(0)
Dim tabImpots As String = arguments(1)
Dim colLimites As String = arguments(2)
Dim colCoeffR As String = arguments(3)
Dim colCoeffN As String = arguments(4)
' tax object creation
Dim objImpôt As impôt = Nothing
Try
objImpôt = New impôt(DSNimpots, tabImpots, colLimites, colCoeffR, colCoeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' infinite loop
While True
' initially no errors
Dim erreur As Boolean = False
' tax calculation parameters are requested
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' anything to do?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' check the number of arguments in the input line
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Integer
If Not erreur Then
' checking the validity of parameters
' married
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
nbEnfants = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
erreur = True
End Try
' salary
salaire = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
erreur = True
End Try
End If
If Not erreur Then
' parameters are correct - tax is calculated
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
End If
End While
End Sub
End Module
سنستخدم البرنامج نفسه ليتمكن الآن من استخدام خدمة الويب impots من خلال فئة الوكيل impots التي تم إنشاؤها سابقًا. يتعين علينا تعديل الكود قليلاً:
- في حين أن فئة الضرائب الأصلية كانت تحتوي على منشئ بخمسة معلمات، فإن فئة الوكيل tax تحتوي على منشئ بدون معلمات. كما رأينا، يتم الآن تعيين المعلمات الخمس في ملف تكوين خدمة الويب.
- لذلك، لم تعد هناك حاجة لتمرير هذه المعلمات الخمس كمعاملات إلى برنامج الاختبار
والكود الجديد هو كما يلي:
Imports System
Imports Microsoft.VisualBasic
' test pg
Module testimpots
Public Sub Main(ByVal arguments() As String)
' interactive tax calculator
' the user enters three data points on the keyboard: married nbEnfants salary
' the program then displays the tax payable
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' tax object creation
Dim objImpôt As impôt = Nothing
Try
objImpôt = New impôt
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' infinite loop
Dim erreur As Boolean
While True
' initially no error
erreur = False
' tax calculation parameters are requested
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' anything to do?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' check the number of arguments in the input line
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
If Not erreur Then
' checking parameter validity
' married
Dim marié As String = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
Dim nbEnfants As Integer = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument nbEnfants incorrect : tapez un entier positif ou nul"))
erreur = True
End Try
' salary
Dim salaire As Integer = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument salaire incorrect : tapez un entier positif ou nul"))
erreur = True
End Try
' if the parameters are correct - the tax is calculated
If Not erreur Then Console.Out.WriteLine(("impôt=" + objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
End If
End While
End Sub
End Module
لدينا ملف الوكيل impots.dll وشفرة المصدر testimpots في نفس المجلد.
dos>dir
09/03/2004 11:28 <REP> bin
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:34 3 396 testimpots.vb
09/03/2004 10:19 431 web.config
نقوم بتجميع ملف المصدر testimpots.vb:
dos>vbc /r:impots.dll /r:microsoft.visualbasic.dll /r:system.web.services.dll /r:system.dll testimpots.vb
dos>dir
09/03/2004 11:28 <REP> bin
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:05 3 364 impots.vb
09/03/2004 11:35 5 632 testimpots.exe
09/03/2004 11:34 3 396 testimpots.vb
09/03/2004 10:19 431 web.config
ثم قم بتشغيله:
dos>testimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
نحصل على النتيجة المتوقعة.





