Bir önceki yazıda sayfa aksiyonlarının(PageAction) yetki kontrolü için her bir sayfa şablonunun içine(PageTemplate) yetki yoksa AccessDenied.aspx sayfasına git gibisinden bir kod eklemiştik. Sonradan aslında bu kontrolün henüz yönlendirilmeden yapılmasını ve merkezi olması gerektiğini düşünerek kodda bir takım eklemeler yaptım. Bunun için Dynamic data nın route handler(yönlendime kontrolörü) tarafından yapılan işi üstlenmek en doğru çözüm diye düşünerek aşağıdaki sınıfı yazdım
namespace DynLibrary.Entities
{
using System.Web.DynamicData;
using DynLibrary.Helpers;
///
/// Dynamic Data'nın route işlemini gerçekleştiren sınıftan kalıtılmıştır.
///
public class DynRouteHandler : DynamicDataRouteHandler
{
public DynRouteHandler()
: base()
{ }
///
/// Dyanmic data'nın GetActionLink metotunun linki oluşturmak
/// aynı zamanda da sayfanın route edilmesi işlemi sırasında
/// çağırılan metottur. null değeri dönmesi durumunda sayfa handle
/// edilmez, dolayısıyla 404 bulunamadı hatası ile karşılaşılır.
/// Benzer şekilde GetActionLink metotundanda null döneceği için
/// linkler boşalmış olacaktır.
///
///
///
///
///
public override System.Web.IHttpHandler CreateHandler(DynamicDataRoute route, MetaTable table, string action)
{
// action değişkeninden gelen açık değer
// Edit, Update, List, Detail gibi
char cCrudOperation;
switch (action)
{
case "Insert":
cCrudOperation = 'c';
break;
case "List":
cCrudOperation = 'r';
break;
case "Detail":
cCrudOperation = 'r';
break;
case "Edit":
cCrudOperation = 'u';
break;
default:
cCrudOperation = 'r';
break;
}
// Eğer yetki yoksa null değerini dönder
if (!PermissionHelper.CanAccessCrudOperation(table.EntityType.Name, cCrudOperation))
return null;
// Herhangi bir durum yoksa normal şekilde route işlemini gerçekleştir
return base.CreateHandler(route, table, action);
}
}
}
İlgili sınıfı yazdıktan sonra global.asax dosyasında aşağıdaki değişiklik yapılmalıdır. Bu tanımlama ile routehandler sınıfının bizim sınıfımız üzerinden yapılacağını bildirmiş oluyoruz.
Önceki hali:
routes.Add(new DynamicDataRoute("{table}/{action}.aspx")
{
Constraints = new RouteValueDictionary(new { action = "List|Details|Edit|Insert" }),
Model = model
});
Yeni hali:
routes.Add(new DynamicDataRoute("{table}/{action}.aspx")
{
Constraints = new RouteValueDictionary(new { action = "List|Details|Edit|Insert" }),
Model = model,
RouteHandler = new DynRouteHandler()
});
Bu şekilde yetkilendirme işlemini daha yönlendirme bile gerçekleşmeden yapmış olduk. Bu işlemin ardından personel tablosunda edit yetkisi olmayanlar aşağıdaki gibi bir sayfa ile karşılacaklar :)

Faydalı olması dileğiyle, mutlu kodlar!
Ömer Faruk ZORLU
de906c43-661e-4ebe-b3f9-4f40cde603fa|0|.0
Merhabalar, bu yazıda dynamic data ile kullanıcı arayüzünde rol tabanlı güvenlik konusunu inceleyeceğiz. Bunun için kendimize özel bir güvenlik yapısı oluşturacağız. Dynamic Data - Tablo ve kolon isimleri için globalizasyon işlemi başlıklı yazının okunmasında büyük fayda vardır. Bir çok yerde globalizasyon yapısında kullanılan yapının bilindiği kabul edilecektir. Yazı boyunca aşağıdaki iki tablo ile alakalı bilgiler yer alacaktır.

