Skip to content

10. خدمات وب

10.1. مقدمه

در فصل قبلی، ما چندین برنامهٔ کلاینت-سرور مبتنی بر TCP/IP را بررسی کردیم. تا زمانی که کلاینت‌ها و سرور خطوط متن را مبادله می‌کنند، می‌توانند به هر زبانی نوشته شوند. کلاینت تنها باید پروتکل ارتباطی مورد انتظار سرور را بشناسد. سرویس‌های وب، برنامه‌های سرور مبتنی بر TCP/IP با ویژگی‌های زیر هستند:

  • آنها توسط وب‌سرورها میزبانی می‌شوند و بنابراین پروتکل ارتباطی کلاینت-سرور HTTP (پروتکل حمل‌ونقل HyperText) است، پروتکلی که بر روی TCP-IP ساخته شده است.
  • خدمات وب صرف‌نظر از سرویس ارائه‌شده، دارای یک پروتکل ارتباطی استاندارد هستند. یک سرویس وب خدمات مختلفی را ارائه می‌دهد: S1، S2، …، Sn. هر یک از این سرویس‌ها انتظار دریافت پارامترهایی را از سمت کلاینت دارد و یک نتیجه را به کلاینت بازمی‌گرداند. برای هر سرویس، کلاینت باید بداند:
    • نام دقیق سرویس، اگر
    • فهرست پارامترهایی که باید ارائه شوند و نوع آن‌ها
    • نوع نتیجه‌ای که توسط سرویس بازگردانده می‌شود

پس از مشخص شدن این جزئیات، دیالوگ کلاینت-سرور صرف‌نظر از اینکه کدام سرویس وب مورد پرس‌وجو قرار می‌گیرد، از همان قالب پیروی می‌کند. این امر شیوه ارتباط کلاینت‌ها را استاندارد می‌کند.

  • به دلایل امنیتی و برای محافظت در برابر حملات اینترنتی، بسیاری از سازمان‌ها شبکه‌های خصوصی دارند و تنها پورت‌های خاصی را روی سرورهای خود به اینترنت باز می‌کنند: عمدتاً پورت ۸۰ برای سرویس وب. سایر پورت‌ها مسدود هستند. در نتیجه، برنامه‌های کلاینت-سرور مانند آنهایی که در فصل قبلی توصیف شدند، در داخل شبکه خصوصی (اینترانت) ساخته می‌شوند و عموماً از خارج قابل دسترسی نیستند. میزبانی یک سرویس روی یک وب‌سرور، آن را برای کل جامعه اینترنتی قابل دسترسی می‌کند.
  • یک سرویس وب می‌تواند به صورت یک شیء راه دور مدل‌سازی شود. سرویس‌های ارائه‌شده سپس به متدهای آن شیء تبدیل می‌شوند. یک کلاینت می‌تواند به این شیء راه دور دسترسی داشته باشد، گویی که محلی است. این کار کل جنبه ارتباطات شبکه‌ای را پنهان می‌کند و اجازه می‌دهد کلاینتی ساخته شود که از این لایه مستقل باشد. اگر این لایه تغییر کند، نیازی به اصلاح کلاینت نیست. این یک مزیت بزرگ و احتمالاً اصلی‌ترین فایده سرویس‌های وب است.
  • مانند برنامه‌های کلاینت-سرور TCP/IP که در فصل قبلی ارائه شدند، کلاینت و سرور می‌توانند به هر زبانی نوشته شوند. آن‌ها خطوط متنی را مبادله می‌کنند. این خطوط از دو بخش تشکیل شده‌اند:
    • سربرگ‌های مورد نیاز پروتکل HTTP
    • بدنه پیام. برای پاسخ از سرور به کلاینت، بدنه پیام در قالب XML (زبان نشانه‌گذاری eXtensible) است. برای درخواست از کلاینت به سرور، بدنه پیام می‌تواند چندین شکل داشته باشد، از جمله XML. درخواست کلاینت XML ممکن است دارای فرمت خاصی باشد که به نام SOAP (پروتکل دسترسی ساده به شیء) شناخته می‌شود. در این صورت، پاسخ سرور نیز از فرمت SOAP پیروی می‌کند.

10.2. مرورگرها و XML

خدمات وب، XML را برای مشتریان خود ارسال می‌کنند. مرورگرها ممکن است هنگام دریافت این جریان XML واکنش متفاوتی نشان دهند. Internet Explorer دارای یک صفحهٔ سبک از پیش تعریف‌شده است که امکان نمایش آن را فراهم می‌کند. با این حال، نت‌اسکیپ کامیونیکیتور این شیوه‌نامه را ندارد و کد دریافتی XML را نمایش نمی‌دهد. برای دسترسی به XML باید کد منبع صفحه دریافتی را مشاهده کنید. در اینجا مثالی از کد XML زیر آورده شده است:

<?xml version="1.0" encoding="utf-8"?>
<string xmlns="st.istia.univ-angers.fr">bonjour de nouveau !</string>

اینترنت اکسپلورر صفحه زیر را نمایش خواهد داد:

Image

در حالی که نت‌اسکیپ ناویگیتور نمایش خواهد داد:

Image

اگر کد منبع صفحه‌ای را که توسط نت‌اسکیپ دریافت شده است مشاهده کنیم، با این کد مواجه می‌شویم:

Image

نت‌اسکیپ در واقع همان داده‌ها را مانند اینترنت اکسپلورر دریافت کرد، اما آن‌ها را به شیوه‌ای متفاوت نمایش داد. از این پس، برای اسکرین‌شات‌ها از اینترنت اکسپلورر استفاده خواهیم کرد.

10.3. یک سرویس وب اول

ما قصد داریم خدمات وب را از طریق یک مثال بسیار ساده که در سه نسخه ارائه شده است، بررسی کنیم.

10.3.1. نسخهٔ ۱

برای این نسخه اول، از VS.NET استفاده خواهیم کرد که این مزیت را دارد که می‌تواند یک اسکلت سرویس وب تولید کند که بلافاصله آماده به کار است. پس از اینکه این معماری را درک کردیم، قادر خواهیم بود به طور مستقل کار را آغاز کنیم. این موضوع محور نسخه‌های بعدی خواهد بود.

با استفاده از VS.NET، بیایید یک پروژه جدید با گزینه [Fichier/Nouveau/Projet] ایجاد کنیم:

Image

لطفاً به نکات زیر توجه کنید:

  • نوع پروژه ویژوال بیسیک است (پنل سمت چپ)
  • قالب پروژه ASP.NET Web Service (پنل سمت راست) است.
  • مکان آن انعطاف‌پذیر است. در اینجا، سرویس وب توسط یک وب سرور محلی IIS میزبانی خواهد شد. بنابراین، آدرس URL آن http://localhost/[chemin] خواهد بود، که در آن [chemin] باید تعریف شود. در اینجا، ما مسیر http://localhost/polyvbnet/demo را انتخاب می‌کنیم. سپس VS.NET یک پوشه برای این پروژه ایجاد خواهد کرد. کجا؟ سرور IIS دارای یک دایرکتوری ریشه برای ساختار درختی اسناد وبی است که ارائه می‌دهد. بیایید این ریشه را <IISroot> بنامیم. این ریشه معادل URL http://localhost است. بنابراین می‌توان نتیجه گرفت که http://localhost مربوط به URL با پوشه <IISroot>/polyvbnet/demo مرتبط خواهد بود. <IISroot> معمولاً پوشه \inetpub\wwwroot روی درایوی است که IIS روی آن نصب شده است. در مثال ما، این درایو E است. بنابراین پوشه‌ای که توسط VS.NET ایجاد می‌شود، پوشه e:\inetpub\wwwroot\polyvbnet\demo است:

Image

همانطور که همیشه، تعداد زیادی پوشه ایجاد شده است. آنها همیشه مفید نیستند. ما فقط پوشه‌هایی را توضیح می‌دهیم که در هر زمان به آنها نیاز داریم. VS.NET یک پروژه ایجاد کرده است:

Image

می‌توانیم برخی از فایل‌های موجود در پوشه فیزیکی پروژه را ببینیم. جالب‌ترین آن‌ها فایلی با پسوند .asmx است. این پسوند برای سرویس‌های وب استفاده می‌شود. یک سرویس وب توسط VS.NET به‌عنوان یک برنامهٔ ویندوزی مدیریت می‌شود، c.a.d – برنامه‌ای که دارای رابط کاربری گرافیکی و کدی برای مدیریت آن است. به همین دلیل است که ما یک پنجرهٔ طراحی داریم:

Image

یک سرویس وب معمولاً رابط کاربری گرافیکی ندارد. این سرویس نمایانگر ابجویی است که می‌توان آن را از راه دور فراخوانی کرد. این سرویس متدهایی دارد و برنامه‌ها این متدها را فراخوانی می‌کنند. بنابراین ما آن را به عنوان یک شی استاندارد در نظر می‌گیریم، با این ویژگی متمایز که می‌تواند به صورت از راه دور از طریق شبکه نمونه‌سازی (instantiated) شود. در نتیجه، ما از پنجره طراحی که توسط VS.NET نمایش داده می‌شود، استفاده نخواهیم کرد. در عوض، بیایید با استفاده از گزینه View/Code بر روی کد سرویس تمرکز کنیم:

Image

چند نکته قابل توجه وجود دارد:

  • این فایل Service1.asmx.vb نام دارد و نه Service1.asmx. کمی بعد به محتویات فایل Service1.asmx باز خواهیم گشت.
  • یک پنجره کد مشابه همان چیزی که هنگام ساخت برنامه‌های ویندوز با 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

