Eski Bir ASP.NET 3.5 Uygulamasını React + .NET 10'a Modernize Etmek
DigiFlow'un kod tabanını ilk açtığımda, on yılı aşkın süredir üretimde olan bir ASP.NET 3.5 WebForms uygulamasıyla karşı karşıyaydım. Yüzlerce .aspx sayfası, code-behind dosyalarına dağılmış inline SQL sorguları, megabaytlarla ölçülen ViewState blob'ları ve DLL'leri elle bir Windows Server'a kopyalamayı gerektiren bir deployment süreci. Uygulama kritikti — binlerce kamu çalışanının tüm doküman iş akışını çalıştırıyordu — ama kendi ağırlığı altında çökmek üzereydi.
Bu, o eski monolitten modern bir React 19 + .NET 10 yığınına geçişi nasıl yönettiğimin ve bunu tek bir gün bile kesinti yaşamadan mümkün kılan mimari kararların hikâyesi.
Neden Sıfırdan Yeniden Yazmadık?
İlk içgüdü her şeyi sıfırdan yeniden yazmaktı. Yönetim bunu istiyordu, geliştiriciler istiyordu ve açıkçası ben de istiyordum. Ama daha iyisini bilecek kadar çok "büyük patlama" tarzı yeniden yazımın başarısız olduğuna tanık olmuştum. Bunun yerine bir strangler fig (boğan incir) deseni önerdim: yeni sistemi eskisinin etrafında inşa edip, eski uygulamayı güvenle devre dışı bırakana kadar işlevselliği kademeli olarak değiştirecektik.
Kilit içgörü, işe API katmanından başlamaktı. Eski uygulamanın hiçbir API'si yoktu — her şey, sunucuda render edilen sayfalarla sıkıca bağlanmıştı. Önce temiz bir API inşa ederek, hem eski frontend'i hem de yeni React frontend'ini aynı anda besleyebilirdik.
API: İlk Günden İtibaren Clean Architecture
.NET 10'u, MediatR ile Clean Architecture ve CQRS kullanarak seçtim. Klasör yapısı, sorumlulukların ayrımını yansıtıyor:
DigiFlow.API/
Controllers/ # 35 controller, ~200 endpoint
DigiFlow.Application/
Commands/ # MediatR üzerinden yazma işlemleri
Queries/ # MediatR üzerinden okuma işlemleri
Validators/ # FluentValidation kuralları
Mappings/ # AutoMapper profilleri
DigiFlow.Domain/
Entities/
Enums/
Interfaces/
DigiFlow.Infrastructure/
Persistence/ # Dapper + Oracle DB
Services/
Middleware/ # 16 güvenlik middleware'i
Her istek öngörülebilir bir pipeline'dan akar: Controller -> MediatR Handler -> Doğrulama -> İş Mantığı -> Veri Erişimi. Kestirme yok, istisna yok.
Veri Katmanı Zorluğu: Oracle DB
En zorlu kısıtlardan biri mevcut Oracle veritabanıydı. Ondan uzaklaşamazdık — çok fazla başka sistem o tablolara ve stored procedure'lere bağımlıydı. Entity Framework yerine Dapper'ı basit bir nedenle seçtim: performans ve kontrol. Karmaşık Oracle sorguları, cursor çıktı parametreli stored procedure'ler ve 100'den fazla sütunu olan tablolarla uğraşırken SQL'i kendiniz yazmanız gerekir.
csharppublic class GetDocumentByIdQueryHandler
: IRequestHandler<GetDocumentByIdQuery, DocumentDto>
{
private readonly IOracleConnectionFactory _connectionFactory;
private readonly IMapper _mapper;
public async Task<DocumentDto> Handle(
GetDocumentByIdQuery request, CancellationToken ct)
{
using var connection = _connectionFactory.Create();
var parameters = new OracleDynamicParameters();
parameters.Add("p_doc_id", request.DocumentId);
parameters.Add("p_cursor", dbType: OracleDbType.RefCursor,
direction: ParameterDirection.Output);
var document = await connection.QueryFirstOrDefaultAsync<Document>(
"PKG_DOCUMENT.GET_BY_ID",
parameters,
commandType: CommandType.StoredProcedure);
return _mapper.Map<DocumentDto>(document);
}
}
Kimlik Doğrulama: Hibrit Kâbus (Ki İşe Yaradı)
Kamu ağları karmaşıktır. Bazı kullanıcılar Windows Authentication ile domain'e bağlı makinelerde çalışır. Bazıları sisteme dışarıdan Azure AD üzerinden erişir. Bir de bir gecede öldürülemeyen, eski bir session-cookie akışı vardır. Smart Auth Handler dediğim şeyi inşa ettim — gelen isteği inceleyen ve onu uygun handler'a yönlendiren özel bir kimlik doğrulama şeması:
csharppublic class SmartAuthHandler : AuthenticationHandler<SmartAuthOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// 1. Azure AD JWT Bearer token'ı kontrol et
if (Context.Request.Headers.ContainsKey("Authorization"))
return await AuthenticateWithJwt();
// 2. Windows Authentication'ı kontrol et (Negotiate/NTLM)
if (Context.User?.Identity?.IsAuthenticated == true
&& Context.User.Identity is WindowsIdentity)
return AuthenticateWithWindows();
// 3. Session cookie'ye düş
if (Context.Request.Cookies.ContainsKey(".DigiFlow.Session"))
return await AuthenticateWithSession();
return AuthenticateResult.NoResult();
}
}
Bu yaklaşım, kullanıcıları kademeli olarak taşımamıza olanak tanıdı. Birimler Azure AD'ye geçtikçe, frontend'de hiçbir değişiklik olmadan otomatik olarak JWT kimlik doğrulamasını kullanmaya başladılar.
React Frontend
Frontend için React 19'u Vite 7 ve TypeScript 5.9 ile seçtim. WFace adını verdiğimiz özel bileşen kütüphanemizin üzerine, MUI 7 tabanlı olarak inşa ettik; bu kütüphane, dahili erişilebilirlik uyumluluğuna sahip kamu temalı UI bileşenleri sağlıyor.
State yönetimi bilinçli bir tercihti: global istemci durumu için Zustand, sunucu durumu için TanStack React Query ve tema ile yerelleştirme için React Context. Redux yok — kurumsal React uygulamalarının Redux boilerplate'inde boğulduğuna fazlasıyla tanık oldum.
typescript// UI durumu için Zustand store'u - yalın ve öngörülebilir
interface WorkflowStore {
activeModule: WorkflowModule | null;
sidebarCollapsed: boolean;
setActiveModule: (module: WorkflowModule) => void;
toggleSidebar: () => void;
}
export const useWorkflowStore = create<WorkflowStore>((set) => ({
activeModule: null,
sidebarCollapsed: false,
setActiveModule: (module) => set({ activeModule: module }),
toggleSidebar: () => set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })),
}));
Geçiş Stratejisi
8 ay boyunca modül modül geçiş yaptık. Her modül aynı süreci izledi:
- Modülün API endpoint'lerini tam test kapsamıyla inşa et
- Bu endpoint'leri tüketen React sayfalarını inşa et
- Feature flag'lerle 2 hafta boyunca eskisini ve yenisini paralel çalıştır
- Reverse proxy yönlendirmesini güncelleyerek geçişi tamamla
- Bir sonrakine geçmeden önce bir hafta boyunca hata oranlarını ve performansı izle
Reverse proxy (IIS ARR) en iyi dostumuzdu. Eski WebForms uygulamasına bakan /legacy/*'ı korurken, /api/*'ı yeni .NET 10 backend'ine yönlendirebiliyorduk. Kullanıcılar geçişi fark bile etmedi.
Sonuçlar
10 aylık kademeli geçişin ardından:
- Sayfa yüklenme süreleri 8-12 saniyeden 1,5 saniyenin altına düştü
- API yanıt süreleri ortalama 45ms oldu (eşdeğer WebForms postback'leri için 2-3 saniyeden düşerek)
- Geliştirici verimliliği üç katına çıktı — haftalar süren yeni özellikler artık günler sürüyor
- Tüm geçiş boyunca sıfır kesinti
- Kritik iş mantığını kapsayan 131 test dosyası
Eski ASP.NET 3.5 uygulaması, nadiren kullanılan iki modül için hâlâ çalışıyor. 2026'nın 2. çeyreğine kadar devre dışı bırakılacak. Ama önemli olan, artık ilerlemenin önünde durmaması.
Çıkarılan Dersler
- Asla yeniden yazma, her zaman taşı. Strangler fig deseni bizi "ikinci sistem etkisinden" kurtardı.
- API ile başla. Temiz bir API katmanı, diğer her şeyin üzerine inşa edildiği temeldir.
- Hibrit kimlik doğrulama dağınıktır ama gereklidir. Kurumsal ortamlar nadiren tek bir kimlik doğrulama mekanizması lüksüne sahiptir.
- Eski veritabanları için EF yerine Dapper. Şemayı kontrol edemediğinizde, tam SQL kontrolüne ihtiyacınız olur.
- Feature flag'ler pazarlık konusu değildir. Her modül geçişi bir flag arkasında tutuldu.
Benzer bir eski sistem modernizasyonu zorluğuyla karşı karşıyaysanız, tavsiyem basit: sabırlı olun, kademeli ilerleyin ve daha en baştan doğru soyutlamaları inşa edin. İlk hafta seçtiğiniz mimari, sizi ya kurtaracak ya da yıllarca peşinizi bırakmayacak.