(19/30)一緒にBlazorを学ぼう:画像アップロード

(19/30)一緒にBlazorを学ぼう:画像アップロード

ほとんどのWebサイトでは、画像アップロードも重要な機能です。今日はそれを行ってみましょう。

最終更新 2021/12/21 22:04
StrayaWorker
読了目安 4 分
カテゴリ
Blazor
テーマ
一緒にBlazorを学ぶシリーズ
タグ
.NET C# ASP.NET Core Blazor

ほとんどのWebサイトでは、画像のアップロードも重要な機能です。今回はそれを操作してみましょう。

(注:これは Blazor Server の方法ですが、あまり多くのファイルをアップロードしないほうが良いです。そのため、アップロードを4枚に制限すると警告が表示されます。これらの処理はすべてサーバー上で行われるため負荷が大きくなり、Microsoft も .NET Core Web API の方法を推奨しています。)

まず、コンポーネント FileUpload を作成します。

以下のコードは FileUpload.razor です。Blazor が提供するコンポーネント <InputFile> を使用しています。multiple は複数のファイルをアップロードできることを意味します。

@page "/FileUpload"

<div>
    <div>
        <InputFile OnChange="OnChange" multiple></InputFile>
    </div>
    <div>
        <MyButton value="Submit" class="btn btn-primary" type="submit" @onclick="OnSubmit"></MyButton>
    </div>
</div>
@if (ImageList.Count > 0)
{
    <table>
        <tr>
            @foreach (var img in ImageList)
            {
                <td>
                    <img src="@img" width="150" height="150"/>
                </td>
            }
        </tr>
    </table>
}

以下のコードは FileUpload.razor.cs です。ここでは partial クラスを使用しています。

using BlazorServer.ViewModels;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.JSInterop;
using System.Text.Json;

namespace BlazorServer.Shared;

public partial class FileUpload
{
	private JsInteropClasses? _jsClass;

	// `<InputFile>` のファイル内容を取得
	public IReadOnlyList<IBrowserFile>? ImageFiles;

	public List<string> ImageList = new();
	public string? ImageSrc;
	[Inject] protected IJSRuntime Js { get; set; }

	/// <summary>
	///     ランタイム時の実行環境を判断するために使用
	/// </summary>
	[Inject]
	protected IWebHostEnvironment? Env { get; set; }

	protected override Task OnInitializedAsync()
	{
		_jsClass = new JsInteropClasses(Js);
		return base.OnInitializedAsync();
	}

	public async Task OnChange(InputFileChangeEventArgs e)
	{
		ImageList = new List<string>();
		const string format = "image/jpeg";

		// ファイルを取得
		ImageFiles = e.GetMultipleFiles();
		foreach (var file in ImageFiles)
		{
			// 画像コンテンツを指定の形式と最大サイズに変換
			var imageFile = await file.RequestImageFileAsync(format, 1200, 675);

			// Stream を使用して画像コンテンツを読み込む
			await using var fileStream = imageFile.OpenReadStream();

			// Stream をメモリに読み込む(アップロード確定前はメモリを無駄にしないため推奨しない)
			await using var memoryStream = new MemoryStream();
			await fileStream.CopyToAsync(memoryStream);
			ImageSrc = $"data:{format};base64,{Convert.ToBase64String(memoryStream.ToArray())}";

			// Data URI の形式で画像を表示
			ImageList.Add(ImageSrc);
		}
	}

	public async Task OnSubmit()
	{
		// 確認メッセージを ViewModel に変換
		var sweetConfirm = new SweetConfirmViewModel
		{
			RequestTitle = "画像をアップロードしてもよろしいですか?",
			ResponseTitle = "アップロード成功"
		};
		var jsonString = JsonSerializer.Serialize(sweetConfirm);
		var result = await _jsClass!.Confirm(jsonString);
		if (result && ImageFiles != null && ImageFiles.Any())
		{
			long maxFileSize = 1024 * 1024 * 15;

			// 画像保存先のパスを指定
			var folder = $@"{Env!.WebRootPath}\images";
			foreach (var file in ImageFiles)
			{
				// Stream を使用してファイルを指定パスに保存
				await using var stream = file.OpenReadStream(maxFileSize);

				// フォルダが存在しない場合は作成
				Directory.CreateDirectory(folder);

				var path = $@"{Env.WebRootPath}\images\{file.Name}";

				// ファイルを作成
				var fs = File.Create(path);

				// 画像の Stream をファイルにコピー
				await stream.CopyToAsync(fs);

				// Stream は使い終わったら閉じる
				stream.Close();
				fs.Close();
			}
		}
	}
}

