(19/30)Learning Blazor Together: Image Upload

(19/30)Learning Blazor Together: Image Upload

In most websites, uploading images is also a very important feature. Today we will practice it.

Last updated 12/21/2021 10:04 PM
StrayaWorker
6 min read
Category
Blazor
Topic
Learning Blazor Together Series
Tags
.NET C# ASP.NET Core Blazor

In most websites, image uploading is also a very important feature. Today we'll walk through it.

(Note: This is done using Blazor Server, but it's best not to upload too many files. So if you try to upload more than 4 photos, there will be a prompt. After all, these operations are performed on the server, which can be a heavy burden. Microsoft also recommends using .NET Core Web API for this.)

First, we create a Component FileUpload.

The code below is for FileUpload.razor, which uses Blazor's built-in component <InputFile>. The multiple attribute allows uploading multiple files.

@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>
}

The code below is for FileUpload.razor.cs, using a partial class.

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;

	// Get the file content from `<InputFile>`
	public IReadOnlyList<IBrowserFile>? ImageFiles;

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

	/// <summary>
	///      Used to determine the execution environment at runtime
	/// </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";

		// Get the files
		ImageFiles = e.GetMultipleFiles();
		foreach (var file in ImageFiles)
		{
			// Convert the image content to the specified format and maximum size
			var imageFile = await file.RequestImageFileAsync(format, 1200, 675);

			// Read the image content using a Stream
			await using var fileStream = imageFile.OpenReadStream();

			// Read the Stream into memory; do not do this if you are not sure about uploading, to avoid wasting memory
			await using var memoryStream = new MemoryStream();
			await fileStream.CopyToAsync(memoryStream);
			ImageSrc = $"data:{format};base64,{Convert.ToBase64String(memoryStream.ToArray())}";

			// Display the image using a Data URI
			ImageList.Add(ImageSrc);
		}
	}

	public async Task OnSubmit()
	{
		// Convert the prompt message into a ViewModel
		var sweetConfirm = new SweetConfirmViewModel
		{
			RequestTitle = "Are you sure you want to upload the image?",
			ResponseTitle = "Upload successful"
		};
		var jsonString = JsonSerializer.Serialize(sweetConfirm);
		var result = await _jsClass!.Confirm(jsonString);
		if (result && ImageFiles != null && ImageFiles.Any())
		{
			long maxFileSize = 1024 * 1024 * 15;

			// Specify the image storage path
			var folder = $@"{Env!.WebRootPath}\images";
			foreach (var file in ImageFiles)
			{
				// Use a Stream to store the file to the specified path
				await using var stream = file.OpenReadStream(maxFileSize);

				// Create the folder if it doesn't exist
				Directory.CreateDirectory(folder);

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

				// Create the file
				var fs = File.Create(path);

				// Copy the image Stream to the file
				await stream.CopyToAsync(fs);

				// Remember to close the Stream after use
				stream.Close();
				fs.Close();
			}
		}
	}
}

For convenience, add a route to navigate to this Component in 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>

Create a new ViewModel so that SweetConfirm can be reused.

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; }
}

Then modify the SweetConfirm in _Layout.cshtml.

function SweetConfirm(jsonString) {
    // Need to parse here so it can be properly passed back
    var arg = JSON.parse(jsonString);
    return new Promise((resolve) => {
        swal({
            title: arg.RequestTitle,
            text: arg.RequestText,
            icon: "warning",
            buttons: ["Cancel", "OK"],
            dangerMode: true
        }).then((willDelete) => {
            resolve(willDelete);
            if (willDelete) {
                swal(arg.ResponseTitle, arg.ResponseText, "success");
            }
        });

    });
}

Since this has been changed, DeletePost in PostBase.razor.cs also needs to be modified.

protected async Task DeletePost()
{
    // Changed to ViewModel
    var sweetConfirm = new SweetConfirmViewModel
    {
        RequestTitle = $"Are you sure you want to delete the post {Post!.Title}?",
        RequestText = "This operation cannot be undone",
        ResponseTitle = "Delete successful",
        ResponseText = "The post has been deleted"
    };
    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!);
    }
}

Change Confirm() in JsInteropClasses.cs to accept a JSON string.

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;
}

You can see the image upload was successful.

References:

  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) Method
  5. Day 26:Blazor WebAssembly Upload Files

Note: The code in this article has been refactored using .NET 6 + Visual Studio 2022. You can click the original article link to compare with the refactored code. Thank you for reading and supporting the original author.

Keep Exploring

Related Reading

More Articles
Same category / Same tag 12/25/2021

(29/30)Learn Blazor Together: Blazor Unit Testing

The most boring part of developing a system is probably fixing bugs, especially errors like trying to access a null object (`Object reference not set to an instance of an object.`), which is the most common problem most people encounter when they first step into programming. To break free from the tedious bug-fixing process, this article introduces `unit testing`.

Continue Reading
Same category / Same tag 12/25/2021

(28/30) Learning Blazor Together: Policy-based Authorization

It was mentioned earlier that `ASP.NET Core Identity` uses `Claim`-based authentication. In fact, `ASP.NET Core Identity` has different types of authorization methods, the simplest being `Login Authorization`, `Role Authorization`, and `Claim Authorization`. However, all of the above are implemented in one way: Policy-based Authorization.

Continue Reading