New Site! Snippet Directory – Free code Snippet source for developers

Hi,

I’d like to inform about a site which Işıl and I just opened.

Snippet Directory aims to be an environment where developers from all over the world share their code with other developers.

One of the things that a developer spends his or her significant amount of time is finding sample code that does one specific thing. We provide a platform that developers reach these kinds of code easily and conveniently. In addition to this, we believe that everyone has something to learn from one another since every sample code reflects one’s different and peculiar way of thinking. We also believe that studying sample code is one of the best ways to improve one’s ability to code. That’s why our site is a useful source for amateur software developers or developers who want to improve themselves.

Your feedback is important to us, so please feel free to share your opinions with us via the contact us page.

You can also submit your code snippets if you think that it will help or be useful to others because this site is not only for providing code samples but also a platform for sharing them.

To visit : http://www.snippetdirectory.com
To follow on Facebook : http://www.facebook.com/snippetdirectory
To follow on Twitter : http://twitter.com/snippetd

The Real World Agile Roadshow

Çevikliği İş Üstünde Görün!

İşleriniz yazılıma mı bağlı? Uygulamalarınız kritik işletme süreçleri ve fonksiyonlarını destekliyor mu? Yazılım geliştirme süreçlerinizi kaliteden ödün vermeden nasıl daha iyi ve hızlı bir hale getirebileceğinizi biliyor musunuz? Nasıl daha çevik olabileceğinizi, işletmenizi nasıl daha iyi destekleyebileceğinizi ve değişimlere nasıl daha hızlı yanıt verebileceğinizi öğrenmek ister misiniz?

Microsoft tarafından sağlanan bu ücretsiz tek günlük seminerde, Team Foundation Server ve Visual Studio 2010 ile çevik bir yazılım geliştirme sürecini nasıl planlayabileceğinizi, yapılandırabileceğinizi ve çalıştırabileceğinizi öğreneceksiniz. Çok katmanlı bir iş hattı uygulama projesini kullanarak geliştirme ekibinizdeki her bir rol için Uygulama Yaşam Döngüsü Yönetimi ve Microsoft ALM çözümünün yararlarını göstereceğiz. Çarpıcı teknolojileri iş başındayken göreceksiniz: Windows Azure ölçeklendirilebilir bulut platform altyapısı, zengin kullanıcı deneyimi için HTML 5 ve çarpıcı mobil deneyim için Silverlight for Windows Phone. Daha fazla inceleyebilmeniz için etkinliğin sonrasında kaynak kodunun tamamını ve adım adım talimatları da alacaksınız!

Gündem

  1. Sunum: Çevikleşmek ve Bunu Sürdürmek Ne Anlama Geliyor?
    Uzunluk: 45 Dakika | Seviye: 100
  2. Çeviklik İş Başında – 1. Oturum: Planlama, Yapılandırma, İhtiyaçlar ve Mimari
    Uzunluk: 60 Dakika | Seviye: 200
  3. Çeviklik İş Başında – 2. Oturum: Arka Plan – Windows Azure Platformu
    Uzunluk: 60 Dakika | Seviye: 200
  4. Çeviklik İş Başında – 3. Oturum: Ön Uç – HTML5 ve Windows Phone 7
    Uzunluk: 60 Dakika | Seviye: 200
  5. Çeviklik İş Başında – Kapanış Sahnesi: Oluşturma, Dağıtma ve Testler
    Uzunluk: 60 Dakika | Seviye: 200
  6. Başlangıç: Çevik olmak bugün size nasıl değer katar?
    Uzunluk: 45 Dakika | Seviye: 100

Nerede ve Ne Zaman?

Yer: Microsoft İstanbul Ofisi
Tarih ve Saat: 10.01.2012 | Başlangıç saati: 09:00 | Bitiş saati: 18:00
Bu ücretsiz etkinliğe kaydolmak için BURAYA tıklayın

C# da Implicit ve Explicit Operatörler ve Kullanımları

Merhabalar,

Daha önce http://www.yazgelistir.com/Makaleler/1000000514.ygpx adresinde yayınlanmış oldığum makalede operator overloading konusunu incelemiştik. Bu makalemde ise implicit ve explicit operator’ler üzerinde duracağız. Bu 3 operatör temel olarak tipler üzerinde işlem yapmamıza yaramaktadır.

Hatırlayacak olursak operator overloading ile tipler arasında bölme, çarpma, çıkarma, toplama gibi işlemleri yapmamız mümkün olmaktaydı. Tabii ki bu işlemlerin nasıl yapılacağına dair kodları kendimizin yazması gerekiyor.

Implicit ve Explicit operator ile bir tipin başka bir tipe çevrilebilirliğini tanımlıyor olacağız tabii ki bir base classdan türeyen tiplerin base class a çevrilmesi mümkündür. Örnek vermek gerekirse Control class’ının bir örneğine bir Textbox class’ını atayabiliriz. Çünkü TextBox kontrolden türemiştir. Ama bir elma class’ına bir armut class’ını her ikisi de meyve class’ından türemiş olsa bile atayamayız. Ayrıca bir birine cast edemeyiz de.

Implicit operator ile bu class’ları birbirlerine hiçbir casting veya convert işlemi yapmadan atanabilir hale getirebiliriz. Bunun için bu class’ların aynı class’dan türemiş olmalarına gerek yok. İmplicit operator ile ilgili bir örneği de makalemin ilerleyen kısımlarında beraber yapacağız.

Explicit bir operator tanımlayarak biz bu class’ları birbirine explicit olarak cast edilebilir hale getirebiliriz. Bunun için aynı class’dan türemiş olmalarına da gerek yok. Bunu makalemizin ilerleyen kısımlarında bir örnekle daha net açıklamaya çalışacağım.

İmplicit operator tanımlanmış bir class’ı diğerine çevirirken compiler hiçbir hata vermez dolayısıyla bu iki class’ı birbirine çevirirken bir veri kaybı veya bir class şemalarında bir olumsuzluk oluyorsa developer bunu hiçbir zmaan anlayamaz. Böyle durumlarda explicit operator kullanılması tercih edilmelidir. Explicit operator tanımlanmış bir class diğerine direkt atanamadığından ve compiler illa ki cast edilmesini istediğinden developer bu işlemin bazı olumsuzlukları olabileceğini düşünecek ve her iki class’ın yapılarını karşılaştıracaktır.

Şimdi isterseniz implicit operator ile ilgili örnek yapalım.

Aşağıdaki gibi bir class’ımız olduğunu düşünelim.

    public class Currency
    {
        public decimal Value { get; set; }
        public string Sign { get; set; }
    }

Bu class’ımızı şu şekilde kullanmaya çalışalım.

            //class imizin bir instance ini aliyoruz
            Currency c = new Currency();
            c.Value = 12;
            c.Sign = "$";
            //simdi bu class'imizi bir decimal degere atamaya calisalim.
            decimal d = c; //<-- Bu satirda Connot implicitly convert type Currency to decimal hatasi verecektir.

Şimdi bu işlemi implicit operator ile nasıl yapılabilir kılacağımızı inceleyelim. Bu işlem için class’ımıza bir implicit operator yazıyoruz.

    public class Currency
    {
        public decimal Value { get; set; }
        public string Sign { get; set; }
        public static implicit operator decimal(Currency c)
        {
            return c.Value;
        }
    }

Bunu operatoru yazdıktan sonra kodumuz hata vermeyecektir ve yukarıdaki cast işlemi implicit olarak yapılacaktır.

Implicit operator syntax’ına baktığınızda publIs static implicit operator CevrilecekTip(MevcutTip) olarak bir syntax görüyorsunuz. Kullanımı oldukça basit.

Ancak burada dikkat edilmesi gereken bir nokta bu işlem sorunsuz bir şekilde yapılmakta ve Currency’nin sign property’sinde ki değer bu adımdan sonra kullanılamamaktadır. Aşağıda bir tane daha implicit operator örneği paylaşıyorum.

    public class Inch
    {
        public decimal Length { get; set; }
        public static implicit operator Cm(Inch i)
        {
            Cm c = new Cm();
            c.Length = i.Length * 2.54M;
            return c;
        }
    }
    public class Cm
    {
        public decimal Length { get; set; }
        public static implicit operator Inch(Cm c)
        {
            Inch i = new Inch();
            i.Length = c.Length / 2.54M;
            return i;
        }
    }

Bu sayede aşağıdaki kodu hatasız bir şekilde çalıştırabiliriz oluyoruz.

            Inch i = new Inch();
            i.Length = 5;
            Cm cm = i;
            Console.WriteLine(cm.Length);

Implicit operator kullanımına ait güzel ve daha karmaşık bir örnek incelemek isterseniz. http://www.yazgelistir.com/Makaleler/1000002697.ygpx adresindeki makaleyi incelemenizi öneriyorum.

Şimdi isterseniz benzer örnekleri explicit operator ile ilgili örnekler yapalım. Class’larımızın aynı olduğunu düşünelim.

Yine aynı nesneleri kullandığımızı sadece karışmaması adına isimlerini değiştirdiğimizi düşünelim.

   public class CurrencyExplicit
    {
        public decimal Value { get; set; }
        public string Sign { get; set; }

    }

Yine aynı atama örneğini deneyelim.

            CurrencyExplicit insCurrencyExplicit = new CurrencyExplicit();
            insCurrencyExplicit.Value = 12;
            insCurrencyExplicit.Sign = "$";
            decimal d = insCurrencyExplicit;//bu atama islemini yapamiyoruz.

Şimdi class’ımıza explicit bir operator ekleyelim.

    public class CurrencyExplicit
    {
        public decimal Value { get; set; }
        public string Sign { get; set; }
        public static explicit operator decimal(CurrencyExplicit c)
        {
            return c.Value;
        }
    }

Ve atama işlemini tekrar yapmayı deneyelim ve bu satırda compiler’ın hala hata verdiğini görelim. Verdiği hata Cannot implicitly convert type ‘CurrencyExplicit’ to ‘decimal’. An explicit conversion exists (are you missing a cast?). Burda zaten bize gerekli ipucunu söylüyor. Ben bu tipleri birbirine çeviremiyorum ama explicit bir çevrim mevcut cast etmeyi atlıyor olabilir misin şeklinde çok anlaşılır bir mesaj veriyor. O zaman biz casting’imizi yapalım.

            CurrencyExplicit insCurrencyExplicit = new CurrencyExplicit();
            insCurrencyExplicit.Value = 12;
            insCurrencyExplicit.Sign = "$";
            //decimal d1 = insCurrencyExplicit;//bu atama islemini yapamiyoruz.
            decimal d1 = (decimal)insCurrencyExplicit;
 

Gördüğünüz gibi çevrim işlemini biz tanımladık ama direkt çevrime compiler izin vermedi ve bizim cast etmemizi istedi. işte bu noktada veri kayıpları olacaksa, örneğin sign değeri kaybolacaksa developer bunun farkına varabilir.

Explicit operator syntax’ına baktığınızda publIs static explicit operator CevrilecekTip(MevcutTip) olarak bir syntax görüyorsunuz. Kullanımı oldukça basit.

