[Yazılım Felsefesi] Asp.Net'de MVVM'i Denemek

by Tahir 28. Mart 2011 18:30

Merhaba Arkadaşlar;

 

Bu makalenin kaynak kodlarını CodePlex'deki Adresinden (AspNetMVVM_TryOut.rar) indirebilirsiniz.

 

Bu yazı fikirsel çalışmalarımı yayınladığım "[Yazılım Fesefesi] UI Pattern'ler ve MVVM Üzerine Fikirsel Çalışmalar" makalemin uygulama örneğidir.


Önceki yazıda fikirler ortaya atmıştık. Sonra hayal ettim, bir şeyler ortaya çıkarttım. Sizinle paylaşmak istedim.

Asp.Net üzerinde MVVM denemeleri yapmaktaki öncelikli amacım; Code-Behind kodlamadan kurtulmaya ve UI Bussiness'ı bir tür model'e almaya çalışmaktı. Bunu kısmen gerçeklediğimi söyleyebilirim. Peki bu söylediğim şeyi Asp.Net MVC yapmıyor mu? Evet bir kısmını yapıyor. Controller sınıfları, bir tür Façade olarak Bussiness Logic'i View'dan kurtarıyor. Peki yeterli geliyor mu bu? Hayır. Çünkü sayfalarımız çoğu zaman UI tarafında da bir tür bussiness taşıyor. Biz bunu Asp.Net MVC'de javascript ile hallediyoruz. Asp.Net'de ise (iğrenç)Postback mimarisi ile hallediyoruz.Tamam sevmiyorum Postback mimarisini ama yine de işimizi oldukça kolaylaştırıyor. Çoğu intranet tabanlı Web uygulaması da -büyük dezavantajlarına rağmen- Asp.Net'i tercih ediyor.

Ben de Asp.Net'i tercih ettim :)

Tabii ki Asp.Net'i MVVM'e uygun hale getirebilmek için biraz üzerinde çalıştım. Mesela Asp.Net sayfasından Model oluşturabilmek için Custom Control ve bu kontrollerden veri toplayan bir Custom DataBinding mekanizması kullandım. Aslında şu anki haliyle çook basit bir durumda bu proje ama hayal ettiklerimi gerçekleyebilmemi sağladı.

Yeterince geyik yaptım Laughing Artık koda geçelim..

Mimarimizin ana amacı UI Bussiness'ı ViewModel'e taşımak olunca, basit bir UI Bussiness örneği bulmamız gerekiyordu. Ben de en sık karşılaştığımız (bir o kadar da karmaşık Laughing) Cascading Dropdown örneği üzerinden gerçekledim. Kaskat olarak birbirine bağlı 3 ComboBox'dan İl-İlçe-Semt seçimini gerçekleyeceğiz. Modelimiz sadece bu 3 kontrolün CanSet property'lerini düzenleyerek, View üzerinde işlem yapmaları sağlanacak. O zaman ViewModel'imize bakalım.

Bu arada mimarinin henüz önemli bir kısmı eksik.. O da Model ve ViewModel tek sınıfta. .Ama mantığı taşıması açısından kodun yeterli olduğunu düşünüyorumTongue out

 

public class AddressViewModel : ViewModelBase<AddressViewModel>
{
    //Default olarak ViewModel'in property'leri Get ve Set edilebilir mi (CanGet)
    protected override bool DefaultAccess { get { return true; } }
    
    //Model üzerindeki propertyler
    public int? IlID { get; set; }
    public int? IlceID { get; set; }
    public int? SemtID { get; set; }

    protected AddressService addressDataService = new AddressService();

    //UI Logic ya da UI Bussiness
    protected override void PrepareBussiness()
    {
        this.CreateAccessRule("IlceID", AllowFor.Get | AllowFor.Set, () => this.IlID.HasValue);
        this.CreateAccessRule("SemtID", AllowFor.Get | AllowFor.Set, () => this.IlceID.HasValue);

        this.AddDatasource("IlID", () => this.addressDataService.IlData());
        this.AddDatasource("IlceID", () => this.addressDataService.IlceData(this.IlID.Value));
        this.AddDatasource("SemtID", () => this.addressDataService.SemtData(this.IlID.Value, this.IlceID.Value));
    }
}

 

Bizim için buradaki önemli kısım "PrepareBussiness" methodu.. Dikkat ederseniz CreateAccessRule ile IlceID, IlID'nin değeri olup olmadığına bağlanıyor. Hakeza SemtID'de IlceID'ye bağlanıyor. Burada dikkat etmemiz gereken bir nokta var. Default ayarda biz Model'imizi erişime açmış olmamıza rağmen, aşağıda Get ve Set property'lerini bir üst değerin var olmasına bağlıyoruz. Yani IlID'nin bir değeri olmadan IlceID Get(Visible) ve Set(Enabled) edilemez.. Meali: İl seçilmeden Ilce görüntülenmeyecek..

Bir diğer önemli nokta da erişimi denetleyen kısımların de birer delegate(Func<bool>) olmaları.. Böylece her erişim denendiğinde; belirtilen Rule çalıştırılacak ve sonuç dönecektir.Tabii ki bu Rule'lar ViewModelBase<> sınıfında Dictionary<string,Func<bool>> içinde tutuluyor.

Peki Model'in property'leri sayfaya nasıl yerleşecekler?

 

<p>
    İl:
    <bss:MyDropDownList ID="ddlIl" runat="server" BindColumnName="IlID" DataTextField="Title" DataValueField="ID">
    </bss:MyDropDownList>

</p>
<p>
            İlçe:
    <bss:MyDropDownList ID="ddlIlce" runat="server" BindColumnName="IlceID" DataTextField="Title" DataValueField="ID">
    </bss:MyDropDownList>
</p>
 <p>
            Semt:
    <bss:MyDropDownList ID="ddlSemt" runat="server" BindColumnName="SemtID"  DataTextField="Title" DataValueField="ID">
    </bss:MyDropDownList>
</p>

 

 

Burada kontrolü sınıfın ilgili kontorlüne Bind edebilmek için kendi Custom Controllerimizi kullanıyoruz. Önemli nokta BindColumnName niteliği.. Bu özellik sayesinde, rekürsif bir metod ile Page ile Model'i bind edeceğiz.

Şimdi.. İşin kalbine gelelim.. Code-Behind'a..

 

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        ViewModelHelper.Run<AddressViewModel>(this);
    }
}

 

Sizi gülümsetebildiğimi umuyorumLaughing Hayallerimizi azar azar gerçekliyoruz. Peki ne mi var ViewModelHelper sınıfında ?

 

public static void Run<TViewModel>(Control mainControl) where TViewModel : ViewModelBase<TViewModel>, new()
{
    TViewModel viewModel = DataBindHelper.GetModel<TViewModel>(mainControl);
    viewModel.RunBussiness();
    DataBindHelper.ConfigureView(mainControl, viewModel);
}

 