#منطقه "کد تولید شده توسط طراح خدمات وب"

    Public Sub New()
        MyBase.New()

        'این فراخوانی توسط طراح خدمات وب مورد نیاز است.
        InitializeComponent()

        'کد инициализаسیون خود را پس از فراخوانی InitializeComponent() اضافه کنید

    End Sub

    'مورد نیاز Web Services Designer
    Private components As System.ComponentModel.IContainer

    'REMARQUE: رویه زیر توسط طراح خدمات وب مورد نیاز است
    'می‌توان آن را با استفاده از طراح خدمات وب تغییر داد.  
    'آن را با ویرایشگر کد تغییر ندهید.
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        components = New System.ComponentModel.Container()
    End Sub

    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        'CODEGEN: این رویه توسط طراح خدمات وب مورد نیاز است
        'این را با ویرایشگر کد تغییر ندهید.
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

#پایان منطقه

    ' EXEMPLE DE SERVICE WEB
    ' سرویس مثال HelloWorld() رشته «Hello World» را برمی‌گرداند.
    'برای تولید پروژه، خطوط زیر را کامنت نکنید، سپس پروژه را ذخیره و بسازید.
    ' برای آزمایش این سرویس وب، اطمینان حاصل کنید که فایل .asmx صفحهٔ شروع باشد
    'و دکمه F5 را فشار دهید.
    '
    '<WebMethod()> تابع عمومی HelloWorld() As String
    '    HelloWorld = "Hello World"
    ' پایان تابع

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. از آن برای قرار دادن سرویس وب در یک فضای نام (namespace) استفاده می‌شود. در واقع، قابل تصور است که چندین سرویس وب با نام «meteo» در سراسر جهان وجود داشته باشد. ما به روشی نیاز داریم تا آن‌ها را از یکدیگر متمایز کنیم. این امر از طریق فضای نام (namespace) انجام می‌شود. ممکن است یکی [espacenom1].meteo و دیگری [espacenom2].meteo نامیده شود. این مفهومی مشابه فضای نام کلاس‌ها است. VS.NET به طور خودکار مقداری کد تولید کرد که آن را در بخشی از سورس قرار داد:

#منطقه "کد تولید شده توسط طراح خدمات وب"

اگر به این کد نگاه کنیم، مشابه کدی است که توسط طراح هنگام ساخت برنامه‌های ویندوز تولید می‌شود. این کدی است که در صورت عدم وجود رابط کاربری گرافیکی، به راحتی قابل حذف است، که این مورد برای سرویس‌های وب صادق است.

کلاس با مثالی از شکل احتمالی یک سرویس وب به پایان می‌رسد:


#پایان منطقه

    ' EXEMPLE DE SERVICE WEB
    '  سرویس نمونه‌ای HelloWorld() رشته 'Hello World' را برمی‌گرداند. 
    'برای تولید پروژه، خطوط زیر را کامنت نکنید، سپس پروژه را ذخیره و بسازید.
    'برای آزمایش این سرویس وب، اطمینان حاصل کنید که فایل .asmx صفحهٔ شروع باشد
    'و دکمه F5 را فشار دهید.
    '
    '<WebMethod()> تابع عمومی HelloWorld() As String
    '    HelloWorld = "Hello World"
    ' پایان تابع

بر اساس آنچه گفته شد، کد را به این صورت تمیز می‌کنیم:


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 نام دارد و تنها یک متد به همین نام دارد که یک رشته را برمی‌گرداند. اکنون برای یک تست اولیه آماده‌ایم.

  • اگر سرور وب 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 مربوطه و انتخاب گزینه [Définir comme page de démarrage] انجام می‌شود.

Image

ممکن است برایتان جالب باشد بدانید فایل service1.asmx حاوی چه چیزی است. در واقع، با VS.NET، ما روی فایل service1.asmx.vb کار کردیم و نه روی فایل service1.asmx. این فایل در پوشه پروژه قرار دارد:

Image

بیایید آن را با یک ویرایشگر متن (Notepad یا مشابه) باز کنیم. محتوای زیر را مشاهده می‌کنیم:

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

