Skip to content

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 الصفحة التالية:

Image

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

Image

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

Image

لقد تلقى Netscape بالفعل نفس المحتوى الذي تلقّاه Internet Explorer، لكنه عرضه بشكل مختلف. من الآن فصاعدًا، سنستخدم Internet Explorer لالتقاط لقطات الشاشة.

10.3. أول خدمة ويب

سنستكشف خدمات الويب من خلال مثال بسيط للغاية متوفر في ثلاث نسخ.

10.3.1. الإصدار 1

بالنسبة لهذا الإصدار الأول، سنستخدم VS.NET، الذي يتميز بقدرته على إنشاء هيكل خدمة ويب جاهز للتشغيل على الفور. بمجرد فهمنا لهذه البنية، سنتمكن من البدء في العمل بشكل مستقل. وسيكون هذا هو محور التركيز في الإصدارات التالية.

باستخدام VS.NET، لنقم بإنشاء مشروع جديد عبر خيار [File/New/Project]:

Image

لاحظ النقاط التالية:

  • نوع المشروع هو 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:

Image

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

Image

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

Image

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

Image

هناك عدة نقاط جديرة بالملاحظة:

  • اسم الملف هو 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:

Public Class Service1
    Inherits System.Web.Services.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:

Image

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

Image

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

Image

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

Image

لنفتحه باستخدام محرر نصوص (Notepad أو غيره). نحصل على المحتوى التالي:

<%@ WebService Language="vb" Codebehind="Service1.asmx.vb" Class="demo.Bonjour" %>

يحتوي الملف على توجيه بسيط لخادم 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:

Image

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

Image

أوه... هذا XML غامض للغاية. لاحظ، مع ذلك، عنوان URL

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

Image

لقد قمنا عمدًا باقتطاع الصفحة الناتجة للحفاظ على إيجاز العرض التوضيحي. لاحظ عنوان URL مرة أخرى:

http://localhost/polyvbnet/demo/Service1.asmx?op=Bonjour

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

Image

هذا 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 (هذا الاسم مطلوب):

dos>dir bin
02/03/2004  18:12                3 072 demo2.dll

نقوم الآن بإنشاء ملف demo2.asmx. هذا هو الملف الذي سيتم استدعاؤه بواسطة عملاء الويب. ومحتوياته هي كما يلي:

<%@ WebService Language="vb" class="Bonjour2,demo2"%>

لقد سبق أن صادفنا هذا التوجيه. وهو يشير إلى أن:

  • فئة خدمة الويب تسمى Bonjour2 وتقع في تجميع demo2.dll. سيبحث IIS عن هذا التجميع في مواقع مختلفة، بما في ذلك مجلد bin الخاص بخدمة الويب. ولهذا السبب وضعنا تجميع demo2.dll هناك.

الآن يمكننا إجراء اختبارات مختلفة. نتأكد من تشغيل IIS ونطلب عنوان URL http://localhost/polyvbnet/demo2/demo2.asmx في متصفح:

Image

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

Image

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

Image

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

Image

لقد نجحنا في الحصول على نتيجة استدعاء طريقة getBonjour الخاصة بخدمة الويب. أصبحنا الآن نعرف كيفية إنشاء خدمة ويب بدون Visual Studio .NET. من الآن فصاعدًا، سنتجاهل التفاصيل الخاصة بكيفية إنشاء خدمة الويب وسنركز فقط على الملفات الأساسية.

10.3.3. الإصدار 3

استخدمت النسختان السابقتان من خدمة الويب [Hello] ملفين:

  • ملف .asmx، وهو نقطة دخول خدمة الويب
  • ملف .vb، وهو شفرة مصدر خدمة الويب

هنا، نوضح أن ملف .asmx واحد كافٍ. فيما يلي كود خدمة demo3.asmx:

<%@ WebService Language="vb" class="Bonjour3"%>

Imports System.Web.Services

<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class Bonjour3
    Inherits System.Web.Services.WebService

    <WebMethod()> Public Function getBonjour() As String
        Return "bonjour en version3 !"
    End Function
End Class

يمكننا أن نرى أن كود مصدر الخدمة موجود الآن مباشرة في ملف المصدر demo3.asmx. التوجيه

<%@ WebService Language="vb" class="Bonjour3"%>

لم تعد تشير إلى فئة في تجميع خارجي، بل إلى فئة موجودة في نفس ملف المصدر. لنضع هذا الملف في المجلد <IISroot>\polyvbnet\demo3:

Image

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

Image

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

Image

10.3.4. الإصدار 4

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

Image

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

Image

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

Image

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

Image

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

Image

10.3.5. الخلاصة

