.NET 10’da API Versiyonlama ve OpenAPI Entegrasyonu: Pratik Rehber
Geçen ay bir finans kuruluşundaki müşterimiz beni aradı. “Aşkın bey, API’mize yeni endpoint’ler eklememiz lazım ama mevcut mobil uygulamayı bozamayız, ne yapalım?” dedi. Tanıdık geldi mi? Bence her API geliştiren ekip bu dertle en az bir kere uğraşıyor. Cevap kağıt üstünde basit: API versiyonlama. Ama işin pratiği var ya, hele OpenAPI dokümantasyonuyla düzgün yürütmeye kalkınca, olay biraz dağılıyor; en azından.NET 10 ve yeni Asp.Versioning v10 paketi çıkana kadar durum buydu.
Bu yazıda.NET 10 uygulamalarında API versiyonlamayı nasıl kurduğumuzu, OpenAPI ile nasıl bağladığımızı. SwaggerUI ya da Scalar tarafında nasıl gösterdiğimizi anlatacağım. Hem controller hem de Minimal API yaklaşımına değineceğim,. Biriyle yetinince diğer tarafta ufak sürprizler çıkabiliyor. Hadi başlayalım.
API Versiyonlama Neden Bu Kadar Kritik?
Bakın şimdi, küçük bir startup’sanız ve tek bir mobil uygulamayla yürüyorsanız, versiyonlama olmadan da bir süre idare edersiniz. Ama sadece bir süre. Kurumsal tarafa geçtiğiniz anda işler değişiyor; banka, telekom ya da perakende gibi yapılarda API’yi kullanan istemci sayısı artıyor, biri güncellenirken öteki kırılmasın diye de versiyonlama şart oluyor.
Türkiye’deki şirketler açısından bakınca, PSD2 ve açık bankacılık düzenlemeleriyle birlikte bu konu iyice ciddileşti. Logosoft’ta bir bankacılık projesinde bunu birebir yaşadık — regülatör tarafı yeni alanlar istedi, eski istemciler işe hâlâ v1’e bağlıydı; işte tam orada versiyonlama yoksa ne olurdu diye düşününce insanın içi biraz ürperiyor, çünkü küçük bir değişiklik bile zincirleme problem çıkarabiliyor.
Temel versiyonlama stratejileri şunlar:
- URL Path Versioning:
/api/v1/resource— en yaygın, en anlaşılır yöntem - Query String Versioning:
/api/resource?api-version=1.0— URL’yi temiz tutar ama bazen gözden kaçıyor - Header Versioning:
X-Api-Version: 1.0— API gateway’lerle iyi çalışır - Media Type Versioning:
Accept: application/json;v=1.0— REST puristlerin hoşuna gider ama pratikte biraz uğraştırır (bence en önemlisi)
Bakın, Hangisini seçmelisiniz? Açık konuşayım, çoğu proje için URL path versioning baya iş görüyor. Debug etmesi kolay, Postman’de test etmesi kolay, yeni başlayan geliştiricinin kafasını da çok karıştırmıyor. Işin aslı biraz sade olsun istiyorsanız buradan başlamak mantıklı. Header versioning işe enterprise tarafta daha rahat hissettiriyor, özellikle Azure API Management gibi bir gateway kullanıyorsanız güzel oturuyor ama küçük ekipseniz önce URL path ile başlayın derim.
Evet.
Asp.Versioning v10 ile İlk Kurulum
Asp.Versioning kütüphanesi zaten yıllardır.NET tarafında versiyonlama işini toparlayan araçlardan biri. Ama açık konuşayım,.NET 9’da Microsoft.AspNetCore.OpenApi ile yan yana kullanmaya kalkınca iş biraz dağılıyordu. Özel kod yazıyordunuz, transformer ekliyordunuz, bir de aynı tanımları iki kere görüyordunuz. Hani insanın canını sıkan türden bir uğraş vardı ortada.
Araya gireyim:.NET 10 ile gelen Asp.Versioning v10 paketi bu tarafta işi bayağı toparladı. İlk kez resmî olarak hem.NET 10’u hem de built-in OpenAPI desteğini görüyor. Kurulum da şöyle başlıyor:
dotnet add package Asp.Versioning.Http --version 10.0.0
dotnet add package Asp.Versioning.Mvc.ApiExplorer --version 10.0.0
dotnet add package Microsoft.AspNetCore.OpenApi --version 10.0.0
Program.cs tarafında temel ayar şu şekilde ilerliyor:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
})
.AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
var app = builder.Build();
app.Run();
Dur bir saniye, buradaki AssumeDefaultVersionWhenUnspecified = true kısmı önemli. Versiyon yazmayan isteği otomatik olarak varsayılan sürüme atıyor, yanı pratikte iş görüyor. Ama production’da ben çoğu zaman bunu false yapıyorum. İşte, peki neden? Çünkü istemcinin hangi versiyonu çağırdığını net görmek istiyorum; belirsizlik var ya, debug sırasında insanı gereksiz yere oyalıyor.
Evet.
Controller Tabanlı Versiyonlama
Controller kullanıyorsanız olay attribute’larla çözülüyor, yanı çok fazla kıvrılmadan ilerleyebiliyorsunuz. Şey, ilk bakışta sade dürüyor ama arka planda düzgün çalışması hoş:
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetAll() => Ok(new[] { "Ürün A", "Ürün B" });
}
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("2.0")]
public class ProductsV2Controller : ControllerBase
{
[HttpGet]
public IActionResult GetAll() => Ok(new[]
{
new { Name = "Ürün A", Category = "Elektronik" },
new { Name = "Ürün B", Category = "Gıda" }
});
}
Burada dikkat ettiğim şey şu: aynı endpoint mantığını koruyup çıktıyı değiştiriyorsunuz, böylece eski istemci kırılmıyor ama yeni yapı da kendine yer buluyor. Az önce sade dedim ama aslında mesele sadece sadelik değil, bakım yükünü de azaltıyor; ikisi birleşince fena değil.
Minimal API ile Versiyonlama
Minimal API tarafında iş biraz farklı akıyor, version set kullanıyorsunuz. 2024’te bir müşteride bunu ilk kurduğumda bana biraz garip gelmişti, çünkü akış controller mantığından daha dolambaçlı görünüyordu; sonra testleri açınca bunun da bayağı düzenli bir yapı sunduğunu gördüm.
var versionSet = app.NewApiVersionSet()
.HasApiVersion(new ApiVersion(1, 0))
.HasApiVersion(new ApiVersion(2, 0))
.ReportApiVersions()
.Build();
app.MapGet("api/v{version:apiVersion}/products", () =>
Results.Ok(new[] { "Ürün A", "Ürün B" }))
.WithApiVersionSet(versionSet)
.MapToApiVersion(new ApiVersion(1, 0));
app.MapGet("api/v{version:apiVersion}/products", () =>
Results.Ok(new[]
{
new { Name = "Ürün A", Category = "Elektronik" }
}))
.WithApiVersionSet(versionSet)
.MapToApiVersion(new ApiVersion(2, 0));
Araya gireyim: Neyse, çok dağıtmayayım, burada hayatı nokta şu: aynı route üzerinde iki ayrı versiyonu net biçimde ayırabiliyorsunuz ve OpenAPI tarafı da bunu daha düzgün kavrıyor. Bu kadar mı? Değil tabi; asıl rahatlık, dokümantasyonla kodun birbirinden kopmaması.
OpenAPI Entegrasyonu: Her Versiyon İçin Ayrı Dokümantasyon
İşin can sıkıcı kısmı burada başlıyor. API versiyonlama güzel, tamam. Her sürüm için ayrı bir OpenAPI dokümanı üretmezseniz, istemci tarafı biraz kör kalıyor, hangi endpoint’in hangi versiyona ait olduğu da ortada dolaşıp dürüyor.
Bir bakıma, peki neden?.NET 10 öncesinde bu işi toparlamak için özel IOpenApiDocumentTransformer yazmanız gerekiyordu, üstüne her versiyon için ayrı ayrı AddOpenApiDocument çağrısı yapıyordunuz (ve bunları bir de senkron tutmaya çalışıyordunuz), yanı açık konuşayım, iş biraz el yordamına dönüyordu. Hmm, baya zahmetliydi.
Asp.Versioning v10 ile tablo değişmiş durumda. Paket, API Explorer üzerinden bulunan versiyonları OpenAPI dokümanlarına eşliyor, sonra da her biri için ayrı kapı açıyor:
builder.Services.AddOpenApi();
// Versiyonlama zaten yukarıda konfigüre edildi
// Asp.Versioning.ApiExplorer otomatik olarak
// versiyon başına grup oluşturuyor
var app = builder.Build();
// Her versiyon için ayrı OpenAPI endpoint'i
app.MapOpenApi("/openapi/{documentName}.json");
Bu kadar. Evet, gerçekten bu kadar. Eski yöntemle kıyaslayınca insanın omzu hafifliyor; /openapi/v1.json. /openapi/v2.json gibi çıktılar kendiliğinden oluşuyor, sız de gidip tek tek uğraşmıyorsunuz. Neyse uzatmayalım.
Asp.Versioning v10,.NET 10’un built-in OpenAPI desteğiyle resmî olarak entegre olan ilk sürüm. Artık üçüncü parti workaround’lara gerek yok — tek paket, tek konfigürasyon.
SwaggerUI ve Scalar ile Görselleştirme
OpenAPI dokümanınız var, tamam. Ama dürüst olayım, kimse kalkıp JSON dosyasına bakarak “vay be” demez. Bir arayüz lazım, net. Burada da iki yol çıkıyor karşımıza: SwaggerUI ve Scalar.
SwaggerUI Kurulumu
Önce klasik taraftan gidelim. Swashbuckle.AspNetCore.SwaggerUI paketini ekliyorsunuz ve sonra her versiyon için ayrı endpoint tanımlıyorsunuz; işin aslı çok süslü değil ama düzgün kurunca baya iş görüyor. Bu konuyla ilgili VS Code Python Environments Nisan Güncellemesi: Hız Farkı yazımıza da göz atmanızı tavsiye ederim.
dotnet add package Swashbuckle.AspNetCore.SwaggerUI
// Program.cs'de:
app.UseSwaggerUI(options =>
{
var descriptions = app.DescribeApiVersions();
foreach (var description in descriptions)
{
var url = $"/openapi/{description.GroupName}.json";
var name = $"API {description.GroupName}";
options.SwaggerEndpoint(url, name);
}
});
Scalar: Modern Alternatif
Scalar tarafı biraz daha yeni havalı diyeyim. Görünüşü fena değil, hatta ilk açtığımda “hmm, bu iyi durmuş” dedim; bir arkadaşım geçen ay geçiş yaptı ve “SwaggerUI’a bir daha geri dönmem” diye kestirip attı, biraz iddialı buldum ama neredeyse tamamen haksız da sayılmaz. azure terraform azapi sağlayıcı nasıl kullanılır türkçe rehberi: Adım Adım yazımızda bu konuya da değinmiştik.
dotnet add package Scalar.AspNetCore
app.MapScalarApiReference(options =>
{
options.WithOpenApiRoutePattern("/openapi/{documentName}.json");
});
İkisini yan yana koyunca tablo daha net oluyor. Hani bazen uzun uzun anlatırsın da kafa karışır ya, burada öyle bir durum yok:
| Özellik | SwaggerUI | Scalar |
|---|---|---|
| Kurulum kolaylığı | Orta | Kolay |
| Görsel tasarım | Klasik, tanıdık | Daha modern dürüyor |
| Versiyon desteği | Dropdown ile | Otomatik keşif |
| Topluluk desteği | Çok geniş | Büyüyor |
| Özelleştirme | Baya iyi gidiyor | Daha esnek hissettiriyor |
| Üretim ortamı kullanımı | Tavsiye etmem, genelde kapatılır | Tavsiye etmem, genelde kapatılır |
Tam da öyle.
Evet.
Türkiye’deki Ekipler İçin Pratik Tavsiyeler
Araya gireyim: Şimdi asıl meseleye gelelim. Kaynaklarda hep “şunu yap, bunu yap” diye geçiyor ama Türkiye tarafında iş biraz başka akıyor, hani teoride güzel duran şeyler sahada bazen tökezliyor.
Birincisi, maliyet. Azure API Management kullanıyorsanız — versiyonlu API’leri toparlamak için baya iş gören bir araç bu — Developer tier bile aylık yaklaşık 50 USD civarında geziyor. TL’ye vurunca küçük ekip için hafif değil. Bütçe dar işe, önce Asp.Versioning ile başlayıp reverse proxy tarafında YARP ya da nginx kullanmak daha mantıklı olabilir; ama enterprise tarafta iş değişiyor, orada policy’ler, rate limiting ve analytics gerçekten ayrı bir rahatlık veriyor.
Bir dakika — bununla bitmedi. azure container apps türkçe başlangıç rehberi: Adım Adım Kurulum yazımızda bu konuya da değinmiştik.
İkincisi şu: Bizde sık gördüğüm hata, versiyonlama stratejisini netleştirmeden kod yazmaya başlamak. “Sonra bakarız” deniyor. Olmaz. Sonra çoğu zaman yetişmiyor. İlk günden karar verin; URL path mi olacak, header mı, semantic versioning mi yoksa date-based mi? Bu kararı ertelemek, teknik borcu sessizce büyütüyor, sonra da kimse niye bu kadar karmaşa çıktı diye anlamıyor.
Hani, Bir de.NET 10 tarafı var, burada iş biraz daha ilginç hâle geliyor. Built-in request validation geldiği için API versiyonlama daha da kritik oluyor. V1’de kurallar başka olabilir, v2’de yeni alanlar açılmış olabilir (ve evet, bu farkı sonradan kapatmak epey yoruyor). Her versiyonun kendi validation mantığı olmalı. Bunu Ubuntu 26.04’te.NET 10: Kurulum. Konteyner Rehberi yazımda.NET 10 kurulumu anlatırken de değinmiştim, orada yeni özelliklerin pratik etkisine biraz dokunmuştuk.
Şimdi gelelim işin can alıcı noktasına.
Evet.
Versiyonları Zamanla Nasıl Yönetirsiniz?
API versiyonu açmak kolay. Asıl uğraş, iş büyüyünce eski sürümleri kenara almak; yanı sessiz sedasız emekli etmek. Asp.Versioning burada da iş görüyor:
[ApiVersion("1.0", Deprecated = true)]
public class ProductsController : ControllerBase
{
// v1 hâlâ çalışır ama "deprecated" olarak işaretlenir
}
Bunu ekleyince, OpenAPI dokümanında ve response header’larında (api-deprecated-versions) bu bilgi otomatik görünür. İstemci de uyarıyı alır. Fena değil.
Ama pratikte şunu gördüm, Türkiye’de eski versiyonları kapatmak bazen teknikten çok politik bir konu oluyor; “Ama X bankası hâlâ v1 kullanıyor, kapatamazsınız!” lafı dönüp dürüyor, sız de ortada kalıyorsunuz, o yüzden ben en baştan SLA içine versiyon ömrünü yazın diyorum (mesela “her versiyon GA’den itibaren 18 ay desteklenir” gibi), yoksa sonra iş uzuyor da uzuyor. Peki neden? Çünkü kural baştan yoksa, sonradan herkes kendi istediğini doğru sanıyor. Daha fazla bilgi için Microsoft ve OpenAI Ortaklığının Yeni Dönemi: Ne Değişiyor? yazımıza bakabilirsiniz.
Ha bu arada, Azure DevOps Git Policy Yönetimi: 10x Hız Kazanmanın Yolu yazısında bahsettiğim branch policy’leri de versiyonlama stratejinizle aynı çizgide olmalı. Her majör versiyon için ayrı (söylemesi ayıp) bir release branch tutmak baya işe yarıyor; ben öyle yapınca ekip daha az karışıklık yaşıyor, ama tabi her projede birebir aynı sonucu vermez. Sız ne dersiniz? Daha fazla bilgi için Copilot Student’ta GPT-5.3-Codex Kalktı: Ne Yapmalı? yazımıza bakabilirsiniz.
Benim Karşılaştığım Sorunlar ve Çözümleri
Asp.Versioning v10’u ilk kurcaladığımda ufak ama can sıkıcı bir duvara tosladım. Minimal API route’larında {version:apiVersion} constraint’i bir türlü tanınmıyordu; hata da netti: The constraint reference 'apiVersion' could not be resolved to a type. Peki neden? Çünkü AddApiVersioning‘den sonra .AddApiExplorer() çağrısını da eklemek gerekiyormuş, ben işe sadece ilk kısmı koyup explorer tarafını atlamışım; işte o küçük eksik yüzünden iki saat gitti, baya sınır bozucuydu.
Bir de OpenAPI tarafında versiyon parametresinin required: true diye görünmesi var. İlk bakışta “tamam, normal” diyorsunuz, ama sonra NSwag ya da AutoRest gibi istemci üreten araçlar her istekte bu versiyonu zorunlu tutmaya başlayınca iş biraz değişiyor. Bazı ekipler bunu ister, bazıları istemez; yanı durum biraz gri. AssumeDefaultVersionWhenUnspecified ayarı burada rahatlatıyor, fakat dokümanın içinde parametre yine zorunlu görünüyor. Şey, %100 doğru olmayabilir ama sanırım bir transformer yazıp OpenAPI çıktısında o required bayrağını kaldırmak mümkün.
Evet.
Açık konuşayım, özellik fena değil. Ama bazı edge case’lerde hâlâ toparlanması gerekiyor gibi dürüyor; hani insan kullanırken “tam oturdu” demiyor. Neyse uzatmayayım, zamanla daha da oturur diye düşünüyorum.
İlk Adım Olarak Ne Yapmalısınız?
Eğer elinizde hali hazırda bir.NET proje varsa. Buna versiyonlama eklemek istiyorsanız, işin başında biraz durup nefes alın. Sonra planı netleştirin:
- Önce versiyonlama stratejinizi belirleyin (ben URL path tarafını daha pratik buluyorum)
- Asp.Versioning v10 paketlerini ekleyin
- Mevcut endpoint’lerinizi v1 olarak etiketleyin — hiçbir şeyi kırmadan ilerleyin
- OpenAPI entegrasyonunu kurun, her versiyon için ayrı doküman üretin
- SwaggerUI veya Scalar ekleyin (development ortamı için yeterli oluyor)
- Yeni değişiklikler için v2 oluşturmaya başlayın
- CI/CD pipeline’ınızda her versiyon için ayrı test suite’leri çalıştırın
İlk üç adımı, açık konuşayım, bir günde toparlayabilirsiniz. Ciddi..NET 10 ile Asp.Versioning v10 bu işi baya kolaylaştırmış; ben denediğimde resmen “keşke bu iki yıl önce gelseydi” dedim, çünkü uğraştıran kısımların çoğu artık daha derli toplu ilerliyor ve insanın kafası da biraz rahat ediyor. .NET 10 Data Protection Güvenlik Açığı. Acil Yama yazısında da.NET 10 ekosistemine dair önemli notlar paylaşmıştım, göz atmanızı tavsiye ederim.
Sıkça Sorulan Sorular
API versiyonlama için ne yapmalıyım?
Çoğu proje için URL path versioning (yanı /api/v1/resource gibi bir yapı) en pratik seçenek. Anlaması ve debug etmesi çok kolay, açıkçası bence de en temiz yol bu (ben de ilk duyduğumda şaşırmıştım). Enterprise ortamda API gateway kullanıyorsanız header versioning da fena bir alternatif değil. Ama hangisini seçerseniz seçin — projenin başında karar verin ve o çizgide kalın.
Peki neden?
.NET 10 olmadan Asp.Versioning v10 çalışır mı?
Hayır, çalışmıyor. Asp.Versioning v10 paketi.NET 10 target framework’ünü gerektiriyor..NET 8 veya.NET 9 kullanıyorsanız, Asp.Versioning’in önceki sürümlerini (v8 veya v9) kullanmanız gerekiyor. Tecrübeme göre, built-in OpenAPI entegrasyonunun bu kadar temiz çalışmasını istiyorsanız.NET 10’a geçmeye değer.
Eski API versiyonlarını ne zaman kapatayım?
Açıkçası, Aslında en mantıklısı, her versiyonun ömrünü en baştan belirlemek. Genelde GA tarihinden itibaren 12-18 ay destek verip, önce “deprecated” olarak işaretlemek, ardından 3-6 aylık bir grace period tanıyıp kapatmak işe yarıyor. İstemcilerinize yeterli süre tanıyın, ama süresiz destek vaat etmeyin — bence bu kısmı çoğu ekip atlıyor.
Minimal API mi kullansam, Controller mı?
Küçük ve orta ölçekli projelerde Minimal API yeterli, hem daha az boilerplate kodu oluyor. Büyük kurumsal projelerde işe, özellikle karmaşık iş mantığı olan yerlerde, controller’lar çok daha organize bir yapı sunuyor. Mesela ikisini aynı projede birlikte de kullanabilirsiniz — Asp.Versioning bunu zaten destekliyor.
OpenAPI dokümanlarını production’da açık bırakayım mı?
Bence kapatın ya da authentication arkasına alın. OpenAPI dokümanları hani tüm API yapınızı, endpoint’lerinizi ve veri modellerinizi dışarıya açıyor. Development. Staging ortamlarında açık bırakabilirsiniz, ama production’da ya tamamen kapatın ya da sadece yetkili kullanıcılara açın. Açıkçası bu konuda ödün vermeyin.
Çok konuştum, örnekle göstereyim.
Kaynaklar ve İleri Okuma
Bence, API Versioning in.NET 10 Applications —.NET Blog
Araya gireyim: ASP.NET API Versioning — GitHub Repository
Microsoft.AspNetCore.OpenApi — Resmî Dokümantasyon
İçeriği paylaş:
Bu içerik işinize yaradı mı?
Benzer içerikleri kaçırmamak için beni sosyal medyada takip edin.









Yorum gönder