Skip to content

10. Web Hizmetleri

10.1. Giriş

Bir önceki bölümde çeşitli TCP/IP istemci-sunucu uygulamalarını ele aldık. İstemciler ve sunucu arasında metin satırları alışverişi yapıldığı için, bu uygulamalar herhangi bir programlama dilinde yazılabilir. İstemcinin yalnızca sunucunun beklediği iletişim protokolünü bilmesi yeterlidir. Web hizmetleri, aşağıdaki özelliklere sahip TCP/IP sunucu uygulamalarıdır:

  • Web sunucuları tarafından barındırılırlar ve bu nedenle istemci-sunucu iletişim protokolü, HTTP (HyperText Taşıma Protokolü) olup, bu protokol TCP-IP protokolünün üzerinde yer alır.
  • Web hizmeti, sağlanan hizmet ne olursa olsun standart bir iletişim protokolüne sahiptir. Bir web hizmeti, S1, S2, .., Sn gibi çeşitli hizmetler sunar. Her biri, istemci tarafından sağlanan parametreleri bekler ve istemciye bir sonuç döndürür. Her hizmet için istemcinin şunları bilmesi gerekir:
    • hizmetin tam adı, eğer
    • hizmetin sağlanması için gerekli parametrelerin listesi ve türleri
    • hizmet tarafından döndürülen sonucun türü

Bu unsurlar bilindiğinde, sorgulanan web hizmeti ne olursa olsun istemci-sunucu iletişimi aynı formatı izler. Böylece istemcilerin yazımı standart hale gelir.

  • İnternetten gelen saldırılara karşı güvenlik nedeniyle, birçok kuruluş özel ağlara sahiptir ve sunucularının yalnızca belirli bağlantı noktalarını İnternet’e açar: esas olarak web hizmetinin 80 numaralı bağlantı noktası. Diğer tüm bağlantı noktaları kilitlidir. Bu nedenle, önceki bölümde anlatıldığı gibi istemci-sunucu uygulamaları özel ağ (intranet) içinde oluşturulur ve genellikle dışarıdan erişilemez. Bir hizmeti bir web sunucusunda barındırmak, onu tüm internet topluluğuna erişilebilir hale getirir.
  • Web hizmeti, uzak bir nesne olarak modellenebilir. Sunulan hizmetler, bu nesnenin yöntemleri haline gelir. Bir istemci, bu uzak nesneye sanki yerelmiş gibi erişebilir. Bu, ağ iletişiminin tamamını gizler ve bu katmandan bağımsız bir istemci oluşturulmasına olanak tanır. Bu katman değişse bile, istemcinin değiştirilmesine gerek kalmaz. Bu, çok büyük bir avantajdır ve muhtemelen Web hizmetlerinin en önemli özelliğidir.
  • Bir önceki bölümde ele alınan TCP/IP istemci-sunucu uygulamalarında olduğu gibi, istemci ve sunucu herhangi bir programlama dilinde yazılabilir. Aralarında metin satırları alışverişi yaparlar. Bu satırlar iki bölümden oluşur:
    • HTTP protokolü için gerekli başlıklar
    • mesajın gövdesi. Sunucudan istemciye gönderilen yanıt, XML (eXtensible İşaretleme Dili) biçimindedir. İstemciden sunucuya gönderilen bir istek için ise mesajın gövdesi, XML dahil olmak üzere çeşitli biçimlerde olabilir. İstemciden gelen XML isteği, SOAP (Simple Object Access Protocol) adı verilen özel bir biçime sahip olabilir. Bu durumda, sunucunun yanıtı da SOAP biçimini izler.

10.2. Tarayıcılar ve XML

Web hizmetleri, müşterilerine XML gönderir. Tarayıcılar, bu XML akışını aldıklarında farklı tepkiler verebilir. Internet Explorer, bunu görüntülemek için önceden tanımlanmış bir stil sayfasına sahiptir. Netscape Communicator’da ise bu stil sayfası bulunmadığından, alınan XML kodunu görüntülemez. XML’e erişmek için alınan sayfanın kaynak kodunu görüntülemek gerekir. Aşağıda, şu XML kodu için bir örnek verilmiştir:

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

Internet Explorer şu sayfayı görüntüleyecektir:

Image

Netscape Navigator ise şunu gösterecektir:

Image

Netscape tarafından alınan sayfanın kaynak koduna bakıldığında şunlar görülür:

Image

Netscape, Internet Explorer ile aynı veriyi almıştır ancak bunu farklı şekilde görüntülemiştir. Bundan sonra ekran görüntüleri için Internet Explorer'ı kullanacağız.

10.3. İlk web hizmeti

Web hizmetlerini, üç farklı versiyonu olan çok basit bir örnek üzerinden inceleyeceğiz.

10.3.1. Sürüm 1

Bu ilk versiyon için, hemen kullanıma hazır bir web hizmeti iskeleti oluşturabilme avantajı sunan VS.NET'i kullanacağız. Bu mimariyi anladıktan sonra, kendi başımıza çalışmaya başlayabileceğiz. Bu, sonraki versiyonların konusu olacaktır.

VS.NET ile, [Fichier/Nouveau/Projet] seçeneğini kullanarak yeni bir proje oluşturalım:

Image

Aşağıdaki noktalara dikkat edelim:

  • proje türü Visual Basic'tir (sol çerçeve)
  • proje şablonu ASP.NET Web Hizmeti'dir (sağdaki çerçeve)
  • konum serbesttir. Burada, web hizmeti yerel bir IIS web sunucusu tarafından barındırılacaktır. Dolayısıyla, URL adresi http://localhost/[chemin] olacaktır; burada [chemin] değeri tanımlanmalıdır. Burada http://localhost/polyvbnet/demo yolunu seçiyoruz. VS.NET, bu proje için bir klasör oluşturacaktır. Nerede? IIS sunucusu, sunduğu web belgeleri ağacının bir kök dizinine sahiptir. Bu kök dizine <IISroot> diyelim. Bu, http://localhost adresindeki URL'e karşılık gelir. Buradan, URL http://localhost/polyvbnet/demo adresinin <IISroot>/polyvbnet/demo klasörüyle ilişkilendirileceği sonucuna varılır. <IISroot> normalde IIS'in kurulduğu sürücüdeki \inetpub\wwwroot klasörüdür. Örneğimizde bu, E sürücüsüdür. Dolayısıyla, VS.NET tarafından oluşturulan klasör, e:\inetpub\wwwroot\polyvbnet\demo klasörüdür:

Image

Her zamanki gibi, çok fazla klasör oluşturulmuştur. Bunların hepsi her zaman gerekli değildir. Biz sadece belirli bir anda ihtiyacımız olanları açıklayacağız. VS.NET bir proje oluşturmuştur:

Image

Projenin fiziksel klasöründe bulunan bazı dosyaları burada da görüyoruz. Bizim için en ilgi çekici olanı, asmx uzantılı dosyadır. Bu, web hizmetlerinin uzantısıdır. Bir web hizmeti, VS.NET tarafından bir Windows uygulaması gibi yönetilir; c.a.d gibi, bir grafik arayüze ve onu yönetmek için kod içeren bir uygulama. Bu nedenle, bir tasarım penceremiz var:

Image

Bir web hizmeti normalde grafik kullanıcı arayüzüne sahip değildir. Uzaktan çağrılabilen bir nesneyi temsil eder. Yöntemlere sahiptir ve uygulamalar bu yöntemleri çağırır. Dolayısıyla onu, ağ üzerinden uzaktan örneklenebilme özelliğine sahip klasik bir nesne olarak ele alacağız. Bu nedenle, VS.NET tarafından sunulan tasarım penceresini kullanmayacağız. Bunun yerine, Görünüm/Kod seçeneğini kullanarak hizmetin koduna odaklanalım:

Image

Dikkat edilmesi gereken birkaç nokta var:

  • dosyanın adı Service1.asmx.vb'tir, Service1.asmx değildir. Service1.asmx dosyasının içeriğine biraz ileride tekrar değineceğiz.
  • VS.NET ile Windows uygulamaları oluştururken karşılaştığımız kod penceresine benzer bir pencere karşımıza çıkıyor

VS.NET tarafından oluşturulan kod şöyledir:


Imports System.Web.Services

<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
    Inherits System.Web.Services.WebService

#Bölge "Web Hizmetleri Tasarımcısı tarafından oluşturulan kod"

    Public Sub New()
        MyBase.New()

        'Bu çağrı, Web Hizmetleri Tasarımcısı tarafından zorunlu kılınmıştır.
        InitializeComponent()

        'InitializeComponent() çağrısından sonra başlatma kodunuzu ekleyin

    End Sub

    'Web Hizmetleri Tasarımcısı tarafından zorunlu kılınmıştır
    Private components As System.ComponentModel.IContainer

    'REMARQUE: Aşağıdaki prosedür, Web Hizmetleri Tasarımcısı tarafından zorunlu kılınmıştır
    'Web Hizmetleri Tasarımcısı kullanılarak değiştirilebilir.  
    'Kod düzenleyicisini kullanarak bunu değiştirmeyin.
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        components = New System.ComponentModel.Container()
    End Sub

    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        'CODEGEN: Bu prosedür, Web Hizmetleri Tasarımcısı tarafından zorunlu kılınmıştır
        'Kod düzenleyicisini kullanarak bunu değiştirmeyin.
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

#Bölge Sonu

    ' EXEMPLE DE SERVICE WEB
    ' HelloWorld() hizmet örneği, "Hello World" dizesini döndürür.
    ' Oluşturmak için aşağıdaki satırları yorum satırına dönüştürmeyin, ardından projeyi kaydedin ve derleyin.
    ' Bu web hizmetini test etmek için, .asmx dosyasının başlangıç sayfası olduğundan emin olun
    ' olduğundan emin olun ve F5'e basın.
    '
    '<WebMethod()> Public Function HelloWorld() As String
    '    HelloWorld = "Hello World"
    ' End Function

End Class

Öncelikle, burada WebService sınıfından türetilen Service1 sınıfı olduğunu belirtelim:

Public Class Service1
    Inherits System.Web.Services.WebService

Bu da bizi System.Web.Services ad alanını içe aktarmaya yönlendirir:


Imports System.Web.Services

Sınıf bildiriminin önünde bir derleme özniteliği bulunur:


<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
    Inherits System.Web.Services.WebService

System.Web.Services.WebService() özniteliği, takip eden sınıfın bir web hizmeti olduğunu belirtir. Bu öznitelik, NameSpace adlı parametre de dahil olmak üzere çeşitli parametreleri kabul eder. Bu parametre, web hizmetini bir ad alanına yerleştirmek için kullanılır. Nitekim, dünyada "meteo" adlı birden fazla web hizmeti olduğunu düşünebiliriz. Bunları birbirinden ayırt etmenin bir yoluna ihtiyacımız var. Ad alanı bunu mümkün kılar. Bir tanesi [espacenom1].meteo, diğeri ise [espacenom2].meteo olarak adlandırılabilir. Burada, sınıf ad alanlarına benzer bir kavramla karşılaşıyoruz. VS.NET, otomatik olarak kod oluşturmuş ve bunu kaynak kodun bir bölümüne yerleştirmiştir:

#Bölge "Web Hizmetleri Tasarımcısı tarafından oluşturulan kod"

Bu koda bakıldığında, Windows uygulamaları geliştirilirken tasarımcının ürettiği kodla aynı olduğu görülür. Grafik kullanıcı arayüzü yoksa bu kodu tamamen silebiliriz; web hizmetleri için durumumuz da budur.

Ders, bir web hizmetinin nasıl olabileceğine dair bir örnekle sona eriyor:


#End Region

    ' EXEMPLE DE SERVICE WEB
    ' HelloWorld() hizmet örneği, "Hello World" dizesini döndürür.
    ' Oluşturmak için aşağıdaki satırları yorum satırına dönüştürmeyin, ardından projeyi kaydedin ve derleyin.
    ' Bu web hizmetini test etmek için, .asmx dosyasının başlangıç sayfası olduğundan emin olun
    ' olduğundan emin olun ve F5'e basın.
    '
    '<WebMethod()> Public Function HelloWorld() As String
    '    HelloWorld = "Hello World"
    ' End Function

Yukarıda anlatılanlara dayanarak, kodu temizleyip şu hale getiriyoruz:


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

Artık durumu biraz daha net görebiliyoruz.

  • Bir web hizmeti, WebService sınıfından türetilen bir sınıftır
  • Sınıf, <System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> özniteliği ile tanımlanmıştır. Dolayısıyla hizmetimizi st.istia.univ-angers.fr ad alanına yerleştiriyoruz.
  • Sınıfın yöntemleri, ağ üzerinden uzaktan çağrılabilen bir yöntem olduğunu belirten <WebMethod()> özniteliği ile nitelendirilmiştir

Web hizmetimizi sağlayan sınıfın adı Bonjour'dur ve kendisi de Bonjour adında, bir karakter dizisi döndüren tek bir yönteme sahiptir. İlk test için hazırız.

  • Henüz yapılmadıysa IIS web sunucusunu başlatalım
  • Hata Ayıklama/Hata Ayıklamadan Çalıştır seçeneğini kullanalım. VS.NET

VS.NET, uygulamanın tamamını derleyecek, bir tarayıcıyı başlatacak (varsa genellikle Internet Explorer) ve http://localhost/polyvbnet/demo/Service1.asmx URL'sini görüntüleyecektir:

Image

Neden http://localhost/polyvbnet/demo/Service1.asmx URL'si? Çünkü bu, projedeki tek .asmx dosyasıydı:

Image

Birden fazla .asmx dosyası olsaydı, hangisinin ilk olarak çalıştırılacağını belirtmemiz gerekirdi. Bunu, ilgili .asmx dosyasına sağ tıklayıp [Définir comme page de démarrage] seçeneğini seçerek yapabilirsiniz.

Image

service1.asmx dosyasının içeriğini merak ediyor olabiliriz. Nitekim, VS.NET dosyasıyla çalışırken service1.asmx.vb dosyası üzerinde çalıştık, service1.asmx dosyası üzerinde değil. Bu dosya proje klasöründe yer almaktadır:

Image

Bir metin düzenleyiciyle (Not Defteri veya başka bir program) açalım. Şu içeriği elde ederiz:

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

Dosya, IIS sunucusuna yönelik,

  • bir web hizmeti ile karşı karşıya olduğumuzu (anahtar kelime WebService)
  • bu hizmetin sınıfının dilinin Visual Basic olduğu (Language="vb")
  • bu sınıfın kaynak kodu Service1.asmx.vb dosyasında bulunur (Codebehind="Service1.asmx.vb")
  • hizmeti uygulayan sınıfın adı demo.Bonjour'tir (Class="demo.Bonjour"). VS.NET'in Bonjour sınıfını, aynı zamanda projenin adı olan demo ad alanına yerleştirdiği dikkatinizi çekecektir.

http://localhost/polyvbnet/demo/Service1.asmx URL'sinden ulaşılan sayfaya geri dönelim:

Image

Yukarıdaki sayfanın HTML kodunu kim yazdı? Biz yazmadık, bunu biliyoruz. Bu, web hizmetlerini standart bir şekilde sunan IIS'tir. Bu sayfa bize iki bağlantı sunuyor. İlkini, [Description du service]'i takip edelim:

Image

Oops... bu oldukça anlaşılmaz bir XML. Yine de şuna dikkat edelim: URL

http://localhost/polyvbnet/demo/Service1.asmx?WSDL adresine dikkat edelim. Bir tarayıcı açın ve bu URL'yi doğrudan yazın. Daha önce gördüğümüzle aynı sonucu elde edersiniz. Dolayısıyla, http://serviceweb?WSDL URL'sinin web hizmetinin XML açıklamasına erişim sağladığını hatırlayalım. Başlangıç sayfasına geri dönelim ve [Bonjour] bağlantısını seçelim. Bonjour’un web hizmetinin bir yöntemi olduğunu hatırlayalım. Eğer birden fazla yöntem olsaydı, hepsi burada gösterilirdi. Aşağıdaki yeni sayfaya ulaşırız:

Image

Örneğimizi gereksiz yere uzatmamak için elde ettiğimiz sayfayı kasıtlı olarak kısalttık. Elde ettiğimiz URL’ye tekrar dikkat edelim:

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

Bu URL'yi bir tarayıcıya doğrudan yazarsak, yukarıdakiyle aynı sonucu alırız. [Appeler] düğmesini kullanmamız isteniyor. Hadi bunu yapalım. Yeni bir sayfa elde ediyoruz:

Image

Bu yine XML. Burada, web hizmetimizde bulunan iki bilgiyi görüyoruz:

  • hizmetimizin st.istia.univ-angers.fr ad alanı

<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>
  • Bonjour yöntemi tarafından döndürülen değer:

        Return "bonjour !"

Ne öğrendik?

  • S web hizmetini yazma yöntemini
  • Bu hizmeti nasıl çağıracağımızı

Şimdi VS.NET'in yardımı olmadan bir web hizmeti yazmaya odaklanacağız.

10.3.2. Sürüm 2

Önceki örnekte, VS.NET birçok işi tek başına halletti. Bu araç olmadan bir web hizmeti oluşturmak mümkün mü? Cevap evet ve şimdi bunu göstereceğiz. Bir metin düzenleyici kullanarak şu web hizmetini oluşturacağız:


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

Sınıfın adı Bonjour2 ve getBonjour adlı bir yöntemi var. Bu sınıf, demo2.vb dosyasına yerleştirilmiştir; bu dosya ise IIS sunucusunun dizin yapısı içinde, E:\Inetpub\wwwroot\polyvbnet\demo2 klasöründe bulunmaktadır. Bu, derlenebilen klasik bir VB.NET sınıfıdır:

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 derlemesini bir bin klasörüne yerleştiriyoruz (bu isim zorunludur):

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

Şimdi demo2.asmx dosyasını oluşturuyoruz. Web istemcileri tarafından çağrılacak olan dosya budur. İçeriği şöyledir:

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

Bu yönergeyle daha önce karşılaşmıştık. Bu yönerge şunu belirtir:

  • web hizmetinin sınıfının adı Bonjour2'dir ve demo2.dll derlemesinde bulunur. IIS, bu derlemeyi çeşitli yerlerde, özellikle de web hizmetinin bin klasöründe arayacaktır. Bu nedenle, demo2.dll derlemesini buraya yerleştirdik.

Şimdi çeşitli testler yapabiliriz. IIS'in etkin olduğundan emin olduktan sonra, bir tarayıcıyla http://localhost/polyvbnet/demo2/demo2.asmx adresine erişelim:

Image

Ardından http://localhost/polyvbnet/demo2/demo2.asmx?WSDL adresini girin

Image

Ardından URL http://localhost/polyvbnet/demo2/demo2.asmx?op=getBonjour, burada getBonjour, web hizmetimizin tek yönteminin adıdır:

Image

Yukarıdaki [Appeler] düğmesini kullanıyoruz:

Image

Web hizmetinin getBonjour yöntemine yapılan çağrının sonucunu başarıyla alıyoruz. Artık vs.net olmadan bir web hizmeti oluşturmayı biliyoruz. Bundan sonra, web hizmetinin nasıl oluşturulduğunu bir kenara bırakıp sadece temel dosyalara odaklanacağız.

10.3.3. Sürüm 3

[Bonjour] web hizmetinin önceki iki sürümü iki dosya kullanıyordu:

  • web hizmetinin giriş noktası olan bir .asmx dosyası
  • web hizmetinin kaynak kodu olan bir .vb dosyası

Burada, yalnızca .asmx dosyasıyla yetinebileceğimizi göstereceğiz. demo3.asmx hizmetinin kodu şöyledir:

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

Imports System.Web.Services

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

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

Hizmetin kaynak kodunun artık doğrudan demo3.asmx dosyasının kaynak dosyasında yer aldığını görüyoruz. Şu yönerge:

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

yönergesi artık harici bir derlemede bulunan bir sınıfa değil, aynı kaynak dosyada bulunan bir sınıfa başvurmaktadır. Bunu <IISroot>\polyvbnet\demo3 klasörüne yerleştirelim:

Image

IIS'i çalıştırıp http://localhost/polyvbnet/demo3/demo3.asmx URL'sini isteyelim:

Image

Önceki sürüme kıyasla önemli bir fark görüyoruz: hizmetin VB kodunu derlememiz gerekmedi. IIS, aynı makineye kurulu olan VB.NET derleyicisi aracılığıyla bu derlemeyi kendisi gerçekleştirdi. Ardından sayfayı sundu. Derleme hatası olması durumunda, bu hata IIS tarafından bildirilir:

Image

10.3.4. Sürüm 4

Burada IIS sunucusunun yapılandırmasıyla ilgileniyoruz. Şimdiye kadar web hizmetlerimizi her zaman IIS sunucusunun <IISroot> kök dizin yapısına yerleştirmiştik; burada ise [e:\inetpub\wwwroot]'e yerleştiriyoruz. Burada, web hizmetini istediğimiz herhangi bir yere yerleştirebileceğimizi gösteriyoruz. Bu, IIS'in sanal klasörleri kullanılarak yapılır. Hizmetimizi şu klasöre yerleştirelim:

Image

[D:\data\devel\vbnet\poly\chap9\demo3] klasörü, IIS sunucusunun dizin yapısında bulunmamaktadır. Bunu, IIS adlı sanal bir klasör oluşturarak sunucuya belirtmeliyiz. IIS'i çalıştıralım ve aşağıdaki [Avancé] seçeneğini seçelim:

Image

Karşımıza sanal dizinlerin bir listesi çıkıyor. Bu liste üzerinde fazla durmayacağız. Yukarıdaki [Ajouter] düğmesiyle yeni bir sanal dizin oluşturuyoruz:

Image

[Parcourir] düğmesini kullanarak, web hizmetini içeren fiziksel klasörü belirleriz; burada bu klasör [D:\data\devel\vbnet\poly\chap9\demo3]'tir. Bu klasöre mantıksal (sanal) bir ad veriyoruz: [virdemo3]. Bu, [D:\data\devel\vbnet\poly\chap9\demo3] fiziksel klasörünün içindeki belgelere ağ üzerinden [http://<machine>/virdemo3] URL'si aracılığıyla erişilebileceği anlamına gelir. Yukarıdaki iletişim kutusunda başka ayarlar da bulunmaktadır; bunları olduğu gibi bırakıyoruz. İletişim kutusunu onaylıyoruz. Yeni sanal klasör, IIS'in sanal klasörler listesinde görünür:

Image