این فایل حاوی یک دستور ساده برای سرور IIS است که نشان می‌دهد:

  • که این یک سرویس وب است (کلیدواژه WebService)
  • که زبان کلاس این سرویس ویژوال بیسیک است (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 بود که سرویس‌های وب را به شیوه‌ای استاندارد ارائه می‌دهد. این صفحه دو لینک در اختیار ما قرار می‌دهد. بیایید اولی را دنبال کنیم، [Description du service]:

Image

اوه... این XML نسبتاً مبهم است. با این حال، بیایید URL را یادداشت کنیم

http://localhost/polyvbnet/demo/Service1.asmx?WSDL. یک مرورگر باز کنید و این آدرس را مستقیماً در نوار آدرس وارد کنید. همان نتیجهٔ قبلی را خواهید دید. بنابراین باید در نظر داشته باشیم که URL http://serviceweb?WSDL دسترسی به توضیحات سرویس وب XML را فراهم می‌کند. بیایید به صفحه اصلی بازگردیم و روی لینک [Bonjour] کلیک کنیم. به یاد داشته باشید که «Bonjour» یک متد سرویس وب است. اگر چندین متد وجود داشت، همگی در اینجا فهرست می‌شدند. ما به صفحه جدید زیر هدایت می‌شویم:

Image

ما عمداً صفحهٔ حاصل را کوتاه کرده‌ایم تا نمایش ما بیش از حد طولانی نشود. بیایید نگاهی دیگر به URL به‌دست‌آمده بیندازیم:

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

اگر این URL را مستقیماً در یک مرورگر وارد کنیم، همان نتیجهٔ بالا را خواهیم گرفت. از ما خواسته می‌شود تا از دکمهٔ [Appeler] استفاده کنیم. بیایید این کار را انجام دهیم. یک صفحهٔ جدید دریافت می‌کنیم:

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. نسخه ۲

در مثال قبلی، 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 در حال اجرا است و سپس در یک مرورگر به آدرس 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

ما از دکمه [Appeler] بالا استفاده می‌کنیم:

Image

ما در واقع نتیجه فراخوانی متد getBonjour سرویس وب را دریافت می‌کنیم. اکنون می‌دانیم چگونه یک سرویس وب بدون vs.net بسازیم. از این پس، نحوه ساخت سرویس وب را نادیده گرفته و صرفاً بر روی فایل‌های اساسی تمرکز خواهیم کرد.

10.3.3. نسخه ۳

دو نسخه قبلی سرویس وب [Bonjour] از دو فایل استفاده می‌کردند:

  • یک فایل .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. نسخه ۴

در اینجا به پیکربندی سرور IIS می‌پردازیم. تا کنون، ما همیشه سرویس‌های وب خود را در دایرکتوری ریشه <IISroot> سرور IIS، در این مورد [e:\inetpub\wwwroot]، قرار می‌دادیم. در اینجا نشان می‌دهیم که می‌توانیم سرویس وب را در هر مکانی قرار دهیم. این کار با استفاده از دایرکتوری‌های مجازی IIS انجام می‌شود. بیایید سرویس خود را در دایرکتوری زیر قرار دهیم:

Image

پوشه [D:\data\devel\vbnet\poly\chap9\demo3] در ساختار دایرکتوری سرور IIS قرار ندارد. ما باید با ایجاد یک پوشه مجازی به نام IIS، این موضوع را به سرور اطلاع دهیم. بیایید IIS را اجرا کرده و گزینه [Avancé] را که در زیر نشان داده شده است، انتخاب کنیم:

Image

فهرستی از دایرکتوری‌های مجازی نمایش داده می‌شود. در اینجا به آن نمی‌پردازیم. با استفاده از دکمه [Ajouter] در بالا یک دایرکتوری مجازی جدید ایجاد کنید:

Image

با استفاده از دکمه [Parcourir]، پوشه فیزیکی حاوی سرویس وب را مشخص می‌کنیم، که در این مورد پوشه [D:\data\devel\vbnet\poly\chap9\demo3] است. ما به این پوشه یک نام منطقی (مجازی) می‌دهیم: [virdemo3]. این بدان معناست که اسناد موجود در پوشه فیزیکی [D:\data\devel\vbnet\poly\chap9\demo3] از طریق URL [http://<machine>/virdemo3] در شبکه قابل دسترسی خواهند بود. کادر محاوره‌ای بالا شامل تنظیمات دیگری است که ما آن‌ها را بدون تغییر باقی می‌گذاریم. تغییرات را تأیید می‌کنیم. پوشه مجازی جدید در لیست پوشه‌های مجازی برای IIS ظاهر می‌شود:

Image

اکنون، یک مرورگر وب را باز کرده و آدرس [http://localhost/virdemo3/demo3.asmx] را وارد می‌کنیم. همان نتیجه قبلی را دریافت می‌کنیم:

Image

10.3.5. نتیجه‌گیری

ما چندین روش برای ایجاد یک سرویس وب را نشان دادیم. از این پس، برای ایجاد سرویس از روش توصیف‌شده در نسخه ۳ و برای محلی‌سازی آن از روش ۴ استفاده خواهیم کرد. بنابراین به VS.NET نیازی نخواهیم داشت. با این وجود، شایان ذکر است که از مزایای استفاده از VS.NET به دلیل کمکی که در عیب‌یابی ارائه می‌دهد، غافل نشویم. ابزارهای رایگان برای توسعه برنامه‌های وب در دسترس هستند، به‌ویژه محصول WebMatrix، با حمایت مالی مایکروسافت، که می‌توان آن را در URL [http://www.asp.net/webmatrix] یافت. این یک ابزار عالی برای شروع برنامه‌نویسی وب بدون هیچ‌گونه سرمایه‌گذاری اولیه است.

10.4. یک سرویس وب برای عملیات

بیایید یک سرویس وب را در نظر بگیریم که پنج تابع ارائه می‌دهد:

  1. add(a,b)، که a+b را بازمی‌گرداند
  2. تفریق(a,b)، که a-b را برمی‌گرداند
  3. ضرب(a, b)، که a * b را برمی‌گرداند
  4. divide(a,b)، که a/b را برمی‌گرداند
  5. do-all(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 

در اینجا برخی توضیحات پیشین را تکرار می‌کنیم که ارزش تکرار یا گسترش دارند. کلاس operations شبیه کلاس VB.NET است، هرچند چند نکته قابل توجه وجود دارد:

  • متدها با یک ویژگی به نام <WebMethod()> پیش‌رویت می‌شوند که به کامپایلر می‌گوید کدام متدها باید «منتشر» (c.a.d) شده و در اختیار کلاینت قرار گیرند. روشی که این ویژگی پیش از آن قرار نگرفته باشد برای کلاینت‌های دور نامرئی خواهد بود. این می‌تواند یک روش داخلی باشد که توسط روش‌های دیگر استفاده می‌شود اما برای انتشار در نظر گرفته نشده است.
  • این کلاس از کلاس 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

ما یک سند وب دریافت می‌کنیم که شامل لینک هر یک از متدهای تعریف‌شده در سرویس وب operations است. بیایید لینک ajouter را دنبال کنیم:

Image

صفحه‌ای که ظاهر می‌شود ما را دعوت می‌کند تا روش ajouter را با ارائه دو آرگومان a و b که نیاز دارد، آزمایش کنیم. بیایید تعریف روش ajouter را به یاد بیاوریم:

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

توجه کنید که صفحه از نام‌های آرگومان‌های a و b که در تعریف متد استفاده شده‌اند، بهره برده است. ما روی دکمه Appeler کلیک می‌کنیم و پاسخ زیر را در یک پنجره مرورگر جداگانه دریافت می‌کنیم:

Image

اگر در مثال بالا از [Affichage/Source] استفاده کنیم، کد زیر را دریافت می‌کنیم:

Image

بیایید این فرآیند را برای متد [toutfaire] تکرار کنیم:

Image

صفحه زیر را دریافت می‌کنیم:

Image

بیایید از دکمه [Appeler] بالا استفاده کنیم:

Image

در همه موارد، پاسخ سرور به شکل زیر است:

<?xml version="1.0" encoding="utf-8"?>
[réponse au format XML]
  • پاسخ در قالب XML است
  • خط ۱ استاندارد است و همیشه در پاسخ وجود دارد
  • خطوط زیر به نوع نتیجه (double, ArrayOfDouble)، تعداد نتایج و فضای نام سرویس وب (در این مورد st.istia.univ-angers.fr) بستگی دارند.

چندین روش برای پرس‌وجو از یک سرویس وب و دریافت پاسخ آن وجود دارد. بیایید به URL سرویس بازگردیم:

Image

و روی لینک [ajouter] کلیک کنید. در صفحه‌ای که ظاهر می‌شود، دو روش برای فراخوانی تابع [ajouter] سرویس وب نشان داده شده است:

Image

Image

Image

این دو روش برای دسترسی به توابع یک سرویس وب به ترتیب HTTP-POST و SOAP نامیده می‌شوند. اکنون آن‌ها را یکی‌یکی بررسی خواهیم کرد.

توجه: در نسخه‌های اولیه VS.NET، یک روش سوم به نام HTTP-GET وجود داشت. در زمان نگارش (مارس ۲۰۰۴)، به نظر می‌رسد این متد دیگر در دسترس نیست. این بدان معناست که سرویس وب تولید شده توسط VS.NET درخواست‌های GET را قبول نمی‌کند. این بدان معنا نیست که نوشتن سرویس‌های وبی که درخواست‌های GET را بپذیرند، امکان‌پذیر نیست، به‌ویژه با استفاده از ابزارهای دیگر غیر از VS.NET یا صرفاً به‌صورت دستی.

10.5. یک کلاینت HTTP-POST

ما روش پیشنهادی سرویس وب را دنبال می‌کنیم:

Image

بیایید آنچه نوشته شده را بررسی کنیم. اول از همه، کلاینت وب باید سربرگ‌های زیر HTTP را ارسال کند:

POST /operations/operations.asmx/ajouter HTTP/1.1
کلاینت وب مطابق پروتکل نسخه ۱.۱ HTTP، درخواستی به نقطه پایانی POST/operations/operations.asmx/add ارسال می‌کند.
HOST: localhost
ماشین مقصد برای درخواست مشخص شده است. در این مورد، localhost. این هدر توسط نسخه ۱.۱ پروتکل 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-encoded» مقادیر پارامترهای 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 ما بیش از ۷ کاراکتر ارسال می‌کند، زیرا آن‌ها را با نشانگر پایان خط (WriteLine) ارسال می‌کند. این موضوع برای سرور هیچ مشکلی ایجاد نمی‌کند، زیرا تنها ۷ کاراکتر اول از داده‌های دریافتی را می‌گیرد و سپس اتصال بسته می‌شود (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]

بیایید کد کلاینت خود را بررسی کنیم:


' فضاهای نام
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Web

'عملیات کلاینت سرویس وب
Public Module clientPOST

    Public Sub Main(ByVal args() As String)
        ' دستور زبان
        Const syntaxe As String = "pg URI"
        Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}

        ' تعداد آرگومان‌ها
        If args.Length <> 1 Then
            erreur(syntaxe, 1)
        End If
        'توجه به URI درخواستی
        Dim URIstring As String = args(0)

        ' اتصال به سرور
        Dim uri As Uri = Nothing        ' l'URI du service web
        Dim client As TcpClient = Nothing        ' la liaison tcp du client avec le serveur
        Dim [IN] As StreamReader = Nothing        ' le flux de lecture du client
        Dim OUT As StreamWriter = Nothing        ' le flux d'écriture du client
        Try
            ' اتصال به سرور
            uri = New Uri(URIstring)
            client = New TcpClient(uri.Host, uri.Port)
            ' ایجاد جریان‌های ورودی-خروجی کلاینت TCP
            [IN] = New StreamReader(client.GetStream())
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True
        Catch ex As Exception
            ' URI نادرست یا مشکل دیگر
            erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
        End Try

        ' ایجاد یک فرهنگ لغت از توابع سرویس وب
        Dim dicoFonctions As New Hashtable
        Dim i As Integer
        For i = 0 To fonctions.Length - 1
            dicoFonctions.Add(fonctions(i), True)
        Next i

        ' درخواست‌های کاربر از طریق صفحه‌کلید وارد می‌شوند
        ' در تابع فرم a b
        ' آنها با فرمان 'end' پایان می‌یابند
        Dim commande As String = Nothing        ' commande tapée au clavier
        Dim champs As String() = Nothing        ' champs d'une ligne de commande
        Dim fonction As String = Nothing        ' nom d'une fonction du service web
        Dim a, b As String        ' les arguments des fonctions du service web

        ' از کاربر درخواست می‌کند
        Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b")

        ' پردازش خطا
        Dim erreurCommande As Boolean
        Try
            'حلقه برای پردازش دستورات واردشده از طریق صفحه‌کلید
            While True
                ' در شروع هیچ خطایی وجود ندارد
                erreurCommande = False
                ' خواندن فرمان
                commande = Console.In.ReadLine().Trim().ToLower()
                ' تمام شد؟
                If commande Is Nothing Or commande = "fin" Then
                    Exit While
                End If
                ' تفکیک دستور به فیلدها
                champs = Regex.Split(commande, "\s+")
                Try
                    'سه فیلد الزامی هستند
                    If champs.Length <> 3 Then
                        Throw New Exception
                    End If
                    'فیلد 0 باید یک تابع شناخته‌شده باشد
                    fonction = champs(0)
                    If Not dicoFonctions.ContainsKey(fonction) Then
                        Throw New Exception
                    End If
                    ' میدان 1 باید یک عدد معتبر باشد
                    a = champs(1)
                    Double.Parse(a)
                    ' میدان ۲ باید یک عدد معتبر باشد
                    b = champs(2)
                    Double.Parse(b)
                Catch
                    ' دستور نادرست
                    Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
                    erreurCommande = True
                End Try
                ' درخواست در حال ارسال به سرویس وب است
                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
        ' پایان اتصال مشتری-سرور
        Try
            [IN].Close()
            OUT.Close()
            client.Close()
        Catch
        End Try
    End Sub
...........
    ' نمایش خطاها
    Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' خطا نمایش داده شد
        System.Console.Error.WriteLine(msg)
        ' با خطا خاتمه یافت
        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)
        ' تابع(a,b) را در سرویس وب با URI URI اجرا می‌کند
        ' ارتباط کلاینت-سرور از طریق جریان‌های IN و OUT انجام می‌شود
        ' نتیجهٔ تابع در خط
        ' <double xmlns="st.istia.univ-angers.fr">double</double>
        ' ارسال‌شده توسط سرور

        'ساخت رشتهٔ پرس‌وجو
        Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
        Dim nbChars As Integer = requête.Length

        ' ساخت آرایهٔ سربرگ‌های HTTP برای ارسال
        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) = ""

        'ارسال سربرگ‌های HTTP به سرور
        Dim i As Integer
        For i = 0 To entetes.Length - 1
            'ارسال به سرور
            OUT.WriteLine(entetes(i))
            ' اکوی صفحه
            Console.Out.WriteLine(("--> " + entetes(i)))
        Next i

        ' اولین پاسخ را از سرور وب می‌خواند: HTTP/1.1 100
        Dim ligne As String = Nothing
        ' یک خط از جریان خواندن
        ligne = [IN].ReadLine()
        While ligne <> ""
            'اکو
            Console.Out.WriteLine(("<-- " + ligne))
            'خط بعدی
            ligne = [IN].ReadLine()
        End While
        'اکو خط آخر
        Console.Out.WriteLine(("<-- " + ligne))

        'ارسال پارامترهای درخواست
        OUT.Write(requête)
        ' اکو
        Console.Out.WriteLine(("--> " + requête))

        'ساخت عبارت منظم برای تعیین اندازه پاسخ XML
        'در جریان پاسخ وب‌سرور
        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

        ' خواندن پاسخ دوم از سرور وب پس از ارسال درخواست
        'ذخیره کردن مقدار خط Content-Length
        ligne = [IN].ReadLine()
        While ligne <> ""
            'اکو صفحه
            Console.Out.WriteLine(("<-- " + ligne))
            ' Content-Length؟
            MatchLength = RegexLength.Match(ligne)
            If MatchLength.Success Then
                longueur = Integer.Parse(MatchLength.Groups(1).Value)
            End If
            ' خط بعدی
            ligne = [IN].ReadLine()
        End While
        ' بازتاب خط آخر
        Console.Out.WriteLine("<--")

        ' ساخت عبارت منظم برای بازیابی نتیجه
        ' در جریان پاسخ وب‌سرور
        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

        ' بقیه پاسخ وب‌سرور را می‌خواند
        Dim chrRéponse(longueur) As Char
        [IN].Read(chrRéponse, 0, longueur)
        Dim strRéponse As String = New [String](chrRéponse)

        ' پاسخ به خطوط متن تقسیم می‌شود
        Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)

        'خطوط متنی برای یافتن نتیجه اسکن می‌شوند
        Dim strRésultat As String = "?"        ' résultat de la fonction
        For i = 0 To lignes.Length - 1
            'پیگیری
            Console.Out.WriteLine(("<-- " + lignes(i)))
            ' خط فعلی را با قالب مقایسه می‌کند
            MatchRésultat = ModèleRésultat.Match(lignes(i))
            'آیا تطابق یافت شده است؟
            If MatchRésultat.Success Then
                ' نتیجه ثبت می‌شود
                strRésultat = MatchRésultat.Groups(1).Value
            End If
        Next i
        ' نمایش نتیجه
        Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
    End Sub

ابتدا، کلاینت HTTP-POST درخواست خود را در قالب POST ارسال می‌کند:


        ' ساختن رشتهٔ پرس‌وجو
        Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
        Dim nbChars As Integer = requête.Length

        'ساخت جدول سربرگ‌های HTTP برای ارسال
        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) = ""

        'ارسال سربرگ‌ها HTTP به سرور
        Dim i As Integer
        For i = 0 To entetes.Length - 1
            'ارسال به سرور
            OUT.WriteLine(entetes(i))
            ' اکوی صفحه
            Console.Out.WriteLine(("--> " + entetes(i)))
        Next i

در هدر

--> Content-Length: 7

شما باید اندازه پارامترهایی را که توسط کلاینت پس از هدرهای HTTP ارسال می‌شوند، مشخص کنید:

--> a=6&b=7

برای این کار، از کد زیر استفاده کنید:


        ' ساختاردهی رشتهٔ پرس‌وجو
        Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
        Dim nbChars As Integer = requête.Length

روش HttpUtility.UrlEncode(string string) برخی کاراکترها را از chaîne به %n1n2 تبدیل می‌کند، جایی که n1n2 کد ASCII برای کاراکتر تبدیل‌شده است. کاراکترهای تحت تأثیر این تبدیل، تمام کاراکترهایی هستند که در یک پرس‌وجو معنای خاصی دارند (مانند فاصله، علامت =، علامت & و غیره). در اینجا، متد 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
<--

کد به سادگی این اولین پاسخ را می‌خواند و روی صفحه نمایش می‌دهد:


        ' اولین پاسخ را از سرور وب HTTP/1.1 100 می‌خواند
        Dim ligne As String = Nothing
        'یک خط از جریان ورودی
        ligne = [IN].ReadLine()
        While ligne <> ""
            'اکو
            Console.Out.WriteLine(("<-- " + ligne))
            ' خط بعدی
            ligne = [IN].ReadLine()
        End While
        'اکو آخرین خط
        Console.Out.WriteLine(("<-- " + ligne))

پس از خوانده شدن این پاسخ اولیه، کلاینت باید پارامترهای خود را ارسال کند:

--> a=6&b=7

آنها این کار را با استفاده از کد زیر انجام می‌دهند:

        'ارسال پارامترهای درخواست
        OUT.Write(requête)
        ' اکو
        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) را بازیابی کند. این با استفاده از یک عبارت منظم بازیابی می‌شود. این کار می‌توانست به روش دیگری و احتمالاً با کارایی بیشتری انجام شود.


        ' ساخت عبارت منظم برای تعیین اندازه پاسخ XML
        'در جریان پاسخ وب‌سرور
        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

        ' خواندن دومین پاسخ از سرور وب پس از ارسال درخواست
        'ذخیره کردن مقدار خط Content-Length
        ligne = [IN].ReadLine()
        While ligne <> ""
            ' اکوی صفحه
            Console.Out.WriteLine(("<-- " + ligne))
            ' Content-Length؟
            MatchLength = RegexLength.Match(ligne)
            If MatchLength.Success Then
                longueur = Integer.Parse(MatchLength.Groups(1).Value)
            End If
            ' خط بعدی
            ligne = [IN].ReadLine()
        End While
        ' بازتاب خط آخر
        Console.Out.WriteLine("<--")

هنگامی که طول N پاسخ XML را به دست آوردیم، کافی است N کاراکتر را از جریان IN در پاسخ سرور بخوانیم. این رشته N کاراکتری برای اهداف نظارت بر صفحه به خطوط متنی تقسیم می‌شود. در میان این خطوط، به دنبال خط حاوی نتیجه می‌گردیم:

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

دوباره با استفاده از یک عبارت منظم. پس از یافتن نتیجه، آن نمایش داده می‌شود.

[résultat=13]

پایان کد کلاینت به شرح زیر است:


        ' ساخت عبارت منظم برای بازیابی نتیجه
        ' در جریان پاسخ وب‌سرور
        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

        ' بقیه پاسخ وب‌سرور را می‌خواند
        Dim chrRéponse(longueur) As Char
        [IN].Read(chrRéponse, 0, longueur)
        Dim strRéponse As String = New [String](chrRéponse)

        ' پاسخ به خطوط متن تقسیم می‌شود
        Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)

        'خطوط متنی برای یافتن نتیجه اسکن می‌شوند
        Dim strRésultat As String = "?"        ' résultat de la fonction
        For i = 0 To lignes.Length - 1
            'پیگیری
            Console.Out.WriteLine(("<-- " + lignes(i)))
            ' خط فعلی را با الگو مقایسه می‌کند
            MatchRésultat = ModèleRésultat.Match(lignes(i))
            'آیا تطابق یافت شده است؟
            If MatchRésultat.Success Then
                ' نتیجه ثبت می‌شود
                strRésultat = MatchRésultat.Groups(1).Value
            End If
        Next i
        ' نمایش نتیجه
        Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
    End Sub

10.6. یک کلاینت SOAP

در اینجا ما در حال بررسی یک کلاینت دوم هستیم که از یک دیالوگ کلاینت-سرور از نوع SOAP (پروتکل دسترسی ساده به شیء) استفاده خواهد کرد. مثالی از چنین دیالوگی برای تابع ajouter نشان داده شده است:

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/QZXW2HTMLCb3BlcmF0aW9ucy.hc214ZQX
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)
        'تابع (a,b) را روی سرویس وب در URI URI اجرا می‌کند
        'ارتباط کلاینت-سرور از طریق جریان‌های IN و OUT انجام می‌شود
        ' نتیجهٔ تابع در خط
        ' <double xmlns="st.istia.univ-angers.fr">double</double>
        ' ارسال‌شده توسط سرور
        'ساخت رشتهٔ پرس‌وجو 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

        'ساخت آرایه سربرگ HTTP برای ارسال
        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) = ""

        'ارسال سربرگ‌ها HTTP به سرور
        Dim i As Integer
        For i = 0 To entetes.Length - 1
            'ارسال به سرور
            OUT.WriteLine(entetes(i))
            ' اکوی صفحه
            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
<--

کد این پاسخ اول به شرح زیر است:


        ' اولین پاسخ را از سرور وب می‌خواند: HTTP/1.1 100
        Dim ligne As String = Nothing
        'یک خط از جریان خواندن
        ligne = [IN].ReadLine()
        While ligne <> ""
            'اکو
            Console.Out.WriteLine(("<-- " + ligne))
            ' خط بعدی
            ligne = [IN].ReadLine()
        End While        'while
        'اکو آخرین خط
        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>

کد:

1
2
3
4
        'ارسال پارامترهای درخواست
        OUT.Write(requêteSOAP)
        ' اکو
        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>

کلاینت در حالی که در جستجوی خط Content-Length است، سربرگ‌های دریافتی HTTP را روی صفحه نمایش می‌دهد:


        ' ساخت عبارت منظم برای تعیین اندازه پاسخ XML
        'در جریان پاسخ وب‌سرور
        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

        'خواندن دومین پاسخ از سرور وب پس از ارسال درخواست
        'مقدار خط Content-Length ذخیره می‌شود
        ligne = [IN].ReadLine()
        While ligne <> ""
            'خروجی صفحه
            Console.Out.WriteLine(("<-- " + ligne))
            ' Content-Length؟
            MatchLength = RegexLength.Match(ligne)
            If MatchLength.Success Then
                longueur = Integer.Parse(MatchLength.Groups(1).Value)
            End If
            ' خط بعدی
            ligne = [IN].ReadLine()
        End While        'while
        ' بازتاب خط آخر
        Console.Out.WriteLine("<--")

