Makale Özeti

Bu makalemizde ASP.NET web sitelerinde, uygulama bazlı olarak işlemleri gerçekleştirmemizi sağlayan Global.asax dosyasını inceleyeceğiz. Application nesnesine ait olayları(event) ele almamızı sağlayan bu dosya ile web projelerimiz için oldukça önemli işlemleri gerçekleştirebileceğimizi göreceğiz. Makalemizin son kısmında 4 farklı kod parçasında Global.asax'ın projelerimizde kullanımına örnekleri inceliyor olacağız.

Makale

"Herşey bir nesnedir" cümlesi aslında .NET Framework'ün temellerini bizlere oldukça iyi anlatmaktadır. .NET platformunda nereye bakarsanız bakın, ne tip proje geliştiriyor olursanız olun heryerde nesnelerle çalıştığınızı ve bu nesnelerinde en tepede System.Object sınıfından(class) kalıtıldığını görürsünüz. ASP.NET web sitelerinde de bu olay aynı şekilde geçerlidir. Bir ASP.NET sayfasına baktığımızda sayfanın aslında arka planda bir class olarak tutuluğunu, sayfalarda kullandığımız sunucu kontrollerinin(TextBox, Button gibi) birer class olduğunu görürürüz. Bu class Page adındaki bir classtan kalıtılarak elde edilir. Olay tabanlı bir mimaride çalıştığımız için sayfa nesnelerimizin belirli bir yaşam döngüsü vardır ve sayfaya her istek geldiğinde sunucuda bu yaşam döngüsü çalışır. Sayfanın PreInit, Load, PreRender... gibi olayları adım adım gerçekleşerek sayfa üretilir, HTML çıktıya dönüştürülür ve çıktı istemciye gönderilir. Bizler programcı olarak yaşam döngüsü içerisindeki bu olayları yakalayıcı metotlarla ele alabilir ve uygun noktalarda sayfaya müdahale edebiliriz. Sayfanın Load olayında çalışan Page_Load metodu çok sık kullandığımız ve sayfa yüklendiği esnada işlemler yapmamızı sağlayan olay yakalayıcı metodumuzdur. Benzer çalışma mantığı TextBox, Calendar, GridView... gibi sunucu kontrolleri içinde geçerlidir.

Uygulamayı hiyerarşik olarak düşündüğümüz zaman kontroller sayfaları, sayfalar da uygulamayı oluşturmaktadır. Peki madem .NET Framework'te herşey bir nesneydi, kontrollerde ve sayfalarda olduğu gibi uygulama da bir nesne midir? Evet, uygulama da bir nesne olarak ele alınabilmektedir. Aslında çokta yabancı olmadığımız Application nesnesi ASP.NET web sitelerinde çalışan uygulamayı temsil eder ve uygulama adına işlemler yapabilmemizi sağlar. Application nesnesinin de tıpkı sayfalarda olduğu gibi belirli bir yaşam döngüsü vardır ve bu yaşam döngüsünü programcı olarak ele alabilmemiz, belirli olaylar tetiklendiğinde işlemler gerçekleştirmemiz mümkündür. Peki bu olayları nasıl ele alabiliriz? Sayfanın olaylarını sayfanın code-behind kısmına yazabildiğimiz metotlarda ele alabilmekteyiz. Uygulamanın olaylarını ise ele alabileceğimiz yer Global.asax uzantılı dosya olacaktır. Global.asax (Global Application Class - Genel Uygulama Sınıfı) dosyası görsel bir arayüzü olmayan ve çalışan uygulamanın metotlarının yakalanmasını sağlayan dosya olarak ASP.NET web sitesi projelerinde kullanılabilmektedir.

