Blazor 파일 업로드 처리 — InputFile 컴포넌트 완전 가이드

파일 업로드 처리

📚 파일 업로드 처리

학습 포인트

InputFile 컴포넌트로 파일을 선택·업로드하고 서버에 저장하는 흐름을 구현합니다.

📌 학습 목표

  • InputFile 컴포넌트의 기본 사용법과 주요 속성을 이해합니다.
  • IBrowserFile 인터페이스의 Name, Size, ContentType, OpenReadStream을 활용할 수 있습니다.
  • 단일 파일 및 다중 파일 업로드를 각각 구현할 수 있습니다.
  • 업로드된 파일을 서버 디렉터리에 안전하게 저장하는 코드를 작성할 수 있습니다.
  • 파일 크기 제한, MIME 타입 검증, 무작위 파일명 생성 등 보안 처리를 올바르게 적용할 수 있습니다.

📝 개념 설명

InputFile 컴포넌트란?

Blazor는 HTML의 <input type='file'>에 대응하는 내장 컴포넌트 InputFile을 제공합니다. 이 컴포넌트는 브라우저의 파일 선택 대화상자를 열고, 선택된 파일의 정보를 IBrowserFile 형태로 .NET 코드에 전달합니다.

 

Blazor Server와 Blazor WebAssembly 모두 동일한 InputFile 컴포넌트를 사용하지만, 파일을 최종 저장하는 방식이 다릅니다.

  • Blazor Server: 서버 측 코드가 직접 실행되므로 서버 파일 시스템에 바로 저장 가능
  • Blazor WebAssembly: 브라우저에서 실행되므로 Web API 엔드포인트를 통해 서버로 전송 필요

IBrowserFile 인터페이스

파일이 선택되면 OnChange 이벤트가 발생하고, 이벤트 인수인 InputFileChangeEventArgs에서 IBrowserFile 객체를 꺼낼 수 있습니다. 이 인터페이스는 파일 처리에 필요한 핵심 정보를 제공합니다.

 

 

📌 IBrowserFile 핵심 속성 & 메서드

 

📛
Name
원본 파일 이름. 서버 저장 시 직접 사용하지 말고 Guid 등 무작위 이름으로 반드시 교체
📏
Size
파일 크기(바이트). 업로드 전 용량 제한 검증에 사용
🗂️
ContentType
MIME 타입 문자열(예: image/jpeg). 허용 형식 서버 측 화이트리스트 검증에 활용
🔓
OpenReadStream
maxAllowedSize를 인수로 받아 파일 스트림 반환. 인수 없이 호출 시 기본 최대 500KB

파일 업로드 전체 흐름

Blazor에서 파일이 업로드되어 서버에 저장되기까지의 과정을 순서대로 이해하면 구현이 명확해집니다.

 

 

🔄 Blazor 파일 업로드 처리 흐름

 

① InputFile에서
파일 선택
② OnChange
이벤트 발생
③ IBrowserFile
객체 획득
④ 크기·형식
검증
⑤ OpenReadStream
스트림 열기
⑥ CopyToAsync로
서버에 저장

💡 예제 & 실습

1. 단일 파일 업로드 (Blazor Server)

가장 기본적인 형태의 단일 파일 업로드입니다. wwwroot/uploads 폴더에 저장하며, 보안을 위해 원본 파일명 대신 GUID 기반 무작위 이름을 사용합니다.

@page "/file-upload"
@inject IWebHostEnvironment Env

<h3>파일 업로드</h3>

<InputFile />

@if (!string.IsNullOrEmpty(message))
{
    <p>@message</p>
}

@code {
    private string message = string.Empty;

    private async Task HandleFileSelected(InputFileChangeEventArgs e)
    {
        IBrowserFile file = e.File;

        // 1. 파일 크기 검증 (최대 5 MB)
        const long maxSize = 5 * 1024 * 1024;
        if (file.Size > maxSize)
        {
            message = "파일 크기가 5 MB를 초과합니다.";
            return;
        }

        // 2. 저장 경로 설정 — 무작위 파일명(보안)
        var uploadsDir = Path.Combine(Env.WebRootPath, "uploads");
        Directory.CreateDirectory(uploadsDir);

        var ext         = Path.GetExtension(file.Name);
        var safeFileName = $"{Guid.NewGuid()}{ext}";
        var filePath    = Path.Combine(uploadsDir, safeFileName);

        // 3. 스트림을 열어 파일로 복사
        await using var stream = file.OpenReadStream(maxAllowedSize: maxSize);
        await using var fs     = File.Create(filePath);
        await stream.CopyToAsync(fs);

        message = $"업로드 완료: {safeFileName}";
    }
}
💡 IWebHostEnvironment 주입
컴포넌트 상단에 @inject IWebHostEnvironment Env를 선언하면 Env.WebRootPathwwwroot의 절대 경로를 얻을 수 있습니다. ASP.NET Core가 자동으로 등록하므로 Program.cs에 별도 코드 없이 사용 가능합니다.

