Xml Yardımıyla Çalışma-Zamanında Sınıflar Oluşturmak

   Microsoft Yaz Okuluna katılamayanlardan zaman zaman aldığım sitem dolu mesajlar ardından, katılımcılarla paylaştığım bazı konuları zaman zaman blogum üzerinden sizlerle de paylaşmaya karar verdim.

   Bir yönetici olmak dışında, aynı zamanda (ve her zaman için) bir yazılım geliştirici olarak, yazdığım kodlarda her zaman için ileriyi düşünmeyi isterim. Her ne kadar pek çok firmada yazılım geliştirme süreçleri kısıtlı bir zaman dilimi içine sıkıştırılmış dahi olsa, proje teslim süreleri oldukça dar tutulsa da esnek kod yazılmasına ayrılacak olan zamanın geri dönüşü yüksek olacaktır.

    Konunun daha pekişmesi adına örnek üzerinden gitmek sanırım daha faydalı olacaktır. Standart bir serileştirme işlemini düşünün; projenizde yer alan aşağıdaki gibi bir Ogrenci sınıfını xml’e serileştirmek eminim ki sizler için oldukça basit bir iş olacaktır:

public class Ogrenci {
    public string Adi;
    public string Bolum;
}

   Bunun için öncelikle yapmanız gereken, aşağıda örneklediğim gibi olmasını istediğimiz xml çıktısı hakkında .Net Framework’ü bilgilendirecek, Xml’in oluşturulmasına yardım edecek olan attribute’leri (öznitelikleri) eklemek olmalı:

[XmlRoot(Namespace="http://www.enterprisecoding.com",ElementName="OgrenciListesi")]
public class OgrenciListesi {
  [XmlArray(ElementName="Ogrenciler")]
  [XmlArrayItem(ElementName="Ogrenci")]
  public Ogrenci[] Ogrenciler;
}

[XmlType(Namespace="http://www.enterprisecoding.com",TypeName="Ogrenci")]
public class Ogrenci {
    [XmlAttribute]
    public string Adi;

    [XmlAttribute]
    public string Bolum;
}

   Bu işlem ardından, aşağıdaki gibi iki-üç satırlık bir kod ile oldukça kolay şekilde sınıflar Xml olarak tutabilir ya da xml’den geri okuyabiliriz:

OgrenciListesi ogrenciListesi = new OgrenciListesi {
    Ogrenciler = new[] {
        new Ogrenci{ Adi = "Duru", Bolum = "Bil. Müh." },
        new Ogrenci{ Adi = "Fatih", Bolum = "Bil. Müh." },
        new Ogrenci{ Adi = "Melis", Bolum = "Deri Müh." }
    }
};

XmlSerializer serializer = new XmlSerializer(typeof(OgrenciListesi));

using (StreamWriter dosya = File.CreateText("ogrenciListesi.xml")) {
    serializer.Serialize(dosya, ogrenciListesi);
}
XmlSerializer serializer = new XmlSerializer(typeof(OgrenciListesi));

using (StreamReader dosya = File.OpenText("ogrenciListesi.xml")) {
 var ogrenciListesi=(OgrenciListesi)serializer.Deserialize(dosya);
    .
    .
    .
}

   Yukarıdaki örnek kodları çalıştırdığımızda ise aşağıdaki şekilde bir xml dosyası oluşturulabilir ve geri okunabilir olacaktır;

<?xml version="1.0" encoding="utf-8"?>
<OgrenciListesi xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                xmlns="http://www.enterprisecoding.com">
  <Ogrenciler>
    <Ogrenci Adi="Duru" Bolum="Bil. Müh." />
    <Ogrenci Adi="Fatih" Bolum="Bil. Müh." />
    <Ogrenci Adi="Melis" Bolum="Deri Müh." />
  </Ogrenciler>