Sistemi ayağa kaldıran kısım burada yatıyor.

  • 3. satır: Rekürsif bir method tüm kontrolleri dolaşıp IMyControl'den türeyen kontrollerin verilerini topluyor ve verilen Model'in BindColumnName'lerinde belirtilen property'lerine basıyor.
  • 4. satır: ViewModel sınfımızda yazdığımız RunBussiness() kısmını aktive ediyoruz.
  • 5. satır: ViewModel sınıfında oluşan rule'lar çerçevesinde, View'ı konfigure eder. (Verilerini Bind Eder, CanGet ve CanSet'ler çerçevesinde Visible ve Enabled'ları ayarlar. Varsa Datasource'larını bind eder.)

3. ve 5. satırdaki methodlar birbirine benzer olduğu, sadece ters çalıştıkları için sadece bir ConfigureView methodunu göstereceğim. Elbetteki kaynak kodlardan tüm diğer ayrıntılara ulaşabilirsiniz.

 

public static void ConfigureView<TViewModel>(Control mainControl, ViewModelBase<TViewModel> viewModel) where TViewModel : ViewModelBase<TViewModel>, new()
{
    Type dataType = viewModel.GetType();
    var properties = dataType.GetProperties();
    foreach (Control ctrl in mainControl.Controls)
    {
        if (ctrl is IMyControl)
        {
            IMyControl myControl = (ctrl as IMyControl);
            if (string.IsNullOrEmpty(myControl.BindColumnName)) continue;

            PropertyInfo propInfo = properties.
                    Where(pi => pi.Name == myControl.BindColumnName).
                            First();

            object dataValue = propInfo.GetValue(viewModel, null);

            myControl.setValue(dataValue);

            myControl.Visible = viewModel.CanGet(myControl.BindColumnName);
            myControl.ControlEnabled = propInfo.GetSetMethod() != null && viewModel.CanSet(myControl.BindColumnName);

            if (myControl is DataBoundControl && viewModel.HasDataSourceFor(myControl.BindColumnName) && myControl.ControlEnabled)
            {
                (myControl as DataBoundControl).DataSource = viewModel.GetDatasourceFor(myControl.BindColumnName);
                (myControl as DropDownList).AutoPostBack = true;
                (myControl as DataBoundControl).DataBind();
            }
        }
        if (ctrl.Controls.Count > 0)
        {
            ConfigureView(ctrl, viewModel);
        }
    }
}

 

Buradaki önemli nokta baştan beri söylediğimiz CanGet, CanSet methodlarının burada kullanılmış olması.. ViewModelBase sınıfından Datasource atama ile ilgili diğer methodların da burada işlevsel hale geldiğini görmekteyiz.

Ve son olarak da ViewModelBase sınıfımızdan küçük bir kaç nüans gösterelim :)

 

public abstract class ViewModelBase<TViewModel> where TViewModel : class,new()
{
..........
   private Dictionary<string, Func<bool>> getSecurityTable { get; set; }
   private Dictionary<string, Func<bool>> setSecurityTable { get; set; }

   internal bool CanGet(string propertyName) { return getSecurityTable.ContainsKey(propertyName) ? getSecurityTable[propertyName]() : DefaultAccess; }
   internal bool CanSet(string propertyName) { return setSecurityTable.ContainsKey(propertyName) ? setSecurityTable[propertyName]() : DefaultAccess; }
..........
   protected void CreateAccessRule(string propertyName, AllowFor allowment, Func<bool> allowMethod)
   {
       if ((allowment & Component.AllowFor.Get) > 0)
       {
           if (getSecurityTable.ContainsKey(propertyName))
           {
               getSecurityTable[propertyName] = allowMethod;
           }
           else
           {
               getSecurityTable.Add(propertyName, allowMethod);
           }
       }
       if ((allowment & Component.AllowFor.Set) > 0)
       {
           if (setSecurityTable.ContainsKey(propertyName))
           {
               setSecurityTable[propertyName] = allowMethod;
           }
           else
           {
               setSecurityTable.Add(propertyName, allowMethod);
           }
       }
   }
..........
}

 

Burada da AccessRule'ları Dictionary'lere atmamızı ve, CanGet ile CanSet canlı kanlı halleriyle ne kadar küçük birer method olduğunu görmenize yetti sanırım :) Nesneye Dayalı Programlama'ya bu yüzden bayılıyorum. İşlerimizi bu kadar kolay hale getiriyorLaughing

Artık sizi kaynak kodlarla başbaşa bırakıyorum..

Umarım [Yazılım Felsefesi]ne bakışınızı değiştirebilecek, genişletebilecek bir makale olmuştur..

Herkese iyi çalışmalar dilerim..

[Yazılım Felsefesi] UI Pattern'ler ve MVVM Üzerine Fikirsel Çalışmalar

by Tahir 18. Mart 2011 21:30

Merhaba Arkadaşlar;

[Yazılım Felsefesi] başlıklı makalelerle, sizlerle hayallerimi paylaşacağım.. Sorun olarak alıglayıp; çözüm için zihnimi zorladığım fikirler üzerine fikirler ortaya atacağım..

Uzun zamandır düşündüğüm ama tam olarak çözüm üretemediğim sorun vardı:

Çok katmanlı Mimari'ye uygun olarak tasarlanan Bussiness katmanı -ya da -; sisteme dair Bussiness Logic kodlarını barındırıyor. Ancak UI Logic(ya da Application Logic) olarak ifade edebileceğimiz kodları hiç bir şekilde arayüz bağımsız hale getiremiyorduk.  Daha fazla detaya girmeden hemen kısaca bu kavramları açıklayalım:

  • Bussiness Logic: "Bir e-ticaret portalında satışın onaylanması için girilen Kredi Kartının, sipariş boyutunca provizyon vermiş olması gerekir." gibi sistemsel kural ve mantığa denir.
  • UI Logic(App Logic): "Adres girme ekranında İl combosu seçildiğinde, İlçesi combosu İl'de seçilen değere göre doldurulur." gibi ön arayüzü ya da kısaca Bussiness Katmanından önce bir model'in kullanıcı tarafından manüple edilmesi olaylarına bu ad verilir. "Hesaplar listesinde, 10.000 TL'den büyük aylık geliri olanların kırmızı görünmesi" gibi kural örnekleri de verilebilir.

Asp.Net MVC bu trendin başlancını bence çok güzel verdi. Model üzerine;

  • Validation
  • Control Type(Ön arayüzdeki gösterilecek kontrolün şekli (EditorFor(...) ya da DisplayFor(...) gibi))
  • FormatString
  • DisplayName(Alanın Kullanıcıya gösterilecek ismi)

gibi alanlar yerleştirerek UI'a bu bilgilerin konulmasının önüne geçmeye çalıştı.. Aslında Model üzerine konulabilecek bence önemli bir kısım daha var.. 