لقد أوضحنا عدة طرق لإنشاء خدمة ويب. في المستقبل، سنستخدم الطريقة الواردة في الإصدار 3 لإنشاء الخدمة والطريقة 4 لنشرها. بهذه الطريقة، لن نحتاج إلى VS.NET. ومع ذلك، تجدر الإشارة إلى فوائد استخدام VS.NET للمساعدة في تصحيح الأخطاء التي يوفرها. تتوفر أدوات مجانية لتطوير تطبيقات الويب، ولا سيما منتج WebMatrix الذي ترعاه Microsoft، والذي يمكن العثور عليه على الرابط [http://www.asp.net/webmatrix]. إنه أداة ممتازة للبدء في برمجة الويب دون أي استثمار مسبق.

10.4. خدمة ويب للعمليات

لنفترض وجود خدمة ويب تقدم خمس وظائف:

  1. add(a,b)، التي تُرجع a+b
  2. subtract(a,b)، التي تُرجع a-b
  3. multiply(a,b)، التي تُرجع a*b
  4. divide(a,b)، التي تُرجع a/b
  5. 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:

Image

نربط الدليل الظاهري لـ IIS [operations] بهذا الدليل الفعلي:

دعونا نصل إلى الخدمة باستخدام متصفح. عنوان URL المطلوب هو [http://localhost/operations/operations.asmx]:

Image

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

Image

تدعونا الصفحة التي تظهر إلى اختبار الدالة add من خلال تزويدها بالمعلمتين a وb اللتين تتطلبهما. لنتذكر تعريف الدالة *add*:

      <WebMethod>  _
      Function ajouter(a As Double, b As Double) As Double
         Return a + b
      End Function 

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

Image

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

Image

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

Image

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

Image

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

Image

في جميع الحالات، يكون رد الخادم بالصيغة التالية:

<?xml version="1.0" encoding="utf-8"?>
[réponse au format XML]
  • الرد بتنسيق XML
  • السطر 1 قياسي ويكون موجودًا دائمًا في الرد
  • الأسطر التالية تعتمد على نوع النتيجة (double، ArrayOfDouble)، وعدد النتائج، ومساحة اسم خدمة الويب (st.istia.univ-angers.fr في هذه الحالة).

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

Image

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

Image

Image

Image

تسمى هاتان الطريقتان للوصول إلى وظائف خدمة الويب، على التوالي: HTTP-POST و SOAP. سنقوم الآن بفحصهما واحدة تلو الأخرى.

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

10.5. عميل HTTP-POST

سنتبع الطريقة التي تقترحها خدمة الويب:

Image

دعونا نحلل ما هو مكتوب. أولاً، يجب على عميل الويب إرسال رؤوس HTTP التالية:

POST /operations/operations.asmx/add HTTP/1.1
يقوم عميل الويب بإرسال طلب POST إلى عنوان URL /operations/operations.asmx/add وفقًا لبروتوكول HTTP الإصدار 1.1
HOST: localhost
نحدد الجهاز المستهدف للطلب. هنا، localhost. أصبح هذا الرأس إلزامياً في الإصدار 1.1 من بروتوكول HTTP
Content-Type: application/x-www-form-urlencoded
يحدد هذا أنه بعد رؤوس HTTP، سيتم إرسال معلمات إضافية بتنسيق urlencoded. يستبدل هذا التنسيق أحرفًا معينة برموزها السداسية العشرية.
Content-Length: 7
هذا هو عدد أحرف سلسلة المعلمة التي سيتم إرسالها بعد رؤوس 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، يُخبر العميل أنه يمكنه إرسال الأحرف السبعة التي قال إنه يريد إرسالها. ما نفعله:

a=2&b=3

لاحظ أن عميل 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 لخدمة الويب إليه:

dos>httpPost2 http://localhost/operations/operations.asmx

بعد ذلك، يقرأ العميل الأوامر المكتوبة على لوحة المفاتيح ويقوم بتنفيذها. وتكون هذه الأوامر بالصيغة التالية:

fonction a b

حيث function هي وظيفة خدمة الويب التي يتم استدعاؤها (الجمع، الطرح، الضرب، القسمة) و a و b هما القيمتان اللتان ستعمل عليهما هذه الوظيفة. على سبيل المثال:

ajouter 6 7

من هناك، سيرسل العميل طلب 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() لقراءة استجابة الخادم، حيث إننا نعلم أن الاستجابة تتكون من سلسلة من الأسطر، لا ينتهي آخرها بحرف سطر جديد. بمجرد استلام استجابة الخادم بالكامل، يقوم العميل بتحليلها للعثور على نتيجة العملية المطلوبة وعرضها:

[résultat=13]

دعونا نفحص كود العميل لدينا:


' 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

في الرأس

--> Content-Length: 7

يجب تحديد حجم المعلمات التي سيقوم العميل بإرسالها خلف رؤوس HTTP:

--> a=6&b=7

للقيام بذلك، استخدم الكود التالي:


        ' 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))

بمجرد قراءة هذا الرد الأولي، يجب على العميل إرسال معلماته:

--> a=6&b=7

ويتم ذلك باستخدام الكود التالي:


        ' envoi paramètres de la requête
        OUT.Write(requête)
        ' echo
        Console.Out.WriteLine(("--> " + requête))

ثم يرسل الخادم استجابته. تتكون هذه الاستجابة من جزأين:

  1. رؤوس HTTP متبوعة بسطر فارغ
  2. الرد بتنسيق 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 حرفًا إلى أسطر نصية لأغراض مراقبة الشاشة. من بين هذه الأسطر، نبحث عن السطر الذي يحتوي على النتيجة:

<-- <double xmlns="st.istia.univ-angers.fr">13</double>

مرة أخرى باستخدام تعبير عادي. بمجرد العثور على النتيجة، يتم عرضها.

[résultat=13]

فيما يلي نهاية كود العميل:


        ' 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 (بروتوكول الوصول البسيط إلى الكائنات). ويُعرض مثال على هذا الحوار لوظيفة الإضافة:

Image

Image

طلب العميل هو طلب POST. لذلك سنرى بعض الآليات نفسها الموجودة في العميل السابق. والفرق الرئيسي هو أنه بينما أرسل عميل HTTP-POST المعلمتين a و b في النموذج

    a=A&b=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

نمرر معلمتين إلى المنشئ:

  1. عنوان URI لخدمة الويب التي يجب أن يتصل بها
  2. معلمة منطقية من نوع boolean، والتي عند تعيينها بالقيمة true، تطلب عرض التبادلات الشبكية على الشاشة؛ وإلا، فلن يتم عرضها.

أثناء الإنشاء، يتم إنشاء دفق IN لقراءة الشبكة، ودفق OUT لكتابة الشبكة، وقاموس الوظائف التي تديرها الخدمة. بمجرد إنشاء الكائن، يتم فتح اتصال العميل-الخادم ويصبح دفقا IN و OUT جاهزين للاستخدام.

تقوم طريقة Close بإغلاق الاتصال بالخادم.

طريقة ExecuteFonction هي الطريقة التي كتبناها لعميل SOAP الذي درسناه، مع بعض الاختلافات الطفيفة:

  1. لم يعد من الضروري تمرير المعلمات `uri` و`IN` و`OUT`، التي كانت تمرر سابقًا كمعلمات إلى الطريقة، لأنها أصبحت الآن سمات مثيل يمكن الوصول إليها من جميع طرق المثيل
  2. طريقة ExecuteFonction، التي كانت تُرجع سابقًا نوع void وتعرض نتيجة الدالة على الشاشة، تُرجع الآن تلك النتيجة—وبالتالي نوع string.

عادةً، يستخدم العميل فئة clientSOAP على النحو التالي:

  1. إنشاء كائن clientSOAP الذي سيقوم بإنشاء الاتصال بخدمة الويب
  2. استدعاء طريقة executeFonction بشكل متكرر
  3. إغلاق الاتصال بخدمة الويب باستخدام طريقة 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

أصبح العميل الآن أبسط بكثير ولا يحتوي على أي اتصال شبكي. يقبل العميل معلمتين:

  1. عنوان URI لعمليات خدمة الويب
  2. الكلمة الرئيسية التفصيلية الاختيارية. في حالة وجودها، سيتم عرض التبادلات الشبكية على الشاشة.

يتم استخدام هذين المعلمتين لإنشاء كائن 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. ستكون الواجهة الرسومية كما يلي:

العناصر التحكمية هي كما يلي:

رقم
النوع
اسم
الدور
1
TextBox
txtURI
عنوان URI لعمليات خدمة الويب
2
زر
btnOpen
يفتح الاتصال بخدمة الويب
3
زر
btnClose
يغلق الاتصال بخدمة الويب
4
ComboBox
cmbFunctions
قائمة الوظائف (الجمع، الطرح، الضرب، القسمة)
5
TextBox
txtA
حجة الدالة
6
مربع نص
txtB
الحجة b للدوال
7
مربع النص
txtResult
نتيجة الدالة(a,b)
8
زر
btnCalculate
يبدأ حساب الدالة (a,b)
9
مربع النص
txtError
يعرض رسالة حول حالة الاتصال

هناك بعض القيود التشغيلية:

  • لا يكون الزر btnOpen نشطًا إلا إذا كان حقل txtURI غير فارغ ولم يكن هناك اتصال مفتوح بالفعل
  • لا يكون زر btnClose نشطًا إلا عند فتح اتصال بخدمة الويب
  • لا يكون زر btnCalculate نشطًا إلا عندما يكون الاتصال مفتوحًا وحقول txtA وtxtB غير فارغة
  • يتم تعيين السمة ReadOnly للحقول txtResult وtxtError على القيمة true

يبدأ العميل بفتح الاتصال بخدمة الويب باستخدام الزر [Open]:

Image

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

Image

Image

Image

Image

Image

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


'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

ثم تم تشغيل الواجهة الرسومية بواسطة:

dos>clientsoapgui

10.8. عميل وكيل

دعونا نلخص ما قمنا به للتو. لقد أنشأنا فئة وسيطة تغلف التبادلات الشبكية بين عميل وخدمة ويب وفقًا للرسم التخطيطي أدناه:

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

كيف يمكنك إنشاء فئة وكيل لخدمة الويب؟ ترافق خدمة الويب دائمًا ملف وصف بتنسيق XML. إذا كان عنوان URI لعمليات خدمة الويب لدينا هو http://localhost/operations/operations.asmx، فإن ملف الوصف الخاص بها متاح على عنوان URL http://localhost/operations/operations.asmx?wsdl، كما هو موضح في لقطة الشاشة التالية:

Image

هذا ملف 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
....

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

Public Class operations
    Inherits System.Web.Services.Protocols.SoapHttpClientProtocol

تحمل الفئة اسم عمليات خدمة الويب التي تم إنشاؤها من أجلها. وهي مشتقة من فئة SoapHttpClientProtocol:

Image

تحتوي فئة الوكيل الخاصة بنا على منشئ واحد:

    Public Sub New()
        MyBase.New
        Me.Url = "http://localhost/operations/operations.asmx"
    End Sub

يقوم المنشئ بتعيين عنوان 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

يمكننا أن نرى أن لها نفس التوقيع الموجود في خدمة الويب للعمليات، حيث تم تعريفها على النحو التالي:

      <WebMethod>  _
      Function ajouter(a As Double, b As Double) As Double
         Return a + b
      End Function 'add

لا تظهر هنا الطريقة التي تتواصل بها هذه الفئة مع خدمة الويب. يتم التعامل مع هذا التواصل بالكامل بواسطة الفئة الأصلية System.Web.Services.Protocols.SoapHttpClientProtocol. يحتوي الوكيل فقط على ما يميزه عن الوكلاء الآخرين:

  • عنوان URL لخدمة الويب المرتبطة
  • تعريف أساليب الخدمة المرتبطة.

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

dos>vbc /t:library /r:system.web.services.dll /r:system.xml.dll /r:system.dll operations.vb
dos>dir
04/03/2004  17:17                6 663 operations.vb
04/03/2004  17:24                7 680 operations.dll

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

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:

<configuration>
    <appSettings>
...
    </appSettings>
</configuration>

سيتم إعلان معلمة تهيئة باسم P بقيمة V بالسطر:


        <add key="P" value="V"/>

كيف تسترد خدمة الويب هذه المعلومات؟ عندما يقوم IIS بتحميل خدمة ويب، فإنه يتحقق من وجود ملف web.config في نفس المجلد. إذا كان موجودًا، فإنه يقرأه. يتم الحصول على القيمة V للمعلمة P باستخدام العبارة:

        String P=ConfigurationSettings.AppSettings["V"];

حيث 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:

dos>dir
09/03/2004  08:25               632 personne.asmx
09/03/2004  08:08               186 web.config

دعونا نربط مجلد IIS افتراضي /config بالمجلد الفعلي أعلاه. قم بتشغيل IIS، ثم استخدم متصفحًا لطلب عنوان URL http://localhost/config/personne.asmx لخدمة person:

Image

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

Image

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

Image

لقد نجحنا في استرداد المعلومات المخزنة في ملف 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 المستخدمة في التوجيه:

Imports 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:

dos>dir impots\bin
30/01/2002  02:02           327 680 Microsoft.Data.Odbc.dll

تم وضع الخدمة وملف التكوين الخاص بها في impots:

dos>dir impots
09/03/2004  10:13             4 669 impots.asmx
09/03/2004  10:19               431 web.config

تم تعيين المجلد الفعلي لخدمة الويب إلى المجلد الظاهري /impots في IIS. وتكون صفحة الخدمة عندئذٍ كما يلي:

Image

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

Image

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

Image

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

لنعد إلى صفحة الخدمة:

Image

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

Image

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

Image

النتيجة صحيحة.

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

نحصل على النتيجة المتوقعة.