پس از آنکه اندازه N پاسخ XML مشخص شد، کلاینت N کاراکتر را از جریان پاسخ سرور می‌خواند، رشته دریافتی را به خطوط متن تقسیم می‌کند تا آنها را روی صفحه نمایش دهد، و در نتیجه به دنبال تگ XML می‌گردد: <ajouterResult>7</ajouterResult> و آن را نمایش می‌دهد:


        ' ساخت عبارت منظم برای بازیابی نتیجه
        ' در جریان پاسخ وب‌سرور
        Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
        Dim ModèleRésultat As New Regex(modèle)
        Dim MatchRésultat As Match = Nothing

        ' بقیه پاسخ وب‌سرور را می‌خواند
        Dim chrRéponse(longueur) As Char
        [IN].Read(chrRéponse, 0, longueur)
        Dim strRéponse As String = New [String](chrRéponse)

        'پاسخ به خطوط متن تقسیم می‌شود
        Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)

        'خطوط متنی برای یافتن نتیجه اسکن می‌شوند
        Dim strRésultat As String = "?"        ' résultat de la fonction
        For i = 0 To lignes.Length - 1
            'پیگیری
            Console.Out.WriteLine(("<-- " + lignes(i)))
            ' خط فعلی را با قالب مقایسه می‌کند
            MatchRésultat = ModèleRésultat.Match(lignes(i))
            'آیا تطابق یافت شده است؟
            If MatchRésultat.Success Then
                'نتیجه ثبت می‌شود
                strRésultat = MatchRésultat.Groups(1).Value
            End If
            'خط بعدی
        Next i
        ' نمایش نتیجه
        Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
    End Sub

10.7. پوشان‌سازی مبادلات کلاینت-سرور

بیایید تصور کنیم که سرویس وب ما operations توسط برنامه‌های مختلف استفاده می‌شود. مفید خواهد بود که به این برنامه‌ها کلاسی ارائه دهیم که به عنوان رابطی بین برنامه مشتری و سرویس وب عمل کند و بیشتر ارتباطات شبکه‌ای را پنهان سازد—که برای اکثر توسعه‌دهندگان، کار ساده‌ای نیست. این امر به نمودار زیر منجر می‌شود:

برنامهٔ کلاینت برای ارسال درخواست‌های خود به سرویس وب، از رابط کلاینت-سرور فراخوانی می‌کند. این رابط تمام ارتباطات شبکه‌ای لازم با سرور را مدیریت کرده و نتیجه را به برنامهٔ کلاینت بازمی‌گرداند. بدین ترتیب، برنامهٔ کلاینت دیگر نیازی به رسیدگی به ارتباطات با سرور نخواهد داشت که این امر توسعهٔ آن را به شدت ساده می‌کند.

10.7.1. کلاس پوشاننده

با توجه به آنچه در پاراگراف‌های قبلی دیدیم، اکنون درک خوبی از ارتباطات شبکه‌ای بین کلاینت و سرور داریم. ما حتی سه متد را بررسی کرده‌ایم. ما انتخاب کرده‌ایم که متد SOAP را در بر بگیریم. کلاس به شرح زیر است:


' فضاهای نام
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports System.Web
Imports Microsoft.VisualBasic

' clientSOAP از سرویس عملیات وب
Public Class clientSOAP

    ' متغیرهای نمونه
    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
    ' فرهنگ توابع
    Private dicoFonctions As New Hashtable
    ' فهرست توابع
    Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
    ' جزئی‌نگر
    Private verbose As Boolean = False    ' à vrai, affiche à l'écran les échanges client-serveur

    ' سازنده
    Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)

        'حالت تفصیلی
        Me.verbose = verbose

        ' اتصال به سرور
        uri = New Uri(uriString)
        client = New TcpClient(uri.Host, uri.Port)

        'ایجاد جریان‌های ورودی/خروجی مشتری TCP
        [IN] = New StreamReader(client.GetStream())
        OUT = New StreamWriter(client.GetStream())
        OUT.AutoFlush = True

        ' ایجاد فرهنگ لغت تابع سرویس وب
        Dim i As Integer
        For i = 0 To fonctions.Length - 1
            dicoFonctions.Add(fonctions(i), True)
        Next i
    End Sub

    'بستن اتصال به سرور
    Public Sub Close()
        'پایان اتصال مشتری-سرور
        [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
        ' تابع(a,b) را در سرویس وب با URI URI اجرا می‌کند
        ' ارتباط کلاینت-سرور از طریق جریان‌های IN و OUT انجام می‌شود
        ' نتیجهٔ تابع در خط
        ' <double xmlns="st.istia.univ-angers.fr">double</double>
        ' ارسال‌شده توسط سرور

        'آیا این تابع معتبر است؟
        fonction = fonction.Trim().ToLower()
        If Not dicoFonctions.ContainsKey(fonction) Then
            Return "[fonction [" + fonction + "] indisponible : (ajouter, soustraire,multiplier,diviser)]"
        End If

        'آیا آرگومان‌های a و 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

        'تقسیم بر صفر؟
        If fonction = "diviser" And doubleB = 0 Then
            Return "[division par zéro]"
        End If

        'ساخت رشتهٔ پرس‌وجو 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

        'ساخت آرایهٔ سربرگ‌ها HTTP برای ارسال
        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) = ""

        'ارسال سربرگ‌ها HTTP به سرور
        Dim i As Integer
        For i = 0 To entetes.Length - 1
            'ارسال به سرور
            OUT.WriteLine(entetes(i))
            ' اکوی صفحه
            If verbose Then
                Console.Out.WriteLine(("--> " + entetes(i)))
            End If
        Next i

        'پاسخ اول را از سرور وب می‌خواند: HTTP/1.1 100
        Dim ligne As String = Nothing
        'یک خط از جریان خواندن
        ligne = [IN].ReadLine()
        While ligne <> ""
            'اکو
            If verbose Then
                Console.Out.WriteLine(("<-- " + ligne))
            End If
            ' خط بعدی
            ligne = [IN].ReadLine()
        End While
        'اکو آخرین خط
        If verbose Then
            Console.Out.WriteLine(("<-- " + ligne))
        End If
        'ارسال پارامترهای درخواست
        OUT.Write(requêteSOAP)
        ' اکو
        If verbose Then
            Console.Out.WriteLine(("--> " + requêteSOAP))
        End If

        ' ساخت عبارت منظم برای تعیین اندازه پاسخ XML
        'در جریان پاسخ وب‌سرور
        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

        ' خواندن پاسخ دوم از سرور وب پس از ارسال درخواست
        'ذخیره کردن مقدار خط Content-Length
        ligne = [IN].ReadLine()
        While ligne <> ""
            ' اکوی صفحه
            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
            ' خط بعدی
            ligne = [IN].ReadLine()
        End While

        ' بازتاب خط آخر
        If verbose Then
            Console.Out.WriteLine("<--")
        End If

        ' ساخت عبارت منظم برای بازیابی نتیجه
        ' در جریان پاسخ وب‌سرور
        Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
        Dim ModèleRésultat As New Regex(modèle)
        Dim MatchRésultat As Match = Nothing

        ' بقیه پاسخ وب‌سرور را بخوانید
        Dim chrRéponse(longueur) As Char
        [IN].Read(chrRéponse, 0, longueur)
        Dim strRéponse As String = New [String](chrRéponse)

        ' پاسخ به خطوط متنی تقسیم می‌شود
        Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)

        'خطوط متنی برای یافتن نتیجه اسکن می‌شوند
        Dim strRésultat As String = "?"        ' résultat de la fonction
        For i = 0 To lignes.Length - 1
            ' و سپس
            If verbose Then
                Console.Out.WriteLine(("<-- " + lignes(i)))
            End If            ' comparaison ligne courante au modèle
            MatchRésultat = ModèleRésultat.Match(lignes(i))
            'آیا یافت شد؟
            If MatchRésultat.Success Then
                ' نتیجه ثبت می‌شود
                strRésultat = MatchRésultat.Groups(1).Value
            End If
        Next i

        'نتیجه بازگردانده می‌شود
        Return strRésultat
    End Function
End Class

در اینجا در مقایسه با آنچه قبلاً دیده‌ایم، چیز جدیدی وجود ندارد. ما به سادگی کد کلاینت SOAP را که مطالعه کردیم، برداشته‌ایم و آن را کمی سازماندهی مجدد کرده‌ایم تا به یک کلاس تبدیل شود. این کلاس یک سازنده و دو متد دارد:


    ' سازنده
    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


    'بستن اتصال به سرور
    Public Sub Close()

و دارای ویژگی‌های زیر است:


     ' متغیرهای نمونه
    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
     ' فرهنگ توابع
    Private dicoFonctions As New Hashtable
     ' فهرست توابع
    Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
     ' جزئیات
    Private verbose As Boolean = False    ' à vrai, affiche à l'écran les échanges client-serveur

دو پارامتر به سازنده ارسال می‌شوند:

  1. URI سرویس وبی که باید به آن متصل شود
  2. یک مقدار بولی verbose که در واقع مشخص می‌کند مبادلات شبکه باید روی صفحه نمایش داده شوند؛ در غیر این صورت، نمایش داده نخواهند شد.

در حین ساخت، جریان خواندن شبکه 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 هنوز وجود نداشت، و اکنون آن را بازسازی می‌کنیم تا از این کلاس استفاده کند:


' فضاهای نام
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic

Public Module testClientSoap

    ' درخواست URI را از سرویس وب عملیات می‌کند
    ' دستورهای واردشده از طریق صفحه‌کلید را به‌صورت تعاملی اجرا می‌کند
    Public Sub Main(ByVal args() As String)
        ' دستور زبان
        Const syntaxe As String = "pg URI [verbose]"

        ' تعداد آرگومان‌ها
        If args.Length <> 1 And args.Length <> 2 Then
            erreur(syntaxe, 1)
        End If
        ' جزئیات؟
        Dim verbose As Boolean = False
        If args.Length = 2 Then
            verbose = args(1).ToLower() = "verbose"
        End If
        ' به سرویس وب متصل می‌شود 
        Dim client As clientSOAP = Nothing
        Try
            client = New clientSOAP(args(0), verbose)
        Catch ex As Exception
            'خطای اتصال
            erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
        End Try

        'درخواست‌های کاربر وارد می‌شوند
        'در تابع فرم a b – آن‌ها با دستور end پایان می‌یابند
        Dim commande As String = Nothing        ' commande tapée au clavier
        Dim champs As String() = Nothing        ' champs d'une ligne de commande

        ' از کاربر درخواست می‌کند
        Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b" + ControlChars.Lf)

        ' مدیریت خطا
        Dim erreurCommande As Boolean
        Try
            'حلقه برای پردازش دستورات واردشده از طریق صفحه‌کلید
            While True
                ' در ابتدا هیچ خطایی وجود ندارد
                erreurCommande = False
                ' خواندن فرمان
                commande = Console.In.ReadLine().Trim().ToLower()
                ' تمام شد؟
                If commande Is Nothing Or commande = "fin" Then
                    Exit While
                End If
                ' تفکیک دستور به فیلدها
                champs = Regex.Split(commande, "\s+")
                ' سه فیلد الزامی هستند
                If champs.Length <> 3 Then
                    Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
                    ' خطا ثبت شد
                    erreurCommande = True
                End If
                ' درخواست را به سرویس وب ارسال کنید
                If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
                ' درخواست بعدی
            End While
        Catch e As Exception
            Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
        End Try
        'پایان اتصال کلاینت-سرور
        Try
            client.Close()
        Catch
        End Try
    End Sub

    ' نمایش خطاها
    Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        'خطا نمایش داده شد
        System.Console.Error.WriteLine(msg)
        ' پایان با خطا
        Environment.Exit(exitCode)
    End Sub
End Module

کلاینت اکنون به‌طور قابل‌توجهی ساده‌تر شده و هیچ ارتباط شبکه‌ای ندارد. کلاینت دو پارامتر را می‌پذیرد:

  1. URI سرویس وب operations
  2. کلمه کلیدی اختیاری verbose. در صورت وجود، ترافیک شبکه روی صفحه نمایش داده خواهد شد.

این دو پارامتر برای ساخت یک شی clientSOAP استفاده می‌شوند که ارتباط با سرویس وب را مدیریت خواهد کرد.


         ' اتصال به سرویس وب 
        Dim client As clientSOAP = Nothing
        Try
            client = New clientSOAP(args(0), verbose)
        Catch ex As Exception
             ' خطای اتصال
            erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
        End Try

پس از برقراری ارتباط با سرویس وب، کلاینت می‌تواند درخواست‌های خود را ارسال کند. این درخواست‌ها وارد شده، تحلیل می‌شوند و سپس با فراخوانی متد executeFonction از شیء clientSOAP به سرور ارسال می‌شوند.


                 ' ارسال یک درخواست به سرویس وب
                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

می‌توانید با درخواست اجرای «verbose» ترافیک شبکه را نظارت کنید:

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. یک کلاینت گرافیکی ویندوز

اکنون با استفاده از یک کلاینت گرافیکی، سرویس وب خود را استعلام خواهیم کرد که از کلاس clientSOAP نیز بهره می‌برد. رابط کاربری گرافیکی به شرح زیر خواهد بود:

کنترل‌ها به شرح زیر هستند:

خیر.
نوع
نام
نقش
1
TextBox
txtURI
URI از بخش عملیات وب
2
دکمه
btnOuvrir
اتصال به سرویس وب را باز می‌کند
3
دکمه
btnFermer
ارتباط با سرویس وب را قطع می‌کند
4
ComboBox
cmbFonctions
فهرست توابع (جمع، تفریق، ضرب، تقسیم)
5
TextBox
txtA
آرگومان‌های توابع
6
TextBox
txtB
آرگومان b توابع
7
TextBox
txtRésultat
نتیجه تابع (a, b)
8
دکمه
btnCalculer
شروع محاسبه تابع (a,b)
9
TextBox
txtErreur
پیام وضعیت اتصال را نمایش می‌دهد

چند محدودیت عملیاتی وجود دارد:

  • دکمه btnOuvrir تنها زمانی فعال است که فیلد txtURI خالی نباشد و اتصال از قبل باز نشده باشد
  • دکمه btnFermer تنها زمانی فعال است که ارتباط با سرویس وب برقرار شده باشد
  • دکمه btnCalculer تنها زمانی فعال است که یک اتصال برقرار باشد و فیلدهای txtA و txtB خالی نباشند
  • میدان‌های txtRésultat و txtErreur دارای ویژگی ReadOnly با مقدار true هستند

کلاینت با استفاده از دکمه [Ouvrir] اتصال خود را با سرویس وب برقرار می‌کند:

Image

سپس کاربر می‌تواند یک تابع و مقادیری برای a و b انتخاب کند:

Image

Image

Image

Image

Image

کد برنامه در ادامه آمده است. ما کد فرم را حذف کرده‌ایم، زیرا در اینجا مرتبط نیست.


'فضاهای نام
Imports System
Imports System.Windows.Forms

' کلاس فرم
Public Class FormClientSOAP
    Inherits System.Windows.Forms.Form

    ' ویژگی‌های نمونه
    Dim client As clientSOAP    ' client SOAP du service web operations

#منطقه "کد تولید شده توسط طراح فرم‌های ویندوز"

    Public Sub New()
        MyBase.New()
        'این فراخوانی توسط طراح فرم‌های ویندوز مورد نیاز است.
        InitializeComponent()
        ' سایر инициализацияها
        myInit()
    End Sub

    'متد Dispose بازنوشته‌شده برای فرم، فهرست کامپوننت‌ها را پاک می‌کند.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
....
    End Sub

...

    Private Sub InitializeComponent()
....
    End Sub

#پایان منطقه


    Private Sub myInit()
        ' ابتکاری فرم
        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
        ' محتوای فیلد ورودی تغییر کرده است – وضعیت دکمه «Open» را تنظیم کنید
        btnOuvrir.Enabled = txtURI.Text.Trim <> ""
    End Sub

    Private Sub btnOuvrir_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnOuvrir.Click
        ' درخواست برای برقراری ارتباط با سرویس وب
        Try
            'ایجاد یک شیء از نوع [clientSOAP]
            client = New clientSOAP(txtURI.Text.Trim, False)
            ' حالت‌های دکمه
            btnOuvrir.Enabled = False
            btnFermer.Enabled = True
            'URI دیگر قابل تغییر نیست
            txtURI.ReadOnly = True
            'وضعیت مشتری
            txtErreur.Text = "Liaison au service web ouverte"
        Catch ex As Exception
            ' یک خطا رخ داده است – این نمایش داده می‌شود
            txtErreur.Text = ex.Message
        End Try
    End Sub

    Private Sub btnFermer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnFermer.Click
        ' بستن اتصال به سرویس وب
        client.Close()
        ' وضعیت دکمه‌ها
        btnOuvrir.Enabled = True
        btnFermer.Enabled = False
        'URI
        txtURI.ReadOnly = False
        'وضعیت مشتری
        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
        'محاسبه تابع f(a,b)
        'پاک کردن نتیجه قبلی
        txtRésultat.Text = ""
        Try
            txtRésultat.Text = client.executeFonction(cmbFonctions.Text, txtA.Text.Trim, txtB.Text.Trim)
        Catch ex As Exception
            ' یک خطای شبکه رخ داده است
            txtErreur.Text = ex.Message
            ' بستن اتصال
            btnFermer_Click(Nothing, Nothing)
        End Try
    End Sub

    Private Sub txtA_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtA.TextChanged
        ' مقدار 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
        ' تغییر در مقدار B
        txtA_TextChanged(Nothing, Nothing)
    End Sub

    ' متد اصلی
    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 این منطق را یک قدم فراتر می‌برد. به محض اینکه سرویس وب مورد دسترسی مشخص شود، می‌توانیم به طور خودکار کلاسی را تولید کنیم که به عنوان واسطه برای دسترسی به توابع سرویس وب عمل کرده و کل لایه شبکه را پنهان می‌کند. به این کلاس، پروکسی (proxy) برای سرویس وبی که برای آن تولید شده است، گفته می‌شود.

چگونه یک کلاس پروکسی برای یک سرویس وب ایجاد می‌کنید؟ یک سرویس وب همیشه با یک فایل توصیفی با فرمت XML ارائه می‌شود. اگر URI برای سرویس وب «عملیات» ما http://localhost/operations/operations.asmx باشد، فایل توصیفی آن در http://localhost/operations/operations.asmx?wsdl موجود است، همانطور که در اسکرین‌شات زیر نشان داده شده است:

Image

این یک فایل XML است که تمام توابع سرویس وب را به تفصیل توصیف می‌کند و برای هر یک نوع و تعداد پارامترها و همچنین نوع نتیجه را مشخص می‌نماید. به این فایل، فایل WSDL سرویس گفته می‌شود زیرا از زبان WSDL (زبان توصیف سرویس‌های وب) استفاده می‌کند. از روی این فایل، می‌توان با استفاده از ابزار wsdl یک کلاس پروکسی تولید کرد:

dos>wsdl http://localhost/operations/QZXW2HTMLCb3BlcmF0aW9ucy.hc214ZQX?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>
'      این کد توسط یک ابزار تولید شده است.
'      نسخهٔ زمان اجرا: 1.1.4322.573
'
'      تغییرات در این فایل ممکن است باعث رفتار نادرست شود و در صورت 
'      کد مجدداً تولید شود.
' </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

'
'این کد منبع به طور خودکار توسط wsdl تولید شده است، نسخه=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

این کلاس به نام operations نام‌گذاری شده است، به خاطر سرویس وبی که برای آن ایجاد شده است. این کلاس از کلاس SoapHttpClientProtocol ارث می‌برد:

Image

کلاس پروکسی ما یک سازنده دارد:

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