</OgrenciListesi>

   Kompleks olmayan, oldukça basit ve eminim ki pek çok senaryoda da işimizi görecek olan bir yöntem; fakat yeterince genişletilebilir değil. Bu yaklaşımda, serileştirmek istediğiniz her yeni sınıfta xml serileştirme özniteliklerini tekrar tekrar tanımlamalı, birden fazla sınıfta toparlanmış olan verilerin bulunduğu senaryolarda yeni ara sınıflar oluşturarak veriyi bu ara sınıflar üzerinden okumalısınız. Böylesi bir durumda da iş hem giderek karmaşıklaşacak, hem de geliştirme süresi eskisine oranla daha da uzayacaktır.

   Peki bu duruma alternatif olarak nasıl bir yaklaşım üretilmeli? Bu yeni senaryoda takip edilmesi gereken yaklaşım öncelikli olarak xml dosyası ile veriyi taşıyan sınıflar arasındaki bire bir bağlantıyı kaldırmak olmalı,  yani her yeni sınıf için yeni bir xml tasarımı geliştirme zorunluluğundan kurtulmalıyız.

   Peki şöyle bir tasarım nasıl olur? Öyle bir kod yazsak ki, xml içerisinde statik olarak sınıfları ve değerlerini vermek yerine hangi sınıfın oluşturulacağını, bu sınıfın hangi alanlarına hangi değerlerin atılacağını tarif etsek. Uygulamamız, böylesi bir xml’i okuyarak yorumlasa ve ardından da önce tarif edilen sınıfları oluştursa, sonra da bu sınıfların alanlarına xml içerisinde verilen değerleri atasa… Güzel olur değil mi!

   Böylesi bir uygulamaya veri saylayacak olan xml dosyasını ise aşağıdaki şekilde tasarladım, sizlerde kendi ihtiyaçlarınız doğrultusunda bu tasarımla oynayabilir, ekleme ve çıkartmalar yapabilirsiniz;

<?xml version="1.0" encoding="utf-8"?>
<yapilandirma xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
              xmlns="http://www.enterprisecoding.com">
  <siniflar>
    <sinif tur="Ogrenci">
      <alanlar>
        <alan adi="Adi" deger="Duru" />
        <alan adi="Bolum" deger="Bil. Müh." />
      </alanlar>
    </sinif>
    
    <sinif tur="Ogrenci">
      <alanlar>
        <alan adi="Adi" deger="Fatih" />
        <alan adi="Bolum" deger="Bil. Müh." />
      </alanlar>
    </sinif>

    <sinif tur="Ogrenci">
      <alanlar>
        <alan adi="Adi" deger="Melis" />
        <alan adi="Bolum" deger="Deri Müh." />
      </alanlar>
    </sinif>
  </siniflar>
</yapilandirma>

  Dikkat edecek olursanız, bu tasarımın öncekinden en büyük farklılıklarından birisi ayağa kaldırılmak istenen, verisi belirtilen sınıfa dair tür bilgisinin de veriliyor olmasıdır. Bizlerde bu bilgiyi kullanarak reflection yardımıyla kolaylıkla ilgili sınıfı oluşturabilir, alanlara değerleri atayabiliriz. Aşağıda, yukarıdaki gibi bir xml dosyasını okuyup yazabilecek  olan sınıfları sizlerle paylaşıyorum;

[XmlRoot("yapilandirma", Namespace = "http://www.enterprisecoding.com")]
public class Yapilandirma {
    [XmlArray(ElementName = "siniflar")]
    [XmlArrayItem(ElementName = "sinif")]
    public Sinif[] Siniflar;
}

[XmlType("Sinif", Namespace = "http://www.enterprisecoding.com")]
public class Sinif {
    [XmlAttribute("tur")]
    public string Tur;

    [XmlArray(ElementName = "alanlar")]
    [XmlArrayItem(ElementName = "alan")]
    public Alan[] Alanlar;
}

[XmlType("Alan", Namespace = "http://www.enterprisecoding.com")]
public class Alan {
    [XmlAttribute("adi")]
    public string Adi;

    [XmlAttribute("deger")]
    public string Deger;
}

  Tasarımımızı yapıp, veriyi sunacak olan xml dosyamızı da hazırladıktan sonra aşağıdaki kod yardımıyla kolaylıkla veriyi okuyabiliriz;

