Arşiv

Nisan 2010 arşivi

ASP.NET Dynamic Data – Gelişmiş Scaffolding

30 Nisan 2010

Merhabalar, daha önce ASP.NET Dynamic Data(yazının devamında DD kısaltmasını kullanacağım) hakkında bazı bilgileri yayınlamıştım. Yeni bir makale konusunda düşünürken makale konusu olarak gerçek bir DD projesinde kullanıcı tarafından gelebilecek isteklerin hızlı bir şekilde nasıl karşılanabilineceğinden bahsetmenin uygun olduğuna karar verdim.
Yazılımcı gözüyle değerlendirmek gerekirse kullanıcı gereksinimleri konusunda gözden kaçan bir çok gereksinimi görülmeyebilir.  Örneğin bir tablo içindeki kayıtların listelenmesi yazılımcı için gayet yeterlidir ancak kullanıcı gözüyle bakıldığında her şeyin listelenmesi çoğu zaman gereksiz olacaktır. Bu kapsamda kullanıcı isteklerine karşılık sorun çözümüyle alakalı makalele dizisinin ilkini yazmaya başlıyorum. Daha önceki makalelerde bahsettiğim isimlendirme, biçimlendirme vb. gibi temel işlemlere bu makalede değinmeyeceğim.

Kullanıcı tarafında tablo üzerindeki her alanın(column) görüntülenmesi istenmeyebilir. Bazı durumlarda ise tüm kayıtların listelenmesi kullanım kolaylığı açısından sıkıntılı olabilir.

Scaffolding tekniğiyle istenilen alanların tüm şablonlarda görüntülenmesini engelleyebiliyoruz. Özellikle listeleme sayfalarında tüm alanların sayfada görünür halde olması yatay olarak uzayan bir sayfaya neden olur ancak bu alanları klasik scaffolding tekniği ile gizlersek diğer şablonlardanda kayboluverir.  İşte burada asıl sorun ortaya çıkıyor: Gizlenen alanların kullanıcı tarafından düzenlenmesi gerekiyorsa yada detaylı görüntüleme şablonunda tüm alanların görüntülenmesi gerekiyorsa ne yapacağız?

  Aletleri geliştirmek DD için her zaman kazanımdır. Bu senaryoda scaffolding tekniğini sayfa şablonlarına göre nasıl özelleştirebileceğimizi öğreneceğiz.

Kullanabileceğimiz veri bileşenleri(GridView, FormView, DetailsView vb.) veri listeleme sırasında listeleme işlemi için IAutoFieldGenerator arayüzünü uygulayan bir sınıf ile veri alanları üzerindeki kararını verir. Varsayılan alan oluşturucularda verideki tüm alanlar DD bildirimleri uygunlandıktan sonra(scaffolding gibi kısıtlamalar vb.) listelenir. Yapılması gereken şey ise veri listeleme işini gerçekleştirme görevini yazacağımız bir sınıfın üstlenmesidir.

IAutoFieldGenerator arayüzünü uygulayan sınıfımız:

namespace Eposta.Core.CustomFieldGenerator{    using System.Collections.Generic;    using System.Web.UI;    using System.Web.DynamicData;     

