Updated at 2021.6.10
Blazor and Image File
Web App에서 사용자 컴퓨터에 있는 이미지 파일을 선택하여 웹에 보여주고, 필요시 서버에 업로드할 필요가 있다. 여기서는 Blazor에서 파일을 선택하고 Data Uri
를 이용하여 이미지를 보여주는 기능을 구현해 볼 것이다.
다음 3가지에 대한 지식이 필요하다.
파일 선택 및 로딩 하기
InputFile Component
파일 선택 창을 보여주고 로컬 위치에서 파일을 선택할 때 그 파일에 대한 정보로 반환하는 컴포넌트이다. 기본적으로 HTML에서의 <input>
요소를 렌더링한 것이다.
<input type="file" multiple>
일반적인 사용법은 다음과 같다.
<InputFile OnChange="@LoadFiles" multiple />
@code {
private void LoadFiles(InputFileChangeEventArgs e)
{
...
}
}
OnChange
이벤트에서 사용되는 메서드는 InputFileChangeEventArgs
파라미터를 포함하고 있는데, 이 클래스는 다음과 같은 프로퍼티와 메서드를 가지고 있다.
- Properties
File
:IBrowserFile
인테페이스 타입이다.
- Methods
- IReadOnlyList
GetMultipleFiles
(int maximumFileCount):multiple
옵션 선택시IBrowserFile
리스트를 반환하는 함수이다.
- IReadOnlyList
위 둘을 활용하여 파일 브라우저에서 선택된 파일의 정보를 가지고 있는 IBrowserFile
를 얻을 수 있다.
IBrowserFile Interface
InputFile
컴포넌트에서 선택된 파일에 대한 정보를 가지고 있는 인터페이스로서 다음과 같은 속성과 메소드를 통해 파일 정보를 얻거나 스트림으로 읽어 오고, 이미지 파일의 형식 및 사이즈를 변경할 수 있다.
- Properties
ContentType
: MIME 타입LastModified
Name
Size
: 몇 바이트인가?
- Methods
- Stream
OpenReadStream
(long maxAllowedSize, CancellationToken cancellationToken)
- Stream
- Extension Methods
- ValueTask
RequestImageFileAsync
(this IBrowserFile browserFile, string format, int maxWidth, int maxHeight)
- ValueTask
위의 두 메서드에 대한 사용 예는 다음과 같다.
private void LoadFiles(InputFileChangeEventArgs e)
{
...
foreach (var file in e.GetMultipleFiles(MaxAllowedFiles))
{
string format = "image/jpeg";
IBrowserFile imageFile = await file.RequestImageFileAsync(format, 980, 980);
Stream fileStream = imageFile.OpenReadStream(maxFileSize);
}
...
}
이미지 데이터를 Uri로
우선 Uri과 Url의 차이에 대해 알아보자.
- Uri: Uniform Resource Identifier (식별자)
- Url: Uniform Resource Locator (위치), 인터넷에서 웹페이지, 이미지, 비디오 등 리스스의 위치를 나타내는 문자열이다.
Uri
Uri의 일반적인 구조는 다음과 같다.
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
scheme
: 프로토콜, 웹에서는 일반적으로http
또는https
이다.user
,passwd
: 인증된 사용자만 접속할 경우의 이름과 비밀번호이다.host
,port
: 접속할 서버의 호스트 이름과 포트 번호이다.path
: 접속한 서버 내에서의 상세 경로이다.query
: 요청할 정보에 대한 추가적인 파라미터로 사용된다fragment
: 요청한 정보 내에서의 추가적인 위치를 나타낸다.
Data Uri
data:
스킴을 활용하여 컨텐츠를 문서 내에 인라인으로 포함시킬 수 있는 방법이다. 기본 구문은 아래와 같다.
data:[<mediatype>][;base64],<data>
mediatype
은 MIME 타입으로 JPEG 이미지는image/jpeg
이다. 생략도 가능한데, 생략하면 기본값은text/plain;charset=US-ASCII
이다.- 텍스트가 아니면
base64
로 인코딩된 이진데이터를 덧붙이면 된다. - 몇가지 예제
- data:,Hello%2C%20World! (단순 텍스트 포함시, 컴마를 빼먹으면 안됨)
- data:text/plain;base64,xxx (MIME 타입 명시하고, 데이터 포멧이 이진화 된 것일 때)
적용하기
우리가 구현하고자 하는 것은 이미지 파일을 이진화 시켜서 웹앱에 인라인으로 포함시키고자 하는 것이어서 다음과 같이 MemoryStream
으로 읽어 들인 데이터를 Uri로 만든다.
...
string format = "image/jpeg";
MemoryStream memoryStream = new MemoryStream();
await fileStream.CopyToAsync(memoryStream);
string url = $"data:{format};base64,{Convert.ToBase64String(memoryStream.ToArray())}";
Full Source Code
실제 실행가능한 소스 코드 및 실행 화면은 다음과 같다.
ImagePreview.razor
<p>
<InputFile OnChange="LoadImages" id="input-image" accept="image/png,image/gif,image/jpeg" multiple />
<span id="exception-message">@exceptionMessage</span>
</p>
@if (isLoading)
{
<p>Loading...</p>
}
@foreach (var (file, content) in loadedFiles)
{
<p id="file-@(file.Name)">
<strong>Name:</strong> <span id="file-name">@(file.Name)</span><br />
<strong>Last modified:</strong> <span id="file-last-modified">@(file.LastModified.ToString())</span><br />
<strong>Size (bytes):</strong> <span id="file-size">@(file.Size)</span><br />
<strong>Content type:</strong> <span id="file-content-type">@(file.ContentType)</span><br />
<img id="image-uploaded" src="@content" /><br />
</p>
}
@using System.IO;
@code {
[Parameter]
public int MaxAllowedFiles { get; set; } = 5;
Dictionary<IBrowserFile, string> loadedFiles = new Dictionary<IBrowserFile, string>();
long maxFileSize = 1024 * 1024 * 15;
bool isLoading;
string exceptionMessage;
async Task LoadImages(InputFileChangeEventArgs e)
{
isLoading = true;
loadedFiles.Clear();
exceptionMessage = string.Empty;
try
{
foreach (var file in e.GetMultipleFiles(MaxAllowedFiles))
{
StateHasChanged();
loadedFiles.Add(file, await LoadImage(file));
}
}
catch (Exception ex)
{
exceptionMessage = ex.Message;
}
isLoading = false;
}
async Task<string> LoadImage(IBrowserFile file)
{
var format = "image/jpeg";
var imageFile = await file.RequestImageFileAsync(format, 980, 980);
using var fileStream = imageFile.OpenReadStream(maxFileSize);
using var memoryStream = new MemoryStream();
await fileStream.CopyToAsync(memoryStream);
return $"data:{format};base64,{Convert.ToBase64String(memoryStream.ToArray())}";
}
}
댓글
댓글 쓰기