https://buraksenyurt.com/Burak Selim Şenyurt - Asp.Net Web API2023-12-14T07:39:40+00:00Matematik Mühendisi Bir Bilgisayar Programcısının NotlarıBurak Selim SenyurtBlogEngine.Net Syndication Generatorhttps://buraksenyurt.com/opml.axdBurak Selim SenyurtMatematik Mühendisi Bir Bilgisayar Programcısının Notlarıtr-TRBurak Selim Şenyurt0.0000000.000000https://buraksenyurt.com/post/effective-engine-bir-uzay-macerasiEffective Engine — Bir Uzay Macerası2021-05-14T18:22:00+00:00bsenyurt<p><img src="https://buraksenyurt.com/image.axd?picture=/2021/Mayis/asset_cover.png" alt="" align="right" />Altunizade’nin bahar aylarında insanı epey dinlendiren yeşil yapraklı ağaçları ile çevrelenmiş cadde<span id="rmm">s</span>inin hemen sonunda, köprüye bağlanmadan iki yüz metre kadar öncesinde dört katlı bir bina vardır. Araba ile geçerken eğer kırmızı ışığa denk gelmediyseniz pek fark edilmez ama yürürken önündeki geniş kaldırımları ile dikkat çeker. Ana cadde tarafındaki yeşillikler binanın ilk katlarını gizemli bir şekilde saklar. Binanın bulunduğu adanın etrafı cadde ve ara sokaklarla çevrilidir. Bir sokağın karşısında yeni yapılmış hastane ile metro çıkışı, ana cadde tarafında ev yapımı tatlılarının kokusu ile insanı baştan çıkaran pastane, eczane, kuaför, camii ve minik bir park bulunur. Dört yola bakan diğer cadde tarafında ise eskiden karakol olan ama çok uzun zamandır kreş olarak işletilen bir komşu yer alır.</p>
<p>Bu binanın bende özel bir yeri vardır. Bir zamanlar Netron adıyla da bilinen yazılım eğitim kurumlarının genel merkeziydi. Şirket eğitimlerindeki başarısı ve iyi eğitmenleri ile adından söz ettirmiş bir kurumdu. 2005 yılının sonlarına doğru adım attığım eğitmenlik kariyeriminin başlangıç noktasıydı. Geniş, yemyeşil bir bahçesi ve <em class="ik">Proxy</em> isimli bir köpeği vardı. Her sabah o güzel bahçeye bakan camekanlı kafesinde taze simit ve poğaçalar olur, ücretsiz dağıtılırdı. İsteyen istediği kadar alabilirdi. Camekanlı kafeye giriş kapsının önündeki basamaklardan indiğinizde solunuzda kalan birde bilgisayar vardı. Şöyle CRT tüplü monitörü olan bir bilgisayar. Otururak kullanabildiğiniz değil de bir bar sandalyesi üstünde hafif rahatsız biçimde kullanabildiğiniz yavaş bir bilgisayar. Uzunca bir süre sınıflardaki birkaç eğitmen bilgisayarı dışında o binada internete bağlanabilinen tek bilgisayar O olmuştu. Şifresiz ve herkesin kullanımına açıktı. O zamanlar öğrencilerin ders sırasında veya arasında sınıftaki bilgisayarları kullanarak internete çıkmalarını pek istemezdik. Nitekim derste öğretilenler ile bir şeyleri çözebileceklerini umut eder, onları buna yönlendirmeye çalışırdık — <em class="ik">Lakin bir defasında Ogame filosuna saldırdıkları için oldukça endişeli görünen bir öğrencim sebebiyle ders arasını birkaç dakika erken vermiştim.</em></p>
<p>O yıllarda eğitimler genellikle Microsoft’un sertifika sınavlarına göre şekillendirdiği müfredata uygun olarak verilirdi. Microsoft’un eğitimciler için hazırladığı eğitim dokümanları epey kallavi olurdu ve ön hazırlıkları bile zaman alırdı. Bazen bir ders saati için tüm haftasonumu heba ettiğim olurdu. özellikle kurumsal bir eğitim söz konusu ise şirketlerin deneyimli personelinden gelen acımasız soruları cevaplayabilmek adına ekstra efor sarf etmek gerekiyordu. Bu durum zamanla deneyim kazanan eğitmenler için sorun olmasa da yeni başlayan bir eğitmen ve onun ilk öğrencileri için bazı hazin sonuçlara sebebiyet verebilirdi. Derken sektörün ihtiyaçlarına bakıp kendi içeriklerimizi planlamaya başladık. Hatta çoğu zaman kendi eğitim materyallerimizi hazırladık. Keyifli ama bir o kadar da külfetli bir işti. Nitekim sahip olunan insan gücü düşünüldüğünde sürekli değişen yeniliklere adapte olmak ve materyalleri güncellemek başlı başına zor oluyordu. Sanki sadece yenilikleri takip edip bu materyalleri hazırlayacak ayrı bir ekip gerekliydi. Gerçi bu endişeler çok geride kaldı.</p>
<p>Profesyonel anlamda eğitmenliği bırakalı oldukça uzun bir zaman oldu. En azından on yıldan fazladır bir eğitim kurumunda eğitmen olarak görev almıyorum. Sadece son dönemlerde çalıştığım firmalar iç eğitmenlik programları kapsamında benden destek istediler. Elimden geldiğince yardımcı olmaya çalıştım/çalışıyorum. İşte 2021'in şeker bayramında eve kapandığımız vakitlerde böyle sıfırdan uzun soluklu bir eğitim vermem gerekse nasıl hazırlanırdım diye düşünmeye başladım.</p>
<p>öyle ya, artık eğitimlerin veriliş şekilleri ve eğitmenlerden beklentiler çok değişti. Artık sınıf eğitimlerinden ziyade çevrimiçi ulaştığımız ve önceden hazırlanıp kaydedilmiş eğitimler daha popüler görünüyor. Ekran görüntüsü kaydetmenin, akış olarak internet ortamına yüklemenin çok daha kolay olduğu bu zaman diliminde hafif hazırlıklar ile bir eğitimi sunmak daha kolay görünüyor — <em class="ik">sizi sıkıştıran anlık soruların olmadığı bir ortam olması sebebiyle bireysel anlamda yeni nesil eğitmenleri ne kadar zorluyor bu da tarışılır tabii.</em> Bununla birlikte uzun metrajlı içerikler algı ve odaklanma sürelerimize göre yerini mikro anlatımlara bırakıyor. Fiziki sınıf eğitimlerinde karşımızdakilerle beden dili kullanarak kurduğumuz sıcak ilişkiyi çevrimiçi ortamda sağlamak zor olduğundan, Icebreaker denen oyunlaştırılmış araçlar kullanılıyor — <em class="ik">ki bana göre hiçbir şey gerçek anlamda görsel ve işitsel temas ile kurulan bağın ötesine geçemez</em>.</p>
<p>Ancak her ne olursa olsun yazılım alanındaki bir eğitimin bazı temel prensipleri ve araçları değişmemelidir. Bu anlamda inandığım bazı ilkeler var.</p>
<ul class="">
<li>Halen daha eğitmenin konuya olan hakimiyeti çevrimdışı bir eğitim bile olsa çok önemli. Bu hakimiyet öğrenciye sorulacak sorular için de zemin hazırlıyor. Yeri geldiğinde karşıda video kaydedici bile olsa düşündürücü bir soru yöneltip es vermek gerekiyor.</li>
<li>Kendi eğitmenlik zamanlarımda da önem arz eden bir diğer konu ise saha tecrübesi. Teorik bilgi birikimi ne kadar yüksek olursa olsun sahada karşılaşılan problemlerin verdiği tecrübe aktarımı bir başka oluyor. Nitekim yazılım eğitimlerinin en zor kısımlarından birisi soyutlaşan kavramların gerçek hayatla örtüştüğü noktaları karşı tarafa aktarabilmek — <em class="ik">ki benim üstadlarım bana “gerekirse konuyu çöp adam kullanarak tahtaya çizip anlatmaya çalış” demişti. Tahtanın yerini şimdi Whiteboard ve dokunmatik ekranlar aldı belki ama prensip aynı; Basitleştirerek anlatmak.</em></li>
<li>Bir eğitim her şeyi eğitmenin yaptığı değil aksine öğrencinin de bir şeyler yaptığı şekilde olmalı. çünkü araştırma ve sorgulayarak cevap bulma kasları bilişim sektörü personeli için çok önemli. Bu nedenle eğitmenin bilhassa açık bıraktığı bazı noktaları keşfetmesi için öğrencilerine ödevler vermesi gerekiyor.</li>
<li>Görev addetmek mühim bir mesele olsa da onu takip etmek ve karşılıklı müzakere yollarını keşfederek önerilerde bulunmak çok daha önemli. öneride bulunup öğrencinin yerine yapmaya çalışmak ise iyi bir pratik değil.</li>
<li>Senaryolaştırmak, bazen konunun ne olduğuna bağlı olarak öncesinde bitmiş eseri gösterip sonra adım adım ilerletmek de kıymetli bir yaklaşım. Bazen yalın bir Hello World hiçbir şey ifade etmez ama senaryosu olan bir Hello World çok şey ifade edebilir.</li>
<li>Eğitime konu olan örneklerin bütünlüğü de kritik bir mesele. Pek çok uygulamalı kitabın ilk noktasından son noktasına gelindiğinde, anlatılan her şeyin kullanıldığı bir ürün ortaya çıkmış oluyor. Bu pratiği eğitimin kendisine yaymak kolay değil ve daha da önemlisi oldukça titiz bir hazırlık süreci gerektiriyor.</li>
</ul>
<p>Düşünceme göre bir eğitime hazırlanmak gerçekten de kolay değil. Büyük sorumluluk, büyük mesuliyet, iyi hazırlık, iyi meziyet gerektirmekte. Bu vesile ile bende bir deneme yapmak istedim. öncesinde geçmiş yıllarımdan bir tecrübe birkaç anı kırıntısı bulmaya çalıştım. Havaların erken karardığı ve eğitimin akşam 19:00da başladığı bir Netron gününde anlattığım Xml Web Service konusu geldi aklıma. Ne ilginçtir ki o zamandan beri Microsoft’un birçok materyalindeki konsept değişmedi. O bir saatlik eğitime hazırlandığım resmi Microsoft eğitim dökümanının ilgili bölümünde bir hava durumu servisinin geliştirilmesi öğretilmekteydi.</p>
<p>önce bitmiş Web servis çalıştırılıp elde edilmesi beklenen sonuç gösteriliyor, sonrasında Request nesnesine ait XML’in hazırlanması öğretiliyor ve nihayetinde bu işin geliştirme ortamında nasıl yapılacağına değiniliyordu. Felsefesi gayet doğruydu. Bir Web Servis temel olarak ne işe yarar baştan görebiliyordunuz. Eğitim seviyesi sebebiyle iç dinamiklerinde XML ne anlama geliyor, bir SOAP talebi hangi parçalardan oluşuyor öğreniyordunuz. Ne elde edeceğim, nasıl çalışıyor terapisinden sonra pratiğe geçiliyor ve uygulama geliştiriliyordu. Bugünkü .Net 5 dünyasına — <em class="ik">veya .Net Core tarafına </em>baktığımızda da bir Web API servisi söz konusu ise benzer bir senaryo koşulduğunu biliyor olmalısınız. Artık bilinç altımıza işlemiş olan, her şablonda karşımıza çıkan WeatherForecast senaryosu.</p>
<p>Pek çok saygın kitapta veya çevrimiçi eğitimde olduğu gibi uzun vadedeki çözümler veri odaklı bir dünya üzerine inşa ediliyor. Ulaşılmak istenen nokta ister Blazor ister MVC olsun, ister Progressive Web App ister Mobil çözüm veya bir başkası olsun o büyük kitabın veya sekiz saatlik eğitimin başlarında bir yerlerde bir REST veya gRPC servisi söz konusu oluyor. üstelik bu servis pratik olması açısından genellikle In-Memory tabanlı, Docker ile kurgulanmış veya local formasyonda çalışan bir veritabanı kullanıyor. İşte eğitimcinin yaratıcılığı bu noktada başlıyor. Renkli bir senaryo kurgulamak, hikayenin bazı noktalarında öğrencinin isteklerini kabul ederek yeni şeyleri sürece katmak<em class="ik">(Farklı entity nesneleri veya fonksiyonellikler gibi)</em>, In-Memory veritabanı ile başlatıp Docker ile diğer türlere geçişlerin yapılacağı ödevler vermek vs</p>
<p>Peki ya bunu nasıl yapacak? İşte bir eğitmenin bence sahip olması gereken en önemli özelliklerden birisi. Yazarak adım adım planlamak. Ben bu tip bir işe kalkışsam sanırım en büyük yardımcılarım Markdown formatındaki bir Readme dosyası ile <a class="bv it" href="https://github.com/buraksenyurt/effective-engine" rel="noopener nofollow">kodları planlayarak tutabileceğim github benzeri bir kaynak deposu</a> olurdu. Hatta o depoyu da belki branch’ler ile kurgulayarak önce şöyle, şimdi böyle, sonra da öyle gibi ifade etmek gerekirdi. <a class="bv it" href="https://github.com/buraksenyurt/hands-on-aspnetcore-di" rel="noopener nofollow">örneğin…</a></p>
<p>Sonuç olarak aşağıdaki gibi amatör bir kurgu oluşturdum. Umarım şirket içi eğitmenlere yol gösterici olur.</p>
<h2>Senaryo</h2>
<p>Haftasonu sıkılan .Net geliştiricisi için eğlencelik bir Web API kodlaması düşündüm. Şirket içi eğitimlerde bir Web API’ye ihtiyaç duyduğumuz durumlar için güzel olabilir. Hani kobay bir Web API servisi olur ya hep, görsellik katılınca sükseli duran. İşte onun için güzel bir senaryo olabileceğini düşünüyorum. Senaryoyu aşağıdaki gibi çizmeye çalıştım.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2021/Mayis/assets_01.png" alt="" /></p>
<p>Gelecekte geçen bir zaman diliminde galaksinin uzak diyarlarını keşfetmek üzere Uzay Yolu’nu izlemiş mürettabattan oluşan gemiler vardır. Güneşin ve ayın konumuzla bir alakası yok ama kompozisyonu tamamlarlar diye düşünüp resme dahil ettim. Bir uzay gemisi<em class="ik">(Spaceship)</em> içinde en az 2 en fazla 7 mürettebat<em class="ik">(Voyager) </em>olabilir. Mürettebat görev kontrolün<em class="ik">(MissionControl)</em> uygun gördüğü gemiyle bir göreve<em class="ik">(Mission)</em> çıkar. Her görev tek bir gemiyle ilişkilendirilir ama itirazınız varsa bunu çoklayabiliriz de. Görevin başlatılması için bir adının olması, kendilerine has takma isimleri olan mürettebatın bulunması, görev süresi verilmesi<em class="ik">(En az 12 en fazla 24 ay)</em>, bir gemiyle görevin ilişkilendirilmesi yeterlidir. Senaryoyu birlikte genişletebiliriz ama varsayılan hali aşağıdaki gibidir.— <em class="ik">Burası öğrencilere senaryonun anlatıldığı kısım. Eğlenceli olmalı, ilgi çekmeli, hatta eğitmen bunu canlı olarak çizerek anlatmalıdır.</em></p>
<h2>0 — Başlangıç</h2>
<p><em class="ik">Solution ve projenin ilk aşamasıdır. Giriş kısmı olduğu için sarf edilen sözler önemlidir. Neden bir Class Library açarak başladık ve ona neden EntityFrameworkCore diye bir paketi ekledik anlatmamız gerekir.</em></p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"># Bir Solution oluşturdum
dotnet new sln -o GalaxyExplorer
# Sonra Voyager, Spaceship ve Mission olarak adlandırdığım nesneler için Entity ile DbContext'in duracağı bir class library oluşturup solution'a ekledim.
cd GalaxyExplorer
dotnet new classlib -o GalaxyExplorer.Entity
dotnet sln add .\GalaxyExplorer.Entity\GalaxyExplorer.Entity.csproj
# EntityFrameworkCore kullanacağım için birde gerekli paketi ekledim
cd GalaxyExplorer.Entity
dotnet add package Microsoft.EntityFrameworkCore -v 5.0.6</pre>
<h2>1 — Entity Sınıflarının İnşası</h2>
<p>Uzay gemilerini Spaceship sınıfı ile işaret edeceğiz. Adı ve ışık yılı olarak gidebileceği mesafeyi taşıması yeterli.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">namespace GalaxyExplorer.Entity
{
public class Spaceship
{
public int SpaceshipId { get; set; }
public string Name { get; set; }
public double Range { get; set; }
public bool OnMission { get; set; }
public int MaxCrewCount { get; set; }
}
}</pre>
<p>Mürettebatı ise Voyager olarak tanımlayabiliriz. Şimdilik aşağıdaki gibi kullanacağız. Kaşifin adı, rütbesi, ilk görev tarihi, aktif olup olmadığı bilgileri olsun yeterli.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
namespace GalaxyExplorer.Entity
{
public class Voyager
{
public int VoyagerId { get; set; }
public string Name { get; set; }
public string Grade { get; set; }
public DateTime FirstMissionDate { get; set; }
public int MissionId { get; set; }
public bool OnMission { get; set; }
}
}</pre>
<p>Bir görev söz konusu. Bunu Mission sınıfı ile temsil edebiliriz. Bir görev bir gemiyle ilişkili olmalıdır diye ifade etmiştik. Ayrıca bir göreve birden fazla mürettebat da dahil olabilmelidir. Bu düşünceleri resmeden bir sınıfı aşağıdaki gibi yazabiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Collections.Generic;
namespace GalaxyExplorer.Entity
{
public class Mission
{
public int MissionId { get; set; }
public int SpaceshipId { get; set; }
public string Name { get; set; }
public int PlannedDuration { get; set; }
public DateTime StartDate { get; set; }
public IEnumerable<Voyager> Voyagers { get; set; }
}
}</pre>
<p><em class="ik">“Neden bu entity sınıflarını inşa ediyoruz?” diye sormalı karşılıklı görüş almalıyız.</em></p>
<p><strong class="ho cu">2 — DbContext Sınıfının Yazılması</strong></p>
<p>Senaryomuzda hangi veritabanını kullanacağımıza henüz karar vermedik lakin Entity Framework Core’dan yararlanmaktayız. Code First modeli ile ilerliyoruz ama Model First ve Database First şeklinde farklı versiyonlar olduğunu da hatırlayalım. Şu anda Domain’e ait tipleri tasarlayıp sonrasında veritabanına geçeceğiz. İlerleyen derslerde isteyen istediği veritabanı ile çalışabilir olacak<em class="ik">(Uygun olan veritabanı tabii)</em> Bu amaçla GalaxyExplorerDbContext sınıfını aşağıdaki gibi yazarak devam edelim. İçinde kullanıma hazır uzay gemileri de var —<em class="ik"> Burada öğrencilerden de uzay gemisi adları alabiliriz. Hayal güçlerini kullanmaları her zaman etkileşimi yükseltir</em></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using Microsoft.EntityFrameworkCore;
namespace GalaxyExplorer.Entity
{
public class GalaxyExplorerDbContext
: DbContext
{
public GalaxyExplorerDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet<Spaceship> Spaceships { get; set; }
public DbSet<Voyager> Voyagers { get; set; }
public DbSet<Mission> Missions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Mission>().HasMany(m => m.Voyagers).WithOne();
modelBuilder.Entity<Spaceship>().HasData(
new Spaceship
{
SpaceshipId=1,
Name = "Saturn IV Rocket",
OnMission = false,
Range = 1.2,
MaxCrewCount=2
},
new Spaceship
{
SpaceshipId = 2,
Name = "Pathfinder",
OnMission = true,
Range = 2.6,
MaxCrewCount = 5
},
new Spaceship
{
SpaceshipId = 3,
Name = "Event Horizon",
OnMission = false,
Range = 9.9,
MaxCrewCount = 3
},
new Spaceship
{
SpaceshipId = 4,
Name = "Captain Marvel",
OnMission = false,
Range = 3.14,
MaxCrewCount = 7
},
new Spaceship
{
SpaceshipId = 5,
Name = "Lucky Tortiinn",
OnMission = false,
Range = 7.7,
MaxCrewCount = 7
},
new Spaceship
{
SpaceshipId = 6,
Name = "Battle Master",
OnMission = false,
Range = 10,
MaxCrewCount = 5
},
new Spaceship
{
SpaceshipId = 7,
Name = "Zerash Guidah",
OnMission = true,
Range = 3.35,
MaxCrewCount = 3
},
new Spaceship
{
SpaceshipId = 8,
Name = "Ayran Hayd",
OnMission = false,
Range = 5.1,
MaxCrewCount = 4
},
new Spaceship
{
SpaceshipId = 9,
Name = "Nebukadnezar",
OnMission = false,
Range = 9,
MaxCrewCount = 7
},
new Spaceship
{
SpaceshipId = 10,
Name = "Sifiyus Alpha Siera",
OnMission = false,
Range = 7.7,
MaxCrewCount = 7
}
);
}
}
}</pre>
<p><strong class="ho cu">3 — DTO Tipleri için Bir Kütüphane Oluşturulması</strong></p>
<p>Görev kontrol tarafına ilk etapta sadece bir başlatma emri gelsin istiyoruz. Görevin adı, katılacak mürettebatın isimleri gibi az sayıda bilgi yeterli olabilir. Entity türlerini doğrudan API üzerinden açmak yerine bir ViewModel vasıtasıyla sadece aksiyona özgü değişkenlerle sunmak niyetindeyiz. O yüzden Data Transfer Object olarak düşünülebilecek sınıfları kullanacağız— “<em class="ik">DTO’lar yazılım dünyasının hangi noktasında karşımıza çıkarlar? Bu senaryoda ki kullanım amaçları dışında bir rolleri olabilir mi?” şeklinde sorular sorup müzakere etmek gerekiyor.</em></p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"># DTO Projesini açtım
dotnet new classlib -o GalaxyExplorer.DTO
# ve Solution'a ekledim
dotnet sln add .\GalaxyExplorer.DTO\GalaxyExplorer.DTO.csproj</pre>
<p>Sonrasında yeni bir görev başlatmak için kullanacağımız aşağıdaki DTO sınıflarını ekleyerek devam edelim.</p>
<p>Göreve katılacak mürettebat için VoyageRequest sınıfı.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.ComponentModel.DataAnnotations;
namespace GalaxyExplorer.DTO
{
public class VoyagerRequest
{
[Required]
[MinLength(3)]
[MaxLength(25)]
public string Name { get; set; }
[Required]
public string Grade { get; set; }
}
}</pre>
<p>Görevin kendisi içinse MissionStartRequest sınıfı. En az iki en fazla yedi mürettebat katılabilen görevlerden bahsetmiştik. Gemi ataması ise havuzdaki müsait olanlardan yapılmalı. Bu yüzden görev gemisi ile ilgili bir bilgi eklemedik. Bu noktada da fark edeceğiniz üzere bir görevi başlatmak için ihtiyaç duyulan veri modeli ile Entity tam olarak örtüşmüyor. İşte Data Transfer Object için bir başka bahane. — <em class="ik">Bu noktada gerçekten doğru bir şeyler söylüyor muyum diye sorgulatmak lazım. öğrencilerle tartışılması gereken bir konu daha.</em></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace GalaxyExplorer.DTO
{
public class MissionStartRequest
{
[Required]
[MinLength(10)]
[MaxLength(50)]
public string Name { get; set; }
[Required]
[Range(12,24)] // En az 12 en fazla 24 aylık görev olabilir
public int PlannedDuration { get; set; }
[Required]
[MinLength(2)]
[MaxLength(7)] //Minimum 2 maksimum 7 mürettebat olsun diye
public List<VoyagerRequest> Voyagers { get; set; }
}
}</pre>
<p><em class="ik">Görevi başlatma sırasında oluşacak hatalar ile ilgili ayrı bir dönüş tipi kullanmak yararlı olabilir. Bunu sağlamak için MissionStartResponse sınıfını ekleyebiliriz. — <em class="ik">Bu noktada “Servis portlarının girdi ve çıktı mesajlarında bir standart kullnamak gerekir mi?” sorusunu sorup tartışabiliriz.</em></em></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">namespace GalaxyExplorer.DTO
{
public class MissionStartResponse
{
public bool Success { get; set; }
public string Message { get; set; }
}
}</pre>
<p><em class="ik">Başka ne tür validasyon nitelikleri kullanılabilir, araştırmalarını söyleyebiliriz. Tabi söylemek yetmez takibini de yapmamız gerekir. Bir sonraki ders kısa bir tekrar sonrası sorulan sorular üstünde tartışmak verimli bir öğrenim süreci sağlar.</em></p>
<p><strong class="ho cu">4 — Servis Bileşenleri için Kütüphane Eklenmesi</strong></p>
<p>Web API haricinde buradaki kurguyu farklı bir ortamda da kullanmak isteyebiliriz. Controller tipinin kullanacağı Entity Framework işlerini başka bir kütüphanede toplayacak şekilde proje bazında soyutlasak güzel olabilir. Hatta servisleştirirsek çok daha iyi olur. Böylece Dependency Injection çatısını kullanarak asıl ürüne eklememiz de kolay olur. önce bir kütüphane oluşturalım ve gerekli projeleri referans edelim— <em class="ik">Dependency Injection. Hassas, çok hassas bir konu. Burada gerekirse uzun süreli es verip karşılıklı konuşmak, </em><a class="bv it" href="https://medium.com/dogustech/asp-net-corea-nas%C4%B1l-merhaba-deriz-4469c0e06ff5" rel="noopener"><em class="ik">önceki derslerde anlatılan kısımlara refernas</em></a><em class="ik"> ederek yönlendirmek gerekebilir.</em></p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"># Projeyi oluştur
dotnet new classlib -o GalaxyExplorer.Service
# Solution'a ekle
dotnet sln add .\GalaxyExplorer.Service\GalaxyExplorer.Service.csproj
# Proje içine gir
cd .\GalaxyExplorer.Service
# DTO projesini referans et
dotnet add reference ..\GalaxyExplorer.DTO\GalaxyExplorer.DTO.csproj
# DbContext'e ihtiyacım olacak.
dotnet add reference ..\GalaxyExplorer.Entity\GalaxyExplorer.Entity.csproj</pre>
<p>önce soyutlamayı sağlayacak arayüz tipini ekleyelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using GalaxyExplorer.DTO;
using System.Threading.Tasks;
namespace GalaxyExplorer.Service
{
public interface IMissionService
{
Task<MissionStartResponse> StartMissionAsync(MissionStartRequest request);
}
}</pre>
<p>Sonra asıl işi yapan sınıfı(Concrete Class) yazalım.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using GalaxyExplorer.DTO;
using GalaxyExplorer.Entity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace GalaxyExplorer.Service
{
public class MissionService
: IMissionService
{
private readonly GalaxyExplorerDbContext _dbContext;
// Servisi kullanan uygulamanın DI Container Service Registery'si üzerinden gelecektir.
// O anki opsiyonları ile birlikte gelir. SQL olur, Postgresql olur, Mongo olur bilemiyorum.
// Entity modelin uygun düşen bir DbContext gelecektir.
public MissionService(GalaxyExplorerDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<MissionStartResponse> StartMissionAsync(MissionStartRequest request)
{
using var transaction = await _dbContext.Database.BeginTransactionAsync(); // Transaction başlatalım
try
{
// Mürettebat sayısı uygun olup aktif görevde olmayan bir gemi bulmalıyız. Aday havuzunu çekelim.
var crewCount = request.Voyagers.Count;
var candidates = _dbContext.Spaceships.Where(s => s.MaxCrewCount >= crewCount && s.OnMission == false).ToList();
if (candidates.Count > 0)
{
Random rnd = new();
var candidateId = rnd.Next(0, candidates.Count);
var ship = candidates[candidateId]; // Index değerine göre rastgele bir tanesini alalım
ship.OnMission = true;
await _dbContext.SaveChangesAsync(); // Gemiyi görevde durumuna alalım
// Görev nesnesini oluşturalım
Mission mission = new Mission
{
Name = request.Name,
PlannedDuration = request.PlannedDuration,
SpaceshipId = ship.SpaceshipId, // Gemi ile ilişkilendirdik
StartDate = DateTime.Now
};
await _dbContext.Missions.AddAsync(mission);
await _dbContext.SaveChangesAsync(); // Görev nesnesini db'ye yollayalım
// Gelen gezginlerin listesini dolaşıp
var voyagers = new List<Voyager>();
foreach (var v in request.Voyagers)
{
Voyager voyager = new Voyager // Her biri için bir Voyager nesnesi örnekleyelim
{
Name = v.Name,
Grade = v.Grade,
OnMission = true,
MissionId = mission.MissionId // Görevle ilişkilendirdik
};
voyagers.Add(voyager);
}
await _dbContext.Voyagers.AddRangeAsync(voyagers); // Bunları topluca Voyagers listesine ekleyelim
await _dbContext.SaveChangesAsync(); // Değişiklikleri kaydedelim.
await transaction.CommitAsync(); // Transaction'ı commit edelim
return new MissionStartResponse
{
Success = true,
Message = "Görev başlatıldı."
};
}
else // Müsait veya uygun gemi yoksa burda durmamızın anlamı yok
{
await transaction.RollbackAsync();
return new MissionStartResponse
{
Success = false,
Message = "Şu anda görev için müsait gemi yok"
};
}
}
catch (Exception exp)
{
await transaction.RollbackAsync();
return new MissionStartResponse
{
Success = false,
Message = $"Sistem Hatası:{exp.Message}"
};
}
}
}
}</pre>
<p><em class="ik">Yazılan servis kodundan çeşitli sorular sorulabilir. örneğin hangi tür injection tekniği kullanılmaktadır, başka ne türleri vardır, veritabanı belli midir, belli ise bağlantı bilgisi nerededir, transaction açılmasının sebebi nedir, temel transaction ilkeleri nelerdir vb. Buradan yola çıkarak “BASE’i duymuş muydunuz?” diye bir soru sorulabilir ve NoSQL ilkelerine geçilip dağıtık sistemler için önem arz eden CAP teoremine atıfta bulunulabilinir. Detaylar ders harici zamanlarda merak edenlerle konuşulur veya araştırma ödevi olarak atanır.</em></p>
<p><strong class="ho cu">5 — Sırada Controller var. Yani Web API’nin İnşası</strong></p>
<p>önce projeyi oluşturup gerekli paketleri ve proje referanslarını aşağıdaki gibi ekleyelim.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"># Web API projesini oluştur
dotnet new webapi -o GalaxyExplorer.API
# Solution'a ekle
dotnet sln add .\GalaxyExplorer.API\GalaxyExplorer.API.csproj
# Proje klasörüne geç
cd .\GalaxyExplorer.API
# EntityFrameworkCore paketini ekle
dotnet add package Microsoft.EntityFrameworkCore -v 5.0.6
# Local SQL kullanmak istedim. Onun paketini ekle
dotnet add package Microsoft.EntityFrameworkCore.SqlServer -v 5.0.6
# Migration için gerekli olacak paket
dotnet add package Microsoft.EntityFrameworkCore.Design -v 5.0.6
# WeatherForecast* tiplerini sildim
# Service ve DTO projelerini referasn ettim
dotnet add reference ..\GalaxyExplorer.Service\GalaxyExplorer.Service.csproj
dotnet add reference ..\GalaxyExplorer.DTO\GalaxyExplorer.DTO.csproj
dotnet add reference ..\GalaxyExplorer.Entity\GalaxyExplorer.Entity.csproj</pre>
<p>Startup.cs içerisindeki ConfigureServices metodunu da takip eden kod parçasında olduğu gibi düzenleyelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public void ConfigureServices(IServiceCollection services)
{
// DI serivslerine DbContext türevini ekliyoruz.
services.AddDbContext<GalaxyExplorerDbContext>(options =>
{
// SQL Server baz alınacak ve appsettings.json'dan GalaxyDbConnStr ile belirtilen bağlantı bilgisi kullanılacak.
options.UseSqlServer(Configuration.GetConnectionString("GalaxyDbConnStr"), b => b.MigrationsAssembly("GalaxyExplorer.API"));
});
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "GalaxyExplorer.API", Version = "v1" });
});
}</pre>
<p>Bu senaryo özelinde makinelerimizde de hazır olması sebebiyle Local SQL Server’ı kullanmayı tercih edebiliriz. Gerekli ConnectionString bilgisini AppSettings.json dosyasına aşağıdaki gibi eklemek gerekir — <em class="ik">Sınıfı katılımcı sayısına göre gruplara bölüp farklı veritabanı ile çalışmalarını da sağlatabiliriz. Postgresql’in Docker Container kullanan bir versiyonu ideal çözüm olabilir.</em></p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">"ConnectionStrings": {
"GalaxyDbConnStr": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=GalaxyExplorer;Integrated Security=True"
}</pre>
<p>Ardından projeye MissionController isimli bir Controller sınıfını ekleyelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using GalaxyExplorer.DTO;
using GalaxyExplorer.Service;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace GalaxyExplorer.API.Controller
{
[Route("api/[controller]")]
[ApiController]
public class MissionController : ControllerBase
{
// DI Container'a kayıtlı IMissionService uyarlaması kimse o gelecek
private readonly IMissionService _missionService;
public MissionController(IMissionService missionService)
{
_missionService = missionService;
}
[HttpPost]
public async Task<IActionResult> StartAsync([FromBody] MissionStartRequest request) // JSON Body'den request nesnesini alsın
{
if (!ModelState.IsValid)
return BadRequest(); // Model validasyon kurallarında ihlal olursa
// Servis metodunu çağıralım
var startResult = await _missionService.StartMissionAsync(request);
if (startResult.Success) // Sonuç başarılı ise HTTP OK
return Ok(startResult.Message);
else
return BadRequest(startResult.Message); // Değilse HTTP Bad Request
}
}
}</pre>
<p>Controller sınıfının IMissionService implementasyonunu kullanabilmesi için Startup dosyasında yer alan DI servislerine gerekli bildirimi yapmayı da ihmal etmemek lazım. —<em class="ik"> Kullanılan Constructor Injection tekniğine göre bu Controller’a talep geldiğinde hazır edilecek bileşenin nerede bildirilmesi gerektiğini önce sınıfa soralım, sonrasında biz gösterelim.</em></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">services.AddTransient<IMissionService, MissionService>();</pre>
<p>Artık bir şeyleri elle tutulur şekilde gösterebilmek de gerekiyor. Bunun için veri tabanının oluşması lazım. Dolayısıyla mevzu Migration. Migration işlemleri için dotnet ef aracını kullanabiliriz ancak öğrencilerin sisteminde bu kurulu olmayabilir. Bu gibi durumlarda sorun yaşamamak ve öğrenciyi kaybetmemek adında “Eğitime Gelmeden önce Makinenizde Yapmanız Gerekenler” tadında basit bir kılavuz hazırlayıp paylaşmak iyi olabilir. Biz aşağıdaki gibi ilerleyerek devam edelim.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"># Tool kurulumu için
dotnet tool install --global dotnet-ef
# tool'u güncellemek için
dotnet tool update --global dotnet-ef
# tool'u projede kullanmak için
dotnet add package Microsoft.EntityFrameworkCore.Design
# kurulduğunu görmek için
dotnet ef
# Aşağıdaki komutları Web API projesi içinde çalıştırdım.
dotnet ef migrations add Initial -o Db/Migrations
dotnet ef database update</pre>
<p><em class="ik">Tam bu noktada SQL tarafına geçip bir veri tabanı oluştuğundan ve hatta Spaceship tablosuna örnek verilerin dolduğundan emin olmak lazım. Diğer yandan Local SQL yerine Docker’dan yararlanarak popüler bir başka veritabanını basitçe kullanabileceğimizi de belirtmemiz önemli. </em><a class="bv it" href="https://github.com/buraksenyurt/studious-adventure" rel="noopener nofollow"><em class="ik">Şuradaki gibi diyerek referans da gösterebiliriz</em></a><em class="ik">.</em></p>
<p><strong class="ho cu">6 — öncü Testler</strong></p>
<p>Artık testlere başlanabilir. Şükür ki Swagger gibi yapılar artık proje şablonlarına entegre edilmiş şekilde geliyorlar. Dolayısıyla örneğimizde Web API’yi doğrudan çalıştırınca aşağıdaki şık arayüzle karşılaşmamız gerekir. Dolayısıyla ilk testleri yapmak oldukça kolay olur. Eskiden buralar dutluktu.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2021/Mayis/assets_02.png" alt="" /></p>
<p>örnek bir JSON içeriğini aşağıdaki gibi uygulayabiliriz.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">{
"name": "Ufuk ötesi Macerası",
"plannedDuration": 18,
"voyagers": [
{
"name": "Kaptan Tupolev",
"grade": "Yüzbaşı"
},
{
"name": "Melani Garbo",
"grade": "Bilim Subayı"
},
{
"name": "Dursun Durmaz",
"grade": "Seyrüseferci"
}
]
}</pre>
<p>Gerekirse diye bir Curl komutu da verebiliriz — <em class="ik">Her platformu düşünmemiz lazım.</em></p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">curl -X POST "https://localhost:44306/api/Mission" -H "accept: */*" -H "Content-Type: application/json" -d "{\"name\":\"Ufuk ötesi Macerası\",\"plannedDuration\":18,\"voyagers\":[{\"name\":\"Kaptan Tupolev\",\"grade\":\"Yüzbaşı\"},{\"name\":\"Melani Garbo\",\"grade\":\"Bilim Subayı\"},{\"name\":\"Dursun Durmaz\",\"grade\":\"Seyrüseferci\"}]}"</pre>
<p>Bu örnek JSON talebi sonrası elde edilen sonuçlar da istediğimiz gibi olmalıdır<em>— öğrencilerin elde ettiği sonuçları da gözlemlemek gerekir.</em></p>
<p><em><img src="https://buraksenyurt.com/image.axd?picture=/2021/Mayis/assets_03.png" alt="" /></em></p>
<p>Doğrulama ifadelerinin işe yarayıp yaramadığını görmek içinse aşağıdaki gibi bir JSON talebi kullandırabiliriz. Mümkün mertebe her tür testi göstermemiz yararlı olabilir.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">{
"name": " ",
"plannedDuration": 10,
"voyagers": [
{
"name": "The Choosen One",
"grade": "Hacker"
}
]
}</pre>
<p>Buna göre şöyle bir çıktı elde etmemiz gerekir. İşler yolunda gitmekte.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2021/Mayis/assets_04.png" alt="" /></p>
<p><em class="ik">Bu andan itibaren başka ne gibi fonksiyonelliklere ihtiyacımız olabilir diye tartışmaya açmak lazım. Düşünülen yeni fonksiyonellikleri öğrencilerin uygulaması istenebilir. öncü olması açısından da “Ek Geliştirmeler” başlığı altındaki adımlar paylaştırılabilir.</em></p>
<p><strong class="ho cu">7 — Ek Geliştirmeler</strong></p>
<p>Temel senaryo aslında tamam ancak…</p>
<p>Gezginler zaman içerisinde sayıca artacaktır. Genelde bu tip senaryolarda HTTP Get ile çağırılan fonksiyonlar tüm listeyi döndürür. En azından basite kaçtığımız senaryolarda böyledir. Ancak satır sayısı fazla ise servisten her şeyi döndürmek iyi bir pratik olmayabilir. Bunun yerine kriter bazlı veri döndürmek daha iyi olur. örneğin aktif görevde olan veya olmayanların listesini çekmek. Bu bile fazla sayıda satır dönmesine sebebiyet verebilir. Ağ trafiği ve servislerin cevap verebilirlik süreleri her zaman kritiktir. Şimdi olmasa bile kullanıcı sayısı arttığında önem arz edecektir. Dolayısıyla sayfalama kriteri eklemek iyi bir çözüm olabilir. Bu sebeple Response ve Request için bazı DTO tiplerini aşağıdaki gibi tasarlayabiliriz. —<em class="ik"> Gerçek hayat senaryolarından dem vurarak bazı öğütlerde bulunmamız oldukça elzem.</em></p>
<p>Controller tipinin ilgili metoduna gelecek talep için aşağıdaki sınıfı tasarlayarak devam edelim. Kaçıncı sayfadan itibaren kaç satır alınacağını belirttiğimiz basit bir kurgu var. Ek olarak görevde olup olmama durumunu taşıdığımız boolean bir özellik bulunuyor.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.ComponentModel.DataAnnotations;
namespace GalaxyExplorer.DTO
{
public class GetVoyagersRequest
{
[Required]
public int PageNumber { get; set; }
[Required]
[Range(5,20)] // Sayfa başına minimum 5 maksimum 20 satır kabul edelim
public int PageSize { get; set; }
public bool OnMission { get; set; }
}
}</pre>
<p>API metodunun dönüşünü ise aşağıdaki gibi geliştirelim. Toplam gezgin sayısı, aktif görevdeki gezgin sayısı, istenen sayfa listesi ve sonraki sayfaya geçiş için yardımcı bağlantı bilgisini döndürmeyi düşünebiliriz. Sayfalama yapılan servislerde önceki ve sonraki bölümlere geçişi kolaylaştıran referans linkleri paylaşmak standart bir pratiktir. — <em class="ik">önceki sayfa linkinin eklenmesi, gidilecek sayfa kalmaması halinde alınması gereken önlem veya yapılması gereken işin ne olduğu öğrencilere görev olarak verilebilir.</em></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Collections.Generic;
namespace GalaxyExplorer.DTO
{
public class GetVoyagersResponse
{
public int TotalVoyagers { get; set; }
public int TotalActiveVoyagers { get; set; }
public List<VoyagerResponse> Voyagers { get; set; }
public string NextPage { get; set; }
}
}</pre>
<p>Bu response tipinde kullanılan liste elemanını ise aşağıdaki gibi ekleyelim. Gezginin adı ve rütbesi dışında hakkında detaylı bilgi almak için Detail isimli bir özellik de bulunmakta.<em class="ik"> — <em class="ik">Detay kısmında büyük ihtimalle ID kullanılması gerekecektir. Bunu söylemeden Detail kısmını nasıl oluşturmamız gerektiği öğrenciler ile karşılıklı olarak tartışılabilinir.</em></em></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">namespace GalaxyExplorer.DTO
{
public class VoyagerResponse
{
public string Name { get; set; }
public string Grade { get; set; }
public string Detail { get; set; }
}
}</pre>
<p>Sonrasında Servis arayüzüne yeni fonksiyon bildirimini eklememiz gerekir.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">Task<GetVoyagersResponse> GetVoyagers(GetVoyagersRequest request);</pre>
<p>Pek tabii eklenen yeni operasyonun MissionService üzerinde uygulanması gerekir. Bunu aşağıdaki şekilde yapabiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public async Task<GetVoyagersResponse> GetVoyagers(GetVoyagersRequest request)
{
var currentStartRow = (request.PageNumber - 1) * request.PageSize;
var response = new GetVoyagersResponse
{
// Kolaylık olsun diye sonraki sayfa için de bir link bıraktım
// Lakin başka kayıt yoksa birinci sayfaya da döndürebiliriz
NextPage = $"api/voyager?PageNumber={request.PageNumber + 1}&PageSize={request.PageSize}&OnMission={request.OnMission}",
TotalVoyagers = await _dbContext.Voyagers.CountAsync(),
TotalActiveVoyagers = await _dbContext.Voyagers.CountAsync(v => v.OnMission == true)
};
var voyagers = await _dbContext.Voyagers
.Where(v => v.OnMission == request.OnMission)
.Skip(currentStartRow)
.Take(request.PageSize)
.Select(v => new VoyagerResponse
{
Name = v.Name,
Grade = v.Grade,
Detail = $"api/voyager/{v.VoyagerId}" // Bu Voyager'ın detaylarını görmek için bir sayfaya gitmek isterse diye
})
.ToListAsync();
response.Voyagers = voyagers;
return response;
}</pre>
<p>Bu yeni fonksiyonu kullanabilmek için Controller tarafına da müdahale etmek gerekir. Voyager ile ilgili bir işlem söz konusu olduğundan VoyagerController isimli yeni bir Controller tipi eklemek çok daha doğrudur.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using GalaxyExplorer.DTO;
using GalaxyExplorer.Service;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace GalaxyExplorer.API.Controller
{
[Route("api/[controller]")]
[ApiController]
public class VoyagerController : ControllerBase
{
// DI Container'a kayıtlı IMissionService uyarlaması kimse o gelecek
private readonly IMissionService _missionService;
public VoyagerController(IMissionService missionService)
{
_missionService = missionService;
}
[HttpGet]
public async Task<IActionResult> GetVoyagers([FromQuery] GetVoyagersRequest request) // Parametreleri QueryString üzerinden almayı tercih ettim
{
var voyagers = await _missionService.GetVoyagers(request);
return Ok(voyagers);
}
}
}</pre>
<p><em class="ik">Burada biraz durup tartışma başlatmak da gerekiyor. İdeal bir Controller dağılımı söz konusu gibi. Voyager ile ilgili operasyonları VoyagerController, Mission ile ilgili operasyonları MissionController üstleniyor. Açıkta bıraktığımız nokta her ikisinin IMissionService türevli bileşenleri kullanması. İdeal bir tasarımda IVoyagerService de söz konusu olmalıdır. Lakin bunun bir soru olarak gelmesini beklemeliyiz. Gelmezse “Sizce ideal bir tasarım oldu mu?” şeklinde sorup öğrencileri bu noktaya çekmeliyiz.</em></p>
<p>Uygulamayı tekrar çalıştırıp başka görevler de başlattıktan sonra Get metodunu yine Swagger arabirimi üzerinden test etmemiz gerekir.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2021/Mayis/assets_05.png" alt="" /></p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"># curl ile test etmek isterseniz
curl -X GET "https://localhost:44306/api/Voyager?PageNumber=1&PageSize=5&OnMission=true" -H "accept: */*</pre>
<p>Aşağıdakine benzer bir çıktı alabilmeliyiz.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">{
"totalVoyagers": 14,
"totalActiveVoyagers": 11,
"voyagers": [
{
"name": "Kaptan Tupolev",
"grade": "Yüzbaşı",
"detail": "api/voyager/1"
},
{
"name": "Melani Garbo",
"grade": "Bilim Subayı",
"detail": "api/voyager/2"
},
{
"name": "Di Ays Men",
"grade": "İkinci Pilot",
"detail": "api/voyager/4"
},
{
"name": "Healseying",
"grade": "Sağlık Subayı",
"detail": "api/voyager/6"
},
{
"name": "Kaptan Fasma",
"grade": "Tugay Komutanı",
"detail": "api/voyager/7"
}
],
"nextPage": "api/voyager?PageNumber=2&PageSize=5&OnMission=True"
}</pre>
<p>Tabi sonraki sayfayı da nextPage ile gelen url bilgisini kullanarak denememiz lazım ki işe yarayıp yaramadığını görelim.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2021/Mayis/assets_06.png" alt="" /></p>
<p><em class="ik">Buraya kadar öğrenciler başarılı bir şekilde gelebiliyse harika! Eğlenceli sayılabilecek ama açık noktaları da olan bir senaryo üstünden iki temel fonksiyon kullanmış olduk. Biraz Dependency Injection, biraz Entity Framework, biraz LINQ, biraz asenkron operasyon kullanımı, biraz migration işleri, biraz Swagger farkındalığı vs… Bu kazanımları “Aklınızda neler kaldı?” diyerek öğrencilere anlattırmak gerekiyor. Sorular da alındıktan sonra onlara bazı ödevler vermek şart.</em></p>
<p><strong class="ho cu">öğrenciye Neler Yaptırılabilir?</strong></p>
<ul class="">
<li>Voyager listesinden herbir gezginin şu ana kadar katıldığı toplam görev sayısını döndürebiliriz.</li>
<li>Voyager listesinden dönen Detail özelliğinin karşılığı olan Controller metodunu tamamlayabiliriz.</li>
<li>Aktif görevler ve bu görevlerdeki gezginlerin listesini döndürecek bir fonksiyon ekletebiliriz.</li>
<li>VoyagerController için MissionService yerine başka bir soyutlama yaptırabiliriz <em class="ik">(IVoyagerService ve VoyagerService gibi)</em></li>
<li>Tamamlanan görevle ilgili güncellemeri yapacak bir PUT fonksiyonu dahil ettirilebiliriz. Bu, ilgili görevin durumunu tamamlandıya çekip, göreve katılan mürettebatı yeni görev almaya uygun olarak işaretleyen bir fonksiyon olabilir. Eksik Entity alanları varsa onların fark edilmesi ve yeni bir Migration planı hazırlanıp çalıştırılmasını isteyebiliriz.</li>
<li>ve öğrencilerin aklına gelen diğer ekler.</li>
</ul>
<p>Görüldüğü üzere çok sık yazılan, anlatılan, öğretilen bir konu için hazırlık yapmak önemli bir efor ve çaba gerektiriyor. üstelik varılan sonuçların tutarlı olması ve ortak stadartlar üzerinde durması da önemli.</p>
<p>Faydalı olması ve ilham vermesi dileğiyle…</p>
<p><em class="ik"> </em></p>2021-05-14T18:22:00+00:00web apiasp.net web api.net 5swaggerentity framework corec#programlamaeğitimcinin eğitimieğitim teknolojileribsenyurtAltunizade’nin bahar aylarında insanı epey dinlendiren yeşil yapraklı ağaçları ile çevrelenmiş caddesinin hemen sonunda, köprüye bağlanmadan iki yüz metre kadar öncesinde dört katlı bir bina vardır. Araba ile geçerken eğer kırmızı ışığa denk gelmediyseniz pek fark edilmez ama yürürken önündeki geniş kaldırımları ile dikkat çeker. Ana cadde tarafındaki yeşillikler binanın ilk katlarını gizemli bir şekilde saklar. Binanın bulunduğu adanın etrafı cadde ve ara sokaklarla çevrilidir. Bir sokağın karşısında yeni yapılmış hastane ile metro çıkışı, ana cadde tarafında ev yapımı tatlılarının kokusu ile insanı baştan çıkaran pastane, eczane, kuaför, camii ve minik bir park bulunur. Dört yola bakan diğer cadde tarafında ise eskiden karakol olan ama çok uzun zamandır kreş olarak işletilen bir komşu yer alır.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=4f335050-0057-4ccc-80dc-6cb802cbf4374https://buraksenyurt.com/trackback.axd?id=4f335050-0057-4ccc-80dc-6cb802cbf437https://buraksenyurt.com/post/effective-engine-bir-uzay-macerasi#commenthttps://buraksenyurt.com/syndication.axd?post=4f335050-0057-4ccc-80dc-6cb802cbf437https://buraksenyurt.com/post/net-core-2-0-ile-basit-bir-web-api-gelistirmek.NET Core 2.0 ile Basit Bir Web API Geliştirmek2017-10-04T09:58:00+00:00bsenyurt<p><img style="float: right;" src="https://buraksenyurt.com/image.axd?picture=/2017/10/bogazmini.gif" alt="" />Merhaba Arkadaşlar,</p>
<p>Günler yoğun geçiyor. Bir süredir sosyal medyadan da uzaktayım. Kendimce sebeplerim var. Ağırlık görev değişikliği sonrası kritik geliştirmeler barındıran işimdeki yoğunluk. Bunun dışında daha çok kitap okuduğumu, telefona neredeyse hiç bakmadığımı<em>(Türkiye ortalamasına göre bir kişi günde 70 kez telefona bakıyormuş-kahrolsun Instagram çağı)</em>, Serdar Kuzuloğlu'ndan <a href="https://www.dunyahalleri.com/" target="_blank">dünya hallerini</a> daha çok okuduğumu, Gündem Özel'i daha çok izlediğimi<em>(Yazıyı yazdığım günlerdeki <a href="https://www.youtube.com/watch?v=GYwyBC5XfFQ" target="_blank">şu yayınlarını</a> tavsiye ederim. <a href="http://www.hasansoylemez.com/" target="_blank">Hasan Söylemez'i</a> de takip edin kitabını alın derim)</em>, okuyup dinlediklerimden kendime küçük küçük notlar çıkarttığımı, daha çok basketbol oynadığımı, işe gittiğim her gün gerek otobüs gerek metorbüs gerek minibüs daha çok sıkıştığımı<em>(tutunmadan seyahat edebilmek dahil)</em> ama Beşiktaş-Üsküdar arası motor hattında nefes alarak huzur bulabildiğim günler geçirdiğimi ifade edebilirim. Kalan zamanlarda eskisi kadar çok olmasa da bir şeyler öğrenmeye gayret ediyorum. Bir süredir de .Net Core tarafında servis geliştirme noktasında neler yapılabileceğini incelemek istiyordum. İşlerden boşluk bulduğum bir sırada Web API nasıl yazılır araştırayım ve yaptığım örneği bloğuma ekleyeyim dedim.</p>
<h1>Ortam Hazırlıkları</h1>
<p>İlk olarak <a href="https://www.microsoft.com/net/download/core" target="_blank">Microsoft'un ilgili adresinden</a> .Net Core'un son sürümünü indirdim. Çalışmaya başladığım tarih itibariyle 2.0 versiyonu bulunuyordu. Kurulumu Windows 7 işletim sistemi olan bir makinede gerçekleştirdim<em>(Şirket bilgisayarı)</em> Saha Hizmetleri ekibimizin de desteği ile makineye 2.0 sürümünü sorunsuz şekilde yükledim<em>(Malum makinede Local Admin'lik olmayınca)</em> Ardından komut satırını ve Notepad++ uygulamasını açtım. Amacım Visual Studio ailesinin <em>(Code dahil)</em> ürünlerini kullanmadan Web API geliştirmenin temellerini anlamaktı. Hem bu sayede hazır şablonların içeridiği kod parçalarını daha iyi anlayabilirdim. Sonrasında terminal penceresine geçtim ve incelediğim kaynaklardan derlediğim notlara da bakarak ilk komutumu verdim.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">dotnet --help</pre>
<p>ile dotnet komut satırı aracının nasıl kullanılabileceğini incelemeye çalıştım. .Net Core'un komut satırında proje şablonlarını otomatik olarak hazırlayan new komutunun nasıl kullanılabileceğini görmek için de şu komutu kullandım. Bu sayede dotnet'in popüler ve gerekli build, restore, run gibi komutlarını nasıl kullanabileceğimizi detaylı bir şekilde görebiliriz.</p>
<pre class="brush:cf;auto-links:false;toolbar:false" contenteditable="false">dotnet new --help</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_1.gif" alt="" /></p>
<p>Sonrasında .Net Core çalışmaları için açtığım klasörde aşağıdaki komutu vererek Fabrika isimli bir Web API projesi oluşturdum.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">dotnet new webapi -o Fabrika</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_2.gif" alt="" /></p>
<p>Peki şimdi ne oldu? -o parametresi ile verdiğimiz Fabrika ismi nedeniyle Fabrika adında bir klasör oluştu ve içerisine gerekli tüm proje dosyaları hazır olarak eklendi.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_3.gif" alt="" /></p>
<p>Dikkat edileceği üzere Controllers isimli bir klasör de bulunuyor. Temel olarak Model View Controller desenini kullanmaya hazır bir şablon oluşturulduğunu ifade edebiliriz. Bir başka deyişle kullanıcılardan gelecek REST taleplerini kontrol eden(Controllers) geriye dönecek varlıkları(Models) varsayılan olarak JSON tipinde basacak(View) bir desen söz konusu. Varsayılan olarak Models klasörü yoktu. Bunu kendim ekledim.</p>
<p>Oluşturulan proje yapısından sonra ilk yaptığım şey kaynaklarda da belirtildiği üzere ortamı çalıştırmaktı.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">dotnet run</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_4.gif" alt="" /></p>
<p>Dikkat edileceği üzere http://localhost:5000 adresinden ayağa kalkan ve istemci taleplerini dinlemeye hazır bir sunucu söz konusu. Tabii direkt bu adrese gidersek bir sonuç alamayız. Çünkü varsayılan olarak gelen bir yönlendirme<em>(Router)</em> sistemi var. Bu adres Controllers klasöründeki Controller tipinden türeyen sınıfa göre şekilleniyor. Hazır şablonla gelen ValuesControllers sınıfının kodlarına baktığımızda Route niteliğinin(attribute) kullanıldığını görürüz. Bu nitelikte ifade edilen api/[Controller] bildirimi talep edebileceğimiz HTTP adresinin şeklini belirler ki bu durumda aşağıdaki gibi olmalıdır.</p>
<p>http://localhost:5000/api/values</p>
<p>Sonuçta örnek olarak konulmuş string dizi içeriği elde edilir.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_5.gif" alt="" /></p>
<p>Elbette varsayılan bir Controller sınıfı söz konusu. ValuesController sınıfının içerisinde yer alan metodlar incelendiğinde HTTP Get, Post, Put ve Delete operasyonları için gerekli hazır fonksiyonların konulduğu görülür. Hangi metodun hangi HTTP talebine cevap vereceğini belirtmek için HttpGet, HttpPost, HttpPut ve HttpDelete niteliklerinden yararlanılmaktadır.</p>
<h1>EntityFrameworkCore Paketinin Yüklenmesi</h1>
<p>Ben bunun üzerine işin içerisine EntityFrameworkCore'u da katmaya ve klasik ürün listelemesi yapan REST servis örneğini inşa etmeye karar verdim. Tabii ilk bulmam gereken Entity Framework Core sürümünün bu projeye nasıl ekleneceğiydi. Söz konusu kütüphane bir NuGet paketi olarak ele alınabildiğinden projenin kullandığı paketler listesinde tanımlanması yeterli olacaktı. Bu yüzden Fabrika.csproj isimli proje dosyasını açtım ve EntityFrameworkCore paketi için ItemGroup elementi altına bir PackageReference bildirimi ekledim.</p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.0.0"/>
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup>
</Project></pre>
<p>Bu işlemin ardından aşağıdaki komutu kullanarak Microsoft.EntityFrameworkCore.InMemory paketinin 2.0.0 versiyonunun indirilmesini sağlanır. restore ile bildirimi yapılan tüm paketler çözümlenir ve projenin kullanımı için gerekli indirme işlemleri yapılır.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">dotnet restore</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_6.gif" alt="" /></p>
<h1>Model Sınıflarının Yazılması</h1>
<p>EntityFrameworkCore paketi eklendiğine göre gerekli Model içeriklerini yazarak ilerleyebilirdim. Product ve FabrikaContext isimli iki sınıfı Models klasörü içerisine aşağıdaki içeriklerle ekledim.</p>
<p>Product sınıfı</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">namespace Fabrika.Models
{
public class Product
{
public long Id {get;set;}
public string Name {get;set;}
public double UnitPrice {get;set;}
}
}</pre>
<p>FabrikaContext sınıfı</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using Microsoft.EntityFrameworkCore;
namespace Fabrika.Models
{
public class FabrikaContext
:DbContext
{
public DbSet<Product> Products{get;set;}
public FabrikaContext(DbContextOptions<FabrikaContext> options)
:base(options)
{
}
}
}</pre>
<p>Product tipik bir POCO<em>(Plain Old C# Object)</em> olarak tasarlanmıştır. FabrikaContext ise DbContext türevli basit bir sınıftır ve içerisinde Product tipini kullanan Products isimli bir DbSet barındırmaktadır. base kullanımı nedeniyle varsayılan bir nesne oluşumu söz konusudur.</p>
<h1>Controller Sınıfının Yazılması</h1>
<p>Model içerikleri de hazır olduğuna göre, istemciden gelecek HTTP talebine göre devreye girecek kontrolcüyü<em>(Controller)</em> yazarak ilerleyebilirim. Bu amaçla Controllers klasörüne ProductsController isimli aşağıdaki içeriğe sahip sınıfı ekledim. Kontrolcünün görevi istemciden gelecek talebi ele alıp modelden yararlanarak bir çıktı üretmekten ibaret.</p>
<p>ProductsController sınıfı</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Fabrika.Models;
namespace Fabrika.Controllers
{
[Route("Fabrika/restapi/[controller]")]
public class ProductsController : Controller
{
private readonly FabrikaContext _context;
public ProductsController(FabrikaContext context)
{
_context=context;
if(_context.Products.Count()==0)
{
_context.Products.Add(new Product{Id=19201,Name="Lego Nexo Knights King I",UnitPrice=45});
_context.Products.Add(new Product{Id=23942,Name="Lego Starwars Minifigure Jedi",UnitPrice=55});
_context.Products.Add(new Product{Id=30021,Name="Star Wars çay takımı ",UnitPrice=35.50});
_context.Products.Add(new Product{Id=30492,Name="Star Wars kahve takımı",UnitPrice=24.40});
_context.SaveChanges();
}
}
[HttpGet]
public IEnumerable<Product> Get()
{
return _context.Products.ToList();
}
[HttpGet("{id}")]
public IActionResult Get(int id)
{
var product=_context.Products.FirstOrDefault(t=>t.Id==id);
if(product==null)
{
return NotFound();
}
return new ObjectResult(product);
}
[HttpPost]
public void Post([FromBody]string value)
{
//TODO:Yazılacak
}
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
//TODO:Yazılacak
}
[HttpDelete("{id}")]
public void Delete(int id)
{
//TODO:Yazılacak
}
}
}</pre>
<p>ProductsController sınıfında route adresinin değiştirildiğinde, DbContext türevli FabrikaContext tipinin kullanıldığında dikkat edelim. Get taleplerini karşılayan iki metodumuz bulunuyor. Birisi tüm ürün listesini döndürmekte. Bu nedenle generi IEnumerable tipini döndürmekte. Diğer Get metodu ise belli bir Id'ye ait ürünü döndürüyor. Bu dönüş için IActionResult arayüzünün taşıyabileceği bir nesne örneği kullanılmakta<em>(ObjectResult)</em> Yapıcı metod içerisinde ürün olmama ihtimaline karşın bir kaç tane örnek ürün eklenmekte. Eklenen ürünler SaveChanges ile veritabanına kayıt altına da alınmakta<em>(Henüz Post, Put ve Delete metodlarını tamamlamadım. Bu fonksiyonlar sonraki boşluk için kendime atadığım görevler)</em></p>
<h1>İlk Deneme</h1>
<p>Hemen</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">dotnet build</pre>
<p>komutu ile kodu derledim. Hatasız olduğunu görünce de sevindim ve</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">dotnet run</pre>
<p>ile sunucuyu başlatıp ürünler için tarayıcıdan bir talep girdim.</p>
<p>http://localhost:5000/Fabrika/restapi/products</p>
<p>Ancak çalışma zamanı hataları ile karşılaştım.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_7.gif" alt="" /></p>
<p>FabrikaContext tipi için gerekli servis çözümlemesi bir şekilde yapılamıyordu. Sonrasında DbContext tipini servis olarak eklemeyi unuttuğumu fark ettim. Startup.cs dosyasını açarak ConfigureServices metoduna aşağıdaki satırı ilave etmek sorunun çözümü için yeterliydi<em>(Fabrika.Models ve Microsoft.EntityFrameworkCore namespace bildirimlerini de aldığım hatalar sonrası eklemem gerektiğini itiraf etmek isterim. Biraz daha dikkatli ol Burak!)</em></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<FabrikaContext>(opt=>opt.UseInMemoryDatabase("FabrikaDb"));
services.AddMvc();
}</pre>
<p>Burada bellekte çalışacak şekilde FabrikaDb isimli bir veritabanını, uygulamanın kullanacağı servisler listesine eklemiş oluyoruz. Örneği tekrar çalıştırdığımda sorun yaşamadım ve tarayıcıdan yaptığım bazı taleplere karşılık aşağıdaki ekran görüntülerinde yer alan sonuçları elde ettiğimi gördüm.</p>
<p>http://localhost:5000/Fabrika/restApi/products talebi sonrası</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_8.gif" alt="" /></p>
<p>http://localhost:5000/fabrika/restapi/products/30021 talebi sonrası</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_9.gif" alt="" /></p>
<p>http://localhost:5000/fabrika/restapi/products/999 talebi sonrası</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_10.gif" alt="" /></p>
<p><strong>--Derken--</strong></p>
<p>Derken Post işlemini de en iyi kaynaklardan birisi olan <a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-vsc#implement-the-other-crud-operations" target="_blank">şuradan</a> öğrenip araya sıkıştırayım dedim ve bu paragraf açılmış oldu.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">[HttpPost]
public IActionResult Post([FromBody]Product newProduct)
{
if(newProduct==null)
return BadRequest();
_context.Products.Add(newProduct);
_context.SaveChanges();
return CreatedAtRoute("GetProduct",new {id=newProduct.Id},newProduct);
}</pre>
<p>İlk olarak metodun IActionResult döndürecek şekilde değiştirildiğini belirtelim. FromBody niteliği ürün bilgisinin HTTP talebinin Body kısmından okunacağını belirtmekte. Eğer bir ürün bilgisi gelmezse BadRequest mesajı basılıyor. Ürünün veritabanına eklenmesi işi standart bir Entity Framework işi. CreatedAtRoute fonksiyonu HTTP 201 mesajının basılmasını sağlarken aynı zamanda GetProduct isimli bir metoda talepte bulunuyor. Tahmin edeceğiniz üzere yeni eklenen ürünün id bilgisini kullanarak bir HTTP Get talebi yapmakta. Önemli olan kısım ilk parametredeki adın nerede tanımlandığı. Bunu anlayana kadar bir kaç hata aldım da...</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">[HttpGet("{id}",Name="GetProduct")]
public IActionResult Get(int id)</pre>
<p>Name niteliğine atanan değer CreateAtRoute'un kullandığı fonksiyon adı. Böylece istemciye hem işlemin başarılı olduğunu söylüyor hem de yeni oluşan ürün içeriğini gönderiyoruz. Tabii senaryoyu test etmenin en pratik yolu Postman gibi bir araçtan yararlanarak JSON tipinden bir talep göndermek. Aynen aşağıdaki ekran görüntülerinde olduğu gibi.<img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_13.gif" alt="" /></p>
<p><strong>--Derken--</strong></p>
<h1>Varsayılan Port Bilgisinin Değiştirilmesi</h1>
<p>Merak ettiğim konulardan birisi de 5000 nolu port bilgisini nasıl değiştirebileceğimdi. Bunun için Program.cs dosyasına uğramak gerekiyor. BuildWebHost fonksiyonunda ortamla ilişkili bir takım ayarlamalar yapılabilir. Örneğin 5555 nolu portun kullanılacağı bilgisi ifade edilebilir. UseUrls fonksiyonuna dikkat edelim.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseUrls("http://localhost:5555/")
.Build();</pre>
<p>Bu arada Fluent bir metod zinciri söz konusu olduğunu ifade edelim <em>(Bilmeyenler Fluent API nasıl yazılır, Fluent Interface nedir gibi sorularla bir araştırma yapsınlar derim. Buradaki pek çok projemizde bu tip Fluent yapılar kullanıyoruz)</em></p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_11.gif" alt="" /></p>
<h1>Statik İçeriklere İzin Verilmesi</h1>
<p>Merak ettiğim bir diğer konu da hazır olarak gelen wwwroot klasörünü hangi amaçlarla kullanabileceğimizdi. Araştırmalarım sonucunda burada static sayfalara yer verebileceğimizi öğrendim ve şöyle bir HTML sayfası ekledim.</p>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><html>
<body>
Fabrika'da üretilen <b><a href="http://localhost:5555/fabrika/restapi/products">ürünler</a></b>.
</body>
</html>
</pre>
<p>Ne varki sayfaya bir türlü erişemedim. Sonrasında statik dosyaları kullanacağımı çalışma zamanına bildirmem gerektiğini öğrendim. Bunun için startup.cs içerisindeki Configure metodunda UseStaticFiles fonksiyon bildirimini yapmak yeterli.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseMvc();
}</pre>
<p>Sonrasında index.html sayfasının geldiğini de gördüm.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_12.gif" alt="" /></p>
<p>Bu arada varsayılan olarak wwwroot olarak tanımlanan klasör bilgisini UseWebRoot metodunu kullanarak farklı bir konuma da yönlendirebiliriz<em>(Static sayfaların kullanımı ile ilgili daha fazla detay da var. <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/static-files" target="_blank">Şu adrese</a> bakmanızı öneririm)</em></p>
<h1>Swagger Tabanlı Yardım Sayfasının Eklenmesi</h1>
<p><a href="https://swagger.io/" target="_blank">Swagger</a> altyapısını baz alan Swashbuckle isimli NuGet paketini kullanarak etkileyici görünüme sahip yardım sayfaları oluşturabiliriz. Böylece API'nin versiyonu, ne tür operasyonlar içerdiği, nasıl kullanıldığı hakkında bilgiler verebilir hatta o an örnek test verileri ile denemeler yaptırtabiliriz. Bunu denemek için ilk olarak komut satırından Fabrika isimli projeye ilgili paketi aşağıdaki ifadeyle ekledim.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">dotnet add Fabrika.csproj package Swashbuckle.AspNetCore</pre>
<p>Komutu çalıştırdıktan sonra proje dosyasına yeni bir PackageReference bildirimi eklendiğini görebiliriz<em>(Bu arada bir paketi manuel olarak proje dosyasına ekleyip dotnet restore komutu ile ilerlemek yerine bu şekilde işlem yapılabileceğini de öğrenmiş bulundum. Mutluyum)</em></p>
<pre class="brush:xml;auto-links:false;toolbar:false" contenteditable="false"><ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />
</ItemGroup></pre>
<p>İlgili servisi kullanıma sunmak içinse Startup.cs sınıfındaki ConfigureServices ve Configure metodlarında bazı değişiklikler yapılması gerekiyor.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">//Diğer isim alanları
using Swashbuckle.AspNetCore.Swagger;
namespace Fabrika
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
//Diğer kodlar
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "Fabrika API", Version = "v1" });
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//Diğer kodlar
app.UseSwagger();
app.UseSwaggerUI(c=>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json","Fabrika API v1.0");
});
}
}
}</pre>
<p>İki önemli ek söz konusu. İlk olarak ConfigureServices metodu içerisinde ilgili Swagger servisinin orta katmana eklenmesi sağlanıyor. Configure fonksiyonunda ise kullanıcı arayüzü için gerekli json içeriğinin adresi<em>(Endpoint bilgisi)</em> belirtilmekte ve Swagger çatısının kullanılacağı ifade edilmekte. Bu ekleri yaptıktan sonra aşağıdaki adrese talep gönderdim ve otomatik olarak üretilen bir JSON çıktısı ile karşılaştım.</p>
<p>http://localhost:5555/swagger/v1/swagger.json</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_14.gif" alt="" /></p>
<p>Sonrasında ise takip ettiğim MSDN dokümanının söylediği gibi doğrudan swagger adresine gittim.</p>
<p>http://localhost:5555/swagger/</p>
<p>Sonuç inanılmaz güzeldi benim için<em>(Otursam böyle bir tasarım yapamayacağım için olsa gerek)</em> Kendimi çok fazla yormadan hazır bir swagger paketini kullanarak söz konusu API operasyonlarını görebileceğim, test edebileceğim bir içeriğe ulaştım.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_15.gif" alt="" /></p>
<p>Artık Fabrika API'sinin yardım sayfası en temel haliyle hazır diyebiliriz. Pek tabi bunu özelleştirmek de gerekiyor ki gayet güzel bir şekilde özelleştirebiliyoruz. Açıklamaları genişletebiliyor, XML Comment'leri kullanarak operasyonlar hakkında daha detaylı bilgiler verebiliyoruz vs... <a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?tabs=netcore-cli" target="_blank">Şu adreste</a> bu konu ile ilgili detaylı bilgiye ulaşabilirsiniz. </p>
<blockquote>
<p>Mesela AddSwaggerGen fonksiyonunu aşağıdaki gibi zengileştirebiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info {
Title = "Fabrika API"
, Version = "v1"
, Description ="Fabrika'da üretilen ürünler hakkında bilgiler"
, Contact=new Contact{
Name="Burak Selim Şenyurt"
, Email="", Url="http://www.buraksenyurt.com"},
License=new License{
Name="Under GNU"
, Url="http://www.buraksenyurt.com"}
});
});</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2017/10/corewebapi_16.gif" alt="" /></p>
</blockquote>
<h1>Son Sözler</h1>
<p>Yazıyı bitirmekte olduğum şu anda içim biraz olsun huzurla doldu beynimdeki dopamin salınımı da arttı diyebilirim. En azından .Net Core 2.0'ı kullanarak, Visual Studio ailesine de el atmadan Notepad++ ile REST tabanlı basit bir Web API servisi yazabildim. Şimdi bunu evdeki Ubuntu üzerinde yapmaya çalışacağım.</p>
<p>Tabii gerçek hayat senaryolarında durum biraz daha farklı. Bir firmanın dışarıya açacağı servis bazlı API'leri düşünelim. Manuel olarak tek tek servis yazmak istenmeyecektir. Şirket içindeki hazır veri üretimi yapan birimlerin dinamik kodlar yardımıyla ayağa kalkacak servisler şeklinde sunulmasına çalışılacaktır. Kısacası bu tip Web API'leri bir Factory yardımıyla dinamik olarak nasıl üretebiliriz sorusu da gündeme geliyor. Örneğin şirketinizde n sayıda kütüphanenin belirli fonksiyonlarının Web API'ler ile açılacağını düşünün. Her bir kütüphane için Web API servisi yazmaya çalışmak yerine otomatik olarak bunları ayağa kaldıracak, yetkilendirmelere tabii tutacak bir mekanizma yazmak çok daha avantajlı olacaktır. Bu açılardan konuyu düşünmemizde ve öğrenmeye devam etmemizde yarar olduğu kanısındayım. Böylece geldim bir makalemin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>
<h2>Yazmaya Üşenenler İçin</h2>
<p><a class="download" href="https://buraksenyurt.com/file.axd?file=/2017/10/ProductsController.cs">ProductsController.cs (2,38 kb)</a></p>
<p><a class="download" href="https://buraksenyurt.com/file.axd?file=/2017/10/FabrikaContext.cs">FabrikaContext.cs (271,00 bytes)</a></p>
<p><a class="download" href="https://buraksenyurt.com/file.axd?file=/2017/10/Product.cs">Product.cs (161,00 bytes)</a></p>
<p><a class="download" href="https://buraksenyurt.com/file.axd?file=/2017/10/Program.cs">Program.cs (665,00 bytes)</a></p>
<p><a class="download" href="https://buraksenyurt.com/file.axd?file=/2017/10/Startup.cs">Startup.cs (1,31 kb)</a></p>2017-10-04T09:58:00+00:00.net coreweb apirest serviceshttpentity frameworkentity framework coreswagger.net core 2.0corerest apimiddlewaremvcmodel view controllerdotnetbuildrunrestorecommand promptbsenyurtBir süredir de .Net Core tarafına bakmak ve servis geliştirme noktasında neler yapılabileceğini incelemek istiyordum. İşlerden boşluk bulduğum bir sırada Web API nasıl yazılır araştırayım ve yaptığım örneği bloğuma ekleyeyim istedim.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=4725123b-8844-4f33-826a-df2468b036f812https://buraksenyurt.com/trackback.axd?id=4725123b-8844-4f33-826a-df2468b036f8https://buraksenyurt.com/post/net-core-2-0-ile-basit-bir-web-api-gelistirmek#commenthttps://buraksenyurt.com/syndication.axd?post=4725123b-8844-4f33-826a-df2468b036f8https://buraksenyurt.com/post/asp-net-web-api-ile-odata-kullanimi1Asp.Net Web API ile OData Kullanımı2016-04-21T09:00:00+00:00bsenyurt<p><img style="float: right;" src="https://buraksenyurt.com/image.axd?picture=/2016/04/ODataWebAPI_G.gif" alt="" />Merhaba Arkadaşlar,</p>
<p>İşlerin epeyce hafiflediği bir haftaydı diyebilirim. Dolayısıyla kırda parkta bayırda oturup dinlenmek için epeyce vaktim vardı. Ya da bir şeyler araştırmayı da tercih edebilirdim ki ben de öyle yaptım. Uzun zamandır <strong>Asp.Net Web API</strong> tarafında bir şeyler yapmıyordum. Araştırmalarım sırasında <strong>OData</strong>'nın <strong>Web API</strong> tarafındaki kullanımına denk geldim. Her zaman ki gibi konuyu olabildiğince basit bir halde öğrenmenin iyi olacağını düşündüm. Sonunda konuyu kaleme almayı başardım. Haydi başlayalım.</p>
<p><strong>OData<em>(Open Data Protocol)</em></strong> ile veri kaynaklarına <strong>HTTP</strong> üzerinden sorgu atabilmek ve bunu <strong>REST</strong> stilinde gerçekleştirmek mümkündür. <strong>OData</strong> standartları sayesinde veri üzerinde filtreleme, belirli alanlarını çekme<em>(Projection)</em>, ana içeriklerden detay içerikleri genişletme<em>(Expand), </em>gruplamalı hesaplamalar yapma<em>(Aggregations)</em> ve benzeri <strong>SQL</strong>'den aşina olduğumuz belli başlı fonksiyonellikleri servis odaklı kullanabiliriz.</p>
<p>Bu standardı dilersek <strong>Asp.Net Web API</strong> tabanlı servisler ile de ele alabiliriz. Tek yapmamız gereken <strong>Microsoft.AspNet.OData</strong> isimli <strong>NuGet</strong> paketinden yararlanmaktır. İşte bu yazımızda çok basit bir örnek ile söz konusu senaryoyu incelemeye çalışacağız. Senaryomuzda kategori ve bu kategoriler bağlı ürünlerimizin olduğu <strong>In-Memory</strong> bir veri depomuz olacak. Bu veri kümesine <strong>Web API</strong> servisleri üzerinden <strong>OData</strong> sorgusu atmaya çalışacağız. Gelin adım adım ilerleyerek örneğimizi geliştirelim.</p>
<h2>Projenin Oluşturulması ve Gerekli NuGet Paketlerinin Yüklenmesi</h2>
<p><strong>Visual Studio</strong> ortamında<em>(ki ben örneği 2013 sürümünde geliştirdim)</em> bir <strong>Asp.Net</strong> projesi oluşturarak işe başlayalım. <strong>Empty</strong> şablonunu tercih edip <strong>Web API</strong> seçeneğini işaretleyerek devam edelim.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2016/04/ODataWebAPI_1.gif" alt="" /></p>
<p>Proje oluşturulduktan sonra <strong>OData</strong> kullanımını kolaylaştıracak olan <strong>NuGet</strong> paketinin yüklenmesi gerekmektedir. <strong>NuGet Package Manager Console</strong> üzerinden ilgili paket aşağıdaki komut ile projeye yüklenir.</p>
<p><strong>install-package Microsoft.AspNet.OData</strong></p>
<blockquote>
<p>Örneği Entity Framework tabanlı olacak şekilde geliştirebilirsiniz de. Bu durumda install-package EntityFramework komutu ile gerekli NuGet paketini yüklemeniz yeterlidir.</p>
</blockquote>
<h2>Örnek Model Sınıflarının Yüklenmesi</h2>
<p>İşlemlerimizi çok basit bir şekilde ele alacağız. Aşağıdaki sınıf çizelgesinde görülen tipleri <strong>Models</strong> klasörüne ekleyerek ilerleyebiliriz. Teorik olarak kategoriler ve bunlara bağlı ürünlerin olduğu miniminacık bir dünyamız var. Her iki <strong>Entity</strong> arasında bir ilişki kurduğumuzu görebilirsiniz. Yani bir kategoriye bağlı n sayıda ürün söz konusudur.</p>
<p>Burada önemli olan kısım <strong>Contained</strong> <strong>niteliğinin<em>(Attribute)</em></strong> kullanımıdır. Bu nitelik ile <strong>OData</strong> sorgularının <strong>Entity</strong>' ler arası <strong>ilişkileri<em>(Relations)</em></strong> çalışma zamanında tanıyabilmesini sağlamaktayız. <strong>AzonDataSources</strong> sınıfı temel olarak veriyi doldurduğumuz yerdir. Örnek çalışmamızda <strong>Entity Framework</strong> yerine <strong>In-Memory</strong> çalışan bir çözüm kullandığımızı bir kere daha hatırlatmak isterim.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2016/04/ODataWebAPI_2.gif" alt="" /></p>
<p>Data Source Sınıfımız</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Collections.Generic;
using System.Data.Entity;
namespace UsingOData.Models
{
public class AzonDataSources
{
private static AzonDataSources ds = null;
public List<Category> Categories { get; set; }
public List<Product> Products { get; set; }
private AzonDataSources()
{
Categories = new List<Category>();
Products = new List<Product>();
this.PrepareData();
}
public static AzonDataSources Instance
{
get
{
if (ds == null)
{
ds = new AzonDataSources();
}
return ds;
}
}
public void PrepareData()
{
Category kitapCategory = new Category
{
CategoryID=1,
Name="Kitap"
};
Category elektronikCategory = new Category
{
CategoryID=2,
Name="Elektronik"
};
kitapCategory.Products = new List<Product>
{
new Product{
Category=kitapCategory,
ListPrice=20,
ProductID=1,
Title="C# All in One"
},
new Product{
Category=kitapCategory,
ListPrice=8.95M,
ProductID=91,
Title="Asp.Net Web API Introduction"
},
new Product{
Category=kitapCategory,
ListPrice=12.50M,
ProductID=8,
Title="Pragmatic Programmer"
},
new Product{
Category=kitapCategory,
ListPrice=5,
ProductID=14,
Title="The Last Lecture"
}
};
elektronikCategory.Products = new List<Product>
{
new Product{
Category=elektronikCategory,
ListPrice=200,
ProductID=28,
Title="LG Tablet x10"
},
new Product{
Category=elektronikCategory,
ListPrice=280.95M,
ProductID=92,
Title="Apple Smart Watch"
},
new Product{
Category=elektronikCategory,
ListPrice=1200M,
ProductID=55,
Title="Diesel Watch Blue"
}
};
this.Categories.Add(kitapCategory);
this.Categories.Add(elektronikCategory);
this.Products.AddRange(kitapCategory.Products);
this.Products.AddRange(elektronikCategory.Products);
}
}
}</pre>
<p>Category isimli sınıfımız,</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Collections.Generic;
using System.Web.OData.Builder;
namespace UsingOData.Models
{
public class Category
{
public int CategoryID { get; set; }
public string Name { get; set; }
[Contained]
public IList<Product> Products { get; set; }
}
}</pre>
<p>Product isimli sınıfımız</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Web.OData.Builder;
namespace UsingOData.Models
{
public class Product
{
public int ProductID { get; set; }
public string Title { get; set; }
public decimal ListPrice { get; set; }
[Contained]
public Category Category { get; set; }
}
}</pre>
<h2>OData Endpoint' inin Ayarlanması</h2>
<p>Şimdi <strong>OData</strong> için gerekli <strong>Route</strong> ayarlarını yapalım. Bunun için <strong>WebApiConfig.cs</strong> içeriğini aşağıdaki gibi düzenlemeliyiz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using Microsoft.OData.Edm;
using System.Web.Http;
using System.Web.OData.Batch;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;
using UsingOData.Models;
namespace UsingOData
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapODataServiceRoute("odata", null, GetEdmModel(),
new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
config.EnsureInitialized();
}
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "ODataSample";
builder.ContainerName = "DefaultContainer";
builder.EntitySet<Category>("Categories");
builder.EntitySet<Product>("Products");
return builder.GetEdmModel();
}
}
}</pre>
<p>Kritik nokta <strong>MapODataServiceRoute</strong> metodunda kullanılan <strong>GetEdmModel</strong> fonksiyonudur. <strong>ODataConventionModelBuilder</strong> tarafından çağırılan <strong>GetEdmModel</strong> fonksiyonunun sonucunu döndürmektedir. Burada builder için gerekli <strong>Namespace</strong>, <strong>Container</strong> ve kullanılacak <strong>EntitySet</strong> bildirimleri yapılır. <strong>EntitySet</strong> bildirimlerinde kullanılan isimler biraz sonra yazacağımız <strong>Controller</strong> sınıflarının da adı olacaktır. <em>(Örneğin Categories için CategoriesController gibi)</em></p>
<h2>Controller Sınıflarının Yazılması</h2>
<p>Pek tabi <strong>Web API</strong> servisimizin önemli aktörlerinden birisi de <strong>Controller</strong> sınıflarıdır. Senaryomuzda iki <strong>Entity</strong> söz konusu olduğu için yine iki adet <strong>Controller</strong> ekleyeceğiz. <strong>Controller</strong> içeriklerimiz son derece basit.</p>
<p>CategoriesController sınıfı</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Linq;
using System.Web.Http;
using System.Web.OData;
using UsingOData.Models;
namespace UsingOData.Controllers
{
public class CategoriesController
: ODataController
{
[EnableQuery]
public IHttpActionResult Get()
{
return Ok(AzonDataSources.Instance.Categories.AsQueryable());
}
}
}</pre>
<p>ve ProductsController sınıfı</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Linq;
using System.Web.Http;
using System.Web.OData;
using UsingOData.Models;
namespace UsingOData.Controllers
{
public class ProductsController
: ODataController
{
[EnableQuery]
public IHttpActionResult Get()
{
return Ok(AzonDataSources.Instance.Products.AsQueryable());
}
}
}</pre>
<p>Her iki sınıf birer <strong>Get</strong> metoduna sahip. Örneğimizi çok basit bir şekilde ele alacağımızdan <strong>Post</strong>, <strong>Put</strong>, <strong>Delete</strong> gibi operasyonları hariç tuttuk. <strong>IHttpActionResult</strong> <strong>arayüzünün<em>(Interface)</em></strong> taşıyabileceği nesne örnekleri cinsinden değer döndüren <strong>Get</strong> metodlarımız <strong>EnableQuery</strong> <strong>niteliği<em>(Attribute)</em></strong> ile işaretlenmiş durumdalar. Bu nitelik sayesinde çalışma zamanında <strong>OData</strong> sorgu komutlarını kullanabileceğimizi belirtmiş oluyoruz. <strong>Ok</strong> isimli fonksiyon <strong>OkNegotiatedContentResult<T></strong> tipinden değer döndürmekte. Örneğimizde <strong>HTTP 200</strong> dönmesini bekliyoruz.</p>
<h2>Test Etmeye Hazırız</h2>
<p>Herhangibir tarayıcıdan aşağıdaki komutları deneyerek örneğimizi test edebiliriz. Sonuçlar tahmin edileceği gibi <strong>JSON</strong> formatında görünecektir.</p>
<p><strong>http://localhost:61708/$metadata</strong> çağrısı ile aslında servisin metadata içeriğine ulaşabiliriz. Böylece servisin hangi entity' leri sunduğunu da görebiliriz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2016/04/ODataWebAPI_5.gif" alt="" /></p>
<p><strong>http://localhost:61708/Categories</strong> ile tüm kategorileri elde ederiz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2016/04/ODataWebAPI_7.gif" alt="" /></p>
<p><strong>http://localhost:61708/Categories?$expand=Products</strong> ile kategorileri ve bunlara bağlı ürünlerin listesini komple elde ederiz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2016/04/ODataWebAPI_6.gif" alt="" /></p>
<p><strong>http://localhost:61708/Products?$select=Title,ListPrice</strong> ile tüm ürünlerin sadece Title ve ListPrice değerlerini elde ederiz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2016/04/ODataWebAPI_8.gif" alt="" /></p>
<p>http://localhost:61708/Products?$filter=startswith(Title,'A') ile A harfiyle başlayan ürün listesini elde ederiz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2016/04/WebAPI_9.gif" alt="" /></p>
<p>Pek tabi <strong>OData</strong> sorgularında kullanabileceğimiz pek çok anahtar kelime var. Bu kabiliyetlere <a href="http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part2-url-conventions.html">http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part2-url-conventions.html</a> adresinden detaylı bir şekilde bakabilirsiniz. </p>
<p>Bu makalemizde bir <strong>Web API</strong> hizmetini <strong>OData</strong> sorgularını destekleyecek hale nasıl getirebileceğimizi incelemeye çalıştık. Bir diğer makalemizde görüşünceye dek hepinize mutlu günler dilerim.</p>2016-04-21T09:00:00+00:00asp.net web apiwcfodataopen data protocolhttpserviceasp.netweb apirestc#bsenyurtİşlerin epeyce hafiflediği bir haftaydı diyebilirim. Dolayısıyla kırda parkta bayırda oturup dinlenmek için epeyce vaktim vardı. Ya da bir şeyler araştırmayı da tercih edebilirdim ki ben de öyle yaptım. Uzun zamandır Asp.Net Web API tarafında bir şeyler yapmıyordum. Araştırmalarım sırasında OData'nın Web API tarafındaki kullanımına denk geldim. Her zaman ki gibi konuyu olabildiğince basit bir halde öğrenmenin iyi olacağını düşündüm. Sonunda konuyu kaleme almayı başardım. Haydi başlayalım.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=5ec7fd7e-e577-437a-bea7-cff7ca25be9a7https://buraksenyurt.com/trackback.axd?id=5ec7fd7e-e577-437a-bea7-cff7ca25be9ahttps://buraksenyurt.com/post/asp-net-web-api-ile-odata-kullanimi1#commenthttps://buraksenyurt.com/syndication.axd?post=5ec7fd7e-e577-437a-bea7-cff7ca25be9ahttps://buraksenyurt.com/post/TFI-109-IIS-Uzerindeki-Uygulamalarc4b1-Kod-Yoluyla-OgrenmekTFİ 109 - IIS Üzerindeki Uygulamaları Kod Yoluyla Öğrenmek2014-08-27T13:52:00+00:00bsenyurt<p>Merhaba Arkadaşlar,</p>
<p>Diyelim ki sunucudaki <strong>IIS </strong>üzerinde konuşlandırdığınız Web uygulamalarının bir listesini almak istiyorsunuz. Bunun elbette pek çok yolu olduğunu biliyorsunuz. Bir Powershell script' i belki de işinizi görür. Ancak belki de siz bunu kendi geliştireceğiniz windows forms uygulamasında bu listeyi kullanmak istiyorsunuz. Ne yaparsınız? Kod yardımıyla <strong>IIS </strong>üzerindeki <strong>Application</strong>' ları, <strong>Site</strong>' ları öğrenebilir misiniz?</p>
<p>Aslında hep elinizin altında olan<em> (Windows\System32\inetsrv\Microsoft.Web.Administration.dll)</em> ve hatta isterseniz <strong>NuGet Package Manager </strong>ile de indirebileceğiniz <strong>Microsoft.Web.Administration</strong> kütüphanesini kullanarak bu işi gerçekleştirmeniz oldukça kolay. Nasıl mı? İşte böyle.</p>
<p><img src="http://www.buraksenyurt.com/pics/2014%2f8%2ftfi109.png" alt="" /></p>
<p>Başka neler mi yapabilirsiniz? Örneğin bir Application Pool' u Recylce edebilirsiniz. Ya da bir Web Site' ı Stop-Start. Hatta yeni bir Web Site bile açabilirsiniz. Araştırmaya değer değil mi?</p>
<p>Başka bir ipucunda görüşmek dileğiyle.</p>2014-08-27T13:52:00+00:00bsenyurthttps://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=b0a0f719-7be3-43f8-96a5-6635b286bc471https://buraksenyurt.com/trackback.axd?id=b0a0f719-7be3-43f8-96a5-6635b286bc47https://buraksenyurt.com/post/TFI-109-IIS-Uzerindeki-Uygulamalarc4b1-Kod-Yoluyla-Ogrenmek#commenthttps://buraksenyurt.com/syndication.axd?post=b0a0f719-7be3-43f8-96a5-6635b286bc47https://buraksenyurt.com/post/AspNet-Web-API-Uzerinden-Resim-DondurmekAsp.Net Web API Üzerinden Resim Döndürmek2013-07-02T13:08:00+00:00bsenyurt<p><a href="https://buraksenyurt.com/pics/Road-Runner-Wile-E-Coyote-looney-tunes-5226561-1024-768.jpg"><img style="margin: 4px 0px; display: inline; float: right;" title="Road-Runner-Wile-E-Coyote-looney-tunes-5226561-1024-768" src="/pics/Road-Runner-Wile-E-Coyote-looney-tunes-5226561-1024-768_thumb.jpg" alt="Road-Runner-Wile-E-Coyote-looney-tunes-5226561-1024-768" width="300" height="225" align="right" /></a>Merhaba Arkadaşlar,</p>
<p>Eminim çocukken çizgi filmlerle aranız vardı. Hatta çoğumuz yaşı kaç olursa olsun çizgi filmlere arada sırada da olsa zaman ayırmakta. <em>(Ben Batman gördüm mü pür dikkat izlerim örneğin) </em>Keza pek çok büyüğümüz de, eskiden izlediği çizgi filmler ile karşılaştığında taaaa çocukluk yıllarına kadar gidip aynı o zamanki gibi içten gülebiliyorlar da<em>(Rahmetli babamdan bilirim)</em></p>
<p>Aslına bakarsanız bazen teknoloji de bizi aynen bu mantıkta epeyce güldürebiliyor. Örneğin <strong>Microsoft</strong>’ un ürünlerini düşünelim. <em>(Gerçi çok fazlalar ama gene de düşünmeye çalışalım)</em> Sürekli yenilikler çıkartıyorlar, sürekli verisyon atlatıyorlar ve işin en acı tarafı da koşan <a href="https://eksisozluk.com/road-runner--32627" target="_blank">Road Runner</a>’ a benziyorlar. Biz mi? Biz ise <strong>Road Runner</strong>’ ı her fırsatta yakalamaya çalışıp yakaladığını zanneden ama son anda hep elinden kaçıran <a href="https://eksisozluk.com/coyote--51208" target="_blank">Coyote</a>’ ye <img class="wlEmoticon wlEmoticon-smile" style="border-style: none;" src="/pics/wlEmoticon-smile_100.png" alt="Smile" /> Bence bu senaryoda developer’ lar biraz daha şanslı. Ya benim gibi düzenli blog tutmaya çalışanlar napsınlar <img class="wlEmoticon wlEmoticon-disappointedsmile" style="border-style: none;" src="/pics/wlEmoticon-disappointedsmile_7.png" alt="Disappointed smile" /> </p>
<p>Sözü fazla uzatmadan ve moralimizi daha da bozmadan konumuza geçelim.</p>
<p>Bu yazımızda <strong>Asp.Net Web API</strong> üzerinden, <strong>SQL</strong> tablolarında <strong>binary</strong> formatta tutulabilen resim içeriklerini nasıl çekebileceğimizi basit bir örnek ile incelemeye çalışıyor olacağız. Örneğimizin özel yanlarından birisi de kısa süre önce yayınlanan <strong><a href="http://www.microsoft.com/visualstudio/tur/2013-downloads" target="_blank">Visual Studio 2013 Preview</a></strong> ile geliştirilecek olması. Önce senaryomuza bir bakalım.</p>
<h1>Senaryo</h1>
<p>Uzun zamandır uğramadığımız hatta pek çok genç arkadaşımızın belki de adını bile duymadığı bir <strong>Microsoft</strong> veritabanını ele alıyor olacağız. <strong>Pubs</strong>, <strong>SQL 2000 </strong>sürümünde sıklıkla <strong>Northwind</strong> ile birlikte andığımız kobay veritabanlarından birisidir <img class="wlEmoticon wlEmoticon-smile" style="border-style: none;" src="/pics/wlEmoticon-smile_100.png" alt="Smile" /> Bu veritabanında yayıncılara ait bazı bilgiler yer almaktadır. Örneğin <strong>pub_info</strong> isimli tablo içerisinde <strong>pub_id, logo</strong> ve pr_<strong>info</strong> isimli 3 adet alan yer almaktadır. Bu alanlardan <strong>logo</strong> tahmin edileceği üzere <strong>Binary</strong> veri tipindedir ve yayıncının firma logosunu tutmaktadır.</p>
<p>Hedefimiz bu <strong>binary</strong> içerikleri<em>(yani logoları)</em> bir <strong>Web API</strong> fonksiyonu üzerinden geriye döndürebilmek ve hatta en azından tarayıcı pencresinde resim formatında gösterebilmek olacaktır. O halde projeyi açarak ilk adımımız atalım.</p>
<p><a href="https://buraksenyurt.com/pics/wapigi_1.png"><img style="margin: 4px 0px; display: inline;" title="wapigi_1" src="/pics/wapigi_1_thumb.png" alt="wapigi_1" width="498" height="266" /></a></p>
<h1>Projenin Oluşturulması</h1>
<p>İlk olarak yeni bir <strong>Web</strong> uygulaması oluşturarak işe başlayabiliriz. Pek tabi <strong>Visual Studio 2013 preview</strong> içerisinde görünen önemli özelliklerden birisi de <strong>One Asp.Net</strong> yeteneğidir. Buna göre tek bir <strong>Web</strong> uygulaması şablonu üzerinden hareket edilerek istenen kabiliyetlere göre seçimler yapılması sağlanmaktadır.</p>
<p><a href="https://buraksenyurt.com/pics/wapigi_2.png"><img style="margin: 4px 0px; display: inline;" title="wapigi_2" src="/pics/wapigi_2_thumb.png" alt="wapigi_2" width="469" height="203" /></a></p>
<blockquote>
<p>Doğruyu söylemek gerekirse Asp.Net tarafındaki proje şablonlarının artması kafa karışıklıkları yanında bir arada kullanmak istediğimiz kabiliyetler olduğunda da sıkıntı yaratmaktaydı. Umarız bu özellik baki olur ve daha da iyileştirilir.</p>
</blockquote>
<p><strong>Asp.Net Web Application</strong> seçimi sonrasında karşımıza gelen pencereden <strong>Empty</strong> <strong>template</strong> tipini seçip <strong>Web API</strong> özelliğini etkineleştirebiliriz. Ya da <strong>Web API</strong> özelliğini işaretleyip ilerleyebiliriz. Ben mümkün mertebe sade bir ortam arzu ettiğimden <strong>Empty</strong> template seçip <strong>Web API </strong>kutusunu işaretledim.</p>
<p><a href="https://buraksenyurt.com/pics/wapigi_3.png"><img style="display: inline;" title="wapigi_3" src="/pics/wapigi_3_thumb.png" alt="wapigi_3" width="640" height="398" /></a></p>
<p>Bu işlemler sonucunda solution ve proje içeriği aşağıdaki gibi oluşacaktır.</p>
<p><a href="https://buraksenyurt.com/pics/wapigi_4.png"><img style="margin: 4px 0px; display: inline;" title="wapigi_4" src="/pics/wapigi_4_thumb.png" alt="wapigi_4" width="315" height="284" /></a></p>
<h1>Modelin Eklenmesi</h1>
<p>İzleyen adımda modelimizi ilave etmemiz gerekiyor. Tahmin edeceğiniz gibi <strong>Entity Framework</strong> den yararlanıyor olacağız. Projeye yeni bir öğe olarak <strong>Ado.Net Entity Data Model</strong> nesnesi ekledikten sonra klasik adımlarımızla ilerliyoruz<em>(Model klasörü altına ekleyebilirsiniz)</em> Lakin <strong>Visual Studio 2013 Preview</strong>’ a has bir özellik olarak <strong>Entity Framework</strong> versiyonunu seçebileceğimiz bir ekranla karşılaşacağız<em>(Sanırım Entity Framework tarafı kadar hızlı versiyon atlatan ürün sayısı nadirdir)</em> Ben <strong>6.0</strong> sürümünü seçtim ve bunun sonucu olarak <strong>Beta 1’</strong> in kütüphane olarak ilave edildiğini fark ettim.</p>
<p><a href="https://buraksenyurt.com/pics/wapigi_5.png"><img style="display: inline;" title="wapigi_5" src="/pics/wapigi_5_thumb.png" alt="wapigi_5" width="367" height="215" /></a></p>
<p>İzleyen kısımda sadece <strong>pub_info</strong> tablosunun eşleniği olan <strong>entity</strong> üretimini yaptırmamız yeterlidir. <em>(Diğer tablolaraı dilersenize ekleyebilirsiniz ancak şu anki senaryomuz için çok da gerekli değiller)</em></p>
<p><a href="https://buraksenyurt.com/pics/wapigi_6.png"><img style="display: inline;" title="wapigi_6" src="/pics/wapigi_6_thumb.png" alt="wapigi_6" width="539" height="542" /></a></p>
<h1>Controller Tipinin Yazılması</h1>
<p><strong>Web API</strong>’ nin temel yapı taşı olan <strong>Controller </strong>tipini ekleyerek örneğimize devam edelim.</p>
<p><a href="https://buraksenyurt.com/pics/wapigi_7.png"><img style="display: inline;" title="wapigi_7" src="/pics/wapigi_7_thumb.png" alt="wapigi_7" width="463" height="323" /></a></p>
<p><strong>Web API</strong> controller sınıfı için iki farklı versiyon bulunmaktadır. <em>(Ben <strong>v1</strong>’i seçerek ilerledim ama bunu yaparken iki versiyon arasındaki farkı tam olarak bilmediğimi itiraf etmek isterim <img class="wlEmoticon wlEmoticon-embarrassedsmile" style="border-style: none;" src="/pics/wlEmoticon-embarrassedsmile_7.png" alt="Embarrassed smile" />)</em></p>
<p><strong>LogosController sınıfı içeriği</strong></p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web.Http;
using WebApplication4.Models;
namespace WebApplication4.Controllers
{
public class LogosController
: ApiController
{
public List<string> Get()
{
List<string> pubIds = null;
using (PubsEntities _context = new PubsEntities())
{
pubIds = (from p in _context.pub_info
select p.pub_id).ToList();
}
return pubIds;
}
public HttpResponseMessage Get(string id)
{
HttpResponseMessage response = null;
using (PubsEntities _context = new PubsEntities())
{
var pubPicture = (from p in _context.pub_info
where p.pub_id == id
select p.logo).FirstOrDefault();
if (pubPicture==null)
{
response = new HttpResponseMessage(HttpStatusCode.NotFound);
}
else
{
MemoryStream ms = new MemoryStream(pubPicture);
response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(ms);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
}
}
return response;
}
}
}</pre>
<p><strong>LogosController</strong> sınıfı içerisinde iki adet <strong>Get</strong> metodu bulunmaktadır. İstemci tarafından gelecek <strong>HTTP</strong> <strong>Get</strong> taleplerine cevap verecek olan bu fonksiyonlardan birisi <strong>pub_info</strong> tablosundaki <strong>pub_id</strong> alanlarını liste olarak döndürmektedir. Diğer yandan senaryomuzun can alıcı <strong>Get</strong> metodu ise, <strong>HttpResponseMessage</strong> tipinden bir nesne örneğini döndürmektedir. Bu metod parametre olarak <strong>string</strong> tipinden olan bir <strong>pub_id</strong> değerini alır. İlgili alana eş satırın <strong>logo</strong> içeriğini bulur<em>(eğer varsa)</em>. Bu içeriğin <strong>byte[]</strong> tipinden olan karşılığı bir <strong>MemoryStream </strong>referansından yararlanılarak <strong>HttpResponseMessage</strong> örneğinin <strong>Content</strong> özelliğine set edilir.</p>
<p>Bundan sonra yapılması gereken, istemciye dönecek cevap içeriğinin bir <strong>image</strong> olduğunu belirtmektir. <strong>Headers.</strong><strong>ContentType</strong> özelliğine bir <strong>MediaTypeHeaderValue</strong> örneğinin atanmasının ve parametre olarak <strong>image/png</strong> verilmesinin sebebi de budur. Çok doğal olarak ilgili <strong>id</strong> değeri yanlış girilebilir ve <strong>LINQ</strong> sorgusu bu durumda <strong>null</strong> değer üretebilir. <strong>Null</strong> değer kontrolü yapılarak böyle bir vaka oluşması halinde<strong> HTTP 404 Not Found</strong> istisnasının döndürülmesi de sağlanmaktadır<em>(Web’ in doğasına ve isteğine uygun şekilde <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-style: none;" src="/pics/wlEmoticon-winkingsmile_210.png" alt="Winking smile" /> )</em></p>
<h1>Testler</h1>
<p>Uygulama kodunun tamamlanmasını müteakip test çalışmalarına başlanabilir. Her hangi bir tarayıcı uygulama ile bu işlemi yapabiliriz<em>(Ben tercihimi Google Chrome’ dan yana kullandım <img class="wlEmoticon wlEmoticon-smile" style="border-style: none;" src="/pics/wlEmoticon-smile_100.png" alt="Smile" /> )</em> Örneğin <strong>api/logos</strong> şeklinde bir talepte bulunulduğunda aşağıdaki ekran görüntüsüne benzer olacak şekilde <strong>pub_id</strong> bilgilerinin elde edildiği görülür.</p>
<p><a><img style="margin: 4px 0px; display: inline;" title="wapigi_8" src="/pics/wapigi_8_thumb.png" alt="wapigi_8" width="578" height="278" /></a></p>
<p>Eğer belirli bir <strong>pub_id</strong> değeri için talepte bulunulursa asıl istediğimiz sonuçlara ulaşırız. Yani yayıncının logosuna <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-style: none;" src="/pics/wlEmoticon-winkingsmile_210.png" alt="Winking smile" /></p>
<p><strong>api/logos/0736</strong> için aşağıdaki sonuç elde edilirken</p>
<p><a><img style="margin: 4px 0px; display: inline;" title="wapigi_9" src="/pics/wapigi_9_thumb.png" alt="wapigi_9" width="364" height="80" /></a></p>
<p><strong>api/logos/1756</strong> için</p>
<p><a><img style="margin: 4px 0px; display: inline;" title="wapigi_10" src="/pics/wapigi_10_thumb.png" alt="wapigi_10" width="358" height="86" /></a></p>
<p>sonucu elde edilir. Çok doğal olarak olmayan bir pub_id için istemci tarafında HTTP 404 hatası dönecektir.</p>
<h1>Daha Neler Yapılabilir ve Size Kalan</h1>
<p>Senaryomuz sadece yayın evinin logosunu ve yayın evi numaralarını döndürecek fonksiyonelliklere sahip bir <strong>Asp.Net</strong> <strong>Web API</strong> hizmetini içermektedir. Ancak siz bu senaryoyu daha da geliştirebilirsiniz.</p>
<ul>
<li>Örneğin <strong>jQuery</strong> kullanarak yayıncıların listesinin logoları ile birlikte bir <strong>View</strong>’ da görünmesini deneyebilirsiniz.</li>
<li>Kuvvetle muhtemel yukarıdaki maddeyi bir <strong>MVC</strong> projesinde denersiniz. Ama aynısını <strong>Web Forms</strong> tabanlı bir uygulama için de yapmaya çalışabilirsiniz.</li>
<li>Resimlerin gösterilmesi haricinde istemcilerin yine <strong>Asp.Net Web API</strong>’ den yararlanarak <strong>upload</strong> etme işlemlerini yapabilmelerini de sağlayabilirsiniz <img class="wlEmoticon wlEmoticon-winkingsmile" style="border-style: none;" src="/pics/wlEmoticon-winkingsmile_210.png" alt="Winking smile" /> Bunu bir araştırmanızı öneririm. <strong>POST</strong> şeklinde bir talebi ele almanız gerektiğini ip ucu olarak verebilirim.</li>
<li>Bir önceki maddede var olan kısmı birden fazla dosyayı bir seferde yükleme senaryosu için ele alabilirsiniz(Multiple Upload)</li>
<li>Büyük boyutlu resimleri parça parça atmayı veya okumayı deneyebilirsiniz.</li>
<li>ve benim aklıma gelmeyen ama sizin ele alacağınız başka bir senaryo da söz konusu olabilir.</li>
</ul>
<p>Görüldüğü üzere bir <strong>Asp.Net Web API</strong> servisini resim içeriklerinin elde edilmesini konu alan bir senaryo da kullanabildik. Örneğimizi yeni göz bebeğimiz <strong>Visual Studio 2013 Preview</strong> üzerinde geliştirmeye çalıştık ve böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2013-07-02T13:08:00+00:00asp.net web apihttp getweb apigetimage formathttpresponsemessagehttpstatuscode404bsenyurtBu yazımızda Asp.Net Web API üzerinden, SQL tablolarında binary formatta tutulabilen resim içeriklerini nasıl çekebileceğimizi basit bir örnek ile incelemeye çalışıyor olacağız. Örneğimizin özel yanlarından birisi de kısa süre önce yayınlanan Visual Studio 2013 Preview ile geliştirilecek olması. Önce senaryomuza bir bakalım...https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=c0ea6323-68ef-4dcb-bd01-7fbd716fad118https://buraksenyurt.com/trackback.axd?id=c0ea6323-68ef-4dcb-bd01-7fbd716fad11https://buraksenyurt.com/post/AspNet-Web-API-Uzerinden-Resim-Dondurmek#commenthttps://buraksenyurt.com/syndication.axd?post=c0ea6323-68ef-4dcb-bd01-7fbd716fad11