C#

C# dilinde Blazor ile WebAssembly Uygulamaları

Blazor WebAssembly, geliştiricilere C# dilini kullanarak tarayıcıda doğrudan çalışabilen güçlü tek sayfa uygulamaları (SPA) geliştirme imkanı sunar. Bu yenilikçi teknoloji, .NET ekosisteminin tüm gücünü istemci tarafına taşıyarak JavaScript’e olan bağımlılığı önemli ölçüde azaltır. Performanslı, güvenli ve yeniden kullanılabilir kod tabanları oluşturmayı hedefleyen Blazor WebAssembly, modern web geliştirmenin yeni ufuklarını açmaktadır.

Blazor WebAssembly Nedir ve Nasıl Çalışır?

Blazor WebAssembly (WASM), Microsoft tarafından geliştirilen ve C# kodunun tarayıcıda WebAssembly standardı aracılığıyla doğrudan çalışmasını sağlayan bir istemci tarafı UI çerçevesidir. Geleneksel web uygulamalarının aksine, Blazor WebAssembly uygulamaları istemci tarayıcısında bir .NET çalışma zamanı indirir ve uygulama kodunu bu çalışma zamanı içinde yürütür. Bu, JavaScript ile karmaşık etkileşimler yazmaya gerek kalmadan tüm uygulama mantığının C# dilinde yazılmasına olanak tanır.

Uygulama başlatıldığında, tarayıcı .NET çalışma zamanını (genellikle küçük bir boyutla sıkıştırılmış olarak) ve uygulamanızın derlenmiş C# kodunu (DLL dosyaları WASM formatına dönüştürülmüş veya AOT ile tamamen WASM’e derlenmiş) indirir. Bu WebAssembly ikilileri, tarayıcının güvenli “sandbox” ortamında neredeyse yerel uygulama hızında çalışır. Blazor, DOM manipülasyonları için JavaScript birlikte çalışma (interop) özelliğini kullanır, ancak bu işlemler çerçeve tarafından soyutlandığı için geliştiricilerin çoğu zaman doğrudan JavaScript yazmasına gerek kalmaz. Bu mimari, mevcut .NET kütüphanelerinin ve araçlarının istemci tarafında kullanılabilmesini sağlar.

C# ile İstemci Tarafı Geliştirmenin Avantajları

Blazor WebAssembly’nin sunduğu başlıca avantajlar, modern web uygulaması geliştirme süreçlerini kökten değiştirmektedir:

  • Tam Yığın .NET Deneyimi

    Geliştiriciler, hem arka uç (ASP.NET Core) hem de ön uç (Blazor WebAssembly) için aynı dili (C#), çerçeveyi ve araçları kullanarak tam yığın .NET uygulamaları oluşturabilirler. Bu, kod paylaşımını, geliştirme verimliliğini ve öğrenme eğrisini önemli ölçüde iyileştirir. Örneğin, bir API için tanımlanmış veri modelleri (DTO’lar) doğrudan istemci tarafında da kullanılabilir.

  • Performans

    WebAssembly, düşük seviyeli bir ara dil olduğu için JavaScript’e kıyasla genellikle daha yüksek performans sunar. Ağır işlem gerektiren görevler veya karmaşık kullanıcı arayüzleri için Blazor WebAssembly, neredeyse yerel uygulama performansı sağlayabilir. .NET 6 ve sonraki sürümlerle birlikte gelen AOT (Ahead-Of-Time) derlemesi, uygulama kodunun tarayıcıda çalışmadan önce WebAssembly’ye tamamen derlenmesini sağlayarak bu performansı daha da artırır.

  • Güvenlik

    WebAssembly, tarayıcının yerleşik güvenlik “sandbox” ortamında çalıştığı için güvenli bir yürütme ortamı sunar. C# kodunun derlenmiş ikililer halinde tarayıcıda çalışması, kaynak kodun direkt olarak görüntülenmesini veya kolayca manipüle edilmesini zorlaştırır. Bu durum, fikri mülkiyetin korunmasına da yardımcı olabilir.

  • Yeniden Kullanılabilirlik ve Bakım Kolaylığı

    C# ile yazılan bileşenler ve iş mantığı, sadece Blazor WebAssembly uygulamalarında değil, aynı zamanda sunucu tarafı Blazor Server uygulamalarında, masaüstü uygulamalarında (.NET MAUI) veya diğer .NET projelerinde de yeniden kullanılabilir. Bu, kod tekrarını azaltır, tutarlılığı artırır ve büyük ölçekli projelerin bakımını önemli ölçüde kolaylaştırır.

  • Zengin Geliştirme Ortamı

    Visual Studio, Visual Studio Code gibi güçlü IDE’ler, Blazor WebAssembly geliştirme için kapsamlı hata ayıklama, IntelliSense, kod analizi ve refactoring yetenekleri sunar. Bu, C# geliştiricilerinin alıştığı üretken ve zengin geliştirme deneyimini web tarafına da taşır.

Blazor WebAssembly ile Uygulama Geliştirme Süreci

Bir Blazor WebAssembly uygulaması genellikle Razor bileşenleri (.razor uzantılı dosyalar) şeklinde geliştirilir. Bu bileşenler, HTML işaretlemesini, C# kodunu ve CSS’i tek bir dosyada birleştirerek modüler ve okunabilir bir yapı sunar. Her bir bileşen, uygulamanın bir bölümünü veya bir UI öğesini temsil edebilir.

Örneğin, bir Blazor uygulamasında bir veri tablosu oluşturmak istediğinizde, tablo için bir Razor bileşeni yazarsınız. Bu bileşen içinde, C# kodu kullanarak bir API’den verileri asenkron olarak çeker ve bu verileri HTML tablo satırlarına bağlarsınız. Kullanıcı arayüzünde bir düğmeye tıklandığında veya bir metin kutusuna giriş yapıldığında, bu olaylar C# tarafındaki metodlar tarafından işlenir ve UI, Blazor’ın dahili diffing mekanizması sayesinde otomatik olarak güncellenir. Blazor, veri bağlama (data binding), olay işleme (event handling), bağımlılık enjeksiyonu (dependency injection) ve yönlendirme (routing) gibi modern SPA çerçevelerinin tüm temel özelliklerini doğal bir şekilde sunar.

Bir e-ticaret uygulamasında ürün detay sayfasını ele alalım. Bu sayfa için bir ProductDetail.razor bileşeni oluşturulur. Bileşen yüklendiğinde, C# kodu içindeki OnInitializedAsync metodu tetiklenir ve HttpClient servisi kullanılarak ürün ID’sine göre arka uçtan ürün verileri (adı, açıklaması, fiyatı, resimleri vb.) çekilir. Çekilen veriler, bileşenin C# kodu içindeki özelliklere atanır ve bu özellikler, Razor işaretlemesinde (örneğin, <h1>@product.Name</h1>) kullanılarak ürün detayları kullanıcıya gösterilir. Kullanıcı sepete ekle düğmesine tıkladığında, ilgili C# metodu çağrılır, ürün sepet objesine eklenir ve UI’daki sepet sayacı güncellenir; tüm bunlar tarayıcıda, sayfa yenilenmeden gerçekleşir.

Blazor WebAssembly’nin Kullanım Alanları ve Geleceği

Blazor WebAssembly, karmaşık kurumsal iş uygulamaları, PWA’lar (Progressive Web Apps), veri yoğun dashboard’lar, finansal uygulamalar, hatta bazı oyunlar ve IoT cihazları için kontrol panelleri gibi geniş bir yelpazede kullanılabilir. Özellikle mevcut .NET altyapısına sahip şirketler için ön yüz geliştirme süreçlerini önemli ölçüde basitleştiren ve tek bir geliştirme ekibiyle hem arka ucu hem de ön ucu yönetme imkanı sunan güçlü bir araçtır.

Microsoft, Blazor’u aktif olarak geliştirmeye devam etmekte olup, sürekli yeni özellikler eklemektedir. WebAssembly standardının evrimi (örneğin, WebAssembly System Interface – WASI gibi masaüstü entegrasyonları) Blazor’ın yeteneklerini daha da genişletecektir. Gelecekte .NET MAUI ile entegrasyonu sayesinde Blazor kod tabanlarının hem web, hem masaüstü hem de mobil uygulamalarda daha da yaygınlaşması beklenmektedir, bu da “bir kez yaz, her yerde çalıştır” felsefesini gerçek kılar.

Dezavantajlar ve Dikkat Edilmesi Gerekenler

  • Başlangıç Yükleme Boyutu

    Blazor WebAssembly uygulamaları, ilk yüklemede .NET çalışma zamanını ve uygulamanın derlenmiş DLL’lerini indirmesi gerektiği için JavaScript tabanlı muadillerine göre daha büyük bir başlangıç dosya boyutuna sahip olabilir. Ancak, .NET ekibinin yaptığı optimizasyonlar ve AOT derlemesi ile bu boyut önemli ölçüde azaltılmıştır.

  • Hata Ayıklama

    Tarayıcıda WebAssembly kodu üzerinde hata ayıklama, geleneksel JavaScript hata ayıklamasına göre biraz daha karmaşık olabilir. Ancak Visual Studio ve Visual Studio Code gibi modern IDE’ler, tarayıcıda çalışan C# kodunda kapsamlı hata ayıklama yetenekleri sunarak bu deneyimi sürekli olarak iyileştirmektedir.

  • Tarayıcı Desteği

    Günümüzdeki modern tüm tarayıcılar (Chrome, Firefox, Edge, Safari vb.) WebAssembly’yi desteklemektedir. Ancak çok eski tarayıcı versiyonlarında veya nadir kullanılan bazı ortamlarda uyumluluk sorunları yaşanabilir.

Blazor WebAssembly, C# geliştiricilerine tarayıcıda güçlü, performanslı ve güvenli web uygulamaları oluşturma olanağı sunan çığır açıcı bir teknolojidir. .NET ekosisteminin tüm avantajlarını istemci tarafına taşıyarak tam yığın geliştirme deneyimini birleştirir. Sürekli geliştirilen bu platform, JavaScript bağımlılığını azaltıp kod yeniden kullanımını artırarak modern web geliştirmenin geleceğinde önemli bir yer edinmektedir. Geliştiriciler için Blazor WebAssembly, verimli ve yenilikçi çözümler sunan değerli bir araçtır.


C# dilinde Reflection, Dynamic ve Expression Trees

C# dilinde Reflection, Dynamic ve Expression Trees, yazılımlara esneklik ve genişletilebilirlik kazandıran güçlü mekanizmalardır. Bu teknolojiler, derleme zamanı kısıtlamalarının ötesine geçerek, çalışma zamanında kod analizi, manipülasyonu ve hatta yeni kod oluşturma imkanı sunar. Geliştiricilerin dinamik ve adaptif uygulamalar inşa etmesine olanak tanıyan bu araçlar, karmaşık senaryolarda vazgeçilmez birer çözüm haline gelmiştir. Şimdi bu kavramları daha derinlemesine inceleyelim.

C# Reflection (Yansıma)

Reflection, C# uygulamalarının çalışma zamanında kendi kendilerini veya diğer kodları incelemesini sağlayan bir yetenektir. Bu, bir assembly içindeki tipleri (sınıflar, arayüzler, yapılar, enum’lar), üyeleri (metotlar, özellikler, alanlar, olaylar) ve bunların niteliklerini (erişim belirleyicileri, imzaları) keşfetmeyi mümkün kılar. Reflection sayesinde, compile-time anında bilinmeyen tipler veya üyelerle etkileşim kurmak mümkündür.

Reflection ile Neler Yapılabilir?

  • Tip Bilgisi Elde Etme: Type.GetType(), typeof() veya bir nesne üzerindeki GetType() metodu ile tipler hakkında bilgi alabilirsiniz.
  • Üye Keşfi ve Çağırma: Bir tipin metotlarını, özelliklerini veya alanlarını GetMethods(), GetProperties(), GetFields() gibi metotlarla keşfedebilir, ardından MethodInfo.Invoke() veya PropertyInfo.SetValue() gibi metotlarla dinamik olarak çağırabilir veya ayarlayabilirsiniz.
  • Örnek Oluşturma: Activator.CreateInstance() kullanarak bir tipin varsayılan veya belirli bir kurucuyu çağırarak örneklerini oluşturabilirsiniz.
  • Nitelik (Attribute) İşleme: Kod üzerindeki nitelikleri (örneğin [Serializable], [JsonProperty]) çalışma zamanında okuyup işleyebilirsiniz. Bu, konfigürasyon veya meta veri odaklı framework’ler için kritik öneme sahiptir.
  • Plug-in Mimarileri: Bir uygulamanın, compile-time’da mevcut olmayan assembly’leri yükleyip içindeki tipleri ve metotları keşfederek genişlemesini sağlar.
  • Serialization/Deserialization: JSON.NET gibi kütüphaneler, nesneleri serileştirmek veya serisini çözmek için tiplerin özelliklerini ve alanlarını Reflection kullanarak keşfeder.

Örnek Kullanım


public class MyClass
{
    public string Name { get; set; }
    public void DisplayMessage(string message)
    {
        Console.WriteLine($"MyClass says: {message}, Name: {Name}");
    }
}

// Reflection ile metot çağırma
Type type = typeof(MyClass);
object instance = Activator.CreateInstance(type); // MyClass örneği oluştur
PropertyInfo nameProperty = type.GetProperty("Name");
nameProperty.SetValue(instance, "Reflection Example"); // Name özelliğini ayarla

MethodInfo displayMethod = type.GetMethod("DisplayMessage");
displayMethod.Invoke(instance, new object[] { "Hello from Reflection!" }); // Metodu çağır

Avantajları ve Dezavantajları

Avantajları: Yüksek esneklik, genişletilebilirlik, meta veri işleme, plug-in mimarileri için temel oluşturma.

Dezavantajları: Performans maliyeti (özellikle sıkça tekrar eden çağrılarda), derleme zamanı tipi denetiminin olmaması nedeniyle potansiyel hatalar, kodun okunabilirliğinin azalması.

C# Dynamic (Dinamik)

C# 4.0 ile birlikte tanıtılan dynamic anahtar kelimesi, çalışma zamanında tipi belirlenecek değişkenler oluşturmaya olanak tanır. Bu, derleyiciye bir ifadenin tip denetimini atlamasını ve bunun yerine çalışma zamanında belirlemesini söyler. Dinamik tipler, genellikle Reflection’a göre daha sade bir sözdizimi sunar ve COM birlikte çalışabilirliği, dinamik dillerle entegrasyon veya Reflection’ın daha az güvenli olduğu senaryolarda kullanılır.

Dynamic Nasıl Çalışır?

Bir değişkeni dynamic olarak tanımladığınızda, derleyici bu değişken üzerinde yapılan tüm işlemleri (metot çağrıları, özellik erişimi, operatör kullanımları) çalışma zamanına erteler. Çalışma zamanında, .NET’in Dinamik Dil Çalışma Zamanı (DLR – Dynamic Language Runtime) bu işlemleri çözmeye çalışır. Eğer işlem geçerliyse yürütülür, aksi takdirde bir çalışma zamanı hatası fırlatılır.

Dynamic Kullanım Alanları

  • COM Birlikte Çalışabilirliği: Microsoft Office otomasyonu gibi COM nesneleriyle daha kolay etkileşim kurmak için.
  • Dinamik Dillerle Entegrasyon: IronPython veya IronRuby gibi DLR tabanlı dillerle C# kodu arasında geçiş yaparken.
  • Reflection’a Basit Alternatif: Belirli senaryolarda Reflection’ın karmaşık API’lerini kullanmak yerine daha okunabilir bir yol sunar. Örneğin, bir nesnenin varlığını bilmediğiniz bir özelliğine veya metoduna erişmek istediğinizde.
  • ExpandoObject Kullanımı: Çalışma zamanında özellikler ve metotlar ekleyip kaldırabileceğiniz dinamik nesneler oluşturmak için.

Örnek Kullanım


// Dynamic ile özellik ve metot çağırma
dynamic myDynamicObject = new MyClass { Name = "Dynamic Example" };
myDynamicObject.DisplayMessage("Hello from Dynamic!");

// Dynamic ile ExpandoObject kullanımı
dynamic expando = new System.Dynamic.ExpandoObject();
expando.FirstName = "John";
expando.LastName = "Doe";
expando.SayHello = (Action)(() => Console.WriteLine($"Hello, {expando.FirstName} {expando.LastName}!"));

Console.WriteLine($"{expando.FirstName} {expando.LastName}");
expando.SayHello();

Avantajları ve Dezavantajları

Avantajları: Basit sözdizimi, Reflection’a göre bazı senaryolarda daha az kod gereksinimi, COM ve dinamik dil entegrasyonunu kolaylaştırma.

Dezavantajları: Derleme zamanı tipi denetiminin olmaması nedeniyle hata yakalama güçlüğü, çalışma zamanı hatalarına yatkınlık, performans maliyeti (genellikle Reflection’dan daha iyi optimize edilmiş olsa da statik koddan yavaş).

C# Expression Trees (İfade Ağaçları)

Expression Trees, lambda ifadeleri ve diğer kod bloklarını, çalışma zamanında incelenebilecek, değiştirilebilecek ve yürütülebilecek bir veri yapısı olarak temsil eder. Bir Expression Tree, kodu metinsel bir biçimde değil, programatik olarak inşa edilebilen bir ağaç yapısı olarak sunar. Her düğüm (node) bir işlemi (örneğin toplama, metot çağırma, parametre) veya bir sabiti temsil eder.

Expression Trees Nasıl Oluşturulur ve Kullanılır?

Bir Expression Tree’yi iki ana yolla oluşturabilirsiniz:

  1. Lambda İfadelerini Dönüştürme: Bir lambda ifadesini Expression<TDelegate> tipine atayarak. Bu, derleyicinin ifadeyi otomatik olarak bir ağaca dönüştürmesini sağlar.
  2. API Kullanımı: System.Linq.Expressions.Expression sınıfının statik metotlarını (Expression.Add, Expression.Call, Expression.Constant, Expression.Parameter vb.) kullanarak ağacı programatik olarak inşa etmek.

Oluşturulan Expression Tree, daha sonra Compile() metodu ile yürütülebilir bir delegeye dönüştürülebilir ve bu delege çağrılabilir.

Expression Trees Kullanım Alanları

  • LINQ Sağlayıcıları: LINQ to SQL, Entity Framework gibi kütüphaneler, lambda ifadelerini SQL sorgularına çevirmek için Expression Trees kullanır. Bu, geliştiricilerin C# koduyla veritabanı sorguları yazmasına olanak tanır.
  • Dinamik Sorgu Oluşturma: Karmaşık filtreleme, sıralama veya gruplama mantığını çalışma zamanında programatik olarak oluşturmak ve uygulamak.
  • Kod Oluşturma: Çalışma zamanında dinamik olarak metotlar oluşturmak ve yürütmek (örneğin, proxy nesneler veya performans kritik senaryolar için).
  • Serileştirme: Belirli senaryolarda kodun kendisini serileştirip farklı bir ortamda tekrar canlandırmak için (örneğin, sunucudan istemciye iş mantığı göndermek).
  • AOP (Aspect-Oriented Programming): Yöntem çağrılarını veya özellik erişimlerini kesmek ve ek mantık enjekte etmek için.

Örnek Kullanım


using System.Linq.Expressions;

// 1. Lambda ifadesinden Expression Tree oluşturma
Expression<Func<int, int, int>> addExpression = (a, b) => a + b;
Console.WriteLine($"Expression Tree: {addExpression}");

// Expression Tree'yi derleyip çalıştırma
Func<int, int, int> addFunc = addExpression.Compile();
Console.WriteLine($"Result: {addFunc(5, 3)}"); // Output: 8

// 2. Programatik olarak Expression Tree oluşturma
ParameterExpression paramA = Expression.Parameter(typeof(int), "a");
ParameterExpression paramB = Expression.Parameter(typeof(int), "b");
BinaryExpression sum = Expression.Add(paramA, paramB);
Expression<Func<int, int, int>> manualAddExpression = Expression.Lambda<Func<int, int, int>>(sum, paramA, paramB);

Console.WriteLine($"Manual Expression Tree: {manualAddExpression}");
Func<int, int, int> manualAddFunc = manualAddExpression.Compile();
Console.WriteLine($"Manual Result: {manualAddFunc(10, 7)}"); // Output: 17

Avantajları ve Dezavantajları

Avantajları: Çalışma zamanında kod manipülasyonu ve oluşturma yeteneği, LINQ sağlayıcıları için temel oluşturma, Reflection’a göre genellikle daha yüksek performans (derlendikten sonra), güçlü ve esnek dinamik sorgulama yetenekleri.

Dezavantajları: Karmaşık API, öğrenme eğrisi yüksek, hata ayıklama zorlukları, doğru kullanılmadığında performans sorunları yaratabilir.

Sonuç

C# dilindeki Reflection, Dynamic ve Expression Trees, her biri farklı ihtiyaçlara hitap eden güçlü araçlardır. Reflection, çalışma zamanında kod hakkında bilgi edinmek ve üyeleri dinamik olarak çağırmak için idealdir. Dynamic anahtar kelimesi, özellikle COM entegrasyonu ve basit dinamik senaryolarda Reflection’dan daha sade bir sözdizimi sunar. Expression Trees ise, kodu veri olarak temsil ederek LINQ sağlayıcıları ve dinamik kod üretimi gibi karmaşık senaryolarda eşsiz esneklik ve performans sağlar. Bu teknolojileri doğru bağlamda kullanmak, daha adaptif, genişletilebilir ve performanslı C# uygulamaları geliştirmenin anahtarıdır.


C# dilinde Gelişmiş Güvenlik ve Performans

C# programlama dili, Microsoft’un .NET ekosisteminin temelini oluşturarak geniş bir uygulama yelpazesi sunar. Bu dilin esnekliği ve gücü, basit masaüstü uygulamalarından karmaşık kurumsal sistemlere, web servislerinden bulut tabanlı çözümlere kadar birçok alanda tercih edilmesini sağlar. Ancak, modern yazılım geliştirme süreçlerinde yalnızca işlevsellik yeterli değildir; uygulamaların hem saldırılara karşı dirençli hem de optimum hızda çalışması kritik öneme sahiptir. Bu bağlamda, C# uygulamalarında gelişmiş güvenlik ve performans stratejilerini anlamak ve uygulamak, başarılı projeler için vazgeçilmezdir.

C# Uygulamalarında Gelişmiş Güvenlik

Güvenlik, bir yazılım ürününün yaşam döngüsünün her aşamasında dikkate alınması gereken birincil önceliktir. C# ve .NET platformu, güçlü güvenlik mekanizmaları sunsa da, bu mekanizmaların doğru bir şekilde kullanılması ve geliştiricilerin güvenli kodlama pratiklerine uyması hayati önem taşır.

Güvenli Kodlama Pratikleri

  • Girdi Doğrulama ve Dezenfeksiyon: Kullanıcıdan veya harici kaynaklardan alınan tüm veriler güvenilmez kabul edilmeli ve titizlikle doğrulanmalıdır. SQL enjeksiyonu, XSS (Cross-Site Scripting) ve yol geçişi gibi yaygın saldırı türlerini önlemek için özel karakterler filtrelenmeli, veri tipleri kontrol edilmeli ve uzunluk sınırlamaları uygulanmalıdır. Örneğin, bir veritabanı sorgusu oluştururken parametreli sorgular kullanmak, SQL enjeksiyonuna karşı en etkili yöntemdir.
  • Kimlik Doğrulama ve Yetkilendirme: Kullanıcıların kimliklerini güvenli bir şekilde doğrulamak (örn. ASP.NET Core Identity, OAuth 2.0, JWT) ve ardından yalnızca yetkili oldukları kaynaklara erişimlerini sağlamak gereklidir. Yetkilendirme mekanizmaları, en az ayrıcalık ilkesine uygun olarak tasarlanmalı ve rollere veya politikalara dayalı erişim kontrolü kullanılmalıdır.
  • Hassas Veri Yönetimi: Şifreler, API anahtarları, kişisel veriler gibi hassas bilgiler asla düz metin olarak saklanmamalıdır. Veritabanında depolanan şifreler için tuzlama (salting) ile birlikte güçlü tek yönlü hash algoritmaları (örn. SHA-256 veya daha iyisi bcrypt, Argon2) kullanılmalıdır. Uygulama yapılandırma dosyalarında (appsettings.json) hassas bilgiler doğrudan bulundurulmamalı, bunun yerine Azure Key Vault, AWS Secrets Manager gibi gizli yönetim hizmetleri veya .NET Core’un kendi Secret Manager aracı kullanılmalıdır.
  • Hata Yönetimi ve Loglama: Üretim ortamında detaylı hata mesajlarının kullanıcıya gösterilmesi potansiyel güvenlik açıkları yaratabilir. Genel, kullanıcı dostu hata mesajları sunulmalı ve ayrıntılı hata bilgileri güvenli bir şekilde loglanmalıdır. Loglama sistemleri, hassas bilgileri loglamaktan kaçınmalı ve log dosyaları yetkisiz erişime karşı korunmalıdır.

Güvenlik Mekanizmaları ve Araçları

  • Kriptografi Kullanımı: .NET’in System.Security.Cryptography namespace’i, şifreleme, hashing ve dijital imzalama gibi güçlü kriptografik yetenekler sunar. Veri aktarımı (HTTPS/TLS) ve depolama için AES gibi simetrik şifreleme algoritmaları veya RSA gibi asimetrik algoritmalar dikkatli bir şekilde kullanılmalıdır. Anahtar yönetimi (key management) kriptografinin en kritik yönüdür.
  • Bağımlılık Güvenliği: Birçok modern uygulama, NuGet paketleri aracılığıyla üçüncü taraf kütüphanelere bağımlıdır. Bu bağımlılıkların bilinen güvenlik açıklarını içerip içermediği düzenli olarak kontrol edilmelidir. `Snyk` veya `WhiteSource` gibi araçlar, bağımlılık taraması yaparak potansiyel riskleri belirleyebilir.
  • Statik ve Dinamik Kod Analizi: SAST (Static Application Security Testing) araçları, kodu derlemeden önce güvenlik açıklarını bulmaya yardımcı olur (örn. Roslyn analizörleri, SonarQube). DAST (Dynamic Application Security Testing) araçları ise çalışan uygulamayı test ederek çalışma zamanı güvenlik açıklarını (örn. OWASP ZAP, Burp Suite) tespit eder. Bu araçların düzenli kullanımı, geliştirme sürecinin erken aşamalarında zafiyetleri yakalamak için önemlidir.
  • Minimum Yetki Prensibi: Uygulamaların ve servislerin çalıştığı hesaplara veya kimliklere yalnızca işlerini yapabilmeleri için gerekli olan en az ayrıcalıklar atanmalıdır. Bu, bir güvenlik ihlali durumunda potansiyel hasarı sınırlar.

C# Uygulamalarında Gelişmiş Performans

Performans, bir uygulamanın tepkiselliğini, kaynak kullanımını ve kullanıcı deneyimini doğrudan etkileyen kritik bir faktördür. C# ve .NET platformu, Just-In-Time (JIT) derlemesi, çöp toplama (garbage collection) ve gelişmiş kütüphaneler sayesinde yüksek performans potansiyeli sunar. Ancak, bu potansiyeli tam olarak kullanmak için belirli optimizasyon tekniklerinin bilinmesi ve uygulanması gerekir.

Bellek Yönetimi ve Çöp Toplama (Garbage Collection – GC)

  • Değer Türleri ve Referans Türleri: C#’ta değer türleri (struct, enum) genellikle yığında (stack) depolanırken, referans türleri (class, interface, delegate) yığında (heap) depolanır. Yığın bellek tahsisleri ve GC işlemleri daha pahalıdır. Küçük, değişmez veri yapıları için değer türlerini kullanmak, yığın tahsisini azaltarak GC yükünü düşürebilir.
  • IDisposable ve using Bildirimi: Dosya kulpları, ağ bağlantıları veya veritabanı bağlantıları gibi yönetilmeyen kaynakların doğru bir şekilde serbest bırakılması için IDisposable arayüzünü uygulamak ve bu kaynakları using bildirimi içinde kullanmak önemlidir. Bu, kaynak sızıntılarını önler ve sistem kaynaklarının verimli kullanımını sağlar.
  • Büyük Nesne Yığını (Large Object Heap – LOH): 85 KB’tan büyük nesneler LOH’a tahsis edilir. LOH’taki parçalanma (fragmentation) ve temizleme işlemleri daha maliyetli olabilir. Büyük array’ler veya koleksiyonlarla çalışırken ArrayPool gibi havuzlama mekanizmalarını kullanarak nesne tahsisini azaltmak performansı önemli ölçüde artırabilir.
  • Nesne Havuzlama: Sıkça oluşturulan ve yok edilen pahalı nesneler için nesne havuzlama (object pooling) tekniği kullanılabilir. Bu, nesnelerin sürekli olarak tahsis edilip serbest bırakılması yerine yeniden kullanılmasını sağlar, böylece GC yükü azalır.

Asenkron Programlama ve Paralellik

  • async/await Kullanımı: G/Ç yoğun (I/O-bound) işlemler (veritabanı sorguları, ağ istekleri, dosya işlemleri) için async ve await anahtar kelimelerini kullanmak uygulamanın tepkiselliğini artırır. Bu, ana iş parçacığını (main thread) bloke etmeden arka planda işlemlerin yürütülmesini sağlar, özellikle web sunucuları ve UI uygulamaları için kritiktir.
  • Görev Paralel Kütüphanesi (Task Parallel Library – TPL): CPU yoğun (CPU-bound) işlemler için Parallel.For, Parallel.ForEach veya Task.Run gibi TPL yapılarını kullanarak birden fazla işlemci çekirdeğinden faydalanılabilir. Bu, ağır hesaplamaların daha hızlı tamamlanmasını sağlar, ancak iş parçacığı yönetimi (thread management) ve senkronizasyon maliyetlerini de beraberinde getirebilir.

Veri Yapıları ve Algoritmalar

  • Doğru Koleksiyon Seçimi: Her veri yapısının (List, Dictionary, HashSet, Queue vb.) farklı performans karakteristikleri vardır. Erişim, ekleme, silme ve arama işlemleri için doğru koleksiyonu seçmek kritik öneme sahiptir. Örneğin, sık arama yapılan koleksiyonlar için Dictionary genellikle List‘den daha hızlıdır.
  • LINQ Sorgularının Optimizasyonu: LINQ sorguları güçlüdür ancak yanlış kullanıldığında performans sorunlarına yol açabilir. Mümkün olduğunca sorgu sonuçlarını önbelleğe almak (`.ToList()`, `.ToArray()`) veya sorguları tembel (lazy) bir şekilde değerlendirmek için IQueryable kullanmak performansı etkiler. Aşırı Join veya Select işlemleri performansı düşürebilir; veritabanı tarafında bu işlemleri optimize etmek genellikle daha iyidir.

JIT Derlemesi ve Mikro Optimizasyonlar

  • Span ve Memory Kullanımı: .NET Core ve .NET 5+ ile tanıtılan Span ve Memory, bellekteki sıralı veri bloklarına düşük maliyetli, güvenli ve performansı yüksek erişim sağlar. Bu yapılar, özellikle büyük veri tamponları üzerinde işlem yaparken bellek tahsisini ve kopyalama işlemlerini en aza indirerek ciddi performans artışları sunar.
  • Değişmezlik (Immutability): Değişmez (immutable) nesneler, iş parçacığı güvenliği sağlar ve daha az yan etki yaratır. C# 9+ ile gelen record türleri, değişmezlik kavramını daha kolay uygulanabilir hale getirir.
  • Profilleme ve Ölçümleme: Performans sorunlarını tespit etmenin en etkili yolu, uygulamanın davranışını profillemek (örn. Visual Studio Profiler, dotTrace). Bottleneck’leri belirlemek ve optimize etmek için kodun belirli bölümlerinin çalışma zamanını ölçmek (benchmarking, örn. BenchmarkDotNet kütüphanesi) gereklidir.
  • Native AOT: .NET 7 ile tanıtılan Native AOT (Ahead-of-Time) derlemesi, uygulamaların çalışma zamanı yerine derleme zamanında doğrudan makine koduna derlenmesini sağlar. Bu, daha hızlı başlangıç süreleri ve daha düşük bellek tüketimi ile sonuçlanabilir, ancak belirli kısıtlamaları ve daha büyük ikili dosya boyutlarını da beraberinde getirebilir.

C# dilinde geliştirilen uygulamaların hem güvenli hem de yüksek performanslı olması, modern yazılım projelerinin başarısı için temel gerekliliklerdir. Güvenli kodlama pratiklerini benimsemek, giriş doğrulamasından kimlik doğrulamaya, hassas veri yönetiminden güvenlik açıklarını taramaya kadar geniş bir yelpazeyi kapsar. Aynı şekilde, bellek yönetimi, asenkron programlama, doğru veri yapısı seçimi ve mikro optimizasyonlar gibi performans iyileştirme teknikleri, uygulamanın kaynakları verimli kullanmasını ve hızlı yanıt vermesini sağlar. Bu iki alanı entegre bir yaklaşımla ele almak, güvenilir, hızlı ve ölçeklenebilir C# uygulamaları geliştirmek için vazgeçilmezdir.


C# dilinde Microservice Mimarisi Giriş

Microservice mimarisi, büyük ve karmaşık uygulamaları bağımsız, küçük ve birbirine bağlı servisler halinde parçalama yaklaşımıdır. C# ve .NET ekosistemi, bu modern mimariyi uygulamak için güçlü araçlar sunar. Ölçeklenebilirlik, esneklik ve hızlı geliştirme süreçleri sağlayan mikroservisler, günümüzün yazılım dünyasında kritik bir rol oynamaktadır. Bu makale, C# ile mikroservis mimarisinin temel prensiplerine bir giriş sunacaktır.

Mikroservis Mimarisi Nedir?

Mikroservis mimarisi, bir uygulamanın bağımsız olarak geliştirilebilen, konuşlandırılabilen ve ölçeklenebilen küçük servisler kümesi olarak yapılandırıldığı bir yaklaşımdır. Geleneksel monolitik mimarilerin aksine, mikroservisler her bir iş alanına odaklanır ve kendi veritabanı, iş mantığı ve API’sine sahip olabilir. Bu modüler yapı, ekiplerin daha çevik çalışmasına, farklı teknolojiler kullanmasına ve hataların izole edilmesine olanak tanır. Örneğin, bir e-ticaret uygulamasında “Kullanıcı Yönetimi”, “Ürün Kataloğu”, “Sipariş İşleme” ve “Ödeme” gibi her biri ayrı bir mikroservis olabilir. Her servis, sadece kendi sorumluluk alanındaki işi yapar ve diğer servislerle belirli arayüzler (genellikle RESTful API’ler veya mesaj kuyrukları) üzerinden iletişim kurar.

C# ve .NET Ekosisteminin Mikroservisler için Avantajları

C# ve özellikle .NET Core/.NET 5+ platformu, mikroservis mimarisi geliştirmek için oldukça elverişli bir ortam sunar. İşte bu avantajlardan bazıları:

  • Performans ve Verimlilik: .NET Core, yüksek performanslı ve hafif bir çalışma zamanı sunar. Bu, kaynakları daha verimli kullanan ve daha hızlı yanıt süreleri sağlayan mikroservisler oluşturmaya olanak tanır.
  • Platform Bağımsızlığı: .NET Core, Linux, Windows ve macOS üzerinde çalışabilir, bu da mikroservislerin farklı ortamlarda konuşlandırılmasını kolaylaştırır ve esneklik sağlar.
  • Geniş Kütüphane ve Araç Desteği: ASP.NET Core, RESTful API’ler oluşturmak için mükemmel bir çerçevedir. Entity Framework Core gibi ORM’ler, veritabanı etkileşimlerini basitleştirir. Ayrıca, Polly (dayanıklılık), Serilog (loglama), OpenTelemetry (izleme) gibi birçok üçüncü taraf kütüphane de C# ile entegrasyonu kolaylaştırır.
  • Asenkron Programlama Yetenekleri: Async/await anahtar kelimeleri, G/Ç yoğun işlemlerde performans kaybı olmadan asenkron kod yazmayı kolaylaştırır, bu da mikroservislerin eşzamanlı istekleri verimli bir şekilde işlemesine yardımcı olur.
  • Gelişmiş Geliştirici Deneyimi: Visual Studio ve Visual Studio Code gibi IDE’ler, kapsamlı hata ayıklama, kod tamamlama ve geliştirme araçları sunarak geliştirme sürecini hızlandırır.

C# ile Mikroservis Geliştirmenin Temel Bileşenleri

C# ile mikroservis mimarisi oluştururken ele alınması gereken temel bileşenler ve kavramlar şunlardır:

API Gateway

Bir API Gateway, dış dünyadan gelen tüm istekleri alan ve bu istekleri ilgili mikroservislere yönlendiren tek bir giriş noktasıdır. Kimlik doğrulama, yetkilendirme, hız sınırlama, önbellekleme ve istek yönlendirme gibi görevleri üstlenir. Ocelot gibi .NET kütüphaneleri, C# ile kolayca bir API Gateway oluşturmak için kullanılabilir.

Servis Keşfi (Service Discovery)

Mikroservis ortamında, servislerin birbirini nasıl bulacağı kritik bir konudur. Servis keşfi mekanizmaları (örneğin Consul, Eureka), servislerin dinamik olarak kaydedilmesini ve diğer servisler tarafından bulunmasını sağlar. Bir servis başlatıldığında kendini kaydeder, durdurulduğunda kaydını siler.

İletişim Mekanizmaları

Servisler arası iletişim, mikroservis mimarisinin temelini oluşturur. C#’ta yaygın kullanılan yöntemler şunlardır:

  • HTTP/REST: En yaygın iletişim şeklidir. Her servis, diğer servislerin çağırabileceği RESTful API’ler sunar. ASP.NET Core Web API, bu tür servisleri oluşturmak için idealdir.
  • gRPC: Yüksek performanslı, protokolle dayalı bir RPC (Remote Procedure Call) çerçevesidir. Büyük veri aktarımlarında ve düşük gecikme gerektiren durumlarda REST’e göre daha avantajlı olabilir. .NET platformu gRPC’yi yerel olarak destekler.
  • Mesaj Kuyrukları: Asenkron iletişim için kullanılır. RabbitMQ, Kafka veya Azure Service Bus gibi mesaj aracıları, servislerin doğrudan iletişim kurmak yerine olayları ve komutları kuyruklar aracılığıyla göndermesini sağlar. Bu, servislerin birbirinden bağımsız çalışmasını ve yük dalgalanmalarına karşı daha dayanıklı olmasını sağlar.

Veri Yönetimi

Mikroservis mimarisinde “veritabanı başına servis” (database per service) yaklaşımı benimsenir. Her mikroservis kendi veritabanını yönetir ve diğer servislerin veritabanlarına doğrudan erişmez. Bu, servislerin bağımsızlığını artırır ve teknoloji seçimi konusunda esneklik sağlar. Veri tutarlılığı, dağıtık işlemler ve olay odaklı mimarilerle (eventual consistency) sağlanır.

Hata Toleransı ve Dayanıklılık (Resilience)

Dağıtık sistemlerde hatalar kaçınılmazdır. Mikroservisler, bir servisin çökmesinin diğerlerini etkilememesi için dayanıklı olmalıdır. C# dünyasında Polly kütüphanesi, devre kesici (circuit breaker), yeniden deneme (retry), zaman aşımı (timeout) ve toplu işlem (bulkhead) gibi kalıpları uygulayarak servislerin daha dayanıklı olmasını sağlar.

Kapsayıcılaştırma ve Orkestrasyon

Mikroservislerin konuşlandırılması ve yönetimi, Docker gibi kapsayıcılaştırma teknolojileri ve Kubernetes gibi kapsayıcı orkestrasyon araçları ile basitleştirilir. Her mikroservis, kendi Docker görüntüsüne (image) sahip olabilir ve Kubernetes, bu kapsayıcıların dağıtımını, ölçeklendirmesini ve yaşam döngüsünü yönetir. .NET Core, Docker ile sorunsuz bir şekilde çalışacak şekilde tasarlanmıştır.

Gözlemlenebilirlik (Observability)

Mikroservis ortamında sistemin durumunu anlamak zor olabilir. Gözlemlenebilirlik, sistemin iç durumunu dışarıdan anlamayı sağlayan araç ve teknikleri kapsar:

  • Loglama: Serilog gibi kütüphanelerle yapılandırılmış loglar toplamak, sorun giderme ve izleme için kritik öneme sahiptir.
  • Metrikler: Servislerin performansını (CPU, bellek, istek sayısı, hata oranı vb.) izlemek için Prometheus, Grafana gibi araçlar kullanılır.
  • Dağıtık İzleme (Distributed Tracing): OpenTelemetry gibi araçlar, bir isteğin farklı mikroservisler arasında nasıl hareket ettiğini izlemeyi sağlar, bu da performans darboğazlarını ve hataları tespit etmeye yardımcı olur.

C# ile Basit Bir Mikroservis Örneği (Kavramsal)

C# ile bir mikroservis geliştirmek için ASP.NET Core Web API projesi temel alınır. Örneğin, bir “Ürünler Servisi” ve bir “Siparişler Servisi” olduğunu düşünelim:


// Ürünler Servisi - ProductController.cs
[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductRepository _productRepository;

    public ProductsController(IProductRepository productRepository)
    {
        _productRepository = productRepository;
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var product = await _productRepository.GetByIdAsync(id);
        if (product == null)
            return NotFound();
        return Ok(product);
    }

    // Diğer CRUD işlemleri...
}

// Siparişler Servisi - OrdersController.cs
[ApiController]
[Route("[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IOrderRepository _orderRepository;
    private readonly IHttpClientFactory _httpClientFactory; // Ürün servisine istek atmak için

    public OrdersController(IOrderRepository orderRepository, IHttpClientFactory httpClientFactory)
    {
        _orderRepository = orderRepository;
        _httpClientFactory = httpClientFactory;
    }

    [HttpPost]
    public async Task<ActionResult<Order>> CreateOrder(OrderCreationDto orderDto)
    {
        // Örnek: Ürün servisine ürün bilgilerini doğrulamak için istek gönder
        var httpClient = _httpClientFactory.CreateClient("ProductApiClient");
        var productResponse = await httpClient.GetAsync($"/products/{orderDto.ProductId}");
        productResponse.EnsureSuccessStatusCode(); // Hata durumunda istisna fırlat

        var order = new Order { ProductId = orderDto.ProductId, Quantity = orderDto.Quantity, ... };
        await _orderRepository.AddAsync(order);
        return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
    }

    // Diğer işlemler...
}

Yukarıdaki örnekte, “Ürünler Servisi” kendi ürün veritabanını yönetirken, “Siparişler Servisi” bir sipariş oluştururken “Ürünler Servisi”nden ürün detaylarını HTTP isteği ile almaktadır. Her servis kendi bağımlılıklarını (örneğin, IProductRepository) bağımlılık enjeksiyonu ile yönetir. Bu yapı, servislerin ayrı ayrı geliştirilip konuşlandırılabilmesini sağlar.

Mikroservis Mimarisi ile Karşılaşılan Zorluklar ve En İyi Uygulamalar

Mikroservisler birçok avantaj sunsa da, beraberinde bazı zorlukları da getirir:

  • Veri Tutarlılığı: “Veritabanı başına servis” yaklaşımı, dağıtık veri tutarlılığı sorunlarını beraberinde getirir. Genellikle SAGA paternleri veya olay odaklı mimariler kullanılarak eventual consistency (nihai tutarlılık) sağlanır.
  • Dağıtık İşlemler: Birden fazla servis arasında gerçekleşen bir işlemin (örneğin, sipariş oluşturma, envanter güncelleme ve ödeme alma) atomik (bölünemez) olması zordur. Bu durum karmaşık senaryolar için özel tasarım paternleri gerektirir.
  • Operasyonel Karmaşıklık: Monolitik bir uygulamayı yönetmek yerine, düzinelerce veya yüzlerce bağımsız servisi yönetmek, konuşlandırmak, izlemek ve hata ayıklamak operasyonel maliyeti artırır. Gelişmiş CI/CD boru hatları, otomasyon ve iyi gözlemlenebilirlik araçları bu karmaşıklığı azaltır.
  • Servisler Arası İletişim Maliyeti: Servisler arası ağ çağrıları, monolitik bir uygulamadaki fonksiyon çağrılarına göre daha yavaş ve daha az güvenilirdir. Bu nedenle servisler arası iletişimin minimize edilmesi ve optimize edilmesi önemlidir.
  • Sınırların Belirlenmesi: Servis sınırlarını doğru çizmek, yani hangi işlevselliğin hangi servise ait olduğunu belirlemek, başarılı bir mikroservis mimarisi için kritik öneme sahiptir. Domain-Driven Design (DDD) bu konuda yol gösterici olabilir.

C# dilinde mikroservis mimarisi, modern uygulamaların geliştirilmesi için güçlü ve esnek bir yol sunar. Bağımsız servisler sayesinde ölçeklenebilirlik artar, hatalar izole edilir ve ekipler daha verimli çalışır. .NET ekosisteminin zengin araçları ve kütüphaneleri, bu geçişi kolaylaştırır. Başarılı bir mikroservis stratejisi, doğru tasarım prensipleri ve sürekli iyileştirme ile mümkündür, bu da geleceğin dirençli ve performanslı sistemlerini inşa etmenin anahtarıdır.


C# dilinde Clean Architecture ve DDD

Modern yazılım geliştirme, sürdürülebilir, test edilebilir ve ölçeklenebilir sistemler inşa etmeyi gerektirir. C# projelerinde bu hedeflere ulaşmak için Clean Architecture ve Domain-Driven Design (DDD) yaklaşımları kritik öneme sahiptir. Bu makale, bu iki güçlü tasarım prensibini C# bağlamında derinlemesine inceleyecek, temel kavramlarını açıklayacak ve gerçek dünya uygulamalarında nasıl entegre edilebileceklerini gösterecektir. Amacımız, karmaşık iş gereksinimlerini karşılayan sağlam yazılımlar geliştirmektir.

Clean Architecture Nedir?

Clean Architecture (Temiz Mimari), Robert C. Martin (Uncle Bob) tarafından popülerleştirilen, yazılım sistemlerinin bağımsızlığını, test edilebilirliğini ve sürdürülebilirliğini artırmayı hedefleyen bir dizi tasarım prensibidir. Temel amacı, iş kurallarını (domain logic) dış katmanlardan (veritabanı, UI, çerçeveler vb.) izole etmek ve bağımlılıkların her zaman içe doğru olmasını sağlamaktır. Bu sayede, dış katmanlarda yapılacak değişiklikler iç katmanları etkilemez ve sistemin temel iş mantığı her zaman kararlı kalır.

Clean Architecture Katmanları

Clean Architecture genellikle dört ana katmanla temsil edilir ve bu katmanlar içten dışa doğru bağımlılık kuralına uyar:

  1. Domain (Etki Alanı)

    Bu, uygulamanın çekirdek iş mantığını ve varlıklarını (Entities, Value Objects, Aggregates) içeren en içteki katmandır. Herhangi bir dış bağımlılığı olmamalıdır. İş kuralları burada tanımlanır ve sistemin kalbini oluşturur. Örneğin, bir sipariş sistemi için Order, OrderItem gibi varlıklar ve siparişin durumunu değiştiren iş kuralları bu katmanda yer alır.

    
    // MyApplication.Domain/Entities/Order.cs
    public class Order
    {
        public Guid Id { get; private set; }
        public Customer Customer { get; private set; }
        public List<OrderItem> Items { get; private set; }
        public OrderStatus Status { get; private set; }
    
        private Order() { /* EF Core required */ }
    
        public Order(Customer customer, IEnumerable<OrderItem> items)
        {
            Id = Guid.NewGuid();
            Customer = customer ?? throw new ArgumentNullException(nameof(customer));
            Items = items?.ToList() ?? throw new ArgumentNullException(nameof(items));
            Status = OrderStatus.Pending;
            // İş kuralları: Boş sipariş oluşturulamaz, vb.
            if (!Items.Any())
            {
                throw new InvalidOperationException("Order must contain at least one item.");
            }
        }
    
        public void ConfirmOrder()
        {
            if (Status != OrderStatus.Pending)
            {
                throw new InvalidOperationException("Only pending orders can be confirmed.");
            }
            Status = OrderStatus.Confirmed;
        }
        // Diğer iş mantığı metotları...
    }
            
  2. Application (Uygulama)

    Uygulamaya özgü iş kurallarını ve kullanım senaryolarını (use cases) içerir. Bu katman, Domain katmanını kullanarak uygulama operasyonlarını düzenler. Örneğin, bir siparişin oluşturulması, güncellenmesi veya sorgulanması gibi işlemler burada tanımlanır. Komutlar (Commands), sorgular (Queries) ve bunların işleyicileri (Handlers), uygulama servisleri (Application Services) bu katmanda bulunur. Domain katmanına bağımlıdır ama dış katmanlardan (Infrastructure, UI) bağımsızdır.

    
    // MyApplication.Application/Features/Orders/CreateOrderCommand.cs
    public class CreateOrderCommand : IRequest<Guid>
    {
        public Guid CustomerId { get; set; }
        public List<OrderItemDto> Items { get; set; }
    }
    
    // MyApplication.Application/Features/Orders/CreateOrderCommandHandler.cs
    public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, Guid>
    {
        private readonly IOrderRepository _orderRepository;
        private readonly ICustomerRepository _customerRepository;
    
        public CreateOrderCommandHandler(IOrderRepository orderRepository, ICustomerRepository customerRepository)
        {
            _orderRepository = orderRepository;
            _customerRepository = customerRepository;
        }
    
        public async Task<Guid> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
        {
            var customer = await _customerRepository.GetByIdAsync(request.CustomerId);
            if (customer == null) throw new NotFoundException($"Customer with ID {request.CustomerId} not found.");
    
            var orderItems = request.Items.Select(item => new Domain.ValueObjects.OrderItem(item.ProductId, item.Quantity, item.UnitPrice));
            var order = new Domain.Entities.Order(customer, orderItems);
    
            await _orderRepository.AddAsync(order);
            await _orderRepository.UnitOfWork.CommitAsync(cancellationToken); // Save changes
    
            return order.Id;
        }
    }
            
  3. Infrastructure (Altyapı)

    Dışsal bağımlılıkların (veritabanı erişimi, dosya sistemi, harici servisler, mesaj kuyrukları, ORM’ler gibi) uygulanmasını içerir. Bu katman, Application katmanında tanımlanan arayüzleri (interfaces) uygular. Örneğin, IOrderRepository arayüzünün Entity Framework Core ile gerçekleştirimi bu katmanda yer alır. Application ve Domain katmanlarına bağımlıdır.

    
    // MyApplication.Infrastructure/Persistence/Repositories/OrderRepository.cs
    public class OrderRepository : IOrderRepository
    {
        private readonly ApplicationDbContext _context;
    
        public OrderRepository(ApplicationDbContext context)
        {
            _context = context;
        }
    
        public async Task<Order> GetByIdAsync(Guid id)
        {
            return await _context.Orders.Include(o => o.Items).FirstOrDefaultAsync(o => o.Id == id);
        }
    
        public async Task AddAsync(Order order)
        {
            await _context.Orders.AddAsync(order);
        }
        // ... Diğer repository metotları
    }
            
  4. Presentation/UI (Sunum/Kullanıcı Arayüzü)

    Kullanıcının uygulama ile etkileşim kurduğu katmandır (ASP.NET Core Web API, MVC, Blazor, Console uygulaması vb.). Bu katman, kullanıcı isteklerini Application katmanına yönlendirir ve Application katmanından gelen sonuçları kullanıcıya sunar. En dış katmandır ve Application katmanına bağımlıdır.

    
    // MyApplication.Presentation.WebAPI/Controllers/OrdersController.cs
    [ApiController]
    [Route("api/[controller]")]
    public class OrdersController : ControllerBase
    {
        private readonly IMediator _mediator; // MediatR for sending commands/queries
    
        public OrdersController(IMediator mediator)
        {
            _mediator = mediator;
        }
    
        [HttpPost]
        public async Task<ActionResult<Guid>> CreateOrder([FromBody] CreateOrderCommand command)
        {
            var orderId = await _mediator.Send(command);
            return Ok(orderId);
        }
        // ... Diğer API endpointleri
    }
            

Domain-Driven Design (DDD) Nedir?

Domain-Driven Design (DDD), karmaşık iş alanlarında yazılım geliştirmeyi basitleştirmeyi ve iş gereksinimlerine daha iyi uyum sağlamayı amaçlayan bir yazılım geliştirme yaklaşımıdır. Eric Evans’ın “Domain-Driven Design: Tackling Complexity in the Heart of Software” adlı kitabıyla popülerleşmiştir. DDD, yazılımın temel odağını teknik detaylardan çok iş alanının (domain) kendisine kaydırır. İş uzmanları ve geliştiriciler arasında ortak bir dil (Ubiquitous Language) oluşturarak iletişimdeki yanlış anlaşılmaları azaltmayı hedefler.

DDD’nin Temel Kavramları

DDD’nin hem stratejik hem de taktiksel desenleri vardır. Stratejik desenler iş alanını büyük ölçekte anlamak ve bölmekle ilgilenirken, taktiksel desenler bu alan içindeki bileşenleri modellemek için kullanılır.

  1. Ubiquitous Language (Ortak Dil)

    İş uzmanları ve geliştiriciler arasında paylaşılan, belirli bir iş alanı bağlamında kesin ve tutarlı bir dildir. Bu dil, yazılım koduna doğrudan yansıtılmalı ve iş konuşmalarında da kullanılmalıdır. Örneğin, bir e-ticaret sisteminde “sipariş”, “ürün”, “müşteri” gibi terimler hem kodda hem de günlük iletişimde aynı anlamı taşımalıdır.

  2. Bounded Context (Sınırlı Bağlam)

    Karmaşık bir sistemin farklı bölümlerini birbirinden ayıran mantıksal sınırlardır. Her Bounded Context’in kendi Ubiquitous Language’i, kendi iş kuralları ve kendi model tanımı vardır. Örneğin, bir e-ticaret uygulamasında “Sipariş Yönetimi”, “Envanter Yönetimi” ve “Müşteri Desteği” farklı Bounded Context’ler olabilir. Bu, büyük sistemlerin yönetilebilir parçalara ayrılmasını sağlar.

  3. Entities (Varlıklar)

    Benzersiz bir kimliği olan ve yaşam döngüsü boyunca bu kimliği koruyan nesnelerdir. Kimlikleri önemli olduğu için değerleri değişse bile aynı varlık olarak kalırlar. Örneğin, bir Customer (Müşteri) veya Order (Sipariş) birer varlıktır. C# dilinde genellikle bir ID özelliği ve iş mantığı içeren sınıflar olarak modellenirler.

    
    // Örnek: Customer Entity
    public class Customer : BaseEntity // BaseEntity kimlik sağlar
    {
        public string Name { get; private set; }
        public string Email { get; private set; }
    
        private Customer() { } // EF Core için
    
        public Customer(Guid id, string name, string email)
        {
            Id = id;
            Name = name;
            Email = email;
        }
    
        public void UpdateEmail(string newEmail)
        {
            // İş kuralı: Email formatı doğru olmalı, vb.
            if (string.IsNullOrWhiteSpace(newEmail) || !IsValidEmail(newEmail))
            {
                throw new ArgumentException("Invalid email format.");
            }
            Email = newEmail;
        }
    }
            
  4. Value Objects (Değer Nesneleri)

    Kimliği olmayan, yalnızca özniteliklerinin değerleriyle tanımlanan nesnelerdir. İki değer nesnesi, tüm öznitelikleri aynıysa eşit kabul edilir. Genellikle değişmez (immutable) olmalıdırlar. Örneğin, bir adres (Address), para birimi (Money) veya tarih aralığı (DateRange) birer değer nesnesidir. C# dilinde genellikle sadece özellikler içeren ve eşitlik karşılaştırması için özelleştirilmiş sınıflar veya kayıtlar (records) olarak tasarlanır.

    
    // Örnek: Address Value Object
    public record Address(string Street, string City, string PostalCode, string Country);
    
    // Örnek: Money Value Object
    public record Money
    {
        public decimal Amount { get; init; }
        public string Currency { get; init; }
    
        public Money(decimal amount, string currency)
        {
            if (amount < 0) throw new ArgumentOutOfRangeException(nameof(amount));
            if (string.IsNullOrWhiteSpace(currency)) throw new ArgumentNullException(nameof(currency));
            Amount = amount;
            Currency = currency;
        }
    
        public Money Add(Money other)
        {
            if (Currency != other.Currency)
                throw new InvalidOperationException("Cannot add money with different currencies.");
            return new Money(Amount + other.Amount, Currency);
        }
    }
            
  5. Aggregates (Kümeler)

    Veri tutarlılığını sağlamak için birlikte ele alınması gereken Entity ve Value Object’lerin bir kümesidir. Her Aggregate’in bir kök varlığı (Aggregate Root) bulunur. Dışarıdan Aggregate’e erişim sadece kök varlık üzerinden olmalıdır. Bu, iş kurallarının tek bir noktadan yönetilmesini sağlar. Örneğin, bir Order (sipariş) ve onun altındaki OrderItem‘lar bir Aggregate oluşturabilir ve Order Aggregate Root’u olur.

    
    // Order Aggregate'i, Order Aggregate Root olarak davranır.
    // OrderItem'lar Order'ın içinde Value Object veya Entity olarak tanımlanabilir.
    public class Order // Aggregate Root
    {
        public Guid Id { get; private set; }
        public Guid CustomerId { get; private set; } // Sadece kimliği tutar, Customer Entity'sini tutmaz
        private readonly List<OrderItem> _items;
        public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
        public OrderStatus Status { get; private set; }
    
        // ... Yapıcı metot ve iş mantığı ...
    }
            
  6. Domain Services (Etki Alanı Servisleri)

    Belirli bir varlığın veya değer nesnesinin sorumluluğuna uymayan, ancak iş alanı içinde önemli olan operasyonlardır. Genellikle birden fazla Aggregate veya Varlık üzerinde işlem yaparlar. Örneğin, bir para transferi hizmeti veya bir siparişin birden fazla farklı kargo sistemini içeren karmaşık bir doğrulama süreci Domain Service olabilir.

  7. Repositories (Depolar)

    Aggregate’leri kaydetme, yükleme ve sorgulama işlemlerini soyutlayan arayüzlerdir. Veritabanı gibi kalıcılık mekanizmalarının detaylarını gizlerler ve Domain/Application katmanlarının bu detaylardan bağımsız kalmasını sağlarlar. Her Aggregate Root için bir Repository tanımlanması yaygın bir DDD prensibidir.

    
    // MyApplication.Domain/Repositories/IOrderRepository.cs
    public interface IOrderRepository : IRepository<Order> // Geniş bir IRepository arayüzü olabilir
    {
        Task<Order> GetByIdAsync(Guid id);
        Task AddAsync(Order order);
        // ... Diğer Aggregate'e özgü sorgu metotları
    }
            

Clean Architecture ve DDD’nin C# ile Entegrasyonu

Clean Architecture ve DDD, birbirini mükemmel şekilde tamamlayan iki güçlü yaklaşımdır. Clean Architecture, sistemin katmanlı yapısını ve bağımlılık akışını tanımlayarak mimari bir çerçeve sağlarken, DDD bu çerçeve içindeki Domain katmanının nasıl zenginleştirileceğine odaklanır. C# projelerinde bu entegrasyon, uygulamanın hem teknik olarak sağlam hem de iş mantığı açısından zengin olmasını sağlar.

Entegrasyonun temel noktaları şunlardır:

  • Domain Katmanı ve DDD: Clean Architecture’ın Domain katmanı, DDD’nin temelini oluşturur. Entities, Value Objects, Aggregates ve Domain Services gibi DDD kavramları bu katmanda C# sınıfları, kayıtları ve arayüzleri olarak modellenir. Ubiquitous Language, kodun içindeki isimlendirmelerde ve sınıf tasarımlarında kendini gösterir.
  • Application Katmanı ve DDD: Application katmanı, Domain katmanındaki iş mantığını kullanarak kullanım senaryolarını gerçekleştirir. DDD’nin iş odaklı yaklaşımı sayesinde, bu katmandaki komutlar ve sorgular doğrudan iş gereksinimlerini yansıtır. Örneğin, bir CreateOrderCommand, bir sipariş Aggregate’ini oluşturmak için Domain Service’lerini veya Repository’leri kullanır.
  • Infrastructure Katmanı ve DDD: DDD’nin Repository desenleri, Infrastructure katmanında somutlaştırılarak kalıcılık mekanizmalarının Domain katmanından soyutlanmasını sağlar. IOrderRepository arayüzü Domain katmanında tanımlanırken, Entity Framework Core veya Dapper gibi ORM’lerle yapılan somut uygulamaları Infrastructure katmanında yer alır. Bu, veritabanı teknolojisi değişse bile Domain ve Application katmanlarının etkilenmemesini sağlar.
  • Bounded Context’ler ve Modülerlik: Her Bounded Context, kendi içindeki Clean Architecture yapısına sahip ayrı bir C# projesi veya çözüm klasörü olarak tasarlanabilir. Bu, büyük sistemlerin modüler ve yönetilebilir parçalara ayrılmasına olanak tanır.

Tipik bir C# çözüm yapısı şu şekilde görünebilir:


MyApplication.sln
├── src
│   ├── MyApplication.Domain             (DDD Entities, Value Objects, Aggregates, Domain Services, Repository Interfaces)
│   ├── MyApplication.Application        (Use Cases, Commands, Queries, Handlers, Application Services, DTOs)
│   ├── MyApplication.Infrastructure     (EF Core, Third-Party APIs, Repository Implementations, Caching)
│   ├── MyApplication.Presentation.WebAPI (ASP.NET Core Controllers, ViewModels, Dependency Injection Setup)
│   └── MyApplication.CrossCutting       (Shared concerns like IoC container setup, common helpers)
└── tests
    ├── MyApplication.Domain.Tests
    ├── MyApplication.Application.Tests
    ├── MyApplication.Infrastructure.Tests
    └── MyApplication.Presentation.WebAPI.Tests

Neden C# Projelerinde Kullanılmalılar?

C# projelerinde Clean Architecture ve DDD’yi birleştirmek, bir dizi önemli avantaj sunar:

  • Sürdürülebilirlik: Katmanlı yapı ve iş mantığının izole edilmesi, kod tabanını daha düzenli ve anlaşılır hale getirir. Bu, yeni özelliklerin eklenmesini ve mevcut özelliklerin bakımını kolaylaştırır.
  • Test Edilebilirlik: Bağımlılıkların içe doğru akması ve arayüzlerin kullanılması, her katmanın ve bileşenin kolayca test edilmesini sağlar. Özellikle Domain ve Application katmanları dış bağımlılıklardan arındığı için hızlı ve güvenilir birim testleri yazılabilir.
  • Ölçeklenebilirlik ve Esneklik: İş mantığı altyapı detaylarından bağımsız olduğu için, veritabanı veya UI teknolojisi gibi dış katmanlar gerektiğinde daha kolay değiştirilebilir veya ölçeklendirilebilir.
  • İş Odaklı Geliştirme: DDD, geliştiricilerin iş alanını ve iş gereksinimlerini daha derinlemesine anlamasına yardımcı olur. Ortak Dil ve Bounded Context’ler, iş uzmanları ve geliştiriciler arasındaki iletişimi güçlendirir.
  • Daha İyi İletişim: Ubiquitous Language, iş terimlerinin kodda doğrudan yansıtılmasıyla herkesin aynı dili konuşmasını sağlar, bu da yanlış anlaşılmaları ve hataları azaltır.
  • Yüksek Kaliteli Yazılım: Her iki yaklaşım da, özellikle karmaşık iş alanlarında yüksek kaliteli, sağlam ve hatasız yazılımlar geliştirmeye odaklanır.

Özetle, Clean Architecture ve DDD, C# projelerinde güçlü, sürdürülebilir ve esnek yazılım çözümleri geliştirmek için vazgeçilmez yaklaşımlardır. Clean Architecture katmanlı yapısıyla bağımlılıkları yönetirken, DDD iş alanının karmaşıklığını modellemeyi sağlar. Bu iki metodolojinin birleşimi, iş mantığına odaklanmayı, test edilebilirliği artırmayı ve uzun vadede bakımı kolaylaştırmayı garanti eder. Böylece, değişen gereksinimlere kolayca adapte olabilen yüksek kaliteli sistemler inşa edilebilir.


C# dilinde Unit Test ve TDD

C# dilinde yazılım geliştirme süreçlerinde kalitenin, sürdürülebilirliğin ve güvenilirliğin sağlanması kritik öneme sahiptir. Unit Testler ve Test-Driven Development (TDD) yaklaşımları, bu hedeflere ulaşmak için geliştiricilerin en güçlü araçları arasındadır. Bu makale, C# ortamında Unit Testlerin ne olduğunu, nasıl yazıldığını ve TDD metodolojisinin ne anlama geldiğini, uygulama adımlarını ve yazılım geliştirmeye katkılarını derinlemesine inceleyecektir.

Unit Test Nedir?

Unit Test (Birim Testi), bir yazılım uygulamasının en küçük test edilebilir parçalarını (genellikle tek bir metot veya fonksiyonu) izole bir şekilde test etme pratiğidir. Amaç, her bir birimin beklendiği gibi çalıştığını doğrulamaktır. C# ekosisteminde NUnit, xUnit ve MSTest gibi popüler test çerçeveleri bulunmaktadır. Bu testler hızlı çalışmalı, bağımsız olmalı, tekrarlanabilir olmalı, kendi kendini doğrulamalı ve zamanında yazılmalıdır (FAST prensibi). Unit Testler, hataları geliştirme sürecinin erken aşamalarında tespit ederek düzeltme maliyetini önemli ölçüde azaltır. Ayrıca, kodda yapılan değişikliklerin mevcut fonksiyonelliği bozup bozmadığını anlamak için güvenli bir ağ sağlar ve refaktoring işlemlerine cesaret verir.

C# Ortamında Unit Test Yazımı

C# projelerinde Unit Test yazmak için genellikle Visual Studio üzerinden yeni bir test projesi oluşturulur. Bu proje, test edilecek ana projenize referans verir. Aşağıda, xUnit framework’ü kullanarak basit bir hesap makinesi sınıfı için test yazımına bir örnek verilmiştir.

Öncelikle test edilecek basit bir sınıf oluşturalım:

“`csharp
// ToplamaIslemleri.cs
public class ToplamaIslemleri
{
public int Topla(int sayi1, int sayi2)
{
return sayi1 + sayi2;
}
}
“`

Şimdi bu sınıf için bir xUnit testi yazalım:

“`csharp
// ToplamaIslemleriTests.cs
using Xunit;

public class ToplamaIslemleriTests
{
[Fact] // Bu metotun bir test metodu olduğunu belirtir
public void Topla_IkiPozitifSayiyiToplar_DogruSonucVerir()
{
// Arrange (Hazırlık): Test edilecek nesneleri ve verileri hazırla
var toplamaIslemleri = new ToplamaIslemleri();
int sayi1 = 5;
int sayi2 = 3;
int beklenenSonuc = 8;

// Act (Eylem): Test edilecek metodu çağır
int gerceklesenSonuc = toplamaIslemleri.Topla(sayi1, sayi2);

// Assert (Doğrulama): Sonucun beklendiği gibi olup olmadığını kontrol et
Assert.Equal(beklenenSonuc, gerceklesenSonuc);
}

[Theory] // Farklı veri setleri ile test yapmak için kullanılır
[InlineData(1, 1, 2)]
[InlineData(-1, -1, -2)]
[InlineData(10, -5, 5)]
public void Topla_FarkliSayilariToplar_DogruSonucVerir(int sayi1, int sayi2, int beklenenSonuc)
{
// Arrange
var toplamaIslemleri = new ToplamaIslemleri();

// Act
int gerceklesenSonuc = toplamaIslemleri.Topla(sayi1, sayi2);

// Assert
Assert.Equal(beklenenSonuc, gerceklesenSonuc);
}
}
“`

Yukarıdaki örnekte `[Fact]` ve `[Theory]` nitelikleri (attributes) xUnit tarafından test metotlarını tanımlamak için kullanılır. `Assert` sınıfı ise beklenen değerler ile gerçekleşen değerleri karşılaştırmak için çeşitli metotlar sunar (Equal, NotEqual, True, False, Throws vb.). Unit testler genellikle “Arrange-Act-Assert” (AAA) deseni kullanılarak yapılandırılır. Bu desen, testin okunabilirliğini ve anlaşılırlığını artırır.

Mocking ve Stubbing

Gerçek dünya uygulamalarında, test edilen birim genellikle diğer birimlere veya dış hizmetlere (veritabanı, API’ler, dosya sistemi vb.) bağımlıdır. Bu bağımlılıklar, bir birimin izole bir şekilde test edilmesini zorlaştırır ve testlerin yavaşlamasına, tekrarlanamaz hale gelmesine veya harici faktörlere bağlı olarak başarısız olmasına neden olabilir. İşte bu noktada Mocking (Taklit etme) ve Stubbing (Yerine geçme) devreye girer.

* **Stub:** Test edilen birimin bağımlılığının belirli bir senaryo için önceden tanımlanmış sabit bir yanıt döndürmesini sağlayan basit bir nesnedir.
* **Mock:** Stub’dan daha fazlasını yapar. Sadece önceden tanımlanmış yanıtlar döndürmekle kalmaz, aynı zamanda test edilen birimin bağımlılıklarla nasıl etkileşim kurduğunu (örneğin, belirli bir metodun çağrılıp çağrılmadığını, kaç kez çağrıldığını veya hangi argümanlarla çağrıldığını) doğrulamamızı sağlar.

C# için Moq ve NSubstitute gibi popüler mocking framework’leri bulunmaktadır. Bir örnekle açıklayalım:

“`csharp
// ILogger.cs
public interface ILogger
{
void Log(string message);
}

// Hesaplayici.cs (Logger bağımlılığı olan bir sınıf)
public class Hesaplayici
{
private readonly ILogger _logger;

public Hesaplayici(ILogger logger)
{
_logger = logger;
}

public int Bol(int bolunen, int bolen)
{
if (bolen == 0)
{
_logger.Log(“Sıfıra bölme hatası tespit edildi.”);
throw new ArgumentException(“Bölen sıfır olamaz.”);
}
return bolunen / bolen;
}
}
“`

Şimdi `Hesaplayici` sınıfını test ederken `ILogger` bağımlılığını Moq ile taklit edelim:

“`csharp
// HesaplayiciTests.cs
using Xunit;
using Moq; // Moq kütüphanesini kullanmak için

public class HesaplayiciTests
{
[Fact]
public void Bol_SifiraBolmedeHataFirlatirVeLogYazar()
{
// Arrange
var mockLogger = new Mock(); // ILogger için bir Mock nesnesi oluştur
var hesaplayici = new Hesaplayici(mockLogger.Object); // Mock nesnesini Hesaplayici’ya enjekte et

// Act & Assert
var exception = Assert.Throws(() => hesaplayici.Bol(10, 0));
Assert.Equal(“Bölen sıfır olamaz.”, exception.Message);

// Verify (Doğrulama): Log metodunun doğru mesajla çağrıldığını kontrol et
mockLogger.Verify(logger => logger.Log(“Sıfıra bölme hatası tespit edildi.”), Times.Once);
}
}
“`

Bu örnekte, `Hesaplayici` sınıfının `Bol` metodunu test ederken gerçek bir `ILogger` nesnesine ihtiyaç duymadık. Bunun yerine Moq kullanarak sahte bir `ILogger` nesnesi yarattık ve test sonunda `Log` metodunun beklenen mesajla bir kez çağrılıp çağrılmadığını doğruladık. Bu, testleri daha hızlı, daha güvenilir ve daha izole hale getirir.

Test-Driven Development (TDD) Nedir?

Test-Driven Development (TDD), yazılım geliştirmede önce testleri yazmayı, sonra kodu yazmayı ve ardından kodu iyileştirmeyi içeren bir yaklaşımdır. Geleneksel yaklaşımların aksine, TDD’de kod yazmaya başlamadan önce yazılımın nasıl çalışması gerektiğini tanımlayan testler oluşturulur. Bu, “Red-Green-Refactor” (Kırmızı-Yeşil-Yeniden Düzenle) olarak bilinen üç adımlı bir döngüde gerçekleştirilir. TDD’nin temel felsefesi, testlerin sadece bir doğrulama aracı olmaktan öte, yazılım tasarımı ve geliştirme sürecine rehberlik etmesidir.

TDD Döngüsü ve Uygulaması

TDD döngüsü şu adımlardan oluşur:

1. **Red (Kırmızı):** Geçmeyecek bir test yazın. Bu test, henüz var olmayan veya yanlış çalışan bir özelliği tanımlar. Testi çalıştırın ve beklendiği gibi başarısız olduğundan emin olun (kırmızı ışık). Bu adım, testin doğru çalıştığını ve başarısızlık nedenini net bir şekilde anlamanızı sağlar.

2. **Green (Yeşil):** Testi geçecek *en basit* kodu yazın. Amaç, testi başarılı hale getirmektir (yeşil ışık). Bu aşamada kod kalitesi, tasarım veya performans endişeleri ikinci plandadır. Sadece testin geçmesini sağlayacak kadar kod yazılır.

3. **Refactor (Yeniden Düzenle):** Kodunuzu temizleyin ve iyileştirin. Tüm testler yeşilken, kodu daha okunabilir, sürdürülebilir ve verimli hale getirmek için yeniden düzenleyebilirsiniz. Bu, kod tekrarını azaltmayı, isimleri iyileştirmeyi, karmaşıklığı düşürmeyi ve tasarımı geliştirmeyi içerir. Yeniden düzenleme sırasında testlerin yeşil kalmaya devam ettiğinden emin olun.

Bu döngü, yeni bir özellik eklenene veya mevcut bir özellik değiştirilene kadar sürekli tekrarlanır. Bir örnekle TDD döngüsünü adım adım inceleyelim. Diyelim ki bir `IndirimHesaplayici` sınıfı yazacağız ve %10 indirim uygulayan bir metot ekleyeceğiz.

**Adım 1: Red (Kırmızı) – Testi Yazın**

Önce, `IndirimUygula` metodunun %10 indirim yapacağını doğrulayan bir test yazalım.

“`csharp
// IndirimHesaplayiciTests.cs
using Xunit;

public class IndirimHesaplayiciTests
{
[Fact]
public void IndirimUygula_YuzdeOnIndirimUygular()
{
// Arrange
var hesaplayici = new IndirimHesaplayici(); // Hata: IndirimHesaplayici henüz yok
decimal fiyat = 100m;
decimal beklenenIndirimliFiyat = 90m;

// Act
decimal gerceklesenIndirimliFiyat = hesaplayici.IndirimUygula(fiyat); // Hata: IndirimUygula metodu yok

// Assert
Assert.Equal(beklenenIndirimliFiyat, gerceklesenIndirimliFiyat);
}
}
“`

Bu test şu anda derleme hatası verecektir çünkü `IndirimHesaplayici` sınıfı ve `IndirimUygula` metodu henüz mevcut değil. Bu test koşulmaya çalışıldığında “Red” olacaktır.

**Adım 2: Green (Yeşil) – En Basit Kodu Yazın**

Şimdi, bu testi geçecek *en basit* kodu yazalım.

“`csharp
// IndirimHesaplayici.cs
public class IndirimHesaplayici
{
public decimal IndirimUygula(decimal fiyat)
{
return fiyat * 0.90m; // Testi geçmek için en basit kod
}
}
“`

Şimdi testi tekrar çalıştırın. Test yeşil olacaktır.

**Adım 3: Refactor (Yeniden Düzenle)**

Şu anda kodumuz oldukça basit. Ancak, bu metot sadece %10 indirim uyguluyor. Ya farklı indirim oranlarına ihtiyacımız olursa? Kodu daha genel hale getirebiliriz.

“`csharp
// IndirimHesaplayici.cs (Refactored)
public class IndirimHesaplayici
{
// Indirim oranını parametre olarak alabilir veya bir sabit yapabiliriz.
// Şimdilik daha esnek bir yapıya geçelim:
public decimal IndirimUygula(decimal fiyat, decimal indirimOrani)
{
if (indirimOrani < 0 || indirimOrani > 1)
{
throw new ArgumentOutOfRangeException(nameof(indirimOrani), “İndirim oranı 0 ile 1 arasında olmalıdır.”);
}
return fiyat * (1 – indirimOrani);
}
}
“`
Refactor sonrası, mevcut testimiz derleme hatası verecektir çünkü `IndirimUygula` metodunun imzası değişti. Bu, yeni gereksinimlere uyum sağlamak için mevcut testi güncellememiz gerektiği anlamına gelir. TDD, bu tür değişikliklerde bizi hemen uyarır.

Yeni gereksinim için yeni bir test yazılabilir veya mevcut test güncellenebilir:

“`csharp
// IndirimHesaplayiciTests.cs (Güncellenmiş)
using Xunit;

public class IndirimHesaplayiciTests
{
[Fact]
public void IndirimUygula_YuzdeOnIndirimUygular_DogruSonucVerir()
{
// Arrange
var hesaplayici = new IndirimHesaplayici();
decimal fiyat = 100m;
decimal indirimOrani = 0.10m; // Yeni parametre
decimal beklenenIndirimliFiyat = 90m;

// Act
decimal gerceklesenIndirimliFiyat = hesaplayici.IndirimUygula(fiyat, indirimOrani);

// Assert
Assert.Equal(beklenenIndirimliFiyat, gerceklesenIndirimliFiyat);
}

[Theory]
[InlineData(100, 0.20, 80)] // %20 indirim
[InlineData(50, 0.05, 47.5)] // %5 indirim
public void IndirimUygula_FarkliIndirimOranlariylaDogruSonucVerir(decimal fiyat, decimal indirimOrani, decimal beklenenSonuc)
{
var hesaplayici = new IndirimHesaplayici();
decimal gerceklesenSonuc = hesaplayici.IndirimUygula(fiyat, indirimOrani);
Assert.Equal(beklenenSonuc, gerceklesenSonuc);
}

[Fact]
public void IndirimUygula_GecersizIndirimOranindaHataFirlatir()
{
var hesaplayici = new IndirimHesaplayici();
Assert.Throws(() => hesaplayici.IndirimUygula(100, 1.10m)); // %110 indirim
Assert.Throws(() => hesaplayici.IndirimUygula(100, -0.05m)); // Negatif indirim
}
}
“`
Bu adımlar, TDD’nin hem kod kalitesini artırdığını hem de bizi her zaman çalışan bir kod tabanıyla bıraktığını gösterir. Her değişiklikten sonra testler tekrar çalıştırılır ve kodun hala doğru çalıştığından emin olunur.

Unit Test ve TDD’nin Avantajları

* **Yüksek Kod Kalitesi ve Güvenilirliği:** TDD, daha temiz, daha modüler ve daha az hata içeren kod yazmaya teşvik eder. Her birim ayrı ayrı doğrulandığı için uygulamanın genel güvenilirliği artar.
* **Daha İyi Tasarım:** TDD’de önce testleri yazmak, geliştiricileri kodun nasıl kullanılacağı ve nasıl test edilebilir olacağı hakkında düşünmeye zorlar. Bu, daha esnek, genişletilebilir ve sürdürülebilir tasarımlara yol açar.
* **Erken Hata Tespiti:** Hatalar geliştirme sürecinin başlarında, düzeltilmesi en ucuz olduğu zamanlarda yakalanır.
* **Refactoring Güvenliği:** Kapsamlı bir Unit Test paketi, mevcut kodu yeniden düzenlerken (refactoring) fonksiyonelliği bozmadığınızdan emin olmanızı sağlar.
* **Geliştirici Güveni:** Kapsamlı testler, geliştiricilere yeni özellikler ekleme veya mevcut kodu değiştirme konusunda daha fazla güven verir.
* **Canlı Dokümantasyon:** İyi yazılmış Unit Testler, bir sınıfın veya metodun ne yapması gerektiğini ve nasıl kullanılması gerektiğini gösteren güncel bir dokümantasyon görevi görür.
* **Kolay Bakım:** Temiz, test edilmiş ve iyi tasarlanmış kod tabanları, uzun vadede bakımı daha kolaydır.

Zorluklar ve Dikkat Edilmesi Gerekenler

Unit Test ve TDD’nin birçok avantajı olsa da, bazı zorlukları da vardır:

* **Başlangıç Maliyeti ve Öğrenme Eğrisi:** Özellikle TDD’ye yeni başlayan ekipler için başlangıçta daha fazla zaman ve çaba gerektirebilir. Alışkanlıkları değiştirmek ve doğru testleri yazmayı öğrenmek zaman alır.
* **Yanlış Testler:** Kötü yazılmış testler (örneğin, çok karmaşık, bağımlı, yavaş veya kırılgan testler) geliştirme sürecini yavaşlatabilir ve testlere olan güveni azaltabilir.
* **Test Kapsamı (Coverage):** %100 test kapsamına ulaşmak her zaman pratik veya gerekli değildir. Önemli olan, iş mantığının kritik kısımlarının yeterince test edildiğinden emin olmaktır.
* **Miras Kodu (Legacy Code):** Mevcut testleri olmayan, karmaşık ve bağımlılıkları yüksek miras kodlara Unit Test yazmak zorlu olabilir. Bu durumda, yavaş yavaş refactoring yaparak ve “change-detection tests” kullanarak ilerlemek gerekebilir.
* **Dış Bağımlılıklar:** Veritabanları, harici API’ler gibi dış bağımlılıkları olan birimleri test etmek, mocking veya entegrasyon testleriyle dikkatli bir planlama gerektirir.

Unit Test ve TDD, C# geliştiricilerinin daha yüksek kaliteli, sürdürülebilir ve güvenilir yazılımlar üretmeleri için vazgeçilmez yaklaşımlardır. Başlangıçtaki öğrenme eğrisine rağmen, bu pratiklerin uzun vadede geliştirme maliyetlerini düşürdüğü ve ürün kalitesini artırdığı kanıtlanmıştır. Her geliştiricinin bu araç setini benimseyerek modern yazılım geliştirme prensiplerine uygun hareket etmesi, hem bireysel başarıları hem de takımın genel verimliliğini olumlu yönde etkileyecektir.


C# dilinde Middleware ve Attribute Geliştirme

“`html

C# geliştirme dünyasında, uygulama akışını yönetmek ve kodun farklı katmanları arasında bilgi paylaşımını sağlamak için güçlü mekanizmalar bulunur. Middleware, özellikle .NET Core tabanlı uygulamalarda HTTP istek/cevap boru hattını şekillendiren, çapraz kesen endişeleri (logging, kimlik doğrulama) merkezi bir yerden ele almayı sağlayan esnek bir yaklaşımdır. Attribute’lar ise, kodumuza meta veri ekleyerek derleme zamanı veya çalışma zamanında ek davranışlar kazandırmamıza olanak tanıyan deklaratif bir programlama aracıdır. Bu makale, bu iki güçlü özelliği derinlemesine inceleyecektir.

C# Dilinde Middleware Geliştirme

Middleware, .NET Core uygulamalarında HTTP istek işleme boru hattının (request pipeline) kalbinde yer alır. Her bir istek, boru hattındaki middleware bileşenlerinden sırayla geçer ve her bileşen, isteği işleyebilir, değiştirebilir veya bir sonraki bileşene devredebilir. Bu yapı, logging, kimlik doğrulama, yetkilendirme, hata yönetimi ve statik dosya sunumu gibi çapraz kesen endişelerin (cross-cutting concerns) uygulamadan bağımsız ve modüler bir şekilde ele alınmasını sağlar.

Middleware Nasıl Oluşturulur?

Özel bir middleware oluşturmanın temel yolu, RequestDelegate tipini yapıcı metodunda alan ve InvokeAsync(HttpContext context) metodunu uygulayan bir sınıf tanımlamaktır. InvokeAsync metodu, middleware’in asıl iş mantığını içerir ve genellikle bir sonraki middleware’i çağırmak için _next(context) çağrısını içerir.

Örnek: İstek Süresini Ölçen Middleware

Aşağıdaki örnek, her HTTP isteğinin ne kadar sürdüğünü loglayan basit bir middleware’i göstermektedir. Bu, performans izleme için faydalı olabilir.


using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Threading.Tasks;

public class RequestTimerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestTimerMiddleware> _logger;

    public RequestTimerMiddleware(RequestDelegate next, ILogger<RequestTimerMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var watch = Stopwatch.StartNew();
        await _next(context); // İstek işlemeye devam et
        watch.Stop();
        _logger.LogInformation($"Request to {context.Request.Path} took {watch.ElapsedMilliseconds} ms.");
    }
}

Middleware Boru Hattına Ekleme

Oluşturulan middleware’i uygulamanın boru hattına eklemek için genellikle bir uzantı metodu (extension method) kullanılır. Bu, kodun daha temiz ve okunabilir olmasını sağlar.


using Microsoft.AspNetCore.Builder;

public static class RequestTimerMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestTimer(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestTimerMiddleware>();
    }
}

Daha sonra Program.cs veya Startup.cs dosyasındaki Configure metodunda bu uzantı metodunu çağırarak middleware’i boru hattına ekleyebilirsiniz:


public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // Diğer middleware'ler...
    app.UseRequestTimer(); // Özel middleware'imizi ekliyoruz
    // Diğer middleware'ler...
}

Middleware’lerin sırası önemlidir. Bir middleware boru hattında ne kadar erken yer alırsa, o kadar önce çalışır ve isteği daha erken müdahale edebilir veya sonlandırabilir.

C# Dilinde Attribute Geliştirme

Attribute’lar, C# dilinde kod elemanlarına (sınıflar, metotlar, property’ler, parametreler vb.) deklaratif olarak meta veri eklemek için kullanılan güçlü bir yapıdır. Bu meta veriler derleme zamanında statik analiz için kullanılabileceği gibi, çalışma zamanında (runtime) Reflection API’leri aracılığıyla okunarak dinamik davranışlar sergilenmesini de sağlayabilir.

Mevcut Attribute’ları Kullanma

C# ve .NET framework birçok dahili attribute sağlar. Örneğin:

  • [Serializable]: Bir sınıfın serileştirilebilir olduğunu belirtir.
  • [Obsolete]: Bir metodun veya sınıfın kullanım dışı kaldığını ve gelecekte kaldırılacağını belirtir.
  • [Required], [StringLength] (Data Annotations): Model doğrulama kurallarını tanımlar.
  • [Authorize] (ASP.NET Core): Bir metodun veya kontrolcünün yetkilendirme gerektirdiğini belirtir.

Özel Attribute Oluşturma

Kendi iş mantığınızı yansıtan özel attribute’lar oluşturmak oldukça kolaydır. Bir attribute oluşturmak için System.Attribute sınıfından türeyen bir sınıf tanımlamanız yeterlidir.

Örnek: Yetki Kontrolü Attribute’u

Belirli bir izne sahip olmayı gerektiren metotları veya sınıfları işaretlemek için bir attribute oluşturabiliriz:


using System;

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public class RequiresPermissionAttribute : Attribute
{
    public string PermissionName { get; }

    public RequiresPermissionAttribute(string permissionName)
    {
        PermissionName = permissionName;
    }
}

Yukarıdaki örnekte:

  • [AttributeUsage] attribute’u, bu özel attribute’un nerede kullanılabileceğini (metotlar veya sınıflar), türetilmiş sınıflar tarafından miras alınıp alınamayacağını (Inherited = true) ve bir elemana birden fazla kez uygulanıp uygulanamayacağını (AllowMultiple = false) belirtir.
  • PermissionName özelliği, hangi iznin gerekli olduğunu belirler ve yapıcı metot aracılığıyla atanır.

Bu attribute’u bir kontrolcüye veya metoda şu şekilde uygulayabiliriz:


[RequiresPermission("AdminPanelAccess")]
public class AdminController : Controller
{
    // ...
    [RequiresPermission("DeleteUser")]
    public IActionResult DeleteUser(int id)
    {
        // ...
    }
}

Attribute’ları Çalışma Zamanında Okuma (Reflection)

Attribute’ların gerçek gücü, çalışma zamanında Reflection API’leri kullanılarak okunabilmesinden gelir. Bu sayede, kodun kendisini inceleyerek dinamik davranışlar sergileyebilirsiniz.


using System;
using System.Reflection;

public class AttributeReader
{
    public static void ReadPermissions(Type type)
    {
        // Sınıf üzerindeki attribute'ları oku
        var classAttribute = type.GetCustomAttribute<RequiresPermissionAttribute>();
        if (classAttribute != null)
        {
            Console.WriteLine($"Class '{type.Name}' requires permission: {classAttribute.PermissionName}");
        }

        // Metotlar üzerindeki attribute'ları oku
        foreach (var method in type.GetMethods())
        {
            var methodAttribute = method.GetCustomAttribute<RequiresPermissionAttribute>();
            if (methodAttribute != null)
            {
                Console.WriteLine($"Method '{method.Name}' requires permission: {methodAttribute.PermissionName}");
            }
        }
    }

    // Kullanım örneği:
    // AttributeReader.ReadPermissions(typeof(AdminController));
}

Middleware ve Attribute’ların Birlikte Kullanımı

Middleware ve Attribute’lar ayrı ayrı güçlü olsa da, birlikte kullanıldıklarında çok daha dinamik ve esnek çözümler sunabilirler. Tipik bir senaryo, bir middleware’in HTTP isteğini işlerken, hedef endpoint’e veya kontrolcü/aksiyon metoduna uygulanmış özel attribute’ları Reflection aracılığıyla okuyarak isteğin davranışını değiştirmesidir.

Örnek: Middleware İçinde Yetki Attribute’unu Kontrol Etme

Daha önce tanımladığımız RequiresPermissionAttribute‘u bir yetkilendirme middleware’i içinde kullanarak, belirli bir izne sahip olmayan kullanıcıların belirli endpoint’lere erişimini engelleyebiliriz.


using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

public class PermissionAuthorizationMiddleware
{
    private readonly RequestDelegate _next;

    public PermissionAuthorizationMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var endpoint = context.GetEndpoint();
        if (endpoint != null)
        {
            // Endpoint'e veya ilgili metoda uygulanmış RequiresPermissionAttribute'ı ara
            var requiresPermissionAttribute = endpoint.Metadata.GetMetadata<RequiresPermissionAttribute>();

            if (requiresPermissionAttribute != null)
            {
                var requiredPermission = requiresPermissionAttribute.PermissionName;
                // Bu kısımda, kullanıcının 'requiredPermission' adlı izne sahip olup olmadığını kontrol edin
                // Örneğin, JWT claim'lerinden veya bir veritabanından sorgulayarak.
                bool userHasPermission = CheckUserPermissions(context.User, requiredPermission); // Varsayımsal metot

                if (!userHasPermission)
                {
                    context.Response.StatusCode = StatusCodes.Status403Forbidden; // Yetkisiz erişim
                    await context.Response.WriteAsync("You do not have the required permission.");
                    return; // İsteği sonlandır
                }
            }
        }

        await _next(context); // İzin varsa bir sonraki middleware'e geç
    }

    private bool CheckUserPermissions(System.Security.Claims.ClaimsPrincipal user, string permission)
    {
        // Gerçek bir uygulamada, kullanıcının rollerini/izinlerini kontrol eden mantık burada yer alacaktır.
        // Örneğin: user.IsInRole("Admin") || user.HasClaim("Permission", permission)
        if (user == null || !user.Identity.IsAuthenticated) return false;
        
        // Örnek basit bir kontrol: eğer istenen izin "AdminPanelAccess" ise, kullanıcının "Admin" rolüne sahip olması gerekir.
        if (permission == "AdminPanelAccess")
        {
            return user.IsInRole("Admin");
        }
        // Daha karmaşık izin kontrolleri burada yapılabilir.
        return true; // Varsayılan olarak izin ver. Gerçek uygulamada daha katı olunmalı.
    }
}

// Ve bu middleware'i eklemek için uzantı metodu:
public static class PermissionAuthorizationMiddlewareExtensions
{
    public static IApplicationBuilder UsePermissionAuthorization(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<PermissionAuthorizationMiddleware>();
    }
}

Bu yaklaşım, yetkilendirme mantığını merkezi bir middleware’e taşırken, her bir endpoint’in veya metodun gerektirdiği özel izinleri deklaratif olarak attribute’lar aracılığıyla belirtme esnekliğini sunar. Bu, kodun temizliğini artırır ve yetkilendirme kurallarının yönetimini kolaylaştırır.

Özetle, C# dilinde Middleware ve Attribute’lar, modern uygulama geliştirmenin temel taşlarındandır. Middleware, istek işleme boru hattı üzerinde eşsiz bir kontrol sağlayarak çapraz kesen sorunları modüler ve yönetilebilir bir şekilde çözmenizi sağlar. Attribute’lar ise, kodunuza anlam katmanları ekleyerek deklaratif programlamanın gücünü ortaya çıkarır ve çalışma zamanında dinamik davranışlar sergilemenize olanak tanır. Bu ikilinin doğru kullanımı, daha sürdürülebilir, ölçeklenebilir ve güçlü C# uygulamaları geliştirmenize büyük katkı sağlayacaktır.

“`


C# dilinde ASP.NET Core Web API Geliştirme

ASP.NET Core, C# dilinde modern ve yüksek performanslı web API’leri geliştirmek için kullanılan güçlü bir çatıydıır. Bu makalede, ASP.NET Core Web API geliştirme süreci detaylı olarak ele alınacak, temel kavramlardan ileri düzey konulara kadar bilgiler sunulacaktır.

ASP.NET Core Web API Nedir?

ASP.NET Core Web API, RESTful servisler oluşturmak için kullanılan açık kaynaklı bir frameworktür. Özellikle mikroservis mimarileri ve mobil uygulamalar için backend sağlama konusunda oldukça yaygındır. API, HTTP isteklerine yanıt veren ve JSON gibi formatlarda veri döndüren servislerdir. ASP.NET Core, .NET 6 ve sonrası sürümlerde minimal API gibi yeniliklerle daha sade ve hızlı bir geliştirme deneyimi sunar.

Temel Gereksinimler ve Kurulum

ASP.NET Core Web API geliştirmeye başlamak için öncelikle sisteminizde .NET SDK yüklü olması gerekir. En güncel sürümü resmi .NET sitesinden indirip kurabilirsiniz. Ayrıca Visual Studio, Visual Studio Code veya JetBrains Rider gibi bir IDE tercih edebilirsiniz. Geliştirme ortamı hazır olduktan sonra aşağıdaki komutla yeni bir Web API projesi oluşturabilirsiniz:

dotnet new webapi -n MyWebAPI

Bu komut, “MyWebAPI” adında yeni bir ASP.NET Core Web API projesi oluşturur. Projeyi çalıştırmak için şu komutları kullanabilirsiniz:

cd MyWebAPI
dotnet run

Controller Tabanlı API Geliştirme

Geleneksel ASP.NET Core Web API geliştirme yaklaşımında, API’ler controller sınıfları aracılığıyla tanımlanır. ControllerBase sınıfından türeyen bir sınıf, API endpoint’lerini tanımlamak için kullanılır. Aşağıda basit bir ProductsController örneği verilmiştir:

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private static List<Product> products = new()
    {
        new Product { Id = 1, Name = "Laptop", Price = 15000 },
        new Product { Id = 2, Name = "Mouse", Price = 200 }
    };

    [HttpGet]
    public IActionResult Get()
    {
        return Ok(products);
    }

    [HttpGet("{id}")]
    public IActionResult Get(int id)
    {
        var product = products.FirstOrDefault(p => p.Id == id);
        if (product == null)
            return NotFound();

        return Ok(product);
    }

    [HttpPost]
    public IActionResult Post(Product product)
    {
        product.Id = products.Max(p => p.Id) + 1;
        products.Add(product);
        return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
    }
}

Bu controller, GET, POST gibi temel HTTP metodlarını kullanarak ürün listesi yönetimi sağlar. Her metodun üzerine attribute ile route ve HTTP verb bilgisi eklenmiştir.

Minimal API Yaklaşımı

.NET 6 ile birlikte tanıtılan minimal API yaklaşımı, daha az boilerplate kodla hızlıca API endpointleri oluşturmayı sağlar. Bu yaklaşım, özellikle mikroservisler ve prototipleme için uygundur. Basit bir örnek aşağıdaki gibidir:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var products = new List<Product>
{
    new Product { Id = 1, Name = "Laptop", Price = 15000 },
    new Product { Id = 2, Name = "Mouse", Price = 200 }
};

app.MapGet("/api/products", () => products);
app.MapGet("/api/products/{id}", (int id) =>
{
    var product = products.FirstOrDefault(p => p.Id == id);
    return product is not null ? Results.Ok(product) : Results.NotFound();
});

app.Run();

Bu örnekte, app.MapGet ile GET istekleri için endpointler tanımlanmıştır. Minimal API’lerde dependency injection, middleware kullanımı ve route tanımlama işlemleri daha sade bir şekilde yapılmaktadır.

Model Doğrulama ve Hata Yönetimi

API’lerde gelen verilerin doğrulanması büyük önem taşır. ASP.NET Core, model binding ve data annotation’lar aracılığıyla veri doğrulama yapısını sağlar. Örneğin:

public class Product
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Name { get; set; }

    [Range(0, double.MaxValue)]
    public decimal Price { get; set; }
}