Aşağıda bir tane daha explicit operator örneği paylaşıyorum. Biraz önceki örneğin aynısını yapalım.

    public class InchExplicit
    {
        public decimal Length { get; set; }
        public static explicit operator CmExplicit(InchExplicit i)
        {
            CmExplicit c = new CmExplicit();
            c.Length = i.Length * 2.54M;
            return c;
        }
    }
    public class CmExplicit
    {
        public decimal Length { get; set; }
        public static explicit operator InchExplicit(CmExplicit c)
        {
            InchExplicit i = new InchExplicit();
            i.Length = c.Length / 2.54M;
            return i;
        }
    }

Şimdi ise nasıl kullanacağımızı inceleyelim.

            InchExplicit ie = new InchExplicit();
            i.Length = 5;
            CmExplicit cme = (CmExplicit)ie;

Gördüğünüz gibi yine cast etmek gerekti.

Bu makaledeimplicit ve explicit operator’leri örneklerle incelemeye çalıştık. Umarım faydalı olmuştur.

INotifyPropertyChanged Kullanımı

Merhabalar,

Bu makalemde sizlerle INotifyPropertyChanged interface’ini temel olarak, nerelerde kullanılabileceğini inceleyeceğiz ve küçük bir örnek yapacağız.

Adından da anlaşılabileceği gibi INotifyPropertyChanged değişen property’lere ait değerleri yakalamak için kullanılır. Bu interface’e baktığımızda içerisinde PropertyChanged isminde bir event olduğunu görüyoruz. Bu event herhangi bir property’nin değeri değiştiğinde fırlayacak olan event’tir.

Ek bir bilgi olarak vermek gerekirse INotifyCollectionChanged ise Collection’a ait herhangi bir üye değiştiğinde kullanılır. Bu interface’in içeriğine baktığımızda CollectionChanged isminde bir evenet olduğunu göryoruz. Bu evet collection üzerinde herhangi bir değişiklik olduğunda fırlayacak event’tir.

Özellikle MVVM pattern’i kullandığınız uygulamaların temeli DataBinging olduğundan bu interface’i kullanmanız kaçılmazdır. Collection kullanmak gereken durumlarda ise ObservableCollecion generic tipini kullanmak bu tip INotifyCollectionChanged ve INotifyPropertyChanged interfacelerini barındırdığından çok daha uygun olacaktır ve implemantasyonu çok daha kolaydır.

İsterseniz bu özelliklerin nasıl kullanıldığını örnek uygulamamız üzerinde inceleyelim.

Yapacağımız örneği arabalarının anlık hızlarını takip etmek için kullanan bir şirket senaryosunu canlandıralım. Örneğimizde herhangi bir dış kaynaktan veri okumadığımız için araçların hızlarını güncellemeyi bir timer nesnesi üzerinden yapacağız. Tüm örneklerimizde aynı ekranı kullanacağız bu sayede metodlar arasındaki avantaj ve dezavantajları karşılaştırabileceğiz.

İlk örneğimizde bir çoğumuzun uygulamalarda kullandığı basit bir araba nesnesi üzerinden çalışalım ve şirketimize ait arabaları ve hızlarını gridimizde gösterelim. Formumuzu aşağıdaki gibi tasarlıyoruz. Tasarımda gözükmeyen bir de timer kontrolümüz var unutmamamız gerek. Yaptığımız 2 örnekte de aynı formu kullanacağız.

Ekran görüntüsünde gördüğünüz gibi 1 button ve 1 gridden oluan bir form. Initiate Cars ile sahadaki arabalarımızın listesini gride yükleyeceğiz. Saniyede bir çalışması için kurduğumuz timer ise arabalarımızın hızını güncellemeyi simule edecek.

Öncelikle araba class’ımızı oluşturalım.

    public class SimpleCar
    {
        public string Plate { get; set; }
        public string Driver { get; set; }
        public int CurrentSpeed { get; set; }
    }

Şimdi ise formumuzun kodunu yazalım.

    public partial class SimpleCarForm : Form
    {
        public SimpleCarForm()
        {
            InitializeComponent();
        }
        BindingList<SimpleCar> cars = new BindingList<SimpleCar>();
        BindingSource bs = new BindingSource();
        private void Form1_Load(object sender, EventArgs e)
        {

        }
        private void timerUpdateCurrentSpeeds_Tick(object sender, EventArgs e)
        {
            Random r = new Random();
            foreach (SimpleCar sc in cars)
            {
                sc.CurrentSpeed = r.Next(30, 120);
            }
        }

        private void btnInitiateCars_Click(object sender, EventArgs e)
        {
            SimpleCar sc1 = new SimpleCar { Plate = "34 AAA 34", Driver = "Mustafa", CurrentSpeed = 0 };
            SimpleCar sc2 = new SimpleCar { Plate = "34 BBB 34", Driver = "Ahmet", CurrentSpeed = 0 };
            SimpleCar sc3 = new SimpleCar { Plate = "34 CCC 34", Driver = "Mehmet", CurrentSpeed = 0 };
            cars.Add(sc1);
            cars.Add(sc2);
            cars.Add(sc3);

            bs.DataSource = cars;
            grvCars.DataSource = bs;
        }
    }

Gördüğünüz gibi Initiate Cars metodunda araba listesini yüklüyoruz. Burdaki yükleme işlemlerini direkt olarak datasource’a list vererek değilde bu şekilde yaptım ki her iki örneğimizde de aynı kodu kullanalım. Formu çalıştırıp InitiateCars buton’una tıkladığımızda timer çalışmasına rağmen griddeki hiçbir hız bilgisinin güncellenmediğini görüyoruz. Gridi güncellemek için gride veriyi tekrar bind etmemiz gerekiyor.

Şimdi ise Car INotifyPropertyChanged’ın kullanım alanını inceleyelim. İkinci örneğimizde yine bir araba nesnesi oluşturalım. Ancak bu sefer INotifyPropertyChanged İnterface’inin implement edelim.

    public class Car : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }

        public Car(string plate, string driver)
        {
            _Plate = plate;
            _Driver = driver;
        }
        private string _Plate = string.Empty;
        public string Plate
        {
            get
            {
                return _Plate;
            }
            set
            {
                if (_Plate != value)
                {
                    _Plate = value;
                    NotifyPropertyChanged("Plate");
                }
            }
        }
        private string _Driver = string.Empty;
        public string Driver
        {
            get
            {
                return _Driver;
            }
            set
            {
                if (_Driver != value)
                {
                    _Driver = value;
                    NotifyPropertyChanged("Driver");
                }
            }
        }
        private int _CurrentSpeed = 0;
        public int CurrentSpeed
        {
            get
            {
                return _CurrentSpeed;
            }
            set
            {
                if (_CurrentSpeed != value)
                {
                    _CurrentSpeed = value;
                    NotifyPropertyChanged("CurrentSpeed");
                }
            }
        }

Gördüğünüz gibi interface’i implement ettiğimizde bir event geliyor ve biz property’lerin değeri değiştiğinde bu event’ı fırlatıyoruz. Formumuzun koduna tekrar göz atalım

public partial class CarForm : Form
    {
        public CarForm()
        {
            InitializeComponent();
        }
        BindingList<Car> cars = new BindingList<Car>();
        BindingSource bs = new BindingSource();
        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void timerUpdateCurrentSpeeds_Tick(object sender, EventArgs e)
        {
            Random r = new Random();
            foreach (Car sc in cars)
            {
                sc.CurrentSpeed = r.Next(30, 120);
            }
        }

        private void btnInitiateCars_Click(object sender, EventArgs e)
        {
            Car sc1 = new Car("34 AAA 34", "Mustafa");
            Car sc2 = new Car("34 BBB 34", "Ahmet");
            Car sc3 = new Car("34 CCC 34", "Mehmet");
            cars.Add(sc1);
            cars.Add(sc2);
            cars.Add(sc3);

            bs.DataSource = cars;
            grvCars.DataSource = bs;
        }
    }

Şimdi isterseniz Program.cs’den başlangıç formumuzu değiştirerek yeni formumuzu çalıştıralım. Gördüğünüz gibi hız değerleri kendi kendine değişiyor. Bizim UI tarafında herhangi bir kod değişikliği yapmamıza gerek kalmadı.

Ayrıca bu interfce’i implement eden class’lar üzerinden PropertyChanged event’ine subscribe olarak bir değer değiştiği gibi kendi işlerimizin yapılmasını da sağlamamız mümkün. Her ne kadar Domain prensiplerine uymasa da değişen nesnenin mail atılması, nesnelerin statelerinin tutulması gibi işlemler buna örnek olarak gösterilebilir. Umarım faydalı bir makale olmuştur.

Unity Application Block – Interception

Merhabalar,

Önceki makalelerimde unity’nin genel kullanımı, dependency injection kavramı ve singleton pattern’inin unity de nasıl uygulanabileceğinden bahsetmiştik. Şimdi ise unity ile bir interceptor’u nasıl yazabileceğimize bakalım.

Birçok IOC Container’da bulunan interception özelliği sayesinde uygulama geliştiricile ristedikleri bir metod çağrılmadan önce veya sonrasında gerekli işlemleri kolaylıkla yapabilmektedirler. Seperation of Concerns prensibine çok uygun olan bu yapı sayesinde iş metodlarınızın içinde hata yakalama fonksiyonlarını tek tek yazmanıza gerek yoktur. Genellikle loglama, security checki policy injection gibi fonksiyonaliteler her sınıfta her metodda tek tek implement etmek yerine interceptor’lar ile yapılmaktadır.

Şimdi isterseniz bunu nasıl yapabileceğimizi inceleyelim. Öncelikle uygulamamıza interceptor ile ilgili referansları ui projemize eklememiz gerekmektedir.

Sonrasınsa ise InterceptorBehavior’u yani interceptor’umuzun nasıl bir davranış sergileyeceğini kodlamamız gerekiyor. Örnek uygulamamızda Servis katmanına yapılan çağrılar üzerinde işlem yapacağımız için Service projesine yeni bir class oluşturuyoruz ve adını InterceptorBehavior koyuyoruz. Bu sınıfımızı ise IInterceptionBehavior’dan implement ediyoruz. Burada implement etmemiz gereken 2 metod ve 1 property var. Bunlar

GetRequiredInterfaces() : Bu metod bize bu behavior’un intercept edeceği tipleri döndürecektir.

Invoke() : Bu metodda esas çağrılmak istenen fonksiyonu çağıracak ve dönüş parametrelerini alacağız.

WillExecute : Bu interceptor’un çalışıp çalışmayacağının belirlendiği property olacak.

En basit şekilde interceptor’umuzu aşağıdaki gibi oluşturalım. Metodların içerilerini sonradan daha detalı dolduracağız.

namespace UI
{
    public class InterceptorBehavior : IInterceptionBehavior
    {
        public IEnumerable<Type> GetRequiredInterfaces()
        {
            return Type.EmptyTypes;
        }

        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            IMethodReturn methodReturn = getNext().Invoke(input, getNext);

            if (methodReturn.Exception == null)
            {
                //metod basariyla calistirildi
            }
            else
            {
             //metodun calismasi sirasinda hata oldu.
            }
            return methodReturn;
        }

        public bool WillExecute
        {
            get { return true; }
        }
    }
}

Kodu inceleyecek olarak GetRequiredInterface metodunda EmptyTypes döndüğü için interceptor’umuz tüm tipler için çalışacak, WillExecute true döndürdüğü için interceptor devreye girecektir. Invoke metodunu inceleyecek olursak ilk satırda esas çağrılan metodu invoke ediyoruz. Sonrasında ise bu metodun çalışması sırasında herhangi bir hata olduysa bu hatayı görüntülüyoruz.

