From b19b197484a6117bf9f05de7b3b15d5a772f666a Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 12 Oct 2023 14:20:15 +0200 Subject: [PATCH 1/4] Add Soap+XML content type support --- .../Activities/SendHttpRequestBase.cs | 25 +++++++++----- .../Elsa.Http/Activities/WriteHttpResponse.cs | 9 ++--- .../HttpContentTypeOptionsProvider.cs | 34 +++++++++++++++++++ .../ContentWriters/BinaryContentFactory.cs | 23 +++++++++++++ .../FormUrlEncodedHttpContentFactory.cs | 4 +-- .../ContentWriters/IHttpContentFactory.cs | 2 +- .../ContentWriters/JsonContentFactory.cs | 4 +-- .../ContentWriters/TextContentFactory.cs | 6 ++-- .../ContentWriters/XmlContentFactory.cs | 13 ++++--- src/modules/Elsa.Http/Features/HttpFeature.cs | 4 +-- ...eHttpResponseContentTypeOptionsProvider.cs | 26 -------------- 11 files changed, 93 insertions(+), 57 deletions(-) create mode 100644 src/modules/Elsa.Http/ActivityOptionProviders/HttpContentTypeOptionsProvider.cs create mode 100644 src/modules/Elsa.Http/ContentWriters/BinaryContentFactory.cs delete mode 100644 src/modules/Elsa.Http/Providers/WriteHttpResponseContentTypeOptionsProvider.cs diff --git a/src/modules/Elsa.Http/Activities/SendHttpRequestBase.cs b/src/modules/Elsa.Http/Activities/SendHttpRequestBase.cs index ad7fc90d0..5100fcf1e 100644 --- a/src/modules/Elsa.Http/Activities/SendHttpRequestBase.cs +++ b/src/modules/Elsa.Http/Activities/SendHttpRequestBase.cs @@ -1,5 +1,6 @@ using System.Net.Http.Headers; using Elsa.Extensions; +using Elsa.Http.ActivityOptionProviders; using Elsa.Http.ContentWriters; using Elsa.Workflows.Core; using Elsa.Workflows.Core.Attributes; @@ -17,7 +18,7 @@ public abstract class SendHttpRequestBase : Activity protected SendHttpRequestBase(string? source = default, int? line = default) : base(source, line) { } - + /// /// The URL to send the request to. /// @@ -46,7 +47,7 @@ public abstract class SendHttpRequestBase : Activity /// [Input( Description = "The content type to use when sending the request.", - Options = new[] { "", "text/plain", "text/html", "application/json", "application/xml", "application/x-www-form-urlencoded" }, + OptionsProvider = typeof(HttpContentTypeOptionsProvider), UIHint = InputUIHints.Dropdown )] public Input ContentType { get; set; } = default!; @@ -88,7 +89,7 @@ public abstract class SendHttpRequestBase : Activity /// Handles an exception that occurred while sending the request. /// protected abstract ValueTask HandleRequestExceptionAsync(ActivityExecutionContext context, HttpRequestException exception); - + /// /// Handles that occurred while sending the request. /// @@ -110,7 +111,7 @@ public abstract class SendHttpRequestBase : Activity await HandleResponseAsync(context, response); } - catch(HttpRequestException e) + catch (HttpRequestException e) { context.AddExecutionLogEntry("Error", e.Message, payload: new { StackTrace = e.StackTrace }); context.JournalData.Add("Error", e.Message); @@ -164,14 +165,20 @@ public abstract class SendHttpRequestBase : Activity if (contentType != null && content != null) { - var contentWriters = context.GetServices(); - var contentWriter = SelectContentWriter(contentType, contentWriters); - request.Content = contentWriter.CreateHttpContent(content, contentType); + var factories = context.GetServices(); + var factory = SelectContentWriter(contentType, factories); + request.Content = factory.CreateHttpContent(content, contentType); } return request; } - private IHttpContentFactory SelectContentWriter(string? contentType, IEnumerable requestContentWriters) => - string.IsNullOrWhiteSpace(contentType) ? new JsonContentFactory() : requestContentWriters.First(w => w.SupportsContentType(contentType)); + private IHttpContentFactory SelectContentWriter(string? contentType, IEnumerable factories) + { + if (string.IsNullOrWhiteSpace(contentType)) + return new JsonContentFactory(); + + var parsedContentType = new System.Net.Mime.ContentType(contentType); + return factories.FirstOrDefault(httpContentFactory => httpContentFactory.SupportedContentTypes.Any(c => c == parsedContentType.MediaType)) ?? new JsonContentFactory(); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Http/Activities/WriteHttpResponse.cs b/src/modules/Elsa.Http/Activities/WriteHttpResponse.cs index 246107371..8fade85d1 100644 --- a/src/modules/Elsa.Http/Activities/WriteHttpResponse.cs +++ b/src/modules/Elsa.Http/Activities/WriteHttpResponse.cs @@ -1,10 +1,10 @@ using System.Net; using System.Runtime.CompilerServices; using Elsa.Extensions; +using Elsa.Http.ActivityOptionProviders; using Elsa.Http.Bookmarks; using Elsa.Http.ContentWriters; using Elsa.Http.Models; -using Elsa.Http.Providers; using Elsa.Workflows.Core; using Elsa.Workflows.Core.Attributes; using Elsa.Workflows.Core.Exceptions; @@ -41,7 +41,7 @@ public class WriteHttpResponse : Activity /// [Input( Description = "The content type to write when sending the response.", - OptionsProvider = typeof(WriteHttpResponseContentTypeOptionsProvider), + OptionsProvider = typeof(HttpContentTypeOptionsProvider), UIHint = InputUIHints.Dropdown )] public Input ContentType { get; set; } = default!; @@ -106,8 +106,9 @@ public class WriteHttpResponse : Activity if (string.IsNullOrWhiteSpace(contentType)) contentType = DetermineContentType(content); - var contentWriter = context.GetServices().FirstOrDefault(x => x.SupportsContentType(contentType)) ?? new TextContentFactory(); - var httpContent = contentWriter.CreateHttpContent(content, contentType); + var factories = context.GetServices(); + var factory = factories.FirstOrDefault(httpContentFactory => httpContentFactory.SupportedContentTypes.Any(c => c == contentType)) ?? new TextContentFactory(); + var httpContent = factory.CreateHttpContent(content, contentType); // Set content type. response.ContentType = httpContent.Headers.ContentType?.ToString() ?? contentType; diff --git a/src/modules/Elsa.Http/ActivityOptionProviders/HttpContentTypeOptionsProvider.cs b/src/modules/Elsa.Http/ActivityOptionProviders/HttpContentTypeOptionsProvider.cs new file mode 100644 index 000000000..e6d6ef8eb --- /dev/null +++ b/src/modules/Elsa.Http/ActivityOptionProviders/HttpContentTypeOptionsProvider.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using Elsa.Http.ContentWriters; +using Elsa.Workflows.Core.Contracts; + +namespace Elsa.Http.ActivityOptionProviders; + +/// +/// Provides options for the activity's property. +/// +public class HttpContentTypeOptionsProvider : IActivityPropertyOptionsProvider +{ + private readonly IEnumerable _httpContentFactories; + + /// + /// Creates a new instance of the class. + /// + public HttpContentTypeOptionsProvider(IEnumerable httpContentFactories) + { + _httpContentFactories = httpContentFactories; + } + + /// + public ValueTask> GetOptionsAsync(PropertyInfo property, CancellationToken cancellationToken = default) + { + var contentTypes = _httpContentFactories.SelectMany(x => x.SupportedContentTypes).Distinct().OrderBy(x => x).ToArray(); + + var options = new Dictionary + { + ["items"] = new[] { "" }.Concat(contentTypes) + }; + + return new(options); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Http/ContentWriters/BinaryContentFactory.cs b/src/modules/Elsa.Http/ContentWriters/BinaryContentFactory.cs new file mode 100644 index 000000000..d430f3b13 --- /dev/null +++ b/src/modules/Elsa.Http/ContentWriters/BinaryContentFactory.cs @@ -0,0 +1,23 @@ +using System.Net.Mime; + +namespace Elsa.Http.ContentWriters; + +/// +/// Creates a object for application/octet-stream. +/// +public class BinaryContentFactory : IHttpContentFactory +{ + /// + public IEnumerable SupportedContentTypes => new[] { MediaTypeNames.Application.Octet }; + + /// + public HttpContent CreateHttpContent(object content, string contentType) + { + return content switch + { + byte[] bytes => new ByteArrayContent(bytes), + Stream stream => new StreamContent(stream), + _ => throw new NotSupportedException($"Content of type {content.GetType()} is not supported.") + }; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Http/ContentWriters/FormUrlEncodedHttpContentFactory.cs b/src/modules/Elsa.Http/ContentWriters/FormUrlEncodedHttpContentFactory.cs index 488f8232d..10bbf5742 100644 --- a/src/modules/Elsa.Http/ContentWriters/FormUrlEncodedHttpContentFactory.cs +++ b/src/modules/Elsa.Http/ContentWriters/FormUrlEncodedHttpContentFactory.cs @@ -8,10 +8,8 @@ namespace Elsa.Http.ContentWriters; /// public class FormUrlEncodedHttpContentFactory : IHttpContentFactory { - private readonly List _supportedContentTypes = new() { "application/x-www-form-urlencoded" }; - /// - public bool SupportsContentType(string contentType) => _supportedContentTypes.Contains(contentType); + public IEnumerable SupportedContentTypes => new[] { "application/x-www-form-urlencoded" }; /// public HttpContent CreateHttpContent(object content, string? contentType = null) => new FormUrlEncodedContent(GetContentAsDictionary(content)); diff --git a/src/modules/Elsa.Http/ContentWriters/IHttpContentFactory.cs b/src/modules/Elsa.Http/ContentWriters/IHttpContentFactory.cs index 8546e2249..0fe563f7f 100644 --- a/src/modules/Elsa.Http/ContentWriters/IHttpContentFactory.cs +++ b/src/modules/Elsa.Http/ContentWriters/IHttpContentFactory.cs @@ -8,7 +8,7 @@ public interface IHttpContentFactory /// /// Returns a value indicating whether this factory supports the specified content type. /// - bool SupportsContentType(string contentType); + IEnumerable SupportedContentTypes { get; } /// /// Creates a concrete derivative based on the specified content type. diff --git a/src/modules/Elsa.Http/ContentWriters/JsonContentFactory.cs b/src/modules/Elsa.Http/ContentWriters/JsonContentFactory.cs index f5fcb53ae..5d61f2fcf 100644 --- a/src/modules/Elsa.Http/ContentWriters/JsonContentFactory.cs +++ b/src/modules/Elsa.Http/ContentWriters/JsonContentFactory.cs @@ -10,10 +10,8 @@ namespace Elsa.Http.ContentWriters; /// public class JsonContentFactory : IHttpContentFactory { - private readonly List _supportedContentTypes = new() { MediaTypeNames.Application.Json, "text/json" }; - /// - public bool SupportsContentType(string contentType) => _supportedContentTypes.Contains(contentType); + public IEnumerable SupportedContentTypes => new[] { MediaTypeNames.Application.Json, "text/json" }; /// public HttpContent CreateHttpContent(object content, string contentType) diff --git a/src/modules/Elsa.Http/ContentWriters/TextContentFactory.cs b/src/modules/Elsa.Http/ContentWriters/TextContentFactory.cs index ff43ed656..d1c14167e 100644 --- a/src/modules/Elsa.Http/ContentWriters/TextContentFactory.cs +++ b/src/modules/Elsa.Http/ContentWriters/TextContentFactory.cs @@ -8,16 +8,14 @@ namespace Elsa.Http.ContentWriters; /// public class TextContentFactory : IHttpContentFactory { - private readonly List _supportedContentTypes = new() + /// + public IEnumerable SupportedContentTypes => new[] { MediaTypeNames.Text.Plain, MediaTypeNames.Text.RichText, MediaTypeNames.Text.Html, }; - /// - public bool SupportsContentType(string contentType) => _supportedContentTypes.Contains(contentType); - /// public HttpContent CreateHttpContent(object content, string contentType) { diff --git a/src/modules/Elsa.Http/ContentWriters/XmlContentFactory.cs b/src/modules/Elsa.Http/ContentWriters/XmlContentFactory.cs index ea3598376..d45ebd61a 100644 --- a/src/modules/Elsa.Http/ContentWriters/XmlContentFactory.cs +++ b/src/modules/Elsa.Http/ContentWriters/XmlContentFactory.cs @@ -5,14 +5,17 @@ using System.Xml.Serialization; namespace Elsa.Http.ContentWriters; /// -/// Creates a object for application/json. +/// Creates a object for XML types. /// public class XmlContentFactory : IHttpContentFactory { - private readonly List _supportedContentTypes = new() { MediaTypeNames.Application.Xml, MediaTypeNames.Text.Xml }; - /// - public bool SupportsContentType(string contentType) => _supportedContentTypes.Contains(contentType); + public IEnumerable SupportedContentTypes => new[] + { + MediaTypeNames.Application.Xml, + MediaTypeNames.Text.Xml, + MediaTypeNames.Application.Soap, + }; /// public HttpContent CreateHttpContent(object content, string contentType) @@ -20,7 +23,7 @@ public class XmlContentFactory : IHttpContentFactory var text = content as string ?? Serialize(content); return new StringContent(text, Encoding.UTF8, contentType); } - + private string Serialize(object value) { using var writer = new StringWriter(); diff --git a/src/modules/Elsa.Http/Features/HttpFeature.cs b/src/modules/Elsa.Http/Features/HttpFeature.cs index 1371a5cfc..df8231b8c 100644 --- a/src/modules/Elsa.Http/Features/HttpFeature.cs +++ b/src/modules/Elsa.Http/Features/HttpFeature.cs @@ -4,6 +4,7 @@ using Elsa.Extensions; using Elsa.Features.Abstractions; using Elsa.Features.Attributes; using Elsa.Features.Services; +using Elsa.Http.ActivityOptionProviders; using Elsa.Http.ContentWriters; using Elsa.Http.Contracts; using Elsa.Http.DownloadableContentHandlers; @@ -14,7 +15,6 @@ using Elsa.Http.Models; using Elsa.Http.Options; using Elsa.Http.Parsers; using Elsa.Http.PortResolvers; -using Elsa.Http.Providers; using Elsa.Http.Selectors; using Elsa.Http.Services; using Elsa.JavaScript.Features; @@ -173,7 +173,7 @@ public class HttpFeature : FeatureBase .AddSingleton() // Activity property options providers. - .AddSingleton() + .AddSingleton() // Port resolvers. .AddSingleton() diff --git a/src/modules/Elsa.Http/Providers/WriteHttpResponseContentTypeOptionsProvider.cs b/src/modules/Elsa.Http/Providers/WriteHttpResponseContentTypeOptionsProvider.cs deleted file mode 100644 index bd28025f1..000000000 --- a/src/modules/Elsa.Http/Providers/WriteHttpResponseContentTypeOptionsProvider.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Reflection; -using Elsa.Http.Options; -using Elsa.Workflows.Core.Contracts; -using Microsoft.Extensions.Options; - -namespace Elsa.Http.Providers; - -internal class WriteHttpResponseContentTypeOptionsProvider : IActivityPropertyOptionsProvider -{ - private readonly HttpActivityOptions _options; - - public WriteHttpResponseContentTypeOptionsProvider(IOptions options) - { - _options = options.Value; - } - - public ValueTask> GetOptionsAsync(PropertyInfo property, CancellationToken cancellationToken = default) - { - var options = new Dictionary - { - ["items"] = new[] { "" }.Concat(_options.AvailableContentTypes) - }; - - return new(options); - } -} \ No newline at end of file From d7bc15571b70bb0423929fcc25bc8236345ae53e Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 12 Oct 2023 14:26:29 +0200 Subject: [PATCH 2/4] Update GH actions --- .github/workflows/packages.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index b5a27586f..b859e8cf2 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -4,6 +4,8 @@ on: push: branches: - v3 + tags: + - preview-*hotfix-* # e.g. preview-1.0.0-hotfix-1 release: types: [ prereleased ] env: From 09dbb9b4d44869bdcf8d27329605d25ca7192ed0 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Thu, 12 Oct 2023 14:38:59 +0200 Subject: [PATCH 3/4] Update GH actions --- .github/workflows/packages.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index b859e8cf2..e314a9673 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -25,7 +25,13 @@ jobs: git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* git branch --remote --contains | grep origin/v3 - name: Set VERSION variable - run: echo "VERSION=3.0.0-preview.${{github.run_number}}" >> $GITHUB_ENV + run: | + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + TAG_NAME=${{ github.ref }} # e.g., refs/tags/preview-740-hotfix-1 + TAG_NAME=${TAG_NAME#refs/tags/} # remove the refs/tags/ prefix + echo "VERSION=3.0.0-preview-${TAG_NAME}.${{github.run_number}}" >> $GITHUB_ENV + else + echo "VERSION=3.0.0-preview.${{github.run_number}}" >> $GITHUB_ENV - name: Build designer package working-directory: ./src/modules/Elsa.Workflows.Designer run: | From 749756871c1961848cc600af8e70dd69a5b10990 Mon Sep 17 00:00:00 2001 From: cristinamudura <80831066+cristinamudura@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:50:16 +0200 Subject: [PATCH 4/4] Update packages.yml --- .github/workflows/packages.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index e314a9673..dabfc4e7d 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -25,13 +25,14 @@ jobs: git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* git branch --remote --contains | grep origin/v3 - name: Set VERSION variable - run: | + run: | if [[ "${{ github.ref }}" == refs/tags/* ]]; then TAG_NAME=${{ github.ref }} # e.g., refs/tags/preview-740-hotfix-1 TAG_NAME=${TAG_NAME#refs/tags/} # remove the refs/tags/ prefix echo "VERSION=3.0.0-preview-${TAG_NAME}.${{github.run_number}}" >> $GITHUB_ENV else echo "VERSION=3.0.0-preview.${{github.run_number}}" >> $GITHUB_ENV + fi - name: Build designer package working-directory: ./src/modules/Elsa.Workflows.Designer run: |