diff --git a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequest.cs b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequest.cs index 3d99da5f7..a0827dab6 100644 --- a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequest.cs +++ b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequest.cs @@ -102,7 +102,8 @@ namespace Elsa.Activities.Http )] public ICollection? SupportedStatusCodes { get; set; } = new HashSet(new[] { 200 }); - [ActivityOutput] public HttpResponseModel? Output { get; set; } + [ActivityOutput] public HttpResponseModel? Response { get; set; } + [ActivityOutput] public object? ResponseContent { get; set; } protected override async ValueTask OnExecuteAsync(ActivityExecutionContext context) { @@ -123,7 +124,7 @@ namespace Elsa.Activities.Http if (hasContent && ReadContent) { var formatter = SelectContentParser(contentType); - responseModel.Content = await formatter.ReadAsync(response, cancellationToken); + ResponseContent = await formatter.ReadAsync(response, cancellationToken); } var statusCode = (int) response.StatusCode; @@ -135,7 +136,7 @@ namespace Elsa.Activities.Http if (!isSupportedStatusCode) outcomes.Add("Unsupported Status Code"); - Output = responseModel; + Response = responseModel; return Outcomes(outcomes); } diff --git a/src/activities/Elsa.Activities.Http/Models/HttpResponseModel.cs b/src/activities/Elsa.Activities.Http/Models/HttpResponseModel.cs index 823545a77..5ab5ee760 100644 --- a/src/activities/Elsa.Activities.Http/Models/HttpResponseModel.cs +++ b/src/activities/Elsa.Activities.Http/Models/HttpResponseModel.cs @@ -7,6 +7,5 @@ namespace Elsa.Activities.Http.Models { public HttpStatusCode StatusCode { get; set; } public Dictionary Headers { get; set; } = new(); - public object? Content { get; set; } } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs index 45e76d031..06cde782c 100644 --- a/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs +++ b/src/core/Elsa.Core/Extensions/ElsaServiceCollectionExtensions.cs @@ -196,14 +196,15 @@ namespace Microsoft.Extensions.DependencyInjection // Workflow providers. services .AddWorkflowProvider() - .AddWorkflowProvider() + .AddWorkflowProvider() .AddWorkflowProvider(); // Workflow Storage Providers. services .AddSingleton() .AddWorkflowStorageProvider() - .AddWorkflowStorageProvider(); + .AddWorkflowStorageProvider() + .AddWorkflowStorageProvider(); // Metadata. services diff --git a/src/core/Elsa.Core/Extensions/SetupActivityExtensions.cs b/src/core/Elsa.Core/Extensions/SetupActivityExtensions.cs index c9c802698..9c4bc5cec 100644 --- a/src/core/Elsa.Core/Extensions/SetupActivityExtensions.cs +++ b/src/core/Elsa.Core/Extensions/SetupActivityExtensions.cs @@ -10,5 +10,6 @@ namespace Elsa { public static ISetupActivity WithTransientStorageFor(this ISetupActivity builder, Expression> propertyAccessor) where T : IActivity => builder.WithStorageFor(propertyAccessor, TransientWorkflowStorageProvider.ProviderName); public static ISetupActivity WithWorkflowInstanceStorageFor(this ISetupActivity builder, Expression> propertyAccessor) where T : IActivity => builder.WithStorageFor(propertyAccessor, WorkflowInstanceWorkflowStorageProvider.ProviderName); + public static ISetupActivity WithBlobStorageFor(this ISetupActivity builder, Expression> propertyAccessor) where T : IActivity => builder.WithStorageFor(propertyAccessor, BlobStorageWorkflowStorageProvider.ProviderName); } } \ No newline at end of file diff --git a/src/core/Elsa.Core/Providers/WorkflowStorage/BlobStorageWorkflowStorageProvider.cs b/src/core/Elsa.Core/Providers/WorkflowStorage/BlobStorageWorkflowStorageProvider.cs new file mode 100644 index 000000000..0186f707a --- /dev/null +++ b/src/core/Elsa.Core/Providers/WorkflowStorage/BlobStorageWorkflowStorageProvider.cs @@ -0,0 +1,96 @@ +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Serialization; +using Newtonsoft.Json; +using Storage.Net.Blobs; + +namespace Elsa.Providers.WorkflowStorage +{ + public class BlobStorageWorkflowStorageProvider : WorkflowStorageProvider + { + public const string ProviderName = "BlobStorage"; + + private readonly IBlobStorage _blobStorage; + private readonly JsonSerializerSettings _serializerSettings; + + public BlobStorageWorkflowStorageProvider(IBlobStorage blobStorage) + { + _blobStorage = blobStorage; + _serializerSettings = DefaultContentSerializer.CreateDefaultJsonSerializationSettings(); + _serializerSettings.TypeNameHandling = TypeNameHandling.All; + } + + public override string DisplayName => "Blob Storage"; + + public override async ValueTask SaveAsync(WorkflowStorageContext context, string key, object? value, CancellationToken cancellationToken = default) + { + if (value == null) + return; + + var path = GetFullPath(context, key); + + if(value is Stream stream) + { + await _blobStorage.WriteAsync(path, stream, cancellationToken: cancellationToken); + var blob = await _blobStorage.GetBlobAsync(path, cancellationToken); + blob.Metadata["ContentType"] = "Binary"; + } + else if (value is byte[] bytes) + { + await _blobStorage.WriteAsync(path, bytes, cancellationToken: cancellationToken); + var blob = await _blobStorage.GetBlobAsync(path, cancellationToken); + blob.Metadata["ContentType"] = "Binary"; + } + else + { + var json = JsonConvert.SerializeObject(value, _serializerSettings); + var jsonBytes = Encoding.UTF8.GetBytes(json); + await _blobStorage.WriteAsync(path, jsonBytes, cancellationToken: cancellationToken); + var blob = await _blobStorage.GetBlobAsync(path, cancellationToken); + blob.Metadata["ContentType"] = "Json"; + } + } + + public override async ValueTask LoadAsync(WorkflowStorageContext context, string key, CancellationToken cancellationToken = default) + { + var path = GetFullPath(context, key); + + if (!await _blobStorage.ExistsAsync(path, cancellationToken)) + return null; + + var blob = await _blobStorage.GetBlobAsync(path, cancellationToken); + var contentType = blob.Metadata.GetItem("ContentType") ?? "Json"; + + if (contentType == "Json") + { + var json = await _blobStorage.ReadTextAsync(path, cancellationToken: cancellationToken); + return JsonConvert.DeserializeObject(json, _serializerSettings); + } + + return await _blobStorage.ReadBytesAsync(path, cancellationToken); + } + + public override async ValueTask DeleteAsync(WorkflowStorageContext context, string key, CancellationToken cancellationToken = default) + { + var path = GetFullPath(context, key); + await _blobStorage.DeleteAsync(path, cancellationToken); + } + + public override async ValueTask DeleteAsync(WorkflowStorageContext context, CancellationToken cancellationToken = default) + { + var path = GetContainerPath(context); + await _blobStorage.DeleteAsync(path, cancellationToken); + } + + private string GetFullPath(WorkflowStorageContext context, string key) + { + var containerPath = GetContainerPath(context); + var activityId = context.ActivityId; + return $"${containerPath}/{activityId}/{key}.dat"; + } + + private string GetContainerPath(WorkflowStorageContext context) => context.WorkflowInstance.Id; + } +} \ No newline at end of file diff --git a/src/core/Elsa.Core/Providers/WorkflowStorage/WorkflowInstanceWorkflowStorageProvider.cs b/src/core/Elsa.Core/Providers/WorkflowStorage/WorkflowInstanceWorkflowStorageProvider.cs index 2c29f94de..da7fc31f0 100644 --- a/src/core/Elsa.Core/Providers/WorkflowStorage/WorkflowInstanceWorkflowStorageProvider.cs +++ b/src/core/Elsa.Core/Providers/WorkflowStorage/WorkflowInstanceWorkflowStorageProvider.cs @@ -38,11 +38,7 @@ namespace Elsa.Providers.WorkflowStorage return new ValueTask(); } - private IDictionary GetData(WorkflowStorageContext context) - { - return context.WorkflowInstance.ActivityData.GetItem(context.ActivityId, () => new Dictionary()); - } - + private IDictionary GetData(WorkflowStorageContext context) => context.WorkflowInstance.ActivityData.GetItem(context.ActivityId, () => new Dictionary()); private void SetState(WorkflowStorageContext context, string propertyName, object? value) => GetData(context)!.SetState(propertyName, value); public object? GetState(WorkflowStorageContext context, string propertyName) => GetData(context)!.GetState(propertyName); } diff --git a/src/core/Elsa.Core/Providers/Workflows/StorageWorkflowProvider.cs b/src/core/Elsa.Core/Providers/Workflows/BlobStorageWorkflowProvider.cs similarity index 88% rename from src/core/Elsa.Core/Providers/Workflows/StorageWorkflowProvider.cs rename to src/core/Elsa.Core/Providers/Workflows/BlobStorageWorkflowProvider.cs index e2066e072..5f97c4304 100644 --- a/src/core/Elsa.Core/Providers/Workflows/StorageWorkflowProvider.cs +++ b/src/core/Elsa.Core/Providers/Workflows/BlobStorageWorkflowProvider.cs @@ -13,14 +13,14 @@ using Storage.Net.Blobs; namespace Elsa.Providers.Workflows { - public class StorageWorkflowProvider : WorkflowProvider + public class BlobStorageWorkflowProvider : WorkflowProvider { private readonly IBlobStorage _storage; private readonly IWorkflowBlueprintMaterializer _workflowBlueprintMaterializer; private readonly IContentSerializer _contentSerializer; private readonly ILogger _logger; - public StorageWorkflowProvider(IBlobStorage storage, IWorkflowBlueprintMaterializer workflowBlueprintMaterializer, IContentSerializer contentSerializer, ILogger logger) + public BlobStorageWorkflowProvider(IBlobStorage storage, IWorkflowBlueprintMaterializer workflowBlueprintMaterializer, IContentSerializer contentSerializer, ILogger logger) { _storage = storage; _workflowBlueprintMaterializer = workflowBlueprintMaterializer;