Peki bu interceptor’u çalıştırmak için servis metodunu çağırdığımız yerde ne yapmamız gerek. Cevap hiçbirşey yapmamız gerekmiyor. Sadece config’de bazı yeni tanımlamalar yapacağız. Config dosyamızın nasıl olacağını aşağıda inceleyelim

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
  </configSections>
  <unity>
    <typeAliases>
      <typeAlias alias="INotificationService" type="ContractService.INotificationService,ContractService"/>
      <typeAlias alias="NotificationService" type="Service.NotificationService,Service"/>
      <typeAlias alias="singleton" type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager, Microsoft.Practices.Unity" />
      <typeAlias alias="transient" type="Microsoft.Practices.Unity.TransientLifetimeManager, Microsoft.Practices.Unity"/>

    </typeAliases>
    <!-- Interception extension ile unity mizi extend ediyoruz-->
    <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension. Configuration.InterceptionConfigurationExtension,  Microsoft.Practices.Unity.Interception.Configuration"/>
    <containers>

      <container>
        <!--kullanacagimiz extension in tipini belirliyoruz.-->
        <extension type="Interception"/>
        <register type="UI.InterceptorBehavior,UI">
        </register>
        <types>
          <type type="INotificationService" mapTo="NotificationService">
            <!-- Bu tip icin interceptor ayarlari-->
            <!-- interceptor'umuzun tipi interface interceptor yani interface e yapilan cagrilari intercept edecek-->
            <interceptor type="InterfaceInterceptor"/>
            <!--Interceptorumuzun intercept ettigi cagrilar icin bu behavior kullanilacak -->
            <interceptionBehavior type="UI.InterceptorBehavior,UI"/>
            <!--lifetime i transient olarak tanimliyoruz-->
            <lifetime type="singleton"></lifetime>
            <property name="UserData" >
              <dependency />
            </property>
          </type>
          <type type="ContractData.IUserData,ContractData" mapTo="Data.UserData,Data">
            <lifetime type="singleton"></lifetime>
          </type>
        </types>
      </container>
    </containers>
  </unity>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

Tüm yapmamız gereken bu config tanımlamasını yapmak. Artık servis üzerine yaptığımız çağrılar Behavior class’ımızın invoke metoduna girecektir. Bunu bir breakpoint koyarak test edebiliriz. Şimdi isterseniz bu kısımda yapılabilecekler hakkında ufak bir demo ile bu makalemizi bitirelim.

namespace UI
{
    public class InterceptorBehavior : IInterceptionBehavior
    {
        public IEnumerable<Type> GetRequiredInterfaces()
        {
            return Type.EmptyTypes;
        }

        public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
        {
            Console.WriteLine(input.MethodBase.ToString() + " cagrildi");

            IMethodReturn methodReturn = getNext().Invoke(input, getNext);
            int parameterCount = input.Inputs.Count;
            for (int i = 0; i < parameterCount; i++)
            {
                Console.WriteLine("Parametre :" + input.Arguments.GetParameterInfo(i).Name + ", Deger:" + input.Inputs[i].ToString());
            }
            if (methodReturn.Exception == null)
            {
                string retval = "";
                if (methodReturn.ReturnValue != null)
                {
                    retval = methodReturn.ReturnValue.ToString();
                }
                Console.WriteLine(input.MethodBase.ToString() + " basariyla calisti,Donus degeri:" +  retval);
            }
            else
            {
                Console.WriteLine(input.MethodBase.ToString() + " hata verdi Hata:" + methodReturn.Exception.ToString());
            }
            return methodReturn;
        }

        public bool WillExecute
        {
            get { return true; }
        }
    }
}

Şimdi servis metodlarına çağrı yaptığımızda konsol çıktımız aşağıdaki gibi olacaktır.

Örneğini de yaptığımız gibi bazı işlemleri interceptor ile yapmak çok kolay olacaktır ve kodlama maliyetlerini ciddi azaltacaktır.

Unity Application Block – Singleton ve Transient Kavramları

Merhabalar,

Makalemin bir önceki kısmında unity ile bir uygulamayı nasıl loose coupled hale getirebileceğimizi inceledik. Şimdi ise bu işlemi yaparken transient veya singleton ayarlarını nasıl yapabileceğimizi transient ve singleton kavramlarını açıklayarak anlatmaya çalışacağım.

Singleton çok basit olmasına karşın belki de farkında olmadan en çok kullandığımız tasarım desenidir. Adından da anlaşıldığı üzere singleton bir sınıfa ait tek bir instance yaratma ve bunu global olarak uygulamanın heryerinden kullanmaktır. Örneğin bir class’dan sadece bir tane oluşturma gereksinim olarak uygulamamızda yer alıyorsa singleton tasarım desenini kullanmamız gerekmektedir.

    public class SingletonOrnek
    {
        protected SingletonOrnek()
        {
        }
        private static SingletonOrnek singletonOrnek = null;
        public static SingletonOrnek SingletonNesne
        {
            get
            {
                if (singletonOrnek == null)
                {
                    singletonOrnek = new SingletonOrnek();
                }
                return singletonOrnek;
            }
        }
    }

Transient ise singleton’un tam tersidir. Yani bir sınıfa her ulaşmak gerektiğinde yeni bir instance alınır. Yeni bir örnek üzerinden işlem yapılır.

Şimdi isterseniz bunu unity ile nasıl uygulayabileceğimizi inceleyelim.

Bunun için unity de hali hazırda 2 tip vardır. Bu tipleri unity’nin config dosyasına dahil etmemiz gerekmektedir. Bu tiplerin tanımlamasını typeAliases içinde yapıp bir alias kullanabileceğimizi gibi direkt tiplerini de yazabiliriz. Ancak ben daha derli toplu olması bakımından alias kullanılmasını tercih ediyorum. Bunun için config’de gerekli tanımlamayı aşağıdaki gibi yapabilirsiniz.

 <typeAliases> 

    <typeAlias alias="singleton" type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager, Microsoft.Practices.Unity" />
    <typeAlias alias="transient" type="Microsoft.Practices.Unity.TransientLifetimeManager, Microsoft.Practices.Unity"/> 

  </typeAliases> 

Şimdi ise nesnelerimizi ayağa kaldırırken transient mi yoksa singleton mu kullanacağımızı tanımlamalıyız. Bunun için mappingleri tanımladığımız type nodu altında bu seçimi lifetime nodu ile yapabiliriz. Örnekler aşağıdaki gibidir.

<!--?xml version="1.0"?-->

<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"></section>

      <!-- alias tanımlaması bu şekilde yapılıyor. Config dosyamızda bu tanımlamadan sonra alias tanımlamasını kullanarak bu tiplere erişebileceğiz.-->

      <!--transient ve singleton tanımlaması-->

          <!-- Alias tanımlaması ile mapping örneği -->
          <!-- INotificationService kullanılan yerlerde NotificationService tipinin ayağa kaldırılması için -->

            <!--lifetime i transient olarak tanimliyoruz-->

            <!--lifetime i singleton olarak tanimliyoruz-->
            <!--	<lifetime type="singleton"></lifetime>-->
            <!-- NotificationService içindeki UserData isimli property'nin NotificationService ayağa kalktığında tanımlı mappingle ayağa kaldırılması için -->

          <!-- Alias tanımlaması olmadan yapılmış mapping örneği -->

            <!--lifetime i singleton olarak tanimliyoruz-->

            <!-- UserData ayağa kalktığında herhangi bir propertynin ayağa kalkmasını istemiyoruz.-->

Şimdi isterseniz singleton ve transient tanımlamalarının kodumuza nasıl etki ettiğine bakalım. Aşağıdaki kodu çalıştıralım.

        static void Main(string[] args)
        {
            Console.WriteLine("Basit bir unity ornegi");
            UI.UIComponent insUIComponent = new UI.UIComponent();
            insUIComponent.Notify("oztamer@hotmail.com");
            Console.WriteLine("Devam etmek icin bir tusa basin.");
            Console.ReadLine();

            Console.WriteLine("Transient Ornek, config dosyasinda once transient sonra singleton tanimlamasini kullanin");
            UIComponent ui1 = new UIComponent();
            ui1.SetDefaultEmailAddress("oztamer@hotmail.com");
            string s1 = ui1.GetDefaultEmailAddress();
            Console.WriteLine("1: " + s1);

            UIComponent ui2 = new UIComponent();
            string s2 = ui2.GetDefaultEmailAddress();
            Console.WriteLine("2: " + s2);
            Console.ReadLine();
        }

Gördüğünüz gibi UIComponent nesnesinin bir örneğini alıyoruz, Örneği aldığımız anda Service ve Data nesneleri ayağa kalkıyor ve service nesnesinin SetDefaultEmailAddress metodu çağıralarak service nesnesi içerisindeki bir değişkene değer atanıyor. Sonrasında ise bir metodla bu değer okunuyor. Daha sonra yeni bir UIComponent nesnesi oluşturuluyor ve bu nesnenin GetDefaultEmailAddress metodu çağrılıyor.

Transient örneği inceleyelim

Transient çağrıda her iki UIComponent nesnesi içinde farklı service classları oluşturulduğundan 2. sinde geriye bir değer dönmüyor. Çünkü 2. nesneden çağrılan servis nesnesinin içerisinde bulunan değişkene henüz değer atanmamış yani ilkindne çağrılan ile ikinciden çağrılan nesneler tipleri aynı olsa bile farklı referanslarda nesneler.

Singleton örneği inceleyelim.

Singleton çağrıda ise baktığımızda unity her iki UIComponent nesnesine de aynı service instance’ını verdiğini görüyoruz. Yani servis nesnesini isteyen tüm yerlerde aynı nesne referansı dönecektir.

Unity ile singleton ve transient tanımlamaları nasıl yapabileceğimizi gördük bir sonraki makalemde ise interceptor’ları inceleyeceğiz.

Dependency Injection ve Unity Application Block

Merhabalar,

Bu makalemde sizlerle Dependency Injection ve Unity Application Block hakkında bilgiler paylaşmaya çalışacağım.

Geliştirdiğimiz uygulamaların kurumsal bir mimariye uygun ve plug and play olmasını istiyorsak uygulamamız gereken prensiplerin başında “Single Responsibility” ve “Loose Coupling” gelmelidir. Biraz daha açıklayacak olursak uygulamamız içindeki her bir bileşen tek bir amaca hizmet etmelidir ve bileşenler arasındaki bağlantı sıkı sıkıya değil de biraz daha hafif bir tarzda bağlantı olmalıdır. Bu ne demektir biraz açıklamak gerekirse bir sınıf başka bir sınıfın içerisindeki metodları direkt çağırıyorsa o sınıfa bağlı demektir. İleride çağrılan sınıftaki metodun yapısında bir değişiklik yapılması uygulama geliştirici için ciddi kodlama maliyeti doğuracaktır. Bilhassa bir paket uygulama geliştirdiğinizi düşünürseniz her müşterinin aynı fonksiyona ihtiyacı olduğu ancak hepsinin bu metodun içindeki hesaplamayı farklı şekilde yaptığı durumlar güzel bir örnek teşkil etmektedir. Bu tarz örneklerde her müşteri için kodu değiştirmeniz hepsi için farklı bir proje oluşturmanız, hepsi için kodu farklı derlemeniz gerekecektir ki bu hiç tavsiye edilen birşey değildir. Zaten durum bir süre sonra içinden çıkılamaz bir hal alacaktır.