    /// <summary>    /// Bu sınıf veri listelemesini işlemlerini    /// özelleştirmek amacıyla yazılmıştır.     /// </summary>    public class CustomColumnGenerator : IAutoFieldGenerator    {        /// <summary>        /// Veri listelemesi için incelenecek        /// tablo değişkenini tutar        /// </summary>        protected MetaTable mTable = null;        #region IAutoFieldGenerator Members        /// <summary>        /// Listeleme işlemini gerçekleştirecek method        /// </summary>        /// <param name="control"></param>        /// <returns></returns>        public System.Collections.ICollection GenerateFields(Control control)        {            // Uygun kolonların biriktirildiği koleksiyon            List<DynamicField> dynFieldList = new List<DynamicField>();            // MetaTable atanmışsa            if (mTable != null)            {                // Her bir MetaColumn için                foreach (MetaColumn mColumn in this.mTable.Columns)                {                    // Scaffolding ataması yapılmışsa koleksiyona eklenmesi                    // uygun değildir. Eğer bu kontrol yapılmazsa ilgili                    // alan görüntülenecektir. Çünkü tüm sorumluluğu almış                    // bulunuyoruz                    if (                            !mColumn.Scaffold                        )                        continue;                     DynamicField dynField = new DynamicField();                    dynField.DataField = mColumn.Name;                    dynFieldList.Add(dynField);                }            }            return dynFieldList;        }        #endregion    }}

Scaffolding tekniğini denetleyerek görüntüleme işlemini üstlenen sınıfımız artık hazır. Şimdi sıra şablonlara göre bu denetleme işlemine geldi. Varsayılan şablonlar için bir enum tanımlayıp GenerateFields methodunda şablona görede bir denetleme yapmamız gerecektir.

namespace Eposta.Core.Enums{    public enum PageTemplateType    {        Details,        Edit,        Insert,        List,        ListDetails    }}

Özelleştirme işlemlerinin uygulanabilmesi için özel bir öznitelik geliştirip bildirimlerin bu öznitelik üzerinden gerçekleştirmesi gerekiyor bunun için aşağıdaki sınıfı kullanabiliriz.

[more]

using System;using Eposta.Core.Enums;

namespace Eposta.Core.CustomAttributes{    [AttributeUsage(AttributeTargets.Property)]    public class CustomHiddenColumnAttribute : Attribute    {        /// <summary>        /// İlgili alanın gizlenmesi gereken sayfa şablonları belirleniyor        /// </summary>        public PageTemplateType[] PageTemplateTypes { get; private set; }        /// <summary>        /// Parametresiz bir constructor yazılması mecburi ancak        /// kullanılmaması için hata fırlatılması daha uygun        /// </summary>        public CustomHiddenColumnAttribute()        {            throw new InvalidOperationException();        }        /// <summary>        /// Sayfa şablonlarının atama işleminin kolaylaştırılması        /// nedeniyle params yöntemi kullanılmıştır. Öznitelik         /// tanımlamasında AllowMultiple=true atamasıda yapılabilir        /// </summary>        /// <param name="PageTemplateParams"></param>        public CustomHiddenColumnAttribute(params PageTemplateType[] PageTemplateParams)        {            this.PageTemplateTypes = PageTemplateParams;        }    }}

Bu özniteliğin örnek tanımlaması aşağıdaki gibidir. Daha öncede belirttiğim gibi öznitelik yazmayı çok severim :) Bundan böyle sadece aşağıdaki kodu kullarak tüm kodu yazmadan işimizi halledebiliriz.

[CustomHiddenColumn(        PageTemplateType.List, PageTemplateType.ListDetails)]public string SmtpHost { get; set; }

Madem bu kadar kod yazdık birde MetaColumn nesneleri için genişletilmiş bir method yazarak kolonda ve şablona göre gizlilik bildirimi yapılmışmı diye kontrol edelimki her seferinde bir yığın kod yazmaktan kurtulalım.

using System.Linq;using Eposta.Core.CustomAttributes.UIHint;using Eposta.Core.CustomAttributes;using Eposta.Core.Enums;/// <summary>/// Ömer Faruk ZORLU/// </summary>namespace Eposta.Core.Extentions{    public static class MetaColumnExtentions    {        /// <summary>        /// Sayfa şablonuna göre alanın gizlilik durumunu dönderir        /// </summary>        /// <param name="mColumn"></param>        /// <param name="pTemplateType"></param>        /// <returns></returns>        public static bool IsHidden(this System.Web.DynamicData.MetaColumn mColumn, PageTemplateType pTemplateType)        {            // Kolon üzerinde atanmış ustomHiddenColumn özniteliği alınıyor            // Eğer çoklu tanımlama(AllowMultiple) ya izin verilmişse bir            // döngü içerisinde kontrol edilmesi gerekir            CustomHiddenColumnAttribute chCAttr =                mColumn.Attributes.OfType<CustomHiddenColumnAttribute>().FirstOrDefault();               //  Herhangi bir öznitelik ataması yoksa gizli değildir            if (chCAttr == null)                return false;             // İlgili alan için yapılan gizlilik bildirimleri parametrede            // gönderilmiş sayfa şablonunu içeriyorsa gizlidir            return chCAttr.PageTemplateTypes.Contains(pTemplateType);;        }    }}

Artık daha önce yazdığımız alan oluşturucu sınıf üzerinde özel gizleme yapımızı denetleyen kodlar yazabiliriz. Yukarıda yazdığımız sınıfa yeni eklenen kodlar kalın olarak yazılmıştır.

 

using System.Collections.Generic;using System.Web.DynamicData;using System.Web.UI;using Eposta.Core.Extentions;using Eposta.Core.Enums; namespace Eposta.Core.CustomFieldGenerator{    /// <summary>    /// Bu sınıf veri listelemesini işlemlerini    /// özelleştirmek amacıyla yazılmıştır.     /// </summary>    public class CustomFieldGenerator : IAutoFieldGenerator    {        /// <summary>        /// Veri listelemesi için incelenecek        /// tablo değişkenini tutar        /// </summary>        protected MetaTable mTable = null;

        protected PageTemplateType pTemplateType;         #region ctor         public CustomFieldGenerator(MetaTable _mTable, PageTemplateType _pTemplateType)        {            this.mTable = _mTable;            this.pTemplateType = _pTemplateType;        }         #endregion         #region IAutoFieldGenerator Members        /// <summary>        /// Listeleme işlemini gerçekleştirecek method        /// </summary>        /// <param name="control"></param>        /// <returns></returns>        public System.Collections.ICollection GenerateFields(Control control)        {            // Uygun kolonların biriktirildiği koleksiyon            List<DynamicField> dynFieldList = new List<DynamicField>();            // MetaTable atanmışsa            if (mTable != null)            {                // Her bir MetaColumn için                foreach (MetaColumn mColumn in this.mTable.Columns)                {                    // Scaffolding ataması yapılmışsa koleksiyona eklenmesi                    // uygun değildir. Eğer bu kontrol yapılmazsa ilgili                    // alan görüntülenecektir. Çünkü tüm sorumluluğu almış                    // bulunuyoruz                    if (                            !mColumn.Scaffold                        )                        continue;                     // List ve ListDetails şablonları için IsHidden                    // genişletilmiş methodu ve IsLongString methodunu                    // kontrol ediyoruz ancak Details şablonu için                    // IsLongString methodunu kontrol edip gizlersek                    // uzun metin alanlar görüntülenmeyecek yani ufak bir                    // bug oluşacaktır.                    switch (this.pTemplateType)                    {                        case PageTemplateType.Details:                            if (mColumn.IsHidden(this.pTemplateType))                                continue; // foreach                            break;                        case PageTemplateType.List:                            if (mColumn.IsHidden(this.pTemplateType) ||                                    mColumn.IsLongString)                                continue; // foreach                            break;                        case PageTemplateType.ListDetails:                            if (mColumn.IsHidden(this.pTemplateType) ||                                    mColumn.IsLongString)                                continue; // foreach                            break;                        default:                            break;                    }                     // Varsayılan şablonların haricinde uzun metinlerin                    // görüntülenmesini engellemek amacıyla bu kontrol                    // yapılıyor                    if (mColumn.IsLongString)                        continue;                     DynamicField dynField = new DynamicField();                    dynField.DataField = mColumn.Name;                     dynFieldList.Add(dynField);                }            }            return dynFieldList;        }        #endregion    }}

Son adımında ise ilgili veri kontrolüne veri listeleme işlemini bizim üstleneceğimizi bildirmek gerekiyor. GridView nesnesi için ColumnGenerator DetailsView içinse RowGenerator parametresi kullanılıyor. Son haliyle List.aspx.cs ve Details.aspx.cs şablon kodları aşağıdaki gibi tanımlanmalıdır.

 

List.aspx.cs kodları:

using System;using System.Web.DynamicData;using Eposta.Core.CustomFieldGenerator;using Eposta.Core.Extentions; public partial class List : System.Web.UI.Page{    protected MetaTable table;    protected void Page_Init(object sender, EventArgs e)    {        DynamicDataManager1.RegisterControl(GridView1, true /*setSelectionFromUrl*/);        table = GridDataSource.GetTable();        GridView1.ColumnsGenerator = new CustomFieldGenerator(table, Eposta.Core.Enums.PageTemplateType.List);    }    protected void Page_Load(object sender, EventArgs e)    {        Title = table.DisplayName;        InsertHyperLink.NavigateUrl = table.GetActionPath(PageAction.Insert);         // Disable various options if the table is readonly        if (table.IsReadOnly)        {            GridView1.Columns[0].Visible = false;            InsertHyperLink.Visible = false;        }    }    protected void OnFilterSelectedIndexChanged(object sender, EventArgs e)    {        GridView1.PageIndex = 0;    }}


Details.aspx.cs kodları:

using System;using System.Linq;using System.Web.DynamicData;using System.Web.UI.WebControls;using Eposta.Core.CustomFieldGenerator;using Eposta.Core.CustomAttributes; public partial class Details : System.Web.UI.Page {    protected MetaTable table;    protected void Page_Init(object sender, EventArgs e) {        DynamicDataManager1.RegisterControl(DetailsView1);    }    protected void Page_Load(object sender, EventArgs e) {        table = DetailsDataSource.GetTable();        DetailsView1.RowsGenerator = new CustomFieldGenerator(table, Eposta.Core.Enums.PageTemplateType.Details);

        Title = table.DisplayName;

        ListHyperLink.NavigateUrl = table.ListActionPath;    }    protected void DetailsView1_ItemDeleted(object sender, DetailsViewDeletedEventArgs e) {        if (e.Exception == null || e.ExceptionHandled) {            Response.Redirect(table.ListActionPath);        }    }}

Çözüm itibari ile Grup tablosunda yapılan tanımlalarla listeleme sayfa şablonlarında bazı alanların gizlenmesini güncelleme ve detaylı görüntüleme sayfa şablonlarında ise kullanıcının verileri görüntülemesine ve düzenlemesine imkan tanımış olduk. Ekran görüntüleri aşağıdaki gibidir.

Güncelleme ekranı:

Listeleme ekranı:

Bir yazının daha sonuna geldik. Kullanıcı gereksinimleriyle alakalı makaleler devam edecektir. Bir sonraki makalede görüşmek üzere. Yaşasın dinamizm!

Ömer Faruk ZORLU

ASP.NET ,

“ExtJS, Coolite ne ola ki? ” Yazı Dizisi Vol.1

30 Nisan 2010

Günümüz Web programcılığı alanında vazgeçemeyeceğimiz bir istemci-taraflı
yorumlanan script dilidir JavaScript. Hayatımıza 1995'de Netscape 2.0 ile
birlikte giren , önceleri  Mocha daha sonra
LiveScript
ve en son JavaScript adını alan (Sun
Microsystems'ın Java sıyla bir alakası yok.)
müthiş buluş diye
tanımlayabiliriz bence JavaScript'i. Web ile uğraşan herkes, en azından bir kere
kullanmıştır eminim. Özellikle görsel sunum tarafında, çok fazla varyasyon elde
etmemize yardımcı oluryor. Hatırlıyorum da , küçükken bir site tasarımıyla
uğraşıyordum ve sitede havai fişek gösterisi olsun istemiştim. O zamanlar da
hazır JavaScript kodu bulabileceğimiz çok kaynak vardı. Birkaç saniye içinde,
web sitesinde havai fişek gösterisi efektini yakalayabilmiştim. Hiç unutmam
içimden : "Allah JavaScript'i başımızdan eksik etmesin!" demiştim.

Sadece görsellik ile bitmiyor JavaScript'in bu kadar popüler olmasındaki
etmenler. AJAX(Asynchoronous JavaScript And XML) kavramının temeli,
isminin açılımından da anlaşılacağı üzere, JavaScript'tir. XMLHttpRequest
objesiyle (IFRAME de kullanılabilir) yapılan bilgi transferi sonrasında, bu
bilgiyi dinamik bir şekilde kullanıcıya sunmak ve bilgiyle etkileşim halinde olmak için
JavaScript kullanırız.

JavaScript'in yukarıda saydığım özelliklerini ve gücünü göz önüne alan
firmalar, açık kaynaklı JavaScript kütüphaneleri oluşturdular. Bu çalışmaların
en çok göz önünde olanı jQuery oldu. AJAX kullanımının artmasıyla jQuery
kullanımı da arttı. Hakkında birçok makale ve kitap yazıldı.
İlgilenenler jQuery.com adresinde, BOOKS ABOUT JQUERY bölümünü inceleyebilirler.
Uğur Umutluoğlu'nun da "ASP.NET’te
jQuery ile AJAX İşlemleri
" başlıklı bir makalesi var.

jQuery'nin yanı sıra, Türkiye'de hakkında pek konuşulmayan; ama üstüne birçok
firmanın framework'ler geliştirdiği bir kütüphane var :
ExtJS
. Şans eseri varlığından haberim oldu
aslında. İş görüşmesine gittiğim firmada (şuan çalıştığım firma), görüşme
sırasında adı geçti. Biraz ARGE çalışmasından sonra aslında ne kadar kullanışlı
bir kütüphane olduğunun farkına vardık.

Tam JS kodlarına gömülmeye hazırlandığım sırada karşıma
Coolite
adında,
efsane, birşey çıktı. Efsane olmasının nedeni .Net'de Markup kodu yazarak Ext objeleri yaratma gücü
veren bir framework olmasıydı. Hatta, yarattığımız objelere C# ya da VB kodu
yazarak ulaşma yolunu sunuyordu. Aramızda kalsın, JS kodu yazmaktansa C#
ya da VB kodu yazmayı tercih ederim.

Bugün yaptığım bir araştırmaya göre de, TURKCELL TEKNOLOJIlabs ExtJS
kütüphanesini referans alarak, Java için açık kaynaklı  bir framework
geliştirmişler. Adı da ISOLA.
Coolite'ın Java versiyonu. Güzel tarafı Türkcell'in bu framework'ü open source
dağıtması. Coolite'ın ücretli bir framework olduğunu düşünürsek, bu nokta
ISOLA'yı daha üstün kılıyor bence.

Tüm bu gelişmeler beni bu konularda makaleler dizisi yazmaya itti ve işte
burdayız.

Genel akış içinde ExtJS ve Coolite nedir, kullanım alanları nelerdir, nasıl
kullanırız, Sıkça Sorulan(bilecek) Sorular başlıklarının altına dallanarak
gelişicek konularımız. Şuan kafamda oluşan plana göre, yazı dizisini yine geniş
bir video dizisi seyredicek.Isola hakkında da yazmak isterdim; fakat Java
konusunda okadar bilgiye sahip değilim ne yazık ki.

Yukarıdaki bölüm sadece giriş paragraflarından oluşuyor. Artık derinlere
inmenin zamanı geldi.

Not (1) : JavaScript ve Ajax'ı bildiğinizi varsayarak başlıyorum. Eğer onları
da yazmaya kalkarsam çok ama çok uzun bir yazı dizisi olur. Bu kavramlar
hakkında bilgisi olmayan arkadaşlara Daron
Yöndem
'in ASP.NET AJAX ve ASP.NET 3.5 AJAX adlı kitaplarını öneririm.

Bölüm 1 : ExtJS'e Giriş

Kısaca tanımlamak gerekirse ExtJS, çoklu browser destekli, güçlü kullanıcı
arayüzleri programlamanıza yardımcı, Ajax işlemlerini yapmanızı çok
kolaylaştıran, Open Source ve Ticari lisansları olan bir JavaScript kütüphanesi.

Lisanslamalarına da dikkat çektim; çünkü eğer ticari bir ürün yaratmak için
kullanmak istiyorsanız Ext kütüphanesini, ücretini verip almanız gerekiyor.

Çoklu Browser(cross-browser) destekli dedim; peki hangi browserları
destekliyor diye bakıcak olursak,

  • Internet Explorer 6 ve sonraki sürümler
  • Firefox 1.5 ve sonraki sürümler
  • Safari 3 ve sonraki sürümler
  • Opera 9 ve sonraki sürümler

Bu maddelere ek olarak Google Chrome'da da güzel bir performans
sunuyor.(Bizzat kendim denedim)

Çoklu Browser desteği özelliğini test ettiğimde (Puanlar 5 üzerindendir) :

  • Internet Explorer 8 'de 4 puanlık
  • Mozilla Firefox 3.5 'da 5 puanlık
  • Google Chrome 'da 4 puanlık

randıman aldım.Diğer browserları deneme şansım olmadı.Eğer siz denerseniz
sonuçları benimle de paylaşın lütfen.

Arayüz programlama konusunda ise gerçekten çok geniş bir yelpaze sunuyor bize
ExtJS kütüphanesi. Benim Ext'e, tabir yerindeyse, aşık olmamı sağlayan bir örnek
vereyim size :
Desktop
örneği.

İşte Desktop

Bu görüntüyü elde etmek için yapmanız gerekenleri, yazmanız gereken JS
kodlarını, bir sonraki makalemizde inceleyeceğiz.

Tanımda bahsettiğim bir başka nokta ise AJAX işlemlerini kolaylaştırdığıydı.
Aşağıdaki örneklerden de anlaşılacağı üzere, jQuery ve Ext deki AJAX işlemleri
çok da farklı değil. Hangi sayfaya istekte bulunacağını, işlem tamamlandığında
hangi fonksiyona gideceğini, işlem tamamlanamadığında hangi fonksiyona
gideceğini, varsa parametreleri tanımlayıp işlemi bitiriyoruz.

 [ExtJS]

Ext.Ajax.request({

          
url: 'sayfa.aspx',

          
success: fonksiyon1,

          
failure: fonksiyon2,

          
params: { ID: '1' }

});

 

 [jQuery]

 

       
$.ajax({

           
type: 'GET',

           
url: 'sayfa.aspx',

           
success: fonksiyon1

           
},

           
error: fonksiyon2

           
}

        );

 

Böylece bu uzun soluklu yazı dizimizin ilk bölümünün sonuna geldik. 2.Bölüm '
de UI tasarlarken ne yapmalıyız, ExtJS 'nin bize sunduğu UI objelerini
kullanarak neler yapabiliriz konuları üzerinde duruyor olacağız.

Temelde ExtJS kullanımını konuşucağız bir süre. Bu konulara hakim olduktan
JS kodları arasında boğulmadan Coolite ile .NET tarafında ne sihirbazlıklar
yapabiliceğimizi göreceğiz.

Merak etmeyin arayı soğutmayacağım, bu aralar müsaitim bol bol yazarım.

Bir sonraki bölümde görüşmek üzere.

 

Bu makalemde " ExtJS ve Coolite ne ola ki? " yazı dizisinin devamı olarak
ExtJS objelerinden "Grid" objesini nasıl kullanırız?, datada grid üzerinden
nasıl değişiklik yaparız? ana başlıkları altında bir çok kavrama değineceğim. 

Lafa başlamadan önce iki makale arasında bu kadar ara verdiğim için herkesten
özür dilerim. Okul ve iş yoğunluğu arasında boğulmakla uğraştığımdan makaleye
zaman ayıramadım. Bu aksaklığı telafi etmeye çalışacağıma söz veriyorum.

ExtJS' nin genel hatlarının üstünden geçmiştik. Şimdi ise birçok konuyu
harmanlayan bir senaryo üzerinden devam edelim. Amacımız, ExtJS objelerini
kullanarak hem güzel bir UI elde etmek, hem de nasıl data işlemleri yaparız
sorusunun cevabını aramak olacak. Senaryomuz da şu olsun : Biz bir meyve
üreticisiyiz. Ürettiğimiz meyveleri görmek, kayıtlarının üstünde değişiklik
yapmak ve onları satmak istiyoruz.

Senaryomuzu hayata geçirmeye başlamadan önce yapmamız gereken bazı önemli
şeyler var :

  1. ExtJS' nin güncel kütüphanesini (versiyon 3.1.1)

    indirmek
  2. HTML ve JavaScript kodlarımızı düzenlemek için uygun bir editör seçmek:
    Visual Studio ortamını kullanabilirsiniz.

Bu iki öngereksinimi de gerçekleştirdikten sonra projemize başlamamız için
önümüzde bir engel kalmıyor. Visual Studio' da bir "Web Site" oluşturarak
başlayabiliriz.

Projeyi oluşturduktan sonra indirdiğimiz ExtJS kütüphanesinden bir kaç
dosyayı projemize dahil etmeliyiz. Bunun için site içinde "extLib" adlı bir
dosya oluşturduktan sonra ExtJS kütüphanesinin kök dosyasından yola çıkarak,

  1. ext-all.js javascript dosyasını,
  2. "adaptor" klasörünü,
  3. "resources" klasörünü,
  4. "examples/ux" klasörünü,
  5. "examples/shared" klasörünü,
  6. "examples/grid/grid-example.css" stil dosyasını

oluşturduğumuz klasöre kopyalayalım. Neden bu dosyaları ve klasörleri
kopyaladığımıza gelicek olursak; içlerinde bize lazım olan temel ext javascript
dosyaları ve css dosyaları bulunuyor. Bu yüzden bu öğeleri projemize dahil
etmemiz gerekiyor.

İşlemlerden sonra oluşması gereken ekran görüntüsü

Kodlama kısmına yavaş yavaş geçerken neler yapmamız gerektiğine adım adım
bakarsak,

1) <head></head>  tagları arasına gerekli olan referanlarımızı eklemeliyiz. Bahsettiğim
javascript ve css referansları aşağıdaki gibidir.

   
<!–JS dosyaları–>

    <script
type="text/javascript"
src="extLib/adapter/ext/ext-base.js"></script>

    <script
type="text/javascript"
src="extLib/ext-all.js"></script>

    <script
type="text/javascript"
src="extLib/ux/RowEditor.js"></script>

 

   
<!–CSS dosyaları–>

    <link
rel="stylesheet"
type="text/css"
href="extLib/resources/css/ext-all.css"
/>

    <link
rel="stylesheet"
type="text/css"
href="extLib/grid-examples.css"
/>

    <link
rel="stylesheet"
type="text/css"
href="extLib/shared/examples.css"
/>

    <link
rel="stylesheet"
type="text/css"
href="extLib/ux/css/RowEditor.css"
/>

 

    <style
type="text/css">

       
.icon-user-add {

           
background-image:
url(extLib/shared/icons/fam/user_add.gif)
!important;

        }

       
.icon-user-delete {

           
background-image:
url(extLib/shared/icons/fam/user_delete.gif)
!important;

        }       

    </style>

 

Bu noktada "ext-base.js" ve "ext-all.js" dosyalarının bizim için vazgeçilmez
olduğunu vurgulamakta fayda var. Her ne yapıyor olursak olalım, bu iki dosyayı
mutlaka referans almalıyız. Onların yanında kullandığımız ekstra özellikleri
içeren JavaScript dosyalarını da projeye dahil etmeliyiz. Örneğin, benim
örneğimde RowEditor kullanıldığından, aynı isimli JavaScript dosyasını da
projeye dahil ettim.

2) Eklememiz gereken bütün dosyaları
projemize referans aldıktan sonra, bütün işlemlerimizi yapacağımız JavaScript
kodumuzu oluşturmaya geçebiliriz.

Uygulamada kullanacağımız dataların şablonunu hazırlamalıyız. 

         
var Urun = Ext.data.Record.create([{

             
name: 'ad',

             
type: 'string'

         
}, {

             
name: 'tip',

             
type: 'string'

         
}, {

             
name: 'uretim',

             
type: 'date',

             
dateFormat: 'n/j/Y'

         
}, {

             
name: 'adet',

             
type: 'integer'

         
}, {

             
name: 'fiyat',

             
type: 'integer'}]);

Yukarıda da gördüğünüz gibi Ext.data.Record.create metodunu kullanarak; alan
adları ad, tip, uretim, adet ve fiyat olan bir tablo oluşturduk ve adına da
"Urun" dedik.

Doğal olarak bu şablonu kullanarak data oluşturmamız gerekmektedir. Ben bu
örnek için istemci-taraflı data oluşturup oynamaları o datanın üstünde yaparız
diye düşündüm. Bir sonraki makalemde sunucu-taraflı dataları çekip istemci
tarafında oynamaları yapıp değişikliklerin kaydedilmesi için yine sunucu
tarafına göndermeyi nasıl yapıcağımıza göz atacağız. Dediğim gibi kayıt
oluşturmak için bir metoda ihtiyacımız var.

         var
genData = function() {

            
     var data = [];

                 
var s = new
Date(2007, 0, 1);

                 
var now = new
Date(), i = -1;

                 
while (s.getTime() < now.getTime()) {

                     
var ecount = Ext.ux.getRandomInt(0, 1);

                     
for (var
i = 0; i < ecount; i++) {

                         
var name = Ext.ux.generateName();

                         
data.push({

                             
uretim: s.clearTime(true).add(Date.DAY,
Ext.ux.getRandomInt(0, 27)),

                             
ad: name,

                             
tip: 'Meyve',

                             
adet: Ext.ux.getRandomInt(10, 100),

                             
fiyat: Ext.ux.getRandomInt(10, 100)

                         
});

                     
}

                     
s = s.add(Date.MONTH, 1);

                 
}

                 
return data;

             
}

Bu fonksiyon bizim işimizi görür. Nasıl işlediğine biraz göz atalım : 2007
den başlayarak günümüze kadar düzgün devam etmeyen bi döngü var. Her bir turda
oluşturduğumuz diziye, alan değerleri gelişi güzel olan yeni bir kayıt
ekliyoruz. Bu sayede yaklaşık on yedi adet, birbirinden farklı kayıt elde etmiş
oluyoruz.

 Bir sonraki adımda oluşan bu kayıtları grid objemize nasıl bağlayacağız
sorunu ortaya çıkıyor. Tabii ki imdadımıza yetişen bir Ext objesi var :
Store.
Store objesi, bizim datalarımızı, üstünde yapılan
değişiklikleri, son halini kısaca herşeyi barındıran, çok özel bir nesne.
Kullanımı ise çok kolay.

             
var store = new
Ext.data.GroupingStore({

                 
reader: new Ext.data.JsonReader({ fields:
Urun }),

                 
//datayı nereden alıcagını belirttik.

                 
data: genData(),

                 
sortInfo: { field: 'uretim', direction:
'ASC' }

             
});

Store nesnesi kayıtları json halinde saklıyor. Bu yüzden hangi tip kayıtları
okuyacağını belirtmemiz gerekiyor. Ayrıca bir çok başka özelliği daha var.
Yukarıda örnek olarak sortInfo özelliğine hangi alanı ne tipte
sıralayacağını belirtmişim. API dökümantasyonunu incelerseniz, bu güzel nesnenin
bir çok farklı özelliğe sahip olduğunu göreceksiniz.

Sıra geldi Grid nesnemizi yaratmaya.

//gridimizi yaratalım

             
var grid = new
Ext.grid.GridPanel({

                 
//grid oluştururken 'store' degerini vermeyi
unutmayınız.

                 
store: store,

                 
width: 600,

                 
region: 'center',

                 
margins: '0 5 5 5',

                 
autoExpandColumn: 'ad',

                 
plugins: [editor],

                 
view: new Ext.grid.GroupingView({

                     
markDirty: false

                 
}),

                 
// 'topbar' daki butonlarımızı oluşturuyoruz.

                 
tbar: [{

                     
iconCls: 'icon-user-add',

                     
text: 'Urun Ekle',

                     
handler: function() {

                         
var e = new
Urun({

                             
ad: 'Meyve Adi',

                             
tip: 'Meyve',

                             
uretim: (new Date()).clearTime(),

                             
adet: 1,

                             
fiyat: 10

                         
});

                         
editor.stopEditing();

                         
store.insert(0, e);

                         
grid.getView().refresh();

                         
grid.getSelectionModel().selectRow(0);

                         
editor.startEditing(0);

                     
}

                 
}, {

                     
ref: '../removeBtn',

                     
iconCls: 'icon-user-delete',

                     
text: 'Urun Sil',

                     
disabled: true,

                     
handler: function() {

                         
editor.stopEditing();

                         
var s =
grid.getSelectionModel().getSelections();

                         
for (var
i = 0, r; r = s[i]; i++) {

                             
store.remove(r);

                         
}

                     
}

                 
}, {

                     
ref: '../removeBtn',

                     
iconCls: 'icon-user-delete',

                     
text: 'Urun Sat',

                     
disabled: true,

                     
handler: function() {

                         
editor.stopEditing();

                         
var s =
grid.getSelectionModel().getSelections();

                         
for (var
i = 0, r; r = s[i]; i++) {

                             
if (r.data['adet']
== 1) {

                                 
para = para + r.data['fiyat'];

                                 
store.remove(r);

                                 
Ext.MessageBox.alert('Satis',
'Satis Islemi tamamlandi. Urun kalmadi!Suan '
+ para + ' kadar paraniz var.');

                             
}

                             
else {

                                 
para = para + r.data['fiyat'];

                                 
r.set("adet",
r.data['adet'] – 1);

                                 
r.commit();

                                 
Ext.MessageBox.alert('Satis',
'Satis Islemi tamamlandi. Suan ' + para
+ ' kadar paraniz var.');

                             
}

                         
}

                     
}

}],

                     
//Kolonlarımızı özellestirelim.

                     
columns: [

       
new Ext.grid.RowNumberer(),

        {

           
id: 'ad',

           
header: 'Meyve Adi',

           
dataIndex: 'ad',

           
width: 220,

           
sortable: true,

           
editor: {

               
xtype: 'textfield',

               
allowBlank: false

           
}

        }, {

           
header: 'Tip',

           
dataIndex: 'tip',

           
width: 150,

           
sortable: true,

           
editor: {

               
xtype: 'textfield',

               
blankText: "Bos Birakma!",

               
allowBlank: false

           
}

        }, {

           
xtype: 'datecolumn',

           
header: 'Uretim Tarihi',

           
dataIndex: 'uretim',

           
format: 'm/d/Y',

           
width: 100,

           
sortable: true,

           
groupRenderer: Ext.util.Format.dateRenderer('M
y'
),

           
editor: {

               
xtype: 'datefield',

               
allowBlank: false,

               
blankText: "Bos Birakma!",

               
maxValue: (new Date()).format('m/d/Y')

           
}

        },

        {

           
header: 'Adet',

           
dataIndex: 'adet',

           
minValue: 1,

           
width: 50,

           
sortable: true,

           
editor: {

               
xtype: 'numberfield',

               
blankText: "Bos Birakma!",

               
allowBlank: false

           
}

        },

        {

           
header: 'Fiyat',

           
dataIndex: 'fiyat',

           
minValue: 1,

           
width: 50,

           
sortable: true,

           
editor: {

               
xtype: 'numberfield',

               
allowBlank: false

           
}

}]

                 
});

Öncellikle Grid' imizin store özelliğine değer atamamız gerekiyor.
Bu değer tabii ki bizim oluşturduğumuz store nesnesinin adı olacak.

Bu noktada size Grid nesnesinin Top Bar özelliğinden bahsetmek
istiyorum. Top Bar bize grid üzerine buton koyma imkanı sağlayan bir
bölüm. Bu sayede grid üzerinde işlem yaparken kullanacağımız butonları buraya
tanımlayabiliyoruz. Yukarıda da görebileceğiniz üzere Top Bar' a 3 tane
buton tanımladık. Bu butonların her birinin text, disabled, iconCls
gibi özellikleri var. Fakat handler özelliği diğer
hepsinden daha çekici bana kalırsa. Bu özelliğe istersek işlem yapıcak
foksiyonun sadece ismini tanımlarız, istersek örnekteki gibi fonksiyonu oraya
yazarız.

Son olarak sıra geldi grid üzerindeki kolonları tanımlamaya. Kolonları
özelleştirme noktasında da Ext bize baya bir esneklik sağlıyor. Başlığından,
editörünün kısıtlanmasına kadar örnekte de görebileceğiniz bir çok
özelleştirmede bulunabiliyoruz.

Böylece kodumuzdaki önemli noktaların ve kavramların üstünden geçmiş olduk.
Kodun tamamı aşağıdaki gibidir.

[Default.aspx]

<%@
Page
Language
="C#"
AutoEventWireup="true"
CodeFile="Default.aspx.cs"
Inherits="_Default"
%>

 

<!DOCTYPE
html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html
xmlns="http://www.w3.org/1999/xhtml">

<head
runat="server">

    <title></title>

   
<!–JS dosyaları–>

    <script
type="text/javascript"
src="extLib/adapter/ext/ext-base.js"></script>

    <script
type="text/javascript"
src="extLib/ext-all.js"></script>

    <script
type="text/javascript"
src="extLib/ux/RowEditor.js"></script>

 

   
<!–CSS dosyaları–>

    <link
rel="stylesheet"
type="text/css"
href="extLib/resources/css/ext-all.css"
/>

    <link
rel="stylesheet"
type="text/css"
href="extLib/grid-examples.css"
/>

    <link
rel="stylesheet"
type="text/css"
href="extLib/shared/examples.css"
/>

    <link
rel="stylesheet"
type="text/css"
href="extLib/ux/css/RowEditor.css"
/>

 

    <style
type="text/css">

       
.icon-user-add {

           
background-image:
url(extLib/shared/icons/fam/user_add.gif)
!important;

        }

       
.icon-user-delete {

           
background-image:
url(extLib/shared/icons/fam/user_delete.gif)
!important;

        }       

    </style>

 

    <script
type="text/javascript">

     
// gelişi güzel isim oluşturma

      (function()
{

         
//Birkaç meyve adı tanımlaması yaptık

         
var meyveler = ['Elma',
'Armut',
'Muz',
'Kavun',
'Karpuz',
'Portakal',
'Nar',
'Mandalina',
'Yeni Dunya',
'Ananas'];

         
var firstLen = meyveler.length;

 

         
//adından da anlaşılacağı üzere gelişi güzel
sayı değerleri oluşturmamıza

         
//yarayan bir fonksiyon

         
Ext.ux.getRandomInt = function(min,
max) {

             
return Math.floor(Math.random() * (max
- min + 1)) + min;

         
}

 

         
//yine gelişi güzel bir şekilde meyve
isimleri oluşturacağız.

         
Ext.ux.generateName = function() {

             
var name =
meyveler[Ext.ux.getRandomInt(0, firstLen - 1)] +
" Meyvesi " +
Ext.ux.getRandomInt(0, 10);

             
//dataların 'ad' degerlerini identity olarak
da kullandığımız için bir kez

             
//oluşturduğumuz ismi tekrar
oluşturmamalıyız

             
if
(Ext.ux.generateName.usedNames[name]) {

                 
return Ext.ux.generateName();

             
}

             
Ext.ux.generateName.usedNames[name] = true;

             
return name;

         
}

         
Ext.ux.generateName.usedNames = {};

 

      })();

 

      Ext.onReady(function()
{

         
var para = 0;

 

         
//Urun adlı kayıt şablonunun alan adlarını
tanıtalım.

         
var Urun = Ext.data.Record.create([{

             
name: 'ad',

             
type: 'string'

         
}, {

             
name: 'tip',

             
type: 'string'

         
}, {

             
name: 'uretim',

             
type: 'date',

             
dateFormat: 'n/j/Y'

         
}, {

             
name: 'adet',

             
type: 'integer'

         
}, {

             
name: 'fiyat',

             
type: 'integer'}]);

             
//

             
//Kullanıcağımız dataları oluşturalım.

             
var genData =
function() {

                 
var data = [];

                 
var s = new
Date(2007, 0, 1);

                 
var now =
new
Date(), i = -1;

                 
while (s.getTime() < now.getTime()) {

                     
var ecount = Ext.ux.getRandomInt(0,
1);

                     
for (var
i = 0; i < ecount; i++) {

                         
var name = Ext.ux.generateName();

                         
data.push({

                             
uretim: s.clearTime(true).add(Date.DAY,
Ext.ux.getRandomInt(0, 27)),

                             
ad: name,

                             
tip: 'Meyve',

                             
adet: Ext.ux.getRandomInt(10, 100),

                             
fiyat: Ext.ux.getRandomInt(10, 100)

                         
});

                     
}

                     
s = s.add(Date.MONTH, 1);

                 
}

                 
return data;

             
}

             
//

             
//Json tipindeki datalarımızı tutacağımız
store objemiz

             
var store =
new
Ext.data.GroupingStore({

                 
reader: new Ext.data.JsonReader({
fields: Urun }),

                 
//datayı nereden alıcagını belirttik.

                 
data: genData(),

                 
sortInfo: { field: 'uretim',
direction: 'ASC' }

             
});

 

             
//Editor butonlarımızın etiketlerin
dilediğimiz gibi değiştirebiliriz

             
var editor =
new Ext.ux.grid.RowEditor({

                 
saveText: 'Kaydet',

                 
cancelText: 'Iptal'

             
});

 

             
//

             
//gridimizi yaratalım

             
var grid =
new
Ext.grid.GridPanel({

                 
//grid oluştururken 'store' degerini vermeyi
unutmayınız.

                 
store: store,

                 
width: 600,

                 
region: 'center',

                 
margins: '0 5 5 5',

                 
autoExpandColumn: 'ad',

                 
plugins: [editor],

                 
view: new Ext.grid.GroupingView({

                     
markDirty: false

                 
}),

                 
// 'topbar' daki butonlarımızı
oluşturuyoruz.

                 
tbar: [{

                     
iconCls: 'icon-user-add',

                     
text: 'Urun Ekle',

                     
handler: function() {

                         
var e = new
Urun({

                             
ad: 'Meyve Adi',

                             
tip: 'Meyve',

                             
uretim: (new Date()).clearTime(),

                             
adet: 1,

                             
fiyat: 10

                         
});

                         
editor.stopEditing();

                         
store.insert(0, e);

                         
grid.getView().refresh();

                         
grid.getSelectionModel().selectRow(0);

                         
editor.startEditing(0);

                     
}

                 
}, {

                     
ref: '../removeBtn',

                     
iconCls: 'icon-user-delete',

                     
text: 'Urun Sil',

                     
disabled: true,

                     
handler: function() {

                         
editor.stopEditing();

                         
var s =
grid.getSelectionModel().getSelections();

                         
for (var
i = 0, r; r = s[i]; i++) {

                             
store.remove(r);

                         
}

                     
}

                 
}, {

                     
ref: '../removeBtn',

                     
iconCls: 'icon-user-delete',

                     
text: 'Urun Sat',

                     
disabled: true,

                     
handler: function() {

                         
editor.stopEditing();

                         
var s =
grid.getSelectionModel().getSelections();

                         
for (var
i = 0, r; r = s[i]; i++) {

                             
if (r.data['adet']
== 1) {

                                 
para = para + r.data['fiyat'];

                                 
store.remove(r);

                                 
Ext.MessageBox.alert('Satis',
'Satis Islemi tamamlandi. Urun
kalmadi!Suan '
+ para + ' kadar
paraniz var.'
);

                             
}

                             
else {

                                 
para = para + r.data['fiyat'];

                                 
r.set("adet",
r.data['adet'] – 1);

                                 
r.commit();

                                 
Ext.MessageBox.alert('Satis',
'Satis Islemi tamamlandi. Suan ' +
para + ' kadar paraniz var.');

                             
}

                         
}

                     
}

}],

                     
//Kolonlarımızı özellestirelim.

                     
columns: [

       
new Ext.grid.RowNumberer(),

        {

           
id: 'ad',

           
header: 'Meyve Adi',

           
dataIndex: 'ad',

           
width: 220,

           
sortable: true,

           
editor: {

               
xtype: 'textfield',

               
allowBlank: false

           
}

        }, {

           
header: 'Tip',

           
dataIndex: 'tip',

           
width: 150,

           
sortable: true,

           
editor: {

               
xtype: 'textfield',

               
blankText: "Bos Birakma!",

               
allowBlank: false

           
}

        }, {

           
xtype: 'datecolumn',

           
header: 'Uretim Tarihi',

           
dataIndex: 'uretim',

           
format: 'm/d/Y',

           
width: 100,

           
sortable: true,

           
groupRenderer: Ext.util.Format.dateRenderer('M
y'
),

           
editor: {

               
xtype: 'datefield',

               
allowBlank: false,

               
blankText: "Bos Birakma!",

               
maxValue: (new Date()).format('m/d/Y')

           
}

        },

        {

           
header: 'Adet',

           
dataIndex: 'adet',

           
minValue: 1,

           
width: 50,

           
sortable: true,

           
editor: {

               
xtype: 'numberfield',

               
blankText: "Bos Birakma!",

               
allowBlank: false

           
}

        },

        {

           
header: 'Fiyat',

           
dataIndex: 'fiyat',

           
minValue: 1,

           
width: 50,

           
sortable: true,

           
editor: {

               
xtype: 'numberfield',

               
allowBlank: false

           
}

}]

                 
});

                 
//

                 
//

                 
var layout =
new Ext.Panel({

                     
title: 'Meyveler',

                     
layout: 'border',

                     
layoutConfig: {

                         
columns: 1

                     
},

                     
width: 600,

                     
height: 600,

                     
items: [grid]

                 
});

                 
layout.render(Ext.getBody());

 

                 
grid.getSelectionModel().on('selectionchange',
function(sm) {

                     
grid.removeBtn.setDisabled(sm.getCount() < 1);

                 
});

             
});

  </script>

 

    <link
rel="stylesheet"
type="text/css"
href="extLib/resources/css/ext-all.css"
/>

    <link
rel="stylesheet"
type="text/css"
href="extLib/grid-examples.css"
/>

    <link
rel="stylesheet"
type="text/css"
href="extLib/shared/examples.css"
/>

    <link
rel="stylesheet"
type="text/css"
href="extLib/ux/css/RowEditor.css"
/>

 

 

 

</head>

<body>

    <form
id="form1"
runat="server">

    <div>

    </div>

    </form>

</body>

</html>

 

Tüm bu işlemler sonrasında aşağıdaki gibi bir görüntü
elde edeceğiz.

 

 

 

Böylece bu makalemizin de sonuna gelmiş olduk. Eğer
örnekteki herhangi bir yeri anlamadığınızı düşünüyorsanız bana mail aracılığıyla
ulaşabilirsiniz.

 

Serinin devamında sunucu-taraflı data işlemlerine
değiniyor olacağız, yani işin içine biraz AJAX girecek.

 

Herkese iyi kodlamalar.


MetaData üzerinden öznitelik ataması yapılarak doğrulama, biçimleme ve özel alan şanlonlarını(FieldTemplates) nasıl kullanacağımızdan bahsetmiştik.

Bu makale özel özniteliklerin ve alan şablonlarının geliştirilmesi konusunu içerecektir. Bu nedenle FileUpload kontrolünün kullanımı ve alan şablonlarının çalışma mantığıyla özel özniteliklerin kullanımı hakkında bilgi sahibi olacağız.

Önceki makalemde bahsettiğim gibi ASP.NET Dynamic Data Web Site ile FileUpload işlemlerini klasik yöntemlerle kullanamıyoruz.

 
ASP.NET Dynamic Data Web Sitesinde Ajax Toolkit ile birlikte kullanıyorsanız ve FileUpload bileşeniniz UpdatePanel içerisinde yer alıyorsa ScriptManager nesnesinin EnablePartialRendering özelliğini false olarak atamanız gerekiyor.

Kodlamaya başlamadan önce yapılması gereken işlemleri özetlemenin faydalı olduğunu düşünüyorum. Kodlama detayları ilgili sınıfların içinde yer almaktadır.

  1. Zorunlu olmasa da dosyanın sunucuya yükleme işleminin gerçekleştirileceği yardımcı bir sınıf.
  2. Her bir dosya yükleme alanı için dosya türü, boyutu vb. bildirimleri yapmak için öznitelik tabanlı bir sınıf
  3. Dosya yükleme alanlarının ekleme ve güncelleme ekranı için FileUpload kontrolü listeleme ve detay sayfaları için ise dosya türüne göre yüklenen dosyaya uygun simge, önizleme vb. için alan şablonlarının(FieldTemplate) gerekli hazırlanması.
  4. İşlevin kazandırılacağı alanlara gerekli öznitelik tanımlamasının yapılması

Temel düzeyde dosya yükleme işlemini gerçekleştirmek üzere aşağıdaki sınıfı kullanacağız

FileUploadUtil.cs

using System;
using System.Web;
using System.IO;
using Eposta.Core.CustomAttributes;
using Eposta.Core.Extentions;
using System.Text.RegularExpressions;

namespace Eposta.Core.Utils
{
    /// <summary>
    /// Ömer Faruk ZORLU
    /// </summary>
    public class FileUploadUtil
    {
        private string _fileName;

        /// <summary>
        /// Dosyanın yükleneceği fiziksel konum
        /// Örnek kullanım: HttpContext.Current.Server.MapPath("~/Uploads/")
        /// </summary>
        public string UploadDictionaryPath { get; set; }

        /// <summary>
        /// Yükleme işlemi başarısız olmuşsa bu değer
        /// üzerinden hata mesajı okunabilir
        /// </summary>
        public string ErrorMessage { get; private set; }

        /// <summary>
        /// Değer atama işleminin ardından güvenlik, tekillik gibi
        /// nedenler gereği isim değiştirilmelidir.
        /// </summary>
        public string FileName
        {
            get { return _fileName; }
            set
            {
                string strExtention = Path.GetExtension(value);

                // ToSEFString methodu gönderilen string değeri arama
                // motoru dostu karakterler ile değiştirir
                //
                // RandomHelper sınıfı doğru rasgele karakterler ve
                // sayılar üretmek için kullanılıyor
                // Bağlantı: http://tinyurl.com/y8of6sn
                _fileName = Path.GetFileNameWithoutExtension(value).ToSEFString()
                + RandomHelper.RandomString(10, true)
                + strExtention;
            }
        }

        /// <summary>
        /// ctor
        /// </summary>
        public FileUploadUtil()
        {
            _fileName = string.Empty;
        }

        /// <summary>
        /// Dosya yükleme işleminin tetiklenmesi için bu method çağırılmalıdır.
        /// </summary>
        /// <param name="postedFile">
        /// Bu değişken klasik html input alanları tarafından post edilen
        /// verilerinde methodu kullanabilmeleri için bu tipte tanımlanmıştır.
        /// FileUpload kontrolü kullanılacaksa PostedFile özelliği gönderilebilir.
        /// </param>
        /// <param name="fuAttribute">
        /// Dosya yükleme işleminin gerçekleşmesi ile ilgili bilgiler içeren sınıftır
        /// Attribute sınıfından türetilmiştir. MetaColumn ile kullanılacaksa ilgili
        /// alan üzerinde tanımlı özniteliğin gönderilmesi yeterlidir.
        /// </param>
        /// <returns></returns>
        public bool UploadFile(HttpPostedFile postedFile, FileUploadAttribute fuAttribute)
        {
            #region Checks
            if (postedFile.ContentLength <= 0)
            {
                this.ErrorMessage = "Veri akışı bulunamadı";
                return false;
            }

            // Maksimum dosya boyutu öznitelikte atanan değere göre kontrol ediliyor.
            long FileSize = postedFile.ContentLength;
            if (FileSize > (fuAttribute.MaximumFileSize * 1024000))
            {
                this.ErrorMessage = string.Format("Maksimum dosya boyutu izin verilenden fazla olamaz. İzin verilen maksimum dosya boyutu: {0}", fuAttribute.MaximumFileSize);
                return false;
            }

            // Dosya uzantısı öznitelikte atanan değere göre kontrol ediliyor.
            // İçerik türüne(MIME) görede kontrol edilebilir.
            if (!Regex.IsMatch(Path.GetExtension(_fileName), fuAttribute.AllowedFileFormats, RegexOptions.IgnoreCase))
            {
                this.ErrorMessage = string.Format("Dosya türüne izin verilmiyor. İzin verilen dosya formatları: {0}", fuAttribute.AllowedFileFormats);
                return false;
            }
            #endregion

            // Klasör yoksa oluştur.
            DirectoryInfo DInfo = new DirectoryInfo(this.UploadDictionaryPath);
            if (!DInfo.Exists)
                DInfo.Create();

            #region WriteStreamToFile
            // Dosyayı kaydet
            string strFullFileName = this.UploadDictionaryPath + this._fileName;
            postedFile.SaveAs(strFullFileName);
            #endregion

            return true;
        }
    }
}

Yukarıdaki sınıf içinde kullanılan FileUploadAttribute sınıfını dosya yükleme işleminin gerçekleşmesini istediğimiz kolon için gerekli bildirimlerin yapılması için kullanacağız . Bu sınıfın kodlaması aşağıdaki gibidir.

using System;

namespace Eposta.Core.CustomAttributes
{
    /// <summary>
    /// Ömer Faruk ZORLU
    /// </summary>
    [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
    public class FileUploadAttribute : Attribute
    {
        /// <summary>
        /// Yüklenecek dosyanın maksimum boyutunun megabyte karşılığıdır.
        /// </summary>
        public short MaximumFileSize { get; set; }

        /// <summary>
        /// Bu özellik adının kontrol edilmesi için kullanılır.
        /// Kontrol işlemi RegularExpressions(Regex) sınıfı ile
        /// gerçekleştirilir.
        ///
        /// Örnek Kullanım: “jpg|gif|png|JPG|GIF|PNG”
        /// </summary>
        public string AllowedFileFormats { get; set; }

        /// <summary>
        /// Yükleme işleminin gerçekleştirileceği klasörün fiziksel
        /// yolunu saklar.
        ///
        /// Örnek Kullanım: HttpContext.Current.Server.MapPath(“~/Uploads/”)
        /// </summary>
        public string UploadDictionaryPath { get; set; }
    }
}

Buraya kadarki kodlamalarla iş katmanı için yükleme işini üstlenecek sınıfların kodlamasını tamamlamış olduk. Sırada bu katmanı istediğimiz kolon üzerinde kullanabilmek için gerekli alan şablonunu tasarlama işi var.  Daha önceki makalelerde belirttiğim gibi güncelleme, ekleme ve listeleme, detay sayfa şablonları için iki adet alan şablonu hazırlanmalıdır. Ekleme ve günnceleme için gerekli alan şablonunun kodları aşağıdaki gibidir.

MyFileUpload_Edit.ascx

<%@ Control Language="C#" CodeFile="MyUpload_Edit.ascx.cs" Inherits="MyUpload_Edit" %>
<asp:Label ID="Label1" runat="server" Text='<%# FieldValueEditString %>'></asp:Label><br />
<asp:FileUpload ID="FileUpload1" runat="server" />
<asp:CustomValidator ID="CustomValidator1" runat="server"
    ErrorMessage="CustomValidator"></asp:CustomValidator>

MyFileUpload_Edit.ascx.cs

using System;
using System.Collections.Specialized;
using System.Web.UI;
using System.Linq;
using Eposta.Core.CustomAttributes;
using Eposta.Core.Utils;

public partial class MyUpload_Edit : System.Web.DynamicData.FieldTemplateUserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }

    protected override void OnDataBinding(EventArgs e)
    {
        base.OnDataBinding(e);
    }

    protected override void ExtractValues(IOrderedDictionary dictionary)
    {
        // DataControl nesnesi olarak Label1 atandığı için
        // dosya yüklemenmesede varsayılan olarak zaten kayıtlı olan
        // veri dönüş değerinde kullanılmak üzere atanıyor
        dictionary[Column.Name] = ConvertEditedValue(Label1.Text);

        // Bu kolon üzerinde atanması zorunlu olan FileUploadAttribute
        // özniteliği alınıyor.
        var fuAttribute = base.Column.Attributes.OfType<FileUploadAttribute>().FirstOrDefault();
        // FileUploadAttribute ataması yoksa dön
        if (fuAttribute == null)
            return;

        // Dosya seçilmişse
        if (FileUpload1.HasFile)
        {
            try
            {
                FileUploadUtil fuUtil = new FileUploadUtil();
                // Atama işleminin ardından dosyaya yeni isim atanıyor.
                fuUtil.FileName = FileUpload1.FileName;
                fuUtil.UploadDictionaryPath = fuAttribute.UploadDictionaryPath;

                // Dosyayı yükle ve yeni dosya adını dönüş değeri olarak ata.
                // Hata kontrolüne karşı ilgili değişken(string ErrorMessage)
                // kontrol edilebilir.
                if (fuUtil.UploadFile(FileUpload1.PostedFile, fuAttribute))
                    dictionary[Column.Name] = fuUtil.FileName;
            }
            catch (Exception Ex)
            {
                // Denetlenmemiş herhangi bir hata oluşursa sayfanın tamamen
                // hata vermesi yerine hata mesajını hata özetlerinde ve atama
                // işleminin yapıldığı alanın yanında görünmesi için CustomValidator1
                // nesnesinden yararlanılıyor.
                CustomValidator1.IsValid = false;
                CustomValidator1.ErrorMessage = Ex.Message;
            }
        }
    }

    public override Control DataControl
    {
        get
        {
            return Label1;
        }
    }
}

Listeleme ve detay sayfa şablonlarında kullanılacak alan şablonu kodları aşağıdaki gibidir. Yüklenen dosyaların(ilgili verilerin) yalnızca    resim dosyası olduğu öngörülerek yazılmıştır. Dosya tipi yada türüne göre özelleştirilmiş görüntüleme tekniği kullanılabilir.

MyUpload.ascx.cs

using System;
using System.Web.UI;

public partial class MyUpload : System.Web.DynamicData.FieldTemplateUserControl
{
    public override Control DataControl
    {
        get
        {
            return imgThumbnail;
        }
    }