Yapilandirma yapilandirma = null;
XmlSerializer serializer = new XmlSerializer(typeof(Yapilandirma));
 
using (StreamReader dosya = File.OpenText("yapilandirmaBilgisi.xml")) {
    yapilandirma = (Yapilandirma)serializer.Deserialize(dosya);
}

   Bu noktadan sonra artık iş reflection konusundaki yeteneğimize kalıyor. Gelen yapılandırma bilgisi içerisindeki her bir sınıf için tür bilgisini alarak ilgili sınıfı oluşturmalı ve alanlarına verilen değerleri atamalıyız; ama merak etmeyin, bu işlemlerde gerçekten oldukça kolay. Aşağıda, her bir sınıf bilgisi için verilen tür bilgisi yardımıyla sınıfın bir örneğinin nasıl oluşturulabileceğini görebilirsiniz;

foreach (Sinif sinif in yapilandirma.Siniflar) {
    string sinifTamAdi = "Enterprisecoding." + sinif.Tur;
    Type sinifTuru = Type.GetType(sinifTamAdi);
 
    object sinifOrnegi = Activator.CreateInstance(sinifTuru);
}

   Dikkat edecek olursanız sınıf’ın adının tutulduğu Tur alanı doğrudan kullanılmayacak bir ön ek ile birleştirilerek kullanılmakta. Bunun sebebi Tür bilgisine ulaşmakta kullandığımız Type.GetType fonksiyonunun sınıfın tam adına ihtiyaç duymasıdır. Bir sınıfın tam adı ise içinde bulunduğu namespace ve sınıf adının birleştirilmesinden oluşmasıdır. Tabi ki isterseniz xml içerisinde, sadece sınıf adını tutmak yerine sınıfın tam adını da tutabilmeniz mümkün; fakat bu durumda kodunuzdaki refaktörlerde (örneğin; sınıfların farklı namespace’lere taşınması sonrasında) xml’inizi yeniden düzenlemeniz gerekecektir. Bu şekilde tutmamın bir başka sebebi de uygulama güvenliği. Sadece sınıf adını tutup, sonrasında namespace ile birleştirerek tür’e ulaşıyor olmak bize sadece belirli bir namespace altındaki türleri bu xml dosyası ile oluşturabilmek gibi önemli bir kontrol mekanizması sunmaktadır. Aşağıda xml içerisinden aldığımız sınıf bilgisinde yer alan her bir alan için nasıl değer atayacağımıza dair kod parçacığını bulabilirsiniz;

foreach (Alan alan in sinif.Alanlar) {
    FieldInfo alanBilgisi = sinifTuru.GetField(alan.Adi);

    alanBilgisi.SetValue(sinifOrnegi, alan.Deger);
}

   Bu kod parçacığı ile artık ihtiyacımı olan tüm adımları tamamlamış oluyoruz. Son olarak; oluşturduğunuz sınıf bir liste içinde tutarak daha sonrasında uygulamamız içerisinde kullanabiliyor olacağız.

    Yazımın başında sizlerle paylaştığım standart xml serileştirme yönteminden farklı olarak öğrendiğimiz bu yeni yöntem sayesinde istediğiniz tüm sınıflara ait bilgileri hiç bir ek işlem yapmaya, kodunuzu değiştirmeye gerek kalmaksızın xml dosyasında okuyabilir, ilgili sınıfları oluşturabiliriz.

    Noktada önemli bir not düşmem gerekli; pek çok yazılımcının yaptığı bir hatadır, yeni öğrenilen bir konuyu hemen devam eden bir projede kullanmak. Aynı, html ve javascript’i yeni öğrenenlerin web sitelerini gereksiz pek çok script ile doldurması gibi.. Sizlerle paylaşmış olduğu bu yeni yöntem her ne kadar oldukça genel geçer ve pek çok uygulama içerisinde kullanılabilir olsa da, tabi ki beraberinde düşükte olsa bir maliyeti söz konusudur. Eğer uygulamanız xml içerisinden sınıf bilgileri okuyorken bu kadar esnek bir yapıya ihtiyacınız yoksa, işlem yapacağınız sınıflar bir elin parmaklarını aşmıyorsa bu yöntemi kullanmanıza büyük ihtimalle gerek kalmayacaktır.

    Konunun anlaşılabilmesi için oldukça basit tutmaya çalıştığım bu kodda, dikkat edecek olursanız, sınıflar içerisindeki alanlarımızın string olduğu ve serileştirilen her sınıfın mutlaka bir default constructer’ı olduğu varsayımı üzerinden gittik. Daha ileri düzey kullanımlarda örnek kod üzerinde yapılacak ufak değişikliklerle string dışındaki veri türleri de kullanabiliriz. Hatta xml içerisinde kullanılmak istenen constructer ve değerleri de alınabilir ve sınıf ilgili constructer yardımıyla ilklendirilebilir de.

   Aşağıda oluşturduğumuz kodu bir bütün halinde bulabilirsiniz;

