myesn

myEsn2E9

hi
github

ABP: BLOBストレージ(ファイルシステムプロバイダー)を使用してファイルのアップロードとダウンロードを完了する

依存関係のインストール#

?.Domain プロジェクトで実行:

abp add-package Volo.Abp.BlobStoring.FileSystem

コマンドを実行すると、自動的に:

  1. プロジェクトに NuGet パッケージ Volo.Abp.BlobStoring.FileSystem がインストールされます。
  2. ?DomainModule.cs に以下のコードが追加されます:
using Volo.Abp.BlobStoring.FileSystem;

[DependsOn(typeof(AbpBlobStoringFileSystemModule))]
public class ?DomainModule : AbpModule { }

Blob ストレージの設定#

Blob ストレージを使用する前に、Blob コンテナを作成する必要があります。

?.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;
            });
        });
    });
}    

デバッグモードでファイルをアップロードすると、ファイルは ?.Web\bin\Debug\net8.0\host\file-container ディレクトリに保存されます。
ファイルパス計算:https://docs.abp.io/en/abp/latest/Blob-Storing-File-System#file-path-calculation

アプリケーション層の作成#

アプリケーションサービスを作成する前に、いくつかのアプリケーションサービスで使用する 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);
    }
}

次に、アプリケーションサービスを作成できます。

プロジェクト ?.ApplicationFileAppService.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 用のコントローラーを作成し、Razor ページで UI を実装します。

コントローラーの作成#

プロジェクト ?.HttpApiFileController.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 を返し、ダウンロードプロセスを開始できます。

ユーザーインターフェースの作成#

アップロードとダウンロード操作が正常に行われることを証明するために、1 ページのみを作成します。

?.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 ページを作成し、そのページを 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 タグヘルパーを使用してページを作成します。

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 コードはダウンロード用です。また、ユーザーがファイルを選択すると自動的にファイル名入力を埋めるための簡単なコードも記述しました。

Ajax を使用した画像のアップロードとプレビュー#

フォーム内の特定の属性の値がファイルをアップロードした後に取得される必要がある場合、このフォームは abp が提供する <abp-dynamic-form> タグヘルパーを使用できず、手動でフォームを作成する必要があります。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 イベントがバインドされ、変更後に自動的にアップロードされ、画像プレビューが設定されます。ただし、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 ページを開きます。任意の名前の任意のファイルをアップロードし、これらのアップロードされたファイルをダウンロードできます。

image

参考#

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。