Quantcast
Channel: Burak Selim Şenyurt
Viewing all 343 articles
Browse latest View live

Dependency Inversion Principle - Kavramak

$
0
0

Merhaba Arkadaşlar,

Bu görsel dersimizde, SOLID ilkelerinden birisi olup Yazılım Tasarım Presinpleri(Software Design Principles) içerisinde yer alan Dependency Inversion’ ı kavramaya çalışıyoruz. Konuyu irdelerken basit bir senaryoyu göz önüne alıyor, önce DIP olmadan ilerliyor ve sorunları teşhis ediyoruz. Sonrasında ise Dependency Inversion prensibini baz alarak bağımlılıkları tersine çeviriyor ve problemli kısımları iyileştiriyoruz.

Bir başka görsel dersimizde görüşmek dileğiyle Winking smile


.Net Uygulama Güvenliği–Hacking ve Tedbirleri

$
0
0

Merhaba Arkadaşlar,

Bu görsel dersimizde önce basit bir .Net uygulamasını, CIL(Common Intermediate Language)kodları üzerinden Hackliyor ve sonrasında olası tedbirleri deneyerek söz konusu vakayı engellemeye çalışıyoruz. Bunun için komut satırından kolayca kullanılabilen sn.exe(Strong Name ile uygulamanın işaretlenmesi) ve SignTool.exe(Uygulamanın bir sertifika ile işaretlenmesi) araçlarını değerlendiriyoruz. Kodun Obfuscate edilmesinden bahsediyor ve son olarak sonuçları irdeliyoruz.

Ara sıra gelen o gıcık öksürük ritmimi bozduğundan sürçü lisan etmişimdir mutlaka. O yüzden affola. Bir başka görsel dersimizde görüşünceye dek hepinize mutlu günler dilerim Winking smile

Eclipse Üzerinden Java ile TFS Client Object Model Konuşuyor

$
0
0

developersMerhaba Arkadaşlar,

Çok değil daha bir kaç sene öncesine kadar(Özellikle .Net’ in duyurulduğu yıllarda ve izleyen bir kaç senede) yazılım dünyasında neredeyse yandaki resimdekine benzer bir kavga vardı(Benzetmeyi biraz abartmış olabilirim) 

Java’ cılar, C#’ çıları pek sevmez iken tam tersi durum da pekala geçerliydi. Ben hiç bir zaman birisinin fanatiği olmadım. Hatta Java ile ufak çaplı bir kaç deneyimim bile oldu.

Peki gerçek dünya böyle mi? Özellikle kalabalık yazılım ekiplerinin olduğu, çok fazla sayıda ürünün koştuğu dünyalarda, sadece Java’ cıları, C#’ çıları değil, daha pek çok programlama dili geliştiricilerini bir arada görmekteyiz. Öneğin ben bulunduğum konum itibariyle C’ cilerin, Assembler’ cıların, PowerBuilder’ cıların, .Net’ çilerin, Java’ cıların ve hatta Cobol’ cuların arasında yaşamaktayım.

Hepsi kendi dünyalarını kullanarak ürünler geliştiriyor olsalar da, zaman içerisinde birbirleriyle konuşması gereken uygulamalar bütününün de bir parçası olmaktan kaçamıyorlar. Özellikle işin içerisine bir ALM(Application LifeCycle Management) aracı girdiğinde. İşte bu günkü konumuzda buna itaf edilecek Winking smile Haydi gelin başlayalım.

Bildiğiniz üzere bir süredir Team Foundation Server’ın çevre dünya ile olan etkileşimini incelemeye çalışıyorum. Açıkçası TFS’ in gerek servis yapısı gerek Client Object Model gibi kütüphaneleri sayesinde, dış dünya ile olan entegrasyonu son derece kolay. Bu gün buna bir kere daha inandım. Çünkü bir Java uygulaması içerisinde TFS Client Object Model’ i kullanarak, bir Team Project’ in WorkItem listesini sorguladım Winking smile Nasıl yaptığımı merak ediyorsanız okumaya devam edin. Tabi bu işte de çok önemli bir yardımcım vardı. O da Microsoft tarafından geliştirilen ve ücretsiz olarak sunulan Client Object Model SDK’sı. Ama Java için olan sürümü.

Microsoft Team Foundation Server 2012 Software Development Kit for Java içeriğini bu adresten indirebilirsiniz(Makaleyi hazırladığım tarih itibariyle 15 Şubat 2013’ te yayınlanmış güncel bir sürümü bulunmaktaydı)

Senaryo

Senaryomuz esas itibariyle yine bir Hello World uygulaması olacak. Embarrassed smile Console ekranına belirli bir Team Project içerisinde yer alan WorkItem bilgilerini(Product Backlog Item, Bug, Task gibi) yazdırmaya çalışacağız. Örneğin Work Item’ ın başlığını(Title), tipini(WorkItem Type), numarasını(ID) düzgün bir sırada çekmeyi deneyebiliriz. Java kodlaması yapacağımız için Eclipse gibi bir IDE tercih edilebilir ki ben öyle yaptım Winking smile

Bebek Adımları

Adım 0

İlk olarak Eclipseüzerinde yeni bir JavaProjesi oluşturarak işe başlayalım(File->New->Java Project). Ben UsingClientObjectModel olarak adlandırdığım projeyi, sistemimde kurulu olan Workspace içerisinde oluşturdum. Runtime olarak 1.6 sürümünü kullanmayı tercih ediyorum. Bu nedenle daha önceki projelerden varsayılan olarak kalan Use an execution environment JRE değerini olduğu gibi bıraktım.

jtfs_1

Bu üretim işlemi sonrasında Eclipse IDE’ sinde aşağıdakine benzer bir içerik oluştuğunu görmeliyiz. (Elbette sisteminizde Java Runtime Environement’ in yüklü olduğu fiziki adresler daha farklı olabilir.)

jtfs_2

Adım 1

İkinci adım ise main metodunu içerecek olan ve asıl kodlarımızı yazacağımız sınıfı eklemek olacaktır. Visual Studio tarafından kalma bir alışkanlık nedeniyle Program olarak isimlendirdiğim sınıf, tfs.clientobjectmodel.application isimli paket içerisinde konuşlandırılacak(Siz kendi istediğiniz paket tanımlamasını yapabilirsiniz) Krtik noktalardan birisi de, public static void main(String[] args) seçeneğinin işaretli olmasıdır. Bu, tahmin edeceğiniz üzere programın giriş noktası olan main metodudur.

C# tarafından da bildiğiniz üzere static Main metodu, exe tipindeki uygulamaların giriş noktasıdır. Aynı durum Java uygulamaları için de geçerlidir. Tek fark Java tarafında isimlendirme standartı gereği küçük m harfi ile başlanmasıdır. Dikkat edileceği üzere her iki dilde metoda parametre olarak string tipinden bir dizi alır. Programa dış ortamdan parametre aktarabilmek amacıyla Winking smile

jtfs_3

Bu işlemin sonucunda Eclipse IDE’ si bizim için aşağıdaki ekran görüntüsünde yer alan içeriği üretmelidir.

jtfs_4

Adım 2

Üçüncü adımda, bilgisayarımıza indirip açtığımız TFS SDK’ sını referans ediyor olacağız. Bunun için ilgili JAR dosyasının uygulamaya bildirilmesi(bir başka deyişle referans edilmesi) gerekmektedir. Söz konusu bildirim için proje özelliklerinden Java Build Path kısmına gelmeli ve Add External JARs düğmesini kullanarak com.microsoft.tfs.sdk-11.0.0 isimli JAR dosyasını seçmeliyiz.

jtfs_5

jtfs_6

Konu ile ilişkili olarak yaptığım araştırmalarda şöyle bir kullanıma da rastladım. Hazırlanan Java uygulamasının dağıtılabileceği de düşünülürse SDK’ nın Redistributable olan parçalarının proje klasörü altına kopyalanması ve ilgili path bildirimlerinin bu fiziki adresleri gösterecek şekilde yapılması yolu da tercih edilebilir. Böyle bir durumda Add JARs düğmesinden hareket edilir.

Sonuç olarak Java Build Path kısmının aşağıdaki ekran görüntüsündeki gibi oluşması gerekmektedir.

jtfs_7

Ne varki bu yeterli değildir. Bir de eklenen JAR dosyası için Native Libary Location değerinin set edilmesi gerekmektedir. Bunun için aşağıdaki ekran görüntüsünde görüldüğü üzere, SDK’ nın açılması sonucu oluşan redist\native klasörüne kadar inilmeli ve platforma uygun olan klasör seçilmelidir. Ben x86 işlemcili win32 tabanlı bir sistem de çalıştığımdan buna uygun olan klasörü seçtim(Native klasörü altında Linux, MacOSX, Solaris gibi pek çok sistem için gerekli kütüphaneler bulunmaktadır)

jtfs_8

Buraya kadar ki işlemlerimiz sonrasında Eclipse IDE’ sinde projemize ait olan son görünüm de aşağıdaki gibi olacaktır. Dikkat edileceği üzere TFS SDK’ sı, Referenced Libraries kısmında görülmektedir.

jtfs_9

Örnek kodlar

Şimdi kod tarafını geliştirmeye başlayabiliriz. Aslında .Net tarafında Client Object Model’ in kullanımından çok da farklı bir işlem yapmamıza gerek yoktur. TFS Client Object Model’ in nesne yapısının hemen hemen aynısı burada da inşa edilmiştir. Şimdi Program sınıfına ait kodlarımızı aşağıdaki gibi geliştirelim.

package tfs.clientobjectmodel.application;

import java.net.URI;
import java.net.URISyntaxException;

import com.microsoft.tfs.core.TFSTeamProjectCollection;
import com.microsoft.tfs.core.clients.workitem.WorkItem;
import com.microsoft.tfs.core.clients.workitem.WorkItemClient;
import com.microsoft.tfs.core.clients.workitem.query.WorkItemCollection;
import com.microsoft.tfs.core.httpclient.Credentials;
import com.microsoft.tfs.core.httpclient.HttpException;

public class Program {

    public static void main(String[] args)
            throws HttpException, URISyntaxException {
       
        System.setProperty("com.microsoft.tfs.jni.native.base-directory"
               , "C:\\Program Files\\TFS-SDK-11.0.0.1302\\TFS-SDK-11.0.0\\redist\\native");
       
        URI uri=new URI("http://tfsserver:8080/tfs/defaultcollection");
        Credentials user=new com.microsoft.tfs.core.httpclient.DefaultNTCredentials();
       
        TFSTeamProjectCollection collection=new TFSTeamProjectCollection(uri,user);
           
        WorkItemClient wiClient=collection.getWorkItemClient();
               
        WorkItemCollection workItems=wiClient
                .query("Select ID,Title from WorkItems " +
                        "where ([Team Project]='ARGE') order by [Work Item Type]");
        System.out.println("Active statusunde olan toplam "+
                workItems.size()+" work item bulunmustur");       
       
        for(int i=0;i<workItems.size();i++)
        {
            WorkItem workItem=workItems.getWorkItem(i);
            System.out.println(workItem.getTitle()
                    +" "+workItem.getType().getName()
                    +" "+workItem.getID()
                    +" "+workItem.getProject().getName()
                    );
        }
    }
}

İlk dikkat edilmesi gereken nokta Systemözelliklerine bir key-value eklenmiş olmasıdır. com.microsoft.tfs.jni.native.base-directory isimli key için C:\\Program Files\\TFS-SDK-11.0.0.1302\\TFS-SDK-11.0.0\\redist\\native adresi value olarak verilmiştir. Normal şartlarda bu bildirim komut satırından java.exe uygulaması kullanılarak da yapılabilir. Söz konusu sistem özelliğinin bir kere set edilmesi yeterlidir. Bu bildirim ile aslında native loader’ ın sisteme tanıtılması işlemi gerçekleştirilir. Eğer ilgili bildirim yapılmazsa, çalışma zamanında aşağıdaki hata mesajı ile karşılaşılması muhtemeldir.

Exception in thread "main" java.lang.UnsatisfiedLinkError: com.microsoft.tfs.jni.internal.platformmisc.NativePlatformMisc.nativeGetEnvironmentVariable (Ljava/lang/String;)Ljava/lang/String;
    at com.microsoft.tfs.jni.internal.platformmisc.NativePlatformMisc.nativeGetEnvironmentVariable (Native Method)
    at com.microsoft.tfs.jni.internal.platformmisc.NativePlatformMisc.getEnvironmentVariable (NativePlatformMisc.java:134)
    at com.microsoft.tfs.jni.PlatformMiscUtils.getEnvironmentVariable (PlatformMiscUtils.java:52)
    at com.microsoft.tfs.core.config.httpclient.DefaultHTTPClientFactory.shouldAcceptUntrustedCertificates (DefaultHTTPClientFactory.java:288)
    at com.microsoft.tfs.core.config.httpclient.DefaultHTTPClientFactory.configureClientParams (DefaultHTTPClientFactory.java:324)
    at com.microsoft.tfs.core.config.httpclient.DefaultHTTPClientFactory.newHTTPClient (DefaultHTTPClientFactory.java:137)
    at com.microsoft.tfs.core.TFSConnection.getHTTPClient (TFSConnection.java:1041)
    at com.microsoft.tfs.core.TFSConnection.getWebService (TFSConnection.java:874)
    at com.microsoft.tfs.core.config.client.DefaultClientFactory$9.newClient (DefaultClientFactory.java:265)
    at com.microsoft.tfs.core.config.client.DefaultClientFactory.newClient (DefaultClientFactory.java:90)
    at com.microsoft.tfs.core.TFSConnection.getClient (TFSConnection.java:1470)
    at com.microsoft.tfs.core.TFSTeamProjectCollection.getWorkItemClient (TFSTeamProjectCollection.java:370)
    at tfs.clientobjectmodel.application.Program.main (Program.java:26)

Bundan sonraki kısımda ilk olarak TFSTeamProjectCollection tipinden bir nesne örneklendiği görülmektedir. Örnekleme sırasında ilk parametre olarak DefaultCollection’ a ait url adresi verilmiştir(Programda TFS sunucusunun kurulu olduğu makinedeki varsayılan Team Project Collection kullanılmaktadır). URI sınıfından yapılan bu bildirimi Credentials tipinden bir parametre takip etmektedir. Makineyi açan kullanıcının Credential bilgisi ile TFS’ e bağlanılmak istendiğinden DefaultNTCredentials tipinden bir nesne örneği ele alınmıştır. Ancak bir kullanıcı adı ve şifre ile gidilmek istenirse, UsernamePasswordCredentials sınıfından da yararlanılabilir(Elbette ilgili kullanıcıların söz konusu TFS sunucusuna ve koleksiyona erişebildiğini, bir başka deyişle gerekli yetkilere sahip olduğunu var sayıyoruz)

Kod parçasında ARGE isimli projeye ait Work Item’ lar çekilmeye çalışılmaktadır. Bu sebepten WorkItemStore servisine erişilmesi gerekmektedir. İlgili servisi kullanabilmek için getWorkItemClient fonksiyonu çağırılmaktadır. Bu metod geriye WorkItemClient tipinden bir referans döndürmektedir. Bu referans üzerinden çağırılan query fonksiyonuna girilen WIQL(WorkItem Query Language) sorgusu ile de WorkItemCollection elde edilir. Bu koleksiyon sorguya uygun bir WorkItem içeriğini taşıyacaktır.

Pek tabi Java tarafında, .Net’ te olduğu gibi Property isimli bir tip üyesi(Type Member) bulunmamaktadır. Ancak .Net’ ten aşina olduğumuz WorkItemözelliklerine getön ekli metodlar yardımıyla ulaşabiliriz. Örnekte Work Item’ ın Title değeri için getTitle(), Work Item tipinin adı için getType().getName(), sistem de kayıtlı olan ID bilgisi için de getID() metodu kullanılmıştır.

Sonuç olarak ARGE isimli projedeki Work Item’ ların ID,Title değerlerinin, Work Item tipine göre sıralanarak elde edilmesi işlemi icra edilmektedir. Uygulamanın çalışma zamanı sonuçları aşağıda görüldüğü gibidir(Farklı WIQL sorguları ile örneği zenginleştirmeyi denemenizi öneririm Winking smile )

jtfs_10

Senaryoyu işlettiğim sistemde test amaçlı olarak kullandığım ARGE isimli Team Project, Scrum 2.0şablonunu kullanmaktaydı. Bu nedenle Product Backlog Item, Task ve Bug gibi Work Item öğelerini barındırmaktadır.

Biraz Daha

Dilerseniz örnek kod parçasını biraz daha geliştirmeye çalışalım. Örneğin yeni bir WorkItem nasıl eklenir ona bakalım. .Net tarafından biraz farklı olarak yeni bir WorkItem nesnesinin örneklenmesi için Project sınıfı üzerinden WorkItemClient referansına ulaşışması ve newWorkItem metodunun çağırılması gerekmektedir(Yani WorkItem sınıfını doğrudan bir yapıcı metod-Constructor ile örnekleyemiyoruz) Aynen aşağıdaki kod parçasında olduğu gibi.

package tfs.clientobjectmodel.application;

import java.net.URI;
import java.net.URISyntaxException;

import com.microsoft.tfs.core.TFSTeamProjectCollection;
import com.microsoft.tfs.core.clients.workitem.WorkItem;
import com.microsoft.tfs.core.clients.workitem.WorkItemClient;
import com.microsoft.tfs.core.clients.workitem.project.Project;
import com.microsoft.tfs.core.clients.workitem.wittype.WorkItemType;

import com.microsoft.tfs.core.httpclient.Credentials;
import com.microsoft.tfs.core.httpclient.HttpException;

public class Program {

    public static void main(String[] args)
            throws HttpException, URISyntaxException {
        
        URI uri=new URI("http://tfsserver:8080/tfs/defaultcollection");
        Credentials user=new com.microsoft.tfs.core.httpclient.DefaultNTCredentials();
       
        TFSTeamProjectCollection collection=new TFSTeamProjectCollection(uri,user);
           
        WorkItemClient wiClient=collection.getWorkItemClient();
        
        Project argeProject=wiClient.getProjects().get("ARGE");
        WorkItemType pbi=argeProject.getWorkItemTypes().get("Product Backlog Item");
       
        WorkItem newWorkItem=argeProject.getWorkItemClient().newWorkItem(pbi);
        newWorkItem.setTitle("Backoffice ekranlarin icin wire frame tasarim calismalari");
        newWorkItem.save();
        System.out.println(newWorkItem.getID()+" numarasi ile bir Product Backlog Item olusturuldu");
    }
}

Dikkat edilmesi gereken noktalardan birisi de newWorkItem metoduna parametre olarak üretilmek istenen workitem tipinin verilmesidir. Bu bildirim için WorkItemType sınıfına ait bir nesne örneği kullanılmaktadır. WorkItemTypeüretimi için projeye ait nesne örneği üzerinden önce var olan WorkItem tiplerinin elde edilmesi işlemi gerçekleştirilmiş, ardından ise ProductBacklogItem tipi çekilmiştir. Bu son derece mantıklıdır, nitekim ilgili projenin şablonu(ki örneğimizde Scrum 2.0 söz konusudur) tarafından kullanılan WorkItem tipleri ne ise, onlara ait WorkItem nesneleri örneklenebilir. Üretilen ProductBackclogItem için bir Title değeri verilmiş ve sonrasında Save metodu kullanılarak kayıt işlemi gerçekleştirilmiştir. Uygulama çalıştırıldığında hem Console penceresinden hem de ilgili projeye ait Backlog’ da bir öğenin oluşturulduğu gözlemlenecektir.

jtfs_11

Görüldüğü üzere Team Foundation Server Client Object Model’ in, Java tarafında kullanılması da son derece kolaydır. Örnek, tahmin edeceğiniz üzere Hello World formatındadır. Ancak daha önceki yazılarımızı baz alarak, .Net tarafındaki Client Object Model kodlarını, Java tarafına taşımayı deneyebilirsiniz. Kim demiş Java, Microsoft’ u, Microsoft’ ta Java’ yı umursamıyor diyeWinking smile Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Asp.Net Web API için Sayfalama Tekniği

$
0
0

lazy-baby-laptopMerhaba Arkadaşlar,

Bu aralar şirkette işler oldukça kesat. En azından benim bulunduğum departman itibariyle böyle bir durum söz konusu. Sanırım kurumsal kimlik kazanmış firmaların genel sorunu da bu olsa gerek. Kaynak planlaması ve dağıtımının bir türlü istenen şekilde yapılamayışı. Hal böyle olunca aynı firmada hatta aynı departman içerisinde, çok yoğun çalışan insanlara ve beraberinde her hangi bir işi olmayanlara(benim gibi) rastlamak mümkün.

Böyle bir durumda keyif sürmek ve tembel tembel internette gezmek(Video paylaşmak, onun bunun ciklemesine yetişmeye çalışmak vb) yapılabilecek en cazip işlerden birisi gibi gözükse de, hızla ilerleyen teknoloji ne yazık ki buna müsade etmemekte. Neredeyse her hafta yeni konuların ele alındığı bilmem kaç katlık yottabyte’ lık bilgi denizinde sürekli bir şeyler öğrenmek zorunda olan biz köle geliştiricilerin iş olmasa da kendisine iş yaratması şart. Eğitim şart da diyebiliriz SmileÖyleyse tembel tembel oturmayalım ve gelin birlikte yeni bir mevzuya dalalım.

Asp.Net Web API alt yapısının popüler olmasının ardında yatan en büyük sebeplerden birisi, HTTP tabanlı servis yayılımına izin vermesidir. Hemen her fonksiyonel birimin veya bütünlüğün servis odaklı teknolojiler ile ele alındığı ve istemcilere sunulduğu bir dünyada, bu ihtiyacı eskiden beri var olan HTTP protokolünün Post, Put, Get, Delete gibi standart metodlarına göre karşılamak elbette önemlidir. Bu sayede Microsoft tabanlı olarak geliştirilen Web API servislerinin, dış dünyadaki herhangibir Client tarafından tüketilmesi de oldukça kolaydır. Üstelik OData(Open Data Protocol) desteği sayesinde, veri odaklı servislerin standart URL bazlı parametreler ile sorgulanabilmesi mümkün hale gelmektedir.

Bir önceki cümlede belirttiğimiz veri odaklı servis(Data-Centric Service)aslında bu yazımızdaki senaryomuzun da ana konularındandır. Veri odaklı servisler tahmin edileceği üzere büyük boyutlu verileri de ele alabilirler. Çok eskilerden de bildiğimiz üzere Asp.Net ile web programlamanın ilk yıllarında yaşanan en büyük sıkıntılardan birisi, verinin sayfalanarak getirilmesiydi. Stored Procedure’ ler de yapılan bazı hamleler ile bu işi çözebiliyor olsak da, SP desteği olmayan bir veri kaynağının kullanılma olasılığını da göz ardı etmemek gerekiyor.

Hatırlayacağınız gibi web tarafındaki ilk veri bağlı kontrollerde sayfalama sistemi şöyle çalışmaktaydı:

Sorgulama sırasında tüm veri çekilir ve içinden örneğin Xnci sıradaki 10 luk eleman kümesi getirilirdi. Çok doğal olarak her seferinde tüm veri kümesinin çekilmesi çok da istenen bir yaklaşım değildi. Örneğin 10 milyon satır içinden 3ncü 50lik kümeyi getirmek istediğimizde Confused smile

Ancak ilerleyen sürümler de bu durum değişti ve özellikle SQL tarafında row_number kullanımı ile doğru sayfalama işlemlerinin yapılabilmesinin yolu açıldı. Buna bir de LINQ tarafındaki anahtar kelime desteği eklenince, Entity Framework gibi alanlarda doğru sayfalama stratejilerini kullanabilir olduk.

Peki Asp.Net Web API tarafında sayfalama işlevselliği nasıl karşılanabilir? İşte bu yazımızda cevap bulmaya çalışacağımız soru bu. Şimdi basit bebek adımları ile ilerleyerek senaryomuzu hayata geçirmeye çalışalım.

Proje için Ön Hazırlıklar

İlk olarak Visual Studio 2012 ortamında Empty MVC 4şablonunda bir web uygulaması açarak yola koyulabiliriz.

wapip_1

Söz konusu senaryomuzda istemci tarafında yazacağımız kodlar oldukça önemlidir. Bu yüzden jQuery ve Knockout.js’ in son sürümlerinin kullanılmasında yarar vardır. Ayrıca OData sorgularını kullanacağımız için Microsoft ASP.NET Web API OData paketini de eklememiz gerekmektedir. Bu referansları NuGet paket yönetim aracı ile projeye kolayca dahil edebiliriz.

jQuery

wapip_3

knockout.js

wapip_4

ve Microsoft ASP.NETWeb API OData

wapip_5

Bu eklentilerin yüklenmesi sonrasında jQuery ve knockout.js için scripts klasörü aşağıdaki hale gelecektir.

wapip_6

Buradaki Javascript kütüphaneleri cshtml tarafında kullanılacaktır.

Veri Modelinin Eklenmesi

Çok doğal olarak senaryomuzda veri odaklı bir uygulama öngörülmektedir. Örneğimizde Entity Framework’ den yararlanılabilir. Bu nedenle Model klasörüne yeni bir Ado.Net Entity Data Modelöğesi ekleyelim. Örneğimizde kobay veritabanlarımızdan birisi olan Chinook’ a bağlanıyor olacağız. Bu veritabanın yer alan Invoice tablosu 400 satırdan fazla veri içermekte ve senaryomuz için ideal bir veri kümesi sunmaktadır. Bu sebpeten sadece ilgili tabloyu kullansak yeterli olacaktır.

