← Tüm yazılar

ZipArchive ile dosyaları akış üzerinden paketlemek

Birden çok dosyayı tek indirmede sunmak için önce diskte geçici bir ZIP oluşturmak şart değil. Küçük çıktılarda akış üzerinde paketleme yeterli; asıl dikkat gereken yer akışların hangi sırada kapatıldığı.

t.Turgay
ZipArchive ile dosyaları akış üzerinden paketlemek

Birkaç rapor dosyasını tek indirmede sunarken araya geçici klasör, zamanlanmış temizleme ve ikinci bir dosya okuma koymak her zaman gerekli değil. Küçük çıktıları doğrudan bir akışta ZIP haline getirebilirim. Buradaki kritik ayrıntı sıkıştırma çağrısından çok kaynak sahipliği: entry, arşiv ve taşıyıcı akış hangi sırada kapanacak?

Arşiv tamamlanmadan dosya tamamlanmış sayılmaz

ZIP içinde yalnız dosya içerikleri bulunmaz; arşivin dizin bilgileri de yazılır. Bu yüzden “stream'e birkaç byte yazıldı” ile “indirilebilir ZIP tamamlandı” farklı durumlardır. Örneği .NET 10 konsol projesinde çalıştırdığında iki metin dosyası oluşturulur, ardından aynı çıktı tekrar açılarak giriş adları okunur. Ek paket veya gerçek proje belgesi gerekmiyor.

using System.IO.Compression;
using System.Text;
using var output = new MemoryStream();
using (var zip = new ZipArchive(output, ZipArchiveMode.Create, leaveOpen: true))
{
    foreach (var file in new[] { (Name:"readme.txt", Text:"hello"), (Name:"notes.txt", Text:"world") })
    {
        var entry = zip.CreateEntry(Path.GetFileName(file.Name));
        using var writer = new StreamWriter(entry.Open(), new UTF8Encoding(false));
        writer.Write(file.Text);
    }
}
output.Position = 0;
using var check = new ZipArchive(output, ZipArchiveMode.Read);
Console.WriteLine(string.Join(",", check.Entries.Select(e => e.FullName)));

Üç yaşam süresi var. StreamWriter tek entry'ye yazıyor ve döngünün o adımında kapanıyor. ZipArchive bütün girişleri kapsıyor; using bloğundan çıkarken arşivi tamamlıyor. MemoryStream ise leaveOpen: true sayesinde o kapanıştan sonra da kullanılabiliyor. Dıştaki stream'i erkenden kapatırsam sonradan Position atamak veya arşivi okumak mümkün olmaz.

Position değerinin görünmeyen etkisi

Yazma bittiğinde MemoryStream imleci sonda durur. Okuma için Position = 0 atamak, aynı byte dizisini baştan yorumlamayı sağlar. Bu işlem “arşivi oluşturma” adımı değildir; yalnız okuma konumunu değiştirir. Bazı API'ler bir stream yerine byte[] ister; ToArray tüm içeriği yeni bir diziye kopyalar. Küçük dosyada önemsiz görünen bu fark büyük raporda ikinci bir bellek kopyası anlamına gelir.

Örnekteki StreamWriter UTF-8 metin yazar. Gerçek PDF, görsel veya XLSX dosyası paketleyeceksem bu dosyaları metne çevirip tekrar yazmam; kaynak stream'den entry stream'ine CopyToAsync ile byte akışını aktarırım. Metin kodlaması, metin dosyasının sözleşmesidir; ZIP içindeki her dosyanın ortak dönüşüm adımı değildir.

Dosya adını daraltmak yeterli mi?

Path.GetFileName, örnekte dizin parçalarını atıp giriş adını küçültüyor. Ancak iki kaynak aynı ada sahipse çakışmayı çözmez. Arşiv içinde hangi adı kullanacağımı baştan seçerim: rapor türü ve kayıt kimliği gibi anlamlı, kararlı bir birleşim okunabilir olur. Kullanıcının beklediği iki dosyadan birinin isim yüzünden kaybolduğu bir arşiv, teknik olarak açılıyor olsa da doğru çıktı değildir.

Burada güvenilir içerikten arşiv oluşturuyorum. Dışarıdan gelen ZIP'i diske çıkarmak ayrı bir güvenlik sınırıdır; her hedef yolu izin verilen dizinin içinde doğrulamak gerekir. Arşiv girdisinin adını uygulamanın dosya sistemi yolu gibi kullanmak, ZIP üretme örneğinden çıkarılacak bir sonuç değildir.

MemoryStream seçimini boyut belirler

Küçük raporları bellekte hazırlamak, çıktı tamamlanmadan yanıt göndermemeyi sağlar. Büyük arşivde ise bütün ZIP'in bellekte tutulması ve olası ek byte[] kopyaları pahalılaşır. Doğrudan HTTP yanıtına yazmayı seçersem başlık gönderildikten sonra oluşan bir hatada düzgün JSON hata yanıtına dönemeyebilirim. Geçici dosya veya arka plan üretimi, büyük çıktı için daha denetlenebilir bir tercih olabilir.

İptal sinyalini dosya okuma ve yazma çağrılarına taşırım. İndirmeyi kapatan istemci için rapor üretmeye devam etmek gereksiz iş yaratır. Hazır bir arşivi tekrar sunacaksam saklama süresi ve erişim kontrolü de dosyanın kendisi kadar önemlidir.

Açılıyor olması ilk kontroldür

Örnekte beklenen isimler readme.txt ve notes.txt. Testte bunların yanında içeriklerini de okuyup hello ve world değerlerini doğrularım. Boş girdi, Türkçe karakterli metin ve aynı adlı iki dosya ayrı senaryolardır. Yazma esnasında hata oluşturmak da değerlidir: yarım kalmış ZIP'in kullanıcıya başarılı indirme diye sunulmadığını görmek isterim.

ZipArchive yaşam döngüsü; Stream.CopyToAsync.

✳ Bu yörüngenin sonuna geldin.
Diğer yazıları keşfet →
turgay.Biraz kod, biraz piksel, bolca merak.© 2026 · Dünya’dan sevgilerle ✳