Akla gelen ilk sorulardan biri belki de "Neden uygulamanın olaylarını ele alalım ki?" olacaktır. Veya daha iyimser bir soru olarak "Uygulamanın olaylarını ele alarak ne gibi işlemleri gerçekleştirebiliriz?" de akla gelebilir :) Bu yazımızda uygulama olaylarını ve uygulama olaylarını ele alarak ne gibi işlemler gerçekleştirebileceğimiz inceliyor olacağız. Uygulama olaylarını ele almamızı sağlayan Application adında bir nesne olduğundan daha önceden bahsetmiştik. Application nesnesi aslında temel olarak durum yönetimini(state management) sağlayabilmek ve uygulama genelinde geçerli olacak nesneleri saklamak amaçlı kullanılmamaktadır. Fakat Application nesnesinin olaylarını(event) ele alarak uygulamayla ilgili belirli aşamalarında birçok önemli işlemde gerçekleştirilebilir. Bu olayları ele alabileceğimiz yer ise projemizin ana dizininde yer alacak olan Global.asax dosyasıdır. Dilerseniz bir web projesine Global.asax dosyasını ekleyerek uygulama olaylarıyla ilgili ne gibi işlemler yapabileceğimize göz atalım.

Global.asax dosyası web projelerinde standart olarak gelmemektedir. Bu dosyayı projemize eklemek için Solution Explorer'dan projemize sağ tıklayıp Add New Item seçeneğinden Global Application Class seçeneğini kullanabiliriz.

Bir ASP.NET projesine Global.asax dosyasının eklenmesi
Bir ASP.NET projesine Global.asax dosyasının eklenmesi

Eklenen dosyanın içeriğine baktığımızda aslında bir .aspx dosyasının code-behind sayfasına çok benzediğini göreceğiz. Global.asax dosyası uygulama çalışırken arka planda işlemler yapacağı ve görsel olarak kullanıcılara herhangi bir içerik sunmayacağı için sadece sunucuda çalışacak olan kodlardan oluşur. .aspx dosyalarından farklı olarak HTML kodu ve sunucu kontrolü barındırmayacağı için sunucuda çalışacak kodlar <script> etiketi içerisinde yer alır. Dosya açıldığında içerisine eklenmiş olarak gelen beş adet metot yer almaktadır. Bu metotların her birisi aslında Application nesnesinin birer olay yakalayıcı(event handler) metodudur.. Application_Start, Application_End, Application_Error, Session_Start ve Session_End metotları Application nesnesinin en çok kullanılan metotlarından olduğu için içerilerine herbir metodun hangi olaylar gerçekleştiğinde çalışacağı bilgileriyle birlikte dosyaya eklenmiş olarak gelmektedir. Tabii ki Application nesnesinin olayları bu beş metoda bağlanan olaylardan ibaret değildir. Application nesnesinin tüm olaylarının listesini görebilmek için Visual Studio arayüzünü kullanabiliriz. Global.asax dosyası açıkken dosya listelerinin yer aldığı tab'ın hemen altındaki iki seçme listesinden bu listeye erişebiliriz. Bu listeden herhangi bir olayı seçmemiz durumunda dosyamıza olayın yakalayıcı metodu otomatik olarak eklenir. Aşağıdaki şekilde bu listeye nasıl ulaşabileceğimiz görülmektedir. (1 numaralı listeden Application nesnesi seçildiğinde sağdaki 2 numaralı listede olaylar listelenecektir)

Application nesnesinin olaylarının listelenmesi
Application nesnesinin olaylarının listelenmesi

Makalemizin ilerleyen kısımlarında buradaki birkaç önemli olay sayesinde ne gibi işlemler gerçekleştirebileceğimiz üzerine örnekler yapıyor olacağız. Dilerseniz örneklere geçmeden önce Application nesnesinin olaylarından en çok kullanacağımız bazı olayların hangi aşamalarda tetikleneceği ve buralarda ne gibi işlemler yapabileceğimiz konularına değinelim.

