.NET Framework Projesini Modern .NET'e Taşıma Rehberi: Envanterden Canlıya Kademeli Geçiş
Mehmet Fatih İçin · · 11 dk okuma
Kurumların büyük bir kısmında hâlâ .NET Framework 4.x üzerinde çalışan, yıllardır işini gören ama her yıl biraz daha zor bakımı yapılan uygulamalar var. .NET Framework 4.8.1 Windows ile birlikte destek almaya devam ediyor, yani bu uygulamalar bir anda çalışmaz hale gelmeyecek. Ancak yeni özellikler, performans iyileştirmeleri, Linux ve konteyner desteği, modern kütüphaneler ve geliştirici araçları artık yalnızca modern .NET tarafında geliyor. Kasım 2025'te yayınlanan .NET 10, uzun süreli destek (LTS) sürümü olduğu için taşıma projeleri için mantıklı bir hedef. Bu yazıda, eski bir .NET Framework uygulamasını modern .NET'e taşırken izlediğim adımları anlatıyorum.
1. Önce neden taşıdığınızı yazın
“Eski teknoloji” tek başına bir taşıma gerekçesi değildir. Taşıma projeleri, iş birimine görünür bir fayda sunmadığı için kolayca ertelenir ya da yarıda kalır. Bu yüzden ilk adımda somut gerekçeleri yazarım: Uygulama Linux sunucuya ya da konteynere mi taşınacak? Performans sorunu mu var? Kullanılan bir kütüphanenin yeni sürümleri yalnızca modern .NET'i mi destekliyor? Yeni geliştiriciler eski teknolojiyle çalışmak istemiyor mu? Güvenlik denetimleri güncel bir çalışma zamanı mı istiyor? Gerekçe, taşımanın kapsamını ve önceliğini belirler.
2. Envanter: neyle karşı karşıyasınız?
Taşımanın zorluğu, projenin boyutundan çok kullandığı teknolojilere bağlıdır. Bir sınıf kütüphanesi ya da konsol uygulaması çoğu zaman birkaç saatte taşınır. ASP.NET Web Forms, WCF sunucusu ya da System.Web'e sıkı bağlı bir MVC uygulaması ise ciddi bir yeniden yazım gerektirebilir. Envanterde her proje için şunları çıkarırım:
- Proje türü: sınıf kütüphanesi, konsol, Windows hizmeti, ASP.NET MVC, Web API, Web Forms, WCF, WinForms, WPF.
- Hedef framework sürümü ve proje dosyası biçimi (eski tarz mı, SDK tarzı mı).
- NuGet paketleri ve her birinin modern .NET'i destekleyen bir sürümünün olup olmadığı.
- Doğrudan referans verilen DLL'ler; özellikle kaynak kodu olmayan, üçüncü taraf ya da şirket içi eski kütüphaneler.
System.Web,System.ServiceModel,AppDomain, .NET Remoting,BinaryFormattergibi modern .NET'te bulunmayan ya da desteklenmeyen API kullanımları.- Windows'a özgü bağımlılıklar: kayıt defteri, COM, Windows kimlik doğrulama, olay günlüğü.
Proje bağımlılık grafiğini çıkarmak da önemlidir. Taşıma her zaman yapraklardan, yani başka hiçbir projeye bağımlı olmayan kütüphanelerden başlar ve uygulamanın giriş noktasına doğru ilerler.
3. Araçlar: Upgrade Assistant ve SDK tarzı proje dosyası
Microsoft'un .NET Upgrade Assistant aracı, Visual Studio eklentisi ve komut satırı aracı olarak kullanılabilir. Proje dosyasını SDK tarzına çevirir, hedef framework'ü günceller, paketleri uyumlu sürümlere yükseltir ve bilinen API değişiklikleri için öneriler sunar. Aracı bir sihirli değnek olarak değil, mekanik işlerin çoğunu yapan bir yardımcı olarak görmek gerekir. Bazı ortamlarda eski try-convert aracı da proje dosyalarını SDK tarzına çevirmek için hâlâ kullanılır.
Eski tarz bir proje dosyası, her kaynak dosyayı tek tek listeleyen yüzlerce satırdan oluşur. SDK tarzı proje dosyası ise çok kısadır:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.66" />
</ItemGroup>
</Project>
Burada önemli bir teknik var: TargetFrameworks ile kütüphaneyi aynı anda hem net48 hem net10.0 için derlemek. Bu çoklu hedefleme, ortak kütüphaneleri eski uygulamayı bozmadan modern .NET'e hazırlamanızı sağlar. Kütüphane her iki tarafta da derlendiği sürece, eski uygulama onu kullanmaya devam eder, yeni uygulama da aynı kodu kullanabilir. Platforma özgü küçük farkları #if NET yönergeleriyle ayırabilirsiniz. Önce bütün ortak kütüphaneleri çoklu hedeflemeye geçirip testlerini her iki tarafta çalıştırmak, taşımanın en güvenli başlangıcıdır.
4. System.Web: en büyük engel
ASP.NET Core, System.Web üzerine kurulu değildir. HttpContext.Current, Session, HttpModule, HttpHandler, Global.asax ve Web Forms sayfaları modern .NET'te yoktur. Bu yüzden web uygulamalarında taşıma, kodun ne kadarının doğrudan bu API'lere bağlı olduğuna göre zorlaşır.
İlk iş, iş mantığını web katmanından ayırmaktır. Denetleyici (controller) içine yazılmış veritabanı sorguları, HttpContext.Current üzerinden okunan kullanıcı bilgileri ve oturumda tutulan durumlar, taşımadan önce servis sınıflarına çekilmelidir. Bu çalışma eski uygulamada yapılabilir ve taşımadan bağımsız olarak kodun kalitesini de artırır.
Modüller ve işleyiciler ASP.NET Core'da ara katmanlara (middleware) dönüşür:
// Eski: IHttpModule ile her isteğe korelasyon kimliği
// Yeni: middleware
app.Use(async (ctx, next) =>
{
var id = ctx.Request.Headers["X-Correlation-Id"].FirstOrDefault()
?? Guid.NewGuid().ToString("N");
ctx.Response.Headers["X-Correlation-Id"] = id;
using (logger.BeginScope(new Dictionary<string, object> { ["CorrelationId"] = id }))
await next();
});
Web Forms uygulamaları için doğrudan bir taşıma yolu yoktur; sayfalar Razor Pages, MVC ya da Blazor ile yeniden yazılır. Bu durumda aşağıda anlatacağım kademeli geçiş yaklaşımı daha da önem kazanır.
5. WCF servisleri: CoreWCF mi, gRPC mi, REST mi?
Modern .NET, WCF istemcisini destekler; yani başka bir sisteme ait bir WCF/SOAP servisini çağırmak için System.ServiceModel paketlerini kullanmaya devam edebilirsiniz. Kamu kurumlarının birçok SOAP servisine bu şekilde bağlanılır. Sunucu tarafında ise WCF modern .NET'te yoktur. Üç seçenek vardır:
- CoreWCF: Topluluk tarafından geliştirilen ve Microsoft tarafından desteklenen, WCF sunucu programlama modelinin modern .NET uyarlaması. Mevcut istemcileri değiştirmeden servisi taşımak istiyorsanız en az riskli yoldur. Her bağlama (binding) türünü desteklemediği için kullandığınız yapılandırmaları kontrol etmek gerekir.
- gRPC: Yüksek performanslı, sözleşme tabanlı iletişim. İç servisler arasında, istemcileri de sizin kontrolünüzde olduğunda iyi bir seçimdir.
- REST API: Tarayıcılar, mobil uygulamalar ve dış paydaşlar için en yaygın ve en kolay tüketilen seçenek.
Pratikte çoğu zaman bunların bir karışımına gidilir: dış paydaşların kullandığı SOAP servisi geçiş süresince CoreWCF ile ayakta tutulur, yeni istemciler REST ya da gRPC ile bağlanır, eski uç nokta tüm istemciler taşındıktan sonra kapatılır.
6. Yapılandırma: Web.config'den appsettings.json'a
ConfigurationManager.AppSettings ve connectionStrings bölümü, modern .NET'te yerini appsettings.json, ortam değişkenleri ve güçlü tipli seçenek (options) sınıflarına bırakır:
// appsettings.json
{
"ConnectionStrings": { "Ana": "Server=...;Database=...;" },
"Odeme": { "ZamanAsimiSaniye": 10, "TekrarSayisi": 2 }
}
// Program.cs
builder.Services.Configure<OdemeAyarlari>(builder.Configuration.GetSection("Odeme"));
public sealed class OdemeAyarlari
{
public int ZamanAsimiSaniye { get; init; }
public int TekrarSayisi { get; init; }
}
Bu geçiş, gizli bilgileri yapılandırma dosyasından çıkarmak için de iyi bir fırsattır. Bağlantı dizelerindeki parolaları ve API anahtarlarını kaynak kod deposundan uzak tutmak, ortam değişkenleri ya da bir gizli bilgi yönetim servisi kullanmak gerekir. Geçiş süresince eski ve yeni uygulamanın aynı ayarları farklı biçimlerde okuyacağını unutmayın; ayarları tek bir kaynaktan üretmek tutarsızlıkları önler.
7. Entity Framework 6 mı, EF Core mu?
EF6, 6.3 sürümünden itibaren modern .NET üzerinde de çalışır. Bu, taşımada çok değerli bir esneklik sağlar: veri erişim katmanını aynı anda değiştirmek zorunda değilsiniz. Önce uygulamayı EF6 ile modern .NET'e taşıyıp çalışır hale getirmek, ardından ayrı bir adım olarak EF Core'a geçmek riski böler.
EF Core'a geçmek performans, yeni özellikler ve uzun vadeli destek açısından doğru hedeftir, ancak birebir aynı davranmaz. EDMX tabanlı (veritabanından tasarım) modeller EF Core'da desteklenmez; modelin koda dönüştürülmesi gerekir. Tembel yükleme varsayılan olarak kapalıdır, bazı LINQ sorguları farklı SQL üretir, istemci tarafında çalıştırılan sorgu parçaları EF Core'da hata verir. Bu yüzden geçişten önce kritik sorgular için üretilen SQL'i ve süreleri kaydetmek, geçişten sonra karşılaştırmak gerekir. Performansın kritik olduğu sorgularda EF yerine Dapper ya da saklı yordam kullanmak da meşru bir seçenektir.
8. Kademeli geçiş: strangler fig ve YARP
Büyük bir web uygulamasını tek seferde yeniden yazıp bir gecede canlıya almak, taşıma projelerinin en riskli yoludur. Daha güvenli yaklaşım, “strangler fig” (boğan incir) olarak bilinen kademeli geçiştir: yeni uygulama eski uygulamanın önüne konur, istekler sayfa sayfa ya da uç nokta uç nokta yeni uygulamaya alınır, henüz taşınmamış her şey eski uygulamaya yönlendirilir. Zamanla eski uygulamaya giden trafik sıfıra iner ve kapatılır.
Microsoft'un YARP (Yet Another Reverse Proxy) kütüphanesi bu yaklaşımı ASP.NET Core içinde kolayca uygulamanızı sağlar:
// Program.cs (yeni ASP.NET Core uygulaması)
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapControllers(); // taşınmış uç noktalar burada çalışır
app.MapReverseProxy(); // geri kalan her şey eski uygulamaya gider
app.Run();
// appsettings.json
"ReverseProxy": {
"Routes": { "eski": { "ClusterId": "eski", "Order": 1000, "Match": { "Path": "{**yol}" } } },
"Clusters": { "eski": { "Destinations": { "d1": { "Address": "https://eski-uygulama/" } } } }
}
Bu yapıda en zor konu, iki uygulama arasında paylaşılan durumlardır: oturum, kimlik doğrulama çerezi ve önbellek. Microsoft'un System.Web adaptörleri, eski ve yeni uygulama arasında oturum ve kimlik paylaşımı için yardımcı bileşenler sunar. Mümkünse oturumda tutulan durumu azaltmak ve kimlik doğrulamayı her iki uygulamanın da tanıdığı ortak bir mekanizmaya taşımak, geçişi büyük ölçüde kolaylaştırır.
9. Test ve karşılaştırma
Taşımanın başarısı “derleniyor” ile değil, “aynı girdiye aynı çıktıyı veriyor” ile ölçülür. Taşımadan önce mevcut davranışı yakalayan testler yazmak bu yüzden kritiktir. Birim testleri az olan eski projelerde, uygulamanın uç noktalarını gerçek isteklerle çağıran ve yanıtları kaydeden karakterizasyon testleri daha hızlı sonuç verir. Aynı istekleri eski ve yeni uygulamaya gönderip yanıtları karşılaştırmak, gözden kaçan davranış farklarını ortaya çıkarır.
Dikkat edilmesi gereken tipik farklar şunlardır: JSON serileştirmede alan adlarının büyük-küçük harfi ve tarih biçimi (Newtonsoft.Json ile System.Text.Json farklı varsayılanlar kullanır), kültüre bağlı sayı ve tarih biçimleri, hata yanıtlarının yapısı ve HTTP durum kodları. Uyumluluğu korumak için geçiş süresince Newtonsoft.Json'a devam etmek de bir seçenektir.
Performans için, eski ve yeni uygulamayı aynı donanımda, aynı veriyle ve aynı yük testiyle ölçmek gerekir. BenchmarkDotNet kritik kod yolları için, k6 ya da benzeri araçlar uç noktaların yük altındaki davranışı için iyi sonuç verir. Modern .NET genellikle belirgin performans kazancı sağlar, ancak bunu varsaymak yerine ölçmek, hem regresyonları yakalar hem de projenin faydasını iş birimine somut olarak göstermenizi sağlar.
10. Taşıma kontrol listesi
- Taşıma gerekçesi ve başarı ölçütü yazılı.
- Projelerin, paketlerin ve desteklenmeyen API'lerin envanteri çıkarılmış.
- Bağımlılık grafiği hazır; taşıma sırası yapraklardan başlıyor.
- Ortak kütüphaneler SDK tarzı proje dosyasına ve çoklu hedeflemeye geçirilmiş.
- İş mantığı
System.Web'den ayrılmış. - WCF sunucuları için CoreWCF, gRPC ya da REST kararı verilmiş.
- Yapılandırma
appsettings.jsonve seçenek sınıflarına taşınmış; gizli bilgiler depodan çıkarılmış. - Veri erişimi için EF6 ile devam mı, EF Core'a geçiş mi kararı ve kritik sorgu karşılaştırması yapılmış.
- Kademeli geçiş için ters vekil sunucu (YARP) ve paylaşılan oturum/kimlik çözümü kurulmuş.
- Karakterizasyon testleri ve performans karşılaştırması tamamlanmış; geri dönüş planı hazır.
Sonuç
Bir .NET Framework uygulamasını modern .NET'e taşımak, doğru planlandığında yıllar sürecek bir yeniden yazım projesi olmak zorunda değildir. Ortak kütüphaneleri çoklu hedeflemeyle hazırlamak, iş mantığını web katmanından ayırmak, veri erişimini ayrı bir adımda ele almak ve yeni uygulamayı eskisinin önüne koyarak kademeli ilerlemek, riski küçük ve yönetilebilir parçalara böler. Kurumsal sistemlerde uyguladığım bu yaklaşımın bir örneğini kurumsal ERP ve merkezi e-bilet ve gişe vaka çalışmalarında görebilirsin.
İlgili yazılar: .NET ile güvenli REST API tasarımı ve SQL Server'da yavaş sorgu analizi. Taşıma sırasında yapılandırma ve yanıtları incelemek için JSON biçimlendirici, kimlik doğrulama token'larını kontrol etmek için JWT çözücü ve yönlendirme kurallarını denemek için regex test aracını kullanabilirsin. Eski bir .NET uygulamasını taşımak için destek arıyorsan teklif formundan yazabilirsin.