wapip_2

Controller Eklenmesi

Pek tabi veri modelinin oluşturulmasının ardından bir de Controller’ ın eklenmesi gerekmektedir. Model ile View arasındaki köprüyü kuracak olan Controller sınıfının temel özelliklerini aşağıdaki gibi belirleyebiliriz.

wapip_7

Sınıf içeriği bizim için otomatik olarak üretilecektir. Ancak senaryomuz için gerekli olmayan detayları çıkartabiliriz. Buna göre içeriği aşağıdaki kod parçasında görüldüğü gibi değiştirmemiz yeterli olacaktır.

using System.Web.Http;
using MvcApplication5.Models;

namespace MvcApplication5.Controllers
{
    public class InvoicesController : ApiController
    {
        private ChinookEntities db = new ChinookEntities();

        // GET api/Invoices
        [Queryable]
        public IQueryable<Invoice> GetInvoices()
       {
            return db.Invoices.AsQueryable();
        }
       
        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

ApiController türevli InvoicesController sınıfı içerisinde yer alan GetInvoices metodu, Queryable tipi ile nitelendirilmiştir. Bu nitelik(Attribute) sayesinde OData sorgu desteği sağlanmış olmaktadır ki bu, sayfalama için kullanılacak olan top,skip ve orderby komutları için gereklidir.

Görsel Taraf(İstemci) için Controller Eklenmesi

ApiController bilindiği üzere Web API servis desteği için gereklidir. Ancak istemci tarafını düşündüğümüzde standart bir MVC Controller’ ının kullanılması gerekecektir. Nitekim cshtml içeriğini kullanarak Web API servisi üzerinden OData anahtar kelimeleri ile sayfalama talebinin gönderilip, sonuçların gösterileceği bir Viewöğesi gerekmektedir. Bunun için projeye yeni bir MVC 4Controller ekleyerek ilerleyebiliriz.

wapip_8

Bu işlem sonucunda aşağıdaki sınıf içeriği üretilmiş olacaktır.

using System.Web.Mvc;

namespace MvcApplication5.Controllers
{
    public class InvoiceListController
        : Controller
    {
        //
        // GET: /InvoiceList/

        public ActionResult Index()
        {
            return View();
        }
    }
}

View Öğesinin Eklenmesi (index.cshtml)

Tahmin edileceği üzere bir de Viewöğesine ihtiyacımız bulunmakta. Bu amaçla Views klasörü içerisinde InvoicesList isimli bir alt klasör açarak içerisine yeni bir View ilave edip devam edebiliriz. (View’ un adını index olarak belirleyip Razor Engine’ i kullanacak şekilde tesis edelim)

İçeriği ise aşağıdaki şekilde düzenleyelim.

@{
    ViewBag.Title = "Index";
}

<h2>Invoice List</h2>

<script src="~/Scripts/jquery-2.0.0.min.js"></script>
<script src="~/Scripts/knockout-2.2.1.js"></script>

<div>
    Gösterilmek istenen satır sayısı<br />
    <input type="text" id="txtRowSize" />
    <br />
    Başlangıç Noktası<br />
    <input type="text" id="txtRowIndex" />
    <br />
  <input type="button" id="btnGetInvoices" value="Get Inovices" data-bind="click:InvoiceModel.GetInvoices"/>
</div>

<table border="1">

<thead>
  <tr>
   <th>InvoiceId</th>
   <th>CustomerId</th>
   <th>InvoiceDate</th>
   <th>BillingAddress</th>
   <th>BillingCity</th>
   <th>BillingState</th>
   <th>BillingCountry</th>
   <th>BillingPostalCode</th>
   <th>Total</th>
  </tr>
</thead>

<tbody data-bind="template: { name: 'InvoiceDataModel', foreach: InvoiceModel.Invoices }">
</tbody>
</table>
<script type="text/html" id="InvoiceDataModel">
<tr>
  <td>
   <span style="width:100px;"  data-bind="text: $data.InvoiceId" />
  </td>
  <td>
   <span style="width:100px;"  data-bind="text: $data.CustomerId" />
  </td>
  <td>
   <span style="width:100px;"  data-bind="text: $data.InvoiceDate" />
  </td>
  <td>
   <span style="width:100px;" data-bind="text: $data.BillingAddress"  />
  </td>
  <td>
   <span style="width:100px;"  data-bind="text: $data.BillingCity" />
  </td>
      <td>
   <span style="width:100px;"  data-bind="text: $data.BillingState" />
  </td>
    <td>
   <span style="width:100px;"  data-bind="text: $data.BillingCountry" />
  </td>
    <td>
   <span style="width:100px;"  data-bind="text: $data.BillingPostalCode" />
  </td>
    <td>
   <span style="width:100px;"  data-bind="text: $data.Total" />
  </td>
</tr>
</script>

<script type="text/javascript">

var InvoiceModel = {
  Invoices:ko.observableArray([])
};

InvoiceModel.GetInvoices= function ()
{
    InvoiceModel.Invoices([]);

  var rowSize = $("#txtRowSize").val();
  var rowIndex = $("#txtRowIndex").val();

 
  var url = "/api/Invoices?$top=" + rowSize + '&$skip=' + (rowIndex * rowSize) + '&$orderby=InvoiceId';
 
  $.ajax({
   type: "GET",
   url: url,
   success: function (data)
   {
    InvoiceModel.Invoices(data);
   },
   error: function (err)
   {
    alert(err.status + "," + err.statusCode);
   }
  });
};
ko.applyBindings(InvoiceModel);
</script>

Kısa da olsa neler yaptığımıza bir bakalım. En önemli kısım tabi ki btnGetInvoices isimli düğmeye basıldıktan sonra çalışan kod içeriğidir. Burada kilit nokra url değişkenine atanan ifadedir. Dikkat edileceği üzere burada bir OData sorgusu oluşturulmakta ve top ile skip komutlarından yararlanılarak bir veri çekme işlemi gerçekleştirilmektedir. Elde edilen sonuç kümesinin ilgili veri kontrolüne bağlanması noktasında ise bir Ajaxçağrısı söz konusudur. success bloğunda sorgu sonuçlarının ilgili veri kontrolüne doldurulması işlemi icra edilmektedir.

Route Ayarları

Testlere başlamadan önce InvoiceList görünümü için Route ayarlarını güncellememizde yarar vardır. Bunun için App_Start klasöründe yer alan RouteConfig.cs içeriğini aşağıdaki gibi değiştirelim.

using System.Web.Mvc;
using System.Web.Routing;

namespace MvcApplication5
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "InvoiceList", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}

Buna göre uygulamamızı çalıştırdığımızda varsayılan olarak InoviceList ile ilişkili View’ a gidilecektir.

Test

İlk olarak Web API servisinin çalıştığından emin olmalıyız. Bu amaçla URL satırına http://localhost:46672/api/Invoices benzer bir ifade girildiğinde, aşağıdaki ekran görüntüsündekine benzer bir içeriğin üretilmiş olması gerekmektedir.

wapip_9

Eğer aşağıya doğru inerseniz tüm Invoice içeriğinin çekildiğini görebilirsiniz.

Ne var ki, Web API servisimiz için test noktasında önem arz eden bir mevzuda top, skip ve orderby komutlarına cevap verebiliyor olmasıdır. Örneğin http://localhost:46672/api/invoices?$top=3&$skip=10&$orderby=InvoiceIdşeklinde bir talep girdiğimizi düşünelim. Aslında bu talep ile servis tarafına şu mesajı iletmiş oluyoruz;

Önce Invoice satırlarını InvoiceId değerine göre bir diz bakalım. Sonra da10ncu indisten itibaren bana ilk 3 sıradakini getir.

Talebin işlenmesi sonrası tarayıcı üzerinde aşağıdakine benzer bir sonuç elde etmemiz gerekmektedir.

wapip_10

Şimdi asıl View içeriğini test ederek asıl senaryomuzu yürütebiliriz. Uygulamamızı varsayılan olarak çalıştırdığımızda Route tanımlaması nedeniyle doğrudan index.cshtml içeriğini görüyor oluruz.

wapip_11

Şimdi bazı veriler girerek örneğimizi test edelim.

wapip_12

Dikkat edileceği üzere 51 numaralı InvoiceId değerinden itibaren 5 adet satır getirilmesi istenmiş ve buna göre bir sonuç kümesi elde edilmiştir. Tabi burada önemli olan bir diğer nokta da fonksiyonun icra edilmesi sırasında SQL tarafında çalıştırılan sorgu ifadesidir. Özellikle bu sorgu ifadesinde doğru bir sayfalama yapılması da çok önemlidir. Bu amaçla SQL Server Profiler aracından yararlanabiliriz. Sonuç itibariyle aşağıdakine benzer bir T-SQL sorgusunun çalıştırılmış olduğunu görürüz.

wapip_13

Dikkat edileceği üzere row_number komutundan yararlanılarak gerçek anlamda sayfalama işlemi uygulanmıştır.

Sonuç

Görüldüğü üzere OData sorgu desteği sunan Asp.Net Web API servislerini kullanarak sayfalama işlemlerini gerçekleştirmek oldukça kolaydır. Bu iş de başrol oyuncu olan top, skip ve orderby anahtar kelimeleri bir OData standardı olduğundan, istemci tarafı Microsoft dışı bir platform da olabilir. Tabi burada tek bağlayıcı nokta SQL veritabanı ve Entity Framework kullanımıdır. Farklı veri kaynaklarında row_number gibi bir kullanım şekli söz konusu olmayabilir. Böyle bir vaka da tahmin edileceği üzere Web API Controller içerisindeki ilgili operasyon noktalarında müdahale de bulunmak gerekebilir(Araştırmadım benim yerime siz bu işi yapın Winking smile ) Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Recursive Fibonacci Neden Yavaş?

$
0
0

Merhaba Arkadaşlar,

Okulda“Algoritma ve Veri Yapıları” dersinde ya da C# benzeri nesne yönelimli(Object Oriented) bir dili öğrenmeye başladığımız ilk zamanlarda, karşımıza muhakkak Recursive fonksiyonlar çıkmıştır(Çıkmaya da devam edecektir). Hatta en meşhur olanları da, bir sayının faktöryelinin(6!=6x5x4x3x2x1=720 ve 0!=1) bulunması veya Fibonacci sayı dizisinin(0,1,1,2,3,5,8,13,21,34…, Fn=(Fn-1(+(Fn-2)) ardışıl olarak ekrana yazdırılmasıdır.

Recursive fonksiyonları ilk etapta anlamakta güçlük çeksek de, matematik ile bağdaşdırmakta zorlansak da, pek çok noktada hayat kurtaran ve gerekli olan metodlar olduklarını biliriz. Örneğin strateji oyunlarında, ikili(Binary) ağaç aramalarında, doğal dil işleme metodolojilerinde, Hanoi kuleleri gibi popüler problemlerin çözümünde ve daha pek çok yerde Recursive fonksiyonellikler söz konusudur.

Biz bu görsel dersimizde, Fibonacci sayı dizisinin Recursive fonksiyonlar ile elde edilmesi halinde sistemin neden ve nasıl yavaşladığını anlamaya çalıştık. Ardından da iteratif bir yaklaşım üzerinde durarak basit bir çıkarımda bulunduk.

Faydalı olması dileğiyle Winking smile

C#’ ın Enteresan Yanları

$
0
0

helpful_tips_image[İlk yazım tarihi 31 ekim 2012]

Merhaba Arkadaşlar,

Yazılım sektöründe yer alan bizler, mutlak suretle en az bir programlama dilini çok iyi seviyede öğrenmeye çalışır ve bunun için epey yoğun çaba sarf ederiz(Hatta değerli bir büyüğümüzün sözüne göre, hayatımızın her hangibir noktasında C veya C++ gibi bir dili öğrenmeye çalışmış ama hiç bir zaman iyi bir C/C++ geliştiricisi olamamışızdır)

Ne varki bazen dilin kullanılmayan pek çok özelliğini, zamanında öğrenmiş olsak dahi unutabiliriz. Hatta bazı ilginç olan yanlarını bugüne kadar hiç görmemiş, denememiş ya da duymamış olabiliriz.

İşte size C# dili ile ilişkili olarak pek çoğumuzun hatırından giden bir kaç enteresan vaka…

Bu arada C# diline ait geniş bir dökümantasyonu C:\Program Files\Microsoft Visual Studio 11.0\VC#\Specifications\1033 klasörü altında bulabilirsiniz Winking smile 527 sayfalık CSharp Language Specification isimli bu döküman, elinizin altındaydı her zaman. Başka bir kitaba ihtiyacınız yok. En azından başlangıç seviyesinde. Visual Studio 2012 kurulumu sonrası gelen bu dökümantasyon aslında C# 3.0 sürümünden beri de mevcut.

Bu yazımızda örnek 5 vaka çalışması üzerinde duruyor olacağız.

Vaka 1 – private olarak tanımlanmış bir alana(field), tanımlandığı sınıf dışından erişilemez.

Hep bu şekilde öğrendik. Genel olarak cümle kalıbı böyleydi. C# tarafından baktığımızda temel olarak 5 erişim belirleyicisi(Access Modifier) olduğunu biliyoruz. private, public, internal, protected ve son olarak da protected internal. private olarak tanımlanış üyelerin de(members), tanımlı oldukları yer dışından erişilemez olduklarını biliyoruz. Tabi istisnai durumlarda yok değil. Söz gelimi aşağıdaki kod parçasını göz önüne alalım.

csmyth_1

class Vehicle
{
   private Guid _vehicleId;

    public Vehicle()
    {
        _vehicleId = Guid.NewGuid();
    }

    public bool IsEqual(Vehicle vehicle)
    {
        return _vehicleId == vehicle._vehicleId;
    }
}

Vehicle sınıfı içerisinde tanımlanmış olan _vehicleId alanı private erişim belirleyicisi ile işaretlenmiştir. Ancak dikkat edilmesi gereken bir nokta vardır. Sınıf içerisinde tanımlanan IsEqual metodu parametre olarak başka bir Vehicle nesne örneğini almakta ve içerisinde bu örneğe ait _vehicleId alanını kullanmaktadır. Sarcastic smile Kullanabilmektedir. Derleyici buna kızmamaktadır. Peki uygulama tarafına geçelim ve aşağıdaki test kodlarını değerlendirelim.

static void Main(string[] args)
{
    #region Case 1 Test (Private Fields)

    Vehicle v1 = new Vehicle();
    Vehicle v2 = new Vehicle();
    Console.WriteLine(v1.IsEqual(v2));

    #endregion
}

Sonuç false dönecektir elbette.

csmyth_2

Dikkat edileceği üzere parametre olarak gelen vehicle değişkeni üzerinden, private olarak tanımlanmış _vehicleId alanına erişilebilmiştir. Tabi _vehicleId’ nin değeri, v2 isimli değişkene ait olarak üretilen Guid değeridir.

Dolayısıyla private alan kullanımları ile ilişkili olarak şunu da ifade edebiliriz. private tanımlanmış bir üyeye tanımlandığı sınıfa ait başka nesne örnekleri(Instance) üzerinden erişilebilinir.

Vaka 2 – Çok yerde faydasını gördüğümüz genişletme metodları(Extension Methods), Enum sabitlerine de uygulanabilir.

Genişletme metodları(Extension Methods) özellikle elimize kodları kapalı olarak gelen assembly dosyaları düşünüldüğünde, bunları ek fonksiyonellikler ile genişletmede kullanılan önemli kavramlardan birisidir. Çoğunlukla türetilemeyen veya az önce de bahsettiğimiz gibi kodları kapalı gelen sınıflar için kullanıldığına sıklıkla şahit oluru. (Extension Method’ lar ile ilişkili bir internet sitesi dahi vardırWinking smile ) Ancak bu özelliğin Enum sabitleri için de kullanılabildiğini fark etmiş miydiniz? Örneğin,

csmyth_3

enum VehicleType
    {
        Tank,
        MLRS,
        Artillary,
        Hummvy,
        HeavyAnrtillary,
        Destroyer,
        Carrier
    }

    static class EnumExtensions
    {
        publicstatic string GetDescription(this VehicleType vType)
        {
            string result = String.Empty;

            switch (vType)
            {
                case VehicleType.Tank:
                    result= "Paletli zırhlı tank. 135mm top";
                    break;
                case VehicleType.MLRS:
                    result = "Kundağı motorlu çoklu roket atar sistemi";
                    break;
                case VehicleType.Artillary:
                    result = "75mm - 205mm arası hafif topçu";
                    break;
                case VehicleType.Hummvy:
                    result = "Hummer jeep";
                    break;
                case VehicleType.HeavyAnrtillary:
                    result = "205mm üstü ağır kara topçusu";
                    break;
                case VehicleType.Destroyer:
                    result = "Güneş sınıfı yeni nesil zırhlı destroyer";
                    break;
                case VehicleType.Carrier:
                    result = "Nimitz sınıfı nükleer Uçak gemisi";
                    break;
            }

            return result;
        }
    }

VehicleType enum sabiti için EnumExtensions sınıfı içerisinde GetDescription isimli bir genişletme metodu tanımlanmıştır. Bu metod, enum sabiti ile ilişkili bir açıklamayı geri döndürecek şekilde tasarlanmıştır. (İlk parametre pek tabi this anahtar kelimesi ile başlamalıdır)

Enum sabitinin kodlarının kapalı bir Assembly içerisinde olduğunu farz edeceğimiz bir senaryoda, bu tip bir yaklaşım önemli esneklikler sağlayacaktır. Örneğin uygulanmasında da, bildiğimiz sınıf bazlı extension metodların kullanımından farklı bir yaklaşım söz konusu değildir.

csmyth_4

Vaka 3 – static alanların tanımlanma sıraları önemli midir?

Bunu yazdığımızda göre önemli olsa gerek Smile Haydi gelin aşağıdaki kod parçasını göz önüne alalım.

using System;
using System.Linq;
using System.Collections.Generic;

namespace HowTo_CSharp_Myths
{
    class Program
    {
        #region Case 3 : static alanların sırası önemli midir?

        static double r1 = Math.PI;
        static double r2 = r1;

        #endregion

        static void Main(string[] args)
        {            
            #region Case 3 Test (static Field order)

            Console.WriteLine(r2.ToString());

            #endregion
        }
    }
}

Program sınıfı içerisinde tanımlanmış olan r1 ve r2static alanlarının şu an pek dikkati çekmeyen sıraları aslında önemlidir. Yukarıdaki kod çalıştırıldığında, tahmin edileceği üzere ekrana pi değeri yazılacaktır. Nitekim r2 tanımlanırken, r1’ in kendisine atandığı görülmektedir. r1’ de zaten Pi değerini taşımaktadır. İşte çalışma zamanı çıktısı.

csmyth_5

Peki static alanların sırasını değiştirirsek? Sarcastic smile

using System;
using System.Linq;
using System.Collections.Generic;

namespace HowTo_CSharp_Myths
{
    class Program
    {
        #region Case 3 : static alanların sırası önemli midir?

        // static double r1 = Math.PI;
        // static double r2 = r1;

        // Sırayı değiştirdik
        static double r2 = r1;
        static double r1 = Math.PI;

        #endregion

        static void Main(string[] args)
        {            
            #region Case 3 Test (static Field order)

            Console.WriteLine(r2.ToString());

            #endregion
        }
    }
}

Yine ekrana Pi değerinin yazılmasını bekleyebiliriz öyle değil mi? Ama,

csmyth_6

sıfır yazmıştır.

Görüldüğü gibi sıralama static alanların kullanıldığı durumda önemlidir. Nitekim r2 ilk tanımlandığında r1 değerini alırken, r1’ in o anki değeri varsayılan int için 0’ dır. Dolayısıyla, sonraki sırada yapılan r1 tanımlanması ve atamasında verilen Pi değeri sadece r1 için söz konusudur.

Peki bu durum static olmayan alanlar için de geçerli midir acaba? Bunu denediğimizde sizce ne olur?

csmyth_7

Böyle bir atamaya zaten bu Console uygulaması açısından bakıldığında, derleme zamanı izin vermeyecektir. Sıralamayı değiştirip double r2=r1; ifadesini bir alt satıra geçirseniz dahi durum değişmez.

Vaka 4 – Indeksleyicilerde params anahtar kelimesi de kullanılabilir

Indeksleyiciler(Indexers) bildiğiniz üzere sınıf örnekleri üzerinden köşeli parantez operatörünü kullanarak iç üyelere erişimde yardımcı olmaktadırlar. Özelliklere(Properties) benzer şekilde get ve set blokları vardır ve çoğunlukla içsel dizi bazlı sınıf üyelerini ele alırlar. Fakat istenirse indeksleyicilerde params anahtar sözcüğü de kullanılabilir. Aşağıdaki örneği göz önüne alalım.

csmyth_8

ve kod parçamız

class Company
{
    private List<Person> _workers = new List<Person>(8);

    public Person this[int id]
   {
        get {
            return
                _workers
               .Where(p=>p.PersonId==id)
                .FirstOrDefault();
        }
        set {
            _workers.Add(value);
        }
    }

   public IQueryable<Person> this[params int[] workerIds]
    {
        get
       {
           return workerIds
               .Select(id => _workers.Where(p=>p.PersonId==id).FirstOrDefault())
                .AsQueryable();
        }
    }
}

class Person
{
    public int PersonId { get; set; }
    public string Nickname { get; set; }

    public override string ToString()
    {
        return string.Format("[{0}]-{1}", PersonId.ToString(), Nickname);
    }
}

Bu örnekte Person tipinden generic bir List alanının indeksleyiciler ile kullanımına yer verilmiştir. Sadece tek bir int parametre alan versiyon, standart bir indeksleyici kullanımını göstermektedir. Diğer yandan params anahtar kelimesinin kullanıldığı ikinci versiyon, asıl odaklanacağımız yerdir. Company sınıfının örnek kullanımını göz önüne alırsak konuyu daha iyi irdeleyebiliriz.

    static void Main(string[] args)
    {
        #region Case 4 (Indeksleyicilerde params)

        Company company = new Company();

        company[0] = new Person { PersonId = 1, Nickname = "Şimşir mek cin" };
        company[1] = new Person { PersonId = 3, Nickname = "Sir Axelroad" };
        company[2] = new Person { PersonId = 4, Nickname = "Raul Şarul" };
        company[3] = new Person { PersonId = 2, Nickname = "William" };
        company[4] = new Person { PersonId = 6, Nickname = "Meytır" };

        var subSet = company[1, 3, 4];
        foreach (var person in subSet)
        {
            Console.WriteLine(person.ToString());
        }

        var singlePerson = company[6]; // params kullanılmayan indeksleyici çağırılır
        Console.WriteLine(singlePerson.ToString());

        #endregion
    }
}

Dikkat edileceği üzere 0, 1, 2, 3 ve 4 numaralı indislere farklı Person nesne örnekleri atanmıştır. subSet değişkeninin elde ediliş şekline dikkat ettiniz mi Winking smileİşte burada params anahtar kelimesinin etkisi görülmektedir. Senaryomuza göre burada PersoneId değeleri gönderilmiş ve ona uygun olacak bir sonuç alınmıştır.

singlePerson değişkeninin elde edilmesi sırasında ise, params anahtar kelimesinin kullanılmadığı indeksleyici versiyonu çalışacaktır. İşte uygulamanın çalışma zamanı sonuçları.

csmyth_9

Vaka 5 – Hiç String sınıfının Intern veya IsInterned metodlarını kullandınız mı/duydunuz mu? (.Net odaklı bir fark ama olsun)

string değişkenler bilindiği üzere System.String sınıfı ile temsil edilirler. Referans tipi olan string değişkenler esasında maliyetleri zaman zaman yüksek olabilecek örneklerdir. Bazı durumlarda(ve hatta çok nadiren de olsa) programlarımızın içerisinde aynı veriyi tutan string değişkenlerin n sayıda örneğine ihtiyaç duyabiliriz. Milyonlarca string değişkeniniz olduğunu ve her birinin aslında aynı veri içeriğini tuttuğunu düşünün. Bu çok doğal olarak CLR(Common Language Runtime) tarafında önemli bir yük anlamına gelmektedir.

Aslında string tipteki değişkenler için CLR bu performans handikapını ortadan kaldırmak adına akıllı bir yol izler ve söz konusu referansları bir havuz da (Intern pool) tutar. Çalışma zamanında yeni bir string değişken gündeme geldiğinde içeriği bu havuzdan kontrol edilir. Kısacası CLR aynı içeriğe sahip olan string değişkenler için unique bir referans tutar.

Peki ya elimizdeki bir string’ in eğer var ise unique olan referansını buradan nasıl alabiliriz? İşte bu noktada devreye String.Intern metodu girer.

Eğer söz konusu içerik InternPool içerisinde var ise, onun referansı ile bir string değişken elde edilir. Yoksa da bu içerik, Intern Pool’ a atılır ve unique bir referans olarak tutulmaya devam edilir. IsInterned metodu ise söz konusu içerik eğer Intern Pool içerisinde yer alıyorsa yine referansı döndürecek ama yoksa null değerini verecektir.

İşte örnek bir kod parçası ve çalışma zamanında elde edilen sonuçlar.

string name1 = "burak"; // name1 Intern Pool içerisinde set edildi
string name2 = String.Intern("burak");

if(name1==name2)
    Console.WriteLine(true);
else
    Console.WriteLine(false);

string name3 = String.Intern("burak s.");
if(name2==name3)
    Console.WriteLine(true);
