https://www.buraksenyurt.com/Burak Selim Şenyurt - Ado.Net Data Services2009-11-16T06:09:54+00:00Matematik Mühendisi Bir Bilgisayar Programcısının NotlarıBurak Selim SenyurtBlogEngine.Net Syndication Generatorhttps://www.buraksenyurt.com/opml.axdBurak Selim SenyurtMatematik Mühendisi Bir Bilgisayar Programcısının Notlarıtr-TRBurak Selim Şenyurt0.0000000.000000https://www.buraksenyurt.com/post/AdoNet-Data-Services-15-CTP2-Data-Binding-Bolum-2Ado.Net Data Services 1.5 CTP2 - Data Binding Bölüm 22009-11-15T23:09:00+00:00bsenyurt<p><img style="float: right;" src="/pics/2009%2f9%2fblg83_Giris.jpg" alt="" />Merhaba Arkadaşlar,</p>
<p>Orta uzunlukta bir yazı için hazır mısınız? Analizi dikkat gerektiren bir <strong>Ado.Net Data Service</strong> örneği geliştiriyor olacağız. Genellikle bu tarz yazılara ait kodları geliştirirken sıkılmamak ve zihnimi açık tutmak için ya kahve içerim yada mutluluktan havalara uçarmış gibi yazabilmek ve kan şekerimi üst seviyede tutabilmek için bazen değişik şekerlemelerden yerim. Aynen yandaki resimde olduğu gibi.<img title="Cool" src="/editors/tiny_mce3/plugins/emotions/img/smiley-cool.gif" alt="Cool" border="0" /> </p>
<p>Hatırlayacağınız gibi bir önceki yazımızda, <strong>Ado.Net Data Service</strong> için istemci taraflı veri bağlama işlemlerinde <strong>DataServiceCollection<T></strong> kolekisyonunu değerlendirmeye çalışmış ve istemci tarafında bu konuyu ele almak için basit bir <strong>WPF</strong> uygulaması geliştirmiştik. Bir önceki örneğimiz aslında tek yönlü veri bağlama işlemine örnek olmasında rağmen, iki yönlü modeli de desteklemektedir. Bu yazımızda geliştireceğimiz örneğimizdeki hedefimiz ise, istemci tarafındaki koleksiyonlar üzerinden yapmış olduğumuz güncelleştirme, ekleme ve silme işlemlerini servis tarafına yansıtmaktır.</p>
<p>Aslında süreç son derece basittir. Veri bağlı kontroller üzerinde yapılan güncelleştirme hareketleri, <strong>DataServiceCollection</strong> koleksiyonu üzerinde de gerçeklenecektir. Sonrasında ise <strong>DataServiceContext</strong> türevli nesne örneği üzerinden <strong>SaveChanges</strong> metodunun çağırılması yeterli olacaktır. Bu sayede koleksiyon üzerinden yapılan tüm güncelleştirme, ekleme ve silme işlemlerinin, servis tarafına bir talep olarak gönderilmesi ve sunucu üzerinde de kullanılan veri kaynağına göre<em>(Entity Framework veya Custom LINQ Provider)</em> uygun bir veri işleminin yapılması sağlanır. Biz örneğimizde kendi geliştireceğimiz bir veritabanı içeriğini ve <strong>Ado.Net Entity Framework</strong> modelini kullanacağımızdan, sunucu üzerinde <strong>SQL</strong> sorgularının çalıştırıldığını göreceğiz. Dilerseniz vakit kaybetmeden örneğimizi geliştirmeye başlayalım ve işleyişini analiz edelim.</p>
<p>Örneğimizde kendi geliştirdiğimiz <strong>BookShop</strong> isimli bir veritabanını kullanıyor olacağız. Hayali olarak bir kitapçının veri ambarı olarak tasarladığımızı farz edebiliriz. <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> Söz konusu veritabanını oluşturmak için <a href="https://www.buraksenyurt.com/pics/2009%2f9%2fBookShopDbScripts.sql">BookShopDbScripts.sql (13,83 kb)</a> dosyasından yararlanabilirsiniz. Bu dosya içerisinde veritabanı, tabloların oluşturulması ve örnek veri girişleri için gerekli <strong>SQL Script'</strong> leri bulunmaktadır. Bu noktadan yola çıkarak geliştireceğimiz <strong>Ado.Net Data Service</strong> örneğinde kullanacağımız <strong>Entity DataModel</strong> diagramını aşağıdaki gibi tasarlayabiliriz. Örneğimizdeki amacımız kitap güncellemek, kitap eklemek ve silmek gibi işlemler olacaktır.</p>
<p><img src="/pics/2009%2f9%2fblg83_Edmx.gif" alt="" /></p>
<p>Gelelim <strong>Ado.Net Data Service</strong> örneğimize. <strong>Version 1.5 CTP2</strong> versiyonuna göre eklediğimiz servisin kod içeriğini aşağıdaki gibi tasarlayabiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Data.Services;
namespace BookShop
{
public class BookService
: DataService<BookShopEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.DataServiceBehavior.MaxProtocolVersion = System.Data.Services.Common.DataServiceProtocolVersion.V2;
}
}
}</pre>
<p>İstemci tarafını yine bir <strong>WPF</strong> uygulaması olarak tasarlayıp, iki yönlü veri bağlama kabiliyetlerini etkin bir şekilde kullanmayı planlıyoruz. Yazıyı hazırladığım sıralarda, <strong>Visual Studio 2008</strong> üzerinde garip ve gizemli bir sorunla karşılaştım. Öyleki, istemci uygulamada <strong>Add Service Reference</strong> ile <strong>proxy </strong>üretimi yapılmasına rağmen, indirilen <strong>Entity</strong> tiplerinin <strong>INotifyPropertyChanged</strong> arayüzünü uygulamadıklarını gördüm. Ama tabiki çaresiz değildim. <strong>DataSvcUtil</strong> aracının <strong>version 1.5 CTP 2</strong> sürümünü aşağıda görüldüğü gibi kullanarak, istemci için gerekli olan ve <strong>INotifyPropertyChanged</strong> arayüzünü implemente etmiş tipleri üretebiliriz.</p>
<p><img src="/pics/2009%2f9%2fblog83_CommandPrompt.gif" alt="" /></p>
<p>Bu noktadan sonra üretilen sınıfı istemci tarafında kullanmamız yeterli olacaktır. İşte istemci tarafı için üretilen <strong>Entity</strong> tipleri.</p>
<p><img src="/pics/2009%2f9%2fblg83_ClassDiagram.gif" alt="" /></p>
<p>Yanlız komut satırından yapılan <strong>proxy</strong> sınıfını istemcide kullanabilmek için, <strong>Ado.Net Data Services Version 1.5 CTP2</strong> ile gelen <strong>Microsoft.Data.Services.Client assembly' </strong>ının projeye referans edilmesi gerekmektedir. Aynen aşağıdaki ekran görüntüsünde olduğu gibi.</p>
<p><img src="/pics/2009%2f9%2fblg83_ClientSolution.gif" alt="" /></p>
<p>Bu ön hazırlıklardan sonra istemci tarafındaki <strong>Window1</strong> içeriğini başlangıçta aşağıdaki gibi tasarladığımızı düşünelim.</p>
<p><img src="/pics/2009%2f9%2fblg83_Window1First.gif" alt="" /></p>
<p>Burada üst tarafta yer alan <strong>ComboBox</strong> içerisinde kitap kategorileri, alt tarafta yer alan <strong>ListBox </strong>kontrolünde ise seçilen kategoriye bağlı kitaplar görüntülenecektir. Yanlız <strong>XAML</strong> tarafına baktığınızda <strong>WPF</strong> açısından etkili bazı tekniklerin kullanıldığını görebilirsiniz.</p>
<p><strong>XAML İçeriği;</strong></p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><Window x:Class="BookSeller.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Book Seller" Height="330" Width="384">
<Grid Name="grdBook" Height="298">
<ComboBox Height="42" Margin="2,12,0,0" Name="cmbCategories" VerticalAlignment="Top" IsSynchronizedWithCurrentItem="true" ItemsSource="{Binding}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Name="txtCategoryId" Text="{Binding Path=CategoryId}" FontSize="22" Foreground="RosyBrown"/>
<TextBlock Name="txtCategoryName" Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ListBox IsSynchronizedWithCurrentItem="True" Name="lstProducts" Margin="0,60,0,53" ItemsSource="{Binding Book}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBox Name="txtProductName" Text="{Binding Name}" Width="250"/>
<TextBox Name="txtListPrice" Text="{Binding ListPrice}" Width="100" Foreground="Gold" Background="Black" HorizontalAlignment="Right"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Height="23" HorizontalAlignment="Left" Margin="2,0,0,24" Name="btnSaveChanges" VerticalAlignment="Bottom" Width="93" Click="btnSaveChanges_Click">Save Changes</Button>
</Grid>
</Window></pre>
<p>Her şeyden önce, <strong>ComboBox</strong> içeriğinin her bir öğesinin birer <strong>StackPanel</strong> olduğunu ve <strong>CategoryId</strong> ile <strong>Name </strong>alanlarının içeriklerinin <strong>TextBlock' </strong>ların <strong>Text</strong> özelliklerine bağlandıklarını görebiliriz. Benzer durum <strong>ListBox</strong> kontrolü içinde geçerlidir. Ne varki <strong>ListBox</strong> kontrolünün içerisinde yer alan ve <strong>Text</strong> özellikleri <strong>Book Entity'</strong> sinin <strong>Name</strong> ile <strong>ListPrice </strong>alanlarına bağlanmış olan <strong>TextBox</strong> kontrolleri, aslında kullanıcı tarafından düzenlenebilirde. <img title="Laughing" src="/editors/tiny_mce3/plugins/emotions/img/smiley-laughing.gif" alt="Laughing" border="0" /> Volaaaa!!! Öyleyse akla şu gelebilir.</p>
<p><strong>"Eğer bu kontrollerin Text özellikleri, kod tarafındaki Entity örneklerine bağlanmışlarsa ve çalışma zamanında içerikleri değiştirilirse bu düzenlemeler Entity içeriklerine de yansır mı? Peki diyelim ki yansıdı. SaveChanges metodunu çağırdığımızda bu değişikliler servis tarafına da yansır mı?" </strong></p>
<p>Aslında bu sorularının tamamının cevabı <strong>Evet'</strong> tir. Ama tabiki bizim bu durumu analiz etmemiz ve gözümüzle görmemiz şart. <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> Buna göre kod içeriğimizi aşağıdaki gibi geliştirmemiz yeterlidir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Data.Services.Client;
using System.Windows;
using BookShopModel;
namespace BookSeller
{
public partial class Window1
: Window
{
BookShopEntities bs = null;
DataServiceCollection<Category> _bookShopCollection = null;
public Window1()
{
InitializeComponent();
bs = new BookShopEntities(new Uri("http://localhost:7995/BookService.svc/"));
_bookShopCollection = DataServiceCollection.CreateTracked<Category>(bs, bs.Category.Expand("Book"));
grdBook.DataContext = _bookShopCollection;
}
private void btnSaveChanges_Click(object sender, RoutedEventArgs e)
{
bs.SaveChanges();
}
}
}</pre>
<p>Programı ilk çalıştırdığımızda aşağıdaki bilgilerin geldiğini görebiliriz. Her ne kadar tasarım konusunda zayıf bir örnek olsada, <strong>ListBox</strong> içerisinde her bir satırda düzenlenebilir, veriye bağlanmış kontrollerin yer alması dahi benim için önemli bir adımdır. <img title="Smile" src="/editors/tiny_mce3/plugins/emotions/img/smiley-smile.gif" alt="Smile" border="0" /></p>
<p><img src="/pics/2009%2f9%2fblg83_FirstRun.gif" alt="" /></p>
<p>Şimdi <strong>ListBox</strong> içerisindeki verilerde değişiklik yapıldığını varsayalım. Örnek olarak <strong>Pro WCF 3.5 </strong>isimli kitabın adına<strong> Second Edition</strong> kelimelerini ilave ettiğimizi ve <strong>ListPrice</strong> değerini <strong>39</strong> birim olarak değiştirdiğimizi düşünebiliriz. Aynen aşağıdaki ekran görüntüsünde olduğu gibi.</p>
<p><img src="/pics/2009%2f9%2fblg83_Change.gif" alt="" /></p>
<p>Şimdi <strong>Save Changes</strong> başlıklı düğmeye basalım ve <strong>BookShopEntities</strong> nesne örneği üzerinden <strong>SaveChanges</strong> metodunun çağırıldığı yerde koyduğumuz <strong>break point</strong> üzerinde duralım. Yapamamız gereken, <strong>BookShopEntities</strong> ve <strong>DataServiceCollection</strong> içeriklerini <strong>Watch</strong> ile incelemek olacaktır. İlk olarak <strong>_bookShopCollection </strong>içeriğine bir bakalım.</p>
<p><img src="/pics/2009%2f9%2fblg83_Breakpoint1.gif" alt="" /></p>
<p>Hımmmmm... <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> Görüldüğü üzere <strong>Category</strong> üzerinden gittiğimiz <strong>Book</strong> özelliğine bağlı koleksiyonda az önce yapılan <strong>Name</strong> ve <strong>ListPrice</strong> değişikliklerin gerçekleştirildiği gözlemlenmektedir. Benzer şekilde <strong>BookShopEntities</strong> nesne örneğinin içeriğine baktığımızda, ilgili <strong>Book</strong> örneği için aynı değişikliklerin yansıtıldığını görebiliriz.</p>
<p><img src="/pics/2009%2f9%2fblg83_Breakpoint2.gif" alt="" /></p>
<p>Dolayısıyla <strong>iki yönlü veri bağlama(Two Way DataBinding)</strong> nedeniyle kontroller üzerine yansıyan verilerde yapılan değişiklikler, istemci tarafındaki ilgili koleksiyonlara da yansıtılmaktadır. Buna göre <strong>SaveChanges</strong> metoduna yapılan çağrı geçildiğinde, servis tarafına gerekli güncelleştirme talebinin gittiği ve aşağıdaki <strong>SQL</strong> sorgusunun çalıştırıldığı gözlemlenir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'update [dbo].[Book]
set [Name] = @0, [ListPrice] = @1, [PageSize] = @2
where ([BookId] = @3)
',N'@0 nvarchar(26),@1 decimal(19,4),@2 smallint,@3 int',@0=N'Pro WCF 3.5 Second Edition',@1=39.0000,@2=500,@3=2</pre>
<p>Eğer birden fazla <strong>Book</strong> örneğinin özelliği değiştirilirse, <strong>SaveChanges</strong> metodu çağrısı sonrasında her bir güncelleştirme için ayrı bir <strong>SQL Update</strong> sorgusunun çalıştırıldığı da gözlemlenecektir. Şimdi örnek bir veri ekleme işlemi yapalım. Bu amaçla <strong>Window1</strong> içeriğini aşağıdaki gibi değiştirdiğimizi düşünebiliriz.</p>
<p><img src="/pics/2009%2f9%2fblg83_SecondDesign.gif" alt="" /></p>
<p>Yapılan bu değişikliklerden sonra ise <strong>XAML</strong> içeriğinin aşağıdaki gibi oluştuğu gözlemlenebilir.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><Window x:Class="BookSeller.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Book Seller" Height="362" Width="384">
<Grid Name="grdBook" Height="317">
<ComboBox Height="42" Margin="2,12,0,0" Name="cmbCategories" VerticalAlignment="Top" IsSynchronizedWithCurrentItem="true" ItemsSource="{Binding}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Name="txtCategoryId" Text="{Binding Path=CategoryId}" FontSize="22" Foreground="RosyBrown"/>
<TextBlock Name="txtCategoryName" Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ListBox IsSynchronizedWithCurrentItem="True" Name="lstProducts" Margin="0,60,0,141" ItemsSource="{Binding Book}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBox Name="txtProductName" Text="{Binding Name}" Width="250"/>
<TextBox Name="txtListPrice" Text="{Binding ListPrice}" Width="100" Foreground="Gold" Background="Black" HorizontalAlignment="Right"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Height="23" HorizontalAlignment="Right" Margin="0,0,12,12" Name="btnSaveChanges" VerticalAlignment="Bottom" Width="93" Click="btnSaveChanges_Click">Save Changes</Button>
<Label Height="28" HorizontalAlignment="Left" Margin="7,0,0,106" Name="label1" VerticalAlignment="Bottom" Width="54">Name</Label>
<Label Height="28" HorizontalAlignment="Left" Margin="7,0,0,77" Name="label2" VerticalAlignment="Bottom" Width="54">ListPrice</Label>
<Label Height="28" HorizontalAlignment="Right" Margin="0,0,104,78" Name="label3" VerticalAlignment="Bottom" Width="65">Page Size</Label>
<TextBox Height="23" Margin="67,0,12,112" Name="txtName" VerticalAlignment="Bottom" />
<TextBox Height="23" Margin="67,0,0,83" Name="txtListPrice" VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="97" />
<TextBox Height="23" Margin="0,0,12,83" Name="txtPageSize" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="97" />
<Button Height="23" HorizontalAlignment="Left" Margin="12,0,0,48" Name="btnAddBook" VerticalAlignment="Bottom" Width="93" Click="btnAddBook_Click">Add</Button>
</Grid>
</Window></pre>
<p>Kullanıcı bu forma göre yeni bir kitap ekleyebilmelidir. Bir kitabın bir kategori altında olması gerektiğinden, oluşturulacak kitabın hangi kategoriye ekleneceğinin belirlenmesi sırasında ComboBox' ta seçili olan <strong>Category</strong> nesne örneğinden yararlanabiliriz. Kitabın tabiki öncelikle nesnel olarak oluşturulması gerekmektedir. Sonrasında ise <strong>ComboBox'</strong> ta seçili olan <strong>Category</strong> öğesinin <strong>Book</strong> özelliği yardımıyla ilgili koleksiyona eklenmelidir. Bu ekleme işleminin ardından yapılacak olan <strong>SaveChanges</strong> çağrısı, ekleme işlemi için <strong>Ado.Net Data Service</strong> tarafına uygun talebin gönderilmesini sağlayacaktır. Bunun doğal sonucu olarakta sunucu tarafında uygun olan <strong>SQL Insert </strong>sorgusu çalıştırılacaktır. Bakalım gerçekten böyle mi? <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> Bu amaçla <strong>Add</strong> başlıklı <strong>Button </strong>kontrolümüzün <strong>Click </strong>olay metodunu aşağıdaki gibi kodladığımızı düşünelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">private void btnAddBook_Click(object sender, RoutedEventArgs e)
{
Category currentCategory = cmbCategories.SelectedItem as Category;
Book newBook = new Book
{
ListPrice = Convert.ToDecimal(txtListPrice.Text),
Name = txtName.Text,
PageSize = Convert.ToInt16(txtPageSize.Text) ,
Category=currentCategory
};
currentCategory.Book.Add(newBook);
}</pre>
<p>İlk olarak kitabın ekleneceği kategori bulunmaktadır. Burada <strong>SelectedItem</strong> özelliğinin <strong>Category </strong>tipine dönüştürüldüğüne dikkat edilmelidir. Sonrasında yeni bir <strong>Book</strong> nesnesi örneklenir ve ilgili özellikleri kontrollerden alınır.<em>(Burada herhangibir hatalı giriş kontrolü yapmadığımızı belirtelim. Aslında yapmamız gerekiyor ancak şu an için odaklanmamız gereken kısım bu değil. Yinede siz örneği denerken mutlaka olası hataların önüne geçmenizi sağlayacak eklemeleri yapmayı unutmayın <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> ) </em></p>
<p>Dikkat edilmesi gereken noktalardan birisi de, <strong>Book</strong> nesnesi örneklenirken <strong>Category</strong> özelliğine <strong>currentCategory</strong> değişkeninin referansını atamamızdır. Bundan sonraki kısım ise son derece basittir. Örneklenen <strong>Book</strong> nesne örneği, o an seçili olan <strong>Category</strong> nesnesinin <strong>Book</strong> özelliğinin temsil ettiği koleksiyona atanır. Birde çalışma zamanına bakalım. Aşağıdaki örnekte bir veri girişi yapılmak istendiğini görüyoruz.</p>
<p><img src="/pics/2009%2f9%2fblg83_BeforeAdd.gif" alt="" /></p>
<p>Şimdi <strong>Add</strong> düğmesine basarsak ve kodu <strong>debug</strong> edersek <strong>currentCategory</strong> değişkeninin içeriğinin aşağıdaki gibi güncellendiğini görürüz.</p>
<p><img src="/pics/2009%2f9%2fblg83_AddDebug.gif" alt="" /></p>
<p>Görüldüğü gibi yeni <strong>Book</strong> nesne örneği ilgili kategori altına eklenmiştir. Öyleyse birde <strong>DataServiceCollection</strong> içeriğimize bakalım.</p>
<p><img src="/pics/2009%2f9%2fblg83_AddWatch.gif" alt="" /></p>
<p>Yeni eklenen <strong>Book</strong> nesne örneğinin <strong>DataServiceCollection</strong> nesne örneği içerisinde de yer aldığı gözlemlenmektedir. Üstelik çalışma zamanının yeni görüntüsüde aşağıdaki gibidir.</p>
<p><img src="/pics/2009%2f9%2fblg83_AfterAdd.gif" alt="" /></p>
<p>Bağlılık buna denir desek yeridir <img title="Cool" src="/editors/tiny_mce3/plugins/emotions/img/smiley-cool.gif" alt="Cool" border="0" /> Nitekim yapmış olduğumuz nesne eklemesinden <strong>ListBox</strong> kontrolüde otomatik olarak etkilenmiş ve içeriğini yenilemiştir. Artık <strong>Save Changes</strong> başlıklı düğmeye basarak değişiklikleri servis tarafına gönderebiliriz. Bu işlem yapıldığı takdirde <strong>SQL</strong> tarafında aşağıdaki sorgunun çalıştırıldığı gözlemlenir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'insert [dbo].[Book]([Name], [ListPrice], [CategoryId], [PageSize])
values (@0, @1, @2, @3)
select [BookId]
from [dbo].[Book]
where @@ROWCOUNT > 0 and [BookId] = scope_identity()',N'@0 nvarchar(28),@1 decimal(19,4),@2 int,@3 smallint',@0=N'Yazılımcılar için SQL Server',@1=45.0000,@2=1,@3=800</pre>
<p>İşte bu kadar...Sırada silme işlemi var ama uzun olan bir yazının verdiği yorgunluğu yaşayan ben bu kutsal görevi siz değerli okurlarıma bırakıyorum <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> Silme operasyonunu uygularken <strong>debug </strong>işlemlerini yapmayı ve çalışma zamanını analiz edip <strong>SQL</strong> tarafında neler olup bittiğini incelemeyi unutmayın. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://www.buraksenyurt.com/pics/2009%2f9%2fBindingV2.rar">BindingV2.rar (93,31 kb)</a></p>2009-11-15T23:09:00+00:00ado.net data servicesbsenyurtHatırlayacağınız gibi bir önceki yazımızda, Ado.Net Data Service için istemci taraflı veri bağlama işlemlerinde DataServiceCollection<T> kolekisyonunu değerlendirmeye çalışmış ve istemci tarafında bu konuyu ele almak için basit bir WPF uygulaması geliştirmiştik. Bir önceki örneğimiz aslında tek yönlü veri bağlama işlemine örnek olmasında rağmen, iki yönlü modeli de desteklemektedir.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=263c2b42-e7a4-4247-97f5-33167c5a3c470https://www.buraksenyurt.com/trackback.axd?id=263c2b42-e7a4-4247-97f5-33167c5a3c47https://www.buraksenyurt.com/post/AdoNet-Data-Services-15-CTP2-Data-Binding-Bolum-2#commenthttps://www.buraksenyurt.com/syndication.axd?post=263c2b42-e7a4-4247-97f5-33167c5a3c47https://www.buraksenyurt.com/post/AdoNet-Data-Services-15-CTP2-Data-Binding-Bolum-1Ado.Net Data Services 1.5 CTP2 - Data Binding Bölüm 12009-11-08T18:30:00+00:00bsenyurt<p>Merhaba Arkadaşlar,</p>
<p><strong>Ado.Net Data Services </strong>ile geliştirilen servislerin tüketilmesi sırasında önem arz eden konulardan biriside, istemci tarafındaki <strong>veri bağlama(DataBinding)</strong> işlemleridir. Öyleki, servisin tüketicisi olan istemcilerin</p>
<ul>
<li>Veriye bağlanılması,</li>
<li>Bağlanılan verilerin ilgili kontrollerde gösterilmesi,</li>
<li>Kontroller üzerinden yapılan değişikliklerin aslında bağlanılan Entity içeriklerinde de gerçekleştirilmesi,</li>
<li>İstemci tarafındaki veri içeriğindeki değişimlerin servis tarafına da gönderilmesi(SaveChanges çağrısı sonrası)</li>
</ul>
<p>gibi fonksiyonellikleri desteklemesi gerekir. Ancak istmeci tarafında <strong>WPF(Windows Presentation Foundation)</strong> veya <strong>Silverlight </strong>gibi zengin içerik sağlayan uygulamalar söz konusu olduğunda, bu modellerin getirdiği veri bağlama kolaylıklarından da yararlanılmalıdır.</p>
<p><strong>Ado.Net Data Services v1.5 </strong>ile birlikte istemci tarafına getirilen <strong>DataServiceCollection</strong> isimli koleksiyonun veri bağlama işlemlerinde kullanılabilmekte olup, <strong>CTP2</strong> versiyonunda dahada iyileştirilmiş olarak karşımıza çıkmaktadır. Buna göre istemci tarafı için üretilen kütüphanede<strong>(Client Library)</strong> kolaylaştırıcı değişiklikler yapıldığı söylenebilir. <strong>DataServiceCollection<T></strong> koleksiyonu <strong>ObservableCollection<T></strong> tipinden türemekte olup, <strong>INotifyPropertyChanged </strong>ve <strong>INotifyCollectionChanged arayüzlerini(Interface) </strong>uygulamaktadır. Aşağıdaki <strong>Object Browser</strong> çıktısında bu tipin içeriği açık bir şekilde görülmektedir.</p>
<p><img src="/pics/2009%2f9%2fblg82_DataServiceCollection.gif" alt="" /></p>
<p>Bu nedenle <strong>WPF</strong> ve <strong>Silverlight</strong> tarafında ele alınanveri bağlama(DataBinding) ihtiyaçlarını hem <strong>one-way</strong> hemde <strong>two-way </strong>olarak karşılayabilecek bir koleksiyon olarak düşünülmelidir. Buna göre <strong>WPF</strong> ve <strong>Silverlight </strong>tarafındaki veri bağlı kontrollerin, <strong>DataServiceCollection</strong> sınıfından örneklenen koleksiyonları kullanabilmeleri mümkündür. Tahmin edileceği üzere iki yönlü bağlama sayesinde istemci <strong>Context'</strong> i ile <strong>Entity'</strong> ler arasındaki iletişimde, değişikliklerin karşılıklı olarak yansıtılabilmesi otomatikleştirilmektedir. Bir <strong>DataServiceCollection</strong> basit olarak,<strong> Ado.Net Data Service</strong> tarafına yapılacak bir çağrı ile kolayca doldurulabilir. Özellikle <strong>CTP2'</strong> de istemci tarafında <strong>DataServiceCollection </strong>örneklerinin daha güçlü bir şekilde ele alınması için gerekli iyileştirmelerin yapıldığı görülmektedir.</p>
<p>Dilerseniz <strong>Ado.Net Data Service </strong>hizmetlerinin kullanıldığı senaryolarda, veri bağlama işlemlerinin nasıl yapılacağını örnekler üzerinden incelemeye başlayalım. İlk olarak <strong>tek yönlü (One Way)</strong> sonrasında ise <strong>iki yönlü(Two Way)</strong> veri bağlama<strong> </strong>işlemlerini ele alıyor olacağız. İşe ilk olarak basit bir <strong>Asp.Net Web Application </strong>projesi oluşturarak başlayabiliriz. Projemizde <strong>Ado.Net Entity Framework </strong>tabanlı bir veri kaynağı kullanıyor olacağız. İstemci tarafında <strong>one-to-many </strong>ilişki içerisinde değerlendirilebilecek tipleri ele almak istediğimizden kobay olarak <strong>AdventureWorks </strong>veritabanındaki <strong>ProductSubcategory </strong>ve <strong>Product </strong>tablolarını kullanıyor olacağız. <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> <strong>Ado.Net Entitiy Diagramımız </strong>aşağıda görüldüğü gibidir.</p>
<p><img src="/pics/2009%2f9%2fblg82_Edm.gif" alt="" /></p>
<p><strong>Ado.Net Data Service</strong> öğesini projeye ekledikten sonra, <strong>ProductionService.svc</strong>' ye ait kod içeriğini aşağıdaki gibi düzenleyebiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Data.Services;
namespace AdventureServices
{
public class ProductionService
: DataService<AdventureWorksEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.DataServiceBehavior.MaxProtocolVersion = System.Data.Services.Common.DataServiceProtocolVersion.V2;
}
}
}</pre>
<p>Önemli olan noktalarda birisi versiyon olarak <strong>CTP2'</strong> nin kullanılacağının <strong>DataServiceProtocolVersion.V2</strong> ile belirtilmesidir.</p>
<p>Artık istemci tarafını tasarlamaya başlayabiliriz. Ancak öncesinde istemci tarafının <strong>Bağlayıcı Arayüzlerini(Binding Interfaces)</strong> uygulayabilmesi için komut satırından<strong><em>(Visual Studio 2008 Command Prompt) </em></strong>kod üretici ile ilgili bazı işlemlerin yapılması gerekmektedir. Visual Studio tarafından ele alanın ilgili <strong>kod üretim(Code Generation) </strong>kütüphanesine bu uygulama işini bildirmek için aşağıdaki komutlarım çalıştırılması yeterli olacaktır.</p>
<p><img src="/pics/2009%2f9%2fblg82_CommandPrompt.gif" alt="" /></p>
<p>Bu kez gerçekten istemci tarafını yazmaya başlayabiliriz. <img title="Smile" src="/editors/tiny_mce3/plugins/emotions/img/smiley-smile.gif" alt="Smile" border="0" /> Bu amaçla basit bir <strong>WPF </strong>uygulaması geliştireceğiz. <em>(Size tavsiyem aynı örneği Silverlight üzerinde geliştirmeye çalışmanız olacaktır.)</em> Uygulamamızı oluşturduktan sonra ilk yapacağımız işlem, <strong>Ado.Net Data Service'</strong> imize ait <strong>URL </strong>adresinden gerekli servis referansının, <strong>Add Service Reference </strong>yardımıyla projeye eklenmesi olacaktır.</p>
<p><img src="/pics/2009%2f9%2fblg82_AddServiceReference.gif" alt="" /></p>
<p>Görüldüğü üzere, servis üzerinden sunduğumuz <strong>Product</strong> ve <strong>ProductSubcategory Entity </strong>içerikleri buraya yansıtılmaktadır. Referansın eklenmesinden sonra istemci tarafında aşağıdaki sınıf diagramında görülen tiplerin oluşturulduğu fark edilebilir.</p>
<p><img src="/pics/2009%2f9%2fblg82_ClassDiagram.gif" alt="" /></p>
<p>Dikkat edileceği üzere <strong>Product</strong> ve <strong>ProductSubcategory </strong>tiplerine <strong>INotifyPropertyChanged </strong>arayüzü uygulanmıştır. Buna göre söz konusu tiplere ait özelliklerde olacak değişimler, örneklerin bağlandığı ortamlara otomatik olarak bildirilecektir. Elbette tam tersi durumda geçerlidir. Yine servis referansının eklenmesi sonrası, istemci tarafına <strong>Microsoft.Data.Services.Client</strong> <strong>assembly'</strong> ınında bildirildiği görülebilir ki bu assembly <strong>DataServiceCollection<T></strong> gibi önemli tipleri içermektedir.</p>
<p><img src="/pics/2009%2f9%2fblg82_ClientAsmbly.gif" alt="" /></p>
<p><strong>WPF</strong> uygulamamızın <strong>Window1</strong> içeriğini görsel olarak aşağıdaki gibi tasarladığımızı düşünelim.</p>
<p><img src="/pics/2009%2f9%2fblg82_Window1Design.gif" alt="" /></p>
<p>Görsel içeriğimizde yer alan <strong>ComboBox</strong> bileşenini <strong>ProductSubcategory</strong> içeriği ile dolduracağız. Diğer yandan alt tarafta görülen <strong>GridView</strong> kontrolünde, <strong>ComboBox</strong> bileşeninden seçilen alt kategoriye bağlı ürünlerin<em>(dolayısıyla Product nesne örneklerinin)</em> bazı özellikleri gösterilecektir. Çok doğal olarak <strong>ComboBox</strong> ve <strong>GridView</strong> bileşenlerinin, <strong>Data Service</strong> tarafından gelen içeriğe bağlanmaları gerekmektedir. Üstelik <strong>ProductSubcategory</strong> ve <strong>Product</strong> entity' leri arasında bire çok ilişki söz konusu olduğundan, <strong>ComboBox</strong> kontrolünde bir öğeden diğerine geçildiğinde, buna bağlı ürünlerinde <strong>GridView</strong> kontrolünde gösterilmesi sağlanmalıdır. Bu durumlar göz önüne alındığında söz konusu <strong>XAML</strong> içeriğini aşağıdaki gibi tasarlamamız yeterli olacaktır.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><Window x:Class="ClientApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Production Space" Height="300" Width="401">
<Grid x:Name="grdProduction">
<ComboBox ItemsSource="{Binding}" Height="23" Margin="0,33,0,0" Name="cmbSubCategories" VerticalAlignment="Top" IsSynchronizedWithCurrentItem="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Height="28" HorizontalAlignment="Left" Margin="0,-1,0,0" Name="label1" VerticalAlignment="Top" Width="136">Product Sub Categories</Label>
<Label Height="28" HorizontalAlignment="Left" Margin="0,71,0,0" Name="label2" VerticalAlignment="Top" Width="136">Products</Label>
<ListView ItemsSource="{Binding Product}" Margin="0,96,0,0">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" DisplayMemberBinding="{Binding Path=ProductID}"/>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}"/>
<GridViewColumn Header="List Price" DisplayMemberBinding="{Binding Path=ListPrice}"/>
<GridViewColumn Header="Class" DisplayMemberBinding="{Binding Path=Class}"/>
<GridViewColumn Header="Number" DisplayMemberBinding="{Binding Path=ProductNumber}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window></pre>
<p><strong>ComboBox</strong> kontrolünün <strong>ItemsSource</strong> özelliğinin <strong>Binding</strong> olarak bırakıldığına dikkat edelim. Buna göre <strong>grdProduction</strong> isimli Grid kontrolünün <strong>DataContext</strong> kaynağı ne ise, <strong>ComboBox</strong> kontrolü bu kaynağa otomatik olarak bağlanacak ve her bir öğesinde, <strong>Name</strong> alanının değerini<strong><em>({Binding Path=Name} den dolayı)</em></strong> gösterecektir. Dolayısıyla kod tarafında <strong>Grid</strong> kontrolünün <strong>DataContext</strong> özelliğine atanan veri kümesi önem arz etmektedir. Diğer yandan <strong>ComboBox</strong> kontrolünün <strong>IsSynchronizedWithCurrentItem</strong> niteliğine <strong>true</strong> değeri atandığına da dikkat edilmelidir. Bu sayede <strong>ComboBox</strong> içerisinde olan değişiklikler, diğer veri bağlı kontrollerede iletilecektir.</p>
<p>Yani <strong>GridView</strong> kontrolünün alt kategoriye bağlı ürünler ile doldurulması işleminin gerçekleştirilebilmesi için söz konusu niteliğin <strong>true</strong> değerine sahip olması gerekmektedir. <strong>ListView</strong> bileşeninin <strong>ItemsSource</strong> özelliğine <strong>{Binding Product}</strong> değeri atanmıştır. Buna göre, <strong>ListView</strong> içerisindeki veri bağlı kontrollerin <strong>Product</strong> kaynağına bağlanabileceği belirtilmiş olur ki bu sayede <strong>GridView</strong> kontrolünün <strong>GridViewColumn</strong> elementlerinin <strong>DisplayMemberBinding</strong> niteliklerine <strong>Product</strong> tipine ait özelliklerin adları atanmıştır. Peki tüm bu veri bağlı kontrollerin baz alacağı veri kaynağı nerede atanacaktır?</p>
<p>Bu amaçla kod tarafında aşağıdaki işlemleri yapmamız yeterli olacaktır.</p>
<p><strong>Window1 Code içeriği;</strong></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Data.Services.Client;
using System.Windows;
using ClientApp.ProductionSpace;
namespace ClientApp
{
public partial class Window1
: Window
{
AdventureWorksEntities adw = new AdventureWorksEntities(new Uri("http://localhost:1757/ProductionService.svc/"));
public Window1()
{
InitializeComponent();
grdProduction.DataContext = DataServiceCollection
.CreateTracked<ProductSubcategory>(adw, adw.ProductSubcategory.Expand("Product"));
}
}
}</pre>
<p><strong>DataServiceContext</strong> türevli nesne örneği oluşturulduktan sonra <strong>Window1</strong> <strong>yapıcı metodu(Constructor)</strong> içerisinde <strong>DataServiceCollection</strong> üzerinden <strong>CreateTracked</strong> isimli bir çağrı yapıldığı görülmektedir. Bu çağrıda <strong>ProductSubcategory</strong> tipinden bir nesne kümesinin listesi alınmaktadır. Ayrıca metodun ikinci parametresine dikkat edilecek olursa, her bir <strong>ProductSubcategory</strong> için <strong>Product</strong> genişletmesinin yapıldığı, yani alt kategoriye bağlı olan ürünlerinde talep edildiği görülmektedir. Uygulamanın çalıştırılması sonrasında <strong>CreateTracked</strong> metodunun çağırıldığı yerde <strong>Breakpoint</strong> ile ilerlenir ve <strong>SQL Server Profiler</strong> aracından arka planda çalıştırılan sorgu incelenirse aşağıdaki ifadenin yürütüldüğü görülebilir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT
[Project1].[ProductSubcategoryID] AS [ProductSubcategoryID], [Project1].[ProductCategoryID] AS [ProductCategoryID], [Project1].[Name] AS [Name], [Project1].[rowguid] AS [rowguid], [Project1].[ModifiedDate] AS [ModifiedDate], [Project1].[C1] AS [C1], [Project1].[C2] AS [C2], [Project1].[ProductID] AS [ProductID], [Project1].[Name1] AS [Name1], [Project1].[ProductNumber] AS [ProductNumber], [Project1].[MakeFlag] AS [MakeFlag], [Project1].[FinishedGoodsFlag] AS [FinishedGoodsFlag], [Project1].[Color] AS [Color], [Project1].[SafetyStockLevel] AS [SafetyStockLevel], [Project1].[ReorderPoint] AS [ReorderPoint], [Project1].[StandardCost] AS [StandardCost], [Project1].[ListPrice] AS [ListPrice], [Project1].[Size] AS [Size], [Project1].[SizeUnitMeasureCode] AS [SizeUnitMeasureCode], [Project1].[WeightUnitMeasureCode] AS [WeightUnitMeasureCode], [Project1].[Weight] AS [Weight], [Project1].[DaysToManufacture] AS [DaysToManufacture], [Project1].[ProductLine] AS [ProductLine], [Project1].[Class] AS [Class], [Project1].[Style] AS [Style], [Project1].[ProductModelID] AS [ProductModelID], [Project1].[SellStartDate] AS [SellStartDate], [Project1].[SellEndDate] AS [SellEndDate], [Project1].[DiscontinuedDate] AS [DiscontinuedDate], [Project1].[rowguid1] AS [rowguid1], [Project1].[ModifiedDate1] AS [ModifiedDate1]
FROM ( SELECT
[Extent1].[ProductSubcategoryID] AS [ProductSubcategoryID], [Extent1].[ProductCategoryID] AS [ProductCategoryID], [Extent1].[Name] AS [Name], [Extent1].[rowguid] AS [rowguid], [Extent1].[ModifiedDate] AS [ModifiedDate], 1 AS [C1], [Extent2].[ProductID] AS [ProductID], [Extent2].[Name] AS [Name1], [Extent2].[ProductNumber] AS [ProductNumber], [Extent2].[MakeFlag] AS [MakeFlag], [Extent2].[FinishedGoodsFlag] AS [FinishedGoodsFlag], [Extent2].[Color] AS [Color], [Extent2].[SafetyStockLevel] AS [SafetyStockLevel], [Extent2].[ReorderPoint] AS [ReorderPoint], [Extent2].[StandardCost] AS [StandardCost], [Extent2].[ListPrice] AS [ListPrice], [Extent2].[Size] AS [Size], [Extent2].[SizeUnitMeasureCode] AS [SizeUnitMeasureCode], [Extent2].[WeightUnitMeasureCode] AS [WeightUnitMeasureCode], [Extent2].[Weight] AS [Weight], [Extent2].[DaysToManufacture] AS [DaysToManufacture], [Extent2].[ProductLine] AS [ProductLine], [Extent2].[Class] AS [Class], [Extent2].[Style] AS [Style], [Extent2].[ProductModelID] AS [ProductModelID], [Extent2].[SellStartDate] AS [SellStartDate], [Extent2].[SellEndDate] AS [SellEndDate], [Extent2].[DiscontinuedDate] AS [DiscontinuedDate], [Extent2].[rowguid] AS [rowguid1], [Extent2].[ModifiedDate] AS [ModifiedDate1],
CASE WHEN ([Extent2].[ProductID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2]
FROM [Production].[ProductSubcategory] AS [Extent1]
LEFT OUTER JOIN [Production].[Product] AS [Extent2] ON [Extent1].[ProductSubcategoryID] = [Extent2].[ProductSubcategoryID]
) AS [Project1]
ORDER BY [Project1].[ProductSubcategoryID] ASC, [Project1].[C2] ASC</pre>
<p>Çok fazla alan var değil mi? <img title="Undecided" src="/editors/tiny_mce3/plugins/emotions/img/smiley-undecided.gif" alt="Undecided" border="0" /> Her neyse...Gelelim çalışma zamanındaki duruma. Örneğin <strong>Handlebase</strong> alt kategorisini seçtiğimizde aşağıdaki şekilde görülen durum oluşmaktadır.</p>
<p><img src="/pics/2009%2f9%2fblg82_Run1.gif" alt="" /></p>
<p>Başka bir alt kategori seçtiğimizde ise<em>(örneğin Bottom Brackets)</em> buna bağlı ürünlerin <strong>GridView</strong> kontrolüne doldurulduğu görülecektir.</p>
<p><img src="/pics/2009%2f9%2fblg82_Run2.gif" alt="" /></p>
<p>Tabiki burada sadece entity içeriklerinin doldurulması ve veri kontrollerine <strong>tek yönlü<em>(One-Way)</em></strong> bağlanması söz konusudur. Ancak tahmin edileceği üzere birde kontroller üzerinden verilerde yapılan değişiklikler sonrası bunların <strong>Entity</strong> içeriklerine yansıtılması ve sonrasında <strong>SaveChanges</strong> metodu ile tüm değişikliklerin servis tarafına gönderilmesi söz konusu olabilir ki buda iki yönlü(Two Way) bağlamanın tesis edilmesi ile kolayca gerçekleştirilebilir. Nitekim <strong>two-way</strong> binding metoduna göre, kolekisyonda olacak değişimler, <strong>SaveChanges</strong> metoduna yapılan çağrı sonucu servis tarafına ve dolayısıyla sunucu üzerindeki veri kaynağına da iletilecekteir. Bu konuyu bir sonraki yazımızda ele alıyor olacağız. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://www.buraksenyurt.com/pics/2009%2f9%2fDataBinding.rar">DataBinding.rar (116,66 kb)</a></p>2009-11-08T18:30:00+00:00ado.net data serviceswpfsilverlightdatabindingodatabsenyurtAdo.Net Data Services v1.5 ile birlikte istemci tarafına getirilen DataServiceCollection isimli koleksiyonun veri bağlama işlemlerinde kullanılabilmekte olup, CTP2 versiyonunda dahada iyileştirilmiş olarak karşımıza çıkmaktadır. Buna göre istemci tarafı için üretilen kütüphanede(Client Library) kolaylaştırıcı değişiklikler yapıldığı söylenebilir. DataServiceCollection<T> koleksiyonu ObservabelCollection<T> tipinden türemekte olup, INotifyPropertyChanged ve INotifyCollectionChanged arayüzlerini(Interface) uygulamaktadır. Aşağıdaki Object Browser çıktısında bu tipin içeriği açık bir şekilde görülmektedir.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=f0d5dc96-2d9b-441f-adb1-83ba29c226300https://www.buraksenyurt.com/trackback.axd?id=f0d5dc96-2d9b-441f-adb1-83ba29c22630https://www.buraksenyurt.com/post/AdoNet-Data-Services-15-CTP2-Data-Binding-Bolum-1#commenthttps://www.buraksenyurt.com/syndication.axd?post=f0d5dc96-2d9b-441f-adb1-83ba29c22630https://www.buraksenyurt.com/post/AdoNet-Data-Services-15-CTP2-Web-Friendly-FeedsAdo.Net Data Services 1.5 CTP2 - Web Friendly Feeds2009-10-31T11:25:00+00:00bsenyurt<p><img style="float: right;" src="/pics/2009%2f9%2fblg80_Giris.jpg" alt="" />Merhaba Arkadaşlar,</p>
<p><strong>Ado.Net Data Services v1.5 CTP1</strong> ile gelen <strong>Web Friendly Feeds</strong> özelliği, <strong>CTP2 </strong>sürümünde eklenen iki yeni eşleştirme seçeneği ile genişletilmiştir. Durun bir dakika...<strong>Web Friendly Feeds </strong>nedir? <img title="Undecided" src="/editors/tiny_mce3/plugins/emotions/img/smiley-undecided.gif" alt="Undecided" border="0" /> Arkadaşlıktan farklı bir şey olsa gerek <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> Öncelikle bu konuya açıklık getirmek gerekiyor.</p>
<p><strong>Web Friendly Feeds</strong> özelliği, bir <strong>Entity</strong>'nin herhangibir <strong>özelliğini(Property)</strong>, <strong>Ado.Net Data Service'</strong> inden çıktı olarak üretilen <strong>Atom </strong>içeriğindeki bir <strong>elemente </strong>eşleştirmekte kullanılmaktadır. Nitekim servisin ürettiği varsayılan <strong>Atom</strong> içeriğinde yer alan <strong>author name, url, title</strong> vs... gibi bilgiler zaten standart olarak kabul edilmiştir ve bu nedenle söz konusu elementleri değerlendiren yorumlayıcılara, var olan Entity içeriğindeki bazı özellik değerlerinin aktarılması istenebilir. Bir başka deyişle, servisin ürettiği içeriğin kaynağındaki özelliklerin çıktıda map edileceği yerler, Atom içeriğindeki belirli noktalar olarak belirlenebilir.</p>
<p>Çok doğal olarak eşleştirmenin çalışma zamanında değerlendirilmesi gerekmektedir. <strong>Ado.Net Data Servislerin</strong>, arka planda <strong>Entity Framework</strong> veya <strong>Custom LINQ Provider </strong>kullandığı düşünüldüğünde, eşleştirme işleminin nerede bildirileceği şu anda bizim için öğrenilmesi gereken yegane noktadır. Bir geliştirici olarak söz konusu eşleştirme bildirimlerinin <strong>nitelik(attribute)</strong> veya <strong>konfigurasyon</strong> bazlı olarak yapılacağının düşünülmesi son derece doğaldır ki bunların çalışma zamanında ele alınması gerekmektedir. Biz bu yazımızda önce eksikliği anlamaya çalışacak, sonrasında ise <strong>Entity Framework</strong> kullanılması halinde eşleştirmeyi nasıl gerçekleştirebileceğimizi inceleyeceğiz.</p>
<p>Öncelikle basit bir <strong>Asp.Net Web </strong>uygulaması oluşturalım ve aşağıdaki <strong>EDM </strong>grafiğinde görülen <strong>Ado.Net Entity Data Model</strong> öğesini söz konusu projeye ekleyelim. Her zamanki gibi kobay olarak AdventureWorks veritabanını hedef alıyor olacağız.</p>
<p><img src="/pics/2009%2f9%2fblg80_Edm.gif" alt="" /></p>
<p><strong>Contact </strong>tablosunu değerlendirmek amacıyla basit bir <strong>Ado.Net Data Services v1.5 CTP2 </strong>öğesini projeye ekleyerek devam edelim. Öğemizin kod içeriğini aşağıdaki gibi geliştirmemiz konumuz için yeterlidir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Data.Services;
namespace WebFriendlyFeed
{
public class AdventureServices
: DataService<AdventureWorksEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = System.Data.Services.Common.DataServiceProtocolVersion.V2;
}
}
}</pre>
<p>Söz konusu servis üzerinden Contact tablosundaki örneğin ilk 3 satırı talep ettiğimizde aşağıdakine benzer bir çıktı elde ederiz.</p>
<p><img src="/pics/2009%2f9%2fblg80_FirstRun.gif" alt="" /></p>
<p>Eksiklik şudur; standart <strong>atom feed</strong> içeriğinde yer alan <strong>author/name </strong>ve <strong>title </strong>elementlerinin içeriği boştur. Bu bilgilerin eksik olması şu aşamada önemli değilmiş gibi görünebilir. Ama bu <strong>atom feed </strong>çıktısını değerlendiren bir uygulamada söz konusu alanlar belirli amaçlar ile kullanılıyor olabilir ve bu nedenden boş olmaları yararlı olmayabilir. Örneğin <strong>Internet Explorer' </strong>ın feed içeriklerini görsel olarak yorumlama özelliğini<strong>(Turn on feed reading view)</strong> açtığımızda aynı sorgu aşağıdaki sonucu üretecektir.</p>
<p><img src="/pics/2009%2f9%2fblg80_FirstRunViewer.gif" alt="" /></p>
<p>Görüldüğü gibi ilk etapta <strong>feed entry'</strong> leri ile ilişkili <strong>title </strong>veya <strong>author/name </strong>gibi elementler doldurulmadığı için okunabilir bir içeriğin oluşmadığı görülmektedir. Üstelik <strong>Title </strong>gibi alanlara göre sıralama işlemi yapılmasıda mümkün değildir. İşte <strong>Ado.Net Data Services 1.5</strong> sürümünde getirilen <strong>Web Friendly Feed </strong>özelliği, söz konusu standart <strong>entry elementlerinden </strong>bazılarının, <strong>entity </strong>içeriğinden beslenmesini sağlayabilmektedir. Peki ama nasıl? <img title="Undecided" src="/editors/tiny_mce3/plugins/emotions/img/smiley-undecided.gif" alt="Undecided" border="0" /> Örneğimizde <strong>Entity Framework</strong> modeli kullanıldığından, <strong>Conceptual Schema Definition Language(CSDL)</strong> içeriğinde bazı ayarlamalar yapılması gerekmektedir. <em>(Tabi Entity modelinin <strong>Update </strong>edilmesi halinde bu değişikliklerin uçabileceğini hatırlatmam gerekir.)</em> Aşağıdaki şekilde görüldüğü gibi.</p>
<p><img src="/pics/2009%2f9%2fblg80_EdmChange.gif" alt="" /></p>
<p>İlk olarak bir <strong>namespace </strong>ilave edildiğini görüyoruz. Bu namespace ilave edilmez ise, <strong>FC_KeepInContent </strong>veya <strong>FC_TargetPath </strong>gibi nitelikleri kullanamayız. Yeri gelmişken bu niteliklerin ne iş yaptıklarını açıklayalım. <strong>CSDL </strong>içeriğinde yer alan <strong>FirstName </strong>özelliğinin değerinin <strong>Atom Feed </strong>içerisindeki <strong>author/name </strong>elementinde çıkması için <strong>FC_TargetPath </strong>niteliğine <strong>SyndicationAuthorName </strong>değeri atanmıştır. Benzer şekilde <strong>Atom Feed </strong>içeriğinin bir <strong>Contact </strong>için üretilen <strong>Title </strong>elementinde <strong>LastName </strong>özelliğinin değerinin çıkması için, FC_<strong>TargetPath</strong> niteliğine <strong>SyndicationTitle </strong>değeri atanmıştır.</p>
<p>Aynı işlem <strong>Email </strong>adresi içinde gerçekleştirilmiş ve özellik değerinin <strong>author/email </strong>elementinde çıkması için <strong>FC_TargetPath </strong>niteliğine <strong>SyndicationAuthorEmail </strong>değeri atanmıştır. Buna göre <strong>FC_TargetPath </strong>niteliğinin değerinin, <strong>entity </strong>özelliğinin hangi <strong>atom </strong>alanında çıkacağını belirttiğini ifade edebiliriz. <strong>FC_KeepInContent </strong>niteliğine atanan <strong>false </strong>değeri ilede, <strong>atom feed' </strong>in element noktlarında çıkan değerlerin, üretilen <strong>Contact </strong>elementine ait <strong>content </strong>içeriğinde <span style="text-decoration: underline;">gösterilmemesi</span> sağlanmaktadır. Buna göre biraz önce yaptığımız talebi tekrarlarsak aşağıdaki çıktıları alırız.</p>
<p><img src="/pics/2009%2f9%2fblg80_SecondRunPreview.gif" alt="" /></p>
<p>Ve atom çıktısını içeriği;</p>
<p><img src="/pics/2009%2f9%2fblg80_SecondRunSource.gif" alt="" /></p>
<p>Görüldüğü üzere <strong>FirstName</strong> ve <strong>EmailAddress </strong>bilgileri, <strong>author </strong>elementi altındaki <strong>name </strong>ve <strong>email </strong>alt elementlerine yazılmıştır. Ayrıca <strong>entry' </strong>nin <strong>title </strong>elementinin içeriğinede <strong>LastName </strong>özelliğinin değerinin yazıldığı görülmektedir. Bununla birlikte buradaki özellikler, <strong>entry </strong>elementi altındaki <strong>content </strong>elementi içeriğindende çıkartılmıştır. Süper <img title="Smile" src="/editors/tiny_mce3/plugins/emotions/img/smiley-smile.gif" alt="Smile" border="0" /> Peki <strong>Atom Feed Entry Elementlerinden </strong>hangilerine atamalar yapabiliriz? İşte <strong>Ado.Net Data Services v1.5 CTP2 </strong>eklentilerinin de yer aldığı son liste. </p>
<p><strong>Entity tip özelliklerini hangi Atom Entry elementlerine eşleştirebiliriz.</strong></p>
<p>author/email -> SyndicationItemProperty.AuthorEmail <br />author/name -> SyndicationItemProperty.AuthorName <br />author/uri -> SyndicationItemProperty.AuthorUri <br />published -> SyndicationItemProperty.Published <br />rights -> SyndicationItemProperty.Rights <br />summary -> SyndicationItemProperty.Summary <em>(CTP2 ile Gelmiştir)</em><br />title -> SyndicationItemProperty.Title <br />Updated -> SyndicationItemProperty.Updated <em>(CTP2 ile Gelmiştir)</em><br />contributor/name -> SyndicationItemProperty.ContributorName <br />contributor/email -> SyndicationItemProperty.ContributorEmail <br />contributor/uri -> SyndicationItemProperty.ContributorUri</p>
<p>Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://www.buraksenyurt.com/pics/2009%2f9%2fWebFriendlyFeed.rar">WebFriendlyFeed.rar (35,28 kb)</a></p>2009-10-31T11:25:00+00:00ado.net data servicesweb firendly feedslinqentity frameworkodatabsenyurtAdo.Net Data Services v1.5 CTP1 ile gelen Web Friendly Feeds özelliği, CTP2 sürümünde eklenen iki yeni eşleştirme seçeneği ile genişletilmiştir. Durun bir dakika...Web Friendly Feeds nedir? Undecided Arkadaşlıktan farklı bir şey olsa gerek Wink Öncelikle bu konuya açıklık getirmek gerekiyor. Web Friendly Feeds özelliği, bir Entity'nin herhangibir özelliğini(Property), Ado.Net Data Service' inden çıktı olarak üretilen Atom içeriğindeki bir elemente eşleştirmekte kullanılmaktadır. Nitekim servisin ürettiği varsayılan Atom içeriğinde yer alan author name, url, title vs... gibi bilgiler zaten standart olarak kabul edilmiştir ve bu nedenle söz konusu elementleri değerlendiren yorumlayıcılara, var olan Entity içeriğindeki bazı özellik değerlerinin aktarılması istenebilir. Bir başka deyişle, servisin ürettiği içeriğin kaynağındaki özelliklerin çıktıda map edileceği yerler, Atom içeriğindeki belirli noktalar olarak belirlenebilir.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=4957c322-a35d-4cca-a2e2-2dde2a47a7990https://www.buraksenyurt.com/trackback.axd?id=4957c322-a35d-4cca-a2e2-2dde2a47a799https://www.buraksenyurt.com/post/AdoNet-Data-Services-15-CTP2-Web-Friendly-Feeds#commenthttps://www.buraksenyurt.com/syndication.axd?post=4957c322-a35d-4cca-a2e2-2dde2a47a799https://www.buraksenyurt.com/post/AdoNet-Data-Services-15-ProjectionsAdo.Net Data Services 1.5 - Projections2009-09-30T00:00:00+00:00bsenyurt<p><img style="float: right;" src="/pics/2009%2f9%2fblg79_Giris.jpg" alt="" />Merhaba Arkadaşlar,</p>
<p>Gün geçmiyorki yazılım teknolojilerinde bir yenilik, bir güncelleme, bir genişletme çıkmasın...Özellikle dünyanın dev yazılım şirketlerinin en büyüğü olarak görebileceğimiz Microsoft tarafında bu gelişme ve güncelleme hızı oldukça yüksek. Gerçektende heyecan verici yenilikler, özellikler ile karşılaşmıyor değiliz. Bu konuya nereden mi geldim?</p>
<p>Çok zaman değil daha bir sene öncesine kadar <strong>Astoria </strong>kod adlı <strong>Ado.Net Data Services </strong>konusunu incelemeye başlamıştım. <strong>Entity Framework</strong> veya <strong>Custom LINQ Provider' </strong>ları ile sunulan veri kümelerine, <strong>REST </strong>bazlı olarak <strong>URL </strong>sorgular atılabilmesini sağlayan ve özellikle <strong>Silverlight </strong>gibi <strong>RIA </strong>içeriklerinde son derece kıymetli olan bir servis uygulaması olarak değerlendirebileceğimiz bu konu ile ilişkili ilk paylaşımlarımı yaptıktan sonra araya <strong>WCF 4.0, WF 4.0, Design Patterns, Design Principles, .Net RIA Services </strong>gibi konular girdi. Bu konulardaki incelemelerimi ve paylaşımlarımı devam ettirirken bir baktım ki <strong>Ado.Net Data Services </strong>konusuna çok uzun zaman ara vermişim. Ara vermeklede iyi yapmamışım <img title="Undecided" src="/editors/tiny_mce3/plugins/emotions/img/smiley-undecided.gif" alt="Undecided" border="0" /></p>
<p>Nitekim program yöneticisi olan <strong>Mike Flasko </strong>boş durmamış ve <a title="Ado.Net Data Services v1.5 CTP2" href="http://www.microsoft.com/downloads/details.aspx?FamilyID=a71060eb-454e-4475-81a6-e9552b1034fc&displaylang=en" target="_blank">Ado.Net Data Services v1.5</a> versiyonu için CTP2 sürümünü duyurmuş<em>(.Net Framework 3.5 Service Pack 1 ve Silverlight 3.0' ı hedefleyen ama .Net Framework 4.0 içerisinede dahil edilecek olan özellikleri içeren bir sürüm olarak düşünülebilir)</em>. Duyurulması ile birlikte hem <a title="Astoria Team Blog" href="http://blogs.msdn.com/astoriateam/default.aspx" target="_blank">blog</a> sitesinde hemde çeşitli kaynaklarda konu ile ilişkili yazılar yayınlanmaya da başlanmış.</p>
<p>Bu versiyonda bazı yenilikler ve daha önceki sürüme ait çeşitli düzeltmeler<strong>(bug-fix)</strong> yer almakta. Gelen yeni özelliklerden birisi de <strong>Projections</strong> kullanımı. Bu yeniliğe göre servis üzerinde gerçekleştirilen <strong>URL</strong> bazlı sorguların sonuçları kırpılabiliyor ve sadece ilgilenilmek istenenlerin istemci tarafına çekilmesi sağlanabiliyor. <img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> Bir başka deyişle, istemcinin yapmış olduğu bir <strong>talebin(Request)</strong> sonuçlarında sadece ilgilendiği özelliklerin getirilmesi sağlanabilmekte. Bunu tam olmasada, LINQ sorguları sırasında anonymous type kullanımına benzetebiliriz. Söz konusu özellik içerisinde <strong>primitive/complex</strong> tipleri veya <strong>navigation</strong> özelliklerini de kullanabilmekteyiz. Özelliğin getirisi, istemcinin talebi sonrası tüm <strong>Entity</strong> kümesinin işlenmesi ve ağ üzerinde hareket etmesi yerine, sadece istediği özellikleri içeren kümenin/kümelerin değerlendirilebilmesi olarak görülebilir.</p>
<p>Bu çok doğal olarak istemci ile sunucu arasındaki trafiği boyutsal olarak azaltmaktadır. <strong>Projections</strong> kullanımı son derece basittir. Bunun için <strong>$select</strong> operatöründen yararlanılmaktadır. Tabiki konuyu anlamamızın en iyi yolu basit bir örneği adım adım geliştirmek ve üzerinde ilerlemekle olacaktır. Bu nedenle kolları sıvayıp işe koyulalım. İlk olarak <strong>Visual Studio 2008</strong> ortamında<em>(Service Pack 1 yüklü olan) </em>basit bir Asp.Net Web Uygulaması oluşturarak işe başlayabiliriz. Sonrasında servisimiz için gerekli Entity kaynağını oluşturmamız gerekiyor. Bu amaçla Ado.Net Entity Framework' ten yararlanabilir ve yine kobay veritabanımız olan AdventureWorks' ü değerlendirebiliriz. Örneğimizde aşağıdaki <strong>EDM</strong> şemasını kullanıyor olacağız.</p>
<p><img src="/pics/2009%2f9%2fblg79_Edm.gif" alt="" /></p>
<p><strong>AdventureWorks</strong> veritabanındaki <strong>Production</strong> şemasında yer alan <strong>ProductCategory, ProductSubCategory </strong>ve <strong>Product </strong>tablolarını kullanmaya çalışıyoruz. <strong>Entity</strong> modelimizi oluşturduktan sonra, projemize yeni bir <strong>Ado.Net Data Services</strong> öğesi ekleyerek devam edebiliriz. Tabi bu seferki örneğimizde <strong>v1.5 CTP2</strong> sürümüne ait öğeyi kullanmamız gerekiyor.</p>
<p><img src="/pics/2009%2f9%2fblg79_NewItem.gif" alt="" /></p>
<p><strong>Ado.Net Data Services</strong> öğemizin kod içeriğini ise aşağıdaki gibi değiştirmemiz yeterli olacaktır.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Data.Services;
namespace Projections
{
public class AdventureServices
: DataService<AdventureWorksEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
// Tüm Entity' leri sadece okuma amaçlı açıyoruz
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
// İstemciden gelecek olan Projection taleplerinin değerlendirileceğini belirtiyoruz
config.DataServiceBehavior.AcceptProjectionRequests = true;
// Versiyon 2 için geliştirme yapacağımızı belirtiyoruz. Bu versiyon belirtilmediği takdirde select operatörü ve projection fonksiyonelliği çalışmayacaktır.
config.DataServiceBehavior.MaxProtocolVersion = System.Data.Services.Common.DataServiceProtocolVersion.V2;
}
}
}</pre>
<p>Dikkat edilmesi gereken noktalardan birisi <strong>AcceptProjectionRequest</strong> ise <strong>MaxProtocolVersion</strong> özelliklerine atanan değerlerdir. Bu değerlere göre servisimiz, istemcilere <strong>Projection</strong> fonksiyonelliğini sunabilecektir. <strong>AdventureServices.svc</strong> dosyasını bir tarayıcı yardımıyla talep ettiğimizde, başlangıç için aşağıdakine benzer bir ekran görüntüsü ile karşılaşırız.</p>
<p><img src="/pics/2009%2f9%2fblg79_FirstRun.gif" alt="" /></p>
<p>Görüldüğü üzere Product, ProductCategory ve ProductSubcategory Entity' leri kullanılmaya hazırdır. Evetttt...Gelelim yazımızın önemli olan kısmına. Tarayıcı üzerinden aşağıdaki sorguyu talep ettiğimizi düşünelim.</p>
<p>http://localhost:1714/AdventureServices.svc/Product<strong>?$select=ProductID,Name,ListPrice</strong></p>
<p>Dikkat edileceği üzere <strong>Product</strong> Entity' si üzerinden select sorgusu atılmış ve sadece <strong>ProductID,Name,ListPrice</strong> alanları talep edilmiştir. Bu sorgunun çalışma zamanı çıktısı aşağıdaki gibi olacaktır.</p>
<p><img src="/pics/2009%2f9%2fblg79_SecondRun.gif" alt="" /></p>
<p>Dikkat edileceği üzere <strong>Product</strong> tablosundaki tüm ürünlerin sadece <strong>ProductID,Name</strong> ve <strong>ListPrice </strong>alanları çekilmiştir. İşin güzel yanı, bu <strong>URL</strong> talebi için arka planda çalıştırılan <strong>SQL</strong> sorgusuda sadece istenen alanları değerlendirmektedir. İşte URL' imize ait SQL sorgusunun <strong>SQL Server Profiler'</strong> dan yakalanan içeriği.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT
1 AS [C1],
CASE WHEN ([Extent1].[ProductID] IS NULL) THEN N'' ELSE N'AdventureWorksModel.Product' END AS [C2],
N'ProductID,Name,ListPrice' AS [C3],
[Extent1].[ProductID] AS [ProductID],
[Extent1].[Name] AS [Name],
[Extent1].[ListPrice] AS [ListPrice]
FROM [Production].[Product] AS [Extent1]</pre>
<p>Dolayısıyla <strong>Projection</strong> kullanılaraktan, bir <strong>Entity</strong> üzerinden sadece istenen alanları içeren çıktıların alınması sağlanabilir. Bu kullanım aynen <strong>SQL</strong> tarafı içinde geçerli olduğundan, performans adına da bazı kazanımların elde edildiği ortadadır.</p>
<p><strong>select</strong> operatörünü dilersek <strong>navigasyon özellikleri(Navigation Properties)</strong> ilede bir aradada kullanabiliriz. Örneğin aşağıdaki gibi bir <strong>URL</strong> talebinde bulunduğumuzu düşünelim.</p>
<p>http://localhost:1714/AdventureServices.svc<strong>/ProductSubcategory?$select=Name,Product</strong></p>
<p>Buna göre <strong>ProductSubcategory</strong> <strong>Entity' </strong>sinden sadece <strong>Name </strong>alanlarının değerlerini isterken, her alt kategoriye bağlı ürünleri tutan <strong>Product Entity </strong>örneklerini de talep etmekteyiz. Bu <strong>URL </strong>talebinin çıktısı aşağıdaki gibi olacaktır.</p>
<p><img src="/pics/2009%2f9%2fblg79_ThirdRun.gif" alt="" /></p>
<p>Dikkat edileceği üzere alt kategoriye bağlı olan <strong>Product </strong>kümeleri için sadece bağlantı bildirimi yapılmaktadır. Söz konusu <strong>URL'</strong> ın çalıştırılması sonucunda <strong>SQL</strong> tarafında da aşağıdaki sorgunun yürütüldüğü görülecektir.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT
1 AS [C1],
CASE WHEN ([Extent1].[ProductSubcategoryID] IS NULL) THEN N'' ELSE N'AdventureWorksModel.ProductSubcategory' END AS [C2],
N'Name,ProductSubcategoryID' AS [C3],
[Extent1].[Name] AS [Name],
[Extent1].[ProductSubcategoryID] AS [ProductSubcategoryID]
FROM [Production].[ProductSubcategory] AS [Extent1]</pre>
<p>Fark edilebileceği gibi, <strong>Product </strong>tablosu ile ilişkili bir sorgu ifadesi yer almamaktadır. Diğer yandan sadece <strong>Name </strong>alanını talep etmemize rağmen, <strong>PrimaryKey </strong>olan <strong>ProductSubcategoryID </strong>alanı da getirilmektedir. Bu son derece doğaldır nitekim, belirli bir <strong>ProductSubcategory</strong>' nin çekilmesinde <strong>primary key</strong> alanı ayırt edici özelliklerdendir üstelik entry/id elementleri içerisinde gereklidir. Diğer yandan, <strong>URL </strong>satırını aşağıdaki gibi değiştirirsek,</p>
<p>http://localhost:1714/AdventureServices.svc/<strong>ProductSubcategory?$select=Name,Product&$expand=Product&$top=2</strong></p>
<p>Hımmm...<img title="Wink" src="/editors/tiny_mce3/plugins/emotions/img/smiley-wink.gif" alt="Wink" border="0" /> Bu sorguya göre <strong>ProductSubcategory</strong> içeriğinden sadece <strong>Name</strong> alanını almakla kalmıyor, aynı zamanda alt kategoriye bağlı olan ürünleride çekiyoruz. Üstelik sadece ilk 2 <strong>ProductSubcategory</strong> tipini ele alıyoruz<em>(Sondaki <strong>top=2</strong> sorgusu nedeniyle)</em>. İşte çalışma zamanı çıktımız.</p>
<p><img src="/pics/2009%2f9%2fblg79_ForthRun.gif" alt="" /></p>
<p>Peki bu <strong>URL</strong> talebi sonrası arka planda nasıl bir <strong>SQL</strong> sorgusu çalışıyor?</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT
[Project2].[ProductSubcategoryID] AS [ProductSubcategoryID],[Project2].[Name] AS [Name], [Project2].[rowguid] AS [rowguid], [Project2].[ModifiedDate] AS [ModifiedDate], [Project2].[C1] AS [C1], [Project2].[C2] AS [C2], [Project2].[C3] AS [C3], [Project2].[C4] AS [C4], [Project2].[C5] AS [C5], [Project2].[C6] AS [C6], [Project2].[C7] AS [C7], [Project2].[ProductID] AS [ProductID], [Project2].[Name1] AS [Name1], [Project2].[ProductNumber] AS [ProductNumber], [Project2].[MakeFlag] AS [MakeFlag], [Project2].[FinishedGoodsFlag] AS [FinishedGoodsFlag], [Project2].[Color] AS [Color], [Project2].[SafetyStockLevel] AS [SafetyStockLevel], [Project2].[ReorderPoint] AS [ReorderPoint], [Project2].[StandardCost] AS [StandardCost], [Project2].[ListPrice] AS [ListPrice], [Project2].[Size] AS [Size], [Project2].[SizeUnitMeasureCode] AS [SizeUnitMeasureCode], [Project2].[WeightUnitMeasureCode] AS [WeightUnitMeasureCode], [Project2].[Weight] AS [Weight], [Project2].[DaysToManufacture] AS [DaysToManufacture], [Project2].[ProductLine] AS [ProductLine], [Project2].[Class] AS [Class], [Project2].[Style] AS [Style], [Project2].[ProductModelID] AS [ProductModelID], [Project2].[SellStartDate] AS [SellStartDate], [Project2].[SellEndDate] AS [SellEndDate], [Project2].[DiscontinuedDate] AS [DiscontinuedDate], [Project2].[rowguid1] AS [rowguid1], [Project2].[ModifiedDate1] AS [ModifiedDate1]
FROM ( SELECT
[Limit1].[ProductSubcategoryID] AS [ProductSubcategoryID], [Limit1].[Name] AS [Name], [Limit1].[rowguid] AS [rowguid], [Limit1].[ModifiedDate] AS [ModifiedDate], [Limit1].[C1] AS [C1], [Limit1].[C2] AS [C2], [Limit1].[C3] AS [C3], [Limit1].[C4] AS [C4], [Limit1].[C5] AS [C5], [Limit1].[C6] AS [C6], [Extent2].[ProductID] AS [ProductID], [Extent2].[Name] AS [Name1], [Extent2].[ProductNumber] AS [ProductNumber], [Extent2].[MakeFlag] AS [MakeFlag], [Extent2].[FinishedGoodsFlag] AS [FinishedGoodsFlag], [Extent2].[Color] AS [Color], [Extent2].[SafetyStockLevel] AS [SafetyStockLevel], [Extent2].[ReorderPoint] AS [ReorderPoint], [Extent2].[StandardCost] AS [StandardCost], [Extent2].[ListPrice] AS [ListPrice], [Extent2].[Size] AS [Size], [Extent2].[SizeUnitMeasureCode] AS [SizeUnitMeasureCode], [Extent2].[WeightUnitMeasureCode] AS [WeightUnitMeasureCode], [Extent2].[Weight] AS [Weight], [Extent2].[DaysToManufacture] AS [DaysToManufacture], [Extent2].[ProductLine] AS [ProductLine], [Extent2].[Class] AS [Class], [Extent2].[Style] AS [Style], [Extent2].[ProductModelID] AS [ProductModelID], [Extent2].[SellStartDate] AS [SellStartDate], [Extent2].[SellEndDate] AS [SellEndDate], [Extent2].[DiscontinuedDate] AS [DiscontinuedDate], [Extent2].[rowguid] AS [rowguid1], [Extent2].[ModifiedDate] AS [ModifiedDate1],
CASE WHEN ([Extent2].[ProductID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C7]
FROM (SELECT TOP (2) [Project1].[ProductSubcategoryID] AS [ProductSubcategoryID], [Project1].[Name] AS [Name], [Project1].[rowguid] AS [rowguid], [Project1].[ModifiedDate] AS [ModifiedDate], [Project1].[C1] AS [C1], [Project1].[C2] AS [C2], [Project1].[C3] AS [C3], [Project1].[C4] AS [C4], [Project1].[C5] AS [C5], [Project1].[C6] AS [C6]
FROM ( SELECT
[Extent1].[ProductSubcategoryID] AS [ProductSubcategoryID],
[Extent1].[Name] AS [Name],
[Extent1].[rowguid] AS [rowguid],
[Extent1].[ModifiedDate] AS [ModifiedDate],
1 AS [C1],
1 AS [C2],
CASE WHEN ([Extent1].[ProductSubcategoryID] IS NULL) THEN N'' ELSE N'AdventureWorksModel.ProductSubcategory' END AS [C3],
N'Name,ProductSubcategoryID' AS [C4],
N'Product' AS [C5],
1 AS [C6]
FROM [Production].[ProductSubcategory] AS [Extent1]
) AS [Project1]
ORDER BY [Project1].[ProductSubcategoryID] ASC ) AS [Limit1]
LEFT OUTER JOIN [Production].[Product] AS [Extent2] ON [Limit1].[ProductSubcategoryID] = [Extent2].[ProductSubcategoryID]
) AS [Project2]
ORDER BY [Project2].[ProductSubcategoryID] ASC, [Project2].[C7] ASC</pre>
<p>Amanınnnn!!! <img title="Sealed" src="/editors/tiny_mce3/plugins/emotions/img/smiley-sealed.gif" alt="Sealed" border="0" /> Aslında biraz can sıkıcı ama doğal olarak tüm <strong>Product</strong> alanlarının değerlendirildiğini görüyoruz. Nitekim aksini belirtmedik. Peki belirtebilir miyiz? Yani <strong>ProductSubcategory </strong>kümesinden ve genişletilebilen <strong>Product</strong> kümesinden bir kaç alanı almayı başarabilir miydik? İşte örnek bir cevabı <img title="Cool" src="/editors/tiny_mce3/plugins/emotions/img/smiley-cool.gif" alt="Cool" border="0" /></p>
<p>http://localhost:1714/AdventureServices.svc<strong>/ProductSubcategory?$select=Name,Product/Name,Product/ListPrice&$expand=Product&$top=5</strong></p>
<p>Görüldüğü gibi <strong>EntityAdı/AlanAdı(örneğin Product/Name)</strong> stilinde yapılan bildirimlerle, üretilecek olan çıktıda birden fazla <strong>Entity'</strong> den gelebilecek alanları ayrı ayrı belirtebiliyoruz. <em>(Buna göre sizlerde ProductCategory,ProductSubcategory ve Product kümelerinin tamamının bir arada bulunduğu örnek URL üzerinde çalışabilirsiniz. Çalışmanızı öneririm.)</em></p>
<p><img src="/pics/2009%2f9%2fblg79_FifthRun.gif" alt="" /></p>
<p>Görüldüğü üzere alt kategori ile ilişkili <strong>Feed</strong> girişlerinde <strong>Name</strong> alanı yer almaktayken, o alt kategoriye bağlı <strong>Product</strong> tipleri için sadece <strong>Name</strong> ve <strong>ListPrice</strong> değerleri getirilmektedir. Dolayısıyla <strong>SQL</strong> sorgusuda buna göre aşağıda görüldüğü gibi oluşacaktır.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">SELECT
[Project2].[ProductSubcategoryID] AS [ProductSubcategoryID], [Project2].[Name] AS [Name], [Project2].[C1] AS [C1], [Project2].[C2] AS [C2], [Project2].[C3] AS [C3], [Project2].[C4] AS [C4], [Project2].[C5] AS [C5],
[Project2].[C9] AS [C6], [Project2].[C6] AS [C7], [Project2].[C7] AS [C8], [Project2].[C8] AS [C9], [Project2].[Name1] AS [Name1], [Project2].[ListPrice] AS [ListPrice], [Project2].[ProductID] AS [ProductID]
FROM ( SELECT
[Limit1].[ProductSubcategoryID] AS [ProductSubcategoryID], [Limit1].[Name] AS [Name], [Limit1].[C1] AS [C1], [Limit1].[C2] AS [C2], [Limit1].[C3] AS [C3], [Limit1].[C4] AS [C4], [Limit1].[C5] AS [C5], [Extent2].[ProductID] AS [ProductID], [Extent2].[Name] AS [Name1], [Extent2].[ListPrice] AS [ListPrice], CASE WHEN ([Extent2].[ProductID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C6],
CASE WHEN ([Extent2].[ProductID] IS NULL) THEN CAST(NULL AS varchar(1)) ELSE CASE WHEN ([Extent2].[ProductID] IS NULL) THEN N'' ELSE N'AdventureWorksModel.Product' END END AS [C7],
CASE WHEN ([Extent2].[ProductID] IS NULL) THEN CAST(NULL AS varchar(1)) ELSE N'Name,ListPrice,ProductID' END AS [C8],
CASE WHEN ([Extent2].[ProductID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C9]
FROM (SELECT TOP (5) [Project1].[ProductSubcategoryID] AS [ProductSubcategoryID], [Project1].[Name] AS [Name], [Project1].[C1] AS [C1], [Project1].[C2] AS [C2], [Project1].[C3] AS [C3], [Project1].[C4] AS [C4], [Project1].[C5] AS [C5]
FROM ( SELECT
[Extent1].[ProductSubcategoryID] AS [ProductSubcategoryID], [Extent1].[Name] AS [Name], 1 AS [C1], 1 AS [C2],
CASE WHEN ([Extent1].[ProductSubcategoryID] IS NULL) THEN N'' ELSE N'AdventureWorksModel.ProductSubcategory' END AS [C3],
N'Name,ProductSubcategoryID' AS [C4],
N'Product' AS [C5]
FROM [Production].[ProductSubcategory] AS [Extent1]
) AS [Project1]
ORDER BY [Project1].[ProductSubcategoryID] ASC ) AS [Limit1]
LEFT OUTER JOIN [Production].[Product] AS [Extent2] ON [Limit1].[ProductSubcategoryID] = [Extent2].[ProductSubcategoryID]
) AS [Project2]
ORDER BY [Project2].[ProductSubcategoryID] ASC, [Project2].[C9] ASC</pre>
<p>Görüldüğü üzere <strong>Ado.Net Data Services v1.5 CTP2</strong> ile gelen <strong>Projection </strong>özelliği performans kazanımı elde etmemizi sağlayacak derecede önemli bir özellik olarak karşımıza çıkmaktadır. Bu yazımızda kullandığımız sorgular aşağıdaki gibidir.</p>
<ul>
<li>http://localhost:1714/AdventureServices.svc/Product<strong>?$select=ProductID,Name,ListPrice -></strong>(Product kümesinden ProductID, Name ve ListPrice alanları alınır)</li>
<li>http://localhost:1714/AdventureServices.svc<strong>/ProductSubcategory?$select=Name,Product -> </strong>(ProductSubcategory kümesinden Name alınır, her bir alt kategoriye bağlı Product kümelerinin sadece linkleri getirilir.)</li>
<li>http://localhost:1714/AdventureServices.svc/<strong>ProductSubcategory?$select=Name,Product&$expand=Product&$top=2 -> </strong>(Bir önceki sorgu değerlendirilir ama Product kümesinin tüm üyeleri ve sadece ilk iki alt kategori tipi çekilir)</li>
<li>http://localhost:1714/AdventureServices.svc<strong>/ProductSubcategory?$select=Name,Product/Name,Product/ListPrice&$expand=Product&$top=5 -> </strong>(Bir önceki sorgu çalışır ancak Product kümesinden sadece Name ve ListPrice alanları hesaba katılır. Alt kategorilerinde ilk 10 adedi getirilir.)</li>
</ul>
<p>Bakalım <strong>Ado.Net Data Services 1.5 CTP2</strong> tarafında bizleri başka ne gibi sürprizler beklemekte. Bu konularıda ilerleyen yazılarımızda değerlendirmeye çalışıyor olacağız. Tekrardan görüşünceye dek hepinize mutlu günler dilerim .</p>
<p><a href="https://www.buraksenyurt.com/pics/2009%2f9%2fProjections.rar">Projections.rar (53,71 kb)</a></p>2009-09-30T00:00:00+00:00ado.net data serviceswcfrestbsenyurtÇok zaman değil daha bir sene öncesine kadar Astoria kod adlı Ado.Net Data Services konusunu incelemeye başlamıştım. Entity Framework veya Custom LINQ Provider' ları ile sunulan veri kümelerine, REST bazlı olarak URL sorgular atılabilmesini sağlayan ve özellikle Silverlight gibi RIA içeriklerinde son derece kıymetli olan bir servis uygulaması olarak değerlendirebileceğimiz bu konu ile ilişkili ilk paylaşımlarımı yaptıktan sonra araya WCF 4.0, WF 4.0, Design Patterns, Design Principles, .Net RIA Services gibi konular girdi.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=e0cf1871-b7e0-460f-9023-d80dbaad10680https://www.buraksenyurt.com/trackback.axd?id=e0cf1871-b7e0-460f-9023-d80dbaad1068https://www.buraksenyurt.com/post/AdoNet-Data-Services-15-Projections#commenthttps://www.buraksenyurt.com/syndication.axd?post=e0cf1871-b7e0-460f-9023-d80dbaad1068https://www.buraksenyurt.com/post/Ado-Net-Data-Services-Ders-Notlarc4b1-7-(Security)-bsenyurt-com-danAdo.Net Data Services Ders Notları - 7 (Security)2009-02-02T12:00:00+00:00bsenyurt<p>Değerli Okurlarım Merhabalar,</p>
<p>Yazılım dünyasının en önemli zorluklarından biriside uygulamanın kapsamına göre güvenliğin etkili bir şekilde nasıl sağlanacağı ile ilişkilidir. Burada hassas bilgilerin korunması, kullanıcıların tanınması ve yetkilendirilmesi, kodun erişim ilkelerinin belirlenmesi, verinin şifrelenmesi gibi pek çok faktör söz konusudur. Genel anlamda günvelik farklı şekillerde göz önüne alınabilir.</p>
<ul>
<li><em>Kimi zaman uygulama içerisinde kullanılan parametrik dış ortam değişkenlerini korumak gerekir. Örneğin uygulamanın kullandığı değişkenlerin konfigurasyon(<strong>app.config, web.config</strong> gibi) dosyasında şifrelenerek saklanması önemlidir ki bu pek çok uygulama standardınında ilkeleri arasında yer almaktadır.</em></li>
<li><em>Kimi zaman uygulamanın içerisindeki kodların ne tür işlemler yapabileceğinin belirlenmesi<strong>(Kode Erişim Güvenliği-Code Access Security) </strong>önemlidir. Örneğin uygulamanın, kurulduğu sistem üzerinde dosya yazma yetkisi olmamasının sağlanması, yada sadece dosya okuma yapmasına izin verilmesi veya uygulama içerisinden ağ ortamına bağlantıya izin verilmemesi gibi.</em></li>
<li><em>Kimi zaman uygulamayı açan kişilerin doğrulanması ve yapabileceklerinin sınırlandırılması gerekir<strong>(Authentication/Authorization)</strong>. Örneğin uygulamayı açma yetkisi olan bir kullanıcının sahip olduğu role göre her menü seçeneğini kullanamaması gibi. </em></li>
</ul>
<p>Vakalar ve gereklilikler çoğaltılabilir. Tek bir makine üzerinde kendi başına çalışan uygulamalar için güvenliğin sağlanması nispeten daha kolaydır. Ancak <strong>istemci/sunucu(Client/Server) </strong>bazlı mimariye geçildiğinde güvenliği sağlamak her zamankinden dahada zor bir hal almaktadır. Bunun en büyük nedenlerinden birisi farklı ortamlar arasında verinin, çeşitli protokollere göre mesajlar üzerinden transfer edilmesi gerekliliği ve bu nedenle iletişiminde güvenli hale getirilmesinin zorluğudur. Öyleki, mesajların şifrelenmesi, iletişim kanalının güvenli hale getirilmesi, aradaki mesajların yakalanması ve değiştirilmesi ihtimaline karşılık gerekli tedbirlerin alınması gibi kıstaslar söz konusudur. Yine durum istemci ve sunucu tarafındaki uygulamaların belirli olmaları halinde biraz daha kolay bir şekilde ele alınabilir. Oysaki sunucu tarafında bir servis uygulamasının bulunması ve buna herhangibir istemcinin bağlanabilecek olması gibi durumlarda ulusal bir takım güvenlik ilkelerine uygun olacak şekilde iletişimi sağlamak ve mesajlaşmak gerekmektedir.</p>
<p>Web servisleri göz önüne alındığında bu tip güvenlik konularını kolay bir şekilde tesis etmek adına <strong>WSE(Web Service Enhancements)</strong> alt yapısından yararlanılmaktadır. <strong>.Net Remoting </strong>tabanlı dağıtık çözümlerde sorumluluk neredeyse geliştiricinin kendisine aittir. Ancak <strong>Windows Communication Foundation </strong>uygulamaları göz önüne alındığında güvenlik, daha kolay ve etkili bir şekilde <strong>iletişim(Transport) </strong>veya <strong>mesaj(Message) </strong>seviyesinde ele alınabilmektedir ki bu kriterler şu anda konumuz dışındadır :)</p>
<p><strong>Ado.Net Data Service</strong>' lerde temel olarak birer <strong>WCF</strong> servisidir. Bununla birlikte söz konusu servisler bilindiği üzere istemcilere <strong>RESTful</strong> modele göre hizmet vermektedir. Yani <strong>HTTP</strong> protokolünün <strong>GET,HEAD,DELETE,PUT </strong>gibi metodlarını ele alıp <strong>ATOM,XML,JSON</strong> gibi standartları kullanmaktadır. Basit bir servis olarak göz önüne alındığında güvenlik konusunda henüz yeteri kadar gelişmiş olmadığı düşünülebilir; mi acaba? İşte bu makalemizde daha çok bu soruya cevap bulmaya çalışacağız.</p>
<p>Her şeyden önce en önemli nokta <strong>Ado.Net Data Service</strong>' lerin veri kaynaklarını istemciye <strong>RESTful</strong> modeline göre sunmasıdır. Bu açıdan bakıldığında geliştiricilerin daha çok üzerinde duracağı nokta verinin erişilebilirliğinin güvenli hale getirilmesidir. Dolayısıyla<strong> Ado.Net Data Service</strong>' leri kullanan istemcilerin, servis tarafında bir şekilde <strong>doğrulanması(Authenticate)</strong> ve sonrasında durumlarına bakılarak <strong>yetkilendirilmeleri(Authorization) </strong>güvenliğin sağlanması adına önemlidir. Oysaki <strong>Ado.Net Data Service</strong> örnekleri, aslında herhangibir uygulama üzerinde host edilebilecek şekilde kullanılabilir. <strong>WCF</strong> kadar geniş bir konsepti yoktur. Bu nedenle kural basittir; doğrulama işlemlerinin sorumluluğu aslında <strong>Ado.Net Data Service</strong> örneğini host eden uygulamaya aittir. Bu anlamda, servisin bir <strong>WCF</strong> projesinde veya bir <strong>ASP.NET</strong> uygulamasında barındırılması doğrulama ve yetkilendirme işlemlerinin daha kolay ele alınabilmesi açısından önemlidir. <strong>Ado.Net Data Service</strong>' lerinde güvenlik 4 farklı alanda değerlendirilmektedir.</p>
<table style="border-collapse: collapse; width: 100%;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="width: 158px; background-color: #ffccaa;" align="center" bgcolor="#336699" width="158"><strong> Kriter</strong></td>
<td style="width: 158px; background-color: #ffccaa;" align="center" bgcolor="#336699" width="103"><strong> Güvenlik Alanı</strong></td>
<td style="width: 158px; background-color: #ffccaa;" align="center" bgcolor="#336699"><strong> Açıklama</strong></td>
</tr>
<tr>
<td width="158">Host uygulamaya ait doğrulama modeli</td>
<td width="103">Authentication<br /> (Doğrulama)</td>
<td>Servisi kullanacak olan istemcilerin doğrulanması için <strong>Host</strong> uygulama ortamı ele alınır. Söz gelimiz <strong>ASP.NET</strong> uygulaması üzerinde yapılan bir hosting işleminde <strong>built-in</strong> <strong>Membership</strong> <strong>API</strong>' si kullanılarak svc dosyalarına olan erişim kısıtlandırılabilir.</td>
</tr>
<tr>
<td width="158">Servis Operasyonları<br /> <strong>(Service Operations)</strong></td>
<td rowspan="3" width="103">Authorization<br /> (Yetkilendirme)</td>
<td>Metod bazlı operasyonlar yazılarak veriye olan erişim kısıtlandırılabilir. Örneğin <strong>HTTP Get </strong>metoduna göre sadece tek bir sonuçun elde edilmesine izin verilmesi<strong>(Single Result) </strong> sağlanabilir.</td>
</tr>
<tr>
<td width="158">Veri Kesmeleri<br /> <strong>(Data Interceptors)</strong></td>
<td>İstemcinin talep ettiği verinin elde edilmesinden<strong>(Read)</strong> veya değiştirilmesinden<strong>(Update,Insert,Delete)</strong> önce işlemin kesilerek kısıtlamaların yapılması sağlanabilir. Örneğin kullanıcının yetkisine göre sadece görebileceği ürün listesinin verilmesi gibi.</td>
</tr>
<tr>
<td width="158">Entity Görünürlüğü<br /> <strong>(Entity Visibility)</strong></td>
<td>Servisin dış ortama sunduğu <strong>Entity</strong> örneklerinin işlenme şekillerinin sınırlandırılmasıdır. Örneğin <strong>Product</strong> isimli <strong>Entity</strong> üzerinde sadece yazma işlemlerine izin verilmesi gibi.</td>
</tr>
</tbody>
</table>
<p>Buradaki kriterlerin uygulanması ile bir <strong>Ado.Net Data Service</strong> ve içeriğine olan erişim yetkilendirilebilir. Aslında teknik detayları, geliştireceğimiz örneğin aralarına serpiştirerek devam edebiliriz. İlk olarak bize bir test servisi gerekmektedir. Konuyu kolay işlemek adına<strong> Ado.Net Entity Framework </strong>öğesini kullanaraktan <strong>Northwind</strong> veritabanındaki tüm tabloları ele aldığımızı düşünelim. Sonrasında ise basit bir <strong>Ado.Net Data Service</strong> öğesi geliştireceğiz. Peki ama bu öğeyi nerede barındıracağız? İşte burada <strong>kullanıcı doğrulamasını(Authentication)</strong> kolayca tesis edebileceğimiz bir <strong>Asp.Net</strong> uygulamasını göz önüne alabiliriz. Elbetteki <strong>Asp.Net Web Site Administration Tool'</strong> unu kullanaraktan bir kaç test kullanıcısı ve rolü oluşturmaktada yarar olacaktır. Örneklerimizde kullanacağımız kullanıcı bilgileri aşağıdaki gibidir.<em>(Örnekte <strong>SQL Express Edition</strong> kullanılmış ve bu nedenle <strong>ASPNETDB.MDF</strong> dosyası web sitesinin olduğu <strong>App_Data</strong> klasörü altında oluşturulmuştur.)</em></p>
<table style="border-collapse: collapse; width: 194px;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="width: 61px; background-color: #ffccaa;" align="center" bgcolor="#336699" width="61"><strong> Kullanıcı</strong></td>
<td style="width: 61px; background-color: #ffccaa;" align="center" bgcolor="#336699" width="54"><strong> Şifre</strong></td>
<td style="width: 61px; background-color: #ffccaa;" align="center" bgcolor="#336699" width="45"><strong> Rol</strong></td>
</tr>
<tr>
<td width="61">dealer1</td>
<td width="54">dealer1.</td>
<td rowspan="3" width="45">Dealer</td>
</tr>
<tr>
<td width="61">dealer2</td>
<td width="54">dealer2.</td>
</tr>
<tr>
<td width="61">dealer3</td>
<td width="54">dealer3.</td>
</tr>
<tr>
<td width="61">region1</td>
<td width="54">region1.</td>
<td rowspan="2" width="45">Region</td>
</tr>
<tr>
<td width="61">region2</td>
<td width="54">region2.</td>
</tr>
</tbody>
</table>
<p>Önemli unsurlardan biriside siteye olan ve amacımız gereği özellikle <strong>Ado.Net Data Service</strong> öğelerine olan erişimi kısıtlamaktır. Yani siteye <strong>isimsiz kullanıcı(Anonymous User)</strong> girişi kesin olarak engellenmelidir. Aşağıdaki ekran görüntüsüde, web.config dosyasında söz konusu engelleme için yapılmış olan değişikliler açık bir şekilde görülebilir.</p>
<p><img src="/makale/images/mk268_1.gif" alt="" width="564" height="157" border="0" /></p>
<p>Dikkat edileceği üzere <strong>Form</strong> tabanlı doğrulama kullanılmaktadır ve isimsiz kullanıcıların sisteme girmeleri yasaklanmıştır.</p>
<blockquote>
<p>Elbetteki Form Tabanlı Doğrulama(Form Based Authentication) şart değildir. Özellikle Intranet sistemlerde Windows tabanlı doğrulama(Windows Based Authentication)' da ele alınabilir. Hatta istenirse Passport Tabanlı Doğrulama da etkinleştirilebilir. Hangisi kullanılırsa kullanılsın, servisi host eden web sisteminin doğrulama yetenekleri, Ado.Net Data Service çalışma zamanı tarafından ele alınabilmektedir.</p>
</blockquote>
<p>Form tabanlı doğrulama söz konusu olduğundan varsayılan olarak aksi belirtilmedikçe<em>(Asp.Net Güvenlik konularını hatırlayalım)</em> <strong>Login.aspx</strong> isimli bir giriş sayfasına ihtiyacımız olacaktır. Bu sayfa üzerinde yine <strong>Asp.Net </strong>bileşenlerinden olan <strong>Login</strong> kontrolü kullanılabilir.</p>
<p><img src="/makale/images/mk268_2.gif" alt="" width="331" height="313" border="0" /></p>
<p>Sonuç olarak servis tarafındaki projenin durumu aşağıdaki şekilde olduğu gibidir.</p>
<p><img src="/makale/images/mk268_3.gif" alt="" width="444" height="219" border="0" /></p>
<p>Dikkat edileceği üzere servisi kullanacak olan kişilerin doğrulanması işlemini host uygulamanın kendisine vermiş bulunmaktayız. Buna göre kullanıcılar herhangibir şekilde servis dosyasını talep ettiklerinde, eğer bir bilete sahip değillerse, otomatik olarak <strong>Login.aspx </strong>sayfasına yönlendirileceklerdir. <strong>ASPNETDB.MDF </strong>veritabanı dosyasında yer alan ve etkin olan bir kullanıcı ile sisteme girildiğnde ise, kodlama tarafında karar vereceğimiz yetkilendirmeler devreye girecektir. Yani veriye olan erişim istenirse kullanıcıya göre kısıtlandırılabilecektir. Şimdi bu durumları analiz etmeye çalışalım. Konuyu son derece basit bir şekilde ele alacağımızdan <strong>NorthwindDataService.cs</strong> kod içeriğini aşağıdaki gibi yazmamız şimdilik yeterli olacaktır.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Data.Services;
using System.Linq;
using System.Linq.Expressions;
using System.ServiceModel.Web;
using System.Web;
using NorthwindModel;
using System.Collections.Generic;
public class NorthwindDataService
: DataService<NorthwindEntities>
{
public static void InitializeService(IDataServiceConfiguration config)
{
// Rol tabanlı veri çekişi işlemleri örnek olarak gösterilebilir; HttpContext.Current.User.IsInRole();
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetEntitySetAccessRule("Employees", EntitySetRights.ReadMultiple);
config.SetEntitySetAccessRule("Orders", EntitySetRights.WriteAppend);
config.SetEntitySetAccessRule("Customers", EntitySetRights.None);
config.SetServiceOperationAccessRule("CustomerCities", ServiceOperationRights.All);
config.SetServiceOperationAccessRule("MySuppliers", ServiceOperationRights.All);
// Eğer alttaki satır açılırsa FilterForProducts metodu devre dışı sayılır ve Products entity' sine hiç bir şekilde erişilemez.
//config.SetEntitySetAccessRule("Products", EntitySetRights.None);
}
[QueryInterceptor("Products")]
public Expression<Func<Products, bool>> FilterForProducts()
{
string name = HttpContext.Current.User.Identity.Name;
if (name == "dealer1")
return p => p.Suppliers.SupplierID == 1 || p.Suppliers.SupplierID == 2;
else if (name == "dealer2")
return p => p.Suppliers.SupplierID == 3;
else if (name == "dealer3")
return p => p.Suppliers.SupplierID == 1 || p.Suppliers.SupplierID == 2 || p.Suppliers.SupplierID == 7;
else
return p => p.Suppliers.SupplierID != null;
}
[ChangeInterceptor("Products")]
public void ProductChange(Products p, UpdateOperations operation)
{
switch (operation)
{
case UpdateOperations.Add:
break;
case UpdateOperations.Change:
if(!HttpContext.Current.User.IsInRole("Region"))
throw new DataServiceException(405,"UnitPrice için bu değişikliğe izin verilmedi");
break;
case UpdateOperations.Delete:
break;
case UpdateOperations.None:
break;
default:
break;
}
}
#region Service Operations
[WebGet]
public IQueryable<string> CustomerCities()
{
return (from c in this.CurrentDataSource.Customers
select c.City).Distinct();
}
// filter, orderby gibi operatörler ve key bazlı erişim gibi sorgulara izin verilmez. Sadece entity bazlı erişim söz konusudur
[WebGet]
public IEnumerable<Suppliers> MySuppliers()
{
return from c in this.CurrentDataSource.Suppliers
orderby c.CompanyName
select c;
}
#endregion
}</pre>
<p>İlk olarak uygulamamızı test etmeye çalıştığımızda <strong>Login.aspx </strong>sayfasına yönlendirildiğimizi göreceğiz. Bir başka deyişle herhangibir isimiz kullanıcı, servise doğrudan erişilmek istendiğinde <strong>http://localhost:1000/SecuritySolutions/login.aspx?ReturnUrl=%2fSecuritySolutions%2fNorthwindDataService.svc</strong> adresine gönderilecektir ve dolayısıyla doğrulama sürecine girilmiş olacaktır. Eğer geçerli bir kullanıcı bilgisi ile giriş yapabilirsek bu durumda standart olarak servise erişebildiğimizi ve izin verilen sorgulamaları yapabildiğimizi göreceğiz. Şimdi kodu biraz analiz etmeye çalışalım. <strong>InitializeService</strong> metodu içerisine bakıldığında <strong>Entity</strong> seviyesinde bazı yetkilendirmeler yapıldığı görülmektedir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetEntitySetAccessRule("Employees", EntitySetRights.ReadMultiple);
config.SetEntitySetAccessRule("Orders", EntitySetRights.WriteAppend);
config.SetEntitySetAccessRule("Customers", EntitySetRights.None);
config.SetServiceOperationAccessRule("CustomerCities", ServiceOperationRights.All);
config.SetServiceOperationAccessRule("MySuppliers", ServiceOperationRights.All);</pre>
<p>Buna göre tüm Entity kümelerine erişim hakkı izni, ilk satırdaki kod ile verilmiştir. Bu yetkilendirme, ilk satırdaki <strong>*</strong> ve <strong>EntitySetRights.All</strong> ile sağlanmaktadır. Ne varki ikinci satırda <strong>Employees</strong> Entity içeriği için, <strong>ReadMultiple</strong> kısıtlaması yapılmıştır. Buna göre <strong>Employees</strong> kümesi üzerinde örneğin anahtar bazlı sorgulara izin verilmeyecektir. Yani <strong>Employees(2)</strong> gibi bir talepte bulunulursa <strong>HTTP 403 Forbidden</strong> hatası alınır. Gerçektende durum <strong>Fiddler</strong> aracı yardımıyla izlendiğinde aşağıdaki ekran görüntüsünde yer alan sonuçlar ile karşılaşılır.</p>
<p><img src="/makale/images/mk268_4.gif" alt="" width="621" height="683" border="0" /></p>
<p>Dikkat edileceği üzere istemci taraına döndürülen <strong>XML</strong> içeriğinde <strong>Forbidden</strong> mesajı yazılmaktadır. Aynı zamanda hata kodu <strong>403</strong>' tür. Bu durum istemci uygulama tarafından değerlendirilmelidir. Şu anda ilk yetkilendirmemizi Entity seviyesinde yapmış bulunuyoruz. Görüldüğü üzere, tüm Entity' lere erişim hakkı verilmiş olmasına rağmen <strong>Employees</strong> üzerinde bir kısıtlama uygulanmıştır. Üçüncü satırdaki kısıtlamaya göre <strong>Orders</strong> kümesi için sadece yeni öğe eklenmesine izin verilmektedir. Bu nedenle <strong>Orders</strong> <strong>entity</strong> içeriği yine tarayıcı üzerinden sorgulanmak istendiğinde <strong>HTTP 403 Forbidden</strong> hatası alınacaktır. Ancak istemci bir uygulama tarafından, Orders isimli veri kümesine yeni öğelerin eklenmesi işlemine izin verilecektir. Son olarak <strong>Customers</strong> <strong>Entity</strong>' sine herhangibir şekilde erişim izni kesinlikle verilmemektedir. Nitekim <strong>EntitySeyRights</strong> enum sabiti için <strong>None</strong> değeri verilmiştir.</p>
<p>Lakin burada <strong>CustomerCities</strong> isimli bir operasyon için izin verildiği gözlemlenmektedir. <strong>CustomerCities</strong> isimli operasyon <strong>string</strong> tabanlı <strong>IQueryable</strong> tipinden bir referans döndürmektedir. İçerideki sorgu cümlesine bakıldığında <strong>Customers</strong> <strong>Entity</strong>' si içerisindeki <strong>City</strong> adlarının <strong>Distinct</strong> fonksiyonu ile benzersiz olacak şekilde döndürüldüğü görülmektedir. Dolayısıyla <strong>Customers</strong> veri kümesini dış ortama tamamen kapatıp, kendisine ait şehir adlarını istemciye sunan bir operasyon tanımlaması söz konusudur ki buda bir güvenlik tedbiri olarak düşünülebilir.</p>
<p>Yine devam eden satırda <strong>MySuppliers</strong> isimli bir servis operasyonuna tüm haklar ile erişim izni verilmiştir. Bu servis operasyonuna bakıldığında ise <strong>IEnumerable<Suppliers></strong> tipinden bir referans döndürdüğü görülmektedir. Operasyon içerisindeki <strong>LINQ</strong> sorgusunda özel bir ifade yoktur. Ancak metodun dönüş tipinin <strong>IEnumerable</strong> olmasının bir anlamı vardır. Buna göre <strong>Suppliers</strong> tablosu sorgulanırken <strong>filter</strong>, <strong>orderby</strong> gibi operatörler kullanılamaz. Ayrıca anahtar bazlı erişimlere de (örneğin <strong>Suppliers(2)</strong> gibi) izin verilmez. Sadece sonuç kümesinin ham hali istemciye sunulur. Buda sonuç itibariyle bir kısıtlamadır. Nitekim çalışma zamaında örneğin, <strong>SupplierId</strong> değeri <strong>3</strong> olan <strong>Supplier</strong> bilgisini almak istediğimizde <strong>HTTP 400 Bad Request</strong> hatasını aldığımız görebiliriz.</p>
<p><img src="/makale/images/mk268_5.gif" alt="" width="634" height="683" border="0" /></p>
<p>Bu <strong>IQueryable</strong> ve <strong>IEnumerable</strong> arasındaki farkı biraz daha net bir şekilde görmüş bulunuyoruz. Yanlız dikkat edilmesi gereken bir nokta vardır. Servis operasyonunun döndürdüğü bir <strong>Entity</strong> içeriği var iken<strong>(örneğin IEnumerable<Suppliers>)</strong> <strong>InitializeService</strong> metodu içerisinde söz konusu tip için <strong>EnititySetRights.None</strong> değerinin kullanılması sonrasında servis çalışmayacaktır. Örneğimizde <strong>Customers</strong> için yapılan kısıtlamaya dikkat edildiğinde <strong>MyCustomers</strong> isimli operasyonun geriye <strong>string</strong> bazlı bir sonuç kümesi döndürdüğü görülmektedir. Bu servisin çalışmasına engel olmamaktadır.</p>
<p>Özellikle <strong>Entity</strong> tipleri ve servis operasyonları için yapılan erişim kısıtlamalarında devreye giren <strong>EntitySetRights</strong> enum sabitinin alabileceği değerler aşağıdaki tabloda belirtildiği gibidir.</p>
<table style="border-collapse: collapse; width: 630px; height: 403px;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="background-color: #ffccaa;" colspan="2" align="center" bgcolor="#336699"><strong> EntitySetRights Değerleri</strong></td>
</tr>
<tr>
<td width="61"><strong>None</strong></td>
<td><strong>Entity</strong> kümesine erişilmesi yasaklanmıştır. <strong>Metadata</strong> içerisinde görünmez ve üzerine okuma yazma işlemleri yapılamaz.</td>
</tr>
<tr>
<td width="61"><strong>ReadSingle</strong></td>
<td><strong>Entity</strong> üzerinde <strong>anahtar bazlı(Key Based)</strong> aramalara izin verilir. (<strong>Customers('ALFKI')</strong> gibi )</td>
</tr>
<tr>
<td width="61"><strong>ReadMultiple</strong></td>
<td><strong>Entity</strong> içeriğinin sorgulanmasına izin verilir, ancak anahtar bazlı erişimlere izin verilmez. Örneğin <strong>Employees(2)</strong> için sonuç kümesi elde edilemez.</td>
</tr>
<tr>
<td width="61"><strong>WriteAppend</strong></td>
<td>Yeni <strong>Entity</strong> örnekleri eklenebilir.</td>
</tr>
<tr>
<td width="61"><strong>WriteMerge</strong></td>
<td>Var olan <strong>Entity</strong> içeriği güncellenirken birleştirilme işlemi uygulanır.</td>
</tr>
<tr>
<td width="61"><strong>WriteReplace</strong></td>
<td>Var olan <strong>Entity</strong> içeriği yenisi ile değiştirilerek güncellenir.</td>
</tr>
<tr>
<td width="61"><strong>WriteDelete</strong></td>
<td><strong>Silme</strong> işlemine izin verilir.</td>
</tr>
<tr>
<td width="61"><strong>AllRead</strong></td>
<td><strong>ReadSingle</strong> ve <strong>ReadMultiple</strong> değerlerinin birleşimidir.</td>
</tr>
<tr>
<td width="61"><strong>AllWrite</strong></td>
<td><strong>WriteInsert</strong>, <strong>WriteUpdate</strong> ve <strong>WriteDelete</strong> değerlerinin birleşimidir.</td>
</tr>
<tr>
<td width="61"><strong>All</strong></td>
<td><strong>Tam</strong> <strong>okuma</strong> ve <strong>yazma</strong> erişimine izin verilir.</td>
</tr>
</tbody>
</table>
<p>Servis operasyonlarında erişim kısıtlamalarını belirleyen <strong>ServiceOperationRights</strong> enum sabitinin alabileceği değerler ise aşağıdaki tabloda görüldüğü gibidir.</p>
<table style="border-collapse: collapse; width: 630px; height: 203px;" border="1" cellspacing="0" cellpadding="5">
<tbody>
<tr>
<td style="background-color: #ffccaa;" colspan="2" align="center" bgcolor="#336699"><strong> ServiceOperationRights Değerleri</strong></td>
</tr>
<tr>
<td width="61"><strong>None</strong></td>
<td>Servis operasyonuna erişim izni yoktur.</td>
</tr>
<tr>
<td width="61"><strong>ReadSingle</strong></td>
<td>Tek bir veri öğesinin okunmasına izin verilir.</td>
</tr>
<tr>
<td width="61"><strong>ReadMultiple</strong></td>
<td>Servis operasyonu kullanılarak birden fazla veri öğesinin okunmasına izin verilir.</td>
</tr>
<tr>
<td width="61"><strong>AllRead</strong></td>
<td>Tekil veya çoğul veri öğelerinin okunmasına izin verilir.</td>
</tr>
<tr>
<td width="61"><strong>All</strong></td>
<td>Servis operasyonu için tüm haklar sağlanır.</td>
</tr>
</tbody>
</table>
<p>Kodumuzda ilerlediğimizde, <strong>FilterForProducts</strong> ve <strong>ProductChange</strong> isimli iki metod ile karşılaşmaktayız. Bu metodlar <strong>veri</strong> <strong>kesme(Data Interceptor)</strong> fonksiyonellikleridir. Dikkat edileceği üzere <strong>FilterByProducts</strong> metodu içerisinde o anki kullanıcının adına bakılaraktan örnek bir içeriğin döndürülmesi sağlanmaktadır. Söz gelimi <strong>dealer2</strong> isimli kullanıcı ile sisteme girildiğinde ve <strong>Products</strong> içeriği talep edildiğinde koddaki kesme metodunun içeriğine göre <strong>SupplierID</strong> değeri <strong>3</strong> olan listenin elde edildiği görülür. Özellikle durumu<strong> SQL Server Profiler </strong>aracı yardımıyla incelendiğinde gerçektende kesme metodunun devreye girdiği aşikardır.</p>
<p><img src="/makale/images/mk268_6.gif" alt="" width="483" height="326" border="0" /></p>
<p>Burada belkide en önemli nokta kesme işlemi sırasında, kullanıcı bilgisinin <strong>HttpContext.Current.User.Identity.Name</strong> ifadesi ile alınmasıdır. Bu tahmin edileceği üzere servisi kullanmak için <strong>Login</strong> olan kullanıcının adıdır.</p>
<p>Örneğimizde senaryoyu çok basit bir şekilde ele almak istediğimizden kullanıcı adlarının <strong>dealer1, dealer2, dealer3</strong> olması halleri ele alınmıştır. Oysaki gerçek hayat senaryolarında daha faydalı bir kesme işlemi yapılabilir. Bununla ilişkili olaraktan sizlere bir alıştırma senaryosu örneği vermek isterim. Söz gelimi, kullanıcının hangi ürünlere bakacağı bilgisi, kullanıcının dahil olduğu bölgeye bağlı olabilir. Bu durumda <strong>ASPNETDB.MDF</strong> veritabanında yer alan kullanıcı bilgisi ile, kullanıcıların dahil olduğu bölgeleri tutan başka bir eşleştirme tablosu bu senaryo için çok yararlı olabilir. Böylece kesme metodu içerisinde, eşleştirme tablosundan yararlanılarak, <strong>giren kullanıcının sadece dahil olduğu bölgeye ait ürünleri görmesi</strong> sağlanabilir. Bunu kendi başınıza denemenizi ve yapmaya çalışmanızı öneririm.</p>
<blockquote>
<p>Örneğimizdeki veri kesme metodları(Data Interceptors) içerisinde servisin host edildiği uygulama ortamının içeriğinin kullanıldığı görülmektedir. Burada Web ortamında olunmasının büyük bir avantaj sağladığı çok açıktır. Nitekim, o anki HTTP içeriğine HttpContext özelliği üzerinden ulaşılabilmektedir. Bu sebepten sisteme giriş yapmış olan kullanıcıyı tespit etmek son derece kolaydır. Ayrıca Ado.Net Data Service' lerin host edildiği web ortamlarında, Application, Session, Caching gibi yapılarında ele alınması mümkün hale gelmektedir.</p>
</blockquote>
<p>Gelelim <strong>ProductChange</strong> metoduna. Bu metod içerisinde giriş yapan kullanıcın <strong>Region</strong> rolünde olması halinde değişiklik yapabilmesine müsade edilmektedir. Eğer giren kullanıcı <strong>Region</strong> rolünde değilse istemci tarafına <strong>HTTP Statu Code 405</strong> mesajı gönderilmektedir. Aslında kesme operasyonlarını daha net bir şekilde ele alabilmek için bir istemci uygulama yazılmasında yarar vardır. Veri değiştirme işlemleri sırasında devreye giren bu metodda önemli olan noktalardan biriside <strong>UpdateOperations</strong> <strong>enum</strong> sabitinin kullanılmasıdır.</p>
<p>Bu sabitin değerine göre kullanıcının nasıl bir operasyon gerçekleştirmek istediği kolayca tespit edilebilir. Metodun ilk parametresi kesme operasyonunun kime uygulanacağını işaret etmektedir. Buna göre söz konusu kesme operasyonları <strong>Products</strong> tipine uygulanabilir. Diğer önemli bir noktada istemci tarafına<strong> HTTP 405</strong> mesajının <strong>DataServiceException</strong> tipinden bir nesne örneği fırlatılarak gönderiliyor olmasıdır. Çok doğal olarak bu istisna tipinin istemci uygulama tarafından ele alınıyor olması gerekmektedir. <em>(Yani istemci tarafında try...catch...finally blokları kullanılarak istisna yönetimi yapılmalıdır.)</em></p>
<p>Söz konusu sistemde istemci uygulamanın, servisten talepte bulunurken belirli bir kullanıcı bilgisini göndermesi de şarttır. Aslında bu noktada <strong>Client Application Services</strong>' lerden faydalanılabilinir.<em>(Bu konu ile ilişkili olarak daha önceki <a href="http://www.bsenyurt.com/makalegoster.aspx?ID=267"> makalemi</a> takip etmenizi öneririm)</em> Özellikle <strong>.Net</strong> tabanlı istemcilerde İstemci Uygulama Servisinin kullanılmasını öneririm. Tabi bunun için servis tarafındaki konfigurasyon dosyasında bir takım değişikliklerin yapılması gerekmektedir. Bu sebeple host uygulamanın <strong>web.config</strong> dosyasında aşağıda yer alan eklemeleri yapabiliriz.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false">...
<appSettings/>
<system.web.extensions>
<scripting>
<webServices>
<authenticationService enabled="true"/>
<roleService enabled="true"/>
</webServices>
</scripting>
</system.web.extensions>
<connectionStrings>
...</pre>
<p>İstemci uygulama tarafında ise <strong>proje özelliklerinden(Properties)</strong>, <strong>Services</strong> kısmına geçmemiz ve geliştirdiğimiz web uygulamasının adresini işaret etmemiz yeterlidir.</p>
<p><img src="/makale/images/mk268_8.gif" alt="" width="485" height="411" border="0" /></p>
<p>Bölyece istemci uygulamanın <strong>doğrulama(authentication)</strong> ve <strong>rol(role)</strong> yönetimi için geliştirilen web uygulamasının üyelik sistemini(<strong>Membership API) </strong>kullanılacağı belirtilmiş olur. Örnek içerisinde <strong>Membership</strong> sınıfını kullanarak doğrulama yapacağımızdan <strong>System.Web.dll</strong> <strong>assembly</strong>' ının servis referansı ile birlikte istemci uygulamaya ekleniyor olmasıda gerekmektedir.</p>
<blockquote>
<p>Servis tarafında isimsiz kullanıcıların(Anonymous Users) sisteme girişini kapattığımız için Add Service Reference kısmından servise ait WSDL içeriğini elde edemediğimizi görürüz. Nitekim Visual Studio ortamında söz konusu servis talep edildiğinde otomatikman web uygulamasının authentication kuralı devreye girmekte ve bizi Login.aspx sayfasına yönlendirmeye çalışmaktadır. Hal böyle oluncada servise ulaşılamamakta ve referansı elde edilememektedir.</p>
<p>Örnekte servisin host edildiği web uygulamasındaki deny user="?" kısmı, istemci uygulamayla aynı solution içerisindeki servis referansı eklendikten sonra etkin hale getirilmiştir. Bu elbetteki istenen çözüm değildir ve ayrıca daha sonrada servisin güncelleştirilmesi sırasında problemlere neden olmaktadır.</p>
<p>Diğer taraftan datasvcutil aracı yardımıylada istemci tarafı için gerekli tipler üretilmek istendiğinde benzer sonuçlar ile karşılaşılacaktır. Aşağıdaki ekran görüntüsünde ilk denemede anonymous kullanıcıların geri çevrildiği senaryo sonrası alınan uyarı mesajı görülmektedir. İkinci deneme yapılmadan önce ise web.config dosyasındaki deny users="?" kısmı allow users="?" olarak değiştirilmiş ve gerekli tiplerin üretildiği görülmüştür. Elbetteki buda istenen bir aktarım şekli değildir.</p>
<p><img src="/makale/images/mk268_10.gif" alt="" width="602" height="243" border="0" /></p>
<p>Bu noktada belkide servisin kullanılabilmesi için, istemci tarafınca gerek duyulan proxy tiplerinin önceden üretilip, kullanacak olan uygulamalara dağıtılması yöntemi tercih edilebilir. Açıkçası bu, servisi kullanacak olan istemcilerin belli olduğu durumlarda düşünülebilecek bir senaryodur ki pek çok büyük çaplı şirket içi projede göz önüne alınabilir.</p>
</blockquote>
<p>Bu arada istemci tarafındaki Console uygulamasının içeriğini aşağıdaki gibi örnekleyebiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Data.Services.Client;
using System.Linq;
using System.Net;
using ClientApplication.NorthwindServiceReference;
using System.Web.ClientServices;
using System.Threading;
using System.Web.Security;
namespace ClientApp
{
class Program
{
static void Main(string[] args)
{
NorthwindEntities entities = new NorthwindEntities(
new Uri("http://buraksenyurt:1000/SecuritySolutions/NorthwindDataService.svc")
);
entities.SendingRequest+=delegate(object sender,SendingRequestEventArgs e)
{
ClientFormsIdentity identity = Thread.CurrentPrincipal.Identity as ClientFormsIdentity;
HttpWebRequest webRequest = e.Request as HttpWebRequest;
if (identity != null)
webRequest.CookieContainer = identity.AuthenticationCookies;
};
try
{
if (Membership.ValidateUser("dealer1", "dealer1."))
{
// QueryInterceptor için istemci kodu
var tumUrunler = from urun in entities.Products
select urun;
foreach (var urun in tumUrunler)
{
Console.WriteLine(urun.ProductName);
}
// Http durum kodları(Http Status Code) için link-> http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
// ChangeInterceptor için istemci kodu
var u = (from urun in entities.Products
where urun.ProductID == 1
select urun).First<Products>();
u.UnitPrice +=1;
entities.UpdateObject(u);
entities.SaveChanges();
}
}
catch (DataServiceRequestException excp)
{
string excpMessage = String.Format("Status Code : {0}\n Inner Exception Message : {1} ",
((DataServiceClientException)excp.InnerException).StatusCode.ToString(), excp.InnerException.Message
);
Console.WriteLine(excpMessage);
}
}
}
}</pre>
<p>Burada belkide en can alıcı nokta doğrulama için istemci tarafından kullanıcı ve şifre bilgilerinin nasıl gönderildiğidir. Dikkat edilecek olursa servise talep gönderilmeden önce ilgili doğrulama bilgileri yollanmaktadır. <strong>Membership</strong> sınıfının <strong>ValidateUser</strong> metodu yardımıyla kullanıcı doğrulandıktan sonra ise <strong>entity</strong> talebinde bulunulmakta ve bir güncelleştirme işlemi gerçekleştirilmektedir. Uygulamayı çalıştırdığımızda aşağıdaki ekran görüntüsü ile karşılaşırız.</p>
<p><img src="/makale/images/mk268_9.gif" alt="" width="588" height="229" border="0" /></p>
<p>Ürün bilgileri alınırken servis tarafındaki <strong>FilterForProducts</strong> isimli veri kesme metodu devreye girmiş ve <strong>delaer1</strong> için <strong>SupplierID</strong> değerleri <strong>1</strong> veya <strong>2</strong> olanlar getirilmiştir. Yine dikkat edilecek olursa <strong>SaveChanges</strong> metodundan sonra servis tarafındaki <strong>ProductChange</strong> kesme metodunun devreye girmesi sonucu istemciye hata mesajı döndürülmüş ve bir <strong>istisna(exception)</strong> oluşmuştur. Bu son derece doğaldır nitekim <strong>dealer1</strong> kullanıcısı <strong>Region</strong> rolünde değildir. Ancak örneğin <strong>region1</strong> kullanıcısı ile giriş yaparsak bu durumda <strong>SupplierID</strong> değeri <strong>null</strong> olmayan ürünleri çekebildiğimizi ve aynı zamanda bunlardan ilkinin <strong>UnitPrice</strong> değerlerinide değiştirebildiğimizi görürüz. Hatta <strong>SQL</strong> <strong>Server</strong> <strong>Profiler</strong> aracı ile arka plandaki sorgu durumunu izlersek aşağıdakine benzer bir sorgunun işletildiğini kolayca izleyebiliriz.</p>
<pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false">exec sp_executesql N'update [dbo].[Products]
set [ProductName] = @0, [QuantityPerUnit] = @1, [UnitPrice] = @2, [UnitsInStock] = @3, [UnitsOnOrder] = @4, [ReorderLevel] = @5, [Discontinued] = @6
where ([ProductID] = @7)
',N'@0 nvarchar(4),@1 nvarchar(18),@2 decimal(19,4),@3 smallint,@4 smallint,@5 smallint,@6 bit,@7 int',@0=N'Chai',@1=N'10 boxes x 20 bags',@2=22.0000,@3=39,@4=0,@5=10,@6=0,@7=1</pre>
<p>Örneklerdende görüldüğü üzere <strong>Ado.Net Data Service</strong>' lerde güvenliği sağlarken <strong>doğrulama(Authentication)</strong> ve <strong>yetkilendirme(Authorization) </strong>adına yapılabilecek belirli işlemler söz konusudur. Bu işlemler için bazı kuralların uygulanması gerekmektedir. Söz gelimi servis operasyonları göz önüne alındığında, yazılacak olan metodlarda dikkat edilmesi gereken kurallar şunlardır.</p>
<ul>
<li><em>Metodun <strong>public</strong> erişim belirleyicisine sahip olması gerekmektedir.</em></li>
<li><em>Metodun dönüş tipi <strong> IQueryable<T></strong> veya <strong>IEnumerable<T> </strong>olabilir. Buradaki <strong>T</strong>, <strong>Entity</strong> tipidir. Eğer operasyonun döndürdüğü sonuç kümesi üzerinde <strong>sıralama</strong>, <strong>sayfalama</strong>, <strong>filtreleme</strong> gibi işlemler yapılacaksa <strong>IQueryable<T></strong> tipinin döndürülmesi gerekir.</em></li>
<li><em><strong>HTTP</strong> <strong>Get</strong> metoduna uygun çağrılar için <strong>WebGet,</strong> <strong>HTTP</strong> <strong>Post,Delete,Put</strong> gibi talepler içinse <strong>WebInvoke</strong> niteliği<strong>(Attribute) </strong> kullanılmalıdır.</em></li>
</ul>
<p>Benzer şekilde okuma işlemleri sırasındaki kesme fonksiyonelliklerininde uygulaması gereken bazı kurallar vardır. Buna göre;</p>
<ul>
<li><em>Metodun <strong>public</strong> erişim belirleyicisine sahip olması gerekir.</em></li>
<li><em>Metoda <strong> [QueryInterceptor("EntityName")]</strong> niteliğinin uygulanması gerekir. <strong>EntityName</strong> yerine kesme işleminin uygulanacağı <strong> Entity</strong> tipinin adı verilir.</em></li>
<li><em>Metodun dönüş tipi <strong> Expression<Func<T,bool>></strong> olmalıdır. <strong>Func</strong> temsilcisinde yer alan <strong>T</strong> <strong>entity</strong> tipidir.</em></li>
<li><em>Metod parametre almaz.</em></li>
<li><em>Metodun işleyişi sırasında bir <strong>istisna(Excpetion) </strong>oluşsa bile istemci talebi tamamlanır ve kendisine hata mesajı uygun <strong>HTTP Statu Code</strong> değeri ile döndürülür.</em></li>
</ul>
<p>Eğer kesme operasyonu veri güncellenmesi,eklenmesi veya silinmesi işlemleri sırasında yapılacaksa, izlenilmesi gereken kurallar aşağıdaki gibidir.</p>
<ul>
<li><em>Metodun <strong>public</strong> erişim belirleyicisi olmalıdır.</em></li>
<li><em>Metoda <strong> [ChangeInterceptor("EntityName")] </strong>niteliği uygulanmalıdır. Buradaki <strong>EntityName</strong>, <strong>entity</strong> tipinin adıdır.</em></li>
<li><em>Metodun dönüş tipi yoktur. Bu nedenle <strong>void</strong> olarak tanımlanır.</em></li>
<li><em>Metodun iki parametresi vardır. İlki <strong>entity</strong> <strong>tipi</strong> ikincisi ise <strong>UpdateOperations</strong> <strong>enum</strong> sabitidir. Bu enum sabiti ile metodu içerisinde <strong> değiştirme, silme, ekleme</strong> operasyonları ele alınabilir.</em></li>
<li><em>Metodun işleyişi sırasında bir <strong>istisna</strong> oluşursa, istemciden gelen talep tamamlanır ve kendisine uygun olan <strong>Http</strong> <strong>Statu</strong> <strong>Code</strong> değerine sahip hata gönderilir. Bu istisna sonrasında sunucu tarafındaki asıl veri kaynağında herhangibir değişiklik kesinlikle olmaz.</em></li>
</ul>
<p>Makalemizi sonlandırmadan önce önemli bir noktayı daha vurgulamakta yarar vardır. Servisin doğrulanması için istemci tarafından gönderilen kullanıcı adı ve şifre bilgileri açık metinler olarak gitmektedir. Eğer örnekler test edilirken <strong>Fiddler</strong> aracı yardımıyla arka plandaki paketler izlenirse aşağıdaki ekran görüntüsünde yer alan durum ile karışalışır.</p>
<p><img src="/makale/images/mk268_11.gif" alt="" width="645" height="683" border="0" /></p>
<p>Bu nedenle en uygun çözüm servisin <strong> HTTPS</strong> tabanlı bir iletişim üzerinden hizmet vermesinin sağlanması olarak düşünülebilir.</p>
<p>Bu yazımızda <strong>Ado.Net Data Service</strong>' lerde <strong>doğrulama</strong> ve <strong>yetkilendirme</strong> işlemlerinin nasıl ele alınabileceğini, bir başka deyişle güvenliğin nasıl sağlanabileceğini en temel hatlarıyla incelmeye çalıştık. <strong>Ado.Net Data Service</strong> konusunda geliştirmeler devam etmektedir. Güvenlik ile ilişkili olaraktan farklı yaklaşımların getirilmeside bu nedenle söz konusu olabilir. Ancak en azından, host uygulamanın bu işte önemli bir rol üstlendiği gözden kaçırılmamalıdır. Bu yazıda kullanılan tekniğe göre, doğrulama işlemini <strong>Asp.Net Web</strong> uygulaması devralmıştır. Size tavsiyem bunu bir <strong>WCF</strong> sitesinden host ederkende gerçekleştiriyor olmanızdır. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.</p>
<p><a href="https://www.buraksenyurt.com/makale/images/guvenlik.rar">Örneği İndirmek İçin Tıklayın</a> <em>(Boyutun küçük olması için ASPNETDB.MDF ve log dosyası çıkartılmıştır. Bu nedenle örneği deneyebilmek için söz konusu veritabanını Asp.Net Web Site Administration Tool ile oluşturmanız gerekmektedir.)</em></p>2009-02-02T12:00:00+00:00ado.net data serviceswcf data serviceswcfwindows communication foundationbsenyurtYazılım dünyasının en önemli zorluklarından biriside uygulamanın kapsamına göre güvenliğin etkili bir şekilde nasıl sağlanacağı ile ilişkilidir. Burada hassas bilgilerin korunması, kullanıcıların tanınması ve yetkilendirilmesi, kodun erişim ilkelerinin belirlenmesi, verinin şifrelenmesi gibi pek çok faktör söz konusudur. Genel anlamda günvelik farklı şekillerde göz önüne alınabilir.https://www.buraksenyurt.com/pingback.axdhttps://www.buraksenyurt.com/post.aspx?id=271768d8-b467-4779-bcd4-dfb6244854fc0https://www.buraksenyurt.com/trackback.axd?id=271768d8-b467-4779-bcd4-dfb6244854fchttps://www.buraksenyurt.com/post/Ado-Net-Data-Services-Ders-Notlarc4b1-7-(Security)-bsenyurt-com-dan#commenthttps://www.buraksenyurt.com/syndication.axd?post=271768d8-b467-4779-bcd4-dfb6244854fc