سازنده، مقدار URL را از سرویس وب مرتبط با پروکسی به ویژگی url اختصاص می‌دهد. کلاس operations که در بالا ذکر شد، خود ویژگی url را تعریف نمی‌کند. این از کلاسی که پروکسی از آن مشتق شده است به ارث رسیده است: System.Web.Services.Protocols.SoapHttpClientProtocol. اکنون بیایید آنچه به متد ajouter مربوط می‌شود را بررسی کنیم:

    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

می‌توانیم ببینیم که این همان امضا را در سرویس وب operations دارد، که به صورت زیر تعریف شده بود:

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

نحوه تعامل این کلاس با سرویس وب در اینجا نشان داده نشده است. این تعامل به‌طور کامل توسط کلاس والد System.Web.Services.Protocols.SoapHttpClientProtocol مدیریت می‌شود. پروکسی تنها شامل مواردی است که آن را از سایر پروکسی‌ها متمایز می‌کند:

  • URL برای سرویس وب مربوطه
  • تعریف متدهای سرویس مرتبط.

برای استفاده از متدهای سرویس وب operations، یک کلاینت تنها به کلاس پروکسی operations که قبلاً تولید شده است نیاز دارد. بیایید این کلاس را در یک فایل assembly کامپایل کنیم:

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

کد مشتری به شرح زیر است:


' فضاهای نام
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic

Public Module testClientProxy

    ' دستورهای واردشده از طریق صفحه‌کلید را به‌صورت تعاملی اجرا می‌کند
    ' و آنها را به سرویس وب عملیات ارسال می‌کند
    Public Sub Main()
        ' هیچ آرگومان دیگری وجود ندارد – URL سرویس وب به‌صورت سخت‌کد در پروکسی درج شده است

        ' ایجاد یک فرهنگ لغت از توابع سرویس وب
        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

        'یک شیء پروکسی عملیات ایجاد می‌شود 
        Dim myOperations As operations = Nothing
        Try
            myOperations = New operations
        Catch ex As Exception
            ' خطای اتصال
            erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
        End Try

        ' درخواست‌های کاربر از طریق صفحه‌کلید وارد می‌شوند
        ' در تابع فرم a b – آن‌ها با دستور end پایان می‌یابند
        Dim commande As String = Nothing        ' commande tapée au clavier
        Dim champs As String() = Nothing        ' champs d'une ligne de commande

        ' از کاربر درخواست می‌کند
        Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b" + ControlChars.Lf)

        ' برخی داده‌های محلی
        Dim erreurCommande As Boolean
        Dim fonction As String
        Dim a, b As Double
        'حلقه برای پردازش دستورات واردشده از طریق صفحه‌کلید
        While True
            ' در ابتدا هیچ خطایی وجود ندارد
            erreurCommande = False
            ' دستور خوانده شد
            commande = Console.In.ReadLine().Trim().ToLower()
            ' تمام شد؟
            If commande Is Nothing Or commande = "fin" Then
                Exit While
            End If
            ' تجزیه دستور به فیلدها
            champs = Regex.Split(commande, "\s+")
            Try
                'سه فیلد الزامی هستند
                If champs.Length <> 3 Then
                    Throw New Exception
                End If
                ' میدان 0 باید یک تابع معتبر باشد
                fonction = champs(0)
                If Not dicoFonctions.ContainsKey(fonction) Then
                    Throw New Exception
                End If
                ' میدان 1 باید یک عدد معتبر باشد
                a = Double.Parse(champs(1))
                ' میدان ۲ باید یک عدد معتبر باشد
                b = Double.Parse(champs(2))
            Catch
                ' دستور نادرست
                Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
                erreurCommande = True
            End Try
            ' درخواست در حال ارسال به سرویس وب است
            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

    ' نمایش خطاها
    Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' خطا نمایش داده شد
        System.Console.Error.WriteLine(msg)
        ' با خطا خاتمه یافت
        Environment.Exit(exitCode)
    End Sub
End Module

ما فقط کد مربوط به استفاده از کلاس پروکسی را بررسی می‌کنیم. ابتدا یک شیء پروکسی operations ایجاد می‌شود:


        'ایجاد یک شی عملیات پروکسی 
        Dim myOperations As operations = Nothing
        Try
            myOperations = New operations
        Catch ex As Exception
            ' خطای اتصال
            erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
        End Try

خطوط a و b تایپ می‌شوند. بر اساس این اطلاعات، متدهای مناسب پروکسی فراخوانی می‌شوند:


            ' درخواست به سرویس وب ارسال شد
            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

در اینجا، برای اولین بار، عملیات toutfaire را پردازش می‌کنیم که چهار عمل اصلی را انجام می‌دهد. این مورد تا به حال نادیده گرفته شده بود، زیرا یک آرایه از اعداد را در قالبی به نام XML ارسال می‌کند که رسیدگی به آن پیچیده‌تر از پاسخ‌های ساده XML از توابع دیگر است که تنها یک نتیجه واحد را بازمی‌گردانند. می‌توانیم ببینیم که در اینجا، با استفاده از کلاس پروکسی، استفاده از متد toutfaire پیچیده‌تر از استفاده از متدهای دیگر نیست. برنامه در یک پنجره 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

   ' ویژگی‌ها
   Private nom As String
   Private age As Integer

   ' سازنده
   Public Sub New()
      ' ابتدایی‌سازی ویژگی‌ها
      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 

سرویس وب personne دارای دو ویژگی، nom و age، است که در سازنده بدون پارامتر آن با مقادیری که از فایل پیکربندی web.config سرویس QZXW2 خوانده شده‌اند، مقداردهی اولیه می‌شوند.HTMLP002664ZQX. این فایل به شرح زیر است:


<configuration>
    <appSettings>
        <add key="nom" value="tintin"/>
        <add key="age" value="27"/>
    </appSettings>
</configuration>

این سرویس وب همچنین یک شناسه <WebMethod> بدون پارامتر دارد که به سادگی ویژگی‌های nom و age را بازمی‌گرداند. این سرویس در فایل منبعی به نام 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 را برای سرویس personne درخواست کنیم:

Image

بیایید لینک مربوط به شناسه روش منحصربه‌فرد را دنبال کنیم:

Image

متد id هیچ پارامتری ندارد. بیایید از دکمه Appeler استفاده کنیم:

Image

ما با موفقیت اطلاعات موجود در فایل web.config سرویس را بازیابی کردیم.

10.10. سرویس وب IMPOTS

ما به برنامه IMPOTS که اکنون آشناست بازمی‌گردیم. آخرین باری که با آن کار کردیم، آن را به یک سرور از راه دور تبدیل کردیم که می‌توانست از طریق اینترنت فراخوانی شود. اکنون آن را به یک سرویس وب تبدیل می‌کنیم.

10.10.1. سرویس وب

ما با کلاس impôt شروع می‌کنیم که در فصل پایگاه داده‌ها ایجاد شده است و با استفاده از اطلاعات موجود در پایگاه داده ODBC ساخته شده است:


' گزینه‌ها
Option Strict On
Option Explicit On 

' فضاهای نام
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections

Public Class impôt
    ' داده‌های مورد نیاز برای محاسبه مالیات
    ' از یک منبع خارجی می‌آیند
    Private limites(), coeffR(), coeffN() As Decimal

    ' سازنده
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
        ' ما بررسی می‌کنیم که سه جدول اندازه یکسانی داشته باشند
        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
        ' همه چیز درست است
        Me.limites = LIMITES
        Me.coeffR = COEFFR
        Me.coeffN = COEFFN
    End Sub

    'سازندهٔ ۲
    Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
        'سه آرایه مرزی coeffR و coeffN را بر اساس
        'محتویات جدول Timpots در پایگاه داده ODBC DSNimpots
        '، colLimites، colCoeffR و colCoeffN سه ستون این جدول هستند
        'ممکن است یک استثنا پرتاب کند
        Dim connectString As String = "DSN=" + DSNimpots + ";"        ' chaîne de connexion à la base
        Dim impotsConn As OdbcConnection = Nothing        ' la connexion
        Dim sqlCommand As OdbcCommand = Nothing        ' la commande SQL
        'پرس‌وجوی SELECT
        Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
        'جداول برای بازیابی داده‌ها
        Dim tLimites As New ArrayList
        Dim tCoeffR As New ArrayList
        Dim tCoeffN As New ArrayList

        ' تلاشی برای دسترسی به پایگاه داده انجام می‌شود
        impotsConn = New OdbcConnection(connectString)
        impotsConn.Open()
        ' یک شیء فرمان در حال ایجاد است
        sqlCommand = New OdbcCommand(selectCommand, impotsConn)
        ' اجرای پرس‌وجو
        Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
        ' پردازش جدول بازیابی‌شده
        While myReader.Read()
            'داده‌های سطر فعلی در آرایه‌ها قرار می‌گیرند
            tLimites.Add(myReader(colLimites))
            tCoeffR.Add(myReader(colCoeffR))
            tCoeffN.Add(myReader(colCoeffN))
        End While
        'منابع آزاد می‌شوند
        myReader.Close()
        impotsConn.Close()

        ' آرایه‌های پویا به آرایه‌های ایستا تبدیل می‌شوند
        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

    'محاسبه مالیات
    Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
        ' محاسبه تعداد سهام
        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
        'محاسبه درآمد مشمول مالیات و ضریب خانوادگی
        Dim revenu As Decimal = 0.72D * salaire
        Dim QF As Decimal = revenu / nbParts
        'محاسبه مالیات
        limites((limites.Length - 1)) = QF + 1
        Dim i As Integer = 0
        While QF > limites(i)
            i += 1
        End While
        ' نتیجه بازگشت
        Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
    End Function
End Class

