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>
اینترنت اکسپلورر صفحه زیر را نمایش خواهد داد:

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

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

نتاسکیپ در واقع همان دادهها را مانند اینترنت اکسپلورر دریافت کرد، اما آنها را به شیوهای متفاوت نمایش داد. از این پس، برای اسکرینشاتها از اینترنت اکسپلورر استفاده خواهیم کرد.
10.3. یک سرویس وب اول
ما قصد داریم خدمات وب را از طریق یک مثال بسیار ساده که در سه نسخه ارائه شده است، بررسی کنیم.
10.3.1. نسخهٔ ۱
برای این نسخه اول، از VS.NET استفاده خواهیم کرد که این مزیت را دارد که میتواند یک اسکلت سرویس وب تولید کند که بلافاصله آماده به کار است. پس از اینکه این معماری را درک کردیم، قادر خواهیم بود به طور مستقل کار را آغاز کنیم. این موضوع محور نسخههای بعدی خواهد بود.
با استفاده از VS.NET، بیایید یک پروژه جدید با گزینه [Fichier/Nouveau/Projet] ایجاد کنیم:

لطفاً به نکات زیر توجه کنید:
- نوع پروژه ویژوال بیسیک است (پنل سمت چپ)
- قالب پروژه 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 است:

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

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

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

چند نکته قابل توجه وجود دارد:
- این فایل 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 ارث میبرد:
این بدان معناست که ما باید فضای نام 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 را نمایش میدهد:

چرا URL http://localhost/polyvbnet/demo/Service1.asmx؟ چون تنها فایل .asmx در پروژه بود:

اگر چندین فایل .asmx وجود داشت، مجبور بودیم مشخص کنیم که کدام یک باید ابتدا اجرا شود. این کار با کلیک راست روی فایل .asmx مربوطه و انتخاب گزینه [Définir comme page de démarrage] انجام میشود.

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

بیایید آن را با یک ویرایشگر متن (Notepad یا مشابه) باز کنیم. محتوای زیر را مشاهده میکنیم:
این فایل حاوی یک دستور ساده برای سرور 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 به آن دسترسی پیدا میکنیم، بازگردیم:

چه کسی کد HTML برای صفحهٔ بالا را نوشته است؟ ما که نه، همانطور که میدانیم. این IIS بود که سرویسهای وب را به شیوهای استاندارد ارائه میدهد. این صفحه دو لینک در اختیار ما قرار میدهد. بیایید اولی را دنبال کنیم، [Description du service]:

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

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

این بار دوباره 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' قرار میدهیم (این نام اجباری است):
اکنون فایل demo2.asmx را ایجاد میکنیم. این فایلی است که توسط کلاینتهای وب فراخوانی خواهد شد. محتویات آن به شرح زیر است:
ما قبلاً با این دستور مواجه شدهایم. این نشان میدهد که:
- کلاس سرویس وب Bonjour2 نام دارد و در اسمبلی demo2.dll قرار دارد. IIS این اسمبلی را در مکانهای مختلف، از جمله پوشه bin سرویس وب، جستجو خواهد کرد. به همین دلیل ما اسمبلی demo2.dll را در آنجا قرار دادهایم.
اکنون میتوانیم تستهای مختلفی را انجام دهیم. بررسی میکنیم که IIS در حال اجرا است و سپس در یک مرورگر به آدرس http://localhost/polyvbnet/demo2/demo2.asmx دسترسی پیدا میکنیم:

سپس URL http://localhost/polyvbnet/demo2/demo2.asmx?WSDL

سپس URL http://localhost/polyvbnet/demo2/demo2.asmx?op=getBonjour، که در آن getBonjour نام تنها متد سرویس وب ما است:

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

ما در واقع نتیجه فراخوانی متد getBonjour سرویس وب را دریافت میکنیم. اکنون میدانیم چگونه یک سرویس وب بدون vs.net بسازیم. از این پس، نحوه ساخت سرویس وب را نادیده گرفته و صرفاً بر روی فایلهای اساسی تمرکز خواهیم کرد.
10.3.3. نسخه ۳
دو نسخه قبلی سرویس وب [Bonjour] از دو فایل استفاده میکردند:
- یک فایل .asmx، نقطه ورود سرویس وب
- یک فایل .vb، حاوی کد منبع سرویس وب
در اینجا نشان میدهیم که تنها فایل .asmx کافی است. کد سرویس demo3.asmx به شرح زیر است:
میتوانیم ببینیم که کد منبع سرویس اکنون مستقیماً در فایل منبع demo3.asmx قرار دارد. دستور
دیگر به کلاسی در یک اسمبلی خارجی ارجاع نمیدهد، بلکه به کلاسی در همان فایل منبع اشاره میکند. بیایید این را در پوشه <IISroot>\polyvbnet\demo3 قرار دهیم:

بیایید IIS را اجرا کرده و URL http://localhost/polyvbnet/demo3/demo3.asmx را درخواست کنیم:

ما میتوانیم تفاوت قابل توجهی را در مقایسه با نسخه قبلی مشاهده کنیم: ما دیگر نیازی به کامپایل کردن کد VB سرویس نداشتیم. IIS خود این کامپایل را با استفاده از کامپایلر VB.NET که روی همان ماشین نصب شده بود، انجام داد. سپس صفحه را ارائه داد. اگر خطای کامپایل وجود داشته باشد، این خطا توسط IIS گزارش خواهد شد:

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

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

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

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

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

10.3.5. نتیجهگیری
ما چندین روش برای ایجاد یک سرویس وب را نشان دادیم. از این پس، برای ایجاد سرویس از روش توصیفشده در نسخه ۳ و برای محلیسازی آن از روش ۴ استفاده خواهیم کرد. بنابراین به VS.NET نیازی نخواهیم داشت. با این وجود، شایان ذکر است که از مزایای استفاده از VS.NET به دلیل کمکی که در عیبیابی ارائه میدهد، غافل نشویم. ابزارهای رایگان برای توسعه برنامههای وب در دسترس هستند، بهویژه محصول WebMatrix، با حمایت مالی مایکروسافت، که میتوان آن را در URL [http://www.asp.net/webmatrix] یافت. این یک ابزار عالی برای شروع برنامهنویسی وب بدون هیچگونه سرمایهگذاری اولیه است.
10.4. یک سرویس وب برای عملیات
بیایید یک سرویس وب را در نظر بگیریم که پنج تابع ارائه میدهد:
- add(a,b)، که a+b را بازمیگرداند
- تفریق(a,b)، که a-b را برمیگرداند
- ضرب(a, b)، که a * b را برمیگرداند
- divide(a,b)، که a/b را برمیگرداند
- 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 ذخیره میکنیم:

ما پوشه مجازی IIS [operations] را با این پوشه فیزیکی مرتبط میکنیم:
![]() | ![]() |
بیایید با استفاده از یک مرورگر وب به سرویس دسترسی پیدا کنیم. URl که باید درخواست شود، [http://localhost/operations/operations.asmx] است:

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

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

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

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

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

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

در همه موارد، پاسخ سرور به شکل زیر است:
- پاسخ در قالب XML است
- خط ۱ استاندارد است و همیشه در پاسخ وجود دارد
- خطوط زیر به نوع نتیجه (double, ArrayOfDouble)، تعداد نتایج و فضای نام سرویس وب (در این مورد st.istia.univ-angers.fr) بستگی دارند.
چندین روش برای پرسوجو از یک سرویس وب و دریافت پاسخ آن وجود دارد. بیایید به URL سرویس بازگردیم:

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



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

بیایید آنچه نوشته شده را بررسی کنیم. اول از همه، کلاینت وب باید سربرگهای زیر HTTP را ارسال کند:
کلاینت وب مطابق پروتکل نسخه ۱.۱ HTTP، درخواستی به نقطه پایانی POST/operations/operations.asmx/add ارسال میکند. | |
ماشین مقصد برای درخواست مشخص شده است. در این مورد، localhost. این هدر توسط نسخه ۱.۱ پروتکل HTTP اجباری شده است. | |
این مشخص میکند که پس از سربرگهای HTTP، پارامترهای اضافی در قالب urlencoded ارسال خواهند شد. این قالب برخی از کاراکترها را با کدهای هگزادسیمال آنها جایگزین میکند. | |
این تعداد کاراکترها در رشته پارامتر است که پس از سربرگهای 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 است، به کلاینت اطلاع میدهد که میتواند ۷ کاراکتری را که اعلام کرده بود مایل به ارسال آن است، ارسال کند. کاری که ما انجام میدهیم:
در اینجا باید توجه داشت که کلاینت 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 از سرویس وب فراخوانی میشود:
سپس کلاینت دستورات واردشده از طریق صفحهکلید را میخواند و اجرا میکند. این دستورات در قالب زیر هستند:
که در آن «function» تابع سرویس وب فراخوانیشده است (جمع، تفریق، ضرب، تقسیم) و «a» و «b» مقادیری هستند که این تابع بر روی آنها عمل میکند. برای مثال:
از آنجا، کلاینت درخواست لازم HTTP را به وبسرور ارسال میکند و یک پاسخ دریافت میکند. تعاملات کلاینت-سرور بر روی صفحه نمایش داده میشوند تا به درک فرآیند کمک کنند:
ajouter 6 7
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=6&b=7
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
[résultat=13]
تبادله نشان داده شده در بالا مشابه موردی است که قبلاً با کلاینت عمومی TCP مواجه شدیم، با این تفاوت که هدر HTTP Connection: Keep-Alive به سرور دستور میدهد که اتصال را قطع نکند. بنابراین اتصال برای عملیات بعدی کلاینت باز باقی میماند، به این معنی که کلاینت نیازی به برقراری مجدد اتصال به سرور ندارد. با این حال، این بدان معناست که کلاینت باید برای خواندن پاسخ سرور از روشی غیر از ReadLine() استفاده کند، زیرا میدانیم که پاسخ از مجموعهای از خطوط تشکیل شده است که آخرین خط آن با خط جدید پایان نمییابد. پس از دریافت کامل پاسخ سرور، کلاینت آن را تجزیه و تحلیل میکند تا نتیجه عملیات درخواستی را پیدا کرده و نمایش دهد:
بیایید کد کلاینت خود را بررسی کنیم:
' فضاهای نام
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
در هدر
شما باید اندازه پارامترهایی را که توسط کلاینت پس از هدرهای HTTP ارسال میشوند، مشخص کنید:
برای این کار، از کد زیر استفاده کنید:
' ساختاردهی رشتهٔ پرسوجو
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))
پس از خوانده شدن این پاسخ اولیه، کلاینت باید پارامترهای خود را ارسال کند:
آنها این کار را با استفاده از کد زیر انجام میدهند:
سپس سرور پاسخ خود را ارسال میکند. این پاسخ از دو بخش تشکیل شده است:
- سربرگهای HTTP، و سپس یک خط خالی
- پاسخ در قالب XML
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
ابتدا، کلاینت سربرگهای HTTP را میخواند تا خط Content-Length را پیدا کرده و اندازه پاسخ XML (در این مورد، 90) را بازیابی کند. این با استفاده از یک عبارت منظم بازیابی میشود. این کار میتوانست به روش دیگری و احتمالاً با کارایی بیشتری انجام شود.
' ساخت عبارت منظم برای تعیین اندازه پاسخ 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 کاراکتری برای اهداف نظارت بر صفحه به خطوط متنی تقسیم میشود. در میان این خطوط، به دنبال خط حاوی نتیجه میگردیم:
دوباره با استفاده از یک عبارت منظم. پس از یافتن نتیجه، آن نمایش داده میشود.
پایان کد کلاینت به شرح زیر است:
' ساخت عبارت منظم برای بازیابی نتیجه
' در جریان پاسخ وبسرور
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 نشان داده شده است:


درخواست کلاینت یک درخواست POST است. بنابراین ما برخی از همان مکانیزمهای موجود در کلاینت قبلی را مشاهده خواهیم کرد. تفاوت اصلی این است که در حالی که کلاینت HTTP-POST پارامترهای a و b را به صورت ارسال میکرد
کلاینت SOAP آنها را در قالب پیچیدهتر XML ارسال میکند:
POST /operations/operations.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "st.istia.univ-angers.fr/ajouter"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>double</a>
<b>double</b>
</ajouter>
</soap:Body>
</soap:Envelope>
در مقابل، پاسخی دریافت میکند، XML، که آن نیز پیچیدهتر از پاسخهای مشاهدهشدهی قبلی است:
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouterResponse xmlns="st.istia.univ-angers.fr">
<ajouterResult>double</ajouterResult>
</ajouterResponse>
</soap:Body>
</soap:Envelope>
اگرچه درخواست و پاسخ پیچیدهتر هستند، این در واقع همان مکانیزم (HTTP) برای مشتری HTTP-POST است. بنابراین کد مشتری SOAP را میتوان بر اساس کد مشتری HTTP-POST مدلسازی کرد. در اینجا یک مثال از اجرا آورده شده است:
dos>clientsoap1 http://localhost/operations/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>
کد:
سرور سپس پاسخ نهایی خود را ارسال خواهد کرد:
<-- 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
دو پارامتر به سازنده ارسال میشوند:
- URI سرویس وبی که باید به آن متصل شود
- یک مقدار بولی verbose که در واقع مشخص میکند مبادلات شبکه باید روی صفحه نمایش داده شوند؛ در غیر این صورت، نمایش داده نخواهند شد.
در حین ساخت، جریان خواندن شبکه IN، جریان نوشتن شبکه OUT و فرهنگ لغت توابع مدیریتشده توسط سرویس ایجاد میشوند. پس از ایجاد شیء، اتصال کلاینت-سرور برقرار شده و استریمهای IN و OUT آن آماده استفاده هستند.
متد Close برای بستن اتصال به سرور استفاده میشود.
متد ExecuteFonction همان متدی است که برای کلاینت SOAP که قبلاً در مورد آن بحث شد نوشتهایم، با چند تفاوت جزئی:
- پارامترهای `uri`، `IN` و `OUT` که قبلاً بهعنوان پارامتر به متد ارسال میشدند، دیگر نیازی به ارسال ندارند، زیرا اکنون بهعنوان ویژگیهای نمونه در دسترس تمام متدهای نمونه قرار دارند
- متد ExecuteFonction که قبلاً یک نوع void را بازمیگرداند و نتیجه تابع را روی صفحه نمایش میداد، اکنون این نتیجه را بازمیگرداند و بنابراین یک نوع string را بازمیگرداند.
به طور معمول، یک کلاینت از کلاس clientSOAP به شرح زیر استفاده خواهد کرد:
- ایجاد یک شیء clientSOAP، که اتصال به سرویس وب را برقرار میکند
- فراخوانی مکرر متد executeFonction
- بستن اتصال به سرویس وب با استفاده از متد 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
کلاینت اکنون بهطور قابلتوجهی سادهتر شده و هیچ ارتباط شبکهای ندارد. کلاینت دو پارامتر را میپذیرد:
- URI سرویس وب operations
- کلمه کلیدی اختیاری 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 نیز بهره میبرد. رابط کاربری گرافیکی به شرح زیر خواهد بود:
![]() |
کنترلها به شرح زیر هستند:
خیر. | نوع | نام | نقش |
TextBox | txtURI | URI از بخش عملیات وب | |
دکمه | btnOuvrir | اتصال به سرویس وب را باز میکند | |
دکمه | btnFermer | ارتباط با سرویس وب را قطع میکند | |
ComboBox | cmbFonctions | فهرست توابع (جمع، تفریق، ضرب، تقسیم) | |
TextBox | txtA | آرگومانهای توابع | |
TextBox | txtB | آرگومان b توابع | |
TextBox | txtRésultat | نتیجه تابع (a, b) | |
دکمه | btnCalculer | شروع محاسبه تابع (a,b) | |
TextBox | txtErreur | پیام وضعیت اتصال را نمایش میدهد |
چند محدودیت عملیاتی وجود دارد:
- دکمه btnOuvrir تنها زمانی فعال است که فیلد txtURI خالی نباشد و اتصال از قبل باز نشده باشد
- دکمه btnFermer تنها زمانی فعال است که ارتباط با سرویس وب برقرار شده باشد
- دکمه btnCalculer تنها زمانی فعال است که یک اتصال برقرار باشد و فیلدهای txtA و txtB خالی نباشند
- میدانهای txtRésultat و txtErreur دارای ویژگی ReadOnly با مقدار true هستند
کلاینت با استفاده از دکمه [Ouvrir] اتصال خود را با سرویس وب برقرار میکند:

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





