myesn

myEsn2E9

hi
github

ABP: Complete file upload and download using BLOB Storing (File System Provider)

Install Dependencies#

Execute in the ?.Domain project:

abp add-package Volo.Abp.BlobStoring.FileSystem

After executing the command, it will automatically:

  1. Install the NuGet package Volo.Abp.BlobStoring.FileSystem in the project.
  2. Add the following code in ?DomainModule.cs:
using Volo.Abp.BlobStoring.FileSystem;

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

Setting up Blob Storage#

Before using Blob storage, we need to create a Blob Container.

Create a class named FileContainer in the ?.Domain\BlobStoring directory with the following content:

using Volo.Abp.BlobStoring;

namespace ?.BlobStoring
{
    [BlobContainerName("file-container")]
    public class FileContainer
    {

    }
}

After this, we can configure AbpBlobStoringOptions in the ConfigureServices method of the ?WebModule.cs file in the ?.Web project:

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

If you run the upload file in Debug mode, the file will be saved in the ?.Web\bin\Debug\net8.0\host\file-container directory.
File path calculation: https://docs.abp.io/en/abp/latest/Blob-Storing-File-System#file-path-calculation

Creating Application Layer#

Before creating the Application Service, we need to create some DTOs used by the Application Service.

Create the following DTOs in the ?.Application.Contracts project:

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

Next, create an interface IFileService.cs in the same project where the DTOs were created:

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

Then, we can create the Application Service.

Create FileAppService.cs in the ?.Application project:

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

We have completed the application layer of this project. Next, we will create a Controller for the API and implement the UI with Razor Page.

Creating Controller#

Create FileController.cs in the ?.HttpApi project:

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>
        /// Upload a single file
        /// </summary>
        /// <param name="folder">Directory name (can be empty)</param>
        /// <param name="file">Single 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>
        /// Download a single file
        /// </summary>
        /// <param name="fileName">File name</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 is used to send files from the server to the client.
This interface only requires a string parameter, and we use that parameter to retrieve the stored Blob. If the blob exists, it returns the result File so that the download process can begin.

Creating User Interface#

We will only create one page to demonstrate that the download and upload operations are functioning correctly.

Create UploadFileDto in the Dtos directory of the ?.Web project:

// UploadFileDto.cs
using Microsoft.AspNetCore.Http;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace ?.Web.Dtos
{
    public class UploadFileDto
    {
        /// <summary>
        /// File name
        /// </summary>
        [Required]
        [DisplayName("File Name")]
        public string Name { get; set; }

        /// <summary>
        /// Single file
        /// </summary>
        [Required]
        [DisplayName("File")]
        public IFormFile File { get; set; }
    }
}

Then create a directory named Files in the Pages directory. Create a Razor page named Index in that directory.

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>File Upload and Download</h3>
    </abp-card-header>
    <abp-card-body>
        <abp-row>
            <abp-column>
                <h3>Upload File</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>Download File</h3>
                <hr />
                <form id="DownloadFile">
                    <div class="form-group">
                        <label for="fileName">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>

We will vertically divide the page, with the left side for uploading and the right side for downloading. We use ABP Tag Helpers to create the page.

Create an Index.js file in the same directory as the Index.cshtml file.

$(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);
  });
});

This jQuery code is used for downloading. Additionally, we wrote a simple code to automatically fill the Filename input when the user selects a file.

Using Ajax to Upload and Preview Images#

If a value of a property in the form needs to be obtained after uploading a file, then this form cannot use the <abp-dynamic-form> tag helper provided by abp, and you need to manually write the form. You can refer to the CreateModal or EditModal in the Pages/Customers directory (this is my own page, and due to time constraints, I haven't organized it yet).

Add general file upload code:

/**
 * Initialize the interaction of all file upload input boxes. Note: HTML must be written in the order specified above.
 */
function initialAllUploadFileInputs() {
    // It is best to use function for the callback here, not an arrow function, otherwise this will point to the wrong context.
    $('form input[type="file"]').on('change', async function (e) {
        var file = e.target.files[0];
        if (!file) {
            // After canceling file selection, clear the hidden input control value and image preview.
            $(this).next().val('');
            $(this).nextAll('img').removeAttr('src');
            return;
        }

        // Upload file
        const result = await uploadFile(null, e.target.files[0]);

        // Note that the input=hidden control must be placed after the current input=file control, otherwise the form control value cannot be set.
        $(this).nextAll('input[type=hidden]').val(result.name);
        // Image preview
        $(this).nextAll('img').attr('src', abp.appPath + 'api/File/download/' + result.name);
    });
}

/**
 * Call HTTP API to upload files
 * @param {any} folder  Directory, can be empty
 * @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,
        // Must be false to automatically add the correct Content-Type
        contentType: false,
        // Must be false to avoid jQuery's default handling of FormData
        // XMLHttpRequest will handle FormData correctly
        processData: false,
    });
}

Then execute on the page:

$(() => {
    initialAllUploadFileInputs();
});

After calling initialAllUploadFileInputs, it will bind the change event to all input=file controls. When changed, it will automatically upload and set the preview image, but the HTML format must conform to the following order specification:

If it is a creation form:

@* Note that label, input=file, input=hidden, img tags must appear in order, as the underlying JavaScript code depends on their order *@
<div class="mb-3">
    <label class="form-label" for="CreateCustomerDto_IdImageFrontId_FileInput">Personal ID Photo Front</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>

If it is an update form:

@* Note that label, input=file, input=hidden, img tags must appear in order, as the underlying JavaScript code depends on their order *@
<div class="mb-3">
    <label class="form-label" for="UpdateCustomerDto_IdImageFrontId_FileInput">Personal ID Photo Front</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>

Result#

After completing the code tutorial, run the ?.Web project and open the /Files page in the browser. You can upload any file with any name and download those uploaded files.

image

References#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.