Şimdi bir tarayıcı açıp [http://localhost/virdemo3/demo3.asmx] URL'sini giriyoruz. Daha önce gördüğümüzle aynı sonucu alıyoruz:

Image

10.3.5. Sonuç

Bir web hizmeti oluşturmak için çeşitli yöntemler gösterdik. Bundan sonra, hizmeti oluşturmak için 3. yöntemi, hizmetin konumlandırılması için ise 4. yöntemi kullanacağız. Böylece VS.NET'e ihtiyacımız olmayacak. Bununla birlikte, hata ayıklamada sağladığı yardım nedeniyle VS.NET'i kullanmanın yararını da belirtelim. Web uygulamaları geliştirmek için ücretsiz araçlar mevcuttur; bunlardan biri, Microsoft tarafından desteklenen ve URL [http://www.asp.net/webmatrix] adresinde bulabileceğiniz WebMatrix ürünüdür. Bu, önceden herhangi bir yatırım yapmadan web programlamaya başlamak için mükemmel bir araçtır.

10.4. İşlemlere yönelik bir web hizmeti

Beş işlev sunan bir web hizmetini ele alalım:

  1. a+b sonucunu veren ekle(a,b)
  2. çıkarma(a,b) işlevi, a-b sonucunu verir
  3. çarpma(a,b) işlevi, a*b sonucunu verir
  4. böl(a,b) – a/b sonucunu verir
  5. her şeyi yapma(a,b) işlevi, [a+b, a-b, a*b, a/b] dizisini döndürür

Bu hizmetin VB.NET kodu şöyledir:


<%@ 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 

Burada, daha önce verilmiş ancak tekrar hatırlatılması veya tamamlanması gereken bazı açıklamaları tekrarlıyoruz. operations sınıfı, VB.NET sınıfına benzer, ancak dikkat edilmesi gereken birkaç nokta vardır:

  • Yöntemlerin başında, derleyiciye hangi yöntemlerin "yayınlanması" gerektiğini (WebMethod()) ve müşteriye sunulması gerektiğini belirten bir öznitelik bulunur. Bu özniteliğin önünde bulunmayan bir yöntem, uzak istemciler tarafından görülemez. Bu, diğer yöntemler tarafından kullanılan ancak yayınlanmak üzere tasarlanmamış bir iç yöntem olabilir.
  • Sınıf, System.Web.Services ad alanında tanımlanan WebService sınıfından türetilmiştir. Bu miras alma her zaman zorunlu değildir. Özellikle bu örnekte, bu miras alma işlemi yapılmasa da olur.
  • Sınıfın başında, web hizmetine bir ad alanı atamak amacıyla <WebService(Namespace="st.istia.univ-angers.fr")> özniteliği yer alır. Bir sınıf satıcısı, sınıflarına benzersiz bir ad vermek ve böylece aynı adı taşıyan diğer satıcıların sınıflarıyla çakışmaları önlemek amacıyla sınıflarına bir ad alanı atar. Web hizmetleri için de durum aynıdır. Her web hizmeti, benzersiz bir adla tanımlanabilmelidir; burada bu ad st.istia.univ-angers.fr'tir.
  • Bir oluşturucu tanımlamadık. Dolayısıyla, üst sınıfın oluşturucusu örtük olarak kullanılacaktır.

Yukarıdaki kaynak kodu, doğrudan VB.NET derleyicisi için değil, IIS web sunucusu için tasarlanmıştır. Bu kodun sonuna .asmx uzantısı eklenmeli ve web sunucusunun dizin yapısına kaydedilmelidir. Burada dosyayı <IISroot>\polyvbnet\operations klasörüne operations.asmx adıyla kaydediyoruz:

Image

Bu fiziksel klasöre, IIS [operations] adlı sanal klasörü eşliyoruz:

Bir tarayıcıyla hizmete erişelim. İstenen URl dosyası, [http://localhost/operations/operations.asmx]'tir:

Image

operations web hizmetinde tanımlanan her bir yöntem için bir bağlantı içeren bir web belgesi elde ediyoruz. ajouter bağlantısını takip edelim:

Image

Karşımıza çıkan sayfa, ajouter yöntemini, bu yöntemin ihtiyaç duyduğu a ve b argümanlarını sağlayarak test etmemizi öneriyor. ajouter yönteminin tanımını hatırlayalım:

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

Sayfanın, yöntem tanımında kullanılan a ve b argümanlarının adlarını aldığını not edelim. Appeler düğmesini kullandığımızda, tarayıcının ayrı bir penceresinde şu yanıtı alıyoruz:

Image

Yukarıdaki örnekte [Affichage/Source] komutunu kullanırsak şu kodu elde ederiz:

Image

Aynı işlemi [toutfaire] yöntemi için tekrar yapalım:

Image

Şu sayfayı elde ediyoruz:

Image

Yukarıdaki [Appeler] düğmesini kullanalım:

Image

Her durumda, sunucunun yanıtı şu biçimdedir:

<?xml version="1.0" encoding="utf-8"?>
[réponse au format XML]
  • yanıt, XML biçimindedir
  • 1. satır standarttır ve yanıtta her zaman bulunur
  • sonraki satırlar, sonuç türüne (double, ArrayOfDouble), sonuç sayısına ve web hizmetinin ad alanına (burada st.istia.univ-angers.fr) bağlıdır.

Bir web hizmetine sorgu göndermek ve yanıtını almak için çeşitli yöntemler vardır. Hizmetin URL yanıtına geri dönelim:

Image

ve [ajouter] bağlantısını takip edelim. Görüntülenen sayfada, web hizmetinin [ajouter] işlevini sorgulamak için iki yöntem gösterilmektedir:

Image

Image

Image

Bir web hizmetinin işlevlerine erişim sağlayan bu iki yöntem sırasıyla HTTP-POST ve SOAP olarak adlandırılır. Şimdi bunları sırayla inceleyeceğiz.

Not: VS.NET'in ilk sürümlerinde, HTTP-GET adlı üçüncü bir yöntem mevcuttu. Bu belgenin yazıldığı tarih itibarıyla (Mart 2004), bu yöntem artık kullanılamıyor gibi görünüyor. Bu, VS.NET tarafından oluşturulan web hizmetinin GET isteklerini kabul etmediği anlamına gelir. Bu, GET isteklerini kabul eden web hizmetleri yazamayacağımız anlamına gelmez; özellikle VS.NET dışındaki diğer araçlarla veya basitçe elle yazarak bu tür hizmetler oluşturulabilir.

10.5. Bir HTTP-POST istemcisi

Web hizmeti tarafından önerilen yöntemi takip ediyoruz:

Image

Yazılanları açıklayalım. Öncelikle, web istemcisi aşağıdaki HTTP başlıklarını göndermelidir:

POST /operations/operations.asmx/ajouter HTTP/1.1
Web istemcisi, POST protokolü sürüm 1.1'e göre URL /operations/operations.asmx/ajouter adresine bir istek gönderir
HOST: localhost
İsteğin hedef makinesi belirtilir. Burada localhost. Bu başlık, HTTP protokolünün 1.1 sürümüyle zorunlu hale getirilmiştir
Content-Type: application/x-www-form-urlencoded
Burada, HTTP başlıklarından sonra urlencoded biçiminde ek parametreler gönderileceği belirtilir. Bu biçim, bazı karakterleri onaltılık kodlarıyla değiştirir.
Content-length: 7
Bu, HTTP başlıklarından sonra gönderilecek parametre dizesinin karakter sayısıdır.

HTTP başlıklarının ardından bir boş satır gelir; ardından POST’in [Content-Length] karakterlik parametre dizisi, a=XX&b=YY biçiminde yer alır; burada XX ve YY, a ve b parametrelerinin değerlerinin "url kodlanmış" dizeleridir. TCP-IP programlama bölümünde daha önce kullandığımız genel TCP istemcimizle yukarıdakini yeniden oluşturmak için yeterli bilgiye sahibiz:

  • IIS'i başlatıyoruz
  • hizmet, [http://localhost/operations/operations.asmx] URL'sinde erişilebilir
  • genel amaçlı TCP istemcisini DOS penceresinde kullanıyoruz
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]

Öncelikle, sunucudan yanıtı gönderdikten sonra bağlantıyı kapatmasını istemek için [Connection: close] başlığını eklediğimizi belirtelim. Bu, burada gereklidir. Aksi belirtilmezse, sunucu varsayılan olarak bağlantıyı açık tutacaktır. Oysa sunucunun yanıtı, son satırı satır sonu işaretiyle bitmeyen bir dizi metin satırından oluşur. Genel amaçlı TCP istemcimiz, ReadLine yöntemi ile satır sonu işaretiyle biten metin satırlarını okur. Sunucu son satırı gönderdikten sonra bağlantıyı kapatmazsa, istemci gelmeyen bir satır sonu işaretini beklediği için kilitlenir. Sunucu bağlantıyı kapatırsa, istemcinin ReadLine yöntemi sona erer ve istemci kilitlenmez.

HTTP başlıklarının sonunu belirten boş satırı alır almaz, IIS sunucusu ilk yanıtı gönderir:

<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--

Yalnızca HTTP başlıklarından oluşan bu yanıt, müşteriye göndermek istediğini belirttiği 7 karakteri gönderebileceğini bildirir. Yaptığımız şey:

a=2&b=3

Burada, TCP istemcimizin 7 karakterden fazlasını gönderdiğini görmek gerekir, çünkü bunları satır sonu işaretiyle (WriteLine) birlikte göndermektedir. Bu durum sunucu için sorun teşkil etmez; çünkü sunucu alınan karakterlerden yalnızca ilk 7'sini alır ve ardından bağlantı kapatılır (Connection: close). Bağlantı açık kalsaydı, bu fazla karakterler sorun yaratabilirdi; çünkü o zaman bunlar müşterinin bir sonraki komutundan gelen karakterler olarak algılanırdı. Parametreler alındıktan sonra, sunucu yanıtını gönderir:

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

Artık web hizmetimiz için programlanmış bir istemci yazmak için gerekli unsurlara sahibiz. Bu, httpPost2 adlı ve şu şekilde kullanılan bir konsol istemcisi olacaktır:

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>

Web hizmetinden gelen URL değeri iletilerek istemci çağrılır:

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

Ardından, istemci klavyeden girilen komutları okur ve yürütür. Bu komutlar şu formattadır:

fonction a b

burada "fonksiyon", çağrılan web hizmetinin fonksiyonudur (toplama, çıkarma, çarpma, bölme) ve "a" ile "b", bu fonksiyonun üzerinde işlem yapacağı değerlerdir. Örneğin:

ajouter 6 7

Bu noktadan itibaren istemci, web sunucusuna gerekli olan HTTP isteğini gönderir ve bir yanıt alır. Sürecin daha iyi anlaşılması için istemci-sunucu arasındaki iletişim ekran üzerinde gösterilir:

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]

Yukarıda, genel TCP istemcisiyle daha önce karşılaştığımız iletişim akışını tek bir farkla görüyoruz: HTTP Connection: Keep-Alive başlığı, sunucudan bağlantıyı kapatmamasını ister. Böylece bağlantı, istemcinin bir sonraki işlemi için açık kalır ve istemcinin sunucuya yeniden bağlanmasına gerek kalmaz. Ancak bu durum, sunucunun yanıtını okumak için ReadLine() dışında başka bir yöntem kullanmasını gerektirir; zira bu yanıtın, son satırı satır sonu işaretiyle bitmeyen bir dizi satırdan oluştuğu bilinmektedir. Sunucudan gelen yanıtın tamamı alındıktan sonra, istemci bunu analiz ederek istenen işlemin sonucunu bulur ve ekrana yazar:

[résultat=13]

İstemcimizin koduna bir göz atalım:


' ad alanları
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Web

' web hizmeti istemcisi operations
Public Module clientPOST

    Public Sub Main(ByVal args() As String)
        ' sözdizimi
        Const syntaxe As String = "pg URI"
        Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}

        ' argüman sayısı
        If args.Length <> 1 Then
            erreur(syntaxe, 1)
        End If
        ' istenen URI not edilir
        Dim URIstring As String = args(0)

        ' sunucuya bağlanılıyor
        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
            ' sunucuya bağlantı
            uri = New Uri(URIstring)
            client = New TcpClient(uri.Host, uri.Port)
            ' TCP istemcisinin giriş-çıkış akışları oluşturuluyor
            [IN] = New StreamReader(client.GetStream())
            OUT = New StreamWriter(client.GetStream())
            OUT.AutoFlush = True
        Catch ex As Exception
            ' URI hatalı veya başka bir sorun
            erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
        End Try

        ' web hizmetinin işlev sözlüğü oluşturuluyor
        Dim dicoFonctions As New Hashtable
        Dim i As Integer
        For i = 0 To fonctions.Length - 1
            dicoFonctions.Add(fonctions(i), True)
        Next i

        ' kullanıcı istekleri klavyeyle girilir
        ' "fonksiyon a b" biçiminde
        ' "end" komutuyla sonlanıyor
        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

        ' kullanıcıya komut istemi gösterilir
        Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b")

        ' hata yönetimi
        Dim erreurCommande As Boolean
        Try
            ' klavyeden girilen komutların işlenmesi döngüsü
            While True
                ' başlangıçta hata yok
                erreurCommande = False
                ' komut okuma
                commande = Console.In.ReadLine().Trim().ToLower()
                ' bitti mi?
                If commande Is Nothing Or commande = "fin" Then
                    Exit While
                End If
                ' komutun alanlara ayrılması
                champs = Regex.Split(commande, "\s+")
                Try
                    ' üç alan gerekiyor
                    If champs.Length <> 3 Then
                        Throw New Exception
                    End If
                    ' 0 numaralı alan tanınan bir işlev olmalı
                    fonction = champs(0)
                    If Not dicoFonctions.ContainsKey(fonction) Then
                        Throw New Exception
                    End If
                    ' 1. alan geçerli bir sayı olmalı
                    a = champs(1)
                    Double.Parse(a)
                    ' 2. alan geçerli bir sayı olmalıdır
                    b = champs(2)
                    Double.Parse(b)
                Catch
                    ' komut geçersiz
                    Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
                    erreurCommande = True
                End Try
                ' web hizmetine istekte bulunuluyor
                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
        ' istemci-sunucu bağlantısı sona erdi
        Try
            [IN].Close()
            OUT.Close()
            client.Close()
        Catch
        End Try
    End Sub
...........
    ' hataların görüntülenmesi
    Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' hata görüntüleniyor
        System.Console.Error.WriteLine(msg)
        ' hata ile durduruldu
        Environment.Exit(exitCode)
    End Sub
End Module

Burada daha önce birkaç kez karşılaştığımız ve özel bir açıklamaya gerek duymayan unsurlar var. Şimdi, yeniliklerin yer aldığı executeFonction yönteminin koduna bakalım:


    ' 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)
        ' URI URI'sindeki web hizmeti üzerinde (a,b) işlevini yürütür
        ' istemci-sunucu iletişimi, IN ve OUT akışları üzerinden gerçekleşir
        ' fonksiyonun sonucu şu satırda yer almaktadır
        ' <double xmlns="st.istia.univ-angers.fr">double</double>
        ' sunucu tarafından gönderildi

        ' istek dizesinin oluşturulması
        Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
        Dim nbChars As Integer = requête.Length

        ' gönderilecek HTTP başlık tablosunun oluşturulması
        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 başlıkları sunucuya gönderiliyor
        Dim i As Integer
        For i = 0 To entetes.Length - 1
            ' sunucuya gönderme
            OUT.WriteLine(entetes(i))
            ' ekran çıktısı
            Console.Out.WriteLine(("--> " + entetes(i)))
        Next i

        ' Web sunucusundan gelen ilk yanıt okunur: HTTP/1.1 100
        Dim ligne As String = Nothing
        ' okuma akışından bir satır
        ligne = [IN].ReadLine()
        While ligne <> ""
            'yankı
            Console.Out.WriteLine(("<-- " + ligne))
            ' sonraki satır
            ligne = [IN].ReadLine()
        End While
        'son satır yankısı
        Console.Out.WriteLine(("<-- " + ligne))

        ' istek parametrelerini gönder
        OUT.Write(requête)
        ' yankı
        Console.Out.WriteLine(("--> " + requête))

        ' yanıtın boyutunu bulmaya yarayan düzenli ifadenin oluşturulması XML
        ' web sunucusunun yanıt akışında
        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

        ' isteğin gönderilmesinden sonra web sunucusundan gelen ikinci yanıtın okunması
        ' Content-Length satırının değeri kaydedilir
        ligne = [IN].ReadLine()
        While ligne <> ""
            ' ekran çıktısı
            Console.Out.WriteLine(("<-- " + ligne))
            ' Content-Length?
            MatchLength = RegexLength.Match(ligne)
            If MatchLength.Success Then
                longueur = Integer.Parse(MatchLength.Groups(1).Value)
            End If
            ' sonraki satır
            ligne = [IN].ReadLine()
        End While
        ' son satırı ekrana yazdır
        Console.Out.WriteLine("<--")

        ' sonucu bulmaya yarayan düzenli ifadenin oluşturulması
        ' web sunucusunun yanıt akışında
        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

        ' web sunucusunun yanıtının geri kalan kısmı okunur
        Dim chrRéponse(longueur) As Char
        [IN].Read(chrRéponse, 0, longueur)
        Dim strRéponse As String = New [String](chrRéponse)

        ' yanıt, metin satırlarına ayrıştırılır
        Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)

        ' sonucu bulmak için metin satırları taranır
        Dim strRésultat As String = "?"        ' résultat de la fonction
        For i = 0 To lignes.Length - 1
            ' izleme
            Console.Out.WriteLine(("<-- " + lignes(i)))
            ' geçerli satırın şablonla karşılaştırılması
            MatchRésultat = ModèleRésultat.Match(lignes(i))
            ' bulundu mu?
            If MatchRésultat.Success Then
                ' sonuç kaydediliyor
                strRésultat = MatchRésultat.Groups(1).Value
            End If
        Next i
        ' sonuç görüntüleniyor
        Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
    End Sub