کد برنامه در ادامه آمده است. ما کد فرم را حذف کردهایم، زیرا در اینجا مرتبط نیست.
'فضاهای نام
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
رابط کاربری گرافیکی سپس توسط: راهاندازی شد
10.8. یک کلاینت پروکسی
بیایید مروری بر آنچه انجام دادهایم داشته باشیم. ما یک کلاس واسط ایجاد کردهایم که مبادلات شبکهای بین یک کلاینت و یک سرویس وب را در بر میگیرد، همانطور که در نمودار زیر نشان داده شده است:
![]() |
پلتفرم NET این منطق را یک قدم فراتر میبرد. به محض اینکه سرویس وب مورد دسترسی مشخص شود، میتوانیم به طور خودکار کلاسی را تولید کنیم که به عنوان واسطه برای دسترسی به توابع سرویس وب عمل کرده و کل لایه شبکه را پنهان میکند. به این کلاس، پروکسی (proxy) برای سرویس وبی که برای آن تولید شده است، گفته میشود.
چگونه یک کلاس پروکسی برای یک سرویس وب ایجاد میکنید؟ یک سرویس وب همیشه با یک فایل توصیفی با فرمت XML ارائه میشود. اگر URI برای سرویس وب «عملیات» ما http://localhost/operations/operations.asmx باشد، فایل توصیفی آن در http://localhost/operations/operations.asmx?wsdl موجود است، همانطور که در اسکرینشات زیر نشان داده شده است:

این یک فایل 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
....
این کد در نگاه اول کمی پیچیده به نظر میرسد. برای استفاده از آن نیازی به درک جزئیات نداریم. ابتدا بیایید به اعلان کلاس نگاه کنیم:
این کلاس به نام operations نامگذاری شده است، به خاطر سرویس وبی که برای آن ایجاد شده است. این کلاس از کلاس SoapHttpClientProtocol ارث میبرد:

کلاس پروکسی ما یک سازنده دارد:
سازنده، مقدار 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>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 قرار میگیرند:
یک پارامتر راهاندازی با نام P و مقدار V با خط زیر اعلام خواهد شد:
<add key="P" value="V"/>
سرویس وب چگونه این اطلاعات را بازیابی میکند؟ وقتی IIS یک سرویس وب را بارگذاری میکند، بررسی میکند که آیا فایلی به نام web.config در همان پوشه وجود دارد یا خیر. اگر چنین باشد، آن را میخواند. مقدار V یک پارامتر P با استفاده از عبارت زیر به دست میآید:
که در آن ConfigurationSettings یک کلاس در فضای نام System.Configuration است.
بیایید این تکنیک را روی سرویس وب زیر آزمایش کنیم:
<%@ WebService language="VB" class=personne %>
Imports System.Web.Services
imports System.Configuration
<WebService([Namespace] := "st.istia.univ-angers.fr")> _
Public Class personne
Inherits WebService
' ویژگیها
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 قرار دارد:
بیایید یک پوشه مجازی IIS/config را با پوشه فیزیکی فوق مرتبط کنیم. بیایید IIS را اجرا کرده و سپس با استفاده از یک مرورگر وب، URL http://localhost/config/personne.asmx را برای سرویس personne درخواست کنیم:

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

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

ما با موفقیت اطلاعات موجود در فایل 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 را که در دستور استفاده شده است، پیدا کند:
پس از مراجعه به مستندات
- یک دستور کامپایل به web.config اضافه شد تا مشخص شود که اسمبلی Microsoft.Data.odbc باید استفاده شود
- یک کپی از فایل microsoft.data.odbc.dll در پوشه bin پروژه قرار داده شد. این پوشه توسط کامپایلر سرویس وب به طور سیستماتیک هنگام جستجو برای یک 'اسمبلی' اسکن میشود.
راهحلهای دیگری نیز ممکن به نظر میرسند اما در اینجا بررسی نشدهاند. بنابراین فایل پیکربندی به این صورت درآمده است:
<configuration>
<appSettings>
<add key="DSN" value="mysql-impots" />
<add key="TABLE" value="timpots" />
<add key="COL_LIMITES" value="limites" />
<add key="COL_COEFFR" value="coeffr" />
<add key="COL_COEFFN" value="coeffn" />
</appSettings>
<system.web>
<compilation>
<assemblies>
<add assembly="Microsoft.Data.Odbc" />
</assemblies>
</compilation>
</system.web>
</configuration>
محتویات پوشه impots\bin:
سرویس و فایل پیکربندی آن در impots قرار داده شدهاند:
پوشه فیزیکی سرویس وب به پوشه مجازی /impots در IIS نگاشت شده است. بنابراین صفحه سرویس به شرح زیر است:

اگر پیوند id را دنبال کنید:

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

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

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

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

نتیجه صحیح است.
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
ما در واقع به نتیجهٔ مورد انتظار میرسیم.