else
    Console.WriteLine(false);

string name4 = new string(new char[]{'s', 'e', 'l', 'i', 'm'});
Console.WriteLine("selim pool {0}",String.IsInterned(name4)==null?"da değil":"da");
String.Intern(name4);
Console.WriteLine("Intern çağrısı sonrası. selim pool {0}", String.IsInterned(name4) == null ? "da değil" : "da");

csmyth_10

Bu yazımızda şöyle kıyıda köşede kalmış olabilecek bir kaç dil kabiliyetine yer vermeye çalıştık. Kimbilir gözümüzden kaçan, dikkat etmediğimiz veya kullanmadığımız için zamanla unuttuğumuz neler var. Biraz ilham vermiş olabilirim. Siz de araştırın bakalım. Bir başka yazımızda görüşmek dileğiyle hepinize mutlu günler dilerim Winking smile

HowTo_CSharp_Myths.zip (42,82 kb)

[İlk yazım tarihi 31 ekim 2012]

Asp.Net 4.5 için Yeni Nesil Doğrulama(Validation)

$
0
0

300px-Opening_crawlMerhaba Arkadaşlar,

Bilişim sektöründe yer alan ve özellikle 70li yıllarda doğanların neredeyse tamamı bu efsane cümleyi bilir.

A long tim ago in a galaxy far, far away…

Hikaye hep yazılı bir anlatım ile başlar ve daha sonra ekran yukarıdaki yıldızlardan aşağıya doğru inerek devam eder. Sornasında ya bir uzay mekiğinin kaçıs sahnesi ya da imparatorluk güçleri ile isyancılar arasındaki savaşla karşı karşıya kalırız.

İşte geçen gün özlediğim Star Wars serilerinden birisini izlerken bir den kendimi bilgisayarımın başında ve başka bir hikayenin giriş noktasında buldum.

Karşımdaki ekran da karalara bağlamış, alacalı bulacalı bir geliştirme penceresi duruyordu. İçerisinde ise HTML ve Asp.Net karışımı bir şeyler…

Hikayenin Başı

Her şey uzun bir zaman önce değil ama kısa bir süre önce Asp.Net 4.5 tabanlı bir  Empty Web Application açmamla başlamış ve sonrasında olanlar olmuştuConfused smile Aslında senaryo gereği çok basit olarak bir web form üzerinde doğrulama kontrollerini kullanacaktım. Bunun için Visual Studio 2012 ortamında  Asp.NetEmpty Web Application tipinden bir proje oluşturdum ve aşağıdaki Web Form içeriğini tasarladım.

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

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
   
        Nickname :
        <asp:TextBox ID="txtNickname" runat="server"></asp:TextBox>
&nbsp;<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server"ControlToValidate="txtNickname" ErrorMessage="Nicknamegirilmeli"></asp:RequiredFieldValidator>
        <br />
        Password :
        <asp:TextBox ID="txtPassword" runat="server"></asp:TextBox>
&nbsp;<asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server"ControlToValidate="txtPassword" ErrorMessage="Şifre girilmeli"></asp:RequiredFieldValidator>
        <br />
        <asp:Button ID="btnLogin" runat="server" OnClick="btnLogin_Click" Text="Login" />   
    </div>
    </form>
</body>
</html>

Web form içeriğinin görsel tasarımı ise aşağıdaki ekran görüntüsündekine benzemişti.

ngv_1

Ekranın görevi oldukça basitti. Kullanıcıdan Nickname ve Password bilgisi ile giriş yapması isteniyordu. Eğer bu TextBox kontrollerinin içeriği boş bırakılırsa da RequiredFiledValidator kontrolleri devreye girerek kullanıcıyı uyarmaktaydı.

İlk Çalışma

Herşey bana göre son derece normaldi ancak çalışma zamanı böyle demiyordu. Sonuç aşağıdaki ekran görüntüsünde ki gibi olmuştu Thinking smile

ngv_2

Tabi ilk dikkatimi çeken nokta Unobtrusive olarak yazılan ve telafüz etmesini halen daha başaramadığım kelime idi. “Dikkati çekmeyen”, “mütevazi”, “kendi halinde”, “fark edilmeyen”, “kolay görülmeyen” gibi Türkçe karşılıkları olan kelimenin Asp.Net açısından önemini araştırırken bakın neler buldum Winking smile

Peki neden böyle oldu?

İlk olarak zamanı geriye alıp Asp.Net 4.5 öncesine bakmamız gerekiyor(Zamanı geriye almak keşke bir Framework değişikliği kadar kolay olsa değil mi?) Bu nedenle projeyi .Net Framework 4.0 odaklı hale getirip yeniden derledim.

ngv_4

ve tabi çalıştırdım Smile

ngv_3

Çalışma zamanında hiç bir sorun yoktu. Her şey yolunda görünmekteydi. Doğrulama(Validation)kontrolleri de devreye girmiş durumdaydı. Sayfanın istemci tarafına gönderilen kaynak içeriğine bakıldığında doğrulama işlemleri için üretilmiş bir takım Javascript kod parçaları olduğu ve CDATA olarak entegre edildiği kolaylıkla görülebiliyordu.

ngv_5

Bilindiği üzere bu sayede istemci tarafı sunucuya gönderilmeden de doğrulama işlemleri yerine getirilebilmektedir.

Web tarafında doğrulama işlemleri istemci tarafında başlamakta ama sunucu tarafında da bir kontrol yapılmaktadır. Bunun en büyük sebebi istemci tarafının Javascript yürütme gibi bir desteği olmaması halinde karşı tedbir alınmak istenmesidir.

Asp.Net 4.5 ile birlikte ise daha önceden kullanılan javascript odaklı sistem yerine, varsayılan olarak HTML 5’ in data-val-controltovalidate, data-val-errormessage, data-val, data-val-evaluationfunction, data-val-initialvalue gibi nitelikleri(attribute) ve jQuery kütüphanesi ele alınmaktadır. Dolayısıla Asp.Net 4.5 tipinden bir Empty Web Application söz konusu olduğunda ve doğrulama işlemlerini uygulamak istediğimizde, bazı ayarlamaları yapmamız söz konusudur.(Bu işlemlere WebForms tipinden bir Asp.Net uygulaması açtığınızda ihtiyaç duymayabilirsiniz)

Adımlar

Tekrar Target Framework’ ü .Net Framework 4.5’ e çekelim. Sonrasında ise UnobtrusiveValidation akışı için ihtiyacımız olan jQuery kütüphanelerini NuGet paket yönetim aracı ile projemize dahil edelim. (Son sürümleri eklememiz daha akıllıca olabilir)

ngv_6

Bu install işlemi sonrası ihtiyacımız olan jQuery kütüphaneleri projeye ilave edilmiş ve Scripts klasörü içerisine atılmış olacaktır.

ngv_7

ScriptResourceDefinition Bildirimleri

Sonrasında tek yapılması gereken uygulamanın başlatıldığı bir yerde doğrulama işlemleri için gerekli path tanımlamalarının ScriptResourceDefinition sınıfı için yapılmasıdır. En uygun yer global.asax.cs içerisindeki Application_Start olay metodudur(global.asax varsayılan olarak bu proje şablonunda yer almamaktadır. Bir başka deyişle ilave etmeniz gerekmektedir) Bu metod içeriğini aşağıdaki gibi düzenleyerek devam edelim.

using System;
using System.Web.UI;

namespace WebApplication2
{
    public class Global : System.Web.HttpApplication
    {

        protected void Application_Start(object sender, EventArgs e)
        {
            ScriptResourceDefinition jQuery = new ScriptResourceDefinition();
           jQuery.Path = "~/scripts/jquery-2.0.0.min.js";
            jQuery.DebugPath = "~/scripts/jquery-2.0.0.js";
            jQuery.CdnPath = "http://ajax.microsoft.com/ajax/jQuery/jquery-2.0.0.min.js";
            jQuery.CdnDebugPath = "http://ajax.microsoft.com/ajax/jQuery/jquery-2.0.0.js";
            ScriptManager.ScriptResourceMapping.AddDefinition("jquery", jQuery);
        }

}

Bir takım path tanımlamaları yapıldığı görülmektedir. jQuery için yapılan tanımlamalar haricinde Content Delivery Network(CDN) için de bazı path bildirimleri belirtilmiştir. Senaryo da, MicrosoftAJAX CDN’ leri kullanılmaktadır. İlgili path tanımlamaları ScriptResourceDefinition sınıf örneği için yapıldıktan sonra, ilgili nesne örneğinin ScriptResourceMappingözelliğine eklenmesi yeterlidir.

Yeni Bir Test

Uygulamamızı tekrar çalıştırıp test edelim. Bir hata oluşmayacak ve doğrulama kontrollerinin çalıştığı gözlemlenecektir.

ngv_8

Tabi ki bizim için daha önemli olan istemci tarafına giden kod içeriğidir. Eğer kaynak koda bakarsak aşağıdaki sonuçlarla karşılaşırız.

ngv_9

Hımmm bir terslik var gibi. Sanki buralar da HTML 5’ den hiç bir eser yok Disappointed smile 

Bu son derece doğal çünkü Web Form’ lar için bu yeni stil doğrulama işleminin yapılacağını bir yerler de belirtmemiz gerekiyor. web.config dosyası tahmin edileceği üzere en uygun yer ve içeriğini aşağıdaki gibi değiştirmemiz bu senaryo için yeterli.

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5"/>
    <httpRuntime/>
    <pages controlRenderingCompatibilityVersion="4.0"/>
  </system.web>
  <appSettings>
    <add key="ValidationSettings:UnobtrusiveValidationMode" value="WebForms" />
  </appSettings>
</configuration>

appSettings sekmesinde yer alan ValidationSettings:UnobtrusiveValidationModekey değeri, Asp.Netçalışma zamanı için anlamlıdır. Value niteliğine WebForms dışında None değeri de verilebilmektedir. None değerinin verilmesi halinde tahmin edileceği üzere eski stil doğrulama sürecine geçilmektedir. Şu anki haliyle de yeni stilin kullanılacağı belirtilmektedir.

Bir Test Daha

Öyleyse uygulama tekrardan çalıştırılır ve istemci tarafına giden kaynak kod içeriğine bakılır.

ngv_10

İstediğimiz olmuştur ve istemci tarafındaki doğrulama işlemleri için HTML 5 nitelikleri devreye girmiştir. Kontrolün boş geçilmemesi için data-val-evaluationfunction niteliğinin değeri ele alınmaktadır. Hangi kontrolün denetleneceği bilgisi için data-val-controltovalidate niteliği kullanılmaktadır. Hata mesajı ise data-val-errormessage ile belirtilir vb…

Size tavsiyem diğer doğrulama kontrollerini de işin içine katarak senaryoyu genişletmeniz ve özellikle data-val-evaluationfunction değerlerinin nasıl üretildiğine bakmanız yönünde olacaktır.

Kıssadan Hisse

Yeni nesil doğrulama stratejisi için son teknoloji ürünüdür diyebilir miyiz acaba? Bence evet Winking smile Hem HTML 5’ e hem de jQuery’ ye yatırım yapmış bir doğrulama süreci söz konusu. Üstelik yeni nesil çıktılar da, doğrulama operasyonları adına Javascript kullanımı(CDATA içerisindeki kısımlar) mevcut değil. Doğrulama bilgisi tamamen HTML 5 içerisine, nitelik-değer(key-value) bazlı olarak yıkılmış durumda. Elbette bunun en büyük artısı sayfa cevap boyutunun(Page Response Size) küçülmüş olması. (Elbette tarayıcı desteği de önem arz eden bir konu)

Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Hanoi Towers Probleminin Recursive Çözümü

$
0
0

Merhaba Arkadaşlar,

Bu görsel dersimizde eğlenceli Matematik oyunlarından birisi olan Hanoi Kuleleri problemini, Recursive bir metod yardımıyla nasıl çözebileceğimizi incelemeye çalışıyoruz. İlk olarak problemi tanımlıyor ve oyunun kurallarına kısaca bakıyoruz. Sonrasında ise 3 disk için gerçekleştirilen çözümü simüle ediyoruz. Problemin çözümü için kullanılan alt problem parçalarını tanımlıyor ve C# ile kodlama işlemlerini gerçekleştirerek uygulamamızı inşa ediyoruz.

Faydalı olması dileğiyle Winking smile


Tek Fotoluk İpucu 100–AutoMapper Kullanımı

$
0
0

Merhaba Arkadaşlar,

Bildiğiniz üzere şu yazımızda nesneler arası özellik(Property) eşleştirmelerinin nasıl yapılabileceğini incelemeye çalışmıştık. Ancak işin çok daha profesyonel bir boyutu var. Örneğin tipler arası özellik adları birbirlerinden farklı olabilir ve bu nedenle bir haritayı önceden söylemeniz gerekebilir. Neyseki NuGetüzerinden yayınlanan AutoMapper kütüphanesi çok gelişmiş özellikleri ile buna imkan vermektedir. Söz gelimi aşağıdaki fotoğraf özellik adlarının farklı olması halinde bile AutoMapper ile başarılı bir şekilde eşleştirme yapılabileceğini göstermektedir.

tfi_100

Denemeden önce en azından install-package AutoMapper komutunu Package Manager Console’ dan çalıştırıp ilgili kütüphaneyi yüklemeyi unutmayınız.

Tek Fotoluk İpucu 101–Team Project Process Template

$
0
0

Merhaba Arkadaşlar,

Bazı komut satırı araçları oldukça işlevseldir ve çoğunlukla tercih edilir. Örneğin TFSüzerinde bir Team Project silinmek istendiğinde, tfsdeleteproject komut satırı aracına başvrulur. Peki şirketinizde kurulu olan TFSüzerindeki TeamProjectörneklerinin kullandıkları ProcessTemplate’ leri yine bir komut satırı aracı ile öğrenmek isteseydiniz, nasıl bir yol izlerdiniz? Aşağıdaki fotoğraf size bir ipucu verebilir belki de Winking smile

tfi_101

Bu örnek farklı fikirlerinde doğmasına yol açabilir. Örneğin TFS sunucusunda ne kadar TeamProject varsa, her birinin ProcessTemplate bilgisini gösteren bir Windows uygulaması(hatta Web’ de olur) geliştirmeyi deneyebilirsiniz Winking smile

Asp.Net Routing – Hatırlamak

$
0
0

obliviousMerhaba Arkadaşlar,

Geçtiğimiz günlerde şirkette çok küçük bir web uygulamasına ihtiyaç duyuldu. Neredeyse bir günlük geliştirme maliyeti olan, küçük bir departmanın önemli bir gereksinimi karşılayacaktı. Tabi insan uzun zaman kodlama yapmayınca veya kodlamaya ara verince bazı temel bilgileri de unutabiliyor.

Ben de kafayı Team Foundation Server entegrasyonu, SOA mimarisi ve Scrum gibi metodolojiler ile bozunca, zihinsel diskimdeki ana partition’ a yeni bilgilerin yazıldığına ve eskilerinin yerinde yeller estiğine şahit oldum. Ama malum, günümüz teknolojilerinde bilginin tamamını ezberlemeye çalışmak yerine, en doğrusuna en hızlı şekilde nasıl ulaşabileceğimizi bilmek daha önemli. İşte bu felsefeden yola çıkıp dedim ki, şu Asp.Net Routing konusunu bir hatırlayayım ve hatta kayıt altına alayım. İşte hikayemiz böyle başladı Smile

Asp.Net MVC’ nin en cazip yanlarından birisi sanırım sağladığı URL eşleştirme(Routing) sistemidir. Özellikle Search Engine Optimization(SEO) kriterleri göz önüne alındığında, orders.aspx?categoryName=Beverages&shipCity=istanbul&orderNumber=12903 gibi bir ifade yerine, orders/beverages/istanbul/12903 şeklinde bir URLçok daha değerlidir.

Bilindiği üzere Asp.Net 4.0 sürümü ile birlikte, URL ve asıl kaynak(aslında yönlendirme sonucu gidilmesi gereken bir aspx sayfa kodu düşünebiliriz) eşleştirmelerinde kullanılan yönlendirme işlemleri oldukça kolaylaştırılmıştır. İşte bu yazımızda, biraz temelleri hatırlamaya çalışacak ve SEO’cu arama motorlarının olmassa olmaz isterlerinden birisi olan Routing konusunu dört basit örnek üzerinden inceleyeceğiz. İlk olarak senaryomuza bir göz atalım.

Senaryo

Senaryomuzda veri kaynağı olarak emektar Northwind veritabanını kullanacağız. Örneklerimizde hem Entity Framework kullanacağımız hem de doğrudan SQL sorgusu çalıştıracağımız bir vakamız yer alacak. Temel olarak amacımız aşağıdaki ekran görüntüsünde yer alan URL eşleştirmelerini web uygulaması üzerinden işlettirmek.

route_2

Dikkat edileceği üzere URL satırından girilecek olan anlamlı ifadeler, aslında arka planda bir eşleştirme tablosuna uygun olacak şekilde ilgili kaynaklara yönlendirilmekteler. Örneğin beverages isimli kategoride yer alan ürünlerin listelenmesi için yazılan urunler/beverages sorgusu, sisteme daha önceden öğretilen Urunler/{CategoryName}üzerinden geçerek urun.aspx sayfasına yönlendiriliyor. Çok doğal olarak ilgili sayfa içerisinde, CategoryName değerine bakılarak bir sonuç kümesinin sunulması gerekiyor.

Route Eşleştirmelerinin Ayarlanması

Bu işlem için global.asax.cs dosyasında aşağıdaki kodlamaları yapmamız gerekmektedir.

using System;
using System.Web;
using System.Web.Routing;

namespace HowTo_EasyRouting
{
    public class Global
        : HttpApplication
    {
        private void SetRouteMaps()
        {
            RouteTable.Routes.MapPageRoute("Varsayilan", "", "~/kategori.aspx");
            RouteTable.Routes.MapPageRoute("Kategoriler", "kategoriler", "~/kategori.aspx");
            RouteTable.Routes.MapPageRoute("KategoriBazliUrunler","urunler/{CategoryName}","~/urun.aspx");
            RouteTable.Routes.MapPageRoute("SehirBazliSiraliMusteriler", "musteriler/{City}$orderby={FieldName}", "~/musteri.aspx");
            RouteTable.Routes.MapPageRoute("SehirBazliSiparisler", "siparisler/{ShipCity}", "~/siparis.aspx");
        }

        protected void Application_Start(object sender, EventArgs e)
        {
            SetRouteMaps();
        }
    }
}

Application_Start olay metodu bilindiği üzere, Web uygulaması ayağa kalktığında devreye girmektedir. Dolayısıyla uygulamanın başladığı bir yerde, URL eşleştirme tanımlamalarını yapmak son derece mantıklıdır. Olayın ana kahramanı RouteTable sınıfıdır. Söz konusu tipin static olarak erişilebilen Routesözelliği bir RouteCollection referansını işaret etmektedir. Bu koleksiyon tahmin edileceği üzere URL ile asıl kaynak eşleştirmelerini taşımaktadır. Bu nedenle MapPageRoute metodundan da yararlanılarak gerekli eşleştirme bilgileri koleksiyona eklenir.

İlk satır ile Root URL adresine gelen bir talebin doğrudan kategori.aspx sayfasına yönlendirilmesi gerektiği ifade edilmektedir. İkinci satırda ise web kök adresini takiben kategoriler şeklinde gelen bir ifadenin gelmesi halinde yine, kategori.aspx sayfasına gidilmesi gerektiği belirtilmektedir.

KategoriBazliUrunler ismi ile tanımlanmış eşleştirmeye göre, urunler/{CategoryName}şeklinde gelen talepler urun.aspx sayfasına yönlendirilmektedir. İlginç kullanımlardan birisi de SehirBazliSiraliMusteriler isimli eşleştirmedir. Burada City ve FieldName isimli iki Route parametresi söz konusudur. İfade ise size sanıyorum tanıdık gelecektir. Neredeyse bir REST servis sorgusuna(örneğin OData sorgusuna) oldukça yakın değil mi? Nerd smile

Şimdi bu durumları kod tarafında nasıl karşılayacağımızı örnek bir Asp.Net uygulaması üzerinden incelemeye çalışalım.

Birinci Durum

İlk olarak kategori.aspx sayfasına doğru yapılacak yönlendirmeleri ele almaya çalışacağız. Bunun için web uygulamamıza kategori.aspx isimli bir sayfa ekleyip içeriği ile kod tarafını aşağıdaki gibi geliştirelim.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Kategori.aspx.cs" Inherits="HowTo_EasyRouting.Kategori" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div id="divCategories" runat="server" style="background-color:lightcyan">
       
    </div>
    </form>
</body>
</html>

kod tarafı

using System;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace HowTo_EasyRouting
{
    public partial class Kategori
        : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                using (NorthwindEntities context = new NorthwindEntities())
                {
                    var categories = from c in context.Categories
                                     orderby c.CategoryName
                                     select new
                                     {
                                         c.CategoryID,
                                         c.CategoryName
                                     };

                    foreach (var category in categories)
                    {
                        HyperLink categoryLink = new HyperLink();
                        categoryLink.NavigateUrl = GetRouteUrl("KategoriBazliUrunler", new { CategoryName = category.CategoryName });
                        categoryLink.Text = string.Format("[{0}]-{1}<br/>", category.CategoryID.ToString(), category.CategoryName);
                        divCategories.Controls.Add(categoryLink);
                    }
                }
            }
        }
    }
}

Aslında kategori.aspx sayfasında tipik olarak Entity Framework odaklı bir sorgulama gerçekleştirilmekte ve kategori adları birer HyperLink bileşeni olarak div içerisine eklenmektedir. Konu itibariyle işin önemli olan kısmı ise HyperLink bileşeninin NavigateUrlözelliğine GetRouteUrl metodu sonucunun atanmasıdır.

GetRouteUrl metodu dikkat edileceği üzere iki parametre alır. İlk parametre route adıdır. Yazdığımız değere göre urunler/{CategoryName}şeklindeki atama değerlendirilir. İkinci parametre ise bu Route içerisinden kullanılmak istenen değişken adı ve değerini içeren nesnenin örneklendiği kısımdır. CategoryName tahmin edileceği üzere Route tanımı içerisindeki parametre adıdır. Değeri ise zaten LINQ(Language INtegrated Query) sorgusu içerisinden elde edilmektedir. İkinci parametre object tipinden olduğundan bir isimsiz tip(anonymous type) ataması yapılabilmiştir. Bu nedenle Route içerisinde birden fazla parametre olması halinde, isimsiz tipin de birden fazla özellik içermesi gerektiğini ifade edebiliriz.

İlk durumda herhangibir sayfa talep edilmediğinde veya kök web adresi ardından /kategorilerşeklinde bir URL ifadesi kullanıldığında, aşağıdaki ekran görüntüsünde yer alan sonuçlar ile karşılaşırız.

route_3

Dikkat edilmesi gereken en önemli nokta, her hangi bir bağlantı üstüne gelindiğinde oluşan sorgu adresidir. Örneğin Condiments için http://localhost:54605/urunler/condimentsşeklinde bir URL tanımı oluşmuştur. Peki bu bağlantıya tıklanırsak ne olur? Who me?

İkinci Durum

kategori.aspx sayfasında bir bağlantıya tıklandığında, HyperLink bileşeninin NavigateUrlözelliğinin sahip olduğu değerin Route tablosundaki eşleniğine bakılmalıdır. Yaptığımız tanımlamalara göre urun.aspx sayfasına gidilmesi beklenmelidir(KategoriBazliUrunler isimli Route tanımına dikkat edin) Buna göre urun.aspx sayfasının içeriğini aşağıdaki gibi düzenleyebiliriz.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Urun.aspx.cs" Inherits="HowTo_EasyRouting.Urun" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1 style="color:purple">
         <asp:Label ID="lblCategoryName" runat="server" /></h1>
        <br />
        <asp:GridView ID="grdUrunler" runat="server" />
    </div>
    </form>
</body>
</html>

kod tarafı

using System;
using System.Linq;
using System.Web.UI;

namespace HowTo_EasyRouting
{
    public partial class Urun
        : Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (RouteData.Values["CategoryName"] != null)
            {
                string categoryName = RouteData.Values["CategoryName"].ToString();
                using (NorthwindEntities context = new NorthwindEntities())
                {
                    var products = from p in context.Products.Include("Product")
                                   where p.Category.CategoryName == categoryName
                                   orderby p.ProductName
                                   select new
                                   {
                                       p.ProductID,
                                       p.ProductName,
                                       p.UnitPrice
                                   };

                    grdUrunler.DataSource = products.ToList();
                    grdUrunler.DataBind();
                }
            }
            else
                Response.Redirect(GetRouteUrl("Kategoriler", null));
        }
    }
}

Tabi bu sayfaya gelindiğinde aslında Route tanımlaması içerisinde yer alan parametre değerinin okunması gerekmektedir. Bu sebepten sayfanın RouteDataözelliğinden hareket edilerek RouteValueDictionary tipinden olan Valuesözelliğine gidilir ve indeksleyiciye verilen CategoryName alanının var olup olmadığına bakılır. Malum urun.aspx sayfasına farklı bir şekilde erişilmek istenebilir ve CategoryName değeri null olarak gelebilir. Bu nedenle bir null değer kontrolü ardından Entity sorgulama işlemi yapılmıştır. RouteData.Values[“CategoryName”] ile URL satırındaki kategori adı bilgisi alındıktan sonra standart olarak bir Entity sorgusu icra edilmektedir. Eğer kategori adı null olarak gelirse bu durumda varsayılan URL eşleştirilmesi nedeniyle kategorilerin gösterildiği sayfaya gidilir.