Controller içinde ModelState.IsValid ile doğrulama yapılabilir:

[HttpPost]
public IActionResult Post(Product product)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    product.Id = products.Max(p => p.Id) + 1;
    products.Add(product);
    return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
}

Minimal API’lerde ise doğrulama için özel middleware veya sonuç filtreleri yazmak gerekebilir. Ancak daha esnek ve performanslı çözümler de sunulabilir.

Dependency Injection ve Servis Yönetimi

ASP.NET Core, yerleşik dependency injection container’ı ile servis yönetimini kolaylaştırır. Örneğin bir veri servisini uygulamaya şu şekilde ekleyebilirsiniz:

builder.Services.AddScoped<IProductService, ProductService>();

Bu yapı sayesinde controller ya da minimal API endpointleri içerisinde servisleri kullanabilirsiniz. Örneğin:

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductsController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet]
    public async Task<IActionResult> GetAll()
    {
        var products = await _productService.GetAllAsync();
        return Ok(products);
    }
}

Middleware Kullanımı ve Güvenlik

Middleware’ler, HTTP pipeline’ında istekleri işleyen bileşenlerdir. ASP.NET Core’da custom middleware yazılabilir veya hazır middleware’ler kullanılabilir. Örneğin, bir isteğin işlenmeden önce loglanmasını sağlamak için:

app.Use(async (context, next) =>
{
    Console.WriteLine($"Request: {context.Request.Method} {context.Request.Path}");
    await next();
});

Güvenlik açısından JWT tabanlı kimlik doğrulama, CORS politikaları ve HTTPS redirection gibi yapılandırmalar da middleware ile yapılır:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "your-issuer",
            ValidAudience = "your-audience",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-secret-key"))
        };
    });

Veritabanı Entegrasyonu

Entity Framework Core, ASP.NET Core ile uyumlu bir ORM (Object-Relational Mapping) aracıdır. EF Core kullanarak veritabanı işlemleri kolayca yapılabilir. İlk olarak, bir DbContext sınıfı tanımlayın:

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }

    public DbSet<Product> Products { get; set; }
}

Startup’ta servis olarak ekleyin:

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer("connection-string"));

Controller içerisinde şu şekilde kullanabilirsiniz:

[HttpGet]
public async Task<IActionResult> Get()
{
    var products = await _context.Products.ToListAsync();
    return Ok(products);
}

API Versiyonlama ve Dokümantasyon

API’ler büyüdükçe versiyonlama gereklidir. Microsoft.AspNetCore.Mvc.Versioning paketi ile kolayca versiyonlama yapılabilir:

builder.Services.AddApiVersioning(options =>
{
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.ReportApiVersions = true;
});