Öncelikle, HTTP-POST istemcisi isteğini POST biçiminde gönderir:


        ' sorgu dizesinin oluşturulması
        Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
        Dim nbChars As Integer = requête.Length

        ' gönderilecek başlık tablosu oluşturuluyor 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 başlıklarını sunucuya gönderme
        Dim i As Integer
        For i = 0 To entetes.Length - 1
            ' sunucuya gönderme
            OUT.WriteLine(entetes(i))
            ' ekran çıktısı
            Console.Out.WriteLine(("--> " + entetes(i)))
        Next i

Başlıkta

--> Content-Length: 7

bölümünde, istemci tarafından HTTP başlıklarının arkasında gönderilecek parametrelerin boyutu belirtilmelidir:

--> a=6&b=7

Bunun için aşağıdaki kod kullanılır:


        ' istek dizesinin oluşturulması
        Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
        Dim nbChars As Integer = requête.Length

HttpUtility.UrlEncode yöntemi(string dizesi), chaîne'teki bazı karakterleri %n1n2'e dönüştürür; burada n1n2, dönüştürülen karakterin ASCII kodudur. Bu dönüştürmenin hedef aldığı karakterler, bir POST sorgusunda özel bir anlam taşıyan tüm karakterlerdir (boşluk, = işareti, & işareti, ...). Burada HttpUtility.UrlEncode yöntemi normalde gereksizdir, çünkü a ve b, bu özel karakterlerin hiçbirini içermeyen sayılardır. Bu yöntem burada örnek olarak kullanılmaktadır. Bu yöntem, System.Web ad alanına ihtiyaç duyar. İstemci, HTTP başlıklarını gönderdikten sonra:

--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->

sunucu, HTTP 100 Continue başlığıyla yanıt verir:

<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<--

Kod, yalnızca bu ilk yanıtı okuyup ekrana görüntüler:


        ' Web sunucusundan gelen ilk yanıt okunur HTTP/1.1 100
        Dim ligne As String = Nothing
        ' okuma akışından bir satır
        ligne = [IN].ReadLine()
        While ligne <> ""
            'yankı
            Console.Out.WriteLine(("<-- " + ligne))
            ' sonraki satır
            ligne = [IN].ReadLine()
        End While
        'son satır yankısı
        Console.Out.WriteLine(("<-- " + ligne))

Bu ilk yanıtı okuduktan sonra, müşteri parametrelerini göndermelidir:

--> a=6&b=7

Bunu aşağıdaki kodla yapar:

        ' istek parametrelerinin gönderilmesi
        OUT.Write(requête)
        ' yankı
        Console.Out.WriteLine(("--> " + requête))

Ardından sunucu yanıtını gönderir. Bu yanıt iki bölümden oluşur:

  1. boş bir satırla biten HTTP başlıkları
  2. XML biçimindeki yanıt
<-- 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>

İlk olarak, istemci HTTP başlıklarını okur, burada Content-Length satırını bulur ve XML yanıtının boyutunu (burada 90) alır. Bu değer, bir düzenli ifade kullanılarak elde edilir. Bunu başka bir şekilde ve muhtemelen daha verimli bir şekilde de yapabilirdik.


        ' yanıtın boyutunu bulmaya yarayan düzenli ifadenin oluşturulması XML
        ' web sunucusunun yanıt akışında
        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

        ' isteğin gönderilmesinden sonra web sunucusundan gelen ikinci yanıtın okunması
        ' Content-Length satırının değeri kaydedilir
        ligne = [IN].ReadLine()
        While ligne <> ""
            ' ekran çıktısı
            Console.Out.WriteLine(("<-- " + ligne))
            ' Content-Length?
            MatchLength = RegexLength.Match(ligne)
            If MatchLength.Success Then
                longueur = Integer.Parse(MatchLength.Groups(1).Value)
            End If
            ' sonraki satır
            ligne = [IN].ReadLine()
        End While
        ' son satırı ekrana yazdır
        Console.Out.WriteLine("<--")

XML yanıtının uzunluğu N elde edildiğinde, sunucunun yanıtındaki IN akışından N karakter okumak yeterlidir. Bu N karakterlik dize, ekran izleme amacıyla metin satırlarına ayrıştırılır. Bu satırlar arasında sonuç satırı aranır:

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

yine bir düzenli ifade kullanılarak aranır. Sonuç bulunduktan sonra ekrana görüntülenir.

[résultat=13]

İstemci kodunun sonu şu şekildedir:


        ' sonucu bulmaya yarayan düzenli ifadenin oluşturulması
        ' web sunucusunun yanıt akışında
        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

        ' web sunucusunun yanıtının geri kalan kısmı okunur
        Dim chrRéponse(longueur) As Char
        [IN].Read(chrRéponse, 0, longueur)
        Dim strRéponse As String = New [String](chrRéponse)

        ' yanıt, metin satırlarına ayrıştırılır
        Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)

        ' sonucu bulmak için metin satırları taranır
        Dim strRésultat As String = "?"        ' résultat de la fonction
        For i = 0 To lignes.Length - 1
            ' izleme
            Console.Out.WriteLine(("<-- " + lignes(i)))
            ' geçerli satır şablonla karşılaştırılır
            MatchRésultat = ModèleRésultat.Match(lignes(i))
            ' bulundu mu?
            If MatchRésultat.Success Then
                ' sonuç kaydediliyor
                strRésultat = MatchRésultat.Groups(1).Value
            End If
        Next i
        ' sonuç görüntüleniyor
        Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
    End Sub

10.6. Bir SOAP istemcisi

Burada, SOAP (Basit Nesne Erişim Protokolü) türünde bir istemci-sunucu diyaloğu kullanacak ikinci bir istemciyi inceliyoruz. ajouter işlevi için bir diyalog örneği sunulmaktadır:

Image

Image

İstemcinin isteği, POST isteğidir. Dolayısıyla, önceki istemcinin bazı mekanizmalarını burada da göreceğiz. Temel fark şudur: HTTP-POST istemcisi, a ve b parametrelerini

    a=A&b=B

şeklinde gönderirken, SOAP istemcisi bunları daha karmaşık bir XML biçiminde gönderiyor:

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>

Karşılığında, daha önce görülen yanıtlardan daha karmaşık olan XML yanıtını alır:

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>

İstek ve yanıt daha karmaşık olsa da, bu mekanizma HTTP müşterisi için olduğu gibi aynı mekanizmadır. SOAP istemcisinin kod yazımı, HTTP-POST istemcisininkine benzer şekilde yazılabilir. İşte bir yürütme örneği:

dos>clientsoap1 http://localhost/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b

ajouter 3 4
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>3</a>
<b>4</b>
</ajouter>
</soap:Body>
</soap:Envelope>
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:33 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 345
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>7</ajouterResult></ajouterResponse
></soap:Body></soap:Envelope>
[résultat=7]