[XmlRoot("yapilandirma", Namespace = "http://www.enterprisecoding.com")]
public class Yapilandirma {
    [XmlArray(ElementName = "siniflar")]
    [XmlArrayItem(ElementName = "sinif")]
    public Sinif[] Siniflar;
}

[XmlType("Sinif", Namespace = "http://www.enterprisecoding.com")]
public class Sinif {
    [XmlAttribute("tur")]
    public string Tur;

    [XmlArray(ElementName = "alanlar")]
    [XmlArrayItem(ElementName = "alan")]
    public Alan[] Alanlar;
}

[XmlType("Alan", Namespace = "http://www.enterprisecoding.com")]
public class Alan {
    [XmlAttribute("adi")]
    public string Adi;

    [XmlAttribute("deger")]
    public string Deger;
}

//.
//.
//.
//.
Yapilandirma yapilandirma = null;
XmlSerializer serializer = new XmlSerializer(typeof(Yapilandirma));

using (StreamReader dosya = File.OpenText("yapilandirmaBilgisi.xml")) {
    yapilandirma = (Yapilandirma)serializer.Deserialize(dosya);
}

List<object> siniflar = new List<object>();
foreach (Sinif sinif in yapilandirma.Siniflar) {
    string sinifTamAdi = "Enterprisecoding." + sinif.Tur;
    Type sinifTuru = Type.GetType(sinifTamAdi);

    object sinifOrnegi = Activator.CreateInstance(sinifTuru);

    foreach (Alan alan in sinif.Alanlar) {
        FieldInfo alanBilgisi = sinifTuru.GetField(alan.Adi);

        alanBilgisi.SetValue(sinifOrnegi, alan.Deger);
    }

    siniflar.Add(sinifOrnegi);
}

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, kurumsal veri yolu sistemleri, kurumsal altyapı ve yazılım geliştirme konularında destek vermektedir. Boş zamanlarında açık kaynak kodlu projeler geliştirmeyi ve bilgisini yazdığı makalelerle paylaşmayı seven Fatih, aynı zamanda Visual C# ve Visual Studio teknolojileri konusundan Microsoft tarafından altı yıl üst üste MVP (En Değerli Profesyonel) ödülüne layık görülmüştür. İş hayatı boyunca masaüstü uygulamaları, web teknolojileri, akıllı istemciler gibi konularda Asp.Net, Php, C#, Java programlama dilleri ve MySql, MsSql ve Oracle gibi veritabanı yönetim yazılımları ile çalışmıştır. İngilizce ve Türkçe olarak yayınlanan makalelerini gerek İngilizce bloğunda, gerekse de Türkçe bloğunda bulabileceğiniz gibi web sitesinden de açık kaynak kodlu geliştirdiği yazılımlarına ulaşabilirsiniz. vCard - Twitter - Facebook - Google+

1 Yorum

  1. kemal   •  

    hocam yazılarınızı takip ediyorum.gayet başarılı ve çok işime yarıyor. teşekkür ederim.

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir