서론
.NET Core 프로젝트에서 여러 파일을 단일 아카이브로 묶어야 하는 경우가 종종 발생합니다. 다양한 라이브러리 중에서 ICSharpCode.SharpZipLib 는 성능과 호환성 측면에서 신뢰할 수 있는 선택지입니다. 본 가이드에서는 해당 라이브러리를 이용하여 로컬 폴더 압축, 원격 파일 통합 압축, 그리고 압축 파일 내부의 폴더 구조 유지 방법을 설명합니다.
1. 패키지 설치
프로젝트에 SharpZipLib 를 추가하기 위해 NuGet 패키지 관리자 또는 CLI 를 사용합니다.
Install-Package ICSharpCode.SharpZipLib
2. 로컬 폴더 재귀 압축
지정한 디렉토리 내의 모든 하위 파일 및 폴더를 포함하여 ZIP 파일로 생성하는 로직입니다. 경로 결합 시 Path.Combine 을 사용하여 운영체제 호환성을 높였습니다.
public static string CompressDirectory(string sourcePath, string outputZipPath)
{
if (!Directory.Exists(sourcePath))
throw new DirectoryNotFoundException($"Source path not found: {sourcePath}");
string directoryPath = Path.GetDirectoryName(outputZipPath);
if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
using (FileStream fs = File.Create(outputZipPath))
using (ZipOutputStream zipStream = new ZipOutputStream(fs))
{
zipStream.SetLevel(6); // 압축 수준 설정 (0-9)
ProcessFolder(sourcePath, zipStream, sourcePath);
zipStream.Finish();
}
return outputZipPath;
}
private static void ProcessFolder(string path, ZipOutputStream zipStream, string rootPath)
{
string[] entries = Directory.GetFileSystemEntries(path);
foreach (string entry in entries)
{
if (Directory.Exists(entry))
{
ProcessFolder(entry, zipStream, rootPath);
}
else
{
using (FileStream fileStream = File.OpenRead(entry))
{
string relativePath = entry.Substring(rootPath.Length).TrimStart('\\', '/');
ZipEntry zipEntry = new ZipEntry(relativePath)
{
DateTime = File.GetLastWriteTime(entry),
Size = fileStream.Length
};
zipStream.PutNextEntry(zipEntry);
fileStream.CopyTo(zipStream);
zipStream.CloseEntry();
}
}
}
}
3. 원격 파일 목록 압축
URL 목록을 입력받아 각 파일을 다운로드한 후 단일 ZIP 파일로 저장합니다. WebClient 대신 현대적인 HttpClient 를 사용하여 비동기 처리를 고려한 구조로 개선하였습니다.
public static void ArchiveRemoteFiles(List<string> urls, string destinationPath)
{
string dirPath = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrEmpty(dirPath) && !Directory.Exists(dirPath))
{
Directory.CreateDirectory(dirPath);
}
using (HttpClient client = new HttpClient())
using (FileStream fs = File.Create(destinationPath))
using (ZipOutputStream zos = new ZipOutputStream(fs))
{
zos.SetLevel(9);
foreach (string url in urls)
{
byte[] fileData = client.GetByteArrayAsync(url).Result;
string fileName = ExtractFileNameFromUri(url);
ZipEntry entry = new ZipEntry(fileName)
{
DateTime = DateTime.Now,
Size = fileData.Length
};
zos.PutNextEntry(entry);
zos.Write(fileData, 0, fileData.Length);
zos.CloseEntry();
}
zos.Finish();
}
}
private static string ExtractFileNameFromUri(string uri)
{
if (string.IsNullOrWhiteSpace(uri)) return "unknown.dat";
char separator = uri.Contains("/") ? '/' : '\\';
return uri.Substring(uri.LastIndexOf(separator) + 1);
}
4. 압축 내부 폴더 구조 정의
단순 파일 목록이 아닌, ZIP 파일 내부에 특정 폴더 계층 구조를 생성하려면 파일 항목 모델에 내부 경로를 포함해야 합니다. 이를 위해 ArchiveResource 클래스를 정의합니다.
public class ArchiveResource
{
public string InternalPath { get; set; } // ZIP 내부에 저장될 경로 (예: images/logo.png)
public string SourceUrl { get; set; } // 실제 파일 소스 URL
}
이 모델을 사용하여 압축을 수행하는 메서드는 다음과 같습니다.
public static string CreateStructuredZip(string zipFileName, List<ArchiveResource> resources, string baseSavePath)
{
string datePath = Path.Combine(DateTime.Now.Year.ToString(), DateTime.Now.Month.ToString(), DateTime.Now.Day.ToString());
string targetDirectory = Path.Combine(baseSavePath, "archives", datePath);
if (!Directory.Exists(targetDirectory))
Directory.CreateDirectory(targetDirectory);
string fullPath = Path.Combine(targetDirectory, zipFileName);
try
{
using (HttpClient client = new HttpClient())
using (FileStream fs = File.Create(fullPath))
using (ZipOutputStream zos = new ZipOutputStream(fs))
{
zos.SetLevel(9);
foreach (var resource in resources)
{
byte[] data = client.GetByteArrayAsync(resource.SourceUrl).Result;
ZipEntry entry = new ZipEntry(resource.InternalPath)
{
DateTime = DateTime.Now,
Size = data.Length
};
zos.PutNextEntry(entry);
zos.Write(data, 0, data.Length);
zos.CloseEntry();
}
zos.Finish();
}
return fullPath;
}
catch (Exception ex)
{
throw new InvalidOperationException($"Archive creation failed: {ex.Message}", ex);
}
}
호출 시 다음과 같은 형태로 데이터를 전달하여 ZIP 내부에 원하는 폴더 트리를 구성할 수 있습니다.
var resources = new List<ArchiveResource>
{
new ArchiveResource { InternalPath = "docs/readme.txt", SourceUrl = "https://example.com/files/readme.txt" },
new ArchiveResource { InternalPath = "images/photo.jpg", SourceUrl = "https://example.com/files/photo.jpg" }
};
CreateStructuredZip("project_bundle.zip", resources, "/var/storage");