Yalnızca executeFonction yöntemi değişir. SOAP istemcisi, isteğinin HTTP başlıklarını gönderir. Bunlar, HTTP-POST'inkilerden sadece biraz daha karmaşıktır:

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"
-->

Bunları oluşturan kod:


    ' 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)
        ' URI URI'sindeki web hizmeti üzerinde (a,b) işlevini yürütür
        ' istemci-sunucu iletişimi, IN ve OUT akışları üzerinden gerçekleşir
        ' fonksiyonun sonucu şu satırda yer almaktadır
        ' <double xmlns="st.istia.univ-angers.fr">double</double>
        ' sunucu tarafından gönderildi
        ' istek dizesinin oluşturulması 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

        ' gönderilecek başlık tablosunun oluşturulması 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 başlıkları sunucuya gönderiliyor
        Dim i As Integer
        For i = 0 To entetes.Length - 1
            ' sunucuya gönderme
            OUT.WriteLine(entetes(i))
            ' ekran çıktısı
            Console.Out.WriteLine(("--> " + entetes(i)))
        Next i

Bu isteği aldığında, sunucu ilk yanıtını gönderir ve istemci bunu görüntüler:

<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--

Bu ilk cevabın okuma kodu şöyledir:


        ' Web sunucusundan gelen ilk yanıt okunur: HTTP/1.1 100
        Dim ligne As String = Nothing
        ' okuma akışından bir satır
        ligne = [IN].ReadLine()
        While ligne <> ""
            'ekran çıktısı
            Console.Out.WriteLine(("<-- " + ligne))
            ' sonraki satır
            ligne = [IN].ReadLine()
        End While        'while
        'son satırın yankısı
        Console.Out.WriteLine(("<-- " + ligne))

Müşteri şimdi parametrelerini XML biçiminde, SOAP adı verilen bir zarf içinde gönderecek:

--> <?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>

Kod:

1
2
3
4
        ' istek parametrelerinin gönderilmesi
        OUT.Write(requêteSOAP)
        ' yankı
        Console.Out.WriteLine(("--> " + requêteSOAP))

Sunucu daha sonra nihai yanıtını gönderecektir:

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

Müşteri, alınan HTTP başlıklarını ekranda görüntülerken, Content-Length satırını arar:


        ' yanıtın boyutunu bulmaya yarayan düzenli ifadenin oluşturulması XML
        ' web sunucusunun yanıt akışında
        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

        ' isteğin gönderilmesinden sonra web sunucusundan gelen ikinci yanıtın okunması
        ' Content-Length satırının değeri kaydedilir
        ligne = [IN].ReadLine()
        While ligne <> ""
            ' ekran çıktısı
            Console.Out.WriteLine(("<-- " + ligne))
            ' Content-Length?
            MatchLength = RegexLength.Match(ligne)
            If MatchLength.Success Then
                longueur = Integer.Parse(MatchLength.Groups(1).Value)
            End If
            ' sonraki satır
            ligne = [IN].ReadLine()
        End While        'while
        ' son satırı ekrana yazdır
        Console.Out.WriteLine("<--")

XML yanıtının boyutu N öğrenildikten sonra, istemci sunucunun yanıt akışından N karakter okur, alınan dizeyi metin satırlarına ayırarak ekrana görüntüler ve sonuçtaki XML etiketini arar: <ajouterResult>7</ajouterResult> ve bunu görüntüler:


        ' sonucu bulmaya yarayan düzenli ifadenin oluşturulması
        ' web sunucusunun yanıt akışında
        Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
        Dim ModèleRésultat As New Regex(modèle)
        Dim MatchRésultat As Match = Nothing

        ' web sunucusunun yanıtının geri kalan kısmı okunur
        Dim chrRéponse(longueur) As Char
        [IN].Read(chrRéponse, 0, longueur)
        Dim strRéponse As String = New [String](chrRéponse)

        ' yanıt, metin satırlarına ayrıştırılır
        Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)

        ' sonucu bulmak için metin satırları taranır
        Dim strRésultat As String = "?"        ' résultat de la fonction
        For i = 0 To lignes.Length - 1
            ' izleme
            Console.Out.WriteLine(("<-- " + lignes(i)))
            ' geçerli satırın şablonla karşılaştırılması
            MatchRésultat = ModèleRésultat.Match(lignes(i))
            ' bulundu mu?
            If MatchRésultat.Success Then
                ' sonuç kaydediliyor
                strRésultat = MatchRésultat.Groups(1).Value
            End If
            'bir sonraki satır
        Next i
        ' sonuç görüntüleniyor
        Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
    End Sub

10.7. İstemci-sunucu iletişiminin kapsüllenmesi

operations web hizmetimizin çeşitli uygulamalar tarafından kullanıldığını varsayalım. Bu uygulamalara, istemci uygulaması ile web hizmeti arasında bir arayüz görevi gören ve çoğu geliştirici için karmaşık olan ağ iletişiminin büyük bir kısmını gizleyen bir sınıf sunmak faydalı olacaktır. Böylece aşağıdaki şemaya sahip oluruz:

İstemci uygulaması, web hizmetine isteklerini iletmek için istemci-sunucu arayüzüne başvurur. Bu arayüz, sunucuyla gerekli tüm ağ iletişimini gerçekleştirir ve elde edilen sonucu istemci uygulamasına geri döndürür. Böylece istemci uygulaması, sunucuyla olan iletişimi artık kendisinin halletmesi gerekmez ve bu da uygulamanın yazılmasını büyük ölçüde kolaylaştırır.

10.7.1. Kapsülleme sınıfı

Önceki paragraflarda gördüklerimizden sonra, artık istemci ile sunucu arasındaki ağ iletişimini iyi biliyoruz. Hatta üç yöntem gördük. SOAP yöntemini kapsüllemeyi seçiyoruz. Sınıf şu şekildedir:


' ad alanları
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports System.Web
Imports Microsoft.VisualBasic

' Web hizmetleri operasyonlarından clientSOAP
Public Class clientSOAP

    ' örnek değişkenleri
    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
    ' fonksiyon sözlüğü
    Private dicoFonctions As New Hashtable
    ' fonksiyon listesi
    Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
    ' ayrıntılı çıktı
    Private verbose As Boolean = False    ' à vrai, affiche à l'écran les échanges client-serveur

    ' oluşturucu
    Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)

        ' ayrıntılı çıktı
        Me.verbose = verbose

        ' sunucuya bağlanma
        uri = New Uri(uriString)
        client = New TcpClient(uri.Host, uri.Port)

        ' TCP istemcisinin giriş-çıkış akışları oluşturulur
        [IN] = New StreamReader(client.GetStream())
        OUT = New StreamWriter(client.GetStream())
        OUT.AutoFlush = True

        ' web hizmetinin işlev sözlüğünün oluşturulması
        Dim i As Integer
        For i = 0 To fonctions.Length - 1
            dicoFonctions.Add(fonctions(i), True)
        Next i
    End Sub

    ' sunucu bağlantısının kapatılması
    Public Sub Close()
        ' istemci-sunucu bağlantısının sonlandırılması
        [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
        ' URI URI'sindeki web hizmetinde (a,b) işlevini yürütür
        ' istemci-sunucu iletişimi, IN ve OUT akışları üzerinden gerçekleşir
        ' fonksiyonun sonucu şu satırda yer almaktadır
        ' <double xmlns="st.istia.univ-angers.fr">double</double>
        ' sunucu tarafından gönderildi

        ' fonksiyon geçerli mi?
        fonction = fonction.Trim().ToLower()
        If Not dicoFonctions.ContainsKey(fonction) Then
            Return "[fonction [" + fonction + "] indisponible : (ajouter, soustraire,multiplier,diviser)]"
        End If

        ' a ve b argümanları geçerli mi?
        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

        ' sıfıra bölme var mı?
        If fonction = "diviser" And doubleB = 0 Then
            Return "[division par zéro]"
        End If

        ' istek dizesinin oluşturulması 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

        ' gönderilecek başlık tablosunun oluşturulması 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 başlıklarını sunucuya gönderiyoruz
        Dim i As Integer
        For i = 0 To entetes.Length - 1
            ' sunucuya gönderme
            OUT.WriteLine(entetes(i))
            ' ekran çıktısı
            If verbose Then
                Console.Out.WriteLine(("--> " + entetes(i)))
            End If
        Next i

        ' Web sunucusundan gelen ilk yanıt okunur: HTTP/1.1 100
        Dim ligne As String = Nothing
        ' okuma akışından bir satır
        ligne = [IN].ReadLine()
        While ligne <> ""
            'yankı
            If verbose Then
                Console.Out.WriteLine(("<-- " + ligne))
            End If
            ' sonraki satır
            ligne = [IN].ReadLine()
        End While
        'son satır yankısı
        If verbose Then
            Console.Out.WriteLine(("<-- " + ligne))
        End If
        ' istek parametrelerinin gönderilmesi
        OUT.Write(requêteSOAP)
        ' yankı
        If verbose Then
            Console.Out.WriteLine(("--> " + requêteSOAP))
        End If

        ' yanıtın boyutunu bulmaya yarayan düzenli ifadenin oluşturulması XML
        ' web sunucusunun yanıt akışında
        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

        ' isteğin gönderilmesinden sonra web sunucusundan gelen ikinci yanıtın okunması
        ' Content-Length satırının değeri kaydedilir
        ligne = [IN].ReadLine()
        While ligne <> ""
            ' ekran çıktısı
            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
            ' sonraki satır
            ligne = [IN].ReadLine()
        End While

        ' son satırı ekrana yazdır
        If verbose Then
            Console.Out.WriteLine("<--")
        End If

        ' sonucu bulmaya yarayan düzenli ifadenin oluşturulması
        ' web sunucusunun yanıt akışında
        Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
        Dim ModèleRésultat As New Regex(modèle)
        Dim MatchRésultat As Match = Nothing

        ' web sunucusunun yanıtının geri kalan kısmı okunur
        Dim chrRéponse(longueur) As Char
        [IN].Read(chrRéponse, 0, longueur)
        Dim strRéponse As String = New [String](chrRéponse)

        ' yanıt, metin satırlarına ayrıştırılır
        Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)

        ' sonucu bulmak için metin satırları taranır
        Dim strRésultat As String = "?"        ' résultat de la fonction
        For i = 0 To lignes.Length - 1
            ' takip
            If verbose Then
                Console.Out.WriteLine(("<-- " + lignes(i)))
            End If            ' comparaison ligne courante au modèle
            MatchRésultat = ModèleRésultat.Match(lignes(i))
            ' bulundumu?
            If MatchRésultat.Success Then
                ' sonuç kaydedilir
                strRésultat = MatchRésultat.Groups(1).Value
            End If
        Next i

        ' sonuç geri gönderilir
        Return strRésultat
    End Function
End Class

Daha önce gördüklerimize kıyasla yeni bir şey görmüyoruz. Sadece incelediğimiz SOAP istemci kodunu aldık ve bir sınıf haline getirmek için biraz yeniden düzenledik. Bu sınıfın bir oluşturucusu ve iki yöntemi vardır:


    ' oluşturucu
    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


    ' sunucu bağlantısı kapatılıyor
    Public Sub Close()

ve şu özelliklere sahiptir:


     ' örnek değişkenleri
    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
     ' fonksiyon sözlüğü
    Private dicoFonctions As New Hashtable
     ' fonksiyon listesi
    Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
     ' ayrıntılı çıktı
    Private verbose As Boolean = False    ' à vrai, affiche à l'écran les échanges client-serveur

Oluşturucuya iki parametre aktarılır:

  1. bağlanması gereken web hizmetinin URI değeri
  2. bir boole değeri olan verbose; bu değer, ağ iletişimlerinin ekranda görüntülenmesini talep eder; aksi takdirde görüntülenmez.

Oluşturma işlemi sırasında, ağ okuma akışı IN, ağ yazma akışı OUT ve hizmet tarafından yönetilen işlevler sözlüğü oluşturulur. Nesne oluşturulduktan sonra, istemci-sunucu bağlantısı açılır ve IN ile OUT akışları kullanılabilir hale gelir.

