Last active
August 2, 2021 17:00
-
-
Save sfmskywalker/836d3cc0accfd009a47daa5a7eb0baee to your computer and use it in GitHub Desktop.
Building Workflow Driven .NET Applications With Elsa 2 - Part 4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using DocumentManagement.Core.Options; | |
using DocumentManagement.Core.Services; | |
using Microsoft.Extensions.DependencyInjection; | |
using Storage.Net; | |
namespace DocumentManagement.Core.Extensions | |
{ | |
public static class ServiceCollectionExtensions | |
{ | |
public static IServiceCollection AddDomainServices(this IServiceCollection services) | |
{ | |
services.Configure<DocumentStorageOptions>(options => options.BlobStorageFactory = StorageFactory.Blobs.InMemory); | |
return services | |
.AddSingleton<ISystemClock, SystemClock>() | |
.AddSingleton<IFileStorage, FileStorage>() | |
.AddScoped<IDocumentService, DocumentService>(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace DocumentManagement.Core.Models | |
{ | |
public class Document | |
{ | |
public string Id { get; set; } = default!; | |
public string DocumentTypeId { get; set; } = default!; | |
public string FileName { get; set; } = default!; | |
public DateTime CreatedAt { get; set; } | |
public string? Notes { get; set; } | |
public DocumentStatus Status { get; set; } | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using DocumentManagement.Core.Models; | |
using Microsoft.EntityFrameworkCore; | |
namespace DocumentManagement.Persistence | |
{ | |
public class DocumentDbContext : DbContext | |
{ | |
public DocumentDbContext(DbContextOptions options) : base(options) | |
{ | |
} | |
public DbSet<Document> Documents { get; set; } = default!; | |
public DbSet<DocumentType> DocumentTypes { get; set; } = default!; | |
protected override void OnModelCreating(ModelBuilder modelBuilder) | |
{ | |
modelBuilder.Entity<DocumentType>().HasData( | |
CreateDocumentType("ChangeRequest", "Change Request"), | |
CreateDocumentType("LeaveRequest", "Leave Request"), | |
CreateDocumentType("IdentityVerification", "Identity Verification")); | |
} | |
private static DocumentType CreateDocumentType(string id, string name) => new DocumentType | |
{ | |
Id = id, | |
Name = name | |
}; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using DocumentManagement.Core.Events; | |
using DocumentManagement.Core.Models; | |
using MediatR; | |
namespace DocumentManagement.Core.Services | |
{ | |
public class DocumentService : IDocumentService | |
{ | |
private readonly IFileStorage _fileStorage; | |
private readonly ISystemClock _systemClock; | |
private readonly IMediator _mediator; | |
private readonly IDocumentStore _documentStore; | |
public DocumentService(IFileStorage fileStorage, IDocumentStore documentStore, ISystemClock systemClock, IMediator mediator) | |
{ | |
_fileStorage = fileStorage; | |
_documentStore = documentStore; | |
_systemClock = systemClock; | |
_mediator = mediator; | |
} | |
public async Task<Document> SaveDocumentAsync(string fileName, Stream data, string documentTypeId, CancellationToken cancellationToken = default) | |
{ | |
// Persist the uploaded file. | |
await _fileStorage.WriteAsync(data, fileName, cancellationToken); | |
// Create a document record. | |
var document = new Document | |
{ | |
Id = Guid.NewGuid().ToString("N"), | |
Status = DocumentStatus.New, | |
DocumentTypeId = documentTypeId, | |
CreatedAt = _systemClock.UtcNow, | |
FileName = fileName | |
}; | |
// Save the document. | |
await _documentStore.SaveAsync(document, cancellationToken); | |
// Publish a domain event. | |
await _mediator.Publish(new NewDocumentReceived(document), cancellationToken); | |
return document; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace DocumentManagement.Core.Models | |
{ | |
public enum DocumentStatus | |
{ | |
New, | |
Archived | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Storage.Net; | |
using Storage.Net.Blobs; | |
namespace DocumentManagement.Core.Options | |
{ | |
public class DocumentStorageOptions | |
{ | |
public Func<IBlobStorage> BlobStorageFactory { get; set; } = StorageFactory.Blobs.InMemory; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace DocumentManagement.Core.Models | |
{ | |
public class DocumentType | |
{ | |
public string Id { get; set; } = default!; | |
public string Name { get; set; } = default!; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Threading; | |
using System.Threading.Tasks; | |
using DocumentManagement.Core.Models; | |
using DocumentManagement.Core.Services; | |
using Microsoft.EntityFrameworkCore; | |
namespace DocumentManagement.Persistence.Services | |
{ | |
public class EFCoreDocumentStore : IDocumentStore | |
{ | |
private readonly IDbContextFactory<DocumentDbContext> _dbContextFactory; | |
public EFCoreDocumentStore(IDbContextFactory<DocumentDbContext> dbContextFactory) | |
{ | |
_dbContextFactory = dbContextFactory; | |
} | |
public async Task SaveAsync(Document entity, CancellationToken cancellationToken = default) | |
{ | |
await using var dbContext = _dbContextFactory.CreateDbContext(); | |
var existingDocument = await dbContext.Documents.FirstOrDefaultAsync(x => x.Id == entity.Id, cancellationToken); | |
if(existingDocument == null) | |
await dbContext.Documents.AddAsync(entity, cancellationToken); | |
else | |
dbContext.Entry(existingDocument).CurrentValues.SetValues(entity); | |
await dbContext.SaveChangesAsync(cancellationToken); | |
} | |
public async Task<Document?> GetAsync(string id, CancellationToken cancellationToken = default) | |
{ | |
await using var dbContext = _dbContextFactory.CreateDbContext(); | |
return await dbContext.Documents.FirstOrDefaultAsync(x => x.Id == id, cancellationToken); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using DocumentManagement.Core.Models; | |
using DocumentManagement.Core.Services; | |
using Microsoft.EntityFrameworkCore; | |
namespace DocumentManagement.Persistence.Services | |
{ | |
public class EFCoreDocumentTypeStore : IDocumentTypeStore | |
{ | |
private readonly IDbContextFactory<DocumentDbContext> _dbContextFactory; | |
public EFCoreDocumentTypeStore(IDbContextFactory<DocumentDbContext> dbContextFactory) | |
{ | |
_dbContextFactory = dbContextFactory; | |
} | |
public async Task<IEnumerable<DocumentType>> ListAsync(CancellationToken cancellationToken = default) | |
{ | |
await using var dbContext = _dbContextFactory.CreateDbContext(); | |
return await dbContext.DocumentTypes.ToListAsync(cancellationToken); | |
} | |
public async Task<DocumentType?> GetAsync(string id, CancellationToken cancellationToken = default) | |
{ | |
await using var dbContext = _dbContextFactory.CreateDbContext(); | |
return await dbContext.DocumentTypes.FirstOrDefaultAsync(x => x.Id == id, cancellationToken); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@page "/file-received/{DocumentId}" | |
@model FileReceivedModel | |
@{ | |
ViewData["Title"] = "Document Received"; | |
} | |
<div> | |
<h1 class="display-4">Document Received</h1> | |
<p>We received your file. Thank you!</p> | |
<p>Document ID: @Model.DocumentId</p> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Threading; | |
using System.Threading.Tasks; | |
using DocumentManagement.Core.Models; | |
using DocumentManagement.Core.Services; | |
using Microsoft.AspNetCore.Mvc; | |
using Microsoft.AspNetCore.Mvc.RazorPages; | |
namespace DocumentManagement.Web.Pages | |
{ | |
public class FileReceivedModel : PageModel | |
{ | |
private readonly IDocumentStore _documentStore; | |
public FileReceivedModel(IDocumentStore documentStore) | |
{ | |
_documentStore = documentStore; | |
} | |
[BindProperty(SupportsGet = true)] public string DocumentId { get; set; } = default!; | |
public Document? Document { get; set; } | |
public async Task<IActionResult?> OnGetAsync(CancellationToken cancellationToken) | |
{ | |
Document = await _documentStore.GetAsync(DocumentId, cancellationToken); | |
return Document == null ? NotFound() : default(IActionResult?); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.IO; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using DocumentManagement.Core.Options; | |
using Microsoft.Extensions.Options; | |
using Storage.Net.Blobs; | |
namespace DocumentManagement.Core.Services | |
{ | |
public class FileStorage : IFileStorage | |
{ | |
private readonly IBlobStorage _blobStorage; | |
public FileStorage(IOptions<DocumentStorageOptions> options) => _blobStorage = options.Value.BlobStorageFactory(); | |
public Task WriteAsync(Stream data, string fileName, CancellationToken cancellationToken = default) => | |
_blobStorage.WriteAsync(fileName, data, false, cancellationToken); | |
public Task<Stream> ReadAsync(string fileName, CancellationToken cancellationToken = default) => | |
_blobStorage.OpenReadAsync(fileName, cancellationToken); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.IO; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using DocumentManagement.Core.Models; | |
namespace DocumentManagement.Core.Services | |
{ | |
public interface IDocumentService | |
{ | |
Task<Document> SaveDocumentAsync(string fileName, Stream data, string documentTypeId, CancellationToken cancellationToken = default); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Threading; | |
using System.Threading.Tasks; | |
using DocumentManagement.Core.Models; | |
namespace DocumentManagement.Core.Services | |
{ | |
public interface IDocumentStore | |
{ | |
Task SaveAsync(Document entity, CancellationToken cancellationToken = default); | |
Task<Document?> GetAsync(string id, CancellationToken cancellationToken = default); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using DocumentManagement.Core.Models; | |
namespace DocumentManagement.Core.Services | |
{ | |
public interface IDocumentTypeStore | |
{ | |
Task<IEnumerable<DocumentType>> ListAsync(CancellationToken cancellationToken = default); | |
Task<DocumentType?> GetAsync(string id, CancellationToken cancellationToken = default); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.IO; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace DocumentManagement.Core.Services | |
{ | |
public interface IFileStorage | |
{ | |
Task WriteAsync(Stream data, string fileName, CancellationToken cancellationToken = default); | |
Task<Stream> ReadAsync(string fileName, CancellationToken cancellationToken = default); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@page | |
@model IndexModel | |
@{ | |
ViewData["Title"] = "Home page"; | |
} | |
<div> | |
<h1 class="display-4">Upload Document</h1> | |
<p>Upload a file for processing</p> | |
</div> | |
<form method="post" enctype="multipart/form-data"> | |
<div class="mb-3"> | |
<label asp-for="DocumentTypeId" class="form-label">Document Type</label> | |
<select asp-for="DocumentTypeId" asp-items="Model.DocumentTypes" class="form-select"></select> | |
</div> | |
<div class="mb-3"> | |
<label asp-for="FileUpload" class="form-label">File</label> | |
<input asp-for="FileUpload" type="file" class="form-control"/> | |
</div> | |
<button type="submit" class="btn btn-primary">Submit</button> | |
</form> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using DocumentManagement.Core.Services; | |
using Microsoft.AspNetCore.Http; | |
using Microsoft.AspNetCore.Mvc; | |
using Microsoft.AspNetCore.Mvc.RazorPages; | |
using Microsoft.AspNetCore.Mvc.Rendering; | |
using Open.Linq.AsyncExtensions; | |
namespace DocumentManagement.Web.Pages | |
{ | |
public class IndexModel : PageModel | |
{ | |
private readonly IDocumentTypeStore _documentTypeStore; | |
private readonly IDocumentService _documentService; | |
public IndexModel(IDocumentTypeStore documentTypeStore, IDocumentService documentService) | |
{ | |
_documentTypeStore = documentTypeStore; | |
_documentService = documentService; | |
} | |
[BindProperty] public string DocumentTypeId { get; set; } = default!; | |
[BindProperty] public IFormFile FileUpload { get; set; } = default!; | |
public ICollection<SelectListItem> DocumentTypes { get; set; } = new List<SelectListItem>(); | |
public async Task OnGetAsync(CancellationToken cancellationToken) | |
{ | |
var documentTypes = await _documentTypeStore.ListAsync(cancellationToken).ToList(); | |
DocumentTypes = documentTypes.Select(x => new SelectListItem(x.Name, x.Id)).ToList(); | |
} | |
public async Task<IActionResult> OnPostAsync(CancellationToken cancellationToken) | |
{ | |
var extension = Path.GetExtension(FileUpload.FileName); | |
var fileName = $"{Guid.NewGuid()}{extension}"; | |
var fileStream = FileUpload.OpenReadStream(); | |
var document = await _documentService.SaveDocumentAsync(fileName, fileStream, DocumentTypeId, cancellationToken); | |
return RedirectToPage("FileReceived", new {DocumentId = document.Id}); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Licensed to the .NET Foundation under one or more agreements. | |
// The .NET Foundation licenses this file to you under the MIT license. | |
using System.ComponentModel; | |
// ReSharper disable once CheckNamespace | |
namespace System.Runtime.CompilerServices | |
{ | |
/// <summary> | |
/// Reserved to be used by the compiler for tracking metadata. | |
/// This class should not be used by developers in source code. | |
/// </summary> | |
[EditorBrowsable(EditorBrowsableState.Never)] | |
internal static class IsExternalInit | |
{ | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace DocumentManagement.Core.Services | |
{ | |
public interface ISystemClock | |
{ | |
DateTime UtcNow { get; } | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using DocumentManagement.Core.Models; | |
using MediatR; | |
namespace DocumentManagement.Core.Events | |
{ | |
/// <summary> | |
/// Published when a new document was uploaded into the system. | |
/// </summary> | |
public record NewDocumentReceived(Document Document) : INotification; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using DocumentManagement.Core.Services; | |
using DocumentManagement.Persistence.HostedServices; | |
using DocumentManagement.Persistence.Services; | |
using Microsoft.EntityFrameworkCore; | |
using Microsoft.Extensions.DependencyInjection; | |
namespace DocumentManagement.Persistence.Extensions | |
{ | |
public static class ServiceCollectionExtensions | |
{ | |
public static IServiceCollection AddDomainPersistence(this IServiceCollection services, string connectionString) | |
{ | |
var migrationsAssemblyName = typeof(SqliteDocumentDbContextFactory).Assembly.GetName().Name; | |
return services | |
.AddPooledDbContextFactory<DocumentDbContext>(x => x.UseSqlite(connectionString, db => db.MigrationsAssembly(migrationsAssemblyName))) | |
.AddSingleton<IDocumentStore, EFCoreDocumentStore>() | |
.AddSingleton<IDocumentTypeStore, EFCoreDocumentTypeStore>() | |
.AddHostedService<RunMigrations>(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Threading; | |
using System.Threading.Tasks; | |
using Microsoft.EntityFrameworkCore; | |
using Microsoft.Extensions.Hosting; | |
namespace DocumentManagement.Persistence.HostedServices | |
{ | |
/// <summary> | |
/// Executes EF Core migrations. | |
/// </summary> | |
public class RunMigrations : IHostedService | |
{ | |
private readonly IDbContextFactory<DocumentDbContext> _dbContextFactory; | |
public RunMigrations(IDbContextFactory<DocumentDbContext> dbContextFactoryFactory) | |
{ | |
_dbContextFactory = dbContextFactoryFactory; | |
} | |
public async Task StartAsync(CancellationToken cancellationToken) | |
{ | |
await using var dbContext = _dbContextFactory.CreateDbContext(); | |
await dbContext.Database.MigrateAsync(cancellationToken); | |
await dbContext.DisposeAsync(); | |
} | |
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.EntityFrameworkCore; | |
using Microsoft.EntityFrameworkCore.Design; | |
namespace DocumentManagement.Persistence | |
{ | |
public class SqliteDocumentDbContextFactory : IDesignTimeDbContextFactory<DocumentDbContext> | |
{ | |
public DocumentDbContext CreateDbContext(string[] args) | |
{ | |
var builder = new DbContextOptionsBuilder<DocumentDbContext>(); | |
var connectionString = "Data Source=elsa.db;Cache=Shared"; | |
builder.UseSqlite(connectionString); | |
return new DocumentDbContext(builder.Options); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace DocumentManagement.Core.Services | |
{ | |
public class SystemClock : ISystemClock | |
{ | |
public DateTime UtcNow => DateTime.UtcNow; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment