BlazorServer(.NET 8/Interactive Server)でクライアントサイドで選択したフォルダ内に含まれる複数のファイルをストリーミングを使ってサーバーへアップロードして一時フォルダに保存する最小構成のサンプル。
今回はクライアントサイドスクリプトをMicrosoft.TypeScript.MSBuild
パッケージを使ってTypeScriptで書いてみましたが、トランスパイル済のJavaScriptソースも掲載しています。
画面構成
Input
で選択したフォルダ内のファイルをUpload
ボタンでアップロードします。
サンプルソースコード(C#/TypeScript/JavaScript)
エラー処理は適宜実装してください。保存先はC:\temp\
決め打ちです。
Uploader.razor:
@page "/uploader"
@rendermode InteractiveServer
@using System.Runtime.CompilerServices
@implements IAsyncDisposable
@inject IJSRuntime JS
<input id="fileInput" type="file" webkitdirectory multiple disabled="@disabled" />
<button @onclick="OnUploadClick" disabled="@disabled">Upload</button>
@code {
private IJSObjectReference? module;
private bool disabled;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>(
"import",
"./Components/Pages/Uploader.razor.js");
}
}
public async ValueTask DisposeAsync()
{
if (module != null)
{
await module.DisposeAsync();
}
}
private async Task OnUploadClick()
{
if (module != null)
{
var jsor = await module.InvokeAsync<IJSObjectReference>("getFileInfo", "fileInput");
if (jsor != null)
{
disabled = true;
StateHasChanged();
var numOfFiles = await jsor.InvokeAsync<int>("getCount");
for (var i = 0; i < numOfFiles; ++i)
{
var name = await jsor.InvokeAsync<string>("getName", i);
// パスは今回のサンプルでは使わないのでコメントアウト
// var relativePath = await jsor.InvokeAsync<string>("getRelativePath", i);
var size = await jsor.InvokeAsync<int>("getSize", i);
var streamRef = await jsor.InvokeAsync<IJSStreamReference>("getStream", i);
using var readStream = await streamRef.OpenReadStreamAsync(maxAllowedSize: 1024 * 1024);
// サーバー側にファイルを保存
var tempFilePath = Path.Combine(@"C:\temp", $"{i:D3}_{name}");
using FileStream fs = new FileStream(tempFilePath, FileMode.Create);
await readStream.CopyToAsync(fs);
}
disabled = false;
StateHasChanged();
}
}
}
}
Uploader.razor.ts
export async function getFileInfo(inputId: string): Promise<FileInfo | null> {
const input = <HTMLInputElement>document.getElementById(inputId);
if (!input || !input.files || input.files.length == 0)
return null;
return new FileInfo(input.files);
}
class FileInfo {
private files: File[] = [];
constructor(files: FileList) {
for (let i = 0; i < files.length; ++i) {
const file = files.item(i);
if (file)
this.files.push(file);
}
}
getCount(): number {
return this.files.length;
}
getName(index: number): string {
return this.files[index].name;
}
getRelativePath(index: number): string {
return this.files[index].webkitRelativePath;
}
getSize(index: number): number {
return this.files[index].size;
}
getStream(index: number): File {
return this.files[index];
}
}
Uploader.razor.js(トランスパイル後/ES2022):
export async function getFileInfo(inputId) {
const input = document.getElementById(inputId);
if (!input || !input.files || input.files.length == 0)
return null;
return new FileInfo(input.files);
}
class FileInfo {
files = [];
constructor(files) {
for (let i = 0; i < files.length; ++i) {
const file = files.item(i);
if (file)
this.files.push(file);
}
}
getCount() {
return this.files.length;
}
getName(index) {
return this.files[index].name;
}
getRelativePath(index) {
return this.files[index].webkitRelativePath;
}
getSize(index) {
return this.files[index].size;
}
getStream(index) {
return this.files[index];
}
}
以上です。