Dependency Injection ise uygulama arasındaki bu tarz bağımlılıkları azaltmayı hedeflemekte ve gerekli yerlerde bağımlılığı kendisi bir config üzerinden gerekli tipe karar vererek enjekte etmeyi hedeflemektedir. İşte bu makalemde Microsoft Enterprise Library ile gelen Unity Application Block’un nasıl kullanılabileceğinden bahsedeceğim.

Şu anda Enterprise Library’nin en güncel versiyonu 5.0′dır ve http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=15104 adresinden indirilebilir.

Öncelikle bir konsol uygulaması oluşturarak başlayalım. EnterpriseLibrary’i kurduktan sonra gerekli referansları projemize eklememiz gerekecek.

Bu referansları projemize ekledikten sonra artık uygulamamızı oluşturmaya başlayalım. Yapacağımız örnekte bir UI, bir servis (iş) ve de bir Veri katmanı projesi olsun. Bu 3 proje arasındaki bağımlılığı nasıl azaltacağımızı ve unity’i nasıl kullanacağımızı inceleyeceğiz.

UI katmanımız Servis katmanımız ile direkt olarak bir bağlantı kurmayacak ve bu bağlantı Contract adı verilen başka bir aracı class library ile gerçekleşecek. Buralardaki bağlantı kurma görevlerini sağlamak için kod yazmayacağız ancak interface tanımlamaları yapacağız. UI katmanımızda ise servis katmanının referansı yerine Contract’ın referansı bulunacak. Dolayısıyla UI katmanımız servisin kendisine sağladığı fonksiyonları, özellikleri bilecek ancak bu işlemlerin nasıl yapıldığı konusunda bilgi sahibi olmayacak. Aynı şekilde Servis ve Data katmanı arasındaki bağlantı da bu şekilde olacak.Şimdi projelerimizi oluşturup hiyerarşimize bir bakalım.

Gördüğünüz gibi UI, Service ve Data katmanlarımız birbirlerine sıkı sıkıya bağlı değil ve interfaceler aracılığıyla birbirlerinin hangi işleri yapabileceklerini biliyorlar ancak nasıl yaptıkları ile ilgilenmiyorlar. Şimdi esas soru benim elimde class yok sadece interface var ben bu interface’den implement olmuş class’ın yapısını biliyorum. Burada class’ı nasıl çağıracağım konusu çözülmesi gereken esas konu. İşte dependency injection bu noktada devreye giriyor. Temel olarak bir interface’in tip olarak gösterildiği property’e eriştiğimizde o interface’den implement olmuş herhangi bir class’ı kullanmalıyız. İşte bu class’ı bu property üzerine unity enjekte edecek. Yani bu class’a olan bağımlılık esasında yok ama çalışma zamanında enjekte edilerek oluşturulacak.

İsterseniz bunu unity ile nasıl yapabileceğimizi inceleyelim.

Öncelikle unity için app.config veya web.config veya benzer bir config dosyamızda bazı konfigurasyon tanımlamaları yapmalıyız. Şimdi bu config yapısını inceleyelim.

Config yapımızda root element Unity, bunun altında typeAliases, sectionExtensions ve containers bulunuyor. typeAliases config dosyamıza bundan sonra kullanacağımız tipleri uzun uzun yazmak yerine bir alias tanımlamamıza yarıyor. sectionExtensions ise unity içerisinde bir extension kullanacaksak bunu tanımlamamıza yarıyor (Örneğin interception). Interception’a bu makalemizin ikinci kısmında değineceğiz. containers içerisinde ise IOC container’da kullanacağımız tipleri ve bu tiplerin hangi tiplerle denk geleceğini tanımlıyoruz. Yani Dependency injection’ın yapılabilmesi için gerekli tanımlamalar burada yapılıyor. Uygulamamızda kullanacağımız config dosyasını oluşturmaya başlayalım.

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
  </configSections>
  <unity>
    <typeAliases>
      <!-- alias tanımlaması bu şekilde yapılıyor. Config dosyamızda bu tanımlamadan sonra alias tanımlamasını kullanarak bu tiplere erişebileceğiz.-->
      <typeAlias alias="INotificationService" type="ContractsService.INotificationService,ContractsService"/>
      <typeAlias alias="NotificationService" type="Service.NotificationService,Service"/>
    </typeAliases>
    <containers>
      <container>
        <types>
          <!-- Alias tanımlaması ile mapping örneği -->
          <!-- INotificationService kullanılan yerlerde NotificationService tipinin ayağa kaldırılması için -->
          <type type="INotificationService" mapTo="NotificationService">
            <!-- NotificationService içindeki UserData isimli property'nin NotificationService ayağa kalktığında tanımlı mappingle ayağa kaldırılması için -->
            <property name="UserData" >
              <dependency />
            </property>
          </type>
          <!-- Alias tanımlaması olmadan yapılmış mapping örneği -->
          <type type="ContractsData.IUserData,ContractsData" mapTo="Data.UserData,Data">
            <!-- UserData ayağa kalktığında herhangi bir propertynin ayağa kalkmasını istemiyoruz.-->
          </type>
        </types>
      </container>
    </containers>
  </unity>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

Gördüğünüz gibi config tanımlaması çok zor değil. İsterseniz şimdi Unity’i nasıl kullanacağımızı görelim.

Öncelikle uygulamamızın içinden kullanabileceğimiz UnityHelper adında bir class yazalım.

    public static class UnityHelper
    {
        static UnityContainer container = new UnityContainer();
        static UnityHelper()
        {
            UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
            section.Configure(container);
        }
        public static object Resolve(Type key)
        {
            return container.Resolve(key);
        }
        public static T Resolve<T>()
        {
            return container.Resolve<T>();
        }
        public static T Resolve<T>(IDictionary args)
        {
            ResolverOverride[] ov = new ResolverOverride[args.Count];
            int i = 0;
            foreach (DictionaryEntry de in args)
            {
                ResolverOverride r = new ParameterOverride(de.Key.ToString(), de.Value);
                ov[i] = r;
                i++;
            }
            return container.Resolve<T>(ov);
        }
    }

Gördüğünüz gibi tüm işlemi UnityContainer yapıyor. Bu class’ın bir instance’ını aldıktan sonra static metodumuzun constructor’unda config dosyasından container’ımızın ayarlarını yapmasını sağlıyoruz. Sonrasında ise Resolve metodunu kullanarak istediğimiz tipe karşılık gelen tipi ayağa kaldırabiliyoruz. Burada resolve değeri parametre olarak bir tip alabilir, bir generic üzerinden çalışabilir veya ayağa kaldırmak istediğimiz nesnenin constructor’una geçireceğimiz parametreleri alabilir.

Şimdi ise class’larımızı aşağıdaki gibi yazalım.

namespace ContractData
{
    public interface IUserData
    {
        string[] GetUsers();
        string GetUserByEmail(string s);
    }
}
namespace ContractService
{
    public interface INotificationService
    {
        void NotifyAllUsers();
        void NotifyUserByEmail(string email);
        void SetDefaultEmailAddress(string email);
        string GetDefaultEmailAddress();
    }
}
namespace Data
{
    public class UserData : IUserData
    {
        public string[] GetUsers()
        {
            return new string[] { "User1", "User2" };
        }

        public string GetUserByEmail(string s)
        {
            return "User1";
        }
    }
}
namespace Service
{
    public class NotificationService : INotificationService
    {
        public IUserData UserData { get; set; }
        private string defaultEmailAddress = string.Empty;
        public NotificationService()
        {

        }
        public void NotifyAllUsers()
        {
            foreach (string s in this.UserData.GetUsers())
            {
                Console.WriteLine(s + " notified");
            }
        }

        public void NotifyUserByEmail(string email)
        {
            string s = this.UserData.GetUserByEmail(email);
            Console.WriteLine(s + " notified");
        }

        public void SetDefaultEmailAddress(string email)
        {
            defaultEmailAddress = email;
        }

        public string GetDefaultEmailAddress()
        {
            return defaultEmailAddress;
        }
    }
}
    public class UIComponent
    {
        public INotificationService NotificationService { get; set; }
        public UIComponent()
        {
            NotificationService = UI.UnityHelper.Resolve<INotificationService>();
        }
        public void NotifyAll()
        {
            this.NotificationService.NotifyAllUsers();
        }
        public void Notify(string email)
        {
            this.NotificationService.NotifyUserByEmail(email);
        }
    }

Bu class’lar yukarıda göreceğiniz proje hiyerarşisinde uygun projeler içinde yer almaktadır. Gördüğünüz gibi class’lar arasında direkt bir ilişki yok ve ilişki tamamen interface’ler üzerinden kurulmuştur. IOC ile nesnelerimizi ayağa kaldırmak için uygulamada bir yerden yazdığımız UnityHelper class’ının Resolve metodunu çağırmalıyız. Ben burda UI bileşeninin constructor’unu uygun gördüm. Gördüğünüz gibi tipi interface olan property’e hiç bir class referansı atanmıyor ancak resolve dedikten sonra mapping olarak tanımladığımız sınıfın instance’ını kullanabiliyoruz. Örnek kodları download kısmından indirebilirsiniz.

Şimdi bu sınıfı çağıralım

        static void Main(string[] args)
        {
            UI.UIComponent insUIComponent = new UI.UIComponent();
            insUIComponent.Notify("oztamer@hotmail.com");
            Console.Read();
        }

Şimdi uygulamamızı çalıştıralım. Aşağıdaki gibi bir hata alacağız.


Bunun sebebi UI projemizde Service, Data ve ContractData projelerinin referansının bulunmamasıdır. Bu projeleri projemize referans eklersek yine bir bağımlılık yaratmış olacağız işte dependency injection’ın güzelliği biraz da burada ortaya çıkıyor. Bu projelerin dll’lerini uygulamamızın exe’sinin olduğu klasöre veya web uygulaması ise bin klasörüne taşıyoruz ve uygulamamızı tekrar çalıştırıyoruz.

Uygulamamız sorunsuz çalıştı. Gördüğünüz gibi UI projemiz Service ve Data projemize runtime’da dependent olmaya başlıyor ve dependency enjekte ediliyor. Buradaki güzel avantajlardan bir tanesi ise tip tanımlamasını versiyon ve publickeytoken kullanarak yapmanız durumunda uygulamanızı tekrar build etmeden dll’leri değiştirebilmenizdir. Veya gördüğünüz gibi Service.dll’in yeni bir versiyonunu sadece kopyalamanız yetecektir.

Makalemin bundan sonraki kısımlarında singleton ve transient yapılar ile interceptor’u nasıl kullanabileceğimize bu örnek üzerinden değineceğim.

Kendi SessionManager’ımızı yazalım

Bu makalemizde hem web uygulamalarında hem de windows uygulamalarında ortak kullanabileceğimiz bir session manager yazacağız. İsterseniz öncelikle oluşturacağımız yapıyı inceleyelim.

 

Artık kodumuzu yazmaya başlayabiliriz. Öncelikle session manager için kullanacağımız interface’lerimizi oluşturalım.

public interface ISession<T>
    {
        T GetValue(string session);
        void SetValue(string session, T value);
        void Clear(string session);
        void Flush();
    }
    public interface ISessionKeyEnum<T>
    {
        T GetValue(Enum session);
        void SetValue(Enum session, T value);
        void Clear(Enum session);
    }
  

