初识ABP vNext(9):ABP模块化开发-文件管理

点击上方蓝字"小黑在哪里"关注我吧


  • 建立模块html

  • 模块开发web

    • 应用服务数据库

    • 运行模块api

    • 单元测试服务器

  • 模块使用微信

前言

在以前的章节中介绍过ABP扩展实体,当时在用户表扩展了用户头像字段,用户头像就涉及到文件上传和文件存储。文件上传是不少系统都会涉及到的一个基础功能,在ABP的模块化思路下,文件管理能够作成一个通用的模块,便于之后在多个项目中复用。单纯实现一个文件上传的功能并不复杂,本文就借着这个简单的功能来介绍一下ABP模块化开发的最基本步骤。app

开始

建立模块

首先使用ABP CLI建立一个模块:abp new Xhznl.FileManagement -t module --no-uiasync

建立完成后会获得以下文件:编辑器

在主项目中添加对应模块的引用,Application=>Application,Domain=>Domain,HttpApi=>HttpApi 等等。例如:ide

须要添加引用的项目:Application、Application.Contracts、Domain、Domain.Shared、EntityFrameworkCore、HttpApi、HttpApi.Client

手动添加这些引用比较麻烦,你能够搭建本身的私有NuGet服务器,把模块的包发布到私有NuGet上,而后经过NuGet来安装引用。两种方式各有优缺点,具体请参考自定义现有模块[1],关于私有NuGet搭建能够参考:十分钟搭建本身的私有NuGet服务器-BaGet[2]

而后给这些项目的模块类添加对应的依赖,例如:

经过上面的方式引用模块,使用visual studio是没法编译经过的:

须要在解决方案目录下,手动执行dotnet restore命令便可:

模块开发

接下来关于文件管理功能的开发,都在模块Xhznl.FileManagement中进行,它是一个独立的解决方案。初学ABP,下面就以尽可能简单的方式来实现这个模块。

应用服务

模块开发一般从Domain层实体创建开始,可是这里先跳过。先在FileManagement.Application.Contracts项目添加应用服务接口和Dto。

modules\file-management\src\Xhznl.FileManagement.Application.Contracts\Files\IFileAppService.cs:

public interface IFileAppService : IApplicationService
{
    Task<byte[]> GetAsync(string name);

    Task<stringCreateAsync(FileUploadInputDto input);
}

modules\file-management\src\Xhznl.FileManagement.Application.Contracts\Files\FileUploadInputDto.cs:

public class FileUploadInputDto
{
    [Required]
    public byte[] Bytes { getset; }

    [Required]
    public string Name { getset; }
}

而后是FileManagement.Application项目,实现应用服务,先定义一个配置类。

modules\file-management\src\Xhznl.FileManagement.Application\Files\FileOptions.cs:

public class FileOptions
{
    /// <summary>
    /// 文件上传目录
    /// </summary>
    public string FileUploadLocalFolder { getset; }

    /// <summary>
    /// 容许的文件最大大小
    /// </summary>
    public long MaxFileSize { getset; } = 1048576;//1MB

    /// <summary>
    /// 容许的文件类型
    /// </summary>
    public string[] AllowedUploadFormats { getset; } = { ".jpg"".jpeg"".png""gif"".txt" };
}

modules\file-management\src\Xhznl.FileManagement.Application\Files\FileAppService.cs:

public class FileAppService : FileManagementAppServiceIFileAppService
{
    private readonly FileOptions _fileOptions;

    public FileAppService(IOptions<FileOptions> fileOptions)
    {
        _fileOptions = fileOptions.Value;
    }

    public Task<byte[]> GetAsync(string name)
    {
        Check.NotNullOrWhiteSpace(name, nameof(name));

        var filePath = Path.Combine(_fileOptions.FileUploadLocalFolder, name);

        if (File.Exists(filePath))
        {
            return Task.FromResult(File.ReadAllBytes(filePath));
        }

        return Task.FromResult(new byte[0]);
    }