Model Based Security diye sallama bir tabir yaptım bu fikir için :) Amacım aslında Model'lerimize, nesne bazlı ya da property bazlı security ayarları koymak(bu Controller'ların üzerine Authorize(Role="Admin") gibi olabilir). Örnek vermek gerekirse;

  • ForumPost nesnesinin sadece CreateUser tarafından değiştirilebilmesi, 
  • Kisi modelinin Ad, Soyad bilgilerini CreateUser değiştirebilmekteyken, Maaş bilgisini sadece Admin değiştirebilmesi
  • Kisi modelinin tüm kayıtlarını Maaş hariç görebiliyor, kendi maaşını görebiliyor vb.

Her şeyin nesneye dayalı olması için bu da güzel bir teknik ama hala bazı isteklerimizi karşılamıyor. UI Logic için bir çözüm bulmalıyız.. Peki MVVM'e ne dersiniz?

Model-View-ViewModel tabiri bize biraz daha başlancıç oluşturuyor. ViewModel kelimesini de; Model of View'a çevirdiğimizde daha da anlaşılır. İlk aklıma gelen şey, "İl" combosuna bir dropdown gibi erişebilmemizi sağlayacak bir sınıf.. Aslında bu sınıf; ..aspx.cs sınıflarımız oluyor.. Bizim için korkutucu, sadece daha fazla karmaşıklığa neden olan sınıflar.. O zaman biraz daha farklı bir bakış açısından gitmeliyim..

Model'den sayfaya.. Test Driven yazarken; sayfayı hayal edip; kodsal olarak test etmek..

Sayfa üzerinde en sık kullandığımız Applicaiton Logic: Visible ve Enabled.. Peki Model üzerinde nasıl tasarlayabiliriz bunu ?

  • CanGet : Visibility
  • CanSet : Enabled
  • CanInvoke : Button Enabled

olarak ifade etsek model üzerinde ? Ortaya aşağıdaki gibi pseude-code çıksa?

 

model.Ensure(c=> c.Il.HasValue).AllowForSet(c=> c.Ilce);

 

Bu bize aslında testlerimizi de ortada bir UI yokken bile yapabilmemizi sağlamaz mı mesela:

 

public int IlceID{
      get
      {
           if ( ! this.CanGet("IlceID")) 
                 thow new Exception();
           return _ilceId;
      }
      set
      {
           if ( ! this.CanSet("IlceID")) 
                 thow new Exception();
           return _ilceId;
      }
}

 

gibi bir kodu rahatlıkla test edebiliriz. 

 

public void TestWeCanNotSetIlceBeforeSetIl()
{
       AdresModel adres= new AdresModel();
       Assert.ThrowsException(()=>adres.IlceId=5);
}

public void TestWeCanSetIlceAfterSetIl()
{
       AdresModel adres= new AdresModel();
       adres.IlID=3;
       Assert.DontThrowException(()=>adres.IlceId=5);
}

 

Tabii ki property'ler sürekli tekrar eden böyle bir koddan Proxy sınıflar aracılığıyla kurtarır ve temiz bir kod elde etmeye çalışırız. Zaten ChangeTracking amacıyla çoğu zaman model'lerimiz proxy nesneler halinde erişmekteyiz - ki bunu da bize ORM'lerimiz sağlar... 

Peki bunu nasıl uygulayabilirim. Bir de bunu üzerine beyin jimnastiği yapalım:

 

Asp.Net MVC'de Model'lerin bind edilme mimarisi hazır zaten. Yaptığım ek değişiklikleri de Html.DisplayFor(...) mimarisi ile gerçekleyebilirim. Ancak Controller ile ViewModel'i birbirinden ayırmam çok zor olacak.

Asp.Net WebForms daha stateful olduğu için belki işime yarayabilir. Asp.Net'in kendi kontollerinden inherit alarak kendi Custom Contol'lerimi yazarsam bunları da Model'in property'lerine bind edecek bir sistem geliştirirsem güzel olabilir.

 

Bu fikri sevdim :) Şimdiden heyecanlanmaya başladım :)

Bakalım hayal ettiklerimi ne kadar gerçekleyebileceğimTongue out

[Tekerleği Yeniden Keşfet] Katılgan ORM

by Tahir 17. Mart 2011 22:46

Merhaba Arkadaşlar;

Bu makalenin kaynak kodlarını CodePlex'deki Adresinden (KatılganORM.rar) indirebilirsiniz.

 

ORM(Object Relational Mapper) kelimesini duyunca "Yeni bir ORM mi?" diye sorduğunuzu duyuyor gibiyim Smile Zaten bu yüzden başlığımız [Tekerleği Yeniden Keşfet].. 

"Tekerlek bir kez keşfedilmişken, niye yeniden emek vereyim ki?" diye düşünmeyin. İnsanlık 1969'da Amerika'lı astronotların Aya çıkmasının 40 yıl sonrasından bile; Çin uzaya insan göndermeye çalışıyordu. Burada amaç aynı zorlu süreçten geçip; benzer hataları yapıp, ders alıp tecrübe edinmek... Bizde burada kendi küçücük "Katılgan ORM"mizi yaparken aynı şekilde tecrübe etmeye çalışacağız.

Tabii ki çok ihtiyacımız olmadıkça NHibernate'den ya da Entity Framework'den vazgeçmeyeceğiz.. Ama ihtiyacımız olunca da kullanmaktan kaçınmayacağız (Maalesef bir projemde benim ihtiyacım oldu. Laughing)

Amacımız oldukça basit bir ORM çıkartmaya çalışmak. Özellikleri aşağıdaki gibi olacak.

  1. Select All, Select By Example gibi basit işlevleri olacak.
  2. Update,Insert, Delete işlemleri olacak..
  3. Gelen Relational bir kaydı Nesne olarak dönecek(Sanırım Object kelimesi buradan geliyordu :)
  4. Ek olarak Basit bir BaseService aracılığıyla direk DB'ye sorgu atmamıza izin verecek, istersek bir sınıfa map edecek..

Kodumuzu olabilidiğince küçük tutumak için DB bağımsızlığını tam olarak implemente etmeyeceğiz. Bu da size ödev olsun :)

Her şeyden önce DB ve Entity'mizi görelim.

public class Kisi
{
    [PrimaryKey(IsIdentity = true)]
    public int KisiID { get; set; }
    public string Ad { get; set; }
    public string Soyad { get; set; }
    public string TelNo { get; set; }
}

Gördüğünüz gibi, entity sınıfımız olabildiğince basit.. PrimaryKey Attribute'u var o da yine çok basit bir sınıf. 

İlk adım Program.cs üzerinde yeni bir kişi nesnesi oluşturup DB'ye göndermek, ardından da edindiği ID değerini ekrana yazdırmak.

 

DataContext<Kisi> kisiContext = new DataContext<Kisi>();
Kisi yeniKisi = new Kisi { Ad = "Tahir", Soyad = "çakmak", TelNo = "9876543" };
kisiContext.InsertEntity(yeniKisi);

Console.WriteLine("Eklenen Kisi'nin ID'si: {0}", yeniKisi.KisiID);

 

DataContext<T> sınıfımız ORM'imizin tek anlamlı, kod barındıran sınıfı.. Üzerinde bütün işlemlerimiz için birer anlamlı method barındırıyor. Tip Güvenlilik için Generic olarak tasarladık.

 

public class DataContext<TEntity> where TEntity : class, new()
{
 ........
    public TE RunCommand<TE>(Func<DbCommand, TE> dbExecute, string conStr)
    {
        DbConnection con = this.CreateConnection();
        DbCommand cmd = con.CreateCommand();
        try
        {
            con.Open();
            return dbExecute(cmd);
        }
        catch (Exception e)
        {
            throw new Exception("DB Hatası", e);
        }
        finally
        {
            con.Close();
        }
    }
........
}

 

DataContext sınıfımızın en temel methodu "RunCommand" methodu; bu bize bütün DB isteklerinin tek bir noktadan akmasını sağlıyor. Dikkat ederseniz, bu method bir "Template Method Pattern"inin C# 3.0 implementasyonu.. İçerisine DBCommand'ı parametre olarak alıp, dışarıya veri dönen tüm methodlar için işlem noktası.. Diğer noktalarda, Con'ın açılıp-kapanması, ya da hata'ların yakalanması ile ilgili merkezi nokta..

 

public bool InsertEntity(TEntity entity)
{
    return RunCommand(cmd =>
    {
        object identityValue = GetInsertQry(entity, cmd).ExecuteScalar();
        var propInfo = entity.GetType().GetProperties().Where(pInfo => pInfo.GetCustomAttributes(typeof(PrimaryKeyAttribute), false).Length > 0).FirstOrDefault();
        if (propInfo != null && (propInfo.GetValue(entity, null) == null || ((int)propInfo.GetValue(entity, null)) == 0))
        {
            propInfo.SetValue(entity, Convert.ChangeType(identityValue, Nullable.GetUnderlyingType(propInfo.PropertyType) ?? propInfo.PropertyType), null);
        }
        return true;
    });
}

public DbCommand GetInsertQry(TEntity entity, DbCommand cmd)
{
    if (entity is BaseEntity) { (entity as BaseEntity).IsSaving = true; }

    string columns = "";
    string parameters = "";
    foreach (PropertyInfo info in typeof(TEntity).GetProperties().
        Where(p => p.GetSetMethod() != null
                && p.GetCustomAttributes(typeof(NonDbColumnAttribute), false).Count() <= 0
                && (p.GetCustomAttributes(typeof(PrimaryKeyAttribute), false) as PrimaryKeyAttribute[]).ToList().Where(pr => pr.IsIdentity).Count() <= 0
                )
        )
    {
        object columnValue = info.GetValue(entity, null);
        if (columnValue != null)
        {
            columns += string.Format("{0},", info.Name);
            parameters += string.Format(" @{0} ,", info.Name);
            cmd.Parameters.Add(this.CreateParameter(info.Name, columnValue));
        }
    }

    StringBuilder querySb = new StringBuilder();
    querySb.AppendFormat("INSERT INTO {0} ({1}) VALUES ({2})", typeof(TEntity).Name, columns.Remove(columns.LastIndexOf(","), 1), parameters.Remove(parameters.LastIndexOf(","), 1));

    cmd.CommandText = querySb.ToString() + "; SELECT SCOPE_IDENTITY();";

    return cmd;
}

 

GetInsertQuery methodu adı üsütünde :) InsertQuery'sini Parametreleriyle beraber oluşturup geriye DBCommand döndürüyor.

Burada yaptığı şey aslında çok basit; reflection ile gelen entity'nin tüm değerlerini alıyor, tek tek kolon ismiyle birebir olarak Insert cümleciğine yerleştiriyor; parametre olarak da değerini veriyor. Property'leri dolaşırken önemli bir nokta var. Metodumuz; PrimaryKey ve Identity olan property'leri, NonDbColumnAttribute attribute'una sahip property'leri, ve de Set özelliği olmayan  property'leri DB'ye göndermiyor..   

Peki gelen kayıtları nasıl nesneye dönüştüreceğiz, reflection ve Convert.ChangeType(...) methodu ile Laughing

 

public List<TEntity> GetAllData(string orderByColumn = "", params string[] columnNames)
{
    return RunCommand(cmd =>
    {
        cmd.CommandText = GetSelectQry(columnNames) + (orderByColumn == "" ? "" : " ORDER BY " + orderByColumn);
        return MapEntity(cmd.ExecuteReader());
    });
}

public string GetSelectQry(params string[] columnNames)
{
    StringBuilder sb = new StringBuilder();
    sb.Append("Select ");
    if (columnNames.Length > 0)
    {
        for (int i = 0; i < columnNames.Length; i++)
        {
            sb.Append(columnNames[i]);
            if (i != columnNames.Length - 1) { sb.Append(","); }
        }
    }
    else { sb.Append(" * "); }

    sb.AppendFormat(" From {0}", typeof(TEntity).Name);
    return sb.ToString();
}

 

Yukardıdaki kısımlar, Insert bölümünden farksız.. Yine string işleme.. Ve sistemin kalbi.. Mapping kod bloğu

 