Swagger (OpenAPI) ile API dokümantasyonu sağlanabilir. Swashbuckle.AspNetCore paketi ile API’leriniz otomatik olarak dokümante edilir ve test edilebilir bir arayüz sunulur:

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

Sonuç: ASP.NET Core Web API, C# dilinde güçlü, hızlı ve esnek RESTful servisler geliştirmek için ideal bir platformdur. Controller tabanlı yapılar ve minimal API yaklaşımı ile farklı ihtiyaçlara uygun çözümler üretilebilir. Veritabanı entegrasyonu, güvenlik, versiyonlama ve dokümantasyon gibi konularla birlikte, bu çatı kurumsal projelerde de yaygın şekilde kullanılmaktadır.


C# dilinde Entity Framework Core Derinlemesine

Entity Framework Core, Microsoft tarafından geliştirilen ve modern .NET uygulamalarında veritabanı işlemlerini kolaylaştırmak için kullanılan popüler bir nesne ilişkisel eşleme (ORM) aracıdır. Bu makalede, Entity Framework Core’un temel yapıları, performans optimizasyonları, migration işlemleri ve daha birçok derinlemesine konu ele alınacaktır.

Entity Framework Core Nedir?

Entity Framework Core (EF Core), .NET platformu için hafif, genişletilebilir ve platform bağımsız bir ORM (Object-Relational Mapping) aracıdır. EF Core, geliştiricilerin veritabanı işlemlerini SQL sorguları yazmadan nesne yönelimli programlama (OOP) yaklaşımıyla gerçekleştirmesine olanak tanır. Bu sayede veritabanı ile uygulama katmanı arasında bir köprü görevi görür.

EF Core, Entity Framework’ün yeniden tasarlanmış halidir. .NET Core ile uyumlu olarak geliştirilmiş olup, .NET 5, 6 ve daha yeni sürümlerinde de desteklenmektedir. Ayrıca açık kaynaklıdır ve GitHub üzerinde aktif olarak geliştirilmektedir.

Temel Bileşenler ve Yapılar

EF Core’u derinlemesine anlamak için öncelikle temel yapı taşlarını bilmek gerekir. Bu yapılar arasında DbContext, DbSet, Entity ve Configuration sınıfları yer alır.

  • DbContext: EF Core uygulamasının kalbidir. Veritabanı bağlantısını yönetir, sorguları çalıştırır ve değişiklikleri veritabanına kaydeder.
  • DbSet: Entity türlerinin koleksiyonunu temsil eder. Örneğin, bir “Products” tablosu için DbSet tanımlanır.
  • Entity: Veritabanındaki bir tabloya karşılık gelen sınıftır. Özellikleri (properties) tablo sütunlarıyla eşleşir.
  • Configuration: Fluent API kullanılarak model yapılandırması yapılır. Öznitelik tabanlı yapılandırmalara alternatif olarak kullanılır.

Veri Modellenmesi ve İlişkiler

EF Core, veritabanı nesnelerinin modellenmesini destekler. Birebir, bire çok ve çoktan çoğa ilişkiler kurulabilir.

Örneğin, bir kullanıcı birden fazla siparişe sahip olabilir. Bu durumda User ile Order arasında bire çok bir ilişki tanımlanabilir:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public int UserId { get; set; }
    public User User { get; set; }
}

Bu ilişkiler, Fluent API ile de yapılandırılabilir. Örneğin:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Order>()
        .HasOne(o => o.User)
        .WithMany(u => u.Orders)
        .HasForeignKey(o => o.UserId);
}

Migration Kullanımı ve Veritabanı Yönetimi

EF Core, migration adı verilen yapılarla veritabanı şemasını kod ile senkronize tutar. Migrationlar, veritabanı şemasında yapılan değişikliklerin izlenmesini ve uygulanmasını sağlar.

Yeni bir migration oluşturmak için aşağıdaki komut kullanılır:

Add-Migration InitialCreate

Bu komut, modelde yapılan değişiklikleri yansıtan bir migration sınıfı oluşturur. Ardından veritabanına uygulamak için:

Update-Database

Migrationlar sayesinde veritabanı şeması versiyonlanabilir ve takım çalışması sırasında senkronizasyon sağlanabilir. Ayrıca, üretim ortamında da migration script’leri oluşturulup uygulanabilir.

Veri Sorgulama Yöntemleri

EF Core ile veri sorgulama LINQ (Language Integrated Query) sorguları ile yapılır. LINQ sayesinde veritabanı sorguları C# kodu gibi yazılabilir.

Basit bir sorgu örneği:

var users = context.Users.Where(u => u.Name.Contains("Ahmet")).ToList();

EF Core aynı zamanda raw SQL sorgularını da destekler. Özellikle karmaşık sorgular veya performans gerektiren işlemler için bu yöntem kullanılabilir:

var users = context.Users.FromSqlRaw("SELECT * FROM Users WHERE Name LIKE '%Ahmet%'").ToList();

İzleme (tracking) ve izleme dışı (no-tracking) sorgular da önemli bir konudur. Özellikle sadece okuma yapılacak sorgularda AsNoTracking() metodu performansı artırabilir:

var users = context.Users.AsNoTracking().ToList();

Performans Optimizasyonu

EF Core performans açısından dikkat edilmesi gereken birçok noktaya sahiptir. Sorgular sırasında gereksiz veri yüklemeleri, N+1 problemi, lazy loading kullanımı gibi etkenler performansı doğrudan etkiler.

  • Eager Loading: Include() yöntemi ile ilişkili verilerin anında yüklenmesi sağlanabilir.
  • Explicit Loading: Gerektiğinde ilişkili verilerin manuel olarak yüklenmesidir.
  • Select Projection: Sadece gerekli alanların seçilmesi, veri trafiğini azaltır.

Örnek olarak, kullanıcılar ve ilişkili siparişler için eager loading kullanımı:

var usersWithOrders = context.Users.Include(u => u.Orders).ToList();

Change Tracking ve EntityState

EF Core, nesneler üzerinde yapılan değişiklikleri otomatik olarak izler. Bu işlem, ChangeTracker bileşeni tarafından yapılır. Nesnelerin durumu EntityState ile izlenir:

  • Unchanged
  • Added
  • Modified
  • Deleted
  • Detached

Bu durumlar, SaveChanges() metodu çağrıldığında hangi işlemlerin veritabanına yansıtılacağını belirler.

Transaction ve Concurrency Kontrolü

EF Core, veritabanı işlemlerinde transaction yönetimini de destekler. Varsayılan olarak her SaveChanges çağrısı bir transaction içinde yürütülür. Ancak ihtiyaç durumunda manuel transaction da başlatılabilir:

using var transaction = context.Database.BeginTransaction();
try
{
    context.Users.Add(new User { Name = "Ali" });
    context.SaveChanges();

    context.Orders.Add(new Order { UserId = 1 });
    context.SaveChanges();

    transaction.Commit();
}
catch
{
    transaction.Rollback();
}

Ayrıca EF Core, optimistik concurrency kontrolünü destekler. Bu yöntem sayesinde veri çakışmaları önlenir. Örneğin ConcurrencyCheck veya Timestamp attribute’leri kullanılabilir.

Advanced Mapping ve Inheritance

EF Core, tablo başına hiyerarşi (TPH), tablo başına tip (TPT) gibi kalıtım yapılarını destekler. TPH yaklaşımında tüm türetilmiş sınıflar tek bir tabloda saklanır ve bir ayırt edici sütun ile ayrılır:

modelBuilder.Entity<BaseEntity>()
    .HasDiscriminator<string>("Type")
    .HasValue<User>("User")
    .HasValue<Admin>("Admin");

Asenkron Programlama ve EF Core

EF Core, asenkron işlemleri destekler. Özellikle web uygulamalarında UI thread’in bloke olmaması için bu yöntemler çok önemlidir. ToListAsync(), SaveChangesAsync() gibi async metotlar kullanılabilir:

var users = await context.Users.Where(u => u.IsActive).ToListAsync();

Sonuç

Entity Framework Core, modern .NET uygulamalarında veritabanı işlemlerini kolaylaştıran güçlü bir ORM aracıdır. Performans, veri modellenmesi ve sorgulama gibi konularda derinlemesine bilgi sahibi olmak, uygulamaların güvenli ve verimli çalışmasını sağlar. EF Core’un sunduğu esneklik ve yetenekler sayesinde, karmaşık veritabanı işlemlerini bile nesne yönelimli bir yaklaşımla yönetmek mümkündür.


C# dilinde Design Patterns (Tasarım Kalıpları)

Design Patterns, yazılım geliştirme sürecinde sıkça karşılaşılan problemlere yönelik standart çözüm önerileridir. C# gibi nesne yönelimli dillerde bu kalıplar, kodun yeniden kullanılabilirliğini artırır, sürdürülebilirliğini sağlar ve geliştirme sürecini kolaylaştırır. Bu makalede, C# dilinde kullanılan temel tasarım kalıpları detaylı bir şekilde ele alınacaktır.

Tasarım Kalıpları Nedir?

Tasarım kalıpları (Design Patterns), yazılım mühendisliğinde tekrar eden problemleri çözmek için ortaya çıkmış, test edilmiş ve yaygın olarak kabul görmüş çözümlerdir. 1990’larda dört yazılım mühendisi (Gang of Four) tarafından yayımlanan “Design Patterns: Elements of Reusable Object-Oriented Software” adlı kitap, bu kalıpların temelini oluşturmuştur. C# gibi nesne yönelimli dillerde tasarım kalıpları, özellikle büyük projelerde kodun daha okunabilir, sürdürülebilir ve esnek olmasına katkı sağlar.

C# Dilinde Tasarım Kalıplarının Önemi

C# dilinin sunduğu güçlü nesne yönelimli programlama özellikleri sayesinde, tasarım kalıplarının uygulanması oldukça kolaydır. Kalıplar, geliştiriciler arasında ortak bir dil oluşturarak iletişim ve iş birliğini artırır. Aynı zamanda, kodun ileride değişikliklere açık hale gelmesini sağlar. Bu sebeple, C# ile profesyonel uygulamalar geliştirirken tasarım kalıplarını bilmek ve uygulamak büyük avantaj sağlar.

Temel Tasarım Kalıpları Kategorileri

Tasarım kalıpları genellikle üç ana kategoriye ayrılır:

1. Creational (Yaratılış) Kalıpları

Bu kalıplar, nesne yaratım sürecini kontrol eder ve daha esnek, yeniden kullanılabilir yapılar oluşturmayı amaçlar. C# dilinde en çok kullanılan creational kalıplar şunlardır:

  • Singleton: Bir sınıfın yalnızca bir örneğinin oluşturulmasını garanti eder. Genellikle loglama, veritabanı bağlantıları veya konfigürasyon yönetimi gibi ihtiyaçlar için kullanılır.
  • Factory Method: Nesne yaratma işlemini alt sınıflara devreder. Bu sayede, hangi nesnenin üretileceği kararı alt sınıflar tarafından verilir.
  • Abstract Factory: Birbirleriyle ilişkili ürün ailelerinin oluşturulmasını sağlar. Farklı platformlara özgü nesnelerin yaratılmasında sıklıkla kullanılır.
  • Builder: Karmaşık nesnelerin adım adım oluşturulmasını sağlar. Nesne yaratımı sırasında farklı varyasyonların oluşturulabilmesini sağlar.
  • Prototype: Mevcut nesnelerin kopyalanarak yeni nesnelerin yaratılmasını sağlar. Performans açısından avantaj sağlar.

2. Structural (Yapısal) Kalıpları

Yapısal tasarım kalıpları, nesnelerin yapılarını ve bileşenleri arasındaki ilişkileri kolaylaştırmayı amaçlar. C# dilinde kullanılan yapısal kalıplar şunlardır:

  • Adapter: Uyumsuz arayüzlerin birlikte çalışmasını sağlar. Var olan bir sınıfı, farklı bir arayüz ile kullanmak gerektiğinde kullanılır.
  • Bridge: Soyutlamayı ve implementasyonu birbirinden ayırarak daha esnek yapılar oluşturmayı sağlar.
  • Composite: Nesneleri ağaç yapısı gibi hiyerarşik yapılar halinde düzenler. Bireysel nesnelerle bileşik nesneler aynı şekilde işlenebilir hale gelir.
  • Decorator: Bir nesneye dinamik olarak yeni sorumluluklar ekler. Kalıtım yerine daha esnek bir alternatif sağlar.
  • Facade: Karmaşık bir alt sistem için sadeleştirilmiş bir arayüz sağlar. Kullanım kolaylığı sunar.
  • Proxy: Bir nesneye erişimi kontrol eder. Özellikle uzaktan erişim veya nesne yaratma maliyetlerinin yüksek olduğu durumlarda kullanılır.

3. Behavioral (Davranışsal) Kalıpları

Bu kalıplar, nesneler arasındaki iletişim ve sorumluluk dağılımını tanımlar. C# dilinde yaygın olan davranışsal kalıplar şunlardır:

  • Observer: Bir nesnede meydana gelen değişiklikleri, ona bağlı diğer nesnelere bildirir. UI bileşenlerinde veya event yönetiminde sıklıkla kullanılır.
  • Strategy: Algoritma ailesini tanımlar ve bunları çalışma zamanında değiştirilebilir hale getirir. Kodda esneklik ve yeniden kullanılabilirlik sağlar.
  • Command: Bir işlemi nesne olarak kapsüller. İşlemlerin kuyrukta tutulması veya geri alınabilmesi gibi durumlarda kullanılır.
  • State: Bir nesnenin durumu değiştiğinde davranışını değiştirmesini sağlar. Nesnenin kendisini farklı sınıflar gibi çalıştırmasına olanak tanır.
  • Template Method: Bir algoritmanın iskeletini tanımlar ama bazı adımların alt sınıflar tarafından yeniden tanımlanmasına izin verir.
  • Chain of Responsibility: Talepleri işleyebilecek nesnelerden oluşan bir zincir oluşturur. Talep, zincir boyunca ilerler ve uygun nesne tarafından işlenir.

C#’ta Singleton Kalıbı Örneği

Singleton kalıbı, en sık kullanılan tasarım kalıplarından biridir. Aşağıda C# dilinde thread-safe bir singleton implementasyonu örneği verilmiştir:


public sealed class Logger
{
    private static Logger _instance = null;
    private static readonly object _lock = new object();

    private Logger() { }

    public static Logger Instance
    {
        get
        {
            if (_instance == null)
            {
                lock (_lock)
                {
                    if (_instance == null)
                    {
                        _instance = new Logger();
                    }
                }
            }
            return _instance;
        }
    }

    public void Log(string message)
    {
        Console.WriteLine($"[LOG] {message}");
    }
}

Design Patterns’in Uygulama Geliştirme Sürecine Katkısı

C# dilinde tasarım kalıplarının uygulanması, uygulama geliştirme sürecinde birçok avantaj sağlar. Özellikle büyük takımlarla çalışırken, tüm geliştiricilerin aynı dili konuşması sağlanır. Ayrıca, kalıplar sayesinde kodun bakımı kolaylaşır, test edilebilirliği artar ve ileride yapılacak değişiklikler için kod esnek hale gelir. İyi uygulanmış tasarım kalıpları, bir projenin yaşam döngüsünü uzatır ve maliyetleri düşürür.

Sonuç

Design Patterns, C# gibi nesne yönelimli dillerde yazılım geliştirme sürecini optimize etmek için kullanılan güçlü araçlardır. Creational, structural ve behavioral kalıplar, farklı senaryolara uygun çözümler sunar. Bu kalıpların bilinçli bir şekilde kullanılması, daha temiz, sürdürülebilir ve anlaşılır kod yazılmasını sağlar. C# geliştiricilerinin bu kalıpları öğrenmesi, profesyonel yazılım projelerinde büyük bir fark yaratır.


© 2002 kiziltas.com - Kamil KIZILTAŞ. Her hakkı saklıdır.