Close yöntemi, sunucu ile olan bağlantıyı kapatmaya yarar.

ExecuteFonction yöntemi, incelediğimiz SOAP istemcisi için yazdığımız yöntemle, birkaç küçük ayrıntı dışında aynıdır:

  1. Daha önce yönteme parametre olarak geçirilen uri, IN ve OUT parametrelerinin artık geçirilmesine gerek yoktur, çünkü bunlar artık örneğin tüm yöntemleri tarafından erişilebilen örnek öznitelikleridir
  2. Daha önce void türünde bir değer döndüren ve fonksiyonun sonucunu ekrana yazdıran ExecuteFonction yöntemi, artık bu sonucu döndürüyor ve dolayısıyla string türünde bir değer veriyor.

Genellikle bir istemci, clientSOAP sınıfını şu şekilde kullanır:

  1. web hizmeti ile bağlantıyı kuracak bir clientSOAP nesnesi oluşturulur
  2. executeFonction yönteminin tekrar tekrar kullanılması
  3. Close yöntemi ile Web hizmeti bağlantısının kapatılması.

İlk istemciyi inceleyelim.

10.7.2. Konsol istemcisi

Burada, clientSOAP sınıfı henüz mevcut değilken incelediğimiz SOAP istemcisini ele alıyoruz ve artık bu sınıfı kullanacak şekilde yeniden düzenliyoruz:


' ad alanları
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic

Public Module testClientSoap

    ' web hizmeti operations'tan URI'i ister
    ' klavyeden girilen komutları etkileşimli olarak yürütür
    Public Sub Main(ByVal args() As String)
        ' sözdizimi
        Const syntaxe As String = "pg URI [verbose]"

        ' argüman sayısı
        If args.Length <> 1 And args.Length <> 2 Then
            erreur(syntaxe, 1)
        End If
        ' ayrıntılı çıktı?
        Dim verbose As Boolean = False
        If args.Length = 2 Then
            verbose = args(1).ToLower() = "verbose"
        End If
        ' web hizmetine bağlanılır 
        Dim client As clientSOAP = Nothing
        Try
            client = New clientSOAP(args(0), verbose)
        Catch ex As Exception
            ' bağlantı hatası
            erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
        End Try

        ' kullanıcı istekleri klavyeyle girilir
        ' a b işlevi biçiminde - 'fin' komutuyla sona erer
        Dim commande As String = Nothing        ' commande tapée au clavier
        Dim champs As String() = Nothing        ' champs d'une ligne de commande

        ' kullanıcıya komut istemi
        Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b" + ControlChars.Lf)

        ' hata yönetimi
        Dim erreurCommande As Boolean
        Try
            ' klavyeden girilen komutların işleme döngüsü
            While True
                ' başlangıçta hata yok
                erreurCommande = False
                ' komut okuma
                commande = Console.In.ReadLine().Trim().ToLower()
                ' bitti mi?
                If commande Is Nothing Or commande = "fin" Then
                    Exit While
                End If
                ' komutun alanlara ayrılması
                champs = Regex.Split(commande, "\s+")
                ' üç alan gerekiyor
                If champs.Length <> 3 Then
                    Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
                    ' hata kaydedildi
                    erreurCommande = True
                End If
                ' web hizmetine istek gönderiliyor
                If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
                ' sonraki istek
            End While
        Catch e As Exception
            Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
        End Try
        ' istemci-sunucu bağlantısı sona erdi
        Try
            client.Close()
        Catch
        End Try
    End Sub

    ' hataların görüntülenmesi
    Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' hatanın görüntülenmesi
        System.Console.Error.WriteLine(msg)
        ' hata ile durdurma
        Environment.Exit(exitCode)
    End Sub
End Module

İstemci artık önemli ölçüde daha basit hale geldi ve içinde hiçbir ağ iletişimi bulunmuyor. İstemci iki parametre kabul ediyor:

  1. URI web hizmetinin operations
  2. isteğe bağlı anahtar kelime verbose. Bu anahtar kelime varsa, ağ iletişimi ekranda görüntülenecektir.

Bu iki parametre, web hizmeti ile iletişimi sağlayacak bir clientSOAP nesnesi oluşturmak için kullanılır.


         ' web hizmetine bağlanılıyor 
        Dim client As clientSOAP = Nothing
        Try
            client = New clientSOAP(args(0), verbose)
        Catch ex As Exception
             ' bağlantı hatası
            erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
        End Try

Web hizmeti ile bağlantı kurulduktan sonra, istemci isteklerini gönderebilir. Bu istekler klavyeyle girilir, analiz edilir ve ardından clientSOAP nesnesinin executeFonction yöntemine çağrı yapılarak sunucuya gönderilir.


                 ' web hizmetine istek gönderiliyor
                If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))

clientSOAP sınıfı bir "derleme" içinde derlenir:

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 istemci uygulaması daha sonra şu şekilde derlenir:

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

İşte ayrıntılı olmayan bir yürütme örneği:

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

"Ayrıntılı" bir çalıştırma talep ederek ağ trafiğini takip edebiliriz:

dos>testClientSOAP http://localhost/operations/operations.asmx ayrıntılı
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

Şimdi bir grafik istemcisi oluşturalım.

10.7.3. Bir Windows grafik istemcisi

Şimdi, yine clientSOAP sınıfını kullanacak bir grafik istemciyle web hizmetimizi sorgulayacağız. Grafik arayüzü şu şekilde olacaktır:

Kontroller şunlardır:

No
tür
ad
rol
1
TextBox
txtURI
web operations hizmetinin URI'i
2
Düğme
btnOuvrir
Web hizmetiyle bağlantıyı açar
3
Düğme
btnFermer
Web hizmeti ile bağlantıyı kapatır
4
ComboBox
cmbFonctions
işlemlerin listesi (toplama, çıkarma, çarpma, bölme)
5
TextBox
txtA
işlevlerin argümanları
6
TextBox
txtB
fonksiyonların b argümanı
7
TextBox
txtRésultat
(a, b) fonksiyonunun sonucu
8
Düğme
btnCalculer
(a,b) fonksiyonunun hesaplanmasını başlatır
9
TextBox
txtErreur
bağlantının durum mesajını görüntüler

Bazı çalışma kısıtlamaları vardır:

  • btnOuvrir düğmesi, yalnızca txtURI alanı boş değilse ve halihazırda açık bir bağlantı yoksa etkin hale gelir
  • btnFermer düğmesi, web hizmeti ile bir bağlantı açıldığında etkin hale gelir
  • btnCalculer düğmesi, yalnızca bir bağlantı açık olduğunda ve txtA ile txtB alanları boş olmadığında etkindir
  • txtRésultat ve txtErreur alanlarının ReadOnly özniteliği "true" değerindeyse

İstemci, öncelikle [Ouvrir] düğmesini kullanarak web hizmeti ile bağlantı kurar:

Image

Ardından kullanıcı bir işlev ve a ile b için değerler seçebilir:

Image

Image

Image

Image

Image

Uygulamanın kodu aşağıdadır. Burada önemi olmayan form kodunu çıkardık.


'ad alanları
Imports System
Imports System.Windows.Forms

' form sınıfı
Public Class FormClientSOAP
    Inherits System.Windows.Forms.Form

    ' örnek öznitelikleri
    Dim client As clientSOAP    ' client SOAP du service web operations

#Bölge "Windows Form Tasarımcısı tarafından oluşturulan kod"

    Public Sub New()
        MyBase.New()
        'Bu çağrı, Windows Form Tasarımcısı tarafından gereklidir.
        InitializeComponent()
        ' diğer başlatma işlemleri
        myInit()
    End Sub

    'Formun Dispose yönteminin, bileşen listesini temizlemek için değiştirilmiş hali.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
....
    End Sub

...

    Private Sub InitializeComponent()
....
    End Sub

#Bölge Sonu


    Private Sub myInit()
        ' form başlatma
        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
        ' giriş alanının içeriği değişti - aç düğmesinin durumu ayarlanıyor
        btnOuvrir.Enabled = txtURI.Text.Trim <> ""
    End Sub

    Private Sub btnOuvrir_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnOuvrir.Click
        ' web hizmeti ile bağlantı açma isteği
        Try
            ' [clientSOAP] türünde bir nesne oluşturuldu
            client = New clientSOAP(txtURI.Text.Trim, False)
            ' düğmelerin durumları
            btnOuvrir.Enabled = False
            btnFermer.Enabled = True
            ' URI artık değiştirilemez
            txtURI.ReadOnly = True
            ' müşteri durumu
            txtErreur.Text = "Liaison au service web ouverte"
        Catch ex As Exception
            ' bir hata oluştu - hata mesajı görüntüleniyor
            txtErreur.Text = ex.Message
        End Try
    End Sub

    Private Sub btnFermer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnFermer.Click
        ' web hizmeti bağlantısı kapatılıyor
        client.Close()
        ' düğme durumları
        btnOuvrir.Enabled = True
        btnFermer.Enabled = False
        ' URI
        txtURI.ReadOnly = False
        ' müşteri durumu
        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) fonksiyonunun hesaplanması
        ' önceki sonucu silme
        txtRésultat.Text = ""
        Try
            txtRésultat.Text = client.executeFonction(cmbFonctions.Text, txtA.Text.Trim, txtB.Text.Trim)
        Catch ex As Exception
            ' ağ hatası oluştu
            txtErreur.Text = ex.Message
            ' bağlantı kapatılıyor
            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 değerinin değiştirilmesi
        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 değerinde değişiklik
        txtA_TextChanged(Nothing, Nothing)
    End Sub

    ' ana yöntem
    Public Shared Sub main()
        Application.Run(New FormClientSOAP)
    End Sub
End Class

Burada da clientSOAP sınıfı, uygulamanın tüm ağ ile ilgili kısımlarını gizlemektedir. Uygulama şu şekilde oluşturulmuştur:

  • clientSOAP sınıfını içeren clientSOAP.dll derlemesi proje klasörüne yerleştirilmiştir
  • clientsoapgui.vb grafik arayüzü, VS.NET ile oluşturuldu ve ardından bir DOS penceresinde derlendi:
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

Ardından grafik kullanıcı arayüzü şu komutla başlatıldı:

dos>clientsoapgui

10.8. Bir proxy istemcisi

Az önce yapılanları hatırlayalım. Aşağıdaki şemaya göre, bir istemci ile bir web hizmeti arasındaki ağ iletişimini kapsülleyen bir ara sınıf oluşturduk:

.NET platformu bu mantığı daha da ileriye götürür. Ulaşılacak web hizmeti belirlendiğinde, web hizmetinin işlevlerine erişmek için aracı olarak kullanacağımız ve tüm ağ kısmını gizleyecek sınıfı otomatik olarak oluşturabiliriz. Bu sınıfa, oluşturulduğu web hizmeti için bir proxy denir.

Bir web hizmetinin proxy sınıfı nasıl oluşturulur? Bir web hizmeti her zaman XML formatında bir açıklama dosyasıyla birlikte gelir. operations web hizmetimizin URI dosyası http://localhost/operations/operations.asmx ise, açıklama dosyası, aşağıdaki ekran görüntüsünde gösterildiği gibi http://localhost/operations/operations.asmx?wsdl adresinde URL olarak bulunur:

Image

Burada, web hizmetinin tüm işlevlerini, her birinin parametre türü ve sayısı ile sonuç türünü ayrıntılı olarak açıklayan bir XML dosyası bulunmaktadır. Bu dosyaya, WSDL (Web Hizmetleri Tanımlama Dili) dilini kullandığı için hizmetin WSDL dosyası denir. Bu dosyadan yola çıkarak, wsdl aracı kullanılarak bir proxy sınıfı oluşturulabilir:

dos>wsdl http://localhost/operations/operations.asmx?wsdl /language=vb
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.