route_4

Üçüncü Durum

URL eşleştirmelerinden SehirBazliSiraliMusteriler isimli olanı, iki adet route parametresi içermektedir. Burada başta da belirttiğimiz üzere OData sorgularına benzer bir ifade tanımlanmıştır. Eşleştirme bilgisine göre musteri.aspx sayfasına doğru bir yönlendirme söz konusudur. musteri.aspx içeriğini aşağıdaki gibi geliştirdiğimizi düşünelim.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Musteri.aspx.cs" Inherits="HowTo_EasyRouting.Musteri" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1 style="color:purple">Customers</h1>
        <asp:GridView ID="grdCustomer" runat="server" />
    </div>
    </form>
</body>
</html>

kod tarafı

using System;
using System.Linq;
using System.Reflection;
using System.Web.Routing;

namespace HowTo_EasyRouting
{
    public partial class Musteri : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
           if(RouteData.Values["City"]!=null
                || RouteData.Values["FieldName"]!=null)
            {
                string cityName=RouteData.Values["City"].ToString();
                string fieldName=RouteData.Values["FieldName"].ToString();
               
                using(NorthwindEntities context=new NorthwindEntities())
                {
                    var customers = context
                        .Customers
                        .Where(c => c.City == cityName)
                        .OrderBy(GetField<Customer>(fieldName))
                        .Select(c => new
                        {
                            ID=c.CustomerID,
                            Title=c.ContactTitle,                         
                            Contact=c.ContactName,
                            Company=c.CompanyName,
                            c.City
                        });

                    grdCustomer.DataSource = customers.ToList();
                    grdCustomer.DataBind();
                }
            }
        }

        publicstatic Func<T, string> GetField<T>(string fieldName)
        {
            PropertyInfo pInfo=typeof(T).GetProperty(fieldName);
            if (pInfo == null)
                pInfo = typeof(T).GetProperty("CustomerID");

            return o => Convert.ToString(pInfo.GetValue(o, null));        
        }
    }
}

RouteData.Valuesözelliğinden yararlanılarak CityName ve FieldName değerlerinin null olup olmamasına göre bir kod parçası çalıştırılmaktadır. Bir önceki örnekten farklı bir durum olmasa da Entity sorgusunda OrderByextension metodunu nasıl kullandığımıza dikkat etmenizi rica ederim. İşte bu vakaya ait örnek ekran çıktıları.

Londra’ daki müşterilerin CustomerId bilgilerini göre sıralı olarak çekilmesi

route_5

Londra’ daki müşterilerin ContactName bilgisine göre sıralı olarak çekilmesi

route_6

Dördüncü Durum

Son vakada bir Route parametrenin her hangi bir veri bağlı kontrol ile nasıl ilişkilendirilebileceğini görmeye çalışacağız. Örneğin bir SqlDataSource bileşenindeki Select sorgusuna ait Where koşullarını Route parametreler ile ilişkilendirebiliriz. Bu durumu siparis.aspx sayfası içerisinde ele almaya çalışalım. Siparis sayfasına gelinebilmesi için Route tablo tanımlamalarına göre /siparisler/{ShipCity}şeklinde bir URL talebinin gönderilmesi gerekmektedir.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Siparis.aspx.cs" Inherits="HowTo_EasyRouting.Siparis" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1 style="color:purple">Siparişler</h1>
        <asp:GridView ID="grdOrders" runat="server" AllowPaging="True" AutoGenerateColumns="False" DataKeyNames="OrderID" DataSourceID="SqlDataSource1" >
            <Columns>
                <asp:BoundField DataField="OrderID" HeaderText="OrderID" InsertVisible="False" ReadOnly="True" SortExpression="OrderID" />
                <asp:BoundField DataField="CustomerID" HeaderText="CustomerID" SortExpression="CustomerID" />
                <asp:BoundField DataField="EmployeeID" HeaderText="EmployeeID" SortExpression="EmployeeID" />
                <asp:BoundField DataField="ShippedDate" HeaderText="ShippedDate" SortExpression="ShippedDate" />
                <asp:BoundField DataField="ShipCity" HeaderText="ShipCity" SortExpression="ShipCity" />
            </Columns>
        </asp:GridView>
        <asp:SqlDataSource ID="SqlDataSource1" runat="server"
            ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
            SelectCommand="SELECT [OrderID], [CustomerID], [EmployeeID], [ShippedDate], [ShipCity] FROM [Orders] WHERE ([ShipCity] = @ShipCity)">
            <SelectParameters>
                <asp:RouteParameter DefaultValue="" Name="ShipCity" RouteKey="ShipCity" Type="String" />
            </SelectParameters>
        </asp:SqlDataSource>
    </div>
    </form>
</body>
</html>

Önemli olan, SqlDataSource bileşenine ait SelectCommand ifadesindeki where koşulunda yer alan ShipCity isimli parametrenin bir RouteParameter ile ilişkilendirilmiş olmasıdır. RouteParameter bileşenine ait RouteKeyözelliği, Route Table’ daki ile aynı olmalıdır. Çok doğal olarak aspx kaynak tarafında yapılabilen bu eşleştirme, Wizardüzerinden de kolayca belirlenebilir. Aynen aşağıdaki ekran görüntüsünde olduğu gibi Winking smile

route_1

Buna göre örneğin Stuttgart’ a yapılan sevkiyatları aşağıdaki gibi elde edebiliriz.

route_7

Sonuç

Görüldüğü üzere URL eşleştirme işlemleri klasik sunucu tabanlı Asp.Net uygulamalarında da etkili bir şekilde kullanılabilir. Hatta bu felsefeden yola çıkarak OData sorgularının daha fazla gelişmişlerini destekleyecek web uygulamaları yazılması da pekala olasıdır. Yazımıza konu olan basit örneklerimizde ki anahtar noktalar, RouteTable sınıfı, RouteData.Valuesözelliği ve GetRouteUrl metodudur. Örneği geliştirmek tamamen sizin elinizde. İşe /siparisler/stuttgart/10301/şeklinde bir sorguyu ele alıp, 10301 numaralı siparişe ait detay bilgileri göstereceğiniz bir sayfayı üretmeyi deneyerek başlayabilirsiniz. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

HowTo_EasyRouting.zip (605,23 kb)

Tek Fotoluk İpucu 102–Ne Zaman XmlInclude Gerekir?

$
0
0

Merhaba Arkadaşlar,

Diyelim ki elinizde Role isimli bir sınıf var. Hatta bu sınıftan türemiş Manager ve Worker isimli iki ayrı sınıf daha var. Hatta Role tipinden Employees isimli bir listeyi özellik(Property) olarak içeren Company isimli başka bir sınıf daha var…Derken Company sınıfına ait bir nesne örneğini çalışma zamanında XML serileştirmek istediniz. Klasik olarak XmlSerializer tipini işin içerisine kattınız. Peki ya sonra? Aldınız InvalidOperationException hatasını oturdunuz aşağıya Who me? Nasıl çözersiniz? Aşağıdaki gibi olabilir mi? Winking smile

tfi_102

Aslında yapılan işlem gayet basittir. Role tipinin Manager ve Worker isimli sınıflara ait nesne örneklerini içerebileceği XmlIncludeniteliği(Attribute) yardımıyla ifade edilmiştir. Hepsi bu. Bir başka ipucunda görüşmek dileğiyle Winking smile

WCF Uygulamalarında Enterprise Library Validation Block Kullanımı

$
0
0

lego-block-tape[Orjinal Yazım Tarihi 10.09.2012]

Merhaba Arkadaşlar,

Enterprise Library ve içerisinde yer alan Application Block’ lar çoğunlukla projelerimizde ihtiyaç duyduğumuz ve Cross-Cutting olarak geçen parçaların hızlı ve kolay bir biçimde uygulanmasında kullanılmaktadır.

Cross-Cutting’ ler özellikle birden fazla katmandan oluşan proje bazlı çözümlerde, katmanların pek çok noktasında sıklıkla kullanılabilen(ihtiyaç duyulabilen) fonksiyonelliklerdir.

Örneğin Exception Handling, Security, Cryptography, Configuration, Logging, Validation, Caching vb…Bu tip modüler yapılar çok sık kullanıldıklarından her çözüm için ayrı ayrı geliştirilmemektedir/geliştirilmemelidir. Bunun yerine yeniden kullanılabilen modüler yapılar olarak ele alınmaları daha doğru bir yaklaşımdır. Örneğin Enterprise Library  Winking smile

Biz bu makalemizde WCF(Windows Communication Foundation) servislerinde, ValidationApplicationBlock’ u nasıl kullanabileceğimizi incelemeye çalışıyor olacağız. Bu block yardımıyla nitelik bazlı(Attribute Based) olacak şekilde doğrulama(Validation) kontrolleri yapılabilmektedir. Söz konusu doğrulama kontrolleri sınıfların özelliklerine uygulanan nitelikler ile yapılabileceği gibi, metodların parametreleri üzerine de enjekte olabilmektedir. Dilerseniz adım adım senaryomuzu geliştirip konuyu basit seviye de kavramaya çalışalım.

Örnek senaryomuzda WCF tabanlı bir servis üzerinden bir metod çağrısı ile Player isimli bir nesne örneğinin oluşturulmasını sağlıyor olacağız. Player tipinin özelliklerine ait değerler, servis operasyonuna parametre olarak gelecekler. İşte doğrulama kriterlerimiz de bu notkada devreye girecek ve bazı veri giriş ihlallerini kontrol edecekler. Haydi başlayalım Winking smile

İlk olarak servis tarafını geliştireceğiz. WCF Service Applicationşablonundan üretilen uygulamamızda, aşağıdaki ekran görüntüsünde yer alan referansların bulundurulması gerekmektedir.

wcfvbe_1

Enterprise Library’ sistemeyüklendikten sonra kurulduğu yerdeki bin klasöründen

  • Microsoft.Practices.EnterpriseLibrary.Common
  • Microsoft.Practices.EnterpriseLibrary.Validation
  • Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WCF
  • Microsoft.Practices.ServiceLocation

assembly’ larının yüklenmesi gerekmektedir. Bunlara ek olarak System.ComponentModel.DataAnnotations.dll assembly’ının da ayrıca projeye ilave edilmesi gerekecektir.

İlgili Assembly’ ların referans edilmesinin ardından servis tarafındaki uygulamanın geliştirilmesine başlanabilir. Örnek senaryomuza göre doğrulama denetimi, servis operasyonundaki metod parametreleri seviyesinde yapılacaktır. Şimdi aşağıdaki sınıf diagramında görülen tipleri geliştirmeye başlayalım.

wcfvbe_8

Player tipinin içeriği aşağıdaki gibidir.

using System.Runtime.Serialization;

namespace MyCompanyServiceApp
{
    [DataContract]
    public class Player
    {
        [DataMember]       
        public int PlayerId { get; set; }
       
        [DataMember]               
        public string Nickname { get; set; }

        [DataMember]
        public string Password { get; set; }

        [DataMember]       
        public string Country { get; set; }
       
        [DataMember]
        public string EMail{ get; set; }

        [DataMember]       
        public int Score{ get; set; }
    }
}

Servis sözleşmesini ise aşağıdaki gibi tasarlayacağız.

using Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WCF;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
using System.ServiceModel;

namespace MyCompanyServiceApp
{
    [ServiceContract]
    public interface IPlayerService
    {
        [OperationContract]
        [FaultContract(typeof(ValidationFault))]
        Player AddPlayer(

            [ValidatorComposition(CompositionType.And)]
            [StringLengthValidator(5, 10, MessageTemplate = "Nickname en az 5 en fazla 10 karakter olabilir")]           
            [NotNullValidator()]
            string nickname,

            string password,
           
            [ValidatorComposition(CompositionType.And)]
            [StringLengthValidator(3, 30, MessageTemplate = "Ülke bilgisi en az 3 en fazla 30 karakter olabilir")]
            [NotNullValidator()]
            string country,

            [RegexValidator(@"^(?("")("".+?""@)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-zA-Z])@))(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,6}))$")]
            string email,

            [RangeValidator(0, RangeBoundaryType.Inclusive, 1000, RangeBoundaryType.Inclusive, MessageTemplate = "Score bilgisi 0 ile 1000 arasında olabilir")]               
            int score,

            [StringLengthValidator(
                10
                , 100
                , MessageTemplate = "Notlar en az 10 en fazla 100 karakter uzunluğunda olabilir"
                , ErrorMessageResourceName="Notes"
                ,ErrorMessageResourceType=typeof(string)
                )]
            string notes
            );
    }
}

Dikkat edileceği üzere metod parametrelerinde bazı nitelikler(Attribute)kullanılmıştır. Validator kelimesi ile biten bu nitelik sınıfları yardımıyla bazı doğrulama kriteleri set edilmiştir. Örneğin nickname bilgisinin string uzunluğu 5 ile 10 karakter arasında olmalıdır. Benzer durum notes ve country parametreleri için de geçerlidir. NotNullValidator, tahmin edileceği üzere uygulandığı parametreinin nullolmamasını gerektirmektedir. Özellikle referans tiplerinin(örneğin string) null geçilmemesi bir doğrulama kriteri olarak sunulabilir.

Kullanımı etkin olan doğrulama tiplerinden birisi de RegexValidator’ dur. String tipinden parametrelere uygulanmakta olup, verinin bir RegEx desenine göre doğruluğunun kontrol edilmesini sağlamaktadır. Örnekte email verisinin geçerli bir elektronik posta adresi olup olmadığının kontrolü yapılmaktadır.

Birden fazla doğrulama kriteri uygulanmak istediğinde ise bu and’ li veya or’ lu bir biçimde enjekte edilebilir. Bunun için ValidatorComposition niteliğinin kullanılması yeterlidir. Örneklerimizde And opsiyonu etkinleştirilmiştir. Yani takip eden tüm Validator’ ların aynı anda true olması halinde bir ihlal söz konusu olmayacaktır.

AddPlayer isimli servis operasyonuna uygulanan bir diğer nitelikte FaultContract’ tır. Nitelik, Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WCF isim alanı(namespace) altında yer alan ValidationFault tipini kullanmaktadır. Dolayısıyla bir hata sözleşmesi olarak WCF’ in ValidationApplicationBlock için üretilmiş olan ValidationFault tipi kullanılacak ve istemci tarafında gönderilecektir.

Servis sözleşmesinin uygulandığı sınıf kodları ise aşağıdaki gibidir.

using System;

namespace MyCompanyServiceApp
{
    public class PlayerService
        : IPlayerService
    {
        public Player AddPlayer(
            string nickname,
            string country,
            string password,
            string email,
            int score,           
            string notes)
        {
            Random rnd = new Random();
            return new Player
            {
                PlayerId=rnd.Next(1,100),
                Nickname=nickname,
                Password=password,
                EMail = email,
                Score=score,
                Country=country
            };
        }
    }
}

PlayerService sınıfı içerisinde yer alan AddPlayer metodu içerisinde çok olağanüstü bir çalışma yoktur. Sadecebir Player nesnesi örneklenmekte ve istemci tarafına geri gönderilmektedir. Tabi herhangibir doğrulama kriterine takılınmadıysa.

Buraya kadar yapılan hazırlıklar ne yazık ki yeterli değildir. WCFçalışma zamanının da söz konusu Validation Application Block ile kullanılacağının bir şekilde belirtilmesi gerekir. Bu aslında servis endpoint’ i için ilave bir davranış(Behavior)belirtilmesinden başka bir şey değildir. Bunun için WCF Service Configuration Editor’ü kullanabiliriz. Şimdi bu adımlarımızı sırasıyla gerçekleştirelim.

Adım 1

wcfvbe_2

İlk olarak Advanced->Extensions->behaviorelement extensions kısmına gidilir ve buradan New düğmesine basıldıktan sonra çıkan iletişim pencersine geçilir. Nameözelliğine bir değer verdikten sonra ise Typeözelliğinin karşısında bulunan 3 nokta düğmesine basılır.

Adım 2

wcfvbe_3

Üç nokta düğmesine basıldıktan sonra ise, projenin bin klasörüne eklenmiş olan Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WCF.dll assembly’ı seçilir.

Adım 3

wcfvbe_4

Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WCF.dll assembly’ nın seçilmesini takiben gelen ekrandaki tek tip olan ValidationElement işaretleniz. Bu durumda aşağıdaki ekran görüntüsünde yer aldığı gibi ilgili elementin, behavior element extensions kısmına eklenmiş olduğu görülür.

wcfvbe_5

Adım 4

wcfvbe_6

Sıradaki adımda ilgili ValidationExtension elementinin bir EndPoint davranışı haline getirilmesi yer almaktadır. Bunun için Advanced->Endpoint Behaviors kısmına yeni bir davranış ilave edilmelidir. Davranışa örnek bir isim verildikten sonra ise Add düğmesi yardımıyla biraz önce sisteme dahil edilmiş olan ValidationExtension tipinin eklenmesi sağlanır.

Adım 5

Son olarak yeni Endpoint davranışının, servise ait Endpoint ile ilişkilendirilmesi yeterli olacaktır. Bunun için Services->[Service Adı]->Endpoints->[Endpoint Adı] kısmından gelen özelliklerden BehaviorConfiguration’ a ValidationBehavior değerinin atanması yeterlidir.

wcfvbe_7

Sonuç olarak servis tarafına ait konfigurasyon dosyası içeriği aşağıdaki gibi olacaktır.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <extensions>
            <behaviorExtensions>
                <add name="ValidationExtension"type="Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WCF.ValidationElement,Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WCF, Version=5.0.414.0,Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
            </behaviorExtensions>
        </extensions>
        <services>
            <service behaviorConfiguration="PlayerServiceBehavior" name="MyCompanyServiceApp.PlayerService">
                <endpoint address="http://localhost:50511/PlayerService.svc"
                    behaviorConfiguration="ValidationExtensionBehavior" binding="basicHttpBinding"
                    name="PlayerServiceHttpEndpoint" contract="MyCompanyServiceApp.IPlayerService" />
            </service>
        </services>
        <behaviors>
            <endpointBehaviors>
               <behavior name="ValidationExtensionBehavior">
                    <ValidationExtension />
                </behavior>
            </endpointBehaviors>
            <serviceBehaviors>
                <behavior name="PlayerServiceBehavior">
                    <serviceMetadata httpGetEnabled="true" />
                    <serviceDebug includeExceptionDetailInFaults="false" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
    </system.serviceModel>
</configuration>

Şimdi örnek bir istemci uygulama geliştirerek senaryomuzu teste çıkabiliriz. Basit bir Console uygulaması pekala işimizi görecektir Sarcastic smile Console uygulamasına servis referansını ekledikten sonra, aşağıdaki kodları geliştirdiğimizi düşünelim.

İstemci uygulamada ValidationFault tipinin kullanılabilmesi için, Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WCF.dll assemby’ ının  da projeye referans edilmesi gerekmektedir. Bu Assembly ne yazık ki Add Service Reference seçeneği sonrası otomatik olarak eklenmemektedir.

using ClientApp.Company;
using Microsoft.Practices.EnterpriseLibrary.Validation.Integration.WCF;
using System;
using System.ServiceModel;

namespace ClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            PlayerServiceClient proxy = new PlayerServiceClient("PlayerServiceHttpEndpoint");

            try
            {
                Player newPlayer = proxy.AddPlayer(
                    nickname: "kısa"
                    , password: "şifre"
                    , country: null
                   , email: "deneme"
                    , score: -10
                    , notes: "kısa not"
                   );

                //Player newPlayer = proxy.AddPlayer(
                //        nickname: "burkicik"
                //        , password: "şifre"
                //        , country: "Birleşik Krallık"
                //        , email: "selim@buraksenyurt.com"
                //        , score: 128
                //        , notes: "oyuna yeni katılmış bir oyuncudur ve ilk seferinde turnayı gözünde vurmuştur"
                //        );

            }
           catch (FaultException<ValidationFault> excp)
            {
                foreach (ValidationDetail validationDetail in excp.Detail.Details)
                {
                    Console.WriteLine("{0} : {1}\n", validationDetail.Tag, validationDetail.Message);
                }
            }
            catch (Exception excp)
            {
                Console.WriteLine(excp.Message);
            }
            finally
            {
                if (proxy.State == CommunicationState.Opened)
                    proxy.Close();
            }
        }
    }
}

Dikkat edileceği üzere doğrulama kurallarına takılacak şekilde test verileri girilmeye çalışılmıştır. Oluşması muhtemel ne kadar doğrulama hatası varsa, bunların tamamı FaultException<ValidationFault> ile gelen exception nesne örneğinin Detailözelliğinin Details koleksiyonunda toplanacaktır. Dolayısıyla bu koleksiyon içeriği dolaşılarak ilgili doğrulama ihlallerine ait bir takım bilgilere ulaşılabilinir.

Uygulamanın çalışma zamanı çıktısı aşağıdaki ekran görüntüsündeki gibi olacaktır.

wcfvbe_9

nickname, country, email, score ve notes metod parametrelerinde tanımlanan doğrulama ihlalleri istemci tarafına bu şekilde yansımıştır. Tabi yorum satırları altına alınmış Player’ ın oluşturulmasını denersek, bu durumda herhangibir doğrulama kriterine takılmadan ekleme işleminin yapılabildiğine şahit oluruz.

Görüldüğü gibi Enterprise Library Validation Application Block’ u kullanarak, WCF tarafında operasyon bazında doğrulama kriterleri uygulanabilmektedir. Burada işin önemli yanlarından birisi ise, nitelik bazlı yapılan bu tanımlamaların servis operasyonları üzerinde gerçekleştirilmesidir. Dolayısıyla bu nitelikler değiştirilse bile, istemci tarafı için yeniden Proxy tipinin üretilmesine gerek yoktur Winking smile Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

WCFandValidationBlock.zip (1,92 mb)

[Orjinal Yazım Tarihi 10.09.2012]

Excel ve Entity Framework Konuşuyor

$
0
0

handshake[Orjinal Yazım Tarihi 03-05-2013]

Merhaba Arkadaşlar,

Artık uygulamaların birbirleri ile konuşmaları çok ama çok kolay. Bu gerçekten önemli bir mesele. Özellikle farklı segmentlerden insanların bir araya geldiği bilişim toplumlarında. Kimi kullanıcı için Office Excel, Word veya Powerpointçok şey ifade ederken, kimi kullanıcı içinde SQL Management Studio ortamında hazırlanan karmaşık bir sorguya bakmak daha anlamlı olabiliyor. Ya da bir Web sayfası üzerinden alınan raporlar şirketin Muhasebe Şefi için değerli iken, kimisi SSRS ile elde edilen raporları mobil cihazında görmeyi tercih edebiliyor.

Ancak Developer gözüyle olaya bakıldığında, her segmenti memnun edecek şekilde geliştirme yapması beklendiği oldukça aşikar. Bu sebepten, farklı uygulamaların birbirleriyle rahatça konuşabilmeleri önemli bir mesele olarak karşımıza çıkıyor. Visual Studio 2012 tarafında olaya baktığımızda bir Office uygulamasının, önceki sürümlere göre .Net Framework ile daha yüksek seviyede etkileşime girerek tasarlanabilmesi/geliştirilebilmesi de pekala mümkün.

Özellikle Sheet’ ler veya Workbook’ lar kodlanabilir birer C#(Vb.Net) dosyası olduğu için, uygulama bazında istediğimiz taklayı atma şansına sahibiz. İşte bu düşünceler ışığında yola çıktığımız ve okumakta olduğunuz yazımızda, Excel’ i, Entity Framework’ü, C#’ ı  işin içerisine katacak ve birbirleri ile konuşmalarını sağlamaya çalışacağız. Haydi hiç vakit kaybetmeden yola koyulalım. Ama önce örnek senaryomuz Winking smile

Senaryo

Amacımız, Northwind veritabanında bulunan 3 adet View nesne örneğinin Excel dokümanı içerisindeki Sheet’ ler de gösterilmesini sağlamak. Bu amaçla Category Sales for 1997, Product Sales for 1997 ve Invoices isimli View nesnelerini kullanıyor olacağız. Sheet1 içeriğini kendi kod dosyası içerisinde üretmek istiyoruz. Sheet2 ve Sheet3 içeriklerini ise, Workbook’ a ait kod dosyasından besliyor olacağız. Bu noktada ağırlıklı olarak Workbook ve Sheet sınıflarının ilgili başlangıç noktalarını değerlendireceğiz. Kabaca senaryomuzu işaret eden aşağıdaki görseli göz önüne alabiliriz.

ewef_8

Konuşan Çözüm

Dilerseniz senaryo içerisinde tarafların gözünden olaya bakalım.

Sheet Konuşuyor

"Merhaba, benim adım Sheet1. Çalıştırıldığımda Sheet1_Startup olay metoduna bir çağrıda bulunurum.  Ey Startup metodu, haydi işini yap derim. O da kendi içinde Entity Framework tabanlı Context nesnesini kullanır ve 1997 yılına ait kategori bazlı satışların verilerini, sahip olduğum hücrelere teker teker aktarırSmile Onunla çok iyi anlaşırız.

Workbook Konuşuyor

Merhaba, ben ThisWorkbook. Ben sahip olduğum tüm Sheet’ leri yönetebilirim. Örneğin kendi Startup metoduma, tüm Sheet’ leri çeşitli yerlerden topladığı veriler ile doldurmasını söyleyebilirim. He-Man ile aramdaki tek fark onun kılıcının olmasıdır.

Hazırlıklar