ISession interface’imizde kodumuzdan session üzerinde işlem yapmamıza yarayacak olan metodları oluşturuyoruz. Burada;

GetValue : Verdiğimiz key’e karşılık gelen T generic tipindeki session nesnesini döndüren metoddur.
SetValue : Verdiğimiz key’e T generic tipindeki value nesnesini atayan metoddur.
Clear : Verdiğimiz key’in değerini sıfırlayan metoddur.
Flush : Session’da bulunan tüm değerleri sıfırlayan metoddur.

ISessionKeyEnum interface’imiz kodumuzda session nesnelerine ulaşmak için gerekli key’lerin string olarak hatalı yazmamızı engellemek üzere ISesion’da bulunan tüm metodların bir örneğinin bir enum ile çalışması için tasarlanmıştır. Bu interface’i implement eden Session nesneleri enum ile çalışabilecektir.

Şimdi session nesnemizi oluşturalım.

    public abstract class Session<T> : ISession<T>, ISessionKeyEnum<T>
    {
        #region ISession<T> Members
        public abstract T GetValue(string session);
        public abstract void SetValue(string session, T value);
        public abstract void Clear(string session);
        public abstract void Flush();
        #endregion 

        #region ISessionKeyEnumable<T> Members
        public T GetValue(Enum session)
        {
            return GetValue(session.ToString());
        }
        public void SetValue(Enum session, T value)
        {
            SetValue(session.ToString(), value);
        }
        public void Clear(Enum session)
        {
            Clear(session.ToString());
        }
        #endregion
    }
  

Öncelikle bir abstract session nesnesi oluşturuyoruz. Abstract class oluşturma sebebimiz ISessionKeyEnum interface’inde bulunan metodların her session sınıfı için ortak işlem yapacak olmasıdır. Dolayısıyla bu interface’de bulunan metodları oluşturduğumuz her session nesnesi için tekrar tekrar yazmak zorunda kalmayacağız. ISession interface’inde bulunan metodlar her session nesnesi için farklı şekilde çalışacağından bu metodları da abstract olarak tanımlıyoruz.

Şimdi Session nesnelerimizi bu abstract class’dan inherit ederek oluşturalım.

    internal class WebSession<T> : Session<T>
    {
        #region ISessionManager<T> Members
        public override T GetValue(string session)
        {
            object o = System.Web.HttpContext.Current.Session[session];
            if (o == null)
            {
                return default(T);
            }
            return (T)o;
        }
        public override void SetValue(string session, T value)
        {
            System.Web.HttpContext.Current.Session[session] = value;
        }
        public override void Clear(string session)
        {
            System.Web.HttpContext.Current.Session[session] = null;
        }
        public override void Flush()
        {
            System.Web.HttpContext.Current.Session.Abandon();
        }
        #endregion
    }
    internal class WindowsSession<T> : Session<T>
    {
        #region ISession<T> Members
        public override T GetValue(string session)
        {
            return (T)AppDomain.CurrentDomain.GetData(session);
        }
        public override void SetValue(string session, T value)
        {
            AppDomain.CurrentDomain.SetData(session, value);
        }
        public override void Clear(string session)
        {
            AppDomain.CurrentDomain.SetData(session, null);
        }
        public override void Flush()
        {
            throw new NotImplementedException();
        }
        #endregion
    }
  

Gördüğünüz gibi iki tane session nesnesi oluşturduk. Bunlardan biri WindowsSession diğeri ise WebSession isminde ve her ikisi de abstract Session nesnesini iherit ediyor. Her iki nesnenin de içerisinde farklı şekilde yazılmış kodları görüyoruz. WebSession nesnesi tüm işlemleri System.Web.HttpContext.Current.Session nesnesi üzerinden yaparken WindowsSession nesnesi AppDomain.CurrentDomain’i kullanıyor. Bu nesnelerin internal olamalarının sebepleri kendi assembly’leri dışında kullanılmasını istemediğimizden. Yani bu assembly’mizi dll olarak verdiğimiz kullanıcılar hiçbir zaman bu nesneleri inherit edemeyecek ve yeni instance alamayacak.

Şimdi isterseniz SessionManager classımızı yazalım.

    public static class SessionManager<T>
    {
        private static bool IsWebApplication()
        {
            return (System.Web.HttpContext.Current != null);
        }
        public static Session<T> Session
        {
            get
            {
                if (IsWebApplication())
                {
                    return new WebSession<T>();
                }
                else
                {
                    return new WindowsSession<T>();
                }
            }
        }
    }
  

SessionManager nesnemiz esasında çok basit bir çalışma prensibine mevcut. Statik bir nesne ve kullanıcı Session property’sine eriştiği zaman kullanıcının HttpContext’i olup olmadığına bakılarak bir WebSession nesnesi veya bir WindowsSession nesnesi kullanıcıya döndürülüyor. Bu iki nesne Session abstract class’ından inherit olduğundan property’nin dönüş tipi Session olabiliyor ve kullanıcılar bu abstract sınıf içerisinde bulunan metodları çağırabiliyorlar ancak geri döndürdüğümüz nesne tipine göre çalışma mantığı farklı oluyor.

Şimdi isterseniz bir örnek yapalım. Aşağıdaki kodları hem windows hem web uygulamanızda hiçbir değişiklik yapmadan kullanabilirsiniz.

        public enum SessionKeys
        {
            UserName,
            DateOfBirth,
            Company,
        }
        public class Company
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }
        static void Main(string[] args)
        {
            //session'a bir deger atiyoruz. Atayacagimiz degerin string tipinden oldugunu generic tipte belirtiyoruz ve SetValue metodunu kullanarak deger atiyoruz.
            SessionManager<string>.Session.SetValue(SessionKeys.UserName, "Yazgeliştir"); 

            //degeri session'dan okumak icin GetValue metodunu kullaniyoruz.
            string s = SessionManager<string>.Session.GetValue(SessionKeys.UserName); 

            //Ayni sekilde nesneleri de session'a atamak ve sessiondan okumak mumkun
            Company c = new Company { Id = 1, Name = "Yazgeliştir A.Ş." };
            SessionManager<Company>.Session.SetValue(SessionKeys.Company, c);
            Company c1 = SessionManager<Company>.Session.GetValue(SessionKeys.Company); 

            Console.Read();
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            //session'a bir deger atiyoruz. Atayacagimiz degerin string tipinden oldugunu generic tipte belirtiyoruz ve SetValue metodunu kullanarak deger atiyoruz.
            SessionManager<string>.Session.SetValue(SessionKeys.UserName, "Yazgeliştir"); 

            //degeri session'dan okumak icin GetValue metodunu kullaniyoruz.
            string s = SessionManager<string>.Session.GetValue(SessionKeys.UserName); 

            //Ayni sekilde nesneleri de session'a atamak ve sessiondan okumak mumkun
            Company c = new Company { Id = 1, Name = "Yazgeliştir A.Ş." };
            SessionManager<Company>.Session.SetValue(SessionKeys.Company, c);
            Company c1 = SessionManager<Company>.Session.GetValue(SessionKeys.Company);
        }
  

Gördüğünüz gibi kodumuzu hiçbir değişiklik yapmadan hem web hem de windows uygulamalarında kullanabiliyoruz. Ayrıca ISession ve ISessionEnumKey veya Session nesnelerini kullanarak kendi session erişim yöntemlerimizi yazabiliriz. Örneğin session’un herhangi bir veri kaynağında saklanmasını kolaylıkla implement edebiliriz.

Umarım faydalı olmuştur.
Tamer ÖZ
oztamer@hotmail.com

Kendi ResourceManager’ımızı yazalım

Bu makalemde kendi ResourceManager’ımızı nasıl yazabileceğimizi inceleyeceğiz. Yazacağımız resource manager varsayılan olarak xml, veritabanı, resx veya bir class dosyasından resource’ları okuyor olabilecek. Ayrıca sağlayacağımız interface’leri implement ederek diğer yazılım geliştiriciler yeni resource tipleri oluşturabilecek. Dolayısıyla genişletilebilir bir yapı hazırlayacağız ve tüm bunların yanısıra config dosyasından ayarlanabiliyor olacak. Bu makalede ayrıca kendi config section’ımızı nasıl yazabileceğimizi de inceleyeceğiz.

Öncelikle oluşturacağımız yapının nasıl bir yapı olacağını inceleyelim.

Şimdi isterseniz kodumuzu yazmaya başlayalım. Öncelikle Resource dosyalarımızın çalışması için gerekli configuration section’u yazalım.

