初识ABP vNext11:聚合根、仓储、领域服务、应用服务、Blob存储

Tips:本篇已加入系列文章阅读目录,可点击查看更多相关文章。

前言

在前两节中介绍了ABP模块开发的基本步骤,试着实现了一个简单的文件管理模块;功能很简单,就是基于本地文件系统来完成文件的读写操作,数据也并没有保存到数据库,所以之前只简单使用了应用服务,并没有用到领域层。而在DDD中领域层是非常重要的一层,其中包含了实体,聚合根,领域服务,仓储等等,复杂的业务逻辑也应该在领域层来实现。本篇来完善一下文件管理模块,将文件记录保存到数据库,并使用ABP BLOB系统来完成文件的存储。

开始

聚合根

首先从实体模型开始,建立File实体。按照DDD的思路,这里的File应该是一个聚合根

\modules\file-management\src\Xhznl.FileManagement.Domain\Files\File.cs:

public class File : FullAuditedAggregateRoot<Guid>,IMultiTenant
{
    public virtual Guid? TenantId { get; protected set; }

    [NotNull]
    public virtual string FileName { get; protected set; }

    [NotNull]
    public virtual string BlobName { get; protected set; }

    public virtual long ByteSize { get; protected set; }

    protected File() { }

    public File(Guid id,Guid? tenantId,[NotNull] string fileName,[NotNull] string blobName,long byteSize) : base(id)
    {
        TenantId = tenantId;
        FileName = Check.NotNullOrWhiteSpace(fileName,nameof(fileName));
        BlobName = Check.NotNullOrWhiteSpace(blobName,nameof(blobName));
        ByteSize = byteSize;
    }
}

在DbContext中添加DbSet

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\IFileManagementDbContext.cs:

public interface IFileManagementDbContext : IEfCoreDbContext
{
    DbSet<File> Files { get; }
}

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\FileManagementDbContext.cs:

public class FileManagementDbContext : AbpDbContext<FileManagementDbContext>,IFileManagementDbContext
{
    public DbSet<File> Files { get; set; }

    ......
}

配置实体

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\FileManagementDbContextModelCreatingExtensions.cs:

public static void ConfigureFileManagement(
    this ModelBuilder builder,Action<FileManagementModelBuilderConfigurationOptions> optionsAction = null)
{
    ......

    builder.Entity<File>(b =>
    {
        //Configure table & schema name
        b.ToTable(options.TablePrefix + "Files",options.Schema);

        b.ConfigureByConvention();

        //Properties
        b.Property(q => q.FileName).IsRequired().HasMaxLength(FileConsts.MaxFileNameLength);
        b.Property(q => q.BlobName).IsRequired().HasMaxLength(FileConsts.MaxBlobNameLength);
        b.Property(q => q.ByteSize).IsRequired();
    });
}

仓储

ABP为每个聚合根或实体提供了 默认的通用(泛型)仓储 ,其中包含了标准的CRUD操作,注入IRepository<TEntity,TKey>即可使用。通常来说默认仓储就够用了,有特殊需求时也可以自定义仓储。

定义仓储接口

\modules\file-management\src\Xhznl.FileManagement.Domain\Files\IFileRepository.cs:

public interface IFileRepository : IRepository<File,Guid>
{
    Task<File> FindByBlobNameAsync(string blobName);
}

仓储实现

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\Files\EfCoreFileRepository.cs:

public class EfCoreFileRepository : EfCoreRepository<IFileManagementDbContext,File,Guid>,IFileRepository
{
    public EfCoreFileRepository(IDbContextProvider<IFileManagementDbContext> dbContextProvider) : base(dbContextProvider)
    {
    }

    public async Task<File> FindByBlobNameAsync(string blobName)
    {
        Check.NotNullOrWhiteSpace(blobName,nameof(blobName));

        return await DbSet.FirstOrDefaultAsync(p => p.BlobName == blobName);
    }
}

注册仓储

\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\FileManagementEntityFrameworkCoreModule.cs:

public class FileManagementEntityFrameworkCoreModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.AddAbpDbContext<FileManagementDbContext>(options =>
        {
            options.AddRepository<File,EfCoreFileRepository>();
        });
    }
}

领域服务

定义领域服务接口

\modules\file-management\src\Xhznl.FileManagement.Domain\Files\IFileManager.cs:

public interface IFileManager : IDomainService
{
    Task<File> FindByBlobNameAsync(string blobName);

    Task<File> CreateAsync(string fileName,byte[] bytes);

    Task<byte[]> GetBlobAsync(string blobName);
}

在实现领域服务之前,先来安装一下ABP Blob系统核心包,因为我要使用blob来存储文件,Volo.Abp.BlobStoring包是必不可少的。

BLOB存储

BLOB(binary large object):大型二进制对象;关于BLOB可以参考 BLOB 存储 ,这里不多介绍。

安装Volo.Abp.BlobStoring,在Domain项目目录下执行:abp add-package Volo.Abp.BlobStoring

Volo.Abp.BlobStoring是BLOB的核心包,它仅包含BLOB的一些基本抽象,想要BLOB系统正常工作,还需要为它配置一个提供程序;这个提供程序暂时不管,将来由模块的具体使用者去提供。这样的好处是模块不依赖特定存储提供程序,使用者可以随意的指定存储到阿里云,Azure,或者文件系统等等。。。

领域服务实现

\modules\file-management\src\Xhznl.FileManagement.Domain\Files\FileManager.cs:

public class FileManager : DomainService,IFileManager
{
    protected IFileRepository FileRepository { get; }
    protected IBlobContainer BlobContainer { get; }

    public FileManager(IFileRepository fileRepository,IBlobContainer blobContainer)
    {
        FileRepository = fileRepository;
        BlobContainer = blobContainer;
    }

    public virtual async Task<File> FindByBlobNameAsync(string blobName)
    {
        Check.NotNullOrWhiteSpace(blobName,nameof(blobName));

        return await FileRepository.FindByBlobNameAsync(blobName);
    }

    public virtual async Task<File> CreateAsync(string fileName,byte[] bytes)
    {
        Check.NotNullOrWhiteSpace(fileName,nameof(fileName));

        var blobName = Guid.NewGuid().ToString("N");

        var file = await FileRepository.InsertAsync(new File(GuidGenerator.Create(),CurrentTenant.Id,fileName,blobName,bytes.Length));

        await BlobContainer.SaveAsync(blobName,bytes);

        return file;
    }

    public virtual async Task<byte[]> GetBlobAsync(string blobName)
    {
        Check.NotNullOrWhiteSpace(blobName,nameof(blobName));

        return await BlobContainer.GetAllBytesAsync(blobName);
    }
}

应用服务

接下来修改一下应用服务,应用服务通常没有太多业务逻辑,其调用领域服务来完成业务。

应用服务接口

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

public interface IFileAppService : IApplicationService
{
    Task<FileDto> FindByBlobNameAsync(string blobName);

    Task<string> CreateAsync(FileDto input);
}

应用服务实现

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

public class FileAppService : FileManagementAppService,IFileAppService
{
    protected IFileManager FileManager { get; }

    public FileAppService(IFileManager fileManager)
    {
        FileManager = fileManager;
    }

    public virtual async Task<FileDto> FindByBlobNameAsync(string blobName)
    {
        Check.NotNullOrWhiteSpace(blobName,nameof(blobName));

        var file = await FileManager.FindByBlobNameAsync(blobName);
        var bytes = await FileManager.GetBlobAsync(blobName);

        return new FileDto
        {
            Bytes = bytes,FileName = file.FileName
        };
    }

    [Authorize]
    public virtual async Task<string> CreateAsync(FileDto input)
    {
        await CheckFile(input);

        var file = await FileManager.CreateAsync(input.FileName,input.Bytes);

        return file.BlobName;
    }

    protected virtual async Task CheckFile(FileDto 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"})
                });
        }

        var allowedMaxFileSize = await SettingProvider.GetAsync<int>(FileManagementSettings.AllowedMaxFileSize);//kb
        var allowedUploadFormats = (await SettingProvider.GetOrNullAsync(FileManagementSettings.AllowedUploadFormats))
            ?.Split(",",StringSplitOptions.RemoveEmptyEntries);

        if (input.Bytes.Length > allowedMaxFileSize * 1024)
        {
            throw new UserFriendlyException(L["FileManagement.ExceedsTheMaximumSize",allowedMaxFileSize]);
        }

        if (allowedUploadFormats == null || !allowedUploadFormats.Contains(Path.GetExtension(input.FileName)))
        {
            throw new UserFriendlyException(L["FileManagement.NotValidFormat"]);
        }
    }
}

API控制器

最后记得将服务接口暴露出去,我这里是自己编写Controller,你也可以使用ABP的自动API控制器来完成,请参考 自动API控制器

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

[RemoteService]
[Route("api/file-management/files")]
public class FileController : FileManagementController
{
    protected IFileAppService FileAppService { get; }

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

    [HttpGet]
    [Route("{blobName}")]
    public virtual async Task<FileResult> GetAsync(string blobName)
    {
        var fileDto = await FileAppService.FindByBlobNameAsync(blobName);
        return File(fileDto.Bytes,MimeTypes.GetByExtension(Path.GetExtension(fileDto.FileName)));
    }

    [HttpPost]
    [Route("upload")]
    [Authorize]
    public virtual 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 FileDto()
        {
            Bytes = bytes,FileName = file.FileName
        });
        return Json(result);
    }
}

单元测试

针对以上内容做一个简单的测试,首先为Blob系统配置一个提供程序。

我这里使用最简单的文件系统来储存,所以需要安装Volo.Abp.BlobStoring.FileSystem。在Application.Tests项目目录下执行:abp add-package Volo.Abp.BlobStoring.FileSystem

配置默认容器

\modules\file-management\test\Xhznl.FileManagement.Application.Tests\FileManagementApplicationTestModule.cs:

[DependsOn(
    typeof(FileManagementApplicationModule),typeof(FileManagementDomainTestModule),typeof(AbpBlobStoringFileSystemModule)
    )]
public class FileManagementApplicationTestModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpBlobStoringOptions>(options =>
        {
            options.Containers.ConfigureDefault(container =>
            {
                container.UseFileSystem(fileSystem =>
                {
                    fileSystem.BasePath = "D:\\my-files";
                });
            });
        });

        base.ConfigureServices(context);
    }
}

测试用例

\modules\file-management\test\Xhznl.FileManagement.Application.Tests\Files\FileAppService_Tests.cs:

public class FileAppService_Tests : FileManagementApplicationTestBase
{
    private readonly IFileAppService _fileAppService;

    public FileAppService_Tests()
    {
        _fileAppService = GetRequiredService<IFileAppService>();
    }

    [Fact]
    public async Task Create_FindByBlobName_Test()
    {
        var blobName = await _fileAppService.CreateAsync(new FileDto()
        {
            FileName = "微信图片_20200813165555.jpg",Bytes = await System.IO.File.ReadAllBytesAsync(@"D:\WorkSpace\WorkFiles\杂项\图片\微信图片_20200813165555.jpg")
        });
        blobName.ShouldNotBeEmpty();

        var fileDto = await _fileAppService.FindByBlobNameAsync(blobName);
        fileDto.ShouldNotBeNull();
        fileDto.FileName.ShouldBe("微信图片_20200813165555.jpg");
    }
}

运行测试

测试通过,blob也已经存入D:\my-files:

模块引用

下面回到主项目,前面的章节中已经介绍过,模块的引用依赖都已经添加完成,下面就直接从数据库迁移开始。

\src\Xhznl.HelloAbp.EntityFrameworkCore.DbMigrations\EntityFrameworkCore\HelloAbpMigrationsDbContext.cs:

public class HelloAbpMigrationsDbContext : AbpDbContext<HelloAbpMigrationsDbContext>
{
    public HelloAbpMigrationsDbContext(DbContextOptions<HelloAbpMigrationsDbContext> options)
        : base(options)
    {

    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        ......
            
        builder.ConfigureFileManagement();
        
        ......
    }
}

打开程序包管理器控制台,执行以下命令:

Add-Migration "Added_FileManagement"

Update-Database

此时数据库已经生成了File表:

还有记得在HttpApi.Host项目配置你想要的blob提供程序。

最后结合前端测试一下吧:

最后

以上就是本人所理解的abp模块开发一个相对完整的流程,还有些概念后面再做补充。因为这个例子比较简单,文中有些环节是不必要的,需要结合实际情况去取舍。代码地址:https://github.com/xiajingren/HelloAbp

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


引言 本文从Linux小白的视角, 在CentOS 7.x服务器上搭建一个Nginx-Powered AspNet Core Web准生产应用。 在开始之前,我们还是重温一下部署原理,正如你所常见的.Net Core 部署图: 在Linux上部署.Net Core App最好的方式是在Linux机器
引言: 多线程编程/异步编程非常复杂,有很多概念和工具需要去学习,贴心的.NET提供Task线程包装类和await/async异步编程语法糖简化了异步编程方式。 相信很多开发者都看到如下异步编程实践原则: 遵守以上冷冰冰的②③条的原则,可保证异步程序按照预期状态正常运作;我们在各大编程论坛常看到违背
一. 宏观概念 ASP.NET Core Middleware是在应用程序处理管道pipeline中用于处理请求和操作响应的组件。 每个组件是pipeline 中的一环。 自行决定是否将请求传递给下一个组件 在处理管道的下个组件执行之前和之后执行业务逻辑 二. 特性和行为 ASP.NET Core处
背景 在.Net和C#中运行异步代码相当简单,因为我们有时候需要取消正在进行的异步操作,通过本文,可以掌握 通过CancellationToken取消任务(包括non-cancellable任务)。 Task&#160;表示无返回值的异步操作, 泛型版本Task&lt;TResult&gt;表示有返
HTTP基本认证 在HTTP中,HTTP基本认证(Basic Authentication)是一种允许网页浏览器或其他客户端程序以(用户名:口令) 请求资源的身份验证方式,不要求cookie,session identifier、login page等标记或载体。 - 所有浏览器据支持HTTP基本认
1.Linq 执行多列排序 OrderBy的意义是按照指定顺序排序,连续两次OrderBy,后面一个有可能会打乱前面一个的排序顺序,可能与预期不符。 要实现sql中的order by word,name类似效果; LINQ 有ThenBy可以紧接使用, ThenBy记住原本排序的值,然后再排其他值,
ASP.NET Core 核心特性:开源、跨平台、高性能是其决战JAVA的必胜法宝,最引人关注的跨平台特性 到底是怎么实现? &#xA; 本文分Unix、Windows剖析跨平台内幕,读完让你大呼过瘾。
前导 Asynchronous programming Model(APM)异步编程模型以BeginMethod(...) 和 EndMethod(...)结对出现。 IAsyncResult BeginGetResponse(AsyncCallback callback, object state
引言 最近在公司开发了一个项目,项目部署架构图如下: 思路 如图中文本所述,公司大数据集群不允许直接访问外网,需要一个网关服务器代理请求,本处服务器A就是边缘代理服务器的作用。 通常技术人员最快捷的思路是在服务器A上部署IISʺpplication Request Routing Module组件
作为一枚后端程序狗,项目实践常遇到定时任务的工作,最容易想到的的思路就是利用Windows计划任务/wndows service程序/Crontab程序等主机方法在主机上部署定时任务程序/脚本。 但是很多时候,若使用的是共享主机或者受控主机,这些主机不允许你私自安装exe程序、Windows服务程序
引言 熟悉TPL Dataflow博文的朋友可能记得这是个单体程序,使用TPL Dataflow 处理工作流任务, 在使用Docker部署的过程中, 有一个问题一直无法回避: 在单体程序部署的瞬间(服务不可用)会有少量流量无法处理;更糟糕的情况下,迭代部署的这个版本有问题,上线后无法运作, 更多的流
合格的web后端程序员,除搬砖技能,还必须会给各种web服务器配置Https,本文结合ASP.NET Core部署模型聊一聊启用Https的方式。 温故知新 目前常见的Http请求明文传输,请求可能被篡改,访问的站点可能被伪造。 HTTPS是HTTP加上TLS/SSL协议构建的可进行加密传输、身份认
长话短说 前文《解剖HttpClientFactory,自由扩展HttpMessageHandler》主要讲如何为HttpClientFactory自定义HttpMessageHandler组件, 现在来完成课后的小作业: 将重点日志字段显示到Nlog的LayoutRenderer上。 本文实现一个
引言问题 作为资深老鸟,有事没事,出去面试;找准差距、定位价值。 面试必谈哈希, Q1:什么是哈希? Q2:哈希为什么快? Q3:你是怎么理解哈希算法利用空间换取时间的? Q4:你是怎么解决哈希冲突的? Q5:你有实际用写过哈希算法吗? 知识储备 哈希(也叫散列)是一种查找算法(可用于插入),哈希算
前言 如题,有感于博客园最近多次翻车,感觉像胡子眉毛一把抓, 定位不了生产环境的问题。 抛开流程问题,思考在生产环境中如何做故障排除,&#160;发现博客园里面这方面的文章比较少。 .Net 本身是提供了sos.dll工具帮助我们在生产中故障排除,通过提供有关内部公共语言运行时(CLR)环境的信息,
.NET程序是基于.NET Framework、.NET Core、Mono、【.NET实现】开发和运行的 ,定义以上【.NET实现】的标准规范称为.NET Standard .NET Standard .NET标准是一组API集合,由上层三种【.NET实现】的Basic Class Library
长话短说 上个月公司上线了一个物联网数据科学项目,我主要负责前端接受物联网事件,并提供 参数下载。 webapp 部署在Azure云上,参数使用Azure SQL Server存储。 最近从灰度测试转向全量部署之后,日志时常收到: SQL Session超限报错。 排查 我在Azure上使用的是 S
临近年关,搜狗,360浏览器出现页面无法成功跳转,同域Cookie丢失? 也许是服务端 SameSite惹的祸。&#xA;本文揭示由于Chrome低版本内核不识别 SameSite= None, 引发的单点登录故障。
本文聊一聊TraceID的作用和一般组成,衍生出ASP. NETCore 单体和分布式程序中 TraceId 的使用方式
通过给 HttpClint请求的日志增加 TraceId,解锁自定义扩展 HttpClientFacroty 的姿势