- Start: Bir ASP.NET uygulaması ilk çalıştığı anda tetiklenir. Bu olay siteyi barındıran sunucu -ki bu sunucu IIS olacaktır- başladıktan sonra istemciden gelen ilk istekte tetiklenecektir. Buradaki en önemli durumlardan birisi bu olayın sadece ilk istekte tetikleneceği ve uygulama bir daha başlayana kadar tetiklenmeyeceğidir. Ne zaman ki uygulama, web sunucusu veya sunucu bilgisayarı kapatılıp tekrar açılır, Start olayı o zaman tetiklenecektir. Peki uygulama ilk başladığında ne gibi işlemler gerçekleştirilebilir? Uygulama ilk başladığında belirli kaynakların uygulama için açılması gerekiyorsa, belirli nesnelerin oluşturulması gerekiyorsa veya uygulama başlangıcı bir yerlere kaydedilmek (ör. log dosyasına) isteniliyorsa bu event kullanılabilir.

- End: ASP.NET uygulaması sonlandırıldığında tetiklenen olaydır. Web sitesi durdurulduğunda, IIS veya sunucu bilgisayarı durdurulduğunda ya da kapatıldığında tetiklenir. Bu olay içerisinde ise genellikle Start olayında başlatılan, açılan kaynakların, nesnelerin durdurulması ve kapatılması gibi işlemler gerçekleştirilebilir.

- Session_Start: Bir ziyaretçi uygulamanın herhangi bir sayfasını ziyaret ettiğinde tetiklenir. Bu olay sadece istemcinin sadece ilk sayfa isteğinde tetiklenir ve sonraki sayfa isteklerinde tetiklenmez. Aslında sitemize bir ziyaretçinin giriş yaptığının habercisidir. Session'ın timeout süresinin geçmesi durumunda kullanıcı bir talepte daha bulunursa bu olay istemcinin yeni bir kullanıcı olarak sitemize geldiğini düşünerek tekrar tetiklenecektir. Kullanıcının sitemizi ilk ziyareti esnasında gerçekleştirilebilecek işlemler bu olay dahilinde gerçekleştirilebilir.

- Session_End: Sitemizi ziyaret eden kullanıcı Session bilgisinin timeout olması durumunda tetiklenir. Kullanıcı sitemizde oturum açtıktan sonra Session'ın timeout değeri süresince bir işlem yapmazsa bu olay tetiklenir. Genellikle Session_Start'ta yapılan işlemlerle ilişkili olarak kullanıcıların çıkışlarıyla ilgili işlemler gerçekleştirilebilir.

- Error: Uygulamanın herhangi bir dosyasında, herhangi bir anında hata oluşursa bu olay tetiklenir. Bu hata sunucu tarafında çalışacak kodlardan dolayı oluşan bir istisna(exception) olabileceği gibi, HTTP durum kodlarından(404-dosya bulunamadı-,403-erişim yok-... gibi) oluşan herhangi bir hata da olabilir. Özellikle log dosyalarında hata kayıtlarının tutulması işlemi için uygulamamızın en ideal noktası Application'ın Error olayıdır.

- BeginRequest: Uygulamadaki herhangi bir dosyaya talep(request) geldiğinde tetiklenir. Gelen talebin durumuna göre farklı işlemler yapılmak istenirse(farklı sayfalara yönlendirme, farklı içerik görüntüleme, sayfaya veya dosyaya erişimi engelleme... gibi) bu olay ele alınabilir. URL Rewriting veya belirli dosyaya erişimin kontrol edilmesi gibi durumlarda kullanılabilir.

- AuthenticateRequest: FormsAuthentication kullanıldığında kullanıcının sisteme başarılı şekilde giriş yapması durumunda tetiklenecek olan olaydır. Giriş yapan kullanıcının kimliğine bakılarak yetkilendirme, yönlendirme gibi işlemler bu olay sayesinde ele alınabilir. Örneğin giriş yapan kullanıcıyı belirli bir role atama işlemi burada yapılabilir. Yine giriş yapan kullanıcının bilgisayarına cookie gönderme işlemi de bu olay sayesinde kolayca ele alınabilir.

Dilerseniz farklı örnekler üzerinde Global.asax dosyasının farklı kullanım alanlarını inceleyelim.