    public string GetSrcUrl()
    {
        // Veri boş ya da null ise NoImage.gif dönder
        if (String.IsNullOrEmpty(FieldValueString)) return "NoImage.gif";

        // Veri http, ftp vb herhangi bir protokolü kullanıyor ise
        // olduğu gibi değilse upload klasörünü kök dizin alarak dönder
        if (-1 < FieldValueString.IndexOf("://"))
            return FieldValueString;
        else
            return "~/uploads/" + FieldValueString;
    }
} 

Dynamic Data’nın en sevdiğim kısımı tabiki MetaData üzerinde yapılan tanımlamalardır. O kadar çok kod yazmak gerekiyorki daha ne yapalım birde diyenleri duyar gibiyim.

Dynamic Data ile yapılan her geliştirme tasarruf niteliği taşır.

Geliştirdiğimiz dosya yükleme aracı için klasik kodlama kullansaydık benzer kodları kullanmak istediğimiz sayfalarda(aynı sayfada pek çok kere de olabilir) harcanan efor göz önüne alınmalıdır. Yapılan geliştirme bir sonraki proje için yatırımdır. Ayrıca geriye dönük olarak yapılan projelerde kullan/a/mamak için bir neden söz konusu değildir.

Şimdi en zevkli kısıma gelelim. Bundan böyle dosya yükleme(FileUpload) işlemi ile çalışmasını istediğiniz herhangi bir kolon için meta data üzerinde aşağıdaki gibi bir tanımlama yapmanız yeterli oluyor.

NORTWIND veritabanında bulunan Employee tablosundaki PhotoPath kolonu için yapılan örnek tanımlama:

using System.ComponentModel.DataAnnotations;

[MetadataType(typeof(EmployeeMetaData))]
public partial class Employee
{

}

public class EmployeeMetaData
{
    [Eposta.Core.CustomAttributes.FileUpload(
        AllowedFileFormats = "jpg|gif|png|JPG|GIF|PNG",
        MaximumFileSize = 1, // MB
        UploadDictionaryPath = "~/uploads/"
        )]
    [UIHint("MyUpload")]
    public string PhotoPath { get; set; }
} 

Ekran görüntüleri
Listemele ve detay sayfa şablonlarında:

Ekleme ve Güncelleme sayfa şablonlarında:

Meraklısından meraklılarına faydalı olması dileğiyle. Mutlu kodlar.

Ömer Faruk ZORLU

 

ASP.NET , ,

ASP.NET Dynamic Data – Gelişmiş Scaffolding

30 Nisan 2010

 

 

Merhabalar, daha önce ASP.NET Dynamic Data(yazının devamında DD kısaltmasını kullanacağım) hakkında bazı bilgileri yayınlamıştım. Yeni bir makale konusunda düşünürken makale konusu olarak gerçek bir DD projesinde kullanıcı tarafından gelebilecek isteklerin hızlı bir şekilde nasıl karşılanabilineceğinden bahsetmenin uygun olduğuna karar verdim.
Yazılımcı gözüyle değerlendirmek gerekirse kullanıcı gereksinimleri konusunda gözden kaçan bir çok gereksinimi görülmeyebilir.  Örneğin bir tablo içindeki kayıtların listelenmesi yazılımcı için gayet yeterlidir ancak kullanıcı gözüyle bakıldığında her şeyin listelenmesi çoğu zaman gereksiz olacaktır. Bu kapsamda kullanıcı isteklerine karşılık sorun çözümüyle alakalı makalele dizisinin ilkini yazmaya başlıyorum. Daha önceki makalelerde bahsettiğim isimlendirme, biçimlendirme vb. gibi temel işlemlere bu makalede değinmeyeceğim.

Kullanıcı tarafında tablo üzerindeki her alanın(column) görüntülenmesi istenmeyebilir. Bazı durumlarda ise tüm kayıtların listelenmesi kullanım kolaylığı açısından sıkıntılı olabilir.

Scaffolding tekniğiyle istenilen alanların tüm şablonlarda görüntülenmesini engelleyebiliyoruz. Özellikle listeleme sayfalarında tüm alanların sayfada görünür halde olması yatay olarak uzayan bir sayfaya neden olur ancak bu alanları klasik scaffolding tekniği ile gizlersek diğer şablonlardanda kayboluverir.  İşte burada asıl sorun ortaya çıkıyor: Gizlenen alanların kullanıcı tarafından düzenlenmesi gerekiyorsa yada detaylı görüntüleme şablonunda tüm alanların görüntülenmesi gerekiyorsa ne yapacağız?

 