便宜上、NavMenu.razor にこのコンポーネントに遷移するルートを追加します。

<div class="nav-item px-3">
  <NavLink class="nav-link" href="FileUpload" Match="NavLinkMatch.All">
    <span class="bi bi-card-image h4 p-2 mb-0" aria-hidden="true"></span> File
    Upload
  </NavLink>
</div>

新しい ViewModel を作成し、SweetConfirm を汎用的に使用できるようにします。

namespace BlazorServer.ViewModels;

public class SweetConfirmViewModel
{
	public string? ResponseTitle { get; set; }

	public string? ResponseText { get; set; }

	public string? RequestTitle { get; set; }

	public string? RequestText { get; set; }
}

さらに、_Layout.cshtmlSweetConfirm を修正します。

function SweetConfirm(jsonString) {
    // ここで parse して正しく戻り値を渡す必要がある
    var arg = JSON.parse(jsonString);
    return new Promise((resolve) => {
        swal({
            title: arg.RequestTitle,
            text: arg.RequestText,
            icon: "warning",
            buttons: ["キャンセル", "確定"],
            dangerMode: true
        }).then((willDelete) => {
            resolve(willDelete);
            if (willDelete) {
                swal(arg.ResponseTitle, arg.ResponseText, "success");
            }
        });

    });
}

こちらも修正したので、PostBase.razor.csDeletePost も変更します。

protected async Task DeletePost()
{
    // ViewModel に変更
    var sweetConfirm = new SweetConfirmViewModel
    {
        RequestTitle = $"ログ {Post!.Title} を削除してもよろしいですか?",
        RequestText = "この操作は元に戻せません",
        ResponseTitle = "削除成功",
        ResponseText = "ログが削除されました"
    };
    var jsonString = JsonSerializer.Serialize(sweetConfirm);
    var result = await _jsClass.Confirm(jsonString);
    if (result)
    {
        var deleted = await PostRepository!.DeletePost(Post.Id);
        if (deleted.IsSuccess)
            await GetPostId.InvokeAsync(Post!.Id);
        else
            await _jsClass.Alert(deleted.Message!);
    }
}

JsInteropClasses.csConfirm() を JSON 文字列を受け取るように変更します。

public async ValueTask<bool> Confirm(string jsonString)
{
    var confirm = await _js.InvokeAsync<object?>("SweetConfirm", jsonString);
    if (confirm == null)
        return false;
    return bool.TryParse(confirm.ToString(), out var result) && result;
}

画像のアップロードが成功したことが確認できます。

参照:

  1. ASP.NET Core Blazor file uploads
  2. Upload Files Using InputFile Component In Blazor
  3. What scope does a using statement have without curly braces
  4. BrowserFileExtensions.RequestImageFileAsync(IBrowserFile, String, Int32, Int32) メソッド
  5. Day 26:Blazor WebAssembly アップロードファイル

注:本記事のコードは .NET 6 + Visual Studio 2022 でリファクタリングされています。詳細は原文のリンクとリファクタリング後のコードを比較してご確認ください。ご覧いただきありがとうございます。原作者をサポートしてください。

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2021/12/25

(29/30)みんなで学ぶBlazor:Blazor単体テスト

システム開発において最も退屈なプロセスは、おそらくバグ修正です。特に、null オブジェクトにアクセスしようとするエラー(`Object reference not set to an instance of an object.`)は、多くの初心者が最初に直面する問題です。退屈なバグ修正から解放されるために、この記事では「単体テスト」を紹介します。

続きを読む
同じカテゴリ / 同じタグ 2021/12/25

(28/30)みんなで学ぶBlazor:ポリシーベースの認可

以前に「ASP.NET Core Identity」は「Claim」ベースの検証を使用すると述べましたが、実は「ASP.NET Core Identity」には異なる種類の認可方法があります。最も簡単な「ログイン認可」「ロール認可」「Claim認可」ですが、これらはすべて同じ方法で実現されています:原則認可(ポリシーベースの認可)です。

続きを読む