4 Kasım 2011 Cuma

MVC için SEO - 301 Redirect

Problem

Arama motorları web üzerinden ki aynı içeriğe birden fazla url ile ulaşılması durumunda tekrarlanan içerik olarak algılamaktadır. Arama motorları sitenizi tararken www'li e www'siz iki ayrı içeriği algılarlar. Yani http://www.siteniz.com/sayfa1.html ve http://siteniz.com/sayfa1.html  arama motorları için iki farklı tekrar edilen içeriktir. Bu durumda arama motorları tüm sitenizi tekrar eden içerik olarak algılar ve sitenizin bulunabilirliği azalır.

301 Redirect

Yapmanız gereken sitenizin www'li veya www'siz versiyonundan birini seçip gelen istekleri  diğer versiyona yönlendirmektir. Genellikle www'li versiyon kullanılmaktadır. Bende örnek olarak www'siz versiyondan gelen istekleri www'li versiyona yönlendireceğim. Yapmamız gereken gelen tüm istekleri kontrol edecek bir MvcHandler yazmak. MvcHandler'ımız gelen istekleri kontrol edecek ve istek adresinde www yoksa isteği www'li versiyona yönlendirecek.

Mvc 301 Redirect

   MvcHandler'lar Global.asax içinde yaptığımız Route tanımlarına eklenen nesnelerdir. MvcHandler bir Rooute işlemi çalışırken hangi Controller'ın seçilmesi gerektiğine karar verirler. Bu işlemi yapabilmek için tüm isteklerin başında çalışırlar. Mvc gelen isteğe göre önce hangi Route tanımının çalışması gerektiğine karar verir daha sonra Route içinde tanımlı Handler'larda hangi Controller sınıfının çalışması gerektiğine karar verir. Böyle her hangi bir Router için tüm istekler "Handle" edebileçeğiniz yer MvcHandler'dır.

Bizim problemimizde www'siz gelen tüm istekleri www'li versiyona çevirecek bir MvcHandler yazmamız gerekmektedir.

[cs]

public class RedirectHandler : MvcHandler
    {
        public RedirectHandler(RequestContext requestContext)
            : base(requestContext) { }

        protected override IAsyncResult BeginProcessRequest(
            HttpContext httpContext,
            AsyncCallback callback, object state)
        {

            if (!httpContext.Request.Url.AbsoluteUri.Contains("://www."))
            {

                httpContext.Response.Status = "301 Moved Permanently";
                httpContext.Response.StatusCode = 301;
                httpContext.Response.AppendHeader(
                    "Location",
                    httpContext.Request.Url.AbsoluteUri
                        .Replace("://", "://www.")
                    );
            }

            return base.BeginProcessRequest(
                httpContext, callback, state);
        }
    }

Mvc 301 Redirect

Yukarıda RedirectHandler içinde istek olarak gelen adresde www yoksa www'li versiyonuna yönlendirme yaptık. Fakat yönlendirmeden MVC sitemizde ki Route tanımlarının haberi bulunmamaktadır. Tüm Route tanımlarımıza RedirectHandler'ı kullanmaları gerektiğini söylememiz gerekmektedir. Yani tüm Route tanımlarımıza yazdığımız RedirectHandler ile çalışarak gerekli Controller'ı seçmesini söylemeliyiz.

[cs]

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            routes.IgnoreRoute("Content/{*pathInfo}");

            routes.MapRoute(
                "DefaultController",
                "{controller}/{action}/{id}",
                new {controller = "Home", action = "Index",id=UrlParameter.Optional});

            RouteHandler<RedirectHandler>.Assign(RouteTable.Routes);

Global.asax içinde sitemizde bulunan tüm Route tanımlarına RedirectHandler ekledik. Burada kullandığım RouteHandler sınıfı verilen MvcHandler tipini tüm Route tanımlarına eklemekte sorumludur. Yani RouteHandler sınıfı MvcHandler sınıflarını Route listelerine ekleyebilecek yardımcı bir sınıfdır.

[cs]

public class RouteHandler<THttpHandler> : MvcRouteHandler
    where THttpHandler : MvcHandler
    {
        public RouteHandler(IControllerFactory controllerFactory)
            : base(controllerFactory) { }
        public static void Assign(RouteCollection routes)
        {
            using (routes.GetReadLock())
            {

                var routeHandler
                    = new RouteHandler<THttpHandler>(
                        ControllerBuilder.Current.GetControllerFactory());

                foreach (var route in routes
                    .OfType<Route>()
                    .Where(r => (r.RouteHandler is MvcRouteHandler)))
                {

                    route.RouteHandler = routeHandler;
                }
            }
        }
    }

Sonuç

SEO için aynı içeriğin tek bir adresi olması gerekmektedir. Sitemizin www'siz adreslerini www'li adrese yönlendirmemiz gerekiyor. Bir MvcHandler yazarak bu işlemi gerçekleştirdik. Mvc sitemizde bulunan tüm Router tanımlarına bu handler'ı ekledik. Bu örnekte tüm isteklerde çalışacak bir MvcHandler yazmayı ve sitemizin Router tanımlarına eklemeyi gördük. Ayrıca SEO için önemli bir problemide çözmüş olduk. Ayrıca 301 Redirect mekanizması sadece adrese www eklemek için kullanılmaz. Eski adresin yeni adrese yönlendirilmesi işlemindede kullanılır.

2 Kasım 2011 Çarşamba

MVC için SEO - RSS

Kullanıcılar site içinde ilgilendikleri içerikler güncellendikce haberdar olmak isterler. Sitede yayınlanan beslemelere (RSS  - feed) abone olarak site içeriğini takip ederler. Arama motorlarıda benzer şekilde site değişikliklerini takip etmek için site haritaları ve site beslemelerini kullanırlar. Peki MVC ile geliştirdiğimiz sitemize web içerik besleme özelliğini nasıl entegre edebiliriz?

RSS Nedir ?

RSS (Really Simple Syndication) web içeriği abonelik formatıdır. RSS içeriği sabit bir xml yapısıdır. <rss> tagları arasında sitede yer tüm içeriği sunabilmektesiniz. Site haritası'na (sitemap.xml) göre çok daha gelişmiş ve detaylı bir veri yapısına sahiptir. Yorumlar, resimler, yayın tarihi web master bilgisine kadar geniş bir veri yapısı ile besleme oluşturabilirsiniz.

RSS ve .NET

Bu kadar gelişmiş bir veri yapısını .NET ortamında nasıl yöneteceksiniz. Üstelik veri yapısı benzerde olsa atom gibi farklı bir  abonelik formatı daha  vardır. Tüm bu farklı besleme formatlarını destekleyen bir besleme kaynağı oluşturma araçına ihtiyaçımız var. .NET yer alan SyndicationFeed sınıfı bu ihtiyacımızı karşılamaktadır.  SyndicationFeed nesnesi oluşturup site içeriğinizi feed.items altına doldurarak kendi besleme kaynağınızı oluşturabilirsiniz.

[cs]

public SyndicationFeed CreateFeed()
        {
            var pages = _pageRepository.Query();
            
            var feed = new SyndicationFeed( 
                    _settingRepository[SiteSettingKey.SiteTitle],
                    _settingRepository[SiteSettingKey.SiteSlogan],
                    new Uri(_settingRepository[SiteSettingKey.SiteUrl]));
            feed.Categories.Add(new SyndicationCategory("Blog"));
            feed.Language = "tr-TR";
            feed.Copyright = new TextSyndicationContent(
                string.Format("{0} {1}", DateTime.Now.Year, _settingRepository[SiteSettingKey.SiteTitle]));
            feed.LastUpdatedTime = DateTime.Now;
            feed.Authors.Add(
                    new SyndicationPerson
                        {
                            Name = _settingRepository[SiteSettingKey.SiteTitle], 
                            Email = _settingRepository[SiteSettingKey.Email]
                        });
            var feedItems = new List<SyndicationItem>();
            foreach (var item in pages)
            {
                var sItem = 
                    new SyndicationItem(
                        item.Title,
                        null,  /*content*/
                        new Uri(string.Format("{0}/{1}/{2}/{3}", 
                                _settingRepository[SiteSettingKey.SiteUrl], 
                                item.Published.Year, 
                                item.Published.Month, 
                                item.Slug)))
                        {
                            Summary = new TextSyndicationContent(item.Summary),
                            Id = item.Slug,
                            PublishDate = item.Created
                        };
                sItem.Links.Add(
                    new SyndicationLink
                        {
                            Title = item.Title,
                            Uri =
                                new Uri(string.Format("{0}/{1}/{2}/{3}", 
                                    _settingRepository[SiteSettingKey.SiteUrl], 
                                    item.Published.Year, 
                                    item.Published.Month, 
                                    item.Slug)),
                            Length = item.ContentHtml.Length,
                            MediaType = "html"
                        });
                feedItems.Add(sItem);
            }
            feed.Items = feedItems;
            return feed;
        }

Yukarıdaki örnekte bir besleme kaynağı (feed) oluşturulmuştur. Besleme kaynağı içerisine sitenin başlığı, url bilgisi, dil bilgisi, yazar bilgisi gibi bir çok bilgi eklenmiştir. Daha sonra sitede bulunan sayfalar feed içerisine eklenmiştir. Bu örnekte sayfa içeriğini olduğu gibi besleme kaynağına (feed) gömmek yerine sadece özet bilgisi göndermeyi tercih ettim. Beslemem içerisine hem içeriğin özetini hemde içeriğin linkini ekledim. Böylece sitemize ait içeriği webde paylaşabilecek duruma geldik.

RSS ve MVC

Rss içeriğimizi oluşturduk. Şimdi bu içeriği MVC sitemizde yayınlamamız gerekiyor. Oluşturduğumuz Rss içeriği kendi formatına sahip.  Web browser uygulamasına geri dönen değerin Rss formatına sahip olduğunu söylememiz gerekiyor. Böylece kullanıcı Rss içeriğini düzgün şekilde görüntüleyebilsin.

[cs]

public class SyndicationFeedResult : ContentResult
    {
        public SyndicationFeedResult(SyndicationFeed feed)
        {
            using (var memstream = new MemoryStream())
            using (var writer = new XmlTextWriter(memstream, System.Text.Encoding.UTF8))
            {
                feed.SaveAsRss20(writer);
                writer.Flush();
                memstream.Position = 0;
                Content = new StreamReader(memstream).ReadToEnd();
                ContentType = "application/rss+xml";
            }
        }
    }

Yukarıdaki kod bloğunda MVC ContentResult sınıfından türettiğimiz SyndicationFeedResult sınıfı  vardır. SyndicationFeedResult sınıfı içerik olarak .NET'e ait SyndicationFeed nesnesini Rss  formatında serialize etmektedir. Veri içeriğinin Rss formatında olduğunu söylemektedir.  Tüm Rss içeriğini CreadFeed() ile oluşturduk ve Rss içeriğini istemciye uygun formatta ulaştıracak SyndicationFeedResult sınıfnı yazdık. Bir action metot içinde site beslememizi paylaşabiliriz.

[cs]

public SyndicationFeedResult Feed()
        {
            var feed = CreateFeed(); 
            return new SyndicationFeedResult(feed);
        }

mvc feed görüntüsü

Sonuç

Rss ve Atom gibi web içerik besleme yapıları gelişmiş veri yapıları ile çok detaylı bir şekilde içerik paylaşmamıza olanak sağlamaktadır. Bu gelişmiş veri yapısı ve farklı veri formatları ile uğraşmadan .NET SyndicationFeed sınıfını kullanarak içerik paylaşımı yapabilirsiniz. MVC ile SyndicationFeed sınıfını entegre etmek için yeni bir ActionResult sınıfnı yazmalısınız. Özelleşmiş ActionResult sınıfınız içerik tipini rss olarak ayarlayıp SyndicationFeed içeriğini döndürmelidir.

1 Kasım 2011 Salı

MVC için SEO - Sitemap

MVC ile site geliştrdiniz ve dünyanın bundan haberdar olması istiyorsunuz. Yapmanız gerek sitenizi arama motorlarına kaydetmek. Arama motorlarının sitenizde dolaşabileceği ve sitenizin listelerde üst
sıralarda çıkarabilecek bir yol sunmalısınız. MVC ile geliştirilen bir sitede SEO nasıl yapılır? Burada her yerde bulunabilecek (saylarda özel başlıklar kullanın, meta etiketleri ile sayfa içeri betimleyin, okunabilir linkler verin gibi) SEO önerileri yerine kodlama yapılarak sitenize kazandırmanız gereken özellikleri anlatacağım.
İlk olarak insanların ve arama motorlarının sitenizde rahatça dolaşabilmesi için site haritaları hazırlamanız gerekmektedir. Sitenizde ziyaretcileriniz için mutlaka bir site haritası sayfası veya arşiv sayfası oluşturunuz.

SiteMap.xml

Site Haritaları, web yöneticilerinin arama motorlarını sitelerindeki taranabilir

sayfalar hakkında bilgilendirmeleri için kolay bir yoldur.En basit biçimiyle, bir

Site Haritası, arama motorlarının siteyi daha akıllıca tarayabilmeleri için, her

bir URL'yle ilgili ek meta verilerle (son güncellenme zamanı, genellikle ne sıklıkta

değiştiği ve sitedeki diğer URL'lere göre ne kadar önemli olduğu) birlikte, bir

siteye ilişkin URL'leri listeleyen bir XML dosyasıdır. (sitemaps.org adresinden alıntıdır)
SiteMap.xml dosyası bir çok arama motoru tarafından desteklenen bir endüstri standartıdır. Sitenizin SiteMap.xml protokolüne uygun site haritası sağlaması gerekmektedir.

MVC sitemap.xml

Eğer siteniz için dinamik bir sitemap.xml oluşturmak istiyorsanız ne yapmalısınız ? İlk olarak yapmamız gereken sitemize gelen sitemap.xml dosya isteğini yakalamamız ve bir action'a yönlendirmemiz gerekmektedir. Uygulamanın başlangıçında (Global.asax Application_Start()) içinde sitemap.xml yönlendirmeyi yapalım:
[cs]
public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("robots.txt");
            routes.MapRoute("sitemap", "sitemap.xml", new { controller = "Rss", action = "SiteMap" });
Sitemize gelen sitemap.xml dosyası isteği RssController.SiteMap() action'a yönlendirdik. RssController.SiteMap() action metotdu stansart bir html yerine sitemap.xml protokolüne uygun çıktı üretmelidir. O halde ilk olarak sitemap.xml protokolüne uygun özelleşmiş bir ActionResult oluşturamamız gerekmektedir. Böylece arama motorlarının anlayabileceği bir içerik oluşturmuş olacağız. Sonuç olarak bir xml döndüreceğimiz için XmlWriter sınıfnı çıktı üretmek için kullanabiliriz.
[cs]
public class SitemapActionResult : ActionResult
    {
        private readonly string _siteUrl;
        private readonly List<Page> _pages;

        public SitemapActionResult(string siteUrl, List<Page> pages)
        {
            _siteUrl = siteUrl;
            _pages = pages;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            context.HttpContext.Response.ContentType = "text/xsl";
            using (XmlWriter writer = XmlWriter.Create(context.HttpContext.Response.Output))
            {
                writer.WriteStartElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9");
                writer.WriteStartElement("url");
                writer.WriteElementString("loc", _siteUrl);
                writer.WriteElementString("lastmod", DateTime.Now.ToString("yyyy-MM-dd"));
                writer.WriteElementString("changefreq", "daily");
                writer.WriteElementString("priority", "1.0");
                writer.WriteEndElement();

                foreach (var page in _pages)
                {
                    writer.WriteStartElement("url");
                    writer.WriteElementString("loc",
                                              string.Format("{0}/{1}/{2}/{3}", _siteUrl, page.Published.Year,
                                                            page.Published.Month, page.Slug)); 
                    writer.WriteElementString("lastmod", page.LastChanged.ToString("yyyy-MM-dd")); 
                    writer.WriteElementString("changefreq", "daily");
                    writer.WriteElementString("priority", "0.5");
                    writer.WriteEndElement();
                } 
                writer.WriteEndElement(); 
                writer.Flush();
                writer.Close();
            }
        }
    }
Böylece protokole uygun çıktı üretip xml forrmatında HttpContext.Response üzerine yazmış olduk. Benim örneğimde sayfaların değişim sıklığı ve önceliklerinin aynı olduğunu kabul edilmiştir. Her bir sayfaya ulaşabilmek için okunabilir bir link verilmiştir. Kendiniz için yukarıdaki ActionResult sınıfnı özelleştirebilirsiniz. Son olarak satimap.xml isteklerini yönlendirdiğimiz RssController.SiteMap() action metodunun SitemapActionResult ile isteklere cevap vermesi gerekmektedir.
[cs]
public ActionResult SiteMap()
        {
            return new SitemapActionResult(
                _settingRepository[SiteSettingKey.SiteUrl],
                _pageRepository.Query()
                    .Where(c => c.IsPublish && c.Published < DateTime.Now)
                    .OrderByDescending(c => c.Published).ToList());
        }
Böylelikle yayında olan sayfaları listeleyip sitemap.xml protokolüne uygun olarak cevap dönmüş olduk.
sitemap.xml görüntüsü

Sonuç

Arama motorlarına sitede bulunan sayfaları listemek ve sayfalar hakkında bilgi vermek için sitemap.xml dosyaları kullanılmaktadır. Buradaki çalışma ile MVC sitenize ait sitemap.xml dosyanızı dinamik olarak üretip kullanabileceğiniz bir yol önerilmiştir. SEO için yapılması gerekenler listesinde önemli bir madde olan sitemap.xml dosyası oluşturmayı çözümlemiş bulunmaktayız.

31 Ekim 2011 Pazartesi

MVC için Elmah ile hata loglama

Problem

MVC uygulamanızda oluşan tüm hataları loglamak ve gerektiğinde listelemek istiyorsunuz. Bunun için tüm muhtemel yerlere try{}catch() blokları yerleştirmeli ve hatayı bir yerlere yazmalısınız. ActionFilter yazarak action'lar içindeki hatayı filter seviyesinde loglayabilirsiniz. Fakat action'lar dışındaki alanlarda oluşan beklenmedik hataları nasıl elde edeceksiniz.

ELMAH Nedir?

ELMAH ( Error Logging Modules And Handlers) çok fazla bilinmeyen açık kaynak kodlu bir ASP.NET projesidir. Çalışma anında beklenmeyen bir durum oluştuğunda loglama için tek satır kodu değiştirmeden tamemen web.config ayaları ile ELMAH sizin yerinize aşağıdaki işlemleri yapacaktır.

  • Bütün beklenmeyen hataları loglama
  • Uzaktan erişilebilir bir web sayfası ile tüm hataları listeleme
  • Uzaktan erişilebilir bir web sayfası ile hata detaylarını gösterme
  • Asp.Net'in sarı hata sayfası yerine özelleştirilmiş hata sayfalarına yönlendirebilme
  • Hata oluştuğunda mail ile bilgilendirme
  • Hata listeleme için RSS bağlantısı
  • Hataları sql server gibi farklı veri kaynaklarında saklayabilme

Nasıl Kullanılır ?

Öncelikle ELMAH'ı kendi projenize kurmanız gerekmektedir. Nuget ile aşağıdaki komutu çalıştırarak kendi projenize ekleyebilirsiniz.

PM> Install-Package elmah

Kurulumdan sonra Nuget gerekli tüm web.config ayarlarını yapacaktır. MVC uygulamanızda elmah.axd sayfasına giderek hata listeleme sayfasına ulaşabilirsiniz.

elmah.axd sayfasının görünümü

ELMAH bu işleri nasıl yapmaktadır. web.config dosyası üzerinde takip edelim.

[xml]

<httpHandlers>
      <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
    </httpHandlers>
    <httpModules>
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
    </httpModules>

Elmah.ErrorLogModule ile Asp.Net işlemi içinde oluşan her beklenmeyen hata kayıt etmektedir. Loglama, mail gönderme ve filtreleme özelliklerini gene web.config üzerinden değiştirebilirsiniz. Oluşan beklenmedik hatalar varsayılan olarak xml dosyalarda tutulmaktadır. Ben örnek olması açısından hataların bir SQL CE veri tabanında tutulmasını göstericeğim. Yapmamız gereken web.config üzerinde ELMAH'a veri saklama yerini değiştirmesini söylemek.

[xml]

<connectionStrings>
    <add name="elmah-sqlservercompact" connectionString="Data Source=|DataDirectory|\Elmah.sdf" />
  </connectionStrings>
  <elmah> 
    <errorLog type="Elmah.SqlServerCompactErrorLog, Elmah" connectionStringName="elmah-sqlservercompact" />
  </elmah>

MVC Entegrasyonu

Hiç bir ekstra geliştirme yapmadan ELMAH çalışacaktır. Fakat MVC ile gelen beklenmeyen hataları yönetem bir HandleErrorAttribute ActionFilter nesmemiz var. Bu attribute MVC uygulamamızda GlobalFilter olarak kayıt edilmektedir. Bunun anlamı MVC ile gelen HandleErrorAttribute tüm Action metotlarımızın için bir merkezi hata yönetim noktasıdır.(Daha fazlası için Aspect Programing, Policy Injection) Action içinde oluşan hatalar HandleErrorAttribute içinde yakalanacağı için ELMAH tarafından listelenemeyecektir. Yapmamız gereken HandleErrorAttribute ile ELMAH'ı konuşturmaktır. Kendi HandleErrorAttribute sınıfmızı yazıp oluşan hatayı ELMAH'a bildirmemiz gerekmektedir.

[cs]

[Export]
    public class HandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
    {
        public override void OnException(ExceptionContext context)
        {
            base.OnException(context);
            if (context.ExceptionHandled)
                ErrorSignal.FromCurrentContext().Raise(context.Exception);
        }
    }

Son olaral Global.asax içinde kendi HandleErrorAttibute sınıfımızı kayıt etmeliyiz.


[cs]

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new Filters.HandleErrorAttribute()); 
        }

Sonuç

ELMAH beklenmeyen hataları loglamak ve yönetmek için açık kaynak kodlu güçlü bir araçtır. Muhtemelen sonraki Asp.Net versiyonlarında framework içinde geleçek bir uygulamadır. MVC ile entegrasyonu oldukça kolaydır. Buradaki örnekte olduğu gibi hata loglarını ayrı bir SQL CE veri tabanında tutmanızı öneriyorum. Zira ELMAH hataların stack bilgisinide sakladığı için hata kayıtları çok fazla yer işgal etmektedir. Ayrı bir SQL CE veri tabanı kullanarak gerektiğinde hata kayıtlarını doğrudann silebilirsiniz.

30 Ekim 2011 Pazar

MVC için Dependency Injection

Problem

MVC mimarisi gereği çok parçalı bir yapıdır. View, controller, filter, model gibi bir çok parçanın bulunduğu ortamda nesneler arasında ki ilişkiyi takip etmek ve performansı korumak bir sorundur. Yani bir DbContext nesnenize her hangi bir view, controller veya filter üzerinden erişebilmek isteriz.

MVC'nin bu karmaşasından arınmanın en iyi yöntemi MVC uygulamıza bir DI mekanizması eklemektir. Dependency Injection(DI) bir ilke veya bir mimari desendir. DI epeyce farklı tanımları vardır. Genel olarak DI uygulama nesneleri arasında nesne bağımlılılarını ve yaşan döngülerini yönetmektedir.

MVC'ye DI nasıl entegre edilir?

MVC çalışma anında ihtiyaç duyduğu tüm nesneleri elde etmek için formal bir yöntem ile geliştirmiştir. MVC IDepenedcyResolver interface uygulaması olan DependencyResolver nesnesi ile birlikte gelmektedir. MVC uygulamanızda oluşturulacak tüm nesneler IDependencyResolver interface ile oluşturulmaktadır. Yani aslında MVC doğrudan DI esteği ile gelmektedir. MVC Uygulamamıza DI entegre etmek için yapmamız gereken IDepenedcyResolver interface uygulayan bir sınıf oluşturmak ve bunu MVC uygulamıza söylemektir.

IDependecyResolver

IDependecyResolver interface'i iki fonksiyona sahiptir.

[cs]

public interface IDependencyResolver
    {
        object GetService(Type serviceType);
        IEnumerable<object> GetServices(Type serviceType);
    }
  • GetService(servicType): Bu fonksiyon bir servis tipini alır ve çözümlemeye çalışır. Eğer istenen servis tipine bağlı servis tanımları varsa uygun servisi üretir ve döndürür. Eğer uygun bir bağımlılık tanımı bulamazsa null döner bu durumda MVC varsayılan davranışına devam eder.
  • GetServices(servicType): Bu mehod servis tipine uygun tüm servis çözümlerini bir koleksiyon (IEnumerable<>) içinde döner. Benzer şekilde eğer servis tipi çözümlenemezse null döner ve MVC'nin varsayılan şekilde çalışmaya devam etmesini sağlar.

DependencyResolver, özel IDependencyResolver nesnemizi kaydetmek için kullandığınız statik bir sınıftır. DependencyResolver.SetResolver ( IDependecyResolver) yöntemini kullanarak uygulamanız için özelleştirdiğiniz IDependencyResolver nesnenizi kurabilirsiniz. Artık MVC uygulamızda üretilen tüm MVC nesneleri özelleştirdiğiniz IDependencyResolver nesnesi üzerinden sağlanacaktır.

CompositionDepencyResolver Oluşturmak

Şimdi basit bir IDependecyResolver örneği yapalım. Tüm MVC nesnelerinin üretileceği Composition kullanan özelleşmiş CompositionDependecyResolver sınıfmız. :

[cs]

[Export(typeof (IDependencyResolver))]
    public class CompositionDependencyResolver
        : IDependencyResolver
    {
        #region IDependencyResolver Members

        public object GetService(Type serviceType)
        {
            try
            {
                IEnumerable<Lazy<object, object>> exports = _container.GetExports(serviceType, null, null);
                if (exports.Any())
                    return exports.First().Value;

                object result = _defaultDependencyResolver.GetService(serviceType);
                if (result != null) _container.ComposeParts(result);
                return result;
            }
            catch (Exception)
            {
                return null;
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            try
            {
                IEnumerable<Lazy<object, object>> exports = _container.GetExports(serviceType, null, null);
                if (exports.Any())
                    return exports.Select(e => e.Value).AsEnumerable();
                IEnumerable<object> result = _defaultDependencyResolver.GetServices(serviceType);
                _container.ComposeParts(result);
                return result;
            }
            catch (Exception)
            {
                return null;
            }
        }

        #endregion
    }


Burada basitçe servis tipi önce katolog üzerinde aranmaktadır. Eğer katalog üzerinde servis tipine ait bir çözüm varsa bu döndürülmektedir. Eğer katalog üzerinde servis tipine ait bir çözüm yoksa MVC'nin varsayılan IDependencyResolver uygulamasına çözüm sorulmaktadır. Varsayılan IDependencyResolver  çözümü bulursa dönen nesne üzerinde tekrar DI işlemi yapılmaktadır.

Şimdi Global.asax içinde özelleştirdiğimiz IDependencyResolver sınıfımızı kayıt edelim:

[cs]

protected void Application_Start()
        {
            var catalog = new AggregateCatalog(new AssemblyCatalog(typeof(PageRepository).Assembly));
            var container = new CompositionDependencyResolver(catalog, DependencyResolver.Current);

            DependencyResolver.SetResolver(container);

Böylelikle tüm MVC nesnelerinizin bağımlılıklarını tek bir sınıf ile çözebilirsiniz.Artık istediğiniz MVC nesnesi ile DI kullanabilirsiniz.

[cs]

[Export, PartCreationPolicy(CreationPolicy.NonShared)]
    public class BlogController : Controller
    {
        private readonly IPageRepository _pageRepository;
        private readonly ILayoutRepository _layoutRepository;
        private readonly ITagRepository _tagRepository;
        private readonly ICommentRepository _commentRepository;
        private readonly ISettingRepository _settingRepository;

        [ImportingConstructor]
        public BlogController(IPageRepository pageRepository, ILayoutRepository layoutRepository, 
            ITagRepository tagRepository, ICommentRepository commentRepository, ISettingRepository settingRepository)
        {
            _pageRepository = pageRepository;
            _layoutRepository = layoutRepository;
            _tagRepository = tagRepository;
            _commentRepository = commentRepository;
            _settingRepository = settingRepository;
        }

Sonuç

MVC  uygulamaları daha fazla DI desteğine ihtiyaç duyduğu için framework IDependecyResolver interface sunmaktadır. Burada özelliştirilmiş bir IDependecyResolver nasıl yazılaçağını inceledik. DI ihtiyaçımızı gene .NET içinde sunulan Composition isim uzayı ile karşıladık.

23 Şubat 2011 Çarşamba

Telerik MVC Extensions

Yazılım geliştiricilerin en çok ihtiyaç duyduğu bileşenlerden biride yetenekli ön yüz (özellikle grid) bileşenleridir. MVC uygulamalarınızda kullanabileceğiniz open source bir çözüm var: Telerik MVC Extensions

Telerik MVC 3 için mevcut kontrol kütüphanesine extension yazıdı ve bunu open source olarak paylaştı. TelerikMVCExtensions projesini NuGet ile kendi MVC projenizde kullanabilirsiniz. (NuGet ile bir çok open source uygulamayı kendi projenize ekleyebilirsiniz. NuGet’i nasıl kurucağınızı ve kullanacağınızı buradan öğrenebilirsiniz.)

Telerik MVC Extensions paket referansını kendi projenize ekledikten sonra gelişmiş grid özelliklerini inceleyelim. Ajax çağrıları ile grid üzerinde CRUD işlemleri nasıl yapılır?

Öncelikle gridi sayfamıza ekleyelim.

[cs]

@using  Telerik.Web.Mvc.UI  
@(  Html.Telerik().Grid()         
 .Name("Grid" )         
 .DataKeys(keys =>         
 {             
  keys.Add(p => p.LinkCategoryId);         
 })         
 .ToolBar(commands =>             
  commands.Insert().ButtonType(GridButtonType .ImageAndText)                 
   .ImageHtmlAttributes(new  { style = "margin-left:0"  }))         
 .DataBinding(dataBinding =>{             
  dataBinding.Ajax()                 
   .Select("_SelectAjaxEditing","LinkCategory")                 
   .Insert("_InsertAjaxEditing","LinkCategory" )                 
   .Update("_SaveAjaxEditing" , "LinkCategory" )                 
   .Delete("_DeleteAjaxEditing" , "LinkCategory" );         
 })         
 .Columns(columns =>{             
  columns.Bound(p => p.Title).Width(210);             
  columns.Bound(p => p.Url).Width(210);             
  columns.Bound(p => p.Description).Width(130);             
  columns.Command(commands =>{                 
   commands.Edit().ButtonType(GridButtonType .ImageAndText);                 
   commands.Delete().ButtonType(GridButtonType .ImageAndText);             
  }).Width(180).Title("Commands" );         
 })         
 .Editable(editing => editing.Mode(GridEditMode .PopUp))         
 .Pageable()         
 .Scrollable()         
 .Sortable())

Yukarıdaki kod bloğunda bir Telerik MVC gridin kullanıldığını görüyoruz. Satır satır inceleyerek kodun ne yaptığına bakalım

  • Gridin öncelikle bir veri tipi ile birlikte oluşturuyoruz:  @Html.Telerik().Grid<LinkCategory>() burada giridin LinkCategory veri yapısı ile birlikte çalışaçağını bildiriyoruz.
  • Daha sonra veri yapımızda ki anahtar alanı tanımlıyoruz: .DataKey( keys => keys.Add(..))
  • Toolbar üzerine Insert Command ekliyoruz:  .Toolbar( commands=> commands.Insert()…)
  • Daha sonrada gridin veri bağlama işlemlerini nasıl yapacağını yani grid ile Controller sınıf arasında ki bağlantıyı belirtiyoruz: .DataBinding(dataBinding.Ajax().Select(…).Insert(…)…. ) Databinding mekanizması olarak Ajax() kullanılacak ve her bir CRUD işlemi için çağırılacak Controller ve Action belirtiliyor: .Select(“SelectAjaxEditing”, “LinkCategory”)
  • Tüm kolonların nasıl gözükmesi gerektiğini söylüyoruz: .Columns( columns=> columns.Bound(…)) (standart gridden gelen DataAnnotation desteği devam ediyor)
  • Command butonları ekliyoruz: columns.Command(….)
  • Gridi edit moduna ceviriyoruz: .Editable(…)
  • Ekstra özellikleri ekliyoruz: .Pageable().Scrollable().Sortable()

Gridin çalışması için gerekli MVC Controller sınıfınada göz atalım:

[cs]

using  Telerik.Web.Mvc; 
namespace  RoC.Modules.Admin.Controllers{     
 public  class  LinkCategoryController:Controller{         
  [Import]private  AdminContext  _adminContext;         
  
  [GridAction] 
  public ActionResult CategoriesManage()        
  {             
   return  View ();         
  }         
  
  [GridAction ]         
  public  ActionResult  _SelectAjaxEditing()         
  {             
   return  View (new  GridModel (_adminContext.Links));         
  }         
  
  [HttpPost ]         
  [GridAction ]         
  public  ActionResult  _SaveAjaxEditing(int  id)         
  {             
   .....             
   return View(new GridModel(_adminContext.LinkCategories));         
  }         

  [HttpPost ]         
  [GridAction ]         
  public  ActionResult  _InsertAjaxEditing () {             
   ....             
   return View(new GridModel(_adminContext.LinkCategories));         
  }         

  [HttpPost ]         
  [GridAction ]         
  public  ActionResult  _DeleteAjaxEditing (int  id)  {            
   ...             
   return  View (new  GridModel (_adminContext .LinkCategories ));         
  }     
 } 
}

Yukarıdaki kod bloğunuda inceleyelim:

  • CategoriesManage()  fonskiyonu sadece view dönmektedir. Çünkü veriler gridin select fonksiyonu ile doldurulacaktır.
  • Alt çizgi ile başlattığımız tüm grid fonksiyonları [GridAction] niteliğine sahiptir. Grid fonksiyonları kendilerine ait veri tabanı işlemini yaptıkdan sonra veriyi GridModel tipinde döndürmektedir.

Tüm bu kütüphaneyi kullanmak için master.page/_layout.cshtml içinde eklemeniz gereken kodlar vardır.

[cs]

@(Html.Telerik().StyleSheetRegistrar()   
 .DefaultGroup(group => 
  group.Add("telerik.common.min.css" )   
   .Add( Html.GetCurrentTheme()+"/"+"telerik.min.css")   
   .Add("telerik.rtl.min.css" )                             
   .Combined(true )                             
   .Compress(true )) )  
@Html.Telerik().ScriptRegistrar()

Yukarıdaki kod bloğu ile Telerik kontrollerinin kullandığı Css ve javascript dosyaları sayfaya eklenmektedir.

Böylelikle tüm CRUD işlemlerini destekleyen bir grid ekranı elde etmiş olduk.

Sonuç

Telerik firmasının standart MVC kontrolerini genişleterek oluşturduğu TelereikMVCExtension paketi MVC uygulamaları ile gerçeken güzel ve yetenekli arayüzler oluşturmanıza yardımcı olaracaktır. Bileşenleri buradan indirebilir. Daha ayrıntılı örneklere buradan erişebilirsiniz.

16 Şubat 2011 Çarşamba

Entity Framework Code Fisrt Veri Doğrulama

Bir önce ki yazıda Veri Açıklaması/DataAnnotations isim uzayını inceledik. Veri açıklamaları ile veri moleliniz üzerinde property seviyesinde doğrulama mantığımızı çalıştırabiliyoruz. Veri modeli sınıflarınıza .NET 4 tarafından tamamen desteklenen [Range], [RegulerExpress] gibi doğrulama nitelikleri ekleyerek veri tabanı işlemleri yapmadan önce gerçekten kolay bir şekilde doğrulama kontrolüne zorlayabiliyoruz.

Property seviyesindeki doğrulama çok işe yaramasına rağmen tüm veri doğrulama ihtiyaclarmızı karşılamamaktadır. Veri modeli sınıfımızdaki bir çok property değerinin birlikte kontrol edileceği sınıf seviyesinde kontrol gerekmektedir.

System.ComponentModel.DataAnnotation isin uzayında bulunan IValidateableObject arayüzü ile entity nesnelerinize sınıf seviyesinde doğrulama ekleyebilirsiniz.

Bu serinin daha önce ki yazılarnda ki gibi bir adres defteri uygulaması üzerinden sınıf seviyesinde doğrulama nasıl yapılır inceleyelim. Aşağıda ki örnekte kişi tanımlarını taşıyan Person entity sınıfımız yer almaktadır. Yapmak isteğimiz adres defterinde ki her erkeğin şirket bilgisinin her bayanında doğum günü bilgisinin boş bırakılmamasını sağlamak. Bu kuralı işletebilmek için aynı anda en az iki property değirini kontrol etmemiz gerekmektedir.

[cs]

public  partial  class  Person  : IValidatableObject 
 {
     public  int  PersonId  { get ; set ; }
     public  string  FullName  { get ; set ; }
     public  string  Company  { get ; set ; }
     public  DateTime ? BirthDay  { get ; set ; }
     public  bool  IsFemale  { get ; set ; }
     public  virtual  List  Adresses  { get ; set ; }
     public  IEnumerableValidate(ValidationContext validationContext)
     {
         if  (IsFemale  && BirthDay  ==  null )
             yield  return   new  
                 ValidationResult (
                     "Bayanların doğum günü boş bırakılamaz" ,
                     new [] { "BirthDay"  });
         if  (!IsFemale  && string .IsNullOrEmpty (Company ))
             yield  return  new  
                 ValidationResult (
                     "Erkeklerin şirket bilgisi boş bırakılamaz" , 
                     new [] { "Company"  });
     }
 }

Yukarıda ki kod bloğunda erkeğin adres bilgisi ve bayanın doğum günü bilgisi kontrol edilmektedir. IValidateableObject.Validate() metodu bir çok property değerini kontrol etmekte ve bir çok doğrulama hatası dönebilmektedir. Doğrulama hatalarının birinci parametresi hata mesajıdır. İkinci parametre ise hataya sebeb olan property isimleri dizisidir.

Otomatik Doğrulamaya Zorlamak

Entity Framework Code First IValidateableObject arayüzünü uygulayan entity neslerini kayıt ederken Validate metodunu otomatik olarak çağırmaktadır. Validate metodunu çağırmak için her hangi bir kod yazmanıza gerek yoktur. DbContext.SaveChanges() metodunu çağırdığınızda IValidateableObject.Validate EF Code First tarafından çağırılacak ve eğer hata oluşursa tüm transaction otomatik olarak geri alınacaktır.

[cs]

var  person = new  Person 
         {
             FullName  = "Ali Veli" ,
             IsFemale  = false ,
             BirthDay  = new  DateTime (2000, 1, 1)
         };
 var  dbContext = new  AdressBook ();
 dbContext.Persons .Add (person);
 // Ali Veli kişi kaydını IvalidateableObject.Validate 
// metodunda ki kurala uymadığı için 
// aşağıda ki satırda exception throw edecektir
 dbContext.SaveChanges ();

Hatanın throw edilmesi her zaman istenmeyen bir durumdur. Çünkü throw edilen hatanın arayüze yakın bir yerde catch edilmesi gerekmektedir. Bu sebebden entity nesnenizi kontrol etmek ve eğer veri iş kurallarına uygunsa veri tabanı işlemleri yapmak isteriz. SaveChanges() metodunu çağırmadan önce GetValidationErrors() metodu ile doğrulama hatalarını throw edilmeden elde ederiz.

[cs]

var  person = new  Person 
 {
     FullName  = "Ali Veli" ,
     IsFemale  = false ,
     BirthDay  = new  DateTime (2000, 1, 1)
 };
 var  dbContext = new  AdressBook ();
 dbContext.Persons .Add (person);
 // tüm hataları listele 
 var  errors = dbContext.GetValidationErrors ();
 if (errors.Count () > 0 )
     // hataları ekranda göster 
     errors.ToList ().ForEach (
         c=>c.ValidationErrors .ToList ().ForEach (
             l=>ModelState .AddModelError (l.PropertyName ,l.ErrorMessage )));  
 else 
     dbContext.SaveChanges ();

Arayüz Entegrasyonu

Bir önce ki yazıda DataAnnotation isim uzayının .NET 4 ile getiştirilen tüm platformlar tarafından desteklendiğine değinmiştik. MVC 3 ile entity sınıfını hazırladığımız adres defteri uygulamamızı geliştirelim ve sınıf seviyesinde doğrulama işleminin önyüzde nasıl bir etki oluşturduğunu görelim. Öncelikle bir PersonController sınıfına göz atalım.

[cs]

public  ActionResult  Create ()
 {
     return  View ();
 }
 [HttpPost ]
 public  ActionResult  Create (Person  person)
 {
     if  (ModelState .IsValid )
     {
         var  adressBook = new  AdressBook ();
         adressBook.Persons .Add (person);
         adressBook.SaveChanges ();
         return  RedirectToAction ("Index" );
     }
     return  View (person);
 }

Yukarıda standart MVC Controller sınıfı kodlaması bulunmaktadır. İlk Create motodu ile arayüz hazırlanmakta ve ikinci Create(person) metodu ile kullanıcının girdiği Person verisi veri tabanına kayıd edilmektedir. Create(person ) metodu öncelikle gelen verinin doğrulamaları geçmesini eklemektedir. Eğer doğrulamaları geçti ise veri tabanına kayıt etmektedir. Veri doğrulama için ekstra hiçbir kodlama bulunmamaktadır. Veri doğrulaması DataAnnotation isim uzayına duyarlı MVC tarafından otomatik olarak yapılmaktadır. Birde önyüzü inceleyelim. Aşağıdaki Create() metodunun döndüğü ön yüz görülmektedir. Burada da hiçbir fazladan kodlama yok.


  @model   RoC.CodeFirst.Models.Person
  @{
     ViewBag.Title = "ViewPage1" ;
  }
 <h2> ViewPage1</h2>
 <script  src="  @Url.Content("~/Scripts/jquery.validate.min.js")"  type="text/javascript"></script>
 <script  src="  @Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"  type="text/javascript"></script>
  @   using  (Html.BeginForm()) {
      @  Html.ValidationSummary(true )
     <fieldset>
         <legend> Person</legend>
         <div  class="editor-label">
              @  Html.LabelFor(model => model.FullName)
         </div>
         <div  class="editor-field">
              @  Html.EditorFor(model => model.FullName)
              @  Html.ValidationMessageFor(model => model.FullName)
         </div>
         <div  class="editor-label">
              @  Html.LabelFor(model => model.Company)
         </div>
         <div  class="editor-field">
              @  Html.EditorFor(model => model.Company)
              @  Html.ValidationMessageFor(model => model.Company)
         </div>
         <div  class="editor-label">
              @  Html.LabelFor(model => model.BirthDay)
         </div>
         <div  class="editor-field">
              @  Html.EditorFor(model => model.BirthDay)
              @  Html.ValidationMessageFor(model => model.BirthDay)
         </div>
         <p>
             <input  type="submit"  value="Create"  />
         </p>
     </fieldset>
 }
 <div>
      @  Html.ActionLink("Back to List" , "Index" )
 </div>

Yukarıda ki Controller ve View entegrasyonu ile fazladan hiçbir doğrulama mantığı kodlamadık. Doğrulama mantığını tamamen veri modeli üzerine taşıdık. İş mantığının yazıldığı Controller sınıfı sadece ModelState.IsValid() fonksiyonu ile doğrulama sonucunu kontrol etti.

Eğer IValidateableObject.Validate() metodundan bir doğrulama hatası dönerse ModelState.IsValid otomatik false değerini alacaktır. Controller’ın ModelState.IsValid false değerini alınca View geri döndürdüğünü görülmektedir. Arayüz tekrar gösterilirken IValidateable.Validation() metodunun sonucları ValidationMessageFor metodu ile arayüzde gösterilecektir Sonuç olarak hatalı bir işlemi kayıd etmeye çalıştığımızda aşağıdaki ekran görüntüsüne ulaşırız.

Veri açıklaması/DataAnnotation ve sınıf seviyesinden doğrulama/IValidateableObject ile Entity Framework Code First bir çok uygulamanın doğrulama ihtiyaçlarına cevap vermektedir.

Sonuç

Entity Framework Code First ile birlikte doğrulama ve iş kurallarının veri modeli üzerinde kalması sağlanmıştır. Hedeflenen kendini tekrar etmeyen kodlama (DRY – Dont Repeat Yourself) yapabilmektir. Tüm iş kodları bir yerde bulunsun, basit okunabilir kodlamalar olsun ve asla iş kodları farklı yerlerde tekrar edilmesin istenmektedir. En çok tekrar edilen kodlama ise doğrulama kodlamasıdır. Hem istemci hem sunucu tarafında aynı veri kontrolleri yapılmaktadır. EF Code First (ve MVC 3) ile sunulan veri açıklaması ve sınıf seviyesi doğrulama çözümleri ile kendini tekrar eden doğrulama kodlamalarına çözüm getirmektedir.

14 Şubat 2011 Pazartesi

Veri Açıklama - Data Annotations

“System.ComponentModel.DataAnnotations” isim uzayı NET 3.5 SP1 ile gelen Asp.Net Dynamic Data projesi ile hayatımıza girdi. Hayatımıza girdiği andan itibaren geliştirilen tüm platformlar bu isim uzayına duyarlı geliştirildi. Asp.Net Dynamic Data, Wcf Ria, Silverlight ve şimdide MVC 3 bu isim uzayı ile entegre calışmaktadır. Peki veri açıklması/Data Annotations isim uzayı nedir nasıl kullanılır?

Veri Modelini Açıklama

“System.ComponentModel.DataAnnotations” isim uzayı [Required] [Display] gibi semantik nitelikleri kullanarak UI üzerinde kullanılabilecek veri açıklamaları sunmaktadır. DataAnnotations isim uzayı ile birlikte sunulan çözüm veri modeli açıklamaları ile her uygulamada yapılan doğrulama, lokalizasyon gibi ön yüz işlemlerinin otomatik hale getirilmesidir.

Nasıl Çalışır ?

ASP.Net web form, Silverlight (wcf ria) ve Mvc platformları DataAnnotations isim uzayını desteklemektedir. Ben örneklemek için MVC 3 ve Entity Framework Code First uygulaması kullanacağım. Burada anlatılan Entity Framework Code First adress defteri uygulamamıza veri modeli açıklamaları ekleyerek nasıl çalıştığını inceliyelim.

Aşağıda adres defteri uygulaması için gerekli veri modeli bulunmaktadır.

[cs]

public  partial  class  Person 
 {
     public int  PersonId  { get ; set ; }
     public string  FullName  { get ; set ; }
     public string  Company  { get ; set ; }
     public DateTime  BirthDay  { get ; set ; }
     public virtual ListAdresses{ get; set;}
 }

Yapmak istediğimiz kişi verisi girilirken Name değeri boş bırakılmasın veya yirmi karakterden uzun değerler girilmesin. MVC uygulamamızda bu istediğimizi aşağıdaki şekilde yapabiliriz.

[cs]

[HttpPost ]
 public  ActionResult  Create (Person  person)
 {
     if (string .IsNullOrEmpty (person.FullName ) )
         ModelState.AddModelError("FullName","Ad değeri boş bırakılamaz" );
     if  (person.FullName  != null  && person.FullName .Length  > 20)
         ModelState.AddModelError("FullName","20 karakterden uzun ad girilemez");
     if  (ModelState .IsValid )
     {
         var  adressBook = new  AdressBook ();
         adressBook.Persons .Add (person);
         adressBook.SaveChanges ();
         return  RedirectToAction ("Index" );
     }
     return  View (person);
 }

Bu kod bloğunda girilen verinin veri yapımıza uygunluğunu test ettik. Fakat sadece bir tek property için bile bir çok if yazmak zorundayız. Karmaşık veri yapalarındaki doğrulama işlemi ciddi bir sorun oluşturacaktır. Bir diğer doğrulama seceneğimizde veri modeli açıklaması oluşturmaktır.

[cs]

[MetadataType (typeof (PersonMetadata ))]
 public  partial  class  Person 
 {
 public  class  PersonMetadata 
 {
     [Key , 
     Display (AutoGenerateField  = false )]
     public  int  PersonId  { get ; set ; }
     [Required (ErrorMessage  = "Ad değeri boş bırakılamaz" ),
     Display (Name  = "Ad" ),
     StringLength (20,ErrorMessage  = "20 karakterden uzun ad girilemez" )]
     public  string  FullName  { get ; set ; }
     [Display (Name  = "Şirket" ),
     StringLength(20,ErrorMessage="20 karakterden uzun şirket ismi girilemez")]
     public  string  Company  { get ; set ; }
     [Display (Name  = "Doğum Tarihi" ), 
     Required (ErrorMessage  = "Doğum tarihi boş bırakılamaz" ),
     DataType (DataType . Date  )]
     public  DateTime  BirthDay  { get ; set ; }
 }
 }

Kişileri saklayan bu sınıfa veri modeli açıklamaları ekledik. Doğrudan sınıflarda bulunan public property’lerede açıklama niteliği ekleyebilirsiniz. Ben daha okunabilir olması için Metadata sınıfı ile birlikte veri modeli açıklama niteliklerini kullandım.Yukardaki kod bloğunda Name özelliğinin ekranda “Ad” olarak görüleceğini değerinin boş bırakılamıyacağını ve yirmi karakterden uzun veri girilemeyeceğiniz söylemektedir. Veri doğrulamalasını belirtilen açıklamalara göre MVC bizim için yapacağından Create metodumuz aşağıdaki şekilde olacaktır.

[cs]

[HttpPost ]
 public  ActionResult  Create (Person  person)
 {
     if  (ModelState .IsValid )
     {
         var  adressBook = new  AdressBook ();
         adressBook.Persons .Add (person);
         adressBook.SaveChanges ();
         return  RedirectToAction ("Index" );
     }
     return  View (person);
 }

Yukarıda ki kod bloğunda hiç bir veri doğrulaması yapılmıyor. Çünkü ModelBinder sınıfı verdiğimiz veri açıklama nitelikleri ile veri doğrulamasını yapmakta ve sonuçları ModelState verisine yüklemektedir. Sunucu tarafında yapılan kontroller gibi MVC istemci taraftada veriyi doğrulamaktadır. Şimdi MVC projemizde kişi ekleme sayfası oluşturalım.

  @   using  (Html.BeginForm()) {
  @  Html.ValidationSummary(true )
 <fieldset>
     <legend> Person</legend>
     <div  class="editor-label">
          @  Html.LabelFor(model => model.FullName)
     </div>
     <div  class="editor-field">
          @  Html.EditorFor(model => model.FullName)
          @  Html.ValidationMessageFor(model => model.FullName)
     </div>
     <div  class="editor-label">
          @  Html.LabelFor(model => model.Company)
     </div>
     <div  class="editor-field">
          @  Html.EditorFor(model => model.Company)
          @  Html.ValidationMessageFor(model => model.Company)
     </div>
     <div  class="editor-label">
          @  Html.LabelFor(model => model.BirthDay)
     </div>
     <div  class="editor-field">
          @  Html.EditorFor(model => model.BirthDay)
          @  Html.ValidationMessageFor(model => model.BirthDay)
     </div>
     <p>
         <input  type="submit"  value="Create"  />
     </p>
 </fieldset>
 }

Yukarıda ki kodda standart bir MVC Razor sayfası görülmektedir ve tüm kodu MVC oluşturdu. LabelFor, EditorFor ve ValidationFor metotları bizim veri modeli açıklalarımızı dikkate alacak ve istediğimiz önyüzü oluşturacaktır.

Görüldüğü üzere MVC veri modeli açıklamaları dikkate aldı ve gerekli şekilde önyüzü ve istemci tarafı doğrulama metotlarını oluşturdu. İstemci tarafında ki javascript fonksiyonlarının ve sunucu tarafında veri kontrollerinin otomatik olarak oluştuğuna özellikle dikkat ediniz. Biz ekstra hiç bir kodlamada bulunmadık.

Daha Fazlası

Sistem içinde bir çok genişleme noktası mevcuttur. Kendi doğrulama/validator sınıflarınızı yazabilirsiniz. Data tiplerine özel önyüz kontrolleri oluşturablirsiniz. MVC örneğimizde veriyi önyüz ve sunucu kodları arasında taşıyan binder sınıfı mevcuttur. Kendinize ait DataAnnotations niteliklerinize özel ModelBinder örneği oluşturabilirsiniz.

Sonuç

Veri modelini doğrulama yaklaşımı aslında bir ilgileri ayırma/(Speration of concers) yöntemidir. En bilinen ilgileri ayırma yöntemi Html ve css kodlarının ayrılmasıdır. DataAnnotations ile veri modeli ve veri modelini ilgilendiren veri doğrulama, görüntüleme gibi özellikler veri modeli sınıfı üzerinde turuluyor. Veri ilgilendirmeyen önyüz kodları ayrı bir yerde tutuluyor. Sunucu tarafında ki iş mantığı kodları ayrı bir alanda tutuluyor.

Veri modeli açıklamasının tüm platformlarda aynı şekilde kodlanması ve kolay anlaşılabilirlik ile birlikte sistemin bir çok genişleme noktası bulunması sistemi kullanılabilir olmasını sağlamaktadır. Ciddi bir kodlama yüküne çözüm getirmesi tüm platformlarda desteklenmesi DataAnnotations isim uzayının hayatımızda çok fazla yer alacağını göstermektedir.

7 Şubat 2011 Pazartesi

Entity Framework Code First- Veri Tabanı Eşleştirme

Code First kod merkezli geliştirme ortamı sağlamaktadır. Her hangi bir tool veya XML eşleştirme dosyasına gerek kalmaksınız geliştirme yapabilirsiniz. Veri modeli nesnelerinizi her hangi bir sınıftan türetmeden basit POCO sınıfları olarak yazabilirsiniz. Konfigurasyon yerine isimlendirme özelliklerini kullanarak POCO nesnelerinizi veri tabanı nesneleriniz ile eşleştirebilirsiniz. Serinin ilk yazısında EF Code First bu özelliklerin nasıl kullanıldığını inceledik.

Varsayılan entity-veri tabanı eşleşmesi nasıl değiştirilir? EF Code First ile varsayılan olarak gelen entity ve veri tabanı nesneleri arasında ki eşleştirme nasıl değiştirilir?

Seri boyunca takip ettiğimiz adres defteri uygulamamıza devam edip yukarıda ki sorularmızın cevabını arıyalım. Standart .NET tipleri olan POCO nesnelerimiz hatırlayalım.

[cs]

public  partial  class  Person  
 {
     public  int  PersonId  { get ; set ; }
     public  string  FullName  { get ; set ; }
     public  string  Company  { get ; set ; }
     public  DateTime ? BirthDay  { get ; set ; }
     public  bool  IsFemale  { get ; set ; }
     public  virtual  List  Adresses  { get ; set ; }
 }
 public  partial  class  Adress 
 {
     public  int  AdressId  { get ; set ; }
     public  string  AdressType  { get ; set ; }
     public  string  FullAdress  { get ; set ; }
     public  int  PersonId  { get ; set ; }
     public  virtual  Person  Person  { get ; set ; }
 }

Bu POCO nesnelerimiz ile veri tabanı eşleşmesini sağlayan DbContext sınıfmızıda hatırlayalım. AddressBook DbContext sınıfından türetilmişti ve Persons ve Adresses property’lerine sahipti.

[cs]

public  class  AdressBook  : DbContext 
 {
     public  DbSet  Persons  { get ; set ; }
     public  DbSet  Adresses  { get ; set ; }
 }


Veri tabanı eşleşmeleri için Code First yaklaşımını kullandık. Bunun manası AdressBook sınıfında yer alan Persons ve Addresses bizim veri tabanımızda aynı isimde ki tablolarla eşleşecekdir. Her bir Persons ve Adresses sınıflarıdandaki her bir property’de Persons ve Adresses tablolarında ki kolonlar ile eşleşecekdir.

Ekstra her hangibir konfigurasyon veya kodlama gerekmeden veri tabanı tablolarımız oluşmaktadır. Veri tabanımızda oluşan veri yapımız aşağıdaki gibidir.

Örnek uygulamada SQL CE kullandım. Yukarıdaki veri yapısında görüldüğü gibi Entity Framework Code First varsayılan olarak entity nesnemize birebir benzeyen bir tablo yapısı oluştudu.

Veri Tabanı Eşleştirme

Entity Framework Code First ile gelen veri tabanı eşleştirmesini DbContext.OnModelCreating metodunu override ederek değiştirebilirsiniz.

[cs]

public  class  AdressBook  : DbContext 
 {
     public  DbSet  Persons  { get ; set ; }
     public  DbSet  Adresses  { get ; set ; }
     protected  override  void  OnModelCreating ( ModelBuilder  modelBuilder)
     {
         // entity - tablo eşleşmesini değiştir 
     }
 }

OnModelCreating(modelBuilder) metodu veri tabanınız ilk defa oluşturulurken çağırılacakdır. OnModelCreating metotu ile veri tabanı nesnelerinizin nasıl oluşturulması gerektiğini söyleyebilirsiniz. Adım adım olabilecek senaryoları inceleyelim.

Tablo İsmini Değiştirme

Nasıl Persons tablosunun ismini aşağıdaki gibi tblPersons yaparız?

Persons tablosunu tlbPersons yapmak için gerekli olan OnModelCreating metodu içinde Entity sınıfmıza ToTable(name) metodu ile özelleştirilmiş bir tablo adı vermektir.

[cs]

public  class  AdressBook  : DbContext 
 {
     public  DbSet  Persons  { get ; set ; }
     public  DbSet  Adresses  { get ; set ; }
     protected  override  void  OnModelCreating ( ModelBuilder  modelBuilder)
     {
         modelBuilder.Entity ().ToTable ("tblPersons" );
     }
 }

Yukarıda ki kod bloğu tablo ismi vermek için kullanılan sezgisel/fluent kuralları devre dışı bırakır ve OnModelCreating metotu ile belirtilen yeni kuralları uygulayarak tablo isimlendirmesini yapılır.

Kolon İsimlerini Değiştirme

Nasıl aşağıdakii gibi tüm kolonlara "col" ön eki ekleyebiliriz?

Tüm kolonlarımıza “col” ön eki eklemek için OnModelCreating metodu içinde entity nesnemiz için yeni bir eşleştirme/mapping eklememiz gerekmektedir.

[cs]

public  class  AdressBook  : DbContext 
 {
 public  DbSet  Persons  { get ; set ; }
 public  DbSet  Adresses  { get ; set ; }
 protected  override  void  OnModelCreating ( ModelBuilder  modelBuilder)
 {
     modelBuilder.Entity ().ToTable ("tblPersons" );
     modelBuilder.Entity ().Property (c => c.PersonId ).HasColumnName ("colId" );
     modelBuilder.Entity ().Property (c => c.FullName ).HasColumnName ("colFullName" );
     modelBuilder.Entity ().Property (c => c.Company ).HasColumnName ("colCompany" );
     modelBuilder.Entity ().Property (c => c.BirthDay ).HasColumnName ("colBirthDay" );
     modelBuilder.Entity ().Property (c => c.IsFemale ).HasColumnName ("colIsFemale" );
     modelBuilder.Entity ().ToTable ("tblAdresses" );
     modelBuilder.Entity ().Property (c => c.AdressId ).HasColumnName ("colId" );
     modelBuilder.Entity ().Property (c => c.FullAdress ).HasColumnName ("colFullAdress" );
     modelBuilder.Entity ().Property (c => c.PersonId ).HasColumnName ("colRefId" );
 }
 }

Yukarıdaki kod bloğunda mevcut kurallar yerine OnModelCreating(modelBuilder) içindeki kuralların işletilmesi sağlanmıştır . Böylelikle varsayılan eşleştirme kuralları override edilmektedir. Code First ile Ansi SQL kolon yapısında desteklenen tüm özellikleri yukarıdaki gibi kullanabilirsiniz. Daha ayrıntılı özellikleri buradan inceleyebilirsiniz.

Bir Çok Entity Bir Table

En sık karşılaşılan seneryolardan biriside uygulama içinde ki entity nesnemiz ile veri tabanında ki tablo yapılarımızın bire bir örtüşmemesidir. Normalizasyon kuralları gereği aynı tabloda yer alması gereken veriler, nesne güdümlü yazılım mimarisi içinde farklı sınıflarda yer alması gerekebilmektedir. Aşağıdaki veri yapısında Person tablosuna göze atalım.

Burada IsWorkWith, IsDrunkWith, MustTakeGift bilgisi gibi alanlar Person tablosunda yer alması istiyoruz. Çünkü bu veriler Person verisine niteleyen verilerdir. Fakat bu bilgi özelleşmiş bir bilgi olduğu için farklı bir entity içinde taşınması daha doğrudur.

[cs]

public  partial   class  Familiarity 
 {
     public  bool  IsWorkWith  { get ; set ; }
     public  bool  IsDrunkWith  { get ; set ; }
     public  bool  MustTakeGift  { get ; set ; }
 }
 public  partial  class  Person  
 {
     public  int  PersonId  { get ; set ; }
     public  string  FullName  { get ; set ; }
     public  string  Company  { get ; set ; }
     public  DateTime ? BirthDay  { get ; set ; }
     public  bool  IsFemale  { get ; set ; }
     public  Familiarity  Familiarity  { get ; set ; }
     public  virtual  List  Adresses  { get ; set ; }
 }

Yukarıda ki entity yapısı ve veri yapısını birbirine uyumlu hale getirmemiz gerekmektedir. İki yapıyı uyumlu hale getirmek için OnModelCreating() metodu içinde Person entity sınıfımızda yer alan Contact property’sinin bir başka entity sınıfı olmadığını Contact sınıfının bir ComplexType olduğunu belirtmemiz gerekmektedir.

[cs]

protected  override  void  OnModelCreating ( ModelBuilder  modelBuilder)
 {
     modelBuilder.ComplexType ();
     modelBuilder.ComplexType ()
  .Property (c => c.MustTakeGift ).HasColumnName ("colMustTakeGift" );
     modelBuilder.ComplexType ()
  .Property (c => c.IsDrunkWith ).HasColumnName ("colIsDrunkWith" );
     modelBuilder.ComplexType ()
  .Property (c => c.IsWorkWith ).HasColumnName ("colIsWorkWith" );
 }

Böylelikle Contact sınıfımız bir entity sınıfı olarak değilde yer aldığı entity nesnesinin alt sınıfı olarak işlem görecektir.

Sonuç

Entity Framework Code First veri erişimini kod merkezli güzel bir yöntem ile çözmektedir. Veri erişimi mekanizmasında yer alan değiştirme noktalarından müdahale ederek veri erişim yapısını kendinize göre özelleştirebilirsiniz. Bu yazıda OnModelCreating(modelBuilder) değiştirme noktasına müdahale ederek veri tabanı yapısını değiştilmesini inceledik. Tüm işlemlerin kodlama ile basitce yapılabilmesi fazladan konfigürasyon gereketirmemesi, doğrulama özelliklerinin önyüz uygulamalar tarafından desteklenemesi gibi özellikler Entity Framework ile uygulama yazmayı kolaylaştırmaktadır.

30 Ocak 2011 Pazar

Entity Framework Code First

EF 4 ile birlikte birçok yeni özellik gelmiştir.

  • POCO: Entity sınıflarının bir üst sınıftan türetilmek zorunda değiller
  • Lazy Load: Entity örneği alt Entity özelliklerini ilk erişim anında yükleyebilirler
  • Self-Tracking: Entity örnekleri değişim durumlarını kendileri takip ediyorlar. Böylece durum bağımsız (stateless) web çağrılarında kullanılabilirler.
  • Test: EF 4 nesneleri interface’ler aracılığı ile daha kolay test edilebilir durumdalar
  • Linq: Artık EF 4 tüm linq metotlarını destekliyor

Visual Studio içinde EF 4 için birçok model tasarımcısı ve araç eklenmiştir. EF tasarımcıları “Model First” olarak adlandırılan yöntem için kullanılırlar. Model First önce veri tabanında veya EF 4 model tasarımcısında veri modelin çıkartılması daha sonra çıkartılan modele ait kodların kod şablonları (T4) ile oluşturulması yöntemidir.

Code First

Entity Framework 4 ile tasarımcı temelli Model First veya Database First yaklaşımına ek olarak “Code First” olarak adlandırılan kod merkezli yeni bir yaklaşım ortaya konmuştur. Code First ile her hangi bir tasarımcıya, xml eşleştirme dosyasına veya bir Entity Base Class’a ihtiyacınız yoktur. Model ile Entity sınıflarınız arasında ki eşleştirmeyi isimlendirme kuralları ile yapabilirsiniz ve bu eşleştirmeyi tamamen kodlama ile özelleştirebilirsiniz.

Uygulama Geliştirme

Öncelikle bir uygulama oluşturalım. Daha sonra Nuget ile projemize EFCodeFirst ekleyelim.

Bir veri tabanı ile başlamamıza gerek yok. Bunun yerine veri yapımızı (Domain Model ) taşıyan standart NET sınıfları yazarak işe başlayacağız.

Model Sınıflarını Oluşturma

Küçük bir adres defteri uygulaması yazıyoruz ve bizim veri yapımız gerçekten küçük bir yapıdır. Veri modelimizde sadece Kişiler ve Kişi Adresleri var. Bu basit iki sınıfı oluşturalım.

[cs]

public  class  Person 
 {
     public  int  PersonId  { get ; set ; }
     public  string  FullName  { get ; set ; }
     public  string  Company  { get ; set ; }
     public  DateTime  BirthDay  { get ; set ; }
     public  virtual  List  Adresses  { get ; set ; }
 }
 public  class  Adress 
 {
     public  int  AdressId  { get ; set ; }
     public  string  AdressType  { get ; set ; }
     public  string  FullAdress  { get ; set ; }
     public  int  PersonId  { get ; set ; }
     public  virtual  Person  Person  { get ; set ; }
 }

Yukarıda ki Person ve Adress model sınıfları POCO sınıflardır. Her hangi bir üst sınıftan veya interface’ den türetmek zorunda değiliz. Her hangi bir veri tabanı bağlantısı da belirtmek zorunda değiliz. Veri modeline uyan herhangi bir veri tabanına bağlanabilirler.

Context Sınıfını Oluşturma

POCO sınıflarımızı oluşturduk. Veri tabanımız ile POCO sınıflarımızı eşleştirme işlemini yapacak Context sınıfımızı yazabiliriz.

[cs]

public  class  AdressBook  : DbContext 
 {
     public  DbSet  Persons  { get ; set ; }
     public  DbSet  Adresses  { get ; set ; }
 }

AdressBook eşleştirme sınıfının DbContext sınıfından türetilmelidir. AddressBook sınıfı iki public özelliği Persons ve Adresses özellikleride DbSet sınıfının uygulamalarıdır. DbContext ve DbSet sınıfları EF Code First kütüphanesinde yer almaktadır.

İhtiyacımız olan tüm kodlamayı yaptık. Veri tabanı ile eşleştirme için başka bir işleme ihtiyacımız yok. Her hangi bir veri tabanı şema eşleştirme dosyası veya eşleştirme aracı veya her hangi bir tasarımcı kullanmayacağız. Veri tabanı üzerinde gerekli tüm işlemleri bu üç sınıf yapacak.

Kural Tabanlı Eşleştirme

Her hangi bir eşleştirme verisi olmadan bu üç sınıf nasıl veri tabanında ki nesneler ile eşleşecekler?

EF Code First “yapılandırma yerine kurallar” yaklaşımı ile eşleştirme yapmaktadır. Tabi ki bu varsayılan kuralları değiştirebilirsiniz.

Nedir EF Code First kuralları?

EF Code First “sadece çalış” mantığı ile mümkün olduğu adar az veri ile çalışabilecek şekilde tasarlanmıştır. Mesela Adres defteri örneğimizde Persons ve Adress olarak iki tablo oluşturacaktır. Bu tabloların kolonları da sınıfların property’lerinden oluşacaktır. Primary key olarak da tablo adı ile aynı sonu Id ile biten property kullanılacaktır. Foreing Key olarakda sınıf içinde ki diğer nesne referansları kullanılacaktır.

Veri Tabanı

Kodlarımız yazdık. Peki ya veri tabanı? Kodları yazarken hiçbir veri tabanına ihtiyacımız yok. Fakat çalışma anında veri tabanı gerekmektedir. İki şekilde veri tabanı oluşturabiliriz. İlki kendimiz gider uygun veri tabanını oluştururuz. İkincisi EF Code First bizim yerimize otomatik olarak tüm veri tabanını oluşturur. Adres defteri uygulamamız için ikinci yolu tercih ediyorum. EF Code First varsayılan olarak DbContext sınıfı ile aynı isimde bir bağlantı adı arayacaktır. Bizim DbConext sınıfımızın adı AdressBook olduğu için aynı isimde bir bağlantı adı kurmalıyız.

   <connectionStrings>
     <add name= "AdressBook "
          connectionString= "Data Source=|DataDirectory|AdressBookDb.sdf "
          providerName= "System.Data.SqlServerCe.4.0 " />
   </connectionStrings>

Bu örnekte ben veri tabanı olarak SQL CE veri tabanında faydalandım.

Model Nasıl Kullanılır?

Tüm ihtiyacımızı karşılayan üç sınıfı yazdık. Linq ile istediğimiz tüm sorguları çalıştırabiliriz.

Tüm kişi listesini getiren bir linq expression yazalım:

[cs]

var  adressBook = new  AdressBook ();
 var  list = from  p in  adressBook.Persons 
                 orderby  p.FullName 
                 select  p;

İki tablo arasında ki ilişkiden de faydalanabiliriz:

[cs]

var  adressBook = new  AdressBook ();
 var  list = from  p in  adressBook.Persons 
             where  p.Adresses .Count  > 0
             orderby  p.FullName 
             select  p;

Yukarıda ki kodda sadece adres verisi olan kişiler listelenmiştir. Primary Key özelliğinden de faydalanabiliriz:

[cs]

var  adressBook = new  AdressBook ();
 var  person = adressBook.Persons .Single (c => c.PersonId  == id);

CRUD işlemleri olmadan olmaz. Yeni bir kişi eklemek için yeni bir Person nesnesi oluşturup gerekli property’leri doldurmalıyız. Daha sonra DbContext üzerinde ki ilgili DbSet’e yeni kişi nesnemizi eklemeliyiz. Son olarak da yaptığımız değişikliği veri tabanına yansıtmamız gerekmektedir.

[cs]

var  person = new  Person 
 {
     FullName  = "Ali Veli" , 
     Company  = "ana okulu" , 
     BirthDay  = DateTime .Today 
 };
 var  adressBook = new  AdressBook ();
 adressBook.Persons .Add (person);
 adressBook.SaveChanges ();

Kayıt güncellemek için önce güncelleyeceğimiz kaydı buluyoruz. Sonra gerekli değişikliği yapıyoruz. En son yapılan değişikliği veri tabanına yansıtıyoruz.

[cs]

var  adressBook = new  AdressBook ();
 var  person = adressBook.Persons .Find (id);
 person.Company  = "FOG" ;
 person.BirthDay  = DateTime .Today .AddYears (20);
 person.FullName  = "Deli Veli" ;
 adressBook.SaveChanges ();

Sonuç

ORM araçları iş uygulaması geliştirmenin en önemli parçasıdır. EF 4 Code First veri ile kod temelli çalışılabilecek bir yol sunmaktadır. EF Code First çok güçlü bir veri işleme yöntemidir. Bu yazıda EF Code First varsayılan kuralları ile ele alınmıştır. Burada değinilmemiş birçok özellik var. System.ComponentModel.DataAnnotations isim uzayında var olan nitelikler ile entity nesnelerinize doğrulama özellikleri ekleyebiliriz. IValidatableObject arayüzü ile sınıf seviyesinde doğrulama metotları ekleyebiliriz. DbContext sınıfının OnModelCreating metodu ile entity ve veri nesnelerini eşleştirmelerini özelleştirebilirsiniz. (Böylece mevcut veri tabanları ile EF Code First yaklaşımını kullanabilirsiniz. ) DatabaseIntialize sınıfları ile veri tabanı oluşunca eklenecek varsayılan kayıtları girebilirsiniz vs.

EF Code First tümleşik bir veri işleme yöntemi sunmaktadır. EF Code First ile kod geliştiriciler daha aktif kullanılabilen bir veri işleme ortamına sahip oldular.


28 Ocak 2011 Cuma

SQL CE

SQL CE kolay veritabanı depolaması sağlayan ücretsiz, gömülü, veritabanı motorudur. SQL CE kullanmak için her hangi bir veri tabanı kurulumu gerektirmez. Sadece gerekli dll’ler bin dizinine kopyalanır ve uygulamanız veri tabanını motorunu kullanır. SQL CE çalışma döngüsü uygulamanın veri tabanına ilk erişim ile başlar ve uygulama kapatılınca uygulama ile birlikte kapatılır. Uygulama ile birlikte kapanan SQL CE veri tabanı otomatik olarak bellekten temizlenir.

SQL CE veri tabanı verileri dosya içinde saklamaktadır. SQL CE veri tabanını sadece dosyayı kopyala-yapıştır yaparak taşıyabilirsiniz. Dosyada verileri saklaması rağmen ASP.NET gibi çok kanallı kullanıma da uygundur. Her hangi bir çökme veya kilitlenme riski yoktur. Tüm uygulamalarda rahatlıkla kullanılabilinir.

Nasıl Kullanılır?

SQL CE kullanmak için geliştiriciler ekstra hiçbir bilgiye ihtiyaçları yoktur. Mevcut tüm .NET veri erişim metotları ile SQL CE erişimi sağlanabilmektedir. Kod geliştiriciler için en uygun veri tabanıdır. SQL CE veri tabanını kullanmak için önce Visual Studio SP 1 (beta)yı yüklemeniz gerekmektedir. Daha sonra SQL CE Tools For Visual Studio yüklemeniz gerekmektedir.

Örnek Uygulama

Örnek bir Asp.Net adres defteri uygulaması yapalım. Öncelikle boş bir Asp.Net uygulaması açalım.

Uygulamaya SQL CE Veri Tabanı Ekleme

Açtığımız boş uygulamanın App_Data dizinine SQL CE veri tabanını aşağıda ki gibi ekleyelim.

Veri tabanını ekledikten sonra projenin görüntüsü aşağıdaki gibi olacaktır.

Proje referanslarına System.Data.SqlServerCe kütüphanesi ve App_Data dizinine Sql Ce veri dosyasının eklendiğini görüyoruz. Veri tabanı ile çalışmak için gereken kütüphaneyi ve verilerin saklanacağı veri dosyasını projemize eklemiş olduk.

Eklediğimiz Sql Ce dosyasına çift tık ile içini açalım. Şimdi yeni bir tablo ekleyebiliriz.

Eklediğimiz tablolarımızın veri modelini oluşturalım ve daha sonra bu tablolara veri girelim. Örnek adres defteri uygulamamız için People tablosu oluşturdum ve tabloya deneme kayıtlarını girdim.

SQL CE Veri Yapısını Alma

Tüm SQL CE işlemlerimizi tamamladık. Artık uygulama tarafında kullanabiliriz. SQL CE veri tabanına Entity Framework ile bağlanmak için Entity Data Model ekleyelim.

Açılan ekranda “Generate from Database” seçeneğini seçip ilerleyelim. Sonra ki ekranda eklediğimiz AdressBook.sdf dosyasının seçili geldiğini göreceksiniz.

Sırada ki ekranda ekran da SQL CE veri tabanımızda ki tabloların geldiğini görüyoruz. Burada iki tabloyu da seçip sihirbazı tamamlayalım. Oluşan Entity Modeli aşağıda ki gibi olacaktır.

SQL CE ile Çalışmak

Veri tabanında oluşturduğumuz modeli uygulamamıza taşımış olduk. Şimdi veriyi ekranda göstermek gerekiyor. Uygulamanın ana sayfasına bir tane grid kontrolü ekleyelim. Grid kontrolünün DataSourceId özelliğinden “<New Data Source>” seçeneğini seçelim. Karşınıza aşağıda ki veri kaynağı ayarlama ekranı gelecektir.

Ben örnek olarak Entity Framework alt yapısını kullanmak istediğim için Entity seçeneğini işaretledim. Sonra ki sayfada projede ki veri kaynaklarının listesi gelmektedir. Oluşturduğumuz entity data modeli listeden seçelim.

Sonra ki sayfada gride hangi tabloyu bağlamak istediğimizi sormaktadır. People tablosunu seçelim.

Artık uygulamayı çalıştırıp önyüzden kayıtları değiştirebiliriz.

Sonuç

SQL CE kolayca veritabanı depolamasını etkinleştirmek için kullanabileceğiniz ücretsiz, gömülü, veritabanı motoru sağlar.

SQL CE ile yapılan çalışmaları isterseniz kolayca uygulamanın herhangi bir kodu değiştirmek zorunda kalmadan SQL Server veya SQL Azure üzerine taşıyabilirsiniz. Yapmanız gereken sadece uygulama yapılandırma dosyasında <connectionString> değerini değiştirmektir. Böylece uygulama geliştirmek için gerekli küçük gömülü veritabanı çözümü esnekliği sağlanmış olmaktadır.

22 Ocak 2011 Cumartesi

Genişletilebilir Uygulamada Hata Yakalama ve İzleme

Problem

Genişletilebilir (Composite) uygulamalar bir birine bağlı parçalardan oluşmaktadır. Eğer bir parça oluşturulurken parçanın ihtiyaç duyduğu eksik bir bağımlılık var ise uygulama hata alacaktır.

Yukarıda A örneğinin oluşturulması sırasında önce A'nın bağımlılıkları oluşturuluyor. C parçası [Import] edilmiş fakat uygulama içinde C parçasını [Export] eden her hangi bir kontrat bulunmamaktadır. Çalışma anında A parçasını üretmeye çalıştığımızda C'nin [Export] kontratı olmadığından dolayı hata üretilecektir.

Hatalar çalışma anında ortaya çıkacağı için oluşan hataları raporlamalı ve temel sorunu bulmalısınız.

Taşıyıcıyı Yazdırma

Örneğimize geri dönersek A örneği B ile bağımlı ve B ise C ile bağımlı ama uygulamada C örneğini [Export] eden bir kontrat bulunmamaktadır. Yani aşağıd ki şekilde bir kodlama olduğunu varsayalım.

[cs]

[Export ]
 public  class  ClassA 
 {
     [Import ] private  ClassB  _classB ;
 }
 [Export ]
 public  class  ClassB 
 {
     [Import ]
     private  ClassC  _classC ;
 }
 public  class  ClassC 
 {
 }

Taşıcının içeriğini bir stream örneğine yazdırmak için mef.codeplex.com adresinde ki Microsoft.ComponentModel.Composition.Diagnostics.dll assembly dosyasına ihtiyacınız var. Burada yer alan CompositionInfo ve CompositionInfoTextFormatter sınıfları ile taşıyıcının tüm içeriğini bir stream örneğine aktarabilirsiniz.

[cs]

var  compositionInfo = new  CompositionInfo (_aggregateCatalog , _compositionContainer );
 CompositionInfoTextFormatter .Write (compositionInfo, Console .Out );

Yukarıda bir katalog üzerinde ki tüm tanımlamaları çalışma anında Console yazan kod blogu bulunmaktadır. Örneğimie ait çıktı ise aşağıdaki gibi oluşacaktır.

Çıktıyı incelersek katalog üzerinde iki tane [Part] bulunduğunu görüyoruz. A için [Export] tanımının ve [Import] B tanımının olduğunu fakat [Import] B tanımının geçersiz olduğunu görüyoruz. Part B için [Primary Rejection] ifadesini görüyoruz bu ifade bize temel sorunun B parçasında olduğunu gösteriyor. B parçasında da [Import] C tanımını görüyoruz. Hatanın detayını okuduğumuzda [Import] C için geçerli bir [Export] kontratının bulunmadığını görmekteyiz. Böylelikle sorunu tesbilt etmiş olduk. C örneğine ait bir [Export] kontratına ihtiyacımız var.

Sonuç

Böylelikle genişletilebilir (Composite) uygulama yazma konulu serinin sonuna gelmiş olduk. Katalog ve taşıyıcı kavramlarını gördük. [Export] kontratlarımıza nasıl Metadata etiketleri verileceğini ve bu etiketleri kullanarak ihtiyacımız olan parçaları nasıl bulabileceğimizi gördük. Parçaların hayat döngüsünü yönetmeyi inceledik. Son olarakda genişletilebilir uygulamada hata yakalama özelliklerini inceledik.

Genişletilebilir uygulama alt yapısı (System.ComponentModel.Composition) bilinen IoC alt yapılarında (Unity, Structure Map,vb ) farklıdır. IoC alt yapıları derleme anında bağımlılıkları bilmektedir. .NET Composition ile bağımlılıklar çalışma anında keşif edilmektedir. Composition bilinmeyen bağımlılıkları yönetirken IoC ile bilinen bağımlılıklar yönetilmektedir.

Genişletilebilir uygulamalar uzun geliştirme süreci olan projelerde daha çok tercih edilmektedir. Bir sonraki yazı dizisinde genişletileblir MVC 3 uygulaması ve genişletilebilir WCF uygulaması üzerinde durucağız.

21 Ocak 2011 Cuma

Derinlemesine Genişletilebilir Uygulama Yazma

Genişletilebilir uygulamaların asıl gücü tak-çalıştır desteğidir. Bir önce ki yazıda ele aldığımız katalog ve taşıyıcı kavramlarının çalışma anında tak-çalıştır modüllerini nasıl takip ettiğini ve kullandığını inceliyoruz.

Çalışma Zamanı Desteği

Çalışma anında uygulamazın tak-çalıştır modülleri nasıl yönettiğini görelim. Öncelikle katalog ve taşıyıcıyı oluşturalım.

[cs]

var  assemblyCatalog = new  AssemblyCatalog (Assembly .GetExecutingAssembly ());
 var  directoryCatalog = new  DirectoryCatalog (Path .Combine ("../.." , "Plugins" ));
 var  aggregateCatalog = new  AggregateCatalog (assemblyCatalog, directoryCatalog);
 _compositionContainer  = new  CompositionContainer (aggregateCatalog);

DirectoryCatalog örneğinin Plugins dizinini dinlediğini görüyorsunuz. DirectoryCatalog siz yenilemedikçe dizin içinde ki modülleri tekrar yüklemeyecektir. Plugins dizinini dinlemeli ve oluşan değişikliklerde DirectoryCatalog örneğinizi yenilemelisiniz.

[cs]

// Plugins dizinini takip et 
 var  watcher = new  FileSystemWatcher (directoryCatalog.FullPath )
 {
     Filter  = "*.dll" ,
     EnableRaisingEvents  = true 
 };
 // dizindeğişince katalogu yenile
 watcher.Changed  +=  (s, e) => directoryCatalog.Refresh ();

Yukarıda ki kodlama ile Plugins dizininde ki dosyalar değiştiğinde katalogumuzu yeniliyoruz. Katalog ilgili dizinde ki tüm assembly dosyalarına bakarak [Export] tanımlarını almaktadır. Basit bir uygulama ile çalışma anında katalogun nasıl değiştiğini görelim. Elimizde karakter dizisi dönen bir ISeriesFactory interface var.

[cs]

public  interface  ISeriesFactory 
 {
     ///  
     ///   seri oluştur 
     ///  
     /// dizide olması gereken eleman sayısı       /// oluşturdupu seriyi dönderir       List  Create (int  numberOfElement);  }

Bu interface ait uygulama yazmak istiyoruz. Fakat interface uygulama sınıfları çalışma anında eklenecekler. Aşağıdakine benzer bir önyüz hazırlıyoruz. Sol tarafta katalogda yer alan ihraç [Export] tanımları yer alacak. Sağ tarafta ise katalogda yer alan ISeries interface’sini uygulayan sınıflar listelenmiş. Seçilen sınıfı çağıracak olan birde buton eklenmiş durumdadır.

Uygulamayı çalıştırıp uygulamanın dinlediği Plugins dizinine ISeries interface’sini uygulayan assembly dosyalarını kopyalıyoruz. Katalog listesinin doğrudan güncellendiğini görürüz. Bir tane ISeries uygulaması seçip serinin eleman sayısını girelim ve çalıştır diyelim.

ContractName ve MetaData

Uygulama katalogu içinde birden çok ISeriesFactory uygulaması var. Bu durumda elimizde ki parçalar arasında sorgular çalıştırarak istediğimiz parçayı bulmamız gerekmektedir. [ExportMetadata] niteliği ile katalog üzerinde arama yapabilmek için[ Export] tanımlarımıza etiket değerleri verebilmekteyiz. Parçalara ContractName değeri ekleyerek doğrudan istediğimiz parçayı içe aktarabilir veya kendimiz özel sorgular yazarak aradığınız parçayı taşıyıcı üzerinde bulabiliriz.

[cs]

[Export ("Fibanacci" ,typeof  (ISeriesFactory ))]
 public  class  Fibanacci  : ISeriesFactory
 private LazyGetFibanacciSeries(CompositionContainer compositionContainer)
 {
     Lazy  fibanacci =
         compositionContainer.GetExport ("Fibanacci" );
     return  fibanacci;
 }

Veya taşıyıcının sizin yerinize ContractName metadata değeri ile [Import] işlemi yapmasını sağlayabilirsiniz.

[cs]

[Import ("Fibanacci" )]private  ISeriesFactory  _fibanacci ;

Yukarıda ki tanımlama katalog üzerinde aşağıdaki gibi saklanmaktadır.

Görüldüğü üzere yukarıda ki [Export] tanımına ait bir Meradata dizisi bulunmaktadır. [Export] tanımın ilk parametresinde verdiğimiz isim ContractName olarak saklanmaktadır. Metadata dizisinde ExportTypeIdentity etiketinin değeri ise [Export] yapılan tipin tam adıdır. Eğer bir [Export] kontratı için bir isim belirtmez iseniz ExportTypeIdentity değeri ContractName alanına atanacaktır. ContractName ve ExportTypeIdentity metadata değeri ile katalog üzerinde sorgulama yapabilirsiniz.

Buraya kadar her şey güzel ama matematik ve karakter serilerinin aynı listede görülmesini istemiyorsunuz. O zaman kendinize ait [ExportMetadata] etiketleri tanımlama zamanı gelmiştir.

[cs]

[Export ("Fibanacci" ,typeof  (ISeriesFactory ))]
 [ExportMetadata ("Series" ,"Math" )]
 public  class  Fibanacci  : ISeriesFactory
 [Export (typeof  (ISeriesFactory ))]
 [ExportMetadata ("Series" , "String" )]
 public  class  UpperLetters  : ISeriesFactory

Yukarda Prime serisinin bir matematik seri olduğunu benzer şekilde UpperLatter serinin bir karakter serisi olduğunu belirtiyoruz. Şimdi formumuza yeni bir combobox ekleyerek kullanıcıya seri türlerini gösterelim. Her seçilen seri türünde de seri uygulamaları listemizi güncelleyelim.

[cs]

private  void  cbTypes_SelectedIndexChanged (object  sender, EventArgs  e)
 {
     var  selectedType = cbTypes .SelectedItem .ToString ();
     var  seriesList = new  StringList ();
     _compositionContainer 
         .Catalog .Parts 
         .SelectMany (part => part.ExportDefinitions )
         .Where (c=>c.Metadata .ContainsKey ("Series" ) 
                 && c.Metadata ["Series" ].ToString () == selectedType)
         .ToList ()
         .ForEach (c=>seriesList.Add (c.ContractName ));
     sourceSeries .DataSource  = seriesList;
 }

Yukarıda görüldüğü üzere seçilen seri türüne göre seri uygulama sınıflarını listeletebiliyoruz. Fakat bu yöntemde benim eklenti sınıflarında verilen etiket değerlerini bilmem gerekiyor. Metadata nın adı “Series” olmalı değeri “Math” veya “String” olmalı şeklinde kurallarım var. Eklenti kodlanırken bu kurallarımı zorlayan bir şey yok. Benim ihtiyacım ISeriesFactory interface ile birlikte sınıflara Metadata bilgisini de dağıtmalı ve böylece beklenmedik metadata değerleri ile karşılaşmamalıyım. O halde yeni bir Metadata sınıfı yazalım ve bu Metadata ya sahip ISeriesFactory sınıfları ile birlikte çalışalım.

[cs]

public  enum  SeriesType  {Math, String}
 public  interface  ISeriesFactoryMetadata 
 {
     SeriesType  SeriesType  { get ;  }
     string  Name  { get ;  }
 }
 [MetadataAttribute ]
 [AttributeUsage (AttributeTargets.Class,AllowMultiple= false )]
 public  class  SeriesFactoryMetadataAttribute  
     : ExportAttribute , ISeriesFactoryMetadata 
 {
     public  SeriesFactoryMetadataAttribute () 
         : base (typeof (ISeriesFactory )) { }
     public  SeriesType  SeriesType  { get ; set ; }
     public  string  Name  { get ; set ; }
 }

Yukarıda [Export] kontratını türettiğimizi ve sınıfmıza [Metadata] özelliği eklediğimizi görüyorsunuz. Böylelikle [Export] kontratı tanımlarken [Export] ve metadatayı ayrı ayrı tanımlamak zorunda değiliz. Ayrıca Metadata string olarak girilen Metadata etiketlerimizin yerini ISeriesFactoryMetadata interface property'leri aldı. Şimdide uygulamamızda ISeriesFactoryMetadata'yı kullanalım.

[cs]

//[Export("Fibanacci",typeof (ISeriesFactory))] 
 //[ExportMetadata("Series", "Math")] 
 [SeriesFactoryMetadata (Name ="Fibannacci Series" , SeriesType =SeriesType.Math)]
 public  class  Fibanacci  : ISeriesFactory

Görüldüğü üzere kod çok daha anlaşılır oldu. Uygulamayıda ISeriesFactoryMetadata'yı kullanacak şekilde değiştirelim.

[cs]

private  void  btnCreate_Click (object  sender, EventArgs  e)
 {
     var  selectedType = (SeriesType ) cbTypes .SelectedItem ;
     var  selectedSeries = cbSeries .SelectedItem .ToString (); 
     var  series =  _series .First (c => 
         c.Metadata .SeriesType  == selectedType 
         && c.Metadata .Name  == selectedSeries).Value ;
     var  list = new  StringList ();
     list.AddRange (series.Create (int .Parse (txtNumber .Text )));
     sourceResult .DataSource  = list;
 }
 [ImportMany ] private  Lazy [] _series ;
 private  void  cbTypes_SelectedIndexChanged (object  sender, EventArgs  e)
 {
     var  selectedType = (SeriesType ) cbTypes .SelectedItem ;
     var  seriesList = new  StringList ();
     _series 
         .Where (c=>c.Metadata .SeriesType  == selectedType)
         .ToList ()
         .ForEach (c => seriesList.Add (c.Metadata .Name ));
     sourceSeries .DataSource  = seriesList;
 }

Uygulama geliştirici olarak eklentilernden geleçek olan Metadata bilgilerimi güçlü bir tipe çekmiş oldum. Eklenti geliştiricilerede uygulama içinde gerekli olan Metadata bilgilerine doğrudan ulaşmış oldular.

Neden SeriesFactoryMetadataAttribute sınıfnıda bir interface uygulamak zorundadır ? Compositon geri tarafta metadata sınıfı için hala bir anahtar-değer sözlüğü üretmektedir. Doğrudan SeriesFactoryMetadataAttribute sınıfnı [Import] ile almak için SeriesFactoryMetadataAttribute sınıfının üretilen sözlüğü constractor parametresi olarak alması gerekmektedir. Daha kolay yöntem ise SeriesFactoryMetadataAttribute sınıfın bir interface uygulaması ve interface üzerinde ki property değerlerinin composition tarafından okunmasıdır.

Parçaların Hayat Döngüsünü Belirlemek

Ben asal sayıları hesaplayan dizinin her çağrılmada tekrar hesaplama yapmasını istemiyorum. Çünkü bu hesapla uzun süremektedir. Bu sınıfın uygulamada sadece bir defa oluşturulmasını istiyorum. Prime sınıfı ürettiği asay sayıları saklasın ve mümkün oldupunca az hesapla yapsın istiyorum. Taşıyıcıya bu sınıfı her çağrıda oluşturma sadece bir defa oluştur ve kullan demem gerekiyor.

[cs]

[SeriesFactoryMetadata (Name  = "Prime Series" , SeriesType  = SeriesType.Math)]
 [PartCreationPolicy (CreationPolicy.Shared)]
 public  class  Prime  : ISeriesFactory

Böylece Prime sınıfını paylaşımlı/Shared işaretleyerek kullandığım taşıyıcı üzerinde aynı anda sadece bir tane örneği bulunacağını belirtiyoruz. Eğere bir nesnenin her erişimde yeniden oluşturulmasını istiyor iseniz NonShared olarak işaretlemeniz gerekmektedir. Fibanacci serisinde nasıl kullanılması gerektiğini [Import] anında karar verilmesini istiyorum. Yani Fibanacci serisinin [Import] eden parça isterse taşıyıcı üzerinde mevcut olan örneği isterse yeni bir örneği kullanabilir.

[cs]

[SeriesFactoryMetadata (Name  = "Fibannacci Series" , SeriesType=SeriesType.Math)]
 [PartCreationPolicy (CreationPolicy.Any)]
 public  class  Fibanacci  : ISeriesFactory
 [Import("Fibanacci", RequiredCreationPolicy = CreationPolicy.NonShared )]
 private  ISeriesFactory  _fibanacci ;

Composition aksi belirtilmedikce [Export] kontratı olarak Any, [Import] kontratı olrakta Shared kullanmaktadır. Yani aksi belirtilmedikce taşıyıcı üzerinde aynı [Export] kontratı için bir tane parça var olacaktır.

Katalogu Çalışma Zamanında Değiştirme

Fibanacci sınıfın yeni versiyonunu yazdık. Şimdi uygulamayı dinamik olarak güncellememiz gerekiyor. Taşıyıcıya [Import] ettiğiniz parçalarda bir değişiklik olursa tekrar [Import] işlemini yapmasını söyleyebiliyoruz.

[cs]

[ImportMany (AllowRecomposition  = true )] 
private  Lazy [] _series ;

Ayrıca her hangi bir [Import] parçası taşıyan bir sınıfta IPartImportsSatisifiedNotification interface'i uyguladı ise tüm [Import] işlemleri bittikten sonra ImportCompleted olayı taşıyıcı tarafından çağırılmaktadır.

Sonuç

Böylelikle [Export] kontratlarımıza Metadata özelliklerini eklemeyi ve eklediğimiz Metadata özelliklerini bir sınıf yapısına getirmeyi inceledik. Taşıyıcının Metadata ve ContractName ile nasıl çalıştığını ve taşıyıcı üzerinde sorgulama yapmayı gördük. Daha sonra taşıyıcı üzerinde ki parçaların hayat döngüsünü yönetmeyi inceledik. Son olarakta çalışma anında değişen katalogdan haberdar olmayı gördük. Bu iki yazı ile her hangi bir .NET platformunda genişletilebilir uygulama yazacak duruma geldik. Bir sonra ki makalede Genişletilebilir Uygulamalarda Hata Ayıklama ve Takip etme özelliklerini inceliyor olacağız.