Örnek-1: Uygulamada oluşacak hataların log dosyasına kaydedilmesi(Error olayının kullanımı)

Bir web uygulamasının hayata girmesinden sonra raporlamak isteyeceğimiz en önemli bilgilerden birisi de uygulamada oluşan hataların kaydedilmesi olacaktır. Zira bizim geliştirme ve test aşamasında göremediğimiz bir çok hata kullanıcıların sayfalarımızı ziyaretleri esnasında oluşabilir. Uygulamamız bir çok dosyadan oluşacağı için her bir dosya içerisinde hata takibi yapmak elbetteki doğru bir pratik olmayacaktır. İşte Application nesnesine ait Error olayı bu noktada uygulamamızın herhangi bir dosyasında oluşacak bir hatada otomatik olarak tetiklenecektir. Tabii ki bu olayın tetiklenmesi bizim için yeterli değildir. Önemli olan hatanın oluşmasını yakalamak olacağı gibi, hatanın sebebi ve hangi dosyada oluşacağı da bizim için önemli bilgilerdir. Request nesnesinin Path özelliği o an hatanın oluştuğu sayfanın yol bilgisini, Server nesnesinin GetLastError metodu ise oluşan hatanın Exception tipinden bilgilerini getirecektir. Aşağıdaki örnek kod parçasında Global.asax dosyasına eklenecek Application_Error metodu ile txt uzantılı bir metin dosyasına kaydedebiliriz.

Global.asax

<%@ Application Language="C#" %>

<%@ Import Namespace="System.IO" %>

 

<script runat="server">

 

    void Application_Error(object sender, EventArgs e)

    {

        //Oluşan hatanın bilgilerini Hatalar.txt adındaki bir dosyaya kaydediyoruz

        StreamWriter sw = new StreamWriter(Server.MapPath("~/Hatalar.txt"), true);

        sw.WriteLine(DateTime.Now.ToString());

        //Server nesnesini GetLastError metodu sunucuda oluşan son hatayı Exception tipinden getirir. Bu da şu an oluşan hata olacaktır

        if (Server.GetLastError().InnerException != null)

            sw.WriteLine(Server.GetLastError().InnerException.Message);

        else

            sw.WriteLine(Server.GetLastError().Message);

 

        //Request nesnesinin Path özelliği şu an istekte bulunulan sayfanın yol bilgisini getirir

        sw.Write(Request.RawUrl != null ? Request.RawUrl : "");

        sw.WriteLine();

        sw.Close();

    }

 

</script>

Kodları incelediğimizde Error isimli Application olayına bağlanan Application_Error metodunda oluşan hatanın Hatalar.txt isimli bir dosyaya kaydedildiğini görüyoruz. Dilerseniz bu ilk Global.asax kod örneğinde önemli birkaç noktaya değinelim:

- Dosya içerisinde uygulama bazlı olaylar ele alınacağı için Application direktifi(<%@ Application ... %>) ile başlamalıdır. Burada kullanılacak dil, eğer varsa dosyanın kalıtıldığı class gibi bilgiler belirlenebilir.
- Yazacağımız kodlar <script> etiketi içerisinde yer alacağı için code-behind dosyalarında olduğu gibi using NamespaceIsmi; şeklinde tanımlamalar yerine Import direktifini(<%@ Import ... %>) kullanarak belirli bir isim alanını dosyamıza dahil edebiliriz. Örneğin bu kod parçasında StreamWriter nesnesini kolay şekilde kullanabilmek için System.IO isim alanı en üst kısımda Import direktifi ile dosyaya eklenmiştir.

Tekrar örneğimize dönecek olursak; uygulama çalıştırılıp sunucuda olmayan bir sayfaya istekte bulunursak veya herhangi bir sayfada Exception oluşturacak bir işlem gerçekleştirirsek bu metodun çalışacağını ve ilgili dosyaya hata ile ilgili temel bilgieri kaydedeceğini görebiliriz. Bu şekilde uygulamamız genelinde oluşacak tüm hataları yakalayabilir ve kayıt işlemlerini gerçekleştirebiliriz.