در سرویس وب، تنها می‌توان از یک سازنده بدون پارامتر استفاده کرد. بنابراین، سازنده کلاس به شکل زیر درخواهد آمد:


    ' سازنده
    Public Sub New()
        ' سه جدول محدوده را، coeffR و coeffN، بر اساس
        'محتویات جدول Timpots در پایگاه داده ODBC DSNimpots
        '، colLimites، colCoeffR و colCoeffN سه ستون این جدول هستند
        'ممکن است یک استثنا پرتاب کند

        'پارامترهای پیکربندی سرویس را بازیابی می‌کند
        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")

        'پرس‌وجو از پایگاه داده انجام می‌شود
        Dim connectString As String = "DSN=" + DSNimpots + ";"     ' chaîne de connexion à la base

پنج پارامتر سازنده کلاس قبلی اکنون از فایل web.config سرویس خوانده می‌شوند. کد در فایل منبع impots.asmx به شرح زیر است. این کد بیشتر کد قبلی را در بر می‌گیرد. ما به سادگی بخش‌های کدی را که به سرویس وب مربوط می‌شوند، برجسته کرده‌ایم:


<%@ WebService language="VB" class=impots %>

'ایجاد یک سرویس وب مالیاتی
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

    'داده‌های مورد نیاز برای محاسبه مالیات
    ' از یک منبع خارجی تأمین می‌شود
    Private limites(), coeffR(), coeffN() As Decimal
    Private OK As Boolean = False
    Private errMessage As String = ""


    ' سازنده
    Public Sub New()
        ' آرایه‌های محدودهٔ سه‌گانه را، coeffR و coeffN، بر اساس
        'محتویات جدول Timpots در پایگاه داده ODBC DSNimpots
        '، colLimites، colCoeffR و colCoeffN سه ستون این جدول هستند
        'ممکن است یک استثنا پرتاب کند

        'پارامترهای پیکربندی سرویس را بازیابی می‌کند
        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")

        ' پایگاه داده پرس‌وجو می‌شود
        Dim connectString As String = "DSN=" + DSNimpots + ";"     ' chaîne de connexion à la base
        Dim impotsConn As OdbcConnection = Nothing     ' la connexion
        Dim sqlCommand As OdbcCommand = Nothing     ' la commande SQL
        Dim myReader As OdbcDataReader     ' lecteur de données Odbc

        'پرس‌وجوی SELECT
        Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots

        ' جدول‌ها برای بازیابی داده‌ها
        Dim tLimites As New ArrayList
        Dim tCoeffR As New ArrayList
        Dim tCoeffN As New ArrayList

        'تلاش برای دسترسی به پایگاه داده
        Try
            impotsConn = New OdbcConnection(connectString)
            impotsConn.Open()
            'یک شیء فرمان ایجاد می‌شود
            sqlCommand = New OdbcCommand(selectCommand, impotsConn)
            'اجرای پرس‌وجو
            myReader = sqlCommand.ExecuteReader()
            ' پردازش جدول بازیابی‌شده
            While myReader.Read()
                'داده‌ها از سطر جاری در آرایه‌ها قرار می‌گیرند
                tLimites.Add(myReader(colLimites))
                tCoeffR.Add(myReader(colCoeffR))
                tCoeffN.Add(myReader(colCoeffN))
            End While
            'منابع آزاد می‌شوند
            myReader.Close()
            impotsConn.Close()

            'آرایه‌های پویا به آرایه‌های ایستا تبدیل می‌شوند
            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
            ' تمام شد
            OK = True
            errMessage = ""
        Catch ex As Exception
            ' خطا
            OK = False
            errMessage += "[" + ex.Message + "]"
        End Try
    End Sub

    'محاسبه مالیات
    <WebMethod()> _
    Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
        'محاسبه تعداد سهام
        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
        'محاسبه درآمد مشمول مالیات و ضریب خانوادگی
        Dim revenu As Decimal = 0.72D * salaire
        Dim QF As Decimal = revenu / nbParts
        'محاسبه مالیات
        limites((limites.Length - 1)) = QF + 1
        Dim i As Integer = 0
        While QF > limites(i)
            i += 1
        End While
        ' نتیجه اظهارنامه
        Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
    End Function

    'شناسه
    <WebMethod()> _
    Function id() As String
        'برای بررسی اینکه همه چیز درست است OK
        Return "[" + OK + "," + errMessage + "]"
    End Function
End Class

بیایید چند تغییر ایجاد شده در کلاس impots را توضیح دهیم، به جز تغییراتی که برای تبدیل آن به یک سرویس وب ضروری بود:

  • خواندن پایگاه داده در سازنده ممکن است با شکست مواجه شود. بنابراین ما دو ویژگی و یک متد به کلاس خود اضافه کرده‌ایم:
    • مقدار بوله‌ای OK در صورتی که پایگاه داده قابل خواندن باشد روی vrai و در غیر این صورت روی faux تنظیم می‌شود
    • زنجیره 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

اگر از دکمه Appeler استفاده کنید:

Image

نتیجه بالا مقادیر ویژگی‌های OK (true) و errMessage ("") را نمایش می‌دهد. در این مثال، پایگاه داده به درستی بارگذاری شده است. این موضوع همیشه صادق نبوده است، به همین دلیل ما متد id را برای دسترسی به پیام خطا اضافه کرده‌ایم. خطا این بود که نام پایگاه داده DSN به عنوان نام کاربری (DSN) تعریف شده بود، در حالی که باید به عنوان نام سیستم (DSN) تعریف می‌شد. این تمایز در مدیر منبع ۳۲ بیتی ODBC ایجاد شده است:

بیایید به صفحه سرویس بازگردیم:

Image

بیایید لینک calculer را دنبال کنیم:

Image

ما پارامترهای فراخوانی را تعریف می‌کنیم و فراخوانی را اجرا می‌کنیم:

Image

نتیجه صحیح است.

10.10.2. پراکسی سرویس impots را تولید کنید

اکنون که سرویس وب impots کار می‌کند، می‌توانیم کلاس پروکسی آن را تولید کنیم. به یاد داشته باشید که این کلاس توسط برنامه‌های کلاینت برای دسترسی شفاف به سرویس وب impots استفاده خواهد شد. ابتدا، از ابزار wsdl برای تولید فایل منبع کلاس پروکسی استفاده می‌کنیم که سپس به یک DLL کامپایل می‌شود.

dos>wsdl /language=vb http://localhost/taxes/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 سپس از کلاس استاندارد impôt، همان کلاسی که در فایل impots.dll قرار دارد، استفاده می‌کرد. کد برنامه testimpots.vb به شرح زیر بود:


Option Explicit On 
Option Strict On

' فضاهای نام
Imports System
Imports Microsoft.VisualBasic

' صفحه آزمایشی
Module testimpots
    Sub Main(ByVal arguments() As String)
        ' برنامه تعاملی محاسبه مالیات
        'کاربر سه مورد اطلاعات را از طریق صفحه‌کلید وارد می‌کند: متأهل nbEnfants حقوق
        ' سپس برنامه مالیات قابل پرداخت را نمایش می‌دهد
        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"

        'بررسی تنظیمات برنامه
        If arguments.Length <> 5 Then
            ' پیام خطا
            Console.Error.WriteLine(syntaxe1)
            'پایان
            Environment.Exit(1)
        End If        'if
        ' بازیابی آرگومان‌ها
        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)

        'ایجاد یک شیء مالیاتی
        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

        'حلقه بی‌نهایت
        While True
            ' در ابتدا هیچ خطایی وجود ندارد
            Dim erreur As Boolean = False

            ' درخواست پارامترها برای محاسبه مالیات
            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()

            'آیا کاری هست که باید انجام شود؟
            If paramètres Is Nothing Or paramètres = "" Then
                Exit While
            End If

            ' بررسی تعداد آرگومان‌ها در خط واردشده
            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
                'بررسی اعتبار پارامترها
                ' متأهل
                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
                ' حقوق
                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
                'پارامترها صحیح هستند – مالیات محاسبه شده است
                Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
            End If
        End While
    End Sub
End Module

ما در حال بازاستفاده از همان برنامه هستیم تا اکنون آن را وادار کنیم از سرویس وب impots از طریق کلاس واسطه impots که قبلاً ایجاد شده است، استفاده کند. ما مجبوریم کد را اندکی تغییر دهیم:

  • در حالی که کلاس اصلی impôt دارای یک سازنده با پنج آرگومان بود، کلاس پروکسی impots دارای یک سازنده بدون هیچ آرگومانی است. همانطور که دیدیم، این پنج پارامتر اکنون در فایل پیکربندی سرویس وب تنظیم می‌شوند.
  • بنابراین دیگر نیازی به ارسال این پنج پارامتر به‌عنوان آرگومان به برنامهٔ آزمایشی نیست.

کد جدید به شرح زیر است:


Imports System
Imports Microsoft.VisualBasic

' صفحه آزمایشی
Module testimpots

    Public Sub Main(ByVal arguments() As String)
        ' برنامه تعاملی محاسبه مالیات
        'کاربر سه مقدار را از طریق صفحه‌کلید وارد می‌کند: متأهل nbEnfants حقوق
        ' سپس برنامه مالیات قابل پرداخت را نمایش می‌دهد
        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"

        ' ایجاد یک شیء مالیاتی
        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

        'حلقه بی‌نهایت
        Dim erreur As Boolean
        While True
            ' در ابتدا هیچ خطایی وجود ندارد
            erreur = False
            ' برنامه پارامترهای محاسبه مالیات را می‌پرسد
            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()
            ' کاری هست که باید انجام شود؟
            If paramètres Is Nothing Or paramètres = "" Then
                Exit While
            End If
            ' بررسی تعداد آرگومان‌ها در خط واردشده
            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
                'بررسی اعتبار پارامترها
                ' متأهل
                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
                ' حقوق
                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 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

ما در واقع به نتیجهٔ مورد انتظار می‌رسیم.