Écriture du fichier 'D:\data\devel\vbnet\poly\chap9\clientproxy\operations.vb'.

dos>dir
04/03/2004  17:17                6 663 operations.vb

wsdl aracı, web hizmetini uygulayan sınıfın adını taşıyan bir VB.NET kaynak dosyası oluşturur (/language=vb seçeneği ile); bu örnekte operations. Oluşturulan kodun bir kısmını inceleyelim:

'------------------------------------------------------------------------------
' <otomatik olarak oluşturulmuş>
'      Bu kod bir araç tarafından oluşturulmuştur.
'      Çalışma Zamanı Sürümü: 1.1.4322.573
'
'      Bu dosyada yapılan değişiklikler hatalı çalışmaya neden olabilir ve 
'      kod yeniden oluşturulursa kaybolacaktır.
' </otomatik olarak oluşturulmuş>
'------------------------------------------------------------------------------

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

'
'Bu kaynak kodu, wsdl tarafından otomatik olarak tégénéré oluşturulmuştur, Sürüm=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

     '<açıklamalar/>
    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

     '<açıklamalar/>
    Public Function Endajouter(ByVal asyncResult As System.IAsyncResult) As Double
        Dim results() As Object = Me.EndInvoke(asyncResult)
        Return CType(results(0),Double)
    End Function
....

Bu kod ilk bakışta biraz karmaşık görünebilir. Ancak onu kullanabilmek için ayrıntılarını anlamamız gerekmez. Öncelikle sınıf tanımına bir göz atalım:

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

Sınıf, oluşturulduğu web hizmetinin adı olan operations adını taşır. SoapHttpClientProtocol sınıfından türetilmiştir:

Image

Proxy sınıfımızın tek bir oluşturucusu vardır:

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

Oluşturucu, url özniteliğine, proxy ile ilişkili web hizmetinin URL değerini atar. Yukarıdaki operations sınıfı, url özniteliğini kendisi tanımlamaz. Bu öznitelik, proxy'nin türetildiği sınıf olan System.Web.Services.Protocols.SoapHttpClientProtocol'ten miras alınmıştır. Şimdi ajouter yöntemiyle ilgili bilgileri inceleyelim:

    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

Görüldüğü üzere, bu yöntem operations web hizmetindekiyle aynı imzaya sahiptir ve orada şu şekilde tanımlanmıştır:

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

Bu sınıfın web hizmeti ile nasıl iletişim kurduğu burada gösterilmemiştir. Bu iletişim, tamamen üst sınıf olan System.Web.Services.Protocols.SoapHttpClientProtocol tarafından desteklenmektedir. Proxy'de, onu diğer proxy'lerden ayıran tek şey şudur:

  • ilişkili web hizmetinin URL'i
  • ilişkili hizmetin yöntem tanımları.

operations web hizmetinin yöntemlerini kullanmak için bir istemcinin, yalnızca daha önce oluşturulan operations proxy sınıfına ihtiyacı vardır. Bu sınıfı assembly dosyasında derleyelim:

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

Şimdi bir konsol istemcisi yazalım. Bu istemci parametre olmadan çağrılır ve klavyeden girilen istekleri yürütür:

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

Müşteri kodu şöyledir:


' ad alanları
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic

Public Module testClientProxy

    ' klavyeden girilen komutları etkileşimli olarak yürütür
    ' ve bunları operations web hizmetine gönderir
    Public Sub Main()
        ' artık argüman yok - web hizmetinin URL değeri proxy'de sabit olarak kodlanmıştır

        ' web hizmetinin işlevlerinden oluşan bir sözlük oluşturulur
        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

        ' bir operations proxy nesnesi oluşturuluyor 
        Dim myOperations As operations = Nothing
        Try
            myOperations = New operations
        Catch ex As Exception
            ' bağlantı hatası
            erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
        End Try

        ' kullanıcının istekleri klavyeyle giriliyor
        ' a b biçiminde işlevler - "end" komutuyla sonlandırılır
        Dim commande As String = Nothing        ' commande tapée au clavier
        Dim champs As String() = Nothing        ' champs d'une ligne de commande

        ' kullanıcıya komut istemi
        Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b" + ControlChars.Lf)

        ' bazı yerel veriler
        Dim erreurCommande As Boolean
        Dim fonction As String
        Dim a, b As Double
        ' klavyeden girilen komutların işlendiği döngü
        While True
            ' başlangıçta hata yok
            erreurCommande = False
            ' komut okuma
            commande = Console.In.ReadLine().Trim().ToLower()
            ' bitti mi?
            If commande Is Nothing Or commande = "fin" Then
                Exit While
            End If
            ' komutun alanlara ayrılması
            champs = Regex.Split(commande, "\s+")
            Try
                ' üç alan gereklidir
                If champs.Length <> 3 Then
                    Throw New Exception
                End If
                ' 0 numaralı alan tanınan bir işlev olmalıdır
                fonction = champs(0)
                If Not dicoFonctions.ContainsKey(fonction) Then
                    Throw New Exception
                End If
                ' 1. alan geçerli bir sayı olmalıdır
                a = Double.Parse(champs(1))
                ' 2. alan geçerli bir sayı olmalıdır
                b = Double.Parse(champs(2))
            Catch
                ' komut geçersiz
                Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
                erreurCommande = True
            End Try
            ' web hizmetine istekte bulunuluyor
            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

    ' hataların görüntülenmesi
    Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
        ' hata görüntüleniyor
        System.Console.Error.WriteLine(msg)
        ' hata nedeniyle durduruldu
        Environment.Exit(exitCode)
    End Sub
End Module

Yalnızca proxy sınıfının kullanımına özgü kodu inceliyoruz. Öncelikle bir operations proxy nesnesi oluşturulur:


        ' bir proxy operations nesnesi oluşturuluyor 
        Dim myOperations As operations = Nothing
        Try
            myOperations = New operations
        Catch ex As Exception
            ' bağlantı hatası
            erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
        End Try

a ve b fonksiyon satırları klavyeden girilir. Bu bilgilere dayanarak, proxy'nin uygun yöntemleri çağrılır:


            ' web hizmetine istek gönderiliyor
            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

Burada, dört işlemi gerçekleştiren toutfaire işlemini ilk kez işliyoruz. Bu işlem, şimdiye kadar göz ardı edilmişti çünkü, tek bir sonuç veren diğer işlevlerin basit XML yanıtlarından daha yönetilmesi zor olan XML zarfına kapsüllenmiş bir sayı dizisi gönderiyordu. Görüldüğü gibi, burada proxy sınıfı kullanıldığında, toutfaire yöntemini kullanmak diğer yöntemleri kullanmaktan daha karmaşık değildir. Uygulama, bir DOS penceresinde şu şekilde derlenmiştir:

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. Bir Web Hizmetini Yapılandırma

Bir web hizmetinin düzgün bir şekilde başlatılabilmesi için yapılandırma bilgilerine ihtiyaç duyabilir. IIS sunucusunda bu bilgiler, web hizmetiyle aynı klasörde bulunan web.config adlı bir dosyaya yerleştirilebilir. Başlatılması için iki bilgiye ihtiyaç duyan bir web hizmeti oluşturmak istediğimizi varsayalım: bir isim ve bir yaş. Bu iki bilgi, web.config dosyasına aşağıdaki biçimde yerleştirilebilir:

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
  <appSettings>
    <add key="nom" value="tintin"/>
    <add key="age" value="27"/>
  </appSettings>   
</configuration>

Başlatma parametreleri XML zarfına yerleştirilir:

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

P adlı ve V değerine sahip bir başlatma parametresi şu satırla tanımlanacaktır:


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

Web hizmeti bu bilgileri nasıl alır? IIS bir web hizmetini yüklediğinde, aynı klasörde web.config adlı bir dosya olup olmadığına bakar. Varsa, bu dosyayı okur. P parametresinin V değeri şu komutla elde edilir:

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

burada ConfigurationSettings, System.Configuration ad alanında bulunan bir sınıftır.

Bu tekniği aşağıdaki web hizmetinde deneyelim:


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

Imports System.Web.Services
imports System.Configuration

<WebService([Namespace] := "st.istia.univ-angers.fr")> _
Public Class personne
   Inherits WebService

   ' özellikler
   Private nom As String
   Private age As Integer

   ' oluşturucu
   Public Sub New()
      ' özelliklerin başlatılması
      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 web hizmeti, nom ve age adlı iki özniteliğe sahiptir; bu öznitelikler, web.config yapılandırma dosyasından okunan değerler kullanılarak, parametresiz yapıcıda başlatılır.HTMLP002667ZQX hizmetinin web.config yapılandırma dosyasından okunan değerler kullanılarak parametresiz yapıcıda başlatılır. Bu dosya şöyledir:


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

Ayrıca, web hizmetinin parametre içermeyen bir <WebMethod> kimliği vardır ve bu kimlik yalnızca nom ve age özniteliklerini döndürür. Hizmet, yapılandırma dosyasıyla birlikte c:\inetpub\wwwroot\st\personne klasöründe bulunan personne.asmx kaynak dosyasında kayıtlıdır:

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

Yukarıdaki fiziksel klasöre IIS /config adlı bir sanal klasör atayalım. IIS dosyasını çalıştıralım ve ardından bir tarayıcıdan personne hizmetinin http://localhost/config/personne.asmx adresini açalım:

Image

Tek yöntem kimliği olan bağlantıyı takip edelim:

Image

id yönteminin herhangi bir parametresi yoktur. Appeler düğmesini kullanalım:

Image

Hizmetin web.config dosyasına yerleştirilen bilgileri başarıyla aldık.

10.10. IMPOTS Web hizmeti

Artık hepimizin iyi bildiği IMPOTS uygulamasını ele alıyoruz. Bu uygulamayla en son çalıştığımızda, onu internet üzerinden çağrılabilen bir uzak sunucu haline getirmiştik. Şimdi ise onu bir web hizmeti haline getiriyoruz.

10.10.1. Web hizmeti

Veritabanları bölümünde oluşturduğumuz ve ODBC veritabanındaki bilgilerden oluşturulan impôt sınıfından yola çıkıyoruz:


' seçenekler
Option Strict On
Option Explicit On 

' ad alanları
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections

Public Class impôt
    ' vergi hesaplaması için gerekli veriler
    ' harici bir kaynaktan gelir
    Private limites(), coeffR(), coeffN() As Decimal

    ' oluşturucu
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
        ' 3 tablonun da aynı boyutta olup olmadığı kontrol edilir
        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
        ' sorun yok
        Me.limites = LIMITES
        Me.coeffR = COEFFR
        Me.coeffN = COEFFN
    End Sub

    ' 2. oluşturucu
    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 adlı üç sınır tablosunu
        ' ODBC veritabanındaki Timpots tablosunun içeriğinden DSNimpots
        ' colLimites, colCoeffR, colCoeffN bu tablonun üç sütunudur
        ' bir istisna oluşturabilir
        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 sorgusu
        Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
        ' verileri almak için tablolar
        Dim tLimites As New ArrayList
        Dim tCoeffR As New ArrayList
        Dim tCoeffN As New ArrayList

        ' veritabanına erişilmeye çalışılır
        impotsConn = New OdbcConnection(connectString)
        impotsConn.Open()
        ' bir command nesnesi oluşturuluyor
        sqlCommand = New OdbcCommand(selectCommand, impotsConn)
        ' sorgu yürütülüyor
        Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
        ' Alınan tablonun işlenmesi
        While myReader.Read()
            ' geçerli satırın verileri tablolara aktarılıyor
            tLimites.Add(myReader(colLimites))
            tCoeffR.Add(myReader(colCoeffR))
            tCoeffN.Add(myReader(colCoeffN))
        End While
        ' Kaynaklar serbest bırakılır
        myReader.Close()
        impotsConn.Close()

        ' Dinamik tablolar statik tablolara aktarılır
        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

    ' Vergi hesaplanması
    Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
        ' pay sayısının hesaplanması
        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
        ' Vergilendirilebilir gelir ve aile katsayısı hesaplaması
        Dim revenu As Decimal = 0.72D * salaire
        Dim QF As Decimal = revenu / nbParts
        ' vergi hesaplaması
        limites((limites.Length - 1)) = QF + 1
        Dim i As Integer = 0
        While QF > limites(i)
            i += 1
        End While
        ' sonuç döndürme
        Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
    End Function