  Aletleri geliştirmek DD için her zaman kazanımdır. Bu senaryoda scaffolding tekniğini sayfa şablonlarına göre nasıl özelleştirebileceğimizi öğreneceğiz.

 

Kullanabileceğimiz veri bileşenleri(GridView, FormView, DetailsView vb.) veri listeleme sırasında listeleme işlemi için IAutoFieldGenerator arayüzünü uygulayan bir sınıf ile veri alanları üzerindeki kararını verir. Varsayılan alan oluşturucularda verideki tüm alanlar DD bildirimleri uygunlandıktan sonra(scaffolding gibi kısıtlamalar vb.) listelenir. Yapılması gereken şey ise veri listeleme işini gerçekleştirme görevini yazacağımız bir sınıfın üstlenmesidir.

IAutoFieldGenerator arayüzünü uygulayan sınıfımız:

namespace Eposta.Core.CustomFieldGenerator{    using System.Collections.Generic;    using System.Web.UI;    using System.Web.DynamicData;     

    /// <summary>    /// Bu sınıf veri listelemesini işlemlerini    /// özelleştirmek amacıyla yazılmıştır.     /// </summary>    public class CustomColumnGenerator : IAutoFieldGenerator    {        /// <summary>        /// Veri listelemesi için incelenecek        /// tablo değişkenini tutar        /// </summary>        protected MetaTable mTable = null;        #region IAutoFieldGenerator Members        /// <summary>        /// Listeleme işlemini gerçekleştirecek method        /// </summary>        /// <param name="control"></param>        /// <returns></returns>        public System.Collections.ICollection GenerateFields(Control control)        {            // Uygun kolonların biriktirildiği koleksiyon            List<DynamicField> dynFieldList = new List<DynamicField>();            // MetaTable atanmışsa            if (mTable != null)            {                // Her bir MetaColumn için                foreach (MetaColumn mColumn in this.mTable.Columns)                {                    // Scaffolding ataması yapılmışsa koleksiyona eklenmesi                    // uygun değildir. Eğer bu kontrol yapılmazsa ilgili                    // alan görüntülenecektir. Çünkü tüm sorumluluğu almış                    // bulunuyoruz                    if (                            !mColumn.Scaffold                        )                        continue;                     DynamicField dynField = new DynamicField();                    dynField.DataField = mColumn.Name;                    dynFieldList.Add(dynField);                }            }            return dynFieldList;        }        #endregion    }}

Scaffolding tekniğini denetleyerek görüntüleme işlemini üstlenen sınıfımız artık hazır. Şimdi sıra şablonlara göre bu denetleme işlemine geldi. Varsayılan şablonlar için bir enum tanımlayıp GenerateFields methodunda şablona görede bir denetleme yapmamız gerecektir.

namespace Eposta.Core.Enums{    public enum PageTemplateType    {        Details,        Edit,        Insert,        List,        ListDetails    }}

Özelleştirme işlemlerinin uygulanabilmesi için özel bir öznitelik geliştirip bildirimlerin bu öznitelik üzerinden gerçekleştirmesi gerekiyor bunun için aşağıdaki sınıfı kullanabiliriz.

[more]

using System;using Eposta.Core.Enums;

namespace Eposta.Core.CustomAttributes{    [AttributeUsage(AttributeTargets.Property)]    public class CustomHiddenColumnAttribute : Attribute    {        /// <summary>        /// İlgili alanın gizlenmesi gereken sayfa şablonları belirleniyor        /// </summary>        public PageTemplateType[] PageTemplateTypes { get; private set; }        /// <summary>        /// Parametresiz bir constructor yazılması mecburi ancak        /// kullanılmaması için hata fırlatılması daha uygun        /// </summary>        public CustomHiddenColumnAttribute()        {            throw new InvalidOperationException();        }        /// <summary>        /// Sayfa şablonlarının atama işleminin kolaylaştırılması        /// nedeniyle params yöntemi kullanılmıştır. Öznitelik         /// tanımlamasında AllowMultiple=true atamasıda yapılabilir        /// </summary>        /// <param name="PageTemplateParams"></param>        public CustomHiddenColumnAttribute(params PageTemplateType[] PageTemplateParams)        {            this.PageTemplateTypes = PageTemplateParams;        }    }}

Bu özniteliğin örnek tanımlaması aşağıdaki gibidir. Daha öncede belirttiğim gibi öznitelik yazmayı çok severim :) Bundan böyle sadece aşağıdaki kodu kullarak tüm kodu yazmadan işimizi halledebiliriz.

[CustomHiddenColumn(        PageTemplateType.List, PageTemplateType.ListDetails)]public string SmtpHost { get; set; }

Madem bu kadar kod yazdık birde MetaColumn nesneleri için genişletilmiş bir method yazarak kolonda ve şablona göre gizlilik bildirimi yapılmışmı diye kontrol edelimki her seferinde bir yığın kod yazmaktan kurtulalım.

using System.Linq;using Eposta.Core.CustomAttributes.UIHint;using Eposta.Core.CustomAttributes;using Eposta.Core.Enums;/// <summary> /// Ömer Faruk ZORLU/// </summary>namespace Eposta.Core.Extentions{    public static class MetaColumnExtentions    {        /// <summary>        /// Sayfa şablonuna göre alanın gizlilik durumunu dönderir        /// </summary>        /// <param name="mColumn"></param>        /// <param name="pTemplateType"></param>        /// <returns></returns>        public static bool IsHidden(this System.Web.DynamicData.MetaColumn mColumn, PageTemplateType pTemplateType)        {            // Kolon üzerinde atanmış ustomHiddenColumn özniteliği alınıyor            // Eğer çoklu tanımlama(AllowMultiple) ya izin verilmişse bir            // döngü içerisinde kontrol edilmesi gerekir            CustomHiddenColumnAttribute chCAttr =                mColumn.Attributes.OfType<CustomHiddenColumnAttribute>().FirstOrDefault();               //  Herhangi bir öznitelik ataması yoksa gizli değildir            if (chCAttr == null)                return false;             // İlgili alan için yapılan gizlilik bildirimleri parametrede            // gönderilmiş sayfa şablonunu içeriyorsa gizlidir            return chCAttr.PageTemplateTypes.Contains(pTemplateType);;        }    }}

Artık daha önce yazdığımız alan oluşturucu sınıf üzerinde özel gizleme yapımızı denetleyen kodlar yazabiliriz. Yukarıda yazdığımız sınıfa yeni eklenen kodlar kalın olarak yazılmıştır.

using System.Collections.Generic;using System.Web.DynamicData;using System.Web.UI;using Eposta.Core.Extentions;using Eposta.Core.Enums; namespace Eposta.Core.CustomFieldGenerator{    /// <summary>    /// Bu sınıf veri listelemesini işlemlerini    /// özelleştirmek amacıyla yazılmıştır.     /// </summary>    public class CustomFieldGenerator : IAutoFieldGenerator    {        /// <summary>        /// Veri listelemesi için incelenecek        /// tablo değişkenini tutar        /// </summary>        protected MetaTable mTable = null;

        protected PageTemplateType pTemplateType;         #region ctor         public CustomFieldGenerator(MetaTable _mTable, PageTemplateType _pTemplateType)        {            this.mTable = _mTable;            this.pTemplateType = _pTemplateType;        }         #endregion         #region IAutoFieldGenerator Members        /// <summary>        /// Listeleme işlemini gerçekleştirecek method        /// </summary>        /// <param name="control"></param>        /// <returns></returns>        public System.Collections.ICollection GenerateFields(Control control)        {            // Uygun kolonların biriktirildiği koleksiyon            List<DynamicField> dynFieldList = new List<DynamicField>();            // MetaTable atanmışsa            if (mTable != null)            {                // Her bir MetaColumn için                foreach (MetaColumn mColumn in this.mTable.Columns)                {                    // Scaffolding ataması yapılmışsa koleksiyona eklenmesi                    // uygun değildir. Eğer bu kontrol yapılmazsa ilgili                    // alan görüntülenecektir. Çünkü tüm sorumluluğu almış                    // bulunuyoruz                    if (                            !mColumn.Scaffold                        )                        continue;                     // List ve ListDetails şablonları için IsHidden                    // genişletilmiş methodu ve IsLongString methodunu                    // kontrol ediyoruz ancak Details şablonu için                    // IsLongString methodunu kontrol edip gizlersek                    // uzun metin alanlar görüntülenmeyecek yani ufak bir                    // bug oluşacaktır.                    switch (this.pTemplateType)                    {                        case PageTemplateType.Details:                            if (mColumn.IsHidden(this.pTemplateType))                                continue; // foreach                            break;                        case PageTemplateType.List:                            if (mColumn.IsHidden(this.pTemplateType) ||                                    mColumn.IsLongString)                                continue; // foreach                            break;                        case PageTemplateType.ListDetails:                            if (mColumn.IsHidden(this.pTemplateType) ||                                    mColumn.IsLongString)                                continue; // foreach                            break;                        default:                            break;                    }                     // Varsayılan şablonların haricinde uzun metinlerin                    // görüntülenmesini engellemek amacıyla bu kontrol                    // yapılıyor                    if (mColumn.IsLongString)                        continue;                     DynamicField dynField = new DynamicField();                    dynField.DataField = mColumn.Name;                     dynFieldList.Add(dynField);                }            }            return dynFieldList;        }        #endregion    }}

Son adımında ise ilgili veri kontrolüne veri listeleme işlemini bizim üstleneceğimizi bildirmek gerekiyor. GridView nesnesi için ColumnGenerator DetailsView içinse RowGenerator parametresi kullanılıyor. Son haliyle List.aspx.cs ve Details.aspx.cs şablon kodları aşağıdaki gibi tanımlanmalıdır.

List.aspx.cs kodları:

using System;using System.Web.DynamicData;using Eposta.Core.CustomFieldGenerator;using Eposta.Core.Extentions; public partial class List : System.Web.UI.Page{    protected MetaTable table;    protected void Page_Init(object sender, EventArgs e)    {        DynamicDataManager1.RegisterControl(GridView1, true /*setSelectionFromUrl*/);        table = GridDataSource.GetTable();        GridView1.ColumnsGenerator = new CustomFieldGenerator(table, Eposta.Core.Enums.PageTemplateType.List);    }    protected void Page_Load(object sender, EventArgs e)    {        Title = table.DisplayName;        InsertHyperLink.NavigateUrl = table.GetActionPath(PageAction.Insert);         // Disable various options if the table is readonly        if (table.IsReadOnly)        {            GridView1.Columns[0].Visible = false;            InsertHyperLink.Visible = false;        }    }    protected void OnFilterSelectedIndexChanged(object sender, EventArgs e)    {        GridView1.PageIndex = 0;    }}


Details.aspx.cs kodları:

using System;using System.Linq;using System.Web.DynamicData;using System.Web.UI.WebControls;using Eposta.Core.CustomFieldGenerator;using Eposta.Core.CustomAttributes; public partial class Details : System.Web.UI.Page {    protected MetaTable table;    protected void Page_Init(object sender, EventArgs e) {        DynamicDataManager1.RegisterControl(DetailsView1);    }    protected void Page_Load(object sender, EventArgs e) {        table = DetailsDataSource.GetTable();        DetailsView1.RowsGenerator = new CustomFieldGenerator(table, Eposta.Core.Enums.PageTemplateType.Details);

        Title = table.DisplayName;

        ListHyperLink.NavigateUrl = table.ListActionPath;    }    protected void DetailsView1_ItemDeleted(object sender, DetailsViewDeletedEventArgs e) {        if (e.Exception == null || e.ExceptionHandled) {            Response.Redirect(table.ListActionPath);        }    }}

Çözüm itibari ile Grup tablosunda yapılan tanımlalarla listeleme sayfa şablonlarında bazı alanların gizlenmesini güncelleme ve detaylı görüntüleme sayfa şablonlarında ise kullanıcının verileri görüntülemesine ve düzenlemesine imkan tanımış olduk. Ekran görüntüleri aşağıdaki gibidir.

Güncelleme ekranı:

Listeleme ekranı:

Bir yazının daha sonuna geldik. Kullanıcı gereksinimleriyle alakalı makaleler devam edecektir. Bir sonraki makalede görüşmek üzere. Yaşasın dinamizm!

Ömer Faruk ZORLU

 

ASP.NET , ,