Dynamic data'yı yönetim paneli oluşturmak için kullandığımızı düşünelim. Tabiki aynı anda bir çok kullanıcı farklı güvenlik seviyeleriyle yönetim panelini kullanabilmelidir. Örneğin Admin seviyesindeki kullanıcı her şeyi yapabilirken editor seviyesinde bazı işllemler kısıtlanmalıdır. Bu kapsamda yapacağımız işi özetleyecek olursak; yetkilendirme işlemleri için bir xml dosyası hazırlayacağız ve MetaColumn sınıfından türettiğimiz yeni bir sınıf yardımıyla tablo ve kolonlar üzerindeki yetkileri kısıtlayacağız.Haydi kodlayalım.
Globalizasyon yazısındakine benzer şekilde xml dosyamız aşağıdaki gibi olsun:
<?xml version="1.0" encoding="utf-8" ?>
<permissions>
<cruds>
<crud name="Personel" operations="c,u,d" denyRoles="users" />
</cruds>
<dbobjects>
<dbobject name="Personel.Soyadi" denyRoles="users,editors" />
</dbobjects>
</permissions>
cruds segmentinde ekleme(c), okuma(r), güncelleme(u) ve silme(d) işlemlerini kısıtlayacağımız tablolar yer alırken dbobjects segmentinde tablo kolonlarının kullanılabilirliğini kısıtlıyor olacağız. Örneğin bu xml dosyasına göre users ve editors grubları Personel tablosundaki Soyadi alanını hiç bir yerde göremeyecekler.
Şimdi xml dosyasını okuyup gerekli kısıtlamayı yapacak sınıfa gelelim.
PermissionsHelper.cs sınıfı kaynak kodları:
namespace DynLibrary.Helpers
{
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.IO;
using System.Web.Hosting;
using System.Web.Security;
public static class PermissionHelper
{
///
/// Tüm kısıtlama tanımlamalarını saklayacak koleksiyon
///
private static Hashtable _permisssionList;
static PermissionHelper()
{
_permisssionList = new Hashtable();
// Kısıtlama tanımlamalarını yükle
LoadPermission();
}
///
/// Kısıtlama tanımlarını yükleyecek metot
///
private static void LoadPermission()
{
string configFolder = ConfigurationManager.AppSettings["DynamicDataConfigFolder"];
string fullDbObjectsPath = Path.Combine(HostingEnvironment.MapPath(configFolder), "Permissions.xml");
string gKey = string.Empty;
DataSet ds = new DataSet();
// Xml dosyası okunuyor
ds.ReadXml(fullDbObjectsPath);
#region dbobject
if (ds.Tables["dbobject"].Rows.Count > 0)
{
List denyDboRolesList;
foreach (DataRow row in ds.Tables["dbobject"].Rows)
{
denyDboRolesList = new List();
// Tüm kısıtlamalar _permisssionList koleksiyonunda tutulacağından
// bu koleksiyona tanımlanacak anahtarların eşsiz olması gerekiyor.
// Koleksiyona atanacak anahtar tanımlanıyor.
gKey = string.Format("dbobjects.{0}.roles.deny", row["name"]);
// denyRoles özniteliğindeki roller virgülle ayırıldığından
// Split yardımıyla her biri tek tek denyDboRolesList
// koleksiyonuna aktarılıyor.
denyDboRolesList.AddRange(row["denyRoles"].ToString().Split(','));
// Eğer koleksiyonda böyle bir anahtar varsa üzerine yaz yoksa ekle
if (!_permisssionList.Contains(gKey))
_permisssionList.Add(gKey, denyDboRolesList);
else
_permisssionList[gKey] = denyDboRolesList;
}
}
#endregion
#region crud
if (ds.Tables["crud"].Rows.Count > 0)
{
List denyCrudRolesList;
foreach (DataRow row in ds.Tables["crud"].Rows)
{
denyCrudRolesList = new List();
// denyRoles özniteliğindeki roller virgülle ayırıldığından
// Split yardımıyla her biri tek tek denyDboRolesList
// koleksiyonuna aktarılıyor.
denyCrudRolesList.AddRange(row["denyRoles"].ToString().Split(','));
foreach (string operation in row["operations"].ToString().Split(','))
{
// Tüm kısıtlamalar _permisssionList koleksiyonunda tutulacağından
// bu koleksiyona tanımlanacak anahtarların eşsiz olması gerekiyor.
// Koleksiyona atanacak anahtar tanımlanıyor.
gKey = string.Format("cruds.{0}.{1}.roles.deny", row["name"], operation);
// Eğer koleksiyonda böyle bir anahtar varsa üzerine yaz yoksa ekle
if (!_permisssionList.Contains(gKey))
_permisssionList.Add(gKey, denyCrudRolesList);
else
_permisssionList[gKey] = denyCrudRolesList;
}
}
}
#endregion
}
///
/// O anki kullanıcının dboObjectName değişkeninde bildirilen
/// nesneye erişme durumunu dönderir
///
/// Erişmek istenilen nesne
///
public static bool CanAccessDboObject(string dboObjectName)
{
// Henüz rol tabanlı güvenlikle alakalı kod yazılmadığından
// roleNames değişkenini el ile atıyorum. Roles.GetRolesForUser()
// metotu ile daha sonra gerçek ortamda kullanılabilir.
string[] roleNames = { "users" }; // TODO: Roles.GetRolesForUser();
// Eşsiz anahtar oluşturuluyor
string roleDenyKey = string.Format("dbobjects.{0}.roles.deny", dboObjectName);
// Eğer herhangi bir kısıtlama varsa false değerini dönder
// metotun en altında eğer hiç bir engele takılmamışsa
// true değeri dönsün.
//
// Bu nesneye ait herhangi bir engelleme var mı?
if (_permisssionList.Contains(roleDenyKey))
{
// Engellemeye ait rol tanımlamalarını getir.
List denyList = (List)_permisssionList[roleDenyKey];
// Kullanıcının rolü engellemede tanımlıysa false değeri dönder
foreach (var roleName in roleNames)
{
if (denyList.Contains(roleName))
return false;
}
}
return true;
}
///
/// O anki kullanıcının tableName değişkeninde bildirilen
/// nesne üzerinde bildirilen crud yetkisine sahip olma durumunu dönderir
///
/// crud yetkisi öğrenilecek tablo adı
///
/// c:Create
/// r=Read
/// u=Update
/// d=Delete
///
///
public static bool CanAccessCrudOperation(string tableName, char crudOperation)
{
// Henüz rol tabanlı güvenlikle alakalı kod yazılmadığından
// roleNames değişkenini el ile atıyorum. Roles.GetRolesForUser()
// metotu ile daha sonra gerçek ortamda kullanılabilir.
string[] roleNames = { "users" }; // TODO: Roles.GetRolesForUser();
// Eşsiz anahtar oluşturuluyor
string roleDenyKey = string.Format("cruds.{0}.{1}.roles.deny", tableName, crudOperation);
// Eğer herhangi bir kısıtlama varsa false değerini dönder
// metotun en altında eğer hiç bir engele takılmamışsa
// true değeri dönsün.
//
// Bu tablo üzerinde ilgili crud işlemi için herhangi bir engelleme var mı?
if (_permisssionList.Contains(roleDenyKey))
{
// Engellemeye ait rol tanımlamalarını getir.
List denyList = (List)_permisssionList[roleDenyKey];
// Kullanıcının rolü engellemede tanımlıysa false değeri dönder
foreach (var roleName in roleNames)
{
if (denyList.Contains(roleName))
return false;
}
}
return true;
}
}
}
PermissionHelpers sınıfından sonra MetaModel, MetaTable ve MetaColumn sınıflarının işini yapacak sınıf tanımlamalarını yapalım.
DynMetaModel.cs dosyası kaynak kodları:
namespace DynLibrary.Entities
{
using System.Web.DynamicData;
using System.Web.DynamicData.ModelProviders;
///
/// MetaModel nesnesinden türetilen DynMetaModel nesnesi
///
public class DynMetaModel : MetaModel
{
public DynMetaModel()
: base()
{
}
///
/// Tablo nesnesini oluşturan metottur. Bu metotta MetaTable yerine kendi
/// nesnemiz olan ve MetaTable nesnesinden türemiş olan DynMetaTable
/// nesnesini üretiyoruz.
///
///
///
protected override MetaTable CreateTable(TableProvider provider)
{
return new DynMetaTable(this, provider);
}
}
}
DynMetaTable.cs dosyası kaynak kodları:
namespace DynLibrary.Entities
{
using System.Web.DynamicData;
using System.Web.DynamicData.ModelProviders;
using DynLibrary.Helpers;
///
/// MetaTable nesnesinden türetilen DynMetaTable nesnesi
///
public class DynMetaTable : MetaTable
{
public DynMetaTable(MetaModel metaModel, TableProvider tableProvider)
: base(metaModel, tableProvider)
{}
protected override void Initialize()
{ base.Initialize(); }
///
/// Kolon nesnesini oluşturan metottur. Bu metotta MetaColumn yerine kendi
/// nesnemiz olan ve MetaColumn nesnesinden türemiş olan DynMetaColumn
/// nesnesini üretiyoruz.
///
///
///
protected override MetaColumn CreateColumn(ColumnProvider columnProvider)
{
return new DynMetaColumn(this, columnProvider);
}
}
}
DynMetaColumn.cs dosyası kaynak kodları:
namespace DynLibrary.Entities
{
using System.Web.DynamicData;
using System.Web.DynamicData.ModelProviders;
using DynLibrary.Helpers;
using System.Web.Security;
public class DynMetaColumn : MetaColumn
{
public DynMetaColumn(MetaTable metaTable, ColumnProvider columnProvider)
: base(metaTable, columnProvider)
{ }
public override bool Scaffold
{
get
{
//if (base.Scaffold)
// return true;
return PermissionHelper.CanAccessDboObject(this.Table.EntityType.Name + "." + this.Name);
}
}
}
}
DynMetaColumn sınıfında ezilen Scaffold özelliğini tabloya yazılan ek sınıflar(partial) ile her bir alan için tek tek tanımlamamız gerekiyordu. Burada ise her kolon oluşturulurken bu özellik çağırılacağından, tüm çağırma işlemlerini denetleme şansımız oluyor. Şimdi sıra son kod değişikliğinde, Global.asax dosyasında oluşturduğumuz MetaModel'i kullan dediğimizde işlem tamamlanmış olacaktır. Sayfa şablonlarından da ilgili değişiklikleri kontrol etmemiz gerekiyor. Bunun gerekli kodları tek tek yayınlamak yerine tüm projeyi(globalizasyon desteği de dahil olmak üzere) konunun en altındaki linkten indirebilirsiniz.
// Eski hali: MetaModel model = new MetaModel();
DynMetaModel model = new DynMetaModel();
Yetki kontrolü öncesi Personel tablosu görünümü:

Yetki kontrolü sonrası Personel tablosu görünümü:

Örnek projeyi indirmek için:
DD_Permissons_Globalization.rar (316,78 kb)
Faydalı olması dileğiyle, mutlu kodlar!
Ömer Faruk ZORLU
d9ab347d-1ebf-4572-a35d-90f9b9904a21|1|5.0
Merhabalar, Dynamic Data Preview 4 ile birlikte daha önce müdahale etmemiz güç olan bir çok noktada açıklığa kavuştuk. Örneğin daha önce MetaTable sınıfı sealed(mühürlenmiş) olarak işaretlendiğinden bu sınıftan nesne türetemiyorduk. Bu yazıda MetaModel, MetaTable ve MetaColumn sınıfları türetip kendimize göre özelleştireceğiz. Örnek olması ve faydalı bir yazı olması adına tablo isimleri ve tablonun içindeki sütunları kullanıcı diline uygun hale getiren kodlamayı yapacağız.
Bilindiği üzere dynamic data tablolar için hazırladığı sayfalarda tablo ve sütun adlarını olduğu gibi gösteriyor. Özellikle her bir kolon için ayrı ayrı DisplayName özniteliğini tanımlamamız gerekiyor. Bu yazıda tek bir dosyadan DisplayName atamalarını yapabildiğimiz gibi başlıktan da anlaşılacağı üzere çoklu dil desteğini de yazmış olacağım. Örneğin veritabanında ID olarak tanımlanmış bir alan dynamic data tarafında da ID olarak görüntülenir. Bu alan için türkçe arabirimi için Kimlik Numarası yazarken ingilizce arabiriminde Identification Number yazdırabileceğiz. Yazı boyunca aşağıdaki iki tabloyu kullanacağım.

İlk etapda çeviri bilgilerini tutacak bir ortam belirlemek gerekiyor. Tabiki veritabanında saklamak en doğrusu olacaktır ancak burada yaptığım geliştirmenin diğer projelerimde de kullanılabilir olması gerektiğini düşündüğüm için ek bir tablo koplayama masrafını göze almıyorum ve bu işi ufak bir xml dosyası üzerinden halletmeyi uygun görüyorum. Aslında ilk etapda resource file ile yapmayı düşünmüştüm ancak class library içerisinden resource dosyalarıyla çalışmak biraz sorun oldu. Bu yüzden önce xml dosyasını hazırlayalım. Xml dosyasında nesne tipi, eşsiz adı ve görüntüleme adı yer alacak. Aşağıdaki xml dosyası üzerinden yazıya devam edelim.
<?xml version="1.0" encoding="utf-8" ?>
<globalization>
<dbobjects>
<dbobject type="t" name="Soru" value="Soru Kayıtları" />
<dbobject type="t" name="Personel" value="Personel Kayıtları" />
<dbobject type="c" name="Soru.SoruID" value="Soru Nu" />
<dbobject type="c" name="Soru.Baslik" value="Başlık" />
<dbobject type="c" name="Soru.Aciklama" value="Açıklama" />
<dbobject type="c" name="Personel.PersonelID" value="Personel Nu" />
<dbobject type="c" name="Personel.Adi" value="Personelin Adı" />
<dbobject type="c" name="Personel.Soyadi" value="Personelin Soyadı" />
</dbobjects>
</globalization>
type niteliğindeki "t" alanı tablo "c" alanı da kolon tipini, "name" kısımı eşsiz adı "value" da görüntüleme adını saklıyor. name kısımı biraz daha abartılıp db ve şema bilgileri de yazılabilir ancak şuan önemsemiyorum. Xml dosyası(tr-TR.xml) hazır olduğuna göre şimdi bu bilgileri okuyup istediğimiz zaman bize söyleyebilecek bir sınıf yazmaya başlayalım.
namespace DynLibrary.Helpers
{
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.IO;
using System.Web.Hosting;
public static class GlobalizationHelper
{
///
/// Globalizasyon değerlerini saklıyacak olan koleksiyon
///
private static Hashtable _globalizationList;
///
/// Veritabanı nesneleri için yapılacak globalizasyon değerlerini
/// _globalizationList koleksiyonuna aktaracak olan metot
///
private static void LoadDbObjects()
{
string configFolder = ConfigurationManager.AppSettings["DynamicDataConfigFolder"];
string fullDbObjectsPath = Path.Combine(HostingEnvironment.MapPath(configFolder), "Globalization");
// Dynamic data ile ilgili ayarların saklı olduğu klasörden
// glabalizasyonla alakalı klasördeki xml dosyalarını getir
string[] fileNames = Directory.GetFiles(fullDbObjectsPath, "*.xml");
DataSet ds;
string gKey = string.Empty;
foreach (var fileName in fileNames)
{
ds = new DataSet();
// Xml dosyasını oku
ds.ReadXml(Path.Combine(fullDbObjectsPath, fileName));
if (ds.Tables["dbobject"].Rows.Count > 0)
{
foreach (DataRow row in ds.Tables["dbobject"].Rows)
{
// _globalizationList koleksiyonu tüm çeviri değerlerini
// saklayacağından her bir anahtarın eşsiz olması gerekiyor
// bunun için anahtar isimlerini dbobjects.tr-TR.TabloAdi.KolonAdı
// şeklinde atamayı şimdilik yeterli görüyorum
gKey = string.Format(
"dbobjects.{0}.{1}.{2}",
row["type"], // t, c
Path.GetFileNameWithoutExtension(fileName), // tr-TR, eUS
row["name"] // TabloAdi, TabloAdi.KolonAdi
);
// Anahtar ile değer birlikte koleksiyona yazılıyor. Eğer daha
// önceden yazılmışsa(Bu metotun tekrar yükleme(update) işlemi
// içinde çağırılabileceği düşünülerek) önceki değerin üzerine yaz
if (!_globalizationList.Contains(gKey))
_globalizationList.Add(gKey, row["value"]);
else
_globalizationList[gKey] = row["value"];
}
}
}
}
///
/// contsr
///
static GlobalizationHelper()
{
_globalizationList = new Hashtable();
LoadDbObjects(); // Veritabanı nesneleriyle alakalı çevirileri yükle
}
///
/// _globalizationList koleksiyonundan değer okuyacak metot
///
///
///
/// Değer bulunamadıysa dönderilecek varsayılan değer
///
///
public static string GetValue(string key, string defaultValue)
{
// Koleksiyonda istenen anahtar yoksa defaultValue değişkenini dönder
if (!_globalizationList.Contains(key))
return defaultValue;
// Koleksiyondan okunan değeri dönder
return Convert.ToString(_globalizationList[key]);
}
///
/// Veritabanı nesneleri çevirilerini okumak için
/// kolaylaştıcı metottur. GetValue metotu çağırılır.
///
/// table_name.column_name
/// Değer bulunamadıysa dönderilecek varsayılan değer
///
public static string GetDbObjectValue(string objectType, string objectName, string defaultValue)
{
// UICulture değerini alınıyor
string uiCultureName = System.Threading.Thread.CurrentThread.CurrentUICulture.CompareInfo.Name;
// Anahtar oluşturuluyor
string key = string.Format(
"dbobjects.{0}.{1}.{2}",
objectType, // TabloAdi, TabloAdi.KolonAdi
uiCultureName, // tr-TR, en-US
objectName
);
// GetValue metotundan ilgili değer okunup dönderiliyor
return GetValue(key, defaultValue);
}
}
}
Xml dosyası ve onu okuyup istediğimiz zaman çeviri okuyabileceğimiz sınıf tamamlandığına göre şimdi sıra geldi dynamic datayı kalbinden vurmak. Bu yazıda kullanılacağı kadarıyla Dynamic data arkaplanda MetaModel adı verilen bir nesneyi kullanıyor. MetaModel nesnesi içinde MetaTable(CreateMetaTable) nesnesi, MetaTable nesnesi içinden de MetaColumn(CreateMetaColumn) nesnesi üretiliyor. Bizim yapmamız gereken iş ise bu üç sınıfının yaptığı işleri üstlenecek sınıflar yazmak ve dynamic data'ya bu sınıfları kullan demekten ibaret. Globalizasyon işini yani nesnelerin ingilizce ve türkçe isimlerini de MetaTable ve MetaColumn nesnelerinin DisplayName özelliğini ezerek xml dosyasından okutacağız.
MetaModel sınıfı kaynak kodları:
namespace DynLibrary.Entities
{
using System.Web.DynamicData;
using System.Web.DynamicData.ModelProviders;
/// MetaModel nesnesinden türetilen DynMetaModel nesnesi
public class DynMetaModel : MetaModel
{
public DynMetaModel()
: base()
{
}
/// Tablo nesnesini oluşturan metottur. Bu metotta MetaTable yerine kendi
/// nesnemiz olan ve MetaTable nesnesinden türemiş olan DynMetaTable
/// nesnesini üretiyoruz.
protected override MetaTable CreateTable(TableProvider provider)
{
return new DynMetaTable(this, provider);
}
}
}
MetaTable sınıfı kaynak kodları:
namespace DynLibrary.Entities
{
using System.Web.DynamicData;
using System.Web.DynamicData.ModelProviders;
using DynLibrary.Helpers;
/// MetaTable nesnesinden türetilen DynMetaTable nesnesi
public class DynMetaTable : MetaTable
{
public DynMetaTable(MetaModel metaModel, TableProvider tableProvider)
: base(metaModel, tableProvider)
{
}
/// Kolon nesnesini oluşturan metottur. Bu metotta MetaColumn yerine kendi
/// nesnemiz olan ve MetaColumn nesnesinden türemiş olan DynMetaColumn
/// nesnesini üretiyoruz.
protected override MetaColumn CreateColumn(ColumnProvider columnProvider)
{
return new DynMetaColumn(this, columnProvider);
}
/// Görüntüleme adını dönderen sınıfı ezen özellik
public override string DisplayName
{
get
{
return GlobalizationHelper.GetDbObjectValue("t", this.EntityType.Name, base.DisplayName);
}
}
}
}
MetaColumn sınıfı kaynak kodları:
namespace DynLibrary.Entities
{
using System.Web.DynamicData;
using System.Web.DynamicData.ModelProviders;
using DynLibrary.Helpers;
public class DynMetaColumn : MetaColumn
{
public DynMetaColumn(MetaTable metaTable, ColumnProvider columnProvider)
: base(metaTable, columnProvider)
{
}
///
/// Görüntüleme adını dönderen sınıfı ezen özellik
///
public override string DisplayName
{
get
{
// TODO: check for displane, uihint etc...
string key = this.Table.EntityType.Name + "." + this.Name;
return GlobalizationHelper.GetDbObjectValue("c", key, base.DisplayName);
}
}
}
}
Gerekli üç sınıfın kodlamasını tamamladıktan sonra geriye dynamic data'ya DynMetaModel nesnemizi kullanması gerektiğini söylemek kalıyor. Bunun için global.asax dosyasında yapılan tanımlamayı aşağıdaki gibi değiştirmemiz gerekiyor.
// Eski hali: MetaModel model = new MetaModel();
DynMetaModel model = new DynMetaModel();
Yapılması gereken son kodlama da buydu. Sonuc olarak artık türkçe dili kullanan kullanıcılar tablo ve kolon adlarını türkçe ve okunabilir görebilecekler. Tabiki diğer diller için de aynı şey söz konusu. Aşağıda kodlamadan önce, kodlamadan sonra türkçe ve kodlamadan sonra ingilizce arayüz görüntüleri mevcuttur.
İlk hali:

Kodlama sonrası - Türkçe

Kodlama sonrası - İngilizce

Faydalı olması dileğiyle, iyi çalışmalar mutlu kodlar!
Ömer Faruk ZORLU
37b6bb1b-9a94-4146-8369-290adab1a688|0|.0
45d38a28-01f7-459f-b9ff-2443187609cd|3|4.3
Bilindiği üzere ASP.NET Dynamic Data ile birlikte kullanıcı etkileşimini sağlayacak sayfalar dynamic bir şekilde sunuluyor. Bu arayüz üzerinde yapılacak bazı güncellemelerle kullanım kolaylığı sağlaman üzere yola çıkıyoruz tabi birazda görsel de oynayalım. Yazının bir çok kısımında anlatım dili geliştirme sırasında .net le yaptığım dialogları yansıtmaktadır. Ayrıca projenin tamamını(resim, js vb.) yazamadığım için makalenin en altında zip dosyasını bulmanızı ümit ediyorum.
Çok fazla tablodan oluşan yapılarda meydana gelen tablo listeleme ekranından dolayı rahatsızlık yaşanabiliyor. Bunun için varsayılan şablonların default sayfasındaki tablo listesini masterpage de uygun bir yere alıp, tablo listeleme mantığında birazcık değişiklik yapalım.

Senaryoya göre çok fazla tablo var ve hangisinin nerede olduğunu bulmak kullanıcılar açısından problem olmaya başladı. Bu sebeple bir şekilde tablo listesinin kullanımını kolaylaştırmalıyız. Tablo listesini kendi içinde gruplayarak bu işi pratik bir şekilde çözebileceğimizi düşünüyorum. Menü masterpage de uygun bir yere alınıp her sayfadan erişimi kolaylaştırılmalı ve gruplama işlemini tanımlamak üzere öznitelik sınıfı yazılmalıdır. Tabi ki son olarak da menunün datasource kısımını biraz değiştirmemiz gerekecek.
Menüyü oluşturan gridview kontrolünü ve veribağlama işlemi için kullanılan kodları masterpage tarafına aktaralım. MasterPage tarafında 1x2 tablosu oluşturup menüyü sol hücreye aldıktan sonra default.aspx’e hoşgeldiniz tarzı bir metin yerleştirelim(Daha sorna hoşgeldiniz yazısı yerine istatistik, özet bilgi ekranı vb. yapılabilir).

Aşağıdaki sınıfı gruplama işlemlerini MetaData üzerinden bildirebilmek için yazalım.
using System;
/// <summary>
/// Ömer Faruk ZORLU
/// </summary>
public class MenuInfoAttribute : Attribute
{
/// <summary>
/// Gruplandırma adını taşır. Üyelik Grubu, Sipariş
/// Menüsü gibi
/// </summary>
public string Name { get; set; }
/// <summary>
// Menüler sıralanırken göz önünde
// bulundurulacak sıralama ölçütüdür.
// Değerin tersi yönünde sıralama yapılır.
/// </summary>
public int Priority { get; set; }
/// <summary>
/// Files/Icon/{IconDegiskeni}.png yolu üzerinden
/// resimlerin yüklenmesi sağlanır. Değer ataması yaparken
/// dosyanın uzantı noktasından önceki kısımı yazılır.
/// </summary>
/// <example>myIcon.png için myIcon değeri atanmalıdır.</example>
public string Icon { get; set; }
public MenuInfoAttribute(string name, int priority)
: this(name, priority, string.Empty)
{ }
public MenuInfoAttribute(string name, int priority, string icon)
{
this.Name = name;
this.Priority = priority;
this.Icon = icon;
}
/// <summary>
/// Sıralama işleminde kullanılmak üzere yazılmıştır. Linq'in
/// orderby işlevini özelleştirmek amacıyla kullanılmıştır.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
if (obj == null) return false;
return this.Name.Equals(((MenuInfoAttribute)obj).Name);
}
}
Sınıfı yazdıktan sonra MenuInfo sınıfına MetaTable sınıfları üzerinden kolay erişebilmek adına aşağıki extension sınıfını yazalım.
using System.Linq;
using System.Web.DynamicData;
public static class MetaTableExtensions
{
/// <summary>
/// Tablolara atanmış MenuInfo özniteliğini dönderir. Eğer MenuInfo ataması
/// yapılmamışsaOthers isimli bir grub içinde oldukları kabul edilir.
/// </summary>
/// <param name="table"></param>
/// <returns></returns>
public static MenuInfoAttribute GetMenuInfo(this MetaTable table)
{
return table.Attributes.OfType<MenuInfoAttribute>().DefaultIfEmpty(new MenuInfoAttribute("Others", -10)).FirstOrDefault();
}
}
Bu aşamadan sonra masterpage içine aldığımız menünün yapısını değiştirmemiz gerekiyor. Halihazırda kullanılan GridView kontrolünün yerine ListView kontrolünü iç içe iki adet olacak şekilde tasarlıyorum. Dıştaki ListView kontrolü kategori gruplamalarını içte Repeater ise o kategorideki tabloları listelemekle görevli olacak. Tanımlamaya ait kod bloğu:
<asp:ListView ID="lvMenu" runat="server">
<LayoutTemplate>
<table style="width: 150px;">
<tbody>
<tr>
<td>
<table id="itemPlaceHolder" runat="server" />
</td>
</tr>
</tbody>
</table>
</LayoutTemplate>
<ItemTemplate>
<table rules="all" cellspacing="0" cellpadding="6" border="1" style="border-collapse: collapse;"
id="ContentPlaceHolder1_Menu1" class="DDGridView">
<tbody>
<tr class="th">
<%# SetRowsVisible(ToSEFString(Eval("GName").ToString())) %>
<th scope="col" style="height: 18px;" tblname='<%# ToSEFString(Eval("GName").ToString()) %>'>
<table>
<tr>
<td style="width: 18px;">
<%# GetIconOutput(Eval("Icon"))%>
</td>
<td>
<%# Eval("GName") %>
</td>
</tr>
</table>
</th>
</tr>
<asp:Repeater ID="rp1" runat="server" DataSource='<%# Eval("Tables") %>'>
<ItemTemplate>
<tr class="td jqMenutd" <%# GetDisplayCase() %>>
<td>
<asp:DynamicHyperLink ID="HyperLink1" runat="server"><%# Eval("DisplayName") %></asp:DynamicHyperLink>
</td>
</tr>
</ItemTemplate>
</asp:Repeater>
</tbody>
</table>
<br />
</ItemTemplate>
</asp:ListView>
MasterPage’e ait kodlar:
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.DynamicData;
public partial class Site : System.Web.UI.MasterPage
{
protected bool isRowVisible = false;
protected void Page_Load(object sender, System.EventArgs e)
{
var sonuc = from p in MetaModel.Default.VisibleTables
orderby p.GetMenuInfo().Priority descending
group p by p.GetMenuInfo() into groups
select new
{
GName = groups.Key.Name,
Icon = groups.Key.Icon,
Tables = groups
};
lvMenu.DataSource = sonuc;
lvMenu.DataBind();
}
protected string GetIconOutput(object objIcon)
{
if (objIcon == null)
return string.Empty;
return string.Format("<img src='{0}Files/Icons/{1}' />", GetAbsolutePath(), objIcon);
}
public string GetAbsolutePath()
{
return VirtualPathUtility.ToAbsolute("~/");
}
protected string SetRowsVisible(object obj)
{
if (obj == null || Convert.ToString(obj) == string.Empty)
isRowVisible = false;
HttpCookie httpCookie = Request.Cookies["ak_" + Convert.ToString(obj)];
if (httpCookie != null && httpCookie.Value == "out")
isRowVisible = false;
else
isRowVisible = true;
return string.Empty;
}
///
protected string GetDisplayCase()
{
return !isRowVisible ? "style='display:none'" : string.Empty;
}
/// <summary>
/// Türkçe karakterler bloklanıyor
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public string ToSEFString(string str)
{
return Regex.Replace(str, "ö|ç|ş|ğ|ü|ı|Ç|Ö|İ|Ş|Ü|Ğ", "_");
}
}
Bu aşamaya kadar gelmişseniz şuanki sayfa çıktısı aşağıdaki gibi olacaktır. Tablo başlığındaki “Others” yazısını görüyorsanız buraya kadar bir sorun yok demektir.

Gelelim menu tarafındaki kodlamanın son aşamasında –en sevdiğim kısımdır- MetaData bildirimlerini yapalım. Adres, kargo ve konum tablolarını “Bütünleşik Veriler” diye bir gruplandırmaya maruz bırakalım. Bunun için bu üç tablo üzerinde aşağıdaki MenuInfo tanımlamasının yapılması gerekiyor. Diğer tablolar içinde benzer tanımlamaları yapmış olalım.
Kargo tablosuna ait örnek MetaData sınıfı:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
[MetadataType(typeof(KargoMetadata))]
public partial class Kargo
{
[MenuInfoAttribute("Bütünleşik Veriler", 30, "text_ruler.png")]
public class KargoMetadata
{
}
}
Tanımlama işlemlerinden sonra menüde aşağıdakine benzer değişikliklerin görülmesi pek muhtemeldir.

Buraya kadar yaptığımız değişiklikler tablo karmaşasını büyük ölçüde engellemiş olacaktır. Ancak genel olarak biraz görsel değişiklik ve kullanım kolaylığı sağlamak hoş olur diye düşünüyorum. Bunun için css dosyasında biraz oynayarak aşağıdaki görünüme büründürdüm.

Açıkcası sol taraftaki menü ile ilgili biraz daha geliştirme yapmak istiyorum. Şuan baktığımda neden hepsi açık duruyor ki diyorum kendi kendime. Biraz javascript ile kasarsak grupları açılır kapanır hale getirebiliriz ve açma kapama olaylarınıda çereze aktarıp sayfa değişikliği olduğunda çerezden okuyup açılma/kapanma durumu korumuş oluruz. İlgili grubun başlığına tıklandığında açılma/kapanma durumunu tetikleyelim.
MasterPage’e tanımladığımız ListView kod bloğunda ItemTemplate şablonu içerisindeki th tagındaki tblname özniteliğini çaktırmadan tanımlamış oldum. Şimdi sıra javascipt tanımlamalarında. Aşağıdaki tanımlalar MasterPage sayfasının Head tagları arasında yapılmalıdır.
<script src="<%= GetAbsolutePath() %>Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
<script src="<%= GetAbsolutePath() %>Scripts/Plugins/jquery.highlight.js" type="text/javascript"></script>
<script src="<%= GetAbsolutePath() %>Scripts/Plugins/jquery.cookie.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
$(".DDGridView th").toggle(function () {
$.cookie("ak_" + $(this).attr("tblName"), "out");
var x = $(this).parent().parent().find(".jqMenutd").fadeOut("fast");
}, function () {
$.cookie("ak_" + $(this).attr("tblName"), "");
var x = $(this).parent().parent().find(".jqMenutd").fadeIn();
});
});
</script>
Bu işlemin ardından ekran görüntüsü aşağıdaki gibi olacaktır. Menü tarafı için artık yeter diyorum ve derin bir oh çekiyorum. Artık görünmesini yada yer kaplamasını istenmeyen kısımlar kullanıcıya özel olarak kısmen de olsa kapatılabiliyor. Ekran görüntüsü aşağıdaki gibi olmalıdır.

