Quantcast
Channel: Burak Selim Senyurt - Asp.NetBurak Selim Senyurt0.0000000.000000
Viewing all 19 articles
Browse latest View live

Caching Mekanizmasını Anlamak - 2

$
0
0

Değerli Okurlarım, Merhabalar

Hatırlayacağınız gibi bir önceki makalemizde, web uygulamalarında caching mekanizmasını incelemeye başlamış ve ara belleğe alma tekniklerinden Output Cache yapısını incelemiştik. Output Cache tekniğinde bir sayfanın tamamının HTML içeriği ara belleğe alınmaktaydı. Oysa çoğu zaman sayfamızda yer alan belirli veri kümelerinin ara bellekte tutulmasını isteyebiliriz. Örneğin, bir alışveriş sitesinin pek çok kısmı dinamik olarak değişebilirken satışı yapılan ürünlerin yer aldığı kategori listeleri çok sık değişmez. Hatta uzun süreler boyunca aynı kalabilirler. İşte böyle bir durumda sayfanın tamamını ara belleğe almak yerine sadece kategori listesini sunan veri kümesini ara belleğe almak daha mantıklıdır. Data Caching olarak adlandırılan bu teknikte çoğunlukla veri kümeleri ara belleğe alınır. Data Caching tekniğinde verileri ara belleğe almak için System.Web.Caching isim alanında yer alan Cache sınıfı ve üyeleri kullanılmaktadır.

Cache sınıfı sealed tipinde olup kendisinden türetme yapılmasına izin vermez. Bununla birlikte, her bir Application Domain için yanlız birCache nesne örneği oluşturulur ve kullanılır.

Cache sınıfına bir veri kümesini eklemek bu veriyi ara belleğe almak demektir. Bunun için aşağıdaki 4 aşırı yüklenmiş prototipe sahip olan ve bize pek çok imkan sağlayan Insert metodunu kullanabiliriz.

public void Insert(stringkey,objectvalue);
public void Insert(stringkey, object value,CacheDependencydependencies);
public void Insert(stringkey,objectvalue,CacheDependencydependencies,DateTime absoluteExpiration,TimeSpanslidingExpiration);
public void Insert(stringkey, object value, CacheDependencydependencies, DateTimeabsoluteExpiration,TimeSpan slidingExpiration,   CacheItemPrioritypriorityCacheItemRemovedCallbackonRemoveCallback);

Insert metodu ara belleğe alınan veri kümesinin durumuna ilişkin olarak çeşitli imkanlar sunar. Örneğin veri kümesinin ara bellekte ne kadar süre ile tutulacağı veya ne kadar süre bu veriye erişilmez ise ara bellekten silineceğinin belirlenmesi vb...Şimdi bu imkanları test etmeden önce basit olarak bir veri kümesini Cache nesnesine nasıl ekleyeceğimizi inceleyeceğiz.

private SqlConnection con;
private SqlCommand cmd;
private SqlDataAdapter da;
private DataTable dt;

/*Categories tablosundan CategoryName alanının değerlerini alıyoruz ve bir DataTable nesnesine aktarıyoruz.*/
private void KategorileriAl()
{
    con=new SqlConnection("data source=BURKI;initial catalog=Northwind;integrated security=SSPI");
    cmd=new SqlCommand("SELECT DISTINCT CategoryName FROM Categories",con);
    da=new SqlDataAdapter(cmd);
    dt=new DataTable();
    da.Fill(dt);
}

private void Page_Load(object sender, System.EventArgs e)
{
    //Güncel saat bilgisini label kontrolüne yazdırıyoruz.
    Label1.Text=DateTime.Now.ToLongTimeString();
    /*Eğer ara bellekte kategori isimli Cache nesnesinin içeriği null ise bu durumda verileri çekiyoruz ve DataTable içeriğini ara belleğe Cache sınıfının Insert metodu ile alıyoruz.*/
    if(Cache["kategori"]==null)
    {
        KategorileriAl();
        Cache.Insert("kategori",dt,null,DateTime.Now.AddMinutes(5),Cache.NoSlidingExpiration); /* Veriler 5 dakika süreyle ara bellekte saklanacak daha sonra ise silinecektir.*/
    }
    /*DataGrid kontrolüne veri kaynağı olarak ara bellekteki kategori isimli Cache nesnesinin içeriğini veriyoruz. Bunu yaparken uygun türe dönüştürme işlemini yapıyoruz.*/
    DataGrid1.DataSource=(DataTable)Cache["kategori"];
    DataGrid1.DataBind();
}

Uygulamamızda, Northwind veritabanında yer alan Categories isimli tablodan CategoryName değerlerini çekiyoruz ve elde ettiğimiz DataTable nesnesini 5 dakika süreyle ara bellekte tutumak üzere Insert metodu ile Cache nesnesine ekliyoruz. Uygulamayı ilk çalıştırışımızda aşağıdaki ekran görüntüsünü elde ederiz.

Sayfayı yenilediğimizde veya başka bir tarayıcı penceresinde tekrardan talep ettiğimizde sürenin düzenli olarak değiştiğini görürüz. Şimdi 5 dakika dolmadan tablodaki verilerde, örneğin Beverages alanının değerini [Beverages] olarak değiştirdiğimizi düşünelim.

Daha sonra sayfayı tekrar çağıralım.

Görüldüğü gibi Beverages değerini değiştirmemize rağmen yapılan değişiklikler uygulamamıza yansımamıştır. Bu, verinin ara bellekten Cache nesnesi vasıtasıyla çekildiğinin bir ispatıdır. Veri ara belleğe alındıktan 5 dakika sonra aynı sayfa tekrar talep edilir ise bu kez verinin güncel hali ekrana gelecektir. Burada veriyi Cache nesnesine alırken kullandığımız Insert metodunda Absolute Expiration (Tam Süre Sonu) zamanı belirlenmiştir. Bu süre, verinin ara bellekten kesin olarak ne zaman atılacağını söylemektedir. Bununla birlikte dilersek ara bellekte bulunan veri kümesine olan erişim sıklığına göre bir Sliding Expiration ( Kayan Süre Sonu ) süreside belirleyebiliriz. Buna göre,

Ara bellekteki verilere belirtilen Sliding Expiration ile belirtilen süre zarfında erişilmez ise bu süre sonunda bellekten atılırlar. Eğer süre zarfı içinde ara bellekteki verilere sürekli erişiliyorsa Sliding Expiration süresi geçse dahi veriler ara bellekten atılmaz ve durumlarını korurlar.

Sürekli bellekte kalmak deyimi tabiki sistem kaynakları azalıp ara bellek verileri otomatik olarak atılınca veya web sunucusu herhangibir neden ile restart olunca geçerli değildir. Şimdi dilerseniz Sliding Expiration durumunu incelemeye çalışalım. Bunun için tek yapmamız gereken örneğimizdeki Insert metodunu aşağıdaki ile değiştirmek olacaktır.

Cache.Insert("kategori",dt,null,Cache.NoAbsoluteExpiration,TimeSpan.FromMinutes(3));

Olayı daha iyi anlayabilmek için aşağıdaki şekli inceleyebiliriz.

Sayfa ilk talep edilip veriler ara belleğa alındığında, Sliding Expiration süresinin 5 dakika ilerisini gösterecek şekilde ayarlandığını düşünelim. Bu süre dolmadan önce ara bellekteki veri tekrardan talep edilirse, Cache nesnesinin içeriğinin boşaltılma süresi şekilden de görüldüğü gibi ilerki bir zamana (o anki andan 5 dakika sonrasına) sarkacaktır.

Insert metodunun parametrelerine dikkat edecek olursanız, ara bellekteki verilerin durumlarının CacheDependency sınıfına ait nesne örnekleri ile başka bir nesneye bağlı olabileceğini görürsünüz. Bu bağımılılıkta çoğunlukla fiziki dosyalar göz önüne alınır. Örneğin, bir XML dosyasındaki veriyi ara belleğe alarak kullandığımızı düşünelim. Bu veriler pekala güncel haber başlıklarını veya bir alışveriş sitesindeki ürünlerin kategorilerini gösterebilir. XML dosyasında meydana gelecek olan güncellemeleri anında ara belleğe yansıtmak için, Cache nesnesini bu XML dosyasına bağımlı hale getirebiliriz. Şimdi bunun nasıl yapılabileceğini incelemeye çalışalım. Yukarıda geliştirdiğimiz örneğimizdeki kodlarımızı aşağıdaki gibi değiştirelim.

Kategoriler.xml dosyasının içeriği;

<?xml version="1.0" encoding="utf-8" ?>
    <Kategoriler>
        <Tipi>Muzik CD</Tipi>
        <Tipi>Kitap</Tipi>
        <Tipi>Film DVD</Tipi>
        <Tipi>Muzik DVD</Tipi>
        <Tipi>Elektronik Eşyalar</Tipi>
        <Tipi>Bilgisayar</Tipi>
        <Tipi>Laptop</Tipi>
</Kategoriler>

default.aspx

private SqlConnection con;
private SqlCommand cmd;
private SqlDataAdapter da;
private DataTable dt;
private DataSet ds;

/*KategoriAlXML metodu Kategoriler.xml dosyası içinden verileri alır ve DataSet’ e yükler.*/
private void KategoriAlXML()
{
    ds=new DataSet();
    ds.ReadXml(Server.MapPath("Kategoriler.xml"));
}

private void Page_Load(object sender, System.EventArgs e)
{
    //Güncel saat bilgisini label kontrolüne yazdiriyoruz.
    Label1.Text=DateTime.Now.ToLongTimeString();
    if(Cache["kategori"]==null)
    {
        KategoriAlXML();
        /* Cache nesnesine DataSet içerisindeki xml dosyasından okunan içeriğe ait veri kümesi yüklenir. Bu yükleme işlemi yapılırken bir CacheDependency nesnesi ile Cache nesnesinin içeriğinin güncelliği belirtilen XML dosyasına bağlanır.*/
        Cache.Insert("kategori",ds.Tables[0],new CacheDependency(Server.MapPath("Kategoriler.xml")));
    }
    /*DataGrid kontrolüne veri kaynagi olarak ara bellekteki kategori isimli Cache nesnesinin içerigini veriyoruz. Bunu yaparken uygun türe dönüstürme islemini yapiyoruz.*/
    DataGrid1.DataSource=(DataTable)Cache["kategori"];
    DataGrid1.DataBind();
}

Burada, Cache nesnesinin içeriğinin durumunu xml dosyamıza bağlayabilmek için CacheDependency sınıfına ait bir nesne örneği oluşturulmuştur. Burada CacheDependency sınıfına ait nesne örneği oluşturulurken yapıcı metoda xml dosyasının sanal adresi atanmıştır.

new CacheDependency(Server.MapPath("Kategoriler.xml"))

Uygulamamızı çalıştırdığımızda ilk olarak aşağıdaki ekran görüntüsünü elde ederiz.

Şimdi Kategoriler.xml dosyasının içeriğinde değişiklik yapalım ve bu değişiklikleri kaydedelim. Örneğin aşağıdaki gibi bir kaç elemanın bilgisini değştirip yeni bir eleman ekleyelim.

Bu değişikliklerden sonra sayfayı tekrardan talep edersek, Cache nesnesinin içerdiği verilerin son yapılan güncellemelere göre yenilendiğini görürüz. Cache nesnesindeki veriyi bir dosyaya yukarıdaki gibi bağımlı hale getirdiğimizde, sayfaya yapılan her talepde dosyanın içeriğinin değişip değişmediği kontrol edilir. Eğer bir değişiklik var ise, Cache nesnesinin içerdiği veri ara bellekten atılır. Biz uygulamamızda Cache nesnesini null olup olmadığına göre yükleme yaptığımız için dosyadaki güncelleme sonucu verinin bu son halini ara belleğe almış ve DataGrid içeriğini yenilemiş oluruz.

Asp.Net 2.0’ da bir Cache nesnesinin doğrudan Sql Server üzerindeki bir tabloya bağımlı hale getirilebilmesi ve dolayısıyla veritabanı içindeki bir tabloda meydan gelecek değişiklilerin Cache nesnesine anında yanısıtlabilmesi içinde SqlCacheDependency sınıfına ait nesne örneklerinin kullanılabileceği öngörülmektedir. (* Bu durumu gelecek görsel derslerimizden birisinde incelemeye çalışacağız.)

Cache nesnelerinin bellekten atılması zamana, dosyaya bağlanabileceği gibi, sistem kaynaklarının azalması durumunda da gerçekleşen bir olaydır. Sistem kaynaklarının azalması ve ara bellekteki nesnelerin atılması gerektiği durumlarda Cache nesnelerinin sahip olduğu öncelikler göz önüne alınır. Her ne sebep ile olursa olsun bir Cache nesnesinin içeriği ara bellekten atıldığında otomatik olarak çalışmasını istediğimiz metodlar bildirebiliriz. Yani CallBack metod tekniğini Cache nesneleri içinde kullanabiliriz. Örneğin ara bellekte tutulan bir nesnenin zaman aşımına uğraması nedeni ile silindiğinde otomatik olarak callback metodu devreye girerek güncel halinin tekrardan ara belleğe alınması sağlanabilir. Ya da Cache nesnesi Remove metodu ile açıkça ara bellekten atıldığı durumlarda CallBack metodlarını çalıştırabiliriz. Burada bir CallBack metodunun çağırılabilmesi için, CacheItemRemovedReason temsilcisi (delegate) tipinden bir nesne örneğinden faydalanılır. CacheItemRemovedReason temsilcisi aşağıdaki prototipe uyan metodlar işaret edebilir.

public delegate void CacheItemRemovedCallback(string anahtar,object deger, CacheItemRemovedReason sebep);

anahtar parametresi Cache nesnesinin key değerine karşılık gelir. deger parametresi ise ilgili Cache nesnesinin taşıdığı veriye sahiptir. Son parametre ise CacheItemRemovedReason numaralandırıcısı (enum sabiti) tipinden bir değerdir ve CallBack metodunun çağırılma nedenini bir başka deyişle Cache nesnesinin hangi sebepten dolayı ara bellekten atıldığının belirlenmesinde kullanılır. CacheItemRemovedReason numaralandırıcısının sahip olduğu değerler aşağıdaki tabloda belirtilmektedir.

CacheItemRemovedReason Numaralandırıcı DeğeriAçıklama
DependencyChangedHerhangibir bağımlılık nedeni ile Cache nesnesinin içeriği ara bellekten atılmıştır. Örneğin Cache nesnesine bağladığımız XML dosyasında yapılan bir değişiklik buna neden olabilir.
ExpiredCache nesnesinin içeriği zaman aşımları nedeni ile ara bellekten atılmıştır.
RemovedCache nesnesinin içeriği Remove metodu ile açıkça ara bellekten atılmıştır. Yani programatik olarak Cache nesnesinin Remove metodu ilgili öğeye uygulanmıştır.
UnderusedSistem kaynaklarının azalması sonucunda web sunucusu, Cache nesnelerinin içeriğini sahip oldukları önem sıralarına göre ara bellekten atmaya başlamıştır.

Şimdi CallBack tekniğinin nasıl uygulandığını basit bir örnek ile incelemeye çalışalım.

private CacheItemRemovedCallback delCallBack=null;
private SqlConnection con;
private SqlCommand cmd;
private SqlDataAdapter da;
private DataTable dt;
private static string m_Sebep;
private static bool m_Durum=false;

private void KategorileriAl()
{
    con=new SqlConnection("data source=BURKI;initial catalog=Northwind;integrated security=SSPI");
    cmd=new SqlCommand("SELECT DISTINCT CategoryName FROM Categories",con);
    da=new SqlDataAdapter(cmd);
    dt=new DataTable();
    da.Fill(dt);
}

private void GeriBildirimMetodu(string anahtar,object deger,CacheItemRemovedReason sebep)
{
    m_Sebep=sebep.ToString();
    m_Durum=true;
}
private void btnRemove_Click(object sender, System.EventArgs e)
{
    if(Cache["Nesne1"]!=null)
    {
        Cache.Remove("Nesne1");
    }
}
private void btnCacheEkle_Click(object sender, System.EventArgs e)
{
    delCallBack=new CacheItemRemovedCallback(this.GeriBildirimMetodu);
    if(Cache["Nesne1"]==null)
    {
        KategorileriAl();
        Cache.Insert("Nesne1",dt,null,DateTime.Now.AddSeconds(10),Cache.NoSlidingExpiration,CacheItemPriority.High,delCallBack);
        m_Durum=false;
    }
}
private void WebForm1_PreRender(object sender, System.EventArgs e)
{
    DataGrid1.DataSource=Cache["Nesne1"];
    DataGrid1.DataBind();
    if(m_Durum)
        lblDurum.Text=m_Sebep;
    else
        lblDurum.Text="";
}

Yukardaki örnekte, Cache nesnesine DataTable içeriğini eklerken Insert metodunun aşağıdaki versiyonu kullanılmıştır.

Cache.Insert("Nesne1",dt,null,DateTime.Now.AddSeconds(10),Cache.NoSlidingExpiration,CacheItemPriority.High,delCallBack);

Burada delCallBack, CallBack metodumuz olan GeriBildirimMetodunu işaret eden CacheItemRemovedCallback tipindeki temsilcimidir. Kullanıcı Remove işlemini gerçekleştirdiğinde veya Cache nesnesi zaman aşımı nedeni ile ara bellekten atıldığında, geri bildirim metodu çalışacaktır. Örneğimizi çalıştırdığımızda ve 10 saniyelik zaman aşımı süresi dolmadan Ekle ve daha sonra Çıkart başlıklı butonlara tıkladığımızda aşağıdaki ekran görüntüsünü elde ederiz.

Eğer 10 saniye süresi dolduktan sonra Çıkart başlıklı butona basarsak Remove metodu çalıştırılmayacak ancak geri bildirim metodumuz çalışarak nesnenin ara bellekten atılma nedeni Expired olarak değişecektir.

CallBack tekniğinin uygulanışını daha iyi kavrayabilmek için, örnekleri debug modunda çalıştırıp breakpoint’ ler vasıtısayıla kodları izlemenizi öneririm. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşmek dileğiyle hepinize mutlu günler dilerim.

Örnek uygulama için tıklayın.

Burak Selim ŞENYURT

selim@bsenyurt.com


Web Uygulamalarında Custom Paging

$
0
0

Değerli Okurlarım Merhabalar,

Geliştirdiğimiz web uygulamalarında özellikle DataGrid kontrollerini kullandığımızda sayfalama işlemini sıkça kullanırız. Genellikle sayfalama işlemlerini var sayılan hali ile kullanırız. Bu modele göre grid üzerinde sayfalama yapabilmek için PageIndexChanged olayını ele almamız gerekir. Burada grid kontrolüne yeni sayfa numarasını DataGridPageChangedEventArgs parametresinin NewPageIndex değeri ile verir ve bilgilerin tekrardan yüklenmesini sağlayacak uygulama kodlarımızı yürütürüz. Tipik olarak bu tarz bir kullanım aşağıdaki kod parçasında olduğu gibi yapılmaktadır.

private void Doldur()
{
    //Bağlantının açılması, verilerin çekilmesi ve çekilen verilerin DataGrid kontrolüne bağlanması
}

private void DataGrid1_PageIndexChanged(object source, DataGridPageChangedEventArgs e)
{
    DataGrid1.CurrentPageIndex=e.NewPageIndex;
    Doldur();
}

Buradaki yaklaşım gerçekten işe yaramaktadır. Ancak aslında performans açısından bazı kayıplar söz konusudur. Çünkü bu tip sayfalama tekniğini kullanırken, sayfa linklerine her basışımızda ilgili veri kaynağındaki tüm veriler çekilmekte ve çekilen veri kümesi üzerinde ilgili sayfaya gidilmektedir. Bu elbetteki büyük boyutlu veri kümeleri ile çalışırken dikkate alınması gereken bir durumdur. Nitekim performansı olumsuz yönde etkileyecektir. Her ne kadar caching (tampon belleğe almak) teknikleri ile sorunun biraz olsun üstesinden gelinebilecek olsa da daha etkili çözümler üretebiliriz.

Büyük boyutlu veri kümeleri ile çalışırken uygulanan varsayılan sayfalama tekniği hız kaybına neden olarak performansı olumsuz yönde etkileyebilir.

İşte bu makalemizde özel sayfalama tekniklerinden bir tanesini incelemeye çalışacağız. Bu teknikte yer alan parçalardan en önemlisi şablon bir tablonun (temporary) kullanılmasıdır. İlk olarak asıl veri kümesini ele alacağımız bir stored procedure (saklı yordam) geliştireceğiz. Bu saklı yordamımız içerisinde asıl veri kümesinin satırlarını alıp temprary bir tabloya aktaracağız. Temporary tablomuzun en büyük özelliği identity tipinde 1 den başlayan ve 1' er artan bir alana sahip olmasıdır. Biz bu alanın değerinden faydalanarak temp tablosu üzerinde sayfalama işlemini uygulayacağız. Buradaki ana fikiri daha iyi anlamak için aşağıdaki şekile bir göz atalım.

Senaryomuzda AdventureWorks2000 veritabanı içerisinde yer alan Employee isimli tabloyu kullanacağız. Bu tablo üzerinde aşağıdaki select sorgusu için sayfalama işlemini gerçekleştireceğiz.
 

SELECT EmployeeID,FirstName,LastName,NationalIDNumber,Title,BirthDate,EmailAddress FROM Employee

Teorimizi şekil üzerinden açıklamaya çalışalım. Önce bir temp tablosu oluşturacağız. Temp tablomuzun alanları yukarıdaki select sorgusundaki alanları karşılayacak şekilde olacak. Bir de ekstradan identity alanımız olacak. Sonra select sorgumuzdaki verileri, temp tablomuz içerisine insert edeceğiz. Ardından temp tablomuz üzerinden yeni bir select sorgusu çalıştıracağız. Ancak bu sefer, Where koşulumuz olacak ve burada identity alanımız için bir aralık belirleyeceğiz. İşte bu aralık sayfanın başlangıç ve bitiş satırlarını belirleyerek istediğimiz sayfaya ait verileri elde etmemizi sağlayacak.

Örneğin, verilerimizi 5' er satırlık sayfalara bölmek istediğimizi düşünelim. Bu durumda 2nci sayfadaki ilk satırın id değerini Baslangic isimli formülümüzden 6 olarak bulabiliriz. Yine 2nci sayfanın bitis satırının değerinide Bitis isimli formülü kullanarak 10 olarak bulabiliriz. Gördüğünüz gibi teori son derece basit. Sayfalamayı gerçekleştirebilmek için aslında temp tablodaki identity alanlarını kullanıyoruz. Son olarak bu işlemlerin hepsini bir stored procedure(saklı yordam) içerisinde barındırarak işlemlerin doğrudan sql sunucusu üzerinde ne hızlı şekilde gerçekleştirilmesini sağlıyoruz. İşte sp kodlarımız.
 

CREATE PROCEDURE WorkSp_Paging

@SayfaNo INT,
@GosterilecekSatirSayisi INT

AS

DECLARE @BaslangicID AS INT
DECLARE @BitisID AS INT
DECLARE @SelectSorgusu AS NVARCHAR(255)

-- Önce Temporary tablomuzu oluşturuyoruz. ID alanı önemli.
CREATE TABLE #TempOfEmployee
(
ID INT IDENTITY(1,1),
EmployeeID INT,
FirstName NVARCHAR(50),
LastName NVARCHAR(50),
NationalIDNumber NVARCHAR(15),
Title NVARCHAR(50),
BirthDate DATETIME,
EmailAddress NVARCHAR(50)
)

-- Employee tablosundaki verileri temporary tablomuza aktarıyoruz.
SET @SelectSorgusu='SELECT EmployeeID,FirstName,LastName,NationalIDNumber,Title,BirthDate,EmailAddress FROM Employee'
INSERT INTO #TempOfEmployee EXEC (@SelectSorgusu)

-- Başlangıç ve bitiş satırlarının ID alanlarının değerlerini belirlemek için formülasyonumuzu kullanıyoruz. SayfaNo ve GösterilecekSatirSayisi sp mize dışarıdan gelen parametreler.
SET @BaslangicID=((@SayfaNo-1)*@GosterilecekSatirSayisi)
SET @BitisID=(@SayfaNo*@GosterilecekSatirSayisi)+1

-- Temporary tablomuz üzerinden ilgili ID aralığındaki veri setini çekiyoruz.
SELECT ID,EmployeeID,FirstName,LastName,NationalIDNumber,Title,BirthDate,EmailAddress FROM #TempOfEmployee
WHERE ID>@BaslangicID AND ID<@BitisID

-- Son olarak sistemde bir karmaşıklığa yer vermemek için temporary tablomuzu kaldırıyoruz.
DROP TABLE #TempOfEmployee
GO

Şimdi sp' mizi asp.net uygulamamızda kullanalım. Özel sayfalama yaptığımız için artık PageIndexChanged olayını kullanamayacağız. Dolayısıyla sayfa linklerini manuel olarak oluşturmamız gerekiyor. Bu durumda DataGrid kontrolümüze ait AllowPaging ve AllowCustomPaging özelliklerinin değerlerini false olarak bırakabiliriz. Eğer sadece ilk, önceki, sonraki, son tarzında linkler oluşturacak isek işimiz kolay. İlgili metodumuza sayfa numarasını ve göstereceğimiz satır sayısını parametre olarak göndermemiz yeterli olacaktır. Ancak sayfa numalarını link olarak sunmak istiyorsak biraz daha fazla çabalamamız gerekecek. Öncelikle asıl işi yapan metodumuzu aşağıdaki gibi oluşturalım. Bu metodumuzda tanımlamış olduğumuz sp' mizi bir SqlCommand nesnesi yardımıyla yürütüyor ve elde ettiğimiz sonuç kümesini DataGrid kontrolümüze bağlıyoruz.
 

private void Doldur(int sayfaNo,int gosterilecekSatirSayisi)
{
    string sql=@"WorkSp_Paging";
    cmd=new SqlCommand(sql,con);
    cmd.CommandType=CommandType.StoredProcedure;
    cmd.Parameters.Add("@SayfaNo",sayfaNo);
    cmd.Parameters.Add("@GosterilecekSatirSayisi",gosterilecekSatirSayisi);
    da=new SqlDataAdapter(cmd);
    dt=new DataTable();
    da.Fill(dt);
    DataGrid1.DataSource=dt;
    DataGrid1.DataBind();
}

Şimdi linklerimizi oluşturalım. Burada kodlama tekniği açısından tamamen serbestsiniz. Ben aşağıdaki gibi kodlamayı tercih ettim. Öncelikle Employee tablosundaki satır sayısını buluyoruz. Sonra sayfalarda gösterilecek satır sayısı ile bunu oranlayarak sayfa sayısını buluyoruz. Sayfa sayısını bulurken dikkat etmemiz gereken nokta artık satırlar için sayfa numarasını bir arttırmamız gerektiğidir. Bunu tespit edebilmek için toplam satır sayısını sayfada gösterilecek satır sayısına bölerken mod operatörü (%) yardımıyla kalan değeri hesaplıyoruz. Eğer kalan değer 0 ise problem yok. Sayfa sayısı tamdır. Ancak 0 değil ise bu durumda sayfa sayısını bir arttırmalıyız ki kalan satırlarıda en sondaki sayfada gösterebilelim.

Bu teknik yardımıyla sayfa sayısını tespit etmemizin ardından her bir sayfa numarası için birer LinkButton kontrolü oluşturuyoruz. Bu kontrollerin Text ve ID özelliklerine ilgili sayfa numarasını set ettikten sonra sayfadaki placeHolder kontrolüne ekliyoruz. Ayrıca LinkButton' lar arasında birer boşluk olmasını sağlamak için Label kontrollerini kullanabiliriz. Şimdi burada önemli olan nokta LinkButton nesnelerinden birisine tıklandığında ilgili sp' mizi çalıştıracak olan metodumuzu çağırabilmek. Bunun için her bir LinkButton nesnesini döngü içerisinde oluştururken aynı Click olay metoduna yönlendiriyoruz. Bu olay metodu içerisinde yaptığımız iş ise ilgili LinkButton kontrolünün ID değerini almak ve Doldur isimli metoda göndermek. Böylece ilgili linke tıklandığında doğruca sp' miz çalıştırılacak ve ilgili sayfaya ait veri seti ekrandaki grid kontrolümüze dolacak.
 

private void SatirSayisiniBul()
{
    cmd=new SqlCommand("SELECT COUNT(*) FROM Employee",con);
    con.Open();
    int toplamSatirSayisi=Convert.ToInt32(cmd.ExecuteScalar());
    con.Close();
    ViewState.Add("TSS",toplamSatirSayisi);
}

private void LinkleriOlustur(int gosterilecekSatirSayisi)
{
    if(ViewState["TSS"]==null)
    {
        SatirSayisiniBul();
    }

    int kalanSatirSayisi=toplamSatirSayisi%gosterilecekSatirSayisi;
    int sayfaSayisi;
    if(kalanSatirSayisi==0)
        sayfaSayisi=(toplamSatirSayisi/gosterilecekSatirSayisi);
    else
        sayfaSayisi=(toplamSatirSayisi/gosterilecekSatirSayisi)+1;

    for(int i=1;i<sayfaSayisi;i++)
    {
        LinkButton link=new LinkButton();
        Label lbl=new Label();
   
        link.Text=i.ToString();
        link.ID=i.ToString();
        link.Click+=new EventHandler(link_Click);
        lbl.Text=" ";
        plhLinkler.Controls.Add(link);
        plhLinkler.Controls.Add(lbl);
    }
}

private void link_Click(object sender, EventArgs e)
{
    LinkButton currLink=(LinkButton)sender;
    int sayfaNo=Convert.ToInt16(currLink.ID);
    Doldur(sayfaNo,5);
}

Son olarak sayfamız yüklenirken Load olayında olmasını istediğimiz kodlarıda aşağıdaki gibi ekleyelim.
 

private void Page_Load(object sender, System.EventArgs e)
{
    con=new SqlConnection("data source=localhost;database=AdventureWorks2000;integrated security=SSPI");
    LinkleriOlustur(5);
    if(!Page.IsPostBack)
    {
        Doldur(1,5);
    }
}

Uygulamamızı çalıştıracak olursak sayfalar arasında başarılı bir şekilde dolaşabildiğimizi görürüz. Görüldüğü gibi özel sayfalama işlemi biraz meşakkatli bir yol gerektirse de performans açısından oldukça iyi verim sunacaktır. Buradaki performans farkını iyi anlayabilmek için normal sayfalama ve özel sayfalama tekniklerini iyice kavramak gerekir. Bir kere daha özetleyecek olursak, normal sayfalama tekniğinde her bir sayfada tüm veri seti tekrardan ilgili bağlantsız katman kontrolüne doldurulmaktadır. Bizim kullandığımız tekniktede dikkat ederseniz buna benzer bir yaklaşım söz konusudur. Çünkü bizde sp' miz içerisinde tüm veri setini çekip bir temp tablo içerisine alıyoruz. Yanlız biz bu işlemi sql sunucusu üzerinde gerçekleştiriyoruz. Oysaki normal sayfalamada hakikaten tüm veri kümesi bağlantısız katman nesnesine doldurulmaktadır. Bizim tekniğimizde ise sadece ilgili sayfadaki belirtilen satır sayısı kadarlık bir veri seti bağlantısız katman nesnesine doldurulmaktadır. İşte aradaki en büyük fark budur. Ki bu fark bize performans sağlamaktadır. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Örnek Kodlar İçin Tıklayınız.

Burak Selim ŞENYURT
selim@bsenyurt.com

Web Uygulamalarında Özel Doğrulama İşlemleri

$
0
0

Değerli Okurlarım Merhabalar,

Web uygulamalarında, kullanıcıların girmiş olduğu verilerin istenen şartlara göre doğruluklarının kontrol edilmesi son derece önemlidir. Asp.Net ile geliştirilen web uygulamalarında, kullanıcı girişlerinin kontrolü için çoğunlukla validation kontrolleri kullanılır. Validation bileşenleri hem istemci tarafında hemde sunucu tarafından veri kontrol işlemlerini gerçekleştirebilir. (Bu makaleyi daha kolay takip edebilmeniz açısından var olan Validator kontrollerinin kullanımını bildiğiniz varsayılmaktadır.)

Temel olarak bir verinin doğruluğunun Validator bileşenleri ile kontrol işlemi, eğer istemci script çalıştırılmasına izin veriyorsa, önce istemci tarafında client script' ler yolu ile gerçekleştirilir. İstemci tarafında kontrol yapılsada, yapılmasada mutlaka ve mutlaka server tarafında da bir doğrulama işlemi gerçekleştirilmektedir.

Temel doğrulama işlemlerinde, girilen verinin belli bir formata uygun olup olmadığı (örneğin geçerli bir mail adresi olup olmadığı), boş olup olmadığı, herhangibir değerden küçük, büyük, eşit vb... olup olmadığı ve benzeri durumlar kontrol edilir. Gerçek şudurki, var olan Validation kontrolleri ile hemen hemen her tür doğrulama işlemini gerçekleştirebiliriz. Ancak bazı durumlarda veri üzerinde doğrulama işlemleri için özel algoritmalara ihtiyacımı olabilir. Örneğin, girilen verinin uygun bir kredi kartı numarası olup olmadığının kontrol edilmesini sağlayan bir algoritmayı, bir validation kontolü içerisinde kullanmak isteyebiliriz. Yada birden fazla doğrulama işlemini bir arada sunmak isteyebiliriz. İşte bu ve benzer durumlar için Asp.Net, CustomValidator isimli bir bileşen içermektedir. Bu bileşen yardımıyla sunucu ve istemci tarafında çalışacak özel kontrol algoritmalarımızı veya süreçlerimizi, çeşitli veri giriş kontolleri ile ilişkili olacak şekilde yazabiliriz. Bu makalemizde, basit olarak Lhun algoritması yardımıyla kredi kartı doğrulama kriterini uygulayan bir CustomValidator örneği geliştireceğiz. Uygulamamız sadece Lhun Algoritma kontrolünü yapacak.

CustomValidator kontrolümüzü kullanmaya başlamadan önce, kısaca Lhun algoritması hakkında da bilgi vermekte fayda olacağı kanısındayım. Lhun algoritması basit olarak kredi kartı gibi sayısal ifadelerin doğruluğunu kontrol etmek amacıyla kullanılan bir matematik algoritmasıdır. Bu algoritmaya "mod 10 algoritması" da denmektedir. Basit olarak bir dizi matematiksel işlem ile, verinin uygun bir kredi kartı numarası olup olmadığı tespit edilir. Eğer kredi kartı numarası uygun ise, sıradaki diğer işlemlere geçilebilir.

Lhun algoritması sadece girilen sayısal ifadenin uygun bir kredi kartı numarası olup olmadığını belirten bir model sunar. İstemci tarafından girilen kredi kartı numarasının doğru olması sadece numaranın dünya çapında kabul görmüş bir algoritma ile doğrulanabildiğini gösterir. Oysaki, girilen kredi kartı numarasının geçerlilik süresinin, kart sahibinin isminin ve CVV2 gibi diğer kriterlerin kontrolüde gerekir ki bu tamamen ayrı bir süreçtir.

Kısaca Lhun algoritması şu şekilde çalışır.

1 - İlk olarak kredi kartı numarasının en sağ ikinci dijitinden başlanarak sırasıyla sola doğru ilerlenir. (Kod yazılırken bunu göz ardı edip soldan ikinci dijitten başlayıp ikişer ikişer de atlayabiliriz. )İkişer ikişer atlanırken her bir dijitin iki katı hesap edilir. Elde edilen sonuçlardan değeri 10 ve 10' dan büyük olanlar var ise bunların basamakları toplanır ve diğer 10' dan küçük olan değerler eklenerek bir toplam değeri elde edilir.

2 - Daha sonra, iki katı alınan dijitlerin dışında kalan dijitler ele alınır ve bu dijitler bir birleriyle toplanarak bir toplam değeri daha elde edilir.

3 - Son olarak 1nci ve 2nci işlemlerdeki toplamların toplamı alınır ve sonucun 10 ile bölünüp bölünmediğine (bir başka deyişle mod 10' un sıfır olup olmadığına) bakılır. Eğer 10 ile tam bölünebiliyorsa bu sayı dizisi bir kredi kartı numarasıdır. Olayı daha iyi anlamak için örnek bir 16 haneli sayı dizisini ele alalım.

Burada 1234 5678 9876 5432 sayı dizisinin geçerli bir kredi kartı numarası olup olmadığının Lhun algoritmasına göre nasıl tespit edilebildiğini görmektesiniz. Dikkat ederseniz sonuç 10 ile tam olarak bölünemediğinden sayı dizisi geçerli bir kredi kartı numarasını temsil etmemektedir. Sahip olduğunuz kredi kartları üzerinde yukarıdaki algoritmayı deneyebilir ve sonuçlarını irdeleyebilirsiniz. Şunuda belirtmekte fayda vardır ki, dünya çapında kullanılan çeşitli tipte kredi kartları mevcuttur. Örneğin master card, visa gibi. Bunlarında kendilerine has bir takım sayı dizisi kuralları vardır. Örneğin bir master card' a ait kredi kartı numarasının ilk iki hanesi, 55 yada 50 olmak zorundadır. Bu, kartın bir master card olduğunun işaretidir. Konumuz validation işlemlerinin CustomValidator kontrolü ile nasıl gerçekleştirilebileceğini incelemek olduğundan, Lhun algoritması üzerinde daha fazla durmayacağız.

Gelelim bu algoritmayı kullanacağımız uygulama kodlarımıza. CustomValidator kontrolümüz sıradan bir Validator kontrolünden farksızdır. Sadece kontrol işleminin yapılacağı olay metodlarını hem client(istemci) tarafında hem de sunucu(server) tarafında kendimiz yazmamız gerekmektedir. Elbette istemci tarafında bir kontrol kodu yazmak zorunda değiliz. Ancak sunucu tarafında mutlaka yazmalıyız. Aksi takdirde doğrulama işlemlerini gerçekleştiremeyiz. Server tarafında yazılan kodlar, ServerValidate olay metodunda ele alınır. Client tarafında yazılacak olan script kodların yer alacağı fonksiyon ise, ClientValidationFunciton özelliğinde belirtiriz.

Normalde, diğer Validator kontrolleri, client script özellikleri kapatılmadığı takdirde, istemci tarafında çalışacak script kodlarını otomatik olarak üretmektedir. Ancak CustomValidator kontrolü söz konusu olduğunda, istemci tarafında çalışacak script kodlarınıda manuel olarak yazmamız gerekmektedir.

ServerValidate metodunun ServerValidateEventArgs parametresi, kontrol edilecek bileşene ait özelliğin değerini temsil eder. Örneğin kredi kartı numarasının girileceği TextBox bileşeninin, Text özelliğinin değerini bu parametrenin Value özelliği ile metod içerisinde alabiliriz. Bu parametrik yapı client tarafında çalışacak script metodu içinde geçerlidir. Bizim örnek kodumuzda yapacağımız kontroller sırasıyla, girilen sayı dizisinin 16 haneli olduğu, sadece sayılardan oluştuğu ve Lhun algoritmasını sağlayıp sağlamadığıdır. Default.aspx sayfamızın tasarımı aşağıdaki gibi olacaktır. (Örneğimiz Asp.Net 2.0 platformunda geliştirilmiştir.)

CustomValidator bileşenin ControlToValidate özelliği, TextBox bileşenine ayarlanmıştır. Nitekim doğrulama işlemi için TextBox kontrolümüz ele alınacaktır. Son olarak, Kontrol başlıklı butonumuz sadece sayfayı postback etmek ve bu sayede server tarafındaki doğrulama sürecine geçebilmek amacıyla kullanılmaktadır. Yani herhangibir kod içermemektedir. Sadece postback işlemini sağlar. İlk olarak ServerValidate olay metodunu aşağıdaki gibi kodlayalım.

protected void custVldtr_ServerValidate(object source, ServerValidateEventArgsargs)
{
    #region Hane Sayısı Kontrolü

    if (args.Value.Length != 16)
    {
        // Hata mesajı değiştirilir.
       
custVldtr.ErrorMessage = "Kart numarası 16 haneli olmalıdır.";
       
args.IsValid = false; // Validation işlemi geçersizdir.
        return; // Metoddan çıkılır.
    }

    #endregion

    #region Sayısal değer kontrolü

    for (int i = 1; i < args.Value.Length; i++)
    {
        if (!char.IsDigit(args.Value[i]))
        {
            // Hata mesajı değiştirilir.
           
custVldtr.ErrorMessage = "Sadece sayısal değer girilmelidir.";
           
args.IsValid = false; // Validation işlemi geçersizdir.
            return; // Metoddan çıkılır.
        }
    }

    #endregion

    #region Lhun Kontrolü

    List<int> kartNumarasi = new List<int>();
    List<int> ciftKartNumaralari = new List<int>();
    int toplam1=0,toplam2 = 0;

    // TextBox' a girilen string formattaki kart numarasina ait sayı dizisinin her bir elemanı List tipinde int' değerler tutan generic kartNumarasi isimli koleksiyona aktarılır.
    for (int i =0;i<args.Value.Length;i++)
    {
        kartNumarasi.Add(Convert.ToInt16(args.Value[i].ToString()));
    }

    // ilk olarak iki katı hesaplaması ve çıkan sayıların toplamı işlemi yapılır.
    for (int i =0; i <kartNumarasi.Count; i =i+ 2)
    {
        ciftKartNumaralari.Add(kartNumarasi[i] * 2);
    }

    for (int i = 0; i < ciftKartNumaralari.Count; i++)
    {
        if (ciftKartNumaralari[i] > 9)
        {
            string var = ciftKartNumaralari[i].ToString();
            toplam1 += Convert.ToInt16(var[0].ToString()) + Convert.ToInt16(var[1].ToString());
        }
        else
        {
            toplam1 += ciftKartNumaralari[i];
        }
    }

    // iki katı hesabı dışında kalan elemanların toplamı hesaplanır.
    for (int i = 1; i < kartNumarasi.Count; i += 2)
    {
        toplam2 += kartNumarasi[i];
    }

    // Genel toplam alınır ve 10 ile tam bölünüp bölünmediğine bakılır.
    int toplam = toplam1 + toplam2;
    if (toplam % 10 == 0)
    {
        args.IsValid = true; // hata mesajı döndürülmez. Validation işlemi geçerlidir.
    }
    else
    {
        // Hata mesajı değiştirilir.
       
custVldtr.ErrorMessage = "Geçersiz kredi kartı numarası girdiniz.";
       
args.IsValid = false; // Validation işlemi geçersizdir.
    }

    #endregion
}

ServerValidate metodu geri dönüş değeri olmayan bir metoddur. ServerValidateEventArgs parametresi sayesinde, ControlToValidate özelliği ile bağlanan kontrolün (ki burada TextBox kontrolüdür) doğrulama sürecine girecek değerine erişilmektedir. Bu parametrenin IsValid özelliği bool tipinden bir özelliktir ve doğrulama işleminin doğruluğunu belirtmektedir. True değerini aldığı takdirde doğrulama geçerlidir. False değerinde ise doğrulama işlemi geçersizdir. Doğrulama işlemi geçersiz olduğu takdirde uygulama işleyişini durduracak ve hata mesajı ile kullanıcı bilgilendirilecektir. Bu sayede izleyen süreçlerinde tutarlılığını sağlamış oluruz. ServerValidate metodumuz Server(sunucu) tarafında çalışmaktadır. Uygulamamızı bu haliyle çalıştırdığımızda aşağıdaki Flash animasyonunda görülen sonuçları elde ederiz.

(Not : Aşağıdaki görüntüyü seyredebilmek için tarayıcınızda Flash Player' ın son sürümünün olması tavsiye edilir. Eğer sisteminizde XP Service Pack 2 yüklüyse ilgili uyarıyı dikkate alıp içeriğe izin vermelisiniz. (Allow Blocked Content). Videoyu yönetmek için sağ tıklayıp çıkan menüyü kullanabilirsiniz.)

Dikkat ederseniz, sayfa sunucuya geri gönderildikten sonra doğrulama işlemi devreye girmektedir. Şu anda client(istemci) tarafı için bir script kodu yazmadığımızdan, normal doğrulama sürecine ait olan istemci kontrolü kısmı otomatik olarak devre dışıdır. Oysaki çoğu zaman sunucuya geri dönülmeden istemci tarafında kontroller yapmak isteyebiliriz. Ancak .Net doğrulama sistemi göz önüne alındığında istemci tarafında herşey doğru olsa bile, sunucu tarafında yinede doğrulama işlemi gerçekleştirilmektedir. O halde client(istemci) tarafında yapılan doğrulamanın ne gibi bir avantajı olabilir? İstemci tarafında yapılan doğrulama ile, sunucuya gereksiz yere gidip gelme işleminin önüne geçmiş oluruz.

Hatırlatma; Doğrulama süreci şu şekilde işler.

Şimdi CustomValidator kontrolümüz için bir de client(istemci) script kodunu ekleyelim. Bu amaçla javascript kullanmayı seçtiğimizi düşünecek olursak tek yapmamız gereken aspx sayfamıza aşağıdaki script kodlarını eklemek olacaktır.

<script type="text/javascript">
function ValidateCreditCard(sender,args)
{
    if (
args.Value.length != 16)
    {
        alert("Kart numarası 16 haneli olmalıdır.");
       
args.IsValid = false;
        return;
    }

    var rakamlar = '0123456789';

    for (i=0; i<args.Value.length; i++)
    {
        if (rakamlar.indexOf(args.Value.charAt(i),0) == -1)
        {
            alert("Sadece sayısal değerler girilebilir.");
           
args.IsValid=false;
            return;
        }
    }

    var toplam=0;
    for (i=0; i < args.Value.length; i++)
    {
        var numara=args.Value.charAt(i);
        if (i % 2 == 0)
            numara=numara * 2;
        if (numara > 9) numara=numara - 9;
            toplam = toplam + parseInt(numara);
    }
    if(toplam % 10!=0)
    {
        alert("Geçersiz kredi kartı.");
       
args.IsValid=false;
        return;
    }
}
</script>

CustomValidator kontrolümüzün aspx tarafındaki kodları ise aşağıdaki gibidir.

<asp:CustomValidator ID="custVldtr" runat="server" ControlToValidate="txtCreditCardNumber" OnServerValidate=custVldtr_ServerValidate" ClientValidationFunction="ValidateCreditCard"> </asp:CustomValidator>

İstemci tarafında çalışan uygulamamızı aşağıdaki flash animasyonunda daha kolay izleyebilirsiniz.

(Not : Aşağıdaki görüntüyü seyredebilmek için tarayıcınızda Flash Player' ın son sürümünün olması tavsiye edilir. Eğer sisteminizde XP Service Pack 2 yüklüyse ilgili uyarıyı dikkate alıp içeriğe izin vermelisiniz. (Allow Blocked Content). Videoyu yönetmek için sağ tıklayıp çıkan menüyü kullanabilirsiniz.)

İstemcilerin doğası gereği, her zaman client tarafında çalışacak script' lere izin verilmez. İstemci tarafında çalışan doğrulama script' leri, sunucuya doğru yapılan gidiş-geliş işlemlerinin sayısını azaltan bir etkendir; ki buda ağ üzerindeki gereksiz yükü azaltır. Ancak bazı istemcilerin, script çalıştırma izni olmadığını için, sunucu tarafında doğrulama işlemi yapılması gerekliliği söz konusudur. Biz örneğimizde CustomValidator kontrolü için hem istemci tarafında hem de server tarafında doğrulama işlemini uygulayabileceğimiz iki metod kullandık. Böylece istemci izin veriyorsa doğrulamayaı o tarafta yapıp gereksiz ağ yükünü azaltmış olduk. Özel algoritmalar içeren veya birden fazla doğrulama işlemini bir kontrol üzerinde birleştirmek istediğimiz durumlarda CustomValidator kontrolü oldukça işimize yaramaktadır. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Burak Selim ŞENYURT
selim@bsenyurt.com

Asp.Net 2.0 Temelleri : Bir Web Sayfasının Anatomisi

$
0
0

Değerli Okurlarım Merhabalar,

Bu makalemizde, bir web sayfasının (.aspx uzantılı dosyalar) anatomosini incelemeye çalışacak, kaynak koddaki özel noktaları, in-line coding, code-behind modelini, yaşam döngüsünü ve çalışma zamanında olay bağlanması gibi temel kavramlara değineceğiz. Böylece basit olarak bir web sayfasının anatomisini öğrenmek için gerekli ip uçlarını değerlendirme fırsatını bulmuş olacağız. İlk olarak basit bir web sayfasını göz önüne alarak başlayalım.

<%@ PageLanguage="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
            </head>
                <body>
                    <
form id="form1" runat="server">
                        <div>
       
                        </div>
                    </form>
                </body>
</html>

Bu sayfa üzerinden konuşulabileceğimiz oldukça fazla özellik bulunmaktadır. Yukarıdaki kod parçası Visual Studio.Net 2005 tarafından üretilen defualt.aspx isimli bir web sayfasının içeriğini göstermektedir. Her web sayfası mutlaka Page direktifi ile başlar. Page direktifinin sahip olduğu nitelikler sayfa için gerekli olan çalışma zamanı ve geliştirme zamanı davranışlarını belirleyen değerler içerir. Örneğin CodeFile niteliği, bu sayfa için çalışma zamanında değerlendirilecek ve özellikle arka kodların (code-behind) tutulacağı dosyanın adını belirtir. Inherits isimli nitelik ile, üretilecek olan çalışma zamanı sayfasının hangi tipten türetileceği söylenmektedir. Language niteliği bu sayfada C# dili ile geliştirme yapılacağını vurgular. Dikkatimizi çeken bir diğer nitelik olan AutoEventWireup' a ise makalemizin sonunda değineceğiz. Ama öncesinde sayfamıza bir Button kontrolü alıp buna ait bir Click olay metodu yazalım. Web sayfasına tasarım zamanında bir Button kontrolü sürüklersek, ilgili kontrolün form takısı içerisine alındığını görürüz. Eğer Button kontrolümüze çift tıklarsak (yada, event' lerinden Click olayına çift tıklarsak), default.aspx.cs isimli code-behind dosyasında, Button bileşenine ait ilgili olay metodunun otomatik olarak oluşturulduğunu görürüz.

default.aspx içerisindeki değişiklik

<form id="form1" runat="server">
    <div>
        <asp:Button ID="Button1" runat="server" Text="Button"
OnClick="Button1_Click"/>
    </div>
</form>

defaulf.aspx.cs

public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
    }
}

Burada en çok dikkat etmemiz gereken nokta, _Default sınıfının Page' den türemesi ve partial bir tip olmasıdır. Bunun önemini ve oluşturduğu farkı görmek için aynı uygulamanın Asp.Net 1.1 versiyonuna bakmamız gerekecektir. Aşağıdaki kod parçaları Visual Studio.Net 2003 ile tasarlanmıştır ve aynı senaryoyu ele almaktadır.

WebForm1.aspx

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="HelloAspNet1.WebForm1" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>WebForm1</title>

<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body MS_POSITIONING="GridLayout">
    <form id="Form1" method="post" runat="server">
       
<asp:Button id="Button1" runat="server" Text="Button"></asp:Button>
    </form>
</body>
</HTML>

WebForm1.aspx.cs code behind dosyası

public class WebForm1 : System.Web.UI.Page
{
   
protected System.Web.UI.WebControls.Button Button1;

    private void Page_Load(object sender, System.EventArgs e)
    {
    }

    #region Web Form Designer generated code
    override protected void OnInit(EventArgs e)
    {
        InitializeComponent();
        base.OnInit(e);
    }

    private void InitializeComponent()
    {
       
this.Button1.Click += new System.EventHandler(this.Button1_Click);
    }
    #endregion

    private void
Button1_Click(object sender, System.EventArgs e)
    {
    }
}

Gördüğünüz gibi Asp.Net 1.1' de sayfaya bir kontrol eklendiğinde, code-behind dosyası içerisinde Asp.Net 2.0' dakine göre daha farklı işlemler yapılmaktadır. Herşeyden önce Code-Behind sayfamızda, Button1 adında bir Button nesnesi tanımlanmıştır. Oysaki Asp.Net 2.0 ile geliştirdiğimiz sayfa içerisinde bu tarz bir tanımlama söz konusu değildir.

protected System.Web.UI.WebControls.Button Button1;

Diğer taraftan Button1 isimli nesne için bir Click event' i Initialize Component metodu içerisinde yüklenmiş ve Button1_Click isimli olay metoduna bağlanmıştır. Initialize Component metodu aslında windows formlarında da benzer bir görev üstlenir. Yani form üzerindeki kontrollerin oluşturulması gerekli olaylarının bağlanması ve formun controls koleksiyonuna dahil edilmesi gibi. Elbette web de durum biraz daha farklı olmasına rağmen yinede kontrollerin oluşturulması gibi bir durum söz konusudur. Asp.Net 1.1 bu işi gerçekleştirmek için sayfanın OnInit isimli olay metodunu göz önüne almaktadır.

this.Button1.Click += new System.EventHandler(this.Button1_Click);

Oysaki Asp.Net 2.0 mimarisinde her sayfanın çalışma zamanında (run-time) derlenmesi (compile) söz konusudur. Bu işlem nedeni ile, bir nesne ve olaylarına ait metodlar çalışma zamanında dll üretimi sırasında otomatik olarak birbirlerine bağlanırlar. Bu Asp.Net 2.0 ile web sayfalarına gelen en önemli yeniliklerden birisidir. Dolayısıyla, sayfa üzerine yerleştireceğimiz bileşenleri ayrıca tanımlamamıza, bunalara ait olayları yüklemek için temsilcilere kadar gitmemize gerek kalmamaktadır. Özellikle .Net 2.0 ile gelen tiplerin parçalara ayrılabilmesinin (Partial Types Modeli) bu yeni çalışma modelinde önemli bir yeri vardır. Bu çalışma sistemini daha net anlayabilmek amacıyla aşağıdaki şekli göz önüne alabiliriz.

Asp.Net 2.0 için çalışma zamanında üretilen parçalı bir sınıf söz konusudur. Bu sınıf var olan aspx sayfasından üretilir ve code-behind dosyasında yer alan sınıf ile birleştirilir. Her iki sınıf partial olarak tanımlanmış olduğundan aslında ortada tek bir sınıf vardır. Sonuç olarak üretilen asıl sınıf, bileşenlerinin tanımlamalarını, ilgili olayları ile olan bağlantılarını vb... içeren bir yapıya sahiptir. Bu sınıfa ait nesne örnekleri istemciden gelen talepler sonrası üretilir, yaşam döngüsünü geçer ve istemciye gönderilemek üzere bir html çıktısının üretilmesinde kullanılır.

Asp.Net 2.0' da sayfa üzerindeki kontroller ve bu kontrollere ait olayların bağlanması gibi işlemler çalışma zamanında gerçekleştirilir. Böylece kod tarafında olay yükleme ve kontrol tanımlama gibi kodu kalabalıklaştıran işlemlerden uzaklaşılabilinmektedir.

Gelelim web sayfalarının anatomisindeki bir diğer konuya. Kodlamayı nerelerde yapabiliriz? Asp.Net iki tür kodlama şekli sunmaktadır. Bunlardan birisi Inline-Coding diğeri ise Code-Behind modelidir. Inline-Coding modelinde, aspx sayfasının kaynak içeriği ile kod kısmı aynı fiziki dosya içerisinde yer alırlar. Örneğin aşağıdaki kod parçasında Inline-Coding modeli kullanılmaktadır.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
    protected void Button1_Click(object sender, EventArgs e)
    {
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:Button ID="Button1" runat="server" Text="Button"
OnClick="Button1_Click"/>
        </div>
    </form>
</body>
</html>

Inline coding modelinde dikkat ederseniz, sayfanın kodlarını aspx sayfası içerisinde yer alan script blokları içerisinde yapmaktayız. Script bloğu içerisinde runat="server" niteliğinin kullanılmasının en büyük nedeni ise bu blok içerisinde çalıştırılacak olan kodların sunucu tarafında ele alınacak olmasıdır. Visual Studio.Net 2005 bir önceki sürümüne göre, Inline-Coding sırasında intelli-sense için tam destek vermektedir. Aşağıdaki ekran çıktısına dikkat ederseniz script bloğu içerisinde .net tipleri için intelli-sense özelliğinin tam olarak çalıştığını görebiliriz.

Inline-Coding' in en önemli avantajlarından birisi kaynak html içeriği ile kodların tek ve aynı fiziki dosya içerisinde yer almasıdır. Bu sayfanın dağıtılmasını (deployement) son derece kolaylaştırır. Ayrıca sayfanın yeninden isimlendirilmesi başka bir kod sayfasına bağımlılık olmadığından daha kolaydır.

Microsoft, kodlama işlemlerinde Code-Behind modelinin kullanılmasını önermektedir.

Gelelim Code-Behind modeline. Bu modelde, kodlarımızı içerisinde barındırıdan ayrı bir fiziki dosya söz konusdur. Aşağıdaki kod parçasında bu model gösterilmektedir.

Aspx tarafında

<form id="form1" runat="server">
        <div>
            <asp:Button ID="Button1" runat="server" Text="Button"
OnClick="Button1_Click"/>
        </div>
    </form>

Code Behind tarafında

public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        Response.Write("Deneme");
    }
}

Code-Behind programlamanın en önemli artısı, kod tarafı ile sunum tarafının kesin olarak birbirlerinden ayrılıyor olmasıdır. Bu nedenle grafik departmanı ile kodlamacıların aynı sayfa üzerinde çalışmaları çok daha kolay olabilmektedir.

Web sayfalarının anatomisinde inceleyeceğimiz bir diğer konuda form takısının görevidir. Bildiğiniz gibi bir istemci web sunucusu üzerinden herhangibir sayfayı ilk talep ettiğinde, sunucu tarafında bir dizi işlem gerçekleştirilir.

Aslında ilk talep Http protokolünün Get metoduna göre gerçekleşir. Talep edilen sayfa sunucuda önce IIS tarafından karşılanır ve Asp.Net sayfası olup olmadığına bakılır. Eğer Asp.Net sayfası ise talep ASPNET_ISAPI.dll' ine devredilir. Bu dll, Asp.Net web uygulamalarını çalıştıran ve yöneten Asp.Net work processor ile IIS arasındaki iletişimi sağlamakla görevlidir. AspNet Work Processor, talep edilen sayfanın bulunduğu web uygulamasının bir application domain içerisine yüklenmesinden, CLR' a açılmasından sorumludur. Eğer talep edilen sayfaya ait web uygulaması için bir Application Domain açılmamışsa, Asp.Net Work Processor bu işlemide üstlenir. Bu şu anlamada gelir; eğer Application Domain yüklü ise bu adım otomatikman atlanır.

Gelen talebe ait sayfanın çalıştırılması, belleğe alınması, html çıktısının üretilmesi gibi işlemler HTTPPIPE adı verilen bir .Net sınıflar koleksiyonun sorumluluğu altındadır. Tabi talep edilen sayfaya ait bir dll daha önceden işletim sisteminin ilgili temp klasörlerine atılmışsa buradaki dll üretme gibi işlemler otomatikman atlanır ve sayfanın doğrudan çalıştırılmasına geçilir. Yani HTTPPIPE talep edilen içerik için dll oluşturma işleminide kontrol altına alır. Üretilen çıktı istemcinin görmesi gereken içeriktir ve aynı yollar ile IIS' e dönerek buradan istemciye gönderilir.

Talep sonrası gerçekleşen arka plan işlemlerinin sonucunda ilgili sayfanın HTML çıktısının istemciye gönderildiğini düşünebiliriz. İstemci bu sayfa üzerinden gelen kontrollerde veri girişleri yapabilir. Bu veri girişlerinden sonra sayfayı tekrardan sunucuya gönderebilir-ki biz buna post-back işlemi diyoruz. İşte sayfanın içerisindeki veriler ile birlikte sunucuya gönderilmesi aşamasında standard olarak HTTP protokolünün Post metodu kullanılır. Bu işlemde HTTP paketi içerisinde sunucuya doğru hareket edecek olan içerik, sayfadaki form takısı içerisinde kullanılan elemanlar için geçerli olacaktır. Kısacası, HTTP Post tekniğine göre sayfa içerisindeki veriler istemciden sunucuya doğru HTTP paketi içerisinde gönderilirler. Oysaki HTTP protkolünün Get isimli bir başka metodu daha vardır. Bu metoda göre form takısı içerisindeki elemanlara ait içerik Url üzerinden bir başka deyişle tarayıcı pencersinin adres satırı üzerinden sunucuya gönderilecektir. Şimdi bu durumu analiz etmek için default.aspx sayfamızı aşağıdaki gibi değiştirelim.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<script runat="server">

    protected void Button1_Click(object sender, EventArgs e)
    {
        Response.Write(txtAd.Text);
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server"
method="get">
        <div>
            <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click"/>
            <asp:TextBox ID="txtAd" runat="server" Text="Adınız" />
        </div>
    </form>
</body>
</html>

İstemci bu sayfayı talep ettikten sonra TextBox kontrolüne veri girişi yapıp, sayfayı tekrar sunucuya gönderdiğinde ViewState, TextBox ve Button nesnelerine ait içerikler url üzerinden sunucuya gönderilmektedir. Bunu aşağıdaki ekran görüntüsünden ve Url satırındaki kod kısmından daha net görebilirsiniz.

http://localhost:1503/Anatomi/Default.aspx?__VIEWSTATE=%2FwEPDwUKMTM0MDE0NzQwOGRkccXHiCjUXf6SyoYO18eFOly9GKU%3D&Button1=Button&txtAd=Burak&__EVENTVALIDATION=%2FwEWAwKYvOnjDAKM54rGBgKM%2B%2FaQCjAN0uAoGcNSpMV1ikOzE6pJ9Fnd

Dikkat ederseniz, TextBox kontrolünün içeriğini Url içerisinde net olarak görebilmekteyiz. Http Get modeline göre sayfa içeriklerini sunucuya göndermek ,özellikle bilgilerin açık olarak Url üzerinden gidiyor olması ve tarayıcıların Url üzerinden gönderebilecekleri karakter sayısının bir sınırının olması gibi nedenlerden dolayı çok tercih edilen bir yol değildir. Lakin, parametrik değerlerin Url satırından değiştirilerek farklı sonuçların elde edilebilmesi gibi bir imkanda söz konusudur ki güvenlik göz önüne alındığında bununda bir avantaj olmadığı düşünülebilir.

Web sayfamıza ait anatomide inceleyeceğimiz bir diğer önemli unsurda yaşam döngüleridir. Aslında her web sayfası sunucu tarafında bir .Net sınıfıdır. Çünkü içeriklerinde başka sınıflara ait örnekler, üye metodlar, olaylar, özellikler vb... vardır. Dahası her web sayfası sunucu tarafında üretilmekte ve işlenmektedir. Yani istemciler bir web sayfasını talep ettiklerinde sunucunun yapacağı iş, talep edilen sayfanın örneğini oluşturmak ve yürütmek olacaktır. Dolayısıyla bu süreç sırasında her web sayfasının ele alınabilecek bir yaşam döngüsü olduğu sonucuna varabiliriz. Standard olarak bir web sayfasının üretilmesi aşamasındaki yaşam döngüsünü tüm ayrıntılarıyla izlemek istersek Trace mekanizmasını kullanabiliriz. Trace konusuna detaya girmeyecek olsakta, yaşam döngüsü içerisindeki adımları görmemiz açısından bir örnek yapmamız gerektiğini düşünüyorum. Trace' i aktif hale getirmek için Web.config dosyasına aşağıdaki elemanı eklememiz yeterli olacaktır.

Buna göre Trace bilgileri ilgili web uygulamasındaki her sayfanın sonunda (pageOutput=true) ve yanlızca sunucu bilgisayar üzerindeki isteklerde (localOnly="true") görünecek şekilde aktif hale getirilmektedir. Şimdi default.aspx sayfamızı tarayıcı pencersinden talep edersek aşağıdaki çıktıyı elde ederiz.

Bu çıktyı sayfamızı ilk talep ettiğimizde elde ederiz. Peki Button kontrolümüze basıp sayfayı istemciden sunucuya gönderdiğimizde (post-back) yaşam döngüsü nasıl işleyecektir? Herşeyden önce sayfa üzerindeki verilerin istemciden gelmesiyle birlikte post edilen dataların ve viewstate verilerinin işlendiği başka olaylarda tetiklenecek ve işletilecektir. Bu durumda trace bilgilerimiz aşağıdaki gibi olacaktır.

Temel olarak buradaki olaylardan bazılarını sayfamız içerisinde ele alabilir ve sayfanın yaşam döngüsü içerisinde kendi isteklerimizi kodlayarak yürütebiliriz. Bir web sayfasının yaşam döngüsünde ele alınabilecek olayları daha basit olarak düşündüğümüzde aşağıdaki sıralamayı ele alabiliriz.

Page_PreInit
Page_Init
Page_Load
   Button için Click / Change Olay Metodları (Eğer tetiklenmişler ise)
Page_PreRender
Page_UnLoad
Dispose (Eğer override edilmişse)

Burada dikkat ederseniz Page_Load ve Page_PreRender olayları arasında sayfa üzerindeki bileşenlere ait Click ve Change olaylarının yer aldığını görürüz. Yukarıdaki Trace çıktısında bunları tam olarak göremesekte Button_Click olay metodu içerisine aşağıdaki kodu yazarak izleme şansına sahip olabiliriz.

Trace.Warn("Button Click olay metodu çalışıyor..."); // Trace' e kırmızı renkte bilgi yazdırıyoruz.

Bu kodu çalıştırdığımızda Trace çıktısında aşağıdaki görüntüyü elde ederiz.

Buradaki çıktının Button kontrolüne basıp sayfayı sunucuya gönderdikten sonra oluştuğuna dikkat edelim.

Sonuç olarak talep edilen her web sayfası, sunucu tarafında bir nesne olarak üretilir, çalıştırılır, HTML çıktısı alınır ve yok edilir (Dispose).

Bu olaylar içerisinde özellikle dikkate değer olan iki tanesi vardır. Page_UnLoad ve Dispose. Page_UnLoad sayfaya ait Html çıktısı üretildikten sonra çalışır. Bu nedenle burada HTML çıktısına müdahale etme şansımız artık kalamamaktadır. Dolayısıyla, sayfa içerisinde kullanılan başka managed kaynakların kapatılması ve sisteme iade edilmesi için ideal bir olay metodudur. Diğer taraftan Dispose metodu Page sınıfı içerisinde override edildiği takdirde, Unmanaged kaynakların sisteme iade edilmesi için biçilmiş kaftandır.

Yaşam döngüsünde yer alan bazı olayları kod tarafında ele alabileceğimizden bahsetmiştik. Web sayfasına ait olay metodları ile (ki bunların yaşam döngüsü içerisinde önemli bir rolü vardır), sayfanın Page direktifinde yer alan AutoEventWireup niteliğinin değeri arasında önemli bir ilişki vardır. Örneğin aşağıdaki kod parçasını ele alalım.

<%@ Page Language="C#" AutoEventWireup="false" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<script runat="server">

    protected void
Page_PreInit(object sender,EventArgs e)
    {
        Response.Write("PreInit olay metodu çalıştı...");
    }

</script>

Default.aspx sayfasını çalıştırdığımızda PreInit isimli olay metodunun işlemediğini görürüz. Bunun sebebi varsayılan olarak true değerine sahip olan AutoEventWireup özelliğine false değerini atamamış olmamızdır. Buradan şu sonuca varabiliriz. Sayfaya ait olay metodlarının otomatik olarak bağlanmasını sağlamak için AutoEventWireup özelliğinin değerinin true olması gerekmektedir. Nitekim bu değeri true yaptığımızda PreInit olay metodunun çalıştığını görebiliriz.

Peki AutoEventWireup niteliğine false değerinin atanabilmesinin nasıl bir katma değeri olabilir. Bunun için aşağıdaki kod parçasını göz önüne alabiliriz.

Bu kod parçasına göre InitOncesi isimli olay metodumuz sayfanın PreInit olayı gerçekleştiği zaman çalıştırılacak metod olacaktır.  AutoEventWireup niteliği Asp.Net 1.1 versiyonunda varsayılan olaran false değerine sahip olan bir niteliktir. Sayfa olaylarını kendi isimlendirdiğimiz metodlar ile ilişkilendirmekten başka bir avantaj sağladığını düşünmemiz ne yazıkki pek mümkün değildir.

Gördüğünüz gibi herhangibir tasarımı olmayan hatta önemli bir iş yapmayan bir web sayfası üzerinde konuşulabilecek oldukça fazla konu vardır. Bu makalemizde bir web sayfasının anatomisini ve göze çarpan noktalarını incelemeye çalıştık. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Burak Selim ŞENYURT
selim@bsenyurt.com

HTTPHandler ve HttpModule Kavramları

$
0
0

Değerli Okurlarım Merhabalar,

Çoğumuz herhangibir tarayıcı penceresinden bir AspNet sayfasını çağırdığımızda resmin hep ön yüzünden bakarız. Oysaki resmin arka yüzünde dikkate değer bir mimari bulunmaktadır. Bu mimari içerisinde yer alan önemli yapılardan ikisi, Http Pipeline' ın bir parçası olan Http Handler ve Http Module kavramlarıdır. Bu makalemizde Http Handler ve Http Module kavramlarını kısaca incelemeye ve tanımaya çalışacağız.  HttpHandler ve HttpModule kavramlarını derinlemesine incelemeden önce işe, bir web sayfasını talep ettiğimizde web sunucusunun bu talebi nasıl değerlendirdiğini ele almaya çalışarak başlayalım. Çalışma modelindeki başrol oyuncularımız ISS (Microsoft Internet Information Services), ASPNET_ISAPI, Asp.Net Work Processor ve HTTPPipeLine dır.

Temel olarak IIS, web sunucusuna gelen html, aspx, asp, jsp vb talepleri karşılayıp cevaplamakla yükümlü bir programıdır. Talep edilen sayfalar farklı tipte olduğundan yada farklı programalama sistemlerince işletildiklerinden IIS, gelen talebi asıl işletecek olan sisteme devretmek için bazı durumlarda arada bir program arayüzüne ihtiyaç duyacaktır. Bazı durumlarda diyoruz çünkü bir HTML sayfası için (yada bir resim dosyası vb...) .Net Framework ve benzeri ortamlara ihtiyaç yoktur. Bunlar zaten doğrudan karşılanabilirler. Ancak örneğin bir asp sayfasına gelen talep için asp.dll Isapi extension kullanılır. Bir Asp.Net sayfası söz konusu olduğunda ise bu extension AspNet_Isapi.dll kütüphanesidir. AspNet_Isapi unmanaged (yönetimsiz) bir kütüphanedir. Dolayısıyla içerisinde .Net Framework kodları çalıştırılmaz. Bunun yerine AspNet_Isapi.dll gelen talepleri, Asp.Net Work Processor' a iletir. Asp.Net Work Processor ise, bu talepleri işetilmek üzere Http Module ve Http Handler' lara devreder. Çok basit olarak düşündüğümüzde çalışma şeklini aşağıdaki gibi düşünebiliriz.

Burada Http Modules ve Http Handler kısmını biraz daha açıklamakta fayda var. Asp.Net Runtime gelen talepleri Http Module' ler üzerinden geçirerek ilgili HttpHandler' a devreder. İlgili HttpHandler diyoruz çünkü Asp.Net çalışma ortamına düşen her talep için ele alınabilecek ayrı ayrı HttpHandler tipleri mevcuttur. Söz gelimi web servisleri için çalışan ayrı bir HttpHandler varken web sayfaları için çalışan başka bir HttpHandler' vardır. Biz bir aspx sayfası için gelen talebi göz önüne aldığımızda ilgili HttpHandler' ın yaptığı bazı işlemler vardır. Bu işlemler sırasında sayfanın bir örneği (nesne olarak) üzerindeki kontroller ile birlikte oluşturulur. Bir başka deyişle sayfanın yaşam döngüsü çalışır. (PreInit -> Init -> Load -> Change/ Click -> PreRender -> UnLoad -> Dispose) Nihayetinde bir HTML çıktısı üretilir. Bu HTML çıktısı Http Module' ler üzerinden geriye doğru Asp.Net Work Processor' a oradanda AspNet_Isapi' ye iletilir ve son olarak IIS üzerinden talepte bulunan istemciye gönderilir. Aslında sistemimizde yüklü olan pek çok HttpHandler ve HttpModule tipi vardır. Bunları root klasördeki web.config dosyası içerisinde bulabiliriz. Örneğin makaleyi yazdığım sistemde ki root web.config dosyası D:\WINDOWS\Microsoft.NET\ Framework\v2.0.50727\CONFIG klasörü altında yer almaktadır. Root klasörde yer alan web.config, bu makinedeki web uygulamlarının konfigurasyon dosyalarının kalıtımsal olarak türediği dosyadır. Bu dosya içerisinde yer alan HttpHandlers ve HttpModules sekmelerine baktığımızda aşağıdakine benzer çıktılar elde ederiz.

Örnek httpHandlers elementi ve alt elementleri; (Burada çok daha fazla Handler tanımı vardır. Örnek olması açısından bir kaç Handler gösterilmektedir.)

<httpHandlers>
    <add path="
trace.axd" verb="*" type="System.Web.Handlers.TraceHandler" validate="true" />
    <add path="*.
aspx" verb="*" type="System.Web.UI.PageHandlerFactory" validate="true" />
    <add path="*.
ashx" verb="*" type="System.Web.UI.SimpleHandlerFactory" validate="true" />
    <add path="*.
asmx" verb="*" type="System.Web.Services. Protocols.WebServiceHandlerFactory, System.Web.Services, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" validate="false" />
    <add path="*.
rem" verb="*" type="System.Runtime.Remoting.Channels. Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false" />
    <add path="*.
soap" verb="*" type="System.Runtime.Remoting.Channels. Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false" />
    <add path="*.
asax" verb="*" type="System.Web.HttpForbiddenHandler" validate="true" />
    <add path="*.
ascx" verb="*" type="System.Web.HttpForbiddenHandler" validate="true" />
    <add path="*.
master" verb="*" type="System.Web.HttpForbiddenHandler" validate="true" />
    <add path="*.
skin" verb="*" type="System.Web.HttpForbiddenHandler" validate="true" />
    <add path="*.
sitemap" verb="*" type="System.Web.HttpForbiddenHandler" validate="true" />
    <add path="*.
dll.config" verb="GET,HEAD" type="System.Web.StaticFileHandler" validate="true" />
    <add path="*.
exe.config" verb="GET,HEAD" type="System.Web.StaticFileHandler" validate="true" />
    <add path="*.
config" verb="*" type="System.Web.HttpForbiddenHandler" validate="true" />
    <add path="*.
cs" verb="*" type="System.Web.HttpForbiddenHandler" validate="true" />
    <add path="*.
csproj" verb="*" type="System.Web.HttpForbiddenHandler" validate="true" />
    <add path="*.
resx" verb="*" type="System.Web.HttpForbiddenHandler" validate="true" />
    <add path="*.
mdb" verb="*" type="System.Web.HttpForbiddenHandler" validate="true" />
     ...diğeleri
</httpHandlers>

HttpModules elementi ve alt elementlerinin içeriği;

<httpModules>
    <add name="
OutputCache" type="System.Web.Caching.OutputCacheModule" />
    <add name="
Session" type="System.Web.SessionState.SessionStateModule" />
    <add name="
WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" />
    <add name="
FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />
    <add name="
PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule" />
    <add name="
RoleManager" type="System.Web.Security.RoleManagerModule" />
    <add name="
UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
    <add name="
FileAuthorization" type="System.Web.Security.FileAuthorizationModule" />
    <add name="
AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" />
    <add name="
Profile" type="System.Web.Profile.ProfileModule" />
    <add name="
ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    <add name="
ServiceModel" type="System.ServiceModel.Activation.HttpModule, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</httpModules>

HttpHandler sekmesine baktığımızda path kısmında bazı dosya uzantıları olduğu görürüz. Bunların çoğu web uygulamalarını tasarlayanlara tanıdık gelecektir.

UzantıTipi
trace.axdsayfalara ait Trace bilgilerinin tutulduğu dosya
aspxAsp.Net web sayfalarımız (Web pages)
ascxKullanıcı web kontrolleri (Web User Controls)
csC# kaynak kod dosyaları (Source Files)
configKonfigurasyon dosyaları (Configuration Files)
asmxWeb servisi dosyaları (Web Service Files)
csprojC# proje dosyaları (C# Projects Files)
skinTemalarda kullanılan skin dosyaları (Skin Files)
masterMaster Page dosyaları (Master Pages)

Bu elementlerin her biri için birde verb niteliği (attribute) tanımlanmıştır. Verb niteliği Http protokolüne göre bu elementler içerisinde belirtilen uzanıtıya sahip kaynaklara gelebilecek olan talep çeşitlerini sınırlamak için kullanılır. Örneğin bu niteliğe Get, Head, Post gibi değerler bir arada yada ayrı ayrı verilebilir. * olması halinde tüm Http isteklerinin geçerli olacağı belirtilmektedir. HttpHandler içerisindeki elementler içinde belkide en önemli kısım type niteliğinin işaret ettiği .Net tipidir. Şimdi şu senaryoyu düşünelim. Normal şartlar altında url üzerinden bir cs dosyasını talep ettiğimizde aşağıdakine benzer bir hata alırız. (Testi IIS üzerinden yapabilmek için bilerek cs uzantılı dosya ilgili virtual directory altına atılmıştır. Aspx dosyası ile cs dosyalarının IIS altına atılması, Vs.Net 2005 Copy Web Site dağıtım modelinin varsayılan çalışma şeklidir.)

Böyle bir mesaj almamızın nedeni, cs uzantılı dosyalara gelecek olan çağrıların HttpForbiddenHandler tipi tarafından ele alınıyor olmasıdır. Bu handler hata mesajından görebileceğimiz gibi ilgili dosyaya erişilmesini engellemektedir. Bu nedenle HttpForbiddenHandler tarafından ele alınabilen tüm dosya tipleri için yukarıdaki hata mesajını alırız. Tam aksine aspx uzantılı sayfalar PageHandlerFactory tipi tarafından ele alınır. Ancak PageHandlerFactory aslında bir Http Handler değildir. Bunun yerine çalışma zamanında gerekli olan HttpHandler' ın üretilmesini ve bunu bir arayüz olarak (IHttpHandlerFactory) döndürülmesini sağlayan fonksiyonelliği içerir. Dolayısıyla aspx uzantılı sayfalar için bu fabrika tipinin ürettiği handler sunucu taraflı derleme ve sayfa nesne örneği üretme işlemlerini üstlenecektir. PageHandlerFactory aslında .Net 2.0 ile gelen ve Asp.Net 2.0 çalışma modelini destekleyen bir tiptir. Aşağıda bu sınıfın türediği IHttpHandlerFactory arayüzü üyeleri ile birlikte görülmektedir. Dikkat ederseniz GetHandler isimli üye metod, geriye IHttpHandlerFactory tipinden bir arayüz referansı döndürmektedir ki bu referans çalışma zamanında gereki olan HttpHandler tipini taşıyacaktır.

Benzer şekilde örneğin web servislerine gelicek olan talepleride (asmx sayfalarına gelicek olan talepleri) WebServiceHandlerFactory tipi üstlenecektir. Bu Handlerın sahip olduğu fonksiyonellikler arasında gelen taleplerdeki Soap mesajlarını deserialize etmek, cevap (response) olarakta tekrar serileştirilmiş Soap paketkerini hazırlayıp karşı tarafa göndermek gibi görevler sayılabilir.

HttpModules kısmına baktığımızda ise yine web uygulamalarımızda sıkça kullandığımız bazı kavramların var olduğunu görebiliriz. Örneğin Cache (ara belleğe alma), Session, Authentication, Role yönetimi vb. Buradan şu sonuç çıkartılabilir. HttpModules içerisinde tanımlanan elementlerde belirtilen tipler bir web uygulaması için ele alabileceğimiz framework özelliklerini kullanabilmemizi sağlar. Örneğin caching sistemini OutputCacheModule, Session sistemini SessionStateModule, Windows tabanlı doğrulama sistemini WindowsAuthenticationModule, Form tabanlı doğrulama sistemini FormsAuthenticationModule vb... tipleri ele almaktadır. O halde bu noktada HttpHandler ve HttpModule sekmesindeki elementler arasındaki ilişkiyi daha kolay açıklayabiliriz. Bir sayfa talebi HttpHandler' a ulaşmadan önce HttpModule' lerden geçer. HttpHandler gerekli Html çıktısını ürettiğinde ise sonuçlar yine HttpModule' ler üzerinden Asp.Net çalışma zamanı motoruna iletilir. Böylece bir sayfa için gerekli olan ara belleğe alma, bellekten Html çıktısına dahil etme, session bilgisini oluşturma veya okuma, güvenlik doğrulamalarını yapma gibi işlevsellikler ilgili HttpModule' ler tarafından hem taleplerde (request) hemde cevaplarda (response), ele alınabilirler.

Çok düşük bir ihtimallde olsa bazen kendi HttpHandler yada HttpModule' lerimizi yazmak isteyebiliriz. Örneğin istemciden özel bir dosya uzantısı ve parametre ile gelecek olan bir resim isteğini, veritabanından okuyup istemcilere html çıktısı olarak gönderecek bir handler, yada bir cs dosyasına gelecek talep sonrası bu talebi istemciye metin formatında döndürecek olan bir handler göz önüne alınabilir. Makalemizin bundan sonraki kısımlarında kendi HttpHandler' larımızı ve hatta HttpModule' lerimizi nasıl yazabileceğimizi incelemeye çalışacağız. Her ne kadar kendi HttpHandler veya HttpModule tiplerimizi oluşturmak için düşünülebilecek senaryo sayısı az olsada amacımız temel olarak bunları nasıl yazabileceğimizi ve Asp.Net çalışma ortamı tarafından nasıl ele alınabileceğini incelemektir. Kendi HttpHandler tiplerimizi oluşturabilmek için ilk olarak IHttpHandler arayüzünü (interface) implemente edecek olan bir sınıf yazmamız gerekmektedir. İlk olarak bu ve benzeri HttpHandler yada HttpModule' leri içerisinde barındıracak bir Class Library projesi oluşturalım. Böylece bu HttpHandler veya HttpModule tiplerimizi başka web uygulamaları içinde kullanabiliriz. ( Hatta bunu GAC (Global Assembly Cache) içerisine atarsak her web uygulamasının ortaklaşa erişebileceği bir dll halinede getirmiş oluruz.) Class Library projesinde kullanacağımız HttpHandler veya HttpModule tiplerimiz içerisinden güncel web içeriklerine (Http Context) erişebilmek için System.Web referansını açıkça eklememiz gerekmektedir.

Şimdi bu sınıf kütüphanesi içerisine IHttpHandler arayüzünü uygulayan bir sınıf dahil edelim.

IHttpHandler arayüzü, MyCustomHandler isimli sınıfımız içerisine iki üye dahil eder. Bunlardan ProcessRequest isimli metod, gelen talepleri değerlendirebileceğimiz üyedir. Yani talebe göre Html içeriğini oluşturabileceğimiz bir başka deyişle http isteğini kendi istediğimiz şekilde ele alabileceğimi yerdir. Dikkat ederseniz bu metod parametre olarak HttpContext tipinden bir değişken almaktadır. Bu değişken sayesinde, Response, Request ve Server nesnelerine erişebiliriz. Bu da gelen talepler HttpHandler içerisinde değerlendirebileceğimiz anlamına gelir. IsReusableözelliği ise sadece okunabilir bir özelliktir ve ilgili HttpHandler nesne örneğine ait referansın başka talepler içinde kullanılıp kullanılmayacağını belirler. Şimdi kodumuzu aşağıda görüldüğü gibi biraz daha geliştirelim.

using System;
using System.Web;

namespace MyHandlers
{
    public class MyCustomHandler:
IHttpHandler
    {
        #region IHttpHandler Members
        public bool
IsReusable
        {
            get { return
true; }
        }

        public void
ProcessRequest(HttpContext context)
        {
            string isim = context.
Request["Ad"];
            context.
Response.Write("<html><body>");
            context.Response.Write("
<b> Adım : " + isim + "</b><br/>");
            context.Response.Write("
</body></html>");
        }
        #endregion
    }
}

Dikkat ederseniz ProcessRequest metodu içerisinde Request üzerinden gelecek olan Ad isimli bir parametreyi alıyoruz ve basit olarak bir Html çıktısı üretiyoruz. Üretilen Html çıktısı için Response nesnesini kullanmaktayız. Yazmış olduğumuz bu HttpHandler türünün bir web uygulamasında, örneğin mypx uzantılı dosyaları ele almasını istediğimizi düşünelim. Öncelikle geliştirdiğimiz sınıf kütüphanesini web projemize ekleyelim. İlk aşamada dosya tabanlı (file based) bir web sitesi geliştireceğiz.

Bu nedenle ilk olarak web.config dosyası içerisinde mypx uzantılı dosyaların az önce yazdığımız sınıf kütüphanesi (class library) içerisindeki MyCustomerHandler tipi tarafından ele alınacağını belirtmemiz gerekiyor. Dolayısıyla web.config içerisinde var olan HttpHandler sekmesindeki elementler arasına yeni bir tanesini eklememiz gerekmektedir.

<system.web>
    <
httpHandlers>
        <add
path="*.mypx" verb="*" type="MyHandlers.MyCustomHandler,MyHandlers" validate="true"/>
    </
httpHandlers>

Böylece mypx uzantılı herhangibir isteği MyCustomHandler isimli sınıfa ait nesne örneği karşılayacaktır. Burada path kısmına *.mypx yazdık. Böylece mypx uzantılı herhangibir dosyaya gelecek olan talebi, type parametresi ile belirtilen sınıfa devretmiş oluyoruz. Durumu aşağıdaki görüntüde görüldüğü gibi test edebiliriz.

Dikkat ederseniz web sitemizde sayfam.mypx isimli bir dosya bulunmamktadır. Ancak bu dosyaya Ad isimli parametreyide içeren bir talep geldiğinde, MyCustomHandler tarafından karşılanmaktadır. Sonuç olarak MyCustomHandler' a ait ProcessRequest metodu çağırılmış ve buna göre bir Html çıktısı üretilmiştir. Örneğimizi doğrudan visual studio.net 2005 içerisinde F5 ile çalıştıramayacağımızı farketmişsinizdir. Nitekim elimizde sayfam.mypx dosyası zaten yok. Dolayısıyla testimizi yaparken tarayıcı üzerinden manuel olarak Url girmemiz gerekmektedir. Dikkat edilmesi gereken bir nokta geliştirilen bu web sitesi IIS altına dağıtıldığında ne olacağıdır. Nitekim IIS' e bir şekilde mypx uzantılı dosyalara gelecek olan taleplerin Asp.Net Çalışma ortamına devredilmesi gerektiğini söylememiz gerekmektedir. IIS' e mypx uzantılı dosyaları tanıtmadan önce, bunu bildirmediğimizde ne olacağına bakmamızda fayda var. Bunun için web sitemizi IIS altında yayımladığımızı düşünelim. Örneğin MySite ismiyle yayınladığımızı farz edelim. Bu durumda sayfam.mypx?Ad=burak isimli bir talepte bulunduğumuzda aşağıdaki ekran görüntüsünde yer alan sonucu elde ederiz.

Bu hatanın sebebi IIS' in mypx isimli uzantılı dosyaları ne yapacağını bilememesidir. Bunun için IIS üzerinde mypx isimli dosyayı tanıtmamız gerekmektedir. Bir başka deyişle, mypx uzantılı dosyalar için gelecek olan taleplerin AspNet_Isapi.dll' e devredilmesini söylemeliyiz. Bu amaçla, IIS üzerinden MySite isimli virtual directory' nin özelliklerine gidelim ve Directory kısmından Configuration sekmesini açalım.

Dikkat ederseniz burada siteye gelebilecek talepler ve bunları değerlendirecek olan programlar ile ilgili eşleştirmeler yapılmaktadır. (Mappings) Bizim tek yapmamız gereken aşağıdaki gibi yeni bir eşleştirme eklemektir.

Dikkat ederseniz executable kısmında aspnet_isapi.dll' ini belirtiyoruz. Böylece extension kısmında belirtilen mypx isimli dosyalar için gelicek olan talepler, aspnet_isapi.dll tarafından ele alınabilecek. Buradan da tahmin edeceğiniz üzere kendi yazdığımız HttpHandler' a kadar gelicek. Check that file exists (dosyanın var olup olamadığını kontrol et) seçeneğini kaldırmamızın nedeni ise olmayan bir mypx sayfasına gelecek olan taleperi IIS' in geri çevirmesini engellemektir. (Bu ayarlamalar IIS 5.1 üzerinde yapılmış olup IIS 6.0 için bazı farklılıklar olabilir.) Artık talebimiz çalışacaktır. Örneğin bu kez mypage.mypx?Ad=Selim talebinde bulunduğumuzu düşünelim. Aşağıdaki sonucu elde ederiz.

Gelelim kendi HttpModule' lerimizi nasıl yazabileceğimize. Yazımızın başında bahsettiğimiz gibi HttpModule' ler ile Http Handler' lar arasında yakın bir ilişki vardır. IIS tarafından gelen her istek ilgili HttpHandler' a iletilmeden önce bazı HttpModule' ler üzerinden geçer. HttpHandler tarafından üretilen Html çıktıları ise aynı module' ler üzerinden IIS' e doğru gönderilirler. HttpModule' ler çoğunlukla olay tabanlı fonksiyonellikler içerir. Örneğin bir kullanıcının doğrulanması sırasında veya sonrasında çalışacak olaylar WindowsAuthenticationModule tarafından kontrol altına alınır. Biz kendi HttpModule' lerimizi oluşturduğumuzda gelen taleplerde veya giden cevaplarda ele alınabilecek module olaylarını özelliştirme şansına sahip olabiliriz. Kendi HttpModule nesnelerimizi yazabilmek için IHttpModule arayüzünden türetilmiş bir sınıf yazmamız gerekmektedir. Dilerseniz makalemizde kullandığımız MyHandlers isimli sınıf kütüphanesi içerisine MyCustomHandler isimli bir tipi aşağıdaki gibi ekleyelim.

using System;
using System.Web;

namespace MyHandlers
{
    class MyCustomModule:IHttpModule
    {
        #region IHttpModule Members
        public void Dispose()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void Init(HttpApplication context)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        #endregion
    }
}

IHttpHandler, uygulandığı tipe iki metod dahil eder. Init ve Dispose. Init metodu HttpApplication tipinden bir parametre almaktadır ki bu parametre sayesinde var olan HttpModule olaylarına müdahale etme, aktif Http içeriğine ulaşma gibi imkanlara sahip olabiliriz. Dispose metodunu ise, bu sınıfa ait nesne örneği yok edilmeden önce yapmak istediğimiz kaynak temizleme işlemleri için kullanabiliriz. Örneğin Module içerisinden kullanılan unmanaged(managed) kaynakların serbest bırakılması için ele alabiliriz. Şimdi modulümüz içerisine biraz kod yazalım ve sonuçlarını incelemeye çalışalım.

using System;
using System.Web;

namespace MyHandlers
{
    class MyCustomModule:IHttpModule
    {
       
HttpContext m_Ctx=null;

        public void Init(HttpApplication context)
        {
            m_Ctx =
context.Context;
            context.
PreSendRequestContent+= new EventHandler(context_PreSendRequestContent);
        }

        void
context_PreSendRequestContent(object sender, EventArgs e)
        {
            m_Ctx.
Response.Write("<!--Bu sayfa Z şirketi tarafından üretilmiştir...-->");
        }

        public void Dispose()
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }
}

Bakın burada uygulama için PreSendRequestContent isimli bir olay yüklenmiştir. Bu olay, HttpHandler tarafından üretilen HTML içeriği gönderilmeden önce çalışır. Böylece bu modülü kullanan bir uygulama içerisindeki her hangibir sayfa talebinde gönderilen Http içeriğine "Bu sayfa Z şirketi tarafından üretilmiştir..." cümlesi bir yorum takısı (comment tag) olarak eklenecektir. Yazdığımız HttpModullerin ilgili web uygulaması içerisinde geçerli olmasını sağlamak için yine web.config dosyasında düzenleme yapmamız gerekmektedir. Bu amaçla, web.config dosyasına aşağıdaki gibi httpModules sekmesini dahil etmemiz ve yeni HttpModule' ümüzü bildirmemiz yeterli olacaktır.

<httpModules>
    <add name="
MyModule" type="MyHandlers.MyCustomModule,MyHandlers"/>
</httpModules>

Artık bu konfigurasyon ayarlarına sahip bir web uygulamasına gelecek her talep sonrasında, üretilecek olan Html içerikleri için yazmış olduğumuz modül devreye girecek ve ilgili olay metodu çalışacaktır. Bunu test etmek amacıyla herhangibir aspx sayfasını web uygulamamıza dahil edelim ve çalıştıralım. Elde edilen sayfanın içeriğine tarayıcı penceresinden baktığımızda aşağıdakine benzer bir çıktı elde ederiz.

Dikkat ederseniz, içeriğe bir yorum satırı eklenmiştir. Bu bilgi mesajı aslında web uygulamamıza gelecek her sayfa talebinde geçerli olacaktır. Init metodu içerisinde yer alan HttpApplication nesne örneği üzerinden yazabileceğimiz pek çok olay metodu vardır. Bunlardan bir kaçı ve ne işe yaradıkları aşağıdaki tabloda listelenmiştir.

Olay (Event)İşlevi
BeginRequestBir talep geldiğinde tetiklenir.
EndRequestCevap istemciye gönderilmeden hemen önce tetiklenir.
PreSendRequestHeadersİstemciye HTTP Header gönderilmeden hemen önce tetiklenir.
PreSendRequestContentİstemciye içerik gönderilmeden hemen önce tetiklenir.
AcquireRequestStateSession gibi durum nesneleri (state objects) elde edilmeye hazır hale geldiğinde tetiklenir.
AuthenticateRequestKullanıcı doğrulanmaya hazır hale geldiğinde tetiklenir.
AuthorizeRequestKullanıcının yetkileri kontrol edilmeye hazır hale geldiğinde tetiklenir.

Bu olaylar dışında ele alabileceğimiz başka olaylarda vardır. Örneğin var olan diğer HttpModule' lerin içeriklerine erişmek ve kullanmak isteyebiliriz. Söz gelimi, WindowsAuthenticationModule' ü ele almak istersek aşağıdaki gibi bir kod parçası geliştirebiliriz.

using System;
using System.Web;
using System.Web.Security;

namespace MyHandlers
{
    class MyCustomModule:IHttpModule
    {
        HttpContext m_Ctx=null;
        WindowsAuthenticationModule authMod = null;

        public void Init(HttpApplication context)
        {
            m_Ctx = context.Context;

            authMod =
(WindowsAuthenticationModule)context.Modules["WindowsAuthentication"];
            authMod.
Authenticate += new WindowsAuthenticationEventHandler(authMod_Authenticate);
        }

        void authMod_Authenticate(object sender,
WindowsAuthenticationEventArgs e)
        {
            m_Ctx.Response.Write("<!--" +
e.User.Identity.Name + "-->");
        }

        public void Dispose()
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }
}

Dikkat ederseniz, context nesnesi üzerinden Modules isimli bir koleksiyona erişebilmekteyiz. Bu koleksiyon içerisinde var olan tüm HttpModule' ler yer almaktadır. Bunu debug mode' da iken görebiliriz.

Dikkat ederseniz kendi geliştirdiğimiz MyCustomModule isimli nesnemiz, web.config dosyasındaki ismiyle (name niteliğinde yazdığımız MyModule) bu koleksiyon içerisinde görülmektedir. Kendi HttpModule' lerimizi yazmak için gerekli senaryoları düşündüğümüzde aklıma çok fazla vakka gelmeyebilir. Ama şu örnekler fikir vermesi açısından ve denenmesi açısından yararlı olabilir. Bir web uygulamasına gelen her hangibir talepte authenticate olan kullanıcıların log dosyalarına kaydedilmesi ele alacak kendi HttpModule sınıfımızı yazabiliriz. Talep edilen url bilgisi çok uzun olduğunda bunu ayrıştırıp (parse) başka ve daha kısa bir url olacak şekilde kullanıcıya gösterebilecek bir olay metodunu barındıracak bir HttpModule yazabiliriz vb...

Bu makalemizde kısaca HttpHandler ve HttpModule kavramlarına değinmeye çalıştık. Her ne kadar kendi Handler yada Module' lerimizi yazmayı pek tercih etmesekte, bazı durumlarda Asp.Net çalışma ortamını alt seviyede özelleştirmek isteyebiliriz. Bu gibi durumlarda IHttpHandler ve IHttpModule arayüzlerinden türeteceğimiz tiplerden yararlanmamız gerekmektedir. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Burak Selim ŞENYURT
selim@bsenyurt.com

Asp.Net Temelleri - Etkin Hata Yönetimi (Error Management)

$
0
0

Değerli Okurlarım Merhabalar,

Uzun süredir Windows Communication Foundation ile ilgili yazılar yayınlıyoruz. Sanıyorumki biraz hava değişimine ihtiyacımız olacak. Bu nedenle bu haftaki yazımızda biraz daha hafif ama önemli olan bir konu üzerinde durmaya çalışacağız. Web uygulamalarında sunucu taraflı hata yönetimi (Server Side State Management). .Net ortamında hataların ele alınmasında kullanılan en bilinen yol try...catch...finally bloklarıdır. Ne varki uygulama ortamları çeşitlilik göstermektedir. Bir sınıf kütüphanesi içerisinde yapılan hata kontrolü ile dağıtık mimari uygulamaları(distributed applications) içerisinde yapılan hata yönetimi farklıdır.(Örneğin WCF içerisindeki Fault Management konusunu hatırlayalım) Bu sebepten Asp.Net uygulamalarındada farklı bir yaklaşımı ele almak gerekmektedir. Web uygulamalarında oluşan hatalar sonucu çok hoş olmayan hata ekranları ile karşılaşmak mümkün olabilmektedir. Ancak hatalar kontrollü bir şekilde yönetilebilirlerse, son kullanıcıyı bilgilendirebilecek şekilde mesajlar verilip hataların düzeltilmesi yönünde daha sağlam ve güçlü adımlar atılabilir. Bu aynı zamanda uygulamanın tutarlılığı ve güvenilirliği açısındanda önemlidir.

Asp.Net ortamı, hataların yönetimi amacıyla istisna(Exception) tiplerini ve hataları yakalayıcı olay(event) metodların göz önüne alır. Söz konusu hata yönetimi metod seviyesinde(Method Level), sayfa seviyesinde(Page Level) ve uygulama seviyesinde(Application Level) gerçekleştirilebilir. Aşağıdaki tabloda söz konusu seviyeler ve aralarındaki temel farklar vurgulanmaya çalışılmaktadır.

Asp.Net Hata Yönetim Seviyeleri (Error Management Levels)
Metod SeviyesindeSayfa SeviyesindeUygulama Seviyesinde
  • Toparlanabilir veya bir başka deyişle kurtarılabilir hatalar çoğunlukla metod seviyesinde ele alınır.
  • Eğer olası hatalar toparlanamayacak cinsten ise bir üst seviyeye yönlendirilir.
  • Bir sayfa ile ilgili tüm hataların tek bir merkezden yönetilebilmesi sağlanır.
  • Olası hatalar sonrasında kullanıcılar çoğunlukla özel sayfalara yönlendirilir.
  • Bu seviyede sayfaların Page_Error olay metodları ele alınır.
  • Hata sayfasına yönlendirilmeden önce sayfa ile ilgili log bilgisi yazdırma, fiziki dosyalara bilgi atma veya adminlere mail gönderme gibi işlemler yapılabilir.
  • Uygulama içerisinde herhangibir sayfada meydana gelen hataların yakalanması sağlanır.
  • Tüm web uygulamasının hata yönetiminin tek bir merkezden kontrol edilebilmesi sağlanmış olur.
  • Bu seviyede global.asax dosyasındaki Application_Error olay metodu ele alınır.
  • Hata sayfasına yönlendirilmeden önce log bilgisi yazdırma, fiziki dosyalara bilgi atma veya adminlere mail gönderme gibi işlemler yapılabilir.

Metod seviyesinden, uygulama seviyesine doğru çıkıldıkça hataların merkezi olarak yönetilmesi ve tek bir merkezden ele alınması dahada kolaylaşmakta ancak, detay bilgilerinden gittikçe uzaklaşılmaktadır. Nitekim bir metod içerisinde meydana gelecek bir hata ile ilişkili yakalanan detayın, sayfa veya uygulama seviyesine aktarılmadığı sürece merkezi olarak ayrıştırılması zor olmaktadır.

Şimdi gelin bu seviyeleri örnekler yardımıyla incelemeye çalışalım. Metod seviyesinde hata yönetiminde try...catch...finally blokları büyük önem arz etmektedir. Ancak bu bloklar istenirse try...catch veya try...finally şeklindede yazılabilir. Çok doğal olarak bunladan hangisinin kullanılacağının kararını vermek için bazı vakkaların göz önüne alınması gerekmektedir. İlk olarak basit bir örnek ile başlayalım. Bu amaçla kullanıcının bölme işlemi yaptığı aşağıdaki gibi bir aspx sayfası olduğunu göz önüne alabiliriz.

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

   
protected void Hesapla_Click(object sender, EventArgs e)
    {
        double deger1 = Convert.ToDouble(txtDeger1.Text);
        double deger2 = Convert.ToDouble(txtDeger2.Text);
        double sonuc = deger1 / deger2;
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Hata Yonetimi (Metod Seviyesinde)</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                Birinci Değer :
                <asp:TextBox ID="txtDeger1" runat="server"></asp:TextBox> <br />
                İkinci Değer :
                <asp:TextBox ID="txtDeger2" runat="server"></asp:TextBox>
                <br />
                <asp:Button ID="btnHesapla" runat="server" Text="Hesapla" OnClick="Hesapla_Click" />
            </div>
        </form>
    </body>
</html>

Öncelikle hata kontrolü yapmadan sayfayı ele almaya çalışacağız. Bu nedenle örnek olarak ikinci kutucuğu boş bırakıp Hesapla isimli düğmeye basıyoruz. Sonuç olarak kullanıcı açısından pekte hoş olmayacak aşağıdaki ekran görüntüsü ile karşılaşırız. (Elbette geliştirme aşamasında bu mesajlar developer açısından daha kıymetli olabilir ;)

Dolayısıyla try...catch blokları yardımıyla Hesapla_Click isimli olay metoduna ait kodların aşağıdaki hale getirilmesi daha doğru olacaktır.

protected void Hesapla_Click(object sender, EventArgs e)
{
   
try
   
{
        double deger1 = Convert.ToDouble(txtDeger1.Text);
        double deger2 = Convert.ToDouble(txtDeger2.Text);
        double sonuc = deger1 / deger2;
   
}
   
catch (FormatException err)
   
{
        Response.Write("<b>Değerler sayısal olmalıdır. Lütfen girdiğiniz değerleri kontrol ediniz.</b><br/>Detaylı Mesaj : " + err.Message);
   
}
    catch (Exception err)
   
{
        Response.Write("<b>Beklenmeyen bir hata oluştu.</b><br/>Detaylı Mesaj : " + err.Message);
   
}
}

Bunun sonucunda aynı hata tekrar edilmeye çalışılırsa bu sefer mantıklı bir ekran ile karşılaşılacak ve kullanıcı hata ile ilişkili olarak daha doğru bir şekilde bilgilendirilebilecektir.

Elbette metod seviyesinde hata yönetimi adına dikkat edilmesi gereken bazı vakkalarda vardır. Eğer oluşan hataların kurtarılabilme(toparlanabilme) ihtimali varsa try...catch blokları döngüler (while, for gibi) içerisinde ele alınabilir. Böylece tekrar sayısına göre istenen rutin bir kaç kez üst üste denenebilir. Diğer taraftan oluşan istisnalar ile ilişikili olarak ekstradan verilebilecek yada kullanılabilecek bilgiler varsa bunların bir üst seviyede (sayfa seviyesinde-page level) ele alınması için catch bloğu içerisinde throw anahtar kelimesine başvurulabilir. Burada özellikle Exception sınıfının aşırı yüklenmiş(overload) yapıcı(constructor) metodlarından faydalanılmaktadır.

Diğer bir vakka metod içinde kullanılan dış kaynakları ele alır. Örneğin ilgili rutinler içerisinde kaynak temizlenmesi gerekiyorsa (bağlantıların veya dosyaların kapatılması, yönetimsiz-unmanaged nesnelerin serbest bırakılması gibi) finally bloklarını kullanmak doğru olacaktır. Burada finally bloklarının kullanılması şart değildir. Nitekim using blokları yardımıylada, IDisposable arayüzünü uygulayan tipler için blok sonunda Disposeçağrıları gerçekleştirilebilir. Son olarak olası hatalar ilgili metod içerisinde ele alınamıyorsa metodu çağıran yerde yakalanmalıdır.

Görüldüğü üzere vakkaların sayısı ve metod içerisindeki hata yönetimi çeşitli şekillerde yapılabilmektedir. Bu sebepten karar verirken aşağıdaki gibi tablodan faydalanmakta yarar vardır.

ÖneriKaynak temizlemesi gerekiyor mu?Olası hata var mı?Olası hatalar kurtarılabilir mi?Eklenecek ilave hata bilgisi var mı?
Hiç bir kontrole gerek yokhayıryokhayıryok
hayırvarhayıryok
try...finallyevetyokhayıryok
evetvarhayıryok
try...catchhayırvarhayırvar
try...catch...finallyevetevethayırvar

Örneğin kaynak temizlenmesi gerekiyorsa, olası hatalar var ise ve hatta olası hatalara eklenebilecek ekstra bilgiler var ise try..catch...finally bloklarını kullanmak daha mantıklıdır. Ne varki olası hatalarda, hata mesajına ilave bilgiler eklemek catch blokları içerisinde throw kullanmak ile mümkün olabilir. Çünkü amaç, bu hatayı ele alan bir üst seviyeye bilgi göndermektir. Bunun ele alınabileceği en güzel yer sayfa seviyesidir (Page Level). Öyleyse sayfa seviyesinde hata yönetiminin nasıl yapılacağını inceleyerek devam edelim.

Özellikle belirli bir sayfada meydana gelebilecek tüm hataların tek bir merkezden kontrolünün sağlanması gerektiği durumlarda sayfa seviyesinde hata yönetimi gerçekleştirilebilir. Bu teknikte önemli olan nokta, Page_Error olay metodunun etkin bir şekilde kullanılmasıdır. Aslında sayfa seviyesinde hatalar ele alınırken izlenen basit bir yol vardır. Aşağıdaki tabloda bu yol gösterilmektedir.

Sayfa Seviyesinde Hata Kontrolü için Tavsiye Edilen Yol
Madde 0Bir hata sayfası tasarlanır. :)
Madde 1Sayfaya Page_Error olay metodu eklenir.
Madde 2Sayfanın ErrorPageözelliğine hata sayfasının Url bilgisi Page direktifi içerisinde eklenir.
Madde 3Page_Error olay metodu içerisinde Server sınıfının static GetLastError() metodu ile son oluşan istisna nesne örneği ele alınır.
Madde 4İstenirse ErrorPageözelliğine burada değer ve hatta querystring yardımıyla bilgi aktarılması sağlanabilir. Böylece hata sayfasına bazı ekstra bilgilerin taşınmasıda sağlanmış olur.
Madde 4.5Gerekirse bu aşamada loglama (özellikle sistemdeki event loglara bilgi yazma), fiziki dosyalara bilgi yazdırma, yönetici veya ilgili kişilere mail gönderme gibi işlemler yapılabilir.

Page_Error isimli olay metodu sayfa seviyesinde ele alınır. Normal şartlarda sayfada ele alınmayan bir hata oluştuğunda bu metod otomatik olarak çağırılacaktır. Biz bu metod içerisinde yönlendirmeler yaparak kullanıcıları daha akıllı hata bilgilendirme sayfalarına yönlendirebilir ve loglama gibi işlemleri gerçekleştirebiliriz. Page_Error olay metodu içerisinden ilgili hata sayfasına yönlendirme yaparken Page sınıfının ErrorPage özelliğine değer atamak gerekebilir. Bu daha çok querystring yardımıyla hata sayfasına ekstra bilgi gönderileceği durumlarda ele alınır. Aksi durumlarda metod içerisinde değilde Page direktifinde bu özelliğin değerinin belirlenmesi yeterlidir. Ancak burada dikkat edilmesi gereken bir nokta vardır. Eğer metod içerisinde ErrorPage özelliğine hata sayfasını atarken querystring kullanılmassa Asp.Net, çalışma zamanında aspxerrorpath isimli bir anahtarı ve değerini otomatik olarak ekleyecektir. Ki buda hatanın oluştuğu sayfanın yakalanabilmesi ve belkide dinamik bir linkin üretilerek tekrar geri gidilebilmesinide sağlayacaktır.

Şimdi yukarıdaki örneğimizi sayfa seviyesinde ele almaya çalışalım. Madde 0' da değindiğimiz gibi öncelikli olarak bir hata sayfası tasarlamakta fayda var. Bu hata sayfası aşağıdaki gibi tasarlanabilir.

HataSayfasi.aspx;

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

   
protected void Page_Load(object sender, EventArgs e)
   
{
        string ekBilgi =
Request.QueryString["EkBilgi"];
        string sayfa=
Request.QueryString["Sayfa"];
        string hataMesaji =
Request.QueryString["HataMesaji"];
        Response.Write("<b>Hata sayfası : </b>" + sayfa+"<br/>");
        Response.Write("<b>Ek bilgi : </b>" + ekBilgi + "<br/>");
        Response.Write("<b>Hata Mesajı : </b>" + hataMesaji);
   
}

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Hata Sayfası</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
            </div>
        </form>
    </body>
</html>

Default.aspx sayfasındaki kodları ise aşağıdaki gibi değiştirebiliriz.

protected void Page_Error(object sender, EventArgs e)
{
    Exception olusanHata =
Server.GetLastError();
   
ErrorPage = "HataSayfasi.aspx?EkBilgi=" + olusanHata.Message + "&HataMesaji=" + olusanHata.InnerException.Message + "&Sayfa="+Page.AppRelativeVirtualPath;
}

protected void Hesapla_Click(object sender, EventArgs e)
{
    try
    {
        double deger1 = Convert.ToDouble(txtDeger1.Text);
        double deger2 = Convert.ToDouble(txtDeger2.Text);
        double sonuc = deger1 / deger2;
    }
    catch (Exception excp)
    {
       
throw new Exception("Sayısal değer girişinde hata oluştu", excp);
    }
}

Hesapla_Click olay metodu içerisinde yer alan catch bloğunda throw anahtar sözcüğü kullanılarak bir Exception nesnesi daha fırlatılmıştır. Bu istisna nesnesinin yakalanacağı yer sayfanın Page_Error isimli olay metodudur. Bu metod içerisinde, sayfada oluşan son hatayı yakalayabilmek için Server sınıfının GetLastError metodu kullanılmıştır. Dikkat edilmesi gereken noktalardan birisi, throw ile fırlatılan Exception nesnesi örneklenirken ilk parametreye örnek bir ekstra veri konulmasıdır. Eklenen bu bilgi GetLastError ile yakalanan Exception nesne örneğinin Messageözelliği ile elde edilebilir. Bu durumda orjinal istisna mesajınının nereden alınabileceği bir soru işaretidir. Cevap InnerExceptionözelliğidir. InnerException özelliği ile fırlatılan asıl Exception nesne örneği yakalanabilir. Bunu çalışma zamanında test ettiğimizde aşağıdaki ekran görüntüsünde olduğu gibi FormatException tipinin InnerExceptionözelliğinde saklandığını görebiliriz.

Son olarak ErrorPageözelliği ile hata sayfasına gerekli yönlendirme yapılmaktadır. Elbette ErrorPage kullanılmak zorunda değildir. Bunun yerine Server sınıfının Transfer metodu veya Response sınıfının Redirect metodlarının kullanımıda tercih edilebilir. Özellikle Server sınıfının Transfer metodu gereksiz roundtrip' lerin önüne geçilmesini sağlamakta ama url satırına bakıldığında halen daha aynı sayfada olunduğu izlenimini vermektedir.

Örnek geliştirilirken dikkat edilmesi gereken bir nokta vardır. Eğer örneği aynı makine üzerinde (localhost) test ediyorsak beklediğimiz yönlendirme sayfasına gidemediğimizi hatta eski sarı ekranın (orjinal hata mesajının basıldığı sayfadan bahsediyoruz)üretildiğini görürüz. Bunun nedeni web uygulamasının özel hata modunun aktif olmayışıdır. Bir başka deyişle web.config dosyasında yer alan customErrors elementinin mode niteliğine On değerinin verilmesi gerekir.

Aslında mode özelliğinin 3 farklı değeri vardır. RemoteOnly, On ve Off. RemoteOnly modu aktif iken özel hata sayfalarını sadece istemciler görebilir. On modunda hem istemciler hemde localhost kullanıcısı özel hata sayfalarını görebilir. Biz örneklerimizdeki sayfalarımızı aynı makine üzerinden test ettiğimiz için bu modu On olarak belirledik. Gerçek bir uygulama ortamına çıkıldığında On yerine RemoteOnly kullanılması tavsiye edilir.


<customErrors mode="On"/>

customErrors elementi içerisine error isimli alt elementlerde konulabilir. Bu element sayesinde sunucu seviyesinde meydana gelen hatalar var ise bunların sonucunda özel hata sayfalarına yönlendirmeler yapılabilir. Söz gelimi aşağıdaki bildirimleri ele alalım.

<customErrors mode="On">
   
<error statusCode="404" redirect="SayfaYok.aspx"/>
</customErrors>

Buna göre sitede olmayan bir sayfa talep edilirse kullanıcılar SayfaYok.aspx' e yönlendirilirer.

Yanlız burada dikkat edilmesi gereken bir nokta vardır. Sonradan ele alacağımız gibi global.asax dosyasındaki Application_Error olayı yazılmışsa, uygulama SayfaYok.aspx' e yönlendirilmeden önce buradaki olay metoduna uğrayacaktır ki burada bir yönlendirme yapıyorsak SayfaYok.aspx yerine oraya gidilebilir. Hatta Server.ClearError metodu kullanılmışsa hata sayfasına gidilmeyedebilir. Buda sistemin istediğimiz şekilde çalışmaması anlamına gelmektedir. Gerçi Application_Error içerisinde oluşan hata örneğin sayfa yok hatası yinede yakalanabilir. Örneğin Debug modda bu aşağıdaki şekilde olduğu giri görünecektir.

Dolayısıyla bu noktalara dikkat etmekte fayda vardır.

Örneğin şu aşamada iken web uygulamasını çalıştırdıkdan sonra Giris.aspx isimli yazmadığımız bir sayfayı talep edersek aşağıdaki ekran görüntüsü ile karşılaşırır.

Özellikle SayfaYok.aspx' ten sonra gelen querystring parametresine dikkat edelim. aspxerrorpath ile gelen değer alınıp kullanıcıya daha anlamlı bir hata sayfası gösterilebilir. Biz tekrardan konumuza geri dönelim ve customErrors elementini aşağıdaki haliyle bırakalım.

<customErrors mode="On"/>

Bu değişiklikten sonra uygulama çalışma zamanında test edilirse default.aspx sayfasında hata oluştuktan sonra HataSayfasi.aspx' e gidildiği görülebilir. Tarayıcı penceresindeki url satırına dikkat edilecek olursa querystring parametreleri ve değerleride başarılı bir şekilde aktarılmıştır.

Şunu itiraf etmeliyim ki sarı ekranın görüntüsü buradakinden daha güzeldir. Dolayısıyla hata sayfaları hazırlanırken biraz daha özenilmeli, gerektiğinde projenin sahibi olan şirket standartlarına uygun olaraktan tasarlanmalı ve zengin bir bilgi sunacak hale getirilmelidir.

Oluşan hatalara ilişkin kullanıcılara bilgi verilmesi dışında, siteyi tasarlayan veya yönetenlerinde bilgilendirilmesi gerekebilir. Bu bilgilendirme farklı şekillerde yapılabilir. Örneğin var olan işletim sistemi loglarına bilgi yazılabilir. Örneğin Application loglarına. Ya da daha basit olarak fiziki bir dosyaya hatalar ile ilişkili bazı bilgiler gönderilebilir. Hatta gerektiği yerlerde çok kritik hatalar söz konusu ise ilgili kişilere mail bile gönderilebilir. Söz gelimi aşağıdaki kod parçası ile Page_Error metodu içerisinde, oluşan son hataya ait bilgi fiziki bir dosyaya eklenmektedir.

protected void Page_Error(object sender, EventArgs e)
{
    Exception olusanHata = Server.GetLastError();
    ErrorPage = "HataSayfasi.aspx?EkBilgi=" + olusanHata.Message + "&HataMesaji=" + olusanHata.InnerException.Message + "&Sayfa="+Page.AppRelativeVirtualPath;

   
using (FileStream stream = new FileStream("C:\\HataLogDosyasi.txt", FileMode.Append, FileAccess.Write))
   
{
       
StreamWriter writer = new StreamWriter(stream);
       
writer.WriteLine("Hata Zamanı " + DateTime.Now.ToString() + " Hata Sayfası " + Page.AppRelativeVirtualPath + " Hata Mesajı " + olusanHata.InnerException.Message);
       
writer.Close();
   
}
}

Burada basit olarak FileStream ve StreamWriter tiplerinden yararlanılarak hata bilgileri C: klasörü altındaki bir text dosyasına yazdırılmaktadır. Sonuç olarak uygulama test edildiğinden oluşturulan hata sonrasında ilgili dosyaya aşağıdaki ekran görüntüsünde olduğu gibi bazı bilgiler eklenecektir.

Page_Error metodu içerisinde hata sayfasına herhangibir şekilde yönlendirme yapılmamasına rağmen gidilmektedir. Bu metodun bir özelliğidir. Metod sonuna gelindiğinde, ErrorPage ile belirlenmiş sayfaya otomatik olarak gidilir. ErrorPage değerinin programatik olarak belirlenmesi haricinde Page direktifi içerisinde ayarlanabileceğini daha önceden söylemiştik. Bu aşağıdaki ekran görüntüsünde olduğu gibi düzenlenebilir.

Elbette metod içerisinde ErrorPage özelliği belirtilmişse Page direktifinde yapılan tanımlama geçersiz sayılacaktır.

Gelelim uygulama seviyesinde hata yönetimine. Bu durumda web uygulamasında meydana gelecek hataların ele alınabileceği bir merkez söz konusudur. Söz konusu merkez global.asax dosyası içerisinde yer alan Application_Error isimli olay metodudur. Bildiğiniz gibi global.asax dosyasında, uygulama genelini ilgilendiren bazı olay metodları yer almaktadır. Örneğin uygulama çalışmaya başladığında devreye giren Application_Start, sonlandığında çağrılan Application_End yada kullanıcıların açtıkları oturumlarda(Session) devreye giren Session_Start gibi. Dolayısıyla ilk yapılması gereken işlem web sitesine, eğer yok ise bir global.asax dosyası eklemek olacaktır. Sayfa seviyesindeki hata yönetiminde olduğu gibi, uygulama seviyesinde yapılacak hata yönetimi içinde tavsiye edilen bir yol haritası vardır ve aşağıdaki tabloda olduğu gibidir.

Uygulama Seviyesinde Hata Kontrolü için Tavsiye Edilen Yol
Madde 0Bir hata sayfası tasarlanır. :)
Madde 1Sayfalara Page_Error olay metodları eklenir.
Madde 2Page_Error olay metodlarında, son olarak elde edilen istisna(Exception) nesnesinin referansı aynen metod içerisinde olduğu gibi bilinçli olarak ortama fırlatılır(throw).
Madde 3global.asax dosyasında yer alan Application_Error olay metodu kodlanır. Bu metod içerisinde son hata bilgisi yine GetLastError metodu ile alınır.
Madde 3.5Gerekirse bu aşamada loglama (özellikle sistemdeki event loglara bilgi yazma), fiziki dosyalara bilgi yazdırma, yönetici veya ilgili kişilere mail gönderme gibi işlemler yapılabilir. (Sistem loglarına yazma sırasında dikkat edilmesi gereken durumlardan birisi ASPNET(IIS 5.0 için) veya Network Service (IIS 6.0 için) kullanıcısının Application, System ve Security loglarına yazma hakkı olup olmadığıdır. Söz gelimi ASPNET kullanıcısının varsayılan olarak Application loglarına yazma hakkı varken System ve Security loglarına yazma hakkı yoktur. Bu nedenle ilgili kullanıcıların haklarının özellikle loglara yazma işlemleri sırasında dikkate alınması gerekebilir.)
Madde 4Kullanıcı hata sayfasına yönlendirilmeden önce Server sınıfının ClearError metodunun çağırılması ve hataların temizlenmesi önerilir.
Madde 5Server.Transfer metodu ile hata sayfasına yönlendirme yapılır.

Buradaki maddelerde dikkat çekici noktalardan biriside son hatanın sayfalara ait Page_Error olay metodları içerisinde tekrardan fırlatılıyor olmasıdır. Bu bir anlamda hatanın bir üst seviyeye aktarılmasıdır. Diğer taraftan bir gerekliliktir. Nitekim, hata bilinçli olarak uygulama seviyesine gönderilmesse Asp.Net çalışma ortamı(Asp.Net RunTime), hatanın ele alınması için HttpUnhandledException tipinden bir nesne örneği üretecektir. Biz hatayı kontrollü bir şekilde ele almak istiyorsak bilinçli bir şekilde fırlatma işlemini üstlenmeliyiz.

Çok doğal olarak web uygulaması içerisinde birden fazla aspx sayfası olduğu düşünülecek olursa, hepsine bir Page_Error metodu eklemek ve kodlamak (en azından GetLastError ile elde edilen istisna nesnelerini fırlatmak) uğraştırıcı ve sabrımızı test edici olabilir. Burada nesne yönelimli mimarinin avantajlarından faydalanmak çok daha akılcı bir çözüm olacaktır. Bir başka deyişle tüm sayfaların türediği bir taban sayfa (base page) içerisindeki Page_Error metodu ele alınabilir.

Alternatif bir yaklaşım olarak MasterPage kullanımı düşünülebilir. Her ne kadar MasterPage içerisine Page_Error isimli bir metod yazılabiliyor olsada, aslında metod seviyesinden throw ile exception fırlatıldığında bu metod herhangibir şekilde tetiklenmez. Eğer Application_Error olay metodu yazılmışsa doğrudan buraya düşülür ve HttpUnhandledException tipinden bir istisna nesnesi yakalanır. Bu sebepten bu tip bir durumda MasterPage içerisindeki Page_Error olay metodunun kullanımı şeklinde bir çözüm ne yazıkki söz konusu değildir.

İlk olarak Application_Error olay metodunu nasıl ele alacağımıza bakalım. Öncelikle aspx sayfalarındaki Page_Error olay metodlarından yine bir üst seviyeye hata fırlatmak gerekir. Bu nedenle örnek olarak default.aspx sayfasındaki Page_Error olay metodu aşağıdaki gibi değiştirilmelidir.

protected void Page_Error(object sender, EventArgs e)
{
    Exception olusanHata =
Server.GetLastError();
   
throw olusanHata;
}

Uygulama seviyesinde hata kontrolü yapılacağından ilgili olay kodunun global application class içerisinde yer alması gerekir. Bu nedenle bir adet global.asax dosyası web sitesine dahil edilmelidir. global.asax dosyasında Application_Error olay metodu içerisinde ise aşağıdakine benzer kodlamalar yapılmalıdır.

void Application_Error(object sender, EventArgs e)
{
   
Exception excp = Server.GetLastError();
    // Burada loglama, dosyaya yazma, mail gönderme gibi işlemler yapılabilir.
   
Server.ClearError();
   
Server.Transfer("GenelHataSayfasi.aspx?EkBilgi="+excp.Message+"&HataMesaji="+excp.InnerException.Message);
}

Bu sefer global.asax dosyası içerisinden GenelHataSayfasi.aspx isimli web sayfasına querystring yardımıyla bilgi taşınmaktadır. GenelHataSayfasi.aspx içeriği, HataSayfasi.aspx' e benzemekle birlikte tek farkı hatanın meydana geldiği sayfa bilgisini ele almıyor oluşudur. Eğer uygulama bu haliyle test edilir ve yine Default.aspx içerisinde hata yaptırılırsa aşağıdaki ekran görüntüsü ile karşılaşılır.

Görüldüğü gibi default.aspx içerisindeki metodda oluşan hata catch bloğunda ek bilgi ile tekrar throw edilmiş ve bunu sayfanın Page_Error olay metodu yakalamıştır. Sonrasında ise Page_Error olay metodunda hata tekrar throw ile fırlatılmış ve bunun sonucunda Application_Error olay metoduna gidilebilmiştir. Aşağıdaki grafikte bu durumun Debug moddaki karşılığı gösterilmektedir.

Önce Metod içi;

Sonra Page_Error;

Son olarak Application_Error;

Çalışma sonrasında dikkati çeken noktalardan birisi tarayıcı penceresindeki url satırında Default.aspx' in görünüyor olmasıdır. Halbuki şu anda üzerinde bulunulan sayfa GenelHataYonetimi.aspx' dir. Bunun sebebi Server.Transfer metodudur. Bunun yerine Response.Redirect' te tercih edilebilir. Ama daha öncedende belirtildiği üzere Transfer metodu ile sunucuya doğru gidiş gelişler azaldığı için özellikle uygulama seviyesindeki hata yönetiminde tercih edilen bir tekniktir. İlgili hata sayfası dahada geliştirilebilir. Örneğin hatanın oluştuğu sayfaya dönülmesi için gerekli bağlantı bilgileri hata mesajı ile birlikte taşınabilir. Bu ve dahası tamamen sizlerin hayal gücüne kalmaktadır.

Böylece geldik bir makalemizin daha sonuna. Bu makalemizde Asp.Net uygulamalarında etkin hata yönetimi adına metod, sayfa ve uygulama seviyesinde neler yapabileceğimizi incelemeye çalıştık. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama için Tıklayın

Burak Selim ŞENYURT
selim@bsenyurt.com

Asp.Net Temelleri : Tablo Bazlı Resimleri Ele Almak

$
0
0

Değerli Okurlarım Merhabalar,

Yazın bu sıcak günlerinde daha hafif konularla web maceralarımıza devam ediyoruz. Geçtiğimiz makalemizde Asp.Net uygulamalarında ektin hata yönetiminin nasıl yapılabileceğini incelemeye çalışmıştık. Bu kez veritabanı tablolarında çoğunlukla binary alanlarda saklanan resimlerin, Asp.Net uygulamalarında nasıl ele alınabileceğini örnek projeler üzerinden incelemeye çalışacağız. Bir Windows uygulaması göz önüne alındığında, resimleri gösterebilecek bir PictureBox kontrolünün çeşitli özellikleriden yararlanarak herhangibir tabloda tutulan binary içeriği kullanmak ve bu içeriğin işaret ettiği resmi göstermek son derece kolaydır. Ne varki Asp.Net uygulamalarında her zaman için, render edilerek istemciye gönderilen bir sayfa içeriği mevcuttur. Bu içeriğin tipi(Content Type) daha farklıdır. Dolayısıyla binary formatta tutulan resimleri ele almak için farklı bir yaklaşım gerekmektedir.

Tabloda binary formatta tutulabilen resimleri Asp.Net uygulamalarında ele almak amacıyla, gösterilmek istenen resmi tek başına yorumlayan bir Asp.Net sayfası mevcuttur. Bu sayfanın tek bir görevi vardır o da ilgili resmi image formatlarından uygun olana göre sayfaya Render etmektir. Bunu incemelek için örnek bir senaryo göz önüne almakta fayda olacağı kanısındayım. Bu amaçla SQL Server 2005 ile birlikte gelen ve Production şemasında(Schema) bulunan Product, ProductPhoto ve ProductProductPhoto tabloları göz önüne alınabilir. Bu tablolar arasındaki ilişki kısaca aşağıdaki şekilde görüldüğü gibidir.

ProductPhoto isimli tabloda yer alan ThumbNailPhoto ve LargePhoto isimli alanlarda binary olarak ürün resimleri saklanmaktadır(tam olarak varbinary tipinde). Buna göre ilk örnek senaryomuzda kullanıcılar ürünlerin listelendiği bir sayfadan detay bilgilerini almak için başka bir sayfaya geçiş yapacaklardır. Detayların verildiği sayfada ürüne ait ThumbNailPhoto içeriğide bir resim olarak sayfa gösterilecektir. Başlamadan önce ProductPhoto tablosundaki herhangibir ThumbNailPhoto alanının içeriğini resim olarak nasıl gösterebileceğimize bakalım. Bu amaçla ResimGoster.aspx isimli aşağıdaki gibi bir sayfa tasarlanarak işe başlanabilir.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ResimGoster.aspx.cs" Inherits="ResimGoster" %>

Eminimki ResimGoster isimli web sayfasının içeriği son derece ilginç gelmiştir. Nitekim herhangibir HTML elementi yer almamaktadır. Aslında bu sayfanın tek amacı yüklenirken(bir başka deyişle Page_Load olay metodu çalışırken), ürün resmini ekrana binary olarak yazdırmaktır. Burada elbetteki hangi resmin gösterileceğide önemlidir. Bunun için sayfaya bir şekilde ProductPhotoID alanının değerinin gelmesi gerekmektedir. Bunun için en güzel yol QueryString kullanımıdır. Öyleyse bu sayfanın kodlarını yazarak işe devam edelim.

protected void Page_Load(object sender, EventArgs e)
{
    string resimId =
Request.QueryString["ResimId"];
    if (!String.IsNullOrEmpty(resimId))
    {
       
byte[] resimBytes=null;
        using (SqlConnection conn = new SqlConnection("data source=localhost;database=AdventureWorks;integrated security=SSPI"))
        {
            SqlCommand cmd = new SqlCommand("Select ThumbNailPhoto From Production.ProductPhoto
Where ProductPhotoId=@PhotoId", conn);
            cmd.Parameters.AddWithValue("@PhotoId", resimId);
            conn.Open();
            SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
            if(reader.Read())
               
resimBytes = reader.GetSqlBytes(0).Value;
            reader.Close();
            if (resimBytes != null)
            {
               
Response.ContentType = "image/gif";
               
Response.BinaryWrite(resimBytes);
            }
            else
                Response.Write("Resim gösterilemiyor.");
        }
    }
    else
        Response.Write("ResimId parametresi eksik yada hatalı.");
}

Page_Load olay metodu içerisinde ilk olarak HttpRequest sınıfının static QueryStringözelliği yardımıyla sayfaya gelen ResimId değeri alınmaktadır. Kullanıcıların bu sayfayı doğrudan talep etme ihtimaline göre ResimId değerinin boş gelme olasılığı bulunmaktadır. Bu nedenle String sınıfının IsNullOrEmpty metodu ile bir kontrol gerçekleştirilir. Sonrasında ise Production şemasındaki ProductPhoto tablosunda gelen değere göre ilgili ThumbNailPhoto alanı çekilir. Eğer gelen ResimId ile eşleşen bir ProductPhotoId alanı var ise bu satırın ThumbNailPhoto alanının değeri SqlDataReader nesne örneğine ait Read metodu yardımıyla okunur. Okuma işlemi sırasında GetSqlBytes metodu kullanılmakta ve Valueözelliği ile elde edilen byte dizisi resimBytes isimli alana aktarılmaktadır. Diğer taraftan HttpResponse sınıfının ContentTypeözelliği ile render edilecek sayfanın içeriği belirlenmektedir. Burada image/gif değeri ile basılacak içeriğin gif formatında bir resim olacağı belirtilmektedir.

ContentTypeözelliğinin varsayılan değeri text/HTML dir. Bu değer, tahmin edileceği üzere sayfanın çıktısının HTML olarak üretileceğini işaret etmektedir. Yaygın olarak kullanılan diğer versiyonlar aşağıdaki gibidir.
  • image/gif
  • image/jpeg
  • text/plain
  • application/vnd.ms-excel (çıktının excel dökümanı olmasını sağlar)
  • application/vnd.ms-word (çıktının word dökümanı olmasını sağlar)


Son olarak, elde edilen byte dizisinin çıktıya aktarılmasını sağlamak için yine HttpResponse sınıfının static metodlarından BinaryWriteçağırılmaktadır.

Artık sayfayı test ederek işlemlerimize devam edebiliriz. Elbette doğru sonuçları görebilmek için ResimId parametresini Url satırından göndermekte fayda vardır. Aşağıda örnek olarak 120 numaralı ProductPhotoId değerine sahip satır için elde edilen çıktı görülmektedir.

Burada, üretilen HTML sayfasının kaynak kodlarına bakılmak istenirse tarayıcı buna izin vermeyebilir(Örneğin Microsoft Internet Explorer 7.0 View Source buna izin vermemiştir). Bu sebepten çıktıyı Save As ile kaydetmek gerekebilir. Kaydedilen çıktının içeriği aşağıdaki ekran görüntüsünde yer aldığı gibi olacaktır. Dikkat edilecek olursa sayfanın çıktısı sonucu oluşturulan içerikte img elementi ve src niteliği yer almaktadır.

Elbette satır olarak karşılığı olmayan bir ResimId değeri girilirse sayfa çıktısı tarayıcı penceresinde aşağıdaki gibi olacaktır. Örneğin ProductPhotoId değeri 17 olan bir satır bulunmamaktadır.

Bununla birlikte kullanıcı bu sayfayı doğrudan talep eder ve ResimId parametresini kullanmassa aşağıdaki ekran çıktısını elde eder.

Burada olası bazı hataların önüne geçilmek amacıyla basit tedbirler alınmış ve ekrana bilgi mesajları verilmiştir. Gerçek hayat uygulamalarında son kullanıcıların daha doğru ve etkin bir şekilde uyarılması bir başka deyişle oluşan hatalar konusunda bilgilendirilmesi gerekmektedir.

Artık tek yapılması gereken senaryoyu biraz daha kullanışlı hale getirmektir. Bu amaçla ürünlerin gösterildiği Urunler.aspx isimli basit bir web sayfası tasarlanarak devam edilbilir. Bu sayfada ürünlere ait bir kaç temel bilgi bulunacak ama detayları için başka bir sayfaya yönlendirmede bulunulacaktır. Yönlendirilme yapılan sayfa tahmin edileceği üzere ürüne ait resmide içeren bir detay sayfasıdır. Urunler.aspx sayfasında basit olarak bir SqlDataSource kontrolü ve bu kontrolü ele alan bir GridView bileşeni düşünülebilir. Buna göre Urunler.aspx sayfasının kaynak kod tarafı aşağıdaki gibi tasarlanabilir.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Urunler.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Urunler</title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
           
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="dsProducts">
                <Columns>
                   
<asp:HyperLinkField DataNavigateUrlFields="ProductId" DataNavigateUrlFormatString="UrunDetay.aspx?PrdId={0}" DataTextField="Name" HeaderText="Urun Adı" />
                    <asp:BoundField DataField="ListPrice" DataFormatString="{0:C}" HeaderText="Liste Fiyatı" HtmlEncode="False" SortExpression="ListPrice" />
                </Columns>
           
</asp:GridView>
           
<asp:SqlDataSource ID="dsProducts" runat="server" ConnectionString="<%$ ConnectionStrings:AdvConStr %>" SelectCommand="SELECT Top 20 ProductID, Name, ListPrice FROM Production.Product Where ListPrice>=1000">
           
</asp:SqlDataSource>
        </div>
    </form>
</body>
</html>

Urunler.aspx sayfasında yer alan GridView kontrolünde HyperLinkField kontrolü kullanılmaktadır. Bu alan, ürünün adını(Name) göstermekte olup üzerine tıklandığında kullanıcıyı UrunDetay.aspx sayfasına göndermektedir. Bu işlem sırasında da PrdId isimli bir QueryString parametresi ProductId alanının değerini detay sayfasına taşımaktadır. Urunler.aspx isimli sayfanın çalışma zamanındaki çıktısı aşağıdaki ekran görüntüsünde yer aldığı gibidir.

Gelelim UrunDetay.aspx sayfasına. Bu sayfada basit olarak Urunler.aspx sayfasında seçilen ürünlere ait detay bilgileri gösterilecektir. Ancak önemli olan, seçilen ürünün resmininde ProductPhoto tablosundan binary olarak çekilerek ekrana bastırılacak olmasıdır. Bu amaçla tasarlanan Urunler.aspx sayfasında yine bir SqlDataSource kontrolü ve detaylar için DetailsView bileşeni aşağıdaki gibi kullanılabilir.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="UrunDetay.aspx.cs" Inherits="UrunDetay" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Urun Detaylari</title>
</head>
<body>
    <form id="form1" runat="server">
        <div>Ürün Detayları :<br />
           
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False" DataSourceID="dsProductDetails" Height="50px" Width="294px">
                <Fields>
                    <asp:BoundField DataField="Name" HeaderText="&#220;r&#252;n Adı" SortExpression="Name" />
                    <asp:BoundField DataField="ProductNumber" HeaderText="&#220;r&#252;n Numarası" SortExpression="ProductNumber" />
                    <asp:BoundField DataField="SafetyStockLevel" HeaderText="Stok Seviyesi" SortExpression="SafetyStockLevel" />
                    <asp:BoundField DataField="ReorderPoint" HeaderText="Sipariş Noktası" SortExpression="ReorderPoint" />
                    <asp:BoundField DataField="ListPrice" HeaderText="Liste Fiyatı" SortExpression="ListPrice" DataFormatString="{0:C}" HtmlEncode="False" />
                    <asp:BoundField DataField="StandardCost" HeaderText="Standart Maliyet" SortExpression="StandardCost" DataFormatString="{0:C}" />
                   
<asp:TemplateField HeaderText="Urun Resmi">
                       
<ItemTemplate>
                            <img alt="Ürün Resmi" runat="server"
src='<%#"ResimGoster.aspx?ResimID="+DataBinder.Eval(Container.DataItem, "ProductPhotoID") %>' id="urunResmi" />
                       
</ItemTemplate>
                   
</asp:TemplateField>
                </Fields>
           
</asp:DetailsView>
           
<asp:SqlDataSource ID="dsProductDetails" runat="server" ConnectionString="<%$ ConnectionStrings:AdvConStr %>" SelectCommand="SELECT Production.Product.ProductID, Production.Product.Name, Production.Product.ProductNumber, Production.Product.SafetyStockLevel, Production.Product.ReorderPoint, Production.Product.ListPrice, Production.Product.StandardCost, Production.ProductProductPhoto.ProductPhotoID FROM Production.Product INNER JOIN Production.ProductProductPhoto ON Production.Product.ProductID =Production.ProductProductPhoto.ProductID WHERE (Production.Product.ProductID = @PrdId)">
               
<SelectParameters>
                   
<asp:QueryStringParameter DefaultValue="1" Name="PrdId" QueryStringField="PrdId" />
               
</SelectParameters>
           
</asp:SqlDataSource>
        </div>
    </form>
</body>
</html>

UrunDetay.aspx isimli web sayfasında dikkat edilmesi ve üzerinde durulması gereken bazı noktalar vardır. Öncelikli olarak SqlDataSource kontrolünde kullanılan sorgu basit olarak aşağıdaki şekilde yer aldığı(Query Builder ile elde edilmiştir) gibi Product ve ProductProductPhoto tablolarının join ile birleştirilmiş bir halidir ve ProductId değerinin where ifadesinde ele almaktadır. Nitekim ürün resminin ProductPhoto tablosundan tedariki için ProductPhotoID değerinin bilinmesi gerekmektedir. Bu nedenle Product ve ProductProductPhoto tabloarı Join ile birleştirilmiştir.

Diğer taraftan DetailsView kontrolü içerisindede resmin gösterilebilmesi için bir TemplateField kullanılmış ve ItemTemplateşablonu içerisinde img elementi aşağıdaki gibi kullanılmıştır.

<asp:TemplateField HeaderText="Urun Resmi">
                       
<ItemTemplate>
                            <img alt="Ürün Resmi" runat="server"
src='<%#"ResimGoster.aspx?ResimID="+DataBinder.Eval(Container.DataItem, "ProductPhotoID") %>' id="urunResmi" />
                       
</ItemTemplate>
                   
</asp:TemplateField>

Burada dikkat edilmesi gereken en önemli nokta srcniteliğine(attribute) değer atamasının nasıl yapıldığıdır. DataBinder sınıfının Eval metodunu kullanarak o anki satırın içerisinde yer alan ProdcutPhotoId değeri ResimGoster.aspx sayfasına ResimID adlı parametre ile gönderilmektedir. Buda yazımızın başında tasarladığımız ResimGoster sayfasının çağırılması ve bir resim içeriğinin elde edilerek buradaki img kontrolü içerisinde gösterilmesi anlamına gelmektedir. Sonuç itibariyle UrunDetay.aspx sayfası test edildiğinde aşağıdakine benzer bir ekran çıktısı ile karşılaşılacaktır.

Buraya kadar yaptıklarımızı özetleyecek olursak eğer, binary olarak tutulan resimlerin gösterilmesi için izlenebilecek yollardan birisinin adımları aşağıdaki gibi olacaktır.

  • İlk olarak resim içeriğini binary olarak tarayıcıya basabilecek bir aspx sayfası tasarlanır.
  • Sayfanın amacı gereği aspx kaynağında(Source) sadece Page direktifi bırakılır ve diğer içerik silinir. Bu zorunlu değildir. Ancak tavsiye edilen yoldur.
  • Sayfanın Page_Load olay metodu kodlanır.
  • Page_Load olay metodunda gösterilmek istenen resme ait satırın bulunabilmesi için QueryString' den yararlanılabilir.
  • Resme ait binary içeriğin kod tarafında bytedizisi(byte[])şeklinde ele alınması sağlanır.
  • Elde edilen byte dizisinin sayfaya resim olarak basılmasını sağlamak için önce ContentTypeözelliğinin değeri image/gif veya image/jpeg olarak belirlenir.
  • Resmi yazdırmak içinse BinaryWrite metodu çağırılır.

Buradaki örnek göz önüne alındığında istemci sayısının fazla olacağı düşünelecek olursa performansı arttırmak adına parametre bazlı olacak şekilde ön bellekleme(Caching) yapılması sağlanabilir. Böylece ResimGoster.aspx sayfasının sürekli olarak Page_Load kodlarını çalıştırmasının önüne geçilmiş olunur.

Resimlerin çok sık değişmediği düşünülüyorsa ve SQL Server kullanılıyorsa tablo bağımlı bir ön belleklemede yapılabilir(Sql Cache Dependency).

Söz gelimi ResimGoster.aspx dosyasının içeriği aşağıdaki gibi değiştirilerek sayfanın çıktısının belirli bir süreliğine(örneğin 60 saniye boyunca)ön bellekte tutulması sağlanabilir.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ResimGoster.aspx.cs" Inherits="ResimGoster" %>
<%@ OutputCache Duration="60" VaryByParam="ResimID" %>

Gelelim resimlerin işlenmesi ile ilgili diğer bir yaklaşıma. Örnekte resmi gösteren img elementinin src niteliğinde bir url adresinden yararlanılmaktadır. Elbetteki bu işlem programatik olarak kod üzerindende geliştirilebilir. Örneğin ürün bilgilerini bir DataList kontrolü üzerinde göstermek istediğimizi varsayalım. Bu kez resim alanlarının DataList kontrolü içerisindeki img elementinin src niteliğine bağlanmasını kod tarafından gerçekleştirmeye çalışacağız. Bu amaçla aşağıdaki gibi UrunListesi.aspx isimli bir web sayfası hazırlanarak işlemlere devam edilebilir.

<form id="form1" runat="server">
    <div>
       
<asp:DataList ID="DataList1" runat="server" DataSourceID="dsProducts" Width="600px">
           
<ItemTemplate>
               
<table>
                    <tr>
                       <td colspan="2">
                            <asp:Label ID="NameLabel" runat="server" Font-Italic="True" ForeColor="#000040" Text='<%# Eval("Name") %>'></asp:Label>
                        </td>
                        <td style="width: 100px; text-align: right">
                            <asp:Label ID="ProductIDLabel" runat="server" Font-Bold="True" ForeColor="#C00000"
Text='<%# Eval("ProductID") %>'></asp:Label>
                        </td>
                    </tr>
                    <tr>
                        <td rowspan="3" style="width: 100px">
                           
<img runat="server" id="urunResmi" alt="Urun Resmi" src="" />
                        </td>
                        <td style="width: 100px">Standart Maliyet</td>
                        <td style="width: 100px">
                            <asp:Label ID="StandardCostLabel" runat="server"
Text='<%# Eval("StandardCost", "{0:C}") %>'></asp:Label>
                        </td>
                    </tr>
                    <tr>
                        <td style="width: 100px">Liste Fiyatı</td>
                        <td style="width: 100px">
                            <asp:Label ID="ListPriceLabel" runat="server"
Text='<%# Eval("ListPrice", "{0:C}") %>'></asp:Label>
                        </td>
                    </tr>
                    <tr>
                        <td style="width: 100px">Sınıf</td>
                        <td style="width: 100px">
                            <asp:Label ID="ClassLabel" runat="server"
Text='<%# Eval("Class") %>'></asp:Label>
                        </td>
                    </tr>
                    <tr>
                        <td colspan="3"><hr /></td>
                    </tr>
                </table>
           
</ItemTemplate>
       
</asp:DataList>
       
<asp:SqlDataSource ID="dsProducts" runat="server" ConnectionString="<%$ ConnectionStrings:AdvConStr %>" SelectCommand="SELECT Top 20 P.ProductID, P.Name, P.StandardCost, P.ListPrice, PP.ProductPhotoID, P.Class FROM Production.Product P INNER JOIN Production.ProductProductPhoto PP ON P.ProductID = PP.ProductID WHERE P.ListPrice>1400">
       
</asp:SqlDataSource>
    </div>
</form>

UrunListesi.aspx sayfasında yer alan DataList kontrolü Production şemasındaki Product ve ProductProductPhoto tablolarının birleşiminden oluşan sonuç kümesinden ilk 20 satırı göstermek üzere tasarlanmıştır ve hatta ListePrice alanına göre filtreleme eklenmiştir. Burada özellikle üzerinde durmamız gereken nokta, img elementidir. ItemTemplateüzerindeki tablo içerisine yerleştirilen img elementinin src niteliğini kod tarafında ele alabilmek için DataList kontrolünün ItemDataBound olayından yararlanılabilir. Bu olay metodu içerisinde söz konusu img elementi bulunmalı ve src niteliğine o anki satırın ProductPhotoId değeri QueryString parametresi olarak aktarılmalıdır. O halde bu amaçla aşağıdaki kod parçasını yazmamız yeterli olacaktır.

protected void DataList1_ItemDataBound(object sender, DataListItemEventArgs e)
{
    if (
e.Item.ItemType == ListItemType.Item
        || e.Item.ItemType ==
ListItemType.AlternatingItem)
    {
        // Önce ProductPhotoId değeri bulunmalı
        string resimId =
DataBinder.Eval(e.Item.DataItem, "ProductPhotoId").ToString();
        // img elementi bulunmalı ve src niteliğinin değeri değiştirilmeli.
       
HtmlImage resimElementi = (HtmlImage)e.Item.FindControl("urunResmi");
        resimElementi.
Src = "~/ResimGoster.aspx?ResimId=" + resimId;
    }
}

ItemDataBound olayı DataList içerisindeki her bir satır için çalışacağından, işlemleri sadece Item ve AlternatingItem tipindeki satırlarda yapmakta fayda vardır. Bu amaçla DataListItemEventArgs tipinden olan e isimli parametrenin özelliklerinden faydalanılmaktadır. Sonrasında ise o anki satır ile gelen ProductPhotoId değerinin elde edilmesi gerekmektedir. Bu amaçlada DataBinder sınıfının Eval metodu ele alınmaktadır. İlk parametre ile o anki veri satırı yakalanmakta, ikinci parametre ilede söz konusu veri satırındaki ProductPhotoId değeri istenmektedir. Eval metodu geriye Object tipinden bir değer döndürdüğü için bilinçli olarak string tipine dönüştürülmüştür. Nitekim url katarında kullanılacak bilgi string' dir.

Bu işlemlerin ardından img kontrolünün elde edilmesi sağlanır. Bunun içinde e.Itemüzerinden FindControl metodu çağırılmıştır. FindControl metodunun parametresi kontrolün id değerinin işaret etmektedir. Hatırlanacağı üzere img kontrolünün id niteliğine kaynak tarafında urunResmi adı verilmiştir. img kontrolü HtmlImage tipinden bir kontroldür ve FindControl metodu geriye Control tipinden bir referans döndürdüğünden sonuç referansı bilinçli olarak HtmlImage tipine dönüştürülmüştür. Son olarak elde edilen HtmlImage kontrolüne ait referans üzerinden Src niteliğinin değeri değiştirilir. Burada yapılan işlem tüm yazı boyunca üzerinde durduğumuz konudur. Uygulamayı bu haliyle çalıştırdığımızda aşağıdakine benzer bir ekran görüntüsü elde ederiz.

Bu makalemizde tablolarda binary olarak tutulan resim alanlarını web sayfalarında küçük bir hile ile nasıl işleyebileceğimizi incelemeye çalıştık. Görsellik hemen hemen tüm uygulamalarda önemli bir faktör olduğundan resim alanlarının bu şekilde ele alınıyor olmasını bilmek önemli bir avantaj sağlamaktadır. Kullanılan teknikte önemli olan nokta binary alanın içeriğini tek başına ele alıp resim formatında çıktı veren bir sayfanın var olmasıdır. Ayrıca resmi gösteren kontrolün basit bir img bileşeni olduğuna ve src niteliğinin önemine dikkat edilmelidir. Bu sayfanın çıktısı örneklerden de görüldüğü gibi pek çok farklı biçimde kullanılabilir ve son kullanıcıya görsel olarak daha doyurucu bir içerik sağlanabilir. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama için Tıklayın

Burak Selim ŞENYURT
selim@bsenyurt.com

Asp.Net Temelleri : Etkili Trace Kullanımı

$
0
0

Değerli Okurlarım Merhabalar,

Web uygulamalarında son kullanıcıların(End Users)şikayetçi olabileceği pek çok konu vardır. Bunlar arasında popüler olanlarından biriside sayfaların yavaş açılıyor olmasıdır. Nihayetinde son kullanıcıları her zaman için sabırsız ve acelesi olan kişiler olarak düşünmek doğru bir yaklaşım olacaktır. Sayfaların yavaş açılıyor yada geç cevap veriyor olmasının donanımsal yada çeşitli çevre faktörleri nedeniyle bilinen sebepleri vardır. Söz gelimi bağlantı hızının düşük olması ilk akla gelen nedendir. Ancak geliştiriciler(developers) olarak bizlerinde üzerine düşen önemli görevler vardır. Sonuç itibariyle bir geliştirici, mininum donanım gereksinimleri karşılandığı takdirde hızlı ve yeterli performasta çalışabilen bir web uygulaması geliştiriyorsa, donanım kapasite ve yeteneklerinin daha yüksek olduğu son kullanıcılarda aynı web uygulamasının çok daha iyi sonuçlar vereceği düşünülebilir.

Trace mekanizmasını kullanmak için küçük bir neden; örneğin talep(request) edilen sayfanın üretilmesi ve HTMLçıktısının hazırlanması uzun zaman alıyor olabilir. Sayfanın üretimi sırasında hangi adımların çok zaman aldığını görmek için Trace mekanizmasından yararlanılabilir.

Elbetteki bir web uygulamasının hızını, performansını ve cevap verebilme yeteneklerini etkiliyecek pek çok faktör vardır. Yine örnek olarak düşünülürse, 10 kullanıcılı bir intranet ortamında gayet iyi çalışan bir web uygulaması, internet' e açıldığında karşılaşabileceği n kullanıcı talebi sonrasında beklenen performansı gösteremeyebilir. Buradaki en büyük etken kullanıcı sayısıdır. Bu nedenle web uygulamalarını ne kadar iyi programladığımızı düşünsekte, sonuçlara bakıldığında gerek test gerekse gerçek ortamlarda istediğimiz sonuçları alamadığımızı görebiliriz. Dolayısıyla test aşamasında uygulamanın genelini izleyebilmek ve sonuçları analiz ederek gerekli tedbirleri alabilmek son derece önemli bir konudur. Bu gibi durumlarda web uygulamasının genelinin yada problemli olabileceği düşünülen sayfaların çalışma zamanı ve sonrasındaki hareketlerinin izlenmesi adına Asp.Net modelinde Trace mekanizasından faydalanılmaktadır. İşte bu makalemizde Trace kavramını derinlemesine incelemeye çalışacağız.

Trace mekanizması ile sayfa(Page) veya uygulama seviyesinde(Application Level), kullanıcıdan gelen bir talebin işlenmesi sırasında ve sonuçların elde edilmesinin sonrasında gerekli host bilgileri yakalanabilir. Bu bilgiler değerlendirilerek sorunun nerede olduğu daha kolay bir şekilde tespit edilebilir.

Trace; HTTP talebi ile ilgili olaraktan sunucu(server) taraflı çalışma zamanı(run-time) ve sonrası detay bilgilerinin alınmasını sağlayan bir Asp.Net mekanizmasıdır.

Trace mekanizması sadece sayfa ve uygulama seviyesinde değil, bileşen seviyesindede(Component Level) ele alınabilir. Nitekim, web uygulamalarında sayfaların çoğu buton arkası programlama yerine bileşenleri ele alır. Çok doğal olarak bileşenler içerisinde meydana gelebilecek sorunların ele alınması sırasında yine Trace mekanizması ele alınabilir. Trace ile elde edilen sonuçlar üretilen genellikle sayfa sonlarına HTML bilgisi olarak eklenebilirler. Bunun dışında Trace.axd isimli özel dosyalardanda uygulama genelinde, talep sırasına göre sayfaların izlenmesi sağlanabilir.

İlk olarak Trace işlemlerini sayfa seviyesinde ele almaya çalışacağız. Bir sayfanın Trace çıktısını yakalamak için iki farklı yöntem vardır. Bunlardan birincisi Page direktifinde yer alan Trace niteliğine true değeri atanmasıdır. Diğer yol ise kod tarafında Trace nesne örneği üzerinden IsEnabledözelliğine true değerini atamaktır.

Direktik içerisinden Trace mekanizmasının açılması;

<%@ Page Language="C#" Trace="true"%>

Kod tarafından Trace mekanizmasının açılması;

protected void Page_Load(object sender, EventArgs ea)
{
   
Page.Trace.IsEnabled = true;
}

Sonuç olarak bu iki kullanım şekline göre sayfanın sonuna Trace bilgileri aşağıdaki ekran görüntüsünde olduğu gibi eklenecektir.

Bu çıktıda tüm Trace bilgisi gösterilmemektedir. Temel olarak Trace çıktısında yer alan kısımlar aşağıdaki tabloda olduğu gibidir.

Trace Çıktısında Yer Alan Bölümler
Request DetailsBu kısımda talep ile ilişkili genel bilgiler yer alır. Örneğin talep tipi(request type) ilk çağrıda Get iken sayfa sunucuya gönderildikten sonra Post değerini alır. Bir başka örnek detay bilgisi olarakta Session Identity(SessionId) değeri verilebilir.
Trace InformationBu kısımda özellikle sayfanın yaşam döngüsü(Page Life Cycle) içerisinde yer alan olayların genel süreleri yer alır. Buradaki bilgilerden hareket ederek sayfanın üretimi sırasında hangi kısımların ne kadar süre harcadığı görülebilir.
Control TreeSayfa içerisindeki kontrollerin ağaç yapısını verir. Böylece özellikle form içerisinde kullanılan başka taşıyıcı kontroller(container controls) var ise, bunların içerisinde yer alan alt kontrollerin elde edilmesi için ne kadar derine inilebileceği kolayca tespit edilebilir.
Session StateKullanıcıya ait oturum(Session) içerisinde tutulan verilerin değerlerinin ve tiplerinin gösterildiği kısımdır.
Application StateApplication nesnesi içerisindeki anahtarlar(keys) ve değerlerinin(values) gösterildiği kısımdır.
Request Cookies CollectionSayfaya gelen talep sırasındaki çerezlere ait bilgilerin tutulduğu koleksiyondur.
Response Cookies CollectionSayfadan istemciye dönen cevap içerisinde yer alan çerezlere(Cookies) ait bilgilerin tutulduğu koleksiyondur.
Headers Collectionİstemciden sunucuya gelen HTTP paketindeki başlık bilgileri görülebilir. Örneğin Accept_Encoding bilgisi.
Response Headers Collectionİstemciye dönen HTTP paketindeki başlık bilgilerini içerir. Örneğin üretilen içerik tipi (Content Type) görülebilir.
Form CollectionSunucudan istemciye gönderilen form bilgileri görülebilir. Örneğin üretilen _VIEWSTATE değerine bakılabilir.
Querystring CollectionSayfadan istenen query string bilgileri var ise bunların adları ve o anki değerleri görülebilir.
Server VariablesSunucu değişkenleri elde edilebilir. Örneğin, sunucu uygulamanın fiziki yolu(APPL_PHYSICAL_PATH), yerel ip adresi(LOCAL_ADDR) gibi.

Aynı bilgilere ulaşmak için adres satırında Trace.axd dosyasıda talep edilebilir.

Trace.axd, WebResource.axd benzeri bir dosyadır. Dolayısıyla çalışma zamanında özel şekilde ele alınır. Asp.Net çalışma ortamı Trace.axd taleplerinin TraceHandler isimli sınıfa ait nesne örneklerine devredilerek karşılanmasını sağlar.

Dolayısıyla söz konusu talep sonrası oluşan ekran çıktısı TraceHandler sınıfı tarafından hazırlanır. Makinedeki ana web.config dosyasının içeriğine bakıldığında bu açıkça görülebilir. Burada dikkat edilmesi gereken noktalardan birisi sadece Trace.axd için böyle bir handler' ın yazılmış olmasıdır. Trace.axd ve WebResource.axd dışında gelecek talepler HttpNotFoundHandler tarafından ele alınmaktadır.

Aşağıdaki ekran görüntüsünde Trace.axd' nin talep edilmesinin örnek sonuçları gösterilmektedir.

Kod tarafında Trace bilgilerini ele almak için Page nesne örneği üzerinden Traceözelliği kullanılmaktadır. Aslında Trace özelliği doğrudan TraceContext sınıfına ait bir nesne örneği döndürmektedir. TraceContext sınıfı sealed olarak işaretlenmiş bir tip olduğundan türetilerek(inherit)özelleştirilemez. Bu sınıfın Framework içerisindeki yeri şekilsel olarak aşağıdaki gibidir.

TraceContext sınıfının üyeleri göz önüne alındığında, Write ve Warn metodları kod içerisinden Trace çıktısına bilgi yazdırmak amacıyla kullanılmaktadır. Bunların arasındaki tek fark Warn metodunun yazıyı kırmızı punto ile basıyor olmasıdır. Diğer taraftan her iki metodda 3 farklı aşırı yüklenmiş versiyona sahiptir. Bu versiyonlar yardımıyla yazdırılan bilginin kategorisi(Category), içeriği(Message) ve o anda yakalanmış bir istisna(Exception) var ise bu istisna nesne örneği Trace çıktısına gönderilebilir. IsEnabledözelliği ile daha öncedende belirtildiği gibi Trace' in etklinleştirilmesi veya pasif moda çekilmesi sağlanabilir. TraceMode özelliği TraceMode enum sabiti tipinden değerler alabilmektedir.

Bu değerler sayesinde Trace çıktısında yer alan Trace Information kısmındaki bilgilerin kategori veya süre bazlı olarak sıralanıp sıralanmayacağı belirlenebilir. Bu üyeler dışında TraceContext sınıfı özellikle bileşenlerin içerisinde ele alınmak istendiğinde yapıcı metoddan bir örnek oluşturulması gerekmektedir. Bunun için yapıcı metod HttpContext tipinden bir parametre alır. Bunu ilerleyen kısımlarda ele alacağız. TraceContext sınıfının birde TraceFinished isimli olayı vardır. TraceContextEventHandler temsilcisinin tanımladığı yapıya uygun olay metodu çağırılabilir. Bu olay TraceContext sınıfına .Net 2.0 ile birlikte katılmıştır. TraceFinished olayı, Trace bilgileri toplandıktan sonra TraceContext sınıfının kendisi tarafından tetiklenir. TraceContextEventArgs tipinden olan ikinci parametre sayesinde Trace ile ilgili veriler elde edilebilir ve bu sayede farklı veri kaynaklarına yazılmaları sağlanabilir. Aşağıdaki kod parçasında bu olayın kullanımı örneklenmeye çalışılmaktadır.

protected void Page_Load(object sender, EventArgs ea)
{
    Page.Trace.IsEnabled = true;
    Page.
Trace.TraceFinished += new TraceContextEventHandler(Trace_TraceFinished);
}

void
Trace_TraceFinished(object sender, TraceContextEventArgs e)
{
   
IEnumerator numarator=e.TraceRecords.GetEnumerator();
    while (numarator.
MoveNext())
    {
       
TraceContextRecord record = (TraceContextRecord)numarator.Current;
        Response.Write(record.
Category + " : " + record.Message + "<br/>");
    }
}

TraceContextEventArgs tipinden e değişkeni üzerinden elde edilen koleksiyon içerisindeki her bir eleman TraceContextRecord sınıfına ait birer nesne örneğidir. Bu tip yardımıyla Trace Information kısmındaki kategori, mesaj, istisna tipi ve uyarı olup olmadığı(IsWarning) bilgilerine erişilebilir. Aşağıdaki ekran görüntüsünde bu bilgiler yer almaktadır.

Bu kodun yer aldığı sayfa ilk talep edildiğinde aşağıdaki ekran çıktısında yer alan içerik elde edilir.

Post işleminden sonra ise TraceFinished içerisinden yakalanan mesajların sayısı sayfanın yaşam döngüsü nedeni ile artacak ve aşağıdakine benzer bir ekran çıktısı elde edilecektir.

Söz konusu olay metodu ile her ne kadar Trace Information ile ilgili çok az bilgiye ulaşsakta zaten geri kalan verilerin çoğu HttpResponse veya HttpRequest gibi tipler yardımıyla elde edilebilmektedir. Dolayısıyla bu olay içerisinde bu tipler aracılığıyla elde edilen veriler başka veri ortamlarına da yazdırılabilirler.

Şimdi Trace mekanizmasını farklı yollar ile incelemeye devam edeceğiz. İlk olarak sayfa seviyesinde Trace bilgilerini izlemenin nasıl faydası olabiliceğini görmeye çalışacağız. Bu amaçla aşağıdaki web sayfası ve kodları göz önüne alınabilir. (Bu ve sonraki asp.net sayfalarında, işlemlerin kolay takip edilmesi amacıyla inline-coding tekniği kullanılmıştır.)

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

protected void btnCek_Click(object sender, EventArgs e)
{
    string urunAdlari="";
    using (SqlConnection conn = new SqlConnection("data source=.;database=AdventureWorks;integrated security=SSPI"))
    {
        SqlCommand cmd = new SqlCommand("Select Top 10000 TransactionId From Production.TransactionHistory", conn);
        conn.Open();
        SqlDataReader reader = cmd.ExecuteReader();
        while (reader.Read())
        {
            urunAdlari += reader["TransactionId"].ToString()+"|";
        }
        reader.Close();
    }
    Session.Add("TumKategoriler", urunAdlari);
}

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <asp:Button ID="btnCek" runat="server" Text="Veriyi Çek" OnClick="btnCek_Click" />
            </div>
        </form>
    </body>
</html>

Web sayfamızda yer alan düğmeye basıldığında, TransactionHistory tablosundaki ilk 10000 TransactionId değeri elde edilir ve bunlar bir string içerisinde birleştirilerek Session' a atılır. Kod her ne kadar anlamlı gözükmesede (ki gözükmediği ortada :) ) ortaya çıkardığı sonuçlar nedeniyle kayda değerdir. Nitekim sayfa talep edildikten sonra düğmeye basıldığında sayfanın uzun bir sürede çıktı verdiği görülecektir. Burada herhangibir hata görünmemektedir. Ancak sayfanın çıktısının üretilmesi ve istemciye gelmesinin neden uzun sürdüğüde incelenmelidir. İşte bu amaçla bu sayfa üzerinde Trace işlemi gerçeklenmelidir. Hatırlanacağı üzere bunu kod veya direktif içerisinde yapabileceğimizi belirtmiştik. Bu nedenle Page direktifinde yer alan Trace niteliğine true değerini verelim ve kodlarımızı aşağıdaki gibi değiştirelim.

using (SqlConnection conn = new SqlConnection("data source=.;database=AdventureWorks;integrated security=SSPI"))
{
    SqlCommand cmd = new SqlCommand("Select Top 10000 TransactionId From Production.TransactionHistory", conn);
    conn.Open();
   
Trace.Warn("TransactionId Cekme", "TransactionId değerleri çekilmeye başlanacak");
    SqlDataReader reader = cmd.ExecuteReader();
    while (reader.Read())
    {
        urunAdlari += reader["TransactionId"].ToString()+"|";
    }
   
Trace.Warn("TransactionId Cekme", "TransactionId değerleri çekildi...");
    reader.Close();
}

Burada Warn metodu kullanılarak urunAdlari toplanmadan önce ve toplandıktan sonra Trace çıktısına kategori bazlı değerler yazdırılmaktadır. Bunun sonucunda sayfa çıktısı aşağıdaki gibi olacaktır.

Sonuçları irdelerken From First(s) ve From Last(s) kısımlarındaki süreler çok önemlidir. From First(s) ile sayfanın talepten sonraki yaşam döngüsü başladığından beri geçen toplam süre ifade edilir. Form Last(s) ise, bir önceki işlem ile son işlem arasındaki geçen süre farkıdır. Böylece kod yardımıyla sayfanın çalışma zamanı çıktısına bakılmış ve süre uzamasının nerede olduğu tespit edilebilmiştir. Görüldüğü gibi string bilgisini oluştururken + operatörü nedeni ile verinin oluşumu son derece fazla zaman almıştır. Aslında burada alınacak tedbir son derece basittir. + operatörü yerine StringBuilder kullanmak. Bunun için kod aşağıdaki gibi değiştirilebilir.

StringBuilder builder = new StringBuilder();
using (SqlConnection conn = new SqlConnection("data source=.;database=AdventureWorks;integrated security=SSPI"))
{
    SqlCommand cmd = new SqlCommand("Select Top 10000 TransactionId From Production.TransactionHistory", conn);
    conn.Open();
    Trace.Warn("TransactionId Cekme", "TransactionId değerleri çekilmeye başlanacak");
    SqlDataReader reader = cmd.ExecuteReader();
    while (reader.Read())
    {
       
builder.Append(reader["TransactionId"].ToString());
       
builder.Append("|");
    }
    Trace.Warn("TransactionId Cekme", "TransactionId değerleri çekildi...");
    reader.Close();
}
Session.Add("TumKategoriler",
builder.ToString());

Buna göre sonuçlar aşağıdaki ekran görüntüsündeki gibi olacaktır.

Görüldüğü gibi kod içerisinde şüpheli bulunulan noktalarda uygulanacak teknikler ile sayfanın çalışma zamanındaki hali çok daha kolay bir şekilde izlenebilmektedir.

Trace sınıfına ait Write ve Warn metodları uygulama içerisinde pek çok yerde kullanılabilir. Buna göre Trace mekanizmasının geçersiz kılınması haline, söz konusu metodlar görmezden gelineceği için, tüm uygulama kodunu gözden geçirerek bu metodlara ait satırların kaldırılmasına gerek kalmayacaktır.

Uygulama seviyesinde Trace mekanizmasını aktif kılabilmek için web.config dosyasında trace elementi kullanılmalı ve enabledözelliğine true değeri atanmalıdır. Bunun dışında trace elementi içerisinden belirlenebilecek bazı ayarlamalarda yapılabilir. Böylece web uygulaması içerisindeki tüm sayfalar için izleme yapılabilir.

trace elementinin içerisinde kullanılabilecek nitelikler ve anlamları ise aşağıdaki tabloda olduğu gibidir.

trace Elementine Ait Genel Özellikler
requestLimittrace log içerisinde uygulamaya ait kaç talebin(request) saklanacağı belirtilir. Limit aşılması halinde eğer mostRecentözelliğinin değeri false ise uygulama yeniden başlatılana veya trace log bilinçli bir şekilde temizlenene kadar log' a bilgi atılmaz. Bu niteliğin varsayılan değeri 10 dur. Maksimum olarak 10000 değeri verilebilir. 10000' den büyük bir değer verilmesi halinde ise, bu değer otomatik olarak 10000' e çekilir.
pageOutputtrue ise Trace bilgileri web uygulaması içerisindeki sayfaların sonuna eklenir. false olması halinde ise izleme bilgileri sadece Trace.axdüzerinden takip edilebilir.
localOnlytrue olmaslı halinde Traceçıktısını istemciler göremez. Sadece web uygulamasının olduğu makinedeki kullanıcı görebilir. Bu bir anlamda Trace çıktısını sadece geliştiricinin izleyebilmesi anlamınada gelebilir ki güncel projelerde sıkça başvurulan bir yöntemdir.
enabledtrue olması haline uygulama bazında(Application Level) izleme modu açık olacaktır.
mostRecentvarsayılan değeri false olan bu niteliğe true değeri verilirse , requestLimit aşılması halinde gelen talepler son elde edilen taleplerin üzerine yazılır. Böylece son taleplerin görülebilmesi sağlanmış olur. Bu nitelik(attribute) Asp.Net 2.0 ile birlikte yeni gelmiştir.
traceModeDaha öncedende değinildiği gibi, Trace Information kısmında yer alan bilgilerin, kategori veya süre bazlı sıralanıp sıralanmayacağını belirtir.
writeToDiagnosticTraceVarsayılan değeri false olan bu özellik Asp.Net 2.0 ile birlikte gelmiş olup, Trace mesajlarının System.Diagnostics alt yapısına gönderilip gönderilmeyeceğini belirlemekte kullanılır.

Aşağıda örnek bir trace elementi içeriği yer almaktadır.

<?xml version="1.0"?>
<configuration>
    <appSettings/>
    <connectionStrings/>
   
<system.web>
       
<trace enabled="true" requestLimit="5" mostRecent="true" pageOutput="false" localOnly="true" writeToDiagnosticsTrace="true" />
        <compilation debug="true"/>
        <authentication mode="Windows"/>
   
</system.web>
</configuration> 

Buna göre son 5 talebe ait trace bilgileri tutulacak, sayfa çıktısı verilmeyecek bir başka deyişle trace.axd ile talep edilebilecek, yanlızca host makinedeki kullanıcı trace.axd' ye bakabilecek ve bilgiler System.Diagnostics alt yapısına devredilebilecektir. Dolayısıyla uygulama çalıştırıldıktan sonra söz konusu izleme bilgileri aşağıdaki gibi olacaktır.

Burada sayfalara ait izleme detaylarına bakmak için View Details linki kullanılabilmektedir. Çıktıda yer alan bilgilere bakıldığında taleplerin zamanı(Time of Request), durumu-status code (örneğin talep edilen sayfa başarılı bir şekilde yüklendiyse 200, olmayan bir sayfa talep edildiyse 404...), HTTP' nin hangi metoduna göre (POST, GET...) talep edildiği(Verb) ve talep sırası(No) gibi bilgiler yer alır.

Geliştirilen web uygulaması dağıtılırken trace.axd dosyasının hiç bir şekilde talep edilememesi sağlanabilir. Bunun için web.config dosyasında yer alan system.web elementi altında aşağıdaki değişikliği yapmak yeterlidir.

<system.web>
   
<httpHandlers>
       
<remove verb="*" path="trace.axd"/>
   
</httpHandlers>
</system.web>

Buna göre söz konusu web.config dosyasının içeren uygulamada herhangibir şekilde Trace.axd dosyası talep edilirse aşağıdaki ekran görüntüsü elde edilecektir.

Gelelim izleme ile ilişkili diğer konulara. Bazı durumlarda kod içerisinde kullandığımız Trace ifadelerinin sadece istisnalar oluştuğunda tutulmasını isteyebiliriz. Buna ek olarak, trace içerisine atılacak istisna bilgilerinin sadece host makinedeki kullanıcıya gösterilmesi istenebilir. Trace bilgisini sadece yerel kullanıcıya göstermek için localOnlyözelliği kullanılabilir. Ancak burada durum biraz daha farklıdır. Nitekim, trace basılmakta ama içeride istisna oluşması halinde gösterilen içerik sadece yerel kullanıcı için oluşturulmak istenmektedir. Bu vakkayı çözmek için Trace ifadelerinin catch blokları içerisinde ele alınacağı ortadadır. Diğer taraftan Write veya Warn metodlarının üçüncü parametreleri burada önemlidir. Nitekim üçüncü parametre oluşan istisna(Exception) referansını taşımaktadır. Exception tipini trace çıktısına vermek dışında, talepte bulunan kullanıcınında host makineden geldiğini tespit etmek gerekir. Bu nedenle aşağıdaki adımlar izlenebilir.(Olayın daha kolay anlaşılabilmesi için konu şema ile desteklenmiştir)

1.Request ile istemcinin Host adresi öğrenilir. Burada Request.UserHostAddress' den faydalanılabilir.

2. Elde edilen adresin 127.0.0.1 olup olmadığına bakılır. Öyleyse talep yerel makineden gelmiştir ve trace açılabilir.

3. Talep yerel makineden gelmiyorsa Requset.ServerVariables("LOCAL_ADDR") ile elde edilen ip adresi ve Request.UserHostAddress ile elde edilen istemci adresi karşılaştırılır. Bu yerel host adresinin 127.0.0.1' den farklı olmasına karşı alınan bir tedbirdir. Eğer eşitlerse talebin yine yerel makineden geldiği anlaşılabilir.

4. Eğer taleplerin yerelden geldiği anlaşıldıysa çıktı üretilir.

Kod tarafında ise örnek olarak aşağıdaki sayfa düşünülebilir. Burada bilinçli olarak bir istisna(Exception) oluşturulmaktadır.

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected
bool TraceYazilsinmi()
    {
        string userHostAddress =
Request.UserHostAddress;
        if (
userHostAddress == "127.0.0.1")
           
return true;
        else
        {
            string localAddress =
Request.ServerVariables.GetValues("LOCAL_ADDR").ToString();
           
if (localAddress == userHostAddress)
               
return true;
            else
                return false;
        }
    }

    protected void btnBaglantiAc_Click(object sender, EventArgs e)
    {
        SqlConnection conn = null;
        try
        {
            conn = new SqlConnection("data source=.;database=" + txtVeritabani.Text + ";integrated security=SSPI");
            conn.Open();
            Response.Write("Bağlantı açıldı");
        }
       
catch (Exception excp)
       
{
            if (TraceYazilsinmi())
            {
               
Trace.IsEnabled = true;
               
Trace.Warn("Developer İçin Hata Bilgisi", "Bağlantı açılması sırasında hata oluştu", excp);
            }
       
}
        finally
        {
            if (conn != null
                && conn.State == ConnectionState.Open)
                    conn.Close();
        }
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            Veritabanı Adı : <asp:TextBox ID="txtVeritabani" runat="server" />
            <asp:Button ID="btnBaglantiAc" runat="server" Text="Bağlantı Aç" OnClick="btnBaglantiAc_Click" />
        </div>
    </form>
</body>
</html>

Uygulamada kullanıcı textbox kontrolünden girdiği veritabanı adı için bir bağlantı açmaya çalışmaktadır. Eğer bağlantının açılması sırasında bir hata oluşursa ve kullanıcı host makineden talepte bulunmuşsa Trace çıktısının etkinleştirilmesi ve istisna mesajının buraya yazdırılması sağlanır. Burada istemcinin Ip adresini tedarik edebilmek için HttpRequest sınıfının UserHostAddressözelliği kullanılır. Local_Addr değeri ilede sunucu değişkenlerinden(Server Variables) sunucu ip adresi elde edilmektedir. Nitekim sunucu ip adresi 127.0.0.1' den farklıda olabilir. O halde talep eden istemcinin ip adresi ile yerel ip adresinin eşit olup olmadığına da bakılmalıdır. Uygulamayı test edip, örneğin olmayan bir veritabanı adı girdiğimizde sonuç sayfası aşağıdaki gibi olacaktır.

Dikkat edilecek olursa istisna bilgisi detayaları ile birlikte Trace Information kısmında özel kategori adı ve mesajı ile birlikte görülmektedir. Bu tarz bir çalışma zamanı bilgisi elbetteki geliştirici(Developer) açısından önemlidir. Aynı sonuçlar çok doğal olarak Trace mekanizması olmadan da tespit edilebilir. Buradaki temel amaç Trace mekanizmasını kullanarak, sayfalarda oluşabilecek hataların kesin yerlerini ve konumlarını daha kolay tespit edebilmektir.

Gelelim Trace mekanizması ile ilgili diğer bir konuya. Çok doğal olarak web uygulamalarında harici bileşenler(Components) kullanılır. Bileşenden kastımız çoğunlukla bir sınıf kütüphanesi(class library) veya ayrı bir sınıf dosyasıdır. Çok doğal olarak bu bileşenler içerisindeki bazı süreçler Trace mekanizması içerisinde ele alınmak istenebilir. Örneğin ilk uygulamada gerçekleştirdiğimiz string birleştirme işleminin ayrı bir sınıf içerisinde bir metod olarak ele alındığını düşünelim.

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Text;
using System.Data.SqlClient;

public class VeriBileseni
{
    public string GetTransactionIdString()
    {
       
HttpContext ctx = HttpContext.Current;
        StringBuilder builder = new StringBuilder();
        using (SqlConnection conn = new SqlConnection("data source=.;database=AdventureWorks;integrated security=SSPI"))
        {
            SqlCommand cmd = new SqlCommand("Select Top 10000 TransactionId From Production.TransactionHistory", conn);
            conn.Open();
           
ctx.Trace.Warn("TransactionId Cekme", "TransactionId değerleri çekilmeye başlanacak");
            SqlDataReader reader = cmd.ExecuteReader();
            while (reader.Read())
            {
                builder.Append(reader["TransactionId"].ToString());
                builder.Append("|");
            }
           
ctx.Trace.Warn("TransactionId Cekme", "TransactionId değerleri çekildi...");
            reader.Close();
        }
        return builder.ToString();
    }
    public VeriBileseni()
    {
    }
}

Burada dikkat edilmesi gereken en önemli nokta Trace sınıfını kullanmak için HttpContext nesnesinin nasıl elde edildiğidir. Bileşenlerde Trace bilgisi ele alınıyorsa, bu bileşenin kullanıldığı HTTP içeriğinin kullanılması gerekmektedir. Bu nedenle HttpContext nesne örneği için statik(static)Currentözelliğinden faydalanılmıştır. Böylece bileşeni o anda kullanan sayfa içeriğine ulaşılmış olunur. Bundan sonra ise Warn veya Write gibi metodlar kullanılabilir.

Bileşenin ayrı bir sınıf kütüphanesi(class library) olarak tasarlanması durumunda, System.Web.dll assembly' ının referans edilmesi gerekecektir. Bunun ise doğuracağı önemli sonuçlardan birisi şudur; bilşenen içerisindeki izleme alt yapısı web bağımlı hale gelmektedir. Bu nedenle alternatif bir yaklaşım olarak ayrı bir dinleyici mekanizması özel olarak geliştirilebilir.

Peki söz konusu bileşen sadece web tabanlı kullanılmıyorsa. Bu durumda HttpContext gerekli işlevsellikleri sağlamak için uygun olmayacaktır. Dolayısıyla farklı bir yol izlemek gerekmektedir. .Net Framework izleme mesajları için dinleyiciler(Listener) kullanır. İstenirse web.config aracılığıyla yada programatik olarak yeni dinleyiciler eklenebilir. Normal şartlarda Trace.Write gibi bir metod çağrısı yapıldığında TraceListener koleksiyonundaki tüm dinleyiciler mesajları alıp işlemeye başlarlar. O halde Trace Listener web.config ile açık bir şekilde belirtilirse, System.Web.dll assembly' ını projeye referans etmeden ve HttpContext tipini kullanmadan Trace çıktıları web uygulamasına doğru gönderilebilir.

Asp.Net 1.1 ile geliştirme yapıyorsak eğer, bu işlemler için özel bir dinleyici yazmamız gerekecektir. Ne varki Asp.Net 2.0 ile sadece bu iş için tasarlanmış WebPageTraceListener isimli bir sınıf gelmektedir. Bu sınıfın web.config dosyasında belirtilmesi ve bileşen içerisinde System.Diagnostics isim alanı altında yer alan Trace sınıfının kullanılması yeterlidir. Böylece bileşenimiz trace çıktısı verirken web' e bağımlı olmaktan kurtulmuş olacaktır. Örnek olarak az önce geliştirilen VeriBileseni sınıfını ayrı bir sınıf kütüphanesi olarak aşağıdaki gibi tasarladığımızı düşünelim.

using System;
using System.Data;
using System.Diagnostics;
using System.Data.SqlClient;
using System.Text;

public class BagimsizVeriBileseni
{
    public string GetTransactionIdString()
    {
        StringBuilder builder = new StringBuilder();
        using (SqlConnection conn = new SqlConnection("data source=.;database=AdventureWorks;integrated security=SSPI"))
        {
            SqlCommand cmd = new SqlCommand("Select Top 10000 TransactionId From Production.TransactionHistory", conn);
            conn.Open();
           
Trace.Write("TransactionId Cekme", "TransactionId değerleri çekilmeye başlanacak");
            SqlDataReader reader = cmd.ExecuteReader();
            while (reader.Read())
            {
                builder.Append(reader["TransactionId"].ToString());
                builder.Append("|");
            }
           
Trace.Write("TransactionId Cekme", "TransactionId değerleri çekildi...");
            reader.Close();
        }
        return builder.ToString();
    }
}

Burada dikkat edilecek olursa herhangibir şekilde System.Web referansı kullanılmamaktadır. Bunun yerine System.Diagnostics isim alanı ve burada yer alan Trace sınıfı ele alınmaktadır. Bir önceki bileşenden farklı olarak Warn metodu kullanılamamaktadır. Bunun nedeni System.Diagnostics isim alanında yer alan Trace sınıfının Warn metodunun olmayışıdır. Diğer önemli noktalardan biriside, System.Diagnostics.Trace sınıfındaki Write metodu versiyonlarından belirli bir Exception nesne örneğininin fırlatılmasının mümkün olmayışıdır. Ancak istisna mesajı gönderilmesi sağlanabilir. Bu işlemin ardından web uygulamasına ait web.config dosyasında aşağıdaki değişiklikler yapılmalıdır.

<?xml version="1.0"?>
<configuration>
    .
    .
    .
   
<system.diagnostics>
       
<trace>
          
 <listeners>
               
<addname="WebPageTraceListener" type="System.Web.WebPageTraceListener,System.Web,Version=2.0.3600.0,Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
           
</listeners>
       
</trace>
   
</system.diagnostics>
    .
    .
    .
</configuration>

Burada listeners bilgisi eklenirken system.diagnostics isim elementi içerisindeki trace elementi kullanılmaktadır. add elementi içerisinde yer alan type kımsında WebPageTraceListener sınıfının tam adı (Qualified Name) belirtilmektedir. Bildiğiniz üzere Qualified Name' i oluşturan değerler tip adı, assembly adı, versiyon numarası, kültür ve publicKeyToken bilgisidir. Bu durumu test etmek için az önceki örnektekine benzer bir web sayfası aşağıdaki gibi geliştirilebilir.

<%@ Page Language="C#" Trace="true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected void btnCek_Click(object sender, EventArgs e)
    {
       
BagimsizVeriBileseni veriBln = new BagimsizVeriBileseni();
        Session.Add("TransactionIDler", veriBln
.GetTransactionIdString());
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:Button ID="btnCek" runat="server" Text="Veriyi Çek" OnClick="btnCek_Click" />
        </div>
    </form>
</body>
</html>

Trace çıktısı ise aşağıdaki gibi olacaktır.

Trace mimarisi ile ilgili olaraktan, ele alınabilecek başka konularda bulunmaktadır. Örneğin Trace bilgilerini başka ortamlara aktarmak, mail gönderimi gerçekleştirilmesi gibi. Tatile çıktığım şu günlerde bu kadar bilginin yeterli olacağı kanısındayım. Dönüşte Trace mimarisinin ikinci makalesi ile devam edeceğiz. Böylece geldik bir makalemizin daha sonuna. Bu makalemizde trace mimarisini tanımaya, gerekliliklerini vurgulamaya çalıştık. Sayfa seviyesinde ve uygulama seviyesinde trace işlemlerinin nasıl yapılacağını, sadece istisna oluştuğunda yanlız yerel makineye trace bilgisinin nasıl verileceğini, bileşen bazında trace' lerin kullanılmasını ve bileşenin web ortamından bağımsız olabilecek şekilde ele alınabilmesini incelemeye çalıştık. Nihayetinde bu uzun makaleyi buraya kadar sabırla okuduğunuz için teşekkür eder bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama için Tıklayın

Burak Selim ŞENYURT
selim@bsenyurt.com


Asp.Net 2.0 URL Rewriting Hakkında Gerçekler

$
0
0

Değerli Okurlarım Merhabalar,

Çok kısa süreliğinede olsa tatilde olduğum şu günlerde yazılım dünyasından kopmak hiç içimden gelmedi. Bu nedenle dinlendiğim zamanlardan arta kalan sürelerde azda olsa bir şeyler karalamak istedim. Sonuç olarak daha hafif ve tatil moduna uygun olacak bir yazı ile yeniden beraberiz. Bu makalemizde Asp.Net 2.0 ile geliştirilen web uygulamalarında, URL eşleştirmelerinin (Url Mapping) nasıl düzenlenebileceğini, bir başka deyişle nasıl özelleştirilebileceğini incelemeye çalışacağız. Son kullanıcılar web ortamında, kendi tarayıcı(browser) uygulamalarında yer alan adres satırlarında zaman zaman karışık ve uzun URL bilgileri ile karşılaşırlar. Genellikle sorgu katarlarının(QueryString) kullanıldığı ve bunların sayılarının çok olduğu durumlarda adres satırlarını okumak gerçekten güçleşebilir. Söz gelimi aşağıdaki URL bilgisini göz önüne alalım.

http://www.azonsitesi.com/urunler.aspx?urunKategori=1&urunAdi=Bilgisayar%20Kitaplari&Sinifi=Ingilizce&BasimYili=2006

Bunun yerine aşağıdaki gibi bir URL bilgisi çok daha kullanışlı ve son kullanıcı açısından okunaklı olabilir.

http://www.azonsitesi.com/Ingilizce/BilgisayarKitaplari/2006Basimi/Goster.aspx

Örnekler çoğaltılabilir. Söz gelimi blog sitelerinde, adres satırlarında okunan içeriğe ait bilgilerin QueryString şeklinde durması yerine örneğin http://buraginblogu/Agustos/7/2007/UrlEslestirme/Oku.aspx gibi bir formata sahip olması son kullanıcı açısından çok daha cezbedicidir.

Bu ve benzer durumlarda, adres satırındaki bilginin daha kolay anlaşılabileceği hale getirilmesi son kullanıcı(End User) için önemli bir hizmettir. Peki bu tarz bir ihtiyaç nasıl karışalanabilir. İlk olarak istemciden gelen adres talebine eş düşecek yeni URL bilgisinin sunucu tarafında ele alınıyor olması gerekir. Sonrasında ise sonuçlar istemci tarayıcı programına istenen formatta gönderilir. Asp.Net 1.1 kullanıldığı takdirde bu işin çözümü özel HttpHandler ve HttpModule sınıflarının yazılması ile mümkün olabilir.(Kendi HttpHandler yada HttpModuler tiplerimizi nasıl yazabileceğimize dair bilgileri daha önceden yayınlanan makalemizden takip edebilirsiniz)Asp.Net 2.0 mimarisindeyse, sadece URL eşleştirilmelerinin daha kolay yapılabilmesini sağlamak amacıyla web.config dosyasında yer alan system.web boğumu(node) içerisinde ele alınabilecek bir urlMappings elementi ve bunun Configuration API' sinde karşılğı olan UrlMappingsSection sınıfı geliştirilmiştir. Bu sayede konfigurasyon bazında URL eşleştirmeleri yapılabilmekte ve özel HttpHandler yada HttpModule tipleri yazılmasına gerek kalmamaktadır.

Her ne kadar urlMappings elementi veya UrlMappingsSection tipi sayesinde, URL eşleştirmelerinin yapılması kolaylaşmışsada bazı özel durumlarda yine HttpHandler veya HttpModule tipleri geliştirmek gerekebilir. Söz gelimi, urlMappings elementinin doğrudan bir regular expression desteği yoktur. Dolayısıyla benzer yazıma sahip URL bilgileri için ortak eşleştirme yapmak adına element bazında bir hamle yapılması zordur. Bunu sağlamak için HttpModule ve HttpHandler yazmak gerekmektedir. Böyle bir ihtiyaç için kendi HttpModule ve HttpHandler tipinizi yazmaya çalışmanız önerilir.

Aslında Asp.Net 2.0 mimarisinde yer alan URL eşleştirme sistemi aşağıdaki grafikte görüldüğü gibi çalışmaktadır.

Buna göre istemciden gelen talepler sonrası, ilgili web uygulaması Asp.Net çalışma zamanı içerisinde normal sürecine devam eder. Taki sayfanın son hali Render işlemine tabi tutulup istemciye gönderilene kadar. Bir başka deyişle Render işleminde önce, Asp.Net çalışma zamanı(Asp.Net RunTime) web.config içerisinde herhangibir eşleştirme olup olmadığına bakar. Eğer talep edilen URL için bir eşleştirme varsa buna göre HttpContext tipinin RewritePath metodu işletilir ve URL adresi değiştirilir. Sonrasında ise sayfa istemciye gönderilir.

Dilerseniz örnek bir senaryo üzerinden hareket ederek URL eşleştirmelerinin Asp.Net 2.0 mimarisinde nasıl yapıldığını yakından incelemeye çalışalım. Senaryo gereği kullanıcının alt kategorisi ve sınıfına göre bazı ürünleri listelediğini düşünebiliriz. Bu amaçla SQL Server 2005 ile gelen AdventureWorks veritabanındaki ProductSubCategories ve Product tablolarını göz önüne alalım. Bu tablolara arasında aşağıdaki şekilde görülen bire-çok(one to many) ilişki vardır.

İlk olarak default.aspx sayfamızı aşağıdaki gibi geliştirelim.

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>URL Mapping Ornegi</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
       
<asp:GridView ID="GridView1" runat="server" DataSourceID="dsCategories" AllowPaging="True" AutoGenerateColumns="False">
            <Columns>
               
<asp:HyperLinkField DataNavigateUrlFields="Name,Class" DataNavigateUrlFormatString="~/Urunler/{0}/{1}/Goster.aspx" DataTextField="Title" HeaderText="Alt Kategori ve Sınıfı" />
            </Columns>
       
</asp:GridView>
       
<asp:SqlDataSource ID="dsCategories" runat="server" ConnectionString="<%$ ConnectionStrings:AdvConStr %>" SelectCommand="SELECT DISTINCT PSC.ProductSubcategoryID, Replace(PSC.Name,' ','') AS Name, RTRIM(PRD.Class) as Class, PSC.Name+' '+PRD.Class AS Title FROM Production.ProductSubcategory AS PSC INNER JOIN Production.Product AS PRD ON PSC.ProductSubcategoryID = PRD.ProductSubcategoryID WHERE (PRD.Class IS NOT NULL)">
       
</asp:SqlDataSource>
    </div>
    </form>
</body>
</html>

Default.aspx sayfası içerisinde yer alan GridView kontrolü, Product ve ProductSubCategory tablolarının birleşiminden bir sonuç kümesine ait satırları göstermek üzere tasarlanmıştır. Söz konusu sonuç kümesi elde edilirken Name ve Class alanındaki boşlukların alınması için Replace ve RTrim isimli T-SQL fonksiyonlarına başvurulmaktadır. GridView bileşenine dikkat edilecek olursa içeride HyperLinkField tipinden bir kontrol kullanılmaktadır. Bu kontrolün dikkate değer özelliği ise DataNavigateUrlFormatString niteliğidir. Sayfa çalışma zamanında aşağıdakine benzer bir sonuç verecektir.

Dikkat edilecek olursa bağlantıların(Links) hedef URL bilgisi, HyperLinkField kontrolünün DataNavigateUrlFormatString niteliğinin değerine göre şekillenmektedir. Söz gelimi, örnek ekran görüntüsünde yer aldığı gibi M sınıfındaki Bottom Brackets ürünleri için URL bilgisi aşağıdaki gibidir.

http://localhost:1292/UrlRewriting/Urunler/BottomBrackets/M/Goster.aspx

Dikkat edilecek olursa bu URL bilgisinin Urunler kelimesinden itibaren olan kısmı çok daha okunaklı ve anlamlıdır. Peki biz bu linke tıkladığımızda Bottom Brackets kategorisinde ve M sınıfında yer alan ürünlerin listesi nasıl elde edilebilir. Bu işlemin Urunler.aspx gibi bir sayfa içerisinde ele alınması düşünüldüğü takdirde, seçilen bağlantı bilgisine göre ilgili kategori ve sınıf bilgilerinin sorgu katarı(QueryString) ile diğer sayfaya gönderilmesi gerekmektedir. Buda çok doğal olarak, default.aspx sayfasında seçilen URL bilgisine eş düşecek asıl URL bilgisinin tanımlanması ile mümkün olabilir. İşte bu noktada, Urunler.aspx sayfasını tasarlamadan önce, web.config içerisinde aşağıdaki ilaveler yapılmalıdır.

<?xml version="1.0"?>
<configuration>
    <appSettings/>
    <connectionStrings>
        <add name="AdvConStr" connectionString="Data Source=.;Initial Catalog=AdventureWorks;Integrated Security=True" providerName="System.Data.SqlClient"/>
    </connectionStrings>
   
<system.web>
       
<urlMappings enabled="true">
           
<addurl="~/Urunler/BottomBrackets/H/Goster.aspx" mappedUrl="~/Urunler.aspx?AltKategoriId=5&amp;AltKategoriAdi=Bottom%20Brackets&amp;Sinifi=H"/>
            <add url="~/Urunler/BottomBrackets/L/Goster.aspx" mappedUrl="~/Urunler.aspx?AltKategoriId=5&amp;AltKategoriAdi=Bottom%20Brackets&amp;Sinifi=L"/>
            <add url="~/Urunler/BottomBrackets/M/Goster.aspx" mappedUrl="~/Urunler.aspx?AltKategoriId=5
&amp;AltKategoriAdi=Bottom%20Brackets&amp;Sinifi=M"/>
            <add url="~/Urunler/Cranksets/H/Goster.aspx" mappedUrl="~/Urunler.aspx?AltKategoriId=5&amp;AltKategoriAdi=Cranksets&amp;Sinifi=H"/>
       
</urlMappings>
        <compilation debug="true"/>
        <authentication mode="Windows"/>
    </system.web>
</configuration>

URL eşleştirmeleri için system.web elementi içerisinde yer alan urlMappings boğumu kullanılmaktadır. Bu boğumda, add elementi içerisinde yer alan url ve mappedUrl nitelikleri ilede gereken eşleştirmeler yapılmaktadır. Buna göre, ~/Urunler/BottomBrackets/M/Goster.aspx gibi bir talep geldiğinde bu, ~/Urunler.aspx?AltKategoriId=5&amp;AltKategoriAdi=Bottom%20Brackets&amp;Sinifi=M olarak algılanacaktır. & operatörünün ele alınması sırasında &amp; ifadesinin kullanılmasına dikkat etmek gerekir. Eğer & işareti kullanılırsa derleme zamanıda hata mesajı alınır.

Artık gerekli bildirimler yapıldığına göre Urunler.aspx sayfası aşağıdaki gibi tasarlanabilir.

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
   
protected void Page_Load(object sender, EventArgs e)
    {
        lblAltKategoriAdi.Text =
Request.QueryString["AltKategoriAdi"]!=null?Request.QueryString["AltKategoriAdi"].ToString():"";
        lblSinifi.Text =
Request.QueryString["Sinifi"]!=null?Request.QueryString["Sinifi"].ToString():"";
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Kategori ve sınıf bazlı ürünler</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Label ID="lblAltKategoriAdi" runat="server" Font-Bold="True" Font-Size="X-Large" Font-Underline="True" ForeColor="#C00000"></asp:Label>
        Alt Kategorisi
        <asp:Label ID="lblSinifi" runat="server" Font-Bold="True" Font-Size="X-Large" Font-Underline="True" ForeColor="#C00000"></asp:Label> sınıfı<br />
        <br />
       
<asp:GridView ID="grdUrunler" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductId" DataSourceID="dsProducts">
            <Columns>
                <asp:BoundField DataField="ProductId" HeaderText="ProductId" InsertVisible="False" ReadOnly="True" SortExpression="ProductId" />
                    <asp:BoundField DataField="Name" HeaderText="Name" SortExpression="Name" />
                    <asp:BoundField DataField="ListPrice" HeaderText="ListPrice" SortExpression="ListPrice" />
                    <asp:BoundField DataField="Class" HeaderText="Class" SortExpression="Class" />
                    <asp:BoundField DataField="SellStartDate" HeaderText="SellStartDate" SortExpression="SellStartDate" />
            </Columns>
       
</asp:GridView>
       
<asp:SqlDataSource ID="dsProducts" runat="server" ConnectionString="<%$ ConnectionStrings:AdvConStr %>" SelectCommand="Select ProductId,Name,ListPrice,Class,SellStartDate From Production.Product Where ProductSubCategoryId=@SubCatId and Class=@Class">
           
<SelectParameters>
               
<asp:QueryStringParameter DefaultValue="1" Name="SubCatId" QueryStringField="AltKategoriId" />
               
<asp:QueryStringParameter DefaultValue="M" Name="Class" QueryStringField="Sinifi" />
           
</SelectParameters>
        </asp:SqlDataSource>
    </div>
    </form>
</body>
</html>

Bu sayfa sadece QueryString ile gelen parametreler değerlendirmekte ve buna göre belirli bir alt kategori ve sınıfa ait ürünlerin bazı alanlarının listelenmesini sağlamaktadır. Bu amaçla yine GridView ve SqlDataSource kontrollerinden yararlanılmış ve uygun sorgu cümleleri kullanılmıştır. Örnek senaryoda yer alan Bottom Brockets alt kategorisi ve M sınıfı seçilirse, Urunler.aspx sayfasında aşağıdaki ekran görüntüsünde yer alan çıktılar elde edilecektir.

Yukarıdaki çıktıya dikkat edilecek olursa URL satırı bizim belirlediğimiz şekilde kalmıştır. Bu çıktının aynısını elde etmek için halen daha sorgu katarı(QueryString) ifadeleride açıkça kullanılabilir. Aynı web uygulamasında aşağıdaki ekran görüntüsünde yer alan URL talebi, aynı sonuçları verecektir.

Ne varki halen daha bazı problemler vardır. Herşeyden önce en azından söz konusu senaryo göz önüne alındığında, var olabilecek tüm olasılıklar için web.config dosyasına teker teker URL eşleştirmelerinin eklenmesi gerekmektedir. Bir geliştirici olarak bunun kod içerisinden daha etkili bir şekilde ele alınması çok doğal bir istektir. Neyseki Asp.Net 2.0 ile gelen güçlü Configuration API alt yapısı sayesinde istersek, kod içerisinde urlMappings kısmına dinamik olarak yeni elemanlar(elements) ekleyebilir, düzenleyebilir ve çıkartabiliriz. Bu amaçla bir admin sayfası veya farklı bir uygulama olacak şekilde bir admin paneli dahi göz önüne alınabilir.(Configuration API' sinin yönetiminin daha detaylı bir şekilde öğrenmek isterseniz daha önce yazılmış olan bir makaleden yararlanabilirsiniz.) Bunun için aşağıdaki gibi bir sayfa tasarladığımızı göz önüne alabiliriz.

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Web.Configuration" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    private static void AddMappings()
    {
        Configuration cfg = ConfigurationManager.OpenExeConfiguration("");
       
UrlMappingsSection urlMapSct = (UrlMappingsSection)cfg.GetSection("system.web/urlMappings");
       
urlMapSct.UrlMappings.Clear();

        string query = "SELECT DISTINCT PSC.ProductSubcategoryID, PSC.Name,PRD.Class FROM Production.ProductSubcategory AS PSC INNER JOIN Production.Product AS PRD ON PSC.ProductSubcategoryID = PRD.ProductSubcategoryID WHERE (PRD.Class IS NOT NULL) ORDER BY PSC.Name, PRD.Class";

        using (SqlConnection conn = new SqlConnection(cfg.ConnectionStrings.ConnectionStrings["AdvConStr"].ConnectionString))
        {
            SqlCommand cmd = new SqlCommand(query, conn);
            conn.Open();
            string url = "", mappedUrl = "";
   
            SqlDataReader reader = cmd.ExecuteReader();
            while (reader.Read())
            {
               
url = "~/Urunler/" + reader.GetString(1).Replace(" ","") + "/" + reader.GetString(2).Replace(" ","") + "/Goster.aspx";
               
mappedUrl = "~/Urunler.aspx?AltKategoriId=" + reader["ProductSubCategoryID"].ToString() + "&AltKategoriAdi=" + reader["Name"].ToString() + "&Sinifi=" + reader["Class"].ToString();
               
UrlMapping map = new UrlMapping(url, mappedUrl);
               
urlMapSct.UrlMappings.Add(map);
            }
            reader.Close();
        }
       
cfg.Save();
    }

    protected void btnMappEkle_Click(object sender, EventArgs e)
    {
        AddMappings();
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Yonetici Sayfasi</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <asp:Button ID="btnMappEkle" runat="server" Text="Güncel URL Eşleştirmelerini Ekle" OnClick="btnMappEkle_Click" />
            </div>
        </form>
    </body>
</html>

Şimdi burada geliştirdiğimiz kodları kısaca inceleyelim. Configuration alt yapısına göre, web.config dosyası içerisinde bilinen hemen her boğumun(Node) birer yönetimli tip(Managed Type) karşılığı vardır. Buradaki tipimiz UrlMappingsSection sınıfıdır. UrlMappingsSection sınıfıda Asp.Net 2.0 ile birlikte gelmiş bir sınıftır. Sınıf diagramından ele alındığında UrlMappingsSection sınıfının Framework içerisindeki yeri aşağıdaki şekilde görüldüğü gibidir.

Görüldüğü gibi UrlMappingsSection sınıfının UrlMappingsözelliği aslında UrlMapping tipinden elemanlar taşıyan özel bir koleksiyonu(UrlMappingCollection) işaret etmektedir. Yeni bir UrlMapping nesne örneği eklenmek istendiğinde bu koleksiyondan yararlanılır. Bu koleksiyondaki elemanları oluşturan UrlMapping tipinin Framework içerisindeki yeri ise aşağıdaki gibidir.

Bu tipi o anki web.config dosyasından elde etmek amacıyla Configuration nesne örneğinin GetSection metodu kullanılmaktadır. Bu metod bilindiği gibi XPath ifadelerini parametre olarak alır.(XPath ile ilgili detaylı bilgiyi daha önceki bir makalemizden edinebilirsiniz) Sonrasında ise veritabanından yapılan sorgu sonucu elde edilen veri kümesine göre url ve mappedUrl değerleri oluşturulur. Bu değişkenler, her bir satır için web.config dosyasına eklenecek UrlMapping tiplerinin yapıcı metodlarına(Constructor) parametre olarak verilmektedir. Son olarak oluşan UrlMapping nesne örneği, Add metodu ile web.config/urlMappings elementi içerisindeki yerini almaktadır.

Elbette, bellek üzerinde yapılan bu değişikliklerin kalıcı olması adına Configuration tipinin Save metodu kullanılmaktadır. Dikkat edilmesi gereken noktalardan biriside URL değeri oluşturulurken Replace metodu ile Name ve Class alanlarındaki boşlukların alınmasıdır. Eğer söz konusu boşluklar alınmassa (özellike Class alanlarındakiler alınmassa) bu linklere tıklandığında çalışma zamanında sayfaların bulunamadığına dair hata mesajları alınabilir. Bu aynı zamanda, URL içerisinde geçersiz olabilecek karakterler var ise bunlarında çıkartılması veya değiştirilmesi gerektiği anlamınada gelmektedir. Örneğin boşlukar çoğunlukla URL satırına %20 şeklinde aktarılırlar. Ancak örnekteki URL bilgisinde klasör tabanlı bir yaklaşım tercih edildiğinden bütün boşlukların çıkartılması yolu tercih edilmiştir. Bu işlemlerin arkasından Admin sayfası çalıştırılır ve düğme tıklanırsa web.config dosyasının aşağıdaki ekran görüntüsünde olduğu gibi değiştiği görülür.

Dikkat edilecek olursa, elde edilen veri kümesine göre, söz konusu olabilecek tüm URL eşleştirmeleri ilave edilmiştir. Configuration API'si sağolsun :) Şimdi default.aspx sayfası çağırılırsa, artık GridView kontrolü içerisindeki her bir bağlantının karşılığının olduğu ve çalıştığı görülür. Default.aspx için örnek görüntü aşağıdaki gibidir.

İlgili bağlantının tıklanması sonrası ise aşağıdaki sonuçlara benzer çıktılar elde edilebilir.

Buraya kadar örnek bir senaryo üzerinden URL eşleştirmesini incelemeye çalıştık. Son teknikte dinamik olarak URL eşleştirmelerini ekledik. Bu teknik her ne kadar göze hoş gelsede dezavantajıda vardır. Öyleki, veri kaynağında değişiklikler yapıldığında örneğin Alt kategori adı değiştiğinde yada yenileri eklendiğinde web.config dosyası içerisinde yeni düzenlemelerin yapılması, bir başka deyişle ilgili fonksiyonun tekrardan çağırılması gerekcektir. Bu çeşitli teknikler ile çözülebilir ama yinede düşünülmesi gereken bir adımdır.

Sonuç olarak Asp.Net 2.0 ile birlikte gelen urlMappings elementi her ne kadar bazı avantajlar sağlasada çeşitli kısıtlamalarda içermektedir. Regular Expression ifadeleri kullanılamadığından, her bir URL için gereken eşleştirmeler teker teker web.config dosyası içerisinde yazılmalıdır. Gerçi Configuration API' si yardımıyla bu bir nebzede olsa aşılabilmektedir ancak Regular Expression'ın yerini tam anlamıyla tutmamaktadır. Regular Expression desteği için geliştiricinin özel HttpModule ve HttpHandler tiplerini geliştirmesi gerekliliği ise bir zorluktur. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama için Tıklayın

Burak Selim ŞENYURT
selim@bsenyurt.com

Asp.Net Temelleri : Derinlemesine Download/Upload İşlemleri

$
0
0

Değerli Okurlarım Merhabalar,

Tatile çıkan herkes, iyi ve dinlendirici geçen günlerin ardından tekrar hayatın akışına kapıldığında kısa süreliğinede olsa adaptasyon problemi yaşar. Tatildeyken hatırlayacağınız gibi hafif ve dinlendirici bir Asp.Net konusu ile ilgilenmeye çalışmıştık. Tatil dönüşündeki adaptasyon sürecinde de benzer nitelikte bir konuyu incelemenin uygun olacağı kanısındayım. Bu yazımızda Asp.Net uygulamalarında sıklıkla başvurduğumuz temel dosya giriş/çıkış (Input/Output -IO) işlemlerinden yararlanarak Download ve Upload işlemlerinin nasıl yapılabileceğini ele almaya çalışacağız.

Özellikle web tabanlı içerik yönetim sistemlerinde (Content Management System), kullanıcıların sunucu üzerinde dökümanlar ile etkin bir şekilde çalışabilmeleri sağlanmaktadır. Bu sistemlerde genel olarak kullanıcı kimliği veya rolüne göre istemci bilgisayarlara indirilebilen(Download). Hatta çoğu içerik yönetim sisteminde, istemciler herkesin okuyabileceği yada belirli kişilerin görebileceği şekilde sunucuya döküman aktarma (Upload) işlemleride yapabilirler. Söz gelimi bir yazılım şirketinin içerik yönetim sistemi göz önüne alındığında, yazılım departmanındaki geliştiricilerin hazırladıkları teknik dökümantasyonları Upload veya Download edebilecekleri bir ortam hazırlanabilir.

Hangi açıdan bakılırsa bakılsın, web tabanlı olarak yapılan bu işlemler için şirketler büyük ölçekli sistemler tasarlayıp geliştirmiştir. Fakat temel ilke ve yaklaşımlar benzerdir. Dosya indirme veya gönderme işlemleri, web tabanlı bir sistem göz önüne alındığında HTTP kurallarına bağlıdır. Dolayısıyla bu kuralların sadece uygulanma ve ele alınma biçimleri programlama ortamları arasında farklılıklar gösterebilir. İşte biz bu makalemizde, Asp.Net 2.0 tarafından olaya bakmaya çalışıyor olacağız. İlk olarak dosya indirme işlemlerini ele alacağız. Sonrasında ise Asp.Net 2.0 ile gelen FileUpload aracı yardımıyla sunucuya dosya gönderme(Upload) işlemlerinin nasıl yapılabileceğini inceleyeceğiz. Ek olarak, upload edilen bir dosyanın kaydedilmeden, sunucu belleği üzerinde canlandırılıp işlenmesinin nasıl gerçekleştirilebileceğine bakacağız. Son olarakta, Upload edilen dosyaların bir veritabanı tablosunda alan(Field) olarak saklanması için gereken adımları göreceğiz. Dilerseniz vakit kaybetmeden dosya indirme süreci ile işe başlayalım.

Dosya indirme (Download) işlemlerinde bilinen IO tiplerinden ve Response sınıfının ilgili metodlarından yararlanılır. Hatırlanacağı gibi herhangibir resim dosyasını bir web sayfası içerisinde göstermek için üretilen HTML içeriği ile oynamak gerektiğinden daha önceki makalemizde bahsetmiştik. Dosya indirme(Download) işlemindede içeriğin tipi(Content-Type), uzunluğu(Content-Length) gibi bilgiler önem kazanmaktadır. İlk örneğimizde, IIS üzerinde yayınlanan bir web projesindeki Dokumanlar isimli bir klasörde yer alan dosyaların indirilme işlemlerini gerçekleştirmeye çalışacağız. Bu amaçla web uygulamasına ait dokumanlar klasörü altına aşağıdaki şekildende görüldüğü üzere farklı formatlarda örnek dosyalar atılmasında fayda vardır.

Web uygulamamızın ilk amacı Dokumanlar klasöründeki dosyaların listelenmesini sağlamak olacak. Bu amaçla sayfada bir GridView kontrolü kullanılabilir. Hatta bu kontrolün içeriği FileInfo tipinden nesnelerden oluşan generic bir liste koleksiyonundan (List<FileInfo>) gelebilir. Böylece istemciler indirebilecekleri dosyalarıda görebilir. Download işleminin gerçekleştirilmesi için GridView kontrolünde bir Select Button' dan faydalanılabilir. İndirme işlemi sırasında indirilmek istenen dosyanın fiziki adresi, uzunluğu gibi bilgiler önemlidir. Bu bilgileri ve fazlasını FileInfo sınıfına ait bir nesne örneği yardımıyla elde edebiliriz. Uygulamamıza ait Default.aspx sayfasının içeriği aşağıdaki gibi olacaktır.

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.IO" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        string[] dosyalar =
Directory.GetFiles(Server.MapPath("Dokumanlar"));
       
List<FileInfo> dosyaBilgileri = new List<FileInfo>();

        foreach (string dosya in dosyalar)
        {
            dosyaBilgileri.Add(
new FileInfo(dosya));
        }
        grdDosyalar.
DataSource = dosyaBilgileri;
        grdDosyalar.
DataBind();
    }
}

protected void
grdDosyalar_SelectedIndexChanged(object sender, EventArgs e)
{
    string dosyaAdi =
Server.MapPath("dokumanlar") + "\\" + grdDosyalar.SelectedRow.Cells[0].Text;
   
FileInfo dosya = new FileInfo(dosyaAdi);

   
Response.Clear(); // Her ihtimale karşı Buffer' da kalmış herhangibir veri var ise bunu silmek için yapıyoruz.
   
Response.AddHeader("Content-Disposition","attachment; filename=" + dosyaAdi); // Bu şekilde tarayıcı penceresinden hangi dosyanın indirileceği belirtilir. Eğer belirtilmesse bulunulan sayfanın kendisi indirilir. Okunaklı bir formattada olmaz.
   
Response.AddHeader("Content-Length",dosya.Length.ToString()); // İndirilecek dosyanın uzunluğu bildirilir.
   
Response.ContentType = "application/octet-stream"; // İçerik tipi belirtilir. Buna göre dosyalar binary formatta indirilirler.
   
Response.WriteFile(dosyaAdi); // Dosya indirme işlemi başlar.
   
Response.End(); // Süreç bu noktada sonlandırılır. Bir başka deyişle bu satırdan sonraki satırlar işletilmez hatta global.asax dosyasında eğer yazılmışsa Application_EndRequest metodu çağırılır.
}
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Dosya Indirme Islemleri</title>
    </head>
<body>
    <form id="form1" runat="server">
        <div>
            <h2>Dosyalar</h2>
           
<asp:GridView ID="grdDosyalar" runat="server" AutoGenerateColumns="False" SelectedRowStyle-BackColor="Gold" OnSelectedIndexChanged="grdDosyalar_SelectedIndexChanged">
                <Columns>
                    <asp:
BoundField DataField="Name" HeaderText="Dosya Adı" />
                    <asp:BoundField DataField="
Length" HeaderText="Dosya Uzunluğu" />
                    <asp:BoundField DataField="
Extension" HeaderText="Uzantısı" />
                    <asp:BoundField DataField="
CreationTime" HeaderText="Oluşturulma Zamanı" DataFormatString="{0:dd.MMMM.yy}" HtmlEncode="False" />
                    <asp:BoundField DataField="
LastWriteTime" HeaderText="Son Yazılma Zamanı" DataFormatString="{0:dd.MMMM.yy}" HtmlEncode="False" />
                    <asp:
CommandField ButtonType="Button" SelectText="indir" ShowSelectButton="True" />
                </Columns>
           
</asp:GridView>
        </div>
    </form>
</body>
</html>

Öncelikli olarak sayfamızda neler yaptığımıza kısaca bakalım. Default.aspx sayfası ilk yüklendiğinde (bunu sağlamak için IsPostBacközelliği ile kontrol yapılmıştır) Dokumanlar klasöründeki dosyaların adları elde edilmektedir. Bu işlem için Directory sınıfının GetFiles metodu kullanılmaktadır. Bir web uygulaması söz konusu olduğu için, sanal klasörün karşılık geldiği fiziki adresi bulmak adına Server.MapPath metodu ele alınmaktadır. GetFiles metodu parametre olarak belirtilen klasördeki dosya isimlerinin elde edilmesini sağlamaktır. Bu nedenle geriye string tipinden bir dizi döndürür. GridView kontrolü içerisinde, elde edilen bu dosyalara ait bazı temel bilgilerin gösterilmesi hedeflenmiştir. Örnekte dosyanın adı(Name), uzunluğu(Length), uzantısı(Extension), oluşturulma(CreationTime) ve son yazılma zamanı(LastWriteTime) bilgileri ele alınmaktadır. Elde edilen dosya adları aslında fiziki adresleride içermektedir. Bu nedenle ilgili dosya adları FileInfo tipinden örneklerin oluşturulmasında kullanılır. Bu nesne örnekleride generic koleksiyonda toplanır. Son olarak GridView kontrolüne veri kaynağı olarak generic liste koleksiyonu atanır. FileInfo ile gelen bilgilerden bazılarını GridView kontrolünde göstermek istediğimizden, AutoGenerateColumnsözelliğine false değeri atanmış ve Columns elementi içerisinde ilgili alanlar açık bir şekilde yazılmıştır. BoundField elementlerine ait DataField niteliklerinin değerleri FileInfo ile gelen nesne örneklerindeki özellik adları olarak ayarlanmıştır.

İndirilmek istenen dosya için GridView kontrolüne bir adet CommandField elementi dahil edilmiştir. Burada seçme işlemi ele alınarak aslında küçük bir hile yapılmaktadır. GridView kontrolünde seçim düğmesine basıldıktan sonra devreye giren SelectedIndexChanged olayı içerisinde dosya indirme(Download) işlemi başlatılmaktadır. Teorik olarak Response sınıfının WriteFile metodu ile parametre olarak verilen dosya istemci bilgisayara indirilebilirmektedir. Ancak ön hazırlıklar yapılması gerekmektedir. Bu amaçla, indirilmek istenen dosya adı, GridView kontrolünde seçilen satıra ait ilk hücreden seçildikten sonra, Response sınıfının ilgili metodları ile ön hazırlıklar yapılır. İndirilecek dosya üretilen çıktının Header kısmında ele alınmaktadır. Benzer şekilde indirilecek dosyanın uzunluğuda Header kısmına eklenir. Daha sonra içerik tipi belirlenir. application/octet-stream değeri, dosyanın ikili(binary) formatta indirileceğini belirtmektedir. Bu işlemlerin arkasındanda Response sınıfının WriteFile ve End metodları sırasıyla çalıştırılır. End metodu, o anki sayfaya ait yaşam sürecinin kesilmesini sağlamaktadır. Bir başka deyişle Response.Endçağrısından sonra herhangibir kod satırı var ise işletilmeyecektir. Hatta, global.asax dosyasında yer alan Application_EndRequest metoduda devreye girecektir. Bu durumu analiz etmeden önceği örneğimizi test edelim. Uygulama çalıştırıldığında aşağıdakine benzer bir ekran görüntüsü ile karşılaşılacaktır.

Burada pek çok ek özellik tasarlanabilir. Örneğin uzantıya göre içerik tipi değiştirilebilir. Hatta download işlemi yerine örnek bir dökümanın sayfaya çıktı olarak verilmesi sağlanabilir. PDF içeriklerinin tarayıcıda gösterilmesi buna örnek olarak verilebilir. Bunların dışında uygulamanın kullanıcıya göre yetkilendirilmesi ve sadece ele alabileceği dosyaları indirebilmesi sağlanabilir. Bu tamamen projenin ihtiyaçlarına ve geliştiricinin kullanıcılara sunmak istediklerine bağlı olarak gelişebilecek bir modeldir. indir başlıklık düğmelerden herhangibirine bastığımızda indirme işleminin aşağıdaki ekran görüntüsünde yer aldığı gibi başladığı görülür.

Şimdi gelelim Response.End metodunun etkisine. Bu durumu analiz etmek için, Response.End sonrasına aşağıdaki gibi örnek bir kod satırı ekleyelim.

protected void grdDosyalar_SelectedIndexChanged(object sender, EventArgs e)
{
    string dosyaAdi = Server.MapPath("dokumanlar") + "\\" + grdDosyalar.SelectedRow.Cells[0].Text;
    FileInfo dosya = new FileInfo(dosyaAdi);

    Response.Clear();
    Response.AddHeader("Content-Disposition","attachment; filename=" + dosyaAdi);
    Response.AddHeader("Content-Length",dosya.Length.ToString());
    Response.ContentType = "application/octet-stream";
    Response.WriteFile(dosyaAdi);
   
Response.End();
   
Response.Write("Dosya indirildi");
}

Hemen arkasından bilinen yaşam döngüsünü izlemek adına default.aspx sayfasını aşağıdaki gibi değiştirelim.

protected void Page_PreInit(object sender, EventArgs e)
{
    Debug.WriteLine("Page_PreInit metodu");
}
protected void
Page_Init(object sender, EventArgs e)
{
    Debug.WriteLine("Page_Init metodu");
}
protected void
Page_Load(object sender, EventArgs e)
{
    // Diğer kod satırları
    Debug.WriteLine("Page_Load metodu");
}
protected void
Page_PreRender(object sender, EventArgs e)
{
    Debug.WriteLine("Page PreRender Metodu");
}
protected void
Page_Unload(object sender, EventArgs e)
{
    Debug.WriteLine("Page Unload Metodu");
}

protected void
grdDosyalar_SelectedIndexChanged(object sender, EventArgs e)
{
   
Debug.WriteLine("Dosya indirme işlemi başlıyor");
    // Diğer kod satırları
   
Response.End();
   
Debug.WriteLine("Response.End metodu çağırıldı");
   
Response.Write("Dosya indirildi");
}

Bu değişikliklere ek olarak projeye global.asax dosyası ekleyip içerisine Application_EndRequest metodunu aşağıdaki gibi dahil edelim.

void Application_EndRequest(object sender, EventArgs e)
{
   
Debug.WriteLine("Application EndRequest Metodu Çağırıldı");
}

Şimdi uygulamayı Debug modda çalıştırıp output penceresindeki çıktıları izleyebiliriz. Bir dosya indirme işlemi gerçekleştirildikten sonra sayfanın yaşam döngüsü aşağıdaki gibi çalışacaktır.

Page_PreInit metodu
Page_Init metodu
Page_Load metodu
Dosya indirme işlemi başlıyor
  
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll
   An exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll but was not handled in user code

Page Unload Metodu
Application EndRequest Metodu Çağırıldı

Çok doğal olarak GridView kontrolü üzerindeki düğmeye basıldığında sayfanın sunucuya tekrar gitmesi ve işlenmesi söz konusudur. Bu nedenle süreç Page_PreInit ile başlamaktadır. Ancak dikkat edilecek olursa Response.End çağrısından sonraki satırlar devreye girmemiştir. Debug penceresine ve tarayıcıdaki çıktıya herhangibir kod yazılmamıştır. Dahası, bir exception(System.Threading.ThreadAbortException) fırlatılmış ve sayfa yaşam döngüsü Page_PreRender metodunu işletmeden doğrudan Page_Unload olayını işletmiş ve arkasından global.asax dosyasındaki Application_EndRequest devreye girmiştir. Elbetteki üretilen istisna(exception) Asp.Net çalışma ortamı tarafından görmezden gelinmektedir. Bu nedenle istemci herhangibir şekilde hata mesajı ile karşılaşmaz.

Gelelim makalemizin ikinci konusuna. İndirme işlemleri kadar Upload işlemleride önemlidir. Tabi burada istemcilerin her dosya tipini veya çeşidini sunucuya göndermesi doğru olmayabilir. Kapalı ağ(intranet) sistemlerinde bu söz konusu olabilir. Nitekim kimin handi dosyayı Upload ettiğinin belirlenmesi dışında, bu kişiye ulaşılmasıda kolaydır :). Ancak internet tabanlı daha geniş sistemlerde her ne kadar kullanıcılar tespit edilebilsede, kötü niyetli istemcilerin varlığı nedeniyle sistemin genelini tehlikeye atmamak adına tedbirler almak doğru bir yaklaşım olacaktır. Biz tabiki basit olarak Upload işlemlerini ele alacağız ve bahsettiğimiz güvenlik konularını şimdilik görmezden geleceğiz.

Upload işlemlerini kolaylaştırmak adına Asp.Net 2.0, FileUpload isimli bir kontrol getirmektedir. Bu kontrol basit olarak istemcinin Upload etmek istediği dosyayı seçebilmesini sağlamaktadır. Bu seçim işlemi ile birlikte, sunucuya gönderilmek istenen dosyaya ait bir takım bilgilerde FileUpload kontrolünce elde edilir. Örneğin içeriğin tipi kontrol edilerek sadece bazı dosyaların gönderilmesine izin verilebilir. İlk olarak web uygulamamıza aşağıdaki gibi Default2.aspx sayfasını ekleyelim.

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected void btnGonder_Click(object sender, EventArgs e)
    {
        if (
uplDosya.HasFile)
        {
           
uplDosya.SaveAs(Server.MapPath("Dokumanlar") + "\\" + uplDosya.FileName);
        }
        else
            Response.Write("Upload edilecek dosya yok");
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Upload Islemleri</title>
    </head>
<body>
    <form id="form1" runat="server">
        <div>
            Dosyayı Seçin :
<asp:FileUpload ID="uplDosya" runat="server" />
            <br />
            <asp:Button ID="bntGonder" runat="server" Text="Gönder"
OnClick="btnGonder_Click" />
        </div>
    </form>
</body>
</html>

Öncelikli olarak neler yaptığımıza bir bakalım. FileUpload kontrolü ile dosya seçilmesi, gönderme işlemi için yeterli değildir. Sayfanın sunucuya doğru gönderilmesi gerekmektedir. Bu işi basit olarak bir Button kontrolü üstelenebilir. Button kontrolümüze ait Click olay metodunda ise öncelikli olarak seçili bir dosya olup olmadığı HasFileözelliği ile kontrol edilmektedir. Bu kontrol, dosya seçmeden gönderme işlemi yapıldığı takdirde oluşacak hataların önüne geçilmesini sağlamaktadır. Eğer seçili olan bir dosya var ise basit olarak FileUpload sınıfının SaveAs metodu çağırılır. SaveAs metoduna dosyanın yol adresinin fiziki olarak verilmesi gerekmektedir. Bu nedenle yine Server.MapPath ile Dokumanlar klasörünün fiziki adresi elde edilir. FileUpload kontrolünün FileNameözelliği ile seçilen dosyanın adı yakalanmakta ve fiziki adresin sonuna eklenmektedir. Eğer kod Debug edilirse, dosya seçildikten sonra Upload işlemi için düğmeye basıldığında FileUpload kontrolü üzerinde, seçilen dosyaya ilişkin çeşitli ek bilgilere ulaşılabildiği görülür.

Söz gelimi dosyanın tipi hakkında fikir elde etmek için ContentTypeözelliğine bakılabilir. Buna göre belirli dosya tiplerinin indirilmesine izin verilmesi istenen durumlara karşı tedbirler alınabilir. Gönderilecek dosyanın boyutu ile ilgili olarak bir kısıtlama getirilmek isteniyorsa içerik uzunluğu ContentLengthözelliği ile tedarik edilerek gerekli değişiklikler yapılabilir. Şimdi örneği deniyerek devam edelim. Basit olarak bir döküman dosyasını aşağıdaki gibi seçip Upload etmeyi deneyeceğiz.

Görüldüğü üzere Browse işleminden sonra otomatik olarak standart Choose File iletişim penceresi (Dialog Box) ile karşılaştık. Örnekte bir resim dosyası seçilmiştir. Seçim işleminden sonra Gönder düğmesine basılırsa dosyanın başarılı bir şekilde Dokumanlar klasörü altına yazıldığı görülecektir.

Upload işlemleri sırasında yetki problemi nedeni ile IIS altındaki herhangibir klasöre dosya yazma işlemi sırasında hata mesajı alınabilir. Bu durumda ASPNET kullanıcısına ilgili klasöre yazma hakkı verilmesi gerekebilir.

Upload işlemleri sırasında dikkat edilmesi gereken kritik bir sayı vardır. 4096 byte. Yani 4 megabyte. Boyutu bu değerin üzerindeki bir dosyayı Upload etmek istediğimizde Asp.Net ortamı bir hata üretir ve dosyanın sunucuya gönderilmesine izin vermez. Ne yazıkki hata üretimi kodların işletilmesinden önce gerçekleşir. Bu nedenle kullanıcıya anlamlı bir mesaj gösterilmeside pek mümkün olmamamaktadır. Eğer 4 megabyte üzerinde dosyaların upload edilebilmesini başka bir deyişle izin verilen limite kadar olan dosyaların gönderilebilmesini istiyorsak web.config dosyası içerisinde httpRuntime elementinin ayarlanması gerekmektedir. system.web boğumu içerisinde yer alan httpRuntime elementi sayesinde, http çalışma zamanına ait çeşitli ayarlamalar yapılabilmektedir. Bizim ihtiyacımız olan dosya büyüklüğü sınıfı ayarı için örneğimizde web.config dosyasını aşağıdaki gibi düzenlememiz yeterlidir.

<?xml version="1.0"?>
<configuration>
    <appSettings/>
    <connectionStrings/>
    <system.web>
       
<httpRuntime maxRequestLength="51200"/>
        <compilation debug="true"/>
        <authentication mode="Windows"/>
    </system.web>
</configuration>

Burada yapılan ayarlamaya göre istemciler 50 megabyte' a kadar dosyaları sunucuya gönderebilecektir. Upload işlemlerini içerik yönetim sistemlerinde resim, döküman veya örnek uygulamaların, programların gönderilmesinde, grafik kütüphanesi tarzındaki sistemlerde çeşitli formatta resim veya akıcı görüntü formatlarının gönderilmesinde ve buna benzer durumlarda kullanmak yaygındır.

Gelelim makalemizin üçüncü konusuna. Yine istemciden sunucuya doğru bir dosya gönderme işlemi gerçekleştirmeyi hedeflediğimizi düşünelim. Ancak bu sefer XML tabanlı bir dosyayı gönderiyor olacağız. Bu dosyanın özelliği, bizim istediğimiz şekilde tasarlanmış olması dışında, sunucu tarafında anında işlenecek olmasıdır. Söz gelimi, XML dosyası içerisinde dinamik olarak sayfaya yüklenmesi istenen kontrollere ait bilgiler yer alabilir. Bu durumda Upload edilen XML dosyasının sunucu tarafında işlenerek bir çıktı üretilmesi gerekmektedir. Bir başka deyişle istemciden sunucuya gönderilen sayfayı, fiziki olarak yazmadan işlemek ve kullanmak istediğimizi göz önüne alıyoruz. Peki bu sistemi nasıl yazabiliriz? İlk olarak istemcinin göndereceği basit XML dökümanını hazırlayarak başlayalım. Örnek olarak aşağıdaki gibi bir içerik düşünülebilir.

<?xml version="1.0" encoding="utf-8"?>
<Kontroller>
    <Kontrol tip="MetinKutusu" id="metinKutusu1"/>
    <Kontrol tip="Dugme" metin="Gonder" id="gonder1"/>
    <Kontrol tip="Label" metin="Ad" id="ad1"/>
</Kontroller>

Olayı basit bir şekilde ele almak adına Xml içeriğini mümkün olduğu kadar basit düşünmeye çalıştık. Elbetteki çok daha karmaşık ve daha çok parametre sunan bir Xml dökümanı söz konusu olabilir. Şimdi bu dosyayı nasıl ele alacağımıza bakalım. Dikkat edilmesi gereken noktalardan birisi, Upload edilecek dökümanın XML formatında olması gerekliliğidir. Bunu sağlamak için, içerik tipine(ContentType) bakmak gerekecektir. Sonrasında ise Upload edilen dosyanın Framework içerisinde yer alan XML tipleri yardımıyla ele alınması yeterlidir. Sonuç olarak Default3.aspx dosyamızın içeriği aşağıdaki gibi olacaktır.

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Xml" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected void btnGonder_Click(object sender, EventArgs e)
    {
        if (
uplKontroller.HasFile)
        {
           
HttpPostedFile dosya = uplKontroller.PostedFile;
            if (dosya
.ContentType == "text/xml")
            {
                XmlDocument doc = new XmlDocument();
                doc.
Load(dosya.InputStream);
               
XmlNodeList kontroller=doc.GetElementsByTagName("Kontrol");
                foreach (
XmlNode kontrol in kontroller)
                {
                    switch (
kontrol.Attributes["tip"].Value)
                    {
                       
case "Dugme":
                            Button btn = new Button();
                            btn.ID = kontrol.
Attributes["id"].Value;
                            btn.Text = kontrol.
Attributes["metin"].Value;
                           
phlKontroller.Controls.Add(btn);
                            break;
                       
case "MetinKutusu":
                            TextBox txt = new TextBox();
                            txt.ID = kontrol.Attributes["id"].Value;
                            phlKontroller.Controls.Add(txt);
                            break;
                       
case "Label":
                            Label lbl = new Label();
                            lbl.ID = kontrol.Attributes["id"].Value;
                            lbl.Text = kontrol.Attributes["metin"].Value;
                            phlKontroller.Controls.Add(lbl);
                            break;
                    }
                }
            }
            else
                Response.Write("Dosya içeriği XML olmalıdır");
        }
        else
            Response.Write("Dosya bulunamadı");
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Upload Edilen Dosyayi O Anda Islemek</title>
    </head>
<body>
    <form id="form1" runat="server">
        <div>
            Xml Dosayasını Seçin :
<asp:FileUpload ID="uplKontroller" runat="server" />
            <br />
            <asp:Button ID="btnGonder" Text="Gönder" runat="server"
OnClick="btnGonder_Click" />
            <br />
            Yüklenen Kontroller:
           
<asp:PlaceHolder ID="phlKontroller" runat="server" />
        </div>
    </form>
</body>
</html>

Dilerseniz kod içerisinde neler yaptığımıza kısaca bakalım. İlk olarak düğmeye basıldığında HasFile özelliği ile seçilen bir dosya olup olmadığını kontrol ediyoruz. Sonrasında ise FileUpload kontrolünün PostedFileözelliğinden yararlanıp seçilen dosyaya ait bazı temel bilgileri taşıyan HttpPostedFile tipine ait nesne örneğini elde ediyoruz. Bu nesne örneği üzerinden ContentTypeözelliğine geçerek içeriğin XML olup olmadığını text/xml eşleştirmesi ile kontrol ediyoruz. Sonrasında ise okunan dosya içeriğini XmlDocument nesnesine yüklüyoruz. XmlDocument nesne örneğine ait Load metodunun parametresi olarak bir Stream kullanılabildiğinden, HttpPostedFile nesne örneğinin InputStreamözelliğinden yararlanıyoruz. Son olarak yüklenen XML dökümanı içerisinde dolaşarak ilgili nitelikleri okuyor ve dinamik olarak oluşturulan kontrolleri, sayfadaki PlaceHolder kontrolünün Controls koleksiyonuna ekliyoruz.

İşlemlerin daha sağlıklı olması açısından yüklenen XML içeriğinin belirli kurallara uygun olup olmadığı bir şema dosyası (XSD olabilir örneğin) yardımıyla kontrol edilebilir. Söz gelimi gelen XML dökümanının yapısı, element adları, nitelik adları veya tipleri bu şema yardımıyla denetlenip, kontrollerin belirli standartlara göre okunabilmesi sağlanmış olunur. Şema kontrolünün nasıl yapılabileceğine dair daha önceki bir makalemizden yararlanabilirsiniz.

Uygulamayı çalıştırdığımızda ve istemci tarafından Kontrollerim.xml dosyasını yüklediğimizde aşağıdaki ekran görüntüsünde olduğu gibi kontrollerin başarılı bir şekilde üretilip sayfaya yüklendiğini görebiliriz.

Sıra geldi makalemizin son konusuna. İçerik yönetimi adına, istemcinin sunucuya gönderdiği dosyaların veritabanı üzerindeki bir tabloda tutulması istenebilir. Eğer ilişkisel veritabanı sistemi (Relational Database Management System) söz konusu ise, dosyaların tabloda tutuluyor olması taşıma işlemlerini kolaylaştırabileceği gibi, içerik güvenliğininde de daha etkin bir seviyede yapılabilmesini sağlayacaktır. (Tabi tersine saklanacak dosya boyutlarına göre veritabanı daha hızlı şişecektir.) Bu tarz bir ihtiyacın çıkış noktası son derece basittir. Her zaman olduğu gibi bir FileUpload kontrolü ile istemciye dosya seçtirilmeli daha sonra ilgili içerik sunucu tarafında işlenerek veritabanındaki ilgili tabloya yazdırılmalıdır. Burada çalıştıralacak komut dışında tablodaki alan tipide önemlidir. Text tabanlı bir içerik gönderilecek olsada, tablo tarafında image veya VarBinary tipinden alanlar tutmak Unicode tutarlılığı açısından daha doğru bir yaklaşım olacaktır. Dilerseniz bir örnek ile bu durumu incelemeye çalışalım. İlk olarak içeriği saklayacağımız basit bir tablo oluşturalım. Bunun için SQL Server 2005 üzerinde aşağıdaki gibi bir tablo göz önüne alınabilir.

Dosyalar isimli tabloda, sunucuya gönderilen dosya içeriğini saklamak için image tipinden bir alan kullanılmaktadır. Bunlara ek olarak dosyanın eklenme tarihi,içeriğin tipi ve dosya adı bilgileride yer almaktadır. Söz konusu alanlar dışında, web sitesinde kullanılan doğrulama(Authentication) sistemine göre, Upload işlemini yapan kullanıcının bilgilerinin saklanması hatta varsa Membership gibi kullanıcı tablo sistemleri ile ilişkilendirilmeside mümkün olabilir. Gelelim yükleme işlemini tabloya yazacak kodlarımıza.

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected void
btnGonder_Click(object sender, EventArgs e)
    {
        if (
uplDosya.HasFile)
        {
            string conStr =
ConfigurationManager.ConnectionStrings["ConStr"].ConnectionString;
            using (SqlConnection conn = new SqlConnection(conStr))
            {
                SqlCommand cmd = new SqlCommand("Insert into Dosyalar (EkleNmeTarihi,DosyaIcerigi,DosyaAdi,IcerikTipi) Values   
(@EklenmeTarihi,@DosyaIcerigi,@DosyaAdi,@IcerikTipi)", conn);
                cmd.Parameters.AddWithValue("@EklenmeTarihi", DateTime.Now);
               
cmd.Parameters.AddWithValue("@DosyaIcerigi", uplDosya.FileBytes);
                cmd.Parameters.AddWithValue("@DosyaAdi", uplDosya.
FileName);
                cmd.Parameters.AddWithValue("@IcerikTipi", uplDosya.
PostedFile.ContentType);
                conn.Open();
                int sonuc=cmd.
ExecuteNonQuery();
                Response.Write(sonuc + " dosya aktarıldı");
            }
        }
        else
            Response.Write("Dosya seçmelisiniz");
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Upload Edilen Dosyayı Veritabanına Yazma</title>
    </head>
<body>
    <form id="form1" runat="server">
        <div>
            Dosyayı Seçin :
<asp:FileUpload ID="uplDosya" runat="server" />
            <br />
            <asp:Button ID="btnGonder" Text="Gönder" runat="server"
OnClick="btnGonder_Click" />
        </div>
    </form>
</body>
</html>

Yine kodları kısaca incelemekte fayda var. Her zamanki gibi, istemcinin bir dosya seçtiğinden emin olduktan sonra gerekli işlemleri yapıyoruz. Burada önemli olan nokta çalıştırılacak SQL cümlesinde kullanılan parametrelerin değerlerinin nasıl verildiği. Dikkat edilecek olursa, image tipindeki alanın içeriğini verirken FileUpload kontrolünün FileBytesözelliğinden yararlanıyoruz. Burada dikkat edilmesi gereken noktalardan biriside çok büyük boyutlu dosyaların aktarılması sırasında yaşanabilecek timeout sorunudur. Böyle bir durumda kalındığı takdirde bağlantı için timeout sürelerinin arttırılması yoluna gidilebilir yada dosyanın parçalanarak sunucuya gönderilmesi ve burada o şekilde ele alınması sağlanabilir. Uygulama çeşitli tipteki dosyalar ile test edildiğinde başarılı bir şekilde çalıştığı görülecektir. Aşağıdaki ekran görüntüsünde bir kaç dosya tipinin upload edilmesi sonrasındaki durum vurgulanmaktadır.

Her dosya eklenme işleminden sonrada tarayıcı penceresindeki görüntü aşağıdaki gibi olacaktır.

Elbette Upload edilen içeriklerin, istemciler tarafından indirilmeside gerekecektir. Bu durumda tablo alanındaki dosya içeriğinin stream olarak yazdırılması gerekir. Tabi bunun için Response sınıfının WriteFile metodu yerine BinaryWrite metodunu tercih edeceğiz. (Alternatif bir yaklaşım olarak, tablodan okunan dosya içeriğinin bir temp dosyaya atılması ve oradanda WriteFile metodu ile yazdırılmasıda düşünülebilir) Nitekim dosya içerikleri tabloda binary olarak tutulmaktadır. Öyleyse son olarak bu işlemide nasıl yapabileceğimizi inceleyeceğimiz bir örnek sayfa daha ekleyelim. Default5.aspx sayfamızın içeriği aşağıdaki gibi olacaktır.

<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected void
GridView1_SelectedIndexChanged(object sender, EventArgs e)
    {
        using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConStr"].ConnectionString))
        {
            SqlCommand cmd=new SqlCommand("SELECT DosyaAdi,DosyaIcerigi,IcerikTipi FROM Dosyalar Where Id=@Id",conn);
            cmd.Parameters.AddWithValue(
"@Id", GridView1.SelectedValue);
            conn.Open();
            SqlDataReader reader=cmd.ExecuteReader();
            if (reader.Read())
            {
               
Response.Clear();
               
Response.AddHeader("Content-Disposition", "attachment; filename=" + reader.GetString(0));
               
byte[] dosyaIcerigi = reader.GetSqlBinary(1).Value;
                Response.AddHeader("Content-Length",
dosyaIcerigi.Length.ToString());
                Response.
ContentType = "application/octet-stream";
                Response.
BinaryWrite(dosyaIcerigi);
            }
          
 reader.Close();
        }
      
 Response.End();
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
<body>
    <form id="form1" runat="server">
        <div>
            Dosyalar<br />
            <br />
            <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataSourceID="dsDosyalar"
OnSelectedIndexChanged="GridView1_SelectedIndexChanged" DataKeyNames="Id">
                <Columns>
                    <asp:BoundField DataField="DosyaAdi" HeaderText="DosyaAdi" SortExpression="DosyaAdi" />
                    <asp:BoundField DataField="EklenmeTarihi" HeaderText="EklenmeTarihi" SortExpression="EklenmeTarihi" />
                    <asp:BoundField DataField="IcerikTipi" HeaderText="IcerikTipi" SortExpression="IcerikTipi" />
                    <asp:CommandField ButtonType="Button" SelectText="indir" ShowSelectButton="True" />
                </Columns>
            </asp:GridView>
           
<asp:SqlDataSource ID="dsDosyalar" runat="server" ConnectionString="<%$ ConnectionStrings:ConStr %>" SelectCommand="SELECT Id,DosyaAdi, EklenmeTarihi, IcerikTipi FROM Dosyalar">
           
</asp:SqlDataSource>
        </div>
    </form>
</body>
</html>

Tasarladığımız sayfayı dilerseniz inceleyelim. Basit olarak Dosyalar tablosunun içeriğini göstermek amacıyla SqlDataSource ve GridView kontrollerinden faydalanıyoruz. Yine ilk örneğimizde olduğu gibi indirme işlemini başlatmak adına küçük hilemizi yaptık ve bir Select düğmesi kullandık. Burada dosya içeriği tabloda alan olarak tutulduğu için, SqlCommand ile verinin çekilmesi gerekiyor. Bunu kolaylaştırmak adına GridView içerisinde seçilen satıra ait Id alanının değerini almalıyız. Bu amaçla GridView kontrolünün DataKeyNamesözelliğine Id değerini verdik. Bu değeri SelectedIndexChanged metodu içerisinden alarak sorgu cümlesinde parametre olarak kullanıyor ve böylece indirilmek istenen dosyaya ait içeriğin olduğu tablo satırını çekebiliyoruz. Bizim için önemli olan nokta, binary içeriği okumak için SqlDataReader sınıfının GetSqlBinary metodunu kullanıyor olmamız. Bu metod ile dönen tipin Valueözelliğinden faydalanıp elde edilen byte[] dizisini Response sınıfının BinaryWrite metoduna parametre olarak verdiğimizde yazma işlemi gerçekleştirilmiş oluyor. Sonuç olarak çalışma zamanında istediğimiz sonuca ulaşıyor ve dosya indirme işlemlerini gerçekleştirebiliyoruz.

Bu makalemizde Asp.Net uygulamalarında Download ve Upload işlemlerini detayları ile incelemeye çalıştık. İlk olarak bir dosyanın indirilme işleminin nasıl yapılabileceğine baktık. Sonrasında ise basit olarak bir Upload işlemi ile sunucuya dosya gönderme olayını ele aldık. Upload işleminin farklı yönlerini ele almak adına, anında sunucu tarafında işleme ve tabloya satır olarak ekleme işlemlerini inceledikten sonra, tablodaki bir binary içeriği indirme sürecine göz attık. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama için Tıklayın(Dosyanın çok yer tutmaması açısından mdf dosyası çıkartılmıştır)

Burak Selim ŞENYURT
selim@bsenyurt.com

Asp.Net Routing – Hatırlamak

$
0
0

obliviousMerhaba Arkadaşlar,

Geçtiğimiz günlerde şirkette çok küçük bir web uygulamasına ihtiyaç duyuldu. Neredeyse bir günlük geliştirme maliyeti olan, küçük bir departmanın önemli bir gereksinimi karşılayacaktı. Tabi insan uzun zaman kodlama yapmayınca veya kodlamaya ara verince bazı temel bilgileri de unutabiliyor.

Ben de kafayı Team Foundation Server entegrasyonu, SOA mimarisi ve Scrum gibi metodolojiler ile bozunca, zihinsel diskimdeki ana partition’ a yeni bilgilerin yazıldığına ve eskilerinin yerinde yeller estiğine şahit oldum. Ama malum, günümüz teknolojilerinde bilginin tamamını ezberlemeye çalışmak yerine, en doğrusuna en hızlı şekilde nasıl ulaşabileceğimizi bilmek daha önemli. İşte bu felsefeden yola çıkıp dedim ki, şu Asp.Net Routing konusunu bir hatırlayayım ve hatta kayıt altına alayım. İşte hikayemiz böyle başladı Smile

Asp.Net MVC’ nin en cazip yanlarından birisi sanırım sağladığı URL eşleştirme(Routing) sistemidir. Özellikle Search Engine Optimization(SEO) kriterleri göz önüne alındığında, orders.aspx?categoryName=Beverages&shipCity=istanbul&orderNumber=12903 gibi bir ifade yerine, orders/beverages/istanbul/12903 şeklinde bir URLçok daha değerlidir.

Bilindiği üzere Asp.Net 4.0 sürümü ile birlikte, URL ve asıl kaynak(aslında yönlendirme sonucu gidilmesi gereken bir aspx sayfa kodu düşünebiliriz) eşleştirmelerinde kullanılan yönlendirme işlemleri oldukça kolaylaştırılmıştır. İşte bu yazımızda, biraz temelleri hatırlamaya çalışacak ve SEO’cu arama motorlarının olmassa olmaz isterlerinden birisi olan Routing konusunu dört basit örnek üzerinden inceleyeceğiz. İlk olarak senaryomuza bir göz atalım.

Senaryo

Senaryomuzda veri kaynağı olarak emektar Northwind veritabanını kullanacağız. Örneklerimizde hem Entity Framework kullanacağımız hem de doğrudan SQL sorgusu çalıştıracağımız bir vakamız yer alacak. Temel olarak amacımız aşağıdaki ekran görüntüsünde yer alan URL eşleştirmelerini web uygulaması üzerinden işlettirmek.

route_2

Dikkat edileceği üzere URL satırından girilecek olan anlamlı ifadeler, aslında arka planda bir eşleştirme tablosuna uygun olacak şekilde ilgili kaynaklara yönlendirilmekteler. Örneğin beverages isimli kategoride yer alan ürünlerin listelenmesi için yazılan urunler/beverages sorgusu, sisteme daha önceden öğretilen Urunler/{CategoryName}üzerinden geçerek urun.aspx sayfasına yönlendiriliyor. Çok doğal olarak ilgili sayfa içerisinde, CategoryName değerine bakılarak bir sonuç kümesinin sunulması gerekiyor.

Route Eşleştirmelerinin Ayarlanması

Bu işlem için global.asax.cs dosyasında aşağıdaki kodlamaları yapmamız gerekmektedir.

using System;
using System.Web;
using System.Web.Routing;

namespace HowTo_EasyRouting
{
    public class Global
        : HttpApplication
    {
        private void SetRouteMaps()
        {
            RouteTable.Routes.MapPageRoute("Varsayilan", "", "~/kategori.aspx");
            RouteTable.Routes.MapPageRoute("Kategoriler", "kategoriler", "~/kategori.aspx");
            RouteTable.Routes.MapPageRoute("KategoriBazliUrunler","urunler/{CategoryName}","~/urun.aspx");
            RouteTable.Routes.MapPageRoute("SehirBazliSiraliMusteriler", "musteriler/{City}$orderby={FieldName}", "~/musteri.aspx");
            RouteTable.Routes.MapPageRoute("SehirBazliSiparisler", "siparisler/{ShipCity}", "~/siparis.aspx");
        }

        protected void Application_Start(object sender, EventArgs e)
        {
            SetRouteMaps();
        }
    }
}

Application_Start olay metodu bilindiği üzere, Web uygulaması ayağa kalktığında devreye girmektedir. Dolayısıyla uygulamanın başladığı bir yerde, URL eşleştirme tanımlamalarını yapmak son derece mantıklıdır. Olayın ana kahramanı RouteTable sınıfıdır. Söz konusu tipin static olarak erişilebilen Routesözelliği bir RouteCollection referansını işaret etmektedir. Bu koleksiyon tahmin edileceği üzere URL ile asıl kaynak eşleştirmelerini taşımaktadır. Bu nedenle MapPageRoute metodundan da yararlanılarak gerekli eşleştirme bilgileri koleksiyona eklenir.

İlk satır ile Root URL adresine gelen bir talebin doğrudan kategori.aspx sayfasına yönlendirilmesi gerektiği ifade edilmektedir. İkinci satırda ise web kök adresini takiben kategoriler şeklinde gelen bir ifadenin gelmesi halinde yine, kategori.aspx sayfasına gidilmesi gerektiği belirtilmektedir.

KategoriBazliUrunler ismi ile tanımlanmış eşleştirmeye göre, urunler/{CategoryName}şeklinde gelen talepler urun.aspx sayfasına yönlendirilmektedir. İlginç kullanımlardan birisi de SehirBazliSiraliMusteriler isimli eşleştirmedir. Burada City ve FieldName isimli iki Route parametresi söz konusudur. İfade ise size sanıyorum tanıdık gelecektir. Neredeyse bir REST servis sorgusuna(örneğin OData sorgusuna) oldukça yakın değil mi? Nerd smile

Şimdi bu durumları kod tarafında nasıl karşılayacağımızı örnek bir Asp.Net uygulaması üzerinden incelemeye çalışalım.

Birinci Durum

İlk olarak kategori.aspx sayfasına doğru yapılacak yönlendirmeleri ele almaya çalışacağız. Bunun için web uygulamamıza kategori.aspx isimli bir sayfa ekleyip içeriği ile kod tarafını aşağıdaki gibi geliştirelim.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Kategori.aspx.cs" Inherits="HowTo_EasyRouting.Kategori" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div id="divCategories" runat="server" style="background-color:lightcyan">
       
    </div>
    </form>
</body>
</html>

kod tarafı

using System;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace HowTo_EasyRouting
{
    public partial class Kategori
        : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                using (NorthwindEntities context = new NorthwindEntities())
                {
                    var categories = from c in context.Categories
                                     orderby c.CategoryName
                                     select new
                                     {
                                         c.CategoryID,
                                         c.CategoryName
                                     };

                    foreach (var category in categories)
                    {
                        HyperLink categoryLink = new HyperLink();
                        categoryLink.NavigateUrl = GetRouteUrl("KategoriBazliUrunler", new { CategoryName = category.CategoryName });
                        categoryLink.Text = string.Format("[{0}]-{1}<br/>", category.CategoryID.ToString(), category.CategoryName);
                        divCategories.Controls.Add(categoryLink);
                    }
                }
            }
        }
    }
}

Aslında kategori.aspx sayfasında tipik olarak Entity Framework odaklı bir sorgulama gerçekleştirilmekte ve kategori adları birer HyperLink bileşeni olarak div içerisine eklenmektedir. Konu itibariyle işin önemli olan kısmı ise HyperLink bileşeninin NavigateUrlözelliğine GetRouteUrl metodu sonucunun atanmasıdır.

GetRouteUrl metodu dikkat edileceği üzere iki parametre alır. İlk parametre route adıdır. Yazdığımız değere göre urunler/{CategoryName}şeklindeki atama değerlendirilir. İkinci parametre ise bu Route içerisinden kullanılmak istenen değişken adı ve değerini içeren nesnenin örneklendiği kısımdır. CategoryName tahmin edileceği üzere Route tanımı içerisindeki parametre adıdır. Değeri ise zaten LINQ(Language INtegrated Query) sorgusu içerisinden elde edilmektedir. İkinci parametre object tipinden olduğundan bir isimsiz tip(anonymous type) ataması yapılabilmiştir. Bu nedenle Route içerisinde birden fazla parametre olması halinde, isimsiz tipin de birden fazla özellik içermesi gerektiğini ifade edebiliriz.

İlk durumda herhangibir sayfa talep edilmediğinde veya kök web adresi ardından /kategorilerşeklinde bir URL ifadesi kullanıldığında, aşağıdaki ekran görüntüsünde yer alan sonuçlar ile karşılaşırız.

route_3

Dikkat edilmesi gereken en önemli nokta, her hangi bir bağlantı üstüne gelindiğinde oluşan sorgu adresidir. Örneğin Condiments için http://localhost:54605/urunler/condimentsşeklinde bir URL tanımı oluşmuştur. Peki bu bağlantıya tıklanırsak ne olur? Who me?

İkinci Durum

kategori.aspx sayfasında bir bağlantıya tıklandığında, HyperLink bileşeninin NavigateUrlözelliğinin sahip olduğu değerin Route tablosundaki eşleniğine bakılmalıdır. Yaptığımız tanımlamalara göre urun.aspx sayfasına gidilmesi beklenmelidir(KategoriBazliUrunler isimli Route tanımına dikkat edin) Buna göre urun.aspx sayfasının içeriğini aşağıdaki gibi düzenleyebiliriz.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Urun.aspx.cs" Inherits="HowTo_EasyRouting.Urun" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1 style="color:purple">
         <asp:Label ID="lblCategoryName" runat="server" /></h1>
        <br />
        <asp:GridView ID="grdUrunler" runat="server" />
    </div>
    </form>
</body>
</html>

kod tarafı

using System;
using System.Linq;
using System.Web.UI;

namespace HowTo_EasyRouting
{
    public partial class Urun
        : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (RouteData.Values["CategoryName"] != null)
            {
                string categoryName = RouteData.Values["CategoryName"].ToString();
                using (NorthwindEntities context = new NorthwindEntities())
                {
                    var products = from p in context.Products.Include("Product")
                                   where p.Category.CategoryName == categoryName
                                   orderby p.ProductName
                                   select new
                                   {
                                       p.ProductID,
                                       p.ProductName,
                                       p.UnitPrice
                                   };

                    grdUrunler.DataSource = products.ToList();
                    grdUrunler.DataBind();
                }
            }
            else
                Response.Redirect(GetRouteUrl("Kategoriler", null));
        }
    }
}

Tabi bu sayfaya gelindiğinde aslında Route tanımlaması içerisinde yer alan parametre değerinin okunması gerekmektedir. Bu sebepten sayfanın RouteDataözelliğinden hareket edilerek RouteValueDictionary tipinden olan Valuesözelliğine gidilir ve indeksleyiciye verilen CategoryName alanının var olup olmadığına bakılır. Malum urun.aspx sayfasına farklı bir şekilde erişilmek istenebilir ve CategoryName değeri null olarak gelebilir. Bu nedenle bir null değer kontrolü ardından Entity sorgulama işlemi yapılmıştır. RouteData.Values[“CategoryName”] ile URL satırındaki kategori adı bilgisi alındıktan sonra standart olarak bir Entity sorgusu icra edilmektedir. Eğer kategori adı null olarak gelirse bu durumda varsayılan URL eşleştirilmesi nedeniyle kategorilerin gösterildiği sayfaya gidilir.

route_4

Üçüncü Durum

URL eşleştirmelerinden SehirBazliSiraliMusteriler isimli olanı, iki adet route parametresi içermektedir. Burada başta da belirttiğimiz üzere OData sorgularına benzer bir ifade tanımlanmıştır. Eşleştirme bilgisine göre musteri.aspx sayfasına doğru bir yönlendirme söz konusudur. musteri.aspx içeriğini aşağıdaki gibi geliştirdiğimizi düşünelim.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Musteri.aspx.cs" Inherits="HowTo_EasyRouting.Musteri" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1 style="color:purple">Customers</h1>
        <asp:GridView ID="grdCustomer" runat="server" />
    </div>
    </form>
</body>
</html>

kod tarafı

using System;
using System.Linq;
using System.Reflection;
using System.Web.Routing;

namespace HowTo_EasyRouting
{
    public partial class Musteri : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
           if(RouteData.Values["City"]!=null
                || RouteData.Values["FieldName"]!=null)
            {
                string cityName=RouteData.Values["City"].ToString();
                string fieldName=RouteData.Values["FieldName"].ToString();
               
                using(NorthwindEntities context=new NorthwindEntities())
                {
                    var customers = context
                        .Customers
                        .Where(c => c.City == cityName)
                        .OrderBy(GetField<Customer>(fieldName))
                        .Select(c => new
                        {
                            ID=c.CustomerID,
                            Title=c.ContactTitle,                         
                            Contact=c.ContactName,
                            Company=c.CompanyName,
                            c.City
                        });

                    grdCustomer.DataSource = customers.ToList();
                    grdCustomer.DataBind();
                }
            }
        }

        publicstatic Func<T, string> GetField<T>(string fieldName)
        {
            PropertyInfo pInfo=typeof(T).GetProperty(fieldName);
            if (pInfo == null)
                pInfo = typeof(T).GetProperty("CustomerID");

            return o => Convert.ToString(pInfo.GetValue(o, null));        
        }
    }
}

RouteData.Valuesözelliğinden yararlanılarak CityName ve FieldName değerlerinin null olup olmamasına göre bir kod parçası çalıştırılmaktadır. Bir önceki örnekten farklı bir durum olmasa da Entity sorgusunda OrderByextension metodunu nasıl kullandığımıza dikkat etmenizi rica ederim. İşte bu vakaya ait örnek ekran çıktıları.

Londra’ daki müşterilerin CustomerId bilgilerini göre sıralı olarak çekilmesi

route_5

Londra’ daki müşterilerin ContactName bilgisine göre sıralı olarak çekilmesi

route_6

Dördüncü Durum

Son vakada bir Route parametrenin her hangi bir veri bağlı kontrol ile nasıl ilişkilendirilebileceğini görmeye çalışacağız. Örneğin bir SqlDataSource bileşenindeki Select sorgusuna ait Where koşullarını Route parametreler ile ilişkilendirebiliriz. Bu durumu siparis.aspx sayfası içerisinde ele almaya çalışalım. Siparis sayfasına gelinebilmesi için Route tablo tanımlamalarına göre /siparisler/{ShipCity}şeklinde bir URL talebinin gönderilmesi gerekmektedir.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Siparis.aspx.cs" Inherits="HowTo_EasyRouting.Siparis" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1 style="color:purple">Siparişler</h1>
        <asp:GridView ID="grdOrders" runat="server" AllowPaging="True" AutoGenerateColumns="False" DataKeyNames="OrderID" DataSourceID="SqlDataSource1" >
            <Columns>
                <asp:BoundField DataField="OrderID" HeaderText="OrderID" InsertVisible="False" ReadOnly="True" SortExpression="OrderID" />
                <asp:BoundField DataField="CustomerID" HeaderText="CustomerID" SortExpression="CustomerID" />
                <asp:BoundField DataField="EmployeeID" HeaderText="EmployeeID" SortExpression="EmployeeID" />
                <asp:BoundField DataField="ShippedDate" HeaderText="ShippedDate" SortExpression="ShippedDate" />
                <asp:BoundField DataField="ShipCity" HeaderText="ShipCity" SortExpression="ShipCity" />
            </Columns>
        </asp:GridView>
        <asp:SqlDataSource ID="SqlDataSource1" runat="server"
            ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
            SelectCommand="SELECT [OrderID], [CustomerID], [EmployeeID], [ShippedDate], [ShipCity] FROM [Orders] WHERE ([ShipCity] = @ShipCity)">
            <SelectParameters>
                <asp:RouteParameter DefaultValue="" Name="ShipCity" RouteKey="ShipCity" Type="String" />
            </SelectParameters>
        </asp:SqlDataSource>
    </div>
    </form>
</body>
</html>

Önemli olan, SqlDataSource bileşenine ait SelectCommand ifadesindeki where koşulunda yer alan ShipCity isimli parametrenin bir RouteParameter ile ilişkilendirilmiş olmasıdır. RouteParameter bileşenine ait RouteKeyözelliği, Route Table’ daki ile aynı olmalıdır. Çok doğal olarak aspx kaynak tarafında yapılabilen bu eşleştirme, Wizardüzerinden de kolayca belirlenebilir. Aynen aşağıdaki ekran görüntüsünde olduğu gibi Winking smile

route_1

Buna göre örneğin Stuttgart’ a yapılan sevkiyatları aşağıdaki gibi elde edebiliriz.

route_7

Sonuç

Görüldüğü üzere URL eşleştirme işlemleri klasik sunucu tabanlı Asp.Net uygulamalarında da etkili bir şekilde kullanılabilir. Hatta bu felsefeden yola çıkarak OData sorgularının daha fazla gelişmişlerini destekleyecek web uygulamaları yazılması da pekala olasıdır. Yazımıza konu olan basit örneklerimizde ki anahtar noktalar, RouteTable sınıfı, RouteData.Valuesözelliği ve GetRouteUrl metodudur. Örneği geliştirmek tamamen sizin elinizde. İşe /siparisler/stuttgart/10301/şeklinde bir sorguyu ele alıp, 10301 numaralı siparişe ait detay bilgileri göstereceğiniz bir sayfayı üretmeyi deneyerek başlayabilirsiniz. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

HowTo_EasyRouting.zip (605,23 kb)

Asp.Net Temelleri–Eğlenceli GridView

$
0
0

Merhaba Arkadaşlar,

Bir süre önce çalıştığım firmada ufak bir web uygulaması ihtiyacı ile karşılaştım. Bir kaç günlük efor isteyen bu basit işte, uzun süredir kullanmadığım eski dostumuz GridView kontrolü ile haşır neşir oldum. Onun bir kaç etkili özelliğini ve işe yarar ip uçlarını tekrardan hatırladım. Hal böyle olunca bir araya getirip basit bir video anlatımında birleştirmek istedim. Ele aldığımız konulara gelince.

  • Hiç veri olmadığında GridView.
  • Alan içeriklerini formatlamak.
  • N sayıda alan kullanan Link vermek.
  • Belirli koşullarda satır bazında makyaj değiştirmek.
  • Null alanlar için hücrede imaj göstermek.
  • Tüm hücre içeriklerini aynı taraftan hizalamak.

Afiyetle izleyin. Eski günlerimizi yaad edelim.

Asp.Net–Custom Configuration Section Geliştirmek

$
0
0

ConfigurationMerhaba Arkadaşlar,

Konfigurasyon tabanlı geliştirme modeli, uygulama kodlarına girilmeden çalışma zamanına yönelik değişiklikler yapabilmemizi sağlar. Bu sayede pek çok programın kodsal müdahale yapmadan davranışları değiştirilebilir. .Net dünyasında baktığımızda da, App.Config, Web.config gibi dosyalar içerisinde Framework’ün geneline yönelik pek çok konfigurasyon ayarı bulunduğu görülür. appSettings, connectionStrings, httpHandler vb…

Eskilerden : Asp.Net 2.0 ile Configuration Management (Konfigurasyon Yönetimi)

Söz konusu konfigurasyon içerikleri aslında XML tabanlı bir dosya şemasının parçalarıdır ve doğal olarak element ile attribute’ lardan oluşmaktadır. Konfigurasyon dosyalarının daha iyi yönetilebilmesi için Asp.Net 2.0 ile birlikte Configuration API alt yapısı geliştirilmiştir. Bu kütüphane sayesinde konfigurasyon içerisindeki elementleresınıfbazında erişmek ve yönetebilmek mümkündür. Pek tabi XML elementlerinin sahip oldukları nitelikler, sınıfların özellikleri(Property) olarak ele alınmaktadır.

Peki konfigurasyon dosyası içerisine kendi özel kısımlarımızı(section) ilave etmek ve hatta bunları çalışma zamanında(Runtime) kullanmak istersek, nasıl bir yol izlememiz gerekir? Thinking smile 

Konfigurasyon Yapısı

Aklımıza ilk gelen belirli tip türetmeleri veya arayüz(interface) uyarlamaları ile bu işin halledilebilecek olmasıdır. Aslında olayı çözümlemek için var olan konfigurasyon parçalarının örnek tip yapısını incelemek yerinde bir hareket olacaktır. Söz gelimi system.web kısımı içerisindeki compilation ve pages elementlerini incelediğimizi düşünelim.

ccs_1

Eğer objectbrowserüzerinden ilgili elementlerin karşılık geldiği sınıfları incelersek şu sonuçlara varırız.

  • system.web elementi SystemWebSectionGroup isimli bir sınıf ile işaret edilmekte olup ConfigurationSectionGroup türevlidir.
  • compilation elementi system.web in alt elementidir ve CompilationSection sınıfı ile işaret edilmektedir. Bu sınıf ise ConfigurationSection’ dan türemiştir.
  • Benzer şekilde pages elementi de ConfigurationSection türevli PageSection sınıfı ile temsil edilmektedir.
  • namespaces elementine baktığımızda aynı tipten birden fazla elementi içerecek şekilde kullanılabildiği görülmektedir. Bunun için NamespaceColletion sınıfı ConfigurationElementCollection türevli olarak tasarlanmıştır.
  • namespaces segmenti içerisinde yer alan add elementleri aslında NamespaceInfo tipini işaret etmekte olup yine ConfigurationElement türevlidir.
  • alt elementler genellikle üst elementlerin birer özelliği(sınıf bazında düşünüldüğünde) olarak karşımıza çıkmaktadır.

Dolayısıyla kendi geliştireceğimiz özel Section elementleri için de bu tip bir yol izlememiz ve ilgili türetmeleri yapmamız yeterli olacaktır.

Örnek Senaryo

Basit bir senaryo üzerinden ilerleyebiliriz. Örneğin bir web uygulamasının web.config dosyası içerisinde aşağıdaki gibi bir konfigurasyon kısmı oluşturmak istediğimizi düşünelim.

ccs_3

serviceConnection ve altında yer alan definition elementlerinin işlevselliği çok önemli değildir aslında. Sadece bir kaç nitelik ve alt element içeren bir XML yapısı söz konusu. Bizim yapacağımız basit olarak bu konfigurasyon içeriğini türlendirmek ve çalışma zamanında yönetebilir hale getirmek. Öyleyse işe koyulalım ne duruyoruz.

Sınıfların İnşa Edilmesi

Boş bir Web uygulamasında aşağıdaki sınıf çizelgesinde yer alan tipleri ürettiğimizi düşünelim.

ccs_2

ServiceConnectionSection tipi dikkat edileceği üzere ConfigurationSection türevlidir ve içerisinde Type isimli ServiceTypeenum sabiti tipinden bir özellik ile ConfigurationElement türevli olan DefinitionSection tipinden başka bir özellik yer almaktadır.

ServiceConnectionSection sınıfı;

using System;
using System.Configuration;

namespace HowTo_WritingCustomConfigSection
{
    public class ServiceConnectionSection
       :ConfigurationSection
    {
        // Save işlemine izin vermesi için false döndürecek şekilde ezdik
        public override bool IsReadOnly()
        {
            return false;
        }

        [ConfigurationProperty("type", DefaultValue = ServiceType.WCF, IsRequired = false)]
        public ServiceType Type
        {
            get
            {
                return (ServiceType)Enum.Parse(typeof(ServiceType), this["type"].ToString());
            }
            set
            {
                this["type"] = value.ToString();
            }
        }

        [ConfigurationProperty("definition",IsRequired=true)]
        public DefinitionSection Definition
        {
            get
            {
                return (DefinitionSection)this["definition"];
            }
            set
            {
                this["definition"]=value;
            }
        }
    }
}

Konfigurasyon içeriğinde çalışma zamanında da değişiklik yapılması mümkündür ama konfigurasyon yöneticisinin Save metoduna tepki verebilmesi için IsReadOnly özelliğinin ezilmesi(override) ve false döndürmesi gerekmektedir.

Dikkat edileceği üzere Definition ve Type isimli özelliklere ConfigurationProperty niteliği(Attribute) uygulanmıştır. Bu niteliğe ait özelliklerden yararlanılarak element adı(konfigurasyon dosyasında görünecek olan isim) ve gereklilik(IsRequired) gibi değerler belirtilebilir.

Özelliklerin get ve set bloklarında fark edileceği gibi this anahtar kelimesinden yararlanılmakta ve üst tipin indeksleyicisine(Indexer) gidilerek değer ataması veya okunması işlemi gerçekleştirilmektedir.

F12 ile ConfigurationSection elementine gidiliğinde bu indexleyici görülebilir.

ccs_6

DefinitionSection sınıfı;

using System.Configuration;

namespace HowTo_WritingCustomConfigSection
{
    public class DefinitionSection
        :ConfigurationElement
    {
        // Save işlemine izin vermesi için false döndürecek şekilde ezdik
        public override bool IsReadOnly()
        {
            return false;
        }

        [ConfigurationProperty("name", IsRequired = true)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}/;'\"|\\,çşöğüı")]
        public string Name
        {
            get
            {
                return this["name"].ToString();
            }
            set
            {
                this["name"] = value;
            }
        }

        [ConfigurationProperty("wsdlAddress", IsRequired = true)]
        public string WsdlAddress
        {
            get
            {
                return this["wsdlAddress"].ToString();
            }
            set
            {
                this["wsdlAddress"] = value;
            }
        }
    }
}

DefinitionSection sınıfı aslında bir alt elementtir ve bu sebepten ConfigurationElement sınıfından türetilmiştir. Save operasyonuna cevap verebilmesi için IsReadOnlyözelliği ezilmiştir. Nameözelliğinde ConfigurationProperty dışında StringValidator niteliği de kullanılmış ve kullanılması istenmeyen bir karakter seti belirtilmiştir.

Web.config Bildirimleri

Artık konfigurasyon dosyası içerisinde kullanacağımız serviceConnection section için gerekli tip desteğine sahip bulunmaktayız. Peki web.config dosyası içerisinde bu bildirimleri nasıl gerçekleştirebiliriz?

Bazen 3ncü parti araçları sisteme dahil ettiğimizde, konfigurasyon dosyası içerisine koyacakları elementler için ekstra bildirimler eklediklerine şahit olmuşuzdur. Yani bir şekilde çalışma zamanına, “izleyen config içeriğinde şu tipe ait elementler kullanılabilir” denilebilmelidir. Bunun için configSections elementi içerisinde sectionGroup ve section tanımlamalarını yapmamız yeterli olacaktır. Aşağıda görüldüğü gibi.

<?xml version="1.0"?>
<configuration>
  <configSections>
    <sectionGroup name="serviceConnectionGroup">
    <section
       name="serviceConnection"
       type="HowTo_WritingCustomConfigSection.ServiceConnectionSection"
       allowLocation="true"
       allowDefinition="Everywhere"
     />
    </sectionGroup>
  </configSections>
 <serviceConnectionGroup>
    <serviceConnection type="MSMQ">
      <definition
        name="Some MSMQ Service"
        wsdlAddress="msmq://www.azon.com/someservicequeue/inbox" />
    </serviceConnection>
  </serviceConnectionGroup>
  <system.web>
      <compilation debug="true" targetFramework="4.5" />
      <httpRuntime targetFramework="4.5" />   
    </system.web>
</configuration>

Kural son derece basittir. serviceConnectionGroup, serviceConnection ve definition elementlerinin kullanılabilmesi için bir sectionGroup tanımlaması yapılması yeterlidir. Bu tanımlama içerisinde ki type kısmı ise ConfigurationSection veya ConfigurationSectionGroup türevli tipi işaret etmektedir.

Test Uygulaması

Şimdi dilerseniz basit bir aspx sayfası hazırlayıp section içeriğini ekrana bastıralım ve hatta üzerinde değişiklik yapıp web.config dosyasına kayıt edelim. Senaryonun bu kısmını gerçekleştirmek için aşağıdaki basit aspx sayfasını tasarlayabiliriz.

ccs_4

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="HowTo_WritingCustomConfigSection.Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
   
        <table>
            <tr>
                <td><strong>Service Type</strong></td>
                <td>
                    <asp:DropDownList ID="ddlServiceType" runat="server">
                    </asp:DropDownList>
                </td>
            </tr>
            <tr>
                <td><strong>Definition</strong> <strong>Name</strong></td>
                <td>
                    <asp:TextBox ID="txtName" runat="server" Width="220px"></asp:TextBox>
                </td>
            </tr>
            <tr>
                <td><strong>Definition WSDL</strong> <strong>Address</strong></td>
                <td>
                    <asp:TextBox ID="txtAddress" runat="server" Width="300px"></asp:TextBox>
&nbsp; </td>
            </tr>
            <tr>
                <td>&nbsp;</td>
                <td>
                    <asp:Button ID="btnSave" runat="server"
                        OnClick="btnSave_Click" Text="Save" Width="60px" />
                </td>
            </tr>
            </table>
   
    </div>
    </form>
</body>
</html>

Gelelim kod tarafına. Sayfa yüklenirken serviceSection içeriğini göstermek arzusundayız. Ayrıca Save düğmesine basıldığında, yaptığımız değişiklikleri kaydetmek ve web.config dosyasını güncellemek istiyoruz.

Senaryomuzda exceptional durumları göz ardı ettiğimizi ifade etmek isterim. Örneğin, boş değer geçilmesi, geçeriz bir url bildirimi yapılması vb. Ancak gerçek hayat senaryolarında bu tip veri doğrulama opsiyonlarını da işin içerisine katmanız önem arz etmektedir.

using System;
using System.Configuration;
using System.Web.Configuration;
using System.Web.UI;

namespace HowTo_WritingCustomConfigSection
{
    public partial class Default
        : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                ddlServiceType.DataSource = Enum.GetNames(typeof(ServiceType));
                ddlServiceType.DataBind();

                ServiceConnectionSection scs =
                    ConfigurationManager.GetSection("serviceConnectionGroup/serviceConnection")
                    as ServiceConnectionSection;

                if (scs != null)
                {
                    txtAddress.Text = scs.Definition.WsdlAddress;
                    txtName.Text = scs.Definition.Name;
                    ddlServiceType.SelectedValue = scs.Type.ToString();
                }
            }
        }

        protected void btnSave_Click(object sender, EventArgs e)
        {
            Configuration manager = WebConfigurationManager.OpenWebConfiguration("/");

            ServiceConnectionSection scs =
                manager.GetSection("serviceConnectionGroup/serviceConnection")
                as ServiceConnectionSection;
            if (scs != null)
            {
                scs.Type = (ServiceType)Enum.Parse(typeof(ServiceType),  ddlServiceType.SelectedValue.ToString());
                scs.Definition.Name = txtName.Text;
                scs.Definition.WsdlAddress = txtAddress.Text;

                manager.Save();
            }
        }
    }
}

serviceSection elementinin managed karşılığını elde edebilmek için Configuration veya ConfigurationManager tipinin GetSection metodundan yararlanılmaktadır. Söz konusu metodun dönüşü ServiceConnectionSection tipine dönüştürüldükten sonra ise Type ve Definition gibi özelliklere erişilebilinir. Hatta Definitionözelliği üzerinden Name ve WsdlAddress değerleri de yakalanabilir. Pek tabi Save işleminin gerçekleştirilebilmesi için WebConfigurationManager ile açılan web.config dosyasını işaret eden manager isimli Configuration tipinden yararlanılmaktadır.

Çalışma Zamanı Sonuçları

Artık çalışma zamanına geçebilir ve sonuçları irdeleyebiliriz. Uygulamayı ilk olarak başlattığımızda Page_Laod içerisindeki kodlar devreye girecektir.

ccs_5

Görüldüğü gibi varsayılan olarak belirtilen değerler çekilebilmiştir. Eğer bu noktada tip, ad ve adres bilgilerinde değişiklik yapıp Save düğmesine basılırsa, kod web.config dosyasında da gerekli etkiyi yapacaktır.

Tabi bu senaryoda çok basit bir section içeriği ele alınmış ve yönetilmiştir. Size tavsiyem connectionStrings gibi bir den fazla aynı tipden element içerebilen bir bölüm geliştirmeye çalışmanız olacaktır. Böyle bir senaryoda devreye ConfigurationElementCollection tipi girecektir. Bu örnek senaryoyu geliştirmeye çalışarak kendinizi bu yönde daha ileri bir noktaya taşıyabilirsiniz. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

HowTo_WritingCustomConfigSection.zip (25,98 kb)

TFİ 109 - IIS Üzerindeki Uygulamaları Kod Yoluyla Öğrenmek

$
0
0

Merhaba Arkadaşlar,

Diyelim ki sunucudaki IIS üzerinde konuşlandırdığınız Web uygulamalarının bir listesini almak istiyorsunuz. Bunun elbette pek çok yolu olduğunu biliyorsunuz. Bir Powershell script' i belki de işinizi görür. Ancak belki de siz bunu kendi geliştireceğiniz windows forms uygulamasında bu listeyi kullanmak istiyorsunuz. Ne yaparsınız? Kod yardımıyla IIS üzerindeki Application' ları, Site' ları öğrenebilir misiniz?

Aslında hep elinizin altında olan (Windows\System32\inetsrv\Microsoft.Web.Administration.dll) ve hatta isterseniz NuGet Package Manager ile de indirebileceğiniz Microsoft.Web.Administration kütüphanesini kullanarak bu işi gerçekleştirmeniz oldukça kolay. Nasıl mı? İşte böyle.

Başka neler mi yapabilirsiniz? Örneğin bir Application Pool' u Recylce edebilirsiniz. Ya da bir Web Site' ı Stop-Start. Hatta yeni bir Web Site bile açabilirsiniz. Araştırmaya değer değil mi?

Başka bir ipucunda görüşmek dileğiyle.

Asp.Net Temelleri : Derinlemesine Download/Upload İşlemleri

$
0
0

Değerli Okurlarım Merhabalar,

Tatile çıkan herkes, iyi ve dinlendirici geçen günlerin ardından tekrar hayatın akışına kapıldığında kısa süreliğinede olsa adaptasyon problemi yaşar. Tatildeyken hatırlayacağınız gibi hafif ve dinlendirici bir Asp.Net konusu ile ilgilenmeye çalışmıştık. Tatil dönüşündeki adaptasyon sürecinde de benzer nitelikte bir konuyu incelemenin uygun olacağı kanısındayım. Bu yazımızda Asp.Net uygulamalarında sıklıkla başvurduğumuz temel dosya giriş/çıkış (Input/Output -IO) işlemlerinden yararlanarak Download ve Upload işlemlerinin nasıl yapılabileceğini ele almaya çalışacağız.

Özellikle web tabanlı içerik yönetim sistemlerinde (Content Management System), kullanıcıların sunucu üzerinde dökümanlar ile etkin bir şekilde çalışabilmeleri sağlanmaktadır. Bu sistemlerde genel olarak kullanıcı kimliği veya rolüne göre istemci bilgisayarlara indirilebilen(Download). Hatta çoğu içerik yönetim sisteminde, istemciler herkesin okuyabileceği yada belirli kişilerin görebileceği şekilde sunucuya döküman aktarma (Upload) işlemleride yapabilirler. Söz gelimi bir yazılım şirketinin içerik yönetim sistemi göz önüne alındığında, yazılım departmanındaki geliştiricilerin hazırladıkları teknik dökümantasyonları Upload veya Download edebilecekleri bir ortam hazırlanabilir.

Hangi açıdan bakılırsa bakılsın, web tabanlı olarak yapılan bu işlemler için şirketler büyük ölçekli sistemler tasarlayıp geliştirmiştir. Fakat temel ilke ve yaklaşımlar benzerdir. Dosya indirme veya gönderme işlemleri, web tabanlı bir sistem göz önüne alındığında HTTP kurallarına bağlıdır. Dolayısıyla bu kuralların sadece uygulanma ve ele alınma biçimleri programlama ortamları arasında farklılıklar gösterebilir. İşte biz bu makalemizde, Asp.Net 2.0 tarafından olaya bakmaya çalışıyor olacağız. İlk olarak dosya indirme işlemlerini ele alacağız. Sonrasında ise Asp.Net 2.0 ile gelen FileUpload aracı yardımıyla sunucuya dosya gönderme(Upload) işlemlerinin nasıl yapılabileceğini inceleyeceğiz. Ek olarak, upload edilen bir dosyanın kaydedilmeden, sunucu belleği üzerinde canlandırılıp işlenmesinin nasıl gerçekleştirilebileceğine bakacağız. Son olarakta, Upload edilen dosyaların bir veritabanı tablosunda alan(Field) olarak saklanması için gereken adımları göreceğiz. Dilerseniz vakit kaybetmeden dosya indirme süreci ile işe başlayalım.

Dosya indirme (Download) işlemlerinde bilinen IO tiplerinden ve Response sınıfının ilgili metodlarından yararlanılır. Hatırlanacağı gibi herhangibir resim dosyasını bir web sayfası içerisinde göstermek için üretilen HTML içeriği ile oynamak gerektiğinden daha önceki makalemizde bahsetmiştik. Dosya indirme(Download) işlemindede içeriğin tipi(Content-Type), uzunluğu(Content-Length) gibi bilgiler önem kazanmaktadır. İlk örneğimizde, IIS üzerinde yayınlanan bir web projesindeki Dokumanlar isimli bir klasörde yer alan dosyaların indirilme işlemlerini gerçekleştirmeye çalışacağız. Bu amaçla web uygulamasına ait dokumanlar klasörü altına aşağıdaki şekildende görüldüğü üzere farklı formatlarda örnek dosyalar atılmasında fayda vardır.

Web uygulamamızın ilk amacı Dokumanlar klasöründeki dosyaların listelenmesini sağlamak olacak. Bu amaçla sayfada bir GridView kontrolü kullanılabilir. Hatta bu kontrolün içeriği FileInfo tipinden nesnelerden oluşan generic bir liste koleksiyonundan (List<FileInfo>) gelebilir. Böylece istemciler indirebilecekleri dosyalarıda görebilir. Download işleminin gerçekleştirilmesi için GridView kontrolünde bir Select Button' dan faydalanılabilir. İndirme işlemi sırasında indirilmek istenen dosyanın fiziki adresi, uzunluğu gibi bilgiler önemlidir. Bu bilgileri ve fazlasını FileInfo sınıfına ait bir nesne örneği yardımıyla elde edebiliriz. Uygulamamıza ait Default.aspx sayfasının içeriği aşağıdaki gibi olacaktır.

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.IO" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        string[] dosyalar =
Directory.GetFiles(Server.MapPath("Dokumanlar"));
       
List<FileInfo> dosyaBilgileri = new List<FileInfo>();

        foreach (string dosya in dosyalar)
        {
            dosyaBilgileri.Add(
new FileInfo(dosya));
        }
        grdDosyalar.
DataSource = dosyaBilgileri;
        grdDosyalar.
DataBind();
    }
}

protected void
grdDosyalar_SelectedIndexChanged(object sender, EventArgs e)
{
    string dosyaAdi =
Server.MapPath("dokumanlar") + "\\" + grdDosyalar.SelectedRow.Cells[0].Text;
   
FileInfo dosya = new FileInfo(dosyaAdi);

   
Response.Clear(); // Her ihtimale karşı Buffer' da kalmış herhangibir veri var ise bunu silmek için yapıyoruz.
   
Response.AddHeader("Content-Disposition","attachment; filename=" + dosyaAdi); // Bu şekilde tarayıcı penceresinden hangi dosyanın indirileceği belirtilir. Eğer belirtilmesse bulunulan sayfanın kendisi indirilir. Okunaklı bir formattada olmaz.
   
Response.AddHeader("Content-Length",dosya.Length.ToString()); // İndirilecek dosyanın uzunluğu bildirilir.
   
Response.ContentType = "application/octet-stream"; // İçerik tipi belirtilir. Buna göre dosyalar binary formatta indirilirler.
   
Response.WriteFile(dosyaAdi); // Dosya indirme işlemi başlar.
   
Response.End(); // Süreç bu noktada sonlandırılır. Bir başka deyişle bu satırdan sonraki satırlar işletilmez hatta global.asax dosyasında eğer yazılmışsa Application_EndRequest metodu çağırılır.
}
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Dosya Indirme Islemleri</title>
    </head>
<body>
    <form id="form1" runat="server">
        <div>
            <h2>Dosyalar</h2>
           
<asp:GridView ID="grdDosyalar" runat="server" AutoGenerateColumns="False" SelectedRowStyle-BackColor="Gold" OnSelectedIndexChanged="grdDosyalar_SelectedIndexChanged">
                <Columns>
                    <asp:
BoundField DataField="Name" HeaderText="Dosya Adı" />
                    <asp:BoundField DataField="
Length" HeaderText="Dosya Uzunluğu" />
                    <asp:BoundField DataField="
Extension" HeaderText="Uzantısı" />
                    <asp:BoundField DataField="
CreationTime" HeaderText="Oluşturulma Zamanı" DataFormatString="{0:dd.MMMM.yy}" HtmlEncode="False" />
                    <asp:BoundField DataField="
LastWriteTime" HeaderText="Son Yazılma Zamanı" DataFormatString="{0:dd.MMMM.yy}" HtmlEncode="False" />
                    <asp:
CommandField ButtonType="Button" SelectText="indir" ShowSelectButton="True" />
                </Columns>
           
</asp:GridView>
        </div>
    </form>
</body>
</html>

Öncelikli olarak sayfamızda neler yaptığımıza kısaca bakalım. Default.aspx sayfası ilk yüklendiğinde (bunu sağlamak için IsPostBacközelliği ile kontrol yapılmıştır) Dokumanlar klasöründeki dosyaların adları elde edilmektedir. Bu işlem için Directory sınıfının GetFiles metodu kullanılmaktadır. Bir web uygulaması söz konusu olduğu için, sanal klasörün karşılık geldiği fiziki adresi bulmak adına Server.MapPath metodu ele alınmaktadır. GetFiles metodu parametre olarak belirtilen klasördeki dosya isimlerinin elde edilmesini sağlamaktır. Bu nedenle geriye string tipinden bir dizi döndürür. GridView kontrolü içerisinde, elde edilen bu dosyalara ait bazı temel bilgilerin gösterilmesi hedeflenmiştir. Örnekte dosyanın adı(Name), uzunluğu(Length), uzantısı(Extension), oluşturulma(CreationTime) ve son yazılma zamanı(LastWriteTime) bilgileri ele alınmaktadır. Elde edilen dosya adları aslında fiziki adresleride içermektedir. Bu nedenle ilgili dosya adları FileInfo tipinden örneklerin oluşturulmasında kullanılır. Bu nesne örnekleride generic koleksiyonda toplanır. Son olarak GridView kontrolüne veri kaynağı olarak generic liste koleksiyonu atanır. FileInfo ile gelen bilgilerden bazılarını GridView kontrolünde göstermek istediğimizden, AutoGenerateColumnsözelliğine false değeri atanmış ve Columns elementi içerisinde ilgili alanlar açık bir şekilde yazılmıştır. BoundField elementlerine ait DataField niteliklerinin değerleri FileInfo ile gelen nesne örneklerindeki özellik adları olarak ayarlanmıştır.

İndirilmek istenen dosya için GridView kontrolüne bir adet CommandField elementi dahil edilmiştir. Burada seçme işlemi ele alınarak aslında küçük bir hile yapılmaktadır. GridView kontrolünde seçim düğmesine basıldıktan sonra devreye giren SelectedIndexChanged olayı içerisinde dosya indirme(Download) işlemi başlatılmaktadır. Teorik olarak Response sınıfının WriteFile metodu ile parametre olarak verilen dosya istemci bilgisayara indirilebilirmektedir. Ancak ön hazırlıklar yapılması gerekmektedir. Bu amaçla, indirilmek istenen dosya adı, GridView kontrolünde seçilen satıra ait ilk hücreden seçildikten sonra, Response sınıfının ilgili metodları ile ön hazırlıklar yapılır. İndirilecek dosya üretilen çıktının Header kısmında ele alınmaktadır. Benzer şekilde indirilecek dosyanın uzunluğuda Header kısmına eklenir. Daha sonra içerik tipi belirlenir. application/octet-stream değeri, dosyanın ikili(binary) formatta indirileceğini belirtmektedir. Bu işlemlerin arkasındanda Response sınıfının WriteFile ve End metodları sırasıyla çalıştırılır. End metodu, o anki sayfaya ait yaşam sürecinin kesilmesini sağlamaktadır. Bir başka deyişle Response.Endçağrısından sonra herhangibir kod satırı var ise işletilmeyecektir. Hatta, global.asax dosyasında yer alan Application_EndRequest metoduda devreye girecektir. Bu durumu analiz etmeden önceği örneğimizi test edelim. Uygulama çalıştırıldığında aşağıdakine benzer bir ekran görüntüsü ile karşılaşılacaktır.

Burada pek çok ek özellik tasarlanabilir. Örneğin uzantıya göre içerik tipi değiştirilebilir. Hatta download işlemi yerine örnek bir dökümanın sayfaya çıktı olarak verilmesi sağlanabilir. PDF içeriklerinin tarayıcıda gösterilmesi buna örnek olarak verilebilir. Bunların dışında uygulamanın kullanıcıya göre yetkilendirilmesi ve sadece ele alabileceği dosyaları indirebilmesi sağlanabilir. Bu tamamen projenin ihtiyaçlarına ve geliştiricinin kullanıcılara sunmak istediklerine bağlı olarak gelişebilecek bir modeldir. indir başlıklık düğmelerden herhangibirine bastığımızda indirme işleminin aşağıdaki ekran görüntüsünde yer aldığı gibi başladığı görülür.

Şimdi gelelim Response.End metodunun etkisine. Bu durumu analiz etmek için, Response.End sonrasına aşağıdaki gibi örnek bir kod satırı ekleyelim.

protected void grdDosyalar_SelectedIndexChanged(object sender, EventArgs e)
{
    string dosyaAdi = Server.MapPath("dokumanlar") + "\\" + grdDosyalar.SelectedRow.Cells[0].Text;
    FileInfo dosya = new FileInfo(dosyaAdi);

    Response.Clear();
    Response.AddHeader("Content-Disposition","attachment; filename=" + dosyaAdi);
    Response.AddHeader("Content-Length",dosya.Length.ToString());
    Response.ContentType = "application/octet-stream";
    Response.WriteFile(dosyaAdi);
   
Response.End();
   
Response.Write("Dosya indirildi");
}

Hemen arkasından bilinen yaşam döngüsünü izlemek adına default.aspx sayfasını aşağıdaki gibi değiştirelim.

protected void Page_PreInit(object sender, EventArgs e)
{
    Debug.WriteLine("Page_PreInit metodu");
}
protected void
Page_Init(object sender, EventArgs e)
{
    Debug.WriteLine("Page_Init metodu");
}
protected void
Page_Load(object sender, EventArgs e)
{
    // Diğer kod satırları
    Debug.WriteLine("Page_Load metodu");
}
protected void
Page_PreRender(object sender, EventArgs e)
{
    Debug.WriteLine("Page PreRender Metodu");
}
protected void
Page_Unload(object sender, EventArgs e)
{
    Debug.WriteLine("Page Unload Metodu");
}

protected void
grdDosyalar_SelectedIndexChanged(object sender, EventArgs e)
{
   
Debug.WriteLine("Dosya indirme işlemi başlıyor");
    // Diğer kod satırları
   
Response.End();
   
Debug.WriteLine("Response.End metodu çağırıldı");
   
Response.Write("Dosya indirildi");
}

Bu değişikliklere ek olarak projeye global.asax dosyası ekleyip içerisine Application_EndRequest metodunu aşağıdaki gibi dahil edelim.

void Application_EndRequest(object sender, EventArgs e)
{
   
Debug.WriteLine("Application EndRequest Metodu Çağırıldı");
}

Şimdi uygulamayı Debug modda çalıştırıp output penceresindeki çıktıları izleyebiliriz. Bir dosya indirme işlemi gerçekleştirildikten sonra sayfanın yaşam döngüsü aşağıdaki gibi çalışacaktır.

Page_PreInit metodu
Page_Init metodu
Page_Load metodu
Dosya indirme işlemi başlıyor
  
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll
   An exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll but was not handled in user code

Page Unload Metodu
Application EndRequest Metodu Çağırıldı

Çok doğal olarak GridView kontrolü üzerindeki düğmeye basıldığında sayfanın sunucuya tekrar gitmesi ve işlenmesi söz konusudur. Bu nedenle süreç Page_PreInit ile başlamaktadır. Ancak dikkat edilecek olursa Response.End çağrısından sonraki satırlar devreye girmemiştir. Debug penceresine ve tarayıcıdaki çıktıya herhangibir kod yazılmamıştır. Dahası, bir exception(System.Threading.ThreadAbortException) fırlatılmış ve sayfa yaşam döngüsü Page_PreRender metodunu işletmeden doğrudan Page_Unload olayını işletmiş ve arkasından global.asax dosyasındaki Application_EndRequest devreye girmiştir. Elbetteki üretilen istisna(exception) Asp.Net çalışma ortamı tarafından görmezden gelinmektedir. Bu nedenle istemci herhangibir şekilde hata mesajı ile karşılaşmaz.

Gelelim makalemizin ikinci konusuna. İndirme işlemleri kadar Upload işlemleride önemlidir. Tabi burada istemcilerin her dosya tipini veya çeşidini sunucuya göndermesi doğru olmayabilir. Kapalı ağ(intranet) sistemlerinde bu söz konusu olabilir. Nitekim kimin handi dosyayı Upload ettiğinin belirlenmesi dışında, bu kişiye ulaşılmasıda kolaydır :). Ancak internet tabanlı daha geniş sistemlerde her ne kadar kullanıcılar tespit edilebilsede, kötü niyetli istemcilerin varlığı nedeniyle sistemin genelini tehlikeye atmamak adına tedbirler almak doğru bir yaklaşım olacaktır. Biz tabiki basit olarak Upload işlemlerini ele alacağız ve bahsettiğimiz güvenlik konularını şimdilik görmezden geleceğiz.

Upload işlemlerini kolaylaştırmak adına Asp.Net 2.0, FileUpload isimli bir kontrol getirmektedir. Bu kontrol basit olarak istemcinin Upload etmek istediği dosyayı seçebilmesini sağlamaktadır. Bu seçim işlemi ile birlikte, sunucuya gönderilmek istenen dosyaya ait bir takım bilgilerde FileUpload kontrolünce elde edilir. Örneğin içeriğin tipi kontrol edilerek sadece bazı dosyaların gönderilmesine izin verilebilir. İlk olarak web uygulamamıza aşağıdaki gibi Default2.aspx sayfasını ekleyelim.

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

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected void btnGonder_Click(object sender, EventArgs e)
    {
        if (
uplDosya.HasFile)
        {
           
uplDosya.SaveAs(Server.MapPath("Dokumanlar") + "\\" + uplDosya.FileName);
        }
        else
            Response.Write("Upload edilecek dosya yok");
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Upload Islemleri</title>
    </head>
<body>
    <form id="form1" runat="server">
        <div>
            Dosyayı Seçin :
<asp:FileUpload ID="uplDosya" runat="server" />
            <br />
            <asp:Button ID="bntGonder" runat="server" Text="Gönder"
OnClick="btnGonder_Click" />
        </div>
    </form>
</body>
</html>

Öncelikli olarak neler yaptığımıza bir bakalım. FileUpload kontrolü ile dosya seçilmesi, gönderme işlemi için yeterli değildir. Sayfanın sunucuya doğru gönderilmesi gerekmektedir. Bu işi basit olarak bir Button kontrolü üstelenebilir. Button kontrolümüze ait Click olay metodunda ise öncelikli olarak seçili bir dosya olup olmadığı HasFileözelliği ile kontrol edilmektedir. Bu kontrol, dosya seçmeden gönderme işlemi yapıldığı takdirde oluşacak hataların önüne geçilmesini sağlamaktadır. Eğer seçili olan bir dosya var ise basit olarak FileUpload sınıfının SaveAs metodu çağırılır. SaveAs metoduna dosyanın yol adresinin fiziki olarak verilmesi gerekmektedir. Bu nedenle yine Server.MapPath ile Dokumanlar klasörünün fiziki adresi elde edilir. FileUpload kontrolünün FileNameözelliği ile seçilen dosyanın adı yakalanmakta ve fiziki adresin sonuna eklenmektedir. Eğer kod Debug edilirse, dosya seçildikten sonra Upload işlemi için düğmeye basıldığında FileUpload kontrolü üzerinde, seçilen dosyaya ilişkin çeşitli ek bilgilere ulaşılabildiği görülür.

Söz gelimi dosyanın tipi hakkında fikir elde etmek için ContentTypeözelliğine bakılabilir. Buna göre belirli dosya tiplerinin indirilmesine izin verilmesi istenen durumlara karşı tedbirler alınabilir. Gönderilecek dosyanın boyutu ile ilgili olarak bir kısıtlama getirilmek isteniyorsa içerik uzunluğu ContentLengthözelliği ile tedarik edilerek gerekli değişiklikler yapılabilir. Şimdi örneği deniyerek devam edelim. Basit olarak bir döküman dosyasını aşağıdaki gibi seçip Upload etmeyi deneyeceğiz.

Görüldüğü üzere Browse işleminden sonra otomatik olarak standart Choose File iletişim penceresi (Dialog Box) ile karşılaştık. Örnekte bir resim dosyası seçilmiştir. Seçim işleminden sonra Gönder düğmesine basılırsa dosyanın başarılı bir şekilde Dokumanlar klasörü altına yazıldığı görülecektir.

Upload işlemleri sırasında yetki problemi nedeni ile IIS altındaki herhangibir klasöre dosya yazma işlemi sırasında hata mesajı alınabilir. Bu durumda ASPNET kullanıcısına ilgili klasöre yazma hakkı verilmesi gerekebilir.

Upload işlemleri sırasında dikkat edilmesi gereken kritik bir sayı vardır. 4096 byte. Yani 4 megabyte. Boyutu bu değerin üzerindeki bir dosyayı Upload etmek istediğimizde Asp.Net ortamı bir hata üretir ve dosyanın sunucuya gönderilmesine izin vermez. Ne yazıkki hata üretimi kodların işletilmesinden önce gerçekleşir. Bu nedenle kullanıcıya anlamlı bir mesaj gösterilmeside pek mümkün olmamamaktadır. Eğer 4 megabyte üzerinde dosyaların upload edilebilmesini başka bir deyişle izin verilen limite kadar olan dosyaların gönderilebilmesini istiyorsak web.config dosyası içerisinde httpRuntime elementinin ayarlanması gerekmektedir. system.web boğumu içerisinde yer alan httpRuntime elementi sayesinde, http çalışma zamanına ait çeşitli ayarlamalar yapılabilmektedir. Bizim ihtiyacımız olan dosya büyüklüğü sınıfı ayarı için örneğimizde web.config dosyasını aşağıdaki gibi düzenlememiz yeterlidir.

<?xml version="1.0"?>
<configuration>
    <appSettings/>
    <connectionStrings/>
    <system.web>
       
<httpRuntime maxRequestLength="51200"/>
        <compilation debug="true"/>
        <authentication mode="Windows"/>
    </system.web>
</configuration>

Burada yapılan ayarlamaya göre istemciler 50 megabyte' a kadar dosyaları sunucuya gönderebilecektir. Upload işlemlerini içerik yönetim sistemlerinde resim, döküman veya örnek uygulamaların, programların gönderilmesinde, grafik kütüphanesi tarzındaki sistemlerde çeşitli formatta resim veya akıcı görüntü formatlarının gönderilmesinde ve buna benzer durumlarda kullanmak yaygındır.

Gelelim makalemizin üçüncü konusuna. Yine istemciden sunucuya doğru bir dosya gönderme işlemi gerçekleştirmeyi hedeflediğimizi düşünelim. Ancak bu sefer XML tabanlı bir dosyayı gönderiyor olacağız. Bu dosyanın özelliği, bizim istediğimiz şekilde tasarlanmış olması dışında, sunucu tarafında anında işlenecek olmasıdır. Söz gelimi, XML dosyası içerisinde dinamik olarak sayfaya yüklenmesi istenen kontrollere ait bilgiler yer alabilir. Bu durumda Upload edilen XML dosyasının sunucu tarafında işlenerek bir çıktı üretilmesi gerekmektedir. Bir başka deyişle istemciden sunucuya gönderilen sayfayı, fiziki olarak yazmadan işlemek ve kullanmak istediğimizi göz önüne alıyoruz. Peki bu sistemi nasıl yazabiliriz? İlk olarak istemcinin göndereceği basit XML dökümanını hazırlayarak başlayalım. Örnek olarak aşağıdaki gibi bir içerik düşünülebilir.

<?xml version="1.0" encoding="utf-8"?>
<Kontroller>
    <Kontrol tip="MetinKutusu" id="metinKutusu1"/>
    <Kontrol tip="Dugme" metin="Gonder" id="gonder1"/>
    <Kontrol tip="Label" metin="Ad" id="ad1"/>
</Kontroller>

Olayı basit bir şekilde ele almak adına Xml içeriğini mümkün olduğu kadar basit düşünmeye çalıştık. Elbetteki çok daha karmaşık ve daha çok parametre sunan bir Xml dökümanı söz konusu olabilir. Şimdi bu dosyayı nasıl ele alacağımıza bakalım. Dikkat edilmesi gereken noktalardan birisi, Upload edilecek dökümanın XML formatında olması gerekliliğidir. Bunu sağlamak için, içerik tipine(ContentType) bakmak gerekecektir. Sonrasında ise Upload edilen dosyanın Framework içerisinde yer alan XML tipleri yardımıyla ele alınması yeterlidir. Sonuç olarak Default3.aspx dosyamızın içeriği aşağıdaki gibi olacaktır.

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Xml" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected void btnGonder_Click(object sender, EventArgs e)
    {
        if (
uplKontroller.HasFile)
        {
           
HttpPostedFile dosya = uplKontroller.PostedFile;
            if (dosya
.ContentType == "text/xml")
            {
                XmlDocument doc = new XmlDocument();
                doc.
Load(dosya.InputStream);
               
XmlNodeList kontroller=doc.GetElementsByTagName("Kontrol");
                foreach (
XmlNode kontrol in kontroller)
                {
                    switch (
kontrol.Attributes["tip"].Value)
                    {
                       
case "Dugme":
                            Button btn = new Button();
                            btn.ID = kontrol.
Attributes["id"].Value;
                            btn.Text = kontrol.
Attributes["metin"].Value;
                           
phlKontroller.Controls.Add(btn);
                            break;
                       
case "MetinKutusu":
                            TextBox txt = new TextBox();
                            txt.ID = kontrol.Attributes["id"].Value;
                            phlKontroller.Controls.Add(txt);
                            break;
                       
case "Label":
                            Label lbl = new Label();
                            lbl.ID = kontrol.Attributes["id"].Value;
                            lbl.Text = kontrol.Attributes["metin"].Value;
                            phlKontroller.Controls.Add(lbl);
                            break;
                    }
                }
            }
            else
                Response.Write("Dosya içeriği XML olmalıdır");
        }
        else
            Response.Write("Dosya bulunamadı");
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Upload Edilen Dosyayi O Anda Islemek</title>
    </head>
<body>
    <form id="form1" runat="server">
        <div>
            Xml Dosayasını Seçin :
<asp:FileUpload ID="uplKontroller" runat="server" />
            <br />
            <asp:Button ID="btnGonder" Text="Gönder" runat="server"
OnClick="btnGonder_Click" />
            <br />
            Yüklenen Kontroller:
           
<asp:PlaceHolder ID="phlKontroller" runat="server" />
        </div>
    </form>
</body>
</html>

Dilerseniz kod içerisinde neler yaptığımıza kısaca bakalım. İlk olarak düğmeye basıldığında HasFile özelliği ile seçilen bir dosya olup olmadığını kontrol ediyoruz. Sonrasında ise FileUpload kontrolünün PostedFileözelliğinden yararlanıp seçilen dosyaya ait bazı temel bilgileri taşıyan HttpPostedFile tipine ait nesne örneğini elde ediyoruz. Bu nesne örneği üzerinden ContentTypeözelliğine geçerek içeriğin XML olup olmadığını text/xml eşleştirmesi ile kontrol ediyoruz. Sonrasında ise okunan dosya içeriğini XmlDocument nesnesine yüklüyoruz. XmlDocument nesne örneğine ait Load metodunun parametresi olarak bir Stream kullanılabildiğinden, HttpPostedFile nesne örneğinin InputStreamözelliğinden yararlanıyoruz. Son olarak yüklenen XML dökümanı içerisinde dolaşarak ilgili nitelikleri okuyor ve dinamik olarak oluşturulan kontrolleri, sayfadaki PlaceHolder kontrolünün Controls koleksiyonuna ekliyoruz.

İşlemlerin daha sağlıklı olması açısından yüklenen XML içeriğinin belirli kurallara uygun olup olmadığı bir şema dosyası (XSD olabilir örneğin) yardımıyla kontrol edilebilir. Söz gelimi gelen XML dökümanının yapısı, element adları, nitelik adları veya tipleri bu şema yardımıyla denetlenip, kontrollerin belirli standartlara göre okunabilmesi sağlanmış olunur. Şema kontrolünün nasıl yapılabileceğine dair daha önceki bir makalemizden yararlanabilirsiniz.

Uygulamayı çalıştırdığımızda ve istemci tarafından Kontrollerim.xml dosyasını yüklediğimizde aşağıdaki ekran görüntüsünde olduğu gibi kontrollerin başarılı bir şekilde üretilip sayfaya yüklendiğini görebiliriz.

Sıra geldi makalemizin son konusuna. İçerik yönetimi adına, istemcinin sunucuya gönderdiği dosyaların veritabanı üzerindeki bir tabloda tutulması istenebilir. Eğer ilişkisel veritabanı sistemi (Relational Database Management System) söz konusu ise, dosyaların tabloda tutuluyor olması taşıma işlemlerini kolaylaştırabileceği gibi, içerik güvenliğininde de daha etkin bir seviyede yapılabilmesini sağlayacaktır. (Tabi tersine saklanacak dosya boyutlarına göre veritabanı daha hızlı şişecektir.) Bu tarz bir ihtiyacın çıkış noktası son derece basittir. Her zaman olduğu gibi bir FileUpload kontrolü ile istemciye dosya seçtirilmeli daha sonra ilgili içerik sunucu tarafında işlenerek veritabanındaki ilgili tabloya yazdırılmalıdır. Burada çalıştıralacak komut dışında tablodaki alan tipide önemlidir. Text tabanlı bir içerik gönderilecek olsada, tablo tarafında image veya VarBinary tipinden alanlar tutmak Unicode tutarlılığı açısından daha doğru bir yaklaşım olacaktır. Dilerseniz bir örnek ile bu durumu incelemeye çalışalım. İlk olarak içeriği saklayacağımız basit bir tablo oluşturalım. Bunun için SQL Server 2005 üzerinde aşağıdaki gibi bir tablo göz önüne alınabilir.

Dosyalar isimli tabloda, sunucuya gönderilen dosya içeriğini saklamak için image tipinden bir alan kullanılmaktadır. Bunlara ek olarak dosyanın eklenme tarihi,içeriğin tipi ve dosya adı bilgileride yer almaktadır. Söz konusu alanlar dışında, web sitesinde kullanılan doğrulama(Authentication) sistemine göre, Upload işlemini yapan kullanıcının bilgilerinin saklanması hatta varsa Membership gibi kullanıcı tablo sistemleri ile ilişkilendirilmeside mümkün olabilir. Gelelim yükleme işlemini tabloya yazacak kodlarımıza.

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected void
btnGonder_Click(object sender, EventArgs e)
    {
        if (
uplDosya.HasFile)
        {
            string conStr =
ConfigurationManager.ConnectionStrings["ConStr"].ConnectionString;
            using (SqlConnection conn = new SqlConnection(conStr))
            {
                SqlCommand cmd = new SqlCommand("Insert into Dosyalar (EkleNmeTarihi,DosyaIcerigi,DosyaAdi,IcerikTipi) Values   
(@EklenmeTarihi,@DosyaIcerigi,@DosyaAdi,@IcerikTipi)", conn);
                cmd.Parameters.AddWithValue("@EklenmeTarihi", DateTime.Now);
               
cmd.Parameters.AddWithValue("@DosyaIcerigi", uplDosya.FileBytes);
                cmd.Parameters.AddWithValue("@DosyaAdi", uplDosya.
FileName);
                cmd.Parameters.AddWithValue("@IcerikTipi", uplDosya.
PostedFile.ContentType);
                conn.Open();
                int sonuc=cmd.
ExecuteNonQuery();
                Response.Write(sonuc + " dosya aktarıldı");
            }
        }
        else
            Response.Write("Dosya seçmelisiniz");
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Upload Edilen Dosyayı Veritabanına Yazma</title>
    </head>
<body>
    <form id="form1" runat="server">
        <div>
            Dosyayı Seçin :
<asp:FileUpload ID="uplDosya" runat="server" />
            <br />
            <asp:Button ID="btnGonder" Text="Gönder" runat="server"
OnClick="btnGonder_Click" />
        </div>
    </form>
</body>
</html>

Yine kodları kısaca incelemekte fayda var. Her zamanki gibi, istemcinin bir dosya seçtiğinden emin olduktan sonra gerekli işlemleri yapıyoruz. Burada önemli olan nokta çalıştırılacak SQL cümlesinde kullanılan parametrelerin değerlerinin nasıl verildiği. Dikkat edilecek olursa, image tipindeki alanın içeriğini verirken FileUpload kontrolünün FileBytesözelliğinden yararlanıyoruz. Burada dikkat edilmesi gereken noktalardan biriside çok büyük boyutlu dosyaların aktarılması sırasında yaşanabilecek timeout sorunudur. Böyle bir durumda kalındığı takdirde bağlantı için timeout sürelerinin arttırılması yoluna gidilebilir yada dosyanın parçalanarak sunucuya gönderilmesi ve burada o şekilde ele alınması sağlanabilir. Uygulama çeşitli tipteki dosyalar ile test edildiğinde başarılı bir şekilde çalıştığı görülecektir. Aşağıdaki ekran görüntüsünde bir kaç dosya tipinin upload edilmesi sonrasındaki durum vurgulanmaktadır.

Her dosya eklenme işleminden sonrada tarayıcı penceresindeki görüntü aşağıdaki gibi olacaktır.

Elbette Upload edilen içeriklerin, istemciler tarafından indirilmeside gerekecektir. Bu durumda tablo alanındaki dosya içeriğinin stream olarak yazdırılması gerekir. Tabi bunun için Response sınıfının WriteFile metodu yerine BinaryWrite metodunu tercih edeceğiz. (Alternatif bir yaklaşım olarak, tablodan okunan dosya içeriğinin bir temp dosyaya atılması ve oradanda WriteFile metodu ile yazdırılmasıda düşünülebilir) Nitekim dosya içerikleri tabloda binary olarak tutulmaktadır. Öyleyse son olarak bu işlemide nasıl yapabileceğimizi inceleyeceğimiz bir örnek sayfa daha ekleyelim. Default5.aspx sayfamızın içeriği aşağıdaki gibi olacaktır.

<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected void
GridView1_SelectedIndexChanged(object sender, EventArgs e)
    {
        using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConStr"].ConnectionString))
        {
            SqlCommand cmd=new SqlCommand("SELECT DosyaAdi,DosyaIcerigi,IcerikTipi FROM Dosyalar Where Id=@Id",conn);
            cmd.Parameters.AddWithValue(
"@Id", GridView1.SelectedValue);
            conn.Open();
            SqlDataReader reader=cmd.ExecuteReader();
            if (reader.Read())
            {
               
Response.Clear();
               
Response.AddHeader("Content-Disposition", "attachment; filename=" + reader.GetString(0));
               
byte[] dosyaIcerigi = reader.GetSqlBinary(1).Value;
                Response.AddHeader("Content-Length",
dosyaIcerigi.Length.ToString());
                Response.
ContentType = "application/octet-stream";
                Response.
BinaryWrite(dosyaIcerigi);
            }
          
 reader.Close();
        }
      
 Response.End();
    }

</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
<body>
    <form id="form1" runat="server">
        <div>
            Dosyalar<br />
            <br />
            <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataSourceID="dsDosyalar"
OnSelectedIndexChanged="GridView1_SelectedIndexChanged" DataKeyNames="Id">
                <Columns>
                    <asp:BoundField DataField="DosyaAdi" HeaderText="DosyaAdi" SortExpression="DosyaAdi" />
                    <asp:BoundField DataField="EklenmeTarihi" HeaderText="EklenmeTarihi" SortExpression="EklenmeTarihi" />
                    <asp:BoundField DataField="IcerikTipi" HeaderText="IcerikTipi" SortExpression="IcerikTipi" />
                    <asp:CommandField ButtonType="Button" SelectText="indir" ShowSelectButton="True" />
                </Columns>
            </asp:GridView>
           
<asp:SqlDataSource ID="dsDosyalar" runat="server" ConnectionString="<%$ ConnectionStrings:ConStr %>" SelectCommand="SELECT Id,DosyaAdi, EklenmeTarihi, IcerikTipi FROM Dosyalar">
           
</asp:SqlDataSource>
        </div>
    </form>
</body>
</html>

Tasarladığımız sayfayı dilerseniz inceleyelim. Basit olarak Dosyalar tablosunun içeriğini göstermek amacıyla SqlDataSource ve GridView kontrollerinden faydalanıyoruz. Yine ilk örneğimizde olduğu gibi indirme işlemini başlatmak adına küçük hilemizi yaptık ve bir Select düğmesi kullandık. Burada dosya içeriği tabloda alan olarak tutulduğu için, SqlCommand ile verinin çekilmesi gerekiyor. Bunu kolaylaştırmak adına GridView içerisinde seçilen satıra ait Id alanının değerini almalıyız. Bu amaçla GridView kontrolünün DataKeyNamesözelliğine Id değerini verdik. Bu değeri SelectedIndexChanged metodu içerisinden alarak sorgu cümlesinde parametre olarak kullanıyor ve böylece indirilmek istenen dosyaya ait içeriğin olduğu tablo satırını çekebiliyoruz. Bizim için önemli olan nokta, binary içeriği okumak için SqlDataReader sınıfının GetSqlBinary metodunu kullanıyor olmamız. Bu metod ile dönen tipin Valueözelliğinden faydalanıp elde edilen byte[] dizisini Response sınıfının BinaryWrite metoduna parametre olarak verdiğimizde yazma işlemi gerçekleştirilmiş oluyor. Sonuç olarak çalışma zamanında istediğimiz sonuca ulaşıyor ve dosya indirme işlemlerini gerçekleştirebiliyoruz.

Bu makalemizde Asp.Net uygulamalarında Download ve Upload işlemlerini detayları ile incelemeye çalıştık. İlk olarak bir dosyanın indirilme işleminin nasıl yapılabileceğine baktık. Sonrasında ise basit olarak bir Upload işlemi ile sunucuya dosya gönderme olayını ele aldık. Upload işleminin farklı yönlerini ele almak adına, anında sunucu tarafında işleme ve tabloya satır olarak ekleme işlemlerini inceledikten sonra, tablodaki bir binary içeriği indirme sürecine göz attık. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

Örnek Uygulama için Tıklayın(Dosyanın çok yer tutmaması açısından mdf dosyası çıkartılmıştır)

Burak Selim ŞENYURT
selim(at)buraksenyurt.com


Asp.Net Routing – Hatırlamak

$
0
0

obliviousMerhaba Arkadaşlar,

Geçtiğimiz günlerde şirkette çok küçük bir web uygulamasına ihtiyaç duyuldu. Neredeyse bir günlük geliştirme maliyeti olan, küçük bir departmanın önemli bir gereksinimi karşılayacaktı. Tabi insan uzun zaman kodlama yapmayınca veya kodlamaya ara verince bazı temel bilgileri de unutabiliyor.

Ben de kafayı Team Foundation Server entegrasyonu, SOA mimarisi ve Scrum gibi metodolojiler ile bozunca, zihinsel diskimdeki ana partition’ a yeni bilgilerin yazıldığına ve eskilerinin yerinde yeller estiğine şahit oldum. Ama malum, günümüz teknolojilerinde bilginin tamamını ezberlemeye çalışmak yerine, en doğrusuna en hızlı şekilde nasıl ulaşabileceğimizi bilmek daha önemli. İşte bu felsefeden yola çıkıp dedim ki, şu Asp.Net Routing konusunu bir hatırlayayım ve hatta kayıt altına alayım. İşte hikayemiz böyle başladı Smile

Asp.Net MVC’ nin en cazip yanlarından birisi sanırım sağladığı URL eşleştirme(Routing) sistemidir. Özellikle Search Engine Optimization(SEO) kriterleri göz önüne alındığında, orders.aspx?categoryName=Beverages&shipCity=istanbul&orderNumber=12903 gibi bir ifade yerine, orders/beverages/istanbul/12903 şeklinde bir URLçok daha değerlidir.

Bilindiği üzere Asp.Net 4.0 sürümü ile birlikte, URL ve asıl kaynak(aslında yönlendirme sonucu gidilmesi gereken bir aspx sayfa kodu düşünebiliriz) eşleştirmelerinde kullanılan yönlendirme işlemleri oldukça kolaylaştırılmıştır. İşte bu yazımızda, biraz temelleri hatırlamaya çalışacak ve SEO’cu arama motorlarının olmassa olmaz isterlerinden birisi olan Routing konusunu dört basit örnek üzerinden inceleyeceğiz. İlk olarak senaryomuza bir göz atalım.

Senaryo

Senaryomuzda veri kaynağı olarak emektar Northwind veritabanını kullanacağız. Örneklerimizde hem Entity Framework kullanacağımız hem de doğrudan SQL sorgusu çalıştıracağımız bir vakamız yer alacak. Temel olarak amacımız aşağıdaki ekran görüntüsünde yer alan URL eşleştirmelerini web uygulaması üzerinden işlettirmek.

route_2

Dikkat edileceği üzere URL satırından girilecek olan anlamlı ifadeler, aslında arka planda bir eşleştirme tablosuna uygun olacak şekilde ilgili kaynaklara yönlendirilmekteler. Örneğin beverages isimli kategoride yer alan ürünlerin listelenmesi için yazılan urunler/beverages sorgusu, sisteme daha önceden öğretilen Urunler/{CategoryName}üzerinden geçerek urun.aspx sayfasına yönlendiriliyor. Çok doğal olarak ilgili sayfa içerisinde, CategoryName değerine bakılarak bir sonuç kümesinin sunulması gerekiyor.

Route Eşleştirmelerinin Ayarlanması

Bu işlem için global.asax.cs dosyasında aşağıdaki kodlamaları yapmamız gerekmektedir.

using System;
using System.Web;
using System.Web.Routing;

namespace HowTo_EasyRouting
{
    public class Global
        : HttpApplication
    {
        private void SetRouteMaps()
        {
            RouteTable.Routes.MapPageRoute("Varsayilan", "", "~/kategori.aspx");
            RouteTable.Routes.MapPageRoute("Kategoriler", "kategoriler", "~/kategori.aspx");
            RouteTable.Routes.MapPageRoute("KategoriBazliUrunler","urunler/{CategoryName}","~/urun.aspx");
            RouteTable.Routes.MapPageRoute("SehirBazliSiraliMusteriler", "musteriler/{City}$orderby={FieldName}", "~/musteri.aspx");
            RouteTable.Routes.MapPageRoute("SehirBazliSiparisler", "siparisler/{ShipCity}", "~/siparis.aspx");
        }

        protected void Application_Start(object sender, EventArgs e)
        {
            SetRouteMaps();
        }
    }
}

Application_Start olay metodu bilindiği üzere, Web uygulaması ayağa kalktığında devreye girmektedir. Dolayısıyla uygulamanın başladığı bir yerde, URL eşleştirme tanımlamalarını yapmak son derece mantıklıdır. Olayın ana kahramanı RouteTable sınıfıdır. Söz konusu tipin static olarak erişilebilen Routesözelliği bir RouteCollection referansını işaret etmektedir. Bu koleksiyon tahmin edileceği üzere URL ile asıl kaynak eşleştirmelerini taşımaktadır. Bu nedenle MapPageRoute metodundan da yararlanılarak gerekli eşleştirme bilgileri koleksiyona eklenir.

İlk satır ile Root URL adresine gelen bir talebin doğrudan kategori.aspx sayfasına yönlendirilmesi gerektiği ifade edilmektedir. İkinci satırda ise web kök adresini takiben kategoriler şeklinde gelen bir ifadenin gelmesi halinde yine, kategori.aspx sayfasına gidilmesi gerektiği belirtilmektedir.

KategoriBazliUrunler ismi ile tanımlanmış eşleştirmeye göre, urunler/{CategoryName}şeklinde gelen talepler urun.aspx sayfasına yönlendirilmektedir. İlginç kullanımlardan birisi de SehirBazliSiraliMusteriler isimli eşleştirmedir. Burada City ve FieldName isimli iki Route parametresi söz konusudur. İfade ise size sanıyorum tanıdık gelecektir. Neredeyse bir REST servis sorgusuna(örneğin OData sorgusuna) oldukça yakın değil mi? Nerd smile

Şimdi bu durumları kod tarafında nasıl karşılayacağımızı örnek bir Asp.Net uygulaması üzerinden incelemeye çalışalım.

Birinci Durum

İlk olarak kategori.aspx sayfasına doğru yapılacak yönlendirmeleri ele almaya çalışacağız. Bunun için web uygulamamıza kategori.aspx isimli bir sayfa ekleyip içeriği ile kod tarafını aşağıdaki gibi geliştirelim.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Kategori.aspx.cs" Inherits="HowTo_EasyRouting.Kategori" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div id="divCategories" runat="server" style="background-color:lightcyan">
       
    </div>
    </form>
</body>
</html>

kod tarafı

using System;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace HowTo_EasyRouting
{
    public partial class Kategori
        : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                using (NorthwindEntities context = new NorthwindEntities())
                {
                    var categories = from c in context.Categories
                                     orderby c.CategoryName
                                     select new
                                     {
                                         c.CategoryID,
                                         c.CategoryName
                                     };

                    foreach (var category in categories)
                    {
                        HyperLink categoryLink = new HyperLink();
                        categoryLink.NavigateUrl = GetRouteUrl("KategoriBazliUrunler", new { CategoryName = category.CategoryName });
                        categoryLink.Text = string.Format("[{0}]-{1}<br/>", category.CategoryID.ToString(), category.CategoryName);
                        divCategories.Controls.Add(categoryLink);
                    }
                }
            }
        }
    }
}

Aslında kategori.aspx sayfasında tipik olarak Entity Framework odaklı bir sorgulama gerçekleştirilmekte ve kategori adları birer HyperLink bileşeni olarak div içerisine eklenmektedir. Konu itibariyle işin önemli olan kısmı ise HyperLink bileşeninin NavigateUrlözelliğine GetRouteUrl metodu sonucunun atanmasıdır.

GetRouteUrl metodu dikkat edileceği üzere iki parametre alır. İlk parametre route adıdır. Yazdığımız değere göre urunler/{CategoryName}şeklindeki atama değerlendirilir. İkinci parametre ise bu Route içerisinden kullanılmak istenen değişken adı ve değerini içeren nesnenin örneklendiği kısımdır. CategoryName tahmin edileceği üzere Route tanımı içerisindeki parametre adıdır. Değeri ise zaten LINQ(Language INtegrated Query) sorgusu içerisinden elde edilmektedir. İkinci parametre object tipinden olduğundan bir isimsiz tip(anonymous type) ataması yapılabilmiştir. Bu nedenle Route içerisinde birden fazla parametre olması halinde, isimsiz tipin de birden fazla özellik içermesi gerektiğini ifade edebiliriz.

İlk durumda herhangibir sayfa talep edilmediğinde veya kök web adresi ardından /kategorilerşeklinde bir URL ifadesi kullanıldığında, aşağıdaki ekran görüntüsünde yer alan sonuçlar ile karşılaşırız.

route_3

Dikkat edilmesi gereken en önemli nokta, her hangi bir bağlantı üstüne gelindiğinde oluşan sorgu adresidir. Örneğin Condiments için http://localhost:54605/urunler/condimentsşeklinde bir URL tanımı oluşmuştur. Peki bu bağlantıya tıklanırsak ne olur? Who me?

İkinci Durum

kategori.aspx sayfasında bir bağlantıya tıklandığında, HyperLink bileşeninin NavigateUrlözelliğinin sahip olduğu değerin Route tablosundaki eşleniğine bakılmalıdır. Yaptığımız tanımlamalara göre urun.aspx sayfasına gidilmesi beklenmelidir(KategoriBazliUrunler isimli Route tanımına dikkat edin) Buna göre urun.aspx sayfasının içeriğini aşağıdaki gibi düzenleyebiliriz.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Urun.aspx.cs" Inherits="HowTo_EasyRouting.Urun" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1 style="color:purple">
         <asp:Label ID="lblCategoryName" runat="server" /></h1>
        <br />
        <asp:GridView ID="grdUrunler" runat="server" />
    </div>
    </form>
</body>
</html>

kod tarafı

using System;
using System.Linq;
using System.Web.UI;

namespace HowTo_EasyRouting
{
    public partial class Urun
        : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (RouteData.Values["CategoryName"] != null)
            {
                string categoryName = RouteData.Values["CategoryName"].ToString();
                using (NorthwindEntities context = new NorthwindEntities())
                {
                    var products = from p in context.Products.Include("Product")
                                   where p.Category.CategoryName == categoryName
                                   orderby p.ProductName
                                   select new
                                   {
                                       p.ProductID,
                                       p.ProductName,
                                       p.UnitPrice
                                   };

                    grdUrunler.DataSource = products.ToList();
                    grdUrunler.DataBind();
                }
            }
            else
                Response.Redirect(GetRouteUrl("Kategoriler", null));
        }
    }
}

Tabi bu sayfaya gelindiğinde aslında Route tanımlaması içerisinde yer alan parametre değerinin okunması gerekmektedir. Bu sebepten sayfanın RouteDataözelliğinden hareket edilerek RouteValueDictionary tipinden olan Valuesözelliğine gidilir ve indeksleyiciye verilen CategoryName alanının var olup olmadığına bakılır. Malum urun.aspx sayfasına farklı bir şekilde erişilmek istenebilir ve CategoryName değeri null olarak gelebilir. Bu nedenle bir null değer kontrolü ardından Entity sorgulama işlemi yapılmıştır. RouteData.Values[“CategoryName”] ile URL satırındaki kategori adı bilgisi alındıktan sonra standart olarak bir Entity sorgusu icra edilmektedir. Eğer kategori adı null olarak gelirse bu durumda varsayılan URL eşleştirilmesi nedeniyle kategorilerin gösterildiği sayfaya gidilir.

route_4

Üçüncü Durum

URL eşleştirmelerinden SehirBazliSiraliMusteriler isimli olanı, iki adet route parametresi içermektedir. Burada başta da belirttiğimiz üzere OData sorgularına benzer bir ifade tanımlanmıştır. Eşleştirme bilgisine göre musteri.aspx sayfasına doğru bir yönlendirme söz konusudur. musteri.aspx içeriğini aşağıdaki gibi geliştirdiğimizi düşünelim.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Musteri.aspx.cs" Inherits="HowTo_EasyRouting.Musteri" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1 style="color:purple">Customers</h1>
        <asp:GridView ID="grdCustomer" runat="server" />
    </div>
    </form>
</body>
</html>

kod tarafı

using System;
using System.Linq;
using System.Reflection;
using System.Web.Routing;

namespace HowTo_EasyRouting
{
    public partial class Musteri : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
           if(RouteData.Values["City"]!=null
                || RouteData.Values["FieldName"]!=null)
            {
                string cityName=RouteData.Values["City"].ToString();
                string fieldName=RouteData.Values["FieldName"].ToString();
               
                using(NorthwindEntities context=new NorthwindEntities())
                {
                    var customers = context
                        .Customers
                        .Where(c => c.City == cityName)
                        .OrderBy(GetField<Customer>(fieldName))
                        .Select(c => new
                        {
                            ID=c.CustomerID,
                            Title=c.ContactTitle,                         
                            Contact=c.ContactName,
                            Company=c.CompanyName,
                            c.City
                        });

                    grdCustomer.DataSource = customers.ToList();
                    grdCustomer.DataBind();
                }
            }
        }

        publicstatic Func<T, string> GetField<T>(string fieldName)
        {
            PropertyInfo pInfo=typeof(T).GetProperty(fieldName);
            if (pInfo == null)
                pInfo = typeof(T).GetProperty("CustomerID");

            return o => Convert.ToString(pInfo.GetValue(o, null));        
        }
    }
}

RouteData.Valuesözelliğinden yararlanılarak CityName ve FieldName değerlerinin null olup olmamasına göre bir kod parçası çalıştırılmaktadır. Bir önceki örnekten farklı bir durum olmasa da Entity sorgusunda OrderByextension metodunu nasıl kullandığımıza dikkat etmenizi rica ederim. İşte bu vakaya ait örnek ekran çıktıları.

Londra’ daki müşterilerin CustomerId bilgilerini göre sıralı olarak çekilmesi

route_5

Londra’ daki müşterilerin ContactName bilgisine göre sıralı olarak çekilmesi

route_6

Dördüncü Durum

Son vakada bir Route parametrenin her hangi bir veri bağlı kontrol ile nasıl ilişkilendirilebileceğini görmeye çalışacağız. Örneğin bir SqlDataSource bileşenindeki Select sorgusuna ait Where koşullarını Route parametreler ile ilişkilendirebiliriz. Bu durumu siparis.aspx sayfası içerisinde ele almaya çalışalım. Siparis sayfasına gelinebilmesi için Route tablo tanımlamalarına göre /siparisler/{ShipCity}şeklinde bir URL talebinin gönderilmesi gerekmektedir.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Siparis.aspx.cs" Inherits="HowTo_EasyRouting.Siparis" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1 style="color:purple">Siparişler</h1>
        <asp:GridView ID="grdOrders" runat="server" AllowPaging="True" AutoGenerateColumns="False" DataKeyNames="OrderID" DataSourceID="SqlDataSource1" >
            <Columns>
                <asp:BoundField DataField="OrderID" HeaderText="OrderID" InsertVisible="False" ReadOnly="True" SortExpression="OrderID" />
                <asp:BoundField DataField="CustomerID" HeaderText="CustomerID" SortExpression="CustomerID" />
                <asp:BoundField DataField="EmployeeID" HeaderText="EmployeeID" SortExpression="EmployeeID" />
                <asp:BoundField DataField="ShippedDate" HeaderText="ShippedDate" SortExpression="ShippedDate" />
                <asp:BoundField DataField="ShipCity" HeaderText="ShipCity" SortExpression="ShipCity" />
            </Columns>
        </asp:GridView>
        <asp:SqlDataSource ID="SqlDataSource1" runat="server"
            ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
            SelectCommand="SELECT [OrderID], [CustomerID], [EmployeeID], [ShippedDate], [ShipCity] FROM [Orders] WHERE ([ShipCity] = @ShipCity)">
            <SelectParameters>
                <asp:RouteParameter DefaultValue="" Name="ShipCity" RouteKey="ShipCity" Type="String" />
            </SelectParameters>
        </asp:SqlDataSource>
    </div>
    </form>
</body>
</html>

Önemli olan, SqlDataSource bileşenine ait SelectCommand ifadesindeki where koşulunda yer alan ShipCity isimli parametrenin bir RouteParameter ile ilişkilendirilmiş olmasıdır. RouteParameter bileşenine ait RouteKeyözelliği, Route Table’ daki ile aynı olmalıdır. Çok doğal olarak aspx kaynak tarafında yapılabilen bu eşleştirme, Wizardüzerinden de kolayca belirlenebilir. Aynen aşağıdaki ekran görüntüsünde olduğu gibi Winking smile

route_1

Buna göre örneğin Stuttgart’ a yapılan sevkiyatları aşağıdaki gibi elde edebiliriz.

route_7

Sonuç

Görüldüğü üzere URL eşleştirme işlemleri klasik sunucu tabanlı Asp.Net uygulamalarında da etkili bir şekilde kullanılabilir. Hatta bu felsefeden yola çıkarak OData sorgularının daha fazla gelişmişlerini destekleyecek web uygulamaları yazılması da pekala olasıdır. Yazımıza konu olan basit örneklerimizde ki anahtar noktalar, RouteTable sınıfı, RouteData.Valuesözelliği ve GetRouteUrl metodudur. Örneği geliştirmek tamamen sizin elinizde. İşe /siparisler/stuttgart/10301/şeklinde bir sorguyu ele alıp, 10301 numaralı siparişe ait detay bilgileri göstereceğiniz bir sayfayı üretmeyi deneyerek başlayabilirsiniz. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

HowTo_EasyRouting.zip (605,23 kb)

Asp.Net Temelleri–Eğlenceli GridView

$
0
0

Merhaba Arkadaşlar,

Bir süre önce çalıştığım firmada ufak bir web uygulaması ihtiyacı ile karşılaştım. Bir kaç günlük efor isteyen bu basit işte, uzun süredir kullanmadığım eski dostumuz GridView kontrolü ile haşır neşir oldum. Onun bir kaç etkili özelliğini ve işe yarar ip uçlarını tekrardan hatırladım. Hal böyle olunca bir araya getirip basit bir video anlatımında birleştirmek istedim. Ele aldığımız konulara gelince.

  • Hiç veri olmadığında GridView.
  • Alan içeriklerini formatlamak.
  • N sayıda alan kullanan Link vermek.
  • Belirli koşullarda satır bazında makyaj değiştirmek.
  • Null alanlar için hücrede imaj göstermek.
  • Tüm hücre içeriklerini aynı taraftan hizalamak.

Afiyetle izleyin. Eski günlerimizi yaad edelim.

Asp.Net–Custom Configuration Section Geliştirmek

$
0
0

ConfigurationMerhaba Arkadaşlar,

Konfigurasyon tabanlı geliştirme modeli, uygulama kodlarına girilmeden çalışma zamanına yönelik değişiklikler yapabilmemizi sağlar. Bu sayede pek çok programın kodsal müdahale yapmadan davranışları değiştirilebilir. .Net dünyasında baktığımızda da, App.Config, Web.config gibi dosyalar içerisinde Framework’ün geneline yönelik pek çok konfigurasyon ayarı bulunduğu görülür. appSettings, connectionStrings, httpHandler vb…

Eskilerden : Asp.Net 2.0 ile Configuration Management (Konfigurasyon Yönetimi)

Söz konusu konfigurasyon içerikleri aslında XML tabanlı bir dosya şemasının parçalarıdır ve doğal olarak element ile attribute’ lardan oluşmaktadır. Konfigurasyon dosyalarının daha iyi yönetilebilmesi için Asp.Net 2.0 ile birlikte Configuration API alt yapısı geliştirilmiştir. Bu kütüphane sayesinde konfigurasyon içerisindeki elementleresınıfbazında erişmek ve yönetebilmek mümkündür. Pek tabi XML elementlerinin sahip oldukları nitelikler, sınıfların özellikleri(Property) olarak ele alınmaktadır.

Peki konfigurasyon dosyası içerisine kendi özel kısımlarımızı(section) ilave etmek ve hatta bunları çalışma zamanında(Runtime) kullanmak istersek, nasıl bir yol izlememiz gerekir? Thinking smile 

Konfigurasyon Yapısı

Aklımıza ilk gelen belirli tip türetmeleri veya arayüz(interface) uyarlamaları ile bu işin halledilebilecek olmasıdır. Aslında olayı çözümlemek için var olan konfigurasyon parçalarının örnek tip yapısını incelemek yerinde bir hareket olacaktır. Söz gelimi system.web kısımı içerisindeki compilation ve pages elementlerini incelediğimizi düşünelim.

ccs_1

Eğer objectbrowserüzerinden ilgili elementlerin karşılık geldiği sınıfları incelersek şu sonuçlara varırız.

  • system.web elementi SystemWebSectionGroup isimli bir sınıf ile işaret edilmekte olup ConfigurationSectionGroup türevlidir.
  • compilation elementi system.web in alt elementidir ve CompilationSection sınıfı ile işaret edilmektedir. Bu sınıf ise ConfigurationSection’ dan türemiştir.
  • Benzer şekilde pages elementi de ConfigurationSection türevli PageSection sınıfı ile temsil edilmektedir.
  • namespaces elementine baktığımızda aynı tipten birden fazla elementi içerecek şekilde kullanılabildiği görülmektedir. Bunun için NamespaceColletion sınıfı ConfigurationElementCollection türevli olarak tasarlanmıştır.
  • namespaces segmenti içerisinde yer alan add elementleri aslında NamespaceInfo tipini işaret etmekte olup yine ConfigurationElement türevlidir.
  • alt elementler genellikle üst elementlerin birer özelliği(sınıf bazında düşünüldüğünde) olarak karşımıza çıkmaktadır.

Dolayısıyla kendi geliştireceğimiz özel Section elementleri için de bu tip bir yol izlememiz ve ilgili türetmeleri yapmamız yeterli olacaktır.

Örnek Senaryo

Basit bir senaryo üzerinden ilerleyebiliriz. Örneğin bir web uygulamasının web.config dosyası içerisinde aşağıdaki gibi bir konfigurasyon kısmı oluşturmak istediğimizi düşünelim.

ccs_3

serviceConnection ve altında yer alan definition elementlerinin işlevselliği çok önemli değildir aslında. Sadece bir kaç nitelik ve alt element içeren bir XML yapısı söz konusu. Bizim yapacağımız basit olarak bu konfigurasyon içeriğini türlendirmek ve çalışma zamanında yönetebilir hale getirmek. Öyleyse işe koyulalım ne duruyoruz.

Sınıfların İnşa Edilmesi

Boş bir Web uygulamasında aşağıdaki sınıf çizelgesinde yer alan tipleri ürettiğimizi düşünelim.

ccs_2

ServiceConnectionSection tipi dikkat edileceği üzere ConfigurationSection türevlidir ve içerisinde Type isimli ServiceTypeenum sabiti tipinden bir özellik ile ConfigurationElement türevli olan DefinitionSection tipinden başka bir özellik yer almaktadır.

ServiceConnectionSection sınıfı;

using System;
using System.Configuration;

namespace HowTo_WritingCustomConfigSection
{
    public class ServiceConnectionSection
       :ConfigurationSection
    {
        // Save işlemine izin vermesi için false döndürecek şekilde ezdik
        public override bool IsReadOnly()
        {
            return false;
        }

        [ConfigurationProperty("type", DefaultValue = ServiceType.WCF, IsRequired = false)]
        public ServiceType Type
        {
            get
            {
                return (ServiceType)Enum.Parse(typeof(ServiceType), this["type"].ToString());
            }
            set
            {
                this["type"] = value.ToString();
            }
        }

        [ConfigurationProperty("definition",IsRequired=true)]
        public DefinitionSection Definition
        {
            get
            {
                return (DefinitionSection)this["definition"];
            }
            set
            {
                this["definition"]=value;
            }
        }
    }
}

Konfigurasyon içeriğinde çalışma zamanında da değişiklik yapılması mümkündür ama konfigurasyon yöneticisinin Save metoduna tepki verebilmesi için IsReadOnly özelliğinin ezilmesi(override) ve false döndürmesi gerekmektedir.

Dikkat edileceği üzere Definition ve Type isimli özelliklere ConfigurationProperty niteliği(Attribute) uygulanmıştır. Bu niteliğe ait özelliklerden yararlanılarak element adı(konfigurasyon dosyasında görünecek olan isim) ve gereklilik(IsRequired) gibi değerler belirtilebilir.

Özelliklerin get ve set bloklarında fark edileceği gibi this anahtar kelimesinden yararlanılmakta ve üst tipin indeksleyicisine(Indexer) gidilerek değer ataması veya okunması işlemi gerçekleştirilmektedir.

F12 ile ConfigurationSection elementine gidiliğinde bu indexleyici görülebilir.

ccs_6

DefinitionSection sınıfı;

using System.Configuration;

namespace HowTo_WritingCustomConfigSection
{
    public class DefinitionSection
        :ConfigurationElement
    {
        // Save işlemine izin vermesi için false döndürecek şekilde ezdik
        public override bool IsReadOnly()
        {
            return false;
        }

        [ConfigurationProperty("name", IsRequired = true)]
        [StringValidator(InvalidCharacters = "~!@#$%^&*()[]{}/;'\"|\\,çşöğüı")]
        public string Name
        {
            get
            {
                return this["name"].ToString();
            }
            set
            {
                this["name"] = value;
            }
        }

        [ConfigurationProperty("wsdlAddress", IsRequired = true)]
        public string WsdlAddress
        {
            get
            {
                return this["wsdlAddress"].ToString();
            }
            set
            {
                this["wsdlAddress"] = value;
            }
        }
    }
}

DefinitionSection sınıfı aslında bir alt elementtir ve bu sebepten ConfigurationElement sınıfından türetilmiştir. Save operasyonuna cevap verebilmesi için IsReadOnlyözelliği ezilmiştir. Nameözelliğinde ConfigurationProperty dışında StringValidator niteliği de kullanılmış ve kullanılması istenmeyen bir karakter seti belirtilmiştir.

Web.config Bildirimleri

Artık konfigurasyon dosyası içerisinde kullanacağımız serviceConnection section için gerekli tip desteğine sahip bulunmaktayız. Peki web.config dosyası içerisinde bu bildirimleri nasıl gerçekleştirebiliriz?

Bazen 3ncü parti araçları sisteme dahil ettiğimizde, konfigurasyon dosyası içerisine koyacakları elementler için ekstra bildirimler eklediklerine şahit olmuşuzdur. Yani bir şekilde çalışma zamanına, “izleyen config içeriğinde şu tipe ait elementler kullanılabilir” denilebilmelidir. Bunun için configSections elementi içerisinde sectionGroup ve section tanımlamalarını yapmamız yeterli olacaktır. Aşağıda görüldüğü gibi.

<?xml version="1.0"?>
<configuration>
  <configSections>
    <sectionGroup name="serviceConnectionGroup">
    <section
       name="serviceConnection"
       type="HowTo_WritingCustomConfigSection.ServiceConnectionSection"
       allowLocation="true"
       allowDefinition="Everywhere"
     />
    </sectionGroup>
  </configSections>
 <serviceConnectionGroup>
    <serviceConnection type="MSMQ">
      <definition
        name="Some MSMQ Service"
        wsdlAddress="msmq://www.azon.com/someservicequeue/inbox" />
    </serviceConnection>
  </serviceConnectionGroup>
  <system.web>
      <compilation debug="true" targetFramework="4.5" />
      <httpRuntime targetFramework="4.5" />   
    </system.web>
</configuration>

Kural son derece basittir. serviceConnectionGroup, serviceConnection ve definition elementlerinin kullanılabilmesi için bir sectionGroup tanımlaması yapılması yeterlidir. Bu tanımlama içerisinde ki type kısmı ise ConfigurationSection veya ConfigurationSectionGroup türevli tipi işaret etmektedir.

Test Uygulaması

Şimdi dilerseniz basit bir aspx sayfası hazırlayıp section içeriğini ekrana bastıralım ve hatta üzerinde değişiklik yapıp web.config dosyasına kayıt edelim. Senaryonun bu kısmını gerçekleştirmek için aşağıdaki basit aspx sayfasını tasarlayabiliriz.

ccs_4

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="HowTo_WritingCustomConfigSection.Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
   
        <table>
            <tr>
                <td><strong>Service Type</strong></td>
                <td>
                    <asp:DropDownList ID="ddlServiceType" runat="server">
                    </asp:DropDownList>
                </td>
            </tr>
            <tr>
                <td><strong>Definition</strong> <strong>Name</strong></td>
                <td>
                    <asp:TextBox ID="txtName" runat="server" Width="220px"></asp:TextBox>
                </td>
            </tr>
            <tr>
                <td><strong>Definition WSDL</strong> <strong>Address</strong></td>
                <td>
                    <asp:TextBox ID="txtAddress" runat="server" Width="300px"></asp:TextBox>
&nbsp; </td>
            </tr>
            <tr>
                <td>&nbsp;</td>
                <td>
                    <asp:Button ID="btnSave" runat="server"
                        OnClick="btnSave_Click" Text="Save" Width="60px" />
                </td>
            </tr>
            </table>
   
    </div>
    </form>
</body>
</html>

Gelelim kod tarafına. Sayfa yüklenirken serviceSection içeriğini göstermek arzusundayız. Ayrıca Save düğmesine basıldığında, yaptığımız değişiklikleri kaydetmek ve web.config dosyasını güncellemek istiyoruz.

Senaryomuzda exceptional durumları göz ardı ettiğimizi ifade etmek isterim. Örneğin, boş değer geçilmesi, geçeriz bir url bildirimi yapılması vb. Ancak gerçek hayat senaryolarında bu tip veri doğrulama opsiyonlarını da işin içerisine katmanız önem arz etmektedir.

using System;
using System.Configuration;
using System.Web.Configuration;
using System.Web.UI;

namespace HowTo_WritingCustomConfigSection
{
    public partial class Default
        : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                ddlServiceType.DataSource = Enum.GetNames(typeof(ServiceType));
                ddlServiceType.DataBind();

                ServiceConnectionSection scs =
                    ConfigurationManager.GetSection("serviceConnectionGroup/serviceConnection")
                    as ServiceConnectionSection;

                if (scs != null)
                {
                    txtAddress.Text = scs.Definition.WsdlAddress;
                    txtName.Text = scs.Definition.Name;
                    ddlServiceType.SelectedValue = scs.Type.ToString();
                }
            }
        }

        protected void btnSave_Click(object sender, EventArgs e)
        {
            Configuration manager = WebConfigurationManager.OpenWebConfiguration("/");

            ServiceConnectionSection scs =
                manager.GetSection("serviceConnectionGroup/serviceConnection")
                as ServiceConnectionSection;
            if (scs != null)
            {
                scs.Type = (ServiceType)Enum.Parse(typeof(ServiceType),  ddlServiceType.SelectedValue.ToString());
                scs.Definition.Name = txtName.Text;
                scs.Definition.WsdlAddress = txtAddress.Text;

                manager.Save();
            }
        }
    }
}

serviceSection elementinin managed karşılığını elde edebilmek için Configuration veya ConfigurationManager tipinin GetSection metodundan yararlanılmaktadır. Söz konusu metodun dönüşü ServiceConnectionSection tipine dönüştürüldükten sonra ise Type ve Definition gibi özelliklere erişilebilinir. Hatta Definitionözelliği üzerinden Name ve WsdlAddress değerleri de yakalanabilir. Pek tabi Save işleminin gerçekleştirilebilmesi için WebConfigurationManager ile açılan web.config dosyasını işaret eden manager isimli Configuration tipinden yararlanılmaktadır.

Çalışma Zamanı Sonuçları

Artık çalışma zamanına geçebilir ve sonuçları irdeleyebiliriz. Uygulamayı ilk olarak başlattığımızda Page_Laod içerisindeki kodlar devreye girecektir.

ccs_5

Görüldüğü gibi varsayılan olarak belirtilen değerler çekilebilmiştir. Eğer bu noktada tip, ad ve adres bilgilerinde değişiklik yapıp Save düğmesine basılırsa, kod web.config dosyasında da gerekli etkiyi yapacaktır.

Tabi bu senaryoda çok basit bir section içeriği ele alınmış ve yönetilmiştir. Size tavsiyem connectionStrings gibi bir den fazla aynı tipden element içerebilen bir bölüm geliştirmeye çalışmanız olacaktır. Böyle bir senaryoda devreye ConfigurationElementCollection tipi girecektir. Bu örnek senaryoyu geliştirmeye çalışarak kendinizi bu yönde daha ileri bir noktaya taşıyabilirsiniz. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

HowTo_WritingCustomConfigSection.zip (25,98 kb)

http://www.buraksenyurt.com/post/TFI-109-IIS-Uzerindeki-Uygulamalarc4b1-Kod-Yoluyla-Ogrenmek.aspxTFİ 109 - IIS Üzerindeki Uygulamaları Kod Yoluyla Öğrenmek

$
0
0

Merhaba Arkadaşlar,

Diyelim ki sunucudaki IIS üzerinde konuşlandırdığınız Web uygulamalarının bir listesini almak istiyorsunuz. Bunun elbette pek çok yolu olduğunu biliyorsunuz. Bir Powershell script' i belki de işinizi görür. Ancak belki de siz bunu kendi geliştireceğiniz windows forms uygulamasında bu listeyi kullanmak istiyorsunuz. Ne yaparsınız? Kod yardımıyla IIS üzerindeki Application' ları, Site' ları öğrenebilir misiniz?

Aslında hep elinizin altında olan (Windows\System32\inetsrv\Microsoft.Web.Administration.dll) ve hatta isterseniz NuGet Package Manager ile de indirebileceğiniz Microsoft.Web.Administration kütüphanesini kullanarak bu işi gerçekleştirmeniz oldukça kolay. Nasıl mı? İşte böyle.

Başka neler mi yapabilirsiniz? Örneğin bir Application Pool' u Recylce edebilirsiniz. Ya da bir Web Site' ı Stop-Start. Hatta yeni bir Web Site bile açabilirsiniz. Araştırmaya değer değil mi?

Başka bir ipucunda görüşmek dileğiyle.

Viewing all 19 articles
Browse latest View live