public sealed class ResourcesConfig : ConfigurationSection
    {

        [ConfigurationProperty("Resources", IsRequired = true, IsDefaultCollection = false)]
        public ResourceConfigElementCollection Resources
        {
            get { return (ResourceConfigElementCollection)this["Resources"]; }
            set { this["Resources"] = value; }
        }
        [ConfigurationProperty("Languages", IsRequired = true, IsDefaultCollection = false)]
        public LanguageConfigElementCollection Languages
        {
            get { return (LanguageConfigElementCollection)this["Languages"]; }
            set { this["Languages"] = value; }
        }

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

Öncelikle ResourcesConfig ismindeki config section’umuzu oluşturuyoruz. Bir class’ı config section olarak kullanmak için ConfigurationSection’dan nesnesinden inherit etmek gerekiyor. Section nesnemizin içine olası Resource tipleri için ResourceConfigElementCollection, olası dil tanımlamaları için LanguageConfigElementCollection ve kullanılacak varsayılan resporce tipi için DefaultResourceName isimlerinde property’ler oluşturuyoruz. Gördüğünüz gibi bu property’lere ConfigurationProperty attribute’ını tanımlamak gerekiyor ve bu tanımlamanın içinde config dosyasında kullanıclacak isim ve zorunlu bir property olup olmadığı belirtiliyor.

Şimdi isterseniz bu property’lerin taşıdıkları tipleri oluşturalım.

public class ResourceConfigElementCollection : ConfigurationElementCollection
        {
            protected override ConfigurationElement CreateNewElement()
            {
                return new ResourceConfigElement();
            }
            protected override object GetElementKey(ConfigurationElement element)
            {
                return ((ResourceConfigElement)element).Name;
            }
        }
        public class LanguageConfigElementCollection : ConfigurationElementCollection
        {
            protected override ConfigurationElement CreateNewElement()
            {
                return new LanguageConfigElement();
            }
            protected override object GetElementKey(ConfigurationElement element)
            {
                return ((LanguageConfigElement)element).Name;
            }
        }

Dikkat edeceğiniz üzere Collection nesnelerinin config section üzerinde property’ler olarak oluşturmak için ConfigurationElementCollection nesnesinden inherit etmek gerekmektedir. İnherit ettiğinizde CreateNewElement ve GetElementKey metodlarını override etmek gerekiyor.

Bu işlemleri tanımladıktan sonra isterseniz resource tiplerimizi ve dil tiplerimizi tanımlamamıza yardım edecek olan ResourceConfigElement ve LanguageConfigElement nesnelerimizi oluşturalım.

public class LanguageConfigElement : ConfigurationElement
        {
            [ConfigurationProperty("Id", IsRequired = true)]
            public string Id
            {
                get { return base["Id"].ToString(); }
                set { base["Id"] = value; }
            }
            [ConfigurationProperty("Name", IsRequired = true)]
            public string Name
            {
                get { return base["Name"].ToString(); }
                set { base["Name"] = value; }
            }
            [ConfigurationProperty("Culture", IsRequired = true)]
            public string Culture
            {
                get { return base["Culture"].ToString(); }
                set { base["Culture"] = value; }
            }
        }
        public class ResourceConfigElement : ConfigurationElement
        {
            [ConfigurationProperty("Name", IsRequired = true)]
            public string Name
            {
                get { return base["Name"].ToString(); }
                set { base["Name"] = value; }
            }

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

            [ConfigurationProperty("ResxConfig", IsRequired = false)]
            public ResxConfigElement ResxConfig
            {
                get { return (ResxConfigElement)base["ResxConfig"]; }
                set { base["ResxConfig"] = value; }
            }

            [ConfigurationProperty("ClassConfig", IsRequired = false)]
            public ClassConfigElement ClassConfig
            {
                get { return (ClassConfigElement)base["ClassConfig"]; }
                set { base["ClassConfig"] = value; }
            }

            [ConfigurationProperty("DbConfig", IsRequired = false)]
            public DbConfigElement DbConfig
            {
                get { return (DbConfigElement)base["DbConfig"]; }
                set { base["DbConfig"] = value; }
            }

            [ConfigurationProperty("XmlConfig", IsRequired = false)]
            public XmlConfigElement XmlConfig
            {
                get { return (XmlConfigElement)base["XmlConfig"]; }
                set { base["XmlConfig"] = value; }
            }

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

        }

LanguageConfigElement ve ResourceConfigElement nesnelerimizi ilk kısımda olduğu gibi ConfigElement nesnesinden inherit ederek oluşturduk isterseniz şimdi içlerindeki property’lerde ne gibi bilgiler taşıyacağımızı inceleyelim.

LanguageConfigElement:
Id(string) : Oluşturulacak dil girdisinin id’si. Örn : 1
Name(string) : Oluşturulacak dil girdisinin adı. Örn: Türkçe
Culture(string) : Oluşturulacak dil girdisinin culture’ı. Örn : tr-TR

ResourceConfigElement:
Name(string):Oluşturulacak resource girdisinin adı. Örn:HataMesajlariXml
ResourceType(string) : Bu girdi için oluşturulacak Resource tipi. Örn : ClassResource
ResxConfıg(ResxConfigElement) : Tipin ResxResource olarak oluşturulması durumunda ayarların yapılacağı nesne.
ClassConfıg(ClassConfigElement): Tipin ClassResource olarak oluşturulması durumunda ayarların yapılacağı nesne.
DbConfıg(DbConfigElement): Tipin DbResource olarak oluşturulması durumunda ayarların yapılacağı nesne.
XmlConfıg(XmlConfigElement): Tipin XmlResource olarak oluşturulması durumunda ayarların yapılacağı nesne.
DefaultLanguageId(string) : Bu resource için resource’lara ulaşılırken spesifik bir dil belirtilmez ise kullanılacak varsayılan dil.

Şimdi yukarıda belirttiğimiz resource tipleri için elemanları tek tek oluşturalım.

public class ResxConfigElement : ConfigurationElement
        {
            [ConfigurationProperty("Type", IsRequired = true)]
            public string Type
            {
                get { return base["Type"].ToString(); }
                set { base["Type"] = value; }
            }
        }

Bir resx dosyasından değerlerimizi okumak için tek ihtiyacımız olan bu resource dosyasının tipidir. Dolayısıyla ResxConfigElement için sadece Type isminde bir property tanımlamak yeterlidir.

        public class ClassConfigElement : ConfigurationElement
        {
            [ConfigurationProperty("Type", IsRequired = true)]
            public string Type
            {
                get { return base["Type"].ToString(); }
                set { base["Type"] = value; }
            }
            [ConfigurationProperty("LanguageSeperator", IsRequired = true)]
            public string LanguageSeperator
            {
                get { return base["LanguageSeperator"].ToString(); }
                set { base["LanguageSeperator"] = value; }
            }
        }

Bir class’dan değerlerinizi okumak için bu class’ın tipine ve class içerisinde bulunan bir değişkenin farklı diller için nasıl tanımlandığını belirten bir dil ayıracı bilgisine ihtiyacımız olacaktır.

Örnek bir class dosyası :

    public static class Resource
    {
        public  const string Password_1="Password c";
        public  const string Password_2="Sifre c";
    }

Bu yapıda, Type alanında class’ın tipi yani NameSpace.Resource,NameSpace LanguageSeperator kisminda ise _ bulunacaktır.

        public class DbConfigElement : ConfigurationElement
        {
            [ConfigurationProperty("Provider", IsRequired = true)]
            public string Provider
            {
                get { return base["Provider"].ToString(); }
                set { base["Provider"] = value; }
            }

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

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

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

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

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

Bir veritabanından değerleri okumak için veritabanına hangi provider ile bağlanacağımız, connection string, tablo adı, resource’ların key’inin durduğu alanın adı, dil bilgisinin durduğu alanın adı ve değerin durduğu alanın adı bilgilerine ihtiyacımız olacaktır.

Örnek Veritabanı:


Bu durumda TableName:Resource, KeyFieldName : Key, LanguageFieldName : Language, ValueFieldName ise Value olarak belirlenmelidir.

        public class XmlConfigElement : ConfigurationElement
        {
            [ConfigurationProperty("XmlPath", IsRequired = true)]
            public string XmlPath
            {
                get { return (string)base["XmlPath"]; }
                set { base["XmlPath"] = value; }
            }
            [ConfigurationProperty("KeyAttribute", IsRequired = true)]
            public string KeyAttribute
            {
                get { return (string)base["KeyAttribute"]; }
                set { base["KeyAttribute"] = value; }
            }
            [ConfigurationProperty("LanguageAttribute", IsRequired = true)]
            public string LanguageAttribute
            {
                get { return (string)base["LanguageAttribute"]; }
                set { base["LanguageAttribute"] = value; }
            }
        }
    }
}

Bir xml dosyasından değerleri okumak için xml dosyasının yoluna ve resource adının ve dil tipinin tutulduğu attribute’ların adına ihtiyacımız var.

Örnek bir xml dosyası:

<?xml version="1.0" encoding="utf-8" ?>
<Resources>
  <Resource Key="UserName">
    <Language Id="1">User Name</Language>
    <Language Id="2">Kullanici Adi</Language>
  </Resource>
  <Resource Key="Password">
    <Language Id="1">Password</Language>
    <Language Id="2">Sifre</Language>
  </Resource>
</Resources>

Bu durumda KeyAttribute : Key ve LanguageAttribute ise Id olarak tanımlanmalıdır.

Resource Manager’ımızın config section ayarlarını yaptık. Örnek bir config dosyasını aşağıdaki gibi oluşturabiliriz.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="ResourceConfig" type="Infra.ResourcesConfig,Infra"/>
  </configSections>
  <ResourceConfig DefaultResourceName="Xml1">
    <Languages>
      <add Id="1" Name="English" Culture="en-US"></add>
      <add Id="2" Name="Turkish" Culture="tr-TR"></add>
    </Languages>
    <Resources>
      <add Name="Xml1" ResourceType="Infra.XmlResource,Infra" DefaultLanguageId="1">
        <XmlConfig XmlPath="ResourceDeneme.Xml" KeyAttribute="Key" LanguageAttribute="Id"></XmlConfig>
      </add>
      <add Name="Xml2" ResourceType="Infra.XmlResource,Infra" DefaultLanguageId="1">
        <XmlConfig XmlPath="ResourceDeneme2.Xml" KeyAttribute="Key" LanguageAttribute="Id"></XmlConfig>
      </add>
      <add Name="Class1" ResourceType="Infra.ClassResource,Infra" DefaultLanguageId="1">
        <ClassConfig Type="WindowsFormsApplication1.Resource,WindowsFormsApplication1" LanguageSeperator="_" ></ClassConfig>
      </add>
      <add Name="Resx1" ResourceType="Infra.ResxResource,Infra" DefaultLanguageId="1">
        <ResxConfig Type="WindowsFormsApplication1.Properties.Resources,WindowsFormsApplication1"></ResxConfig>
      </add>
      <add Name="Db1" ResourceType="Infra.DbResource,Infra" DefaultLanguageId="1">
        <DbConfig Provider="System.Data.SqlClient" ConnectionString="Data Source=.;IntegratedSecurity=SSPI" TableName="Resource" KeyFieldName="Key" LanguageFieldName="Language" ValueFieldName="Value"></DbConfig>
      </add>
    </Resources>
  </ResourceConfig>
</configuration>

Gördüğünüz gibi config dosyamıza birden fazla resource tipi tanımlayabiliyoruz.

Şimdi isterseniz config dosyamızdan okuyacağımız bu resource tiplerinin ResourceManager ile nasıl kullanacağımızı inceleyelim. İşe temelden başlamak gerekirse öncelikle kullanacağımız interface’leri inceleyelim.

      public interface IResource
    {
        Infra.ResourcesConfig.ResourceConfigElement Config { get; set; }
        string GetString(string key);
        string GetString(string key, string language);
    }
    public interface IResourceConfig<T> where T:ConfigurationElement
    {
        T ResourceConfig
        {
            get;
        }
    }

Uygulamamızda iki tane interface bulunuyor bunlardan biri resource tiplerimizi oluştururken kullanacağımız IResource tipinde ve diğer ise config dosyasından bir resource ile ilgili bilgileri okurken kullanacağımız IResourceConfig interface’i.

IResourceConfig interface’inde bulunan ResourceConfig Property’si ile bu interface’e generic olarak verdiğimiz ConfigurationElement tipinden nesnelere ulaşabileceğiz.

IResource interface’inde bulunan Config property’si ise tüm config bilgilerine ulaşabileceğiz.
IResource interface’inde bulunan GetString metodlarının içerisine ise resource kaynağımızdan belirttiğimiz key ve language değişkenlerine göre değer okumak için gerekli kodu yazacağız.

Şimdi isterseniz bu interfacelerimizi implement edeceğimiz class’larımıza geçelim.

   public class ClassResource : IResource, IResourceConfig<Infra.ResourcesConfig.ClassConfigElement>
    {
        #region IResource Members
        public string GetString(string key)
        {
            return GetString(key, Config.DefaultLanguageId);
        } 

        public string GetString(string key, string language)
        {
            Type t = Type.GetType(ResourceConfig.Type);
            if (t == null)
            {
                throw new Exception("Type can not be found");
            }
            FieldInfo fi = t.GetField(key + ResourceConfig.LanguageSeperator + language);
            if (fi==null)
            {
                throw new Exception("Key Can not be found");
            }
            return fi.GetValue(null).ToString();
        }
        public ResourcesConfig.ResourceConfigElement Config
        {
            get;
            set;
        }
        #endregion 

        #region IResourceConfig<ClassConfigElement> Members 

        public ResourcesConfig.ClassConfigElement ResourceConfig
        {
            get
            {
                return this.Config.ClassConfig;
            }
        } 

        #endregion
    }

Gördüğünüz gibi ClassResource nesnemizi IResource interface’inden ve IResourceConfig interface’inden implement ediyoruz. IResourceConfıg interface’ine ise generic olarak ClassConfigElement’i veriyoruz. Buda bu class’ımızı içerisinde ResourceConfig dediğimizde bize ClassConfigElement tipinden bir nesne geleceğini belirtiyor.

