diff --git a/Elsa.sln b/Elsa.sln
index 186bbe3e9..76e444749 100644
--- a/Elsa.sln
+++ b/Elsa.sln
@@ -277,6 +277,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Quartz.EntityFramework
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Quartz.EntityFrameworkCore.PostgreSql", "src\modules\Elsa.Quartz.EntityFrameworkCore.PostgreSql\Elsa.Quartz.EntityFrameworkCore.PostgreSql.csproj", "{CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.FileStorage", "src\modules\Elsa.FileStorage\Elsa.FileStorage.csproj", "{732BF088-6AD7-4C4D-9A48-8074253596D4}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "storage", "storage", "{B818988E-639C-4E6E-85C1-B231BCAD9DAB}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -695,6 +699,10 @@ Global
{CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {732BF088-6AD7-4C4D-9A48-8074253596D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {732BF088-6AD7-4C4D-9A48-8074253596D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {732BF088-6AD7-4C4D-9A48-8074253596D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {732BF088-6AD7-4C4D-9A48-8074253596D4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -822,6 +830,8 @@ Global
{05F3B23E-CF28-467B-AD3C-595EA4ED6B96} = {AB797AF0-C12C-46DE-A157-7E25625C6200}
{74ACD9D0-8B4C-42FA-A582-E93BF0075023} = {AB797AF0-C12C-46DE-A157-7E25625C6200}
{CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D} = {AB797AF0-C12C-46DE-A157-7E25625C6200}
+ {B818988E-639C-4E6E-85C1-B231BCAD9DAB} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
+ {732BF088-6AD7-4C4D-9A48-8074253596D4} = {B818988E-639C-4E6E-85C1-B231BCAD9DAB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E}
diff --git a/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj b/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj
index fd10cdc39..b6a957b6a 100644
--- a/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj
+++ b/src/bundles/Elsa.WorkflowServer.Web/Elsa.WorkflowServer.Web.csproj
@@ -15,6 +15,7 @@
+
@@ -39,6 +40,7 @@
+
diff --git a/src/bundles/Elsa.WorkflowServer.Web/Program.cs b/src/bundles/Elsa.WorkflowServer.Web/Program.cs
index dc2a02c63..bf8c8d8f8 100644
--- a/src/bundles/Elsa.WorkflowServer.Web/Program.cs
+++ b/src/bundles/Elsa.WorkflowServer.Web/Program.cs
@@ -1,4 +1,5 @@
using System.Text.Encodings.Web;
+using Elastic.Clients.Elasticsearch.IndexManagement;
using Elsa.Alterations.Extensions;
using Elsa.Alterations.MassTransit.Extensions;
using Elsa.Dapper.Extensions;
@@ -17,6 +18,7 @@ using Elsa.MongoDb.Modules.Identity;
using Elsa.MongoDb.Modules.Management;
using Elsa.MongoDb.Modules.Runtime;
using Elsa.WorkflowServer.Web.WorkflowContexts;
+using FluentStorage;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Options;
using Proto.Persistence.Sqlite;
@@ -65,6 +67,8 @@ services
.AddActivitiesFrom()
.AddWorkflowsFrom()
.UseFluentStorageProvider()
+ .UseFileStorage()
+ // .UseFileStorage(sp => StorageFactory.Blobs.AzureBlobStorageWithSas(configuration.GetConnectionString("AzureStorageSasUrl")))
.UseIdentity(identity =>
{
if (useMongoDb)
diff --git a/src/bundles/Elsa.WorkflowServer.Web/appsettings.json b/src/bundles/Elsa.WorkflowServer.Web/appsettings.json
index 294b390fb..b4853ecc6 100644
--- a/src/bundles/Elsa.WorkflowServer.Web/appsettings.json
+++ b/src/bundles/Elsa.WorkflowServer.Web/appsettings.json
@@ -96,8 +96,5 @@
"SweepInterval": "00:00:10:00",
"BatchSize": 1000
}
- },
- "Quartz": {
-
}
}
\ No newline at end of file
diff --git a/src/modules/Elsa.FileStorage/Activities/OpenFile.cs b/src/modules/Elsa.FileStorage/Activities/OpenFile.cs
new file mode 100644
index 000000000..e2f079390
--- /dev/null
+++ b/src/modules/Elsa.FileStorage/Activities/OpenFile.cs
@@ -0,0 +1,31 @@
+using Elsa.Extensions;
+using Elsa.Workflows.Core;
+using Elsa.Workflows.Core.Attributes;
+using Elsa.Workflows.Core.Models;
+
+namespace Elsa.FileStorage.Activities;
+
+///
+/// Save a file to the configured storage provider.
+///
+[Activity("Elsa", "Storage", "Open a file from the configured storage provider.", Kind = ActivityKind.Task)]
+public class OpenFile : CodeActivity
+{
+ ///
+ /// Gets or sets the path to save the file to.
+ ///
+ [Input(Description = "The path to the file to open.")]
+ public Input Path { get; set; } = default!;
+
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ var cancellationToken = context.CancellationToken;
+ var path = Path.Get(context);
+ var blobStorageProvider = context.GetRequiredService();
+ var blobStorage = blobStorageProvider.GetBlobStorage();
+ var data = await blobStorage.OpenReadAsync(path, cancellationToken);
+
+ Result.Set(context, data);
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.FileStorage/Activities/SaveFile.cs b/src/modules/Elsa.FileStorage/Activities/SaveFile.cs
new file mode 100644
index 000000000..951a8fa02
--- /dev/null
+++ b/src/modules/Elsa.FileStorage/Activities/SaveFile.cs
@@ -0,0 +1,92 @@
+using System.Collections;
+using System.IO.Compression;
+using System.Text;
+using Elsa.Extensions;
+using Elsa.Workflows.Core;
+using Elsa.Workflows.Core.Attributes;
+using Elsa.Workflows.Core.Models;
+using Microsoft.AspNetCore.Http;
+
+namespace Elsa.FileStorage.Activities;
+
+///
+/// Save a file to the configured storage provider.
+///
+[Activity("Elsa", "Storage", "Save a file to the configured storage provider.", Kind = ActivityKind.Task)]
+public class SaveFile : CodeActivity
+{
+ ///
+ /// Gets or sets the file data to save.
+ ///
+ [Input(Description = "The file data to save. This can be a stream, binary data, a string, a form file or a collection of files.")]
+ public Input