Blazor 의 InputFile 을 이용한 파일 업로드에 대한 의문

안녕하세요.

Blazor wasm 를 테스트 해보고 있습니다. 파일 업로드를 위해 InputFile과 OnChange 이벤트를 이용해서 서버로 파일 업로드를 아래와 같이 구현 했습니다.


<InputFile OnChange="@OnChooseFile"/>

...중략...

private async void OnChooseFile(InputFileChangeEventArgs e)
{
    Console.WriteLine($"file info : {e.File.Name} , {e.File.Size}");
    using (var filestream = e.File.OpenReadStream(1024 * 300 * 1000))
    {
        var url = GetApiUrl("/upload/bigfiletest");
        var httpclient = new HttpClient();
        httpclient.DefaultRequestHeaders.Add("filename" , e.File.Name); 
        
        var r = await httpclient.PostAsync(url, new StreamContent(filestream));
        if (r.IsSuccessStatusCode == false)
        {
            var raw = await r.Content.ReadAsStringAsync();
        }
        else
        {
            var raw = await r.Content.ReadAsStringAsync();
        }
    }
}

결과적으로는 잘 업로드 되나, 한가지 개운하지 못한 점은 큰 파일을 업로드 하게 되면 문제를 확인 할 수 있는데, e.File.OpenStream 에서 파일 전체 bytes를 다 읽고 나서 Post 하는것 처럼 보입니다.

이렇게 생각하는 이유는 Postman, Javascript 의 fetch, 별도의 Console 프로젝트 위에 HttpClient 를 이용해서 동일한 파일을 업로드 하게 되면 Post 하자마자 거의 시간차 없이 서버가 반응 하는것을 확인 했습니다만 위의 코드 처럼 e.File.OpenStream 을 이용하면 프로시져가 시작된 한~참 뒤에 서버가 반응 하는것을 확인 했습니다.

  • 질문
  1. 혹시 Blazor 의 InputFile 의 내부적인 동작을 설명 해주실 분이 있으실지요 ?
  2. 혹은 문제 상황 처럼 메모리에 모두 적제 한뒤 서버로 Post 하지 않고, 서버로 업로드 할 방법이 있을까요?
    (사실 불필요 하게 메모리에 적재 하지 않기 위해 OpenStream 을 이용한건데, 뜻대로 동작하지 않네요)

감사합니다.

3 Likes

답은 아니지만 razor 영역에서 순수한 fetch 를 통해 업로드를 구현한 예시를 공유 합니다.

@page "/UploadTest2"
<h3>UploadTest2</h3>
@inherits IMBucket.Client.Pages.HttpReqPageBase


@* HTML 로 구현한 동일한 버튼 *@
<fluent-button appearance="accent" class="accent" type="file" id="fbt1">
      BUTTON3
</fluent-button>

@* 숨겨진 파일 업로드 컨트롤 *@
<input type="file" id="file" style="display: none">

<br/>


<script>
    const fileinput = document.getElementById("file");
    const fbt1 = document.getElementById("fbt1");
    fbt1.addEventListener("click", function () {
        console.log("change ");
        fileinput.click();
    });
    
    fileinput.addEventListener("change", function () {
        console.log("change: ");
        console.log(fileinput.files);
        uploadFile(fileinput.files[0]);
    });
    
    function uploadFile(file)
    {
        console.log("uploadFile : " + file.name);
        const url = "https://myservice.com/upload"
        
        fetch(url, {
            method: "POST",
            body: file,
            signal : AbortSignal.timeout(50000),
            headers: {
              "filename": file.name,
              "Content-Type": "application/octet-stream"
            }
        }).then(response => {
            console.log("res:" + response);
        return response.json();
        }).catch(error => {
            console.log("err" + error);
        });
        
        return file;
    }
    
</script>


@code {

}

(Javascript 에 익숙치 못해 군데 군데 console log 가 있네요 :slightly_smiling_face:)
주요 아이디어는 input 컨트롤을 만들고, 이것의 모양이 지금 적용하고 있는 스타일 (Fluent) 와 어울리지 않기에, 숨김 처리 (display:none) 하고, 별도의 버튼을 클릭 하면 숨겼던 input을 강제로 클릭 (fileinput.click()) 시켜 파일 브라우져를 엽니다.
이후 change 이벤트에서 일상(?) 적으로 fetch를 수행 하여 파일을 업로드 합니다.

이러한 방법은 c# 을 이용하여 스트림을 얻어 대용량 파일도 클라이언트 메모리 적제 없이 첫번째 패킷부터 서버에 곧바로 업로드 됩니다 (아시는것 처럼 :grinning:)

일전에 razor 를 사용할때 항상 js 파일을 별도로 만들어 그것을 JSRuntime 으로 로드 하여 사용 하였는데, 이렇게 간편하게 razor 안에서도 를 사용 할 수 있는지는 이번에 알게 되었네요. 간단한 코드는 이 방법이 한결 더 간편 한것 같습니다.

3 Likes


출처
InputFile 이 어떻게 동작하는지에 대한 한가지 단서로 위의 문구를 MSDN에서 발견 하였습니다.

어줍잖은 지식으로 뇌피셜 해보면 c# 레벨에서 브라우져를 통해 획득한 데이터 활용을 위해, 스트림을 호출 하면 모든 byte를 base64로 바꾼 후 다시 이것을 활용 하는것이 아닐까 하는 생각 입니다. 때문에 어디 서버로 다시 POST 하려면 또 한번 더 시간이 걸리는 것 아닐까 하는 아이디어 입니다.

혹시 다른 인사이트나 정보가 있으신분은 공유 바랍니다.;

2 Likes
1 Like

감사합니다. 용도가 다른것으로 이해 하겠습니다.

1 Like