Last active
August 4, 2021 18:30
-
-
Save sfmskywalker/cc4a4bbca1e805a5c43afc2af21b6af1 to your computer and use it in GitHub Desktop.
Building Workflow Driven .NET Applications With Elsa 2 - Part 6
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.Tasks; | |
using DocumentManagement.Core.Models; | |
using DocumentManagement.Core.Services; | |
using Elsa.ActivityResults; | |
using Elsa.Attributes; | |
using Elsa.Expressions; | |
using Elsa.Providers.WorkflowStorage; | |
using Elsa.Services; | |
using Elsa.Services.Models; | |
namespace DocumentManagement.Workflows.Activities | |
{ | |
[Activity(Category = "Document Management", Description = "Archives the specified document.")] | |
public class ArchiveDocument : Activity | |
{ | |
private readonly IDocumentStore _documentStore; | |
public ArchiveDocument(IDocumentStore documentStore, IFileStorage fileStorage) | |
{ | |
_documentStore = documentStore; | |
} | |
[ActivityInput( | |
Label = "Document", | |
Hint = "The document to archive", | |
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}, | |
DefaultWorkflowStorageProvider = TransientWorkflowStorageProvider.ProviderName | |
)] | |
public Document Document { get; set; } = default!; | |
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context) | |
{ | |
Document.Status = DocumentStatus.Archived; | |
await _documentStore.SaveAsync(Document); | |
return Done(); | |
} | |
} | |
} |
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.Workflows.Activities; | |
using Elsa.Scripting.Liquid.Messages; | |
using Fluid; | |
using MediatR; | |
namespace DocumentManagement.Workflows.Scripting.Liquid | |
{ | |
public class ConfigureLiquidEngine : INotificationHandler<EvaluatingLiquidExpression> | |
{ | |
public Task Handle(EvaluatingLiquidExpression notification, CancellationToken cancellationToken) | |
{ | |
var memberAccessStrategy = notification.TemplateContext.Options.MemberAccessStrategy; | |
memberAccessStrategy.Register<Document>(); | |
memberAccessStrategy.Register<DocumentFile>(); | |
return 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 System.IO; | |
using System.Threading.Tasks; | |
using DocumentManagement.Core.Models; | |
using DocumentManagement.Core.Services; | |
using Elsa.ActivityResults; | |
using Elsa.Attributes; | |
using Elsa.Expressions; | |
using Elsa.Providers.WorkflowStorage; | |
using Elsa.Services; | |
using Elsa.Services.Models; | |
namespace DocumentManagement.Workflows.Activities | |
{ | |
public record DocumentFile(Document Document, Stream FileStream); | |
[Action(Category = "Document Management", Description = "Gets the specified document from the database.")] | |
public class GetDocument : Activity | |
{ | |
private readonly IDocumentStore _documentStore; | |
private readonly IFileStorage _fileStorage; | |
public GetDocument(IDocumentStore documentStore, IFileStorage fileStorage) | |
{ | |
_documentStore = documentStore; | |
_fileStorage = fileStorage; | |
} | |
[ActivityInput( | |
Label = "Document ID", | |
Hint = "The ID of the document to load", | |
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid} | |
)] | |
public string DocumentId { get; set; } = default!; | |
[ActivityOutput( | |
Hint = "The document + file.", | |
DefaultWorkflowStorageProvider = TransientWorkflowStorageProvider.ProviderName, | |
DisableWorkflowProviderSelection = true)] | |
public DocumentFile Output { get; set; } = default!; | |
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context) | |
{ | |
var document = await _documentStore.GetAsync(DocumentId, context.CancellationToken); | |
var fileStream = await _fileStorage.ReadAsync(document!.FileName, context.CancellationToken); | |
Output = new DocumentFile(document, fileStream); | |
return Done(); | |
} | |
} | |
} |
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.Security.Cryptography; | |
using System.Threading.Tasks; | |
using Elsa; | |
using Elsa.ActivityResults; | |
using Elsa.Attributes; | |
using Elsa.Expressions; | |
using Elsa.Providers.WorkflowStorage; | |
using Elsa.Services; | |
using Elsa.Services.Models; | |
namespace DocumentManagement.Workflows.Activities | |
{ | |
[Activity( | |
Category = "Document Management", | |
Description = "Saves a hash of the specified file onto the blockchain to prevent tampering." | |
)] | |
public class UpdateBlockchain : Activity | |
{ | |
[ActivityInput( | |
Label = "File", | |
Hint = "The file to store its hash of onto the blockchain. Can be byte[] or Stream.", | |
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}, | |
DefaultWorkflowStorageProvider = TransientWorkflowStorageProvider.ProviderName | |
)] | |
public object File { get; set; } = default!; | |
[ActivityOutput(Hint = "The computed file signature as stored on the blockchain.")] | |
public string Output { get; set; } = default!; | |
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context) | |
{ | |
// Determine the type of File object: is it a Stream or a byte array? | |
var bytes = File is Stream stream | |
? await stream.ReadBytesToEndAsync() | |
: File is byte[] buffer | |
? buffer | |
: throw new NotSupportedException(); | |
// Compute hash. | |
var fileSignature = ComputeSignature(bytes); | |
// TODO: Schedule background work using Hangfire that will fake-update an imaginary blockchain with the file signature. | |
// Suspend the workflow. | |
return Suspend(); | |
} | |
protected override IActivityExecutionResult OnResume(ActivityExecutionContext context) | |
{ | |
// When we resume, read the simply complete this activity. | |
// TODO: Receive the computed file signature and set it as Output. | |
return Done(); | |
} | |
private static string ComputeSignature(byte[] bytes) | |
{ | |
using var algorithm = SHA256.Create(); | |
var hashValue = algorithm.ComputeHash(bytes); | |
return Convert.ToBase64String(hashValue); | |
} | |
} | |
} |
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.Security.Cryptography; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Elsa; | |
using Elsa.ActivityResults; | |
using Elsa.Attributes; | |
using Elsa.Expressions; | |
using Elsa.Models; | |
using Elsa.Providers.WorkflowStorage; | |
using Elsa.Services; | |
using Elsa.Services.Models; | |
using Hangfire; | |
namespace DocumentManagement.Workflows.Activities | |
{ | |
[Activity( | |
Category = "Document Management", | |
Description = "Saves a hash of the specified file onto the blockchain to prevent tampering." | |
)] | |
public class UpdateBlockchain : Activity | |
{ | |
public record UpdateBlockchainContext(string WorkflowInstanceId, string ActivityId, string FileSignature); | |
private readonly IBackgroundJobClient _backgroundJobClient; | |
private readonly IWorkflowInstanceDispatcher _workflowInstanceDispatcher; | |
public UpdateBlockchain(IBackgroundJobClient backgroundJobClient, IWorkflowInstanceDispatcher workflowInstanceDispatcher) | |
{ | |
_backgroundJobClient = backgroundJobClient; | |
_workflowInstanceDispatcher = workflowInstanceDispatcher; | |
} | |
[ActivityInput( | |
Label = "File", | |
Hint = "The file to store its hash of onto the blockchain. Can be byte[] or Stream.", | |
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}, | |
DefaultWorkflowStorageProvider = TransientWorkflowStorageProvider.ProviderName | |
)] | |
public object File { get; set; } = default!; | |
[ActivityOutput(Hint = "The computed file signature as stored on the blockchain.")] | |
public string Output { get; set; } = default!; | |
/// <summary> | |
/// Invoked by Hangfire as a background job. | |
/// </summary> | |
public async Task SubmitToBlockChainAsync(UpdateBlockchainContext context, CancellationToken cancellationToken) | |
{ | |
// Simulate storing it on an imaginary blockchain out there. | |
await Task.Delay(TimeSpan.FromSeconds(15), cancellationToken); | |
// Resume the suspended workflow. | |
await _workflowInstanceDispatcher.DispatchAsync(new ExecuteWorkflowInstanceRequest(context.WorkflowInstanceId, context.ActivityId, new WorkflowInput(context.FileSignature)), cancellationToken); | |
} | |
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context) | |
{ | |
// Compute hash. | |
var bytes = File is Stream stream ? await stream.ReadBytesToEndAsync() : File is byte[] buffer ? buffer : throw new NotSupportedException(); | |
var fileSignature = ComputeSignature(bytes); | |
// Schedule background work using Hangfire. | |
_backgroundJobClient.Enqueue(() => SubmitToBlockChainAsync(new UpdateBlockchainContext(context.WorkflowInstance.Id, context.ActivityId, fileSignature), CancellationToken.None)); | |
// Suspend the workflow. | |
return Suspend(); | |
} | |
protected override IActivityExecutionResult OnResume(ActivityExecutionContext context) | |
{ | |
// When we resume, simply complete this activity. | |
var fileSignature = context.GetInput<string>(); | |
Output = fileSignature; | |
return Done(); | |
} | |
private static string ComputeSignature(byte[] bytes) | |
{ | |
using var algorithm = SHA256.Create(); | |
var hashValue = algorithm.ComputeHash(bytes); | |
return Convert.ToBase64String(hashValue); | |
} | |
} | |
} |
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.IO.Compression; | |
using System.Threading.Tasks; | |
using Elsa.ActivityResults; | |
using Elsa.Attributes; | |
using Elsa.Expressions; | |
using Elsa.Providers.WorkflowStorage; | |
using Elsa.Services; | |
using Elsa.Services.Models; | |
namespace DocumentManagement.Workflows.Activities | |
{ | |
[Action(Category = "Document Management", Description = "Zips the specified file.")] | |
public class ZipFile : Activity | |
{ | |
[ActivityInput( | |
Hint = "The file stream to zip.", | |
SupportedSyntaxes = new[] {SyntaxNames.JavaScript}, | |
DefaultWorkflowStorageProvider = TransientWorkflowStorageProvider.ProviderName, | |
DisableWorkflowProviderSelection = true | |
)] | |
public Stream Stream { get; set; } = default!; | |
[ActivityInput( | |
Hint = "The file name to use for the zip entry.", | |
SupportedSyntaxes = new[] {SyntaxNames.JavaScript} | |
)] | |
public string FileName { get; set; } = default!; | |
[ActivityOutput( | |
Hint = "The zipped file stream.", | |
DefaultWorkflowStorageProvider = TransientWorkflowStorageProvider.ProviderName, | |
DisableWorkflowProviderSelection = true | |
)] | |
public Stream Output { get; set; } = default!; | |
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context) | |
{ | |
var outputStream = new MemoryStream(); | |
using (var zipArchive = new ZipArchive(outputStream, ZipArchiveMode.Create, true)) | |
{ | |
var zipEntry = zipArchive.CreateEntry(FileName, CompressionLevel.Optimal); | |
await using var zipStream = zipEntry.Open(); | |
await Stream.CopyToAsync(zipStream); | |
} | |
// Reset position. | |
outputStream.Seek(0, SeekOrigin.Begin); | |
Output = outputStream; | |
return Done(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment