Tips:本篇已加入系列文章閱讀目錄,可點擊查看更多相關文章。html
在前兩節中介紹了ABP模塊開發的基本步驟,試着實現了一個簡單的文件管理模塊;功能很簡單,就是基於本地文件系統來完成文件的讀寫操做,數據也並無保存到數據庫,因此以前只簡單使用了應用服務,並無用到領域層。而在DDD中領域層是很是重要的一層,其中包含了實體,聚合根,領域服務,倉儲等等,複雜的業務邏輯也應該在領域層來實現。本篇來完善一下文件管理模塊,將文件記錄保存到數據庫,並使用ABP BLOB系統來完成文件的存儲。前端
首先從實體模型開始,創建File實體。按照DDD的思路,這裏的File應該是一個聚合根。git
\modules\file-management\src\Xhznl.FileManagement.Domain\Files\File.cs:github
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:api
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; } ...... }
配置實體async
\modules\file-management\src\Xhznl.FileManagement.EntityFrameworkCore\EntityFrameworkCore\FileManagementDbContextModelCreatingExtensions.cs:ide
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(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