Makale Özeti

Binary serileştirmede geriye dönük uyumluluk her zaman sıkıntı olmuştur. Tür adının değişmesi, Assembly sürümü ya da genel anahtar simgesinin değişmesi binary serileştirme kullanılan kodlar için tam bir kabustur. Bu makalemde sizlerle bu sorunları nasıl aşabileceğinizi paylaşıyorum.

Makale

   C# ile hafızadaki bir nesneyi saklamak iserseniz (örneğin, dosya sisteminde ya da veritabanındaki bir tabloda) bu işlem için kullanabileceğiniz iki temel yaklaşım bulunmakta ve bu iki yöntemin de kendilerine göre artıları ve eksileri söz konusu;

  • Xml Serialization      : Birlikte işlerlik sizin için önemli ise şüphesiz ki ilk tercihiniz (hatta tek tercihiniz) xml serileştirme olacaktır. Bu önemli artısına karşın gerek fazla yer kaplaması, gerekse de serialization/deserialization işlemlerinde fazla işlemci zamanı ve hafıza tüketmesi eksi olarak düşülmeli.
  • Binary Serialization : Xml serileştirmesinin aksine birlikte işlerlik söz konusu ise  (özellikle C# ile diğer diller arasında) binary serileştirme seçeneğiniz olmamalı. Öte yandan xml serileştirmesine göre daha az yer kaplaması, düşük işlemci zamanı ve hafıza tüketimi artı hanesine yazılanlar.

   Bu iki yöntemi karşılaştırırken göz önüne almamız gereken bir başka önemli kriter de geriye uyumluluk olmalı. Bu konuda hiç şüphe yok ki Xml serileştirme de çok rahat edersiniz,.Binary serileştirmede nesneler barındırdıkları veriler dışında tür bilgileriyle birlikte saklanır. Bu durumda da kullandığınız kütüphanenin yeni sürümü ile eski serileştirilmiş verileri açmakta problem yaşarsınız.

   Eski serileştirilmiş veriler yeni kütüphaneyle açılmaya çalışıldığında, BinaryFormatter verinin serileştirilmesi sırasında kullanılan tür’ü içerisinde bulunduğu assambly adı ve tür adı ikilisi ile arayacaktır. Eğer application domain içerisinde bu tür bilgisi bulunmuyorsa/tür yüklenemiyorsa hata alarak işleminiz yarım kalacaktır. Serileştirilmiş veri içerisinde bulunan türlerin assembly adı ve tür adı ikilisi ile geri yükleniyor olması nedeniyle assembly adında, sürüm numarasında, genel anahtar simgesinde (public key token) ya da tür adında yapılacak en ufak bir değişiklik geriye dönük serileştirilmiş verilerin açılmasında büyük sıkıntıya neden olmakta.

   Bu noktada pek çok yazılımcı binary serileştirme için kullanılan türlerin bulunduğu assembly’nin sürüm numarasını, genel anahtar simgesini sabitleyerek ve tür içeriklerine dokunmayarak çözme yoluna gitmekte. Bu yöntem gerçek anlamda bir çözüm olmasa da çoğunlukla problemi ortadan kaldırmakta, daha doğrusu, üstünü örtmektedir. İşler, tür içeriğine dokunulduğunda, assembly sürüm numarası değiştiğinde ya da assembly bir anahtar ile imzalanarak bir genel anahtar simgesine sahip olduğunda zorlaşacaktır.

   Şanslıyız ki .Net framework tasarımı sırasında binary serileştirmede böylesi bir problem yaşanabileceği göz önüne alınarak ilk sürümünden beri pek bilinmeyen bir çözüm sunulmuş durumda : SerializationBinder. Binary serileştirme sırasında kullanılan BinaryFormatter sınıfı tür bilgilerini binder özelliğini kullanarak almaktadır. Yukarıdaki gibi bir değişiklik sonrasında serileştirilmiş veri içerisindeki tür bilgilerini farklı yorumlamanız gerektiğinde BinaryFormatter ile birlikte gelen varsayılan Binder’a müdahale ederek kendi tasarladığınız SerializationBinder’ı kullanabiliriz. Yapmanız gereken SerializationBinder sınıfından türeyen yeni bir tür oluşturarak serileştirilmiş veri içerisindeki tür bilgisinin sorgulandığı BindToType metodunun içerisine ihtiyacınız olan yönlendirme kodunu eklemek. Ardından bu sınıfın bir örneğini BinaryFormatter’ın Binder özelliğine atamak.

   Aşağıda kodunu bulabileceğiniz OzellesmisBinaryFormatter sizlerle paylaşmış olduğum yöntemi kullanan ve SerializationBinder sınıfından türetilmiş bir sınıftır. İçerisinde bir BinaryFormatter barındıran bu sınıf, BinaryFormatter’ın Binder özelliğine OzellesmisBinaryFormatter sınıf örneği atamaktadır. Bu örnekte 1f91253719787bdf genel anahtar simgesi ile serileştirmesi yapılmış bir verinin bu genel anahtar simgesinin 6496550b925512 olarak güncellenmesi sonrasında da açılabilmesi sağlanmıştır.

public class OzellesmisBinaryFormatter : SerializationBinder {
    private BinaryFormatter formatter;

    public OzellesmisBinaryFormatter() {
        formatter = new BinaryFormatter {
            Binder = this
        };
    }

    public object Deserialize(Stream serializationStream) {
        return formatter.Deserialize(serializationStream);
    }

    public void Serialize(Stream serializationStream, object graph) {
        formatter.Serialize(serializationStream, graph);
    }

    public override Type BindToType(string assemblyAdi, string turAdi) {
        if (assemblyAdi.EndsWith("PublicKeyToken=1f91253719787bdf")) {
            assemblyAdi = assemblyAdi.Replace("PublicKeyToken=1f91253719787bdf", "PublicKeyToken=6496550b925512");
        }

        Type tur = Type.GetType(String.Format("{0}, {1}", turAdi, assemblyAdi));

        return tur;
    }
}

   SerializationBinder sınıfından gelen ve deserialization sırasında framework tarafından kullanılan BindToType metodunu incelediğimizde string formatında gelen assembly ve tür adlarını kullanarak ilgili tür örneğinin dönüldüğünü görebiliriz. Gelen bilgiler içerisinde iş mantığımız doğrultusunda yapacağımız değişikliklerle serileştirme işlemi sırasında istediğiniz tür’e yönlendirme yapmamız mümkün. Bu yönlendirme neticesinde de geriye dönük olarak serileştirimiş verilerin açılması mümkün olmakta.

   SerializationBinder’ın tek kullanım amacı geriye dönüş uyumluluğun sağlanması değildir. Bu sınıf/yöntem kullanılarak istenirse 3.parti kaynaklardan gelen verilerin içerisinde istenmeyen türlerin iletilmesi de önlenerek yazılımımıza dışarıdan müdahale de önlenebilir ve güvenlik sağlanmış olur.

Fatih Boy

http://www.enterprisecoding.com
http://twitter.com/fatihboy