Geek Culture
Published in

Geek Culture

Part 4 of Building Workflow Driven .NET Applications with Elsa 2

Setting up Persistence & File Uploads

  • Implement the Document domain model.
  • Implement DB persistence.
  • Implement local file storage using Storage.NET

File Upload Screen

To implement the File Upload functionality, we will be reusing the home page (implemented in Index.cshtml and Index.cshtml.cs)

Index.cshtml

Let’s start by replacing the entire contents of Index.cshtml with the following:

Index.cshtml

Index.cshtml.cs

Open Index.cshtml.cs and replace its contents in one fell swoop with the following:

Index.cshtml.cs
  • On GET, we select a list of available document types provided by IDocumentTypeStore, which as an abstraction for data access not yet implemented.
  • On POST, we open a stream to the uploaded file and invoke SaveDocumentAsync on the IDocumentServiceservice.
  • We then redirect to a page called "FileReceived" .

FileReceived.cshtml

This page displays a simple message to the user that the document was received successfully and displays the generated document ID.

Domain Models

The domain models and services will mostly be provided and implemented by the DocumentManagement.Core project. I say “mostly”, because some abstractions, although provided by the core project, will be implemented in DocumentManagement.Persistence, as we will see.

Document

Create a new folder called Models and create a new class called Document as follows:

DocumentStatus

In the same folder, create an enum called DocumentStatus, as follows:

DocumentType

In the same folder still, create the following class:

Domain Services

The domain services will provide abstractions and some concrete implementations to deal with persisting documents and storing files. Some of these abstractions are implemented in the Core project, whilst others are implemented elsewhere, such as the implementation for IDocumentStore.

IDocumentStore

Create a new folder called Services and add the following C# interface:

  • Load Document entities from the database by ID.

IDocumentTypeStore

Create the IDocumentTypeStore interface with the following code:

  • Get a single document type by ID

ISystemClock & SystemClock

This interface abstracts access to DateTime.UtcNow, which is considered an external resource to the domain, which means we should abstract it away. This is helpful also when one wants to write some unit tests, allowing us to provide a mock implementation of the interface.

IFileStorage & FileStorage

The file storage service is responsible for storing files somewhere. The implementation will rely on Storage.NET abstractions so that our domain logic does not depend on concrete implementations directly. Instead, it will be up to our host application to configure file access, as it should be.

dotnet add DocumentManagement/src/DocumentManagement.Core/DocumentManagement.Core.csproj package Storage.Net
dotnet add DocumentManagement/src/DocumentManagement.Core/DocumentManagement.Core.csproj package Microsoft.Extensions.Options

DocumentStorageOptions

Create a new folder called Options and add the following class:

IDocumentService & DocumentService

The document service has just two responsibilities:

  • Publish a domain event called NewDocumentReceived
dotnet add DocumentManagement/src/DocumentManagement.Core/DocumentManagement.Core.csproj package MediatR

NewDocumentReceived

The “new document received” event is published by the document service every time a new document is created.

IsExternalInit

Since we are using a C# 9 feature called “records” in a .NET Standard project, you will probably see a compiler error along the lines of:

Registering Domain Services

Before we move on to building out the persistence layer, let’s first create an extension method to facilitate the registration of our domain services with the service container.

Persistence

We will implement the persistence layer in the DocumentManagement.Persistence project.

  • Define a design-time DB context factory class (for generating migrations).
  • Generate migrations.
  • Implement IDocumentStore and IDocumentTypeStore
  • Implement a hosted service to auto-run migrations at application startup.
  • Provide an extension method to register EF Core & domain services with the service container.
  • Microsoft.EntityFrameworkCore.Sqlite
  • Microsoft.Extensions.Hosting.Abstractions
dotnet add DocumentManagement/src/DocumentManagement.Persistence/DocumentManagement.Persistence.csproj package Microsoft.EntityFrameworkCore.Designdotnet add DocumentManagement/src/DocumentManagement.Persistence/DocumentManagement.Persistence.csproj package Microsoft.EntityFrameworkCore.Sqlitedotnet add DocumentManagement/src/DocumentManagement.Persistence/DocumentManagement.Persistence.csproj package Microsoft.Extensions.Hosting.Abstractions
dotnet add DocumentManagement/src/DocumentManagement.Persistence/DocumentManagement.Persistence.csproj reference DocumentManagement/src/DocumentManagement.Core/DocumentManagement.Core.csproj
  • “LeaveRequest”
  • “IdentityVerification”

Document DB Context Design Factory

In order to let the dotnet ef tool generate migrations, we should implement a DB context factory class that the tool can use to instantiate new instances of the DB context.

Generate Migrations

Now that we have the DB context and factory in place, let’s generate the migrations by executing the following command (make sure to run the command from the same directory as where the project resides):

dotnet ef migrations add Initial

Implementing the Stores (aka Repositories)

Let’s implement the IDocumentStore and IDocumentTypeStore interfaces next.

Registering Persistence Services

Let’s now create an extension method to facilitate the registration of our persistence services with the service container.

Startup

With all the work we’ve done, all that’s left now is to invoke the two extension methods that register the domain & persistence services and to configure the file storage options introduced earlier.

private void AddDomainServices(IServiceCollection services)
{
services.AddDomainServices();

// Configure Storage for DocumentStorage.
services.Configure<DocumentStorageOptions>(options => options.BlobStorageFactory = () => StorageFactory.Blobs.DirectoryFiles(Path.Combine(Environment.ContentRootPath, "App_Data/Uploads")));
}
private void AddPersistenceServices(IServiceCollection services, string dbConnectionString)
{
services.AddDomainPersistence(dbConnectionString);
}
// Domain services.
AddDomainServices(services);
// Persistence.
AddPersistenceServices(services, dbConnectionString);

Try It Out

At this point, we should be able to run the application, upload a file, and confirm that the file was saved and a document record was created, as shown in the following screen recording.

Next

This part didn’t necessarily introduce anything new related to Elsa, but we did setup a pretty decent base onto which we can now start hooking in custom logic to process uploaded documents. This was made easy by publishing the “new document received” domain event.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store