Dilerseniz senaryomuzu nihai sonuca ulaştırmak için adım adım ilerlemeye başlayalım. İlk olarak Visual Studio 2012 ortamında bir Excel 2010 Workbook projesi oluşturmalıyız. Bu nedenle New Project kısmında ilgili proje tipini işaretliyoruz.

ewef_7

Bu işlemin ardından Northwind veritabanına ait Entity içeriğini barındıracak bir ClassLibrary projesini aynı Solution içerisine ekleyebilir ve aşağıdaki gibi mevzuya konu olan View nesnelerini içeren Entity Data Model öğesini üretebiliriz.

ewef_1

Çok doğal olarak Excel uygulamasının, söz konusu Class Library’ yi ve ayrıca EntityFrameworkAssembly’ ını referans etmesi gerekmektedir. Solution içeriğişu anitibariyle aşağıdaki gibi olacaktır.

ewef_2

Dikkat edileceği üzere HowTo_ExcelWithEF uygulaması içerisinde Workbook ve söz konusu Workbook’ a ait Sheet’ ler için birer kod dosyası bulunmaktadır. Bu kod dosyaları içerisindeki pek çok olay metoduna müdahale edebilir ve çalışma zamanını kontrol altına alabiliriz ki örneğimizde de benzer bir işi gerçekleştiriyor olacağız.

Kritik noktalardan birisi de, Entity kütüphanesini kullanacak çalıştırılabilir uygulamanın, ConnectionString bilgisine sahip olması zorunluluğudur. Bu nedenle NorthwindLibrary içerisindeki App.Config dosyasına ait ConnectionString bilgisini, HowTo_ExcelWithEF uygulamasında da kullanmalıyız.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="NorthwindEntities" connectionString="metadata=res://*/NorthwindModel.csdl|res://*/NorthwindModel.ssdl| res://*/NorthwindModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=.;initial catalog=Northwind;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
  </connectionStrings>
</configuration>

Artık kodlama tarafını tamamlayabiliriz. İlk olarak Sheet1.cs içeriğini aşağıdaki gibi geliştirdiğimizi düşünelim.

using NorthwindLibrary;
using System.Linq;

namespace HowTo_ExcelWithEF
{
    public partial class Sheet1
    {
        private void Sheet1_Startup(object sender, System.EventArgs e)
        {
            using(NorthwindEntities context=new NorthwindEntities())
            {
                var categoryBasedSales = from s in context.Category_Sales_for_1997
                            orderby s.CategorySales descending
                            select new {
                                s.CategoryName, s.CategorySales
                            };

                int rowIndex = 1;
               
                Cells[rowIndex, 1] = "Kategori";
                Cells[rowIndex, 2]= "Satışlar";

                Cells[rowIndex, 1].Font.Bold = true;
                Cells[rowIndex, 2].Font.Bold = true;

                foreach (var sale in categoryBasedSales)
                {
                    rowIndex++;
                    Cells[rowIndex, 1]= sale.CategoryName;
                    Cells[rowIndex, 2]= sale.CategorySales;
                }
            }
        }

        private void Sheet1_Shutdown(object sender, System.EventArgs e)
        {
        }

        #region VSTO Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(Sheet1_Startup);
            this.Shutdown += new System.EventHandler(Sheet1_Shutdown);
        }

        #endregion

    }
}

Startup metodunda context nesne örneği kullanılarak ilgili View’ dan Anonymous tipte bir nesne listesi oluşturulmaktadır. Söz konusu liste Sheet1 içerisindeki hücrelere yerleştirilir. Bunun için Cells[rowIndex,columnIndex] ifadesi ele alınmaktadır. Kodu yazarken dikkat edilecek olursa dynamicözelliğinin kullanılmakta olduğu görülebilir. Yani noktadan sonrasındaki atamalar ve bunlara ait tipler çalışma zamanında çözümlenecektir. Bu biraz performans kaybına yol açacak olsa da şu anki senaryodaki gibi küçük veri kümelerinde önemsizdir.

ewef_9

1,1 ve 1,2 hücrelerini başlıklar ile doldurduktan sonra(Excel tarafında ilk hücre satır sütun değeri 0,0 değil 1,1 dir) bunların Bold olması sağlanmıştır. Diğer yandan sonuç listesi üzerinde dönülmekte ve diğer hücreler, View dan gelen veriler ile beslenmektedir. Bu kodlamaya göre veri Sheet1’ in kod dosyası da doldurulmaktadır. Pek tabi Sheet’ lerin Workbook’ a ait olay metodlarında şekillendirilmesi de söz konusudur. Sheet2 ve Sheet3 bu amaçla Workbook.cs içerisinde ele alınmıştır. Aynen aşağıdaki kod parçasında görüldüğü gibi.

using NorthwindLibrary;
using System.Drawing;
using System.Linq;

namespace HowTo_ExcelWithEF
{
    public partial class ThisWorkbook
    {
        private void ThisWorkbook_Startup(object sender, System.EventArgs e)
        {
            using(NorthwindEntities context=new NorthwindEntities())
            {
                #region Entity View içeriklerinin çekilmesi

                var invoices = from i in context.Invoices
                               orderby i.CustomerName
                               select new
                              {
                                   From = i.Country + "," + i.City,
                                   i.CustomerID,
                                   i.CustomerName,
                                   i.OrderDate,
                                   ShipTo = i.ShipCountry + "," + i.ShipCity
                               };

                var prdoductSalesFor1997 = from s in context.Product_Sales_for_1997
                                           orderby s.ProductSales descending
                                           ,s.CategoryName ascending
                                           ,s.ProductName ascending
                                           select new
                                           {
                                               Product=s.CategoryName+"-"+s.ProductName,
                                               s.ProductSales
                                           };

                #endregion

                #region Sheet2 nin Invoices içeriği ile doldurulması

                int rowIndex = 1;

                Sheets["sheet2"].Cells[rowIndex, 1] = "From";
                Sheets["sheet2"].Cells[rowIndex, 1].Font.Bold = true;
                Sheets["sheet2"].Cells[rowIndex, 2] = "Customer ID";
                Sheets["sheet2"].Cells[rowIndex, 2].Font.Bold = true;
                Sheets["sheet2"].Cells[rowIndex, 3] = "Customer";
                Sheets["sheet2"].Cells[rowIndex, 3].Font.Bold = true;
                Sheets["sheet2"].Cells[rowIndex, 4] = "Order Date";
                Sheets["sheet2"].Cells[rowIndex, 4].Font.Bold = true;
                Sheets["sheet2"].Cells[rowIndex, 5] = "Ship To";
                Sheets["sheet2"].Cells[rowIndex, 5].Font.Bold = true;

                foreach (var invoice in invoices)
                {
                    rowIndex++;
                    Sheets["sheet2"].Cells[rowIndex, 1] = invoice.From;
                    Sheets["sheet2"].Cells[rowIndex, 2] = invoice.CustomerID;
                    Sheets["sheet2"].Cells[rowIndex, 3] = invoice.CustomerName;
                    Sheets["sheet2"].Cells[rowIndex, 4] = invoice.OrderDate;
                    Sheets["sheet2"].Cells[rowIndex, 5] = invoice.ShipTo;
                }

                #endregion Sheet2 nin Invoices içeriği ile doldurulması

                #region Sheet3 ün 1997 yılına ait satış verileri ile doldurulması

                rowIndex = 1;

                Sheets["sheet3"].Cells[rowIndex, 1] = "Product";
                Sheets["sheet3"].Cells[rowIndex, 1].Font.Bold = true;
                Sheets["sheet3"].Cells[rowIndex, 2] = "Sales";
                Sheets["sheet3"].Cells[rowIndex, 2].Font.Bold = true;

                foreach (var sales in prdoductSalesFor1997)
                {
                    rowIndex++;
                    Sheets["sheet3"].Cells[rowIndex, 1] = sales.Product;
                    if (sales.ProductSales > 30000)
                    {
                        Sheets["sheet3"].Cells[rowIndex, 2].Interior.Color = Color.Black;
                        Sheets["sheet3"].Cells[rowIndex, 1].Interior.Color = Color.Black;
                        Sheets["sheet3"].Cells[rowIndex, 2].Font.Color = Color.Gold;
                        Sheets["sheet3"].Cells[rowIndex, 1].Font.Color = Color.Gold;
                    }

                    Sheets["sheet3"].Cells[rowIndex, 2] = sales.ProductSales;
                }

                #endregion Sheet3 ün 1997 yılına ait satış verileri ile doldurulması
            }        
        }

        private void ThisWorkbook_Shutdown(object sender, System.EventArgs e)
        {
        }

        #region VSTO Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(ThisWorkbook_Startup);
            this.Shutdown += new System.EventHandler(ThisWorkbook_Shutdown);
        }

        #endregion

    }
}

Office uygulamalarını geliştirirken C# 4.0 ile birlikte gelen dynamic, Optional and Named Parameters gibi yeniliklerin işlerimizi nasıl kolaylaştırdığına bir kere daha şahit oluyoruz.

Kodlarda bizi zorlayacak bir karmaşa yoktur. İki View sırasıyla sorgulanmakta ve elde edilen listeler ilgili Sheet’ ler deki hücrelere satır satır doldurulmaktadır. Özellikle 1997 yılına ait satış rakamları alınırken 30000 birimden büyük olunması halinde, o satırın arka plan ve font renkleri değiştirilerek göze batmaları sağlanmaktadır Smile Dikkate değer noktalar, ilgili hücrelerin nasıl formatlandığı veya belirli bir Sheet’ e nasıl ulaşıldığı ile alakalıdır.

Sonuçlar

Uygulamayı çalıştırdığımızda çalışma zamanında bir Excel Workbook’ un açıldığını ve Sheet1, Sheet2, Sheet3 sayfalarının ilgili View içerikleri ile doldurulduğunu görebiliriz.

Sheet1 içeriği kategori bazlı satış verilerl ile

ewef_3

Sheet 2 içeriği Müşteri faturalarıyla,

ewef_4

son olarak Sheet3 içeriği de ürün bazlı satış rakamlarıyla doldurulacaktır.

ewef_5

Görüldüğü üzere bir Excel içeriğini Entity Frameworküzerinden geçerek veri ile doldurmak son derece kolaydır. Elbette gerçek hayat senaryoları düşünüldüğünde, ilgili veri içeriğinin asenkron olarak ve hatta servisler yardımıyla doldurulması daha doğru bir yaklaşım olacaktır. Zaten Excel’ in konuşabildiği pek çok dış dünya aracı servis bazlı veri sunmaktadır(OData servislerinden veri çekilmesi ve Excel içerisinde Pivot table olarak gösterilebilmesi konusunu araştırabilirsiniz)

Tamamlanan uygulamanın herhangibir bilgisayarda veya ortamda çalıştırılabilmesi için aşağıdaki görselde yer alan içeriğin taşınması yeterli olacaktır. Elbette veritabanı bağlantısının olduğunu ve ilgili Northwind içeriklerine ulaşılabildiğini varsayıyoruz.

ewef_6

Böylece geldik bir yazımızın daha sonuna. Size düşen örneği daha da zengilenştirmektir. Örneğin,

  • n sayıda Sheet’ in doldurulması noktasında asenkron bir işleyişin ele alınmasını sağlayabilirsiniz.
  • Aynı uygulamayı Office 2013 standartlarında ele alıp, .Net Framework 4.5 hedefli düşünüp, async ve await anahtar kelimelerini devreye sokabilirsiniz.
  • Excel’ de yapılacak olan değişikliklerin, Save işlemleri sonrasında veri kaynağına doğru yansıtılmasını düşünebilirsiniz.
  • Bazı View’ lardan yararlanarak Sheet içerisine çok basit anlamda Chart(örneğin bir Pie Chart çok şık durabilir)çizdirmeyi deneyebilirsiniz

vb Winking smile Burada hayal gücünüzü kullanmadan önce, bir Excel uygulamasının aslında bir Windows uygulaması olduğunu ve Visual Studio 2012 tarafında istenildiği gibi özelleştirilebildiğini düşünmenizde yarar olacaktır. Nitekim gördüğünüz üzere aynen bir Windows Forms uygulamasında olduğu gibi olay metodlarına girebiliyor ve C# kodlarımızı konuşturabiliyoruz. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

HowTo_ExcelWithEF.zip (1,20 mb)

[Orjinal Yazım Tarihi 03-05-2013]

Asp.Net Web API Üzerinden Resim Döndürmek

$
0
0

Road-Runner-Wile-E-Coyote-looney-tunes-5226561-1024-768Merhaba Arkadaşlar,

Eminim çocukken çizgi filmlerle aranız vardı. Hatta çoğumuz yaşı kaç olursa olsun çizgi filmlere arada sırada da olsa zaman ayırmakta. (Ben Batman gördüm mü pür dikkat izlerim örneğin) Keza pek çok büyüğümüz de, eskiden izlediği çizgi filmler ile karşılaştığında taaaa çocukluk yıllarına kadar gidip aynı o zamanki gibi içten gülebiliyorlar da(Rahmetli babamdan bilirim)

Aslına bakarsanız bazen teknoloji de bizi aynen bu mantıkta epeyce güldürebiliyor. Örneğin Microsoft’ un ürünlerini düşünelim. (Gerçi çok fazlalar ama gene de düşünmeye çalışalım) Sürekli yenilikler çıkartıyorlar, sürekli verisyon atlatıyorlar ve işin en acı tarafı da koşan Road Runner’ a benziyorlar. Biz mi? Biz ise Road Runner’ ı her fırsatta yakalamaya çalışıp yakaladığını zanneden ama son anda hep elinden kaçıran Coyote’ ye Smile Bence bu senaryoda developer’ lar biraz daha şanslı. Ya benim gibi düzenli blog tutmaya çalışanlar napsınlar Disappointed smile 

Sözü fazla uzatmadan ve moralimizi daha da bozmadan konumuza geçelim.

Bu yazımızda Asp.Net Web APIüzerinden, SQL tablolarında binary formatta tutulabilen resim içeriklerini nasıl çekebileceğimizi basit bir örnek ile incelemeye çalışıyor olacağız. Örneğimizin özel yanlarından birisi de kısa süre önce yayınlanan Visual Studio 2013 Preview ile geliştirilecek olması. Önce senaryomuza bir bakalım.

Senaryo

Uzun zamandır uğramadığımız hatta pek çok genç arkadaşımızın belki de adını bile duymadığı bir Microsoft veritabanını ele alıyor olacağız. Pubs, SQL 2000 sürümünde sıklıkla Northwind ile birlikte andığımız kobay veritabanlarından birisidir Smile Bu veritabanında yayıncılara ait bazı bilgiler yer almaktadır. Örneğin pub_info isimli tablo içerisinde pub_id, logo ve pr_info isimli 3 adet alan yer almaktadır. Bu alanlardan logo tahmin edileceği üzere Binary veri tipindedir ve yayıncının firma logosunu tutmaktadır.

Hedefimiz bu binary içerikleri(yani logoları) bir Web API fonksiyonu üzerinden geriye döndürebilmek ve hatta en azından tarayıcı pencresinde resim formatında gösterebilmek olacaktır. O halde projeyi açarak ilk adımımız atalım.

wapigi_1

Projenin Oluşturulması

İlk olarak yeni bir Web uygulaması oluşturarak işe başlayabiliriz. Pek tabi Visual Studio 2013 preview içerisinde görünen önemli özelliklerden birisi de One Asp.Net yeteneğidir. Buna göre tek bir Web uygulaması şablonu üzerinden hareket edilerek istenen kabiliyetlere göre seçimler yapılması sağlanmaktadır.

wapigi_2

Doğruyu söylemek gerekirse Asp.Net tarafındaki proje şablonlarının artması kafa karışıklıkları yanında bir arada kullanmak istediğimiz kabiliyetler olduğunda da sıkıntı yaratmaktaydı. Umarız bu özellik baki olur ve daha da iyileştirilir.

Asp.Net Web Application seçimi sonrasında karşımıza gelen pencereden Emptytemplate tipini seçip Web APIözelliğini etkineleştirebiliriz. Ya da Web APIözelliğini işaretleyip ilerleyebiliriz. Ben mümkün mertebe sade bir ortam arzu ettiğimden Empty template seçip Web API kutusunu işaretledim.

wapigi_3

Bu işlemler sonucunda solution ve proje içeriği aşağıdaki gibi oluşacaktır.

wapigi_4

Modelin Eklenmesi

İzleyen adımda modelimizi ilave etmemiz gerekiyor. Tahmin edeceğiniz gibi Entity Framework den yararlanıyor olacağız. Projeye yeni bir öğe olarak Ado.Net Entity Data Model nesnesi ekledikten sonra klasik adımlarımızla ilerliyoruz(Model klasörü altına ekleyebilirsiniz) Lakin Visual Studio 2013 Preview’ a has bir özellik olarak Entity Framework versiyonunu seçebileceğimiz bir ekranla karşılaşacağız(Sanırım Entity Framework tarafı kadar hızlı versiyon atlatan ürün sayısı nadirdir) Ben 6.0 sürümünü seçtim ve bunun sonucu olarak Beta 1’ in kütüphane olarak ilave edildiğini fark ettim.

wapigi_5

İzleyen kısımda sadece pub_info tablosunun eşleniği olan entityüretimini yaptırmamız yeterlidir. (Diğer tablolaraı dilersenize ekleyebilirsiniz ancak şu anki senaryomuz için çok da gerekli değiller)

wapigi_6

Controller Tipinin Yazılması

Web API’ nin temel yapı taşı olan Controller tipini ekleyerek örneğimize devam edelim.

wapigi_7

Web API controller sınıfı için iki farklı versiyon bulunmaktadır. (Ben v1’i seçerek ilerledim ama bunu yaparken iki versiyon arasındaki farkı tam olarak bilmediğimi itiraf etmek isterim Embarrassed smile)

LogosController sınıfı içeriği

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;

using System.Net.Http;
using System.Net.Http.Headers;
using System.Web.Http;

using WebApplication4.Models;

namespace WebApplication4.Controllers
{
    public class LogosController
        : ApiController
    {
        public List<string> Get()
        {
            List<string> pubIds = null;
            using (PubsEntities _context = new PubsEntities())
            {
                pubIds = (from p in _context.pub_info
                          select p.pub_id).ToList();
            }
            return pubIds;
        }
        public HttpResponseMessage Get(string id)
        {
            HttpResponseMessage response = null;

            using (PubsEntities _context = new PubsEntities())
            {
                var pubPicture = (from p in _context.pub_info
                                       where p.pub_id == id
                                       select p.logo).FirstOrDefault();
                if (pubPicture==null)
                {
                    response = new HttpResponseMessage(HttpStatusCode.NotFound);
                }
                else
                {
                    MemoryStream ms = new MemoryStream(pubPicture);
                    response = new HttpResponseMessage(HttpStatusCode.OK);
                    response.Content = new StreamContent(ms);
                    response.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
                }
            }
            return response;
        }
    }
}

LogosController sınıfı içerisinde iki adet Get metodu bulunmaktadır. İstemci tarafından gelecek HTTPGet taleplerine cevap verecek olan bu fonksiyonlardan birisi pub_info tablosundaki pub_id alanlarını liste olarak döndürmektedir. Diğer yandan senaryomuzun can alıcı Get metodu ise, HttpResponseMessage tipinden bir nesne örneğini döndürmektedir. Bu metod parametre olarak string tipinden olan bir pub_id değerini alır. İlgili alana eş satırın logo içeriğini bulur(eğer varsa). Bu içeriğin byte[] tipinden olan karşılığı bir MemoryStream referansından yararlanılarak HttpResponseMessageörneğinin Contentözelliğine set edilir.

Bundan sonra yapılması gereken, istemciye dönecek cevap içeriğinin bir image olduğunu belirtmektir. Headers.ContentTypeözelliğine bir MediaTypeHeaderValueörneğinin atanmasının ve parametre olarak image/png verilmesinin sebebi de budur. Çok doğal olarak ilgili id değeri yanlış girilebilir ve LINQ sorgusu bu durumda null değer üretebilir. Null değer kontrolü yapılarak böyle bir vaka oluşması halinde HTTP 404 Not Found istisnasının döndürülmesi de sağlanmaktadır(Web’ in doğasına ve isteğine uygun şekilde Winking smile )

Testler

Uygulama kodunun tamamlanmasını müteakip test çalışmalarına başlanabilir. Her hangi bir tarayıcı uygulama ile bu işlemi yapabiliriz(Ben tercihimi Google Chrome’ dan yana kullandım Smile )Örneğin api/logosşeklinde bir talepte bulunulduğunda aşağıdaki ekran görüntüsüne benzer olacak şekilde pub_id bilgilerinin elde edildiği görülür.

wapigi_8

Eğer belirli bir pub_id değeri için talepte bulunulursa asıl istediğimiz sonuçlara ulaşırız. Yani yayıncının logosuna Winking smile

api/logos/0736 için aşağıdaki sonuç elde edilirken

wapigi_9

api/logos/1756 için

wapigi_10

sonucu elde edilir. Çok doğal olarak olmayan bir pub_id için istemci tarafında HTTP 404 hatası dönecektir.

Daha Neler Yapılabilir ve Size Kalan

Senaryomuz sadece yayın evinin logosunu ve yayın evi numaralarını döndürecek fonksiyonelliklere sahip bir Asp.NetWeb API hizmetini içermektedir. Ancak siz bu senaryoyu daha da geliştirebilirsiniz.

  • Örneğin jQuery kullanarak yayıncıların listesinin logoları ile birlikte bir View’ da görünmesini deneyebilirsiniz.
  • Kuvvetle muhtemel yukarıdaki maddeyi bir MVC projesinde denersiniz. Ama aynısını Web Forms tabanlı bir uygulama için de yapmaya çalışabilirsiniz.
  • Resimlerin gösterilmesi haricinde istemcilerin yine Asp.Net Web API’ den yararlanarak upload etme işlemlerini yapabilmelerini de sağlayabilirsiniz Winking smile Bunu bir araştırmanızı öneririm. POSTşeklinde bir talebi ele almanız gerektiğini ip ucu olarak verebilirim.
  • Bir önceki maddede var olan kısmı birden fazla dosyayı bir seferde yükleme senaryosu için ele alabilirsiniz(Multiple Upload)
  • Büyük boyutlu resimleri parça parça atmayı veya okumayı deneyebilirsiniz.
  • ve benim aklıma gelmeyen ama sizin ele alacağınız başka bir senaryo da söz konusu olabilir.

Görüldüğü üzere bir Asp.Net Web API servisini resim içeriklerinin elde edilmesini konu alan bir senaryo da kullanabildik. Örneğimizi yeni göz bebeğimiz Visual Studio 2013 Previewüzerinde geliştirmeye çalıştık ve böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.


Tek Fotoluk İpucu 103–Database.Query ve dynamic Avantajı

$
0
0

Merhaba Arkadaşlar,

SQL gibi bir veri kaynağına erişmek için pek çok yol olduğunu gayet iyi biliyoruz. Hatta bu işi öğrenmeye başladığımız ilk zamanları hatırlayın. Connection' ın açılması, Command hazırlanması, bir DataAdapter' dan yararlanılarak DataTable/DataSet doldurulması ve DataReader ile veri setinin ileri yönlü dolaşılması ve benzeri tiplerle uğraşırız. Hatta Entity Framework gibi alt yapılar da kendi içlerinde bu temel türlerin ata tiplerinden fazlasıyla yararlanmaktadır.

Son zamanlarda popüler olan(en azından benim dikkatimi yeni çeken) kütüphanelerden birisi de WebMatrix.Data dır(Oldukça eğlenceli olduğunu da ifade etmek isterim) Bu kütüphane içerisinde yer alan Database tipi ise tam bir sihirbaz. Özellikle dynamic desteği sağlayan metodları.

Söz gelimi Northwind veritabanındaki ürünlerin kategori bazlı sayılarını öğrenmek istediniz. Aşağıdaki gibi bir kod parçası pekala işinize yarar.

tfi_103

dynamic türü nedeniyle sorgu sonucu elde edilen liste elemanları üzerinden doğrudan CategoryName ve ProductCount alanlarına gidilmesi mümkün olmuştur Winking smile 

Bir başka ip ucunda görüşmek dileğiyle.

Word Dosyası İçerisinden Entity Framework’ e

$
0
0

275344[Orjinal Yazım Tarihi 03-15-2013]

Merhaba Arkadaşlar,

Geliştirdiğimiz veya kullanmakta olduğumuz yazılım ürünlerine dahil olan, farklı segmentlerden gelen pek çok kullanıcı profili vardır. Farklı profillerin olması, bazı hallerde geliştirilmekte olan ürünlerin başarısını doğrudan etkilemektedir.

Bir fotoğraf işleme programını geliştirirken çoğu zaman annemizin olası kullanıcı profilleri arasına gireceğini pek düşünmeyiz. Genellikle fotoğraf işleme programını kullanacak olanların, en azından temek düzeyde fotoğrafçılık bilgisine sahip olduğunu kabul eder, menü komutlarını buna göre belirler, arayüzü buna göre hazırlarız. Ama bazı uygulamalarda annemizi hedef alır ve çektiği fotoğraflara kolayca efekt uygulamasına bir kaç basit adımda olanak tanır. Örneğin instagram’ ın iPhone uygulamasında olduğu gibi.

Bir yazılım geliştirme ürününü tasarlarken ise, son kullanıcının programcılar olduğunu varsayar ve arabirimin karmaşık olmasının herhangibir sorun oluşturmayacağını düşünürüz. Oysaki Team Foundation Server gibi geniş ürün yelpazesine sahip aileler düşünüldüğünde, işe dahil olan farklı profildeki kullanıcılar için işleri kolaylaştırıcı şekilde düşünüldüğüne şahit oluruz.