public List<TEntity> MapEntity(IDataReader dr, params string[] exceptColumns)
{
    List<TEntity> entityList = new List<TEntity>();
    while (dr.Read())
    {
        TEntity entity = new TEntity();
        var props = typeof(TEntity).GetProperties();
        foreach (PropertyInfo info in props)
        {
            foreach (DataRow row in dr.GetSchemaTable().Rows)
            {
                if (row[0].ToString().ToLowerInvariant() == info.Name.ToLowerInvariant())
                {
                    info.SetValue(entity, FormatHelper.AssignValue(dr[info.Name], Nullable.GetUnderlyingType(info.PropertyType) ?? info.PropertyType, !exceptColumns.Contains(info.Name)), null);
                }
            }
        }
        entityList.Add(entity);
    }
    return entityList;

Burada bize en çok yol gösteren IDataReader üzerindeki GetSchemaTable() methodu. Veritabanından gelen kolonların tiplerini belirtiyor; böylece gerekliyse bizim dönüşümleri sağlamamıza yardımcı oluyor.. Bir diğer durum da Nullable alanların gerçek tiplerini almamızı sağlayan Nullable.GetUnderlyingType metodu..

Kaynak kodları karıştırıyorken, BaseService<T> sınıfıyla karşılaşacaksınız. Bu sınıf DBContext<T> sınfını kullanan; Data Access Facade amacıyla kullanılan Servis katmanı için bir üst sınıfdır.

Sistem genel olarak Select, Update ve Delete için aynı işlemleri tekrar etmek üzerine gidiyor.. Ancak buraya kadar ki kısmın bir sonraki bölümleri incelemkte yeterli olduğunu düşünüyorum. Ve Sizleri kaynak kodlarla başbaşa bırakıyorum Laughing

[Rehber Makale] Distributed Transaction

by Tahir 16. Mart 2011 22:54

Merhaba Arkadaşlar;

[Rehber Makale]'ler ile sizlere özel bir konu hakkında bilgi tekrarı olmadan, sizlere pratik tecrübelerimi aktardığım makaleler ulaştırmayı planlıyorum.

Nedir bu Distributed Transaction?

Bir uygulamada oluşan kayıtların birden fazla ve/veya birbirinden farklı markalardaki kayıtların tek bir transaction içerisinde işlem görmesidir. Bizim burada inceleyeceğimiz kısım; WCF üzerindeki uygulamalarıdır.

 

Başlangıç için okunması gereken makaleler:

1. WCF - Transaction Yonetimi (Transaction Management) – 1

2. WCF - Transaction Yonetimi (Transaction Management) – 2

3. 6 steps to enable transactions in WCF

Bu makaleler başlangıç için oldukça iyi fikir verecektir.

Önemli olan sadece bir kaç nokta vardır: WCF Binding Transaction ayarı; 1-2 attribute

Distributed Transaction Coordinator (MSDTC.exe)

Microsoft tarafında distributed transaction görevini üstlenen araçtır. Default olarak Windows sistemlerde kurulu bir servistir... Servislerde yukarıdaki isimlerle görüntüleyebilirsiniz. Com üzerinden iletişime geçilir. (Ki .Net ‘de arka planda Com üzerinden iletişime geçmektedir.) Yapı hakkında net bilgiler veremiyor olsam da şunu söyleyebilirim. WCF üzerinden bir Distributed Transaction(DT) isteğinde bulunduğunuzda(ki TransactionScope bunun için yeterlidir) MSDTC Database dahil tüm transaction işlemlerini kendisi yönetir. Sql Server ve Oracle kendi servisleri aracılığıyla; DTC için API’lar sağlamışlardır. (Oracle için Oracle Client’ın custom kurulumunda görebileceğiniz OracleMTSRecoveryService- MTS(Microsoft Transaction Server)) servisi ile gerçekleşir.

Oracle’ın işi tam olarak nasıl yürüttüğü ile ilgili Oracle® Services for Microsoft Transaction Server Developer's Guide adlı güzel bir kitabı var. Bu kitapta gördüğüm ve DTC’ye ait tek şema aşağıdaki resim bir çok konuda bilgi sahibi etmekte bizleri:

image

 

Sorun çözmek üzerine

MSDTC’nin en kötü yanlarından bir tanesi de sorunun ne olduğunu anlamanın zorluğu... MSDTC’nin çalışmasını gördüğümüz 2 yer var:

1- Component Services(Control Panel’a Administrative Tools) üzerinden

a. Computer - My Computer – Distributed Transaction C.. - Local DTC – Transaction Statistics bölümün de erişiebilirsiniz.

b. Tüm transactionları görebilmek için Local DTC’nin sağ tuş Properties – Trace sekmesinde “Trace All Transactions”ı seçmelisiniz.

c. Statistics ekranındaki verileri sıfırlamak için servisi restart etmeniz gerekli

2- Daha ayrıntılı bilgi için C:\Windows\System32\Msdtc\Trace altında bir log dosyası oluşturuyor.Bu dosyayı okumak için şu makaleyi kullanabilirsiniz. Kısa ayrıntılar aşağıda:

a. Tracemft dosyası için Windows XP Service pack 2 Support Tools’u adresinden indirebilirsiniz. WinRar gibi bir araçla açıp Tracefmt.exe ve traceprt.dll dosyalarını yukarıdaki belirtilen dosya yoluna atın.

b. Eski logları arşivlemek için Component Services’da Local DTC Prop’da Logging Options bölümünde Stop Session – New Session diyebilirsiniz. Eski loglar bir dosyaya atılır.

c. Yaptığınız işleme ait güncel logu dosyadan okuyabilmek için Local DTC’den ‘Flush’ diyerek buffer’dakilerin yazılmasını sağlayabilirsiniz.

d. msdtcvtr –tracelog dtctrace.log –o aa diyerek çalıştırabilir ve logları inceleyebilirsiniz.

3- Sql Server’da çalışıyorsanız; SQL Profiler’da trace etmek için Event’lar arasından (TransactionsàDTC Transactions)’ı seçerseniz DTC transaciton çağrılarını inceleyebilirsiniz.

C# tarafında yazdığınız kodlar üzerinde ise aşağıdaki yöntemleri kullanarak sorunu algılamaya çalışabilirsiniz.

1- Aldığınız hatanın Inner Exception’ında Com hata (hexadecimal)kodunu şuraya sorabilirsiniz.

2- TransactionScope içerisinde o anki transaction’a yakalamak için “Transaction.Current” demeniz yeterlidir.(System.Transactions.dll’i refere etmeniz gerekebilir.)

a. Altındaki TransactionInformation Distributed ID dahil olmak üzere yeterli bilgi vermektedir.

b. Transaction Status’une buradan bakabilirsiniz.

Önemli Not: Debug işlemi uzun sürerse Transaction TimeOut’a düşer.

 

Bilinen Hatalar

Aynı TransactionScope içerisinde hem başka bir WCF servisi çağrılır hemde DB’ye(OracleConnection ile) bağlanılmaya çalışılırsa; connection transaction commit edilmeden kapatılmaması gerekiyor. Aşağıdaki kod örneği bunu açıklıyor:

Bu hatayı temel olarak şu şekilde yakaladım:

1- Transaction.Current.TransactionInformation altındaki Status property’sine Watch ekledim. Kısa bir takip sonucu sorun; ortaya çıktı.

ImyServiceClient myClient = new ImyServiceClient();

using (TransactionScope TranScope = new TransactionScope(TransactionScopeOption.Required))
{
    try
    {
        myClient.AddRecord();
        Connection = new OracleConnection(ConnectionString);
        Command = new OracleCommand(Query);
        Connection.Open();
        Command.ExecuteNonQuery();
        //Yanlış yerde
        //Connection.Close();
        TranScope.Complete();
        //Doğru yer.
        Connection.Close();

    }

    catch (Exception)
    {
        TranScope.Dispose();
        throw;
    }
}

İp Ucu : Servis üzerinden çağrımlarda; Connection.Close() edilme komutu verilmiş olsa dahi; transaction’dan haber bekliyor. Ancak aynı yerde hem yeni bir servis çağrımı hem de direk connection açılması gerekiyorsa yukarıdaki gibi bir sorun ortaya çıkıyor.

İp Ucu: Transaction bittiğinde işlem yapmak istiyorsak; Transaction.Current.TransactionCompleted eventına register olmamız yeterli..

[Cin Fikir - Mini Trick] DoIf(...)

by Tahir 16. Mart 2011 19:55

Selamlar dostlar;

C# 3.0'ın bize verdiği Lambda Expression nimetine ne kadar şükretsek azSmile Bugün sizinle işimi kolaylaştırdığını düşündüğüm küçük bir kod bloğunu paylaşacağım.

Aşağıdaki gibi if blokları sıklıkla karşılaşırız. Çoğunlukla okuması zordur. Üstelik, nesnenin 2 defa üretilmesine neden olduğumuz için de performansdan da yeriz..

 

if(this.GetType().GetProperty(setSecurityPair.Key).GetSetMethod() != null)
{
	this.GetType().GetProperty(setSecurityPair.Key).SetValue(this,null,null);
}

Burada bir reflection kod bloğu görmekteyiz. ve "this.GetType().GetProperty(setSecurityPair.Key)" kısmının da tekrar etmekte.. Peki kod bloğunu aşağıdaki hale dönüştürsek ?

 

this.GetType().GetProperty(setSecurityPair.Key).
       DoIf(me => me.GetSetMethod() != null, me=> me.SetValue(this,null,null));

 

Nasıl? Güzel olmaz mı Laughing DoIf(...) methodumuz, birinci lambda delegate'i çalıştırıyor; true değeri dönmesi durumunda da ikinci delegate çalıştırıyor. İki delegate de parametre olarak nesnenin kendisini alıyor; böylece de nesnenin tekrar üretilmesinden de kurtulunmuş oluyor.. Pekiiii.... Nasıl mı implemente ediyoruz?

 

public static class CodeHelpers
{
       public static void DoIf<T>(
                               this T instance, 
                               Func<T,bool> ensure, 
                               params Action<T>[] actionList) where T : class
       {
           if (ensure(instance))
           {
               foreach (var action in actionList)
               {
                   action.Invoke(instance as T);
               }
           }
       }
}
  • Satır 4: Bu bir extension method
  • Satır 5: İçine T tipinde değişken alan, geriye bool dönen bir method delegate
  • Satır 6: İçine T tipinde değişken alan bir method delegate

Çok basit bir yöntemle daha okunabilir ve kısa bir kod bloğu ortaya çıkartmış olduk..

Umarım sizin işinize de yarar, vizyonunuza "Cin Fikir"ler katar Wink

[Tips & Tricks] İleri Visual Studio Teknikleri

by Tahir 30. Ocak 2011 16:34

Merhaba Arkadaşlar;

Şimdiye kadar Visual Studio ile yazılım geliştirdik. Peki nimetlerinden ne kadar yararlanıyoruz? Daha doğrusu ne kadar farkındayız? Bu makalede sizlerle çok fazla seslendirilmeyen Visual Studio kullanım teknikleri ve VS Macro'lar üzerine konuşacağız.

Hızlı kod yazabilmek için temel gereksinim; editörü hızlı kullanabilmektir. Ne kadar hızlı mouse kulanırsak kullanalım, hiç bir zaman klavyede eriştiğimiz hızlara erişemeyiz( en azından benim ve çevremdeki arkadaşlar için böyle :). Bu da bizi Visual Studio kullanımında da klavye'ye yönelmemiz gerektiği gerçeğiyle karşılaştırır. O zaman hemen sık kullanılan Visual Studio Klavye Kısayollarına bakalım. Bunların broşür halinin şuradan da indirebilirsiniz.

  • Ctrl+. (Shift+Alt+F10un yaptığını yapıyor; using eklemek, gerektiğinde method oluşturmak vb. işlemlerde)
  • F12 - Go to Definiton
  • Alt+F12 - Find Symbol
  • Shift+Alt+F12 - Quick Find Symbol(Seçtiğin bir string'i Symbol olarak direk arar. En çok Web.Config gibi yerlerde işe yarıyor.)
  • Ctrl+Shift+F - Find In Files - Tüm bulduğu eşleşenleri liste halinde gösterir.
  • Shift+F12 - Find All References
  • Ctrl+Shift+A - Add New Item
  • Shift+Alt+C - Add New Class
  • Ctrl+W,S - Solution Explorer
  • Ctrl+W,E - Error List
  • Ctrl+F4 - Close Current Tab
  • Ctrl+Tab - Açık pencereler arasında dolaşmanı sağlıyor
  • Ctrl+ - : Bir kod satırından uzak bir satıra atladıysan Geri gelmeni sağlıyor. "Go to Definition" gibi atlamalarında da sayfalar arası atlama falan da yapabiliyorsun.(Navigate Backward)
  • Ctrl+Shift+ - : Navigate Forward
  • F7 - View Code  : Shift+F7 - View Designer
  • Ctrl+K, S  - Surround with (Snippet'le sorrund etmek için(#region,foreach,if vb.))
  • Ctrl+M,M - Bulunduğun bölümü collapse eder
  • Ctrl+M,O - Tüm bölümleri(region, method ne varsa) collapse eder.
  • Shift+F9 - Quick Watch
  • F9 - Toggle Break Point - Break point koyar.
  • Debug işlemlerinde F10,F11,Shift+F11,Shift+F5,F5 gibi bildiğiniz şeyler...

İhtiyaçlarınıza göre bu kısayolları ezberleyin derim. Kısa sürede kod yazma hızınızdaki artışı fark edeceksiniz. Çünkü mouse kullanımı etkileşimli(mouse'in ekranın neresine gittiğini görüp hızını ayarlamanız gerekmekte); ancak klavye kullanımı otonom(klavyeye bakmadan tuşlara basar ve istediğiniz sonucu alırsınız) bir harekettir. Klavye kullanımındaki otonom hareket, zihinizi kod yazarken mouse'den kurtarıp daha etkili kod yazmanızı sağlayacaktır.

Visual Studio'da Regular Expressions

Visaul Studio'nun çok fazla kullanılmayan nimetlerinden biri de Regular Expression'lar. Arama işlemlerinde işlerinizi ne kadar kolaylaştırdığını fark ettiğiniz de bilmeden geçirdiğiniz günlere acıyacaksınız.

Mesela kodlarınızdaki tüm tek satırda yazılan int propertyleri bulmak istiyorsunuz. Arama penceresinde "Use Regular Expressions" seçtikten sonra "int.*{.*}" yazmanız  yeterli :) Nasıl ama kolay değil mi? Ya da sık kullanabileceğimiz boş satırları silme işlemi için "\n:b*\n" aratıp, "\n" ile replace edebilirsiniz.":b" burada özel bir karakter kısmını ifade eder, tab ya da space.. * ise RegEx'den bildiğimiz şekilde çalışır: 0 ya da sonsuz tane tekrar..

Özellikle HTML ve XML kullanımlarında işinizi oldukça kolaylaştıracaktır. Find & Replace özelliğinde daha özel işler yapabilirsiniz de.. Mesela seçtiğiniz bir XML taginin başına ve sonuna özel bir tag ekleyebilirsiniz.

Burada önemli nokta Özel karakterlere hızlı erişim için arama textbox'ının yanındaki ">" butonunu kullanabilirsiniz. Diğer tüm anahtar karakterlere adresinden erişebilirsiniz.

Visual Studio Macros

Macro'lar tekrarlanan işler yaparken işlerimizi kolaylaştırmak için elimiz ayağımız oluyor. Visual Basic olması bir dezavantaj olsa da, online C#2Vb Code Converter'lar oldukça işinize yarayabilir.

2 türlü kullanabilirsiniz. Ctrl+Shift+R kısayolu ile geçici(temporary) Macro kaydedip, Ctrl+Shift+P ile de bu Macro'yu çalıştırabilirsiniz. Aynı zamanda bu kaydettiğiniz macroları Visual Basic kodu olarak da Macro Explorer üzerinden görüntüleyebilirsiniz. Sık kullandığım, ve bu kaydedilmiş Macro'lara bakarak oluşturduğum bir kaç Macro örneğini sunayım size:

 

IIS Worker Process'e Attach olmanızı sağlayan Macro Kodu

 

Sub AttachToW3wp()
        Dim attached As Boolean = False
        Dim proc As EnvDTE.Process

        For Each proc In DTE.Debugger.LocalProcesses
            If (Right(proc.Name, 8) = "w3wp.exe") Then
                proc.Attach()
                attached = True
                Exit For
            End If
        Next

        If attached = False Then
            MsgBox("w3wp.exe çalışmıyor")
        End If
    End Sub

 

Aktif dosyayı sadce çalıştırıldığı anda göstern Macro Kodu(Bu özellik zaten Visual Studio'da var ama sürekli aktif olması dez avantaj olduğu için böyle bir çözüm güdülmüş. Ben de bu kodu başka bir yerde gördüm :)

 

Public Sub LocateFileInSolutionExplorer()
        DTE.ExecuteCommand("View.TrackActivityinSolutionExplorer")
        DTE.ExecuteCommand("View.TrackActivityinSolutionExplorer")
        DTE.ExecuteCommand("View.SolutionExplorer")
End Sub

 

 

Açık olan tüm dökümanlara Format Document komutunu veren kod bloğu

 

Sub FormatEachOpenedPage()
        For Each doc As Document In DTE.Documents
            doc.Activate()
            DTE.ExecuteCommand("Edit.FormatDocument")
            doc.Save()
        Next
End Sub

 

 

 

DB'de bir alandaki değeri çekip, bu veriyi bir dosya olarak Solution'a ekleyen Macro kodu.. Mail templateini DB'de tutan bir kod bloğu gördüm. HTML editör olmadığı için oldukça zor değiştiriliyordu. Bende Visual Studio üzerine çalışabileceğim bir ortam oluşturdum.

 

Sub LoadAllMail()
        'Dim selectedfileName As String = DTE.ActiveDocument.Selection.Text
        Dim adap As New OracleDataAdapter("SELECT  *   FROM CC_MAILTEMPLATE where ISACTIVE=1 MAILCODE IN (' + selectedfileName + ') ", Helpers.RealConStr)
        Dim commandWin As EnvDTE.CommandWindow
        commandWin = DTE.Windows.Item(EnvDTE.Constants.vsWindowKindCommandWindow).Object

        Dim dTable As New DataTable()
        adap.Fill(dTable)

        For Each row As DataRow In dTable.Rows
            DTE.Windows.Item(Constants.vsWindowKindSolutionExplorer).Activate()
            DTE.ActiveWindow.Object.GetItem("DBSources\" + Helpers.DefaultProject + "\MailTemplate").Select(vsUISelectionType.vsUISelectionTypeSelect)
            Try
                DTE.ItemOperations.AddNewItem("Visual C# Items\General\Text File", row("MAILCODE").ToString() + ".html")

                DTE.ActiveDocument.Selection.SelectAll()
                DTE.ActiveDocument.Selection.Delete()

                DTE.ActiveDocument.Selection.Insert(row("BODY_TR_TR").ToString())
            Catch ex As Exception
                commandWin.OutputString("Yüklenemedi " + row("MAILCODE").ToString())
            End Try

        Next
    End Sub

 

Alttaki de bu dosyadan tekrar veritabanına gönderen kod bloğudur.



Sub SaveMail()
        DTE.ActiveDocument.Selection.SelectAll()
        Dim name As String = DTE.ActiveDocument.Name.Split(".")(0)
        Dim con As New OracleConnection(OttoHelpers.ConStr)
        Dim cmd As New OracleCommand()
        cmd.Connection = con
        '
        cmd.CommandText = "UPDATE CC_MAILTEMPLATE SET BODY = :MAILBODY WHERE MAILCODE='" + name + "' AND ISACTIVE=1"
        cmd.Parameters.AddWithValue("MAILBODY", DTE.ActiveDocument.Selection.Text.ToString())
        Try
            con.Open()
            cmd.Transaction = con.BeginTransaction()
            Dim affectedRows As Int32 = cmd.ExecuteNonQuery()
            If affectedRows = 1 Then
                cmd.Transaction.Commit()
                MsgBox("Güncellenen kayıt sayısı:" + affectedRows.ToString())
            End If
        Catch ex As Exception
            cmd.Transaction.Rollback()
            MsgBox("Transaction Geri Alındı (Rollback) !")
        Finally
            If con.State = ConnectionState.Open Then
                con.Close()
            End If
        End Try
        DTE.ActiveDocument.Selection.StartOfLine(vsStartOfLineOptions.vsStartOfLineOptionsFirstText)
    End Sub

Bu kodlar size de neler yapılabileceği ile ilgil oldukça iyi fikirler vermiştir diye düşünüyorum.

Umarım bu İp Uçları( [Tips & Tricks] ) işinize yarar ve size kolaylık sağlar.

Herkese iyi çalışmalar dilerim.

 

[Cin Fikir]: No Client WCF !

by Tahir 29. Ocak 2011 21:23

Merhaba Arkadaşlar;

Bu makalenin kaynak kodlarını CodePlex'deki Adresinden (WCFNonClient.rar)  indirebilirsiniz.

 

Bugünden itibaren, "Cin Fikir" diye ifade ettiğim; kodlamaktan da oldukça hoşlandığım bir kaç sıradışı fikrimi sizlerle paylaşmaya karar verdim. Cin Fikir'lerim genelde bir sorun görüp, bunu nasıl bir "Cin"lik ile çözebilirim ana fikrinden ortaya çıkan fikirler oluyor. Bugünkü fikrimiz WCF ile ilgili:
Problemin Tanımı: Outsource olarak bulunduğum bir iş yerinde, her şeyin olabildiğince servislere yıkılmasını ve gerektiğinde de farklı server ya da uygulamalara geçirilmesi; "Yazılım Geliştirme Stratejisi" olarak belirlenmişti. Ancak bu durum; küçük bir veri çekme işleminde dahi yazılan servislere bir client oluşturulması zorunluluğu getiriyordu. Üstelik de servis sayısı arttıkça; development için kullandığınız PC her servis çağrısı için TCP portlarına veri göndermek zorunda kalıyordu ve performansı çok düşük oluyordu. Üstelik geliştirilen uygulama gerçek ortama alınıp da servisler aynı PC'ye kurulursa, gereksiz olarak performans kaybına uğranıyordu.
Cin Fikir ! : Servis çağrılarını bir Façade sınıf üzerinden, interface üzerinden çekersek; concrete sınıfı da bir konfigurasyona göre ürettirirsek Bussiness kodlarını DLL üzerinden ya da WCF üzerinden çağırmamızı  bilmeden kod yazabiliriz. Bir de bu interface'i Servis sınıfına implemente ettirip, Client sınıfını da bu inteface'i implenmente edecek bir proxy nesnesiyle oluşturursak deymeyin keyfimize :D Daha fazla beklemeden kodlamaya geçelim...
Kodlama:
İlk olarak Service Interface'imize ve implemetasyonuna göz atalım.
[ServiceContract]
public interface IComplexService
{
   [OperationContract]
    int Sum(int x, int y);
}

public class ComplexService : IComplexService
{
    public int Sum(int x, int y)
    {
        return x + y;
    }
}
Karşınızda çok komplex bir servis :)
Şimdi de methodu çağıran sınıftaki kod'a göz atalım. Bu bir Console uygulaması..


class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(
           Core.ComplexService.Sum(2, 5)
        );
        Console.ReadLine();
    }
}
Burası sistemin can alıcı noktalarından birisi; Core sınıfı üzerindeki ComplexService static property'si IComplexService tipindedir. Bu property içinde Configuration içinden aldığımız değere göre gerçek nesneyi ya da WCF üzerinden çağrıyı gerçekleştirecek proxy sınıfını geri döndürüyoruz.

public static class Core
{
    public static IComplexService ComplexService
    {
        get
        {
            if (ConfigurationManager.AppSettings["userService"] == "1")
                return new ComplexService();

            return new ChannelFactory("BasicHttpBinding_ComplexService")
                               .CreateChannel();
        }
    }
}
Burada belirtilen ChannelFactory sınıfı; Visual Studio'nun WCF Client kod üreticisinin ürettiği koda benzer şekilde verilen konfigurasyona ve service interface'ine göre, verilen interface tipinde WCF client nesnesi üretmekte.. Yani bir tür proxy nesnesi.. Buradaki sıkıntımız, bizim WCF method çağrımlarında her method'dan önce ve sonra işlem yapmak zorunda olduğumuzu varsaydığımızda sıkıntı çıkartması..

Bu durumda kendi proxy nesnelerimiz üretebileceğimiz bir yapıya ihtiyaçımız var. Bunun için bir çok çözüm var; biz burada RealProxy nesnesini kullanıyor olacağız. (Konu hakkında ayrıntılı bilgi için Sefer Algan Hocam'ın yazdığı CSharpNedir'deki şu makalesine göz atabilirsiniz).


internal class ProxyFactory : RealProxy
{
    public TObject RealObject { get; private set; }

    public ProxyFactory(TObject realObject)
        : base(typeof(TObject))
    {
        this.RealObject = realObject;
    }


    private Func, object> _surroundDelegate;
    public TObject GetProxy(Func, object> surroundDelegate)
    {
        this._surroundDelegate = surroundDelegate;
        return (TObject)this.GetTransparentProxy();
    }

    public override IMessage Invoke(IMessage msg)
    {
        //Bu örnek gelen çağrımın bir metot olduğu varsayımı altında yapılmıştır.
        //Eğer çağrım bir Property ise ona göre bu kodu değiştirmek gerekir.Yani set_ ve get_ önekleri ile metodu dinamik bir şekilde çağırmak gerekir.

        IMethodCallMessage message = (IMethodCallMessage)msg;

        if (message != null)
        {
            //MessageBox.Show(message.MethodName + " metodu çağrılmak üzere. Biz araya girdik çağrılmadan önce");

            object methodRetval = _surroundDelegate(() => message.MethodBase.Invoke(RealObject, message.InArgs));

            ReturnMessage retVal = new ReturnMessage(methodRetval, null, 0, message.LogicalCallContext, message);

            return retVal;
        }
        return null;
    }
}


Burada göz atılacak 3 önemli satır var.

  1. 5 numaralı satır'da; bir nesnenin proxy'ini üretmek için ProxyGenerator sınıfını üretirken; gerçek nesneyi de vermeyi zorunlu tutuyoruz.
  2. 13 numaralı satırda method parametresi :"Func<Func<object>, object>". Yani içerisine object tipinde değer döndüren; verilen Delegate'i çalıştırıp, dönen değeri geri bize veren bir Delegate istenmektedir. Biliyorum cümle çok karmaşık oldu ama kod yeterince açık değil mi zaten :D
  3. 30 numaralı satırda ise; proxy nesne üzerinden çağrılan, gerçek sınıfın methodlarını çağırdığında GetProxy nesnesinde aldığımız delegate; içerisine gerçek nesnenin ilgili methodunu barındıran delegate verilerek çalıştırılıyor.
Son olarak da: Core Sınıfımızın son hali :
public static class Core
{
    public static IComplexService ComplexService
    {
        get
        {
            if (ConfigurationManager.AppSettings["useService"] == "0")
                return new ComplexService();
            //return new ChannelFactory("BasicHttpBinding_ComplexService").CreateChannel();

            var proxyCreater = new ProxyFactory(
                            new ChannelFactory("BasicHttpBinding_ComplexService")
                                .CreateChannel()
                            );

            return proxyCreater.GetProxy(fnc =>
                {
                    using (OperationContextScope scope = new OperationContextScope(proxyCreater.RealObject as IContextChannel))
                    {
                        return fnc.Invoke();
                    }
                });
        }
    }
}
Proxy methodunu alırken Lambda Expression'lar gerçek methodu, OperationScope içerisinde gerçeklemiş oldum. İstersek, kendi case'lerimize özel çalışmalar da yapabiliriz.

Artık istediğimiz an basit bir konfigurasyon ayarıyla sistemimizi WCF üzerinden çalışır hale getirebiliriz. Üstelik servis method'larını çalıştırıken tekrar etmek zorunda kaldığımız kodları da merkezi noktalara çekebilmiş olduk.

Çalışan örneğin kaynak kodlarını buradan indirebilirsiniz.

Umarım fikrimi yeterince "Cin" bulumuşsunuzdur :)

Sıradışı günler, sıradışı başarılar dilerim.