End Class

Web hizmetinde yalnızca parametresiz bir oluşturucu kullanılabilir. Bu nedenle sınıfın oluşturucusu şu şekilde olacaktır:


    ' oluşturucu
    Public Sub New()
        ' coeffR, coeffN adlı üç sınır tablosunu
        ' ODBC veritabanındaki Timpots tablosunun içeriğinden DSNimpots
        ' colLimites, colCoeffR, colCoeffN bu tablonun üç sütunudur
        ' bir istisna oluşturabilir

        ' hizmetin yapılandırma parametreleri alınır
        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")

        ' veritabanını işler
        Dim connectString As String = "DSN=" + DSNimpots + ";"     ' chaîne de connexion à la base

Önceki sınıfın oluşturucusundaki beş parametre artık hizmetin web.config dosyasında okunmaktadır. impots.asmx kaynak dosyasının kodu aşağıdaki gibidir. Bu kod, önceki kodun büyük bir kısmını içermektedir. Biz sadece web hizmetine özgü kod bölümlerini çerçevelemeyle yetindik:


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

' vergi web hizmeti oluşturulur
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

    ' vergi hesaplaması için gerekli veriler
    ' harici bir kaynaktan sağlanır
    Private limites(), coeffR(), coeffN() As Decimal
    Private OK As Boolean = False
    Private errMessage As String = ""


    ' oluşturucu
    Public Sub New()
        ' coeffR ve coeffN adlı üç sınır tablosunu
        ' ODBC veritabanındaki Timpots tablosunun içeriğinden DSNimpots
        ' colLimites, colCoeffR, colCoeffN bu tablonun üç sütunudur
        ' bir istisna oluşturabilir

        ' hizmetin yapılandırma parametreleri alınır
        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")

        ' veritabanı işlenir
        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 sorgusu
        Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots

        ' verileri almak için tablolar
        Dim tLimites As New ArrayList
        Dim tCoeffR As New ArrayList
        Dim tCoeffN As New ArrayList

        ' veritabanına erişilmeye çalışılır
        Try
            impotsConn = New OdbcConnection(connectString)
            impotsConn.Open()
            ' bir komut nesnesi oluşturuluyor
            sqlCommand = New OdbcCommand(selectCommand, impotsConn)
            ' sorgu yürütülüyor
            myReader = sqlCommand.ExecuteReader()
            ' Alınan tablonun işlenmesi
            While myReader.Read()
                ' geçerli satırın verileri tablolara aktarılıyor
                tLimites.Add(myReader(colLimites))
                tCoeffR.Add(myReader(colCoeffR))
                tCoeffN.Add(myReader(colCoeffN))
            End While
            ' Kaynaklar serbest bırakılır
            myReader.Close()
            impotsConn.Close()

            ' Dinamik tablolar statik tablolara aktarılır
            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
            ' Tamam
            OK = True
            errMessage = ""
        Catch ex As Exception
            ' hata
            OK = False
            errMessage += "[" + ex.Message + "]"
        End Try
    End Sub

    ' vergi hesaplaması
    <WebMethod()> _
    Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
        ' pay sayısının hesaplanması
        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
        ' vergilendirilebilir gelir ve aile katsayısı hesaplaması
        Dim revenu As Decimal = 0.72D * salaire
        Dim QF As Decimal = revenu / nbParts
        ' vergi hesaplaması
        limites((limites.Length - 1)) = QF + 1
        Dim i As Integer = 0
        While QF > limites(i)
            i += 1
        End While
        ' sonuç döndürme
        Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
    End Function

    ' kimlik
    <WebMethod()> _
    Function id() As String
        ' her şeyin yolunda olup olmadığını kontrol etmek için OK
        Return "[" + OK + "," + errMessage + "]"
    End Function
End Class

impots sınıfında, onu bir web hizmeti haline getirmek için gerekli olan değişikliklerin dışında yapılan birkaç değişikliği açıklayalım:

  • yapıcıda veritabanının okunması başarısız olabilir. Bu nedenle sınıfımıza iki öznitelik ve bir yöntem ekledik:
    • Veritabanı okunabilmişse OK boole değeri vrai olur, aksi takdirde faux olur
    • Veritabanı okunamadıysa, errMessage dizesi bir hata mesajı içerir.
    • Parametre içermeyen id yöntemi, bu iki özniteliğin değerini almayı sağlar.
  • Veritabanına erişimde meydana gelebilecek hataları yönetmek için, bu erişimle ilgili oluşturucu kodunun ilgili kısmı bir try-catch ile çevrelenmiştir.

Hizmetin yapılandırma dosyası web.config şu şekildedir:


<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 hizmetini ilk kez yüklemeye çalışıldığında, derleyici, yönergede kullanılan Microsoft.Data.Odbc ad alanını bulamadığını bildirdi:

Imports Microsoft.Data.Odbc

Belgelere başvurulduktan sonra

  • web.config dosyasına, Microsoft.Data.odbc derleme biriminin kullanılması gerektiğini belirten bir derleme yönergesi eklendi
  • microsoft.data.odbc.dll dosyasının bir kopyası, projenin bin klasörüne yerleştirildi. Bu klasör, bir web hizmeti derleyicisi "assembly" ararken sistematik olarak taranır.

Başka çözümler de mümkün görünse de burada ayrıntılı olarak ele alınmamıştır. Dolayısıyla yapılandırma dosyası şu hale gelmiştir:


<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 dosyasının içeriği:

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

Hizmet ve yapılandırma dosyası impots dosyasına yerleştirildi:

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

Web hizmetinin fiziksel klasörü, IIS'teki /impots sanal klasörüyle ilişkilendirilmiştir. Hizmetin sayfası şu şekildedir:

Image

id bağlantısını takip edersek:

Image

Appeler düğmesini kullanırsak:

Image

Yukarıdaki sonuç, OK (true) ve errMessage ("") özniteliklerinin değerlerini gösterir. Bu örnekte, veritabanı başarıyla yüklenmiştir. Bu her zaman böyle olmamıştır ve bu nedenle hata mesajına erişebilmek için id yöntemini ekledik. Hata, veritabanının DSN adının kullanıcı olarak DSN olarak tanımlanmış olmasıydı; oysa bunun sistem olarak DSN olarak tanımlanması gerekiyordu. Bu ayrım, 32 bit ODBC kaynak yöneticisinde yapılır:

Hizmet sayfasına geri dönelim:

Image

calculer bağlantısını takip edelim:

Image

Çağrı parametrelerini tanımlayıp çağrıyı gerçekleştiriyoruz:

Image

Sonuç doğru.

10.10.2. impots hizmetinin proxy'sini oluşturma

Artık çalışır durumda bir impots web hizmetimiz olduğuna göre, bunun proxy sınıfını oluşturabiliriz. Bu sınıfın, istemci uygulamaları tarafından impots web hizmetine şeffaf bir şekilde erişmek için kullanılacağını hatırlatırız. Öncelikle wsdl yardımcı programını kullanarak proxy sınıfının kaynak dosyasını oluşturuyoruz, ardından bu dosya bir DLL olarak derleniyor.

dos>wsdl /language=vb http://localhost/impots/impots.asmx
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.

Écriture du fichier 'D:\data\serge\devel\vbnet\poly\chap9\impots\impots.vb'.

D:\data\serge\devel\vbnet\poly\chap9\impots>dir
09/03/2004  10:20    <REP>          bin
09/03/2004  10:58             4 651 impots.asmx
09/03/2004  11:05             3 364 impots.vb
09/03/2004  10:19               431 web.config

dos>vbc /t:library /r:system.dll /r:system.web.services.dll /r:system.xml.dll impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
pour Microsoft (R) .NET Framework version 1.1.4322.573
Copyright (C) Microsoft Corporation 1987-2002. Tous droits réservés.

dos>dir
09/03/2004  10:20    <REP>          bin
09/03/2004  10:58             4 651 impots.asmx
09/03/2004  11:09             5 120 impots.dll
09/03/2004  11:05             3 364 impots.vb
09/03/2004  10:19               431 web.config

10.10.3. Proxy'yi bir istemciyle kullanma

Veritabanları ile ilgili bölümde, vergi hesaplamasına olanak tanıyan bir konsol uygulaması oluşturmuştuk:

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 programı, o zamanlar impots.dll dosyasında bulunan klasik impôt sınıfını kullanıyordu. testimpots.vb programının kodu şöyleydi:


Option Explicit On 
Option Strict On

' ad alanları
Imports System
Imports Microsoft.VisualBasic

' test sayfası
Module testimpots
    Sub Main(ByVal arguments() As String)
        ' etkileşimli vergi hesaplama programı
        ' kullanıcı klavyeden üç veri girer: evli nbEnfants maaş
        ' program daha sonra ödenecek vergiyi gösterir
        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"

        ' program ayarlarının kontrolü
        If arguments.Length <> 5 Then
            ' hata mesajı
            Console.Error.WriteLine(syntaxe1)
            ' son
            Environment.Exit(1)
        End If        'if
        ' argümanlar alınır
        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)

        ' vergi nesnesi oluşturuluyor
        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

        ' sonsuz döngü
        While True
            ' başlangıçta hata yok
            Dim erreur As Boolean = False

            ' vergi hesaplaması için parametreler isteniyor
            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()

            ' ne yapılmalı?
            If paramètres Is Nothing Or paramètres = "" Then
                Exit While
            End If

            ' giriş satırındaki argüman sayısının kontrolü
            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
                ' parametrelerin geçerliliğinin kontrolü
                ' evli
                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
                ' maaş
                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
                ' parametreler doğru - vergi hesaplanıyor
                Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
            End If
        End While
    End Sub
End Module

Aynı programı yeniden ele alıyoruz ve şimdi daha önce oluşturduğumuz impots proxy sınıfı aracılığıyla impots web hizmetini kullanmasını sağlıyoruz. Kodu biraz değiştirmek zorundayız:

  • Orijinal impôt sınıfı beş argümanlı bir oluşturucuya sahipken, impots proxy sınıfı argüman içermeyen bir oluşturucuya sahiptir. Gördüğümüz gibi, bu beş parametre artık web hizmetinin yapılandırma dosyasında sabitlenmiştir.
  • Bu nedenle, bu beş parametreyi test programına argüman olarak aktarmaya artık gerek kalmamıştır

Yeni kod şu şekildedir:


Imports System
Imports Microsoft.VisualBasic

' test sayfası
Module testimpots

    Public Sub Main(ByVal arguments() As String)
        ' etkileşimli vergi hesaplama programı
        ' kullanıcı klavyeden üç veri girer: evli nbEnfants maaş
        ' program daha sonra ödenecek vergiyi gösterir
        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"

        ' bir vergi nesnesinin oluşturulması
        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

        ' sonsuz döngü
        Dim erreur As Boolean
        While True
            ' başlangıçta hata yok
            erreur = False
            ' vergi hesaplaması için parametreler isteniyor
            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()
            ' ne yapılmalı?
            If paramètres Is Nothing Or paramètres = "" Then
                Exit While
            End If
            ' giriş satırındaki argüman sayısının kontrolü
            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
                ' parametrelerin geçerliliğinin kontrolü
                ' evli
                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
                ' maaş
                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
                ' parametreler doğruysa - vergi hesaplanır
                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

Aynı klasörde impots.dll proxy'si ve testimpots kaynak dosyası bulunmaktadır.

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 kaynak kodunu derliyoruz:

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

ardından çalıştıralım:

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

Beklediğimiz sonucu elde ediyoruz.