Skip to content

4. Geliştirmenin temelleri ASP.NET

4.1. Web uygulaması kavramı ASP.NET

4.1.1. Giriş

Bir web uygulaması, çeşitli belgeleri (HTML, .NET kodu, resimler, sesler vb.) bir araya getiren bir uygulamadır. Bu belgeler, web uygulamasının kökü olarak adlandırılan tek bir kök dizin altında bulunmalıdır. Bu kök dizine, web sunucusunun sanal bir yolu atanır. Cassini web sunucusu için sanal klasör kavramıyla daha önce karşılaşmıştık. Bu kavram, IIS web sunucusu için de geçerlidir. İki sunucu arasındaki önemli bir fark, IIS'in herhangi bir anda istediği sayıda sanal klasöre sahip olabilmesi, oysa Cassini web sunucusunun yalnızca bir taneye sahip olmasıdır; bu, sunucunun başlatılması sırasında belirtilen sanal klasördür. Bu, IIS sunucusunun aynı anda birden fazla web uygulamasını barındırabilmesine karşın, Cassini sunucusunun her seferinde yalnızca bir tanesini barındırabileceği anlamına gelir. Önceki örneklerde, Cassini sunucusu her zaman /aspnet sanal klasörünü <webroot> fiziksel klasörüyle eşleştiren (<webroot>,/aspnet) parametreleriyle başlatılıyordu. Dolayısıyla web sunucusu her zaman aynı web uygulamasını barındırıyordu. Ancak bu, bu tek web uygulaması içinde farklı ve bağımsız sayfalar yazmamızı ve test etmemizi engellemedi. Her web uygulamasının kendine özgü kaynakları vardır ve bunlar fiziksel <webroot> kök dizininde bulunur:

  • önceden derlenmiş sınıfları yerleştirebileceğimiz bir [bin] klasörü
  • web uygulamasının tamamını ve her bir kullanıcının çalışma ortamını başlatmaya olanak tanıyan bir [global.asax] dosyası
  • uygulamanın işleyişini yapılandırmaya olanak tanıyan bir [web.config] dosyası
  • uygulamanın giriş kapısı işlevi gören bir [default.aspx] dosyası
  • ...

Bir uygulama bu üç kaynaktan herhangi birini kullandığında, kendisine özgü bir fiziksel ve sanal yola ihtiyaç duyar. Aslında, iki farklı web uygulamasının aynı şekilde yapılandırılması için hiçbir neden yoktur. Önceki örneklerimizin tümü, yukarıdaki kaynakların hiçbirini kullanmadıkları için aynı uygulama (<webroot>,/aspnet) içine yerleştirilebilmişti.

Bu bölümün başında bir web uygulaması geliştirme için önerilen MVC mimarisine geri dönelim:

Image

Web uygulaması, sınıf dosyaları (denetleyici, iş sınıfları, veri erişim sınıfları) ve sunum dosyaları (HTML belgeleri, resimler, sesler, stil sayfaları vb.) oluşur. Bu dosyaların tümü, zaman zaman <application-path> olarak adlandıracağımız tek bir kök dizin altında yer alacaktır. Bu kök dizin, <application-vpath> adlı sanal bir yol ile ilişkilendirilecektir. Bu sanal yol ile fiziksel yol arasındaki ilişkilendirme, web sunucusunun yapılandırılmasıyla gerçekleştirilir. Cassini sunucusu için bu ilişkilendirmenin, sunucunun başlatılması sırasında yapıldığını görmüştük. Örneğin bir DOS penceresinde Cassini şu komutla başlatılır:

webserver.exe /port:80 /path:<application-path> /vpath:<application-vpath>

<application-path> klasöründe, ihtiyaçlarımıza göre şunları bulabiliriz:

  • önceden derlenmiş sınıfları (dll) yerleştirmek için [bin] klasörü
  • uygulamanın başlatılması sırasında ya da bir kullanıcı oturumunun başlatılması sırasında başlatma işlemleri yapmamız gerektiğinde [global.asax] dosyası
  • uygulamayı yapılandırmamız gerektiğinde [web.config] dosyası
  • Uygulamada varsayılan bir sayfaya ihtiyaç duyduğumuzda [default.aspx] dosyası

Bu web uygulaması konseptine uymak amacıyla, ileride verilecek örneklerin tümü, uygulamaya özgü bir <application-path> klasörüne yerleştirilecek ve bu klasöre bir <application-vpath> sanal klasörü eşleştirilecektir; Cassini sunucusu, bu iki parametreyi birbirine bağlayacak şekilde başlatılacaktır.

4.1.2. Bir web uygulamasını yapılandırma

Eğer <application-path> bir ASP.NET uygulamasının kök diziniyse, uygulamayı yapılandırmak için <application-path>\web.config dosyasını kullanabiliriz. Bu dosya XML biçimindedir. İşte bir örnek:

<?xml version="1.0" encoding="UTF-8" ?>

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

XML etiketlerinin büyük/küçük harfe duyarlı olduğuna dikkat edilmelidir. Tüm yapılandırma bilgileri <configuration> ve </configuration> etiketleri arasında yer almalıdır. Kullanılabilecek birçok yapılandırma bölümü bulunmaktadır. Burada bunlardan sadece birini, <add> etiketiyle verileri başlatmaya olanak tanıyan <appSettings> bölümünü ele alacağız. Bu etiketin sözdizimi şöyledir:

<add key="identificateur" value="valeur"/>

Web sunucusu bir uygulamayı başlattığında, <application-path> içinde web.config adlı bir dosya olup olmadığına bakar. Varsa, dosyayı okur ve bilgilerini [ConfigurationSettings] türünde bir nesneye kaydeder; bu nesne, uygulama aktif olduğu sürece uygulamanın tüm sayfalarında kullanılabilir. [ConfigurationSettings] sınıfı, [AppSettings] adlı bir statik yönteme sahiptir:

Image

Yapılandırma dosyasındaki bir C anahtarının değerini almak için ConfigurationSettings.AppSettings("C") yazılır. Sonuç olarak bir karakter dizisi elde edilir. Önceki yapılandırma dosyasını kullanmak için bir [default.aspx] sayfası oluşturalım. [default.aspx.vb] dosyasındaki VB kodu şu şekilde olacaktır:


Imports System.Configuration

Public Class _default
    Inherits System.Web.UI.Page

    Protected nom As String
    Protected age As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'yapılandırma bilgileri alınır
        nom = ConfigurationSettings.AppSettings("nom")
        age = ConfigurationSettings.AppSettings("age")
    End Sub

End Class

Görüldüğü gibi, sayfa yüklendiğinde [nom] ve [age] yapılandırma parametrelerinin değerleri alınır. Bu değerler, [default.aspx] sunum kodu tarafından görüntülenecektir:


<%@ Page src="default.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="_default" %>
<html>
    <head>
        <title>Configuration</title>
    </head>
    <body>
        Nom :
        <% =nom %><br/>
        Age :
        <% =age %><br/>
    </body>
</html>

Test için [web.config], [default.aspx] ve [default.aspx.vb] dosyalarını aynı klasöre koyuyoruz:

D:\data\devel\aspnet\poly\chap2\config1>dir
30/03/2004  15:06                  418 default.aspx.vb
30/03/2004  14:57                  236 default.aspx
30/03/2004  14:53                  186 web.config

