首頁

目前文章總數:157 篇

  

最後更新:2024年 12月 07日

0076. 實現網站 - 檔案管理系統 - 基於 MinIO (Asp.NET Core Web MVC)

日期:2024年 11月 02日

標籤: MinIO Asp.NET Core Asp.NET Core Web MVC Web WebAPI Html Docker Ubuntu

摘要:C# 學習筆記


範例所需: 1. Visual Studio 2022 Asp.net Core
      2. Ubuntu 主機 - 已架設好 MinIO 伺服器
範例檔案:本篇範例代碼
相關參考:MinIO GitHub 連結
     0075. 架設檔案伺服器 - 高性能的物件存儲系統 - MinIO (開源、高速、免費)
解決問題:實現範例網站與 MinIO 伺服器的互動 (CRUD)、並且範例用教師上傳系統做舉例
基本介紹:本篇分為四大部分。
第一部分:MinIO API 文件用法
第二部分:Web專案架構
第三部分:代碼解析
第四部分:DEMO 範例網站上傳功能






第一部分:MinIO API 文件用法

Step 1:官方網站文件

進入官網後,可以獲得 Asp.net Core 對 MinIO 操作的 API 範例說明,官方文件


Step 2:官方網站文件 - 建議不使用

我們實際將代碼貼上後,可以發現會異常,參數不完整, MinIO 官方網站的文件更新速度較慢的關係。

Step 3:官方 GitHub - 範例代碼1

建議直接從 MinIO 官方 Github Source Code 檢閱 https://github.com/minio



Step 4:官方 GitHub - 範例代碼

到頁面最下方輸入自己的開發語言 (本篇介紹 Asp.net Core)

Step 5:官方 GitHub - 正確使用範例

找到相對應的代碼,複製到專案中,可以發現可以正常使用。
官方 Github 開發人員代碼比釋出的文件更即時



第二部分:Web專案架構

Step 1:範例專案架構

打開範例專案後,架構基本分成以下:

1. MinIO 實現工廠模式, Signleton 提供 Web 與 MinIO Server 的實際互動
2. 假資料庫 為了方便說明,利用 Singleton 保存記憶體的資料,偽裝成教師個人資料的資料庫
3. Service 這邊實現建立帳號、上傳檔案、下載檔案、刪除檔案的方式,並且製造假的資料庫互動
4. Web控制器 提供 Html 畫面取資料、與 API 接口進行 CRUD
5. Html 畫面 教師上傳系統頁面,可以模擬管理者統一管理教師檔案
6. 初始化配置 MinIO Server 連線設定值、依賴注入






第三部分:代碼解析

Step 1:MinIO - 區塊

整個 MinIo 資料夾分成 4 個部分
總共分成以下 4 個區塊:

1. Factory 每個 Web 端操作當與 MinIO 操作時,已建立的 Client 物件會記錄,並且在每次呼叫時重複使用,並且網站結束時安全釋放資源
2. Model 定義與 MinIO Server 連線基本屬性
3. Util 提供取得共用的建立連線字串物件
4. MinIOClientInstance Web 端 Service 實際互動的對象,提供目前教師系統定義的 MinIO 可操作功能



Step 2:MinIO - Factory

工廠方法,提供 Web 端呼叫後,建立此用戶的連線資訊,並且避免同個 Server 下異步操作同時進行鎖物件
在 Application 結束後,會安全釋放資源 Dispose()

public class MinIOClientFactory : IMinIOClientFactory, IDisposable
{       
    private static readonly ConcurrentDictionary<string, IMinioClient> _minIOClientDict = new();
    private static readonly object _lockObject = new();
    private static readonly ConcurrentDictionary<string, object> _lockObjectDict = new();
    private readonly MinIOConnectionModel _ConnectionItem;

    public MinIOClientFactory(IConfiguration configuration)
    {
        _ConnectionItem = MinIOUtil.GetConfigureSetting(configuration);
    }