Söz gelimi proje yöneticisinin, Ms Project ürünü ile TFS’ e entegre olabildiğini, Scrum Master’ ın isterse tüm Product Backlog içeriğini bir Excel dosyasına indirip senkronize edebildiğini, Yazılım Mimarının ve Geliştiricilerin,Visual Studio ile ortama bağlanabildikleri ama birim müdürü için sadece Team Explorer’ ın kafi gelebildiğini, İş Analisti gibi geliştiriciden farklı profile sahip elemanların ise Web arayüzünü kullanarak basit bir şekilde Requirement ekleyebildikleri görürüz.

Sonuç olarak geliştirilen ürünün kullanım alanına dahil olan tüm personeli göz önüne alarak hareket etmemiz gerektiğinin farkında olmalıyız. İşte bu yazımızda bu konuyu tecrübe etmeye çalışacağımız bir örnek geliştiriyor olacağız. Şimdi izleyen paragraftaki senaryoyu göz önüne alalım.

Senaryo

Selim Usta bir oto yedek parçacıdır. İşlerini kolaylaştırmak amacıyla uzun zamandır bilgisayar öğrenmeye çalışmaktadır ve en nihayetinde Word, Excel gibi ofis uygulamalarını basit seviyede de olsa kullanmaya başlamıştır. En sık yaptığı işlemlerden birisi ise stoğuna dahil ettiği yeni ürünleri bilgisayarda bir Word dosyasına kaydetmektir. Lakin zaman içerisinde Word dosyası epeyce şişmiş, içeride bir şey aramak epeyce zorlaşmıştır. Üstelik içeriğin düzgün bir formatı da yoktur.

Aslında ihtiyacı olan basit bir arayüz ile stoğunu takip etmektir. Rukiye, Selim Usta’ nın torunudur ve Üniversite son sınıftadır. Matematik Mühendisliği okumaktadır. Dedesinin işlerini kolaylaştırmak için bir Web arayüzü hazırlamıştır. Asp.Net MVC kullanmıştır. İyi renkler seçmiş, adımları sadeleştirmiş ve Selim Usta için kullanıcı deneyimi(User Experience) epeyce yüksek bir arabirim sunmuştur. Herşey yolunda gibidir. Lakin Selim Usta’ nın vazgeçemediği alışkanlıkları vardır Smile Bir gün torununa şöyle der; “Kızım keşke şu yeni gelen tamponları bu öğrendiğim Word ile de kayıt edebilsem. Bu internet üzerinden girmek zor geliyor bana…”

İşte şimdi bizim devreye girme sıramız. Bu yazımızda gerçekleştireceğimiz iş, bir Word dokümanının içeriğini basit bir şekilde bir veritabanına eklemek olacak. Bunun için Wordüzerinde bazı WindowsForm kontrollerini kullanıyor olacağız ve ayrıca kod yazarak, Entity Framework tabanlı giriş işlemleri gerçekleştireceğiz. Dilerseniz hiç vakit kaybetmeden yola koyulalım.

Word Document Projesini Oluşturmak

Visual Studio 2012 ortamımızda yeni bir Solution açarak işe başlayabiliriz. İlk projemiz Visual C#->Office/SharePoint/Office Add-ins sekmesinde yer alan Word 2010 Document tipinden olacak.

wrp_1

ProductDocument olarak projeyi isimlendirdikten sonra küçük bir soru ile karşılacağız.

wrp_2

Yeni bir doküman oluşturarak ilerlemeyi tercih edelim. Ama var olan bir dokümanı da kullanabiliriz. Bu işlemlerin sonucunda Visual Studio ortamımız aşağıdaki şekle bürünecektir Surprised smile

wrp_3

Dikkat edileceği üzere IDE’ nin göbeğinde docx uzantılı bir Word belgesi yer almaktadır. Hatta Solution Explorer penceresine baktığımızda, ThisDocument.cs isimli bir sınıf dosyası olduğunu, Toolbox içerisinde ise pek çok Form kontrolünün yer aldığını görürüz Winking smile Bu, kısaca şu anlama gelmektedir; Word Object Model’ in üzerine Windows Form kontrollerini ekleyebilir ve C# ile kodlama yapabiliriz. Öyleyse Selim Usta için basit bir içerik hazırlayarak ilerleyelim(Word Object Model ile ilişkili olarak bu adresten daha detaylı bilgi tedarik edebilirsiniz)

Word Tasarımını Yapalım

Word belgesini Ribon tarafında yer alan bileşenler ile donatabileceğimiz gibi Toolbox üzerinde yer alan Component’ leri içermesini de sağlayabiliriz. Bu anlamda melez bir arayüz geliştirme ortamı oluştuğunu ifade edebiliriz. Hem Word hem de Windows Forms kontrollerini bir arada ele alabilmekteyiz. Bu düşünceler ışığında Selim Usta için aşağıdaki formu oluşturduğumuzu düşünelim.

wrp_4

Dikkat edileceği üzere Word içeriğine TextBox, DateTimePicker, NumericUpDown, Button kontrolleri eklenmiştir. Kontrollere kod tarafı için anlamlı isimler vererek ilerleyelim. Ben örnekte [KontrolTipi][TabloKolonAdı]şeklinde bir notasyon kullandım. Yani Name alanı için TextBoxName, açıklama alanı için TextBoxDescription vb…

Entity Framework Çözümünün Eklenmesi

Solution içerisinde Entity Framework tabanlı bir ClassLibrary projesi de yer almaktadır. Söz konusu proje bir Ado.Net Entity Modelöğesi bulundurmakta olup aşağıdaki diagramda görülen içeriğe sahiptir.

wrp_5

ProductEntity tipi, Depomuz isimli SQL 2008 veritabanında yer alan Product tablosunu işaret etmektedir. Otomatik artan ve Primary Key olan ProductId alanı dışında, Name(nvarchar(50)), Description(nvarchar(250)), ListPrice(decimal(18,0)), RealPrice(decimal(18,0)), InsertDate(Date), Quantity(int) ve Notes(nvarchar(250)) gibi kolonları da içermektedir.

Pek tabi bir Word dokümanından C# kodlarını kullanarak farklı bir kütüphaneye erişebildiğimize göre, WCF servislerini de çağırmamız mümkündür. Özellikle bir şirketin bu tip bir dokümanı kullanarak, çeşitli Repository’ lere kayıt atması istenen durumlarda, bir Servis kanalına uğranmasını sağlamak yerinde bir davranış olabilir. (Dolayısıyla senaryonuzu, Entity Framework kütüphanesi ile Word projesi arasına bir WCF Servisi sokarak genişletebilirsiniz)

Kodlar

Tabiki ilk etapta Word projesinin, Entity Library kütüphanesini referans etmesi gerekmektedir.

Burada dikkat edilmesi gereken noktalardan birisi de Target Framework seçimidir. Oluşturduğumuz Word Document projesi .Net Framework 4.0 odaklıdır. Bu sebeple Entity Framework tabanlı olan Class Library projesinin de .Net Framework 4.0 odaklı olması gerekmektedir. Aksi durumda Target Framework uyuşmazlığı oluşuacak ve Word Document tipi projeye eklenen referans için bir Warning alınacaktır.

Kod yazımı işin eğlenceli taraflarından birisidir elbette Winking smile Word projesindeki docx dosyası, bir de C# kod dosyası içermektedir. Burada Word’ ün Object Model’ ine this anahtar kelimesi üzerinden kolayca erişilebilinir. Daha da önemlisi bazı olaylar(Events) yüklenerek dokümanının davranışları fonksiyonel olarak değiştirilebilir. Örneğin bu Word dokümanı kayıt edilmeden önce veya sonra devreye girecek olay metodları(Event Handlers) dahil edilebilir. Dolayısıyla yapabileceklerimiz oldukça geniş bir yelpazeye yayılmaktadır. Biz örneğimizde oldukça basit ilerledik ve aşağıdaki kod yapısını oluşturduk.

using AzonEntityLibrary;
using System;
using System.Windows.Forms;
using Word = Microsoft.Office.Interop.Word;

namespace ProductDocument
{
    public partial class ThisDocument
    {
        private void ThisDocument_Startup(object sender, System.EventArgs e)
        {
            this.Application.DocumentBeforeSave += Application_DocumentBeforeSave;
        }

        private void ThisDocument_Shutdown(object sender, System.EventArgs e)
        {

        }

        void Application_DocumentBeforeSave(Word.Document Doc, ref bool SaveAsUI, ref bool Cancel)
        {
            SaveToDatabase();
        }

        private bool SaveToDatabase()
        {
            bool result = false;
            try
            {
                using (DepomuzEntities context = new DepomuzEntities())
                {
                    Product newProduct = new Product
                    {
                        Name = TextBoxName.Text,
                        Description = TextBoxDescription.Text,
                        InsertDate = DateTimePickerEnterDate.Value,
                        ListPrice = System.Convert.ToDecimal(TextBoxListPrice.Text),
                        RealPrice = System.Convert.ToDecimal(TextBoxRealPrice.Text),
                        Quantity = System.Convert.ToInt32(NumericUpDownQuantity.Value),
                        Notes = TextBoxNotes.Text
                   };
                    context.Products.Add(newProduct);
                    context.SaveChanges();

                    result = true;

                    MessageBox.Show(
                        string.Format("Selim Ustam ürün {0} numarası ile dosyalandı"
                        ,newProduct.ProductId.ToString()));
                }
            }
            catch(Exception excp)
            {
                MessageBox.Show("Selim Ustam beni arar mısın? Sanırım işlemin sırasında bir hata oluştu");
                //TODO@Rukiye burada oluşan istisnaları kendime mail atayım ve hatta log dosyasına yazdırayım
            }

            return result;
        }

        private void ButtonAdd_Click(object sender, EventArgs e)
        {
            SaveToDatabase();
        }

        #region VSTO Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InternalStartup()
        {
           this.ButtonAdd.Click += new System.EventHandler(this.ButtonAdd_Click);
            this.Startup += new System.EventHandler(this.ThisDocument_Startup);
            this.Shutdown += new System.EventHandler(this.ThisDocument_Shutdown);
        }

        #endregion
    }
}

Kod dosyasında dikkat çeken noktalardan birisi default olarak üretilen olay metodlarıdır. ThisDocument_Startup ve ThisDocument_Shutdown. Bu olay metodları Word dokümanı başlatılırken ve kapatılırken devreye girmektedir. Örnekte, ThisDocument_Startup fonksiyonunda, DocumentBeforeSave olay metodunun yüklenmesi işlemi yapılmıştır.

Yeni bir ürünün Entity Frameworküzerinden veritabanına kayıt edilmesi işlemi iki farklı noktadan tetiklenmektedir. Birincisi, Word dokümanının Save komutu verildikten ama Save operasyonu tamamlanmadan öncedir. Bunun için dikkat edileceği üzere Application referansı üzerinden DocumentBeforeSave olay metodu yüklenmiştir. İkinci tetikleme noktamız ise Word dokümanı üstüne atılmış olan Button kontrolünün Click olay metodudur. Yani, bir Word dokümanının var olan Object Model’ ine ait olay metodlarından yararlanılabileceği gibi, doküman üzerine bırakılmış Windows Form kontrollerinin olay metodlarından da yararlanılabilinir.

SaveToDatabase metodu içerisinde Context nesnesi kullanılmaktadır. Word dokümanı üzerindeki kontrollere ait bilgiler yeni üretilen Product nesne örneğinin özelliklerine set edildikten sonras ise kayıt altına alma işlemi yapılmaktadır. Yeni Productörneği, Productsözelliği üzerinden Add metodu ile generic DbSet’ e  ilave edildikten sonra SaveChangesçağrısı ile de veritabanına yazılmaktadır.

Kaba Testler

Uygulamayı F5 ile çalıştırdığımızda ve örnek bazı veriler girdiğimizde kayıt işleminin başarılı bir şekilde gerçekleştirildiğine dair bir mesaj kutusu ile karşılaşırız. Aynen aşağıdaki ekran görüntüsündeki gibi.

wrp_6

Ki eklenme işleminin başarılı olup olmadığını hemen veritabanına bakarak öğrenebiliriz.

wrp_8

Eğer ters bir durum oluşursa(örneğin form boş iken Save etmeye çalışmak gibi), bu durumda bir çalışma zamanı hatası alınacak ama durum Selim Usta’ ya yumuşatılarak iletilecektir Laughing out loud

wrp_7

Build Sonrası

Aslında uygulamanın Build sonrası durumuna baktığımızda aşağıdaki şekilde görülen içeriğin üretildiğini fark edebiliriz.

wrp_9

Word Document projesi, ProductDocument.docx haricinde, Solution da kullanılan başka dosyaları da doğal olarak içerecektir. Tabi burada akla gelen ilk soru bu içeriği nasıl dağıtılabileceğidir? Development yapılan makinede herhangibir sorun olmayacaktır. Debug veya Release klasöründeki ProductDocument.dox dosyasının çalıştırılması yeterlidir. Ancak bu çözümü Selim Usta’ nın bilgisayarına yüklemek için en azından bir Setup paketine sahip olmak dağıtım işini kolaylaştıracaktır.

Bunun için Publish işlemi Word Document projesi için uygulanabilir. Burada Click Once teknolojisinden yararlanılabildiğini de belirtelim. Bir Publish paketini söz konusu uygulama için nasıl hazırlayabileceğinizi MSDN üzerindeki bu adresten öğrenebilirsiniz. Detayları ayrı bir makale konusu olacağından burada derinlemesine incelemedik.

İlgili adresteki içeriği dikkatlice okumanızı ve özellikle Post-Deployment kullanımına bakmanızı öneririm. Nitekim Post-Deployment sırasında, ilgili Word dosyasının bir kopyasının istemci bilgisayarda istenen bir klasöre atanması işlemi gerçekleştirilebilmektedir. Ayrıca Publisher’ ın Trusted olarak kabul edilebilmesi için gerekli Signing işlemlerini de atlamayın derim Winking smile 

Publish işlemi sonrası aşağıdaki ekran görüntüsündekine benzer bir içerik oluşacaktır. Setup dosyasını kullanarak tipik bir install işlemi gerçekleştirebilirsiniz. Ayrıca bu klasörde yer alan ProductDocumentçalıştırıldığında doğrudan tasarlamış olduğumuz Form’ un açıldığını görebiliriz.

wrp_10

Sonuç

Geliştirdiğimiz örnek aslında Document Level tipinden kabul edilen bir proje uygulamasıdır. İstenirse Application Levelşeklinde bir proje de üretilebilir ki bu durumda tüm Word dokümanları için uygulama seviyesinde bir geliştirme yapma şansına sahip olabiliriz. Elbette yazımıza konu olan ve Selim Usta’ nın işine yarayacağını düşündüğümüz çözümün geliştirilmesi gereken pek çok yeri vardır. Örneğin,

  • Form üzerinde bir doğrulama(Validation) mekanizması mevcut değildir. Çok doğal olarak Selim Usta sayısal olması gereken alanlara metinsel içerik girebilir.
  • Word dosyası üzerinden yeni bir ürün eklendiğinde belki form temizlenebilir ve hatta belki de alt sayfalarda yer alan bir DataGridView kontrolü içerisine, yapılan ekler basılarak özet bir durum raporu sunulabilir.
  • Selim Usta dalgınlıkla yanlış bir içerik girebilir. Bu durumda ilgili kayıtları silmek isteyecektir. Bu vakanın da doküman yoluyla karşılanması gerekebilir. Ya da mevzunun Rukiye’ ye iletilmesi yolu tercih edilebilir Smile
  • Bir WCF servis katmanının olması da çok daha iyi olabilir. Nitekim bu sayede Word Document projesini kullanan herhangibir istemci bilgisayarının sadece servise ulaşması yeterli olacaktır. Ki servis de Host edildiği makinede Entity Framework’ü kullanarak bir Repository’ ye yazma işlemini icra edebilir ve bu sayede Word dosyasının olduğu bilgisayar için Loosely Coupled bir geçerlilik de sağlanmış olur. 

Bu ve benzeri eksiklikleri siz değerli okurlarıma bırakıyorum. Diğer yandan senaryoyu biraz daha farklılaştırabilirsiniz. Söz gelimi TFS Client Object Model’ i de işin içerisine katabilir ve bir WordTemplate’ i üzerinden Product Backlog Item ve Task girişlerini yaptırabilirsiniz. Bu ödev için aşağıdaki grafiği göz önüne alabilirsiniz.

wrp_11

Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim Winking smile

HowTo_Word.zip (1,80 mb) 
[Dosya boyutunun küçülmesi için Packages ve Release klasörleri silinmiştir]

[Orjinal Yazım Tarihi 03-15-2013]

STSdb ile Hello World

$
0
0

Cadillac_STS[İlk Yazım Tarihi 2013-01-15]

Merhaba Arkadaşlar,

Matrix Reloaded'ı seyrettiğim zamanları düşündüğümde, anımsadıklarım arasında heyecanlı aksiyon sahnelerinde yer alan ve eski Amerikan stilini de yansıtan kocaman otomobiller vardı. (Hatta bildiğim kadarı ile ikinci dünya savaşı sonrası çelik stoklarının fazlalığı nedeniyle Amerikan otoları hep kocaman olmuşlardı)

General Motors firmasına ait olan otomobillerden birisi de, Cadillac STS' in farklı bir versiyonu olan CTS idi. Tabi ben konuyu bir şekilde bu günkü yazının konusu olan STSdb' ye getirmek istediğimden Cadillac STS' e ait bir fotoğrafa yer vermek istedim SmileÖyleyse vakit kaybetmeden konumuza geçelim.

Bilindiği üzere bir süredir NoSQL(Not only SQL) veritabanlarını incelemeye(öğrenmeye)çalışıyoruz. Daha önceki yazılarımızda Apache Cassandra, RavenDB ve DEXürünlerine bir göz atmış ve .Net uygulamalarında nasıl kullanılabileceklerini görmüştük. Klasik olarak yaptığımız Hello World uygulamalarının bir benzerini de bu gün inceleyeceğimiz STSdb için gerçekleştiriyor olacağız.

Bu veritabanı için bazı kaynaklarda Revolutionary(Devrimci, devrimsel, devrim niteliğinde) sıfatı kullanılmış. Gerçekten böyle bir sistem olduğunu ispat etmemiz oldukça zor tabi. Yine de bu konu ile ilişkili yapılmış bir kaç veritabanı testine bakmak az da olsa fikir verebilir. Söz gelimi kaynağının ne kadar güvenilir olduğunu tam olarak bilmediğim bu adreste, karşılaştırıldığı veritabanlarına göre en hızlısı olduğu iddia edilmiş.

Aslında bizim temel amacımız key-value modeline göre çalışan NoSQL veritabanlarından bir diğerini incelemektir Winking smile Dilerseniz STSdbürününü kısaca mercek atlına almaya başlayalım.

STSdb ile ilişkili olarak karşılaştığım ilk sıkıntı dokümantasyonun yeteri kadar doyurucu olmamasıydı. MSDN benzeri API dokümantasyonu ve bir kaç örnek kod parçası haricinde, forumlarda da yeteri kadar doyurucu içeriğe rastlayamadım. Amazon.com' da konu ile ilişkili bir kitabı da ne yazık ki bulamadım. Pek tabi zaman ilerledikçe bu durum değişebilir.

Hal böyle olunca biraz dene-keşfet modeline göre öğrenilmeye çalışılan bir ürün oldu benim için. Kaldı ki API dokümantasyonuna baktığımızda gerçekten çok önemli görünen pek çok fonksiyonellik var. Örneğin Snapshot almak için XTable sınıfına ait bir metod var ama örnek bir kod parçası yok. Dolayısıyla biraz kurcalanması gereken bir ürün olarak karşımıza çıkmakta.

STSdb, Key-Value modeline göre çalışan açık kaynak(GPL 2 ve GPL 3. Ancak Community lisanslaması da yapılabiliyor)NoSql depolama API' lerinden birisidir. .Net Frameworküzerinde C# programlama dili kullanılarak geliştirilmiş gömülü(Embedded) bir sistem olarak karşımıza çıkıyor. Her platformu destekleyebilir ilkesini ilgili platformlarda Mono yüklü olması halinde karşılamakta(Tam bir plaform bağımsızlık olduğunu ifade edemeyiz ama Mono ile Linux, MacOS X ve Unix gibi sistemlere de entegre edilebilir) 

Genel Özellikler

Genel özelliklerini ise aşağıdaki maddeler halinde ifade edebiliriz.

  • .Net Framework’ e gömülü olarak geliştirildiğinden doğrudan uygulama ile ilişkilendirilip çalıştırılabiliyor. Yani ek bir konfigurasyon hazırlığına ihtiyaç yok.
  • Veriyi disküzerinde, bellekte(In-Memory)veya her ikisinin kombinasyonu olacak şekilde hibrid bir yapıda tutabilir.
  • Kullanılabilirlik alanları oldukça geniştir. İnsan Makine Arayüzleri(Human Machine Interface), Süreç kontrol sistemleri(Process Control System), Otomotiv Endüstürisi, çeşitli tipte finansal uygulamalar vb...
  • Mantıksal modeli iki katmandan oluşmaktadır. Birinci kat dahili dosya sistemidir(File System). Bu katman 2üzeri64 dosyaya(Her biri ile de 2üzeri64 byte’ a çıkılabilir) kadar destek vermektedir.
    İkinci katman ise tabloların tutulduğu veritabanıdır(Database Layer). Burada tablo yönetimine ilişkin işlemler yapılır. Tabloların oluşturulması, silinmesi, tablolar üzerinde ileri/geri hareket edilmesi, veya düzenlenmesi bu operasyonlar arasında sayılabilir.
  • Transaction desteği vardır. Bu nedenle Atomicity, Consistency, Isolation ve Durability olarak bilinen ACID prensiplerini karşılamaktadır.
  • Karmaşık bir .Net tipi, primary key olarak kullanılabilir.
  • Snapshotözelliği sayesinde gerçek zamanlı yedekleme(Real-time Backup) imkanı da sunar.
  • Çok doğal olarak .Net Frameworküzerinde geliştirilmiş olduğundan LINQ(Language INtegrated Query)desteğine sahiptir.
  • Veriyi bir algoritma yardımıyla sıkıştırmaktadır. Sıkıştırma önemli ölçüde yer kazanımına da imkan sağlamaktadır.
  • Belki de en önemli özelliklerinden birisi tutabileceği tablo veya kayıt için bir üst limit değerinin olmayışıdır. Bu nedenle çok büyük boyutlu veri kümeleri için tercih edilebilir.

İlk olarak ürünü tedarik etmemiz gerekiyor tabi ki Laughing out loud  Bunun için şu adresi kullanabiliriz. İndirilen içerik aşağıdaki gibidir. (Yazının yayınlandığı tarihi itibariyle 4.0 RC sürümüde mevcut)

sts_2

Tahmin edileceği üzere STSdb.dll assembly’ ının, projeye referans edilmesi kullanılması için yeterlidir.

sts_1

Console uygulaması olarak geliştireceğimiz programımızda Hello World demek maksadıyla aşağıdaki kod içeriğini yazdığımızı düşünelim. Table Record olarak AutoMobile isimli bir sınıftan yararlanıyor olacağız.

sts_3

Veri Ekleme Operasyonu

ilk olarak veri ekleme işini sembolize edelim.

using STSdb.Data;
using System;
using System.Linq;

namespace HelloSTSdb
{
    class Program
    {
        static void Main(string[] args)
        {
            Add();
        }

        private static void Add()
        {
            var index1 = new XKey<byte[], string>(Guid.NewGuid().ToByteArray(), "Kadillak STS");
            var index2 = new XKey<byte[], string>(Guid.NewGuid().ToByteArray(), "Lamborjini Diyablo");
            var index3 = new XKey<byte[], string>(Guid.NewGuid().ToByteArray(), "Ferrarriim");
            var index4 = new XKey<byte[], string>(Guid.NewGuid().ToByteArray(), "Doğanım");

            #region Örnek veri kümesi oluşturulması

            using (StorageEngine sEngine = StorageEngine.FromFile("gm.stsdb"))
           {
               var table = sEngine.Scheme
                    .CreateOrOpenXTable<XKey<byte[], string>, AutoMobile>(new Locator("Automobile"));
                table.KeyMap = new XKeyMap<byte[], string>(null, 16, null, 512);

                sEngine.Scheme.Commit();

                table[index1] = new AutoMobile
               {
                    Manufacturer = "Ceneral Motor Kampani",
                    Model = "STS Turbo CRDI AK/S",
                    Price = 90000,
                    ProductionDate = "2005"
                };
                table[index2] = new AutoMobile
                {
                    Manufacturer = "İtalyan cob",
                    Model = "Diablo III",
                    Price = 250000,
                    ProductionDate = "2008"
                };
                table[index3] = new AutoMobile
                {
                    Manufacturer = "Ferrari",
                    Model = "799",
                    Price = 350000,
                    ProductionDate = "2013"
                };
                table[index4] = new AutoMobile
                {
                    Manufacturer = "Türk işi",
                    Model = "Doğan SLX/AK 8 ileri",
                    Price = 450,
                    ProductionDate = "2014"
                };

                table.Commit();
                table.Close();
            }

            #endregion
        }

    }

    public class AutoMobile
    {
        public string Model { get; set; }
        public string Manufacturer { get; set; }
        public string ProductionDate { get; set; }
        public decimal Price { get; set; }