    [Authorize]
    public Task<stringCreateAsync(FileUploadInputDto input)
    {
        if (input.Bytes.IsNullOrEmpty())
        {
            throw new AbpValidationException("Bytes can not be null or empty!",
                new List<ValidationResult>
                {
                    new ValidationResult("Bytes can not be null or empty!"new[] {"Bytes"})
                });
        }

        if (input.Bytes.Length > _fileOptions.MaxFileSize)
        {
            throw new UserFriendlyException($"File exceeds the maximum upload size ({_fileOptions.MaxFileSize / 1024 / 1024} MB)!");
        }

        if (!_fileOptions.AllowedUploadFormats.Contains(Path.GetExtension(input.Name)))
        {
            throw new UserFriendlyException("Not a valid file format!");
        }

        var fileName = Guid.NewGuid().ToString("N") + Path.GetExtension(input.Name);
        var filePath = Path.Combine(_fileOptions.FileUploadLocalFolder, fileName);

        if (!Directory.Exists(_fileOptions.FileUploadLocalFolder))
        {
            Directory.CreateDirectory(_fileOptions.FileUploadLocalFolder);
        }

        File.WriteAllBytes(filePath, input.Bytes);

        return Task.FromResult("/api/file-management/files/" + fileName);
    }
}

服务实现很简单,就是基于本地文件系统的读写操做。

下面是FileManagement.HttpApi项目,添加控制器,暴露服务API接口。

modules\file-management\src\Xhznl.FileManagement.HttpApi\Files\FileController.cs:

[RemoteService]
[Route("api/file-management/files")]
public class FileController : FileManagementController
{
    private readonly IFileAppService _fileAppService;

    public FileController(IFileAppService fileAppService)
    {
        _fileAppService = fileAppService;
    }

    [HttpGet]
    [Route("{name}")]
    public async Task<FileResult> GetAsync(string name)
    {
        var bytes = await _fileAppService.GetAsync(name);
        return File(bytes, MimeTypes.GetByExtension(Path.GetExtension(name)));
    }

    [HttpPost]
    [Route("upload")]
    [Authorize]
    public async Task<JsonResult> CreateAsync(IFormFile file)
    {
        if (file == null)
        {
            throw new UserFriendlyException("No file found!");
        }

        var bytes = await file.GetAllBytesAsync();
        var result = await _fileAppService.CreateAsync(new FileUploadInputDto()
        {
            Bytes = bytes,
            Name = file.FileName
        });
        return Json(result);
    }

}

运行模块

ABP的模板是能够独立运行的,在FileManagement.HttpApi.Host项目的模块类FileManagementHttpApiHostModule配置FileOptions:

修改FileManagement.HttpApi.Host和FileManagement.IdentityServer项目的数据库链接配置,而后启动这2个项目,不出意外的话能够看到以下界面。

FileManagement.HttpApi.Host:

FileManagement.IdentityServer:

如今你能够使用postman来测试一下File的2个API,固然也能够编写单元测试。

单元测试

更好的方法是编写单元测试,关于如何作好单元测试能够参考ABP源码,下面只作一个简单示例:

模块使用

模块测试经过后,回到主项目。模块引用,模块依赖前面都已经作好了,如今只需配置一下FileOptions,就能够使用了。

目前FileManagement.Domain、FileManagement.Domain.Shared、FileManagement.EntityFrameworkCore这几个项目暂时没用到,项目结构也不是固定的,能够根据本身实际状况来调整。

最后

本文的模块示例比较简单,只是完成了一个文件上传和显示的基本功能,关于实体,数据库,领域服务,仓储之类的都暂时没用到。可是相信能够经过这个简单的例子,感觉到ABP插件式的开发体验,这是一个好的开始,更多详细内容后面再作介绍。本文参考了ABP blogging模块的文件管理,关于文件存储,ABP中也有一个BLOB系统能够了解一下。

参考资料

[1]

自定义现有模块: https://docs.abp.io/zh-Hans/abp/latest/Customizing-Application-Modules-Guide

[2]

十分钟搭建本身的私有NuGet服务器-BaGet: https://www.cnblogs.com/xhznl/p/13426918.html


若是本文对您有用,

不妨点个“”或者转发朋友圈支持一下

本文分享自微信公众号 - dotNET跨平台(opendotnet)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索