    public IMinioClient CreateClient(MinIOConnectionModel param)
        {
            var key = $"{param.Host}_{param.Port}_{param.SecretKey}_{param.AccessKey}";
            var minIOClientItem = GetMinIOClient(key);
            return minIOClientItem;

            // 取得 MinIO 客戶端
            IMinioClient GetMinIOClient(string key)
            {
                if (!_minIOClientDict.TryGetValue(key, out var minIOClientItem))
                {
                    var lockObject = GetLockObj(key);
                    lock (lockObject)
                    {
                        if (!_minIOClientDict.TryGetValue(key, out minIOClientItem))
                        {
                            minIOClientItem = new MinioClient()
                                .WithEndpoint(param.Host, param.Port)
                                .WithCredentials(param.AccessKey, param.SecretKey)
                                .Build();
                            _minIOClientDict[key] = minIOClientItem;
                        }
                    }
                }
                return minIOClientItem;
            }
        }

    /// <summary>
    /// 取得鎖物件
    /// </summary>
    private object GetLockObj(string key)
        {
            if (!_lockObjectDict.TryGetValue(key, out var obj) || obj == null)
            {
                lock (_lockObject)
                {
                    if (!_lockObjectDict.TryGetValue(key, out obj) || obj == null)
                    {
                        obj = new object();
                        _lockObjectDict[key] = obj;
                    }
                }
            }
            return obj;
        }
    public void Dispose()
        {
            foreach (var client in _minIOClientDict.Values)
            {
                client.Dispose();
            }
            _minIOClientDict.Clear();
        }
}


Step 3:MinIO - Model

MinIO 連線模型,缺一不可

/// <summary>
/// MinIO 連線模型
/// </summary>
public class MinIOConnectionModel
{
    /// <summary>
    /// 主機位置
    /// </summary>
    public string Host { get; set; } = string.Empty;

    /// <summary>
    /// Port 號
    /// </summary>
    public int Port { get; set; }

    /// <summary>
    /// 存取金鑰
    /// </summary>
    public string AccessKey { get; set; } = string.Empty;

    /// <summary>
    /// 密鑰
    /// </summary>
    public string SecretKey { get; set; } = string.Empty;
}


Step 4:MinIO - Util

靜態共用方法,只要啟動 Asp.net Web 應用後,呼叫此方法可以依照 Appsettings.json 取得 MinIO 配置

public static MinIOConnectionModel GetConfigureSetting(IConfiguration configuration)
{
    var result = new MinIOConnectionModel();
    var minIOParam = configuration.GetSection("MinIO").Get<MinIOConnectionModel>();
    result.Host = minIOParam?.Host ?? string.Empty;
    result.Port = minIOParam?.Port ?? default(int);
    result.AccessKey = minIOParam?.AccessKey ?? string.Empty;
    result.SecretKey = minIOParam?.SecretKey ?? string.Empty;
    return result;
}   


Step 5:MinIO - MinIOClientInstance

Web 與 MinIO 伺服器操作的核心代碼,這邊實現 6 種方法(取檔、上傳、下載、刪除、檢查、取得 Bucket)

public class MinIOClientInstance : MinIOConnectionModel, IDisposable
{
    private readonly IMinIOClientFactory _minioClientFactory;
    private readonly IMinioClient _minIOClientSelf = null;

    public MinIOClientInstance(MinIOConnectionModel param,
        IConfiguration configuration,
        IMinIOClientFactory minioClientFactory)
    {
        _minioClientFactory = minioClientFactory;
        _minIOClientSelf = _minioClientFactory.CreateClient(MinIOUtil.GetConfigureSetting(configuration));
    }