        public override string ToString()
        {
            return string.Format("{0} {1}({2}) by {3}", ProductionDate, Model, Price, Manufacturer);
        }
    }
}

Dikkat edileceği üzere StorageEngine ana nesnedir. using bloğu ile kullanılabilen, bir başka deyişle Dispose edilebilir olan bu nesne örneğinin FromFile metodundan yararlanarak depolama işlemini yapan dosya belirtilmektedir. Key değerleri için XKey<byte[],string> tipinden nesneler örneklenmiştir. Veriyi tutacak olan ise XTable tipine ait nesne örnekleridir. Bu tanımlama için StorageEngine nesne örneği üzerinden hareket edilir ve CreateOrOpenXTable metoduna başvurulur. Bir başka deyişle bir şema tanımı yaptığımızı ifade edebiliriz.

Dikkat edilmesi gereken noktalardan birisi de Key olarak Primitive tip kullanmadığımız için KeyMapözelliğine bir değer atama zorunluluğumuz olmasıdır. Bu atama sırasında, XKey içerisindeki tip yapısına göre maksimum boyutlar belirtilmek zorundadır. Aksi durumda çalışma zamanında nullreferans nedeniylebir istisna(Exception)alınacaktır.

Veri ekleme işlemi aslında son derece basittir. XTable tipinin indeksleyici operatörüne tanımlanan Keyörnekleri atanır. Eşitlikten sonra ise yine bildirimi yapılmış olan tipten(ki örneğimizde AutoMobile sınıfına ait nesnelerdir)örnekler atanır. Tüm işlemlerin tamamlanmasının ardından bir Close ve Commit çağrısının yapılması, verilerin kalıcı olarak yazılması açısından kritiktir. Console uygulamasını bu şekilde çalıştırdığımızda dosya sistemi üzerinde gm.stsdb isimli bir dosya oluşturulduğu gözlemlenir.

sts_4

Veri Okuma

Gelelim veri okuma işine. Örneğin az önce eklemiş olduğumuz XTable içeriğini ekrana yazdırmayı deneyelim. Bu amaçla aşağıdaki Read metodunu ele alabiliriz.

private static void Read()
{
    #region Veri okunması

    using (StorageEngine sEngine = StorageEngine.FromFile("gm.stsdb"))
   {
        var table =sEngine.Scheme
           .OpenXTable<XKey<byte[], string>, AutoMobile>(new Locator("Automobile"));

        foreach (var row in table.Where(r => r.Record.Price > 100000))
        {
            Console.WriteLine("{0}\n{1}\n"
                , row.Key.ToString()
                , row.Record.ToString()
                );
        }

        Console.WriteLine("\nTüm Kayıtlar\n");
        foreach (var row in table)
        {
            Console.WriteLine("{0}-{1}\n{2}\n"
                , (new Guid(row.Key.SubKey0)).ToString()
                , row.Key.SubKey1
                , row.Record.ToString()                
                );
        }

        table.Close();
    }

    #endregion Veri okunması
}

Okuma işleminde başrol oyuncusu olarak yine, StorageEngine sınıfı devreye girmektedir. FromFile metodu ile sts veritabanı dosyası yüklendikten sonra ise, veri okuması yapılmak istenen XTable’ ın açılması gerekmektedir. Bu işlem için OpenXTable metodundan yararlanılmaktadır. Generic parametreye(<XKey<byte[], string>, AutoMobile>) dikkat edileceği üzere, oluşturulan XTable’ ın şema yapısına uygun olacak şekilde verildiği görülmektedir.

table nesne örneği elde edildikten sonra basit bir foreach iterasyonu ile kayıtlar arasından dolaşılabilinir. Hatta örnekte görüldüğü gibi Where genişletme metodundan yararlanılarak bir LINQ sorgusunun icra edilmesi de sağlanabilir. lambda(=>) operatörü etrafında kullanılan r değişkeni, AutoMobile sınıfından bir nesne örneğidir. Dolayısıyla özellikleri sorgulamada filtre kriteri olarak kullanılabilir. Uygulamanın çalışma zamanı çıktısı aşağıdaki gibi olacaktır.

sts_5

Temel olarak key-value teorisini baz alarak çalışan STSdb sisteminde, tablo anahtar(Table Key) ve kayıtlarının(Table Record) hangi türlerden oluşabileceği belirlidir. Burada oldukça geniş bir nesne yelpazesinin olduğunu ifade edebiliriz.

Tablo anahtarlarına(Table Keys) baktığımızda aşağıdaki tiplerin desteklendiğini görmekteyiz.

  • .Net primitive types (Boolean, Byte, Char, DateTime, Decimal, Double, Int16, Int32, Int64, TimeSpan, SByte, Single, String, UInt16, UInt32, Uınt64)
  • byte[] dizisi(Örneğin bir resmin byte içeriğini Key olarak kullanabilirsiniz Winking smile )
  • STSdb’ ye özgün Locator tipi
  • Tn primitive olmak suretiyle XKey<T1, T2[, T3[, T4[, T5[, T6[, T7]]]]]>,
  • IKeyMap<TKey> arayüzü(Interface) türetmeleri

Tablo kayıtlarına(Table Records) baktığımızda ise benzer tiplerin desteklendiğini görürüz.

  • .Net primitive types (Boolean, Byte, Char, DateTime, Decimal, Double, Int16, Int32, Int64, TimeSpan, SByte, Single, String, UInt16, UInt32, Uınt64)
  • byte[] dizisi, enum, Type Winking smile, Image, Icon, MemoryStream, Guid, XElement gibi diğer tipler
  • STSdb’ ye özgü Locator ve BlobStream(Çok büyük boyutlu binary içerikleri alabilirsiniz demek)
  • Serileştirilebilir nesneler(Serializable Objects)
  • IBinaryPersist<IIndexer<TRecord>> arayüzünün türevleri (Özellikle kendi Persistence mekanizmamızı yazmak istediğimiz durumlarda yine bu arayüzden türetilmiş tipler söz konusu olacaktır. Bir başka deyişle Binary olarak yazma ve okuma işlemlerine müdahale edip Persistence şeklini değiştirebiliriz)

Görüldüğü üzere özellikle Key değerlerini karmaşık tipler şeklinde tutmak mümkün. Burada nesne yönelimli dünyanın(Object Oriented World) faydalarını da görüyoruz. Interface türetmeli tiplerin Key veya Record olarak kullanılabilmesi bunun en güzel örneği belki de.

STSdb’ yi göz önüne alırken .Net Framework platformu için garanti, embedded bir NoSQLçözümü olduğunu ve özellikle Cross-Platform alanında Mono’ ya bağımlı olduğunu unutmamalıyız. Bu bir kısıtlama gibi görünse de, ürünlerini .Net ortamında geliştirenler için çok da büyük bir sorun değil. Ayrıca sunduğu üst limitsiz depolama alanı, Transaction, Snapshot, sıkıştırma kabiliyetleri vb diğer imkanlar nedeniyle, özellikle büyük boyutlu veri kümeleri için ideal görünüyor. STS firmasının bir ürünü olan bu veritabanı, şirketin uzun yıllardır FOREX pazarı üzerine yaptığı uygulama geliştirmelerine ait tecrübelerinin bir çıktısı aslında.

Bu yazımızda kısaca STSdbürününü incelemeye çalıştık. Bir Hello World uygulaması geliştirdik ve veri ekleme ile okuma işlemlerinin nasıl yapılabildiğine baktık. Elbette örnekleri zenginleştirmek ve gerçek saha tecrübesini yapmak sizin elinizde. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

HelloSTSdb.zip (249,52 kb)

[İlk Yazım Tarihi 2013-01-15]

Nedir Bu MSBuild?

$
0
0

msbuild_9[Orjinal Yazım Tarihi – 25/02/2013]

Merhaba Arkadaşlar,

Yıllar öncesinde bir kaç seneliğine de olsa saygın bir eğitim kurumunda eğitmen olarak görev alma şansını yakalamıştım. Özellikle C#’ ın öğretilmeye çalışıldığı başlangıç niteliğindeki seanslarda dilin temel özelliklerini anlatırken, tüm dış çevre ile olan bağlantıyı kesip, sadece anahtar kelime(keyword), ifade ve materyale odaklanmaya çalışırdık. Bu sebepten genellikle ilk örneklerimiz ve Hello World uygulamamız, Notepad gibi bir program ve komut satırındaki csc(C# Compiler) ile inşa edilirdi.

O zamanlar bu bizim için yeterli görünüyordu ama tabi .Net Framework 2.0 ile birlikte hayatımıza yeni bir inşa süreci de girdi. Aslında bu günkü konumuzda da, Notepad(tam olarak Notepad 2) ve komut satırı aracını kullanarak ilerlemeye çalışıyor olacağız. Amacımız MSBuild platformunu çok kısaca tanımaya ve anlamaya çalışmak.

Microsoft Build Engine aslında başlı başına bir platformdur. Kısaca MSBuild olarak anılmaktadır ve bir uygulamanın inşa edilmesi noktasında devreye giren XML(eXtensible Markup Language) tabanlı bir Script bütününü esas alır. Kısacası uygulamanın inşa edilmesi sırasındaki aşamalar XML tabanlı bir akış olarak ifade edilebilmektedir. MSBuild platformunun en önemli özelliği ise, inşa sürecinde Visual Studio gibi bir araca ihtiyaç duymuyor oluşudur.

Evet Visual Studio’ nun kendisi, Build işlemlerinde bu platformu kullanmaktadır doğru ama, tam tersi durum geçerli değildir. Yani istersek MSBuild aracını kullanarak, bir uygulamanın veya uygulama ortamının üretilmesi sırasındaki aşamaları, basit bir Notepad aracı ile tasarlayabilir ve MSBuild.exe’ den yararlanarak hayata geçirebiliriz(Ancak bu gün şanslısınız çünkü Notepad2 isimli ürünü kullanacağız. Sourceforge adresinden indirebilirsiniz  Smile )

Bu fikir tabi ki otomatize edilmiş Build işlemlerinin de icra edilebileceği anlamına gelmektedir. Ki Team Foundation Serverürünü de MSBuild’ un etkin bir şekilde kullanılmasına olanak tanımaktadır. Özellikle Team Foundation Build olarak anılan platform içerisinde Build Server’ un kurulduğu ortam MSBuild Script’ lerini kullanarak üretim işlemlerini gerçekleştirmektedir. Söz gelimizi TFS tarafında eğer Continous Integration gibi bir strateji tercih edilmişse, kodlamacıların Check-In işlemleri sonrası Team Foundation Build devreye girecek ve MSBuild script’ leri otomatik olarak çalıştırılarak inşa işlemleri icra edilecektir. (Team Foundation Build ayrıca incelenmesi gereken bir konu olduğundan bu yazımızda detaylandırılmamıştır) 

Visual Studio ortamında geliştirdiğimiz projeleri göz önüne aldığımızda, oluşturulan proje dosyaları içerisinde, MSBuild’ un kullanacağı ayarlar(Settings) ve bazı koşul bağlı işlevsellikler konuşlandırılmaktadır. Tabi geliştirici olarak bu kısımlar ile pek fazla uğraşmayız ve nihayetinde Visual Studio bizim için bu akışları otomatik olarak inşa eder. Ancak Release Manager gibi pozisyonlar özellikle bu proje dosya içeriklerini değerlendirerek genişletmeler yapabilir ve MSBuild sürecini farklılaştırabilirler.

Visual Studio ile geliştirilen proje dosyaları C# tarafı için csproj, Visual Basic tarafı için vbproj, Managed C++ tarafı içinse vcxproj uzantılı olanlardır.

Hangi Durumlarda MSBuild

Peki MSBuild ağırlık olarak hangi hallerde ele alınır.

  • En bilinen sebep elimizde Visual Studio olmayan bir ortamda inşa etme işlemlerinde değerlendirilebiliyor olmasıdır.
  • Compiler devreye girmeden önce bazı dosyaların Process edilmesi gereken durumlarda ele alınabilir.(Pre-Processing Steps) 
  • Benzer şekilde Process sonrası yapılması istenen işlemler içinde kullanılabilir(Post-Processing Steps)
  • Build işlemi sonucu çıktıların farklı bir klasöre taşınması sağlanabilir. (Ki bu klasör pek çok projenin ortaktaşa kullandığı bir assembly için paylaşımdaki bir makine bile olabilir)
  • Çıktıların sıkıştırılması istendiği bir durumda ele alınabilir.
  • İnşa işleminin birden fazla Process’ e bölünerek daha hızlı tamamlanması istendiği durumlarda değerlendirilebilir. (Özellikle Enterprise çözümlerde, n sayıda Branch’ in ve dolayısıyla çıktının söz konusu olabileceği senaryolarda, uzun sürebilecek Build işlemleri için kritik bir özelliktir)
  • MSBuild 64bitlik bir sistem için inşa işlemini icra edebilir.
  • Build işleminin herhangibir noktasında harici bir aracın kullanılması istendiği hallerde göz önüne alınabilir.

ve benzeri pek çok durumda MSBuild’ u açık bir şekilde özelleştirerek kullanabiliriz.

Proje Dosyasının İçine Bakalım

Hızlı bir şekilde uygulama geliştirme işine giren pek çok yazılımcı çoğunlukla MSBuild gibi programların ürettiği çıktıları göz ardı etmektedir. Nasıl olsa binlerce dolar verilerek satın alınan Visual Studio bizim yerimize pek güzel bu işi halletmektedir. Ancak gerçek hayatta öyle vakalar ve senaryolara vuku bulmaktadır ki, bunların üstesinden gelebilmek için özelleştirmelere gidilmesi şart olmaktadır. Bu özelleştirme konsepti MSBuild tarafı için de geçerlidir. Bu sebepten proje dosyalarının içeriğinin az da olsa bilinmesi en azından şema yapısının anlaşılması yararlıdır. Tipik olarak bir Console uygulaması dahi açsak, üretilen proje dosyası içerisinde(ki örneğimiz csproj uzantılı olandır) aşağıdakine benzer bir içerik oluştuğu görülecektir.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{21E86FBF-5A78-4175-8522-A6FC854BB637}</ProjectGuid>
    <OutputType>Exe</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>ConsoleApplication33</RootNamespace>
    <AssemblyName>ConsoleApplication33</AssemblyName>
    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="System.Data.DataSetExtensions" />
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System.Data" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
</Project>

Bu XML içeriğinin şema yapısına bakıldığında aşağıdaki grafikte görülen iskeletin söz konusu olduğu görülecektir.

msbuild_2

Aslında içerik okunduğunda, Visual StudioProjeÖzelliklerinden de ayarladığımız pek çok öğenin buraya yazıldığı görülebilir. Örneğin PropertyGroup elementlerinde Debug, Debug-Any CPU ve Release-Any CPU için bazı atamalar söz konusudur.

Debug ile alakalı PropertyGroup’ a bakıldığında uygulamanın vereceği çıktının exe olacağı, .NetFramework 4.5 platformunu hedef aldığı, Assembly adının ConsoleApplication33 olduğu ve buna bağlı olarak da RootNamespace’ in yine ConsoleApplication33şeklinde set edildiği görülebilir. Condition niteliklerinde belirtilen kısımlar, MSBuild uygulamasına bazı kriterlere göre nasıl çıktı üretmesi gerektiğini de söylemektedir. Örneğin uygulama Release modda inşa edildiğinde, çıktının bin\Release klasörüne doğru yapılması gerektiği yine bir PropertyGroup içerisinde ifade edilmiştir.

Condition niteliklerinde kullanılan $ notasyonu mutlaka dikkatinizi çekmiştir. Aslında burada $[PropertyName] şeklinde bir kullanım söz konusudur. Burada inanılmaz geniş bir esneklik vardır.

Örneğin $(registery:Hive\SomeKey\SomeSubKey@Value) gibi bir ifade ile Registery’ deki bir değeri okuyabilir ve Build içerisinde kullanabiliriz. Ya da $([System.DateTime]::Now.ToString("yyyy.MM.dd")) gibi bir kullanım ile o anki zamanı istediğimiz formatta elde edebiliriz vb…

[PropertyName] yerine gelecek MSBuild özelliklerinin neler olabileceğini MSDN adresindenöğrenebilirsiniz.

ItemGroup elementleri içerisine bakıldığında, uygulamanın referans ettiği diğer Assembly’ ların adları, Compile işlemi sırasında derlemeye tabi olacak C# dosyaları gibi bilgiler yer almaktadır. Visual Studio’ nun ürettiği XML içeriğine bakıldığında, son kısımda yorum satırları içerisine dahil edilmiş Target isimli bir element daha olduğu görülmektedir. Bu elementi kullanarak MSBuild için bazı Task’ lar tanımlanabilir ve inşa işlemi sırasında devreye girmeleri sağlanabilir.

Task olarak yapılan tanımlamalar paylaşılabilir ve farklı geliştirme Build’ larında da kullanılabilir. Bu yüzden bir yazılım evinin Build işlemlerinde standart olarak gerçekleştirdiği bazı yürütmeler, Task olarak tanımlanıp ilgili Build paketlerine gömülebilir. Ayrıca Task’ lar Reusableözelliği taşımaktadır. (Target elementleri genellikle yazıldıkları sırada çalışmaktadır. Yani belirli bir sırada icra edilmesi istenen Task’ lar var ise, Target elementinin buna uygun olacak şekilde kullanılması gerekir)

Schema yapısı bu kadar basit değildir. Kullanılabilecek tüm elementler için MSDN üzerindeki şu adrese gitmeniziöneririm.

Klavye Başına

Yazımızın bu bölümünde basit bir örnek geliştirmeye çalışıyor olacağız. Amacımız temel seviyede MSBuild aracını kullanmak ve konsepti anlamaya çalışmak olacaktır. İlk olarak basit bir C# koduna ihtiyacımız var. Bu amaçla örneğin C:\Samples\HowToMSBuild\ isimli klasör altında, aşağıdaki içeriğe sahip bir C# dosyası oluşturduğumuzu düşünelim.

msbuild_3

Bu adımdan sonra yine Notepad2 aracını kullanarak aşağıdaki içeriğe sahip bir csproj dosyası üreterek devam edelim.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <Compile Include="MSBuildHowTo.cs" />
  </ItemGroup>
  <Target Name="Build">
    <Csc Sources="@(Compile)"/> 
  </Target>
</Project>

Project içerisinde bir adet ItemGroup ve Target elementi yer almaktadır. ItemGroup içerisinde yer alan Compile elementi, Includeniteliğinde(attribute), derleme işlemine tabi olacak dosyayı belirtmektedir. Target altındaki Csc elementinin Sources niteliğinde ise Compile isimli bir komut yer almaktadır. Dolayısıyla bu Task, MSBuild aracına, Compile elementine dahil edilen dosyanın derlenmesi gerektiğini söylemektedir.

Bu içeriği Builder.csproj adı ile kaydettikten sonra ise sıradaki operasyon, MSBuild komut satırı aracını kullanarak inşa işlemini yürüttürmektir. Bunun için MSBuild’ u aşağıdaki gibi kullanabiliriz.

msbuild Builder.csproj /t: Build /verbosity: detailed

msbuild_5

Görüldüğü gibi çalışma sonrasında MSBuildHowTo isimli bir exe dosyası oluşmuştur. Söz konusu exe dosyasını doğrudan çalıştırdığımızda ise, pre-precessor direktifi dışında kalan kod parçasının yürütüldüğü gözlemlenecektir.

MSBuild özellikle kurulu olan .Net Framework versiyonuna bağlı olaraktan Microsoft.Net\Framework\vX.X.XXXXX altında yer almaktadır. Örneğin ben kendi sistemimde aşağıdaki klasörde yer alan sürümü kullandım.

msbuild_4

Bunu sistem’ de Path olarak belirtebiliriz ama dilerseniz doğrudan Visual Studio Command Prompt’ tan da yararlanabiliriz.

CSPROJ İçeriğini Genişletelim

Şimdi csproj dosyasının içeriğini biraz daha genişletelim ve aşağıdaki hale getirelim.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <AssemblyName>MSBuildHowTo</AssemblyName>
    <OutputPath>Bin\</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="MSBuildHowTo.cs" />
  </ItemGroup>
  <Target Name="Build">
    <MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
    <Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
  </Target>
</Project>

Bu sefer PropertyGroup içerisinde assembly adını(AssemblyName elementi) ve build işlemi sonrası ortaya çıkacak olan çıktının konuşlandırılacağı klasörü de belirttik(OutputPath). Diğer yandan bu örneğimizde yer alan en önemli kısım Build isimli Target elementinin içeriğidir. Dikkat edilecek olursa ilk sırada MakeDir isimli element yer almaktadır. Burada $(OutputPath) ile çıktının yapılacağı klasör işaretlenirken aslında PropertyGroup>OutputPath elementinin içeriği ifade edilmektedir. Önemli olan kısım ise Condition niteliğinde yazılan değerdir. !Exists(‘$(OutputPath)’) ifadesi ile şu söylenmektedir. “OutputPath yoksa…”. Buna göre eğer Condition sağlanırsa, MakeDirOutputPath’ in oluşturulması gerektiğini belirtecektir.

Csc elementinde ise derleme işlemi için kullanılacak olan kaynak Sources elementi ile ifade edilirken, aslında ItemGroup içerisindeki Compile elementinde yer alan Include niteliğinin değeri işaret edilmektedir. OutputAssembly niteliğine atanan değer ise, OutputhPathözelliğinin belirttiği klasörün altına AssemblyName’ in işaret ettiği isimle bir exeüretilmesi gerektiğini belirtmektedir. Buna göre komut satırından yapılan MSBuildçağrısı sonucu aşağıdaki gibi olacaktır.

msbuild Builder.csproj /t: Build /verbosity: detailed

msbuild_6

Oldukça zevkli öyle değil mi? Smile

Target Belirtmek

Öyleyse gelin olayı biraz daha genişleterek devam edelim.

<Project DefaultTargets="Build" xmlns=" http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <AssemblyName>MSBuildHowTo</AssemblyName>
    <OutputPath>Bin\</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="MSBuildHowTo.cs" />
  </ItemGroup>
  <Target Name="Build">
    <MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
    <Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
  </Target>
  <Target Name="Clean" >
    <Delete Files="$(OutputPath)$(AssemblyName).exe" />
  </Target>
  <Target Name="Rebuild" DependsOnTargets="Clean;Build" />
</Project>

Bu seferki çalışmada 3 adetTarget elementi söz konusudur. Ancak her birinin Name niteliğinin değerleri farklıdır. Buna göre, Build, Clean ve Rebuild isimli Target’ lar olduğunu ve aslında bunların 3 ayrı Task’ ı ifade ettiğini düşünebiliriz. Yani MSBuild aracını kullanırken /t: [TargetNameValue]şeklinde bir yaklaşım ile, istediğimiz Target’ın yürütülmesini sağlayabiliriz. Söz gelimi;

msbuild Builder.csproj /t: Clean /verbosity: detailed

şeklinde yapılan çağrı sonucunda, proje dosyasındaki Target elementlerinden Clean isimli olanı çalıştırılacaktır. Bu elementte ise Delete isimli bir alt element yer almakta olup Files niteliğinde belirtilen kritere göre, Bin klasöre altında assembly adı ile duran exe dosyasının silinmesi gerektiği ifade edilmektedir.

msbuild_7

Eğer Rebuild takısını kullanırsak bu durumda Target->Name niteliği Rebuild olan bölüm devreye girecektir. Bu vakada dikkat edilmesi gereken ise DependsOnTargets niteliği içerisinde yazılan Clean;Build ifadesidir. Yani şunu ifade etmiş oluruz;

Ey MSBuild!…Önce Clean isimli Target, sonrasında ise Build isimli Target içeriğini icra et

ki bu durumda Bin klasörü içeriği silinecek ve tekrardan bir derleme işlemi yapılarak, orada ilgili exeçıktısının üretilmesi sağlanacaktır.

msbuild Builder.csproj /t: Rebuild /verbosity: detailed

msbuild_8

Örnekler daha da çoğaltılabilir. Yapılabilecek pek çok şey var Winking smile Bunun için mutlaka MSDN orjinli  bir kaynağa başvurmanızı veya yazımızın sonunda belirttiğimiz tarzdaki bir kitabı tedarik etmenizi öneririm. Ancak temel mantığı ifade edebildiğimizi varsayıyorum. Bundan sonrasında Condition’ lara ve kullanılabilecek element/attribute tiplerine bakılması yeterli olur düşüncesindeyim.

.Net Framework 4.5 ile Gelen Yenilikler

MSBuildürününe Framework 4.5 sürümü ile birlikte bazı geliştirmeler ve yeni özellikler de katılmıştır. Henüz inceleme fırsatı bulamadığım bu özellikleri aşağıdaki maddeler ile özetleyebiliriz.

  • ARM(Advanced RISC Machine) desteği gelmiştir. Yani Buildçıktıları ARM işlemcilerini hedef alacak şekilde üretilebilir.
  • Bir Task (Target elementi ile belirtilen bir görev diyelim) süreç dışı(Out of Process) modda çalışmaya zorlanabilir.
  • Yeni bazı XMLelement ve nitelikleri(Attributes) gelmiştir.
  • Klasik bir cümle olacak ama, Performans(Performance) ve Ölçeklendirme(Scabilitiy) noktasında iyileştirmeler vardır Smile

MSBuild’ un etkili kullanımı üzerine geliştirilmiş bazı araçlar da vardır. Görsel arabirimleri olan bu araçlar yardımıyla MSBuild akışlarını daha kolay yönetebiliriz. Attrice firmasının bu alanda ön plana çıkan Microsoft Build Sidekick isimli ürünü gibi.