Örnek-2: Uygulamadaki aktif kullanıcı sayısını saklamak (Session_Start ve Session_End olaylarının kullanımı)

Birçok web sitesinde gördüğümüz "Şu an sitedeki aktif kullanıcı sayısı: x" şeklindeki bilgiler aslında siteye bir ziyaretçi giriş yaptığında bir değişkenin değerini bir arttırma, kullanıcı siteden çıktığında da(oturum kapatıldığında) bu değişkenin değerini bir azaltma mantığıyla çalışır. Yani bu işlemi yapabilmemizin yolu bir kullanıcının oturum açması(Session_Start) ve kullanıcının oturum kapatması(Session_End) durumlarını ele almak olacaktır. Tüm uygulama genelinde geçerli olacak bir değişkenin değerini arttırma ve azaltma işlemini Global.asax dosyasının ilgili iki olayında ele almamız ve uygulama genelinde geçerli olacak global değişkenimizi de Application nesnesi içerisinde saklamamız bu tip bir işlemi kolayca yapabilmemizi sağlar. Aşağıdaki kod parçalarında da sitemizdeki aktif kullanıcı sayısını nasıl saklayabileceğimizi görebilirsiniz.

Global.asax

<%@ Application Language="C#" %>

 

<script runat="server">

 

    void Session_Start(object sender, EventArgs e)

    {

        int onlineKullanici = Convert.ToInt32(Application["kullaniciSayisi"]);

        onlineKullanici++;

        Application.Lock();

        Application["kullaniciSayisi"] = onlineKullanici;

        Application.UnLock();

        //Lock metodu anlık olarak uygulama nesnesini farklı bir kullanıcının erişimine kapatırken, UnLock metodu da kapatılan nesneyi tekrar açar

    }

 

    void Session_End(object sender, EventArgs e)

    {

        int onlineKullanici = Convert.ToInt32(Application["kullaniciSayisi"]);

        onlineKullanici--;

        Application.Lock();

        Application["kullaniciSayisi"] = onlineKullanici;

        Application.UnLock();

    }

</script>

Herhangi bir dosya içerisinden Application nesnesinde saklanan değeri görüntülenmek için lblAktifZiyaretci adındaki bir Label kontrolüne lblAktifZiyaretci.Text = Application["kullaniciSayisi"]; şeklindeki atama yeterli olacaktır.


Örnek:3 - Uygulama genelinde geçerli olacak ve heryerden erişilebilecek nesneler oluşturmak(Application_Start olayının kullanımı)

Uygulama içerisindeki tüm dosyalardan erişilebilir bir nesne tanımlamak için yine Global.asax dosyası ve dolayısıyla Application nesnesi kullanılabilir. Application nesnesine atanan bir değer uygulama çalıştığı sürece bellekte tutulacağı için App Application'un Start olayında bu nesneye bir değer atamak bizim için yeterli olacaktır. Aslında uygulama genelinde erişilebilir değerler saklamak için web.config dosyasının içerisinde yer alan <appSettings> düğümü kullanılabilir; fakat saklanacak değer key-value çifti şeklinde değilse ya da uygulama başladığı anda dinamik olarak oluşturulmak isteniliyorsa Global.asax dosyasını kullanmak en doğru çözüm olacaktır. Aşağıdaki kod parçasında Application_Start metodun Application nesnesine bazı değerler eklenmiştir. Bu değerler uygulama açık olduğu sürece, tüm dosyalarda erişilebilir olacaktır.

Global.asax

<%@ Application Language="C#" %>

<%@ Import Namespace="System.Collections.Generic" %>

 

<script runat="server">

 

    void Application_Start(object sender, EventArgs e)

    {

        string[] renkler = new string[] {"Beyaz", "Sarı", "Pembe", "Mor" };

        Application["renkListesi"] = renkler;

 

        Application["resimKlasor"] = Server.MapPath("v2/images/resimler");

    }

</script>

Uygulama çalıştığı sürece bellekte saklanacak olan Application nesnelerine uygulamadaki herhangi bir dosyadan Application["resimKlasör"] şeklinde erişilebilir.


Örnek-4: Gelen talepleri inceleyerek farklı işlemler gerçekleştirmek (BeginRequest olayı)

Global.asax dosyası ile web sitemizdeki herhangi bir dosyaya gelen istekleri yakalayabilme ve isteğin durumuna göre de farklı işlemler yapabilme şansımız vardır. Application'ın BeginRequest adındaki olayı sitemizdeki tüm dosyalara gelen isteklerde tetiklenecektir. Dolayısıyla bu olayın metodu içerisinde yazacağımız kodlarla gelen isteğe(yani Response nesnesine) erişebiliriz. İstenen dosyanın uzantısına göre, dosyanın yoluna göre, içerdiği QueryString bilgisine göre işlemler yapılabileceği gibi, kullanıcının tarayıcısına veya IP numarasına bakılarak da farklı işlemlerin gerçekleştirilmesi bu olay içerisinde ele alınabilir. Şöyle bir durum üzerinde bu tip bir işlemi nasıl yapabileceğimizi inceleyelim: Sitenizde yoğun şekilde QueryString bilgilerini toplarak veritabanında sorgular çalıştırıyorsunuz. Dolayısıyla kullanıcıların QueryString içerisine tehlikeli olabilecek SQL komutları eklemesi(SQL Injection) sizi tehdit edecektir. Böyle bir durumda elbetteki en kolay yöntem parametre kullanmaktır, fakat parametreyi kullanmayı unuttuğunuz bir sayfanız varsa?

İşte böyle bir durumda projenin sağlığını biraz daha garanti altına almak istiyorsak Global.asax dosyasında gelen isteğin QueryString bilgilerini kontrol ederek tehlikeli bir ifade içermesi durumunda kullanıcıyı hata sayfasına yönlendirebiliriz. Aşağıdaki kod parçasında gelen her istekteki QueryString bilgisinin kontrol edilmesi ve tehlikeli ifade bulunması durumunda hata sayfasına yönlendirilmesi gerçekleştirilmiştir.

Global.asax

<%@ Application Language="C#" %>

 

<script runat="server">

 

    protected void Application_BeginRequest(object sender, EventArgs e)

    {

        try

        {

            string q = this.Context.Request.QueryString.ToString();

            // Tehlikeli olabilecek kelimeler burada kontrol edilebilir

            if (q.Contains("delete") || q.Contains("update") || q.Contains("drop") || q.Contains("exec"))

                Response.Redirect("hata.html");

        }

        catch

        {

            // Request nesnesi bazı isteklerde null değer getirebildiği için sorunla karşılaşmamak için ifadeleri try-catch bloğuna aldık, fakat hata durumunda bir işlem yapmayacağımız için de bu kısmı boş bıraktık

        }

    }

</script>

 

 

Görüldüğü gibi Global.asax dosyası çok karmaşık veya yapılması zahmetli gibi görünen birçok işlemi çok basit şekilde yönetebilmemizi sağlıyor. Bu makalemizde Global.asax dosyasını etkin şekilde web uygulamalarında kullanmamızın farklı yollarını inceledik. Uygulama bazlı işlemlerde gerçekten işimizi oldukça kolaylaştıran bu dosya ve Application nesnesi ASP.NET'in bize sunmuş olduğu en önemli kolaylıklardan birisidir. Bu makalede inceleme şansı bulamadığımız diğer Application olaylarını da inceleyecek olursanız, gelecekte karşınıza çıkabilecek bu tip durumlara daha kolay çözüm üretebilirsiniz. Bir başka makalede görüşmek dileğiyle.


Uğur UMUTLUOĞLU
www.umutluoglu.com
www.nedirtv.com