    /// <summary>
    /// 1. 建立 Bucket
    /// </summary>                
    public async Task CreateBucket(string bucketName)
        {
            try
            {
                // 取得是否存在               
                var isExist = await IsExistBucket(bucketName);

                // 不存在 - 才創建
                if (!isExist)
                {
                    await _minIOClientSelf.MakeBucketAsync(
                        new MakeBucketArgs()
                            .WithBucket(bucketName)
                    ).ConfigureAwait(false);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine($"[CreateBucket]  Exception: {e}");
            }
        }

    /// <summary>
    /// 2. 取得 Bucket 內的資料
    /// </summary>        
    public async Task<FileModel> GetBucketList(string bucketName)
        {
            var result = new FileModel();
            try
            {
                // 取得是否存在               
                var isExist = await IsExistBucket(bucketName);
                // 不存在捨棄
                if (!isExist)
                    return result;

                // 使用 ListObjectsAsync 方法來列出所有的物件
                var listArgs = new ListObjectsArgs()
                                   .WithBucket(bucketName)
                                   .WithRecursive(true); // 設為 true 可以列出所有物件,包括子目錄

                var objects = _minIOClientSelf.ListObjectsEnumAsync(listArgs);

                // 遍歷 bucket 中的所有物件
                result.BucketName = bucketName;
                await foreach (Minio.DataModel.Item obj in objects)
                {
                    result.Files.Add(new FileItem()
                    {
                        FileName = obj.Key,
                        FileExtension = Path.GetExtension(obj.Key),
                        FileSize = obj.Size,
                        LastUpdateTime = obj.LastModifiedDateTime
                    });
                }
            }
            catch (MinioException e)
            {
                Console.WriteLine($"[GetBucketList]  Exception: {e}");
            }

            return result;
        }

    /// <summary>
    /// 3. 下載檔案 - 記憶體資料
    /// </summary>        
    public async Task<MemoryStream> GetObjectAsync(string fileName, string bucketName)
        {
            var memoryStream = new MemoryStream();
            try
            {                
                var getObjectArgs = new GetObjectArgs()
                    .WithBucket(bucketName)
                    .WithObject(fileName)
                    .WithCallbackStream((stream) =>
                    {                        
                        stream.CopyTo(memoryStream);
                        memoryStream.Position = 0;
                    });
                
                await _minIOClientSelf.GetObjectAsync(getObjectArgs).ConfigureAwait(false);

                return memoryStream; // 成功返回文件流
            }
            catch (Exception e)
            {
                Console.WriteLine($"[GetObjectAsync]  Exception: {e}");
            }
            return memoryStream;
        }

    /// <summary>
    /// 4. 上傳檔案
    /// </summary>                
    public async Task UploadFile(IFormFile file, string bucketName) 
        {
            try
            {
                // 取得是否存在               
                var isExist = await IsExistBucket(bucketName);
                // 不存在捨棄
                if (!isExist)
                    return;

                // 取得上傳檔案的流
                using (var fileStream = file.OpenReadStream())
                {
                    var objectName = file.FileName; // 使用檔案名稱作為物件名稱

                    // 設置 PutObjectArgs
                    var putObjectArgs = new PutObjectArgs()
                        .WithBucket(bucketName)
                        .WithObject(objectName)
                        .WithStreamData(fileStream)
                        .WithObjectSize(fileStream.Length)
                        .WithContentType(file.ContentType); // 使用上傳檔案的 Content-Type
                    await _minIOClientSelf.PutObjectAsync(putObjectArgs);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine($"[UploadFile]  Exception: {e}");
            }
        }

    /// <summary>
    /// 5. 完整刪除整個 Bucket 
    /// </summary>                
    public async Task DeleteBucket(string bucketName)
        {
            try
            {
                // 取得是否存在               
                var isExist = await IsExistBucket(bucketName);

                // 不存在 - 退出
                if (!isExist)
                    return;

                // 列出並刪除所有物件                                
                var listArgs = new ListObjectsArgs()
                                   .WithBucket(bucketName)
                                   .WithRecursive(true);

                var objects = _minIOClientSelf.ListObjectsEnumAsync(listArgs);
                await foreach (Minio.DataModel.Item obj in objects)
                {
                    await _minIOClientSelf.RemoveObjectAsync(new RemoveObjectArgs()
                        .WithBucket(bucketName)
                        .WithObject(obj.Key));
                }

                // 物件內的資料都清除後才能刪除 Bucket
                await _minIOClientSelf.RemoveBucketAsync(
                      new RemoveBucketArgs().WithBucket(bucketName)
                      ).ConfigureAwait(false);

            }
            catch (Exception e)
            {
                Console.WriteLine($"[DeleteBucket]  Exception: {e}");
            }
        }

    /// <summary>
    /// 6. 刪除單一檔案
    /// </summary>                
    public async Task DeleteFile(string fileName, string bucketName)
        {
            try
            {
                // 取得是否存在               
                var isExist = await IsExistBucket(bucketName);

                // 不存在 - 退出
                if (!isExist)
                    return;

                // 刪除指定物件                                
                await _minIOClientSelf.RemoveObjectAsync(new RemoveObjectArgs()
                        .WithBucket(bucketName)
                        .WithObject(fileName));
            }
            catch (Exception e)
            {
                Console.WriteLine($"[DeleteFile]  Exception: {e}");
            }

        }

    /// <summary>
    /// 7.是否存在指定 Bucket
    /// </summary>
    public async Task<bool> IsExistBucket(string bucketName)
        {
            try
            {
                // 取得是否存在
                var getArgs = new BucketExistsArgs().WithBucket(bucketName);
                var isExist = await _minIOClientSelf.BucketExistsAsync(getArgs).ConfigureAwait(false);

                return isExist;
            }
            catch (Exception e)
            {
                Console.WriteLine($"[IsExistBucket]  Exception: {e}");
                return false;
            }
        }

    #region 解構式 - 釋放資源
        ~MinIOClientInstance()
        {
            Dispose();
        }

        public void Dispose()
        {
            Dispose();
            GC.SuppressFinalize(this);
        }

        #endregion
}

Step 6:假資料庫

假資料,預設系統有 3 個教師,每個 Bucket 都是以教師 Id 自動建立。
新增帳號時會在本次記憶體操作中,增加一筆假資料(模擬資料庫操作)

public class FakeDataBase
{
    private List<TeacherModel> _teachers = new List<TeacherModel>();

    /// <summary>
    /// 預設假資料
    /// </summary>
    public FakeDataBase()
    {
        _teachers.Add(new TeacherModel() { Id = 20240928001, Name = "張明宇" });
        _teachers.Add(new TeacherModel() { Id = 20240928002, Name = "李曉峰" });
        _teachers.Add(new TeacherModel() { Id = 20240928003, Name = "陳佳玲" });
    }

    /// <summary>
    /// 取得教師資料
    /// </summary>
    public List<TeacherModel> GetTeachers()
    {
        return _teachers;
    }

    /// <summary>
    /// 新增假資料庫的帳號 (名字隨機)
    /// </summary>
    public long CreateTeacher()
    {
        var fakeAutoIncretmentId = this._teachers.Max(item => item.Id) + 1;
        var faker = new Faker("zh_CN");
        var now = DateTime.Now;
        _teachers.Add(new TeacherModel()
        {
            Id = fakeAutoIncretmentId,
            Name = faker.Name.FullName()
        });
        return fakeAutoIncretmentId;
    }

    /// <summary>
    /// 移除假資料庫的帳號
    /// </summary>        
    public void DeleteTeacher(long id)
    {
        var getItem = _teachers.Where(item => item.Id == id).FirstOrDefault();
        if (getItem != null)
        {
            _teachers.Remove(getItem);
        }
    }
}
 


Step 7:Service

教師上傳系統的實際業務邏輯,目前有以下 6 種,實現完整的 CRUD

1. 取得所有教師資料
2. 下載檔案
3. 上傳檔案
4. 刪除檔案
5. 建立帳號
6. 刪除帳號
public class TeacherManageService : ITeacherManageService
{
    private readonly FakeDataBase _dataBase;
    private readonly MinIOClientInstance _minIOClientInstance;

    public TeacherManageService(FakeDataBase dataBase,
        MinIOClientInstance minIOClientInstance)
    {
        _dataBase = dataBase;
        _minIOClientInstance = minIOClientInstance;
    }

    /// <summary>
    /// 取得所有教師資料
    /// </summary>       
    public async Task<List<TeacherModel>> GetTeachers()
        {
            var result = new List<TeacherModel>();
            result = await Task.Run(() =>
            {
                return _dataBase.GetTeachers();
            });
            foreach (var item in result)
            {
                var bucketName = $@"{item.Id}";
                // 不存在就建立 Bucket
                if (!await _minIOClientInstance.IsExistBucket(bucketName))
                {
                    await _minIOClientInstance.CreateBucket(bucketName);
                }
                else// 存在就將檔案取回
                {
                    var getFiles = await _minIOClientInstance.GetBucketList(bucketName);
                    item.MySelfFiles = getFiles;
                }
            }
            return result;
        }

    /// <summary>
    /// 下載檔案
    /// </summary>        
    public async Task<MemoryStream> DownloadFile(string fileName, string bucketName)
        {
            return await _minIOClientInstance.GetObjectAsync(fileName, bucketName);
        }

    /// <summary>
    /// 上傳檔案
    /// </summary>        
    public async Task UploadFile(IFormFile file, string bucketName)
        {
            if (file == null ||
                file.Length == 0 ||
                string.IsNullOrEmpty(bucketName))
            {
                throw new Exception("參數異常");
            }
            await _minIOClientInstance.UploadFile(file, bucketName);
        }

    /// <summary>
    /// 刪除檔案
    /// </summary>        
    public async Task DeleteFile(string fileName, string bucketName)
        {
            if (string.IsNullOrEmpty(fileName) ||
                string.IsNullOrEmpty(bucketName))
            {
                throw new Exception("參數異常");
            }
            await _minIOClientInstance.DeleteFile(fileName, bucketName);
        }

    /// <summary>
    /// 建立帳號
    /// </summary>
    public async Task<long> CreateAccount()
        {
            var createdId = _dataBase.CreateTeacher();
            var bucketName = $@"{createdId}";
            // 不存在就建立 Bucket
            if (!await _minIOClientInstance.IsExistBucket(bucketName))
            {
                await _minIOClientInstance.CreateBucket(bucketName);
            }
            return createdId;
        }

    /// <summary>
    /// 刪除帳號
    /// </summary>
    public async Task DeleteAccount(long id)
        {
            _dataBase.DeleteTeacher(id);

            var bucketName = $@"{id}";
            await _minIOClientInstance.DeleteBucket(bucketName);
        }

}
 


Step 8:Web控制器

API 與 Service 對應功能,並且檢視控制器提供 Html 畫面

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly ITeacherManageService _teacherManageService;
    public HomeController(ILogger<HomeController> logger,
        ITeacherManageService teacherManageService)
    {
        _logger = logger;
        _teacherManageService = teacherManageService;
    }

    /// <summary>
    /// 頁面資料
    /// </summary>
    public IActionResult Index()
        {
            var getResult = (_teacherManageService.GetTeachers()).Result;
            return View(getResult);
        }

    /// <summary>
    /// 上傳單一檔案
    /// </summary>        
    [HttpPost]
    public async Task<IActionResult> UploadFile(IFormFile file, string bucketName)
    {
        await _teacherManageService.UploadFile(file, bucketName);
        return Ok("上傳成功");
    }

    /// <summary>
    /// 下載單一檔案
    /// </summary>        
    [HttpGet]
    public async Task<IActionResult> DownloadFile(string fileName, string bucketName)
    {
        var fileStream = await _teacherManageService.DownloadFile(fileName, bucketName);
        if (fileStream != null)
        {
            return new FileStreamResult(fileStream, "application/octet-stream")
            {
                FileDownloadName = fileName
            };
        }

        return BadRequest(new { message = $@"Cannot download file {fileName}." });
    }

    /// <summary>
    /// 刪除單一檔案
    /// </summary>        
    [HttpGet]
    public async Task<IActionResult> DeleteFile(string fileName, string bucketName)
    {
        await _teacherManageService.DeleteFile(fileName, bucketName);
        return Ok($@"{fileName} 已刪除");
    }

    /// <summary>
    /// 刪除帳號
    /// </summary>        
    [HttpGet]
    public async Task<IActionResult> DeleteAccount(long Id)
    {
        await _teacherManageService.DeleteAccount(Id);
        return Ok(new { message = $@"教師已刪除 ID:{Id}" });
    }

    /// <summary>
    /// 新建帳號
    /// </summary>        
    [HttpGet]
    public async Task<IActionResult> CreateAccount()
    {
        var createdId = await _teacherManageService.CreateAccount();
        return Ok(new { message = $@"教師帳號已建立 ID:{createdId}" });
    }
}


Step 9:Html 畫面 - 首頁

與 Home/Index 檢視器互動,此頁面實現了所有前端 Javascript 呼叫 API 的工作。

@model IEnumerable<Example.Common.FakeDataBase.Model.TeacherModel>
@{
    ViewData["Title"] = "MinIO CRUD Example Page";
}

<div id="teachersContainer">
    @await Html.PartialAsync("_TeachersPartial", Model)
</div>

@section Scripts {
    <script>
        // 1. Upload File
        document.querySelectorAll('.uploadForm').forEach(function (form) 
        {
            form.addEventListener('submit', async function (event) {
                event.preventDefault();
                debugger;
                const formData = new FormData(form);
                const bucketName = form.getAttribute('data-bucket');
                formData.append('bucketName', bucketName);

                try {
                    const response = await fetch('/Home/UploadFile', {
                        method: 'POST',
                        body: formData
                    });

                    if (!response.ok) {
                        throw new Error('上傳失敗');
                    }
                    alert("上傳成功");
                    location.reload();
                } catch (error) {
                    const uploadResultDiv = form.nextElementSibling;
                    uploadResultDiv.innerText = `上傳時發生錯誤: ${error.message}`;
                }
            });
        });

        // 2. Download File
        function downloadFile(bucketName, fileName) {            
            // 呼叫 API 下載文件
            fetch(`/Home/downloadfile?fileName=${encodeURIComponent(fileName)}&bucketName=${encodeURIComponent(bucketName)}`)
                .then(response => {
                    if (!response.ok) {
                        throw new Error('File not found or server error.');
                    }
                    return response.blob();// 轉換為 Blob 形式
                })
                .then(blob => {
                    // 創建一個隱藏的 <a> 元素來下載文件
                    const url = window.URL.createObjectURL(blob);
                    const aDom = document.createElement('a');
                    aDom.href = url;
                    aDom.download = fileName;// 設置下載的文件名
                    document.body.appendChild(aDom);
                    aDom.click();//  模擬點擊下載
                    aDom.remove();// 下載完成後移除 <a> 元素
                })
                .catch(error => {
                    console.error('Download error:', error);
                    alert('Error downloading file: ' + error.message);
                });
        }

        // 3. Delete File
        function deleteFile(bucketName, fileName) {
            fetch(`/Home/DeleteFile?fileName=${encodeURIComponent(fileName)}&bucketName=${encodeURIComponent(bucketName)}`)
                .then(response => {
                    if (!response.ok) {
                        throw new Error('File not found or server error.');
                    }
                    // 刷新頁面
                    alert(fileName + ' 刪除成功!');
                    location.reload();
                })
                .catch(error => {
                    console.error('Delete error:', error);
                    alert('Delete file Error: ' + error.message);
                });
        }

        // 4. Delete Account
        function deleteAccount(Id) {            
            fetch(`/Home/DeleteAccount?Id=${encodeURIComponent(Id)}`)
                .then(response => {                                        
                    return response.json();
                })
                .then(data => {                    
                    alert(data.message);
                    location.reload();
                })
                .catch(error => {
                    console.error('Delete Account error:', error);
                    alert('Delete Account Error: ' + error.message);
                });
        }

        // 5. Create Account
        function createAccount() {            
            fetch(`/Home/CreateAccount`)
                .then(response => {
                    return response.json();
                })
                .then(data => {                    
                    alert(data.message);
                    location.reload();
                })
                .catch(error => {
                    console.error('Create Account error:', error);
                    alert('Create Account Error: ' + error.message);
                });
        }
    </script>
}


Step 10:Html 畫面 - 部分檢視

與 Home/Index 檢視器互動,此頁面為首頁的一部分,會將取得的資料渲染

@model IEnumerable<Example.Common.FakeDataBase.Model.TeacherModel>

<div>
    <div>
        <button id="createAccountButton" onclick="createAccount()">新增帳號</button>
    </div>
    @foreach (var item in Model)
    {
        <table class="table">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>姓名</th>
                    <th>上傳操作</th>
                    <th>刪除帳號</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>@item.Id</td>
                    <td>@item.Name</td>
                    <td>
                        <!-- 上傳表單 -->
                        <form class="uploadForm" enctype="multipart/form-data" data-bucket="@item.Id">
                            <input type="file" name="file" required />
                            <button type="submit">執行上傳</button>
                        </form>
                        <div class="uploadResult"></div>
                    </td>
                    <td>
                        <button id="deleteAccountButton_@item.Id" onclick="deleteAccount('@item.Id')">刪除帳號</button>
                    </td>
                </tr>
                <tr>
                    <td colspan="4">
                        <table class="table">
                            <thead>
                                <tr>
                                    <th>檔案名稱</th>
                                    <th>檔案大小</th>
                                    <th>副檔名</th>
                                    <th>最後更新時間</th>
                                    <th>操作</th>
                                </tr>
                            </thead>
                            <tbody>
                                @foreach (var file in item.MySelfFiles.Files)
                                {
                                    <tr>
                                        <td>@file.FileName</td>
                                        <td>@file.ShowSize</td>
                                        <td>@file.FileExtension</td>
                                        <td>@file.LastUpdateTime</td>
                                        <td>
                                            <!-- 下載按鈕 -->
                                            <button id="downloadButton_@file.FileName" onclick="downloadFile('@item.Id', '@file.FileName')">下載</button>
                                            <!-- 刪除按鈕 -->
                                            <button id="deleteButton_@file.FileName" onclick="deleteFile('@item.Id', '@file.FileName')">刪除</button>
                                        </td>
                                    </tr>
                                }
                            </tbody>
                        </table>
                    </td>
                </tr>
            </tbody>
        </table>

        <br/>
        <br/>
        <br/>
        <br/>
    }
</div>


Step 11:初始化配置

初始化配置, MinIO 設定、依賴注入

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "MinIO": {
    "Host": "192.168.51.188",
    "Port": 9011,    
    "AccessKey": "uaub5kcqjFJovEpKZWr1",
    "SecretKey": "fIsxDIHae8Zx8Sa7wJt6KgN1hCXE490cLX1YOPRL"
  }
}