<application-path>, uygulamanın üç dosyasının bulunduğu klasör olsun. Cassini sunucusu (<application-path>,/aspnet/config1) parametreleriyle başlatılır. URL ve [http://localhost/aspnet/config1] dosyalarını talep ediyoruz. [config1] bir klasör olduğu için, web sunucusu bu klasörün içinde [default.aspx] adlı dosyayı arar ve bulursa görüntüler. Bu durumda, dosyayı bulacaktır:

Image

4.1.3. Uygulama, Oturum, Bağlam

4.1.3.1. global.asax dosyası

[global.asax] dosyasındaki kod, mevcut istekle talep edilen sayfa yüklenmeden önce her zaman yürütülür. Bu dosya, uygulamanın <application-path> kök dizininde bulunmalıdır. Varsa, [global.asax] dosyası web sunucusu tarafından çeşitli zamanlarda kullanılır:

  1. web uygulaması başlatıldığında veya sonlandırıldığında
  2. bir kullanıcı oturumu başladığında veya sona erdiğinde
  3. bir kullanıcı isteği başladığında

.aspx sayfalarında olduğu gibi, [global.asax] dosyası farklı şekillerde yazılabilir; özellikle de VB kodunu bir denetleyici sınıfı ve sunum koduna ayırarak. Bu, Visual Studio aracı tarafından varsayılan olarak yapılan seçimdir ve biz de burada aynı şeyi yapacağız. Normalde herhangi bir sunum yapılmaz; bu görev .aspx sayfalarına aittir. Dolayısıyla, [global.asax] dosyasının içeriği, denetleyici kodunu içeren dosyaya referans veren bir yönergeye indirgenir:


<%@ Application src="Global.asax.vb" Inherits="Global" %>

Yönergenin artık [Page] değil, [Application] olduğu dikkatinizi çekecektir. Visual Studio aracı tarafından oluşturulan ve bu yönergeye eşleştirilen [global.asax.vb] denetleyici kodu şöyledir:


Imports System
Imports System.Web
Imports System.Web.SessionState

Public Class Global
    Inherits System.Web.HttpApplication

  Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Uygulama başlatıldığında tetiklenir
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Oturum başlatıldığında tetiklenir
    End Sub

    Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
        ' Her isteğin başında tetiklenir
    End Sub

    Sub Application_AuthenticateRequest(ByVal sender As Object, ByVal e As EventArgs)
        ' Kullanıcı kimlik doğrulama denemesi sırasında tetiklenir
    End Sub

    Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
        ' Bir hata oluştuğunda tetiklenir
    End Sub

    Sub Session_End(ByVal sender As Object, ByVal e As EventArgs)
        ' Oturum sona erdiğinde tetiklenir
    End Sub

    Sub Application_End(ByVal sender As Object, ByVal e As EventArgs)
        ' Uygulama sonlandırıldığında tetiklenir
    End Sub

End Class

Denetleyicinin sınıfının [HttpApplication] sınıfından türediğine dikkat edilmelidir. Bir uygulamanın yaşam döngüsü boyunca birçok önemli olay meydana gelir. Bunlar, yukarıda ana yapısı verilen prosedürler tarafından yönetilir.

  • [Application_Start]: Bir web uygulamasının sanal bir yol içinde "kapalı" olduğunu hatırlayalım. Bu sanal yol içinde bulunan bir sayfa bir istemci tarafından talep edildiğinde uygulama başlar. Bu durumda [Application_Start] yordamı yürütülür. Bu, tek seferlik bir işlemdir. Bu prosedürde, örneğin ömrü uygulamanın ömrü kadar olan nesneler oluşturmak gibi, uygulama için gerekli tüm başlatma işlemleri gerçekleştirilecektir.
  • [Application-End]: Uygulama sona erdiğinde yürütülür. Her uygulamaya, [web.config]'te yapılandırılabilen bir atıl kalma süresi atanır; bu sürenin sonunda uygulama sona ermiş sayılır. Dolayısıyla, bu kararı uygulama ayarlarına göre web sunucusu verir. Bir uygulamanın etkin olmayan kalma süresi, hiçbir istemcinin uygulamanın bir kaynağına istek göndermediği süre olarak tanımlanır.
  • [Session-Start]/[Session_End]: Uygulama oturumsuz olarak yapılandırılmadıkça, her istemciye bir oturum atanır. Bir istemci, ekran başında oturan bir kullanıcı değildir. Eğer bir kullanıcı uygulamaya erişmek için 2 tarayıcı açmışsa, bu durumda iki istemci olarak kabul edilir. Bir istemci, her isteğine eklemesi gereken bir oturum jetonuyla tanımlanır. Bu oturum jetonu, web sunucusu tarafından rastgele üretilen ve benzersiz olan bir karakter dizisidir. İki istemcinin aynı oturum jetonuna sahip olması mümkün değildir. Bu jeton, istemciyi şu şekilde takip eder:
    • İlk isteğini yapan müşteri oturum jetonu göndermez. Web sunucusu bunu fark eder ve ona bir jeton atar. Bu, oturumun başlangıcıdır ve [Session_Start] prosedürü yürütülür. Bu, tek seferlik bir işlemdir.
    • İstemci, sonraki isteklerini kendisini tanımlayan jetonu göndererek yapar. Bu, web sunucusunun bu jetonla ilgili bilgileri bulmasını sağlar. Böylece istemcinin farklı istekleri arasında bir takip sağlanır.
    • Uygulama, bir müşteriye oturum sonlandırma formu sunabilir. Bu durumda, oturumun sonlandırılmasını talep eden müşteri kendisidir. [Session_End] prosedürü çalıştırılır. Bu işlem yalnızca bir kez gerçekleştirilir.
    • Müşteri oturumunu hiçbir zaman kendisi sonlandırmayabilir. Bu durumda, oturumda belirli bir süre etkinlik olmaması halinde (bu süre de [web.config] ile yapılandırılabilir), oturum web sunucusu tarafından sonlandırılır. Bu durumda [Session_End] prosedürü yürütülür.
  • [Application_BeginRequest]: Bu prosedür, yeni bir istek geldiğinde yürütülür. Dolayısıyla, herhangi bir istemciden gelen her istek sırasında yürütülür. Bu, isteği talep edilen sayfaya iletmeden önce incelemek için uygun bir yerdir. Hatta isteği başka bir sayfaya yönlendirme kararı da alınabilir.
  • [Application_Error]: [global.asax.vb] denetleyici kodunda açıkça yönetilmeyen bir hata oluştuğunda her seferinde yürütülür. Burada, müşterinin isteğini hatanın nedenini açıklayan bir sayfaya yönlendirebilirsiniz.

Bu olayların hiçbirinin işlenmesi gerekmiyorsa, [global.asax] dosyası göz ardı edilebilir. Bu bölümün ilk örneklerinde de böyle yapılmıştır.

4.1.3.2. Örnek 1

Uygulamanın başlatılması, oturumun başlatılması ve bir istemci isteğinin başlatılması olmak üzere bu üç aşamayı daha iyi anlamak için bir uygulama geliştirelim. [global.asax] dosyası şu şekilde olacaktır:

<%@ Application src="Global.asax.vb" Inherits="global" %>

İlgili [global.asax.vb] dosyası şu şekilde olacaktır:


Imports System
Imports System.Web
Imports System.Web.SessionState

Public Class global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Uygulama başlatıldığında tetiklenir
        ' saat kaydedilir
        Dim startApplication As String = Date.Now.ToString("T")
        ' Uygulama bağlamına kaydedilir
        Application.Item("startApplication") = startApplication
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Oturum başlatıldığında tetiklenir
        ' saat kaydedilir
        Dim startSession As String = Date.Now.ToString("T")
        ' oturuma eklenir
        Session.Item("startSession") = startSession
    End Sub

    Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
        ' saat kaydedilir
        Dim startRequest As String = Date.Now.ToString("T")
        ' Oturuma eklenir
        Context.Items("startRequest") = startRequest
    End Sub
End Class

Kodun önemli noktaları şunlardır:

  • Web sunucusu, [HttpApplication] sınıfına [global.asax.vb]'ten belirli sayıda nesne sunar:
    • [HttpApplicationState] türündeki uygulama - web uygulamasını temsil eder - uygulamanın tüm istemcileri tarafından erişilebilen bir [Application.Item] nesne sözlüğüne erişim sağlar - farklı istemciler arasında bilgi paylaşımına olanak tanır - birden fazla istemcinin aynı veriye okuma/yazma amacıyla eşzamanlı erişimi, istemcilerin senkronizasyonunu gerektirir.
    • [HttpSessionState] türündeki oturum - belirli bir istemciyi temsil eder - bu istemcinin tüm istekleri tarafından erişilebilen [Session.Item] nesne sözlüğüne erişim sağlar - istemciyle ilgili bilgilerin depolanmasına olanak tanır; bu bilgiler, istemcinin sonraki istekleri sırasında geri çağrılabilir.
    • [HttpRequest] türündeki istek - müşterinin o anda yaptığı HTTP isteğini temsil eder
    • [HttpResponse] türündeki yanıt - sunucudan müşteriye gönderilmek üzere oluşturulmakta olan HTTP yanıtını temsil eder
    • [HttpServerUtility] tipi sunucu - özellikle isteği başlangıçta öngörülen sayfadan başka bir sayfaya aktarmak için yardımcı yöntemler sunar.
    • [HttpContext] tipi bağlam - bu nesne her yeni istek sırasında yeniden oluşturulur, ancak isteğin işlenmesine katılan tüm sayfalar tarafından paylaşılır; Items sözlüğü sayesinde bir isteğin işlenmesi sırasında sayfalar arasında bilgi aktarımı yapılmasına olanak tanır.
  • [Application_Start] yordamı, uygulamanın başlangıcını uygulama düzeyinde erişilebilir bir sözlükte depolanan bir değişkene kaydeder
  • [Session_Start] prosedürü, oturumun başlangıcını oturum düzeyinde erişilebilir bir sözlükte depolanan bir değişkene kaydeder
  • [Application_BeginRequest] prosedürü, isteğin başlangıcını istek düzeyinde erişilebilir bir sözlükte depolanan bir değişkene kaydeder (c.a.d, işleme süresince her zaman kullanılabilir ancak işleme sonunda kaybolur)

Hedef sayfa, aşağıdaki [main.aspx] sayfası olacaktır:


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<html>
    <head>
        <title>global.asax</title>
    </head>
    <body>
        jeton de session  :
        <% =jeton %><br/>
        début Application  :
        <% =startApplication %><br/>
        début Session  :
        <% =startSession %><br/>
        début Requête  :
        <% =startRequest %><br/>        
    </body>
</html>

Bu tanıtım sayfası, [main.aspx.vb] denetleyicisi tarafından hesaplanan değerleri gösterir:

Public Class main
    Inherits System.Web.UI.Page

    Protected startApplication As String
    Protected startSession As String
    Protected startRequest As String
    Protected jeton as String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
         ' uygulama ve oturum bilgilerini alın
        jeton=Session.SessionId
        startApplication = Application.Item("startApplication").ToString
        startSession = Session.Item("startSession").ToString
        startRequest = Context.Items("startRequest").ToString
    End Sub

End Class

Denetleyici, [global.asax.vb] tarafından sırasıyla uygulama, oturum ve bağlamda yer alan üç bilgiyi alır.

Uygulamayı şu şekilde test ediyoruz:

  1. dosyalar aynı <application-path> klasöründe toplanır

Image

  1. Cassini sunucusu parametrelerle (<application-path>,/aspnet/globalasax1) başlatılır
  2. İlk istemci [http://localhost/aspnet/globalasax1/main.aspx] URL'sini ister ve şu sonucu alır:

Image

  1. Aynı istemci yeni bir istek gönderir (tarayıcının Yenile seçeneği):

Image

Görüldüğü gibi, yalnızca isteğin saati değişmiştir. Bu durum iki şeyi göstermektedir:

  • [global.asax]'in [Application_Start] ve [Session_Start] prosedürleri, ikinci istek sırasında yürütülmemiştir.
  • Uygulamanın ve oturumun başlangıç saatlerinin depolandığı [Application] ve [Session] nesneleri, ikinci istek için hâlâ kullanılabilir durumdadır.
  1. İkinci bir tarayıcı açarak ikinci bir istemci oluşturuyoruz ve aynı URL'yi tekrar istiyoruz:

Image

Bu sefer, oturum saatinin değiştiğini görüyoruz. İkinci tarayıcı, aynı makinede olmasına rağmen ikinci bir istemci olarak kabul edildi ve onun için yeni bir oturum oluşturuldu. İki istemcinin aynı oturum jetonuna sahip olmadığı görülebilir. Uygulamanın başlangıç saati değişmemiştir; bu da şu anlama gelir:

  • [global.asax.vb]'in [Application_Start] prosedürü yürütülmemiştir
  • uygulamanın başlangıç saatinin depolandığı [Application] nesnesine ikinci kullanıcı erişebilir. Dolayısıyla, uygulamanın farklı istemcileri arasında paylaşılması gereken bilgiler bu nesneye kaydedilmelidir; [Session] nesnesi ise aynı istemcinin istekleri arasında paylaşılması gereken bilgileri depolamak için kullanılır.

4.1.3.3. Genel Bakış

Şu ana kadar öğrendiklerimizle, bir web sunucusunun ve bu sunucunun hizmet verdiği web uygulamalarının işleyişine dair ilk açıklayıcı şemayı oluşturabiliriz:

Image

Yukarıdaki şema, her biri iki müşteriye sahip olan A ve B adlı iki uygulamaya hizmet veren bir sunucuyu göstermektedir. Bir web sunucusu, aynı anda birden fazla web uygulamasına hizmet verebilir. Bu uygulamalar birbirinden tamamen bağımsızdır. Uygulama A üzerinden bir örnek verelim. Müşteri-1A'nın uygulama A'ya yaptığı bir isteğin işlenmesi şu şekilde gerçekleşecektir:

  • 1A adlı istemci, web sunucusundan uygulama A’nın etki alanına ait bir kaynak talep eder. Bu, URL biçiminde bir [http://machine:port/VA/ressource] istediği anlamına gelir; burada VA, uygulama A’nın sanal yoludur.
  • Web sunucusu, bunun uygulama A'ya ait bir kaynağa yönelik ilk istek olduğunu algılarsa, uygulama A'nın [global.asax] dosyasındaki [Application_Start] olayını tetikler. [HttpApplicationState] türünde bir [ApplicationA] nesnesi oluşturulacaktır. Uygulamanın çeşitli kodları, bu nesneye [Application] ve c.a.d kapsamındaki verileri, yani tüm kullanıcılarla ilgili verileri depolayacaktır. [ApplicationA] nesnesi, web sunucusu Uygulama A'yı kapatana kadar var olmaya devam edecektir.
  • Web sunucusu, uygulama A’nın yeni bir istemcisiyle karşı karşıya olduğunu tespit ederse, uygulama A’nın [global.asax] dosyasındaki [Session_Start] olayını tetikleyecektir. [HttpSessionState] türünde bir [Session-1A] nesnesi oluşturulacaktır. Bu nesne, Uygulama A’nın belirli bir müşteriye ait [Session] ve c.a.d kapsamındaki nesneleri depolamasına olanak sağlayacaktır. [Session-1A] nesnesi, 1A müşterisi istek gönderdiği sürece var olacaktır. Bu nesne, söz konusu müşterinin izlenmesini sağlayacaktır. Web sunucusu, iki durumda yeni bir müşteriyle karşı karşıya olduğunu algılar:
    • istemci, isteğinin HTTP başlıklarında bir oturum jetonu göndermediyse
    • istemci, var olmayan (istemcide bir arıza veya korsanlık girişimi) ya da artık mevcut olmayan bir oturum jetonu göndermişse. Bir oturum jetonu, istemcinin belirli bir süre boyunca etkin olmaması durumunda (IIS ile varsayılan olarak 20 dakika) geçerliliğini yitirir. Bu süre programlanabilir.
  • Her durumda, web sunucusu [global.asax] dosyasındaki [Application_BeginRequest] olayını tetikleyecektir. Bu olay, bir istemci isteğinin işlenmesini başlatır. Genellikle bu olay işlenmez ve istemci tarafından talep edilen sayfaya kontrol devredilir; bu sayfa da isteği işler. Bu olayı, isteği analiz etmek, işlemek ve yanıt olarak hangi sayfanın gönderileceğine karar vermek için de kullanabiliriz. Bu tekniği, daha önce bahsettiğimiz MVC mimarisine uygun bir uygulama kurmak için kullanacağız.
  • [global.asax] filtresinden geçtikten sonra, istemcinin isteği, isteği işleyecek bir .aspx sayfasına aktarılır. Daha sonra göreceğimiz gibi, isteği birden fazla sayfadan oluşan bir filtreden geçirmek mümkündür. Son sayfa, yanıtı istemciye göndermekle görevlendirilecektir. Sayfalar, müşterinin ilk isteğine kendi hesapladıkları bilgileri ekleyebilir. Bu bilgileri Context.Items koleksiyonunda depolayabilirler. Nitekim, bir müşterinin isteğini işleyen tüm sayfalar bu veri havuzuna erişebilir.
  • Farklı sayfaların kodları, [ApplicationA], [Session-1A] gibi veri havuzlarına erişebilir. Web sunucusunun A uygulaması için aynı anda birden fazla müşteriyi işlediği unutulmamalıdır. Tüm bu müşteriler [Application A] nesnesine erişebilir. Bu nesnedeki verileri değiştirmeleri gerekiyorsa, istemciler arasında bir senkronizasyon işlemi yapılması gerekir. Ayrıca, her bir XA istemcisi, [Session-XA] veri havuzuna erişebilir. Bu veri havuzu kendisine ayrılmış olduğundan, burada herhangi bir senkronizasyon işlemi gerekmez.
  • Web sunucusu, aynı anda birden fazla web uygulamasına hizmet vermektedir. Bu farklı uygulamaların istemcileri arasında herhangi bir etkileşim yoktur.

Bu açıklamalardan şu noktalar akılda tutulmalıdır:

  • Belirli bir anda, bir web sunucusu birden fazla istemciye aynı anda hizmet verir. Bu, bir isteğin bitmesini beklemeden başka bir isteği işleme koyduğu anlamına gelir. Dolayısıyla T zamanında, farklı uygulamalar için farklı istemcilere ait işlenmekte olan birden fazla istek vardır. Web sunucusu içinde aynı anda çalışan işleme kodlarına bazen yürütme iş parçacıkları denir.
  • Farklı web uygulamalarının istemcilerine ait yürütme iş parçacıkları birbirleriyle etkileşime girmez. Aralarında yalıtım vardır.
  • Aynı uygulamaya ait istemcilerin yürütme iş parçacıkları verileri paylaşmak zorunda kalabilir:
    • İki farklı müşterinin (aynı oturum jetonu olmayan) isteklerine ait yürütme iş parçacıkları, [Application] nesnesi aracılığıyla verileri paylaşabilir.
    • Aynı müşterinin ardışık isteklerinin yürütme iş parçacıkları, [Session] nesnesi aracılığıyla veri paylaşabilir.
    • Belirli bir müşterinin aynı isteğini işleyen ardışık sayfaların yürütme iş parçacıkları, [Context] nesnesi aracılığıyla veri paylaşabilir.

4.1.3.4. Örnek 2

Az önce gördüklerimizi açıklayan yeni bir örnek geliştirelim. Aşağıdaki dosyaları aynı klasörde bir araya getiriyoruz:

[global.asax]

<%@ Application src="Global.asax.vb" Inherits="global" %>

[global.asax.vb]


Imports System
Imports System.Web
Imports System.Web.SessionState

Public Class global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Uygulama başlatıldığında tetiklenir
        ' istemci sayacını başlat
        Application.Item("nbRequêtes") = 0
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Oturum başlatıldığında tetiklenir
        ' İstek sayacını başlatır
        Session.Item("nbRequêtes") = 0
    End Sub
End Class

Uygulamanın çalışma prensibi, uygulamaya yapılan toplam istek sayısını ve müşteri başına istek sayısını saymaktır. Uygulama [Application_Start] ile başlatıldığında, uygulamaya yapılan istek sayacı 0'a sıfırlanır. Bu sayaç, tüm müşteriler tarafından artırılması gerektiğinden [Application] kapsamına yerleştirilir. Bir müşteri [Session_Start]'e ilk kez bağlandığında, bu müşteri tarafından yapılan istek sayacını 0'a sıfırlıyoruz. Bu sayaç, yalnızca belirli bir müşteriyle ilgili olduğu için [Session] kapsamına yerleştirilir.

[global.asax] çalıştırıldıktan sonra, bir sonraki [main.aspx] dosyası çalıştırılacaktır:


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<html>
    <head>
        <title>application-session</title>
    </head>
    <body>
        jeton de session :
        <% =jeton %>
        <br />
        requêtes Application :
        <% =nbRequêtesApplication %>
        <br />
        requêtes Client :
        <% =nbRequêtesClient %>
        <br />
    </body>
</html>

Denetleyicisi tarafından hesaplanan üç bilgiyi görüntüler:

  1. Müşterinin oturum jetonu aracılığıyla kimliği: [jeton]
  2. uygulamaya yapılan toplam istek sayısı: [nbRequêtesApplication]
  3. 1 numaralı müşteri tarafından yapılan toplam istek sayısı: [nbRequêtesClient]

Bu üç bilgi, [main.aspx.vb]'te hesaplanır:

Public Class main
    Inherits System.Web.UI.Page

    Protected nbRequêtesApplication As String
    Protected nbRequêtesClient As String
    Protected jeton As String

    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
         ' Uygulama için bir istek daha
        Application.Item("nbRequêtes") = CType(Application.Item("nbRequêtes"), Integer) + 1
         ' Oturumda bir istek daha
        Session.Item("nbRequêtes") = CType(Session.Item("nbRequêtes"), Integer) + 1
         ' sunum değişkenlerini başlat
        nbRequêtesApplication = Application.Item("nbRequêtes").ToString
        jeton = Session.SessionID
        nbRequêtesClient = Session.Item("nbRequêtes").ToString
    End Sub
End Class

[main.aspx.vb] çalıştırıldığında, belirli bir müşterinin talebini işliyoruz. Uygulamanın istek sayısını artırmak için [Application] nesnesini, şu anda isteği işlenmekte olan müşterinin istek sayısını artırmak için ise [Session] nesnesini kullanıyoruz. Unutmayalım ki, aynı uygulamanın tüm müşterileri aynı [Application] nesnesini paylaşsa da, her birinin kendine ait bir [Session] nesnesi vardır.

Uygulamayı test etmek için, yukarıdaki dört dosyayı <application-path> adını verdiğimiz bir klasöre yerleştiriyoruz ve Cassini sunucusunu (<application-path>,/aspnet/webapplia) parametreleriyle başlatıyoruz. İlk tarayıcıyı açıp [http://localhost/aspnet/webapplia/main.aspx] URL’sini çağırıyoruz:

Image

[Reload] düğmesiyle ikinci bir istek yapıyoruz:

Image

Aynı URL'yi istemek için ikinci bir tarayıcı açıyoruz. Web sunucusu için bu, yeni bir istemcidir:

Image

Oturum jetonunun değiştiğini ve dolayısıyla yeni bir istemciye sahip olduğumuzu görebiliriz. Bu durum, istemcinin istek sayısında da yansıtılmaktadır. Şimdi ilk tarayıcıya geri dönelim ve aynı URL'yi tekrar isteyelim:

Image

Uygulamaya yapılan isteklerin sayısı doğru bir şekilde sayılmıştır.

4.1.3.5. Bir uygulamanın istemcilerini senkronize etmenin gerekliliği

Önceki uygulamada, uygulamaya yapılan istek sayacı, [main.aspx] sayfasındaki [Form_Load] prosedüründe şu şekilde artırılıyor:

         ' uygulama için bir istek daha
        Application.Item("nbRequêtes") = CType(Application.Item("nbRequêtes"), Integer) + 1

Bu komut, basit olmasına rağmen, yürütülmesi için işlemcinin birkaç komutunu gerektirir. Bunun için üç komut gerektiğini varsayalım:

  1. sayaç okuma
  2. sayaç artırma
  3. sayaçın yeniden yazılması

Web sunucusu çoklu görevli bir makinede çalışır; bu da her göreve birkaç milisaniye süreyle işlemci tahsis edilmesini, ardından diğer tüm görevler de kendilerine ayrılan zaman dilimini tamamladıktan sonra işlemciyi tekrar almasını gerektirir. A ve B adlı iki istemcinin aynı anda web sunucusuna istek gönderdiğini varsayalım. İstemci A'nın ilk sırada olduğunu, [main.aspx.vb] prosedüründen [Form_Load] prosedürüne geçtiğini, sayacı (=100) okuduğunu ve ardından zaman dilimi dolduğu için kesintiye uğradığını varsayalım. Şimdi de B müşterisinin sırası geldiğini ve onun da aynı kaderi paylaştığını varsayalım: sayaç değerini (=100) okumayı başarır ancak değeri artırmaya vakti kalmaz. Hem A hem de B müşterisi, 100 değerinde bir sayaca sahiptir. Şimdi, sıra tekrar A müşterisine geldiğini varsayalım: o, sayacını artırır, değeri 101'e getirir ve ardından işlemi sonlandırır. Sıra, sayacın yeni değerini değil, eski değerini elinde bulunduran B müşterisine gelir. Dolayısıyla o da sayacın değerini 101'e getirir ve işlemi sonlandırır. Uygulamanın istek sayacının değeri artık hatalıdır.

Bu sorunu örneklemek için önceki uygulamayı ele alalım ve şu şekilde değiştirelim:

  • [global.asax], [global.asax.vb] ve [main.aspx] dosyaları değişmez
  • [main.aspx.vb] dosyası şu hale gelir:

Imports System.Threading

Public Class main
    Inherits System.Web.UI.Page

    Protected nbRequêtesApplication As Integer
    Protected nbRequêtesClient As Integer
    Protected jeton As String

    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' uygulama ve oturum için bir istek daha
        ' sayaçları okuma
        nbRequêtesApplication = CType(Application.Item("nbRequêtes"), Integer)
        nbRequêtesClient = CType(Session.Item("nbRequêtes"), Integer)
        ' 5 saniye bekleme
        Thread.Sleep(5000)
        ' sayaçların artırılması
        nbRequêtesApplication += 1
        nbRequêtesClient += 1
        ' sayaçların kaydedilmesi
        Application.Item("nbRequêtes") = nbRequêtesApplication
        Session.Item("nbRequêtes") = nbRequêtesClient
        ' görüntüleme değişkenlerini başlatma
        jeton = Session.SessionID
    End Sub
End Class

Sayaçların okunması dört aşamaya ayrılmıştır:

  1. sayaçın okunması
  2. çalışma iş parçacığının askıya alınması
  3. sayaçın artırılması
  4. sayaçın yeniden yazılması

A ve B adlı iki müşterimizi tekrar ele alalım. Okuma aşaması ile istek sayaçlarının artırılması aşaması arasında, yürütme iş parçacığını 5 saniye boyunca durdurmaya zorluyoruz. Bunun hemen ortaya çıkacak sonucu, iş parçacığının işlemciyi kaybetmesi ve işlemcinin başka bir göreve tahsis edilmesidir. İstemci A’nın ilk sırada olduğunu varsayalım. İstemci A, sayacın N değerini okuyacak ve 5 saniye boyunca kesintiye uğrayacaktır. Bu süre zarfında işlemci istemci B’nin elindeyse, istemci B de sayacın aynı N değerini okumalıdır. Sonuç olarak, her iki istemci de sayacın aynı değerini göstermelidir ki bu durum anormal olur.

Uygulamayı test etmek için önceki dört dosyayı <application-path> adını verdiğimiz bir klasöre yerleştiriyoruz ve Cassini sunucusunu (<application-path>,/aspnet/webapplib) parametreleriyle başlatıyoruz. [http://localhost/aspnet/webapplib/main.aspx] URL’sini kullanarak iki farklı tarayıcı hazırlıyoruz. İlk tarayıcıyı URL adresini isteyecek şekilde başlatıyoruz, ardından 5 saniye sonra gelecek yanıtı beklemeden ikinci tarayıcıyı başlatıyoruz. 5 saniyeden biraz fazla bir süre sonra şu sonucu elde ediyoruz:

Image

Buradan şunu görüyoruz:

  • iki farklı istemci olduğu (aynı oturum jetonu değil)
  • her bir istemcinin bir istek gönderdiği
  • uygulamaya yapılan istek sayacının bu nedenle iki tarayıcıdan birinde 2 olması gerektiği. Ancak durum böyle değil.

Şimdi başka bir deney yapalım. Aynı tarayıcıyla, [http://localhost/aspnet/webapplib/main.aspx] URL’sine beş istek gönderiyoruz. Yine, sonuçları beklemeden istekleri arka arkaya gönderiyoruz. Tüm istekler gerçekleştirildiğinde, son istek için şu sonucu elde ediyoruz:

Image

Şu noktaya dikkat edilebilir:

  • 5 istek de aynı istemciden gelmiş olarak kabul edilmiştir, çünkü istemci istek sayacı 5'tedir. Yukarıda gösterilmese de, 5 istek için oturum jetonunun gerçekten aynı olduğu görülmektedir.
  • Uygulamaya yapılan istek sayacının doğru olduğu.

Bundan ne sonuç çıkarılabilir? Kesin bir sonuç yok. Belki de web sunucusu, bir müşterinin halihazırda yürütülmekte olan bir isteği varsa, o müşteriden gelen yeni bir isteği yürütmeye başlamıyordur? Dolayısıyla, aynı müşterinin isteklerinin eşzamanlı olarak yürütülmesi asla söz konusu olmaz. İstekler birbiri ardına yürütülür. Bu nokta doğrulanmalıdır. Bu durum, kullanılan müşteri türüne bağlı olabilir.

4.1.3.6. İstemcilerin Senkronizasyonu

Önceki uygulamada ortaya çıkan sorun, bir kaynağa özel erişimle ilgili klasik (ancak çözümü kolay olmayan) bir sorundur. Bizim özel durumumuzda, A ve B adlı iki istemcinin aynı anda şu kod dizisinde bulunmaması sağlanmalıdır:

  1. sayaç okuma
  2. sayaç artırma
  3. sayaçın yeniden yazılması

Bu tür bir kod dizisine kritik dizi denir. Bu dizi, onu eşzamanlı olarak yürütecek iş parçacıklarının senkronizasyonunu gerektirir. .NET platformu, bunu sağlamak için çeşitli araçlar sunar. Burada [Mutex] sınıfını kullanacağız.

Image

Burada yalnızca aşağıdaki yapıcıları ve yöntemleri kullanacağız:

public Mutex()
bir M senkronizasyon nesnesi oluşturur
public bool WaitOne()
M.WaitOne() işlemini yürüten T1 iş parçacığı, M senkronizasyon nesnesinin mülkiyetini talep eder. Mutex M hiçbir iş parçacığı tarafından tutulmuyorsa (başlangıçta durum böyledir), bu Mutex, talep eden T1 iş parçacığına "verilir". Biraz sonra, T2 iş parçacığı aynı işlemi yaparsa, bu iş parçacığı bloke olur. Zira bir Mutex yalnızca bir iş parçacığına ait olabilir. T1 iş parçacığı, elinde tuttuğu Mutex M’yi serbest bıraktığında bu iş parçacığının engeli kaldırılacaktır. Böylece, Mutex M’yi bekleyen birden fazla iş parçacığı engellenebilir.
public void ReleaseMutex()
M.ReleaseMutex() işlemini gerçekleştiren T1 iş parçacığı, Mutex M'nin sahipliğini bırakır. T1 iş parçacığı işlemciyi kaybettiğinde, sistem bunu Mutex M'yi bekleyen iş parçacıklarından birine verebilir. Sırayla yalnızca biri Mutex'i alabilecek, Mutex M'yi bekleyen diğer iş parçacıkları ise bloke kalacaktır

Bir Mutex M, paylaşılan bir kaynak R'ye erişimi yönetir. Bir iş parçacığı, M.WaitOne() işleviyle kaynak R'yi talep eder ve M.ReleaseMutex() işleviyle geri verir. Bir seferde yalnızca tek bir iş parçacığı tarafından yürütülmesi gereken kritik bir kod bölümü, paylaşılan bir kaynaktır. Kritik bölümün yürütülmesinin senkronizasyonu şu şekilde yapılabilir:

M.WaitOne()
' buraya yalnızca iş parçacığı girebilir
' kritik bölüm
....
M.ReleaseMutex()

burada M, bir Mutex nesnesidir. Elbette, artık gereksiz hale gelen bir Mutex nesnesini serbest bırakmayı asla unutmamak gerekir; aksi takdirde, serbest bırakılmayan bir Mutex'i bekleyen iş parçacıkları işlemciye asla erişemeyecektir. Ayrıca, iki iş parçacığının birbirini beklediği kilitlenme durumu (deadlock) da önlenmelidir. Zaman içinde birbirini izleyen aşağıdaki eylemleri ele alalım:

  • T1 iş parçacığı, R1 adlı paylaşılan kaynağa erişmek için M1 adlı Mutex'in mülkiyetini alır
  • T2 iş parçacığı, R2 adlı paylaşılan kaynağa erişmek için M2 adlı Mutex'in sahipliğini alır
  • T1 iş parçacığı, M2 Mutex'ini talep eder. Engellenir.
  • T2 iş parçacığı, M1 Mutex'ini talep eder. Engellenir.

Burada, T1 ve T2 iş parçacıkları birbirlerini bekliyor. Bu durum, iş parçacıklarının iki paylaşımlı kaynağa ihtiyaç duyması halinde ortaya çıkar: M1 Mutex'i tarafından kontrol edilen R1 kaynağı ve M2 Mutex'i tarafından kontrol edilen R2 kaynağı. Olası bir çözüm, tek bir Mutex (M) kullanarak her iki kaynağa aynı anda erişim talep etmektir. Ancak bu her zaman mümkün değildir; özellikle de bu durum, maliyetli bir kaynağın uzun süreli işgaline yol açıyorsa. Başka bir çözüm ise, M1'e sahip olan ve M2'i alamayan bir iş parçacığının, kilitlenmeyi önlemek için M1'i serbest bırakmasıdır.

Az önce öğrendiklerimizi uygulamaya koyarsak, uygulamamız şu şekilde olur:

  • [global.asax] ve [main.aspx] dosyaları değişmez
  • [global.asax.vb] dosyası şu hale gelir:

Imports System
Imports System.Web
Imports System.Web.SessionState
Imports System.Threading

Public Class global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Uygulama başlatıldığında tetiklenir
        ' müşteri sayacının başlatılması
        Application.Item("nbRequêtes") = 0
        ' senkronizasyon kilidi oluşturma
        Application.Item("verrou") = New Mutex
    End Sub

    Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Oturum başlatıldığında tetiklenir
        ' istek sayacını başlat
        Session.Item("nbRequêtes") = 0
    End Sub
End Class

Tek yenilik, müşterilerin senkronizasyon için kullanacağı bir [Mutex] dosyasının oluşturulmasıdır. Bu dosya tüm müşteriler tarafından erişilebilir olması gerektiğinden, [Application] nesnesine yerleştirilmiştir.

  • [main.aspx.vb] dosyası şu şekilde olur:

Imports System.Threading

Public Class main
    Inherits System.Web.UI.Page

    Protected nbRequêtesApplication As Integer
    Protected nbRequêtesClient As Integer
    Protected jeton As String

    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' uygulama ve oturum için bir istek daha
        ' kritik bölüme giriliyor - senkronizasyon kilidi alınır
        Dim verrou As Mutex = CType(Application.Item("verrou"), Mutex)
        ' Takip eden kritik bölüme tek başına girme talebi
        verrou.WaitOne()
        ' sayaçları okuma
        nbRequêtesApplication = CType(Application.Item("nbRequêtes"), Integer)
        nbRequêtesClient = CType(Session.Item("nbRequêtes"), Integer)
        ' 5 saniye bekleme
        Thread.Sleep(5000)
        ' sayaçların artırılması
        nbRequêtesApplication += 1
        nbRequêtesClient += 1
        ' sayaçların kaydedilmesi
        Application.Item("nbRequêtes") = nbRequêtesApplication
        Session.Item("nbRequêtes") = nbRequêtesClient
        ' kritik bölüme erişim izni verilir
        verrou.ReleaseMutex()
        ' görüntüleme değişkenlerini başlat
        jeton = Session.SessionID
    End Sub
End Class

Görüldüğü üzere istemci:

  • kritik bölüme tek başına girmek istediği anlaşılmaktadır. Bunun için [verrou] Mutex'inin münhasır sahipliğini talep eder
  • kritik bölümün sonunda, başka bir müşterinin de sırayla kritik bölüme girebilmesi için [verrou] Mutex'ini serbest bırakıyor.

Uygulamayı test etmek için önceki dört dosyayı <application-path> adını verdiğimiz bir klasöre yerleştiriyoruz ve Cassini sunucusunu (<application-path>,/aspnet/webapplic) parametreleriyle başlatıyoruz. [http://localhost/aspnet/webapplic/main.aspx] URL'sini kullanarak iki farklı tarayıcı hazırlıyoruz. İlk tarayıcıyı URL adresini isteyecek şekilde başlatıyoruz; ardından, 5 saniye sonra gelecek yanıtı beklemeden ikinci tarayıcıyı başlatıyoruz. 5 saniyeden biraz fazla bir süre sonra şu sonucu elde ediyoruz:

Image

Bu sefer, uygulamanın istek sayacı doğru değer gösteriyor.

Bu uzun gösterimden çıkarılacak ders, tüm istemciler tarafından paylaşılan öğeleri güncellemeleri gerekiyorsa, aynı web uygulamasının istemcilerini senkronize etmenin mutlak bir gereklilik olduğudur.

4.1.3.7. Oturum Jetonunun Yönetimi

İstemci ile web sunucusu arasında alışverişi yapılan oturum jetonundan birçok kez bahsettik. Çalışma prensibini hatırlayalım:

  • İstemci, sunucuya ilk isteğini gönderir. O sırada oturum jetonu göndermez.
  • İstekte oturum jetonu bulunmadığı için sunucu yeni bir istemci olarak algılar ve ona bir jeton atar. Bu jetona, söz konusu istemciye özgü bilgileri depolamak için kullanılacak [Session] nesnesi de eşleştirilir. Bu jeton, söz konusu müşterinin tüm isteklerini takip edecektir. Jeton, müşterinin ilk isteğine verilen yanıtın HTTP başlıklarına dahil edilecektir.
  • Artık müşteri oturum jetonunu bilmektedir. Web sunucusuna yapacağı sonraki her isteğin HTTP başlıklarında bu jetonu geri gönderecektir. Jeton sayesinde sunucu, müşteriye bağlı [Session] nesnesini bulabilecektir.

Bu mekanizmayı vurgulamak için, önceki uygulamayı ele alıyoruz ve yalnızca [main.aspx.vb] dosyasını değiştiriyoruz:


Imports System.Threading

Public Class main
    Inherits System.Web.UI.Page

    Protected nbRequêtesApplication As Integer
    Protected nbRequêtesClient As Integer
    Protected jeton As String

    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' uygulama ve oturum için bir istek daha
        ' kritik bölüme giriliyor - senkronizasyon kilidi alınır
        Dim verrou As Mutex = CType(Application.Item("verrou"), Mutex)
        ' bir sonraki bölüme tek başına girme talebinde bulunulur
        verrou.WaitOne()
        ' sayaçları okuma
        nbRequêtesApplication = CType(Application.Item("nbRequêtes"), Integer)
        nbRequêtesClient = CType(Session.Item("nbRequêtes"), Integer)
        ' 5 saniye bekleme
        Thread.Sleep(5000)
        ' sayaçların artırılması
        nbRequêtesApplication += 1
        nbRequêtesClient += 1
        ' sayaçların kaydedilmesi
        Application.Item("nbRequêtes") = nbRequêtesApplication
        Session.Item("nbRequêtes") = nbRequêtesClient
        ' kritik bölüme erişim izni verilir
        verrou.ReleaseMutex()
        ' görüntüleme değişkenlerini başlat
        jeton = Session.SessionID
    End Sub

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
        ' istemcinin isteğini uygulama klasöründeki request.txt dosyasına kaydedilir
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub
End Class

[Page_Init] olayı meydana geldiğinde, istemcinin isteğini uygulama klasörüne kaydederiz. Birkaç noktayı hatırlatalım:

  • [TemplateSourceDirectory], o anda çalışmakta olan sayfanın sanal yolunu temsil eder;
  • MapPath (TemplateSourceDirectory) ise buna karşılık gelen fiziksel yolu temsil eder. Bu, oluşturulacak dosyanın fiziksel yolunu oluşturmamızı sağlar,
  • [Request], işlenmekte olan isteği temsil eden bir nesnedir. Bu nesne, istemci tarafından gönderilen ham istek olan c.a.d kullanılarak oluşturulmuştur. Bu istek, şu biçimdeki bir dizi metin satırından oluşur:

Image

  • Request.Save([FileName]), istemcinin isteğinin tamamını (HTTP başlıkları ve varsa ardından gelen belge) yol bilgisi parametre olarak geçirilen bir dosyaya kaydeder.

Böylece, müşterinin isteğinin tam olarak ne olduğunu öğrenebiliriz. Uygulamayı test etmek için, yukarıdaki dört dosyayı <application-path> adını verdiğimiz bir klasöre yerleştiriyoruz ve Cassini sunucusunu (<application-path>,/aspnet/session1) parametreleriyle başlatıyoruz. Ardından bir tarayıcı kullanarak URL

[http://localhost/aspnet/session1/main.aspx] adreslerini talep ediyoruz. Aşağıdaki sonucu alıyoruz:

Image

[main.aspx.vb] tarafından kaydedilen [request.txt] dosyasını kullanarak tarayıcının isteğine erişiyoruz:

GET /aspnet/session1/main.aspx HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.7b) Gecko/20040316

Tarayıcının URL ve [/aspnet/session1/main.aspx] isteklerini gönderdiğini ve önceki bölümde bahsettiğimiz diğer bilgileri de ilettiğini görüyoruz. Burada oturum belirteci görülmüyor. Yanıt olarak alınan sayfa ise sunucunun bir oturum belirteci oluşturduğunu gösteriyor. Tarayıcının bunu alıp almadığı henüz bilinmiyor. Şimdi aynı tarayıcıyla ikinci bir istek gönderelim (Sayfayı Yenile). Aşağıdaki yeni yanıtı alıyoruz:

Image

Oturum izleme düzgün bir şekilde çalışıyor, çünkü oturumdaki istek sayısı doğru şekilde artırılmış. Şimdi [request.txt] dosyasının içeriğine bakalım:

GET /aspnet/session1/main.aspx HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Cookie: ASP.NET_SessionId=y153tk45sise0lrhdzrf22m3
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.7b) Gecko/20040316

Görüldüğü üzere, bu ikinci istek için tarayıcı, sunucuya HTTP [Cookie:] adlı yeni bir başlık göndermiştir; bu başlık, [ASP.NET_SessionId] adlı bir bilgiyi tanımlamakta ve değeri, ilk isteğin yanıtında görülen oturum jetonudur. Bu jeton sayesinde, web sunucusu bu yeni isteği [y153tk45sise0lrhdzrf22m3] jetonuyla tanımlanan [Session] nesnesine bağlayacak ve ilgili istek sayacını bulacaktır.

Sunucunun bu jetonu müşteriye hangi mekanizma yoluyla gönderdiği hâlâ bilinmemektedir, çünkü sunucunun HTTP yanıtına erişimimiz yoktur. Bu yanıtın, müşterinin isteğiyle aynı yapıya sahip olduğunu hatırlayalım; yani şu biçimdeki bir dizi metin satırından oluşur:

Image

Web sunucusunun HTTP yanıtına erişim sağlayan bir web istemcisi, yani curl istemcisini kullanma fırsatımız olmuştu. Bunu, bir DOS penceresinde, önceki tarayıcıyla aynı URL'yi sorgulamak için tekrar kullanıyoruz:

E:\curl>curl --include http://localhost/aspnet/session1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 07:31:42 GMT
X-AspNet-Version: 1.1.4322
Set-Cookie: ASP.NET_SessionId=qxnxmqmvhde3al55kzsmx445; path=/
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 228
Connection: Close


<HTML>
        <HEAD>
                <title>application-session</title>
        </HEAD>
        <body>
                jeton de session :
                qxnxmqmvhde3al55kzsmx445
                <br>
                requêtes Application :
                3
                <br>
                requêtes Client :
                1
                <br>
        </body>
</HTML>

Sorumuzun cevabını bulduk. Web sunucusu, oturum jetonunu HTTP [Set-Cookie:] başlığı altında gönderiyor:

Set-Cookie: ASP.NET_SessionId=qxnxmqmvhde3al55kzsmx445; path=/

Şimdi oturum jetonunu geri göndermeden aynı isteği yapalım. Şu yanıtı alıyoruz:

E:\curl>curl --include http://localhost/aspnet/session1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 07:36:06 GMT
X-AspNet-Version: 1.1.4322
Set-Cookie: ASP.NET_SessionId=cs2p12mehdiz5v55ihev1kaz; path=/
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 228
Connection: Close


<HTML>
        <HEAD>
                <title>application-session</title>
        </HEAD>
        <body>
                jeton de session :
                cs2p12mehdiz5v55ihev1kaz
                <br>
                requêtes Application :
                4
                <br>
                requêtes Client :
                1
                <br>
        </body>
</HTML>

Oturum jetonunu geri göndermediğimiz için sunucu bizi tanıyamadı ve bize yeni bir jeton verdi. Başlatılan bir oturuma devam etmek için, istemci aldığı oturum jetonunu sunucuya geri göndermelidir. Bunu burada, curl'ün [--cookie clé=valeur] seçeneğini kullanarak yapacağız; bu seçenek, HTTP [Cookie: clé=valeur] başlığını oluşturacaktır. Tarayıcının ikinci isteğinde bu HTTP başlığını gönderdiğini görmüştük.

E:\curl>curl --include --cookie ASP.NET_SessionId=cs2p12mehdiz5v55ihev1kaz http://localhost/aspnet/session1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 07:40:20 GMT
X-AspNet-Version: 1.1.4322
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 228
Connection: Close


<HTML>
        <HEAD>
                <title>application-session</title>
        </HEAD>
        <body>
                jeton de session :
                cs2p12mehdiz5v55ihev1kaz
                <br>
                requêtes Application :
                5
                <br>
                requêtes Client :
                2
                <br>
        </body>
</HTML>

Burada birkaç husus dikkat çekmektedir:

  • istemci istek sayacı artmıştır; bu da sunucunun jetonumuzu doğru bir şekilde tanıdığını göstermektedir.
  • Sayfa tarafından görüntülenen oturum jetonu, bizim gönderdiğimiz jetonla aynıdır
  • Oturum jetonu, web sunucusu tarafından gönderilen HTTP başlıklarında artık yer almıyor. Aslında sunucu bunu yalnızca bir kez gönderir: yeni bir oturumun başlangıcında jeton oluşturulurken. İstemci jetonunu aldıktan sonra, tanınmak için istediği zaman bu jetonu kullanmak kendisine kalmıştır.

Bir istemcinin birden fazla oturum jetonuyla işlem yapmasına engel olan hiçbir şey yoktur; aşağıdaki [curl] örneğinde olduğu gibi, burada ilk isteğimizde (istek no. 1) elde ettiğimiz jetonu kullanıyoruz:

E:\curl>curl --include --cookie ASP.NET_SessionId=qxnxmqmvhde3al55kzsmx445 http://localhost/aspnet/session1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 07:48:47 GMT
X-AspNet-Version: 1.1.4322
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 228
Connection: Close


<HTML>
        <HEAD>
                <title>application-session</title>
        </HEAD>
        <body>
                jeton de session :
                qxnxmqmvhde3al55kzsmx445
                <br>
                requêtes Application :
                6
                <br>
                requêtes Client :
                2
                <br>
        </body>
</HTML>

Bu örnek ne anlama geliyor? Biraz önce elde ettiğimiz bir jetonu gönderdik. Web sunucusu bir jeton oluşturduğunda, bu jetonla ilişkili istemci kendisine istek göndermeye devam ettiği sürece jetonu saklar. Belirli bir süre boyunca etkinlik olmaması durumunda (IIS ile varsayılan olarak 20 dakika), jeton silinir. Yukarıdaki örnek, hâlâ aktif olan bir jeton kullandığımızı göstermektedir.

Tüm bu işlemler sırasında [curl] istemcisinin HTTP jetonuyla hangi istekleri gönderdiğini merak edebilirsiniz. Bu isteklerin [request.txt] dosyasına kaydedildiğini biliyoruz. İşte son istek:

GET /aspnet/session1/main.aspx HTTP/1.1
Pragma: no-cache
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Cookie: ASP.NET_SessionId=qxnxmqmvhde3al55kzsmx445
Host: localhost
User-Agent: curl/7.10.8 (win32) libcurl/7.10.8 OpenSSL/0.9.7a zlib/1.1.4

Burada, oturum jetonunu gönderen HTTP başlığı açıkça görülmektedir.

Sunucu tarafından HTTP ve [Set-Cookie:] başlıkları aracılığıyla iletilen bilgilere çerez denir. Sunucu, bu mekanizmayı oturum jetonu dışındaki diğer bilgileri iletmek için de kullanabilir. S sunucusu bir çerezi bir istemciye ilettiğinde, çerezin ömrü D'yi ve ilişkili URL U değerini de belirtir. Bu, istemci için şu anlama gelir: S sunucusuna /U/yol biçiminde bir URL talep ettiğinde, D süresinden daha uzun bir süre boyunca bu çerezi almamışsa, çerezi geri gönderebilir. Bir istemcinin bu etik kurallara uymamasını engelleyen hiçbir şey yoktur. Tarayıcılar ise bu kurallara uymaktadır. Bazı tarayıcılar, aldıkları çerezlerin içeriğine erişim sağlar. Mozilla tarayıcısı buna bir örnektir. Örneğin, önceki bir örnekte sunucu tarafından gönderilen çerezle ilgili bilgiler şunlardır:

Image

Burada şunlar yer almaktadır:

  • çerezin adı [ASP.NET_SessionId]
  • değeri: [y153...m3]
  • çerezin ilişkili olduğu cihaz: [localhost]
  • ilişkili olduğu URL: [/]
  • geçerlilik süresi: [at end of session]

Dolayısıyla tarayıcı, URL biçimindeki bir URL'yi her talep ettiğinde ([http://localhost/...], c.a.d gibi) oturum jetonunu gönderecektir. [localhost] makinesinin web sunucusuna bir URL isteğinde bulunduğu her seferinde. Çerezin ömrü, oturumun süresi kadardır. Tarayıcı açısından bu, çerezin hiçbir zaman süresi dolmayacağı anlamına gelir. [localhost] makinesinden bir URL isteğinde bulunduğu her seferinde çerezi gönderecektir. Dolayısıyla, tarayıcı D günü oturum jetonunu alırsa, kapatılıp ertesi gün yeniden kullanıldığında, oturum jetonunu (bir dosyada saklanmış olan) geri gönderecektir. Sunucu, artık elinde bulunmayan bu jetonu alacaktır; çünkü bir oturum jetonunun sunucudaki ömrü sınırlıdır (IIS'te 20 dakika). Bu nedenle yeni bir oturum başlatacaktır.

Tarayıcıda çerezlerin kullanımını devre dışı bırakmak mümkündür. Bu durumda, istemci oturum jetonunu alır ancak geri göndermez; bu da oturumun izlenmesini engeller. Bunu göstermek için tarayıcımızda (burada Mozilla) çerezlerin kullanımını devre dışı bırakıyoruz:

Image

Ayrıca, mevcut tüm çerezleri siliyoruz:

Image

Bunu yaptıktan sonra, sıfırdan başlamak için Cassini sunucusunu yeniden başlatıyoruz ve tarayıcıdan [http://localhost/aspnet/session1/main.aspx] URL'sini tekrar istiyoruz:

Image

Tarayıcımızın bir çerez kaydetmiş olup olmadığını kontrol edelim:

Image

Tarayıcının, sunucunun gönderdiği oturum jetonu çerezini kaydetmediğini görüyoruz. Dolayısıyla, oturum takibi yapılmayacağını bekleyebiliriz. Aynı URL'yi tekrar istiyoruz (Yenile):

Image

Beklediğimiz sonuç ortaya çıktı. Tarayıcı, aldığı ancak kaydetmediği oturum jetonunu geri göndermedi. Dolayısıyla sunucu, yeni bir jetonla yeni bir oturum başlattı. Bu örnekten çıkarılacak ders, kullanıcı tarayıcısında çerez kullanımını engellediğinde oturum izleme politikamızın işlevsiz hale geldiğidir. Ancak, sunucu ile istemci arasında oturum jetonunu aktarmak için çerezler dışında başka bir yol daha vardır. Aslında, web sunucusuna uygulamanın çerez olmadan çalıştığını bildirmek mümkündür. Bu, [web.config] yapılandırma dosyası aracılığıyla yapılır:


<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <system.web>
        <sessionState cookieless="true" timeout="10" />
    </system.web>
</configuration>

Yukarıdaki yapılandırma dosyası, uygulamanın çerezler olmadan çalışacağını (cookieless="true") ve bir oturum jetonunun maksimum etkin olmayan kalma süresinin 10 dakika olduğunu (timeout="10") belirtir. Bu süre geçtikten sonra, jetonla ilişkili oturum sonlandırılır. Sunucu ile istemci arasında oturum jetonunun alışverişi şu şekilde gerçekleşir:

  1. istemci, [http://machine:port/V/chemin] URL'sini ister; burada V, web sunucusundaki sanal bir klasördür
  2. sunucu bir J jetonu oluşturur ve istemciye [http://machine:port/V/(J)/chemin] URL'sine yönlendirilmesi gerektiğini bildirir. Böylece, sorgulanacak URL'ye, sanal klasör V'nin hemen arkasına jetonu yerleştirmiştir
  3. İstemci bu yönlendirmeye uyar ve URL [http://machine:port/V/(J)/chemin] adresini ister.
  4. Sunucu bu isteğe yanıt verir ve bir yanıt sayfası gönderir.

Bu farklı noktaları örneklerle açıklayalım. Önceki uygulamanın tamamını yeni bir <application-path> klasörüne yerleştiriyoruz. Aynı klasöre önceki [web.config] dosyasını da koyuyoruz. Ayrıca, [main.aspx] sunum kodunu bir bağlantı ekleyecek şekilde değiştiriyoruz:


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
    <HEAD>
        <title>application-session</title>
    </HEAD>
    <body>
        jeton de session :
        <% =jeton %>
        <br>
        requêtes Application :
        <% =nbRequêtesApplication %>
        <br>
        requêtes Client :
        <% =nbRequêtesClient %>
        <br>
        <a href="main.aspx">Recharger l'application</a>
    </body>
</HTML>

Bu bağlantı, [main.aspx] sayfasına yönlendirir ve dolayısıyla tarayıcının (Yenile) düğmesine eşdeğerdir. Cassini sunucusu (<application-path>,/session2) parametreleriyle başlatılır. [/aspnet/XX] sanal klasörünü not etme alışkanlığımızdan bu sefer sapıyoruz. Nitekim, URL’ye oturum jetonunun eklenmesi nedeniyle sanal klasörde yalnızca /XX öğesi bulunmalıdır. Öncelikle [curl] istemcisini kullanarak [http://localhost/session2/main.aspx] URL’sini talep ediyoruz:

E:\curl>curl --include http://localhost/session2/main.aspx
HTTP/1.1 302 Found
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 13:52:36 GMT
X-AspNet-Version: 1.1.4322
Location: /session2/(hinadjag3bt0u155g5hqe245)/main.aspx
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 163
Connection: Close

<html><head><title>Object moved</title></head><body>
<h2>Object moved to <a href='/session2/(hinadjag3bt0u155g5hqe245)/main.aspx'>here
</body></html>

Sunucunun, [HTTP/1.1 200 OK] yerine HTTP [HTTP/1.1 302 Found] başlığıyla yanıt verdiğini görüyoruz. Bu başlık, istemciden HTTP Location [Location: /session2/(hinadjag3bt0u155g5hqe245)/main.aspx] başlığında belirtilen URL'ye yönlendirilmesini ister. Yönlendirme URL'sine eklenmiş oturum jetonunu görebiliriz. Bu yanıtı alan bir tarayıcı, kullanıcıya yeni isteği göstermeden yeni URL'yi şeffaf bir şekilde ister. Tarayıcının yönlendirmeyi kendi başına yönetememesi durumunda, yukarıdaki HTTP kodunun arkasına bir HTML belgesi gönderilir. Bu belgede, kullanıcının tıklayabileceği yönlendirme URL’sine bir bağlantı bulunur.

Şimdi, çerezlerin devre dışı bırakıldığı bir tarayıcıda da aynı işlemi yapalım. Yine [http://localhost/session2/main.aspx] URL’sini talep ediyoruz. Sunucudan şu yanıtı alıyoruz:

Image

Öncelikle, tarayıcı tarafından görüntülenen URL'nin talep ettiğimiz URL ile aynı olmadığını görüyoruz. Bu, bir yönlendirme gerçekleştiğinin işaretidir. Nitekim tarayıcı her zaman en son alınan belgenin URL URL'sini görüntüler. Dolayısıyla, [http://localhost/session2/main.aspx] URL'sini görüntülemiyorsa, bu, tarayıcıdan başka bir URL'ye yönlendirilmesi istendiği anlamına gelir. Birden fazla yönlendirme olabilir. Tarayıcı tarafından görüntülenen URL, son yönlendirmenin URL'sidir. Tarayıcı tarafından görüntülenen URL'de oturum jetonunun bulunduğunu görebiliriz. Bunu görebiliyoruz çünkü bu jeton, programımız tarafından sayfada da görüntülenmektedir.

Sayfaya yerleştirilen bağlantının kodunu hatırlayalım:


        <a href="main.aspx">Recharger l'application</a>

Bu, mutlak bir bağlantı haline getirecek / işaretiyle başlamadığı için göreceli bir bağlantıdır. Neye göre göreceli? Bu noktayı anlamak için, şu anda görüntülenen belgenin URL'sine geri dönmeliyiz: [http://localhost/session2/(gu5ee455pkpffn554e3b1a32)/main.aspx]. Bu belgede bulunan göreceli bağlantılar, [http://localhost/session2/(gu5ee455pkpffn554e3b1a32)] yoluna göre belirlenir. Dolayısıyla yukarıdaki bağlantımız şu bağlantıya eşdeğerdir:


        <a href=" http://localhost/session2/(gu5ee455pkpffn554e3b1a32)/main.aspx">Recharger l'application</a>

Fareyi bağlantının üzerine getirdiğimizde tarayıcı bize şunu gösterir:

Image

[Recharger l'application] bağlantısına tıklarsak, çağrılan URL

[http://localhost/session2/(gu5ee455pkpffn554e3b1a32)/main.aspx] adresi çağrılır. Böylece sunucu, oturum jetonunu alır ve bununla ilişkili bilgileri bulabilir. Tarayıcının verdiği yanıt da bunu gösterir:

Image

Unutmayalım ki, bir web uygulamasında oturum takibi yapmamız gerekiyorsa ve bu uygulamanın istemci tarayıcılarının çerez kullanımına izin vereceğinden emin değilsek,

  • uygulamayı çerezler olmadan çalışacak şekilde yapılandırmalıyız
  • uygulamanın sayfalarında mutlak değil, göreceli bağlantılar bulunmalıdır

4.2. Bir istemci isteğinden bilgileri alma

4.2.1. Web istemci-sunucu istek-yanıt döngüsü

Burada bir web uygulamasının istemci-sunucu bağlamını hatırlayalım:

Image

Bir web uygulaması için istemciden gelen istek şu şekilde işlenir:

  1. İstemci, web uygulamasını barındıran M makinesinin web hizmetinin P numaralı bağlantı noktasına bir TCP/IP bağlantısı açar
  2. Bu bağlantı üzerinden HTTP protokolüne göre bir dizi metin satırı gönderir. Bu satırlar topluluğu, istemci isteği olarak adlandırılır. Şu şekildedir:

Image

İstek gönderildikten sonra, istemci yanıtı bekler.

  1. HTTP başlıklarının ilk satırı, web sunucusundan istenen eylemi belirtir. Bu, çeşitli biçimlerde olabilir:
    1. GET url HTTP/<version>, burada <version> şu anda 1.0 veya 1.1'dir. Bu durumda, istek [Document] kısmını içermez
    2. POST url HTTP/<version>. Bu durumda istek, [Document] kısmını içerir; bu kısım genellikle web uygulamasına yönelik bir bilgi listesidir
    3. PUT url HTTP/<sürüm>. İstemci, [Document] bölümünde bir belge gönderir ve bunu url adresinde sunucuya kaydetmek ister

İstemci, bağlandığı web uygulamasına bilgi iletmek istediğinde, temel olarak iki yol kullanabilir:

  • (devamı)
    1. isteği [GET url_enrichie HTTP/<version>] şeklindedir; burada url_enrichie, [url?param1=val1&param2=val2&...] biçimindedir. İstemci, URL'nin yanı sıra [clé=valeur] biçiminde bir dizi bilgi de iletir.
    2. İsteği [POST url HTTP/<version>] şeklindedir. [Document] bölümünde, önceki ile aynı biçimde bilgiler iletir: [param1=val1&param2=val2&...].
  1. Sunucuda, istemcinin isteğinin işleme zincirinin tamamı, Request adlı bir küresel nesne aracılığıyla bu isteğe erişebilir. Web sunucusu, istemcinin isteğinin tamamını bu nesneye, birazdan göreceğimiz bir biçimde yerleştirmiştir. İstenen uygulama bu nesneyi işleyecek ve müşteriye bir yanıt oluşturacaktır. Bu yanıt, Response adlı bir küresel nesne içinde mevcuttur. Web uygulamasının rolü, alınan [Request] nesnesinden bir [Response] nesnesi oluşturmaktır. İşlem zinciri, daha önce bahsettiğimiz ve farklı müşteriler arasında (Uygulama) veya aynı müşterinin ardışık istekleri arasında (Oturum) veri paylaşımına olanak tanıyan [Application] ve [Session] küresel nesnelerine de sahiptir.
  2. Uygulama, yanıtını [Response] nesnesi aracılığıyla sunucuya gönderecektir. Bu yanıt, ağa ulaştığında aşağıdaki HTTP biçimini alacaktır:

Image

Bu yanıt gönderildikten sonra, sunucu alıcı tarafta ağ bağlantısını kapatacaktır (istemci aksi yönde talimat vermedikçe).

  1. İstemci yanıtı alacak ve sırayla bağlantıyı (gönderme sırasında) kapatacaktır. Bu yanıtın nasıl işleneceği, istemcinin türüne bağlıdır. İstemci bir tarayıcıysa ve alınan belge bir HTML belgesi ise, bu belge görüntülenecektir. İstemci bir programsa, yanıt analiz edilip işlenecektir.
  2. İstek-yanıt döngüsünün ardından, istemciyi sunucuya bağlayan bağlantının kapatılması, HTTP protokolünü durum bilgisi içermeyen bir protokol haline getirir. Bir sonraki istek sırasında, istemci aynı sunucuya yeni bir ağ bağlantısı kuracaktır. Artık aynı ağ bağlantısı olmadığı için, sunucunun (TCP/IP ve HTTP düzeyinde) bu yeni bağlantıyı önceki bir bağlantıyla ilişkilendirme imkânı yoktur. Bu bağlantıyı sağlayacak olan, oturum jetonu sistemidir.

4.2.2. İstemci tarafından iletilen bilgileri alma

Şimdi, uygulama kodunun istemcinin isteğine ve dolayısıyla istemcinin ilettiği bilgilere erişmesini sağlayan [Request] nesnesinin bazı özelliklerini ve yöntemlerini inceleyeceğiz. [Request] nesnesi, [HttpRequest] türündedir:

Image

Bu sınıfın birçok özelliği ve yöntemi vardır. Bizim ilgilendiğimiz özellikler, HttpMethod, QueryString, Form ve Params'tır; bunlar, [param1=val1&param2=val2&...] bilgi dizisindeki öğelere erişmemizi sağlayacaktır.

HttpMethod as String
istemci sorgu yöntemi: GET, POST, HEAD, ...
QueryString as NameValueCollection
1. satırdaki param1=val1&param2=val2&.. biçimindeki istek dizesinin öğelerinin toplanması HTTP [méthode]?param1=val1&param2=val2&... burada [méthode], GET, POST veya HEAD olabilir.
Form as NameValueCollection
isteğin [Document] bölümünde bulunan param1=val1&param2=val2&... sorgu dizesi öğelerinin toplanması (POST yöntemi).
Params as NameValueCollection
QueryString, Form, ServerVariables ve Cookies koleksiyonlarını tek bir koleksiyon altında birleştirir.

4.2.3. Örnek 1

Bu öğeleri ilk örneğimizde uygulayalım. Uygulama yalnızca bir [main.aspx] öğesine sahip olacaktır. [main.aspx] sunum kodu şu şekilde olacaktır:


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<html>
    <head>
        <title>Requête client</title>
    </head>
    <body>
        Requête :
        <% = méthode %>
        <br />
        nom :
        <% = nom %>
        <br />
        âge :
        <% = age %>
        <br />
    </body>
</html>

Sayfa, denetleyici kısmı [main.aspx.vb] tarafından hesaplanan üç bilgiyi ([méthode, nom, age]) görüntüler:

Public Class main
    Inherits System.Web.UI.Page

    Protected nom As String = "xx"
    Protected age As String = "yy"
    Protected méthode As String

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
         ' istemcinin isteği, uygulama klasöründeki request.txt dosyasına kaydedilir
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
         ' isteğin parametreleri alınır
        méthode = Request.HttpMethod.ToLower
        If Not Request.QueryString("nom") Is Nothing Then nom = Request.QueryString("nom").ToString
        If Not Request.QueryString("age") Is Nothing Then age = Request.QueryString("age").ToString
        If Not Request.Form("nom") Is Nothing Then nom = Request.Form("nom").ToString
        If Not Request.Form("age") Is Nothing Then age = Request.Form("age").ToString
    End Sub

End Class

Sayfa yüklendiğinde (Form_Load), istemcinin isteğinden [nom, age] bilgileri alınır. Bu bilgiler, [QueryString] ve [Form] koleksiyonlarında aranır. . Ayrıca, [Page_Init] dosyasında, müşterinin ne gönderdiğini doğrulayabilmek için isteği saklıyoruz. Bu iki dosyayı <application-path> klasörüne yerleştirip Cassini sunucusunu (<application-path>,/request1) parametreleriyle başlatıyoruz, ardından bir tarayıcıdan

[http://localhost/request1/main.aspx?nom=tintin&age=27] adresini çağırıyoruz. Aşağıdaki yanıtı alıyoruz:

Image

İstemci tarafından iletilen bilgiler doğru bir şekilde alındı. [request.txt] dosyasında saklanan tarayıcı isteği şöyledir:

GET /request1/main.aspx?nom=tintin&age=27 HTTP/1.1
Cache-Control: max-age=0
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7b) Gecko/20040316

Tarayıcının GET isteği gönderdiği görülüyor. POST isteği göndermek için [curl] istemcisini kullanacağız. Bir DOS penceresinde şu komutu yazıyoruz:

C:\curl>curl --include --data nom=tintin --data age=27 http://localhost/request1/main.aspx
--include
yanıtın HTTP başlıklarını görüntülemek için
--data param=valeur
param=değer bilgisini POST aracılığıyla göndermek için

Sunucunun yanıtı şöyledir:

HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Fri, 02 Apr 2004 09:27:25 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 178
Connection: Close


<html>
        <head>
                <title>Requête client</title>
        </head>
        <body>
                Requête :
                post
                <br />
                nom :
                tintin
                <br />
                âge :
                27
                <br />
        </body>
</html>

Sunucu, bu sefer bir POST tarafından gönderilen parametreleri yine başarıyla aldı. Bu son noktayı doğrulamak için [request.txt] dosyasının içeriğini kontrol edebiliriz:

POST /request1/main.aspx HTTP/1.1
Pragma: no-cache
Content-Length: 17
Content-Type: application/x-www-form-urlencoded
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Host: localhost
User-Agent: curl/7.10.8 (win32) libcurl/7.10.8 OpenSSL/0.9.7a zlib/1.1.4

nom=tintin&age=27

[curl] müşterisi, POST'i başarıyla gerçekleştirdi. Şimdi, bu iki bilgi aktarım yöntemini birleştirelim. İstenen URL'ye [age]'i, gönderilen belgeye ise [nom]'i ekliyoruz:

E:\curl>curl --include --data nom="tintin" http://localhost/request1/main.aspx?age=27

[curl] tarafından gönderilen istek şöyledir (request.txt):

POST /request1/main.aspx?age=27 HTTP/1.1
Pragma: no-cache
Content-Length: 10
Content-Type: application/x-www-form-urlencoded
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Host: localhost
User-Agent: curl/7.10.8 (win32) libcurl/7.10.8 OpenSSL/0.9.7a zlib/1.1.4

nom=tintin

Görüldüğü gibi yaş bilgisi istenen URL'ye aktarılmıştır. Bu bilgiyi [QueryString] koleksiyonundan elde edeceğiz. İsim bilgisi ise bu URL'ye gönderilen belgeye aktarılmıştır. Bu bilgiyi [Form] koleksiyonundan elde edeceğiz. [curl] istemcisi tarafından alınan yanıt:

<html>
        <head>
                <title>Requête client</title>
        </head>
        <body>
                Requête :
                post
                <br />
                nom :
                tintin
                <br />
                âge :
                27
                <br />
        </body>
</html>

Son olarak, sunucuya hiçbir bilgi göndermeyelim:

E:\curl>curl --include http://localhost/request1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Fri, 02 Apr 2004 12:43:14 GMT
X-AspNet-Version: 1.1.4322
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 173
Connection: Close


<html>
        <head>
                <title>Requête client</title>
        </head>
        <body>
                Requête :
                get
                <br />
                nom :
                xx
                <br />
                âge :
                yy
                <br />
        </body>
</html>

Okuyucunun bu cevabı anlamak için [main.aspx.vb] denetleyici kodunu tekrar okumasını öneririz.

4.2.4. Örnek 2

Müşterinin aynı anahtar için birden fazla değer göndermesi mümkündür. Peki, önceki örnekte [http://localhost/request1/main.aspx?nom=tintin&age=27&nom=milou] URL’sini talep ettiğimizde, burada [nom] anahtarı iki kez geçiyorsa ne olur? Bir tarayıcıyla deneyelim:

Image

Uygulamamız, [nom] anahtarıyla ilişkili iki değeri de başarıyla aldı. Ekran görüntüsü biraz yanıltıcı olabilir. Bu sonuç şu komutla elde edildi:


        If Not Request.QueryString("nom") Is Nothing Then nom = Request.QueryString("nom").ToString

[ToString] yöntemi, görüntülenen [tintin,milou] dizesini üretmiştir. Bu, aslında [Request.QueryString("nom")] nesnesinin {"tintin","milou"} karakter dizilerinden oluşan bir dizi olduğu gerçeğini gizlemektedir. Aşağıdaki örnek bu durumu açıkça ortaya koymaktadır. [main.aspx] adlı sunum sayfası şu şekilde olacaktır:


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
    <HEAD>
        <title>Requête client</title>
    </HEAD>
    <body>
        <P>Informations passées par le client :</P>
        <form runat="server">
            <P>QueryString :</P>
            <P><asp:listbox id="lstQueryString" runat="server" EnableViewState="False" Rows="6"></asp:listbox></P>
            <P>Form :</P>
            <P><asp:listbox id="lstForm" runat="server" EnableViewState="False" Rows="2"></asp:listbox></P>
        </form>
    </body>
</HTML>

Bu sayfada, "sunucu denetimleri" olarak adlandırılan yeni özellikler bulunmaktadır. Bunlar, [runat="server"] özniteliği ile tanımlanır. Sunucu denetimi kavramını tanıtmak için henüz çok erkendir. Şu anda şunu bilmeniz yeterlidir:

  • sayfada iki liste ( <asp:listbox> etiketleri) olduğunu
  • bu listelerin, sayfa denetleyicisi tarafından oluşturulacak [ListBox] türünde nesneler (lstQueryString, lstForm) olduğunu
  • bu nesnelerin yalnızca web sunucusu içinde var olduğunu. Yanıt verildiği anda, bunlar istemcinin anlayabileceği klasik HTML etiketlerine dönüştürülecektir. Böylece bir [listbox] nesnesi, <select> ve <option> HTML etiketlerine dönüştürülecektir (buna "render" de denir).
  • Bu nesnelerin temel amacı, sunum kodunu tüm VB kodundan arındırmaktır; bu kod, denetleyici içinde kalır.

[lstQueryString] ve [lstForm] nesnelerini oluşturmakla görevli [main.aspx.vb] denetleyicisi şöyledir:


Imports System.Collections
Imports System
Imports System.Collections.Specialized

Public Class main
    Inherits System.Web.UI.Page

    Protected infosQueryString As ArrayList
    Protected WithEvents lstQueryString As System.Web.UI.WebControls.ListBox
    Protected WithEvents lstForm As System.Web.UI.WebControls.ListBox
    Protected infosForm As ArrayList

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Init
        ' istemcinin isteği, uygulama klasöründeki request.txt dosyasına kaydedilir
        Dim requestFileName As String = Me.MapPath(Me.TemplateSourceDirectory) + "\request.txt"
        Me.Request.SaveAs(requestFileName, True)
    End Sub

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' QueryString dosyasındaki tüm bilgi koleksiyonu alınır
        infosQueryString = getValeurs(Request.QueryString)
        lstQueryString.DataSource = infosQueryString
        lstQueryString.DataBind()
        infosForm = getValeurs(Request.Form)
        lstForm.DataSource = infosForm
        lstForm.DataBind()
    End Sub

    Private Function getValeurs(ByRef data As NameValueCollection) As ArrayList
        ' Başlangıçta bilgi listesi boştur
        Dim infos As New ArrayList
        ' koleksiyondaki anahtarları alıyoruz
        Dim clés() As String = data.AllKeys
        ' anahtar tablosu taranır
        Dim valeurs() As String
        For Each clé As String In clés
            ' anahtara bağlı değerler
            valeurs = data.GetValues(clé)
            ' tek bir değer mi?
            If valeurs.Length = 1 Then
                infos.Add(clé + "=" + valeurs(0))
            Else
                ' birden fazla değer
                For ivalue As Integer = 0 To valeurs.Length - 1
                    infos.Add(clé + "(" + ivalue.ToString + ")=" + valeurs(ivalue))
                Next
            End If
        Next
        ' sonuç döndürülür
        Return infos
    End Function
End Class

Bu kodun önemli noktaları şunlardır:

  • [Form_Load] kodunda sayfa, [QueryString] ve [Form] adlı iki koleksiyonu alır. Sayfa, [getValeurs] işlevini kullanarak bu iki koleksiyonun içeriğini, [ArrayList] türünde iki nesneye yerleştirir; koleksiyon anahtarı tek bir değerle ilişkilendirilmişse bu nesneler [clé=valeur] türünde karakter dizelerini içerir, anahtar birden fazla değerle ilişkilendirilmişse ise [clé(i)=valeur] türünde karakter dizeleri içerecek.
  • Her bir [ArrayList] nesnesi daha sonra, iki komut aracılığıyla tanıtım sayfasındaki [ListBox] nesnelerinden birine eklenir:
    • [ListBox.DataSource=ArrayList] ve [ListBox.DataBind]. Bu son komut, [DataSource] nesnesindeki öğeleri, [ListBox] nesnesinin [Items] koleksiyonuna aktarır

Burada, iki [ListBox] nesnesinin hiçbirinin [New] işlemi tarafından açıkça oluşturulmadığına dikkat edilmelidir. Buradan, <asp:listbox id="xx">...<asp:listbox/> etiketinin varlığında, web sunucusunun etiketin [id] özniteliği tarafından referans verilen [ListBox] nesnesini kendisi oluşturduğu sonucuna varılabilir.

  • [getValeurs] işlevi, kendisine parametre olarak geçirilen [NameValueCollection] türündeki nesneyi kullanarak [ArrayList] türünde bir sonuç üretir.

Yukarıdaki iki dosyayı <application-path> klasörüne yerleştirip Cassini sunucusunu (<application-path>,/request2) parametreleriyle başlatıyoruz, ardından

[http://localhost/request2/main.aspx?nom=tintin&age=27] adresine erişim talebinde bulunuyoruz. Aşağıdaki yanıtı alıyoruz:

Image

Şimdi, [nom] anahtarının iki kez yer aldığı bir URL'yi sorguluyoruz:

Image

[Request.QueryString("ad")) nesnesinin gerçekten bir dizi olduğunu görüyoruz. Burada istekler, GET yöntemi ile yapılmıştı. [curl] istemcisini kullanarak POST sorgusu yapıyoruz:

E:\curl>curl --data nom=milou --data nom=tintin --data age=14 --data age=27 http://localhost/request2/main.aspx

<HTML>
        <HEAD>
                <title>Requête client</title>
        </HEAD>
        <body>
                <P>Informations passées par le client :</P>
                <form name="_ctl0" method="post" action="main.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwtMTI3MjA1MzUzMTs7PtCDC7NG4riDYIB4YjyGFpVAAviD" />

                        <P>QueryString :</P>
                        <P><select name="lstQueryString" size="6" id="lstQueryString">

</select></P>
                        <P>Form :</P>
                        <P><select name="lstForm" size="2" id="lstForm">
        <option value="nom(0)=milou">nom(0)=milou</option>
        <option value="nom(1)=tintin">nom(1)=tintin</option>
        <option value="age(0)=14">age(0)=14</option>
        <option value="age(1)=27">age(1)=27</option>

</select></P>
                </form>
        </body>
</HTML>

Görüldüğü gibi, istemci sayfanın her iki listesi için de klasik HTML kodunu almaktadır. Gizli alan [_VIEWSTATE] gibi, bizim eklemediğimiz bilgiler de görünüyor. Bu bilgiler <asp:xx runat="server"> etiketleri tarafından oluşturulmuştur. Bunları nasıl kullanacağımızı öğrenmemiz gerekecek.

4.3. MVC mimarisinin uygulanması

4.3.1. Kavram

Bu uzun bölümü, MVC (Model-View-Controller) modeline göre oluşturulmuş bir uygulamanın uygulanmasıyla bitirelim. Bu modele göre tasarlanmış bir web uygulaması şuna benzer:

Image

  • istemci, isteklerini uygulamanın "denetleyici" adı verilen belirli bir birimine gönderir
  • kontrolör, istemcinin isteğini analiz eder ve yürütülmesini sağlar. Bunun için, uygulamanın iş mantığını barındıran sınıflar ve veri erişim sınıflarından yardım alır.
  • İsteğin yürütülmesinin sonucuna göre, denetleyici müşteriye yanıt olarak belirli bir sayfayı göndermeyi seçer

Modelimizde, tüm istekler web uygulamasının orkestra şefi olan tek bir denetleyiciden geçer. Bu modelin avantajı, her istek öncesinde yapılması gereken her şeyi denetleyicide bir araya getirebilmemizdir. Örneğin, uygulamanın kimlik doğrulaması gerektirdiğini varsayalım. Bu işlem tek seferlik olarak yapılır. Kimlik doğrulama başarılı olduğunda, uygulama oturuma az önce kimlik doğrulaması yapan kullanıcıyla ilgili bilgileri kaydeder. Bir istemci, kimlik doğrulaması yapmadan doğrudan uygulamanın bir sayfasını çağırabileceğinden, her sayfa oturumda kimlik doğrulamasının yapıldığını kontrol etmelidir. Tüm istekler tek bir denetleyiciden geçtiği için, bu işi denetleyici üstlenebilir. İsteğin daha sonra iletileceği sayfaların bunu yapmasına gerek kalmaz.

4.3.2. Oturum olmadan MVC uygulamasını denetlemek

Şu ana kadar gördüklerimize göre, [global.asax] dosyasının denetleyici rolünü üstlenebileceği düşünülebilir. Nitekim, tüm isteklerin bu dosyadan geçtiği bilinmektedir. Dolayısıyla her şeyi denetlemek için uygun konumdadır. Aşağıdaki uygulama bunu bu amaçla kullanmaktadır. Sanal yolu [http://localhost/mvc1/main.aspx] olacaktır. İstemci, ne istediğini belirtmek için URL’nin sonuna action=değer parametresini ekleyecektir. [action] parametresinin değerine göre, [global.asax] denetleyicisi isteği belirli bir sayfaya yönlendirecektir:

  1. action parametresi tanımlanmamışsa veya action=main ise [main.aspx]
  2. action=action1 ise [action1.aspx]
  3. action, 1. ve 2. durumlara uymuyorsa: [inconnu.aspx]

[main.aspx, action1.aspx, inconnu.aspx] sayfaları, kendi görüntülenmelerine neden olan [action] değerini görüntülemekle yetinir. Aşağıda bu uygulamanın sekiz dosyasını listeliyoruz ve gerektiğinde açıklamalar ekliyoruz:

[global.asax]

<%@ Application src="Global.asax.vb" Inherits="Global" %>

[global.asax.vb]


Imports System
Imports System.Web
Imports System.Web.SessionState

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
        ' yapılması gereken eylemi alıyoruz
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "main"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If
        ' eylemi istek bağlamına yerleştirilir
        Context.Items("action") = action
        ' eylemi yürütüyoruz
        Select Case action
            Case "main"
                Server.Transfer("main.aspx", True)
            Case "action1"
                Server.Transfer("action1.aspx", True)
            Case Else
                Server.Transfer("inconnu.aspx", True)
        End Select
    End Sub
End Class

Dikkat edilmesi gereken noktalar:

  • Uygulamaya yapılan her yeni isteğin başlangıcında otomatik olarak yürütülen [Application_BeginRequest] prosedüründe, istemciden gelen tüm istekleri yakalıyoruz.
  • Bu prosedürde, istemcinin HTTP isteğinin bir kopyası olan [Request] nesnesine erişebiliyoruz. [http://localhost/mvc1/main.aspx?action=xx] biçiminde bir URL beklediğimiz için, [Request.QueryString] koleksiyonunda [action] anahtarını arıyoruz. Eğer bu anahtar yoksa, varsayılan olarak action değerini main olarak ayarlıyoruz.
  • [action] parametresinin değeri, [Context] nesnesine yerleştirilir. [Application, Session, Request, Response, Server] nesneleri gibi, bu nesne de küreseldir ve tüm kodlardan erişilebilir. Bu nesne, isteğin burada olduğu gibi birden fazla sayfa tarafından işlenmesi durumunda sayfadan sayfaya aktarılır. Yanıt müşteriye gönderilir gönderilmez silinir. Dolayısıyla ömrü, isteğin işlenme süresi kadardır.
  • [action] parametresinin değerine göre, istek uygun sayfaya aktarılır. Bunun için, mevcut isteği başka bir sayfaya aktarmayı sağlayan yöntemine sahip küresel [Server] nesnesi kullanılır. Bu nesnenin ilk parametresi hedef sayfanın adıdır; ikinci parametre ise [QueryString] ve [Form] koleksiyonlarının hedef sayfaya aktarılıp aktarılmayacağını belirten bir boole değeridir. Burada cevap evettir.

[main.aspx] ve [main.aspx.vb] dosyaları:


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
    <head>
        <title>main</title></head>
    <body>
        <h3>Page [main]</h3>
        Action : <% =action %>
    </body>
</HTML>

Public Class main
    Inherits System.Web.UI.Page

    Protected action As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' devam eden eylemi alıyoruz
        action = Me.Context.Items("action").ToString
    End Sub
End Class

[main.aspx.vb] denetleyicisi, bağlamdan [action] anahtarının değerini alır; bu değer, sunum kodu tarafından görüntülenir. Burada, [Context] nesnesinin, aynı müşteri isteğini işleyen farklı sayfalar arasında geçişini göstermeyi amaçlıyoruz. [action1.aspx] ve [inconnu.aspx] sayfaları da benzer şekilde çalışır:

[action1.aspx]


<%@ Page src="action1.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="action1" %>
<HTML>
    <head>
        <title>action1</title></head>
    <body>
        <h3>Page [action1]</h3>
        Action : <% =action %>
    </body>
</HTML>

[action1.aspx.vb]

Public Class action1
    Inherits System.Web.UI.Page

    Protected action As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
         ' devam eden eylemi alınır
        action = Me.Context.Items("action").ToString
    End Sub
End Class

[inconnu.aspx]


<%@ Page src="inconnu.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="inconnu" %>
<HTML>
    <head>
        <title>inconnu</title></head>
    <body>
        <h3>Page [inconnu]</h3>
        Action : <% =action %>
    </body>
</HTML>

[inconnu.aspx.vb]

Public Class inconnu
    Inherits System.Web.UI.Page

    Protected action As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
         ' devam eden eylemi alınır
        action = Me.Context.Items("action").ToString
    End Sub
End Class

Test amacıyla, yukarıdaki belgeler <application-path> klasörüne yerleştirilir ve Cassini (<application-path>,/mvc1) parametreleriyle başlatılır. [http://localhost/mvc1/main.aspx] URL'sini çağırıyoruz:

Image

İstek, [action] parametresini göndermedi. [global.asax.vb] uygulama denetleyicisi kodu, [main.aspx] sayfasını döndürdü. Şimdi [http://localhost/mvc1/main.aspx?action=action1] URL'sini istiyoruz:

Image

[global.asax.vb] uygulama denetleyicisi kodu, [action1.aspx] sayfasını oluşturdu. Şimdi [http://localhost/mvc1/main.aspx?action=xx] URL'sini istiyoruz:

Image

Eylem tanınmadı ve [global.asax.vb] denetleyicisi, [inconnu.aspx] sayfasını döndürdü.

4.3.3. Oturum kullanarak MVC uygulamasını kontrol etme

Çoğu zaman, bir istemcinin bir uygulamaya yönelik farklı istekleri arasında bilgi paylaşımı gerekir. Bu soruna yönelik olası bir çözüm gördük: paylaşılacak bilgileri isteğin [Session] nesnesinde depolamak. Bu nesne, tüm istekler tarafından paylaşılır ve bilgileri (anahtar, değer) biçiminde depolayabilir; burada anahtar, [String] türündedir ve değer, [Object]'ten türetilen herhangi bir türdür.

Önceki örnekte, farklı eylemlere ilişkin sayfalar, [global.asax.vb] dosyasındaki [Application_BeginRequest] yordamında çağrılıyordu:


    Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
        ' yapılması gereken eylemi alınır
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "main"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If
        ' eylemi isteğin bağlamına yerleştirilir
        Context.Items("action") = action
        ' eylemi yürütülür
        Select Case action
            Case "main"
                Server.Transfer("main.aspx", True)
            Case "action1"
                Server.Transfer("action1.aspx", True)
            Case Else
                Server.Transfer("inconnu.aspx", True)
        End Select
    End Sub

[Application_BeginRequest] prosedüründe [Session] nesnesine erişilemiyor. Yürütme işleminin aktarıldığı sayfada da durum aynı. Dolayısıyla bu şablon, oturum içeren bir uygulama için kullanılamaz. Denetleyici rolünü herhangi bir sayfaya, örneğin [default.aspx]'e atayabiliriz. Bu durumda [global.asax, global.asax.vb] dosyaları ortadan kalkar ve yerlerini [default.aspx, default.aspx.vb] dosyaları alır:

[default.aspx]

<%@ Page codebehind="default.aspx.vb" Inherits="vs.controleur" %>

[default.aspx.vb]


Imports System
Imports System.Web
Imports System.Web.SessionState

Public Class controleur
    Inherits System.Web.UI.Page

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' yapılacak eylemi alıyoruz
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "main"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If

        ' eylemi isteğin bağlamına yerleştirilir
        Context.Items("action") = action
        ' varsa önceki eylemi alınır
        Context.Items("actionPrec") = Session.Item("actionPrec")
        If Context.Items("actionPrec") Is Nothing Then Context.Items("actionPrec") = ""
        ' geçerli eylemi oturumda saklanır
        Session.Item("actionPrec") = action

        ' eylemi yürütülür
        Select Case action
            Case "main"
                Server.Transfer("main.aspx", True)
            Case "action1"
                Server.Transfer("action1.aspx", True)
            Case Else
                Server.Transfer("inconnu.aspx", True)
        End Select
    End Sub
End Class

Oturum mekanizmasını vurgulamak amacıyla, farklı sayfalar mevcut eylemin yanı sıra bir önceki eylemi de gösterecektir. A1, A2, ..., An şeklinde bir eylem dizisi için, Ai eylemi gerçekleştiğinde, yukarıdaki denetleyici:

  • cari eylem Ai’yi bağlama ekler
  • oturumda, Ai'den önceki Ai-1 eylemini bulur. Eğer böyle bir eylem yoksa (A1 eylemi örneği), önceki eyleme ilişkin boş bir dize ekler.
  • geçerli Ai eylemini Ai-1'in yerine oturuma yerleştirir
  • yürütmeyi uygun sayfaya aktarır

Uygulamanın üç sayfası şunlardır:

[main.aspx]


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
    <HEAD>
        <title>main</title>
    </HEAD>
    <body>
        <h3>Page [main]</h3>
        Action courante :
        <% =action %>
        <br>
        Action précédente :
        <% =actionPrec %>
    </body>
</HTML>

[action1.aspx]


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
    <head>
        <title>action1</title></head>
    <body>
        <h3>Page [action1]</h3>
        Action courante :
        <% =action %>
        <br>
        Action précédente :
        <% =actionPrec %>
    </body>
</HTML>

[inconnu.aspx]


<%@ Page src="main.aspx.vb" Language="vb" AutoEventWireup="false" Inherits="main" %>
<HTML>
    <head>
        <title>inconnu</title>
    </head>
    <body>
        <h3>Page [inconnu]</h3>
        Action courante :
        <% =action %>
        <br>
        Action précédente :
        <% =actionPrec %>
    </body>
</HTML>

Üç sayfa da aynı bilgileri ([action, actionPrec]) görüntülediğinden, üçü de aynı sayfa denetleyicisine sahip olabilir. Bu nedenle, hepsini [main.aspx.vb] dosyasındaki [main] sınıfından türetmişiz:


Public Class main
    Inherits System.Web.UI.Page

    Protected action As String
    Protected actionPrec As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' devam eden eylemi alınır
        action = Me.Context.Items("action").ToString
        ' ve önceki eylemi
        actionPrec = Me.Context.Items("actionPrec").ToString
    End Sub
End Class

Yukarıdaki kod, [default.aspx.vb] uygulama denetleyicisi tarafından bağlama yerleştirilen bilgileri almaktan ibarettir.

Tüm bu dosyalar <application-path> dizinine yerleştirilir ve Cassini, (<application-path>,/mvc2) parametreleriyle başlatılır. Öncelikle [http://localhost/mvc2] URL'si istenir:

Image

[http://localhost/mvc2] URL'si bir klasörü belirtir. Bu durumda, eğer varsa, sunucu tarafından bu klasördeki [default.aspx] belgesinin döndürüldüğünü biliyoruz. Burada herhangi bir eylem belirtilmemişti. Dolayısıyla [main] eylemi gerçekleştirildi. Şimdi [action1] eylemine geçelim:

Image

Mevcut eylem ve önceki eylem doğru şekilde tanımlanmıştır. [xx] eylemine geçelim:

Image

4.4. Conclusion

Artık tüm ASP.ET uygulamalarının temelini oluşturan unsurlara sahibiz. Ancak, tanıtmamız gereken önemli bir kavram daha var: form kavramı. Bu, bir sonraki bölümün konusu olacaktır.