GetString metodlarını ineleyecek olursak sadece key alan overload’ın diğer overload’ı Config dosyasında tanımladığımız DefaultLanguage değerini okuyarak çağırdığını görüyoruz.

GetString metodunun 2. overloadında ise reflection ile class içerisinden değerlerin okunduğunu görüyoruz. Oluşturacağımız her Resource nesnesinde GetString metodunun 2. overload’ı farklı bir şekilde implement edilecektir. Bu implementasyonlar makalemizin konusu olmadığından tek tek anlatmayacağım ama kodu burada paylaşacağım.

Şimdi isterseniz diğer Resource nesnelerimizi oluşturalım.

  public class DbResource : IResource, IResourceConfig<Infra.ResourcesConfig.DbConfigElement>
    {
        DbProviderFactory insDbProviderFactory = null;
        #region IResource Members 

        public string GetString(string key)
        {
            return GetString(key, Config.DefaultLanguageId);
        }
        public string GetString(string key, string language)
        {
            if (insDbProviderFactory == null)
            {
                insDbProviderFactory = DbProviderFactories.GetFactory(ResourceConfig.Provider);
            }
            DbConnection conn = insDbProviderFactory.CreateConnection();
            conn.ConnectionString=ResourceConfig.ConnectionString;
            DbCommand cmd = insDbProviderFactory.CreateCommand();
            cmd.CommandText = "SELECT [" + ResourceConfig.ValueFieldName + "] From [" + ResourceConfig.TableName + "] WHERE [" + ResourceConfig.KeyFieldName + "]=@Key AND [" + ResourceConfig.LanguageFieldName + "]=@Language";
            DbParameter paramKey = insDbProviderFactory.CreateParameter();
            paramKey.DbType = System.Data.DbType.String;
            paramKey.Direction = System.Data.ParameterDirection.Input;
            paramKey.ParameterName = "@Key";
            paramKey.Value = key; 

            DbParameter paramLanguage = insDbProviderFactory.CreateParameter();
            paramLanguage.DbType = System.Data.DbType.String;
            paramLanguage.Direction = System.Data.ParameterDirection.Input;
            paramLanguage.ParameterName = "@Language";
            paramLanguage.Value = language; 

            cmd.Parameters.Add(paramKey);
            cmd.Parameters.Add(paramLanguage);
            cmd.Connection = conn; 

            string result = "";
            try
            {
                conn.Open();
                result = cmd.ExecuteScalar().ToString(); ;
                conn.Close();
            }
            catch (Exception)
            {
                conn.Close();
                throw;
            }
            return result; 

        }
        public ResourcesConfig.ResourceConfigElement Config
        {
            get;
            set;
        }
        #endregion 

        #region IResourceConfig<DbConfigElement> Members 

        public ResourcesConfig.DbConfigElement ResourceConfig
        {
            get
            {
                return this.Config.DbConfig;
            }
        } 

        #endregion
    }
    public class ResxResource : IResource, IResourceConfig<Infra.ResourcesConfig.ResxConfigElement>
    {
        #region IResource Members 

        public string GetString(string key)
        {
            return GetString(key, Config.DefaultLanguageId);
        } 

        public string GetString(string key, string language)
        {
            string culture = ResourceManager.GetLanguageCulture(language);
            Type t=Type.GetType(ResourceConfig.Type);
            System.Resources.ResourceManager rm = new System.Resources.ResourceManager(t);
            return rm.GetString(key,CultureInfo.GetCultureInfo(culture));
        }
        public ResourcesConfig.ResourceConfigElement Config
        {
            get;
            set;
        }
        #endregion 

        #region IResourceConfig<ResxConfigElement> Members 

        public ResourcesConfig.ResxConfigElement ResourceConfig
        {
            get
            {
                return this.Config.ResxConfig;
            }
        } 

        #endregion
    }
    public class XmlResource : IResource, IResourceConfig<Infra.ResourcesConfig.XmlConfigElement>
    {
        XDocument doc = null;
        #region IResource Members
        public string GetString(string key)
        {
            return GetString(key, Config.DefaultLanguageId);
        }
        public string GetString(string key, string language)
        {
            if (doc == null)
            {
                doc = XDocument.Load(ResourceConfig.XmlPath);
            }
            var result = doc.Descendants("Resource")
                .Where(e => e.Attribute(ResourceConfig.KeyAttribute).Value == key)
                .Elements().Where(e=>e.Attribute (ResourceConfig.LanguageAttribute).Value==language);
            return result.FirstOrDefault().Value;
        } 

        public ResourcesConfig.ResourceConfigElement Config
        {
            get;set;
        } 

        #endregion 

        #region IResourceConfig<XmlConfigElement> Members 

        public ResourcesConfig.XmlConfigElement ResourceConfig
        {
            get
            {
                return this.Config.XmlConfig;
            }
        } 

        #endregion
    }

Şimdi isterseniz tüm bu yazdıklarımızı wrap edecek olan ResourceManager sınıfımızı yazalım.

    public static class ResourceManager
    { 

        private static ResourcesConfig _Config = null;
        public static ResourcesConfig Config
        {
            get
            { 

                try
                {
                    if (_Config == null)
                    {
                        ResourcesConfig config = null;
                        foreach (ConfigurationSection cs in System.Configuration.ConfigurationManager.OpenExeConfiguration (System.Configuration.ConfigurationUserLevel.None).Sections)
                        {
                            if (cs.ElementInformation.Type == typeof(ResourcesConfig))
                            {
                                config = (ResourcesConfig)System.Configuration.ConfigurationManager.GetSection (cs.SectionInformation.Name);
                                break;
                            }
                        }
                        if (config == null)
                        {
                            throw new Exception("Resource configuration not found.");
                        }
                        _Config = config;
                    }
                    return _Config;
                }
                catch (Exception ex)
                { 

                    throw;
                }
            }
        }

ResourceManager class’ımızı static olarak oluşturuyoruz ve içerisine Config tipinde private bir değişken ve bu değişkeni encapsulate eden readonly bir property yazıyoruz. Bu property’mizin get metodunda ise daha önceden bir config nesnesi oluşturulmamızsa config dosyasındaki bilgileri okuyarak bir config nesnesi oluşturuyoruz ve oluşturduğumuz nesneyi private değişkene atıyoruz.. Bu property’e daha sonraki erişmelerimizde config dosyasına erişilmeyecek değişkende sakladığımız config nesnesi kullanılacaktır. Dolayısıyla bir performans kazancı sağlanacaktır.

               private static Dictionary<string, IResource> Resources = new Dictionary<string, IResource>();

        public static string GetLanguageCulture(string languageId)
        {
            return Config.Languages.OfType<Infra.ResourcesConfig.LanguageConfigElement>().Where(l => l.Id == languageId).FirstOrDefault().Culture;
        }
        private static IResource GetResource(string name)
        {
            Type t = Type.GetType(name);
            if (t == null)
            {
                throw new Exception("Resource specified can not be found");
            }
            if (t.GetInterface("IResource") == null)
            {
                throw new Exception("Resource specified should implement IResource");
            }
            return (IResource)Activator.CreateInstance(t);
        }
        public static IResource Resource()
        {
            return Resource(Config.DefaultResourceName);
        }
        public static IResource Resource(string resourceName)
        {
            if (!Resources.ContainsKey(resourceName))
            {
                Infra.ResourcesConfig.ResourceConfigElement cfg = Config.Resources.OfType<Infra.ResourcesConfig.ResourceConfigElement>().Where(r => r.Name == resourceName).FirstOrDefault();
                Resources[resourceName] = GetResource(cfg.ResourceType);
                Resources[resourceName].Config = cfg;
            }
            return Resources[resourceName];
        }
    }

Config dosyasında tanımladığımız tüm resource’ları saklayabileceğimiz bir private değişken oluşturuyoruz ve adına Resources diyoruz.

GetResource metodunda ise string olarak verilen tipteki bir resource nesnemizi oluşturuyoruz. Burada önemli bir nokta bu metodun oluşturabileceği tüm resource nesnelerinin IResource interface’inden türemiş olması gerekliliğidir.

Resource metodlarında ise erişmek istediğimiz bir resource daha önceden oluşturulmuşsa bu nesneyi alıyor ve kullanıyoruz. Daha önceden oluşturulmamış ise GetResource metodu ile resource’umuzu oluşturuyoruz ve bu nesneyi kullanıyoruz.

Şimdi isterseniz resourcemanager nesnemizi nasıl kullanabileceğimizi inceleyelim. Config olarak yukarıda örnek verdiğimiz config’i kullandığımızı düşünürsek;

            //Varsayilan resource'a erismek. Varsayilan resource config de ResourceConfig nesnesinde DefaultResourceName property'sinde tutulmaktadır.
            Infra.IResource resource = Infra.ResourceManager.Resource(); 

            //Herhangi bir resource'a ResourceName kullanarak erismek. ResourceName Config dosyasinda resources collectionunda tanimlanmis resourcelarda bulunan name property'sinde bulunur.
            resource = Infra.ResourceManager.Resource("XML1"); 

            //Bir resourcedeki bir key icin varsayilan dil bilgisi degerini almak. Varsayilan dil bilgisi kullanilan resource icin DefaultLanguageId property'sinde bulunan degerdir
            string s = resource.GetString("UserName"); 

            //Bir resourcedaki bir key icin herhangi bir dil bilgisini almak. Dil parametresinde config dosyasindaki languages collection'unda ilgili dil icin bulunan Id degeri gecirilmelidir.
            s = resource.GetString("UserName", "2");

Siz de IResource ve IResourceConfig interfacelerini implement ederek kendi resource class’larınızın yazmış olduğumuz ResourceManager ile çalışmasını sağlayabilirsiniz.

Umarım faydalı olmuştur.
Tamer ÖZ
oztamer@hotmail.com
 

Nesnelerimize Çoklu Dil Desteği Eklemek

Merhabalar,

Artık yazılan uygulamalarda lokalizasyon özelliklerinin kullanılması yavaş yavaş kaçınılmaz duruma gelmektedir. Dolayısıyla uygulama içerisinde kullandığımız bazı lokalize etmemiz gereken metinler vs resx dosyalarından okuyarak bu işlemi kolaylıkla yapabilmekteyiz. Ancak bu durum bir veritabanı veya data kaynağından gelecek dataların lokalizasyonunda farklı olacaktır. Günümüzdeki duruma bir örnek üzerinden baktığımızda genellikle uygulanan yöntem aşağıdaki gibi bir dizayn ile bu sorunu çözmektir.

Dizayna baktığımızda bir nesneyin bir özelliğini farklı dillerde kullanabilmek için farklı bir nesne oluşturuyoruz ve bu nesneyi ana nesne içerisine list olarak ekliyoruz. Ancak uygulamamızda bu durumda birden fazla nesne olması durumunda nesne sayımız artacak ve gereksiz birçok kod yazmak zorunda kalacağız. Tüm bunların yanısıra bir orm aracı kullanıyorsak birçok mapping yapmamız gerekecek.

Bu yöntemin yerine nesnelerin çoklu dil desteği olan özelliklerine yönelik bir geliştirme yapılabilir. Bunun modelini aşağıdaki gibi kurduğumuzu düşünelim.

Gördüğünüz gibi bu yöntem hem daha anlaşılabilir hem de daha genel bir tasarım içermektedir. Çoklu dil desteği olan birçok nesnenin birçok özelliği için yukarıda gördüğünüz MultilanguageProperty nesnesini kullanabilirsiniz.