Öneri Kitap

msbuild_1Microsoft’ un Visual Studio ailesi ve geliştirme platformu oldukça geniş bir alana yayılmakta olup, pek çok notkasında uzmanlık gerektiren yapılar içermektedir. Bu sebepten söz konusu yapılara yönelik pek çok yayın da(kitap, official site, blog vb) mevcuttur.

Örneğin MSBuild tarafında daha önceden yayınlanmış olan Inside The Microsoft Build Engine isimli kitabın Nisan ayı içerisinde yayınlanan yeni bir tamamlayıcı baskısı mevcuttur. Yaklaşık olarak 120 sayfalık bir kitap olmasına rağmen odaklandığı konu özünde MSBuild ürünüdür. Kitaba Amazon üzerinden bu adres yardımıyla erişebilirsiniz.

Böylece geldik bir yazımızın daha sonuna. Bu yazımızda kısada olsa, MSBuild platformunu ve XML tarafındaki betikleri(Scripts) anlamaya çalıştık. Ağırlıklı olarak bir inşa sürecine müdahale edebildiğimizi, bunun için platformun sunduğu bazı standart element ve niteliklerin olduğunu gördük. Devamı sizde artık Smile Bir başka yazımızda görüşünceye dek hepinize mutlu günler dilerim.

[Orjinal Yazım Tarihi – 25/02/2013]

[Güncel bilgi –> MS Build is now part of Visual Studio!]

MigraDoc ile PDF Rapor Üretimi - Hello World

$
0
0

cpdf_10"Mösyö Reno" dedi, oturduğu yerden Jimmy Carl.

Uzun süredir bu dev şirketi yönetiyordu. Son zamanlarda teknolojiye büyük yatırım yapan firmanın, bundan en iyi şekilde yararlanabilmesini isteyenlerin başında geliyordu. Oldukça meraklı biri olan Carl, bilgisayarına bakarken içeriye orta boylarda, saçlarının bir kısmı ağırmış, numarası büyük olduğu belli olan kalın çerçeveli gözlüklü, hafif de göbekli ama güler yüzlü birisi girdi. Üstünde rengarenk bir hawai t-shirt, altında bermuda şort ve parmak arası terlikleri ile.

"Buyrun" dedi Reno, nefes nefese kalmış bir halde.

Fransız, yazılım alanında çift doktora yapmış birisiydi. Şirketin en kilit projelerinde görev almıştı. Bu yüzden Carl' ın da bir numaralı adamıydı.

"Sizin için ne yapabilirim?" diyerek devam etti sözlerine Fransız.

"Şu son satış rakamlarına ait raporlı diyorum Mösyö; acaba bunları PDF dosyasına kayıt edebilir miyiz?"

Gülümsedi Fransız Reno.

"Neden olmasın? Bana mesai bitimine kadar müddet verin lütfen."

Merhaba Arkadaşlar,

Özellikle veri odaklı(Data-Centric)çalışan uygulamalar düşünüldüğünde çeşitleri ne olursa olsun raporlama, işin oldukça önemli bir parçasını oluşturmaktadır. Ağırlıklı olarak rapora ihtiyaç duyan pozisyonlar, söz konusu raporları çeşitli ortamlarda görmek isteyen elemanlardır. Örneğin bunları Web arayüzünde açabilmeyi, Excel veya Word formatındaki dosyalara çıktı olarak alabilmeyi ve mobil cihazlarından takip edebilmeyi isterler. Günümüzün pek çok modern uygulaması zaten bu tip çıktıların alınmasını standart olarak olanak sunmaktadır.

Elbette çok farklı istekler de gelebilmektedir. Söz gelimi çıktı olarak basılacak veya bir dergi içerisinde kullanılması düşünülen raporlar için PDF, XPS gibi dosya formatlarında üretilmeleri istenebilir. İşte bu yazımızda bir rapor içeriğinin, PDF formatında nasıl oluşturulabileceğini basit bir Hello World uygulaması ile anlamaya çalışacağız. Örnek uygulamamızda açık kaynak olarak sunulan MigraDoc kütüphanelerinden yararlanacağız.

MigraDoc aslında, PDFSharp and MigraDoc Foundation isimli ürün ailesinin bir parçasıdır. Bu ürünlere ait kaynak kodları veya derlenmiş Binary dosyalarını Codeplex üzerinden indirebilirsiniz.

Senaryo

İlk olarak örnek senaryomuzu ele alalım. Bilindiği üzere Northwind veritabanında aşağıdaki ekran görüntüsünde yer alan standart View nesneleri varsayılan olarak yer almaktadır.

cpdf_7

Bu View nesnelerinde örnek pek çok rapor içeriği bulunmaktadır. Söz gelimi 1997 yılına ait kategori bazlı satışları veya yıldan yıla satış rakamlarının özetini görebilir, listede yer alan tüm ürünlerimizi kategori bazlı olarak elde edebiliriz. Dolayısıyla bu veriler raporlanabilir nitelikte olup çıktı şeklinde değerlendirilebilirler.

Senaryomuza göre basit bir Windows Forms uygulamasında, Northwind veritabanı içerisinde yer alan View nesnelerini kullanıcıya seçilebilir halde sunuyor olacağız. Bu View nesnelerinden her hangi biri seçildiğinde ise, veri içeriğini barındıran bir PDF dokümanının üretilmesini sağlayacağız. Örneğimizde ulaşmak istediğimiz hedef aşağıdaki ekran görüntüsündekine benzer olacaktır.

cpdf_2

Sol üst köşede şirkete ait bir logo, Footer ve Header kısımlarında açık gri formatta bir bilgi, raporun alındığı View nesnesinin adı, üretildiği tarih, detay için URL adresine gönderme yapan bir link ve çok doğal olarak verinin kendisini içeren bir tablo. Peki bu içeriği nasıl üretiyor olacağız?

Kodlama Zamanı

Örneğimiz basit bir Windows Forms uygulaması şeklinde geliştirilecektir. İlk etapta uygulamaya aşağıdaki görselde yer alan MigraDoc.DocumentObjectModel, MigraDoc.Rendering ve MigraDoc.RtfRendering isim Assembly' ları referans etmemiz gerekiyor.

cpdf_1

İlgili referansların eklenmesini takiben, aşağıdaki ekran görüntüsünde yer alan Form içeriğini tasarlayarak devam edebiliriz.

cpdf_4

Pek tabi uygulamamız Northwind veritabanına bağlanacağından bazı SQL işlemlerini de icra ediyor olacak. Söz gelimi View adlarını, sys.Viewsmetadata içeriğini kullanarak çekeceğiz. Diğer yandan bir View nesnesinin sunduğu veriyi almak için de SQL sorgusuna da ihtiyacımız olacak. Uzun lafın kısası temelde bir ConnectionString bilgisi gerekiyor Laughing out loudİşte o bilgiyi App.config dosyasından tedarik edebiliriz.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="Northwind"
         connectionString="data source=localhost;initial catalog=Northwind;integrated security=SSPI"
         providerName ="System.Data.SqlClient"
         />
  </connectionStrings>
</configuration>

Genel fonksiyonelliklerimizi Utility isimli yardımcı bir static sınıf içerisinde tutabiliriz. Sınıf içeriği biraz uzun olduğundan adım adım ilerlemeye çalışmanızı öneririm. İşte Utility içeriği;

cpdf_8

using MigraDoc.DocumentObjectModel;
using MigraDoc.DocumentObjectModel.Shapes;
using MigraDoc.DocumentObjectModel.Tables;
using MigraDoc.Rendering;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.IO;

namespace ReportApp
{
    public static class Utility
    {
        #region Genel değişkenler

        static Document document = null;
        static Table table = null;

        #endregion Genel değişkenler

        // Hazır bir SqlConnection nesnesini bize verecek olan private fonksiyonumuz
        static SqlConnection GetConnection()
        {
            SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString);
            if (conn.State == ConnectionState.Closed)
                conn.Open();
            return conn;
        }

        // Rapor almak için kullanacağımız View bilgilerini çektiğimiz fonksiyon
       public static List<string> GetNorthwindViews()
        {
            List<string> viewNames = new List<string>();

            string query = "Select name from sys.views order by name";
            using(SqlConnection conn=GetConnection())
            {
                using(SqlCommand cmd=new SqlCommand(query,conn))
                {
                    SqlDataReader reader = cmd.ExecuteReader();
                    while (reader.Read())
                    {
                        viewNames.Add(reader["name"].ToString());
                    }
                    reader.Close();
                }
            }
            return viewNames;
        }

        // PDF Dosyasını oluşturacak olan metodumuz
        public static bool CreatePDFReportFile(string fileName,string viewName)
        {
            bool result = false;

            document = new Document();
            DataTable dataContent=GetViewContent(viewName);

            CreateDefaultStyles();
            CreatePDFSection(viewName,fileName, dataContent);
           FillDataToContent(dataContent);
            SavePdfFile(fileName);

            result = true;

            return result;
        }

        // İlgili View içeriğini bir DataTable nesnesi olarak geriye döndüren private metodumuz
        static DataTable GetViewContent(string viewName)
        {
            string query = string.Format("select * from [{0}]", viewName);
            DataTable table = new DataTable();
            using (SqlConnection conn = GetConnection())
            {
                using (SqlDataAdapter adapter = new SqlDataAdapter(query, conn))
                {
                    adapter.Fill(table);
                }
            }

            return table;
        }

        // PDF içeriğinde kullanılacak olan stiller belirlenir. Bu Style tiplerini HTML içeriğindeki style kavramına benzetebiliriz.
        static void CreateDefaultStyles()
        {
            Style style = document.Styles["Normal"];
            style.Font.Name = "Calibri";
            style =document.Styles[StyleNames.Header];
            style.ParagraphFormat.AddTabStop("12cm", TabAlignment.Right);
            style = document.Styles[StyleNames.Footer];
            style.ParagraphFormat.AddTabStop("8cm", TabAlignment.Center);

            // Table isimli bir style oluşturuyoruz. Normal isimli Style' dan türemekte
            style = document.Styles.AddStyle("Table", "Normal");
            style.Font.Name = "Calibri";
            style.Font.Size = 9;

            // Normal isimli Style' ı baz alan Reference isimli bir Style oluşturuyoruz
            style = document.Styles.AddStyle("Reference", "Normal");
            style.ParagraphFormat.SpaceBefore = "3mm";
            style.ParagraphFormat.SpaceAfter = "3mm";
            style.ParagraphFormat.TabStops.AddTabStop("12cm", TabAlignment.Right);
        }

        // PDF Sayfası üretilir.
        static void CreatePDFSection(string viewName,string filePath,DataTable dataTable)
        {
            string info = string.Format("{0} raporu - Northwind Tarafından Üretilmiştir - Her Hakkı Saklıdır {1}"
                , viewName
                , DateTime.Now.Year);

            // Landscape olacak şekilde dokümanın yönünü belirliyoruz
            document.DefaultPageSetup.Orientation = Orientation.Landscape;
            // İçeriğimizi koyacağımız bir Section oluşturuyoruz
            Section section = document.AddSection();

            #region Firma Logosunun Eklenmesi

            Image image = section.AddImage(Path.Combine(Environment.CurrentDirectory, "northwind.jpg"));           
            image.Top = ShapePosition.Top;
            image.Left = ShapePosition.Left;                     

            #endregion Firma Logosunun Eklenmesi

            #region Header kısmı

            Paragraph paragraph = section.Headers.Primary.AddParagraph();
            paragraph.AddText(info);
            paragraph.Format.Font.Size = 10;
            paragraph.Format.Font.Color = Colors.LightGray;
            paragraph.Format.Alignment = ParagraphAlignment.Left;

            #endregion Header kısmı

            #region Footer kısmı

            paragraph = section.Footers.Primary.AddParagraph();
            paragraph.AddText(info);
            paragraph.Format.Font.Size = 10;
            paragraph.Format.Font.Color = Colors.LightGray;
            paragraph.Format.Alignment = ParagraphAlignment.Left;

            #endregion Footer kısmı

            #region Adres bildirimi
                       
            paragraph = section.AddParagraph();
            paragraph.Format.SpaceBefore = "3cm";
            paragraph.Style = "Reference";
            paragraph.AddFormattedText(viewName, TextFormat.Italic);
            paragraph.AddTab();
            paragraph.AddText("Rapor Tarihi, ");
            paragraph.AddDateField("dd.MM.yyyy");
            paragraph.AddLineBreak();
            paragraph.AddText("Rapor, Migra Document API ile üretilmiştir");
            paragraph.AddLineBreak();
            Hyperlink link = paragraph.AddHyperlink("http://www.buraksenyurt.com");
            link.Type = HyperlinkType.Url;
            link.Font.Underline = Underline.Single;
            link.AddText("Detaylı Bilgi burada");

            #endregion Adres bildirimi

            #region View içeriğinin basılacağı Table ve öğelerinin üretimi

            table = section.AddTable();
            table.Style = "Table";
            table.Borders.Color = Colors.LightYellow;
            table.Borders.Width = 0.25;
            table.Borders.Left.Width = 0.5;
            table.Borders.Right.Width = 0.5;
            table.Rows.LeftIndent = 0;

            Column column;
            foreach (DataColumn col in dataTable.Columns)
            {
                column = table.AddColumn(Unit.FromCentimeter(2.5));
                column.Format.Alignment = ParagraphAlignment.Center;
            }

            // Tablonun Header kısmı üretiliyor
            Row row = table.AddRow();
            row.HeadingFormat = true;
            row.Format.Alignment = ParagraphAlignment.Center;
            row.Format.Font.Bold = true;

            for (int i = 0; i < dataTable.Columns.Count; i++)
            {
                row.Cells[i].AddParagraph(dataTable.Columns[i].ColumnName);
                row.Cells[i].Format.Font.Color = Colors.White;
                row.Cells[i].Format.Shading.Color = Colors.Black;
                row.Cells[i].Format.Font.Bold = true;
                row.Cells[i].Format.Alignment = ParagraphAlignment.Left;
                row.Cells[i].VerticalAlignment = VerticalAlignment.Bottom;
            }
            table.SetEdge(0, 0, dataTable.Columns.Count, 1, Edge.Box, BorderStyle.Single, 0.75, Color.Empty);

            #endregion View içeriğinin basılacağı Table ve öğelerinin üretimi
        }

        // Sayfa içeriği parametre olarak gelen DataTable içeriğine göre doldurulur
        static void FillDataToContent(DataTable dataTable)
        {
            Row newRow;
            for (int i = 0; i < dataTable.Rows.Count; i++)
            {
                newRow = table.AddRow();
                newRow.TopPadding = 1.5;
                for (int j = 0; j < dataTable.Columns.Count; j++)
                {
                    newRow.Cells[j].Shading.Color = Colors.Gold;
                    newRow.Cells[j].VerticalAlignment = VerticalAlignment.Center;
                    newRow.Cells[j].Format.Alignment = ParagraphAlignment.Left;
                    newRow.Cells[j].Format.FirstLineIndent = 1;
                    newRow.Cells[j].AddParagraph(dataTable.Rows[i][j].ToString());
                    table.SetEdge(0, table.Rows.Count - 2, dataTable.Columns.Count, 1, Edge.Box, BorderStyle.Single, 0.75);
                }
            }
        }

        // PDF Dosyası parametre olarak belirtilen adrese kayıt edilir
        static void SavePdfFile(string fileName)
        {
            PdfDocumentRenderer pdfRenderer = new PdfDocumentRenderer(true);           
            pdfRenderer.Document = document;
            pdfRenderer.RenderDocument();
            pdfRenderer.Save(fileName);                      
        }
    }
}

Aslında fonksiyonları dikkatlice incelediğimizde PDF içeriğini oluşturma işleminin oldukça basit olduğunu görebiliriz. Baş kahraman MigraDoc.DocumentObjectModel içerisinde yer alan Document tipidir. Tahmin edileceği üzere MigraDoc, PDF içeriğine ait Document Object Model' i kullanmaktadır.

Document tipi aslında sayfa içerisinde kullanılacak globalStyle' leri ve Section' ları oluşturmak için kullanılır. Bu nedenle PDF içersine bir paragraf, tablo, resim, link ve benzeri materyalleri eklemek istediğimizde bir Section tipinden yararlanmalıyız. Örnekte dikkat edileceği üzere Logo' nun eklenmesi için section nesne örneği üzerinden AddImage metodu çağırılmaktadır. Diğer yandan bir paragraf eklenmek istendiğinde yine section nesne örneği üzerinden ama bu kez AddParagraph fonksiyonuna çağrıda bulunulmaktadır.

Örneğimizin kalbi rapor içeriklerinin bir tablo içerisinde gösterilmesidir. Bu sebepe bir Table tipi kullanılmış ve örneklenmesi için yine section nesnesi üzerinden hareket edilerek, AddTable metoduna başvuruda bulunulmuştur. Bir Tableörneklendikten sonra içerisinde yer alan satırların(Rows) veya hücrelerin(Cells) doldurulması işi, aslında bir HTML Table içeriğinin dinamik olarak üretilmesinden pek de farklı değildir. Yeterki doküman nesne modeline hakim olalım Winking smile

Örneğimizin daha kolay anlaşılması açısından aşağıdaki görseli dikkate alabilirsiniz.

cpdf_11

Kodlama sırasında keşfetmemiz gereken veya bizleri zorlayabilecek olan noktalar genellikle ilgili içeriğin(paragraf, tablo, resim vb) sayfa içerisindeki yerleşimlerinin ayarlanmasıdır. Aslında bir kaç deneme yanılma ile düzgün bir şablon oturtabiliriz. Ancak yine de dikkate alınması gereken bazı hususlar vardır. Söz gelimi View' un döndürdüğü sonuç kümesinde yer alan kolon sayısının çok fazla olması halinde sayfaya sığmayacak ve yatay olarak görüntü kayıpları yaşanacaktır ki bu Jimmy Carl' ın pek de hoşuna gitmeyecektir. Bu gibi ileri seviye sayılabilecek hususlar örneğimizde ele alınmamıştır. Dolayısıyla siz kendi örneklerinizi icra ederken daha dikkatli davranmalısınız.

Satır sayısı çok fazla olan bir içeriğin PDF' e yazılması oldukça uzun sürebilir/sürdüğü gözlemlenmiştir. Bu sebepten ilgili dosya kaydetme operasyonunun aslında asenkron bir düzenek ile icra edilmesi çok daha uygun olabilir. Hatta büyük boyutlu raporlar için bir Progress Bar ile durum bildirimi bile yapabilirsiniz Sarcastic smile (async ve await kullanmayı deneyiniz)

Form içeriğindeki kodlarımız ise aşağıdaki gibidir. Utility tipi pek çok ağır fonksiyonelliği kapsüllediğinden bu kısımın okunurluğu çok daha kolaydır.

using System;
using System.Diagnostics;
using System.Windows.Forms;

namespace ReportApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            // Northwind veritabanında yer alan View isimleri ComboBox kontrolüne basıyoruz.
            cmbViews.DataSource = Utility.GetNorthwindViews();
        }

        private void btnCreatePDF_Click(object sender, EventArgs e)
        {
            // Kullanıcıdan bir PDF dosya adı istiyoruz
            if(sfdReportFile.ShowDialog()== DialogResult.OK)
            {               
                // Dosyanın var olması halinde kullanıcının tepkisini bekliyoruz. Yes düğmesine basarsa üzerine yazacak
               if(sfdReportFile.CheckFileExists==true)
                        return;

                string pdfFileName = sfdReportFile.FileName;
                // PDF oluşturma işlemini üstlenen Utility tipini çağırıyoruz.
                Utility.CreatePDFReportFile(pdfFileName, cmbViews.SelectedValue.ToString());

                // Üretilen PDF dosyasını sistemde PDF Read edebileceğimiz bir uygulama olduğunu var sayarak Process tipi yardımıyla açtırıyoruz.
                // Bu sayede üretilen raporu da görebiliriz.
                Process.Start(pdfFileName);              
            }
        }
    }
}

Dikkat edilmesi gereken noktalardan birisi de PDF dosyasının kayıt edilmesinden sonra Process.Start operasyonu ile ilgili içeriğin otomatik olarak açılmasıdır. Bu sayede sonuçları anında görebiliriz.

Çalışma Zamanı

Örneğimizi çalıştırdığımızda Selectname from sys.views order by name sorgusunun bir sonucu olarak tüm View nesnelerinin elde edilebildiği görülecektir. Bu şekilde bir kullanım nedeni ile, Northwind veritabanına eklenecek olan yeni View' ları da PDF üretimi sürecine katabiliriz.

cpdf_3

ve bir kaç rapor örneğine ait ekran çıktısına yer vererek devam edelim.

Örnek PDF Çıktıları

Products by Category View' u için örnek ekran çıktısı

cpdf_5

Summary of Sales By Year View' u için örnek ekran çıktısı

cpdf_6

Order Subtotals View' u için örnek ekran çıktısı

cpdf_9

PDF içinde Chart Üretimi

Elbette PDF dosyasına çıktı olarak verilen bu raporlar arasında en etkileyici olanlarından birisi de Chart tipindekilerdir. Şimdi örnek senaryomuzda aşağıdaki görsel de yer alan ve kategori bazlı toplam satış rakamlarını gösteren View nesnesini kullanarak Line tipinde bir raporu üretmeye çalışalım.

pdfchart_1

İşte kodlarımız

public static bool CreateChart(string fileName)
{
    bool result=false;

    document = new Document();
    document.DefaultPageSetup.Orientation = Orientation.Landscape;

    DataTable dataContent = GetViewContent("Total Sales By Category");
   DrawChart(dataContent);
    SavePdfFile(fileName);

    result = true;
    return result;
}

private static void DrawChart(DataTable dataTable)
{
    List<double> serieValues = new List<double>();
    List<string> xAxisValues = new List<string>();

    Section section = document.AddSection();
    Chart chart = new Chart();
    chart.Left = 0;
    chart.Width = Unit.FromCentimeter(24);
    chart.Height = Unit.FromCentimeter(16);
    Series series = chart.SeriesCollection.AddSeries();
    series.ChartType = ChartType.Line;

    foreach (DataRow row in dataTable.Rows)
    {
        serieValues.Add(Convert.ToDouble(row["Total"]));
        xAxisValues.Add(row["CategoryName"].ToString());
    }
    series.Add(serieValues.ToArray());

    XSeries xSeries = chart.XValues.AddXSeries();
    xSeries.Add(xAxisValues.ToArray());
    chart.XAxis.Title.Caption = "Kategori";
    chart.XAxis.HasMajorGridlines = true;
    chart.YAxis.Title.Caption = "Toplam Satış";
    chart.YAxis.HasMajorGridlines = true;           
    chart.PlotArea.FillFormat.Color = Colors.SandyBrown;
    chart.PlotArea.LineFormat.Width = 3;

    section.Add(chart);
}

Baş rol oyuncusu bu kez Chart tipinden olan nesne örneğidir. Bu nesne örneği bir Section içerisinde yer almalıdır. Aynen Excel Chart nesnelerinde olduğu gibi X ve Y eksenleri ve bu eksenlere dizilmiş değerler bulunmaktadır. Dolayısıyla bu serileri veri ile doldurmak önemlidir. Bu sebepten DataTable içeriğinde dolaşılmış ve toplam satış rakamları ile kategori adları, sırasıyla XAxis ve YAxisözelliklerine ait koleksiyonlara yerleştirilmiştir. Bu fonksiyonellikleri yeni bir Button arkasında  aşağıdaki kod parçasında olduğu gibi deneyebiliriz.

private void btnCreateChart_Click(object sender, EventArgs e)
{
    if (sfdReportFile.ShowDialog() == DialogResult.OK)
    {
        if (sfdReportFile.CheckFileExists == true)
            return;

        string pdfFileName = sfdReportFile.FileName;
        Utility.CreateChart(pdfFileName);

        Process.Start(pdfFileName);
    }
}

Uygulamayı çalıştırdığımızda aşağıdakine benzer bir sonuç ile karşılaşırız.

pdfchart_2

Sonuç

Sonuç olarak en azından işe yarar PDF içeriklerini kolayca üretebildiğimize şahit olduk. Örnekleri geliştirmek tamamen sizin elinizde. Söz gelimi Chart bileşenini kullandığımız senaryoyu daha  da ileri götürebilir, örneğin Elma Dilimi raporları işin içersine katarak daha etkileyici çıktılar sunabilirsiniz(Üstelik bu tip görsel raporlar Jimmy Carl' ın da çok hoşuna gidecektir) Buna ilaveten bir ön iletişim kutusundan yararlanarak görsellik üzerine detay bilgilerini(örneğin tablonun arka plan rengi, font büyüklükleri, logonun gösterilip gösterilmeyeceği, bir özet bilginin konulup konulmayacağı, header veya footer kısımlarında ne yazılması istendiği vb) kullanıcıdan alabilirsiniz. Biz bu yazımızda sadece Hello World demeye çalıştık Winking smile 

PDF Sharp ve Migra kütüphanelerinin detaylı kullanımı ile ilişkili olarak bu Wiki sayfasından da yararlanabilirsiniz. Oldukça geniş kullanım örnekleri olduğunu ifade edebilirim.

Böylece geldik bir makalemizin daha sonuna. Bir diğer yazımızda görüşünce dek hepinize mutlu günler dilerim.

HowTo_PDFSharpV2.zip (555,18 kb)

Viewing all 343 articles
Browse latest View live