16 Mart 2012 Cuma

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

Selamlar dostlar;
C# 3.0'ın bize verdiği Lambda Expression nimetine ne kadar şükretsek az :) 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ı :) 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 :)

28 Mart 2011 Pazartesi

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

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 :D 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 :D) 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üyorum ;)

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 umuyorum :) 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 getiriyor :D 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..

18 Mart 2011 Cuma

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

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