Day 19 上传图片

在大部分的网站中,上传图片也是很重要的功能,今天我们就来实作。
(注:这是用 Blazor Server 的方式,但最好不要上传太多档案,所以限定上传4张照片的话就会提示,毕竟这些事都是在服务器上做,负担太大,微软也建议用 .NET Core API 的方式实作)

我们先建立一个 Component FileUpload

下面程序码为FileUpload.razor,使用 Blazor 提供的 Component <InputFile>,multiple代表可以传送多个档案

@page "/FileUpload"

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

下面程序码为FileUpload.razor.cs,这里用partial class

using BlazorServer.ViewModels;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Hosting;
using Microsoft.JSInterop;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;

namespace BlazorServer.Shared
{
    public partial class FileUpload
    {
        [Inject] protected IJSRuntime js { get; set; }
        //用以判断runtime期间在什麽环境执行
        [Inject] protected IWebHostEnvironment env { get; set; }
        private JsInteropClasses jsClass;

        public List<string> ImageList = new List<string>();
		//取得`<InputFile>`的档案内容
        public IReadOnlyList<IBrowserFile> ImgFiles;
        public string ImgSrc;

        protected override Task OnInitializedAsync()
        {
            jsClass = new(js);
            return base.OnInitializedAsync();
        }

        public async Task OnChange(InputFileChangeEventArgs e)
        {
            ImageList = new List<string>();
            string format = "image/jpeg";
			//取得档案
            ImgFiles = e.GetMultipleFiles();
            foreach (var file in ImgFiles)
            {
				//将图片内容转换成指定类型及最大尺寸
                var imageFile = await file.RequestImageFileAsync(format, 1280, 960);
				//利用 Stream 读取图片内容
                using var fileStream = imageFile.OpenReadStream();
				//将 Stream 读到记忆体中,如果没有要上传前预览就不要这麽做,以免耗费记忆体
                using var memoryStream = new MemoryStream();
                await fileStream.CopyToAsync(memoryStream);
                ImgSrc = $"data:{format};base64,{Convert.ToBase64String(memoryStream.ToArray())}";
				//以 Data URI 的方式将图片呈现
                ImageList.Add(ImgSrc);
            }

        }
        public async Task OnSubmit()
        {
			//将提示讯息变成 ViewModel
            SweetConfirmViewModel sweetConfirm = new SweetConfirmViewModel()
            {
                RequestTitle = "是否确定上传图片?",
                ResponseTitle = "上传成功",
            };
            string jsonString = JsonSerializer.Serialize(sweetConfirm);
            bool result = await jsClass.Confirm(jsonString);
            if (result && ImgFiles.Any())
            {
                long maxFileSize = 1024 * 1024 * 15;
				//指定图片要存到哪个路径
                string folder = $@"{env.WebRootPath}\images";
                foreach (var file in ImgFiles)
                {
					//使用 Stream 将档案存到指定路径
                    using (var stream = file.OpenReadStream(maxFileSize))
                    {
						//如果资料夹不在会先建立
                        Directory.CreateDirectory(folder);
                        var path = $@"{env.WebRootPath}\images\{file.Name}";
						//建立档案
                        FileStream fs = File.Create(path);
						//将图片 Stream 复制到档案中
                        await stream.CopyToAsync(fs);
						//Stream 用完一定要关闭
                        stream.Close();
                        fs.Close();
                    }
                }
            }
        }
    }
}

为了方便,NavMenu.razor.cs加上路由通往这个 Component

        <li 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>
        </li>

建立新的 ViewModel 让SweetConfirm可以通用

namespace BlazorServer.ViewModels
{
    public class SweetConfirmViewModel
    {
        public string RequestTitle { get; set; }
        public string RequestText { get; set; }
        public string ResponseTitle { get; set; }
        public string ResponseText { get; set; }
    }
}

再把_Host.cshtmlSweetConfirm修改一下

        function SweetConfirm(jsonString) {
			// 这边要 parse 才能正常转回来
            var arg = JSON.parse(jsonString);
            return new Promise((resolve) => {
                Swal.fire({
                    title: arg.RequestTitle,
                    text: arg.RequestText,
                    icon: "warning",
                    showCancelButton: true,
                    cancelButtonText: "取消",
                    confirmButtonColor: "#3085d6",
                    cancelButtonColor: "#d33",
                    confirmButtonText: "确定"
                }).then((result) => {
                    resolve(result.isConfirmed);
                    if (result.isConfirmed) {
                        Swal.fire(
                            arg.ResponseTitle,
                            arg.ResponseText,
                            "success"
                        );
                    }
                })
            });
        }

既然这边改了,PostBase.razor.csdeletePost也要修改

        protected async Task deletePost()
        {
            // 改成 ViewModel
            SweetConfirmViewModel sweetConfirm = new SweetConfirmViewModel()
            {
                RequestTitle = $"是否确定删除日志{Post.Title}?",
                RequestText = "这个动作不可复原",
                ResponseTitle = "删除成功",
                ResponseText = "日志被删除了",
            };
            string jsonString = JsonSerializer.Serialize(sweetConfirm);
            bool result = await jsClass.Confirm(jsonString);

            if (result)
            {
                var deleted = await PostRepository.DeletePost(Post.PostId);
                if (deleted.IsSuccess)
                {
                    await getPostId.InvokeAsync(Post.PostId);
                }
                else
                {
                    await jsClass.Alert(deleted.Message);
                }
            }
        }

JsInteropClasses.csConfirm()改成 JSON 字串

        public async ValueTask<bool> Confirm(string jsonString)
        {
            bool confirm = await js.InvokeAsync<bool>("SweetConfirm", jsonString);
            return confirm;
        }

可以看到图片上传成功了
https://ithelp.ithome.com.tw/upload/images/20210920/20140893JMsNUcifPz.png
https://ithelp.ithome.com.tw/upload/images/20210920/201408935KrAk8WvL0.png
https://ithelp.ithome.com.tw/upload/images/20210920/201408930EoWPFlQ7d.png

Ref: ASP.NET Core Blazor file uploads

Ref: Upload Files Using InputFile Component In Blazor

Ref:What scope does a using statement have without curly braces

Ref:BrowserFileExtensions.RequestImageFileAsync(IBrowserFile, String, Int32, Int32) 方法

Ref:Day 26:Blazor WebAssembly 上传档案


<<:  创建App功能-登入与副界面

>>:  基本元件

Day28 CocoaPods

CocoaPods CocoaPods 是一款第三方套件的相依管理器,我们可以透过它来安装许多第三方...

[第一天]从0开始的UnityAR手机游戏开发-前言

AR介绍 AR为Augmented Reality(扩增实境)的简称,透过照相镜头和APP将真实世界...

[第六天]从0开始的UnityAR手机游戏开发-如何汇入Unity Package到Unity和Unity使用Vuforia插件的基本设置

Unity Package汇入方式 Unity Package汇入Unity有以下3种方式 双击两...

【Day 18】 实作 - 透过 AWS 服务 Glue Crawler 自动建立 VPC Log 资料表

大家午安 ~ 昨天我们已经启用 VPC Flow Log 并且存放到 S3,今天我们会设定 AWS ...

Day16 X Polyfill-less Bundling Script & File Compression

今天是 Build Optimizations 主题的最後一篇了,到目前为止我们已经认识了 Cod...