Bu yazı boyunca oldukca eğlendiğimi belirmek isterim. Herkese mutlu kodlar.
Ömer Faruk ZORLU
63e0f8a8-6bfd-4388-bd22-41a3bb715f72|0|.0
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...
1d9718a9-41e9-4a97-94ac-8a429c5f5fc8|0|.0
MetaData üzerinden öznitelik ataması yapılarak doğrulama, biçimleme ve özel alan şanlonlarını(FieldTemplates) nasıl kullanacağımızdan bahsetmiştim.
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 Tookit 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.
- Zorunlu olmasa da dosyanın sunucuya yükleme işleminin gerçekleştirileceği yardımcı bir sınıf.
- Her bir dosya yükleme alanı için dosya türü, boyutu vb. bildirimleri yapmak için öznitelik tabanlı bir sınıf
- 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ı.
- İş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
More...
a73c5a77-c3f5-44ca-86bc-66e3c5f9b461|0|.0
A simple dynamic data web site with everything App_Code are resolved. But will use tier architecture is not sufficient. Data access layer needs to be used in other projects. Therefore, the class library, I put Linq To Sql file metadata classes, but was ineffective. Researched solution for a few days. (Time loss) Linq To Sql file automatically when I reviewed the file name space designer.cs add noticed. I cleaned the added name space. Metadata has become active classes. Dynamism!
0b5d5652-581c-4a3f-8b3d-0d2e11e6b493|0|.0

Daha önce de bazı sunumlar yapmıştım ancak MSP olarak yaptığım ilk sunum olduğundan baya heyecanlıydım. 25 dakikada anlatmayı düşünüyordum ancak Agile Programming ve Rapid Application Development hakkında konunun tam olarak anlaşılması için bilgi vermem gerektiğini düşünerek anlatım süresini 45 dakikaya çıkarttım. ASP.NET Dynamic Data Web Site sunumu konusunda verdiği destekler için değerli Uğur UMUTLUOĞLU'na çok teşekkür ederim.
1b82d3d3-9b16-4554-823d-1df0e1ebc017|0|.0
Merhabalar, bu yazım da Custom Pages (özel sayfalar) kullanımı hakkında bilgi vereceğim. Dynamic Data Web sitesinin varsayılan şablonlarını bazı sayfalar için kullanmak istemeyebiliriz. Herhangi bir şablon için özel sayfa tasarlamak istiyorsak varsayılan Dynamic Data Web sitesi ile gelen ~/DynamicData/CustomPages klasörü içine TabloAdı/ŞablonAdi.aspx oluşturulması yeterli. Daha iyi anlaşılması için Product tablosu için özel sayfamızı tasarlamaya başlayalım.

Artık standart şablondan kopyala yapıştır ile ~/DynamicData/CustomPages/Products/ konumuna yapıştırdığım List.aspx şablonu kullanımdadır. Şablon üzerinde özelleştirme yapalım. Product tablosundaki tüm alanların görünmesini istemiyorum. Bunun için MetaData'da yazabiliriz ancak DynamicDataField kullanımını da gösterebilmek adına GridView1 nesnesinin AutoGenerateColumns özelliğini false yapıyorum.
More...
8a9219bc-c900-4d7f-90f2-2d59c49a5167|0|.0