Şimdi isterseniz bu gibi bir nesneyi nasıl yazacağımızı inceleyelim.

Öncelikle MultilanguageProperty nesnemizin içerisinde kullanacağımız Language class’ını oluşturalım.

 

   public class Language
    {
        public enum ApplicationLanguage
        {
            Turkish,
            English,
        }
        public static ApplicationLanguage GetDefaultLanguage()
        {
            return ApplicationLanguage.Turkish;
        }
    }

Gördüğünüz gibi Language nesnemizi oluştururken içerisinde bir enum’da kullanacağımız dilleri tanımlıyoruz ve uygulamamızda kullanılacak varsayılan dili tanımlıyoruz. Burada varsayılan dili web uygulamalarında bir session değişkeninden, windows uygulamalarında ise static bir değişkenden okuyarak uygulamanızın kullanıcı basında varsayılan dil kullanmasını sağlayabilirsiniz.

Artık MultilanguageProperty nesnemizi kodlamaya başlayabiliriz.

    public class MultilanguageProperty<T>:IEnumerable<KeyValuePair<Language.ApplicationLanguage,T>>
    {
        public IDictionary<Language.ApplicationLanguage, T> Values { get; set; }
        public MultilanguageProperty()
        {
            Values = new Dictionary<Language.ApplicationLanguage, T>();
        }

Öncelikle nesnemizi string, int, datetime veya herhangi başka bir tipteki özelliklerin yerine kullanabilmemiz için generic bir nesne olarak tanımlıyoruz. Ve kullanıcıların bu property içerisindeki birçok değeri almak için değerler arasında dönebilmesini sağlamak amacıyla IEnumerable interface’ini implement ediyoruz.

Ayrıca nesnemizde IDictionary tipinden values adında bir property tanımlıyoruz. IDictionary generic tipine baktığımızda bu nesnenin key olarak ApplicationLanguage enum’unu ve karşılığında value olarak tanıttığımız generic T tipini taşıdığını görüyoruz. Bu property bizim çokludil değerlerimizi saklayacak olan property’dir. Ve nesnemizin yapıcı metodu içerisinde bu property’e Dictionary nesnesinin yeni bir instance’ını alarak atıyoruz.

Şimdi Values property’sinde sakladığımız değerlere nasıl ulaşabileceğimizi inceleyelim.

        public T GetValue(Language.ApplicationLanguage language)
        {
            return (T)Values[language];
        }
        public T GetValue()
        {
            Language.ApplicationLanguage applicationLanguage = Language.GetDefaultLanguage();
            return (T)Values[applicationLanguage];
        }
        public T this[Language.ApplicationLanguage l]
        {
            get
            {
                return Values[l];
            }
            set
            {
                Values[l] = value;
            }
        }

Sakladığımız değerlere ulaşmak için GetValue adındaki metodumuzu kullanıyoruz. Gördüğünüz gibi değerlere ulaştığımızda metodların bize geridönüş tipi generic tiptendir. GetValue metodunun iki tane overload’ı vardır. Bunlardan biri belirtilen dildeki değeri vermek için diğeri ise biraz önce Language class’ında implemantasyonunu yaptığımız varsayılan dildeki değeri döndürmesi içindir. Ayrıca indexer kullanarak kullanıcıların direk olarak property üzerinden indexer kullanarak istedikleri dildeki değeri almalarını sağlayabiliyoruz.

Şimdi ise kodumuzu kullanacak kişiler için farklı bir kolaylık sağlayacağız. Implicit operatörler tanımlayarak kullanıcıların bir tip çevirme yapmadan nesnemizi kullanabilmeleri için aşağıdaki kodu nesnemize ekliyoruz.

        public static implicit operator T(MultilanguageProperty<T> instance)
        {
            if (instance == null)
            {
                return default(T);
            }
            return instance.GetValue();
        }
        public static implicit operator List<KeyValuePair<Language.ApplicationLanguage,T>>(MultilanguageProperty<T> instance)
        {
            if (instance == null)
            {
                return null;
            }
            return instance.Values.ToList();
        }

Gördüğünüz gibi ilk metodda nesnemizin tanıttığımız generic T tipindeki bir değişkene direkt atanabilmesi için implicit conversation operatörümüzü yazıyoruz ve ikinci metodda ise nesnemizin bir List değişkenine atanabilmesi için gerekli kodumuzu yazıyoruz. Bu kodların kullanımda nasıl bir kolaylık sağladığını makalemizin sonunda göreceğiz.

       #region IEnumerable<KeyValuePair<ApplicationLanguage,T>> Members

        public IEnumerator<KeyValuePair<Language.ApplicationLanguage, T>> GetEnumerator()
        {
            return Values.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Values.GetEnumerator();
        }

        #endregion
    }
}

Ve en son olarak kullanıcıların bu property içinde dönebilmeleri için implement ettiğimiz IEnumerable inteface’inin gerektirdiği metodları yazıyoruz ve nesnemizi kodlamayı bitiriyoruz.

Şimdi isterseniz bir örnek yaparak nesnemizin kullanımını inceleyelim. Örneğimizde kullanacağımız test nesnemizi aşağıdaki gibi oluşturuyoruz.

    public class TestObject
    {
        public TestObject()
        {
            this.Name = new MultilanguageProperty<string>();
        }
        public int Id { get; set; }
        public string UniversalCode { get; set; }
        public MultilanguageProperty<string> Name { get; set; }
    }

Gördüğünüz gibi test nesnemiz üzerinde Name property’sinin farklı diller için farklı değerleri barındırmasını istediğimizden MultilanguageProperty tipinden oluşturduk ve generic tip olarak string’i belirledik.

Şimdi örnek uygulamamızı yazıyoruz.

    class Program
    {
        public static List<TestObject> GetSampleDataForTestObject()
        {
            List<TestObject> listTestObject = new List<TestObject>();

            TestObject testObject1 = new TestObject { Id = 1, UniversalCode = "TR" };
            testObject1.Name.Values.Add(Language.ApplicationLanguage.Turkish, "Türkiye");
            testObject1.Name.Values.Add(Language.ApplicationLanguage.English, "Turkey");

            TestObject testObject2 = new TestObject { Id = 2, UniversalCode = "USA" };
            testObject2.Name.Values.Add(Language.ApplicationLanguage.Turkish, "Amerika Birleşik Devletleri");
            testObject2.Name.Values.Add(Language.ApplicationLanguage.English, "United States Of America");

            TestObject testObject3 = new TestObject { Id = 3, UniversalCode = "FR" };
            testObject3.Name.Values.Add(Language.ApplicationLanguage.Turkish, "Fransa");
            testObject3.Name.Values.Add(Language.ApplicationLanguage.English, "France");

            listTestObject.Add(testObject1);
            listTestObject.Add(testObject2);
            listTestObject.Add(testObject3);

            return listTestObject;
        }
        static void Main(string[] args)
        {

            List<TestObject> listTestObject = GetSampleDataForTestObject();
            WriteAllList(listTestObject);
            Console.WriteLine();
            WriteForSpecificLanguageByIndexer(listTestObject);
            Console.WriteLine();
            WriteForSpecificLanguageByGetValue(listTestObject);
            Console.WriteLine();
            WriteForDefaultLanguageByIndexer(listTestObject);
            Console.WriteLine();
            WriteForDefaultLanguageByGetValue(listTestObject);
            Console.WriteLine();
            WriteForDefaultLanguageByImplicit(listTestObject);
            Console.Read();
        }
        private static void WriteAllList(List<TestObject> listTestObject)
        {
            Console.WriteLine("Writing All List");
            foreach (TestObject to in listTestObject)
            {
                Console.WriteLine("Id:" + to.Id + " Code:" + to.UniversalCode);
                foreach (KeyValuePair<Language.ApplicationLanguage, string> kvp in to.Name)
                {
                    Console.WriteLine("     Language:" + kvp.Key.ToString() + " Name:" + kvp.Value.ToString());
                }
            }
        }
        private static void WriteForSpecificLanguageByIndexer(List<TestObject> listTestObject)
        {
            Console.WriteLine("Writing Specific Language Value By Indexer");
            string s = listTestObject[0].Name[Language.ApplicationLanguage.English];
            Console.WriteLine(s);
        }
        private static void WriteForSpecificLanguageByGetValue(List<TestObject> listTestObject)
        {
            Console.WriteLine("Writing Specific Language Value By GetValue Method");
            string s = listTestObject[0].Name.GetValue(Language.ApplicationLanguage.English);
            Console.WriteLine(s);
        }
        private static void WriteForDefaultLanguageByIndexer(List<TestObject> listTestObject)
        {
            Console.WriteLine("Writing Default Language Value By Indexer");
            string s = listTestObject[1].Name[Language.GetDefaultLanguage()];
            Console.WriteLine(s);
        }
        private static void WriteForDefaultLanguageByGetValue(List<TestObject> listTestObject)
        {
            Console.WriteLine("Writing Default Language Value By GetValue Method");
            string s = listTestObject[1].Name.GetValue();
            Console.WriteLine(s);
        }
        private static void WriteForDefaultLanguageByImplicit(List<TestObject> listTestObject)
        {
            Console.WriteLine("Writing Default Language Value By Implicit Operator");
            string s = listTestObject[2].Name;
            Console.WriteLine(s);
        }

    }

Kodumuzu inceleyecek olursak;

GetSampleDataForTestObject metodunda test nesnemizden birçok örnek yaratarak bir list’e atıyoruz aynı zamanda test nesnemizdeki Name özelliğine çoklu dil değerlerinin nasıl atanabileceğinide görüyoruz. Gerçek uygulamalarınızda, bilhassa orm araçları kullanıyorsanız bu nesne doldurma işlemini birkaç satır kodla veritabanından yapabilirsiniz.

Main’de ise gördüğünüz gibi listemize çeşitli metodlar kullanarak erişiyoruz ve Multilanguage Name property’sindeki değerleri farklı diller için farklı yöntemlere kullanarak okuyoruz.

WriteAllList metodu nesne içerisindeki tüm dil değerlerine nasıl erişebileceğinizi göstermektedir. Bu metod içerisindeki kodumuzda Name property’sinin değerleri içerisinde dönerken Multilanguage nesnemize implement ettiğimiz IEnumerable interface’inden faydalanıyoruz.

WriteForSpecificLanguageByIndexer metodunda ise Name property’mizin indexer’ına değerini almak istediğimiz dil tipini vererek değeri okuyoruz.

WriteForSpecificLanguageByGetValue metodunda ise Name property’mizin GetValue metoduna değerini almak istediğimiz dil tipini vererek değeri okuyoruz.

WriteDefaultLanguageByIndexer metodunda ise Name property’mizin indexer’ına varsayılan dil değerini okuyarak veriyoruz ve değeri okuyoruz.

WriteDefaultLanguageByGetValue metodunda ise Name property’mizin GetValue metoduna parametre vermiyoruz ve varsayılan dil değerini okuyoruz.

WriteForDefaultLanguageByImplicit metodunda ise gördüğünüz gibi nesnemizin Name property’sini direkt olarak bir string değişkene atayabiliyor ve varsayılan dil değerini bu şekilde kolaylıkla okuyabiliyoruz.

Uygulamamızı çalıştırdığımızda örnek bir ekran görüntüsü aşağıdaki gibi olacaktır.