2. 다중 파일 업로드

multiple 속성을 추가하고 e.GetMultipleFiles()로 파일 목록을 순회하여 저장합니다. maximumFileCount를 명시해 과도한 파일 전송을 차단합니다.

@page "/multi-upload"
@inject IWebHostEnvironment Env

<h3>다중 파일 업로드</h3>

<InputFile multiple />

@if (uploadedNames.Count > 0)
{
    <ul>
        @foreach (var name in uploadedNames)
        {
            <li>@name</li>
        }
    </ul>
}

@code {
    private List<string> uploadedNames = new();

    private async Task HandleMultipleFiles(InputFileChangeEventArgs e)
    {
        uploadedNames.Clear();
        const int  maxFiles = 5;                   // 최대 5개
        const long maxSize  = 5 * 1024 * 1024;    // 파일당 5 MB

        var files = e.GetMultipleFiles(maximumFileCount: maxFiles);

        var uploadsDir = Path.Combine(Env.WebRootPath, "uploads");
        Directory.CreateDirectory(uploadsDir);

        foreach (var file in files)
        {
            var ext          = Path.GetExtension(file.Name);
            var safeFileName = $"{Guid.NewGuid()}{ext}";
            var filePath     = Path.Combine(uploadsDir, safeFileName);

            await using var stream = file.OpenReadStream(maxAllowedSize: maxSize);
            await using var fs     = File.Create(filePath);
            await stream.CopyToAsync(fs);

            uploadedNames.Add($"{file.Name} → {safeFileName}");
            StateHasChanged(); // 파일마다 목록 즉시 갱신
        }
    }
}

3. 파일 형식 필터링

HTML accept 속성은 파일 선택 대화상자에서 표시할 형식을 브라우저에 힌트로 줍니다. 단, 클라이언트 측 제한이므로 서버에서도 반드시 ContentType을 검증해야 합니다.

<!-- 이미지 파일만 표시 -->
<InputFile accept=".jpg,.jpeg,.png,.gif,.webp" />

<!-- PDF만 표시 -->
<InputFile accept=".pdf" />

서버 측 MIME 타입 화이트리스트 검증:

private static readonly HashSet<string> AllowedTypes = new()
{
    "image/jpeg", "image/png", "image/gif", "image/webp"
};

private async Task HandleImage(InputFileChangeEventArgs e)
{
    var file = e.File;

    if (!AllowedTypes.Contains(file.ContentType))
    {
        message = $"허용되지 않는 파일 형식: {file.ContentType}";
        return;
    }

    // 크기 검증 및 저장 코드 (위 예제와 동일)
}

4. 업로드 진행률 표시

대용량 파일을 처리할 때는 청크(buffer) 단위로 읽으면서 진행률을 계산하고, StateHasChanged()를 호출해 UI를 실시간으로 갱신합니다.

@page "/upload-progress"
@inject IWebHostEnvironment Env

<h3>파일 업로드 (진행률)</h3>
<InputFile />

@if (progress > 0)
{
    <progress value="@progress" max="100" style="width:320px"></progress>
    <span> @progress%</span>
}

@code {
    private int progress = 0;

    private async Task HandleWithProgress(InputFileChangeEventArgs e)
    {
        progress = 0;
        var  file       = e.File;
        const long maxSize   = 50 * 1024 * 1024; // 50 MB
        long totalBytes = file.Size;
        long readBytes  = 0;
        const int bufferSize = 81920; // 80 KB 청크

        var uploadsDir = Path.Combine(Env.WebRootPath, "uploads");
        Directory.CreateDirectory(uploadsDir);
        var filePath = Path.Combine(uploadsDir,
            $"{Guid.NewGuid()}{Path.GetExtension(file.Name)}");

        await using var src  = file.OpenReadStream(maxAllowedSize: maxSize);
        await using var dest = File.Create(filePath);

        var buffer = new byte[bufferSize];
        int bytesRead;

        while ((bytesRead = await src.ReadAsync(buffer)) != 0)
        {
            await dest.WriteAsync(buffer.AsMemory(0, bytesRead));
            readBytes += bytesRead;
            progress   = (int)(readBytes * 100 / totalBytes);
            StateHasChanged(); // UI 갱신
        }

        progress = 100;
        StateHasChanged();
    }
}

5. Blazor WebAssembly에서 Web API로 전송

Blazor WebAssembly는 서버 파일 시스템에 직접 접근할 수 없으므로, MultipartFormDataContent로 파일 스트림을 HTTP POST 요청에 담아 Web API 컨트롤러로 전송합니다.

클라이언트 컴포넌트 (Blazor WASM):
@inject HttpClient Http

private string message = string.Empty;

private async Task UploadToApi(InputFileChangeEventArgs e)
{
    var file    = e.File;
    const long maxSize = 5 * 1024 * 1024;

    var stream  = file.OpenReadStream(maxAllowedSize: maxSize);
    var content = new MultipartFormDataContent();
    content.Add(new StreamContent(stream), "file", file.Name);

    var response = await Http.PostAsync("/api/upload", content);
    message = response.IsSuccessStatusCode ? "업로드 성공" : "업로드 실패";
}
서버 Web API 컨트롤러:
[ApiController]
[Route("api/[controller]")]
public class UploadController : ControllerBase
{
    private readonly IWebHostEnvironment _env;
    public UploadController(IWebHostEnvironment env) => _env = env;

    [HttpPost]
    public async Task<IActionResult> Upload(IFormFile file)
    {
        if (file == null || file.Length == 0)
            return BadRequest("파일이 없습니다.");

        var dir = Path.Combine(_env.WebRootPath, "uploads");
        Directory.CreateDirectory(dir);

        var safeFileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
        var path         = Path.Combine(dir, safeFileName);

        await using var fs = System.IO.File.Create(path);
        await file.CopyToAsync(fs);

        return Ok(new { FileName = safeFileName });
    }
}

⚠️ 자주 틀리는 것 / 주의사항

⚠️ 1. OpenReadStream의 기본 최대 크기는 500 KB
file.OpenReadStream()을 인수 없이 호출하면 기본 최대 500 KB까지만 읽으며, 그보다 큰 파일에서 예외가 발생합니다. 반드시 maxAllowedSize를 명시하세요.
예) file.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024)
⚠️ 2. 원본 파일명(file.Name)을 서버 경로에 직접 사용 금지
업로드된 파일명에 ../../../etc/passwd 같은 경로 탐색 문자열이 포함될 수 있습니다(Path Traversal 공격). 반드시 Guid.NewGuid()로 무작위 이름을 생성하고, Path.GetExtension(file.Name)으로 확장자만 가져오세요.
⚠️ 3. accept 속성은 UI 힌트일 뿐 — 서버 검증 필수
HTML accept 속성은 파일 선택 대화상자의 표시 형식을 제한하지만, 공격자는 이를 무시하고 임의 파일을 전송할 수 있습니다. 서버 측 file.ContentType 화이트리스트 검증을 반드시 추가하세요.
⚠️ 4. GetMultipleFiles() 기본 최대 개수는 10개
e.GetMultipleFiles()를 매개변수 없이 호출하면 최대 10개 파일을 허용합니다. 의도적인 대량 파일 전송 공격을 막으려면 maximumFileCount를 명시하여 허용 개수를 제한하세요.
⚠️ 5. Blazor Server에서 SignalR 메시지 크기 제한
Blazor Server는 SignalR을 통해 서버-클라이언트가 통신합니다. 기본 메시지 크기 제한 때문에 대용량 파일 업로드 시 오류가 발생할 수 있습니다. Program.cs에서 제한을 명시적으로 늘려주세요.
builder.Services.AddSignalR(o => o.MaximumReceiveMessageSize = 10 * 1024 * 1024);

🎯 마무리

Blazor 파일 업로드의 핵심은 InputFile 컴포넌트IBrowserFile 인터페이스를 조합하는 것입니다. OnChange 이벤트에서 파일을 받아 크기·형식을 검증하고, OpenReadStream으로 스트림을 열어 CopyToAsync로 서버에 저장하는 패턴이 핵심입니다. 이 패턴은 단일 파일, 다중 파일, 진행률 표시 어느 경우에나 동일하게 적용됩니다.

 

실전 팁: 프로덕션 환경에서는 로컬 디스크 대신 Azure Blob StorageAmazon S3 같은 클라우드 스토리지를 사용하는 것이 확장성과 가용성 면에서 유리합니다. 이 경우에도 OpenReadStream으로 스트림을 열어 클라우드 SDK의 업로드 메서드에 전달하는 패턴은 동일하게 적용됩니다.

✅ 핵심 정리
  • InputFileOnChange 이벤트로 파일 선택을 감지하고 IBrowserFile 객체를 제공하는 Blazor 내장 컴포넌트입니다.
  • 단일 파일: e.File / 다중 파일: e.GetMultipleFiles(maximumFileCount: N)
  • OpenReadStream(maxAllowedSize)로 스트림을 열고 CopyToAsync로 서버 파일 시스템에 저장합니다.
  • 보안 3원칙: ① 파일명은 Guid로 교체 ② ContentType 서버 화이트리스트 검증 ③ 파일 크기 제한 명시
  • Blazor WebAssembly는 MultipartFormDataContent로 Web API에 전송하고, 서버는 IFormFile로 수신합니다.
  • 대용량 파일은 청크 단위 읽기로 진행률을 계산하고 StateHasChanged()로 UI를 실시간 갱신합니다.
  • Blazor Server 사용 시 SignalR의 MaximumReceiveMessageSizeProgram.cs에서 반드시 조정해야 합니다.

댓글 남기기

Wordpress Social Share Plugin powered by Ultimatelysocial
Copy link
URL has been copied successfully!
THREADS
RSS
error: 저작권 콘텐츠보호를 부탁드립니다.