// 1. 注入相依 - MinIO 皆為單例
builder.Services.AddSingleton<IMinIOClientFactory, MinIOClientFactory>();
builder.Services.AddSingleton<FakeDataBase>();
builder.Services.AddSingleton<MinIOClientInstance>();
builder.Services.AddSingleton<MinIOConnectionModel>();

// 2. 注入相依 - Service 為 Scoped
builder.Services.AddScoped<ITeacherManageService, TeacherManageService>(); 





第四部分:DEMO 範例網站上傳功能

Step 1:啟動範例程式

啟動範例專案後,預設可以看到如下畫面


這時檢閱 MinIO 伺服器可以看到會依照教師 ID 自動建立 Bucket,以便於保存教師的檔案

Step 2:新建帳號 - 點擊按鈕

執行新增帳號按鈕

Step 3:新建帳號 - 刷新頁面

會自動刷新頁面,最下方會出現新建的教師資料,模擬新建的行為

Step 4:新建帳號 - 完成

模擬出新增帳號後,MinIO Server 會建立 Bucket ,來保存這個教師的檔案資料

Step 5:上傳檔案 - 選擇檔案

執行選擇檔案按鈕,並且選擇一個測試檔案

Step 6:上傳檔案 - 執行上傳

執行執行上傳按鈕,並得到響應

Step 7:上傳檔案 - 完成

頁面自動刷新後,出現檔案資訊
MinIo Server 檢查確實上傳成功

Step 8:下載檔案 - 下載 & 完成

執行下載按鈕,也可以成功從 MinIO Server 取得此檔案

Step 9:刪除檔案 - 刪除 & 完成

執行刪除按鈕,實際刪除此檔案

Step 10:刪除帳號 - 執行刪除帳號

執行刪除帳號按鈕,將此教師移除

Step 11:刪除帳號 - 完成

MinIo Server 此 Bucket 也會完整移除。