安裝依賴#
在 ?.Domain
專案中執行:
abp add-package Volo.Abp.BlobStoring.FileSystem
命令執行後,會自動:
- 在專案中安裝 NuGet 包
Volo.Abp.BlobStoring.FileSystem
- 在
?DomainModule.cs
中添加以下代碼:
using Volo.Abp.BlobStoring.FileSystem;
[DependsOn(typeof(AbpBlobStoringFileSystemModule))]
public class ?DomainModule : AbpModule { }
設定 Blob 存儲#
在使用 Blob 存儲之前,我們需要創建 Blob Container。
在 ?.Domain\BlobStoring
目錄下創建一個 FileContainer
的類,內容如下:
using Volo.Abp.BlobStoring;
namespace ?.BlobStoring
{
[BlobContainerName("file-container")]
public class FileContainer
{
}
}
在此之後,我們可以在 ?.Web
專案的 ?WebModule.cs
文件中的方法 ConfigureServices
下配置 AbpBlobStoringOptions
:
public override void ConfigureServices(ServiceConfigurationContext context)
{
...
ConfigureBlobStoring();
}
private void ConfigureBlobStoring()
{
Configure<AbpBlobStoringOptions>(options =>
{
options.Containers.Configure<MiaoXinFileContainer>(container =>
{
container.UseFileSystem(fileSystem =>
{
fileSystem.BasePath = AppDomain.CurrentDomain.BaseDirectory;
});
});
});
}
如果在 Debug 模式下運行上傳文件,那麼文件將會被保存到 ?.Web\bin\Debug\net8.0\host\file-container
目錄中
文件路徑計算:https://docs.abp.io/en/abp/latest/Blob-Storing-File-System#file-path-calculation
創建應用層#
在創建 Application Service 之前,我們需要創建一些 Application Service 使用的 DTO。
在專案 ?.Application.Contracts
中創建以下 DTO:
// BlobDto.cs
using System.ComponentModel.DataAnnotations;
namespace ?.Dtos
{
public class BlobDto
{
public byte[] Content { get; set; }
public string Name { get; set; }
}
public class GetBlobRequestDto
{
[Required]
public string Name { get; set; }
}
public class SaveBlobInputDto
{
public byte[] Content { get; set; }
[Required]
public string Name { get; set; }
}
}
繼續在創建 DTO 的專案中創建一個 IFileService.cs
接口:
using ?.Dtos;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace ?
{
public interface IFileService: IApplicationService
{
Task SaveBlobAsync(SaveBlobInputDto input);
Task<BlobDto> GetBlobAsync(GetBlobRequestDto input);
}
}
然後,我們可以創建的 Application Service。
在專案 ?.Application
中創建 FileAppService.cs
:
using ?.BlobStoring;
using ?.Dtos;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.BlobStoring;
namespace ?
{
public class FileService : ApplicationService, IFileService
{
private readonly IBlobContainer<FileContainer> _fileContainer;
public FileService(IBlobContainer<FileContainer> fileContainer)
{
_fileContainer = fileContainer;
}
public async Task<BlobDto> GetBlobAsync(GetBlobRequestDto input)
{
var blob = await _fileContainer.GetAllBytesAsync(input.Name);
return new BlobDto
{
Name = input.Name,
Content = blob
};
}
public async Task SaveBlobAsync(SaveBlobInputDto input)
{
await _fileContainer.SaveAsync(input.Name, input.Content, true);
}
}
}
我們完成了這個專案的應用層。之後,我們將為 API 創建一個 Controller,並用 Razor Page 來實現 UI。
創建 Controller#
在專案 ?.HttpApi
中創建 FileController.cs
:
using ?.Dtos;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Threading.Tasks;
using Volo.Abp.Guids;
namespace ?.Controllers
{
[Route("api/[controller]")]
public class FileController : MiaoXinController
{
private readonly IFileService _fileService;
private readonly IGuidGenerator _guidGenerator;
public FileController(IFileService fileService, IGuidGenerator guidGenerator)
{
_fileService = fileService;
_guidGenerator = guidGenerator;
}
/// <summary>
/// 上傳單個文件
/// </summary>
/// <param name="folder">目錄名稱(可為空)</param>
/// <param name="file">單個文件</param>
/// <returns></returns>
[HttpPost("upload")]
public async Task<IActionResult> UploadAsync(string? folder, IFormFile file)
{
var saveFileName = $"{_guidGenerator.Create()}{Path.GetExtension(file.FileName)}";
var fileBytes = await file.GetAllBytesAsync();
await _fileService.SaveBlobAsync(new SaveBlobInputDto
{
Name = Path.Combine(folder ?? string.Empty, saveFileName),
Content = fileBytes,
});
return Ok(new
{
Name = saveFileName
});
}
/// <summary>
/// 下載單個文件
/// </summary>
/// <param name="fileName">文件名稱</param>
/// <returns></returns>
[HttpGet("download/{fileName}")]
public async Task<IActionResult> DownloadAsync([FromRoute] string fileName)
{
var fileDto = await _fileService.GetBlobAsync(new Dtos.GetBlobRequestDto { Name = fileName });
return File(fileDto.Content, "application/octet-stream", fileDto.Name);
}
}
}
DownloadAsync 用於將文件從伺服器發送到客戶端。
此接口只需要一個 string 參數,然後我們使用該參數來獲取存儲的 Blob。如果 blob 存在,則返回結果 File ,以便可以開始下載過程。
創建用戶界面#
我們將只創建一個頁面來證明下載和上傳操作正常。
在 ?.Web
專案的 Dtos
目錄中創建 UploadFileDto
:
// UploadFileDto.cs
using Microsoft.AspNetCore.Http;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace ?.Web.Dtos
{
public class UploadFileDto
{
/// <summary>
/// 文件名稱
/// </summary>
[Required]
[DisplayName("文件名稱")]
public string Name { get; set; }
/// <summary>
/// 單個文件
/// </summary>
[Required]
[DisplayName("文件")]
public IFormFile File { get; set; }
}
}
然後在 Pages
目錄中創建名稱為 Files
的目錄。再創建一個 Razor page,該頁面命名 Index 。
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
namespace ?.Web.Pages.Files
{
public class Index : ?PageModel
{
[BindProperty]
public UploadFileDto UploadFileDto { get; set; }
private readonly IFileService _fileService;
public bool Uploaded { get; set; } = false;
public Index(IFileService fileService)
{
_fileService = fileService;
}
public void OnGet()
{
}
public async Task<IActionResult> OnPostAsync()
{
using (var memoryStream = new MemoryStream())
{
await UploadFileDto.File.CopyToAsync(memoryStream);
await _fileService.SaveBlobAsync(
new SaveBlobInputDto
{
Name = UploadFileDto.Name,
Content = memoryStream.ToArray()
}
);
}
return Page();
}
}
}
Index.cshtml:
@page
@model ?.Web.Pages.Files.Index
@section scripts{
<abp-script src="/Pages/Files/Index.js" />
}
<abp-card>
<abp-card-header>
<h3>文件上傳和下載</h3>
</abp-card-header>
<abp-card-body>
<abp-row>
<abp-column>
<h3>上傳文件</h3>
<hr />
<form method="post" enctype="multipart/form-data">
<abp-input asp-for="UploadFileDto.Name"></abp-input>
<abp-input asp-for="UploadFileDto.File"></abp-input>
<input type="submit" class="btn btn-info" />
</form>
</abp-column>
<abp-column style="border-left: 1px dotted gray">
<h3>下載文件</h3>
<hr />
<form id="DownloadFile">
<div class="form-group">
<label for="fileName">文件名</label><span> * </span>
<input type="text" id="fileName" name="fileName" class="form-control ">
</div>
<input type="submit" class="btn btn-info"/>
</form>
</abp-column>
</abp-row>
</abp-card-body>
</abp-card>
我們將頁面垂直劃分,左側用於上傳,右側用於下載。我們使用 ABP Tag Helpers 來創建頁面。
在與 Index.cshtml 文件相同的目錄下創建 Index.js 文件。
$(function () {
var DOWNLOAD_ENDPOINT = "/api/download";
var downloadForm = $("form#DownloadFile");
downloadForm.submit(function (event) {
event.preventDefault();
var fileName = $("#fileName").val().trim();
var downloadWindow = window.open(
DOWNLOAD_ENDPOINT + "/" + fileName,
"_blank"
);
downloadWindow.focus();
});
$("#UploadFileDto_File").change(function () {
var fileName = $(this)[0].files[0].name;
$("#UploadFileDto_Name").val(fileName);
});
});
此 jQuery 代碼用於下載。此外,我們還編寫了一個簡單的代碼,以便在用戶選擇文件時自動填充 Filename 輸入。
使用 Ajax 上傳和預覽圖片#
如果表單中某個屬性的值需要上傳文件後才能得到,那麼這個表單就不能用 abp 提供的 <abp-dynamic-form>
tag helper,需要自己手動編寫表單,可以參考 Pages/Customers
目錄中的 CreateModal
或 EditModal
(這個是我自己的頁面,時間關係,暫時不整理出來了)。
添加通用文件上傳代碼:
/**
* 初始化所有上傳文件輸入框的交互,注意: html 一定要按照上面的規則順序編寫
*/
function initialAllUploadFileInputs() {
// 注意這裡回調函數最好是 function,而不是箭頭函數,否則 this 指向會出錯
$('form input[type="file"]').on('change', async function (e) {
var file = e.target.files[0];
if (!file) {
// 取消文件選擇後,也要清空隱藏的 input 控件值、圖片預覽
$(this).next().val('');
$(this).nextAll('img').removeAttr('src');
return;
}
// 上傳文件
const result = await uploadFile(null, e.target.files[0]);
// 注意 input=hidden 控件必須放在當前 input=file 控件之後,否則無法設置表單控件值
$(this).nextAll('input[type=hidden]').val(result.name);
// 圖片預覽
$(this).nextAll('img').attr('src', abp.appPath + 'api/File/download/' + result.name);
});
}
/**
* 調用 HTTP Api 上傳文件
* @param {any} folder 目錄,可為空
* @param {any} file e.target.files[0]
* @returns
*/
async function uploadFile(folder, file) {
const formData = new FormData();
if (folder) {
formData.append('folder', folder);
}
formData.append('file', file);
return await miaoXin.controllers.file.upload({}, {
data: formData,
// 必須 false 才會自動加上正確的 Content-Type
contentType: false,
// 必須 false 才會避開 jQuery 對 FormData 的默認處理
// XMLHttpRequest 會對 FormData 進行正確的處理
processData: false,
});
}
然後在頁面執行:
$(() => {
initialAllUploadFileInputs();
});
調用 initialAllUploadFileInputs
後會為所有的 input=file
控件綁定 change
事件,當 change 後會自動上傳,並設置預覽圖片,但是要求 html 格式必須符合下面的順序規範:
如果是創建表單:
@* 注意,label、input=file、input=hidden、img 標籤必須按照順序出現,因為背後的 JavaScript 代碼依賴於它們的順序 *@
<div class="mb-3">
<label class="form-label" for="CreateCustomerDto_IdImageFrontId_FileInput">個人證件照片正面</label>
<input type="file" class="form-control" id="CreateCustomerDto_IdImageFrontId_FileInput" />
<input type="hidden" name="CreateCustomerDto.IdImageFrontId" />
<img style="width: 100%; max-height: 200px; margin-top: 2px;" />
</div>
如果是更新表單:
@* 注意,label、input=file、input=hidden、img 標籤必須按照順序出現,因為背後的 JavaScript 代碼依賴於它們的順序 *@
<div class="mb-3">
<label class="form-label" for="UpdateCustomerDto_IdImageFrontId_FileInput">個人證件照片正面</label>
<input type="file" class="form-control" id="UpdateCustomerDto_IdImageFrontId_FileInput" disabled="@Model.ReadOnly" />
<input type="hidden" name="UpdateCustomerDto.IdImageFrontId" value="@Model.UpdateCustomerDto.IdImageFrontId" />
<img src="@($"api/File/download/{Model.UpdateCustomerDto.IdImageFrontId}")" style="width: 100%; max-height: 200px; margin-top: 2px;" />
</div>
結果#
完成代碼教程後,運行 ?.Web
專案,並在瀏覽器中打開 /Files
頁面 。可以上傳任何名稱的任何文件,也可以下載這些上傳的文件。
參考#
- https://medium.com/volosoft/file-upload-download-with-blob-storage-system-in-asp-net-core-abp-framework-eeb532a1aa23
- https://www.google.com.hk/search?q=blob+storing+with+abp+layerd+solution&oq=Blob+storing+with+abp+layerd+solution&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIKCAEQABiABBiiBDIKCAIQABiABBiiBDIKCAMQABiABBiiBDIKCAQQABiABBiiBNIBCTEzNDU3ajBqMagCALACAA&sourceid=chrome&ie=UTF-8
- https://docs.abp.io/en/abp/latest/Blob-Storing-File-System
- https://www.feidaoboke.com/post/use-abp-blob-storing-manage-file.html
- https://www.youtube.com/watch?v=zR_RmQ7q3Ek