From ebaeabb934d97ed8077d0660fc3ff58247a23f7a Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Tue, 15 Jun 2021 19:47:46 +0200 Subject: [PATCH] Implement support for HTTP file download for SendHttpRequest and WriteHttpResponse --- .../SendHttpRequest/SendHttpRequest.cs | 23 ++++---- .../WriteHttpResponse/WriteHttpResponse.cs | 52 +++++++++++++++---- .../Extensions/ServiceCollectionExtensions.cs | 7 ++- .../Middleware/HttpEndpointMiddleware.cs | 1 + .../Parsers/DefaultHttpResponseBodyParser.cs | 15 ------ .../DefaultHttpRequestBodyParser.cs | 2 +- .../FormHttpRequestBodyParser.cs | 0 .../JsonHttpRequestBodyParser.cs | 0 .../DefaultHttpResponseContentReader.cs | 14 +++++ .../Response/FileResponseContentReader.cs | 21 ++++++++ .../JsonHttpResponseContentReader.cs} | 12 ++--- .../Services/IHttpResponseBodyParser.cs | 14 ----- .../Services/IHttpResponseContentReader.cs | 13 +++++ .../Converters/Jint/ByteArrayConverter.cs | 24 +++++++++ .../TruncatingNumberJsonConverter.cs | 2 +- .../Services/EnumerableResultConverter.cs | 37 ++++++------- ...tionaryWhenNoDesiredTypeResultConverter.cs | 6 +-- .../Services/JObjectResultConverter.cs | 20 ------- .../JintEvaluationResultConverterFactory.cs | 21 +++----- .../Services/JintJavaScriptEvaluator.cs | 4 +- .../Services/NullResultConverter.cs | 2 +- .../Services/PlainObjectResultConverter.cs | 2 +- .../Services/TypeConverterResultConverter.cs | 2 +- 23 files changed, 170 insertions(+), 124 deletions(-) delete mode 100644 src/activities/Elsa.Activities.Http/Parsers/DefaultHttpResponseBodyParser.cs rename src/activities/Elsa.Activities.Http/Parsers/{ => Request}/DefaultHttpRequestBodyParser.cs (92%) rename src/activities/Elsa.Activities.Http/Parsers/{ => Request}/FormHttpRequestBodyParser.cs (100%) rename src/activities/Elsa.Activities.Http/Parsers/{ => Request}/JsonHttpRequestBodyParser.cs (100%) create mode 100644 src/activities/Elsa.Activities.Http/Parsers/Response/DefaultHttpResponseContentReader.cs create mode 100644 src/activities/Elsa.Activities.Http/Parsers/Response/FileResponseContentReader.cs rename src/activities/Elsa.Activities.Http/Parsers/{JsonHttpResponseBodyParser.cs => Response/JsonHttpResponseContentReader.cs} (58%) delete mode 100644 src/activities/Elsa.Activities.Http/Services/IHttpResponseBodyParser.cs create mode 100644 src/activities/Elsa.Activities.Http/Services/IHttpResponseContentReader.cs create mode 100644 src/scripting/Elsa.Scripting.JavaScript/Converters/Jint/ByteArrayConverter.cs rename src/scripting/Elsa.Scripting.JavaScript/Converters/{ => Json}/TruncatingNumberJsonConverter.cs (97%) delete mode 100644 src/scripting/Elsa.Scripting.JavaScript/Services/JObjectResultConverter.cs diff --git a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequest.cs b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequest.cs index 8afc7e5d9..3d99da5f7 100644 --- a/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequest.cs +++ b/src/activities/Elsa.Activities.Http/Activities/SendHttpRequest/SendHttpRequest.cs @@ -29,11 +29,11 @@ namespace Elsa.Activities.Http public class SendHttpRequest : Activity { private readonly HttpClient _httpClient; - private readonly IEnumerable _parsers; + private readonly IEnumerable _parsers; public SendHttpRequest( IHttpClientFactory httpClientFactory, - IEnumerable parsers) + IEnumerable parsers) { _httpClient = httpClientFactory.CreateClient(nameof(SendHttpRequest)); _parsers = parsers; @@ -68,7 +68,7 @@ namespace Elsa.Activities.Http [ActivityInput( UIHint = ActivityInputUIHints.Dropdown, Hint = "The content type to send with the request.", - Options = new[] { "text/plain", "text/html", "application/json", "application/xml", "application/x-www-form-urlencoded" }, + Options = new[] { "", "text/plain", "text/html", "application/json", "application/xml", "application/x-www-form-urlencoded" }, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid } )] public string? ContentType { get; set; } @@ -102,7 +102,7 @@ namespace Elsa.Activities.Http )] public ICollection? SupportedStatusCodes { get; set; } = new HashSet(new[] { 200 }); - [ActivityOutput] public HttpResponseModel Output { get; set; } + [ActivityOutput] public HttpResponseModel? Output { get; set; } protected override async ValueTask OnExecuteAsync(ActivityExecutionContext context) { @@ -122,8 +122,8 @@ namespace Elsa.Activities.Http if (hasContent && ReadContent) { - var formatter = SelectContentParser(contentType!); - responseModel.Content = await formatter.ParseAsync(response, cancellationToken); + var formatter = SelectContentParser(contentType); + responseModel.Content = await formatter.ReadAsync(response, cancellationToken); } var statusCode = (int) response.StatusCode; @@ -139,13 +139,12 @@ namespace Elsa.Activities.Http return Outcomes(outcomes); } - private IHttpResponseBodyParser SelectContentParser(string contentType) + private IHttpResponseContentReader SelectContentParser(string? contentType) { - var simpleContentType = contentType?.Split(';').First(); + var simpleContentType = contentType?.Split(';').First() ?? ""; var formatters = _parsers.OrderByDescending(x => x.Priority).ToList(); - return formatters.FirstOrDefault( - x => x.SupportedContentTypes.Contains(simpleContentType, StringComparer.OrdinalIgnoreCase) - ) ?? formatters.Last(); + + return formatters.FirstOrDefault(x => x.GetSupportsContentType(simpleContentType)) ?? formatters.Last(); } private HttpRequestMessage CreateRequest() @@ -153,7 +152,7 @@ namespace Elsa.Activities.Http var method = Method ?? HttpMethods.Get; var methodSupportsBody = GetMethodSupportsBody(method); var url = Url; - var request = new HttpRequestMessage(new HttpMethod(Method), url); + var request = new HttpRequestMessage(new HttpMethod(method), url); var authorizationHeaderValue = Authorization; var requestHeaders = new HeaderDictionary(RequestHeaders.ToDictionary(x => x.Key, x => new StringValues(x.Value.Split(',')))); diff --git a/src/activities/Elsa.Activities.Http/Activities/WriteHttpResponse/WriteHttpResponse.cs b/src/activities/Elsa.Activities.Http/Activities/WriteHttpResponse/WriteHttpResponse.cs index 9d732c95a..0ccd58e56 100644 --- a/src/activities/Elsa.Activities.Http/Activities/WriteHttpResponse/WriteHttpResponse.cs +++ b/src/activities/Elsa.Activities.Http/Activities/WriteHttpResponse/WriteHttpResponse.cs @@ -1,10 +1,13 @@ +using System; using System.Net; +using System.Threading; using System.Threading.Tasks; using Elsa.Activities.Http.Models; using Elsa.ActivityResults; using Elsa.Attributes; using Elsa.Design; using Elsa.Expressions; +using Elsa.Serialization; using Elsa.Services; using Elsa.Services.Models; using Microsoft.AspNetCore.Http; @@ -22,11 +25,13 @@ namespace Elsa.Activities.Http public class WriteHttpResponse : Activity { private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IContentSerializer _contentSerializer; - public WriteHttpResponse(IHttpContextAccessor httpContextAccessor, IStringLocalizer localizer) + public WriteHttpResponse(IHttpContextAccessor httpContextAccessor, IStringLocalizer localizer, IContentSerializer contentSerializer) { T = localizer; _httpContextAccessor = httpContextAccessor; + _contentSerializer = contentSerializer; } private IStringLocalizer T { get; } @@ -45,10 +50,10 @@ namespace Elsa.Activities.Http public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.OK; /// - /// The content to send along with the response + /// The content to send along with the response. /// [ActivityInput(Hint = "The HTTP content to write.", UIHint = ActivityInputUIHints.MultiLine, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })] - public string? Content { get; set; } + public object? Content { get; set; } /// /// The Content-Type header to send along with the response. @@ -68,7 +73,7 @@ namespace Elsa.Activities.Http [ActivityInput( Hint = "The character set to use when writing the response.", UIHint = ActivityInputUIHints.Dropdown, - Options = new[] { "utf-8", "ASCII", "ANSI", "ISO-8859-1" }, + Options = new[] { "", "utf-8", "ASCII", "ANSI", "ISO-8859-1" }, DefaultValue = "utf-8", SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }, Category = PropertyCategories.Advanced)] @@ -78,10 +83,10 @@ namespace Elsa.Activities.Http /// The headers to send along with the response. /// [ActivityInput( - Hint = "Additional headers to write.", + Hint = "Additional headers to write.", UIHint = ActivityInputUIHints.MultiLine, DefaultSyntax = SyntaxNames.Json, - SupportedSyntaxes = new[]{ SyntaxNames.JavaScript, SyntaxNames.Liquid, SyntaxNames.Json }, + SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid, SyntaxNames.Json }, Category = PropertyCategories.Advanced )] public HttpResponseHeaders? ResponseHeaders { get; set; } @@ -95,7 +100,7 @@ namespace Elsa.Activities.Http return Fault(T["Response has already started"]!); response.StatusCode = (int) StatusCode; - response.ContentType = $"{ContentType};charset={CharSet}"; + response.ContentType = string.IsNullOrWhiteSpace(CharSet) ? ContentType : $"{ContentType};charset={CharSet}"; var headers = ResponseHeaders; @@ -105,12 +110,37 @@ namespace Elsa.Activities.Http response.Headers[header.Key] = header.Value; } - var bodyText = Content; - - if (!string.IsNullOrWhiteSpace(bodyText)) - await response.WriteAsync(bodyText, context.CancellationToken); + await WriteContentAsync(context.CancellationToken); return Done(); } + + private async Task WriteContentAsync(CancellationToken cancellationToken) + { + var httpContext = _httpContextAccessor.HttpContext ?? new DefaultHttpContext(); + var response = httpContext.Response; + + var content = Content; + + if (content == null) + return; + + if (content is string stringContent) + { + if (!string.IsNullOrWhiteSpace(stringContent)) + await response.WriteAsync(stringContent, cancellationToken); + + return; + } + + if (content is byte[] buffer) + { + await response.Body.WriteAsync(buffer, cancellationToken); + return; + } + + var json = _contentSerializer.Serialize(content); + await response.WriteAsync(json, cancellationToken); + } } } \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs b/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs index e873f6deb..794b55b6e 100644 --- a/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs +++ b/src/activities/Elsa.Activities.Http/Extensions/ServiceCollectionExtensions.cs @@ -6,6 +6,8 @@ using Elsa.Activities.Http.JavaScript; using Elsa.Activities.Http.Liquid; using Elsa.Activities.Http.Options; using Elsa.Activities.Http.Parsers; +using Elsa.Activities.Http.Parsers.Request; +using Elsa.Activities.Http.Parsers.Response; using Elsa.Activities.Http.Services; using Elsa.Scripting.Liquid.Extensions; using Microsoft.AspNetCore.Http; @@ -36,8 +38,9 @@ namespace Microsoft.Extensions.DependencyInjection .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() - .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddBookmarkProvider() diff --git a/src/activities/Elsa.Activities.Http/Middleware/HttpEndpointMiddleware.cs b/src/activities/Elsa.Activities.Http/Middleware/HttpEndpointMiddleware.cs index 20fd38f72..c608a71fd 100644 --- a/src/activities/Elsa.Activities.Http/Middleware/HttpEndpointMiddleware.cs +++ b/src/activities/Elsa.Activities.Http/Middleware/HttpEndpointMiddleware.cs @@ -7,6 +7,7 @@ using Elsa.Activities.Http.Bookmarks; using Elsa.Activities.Http.Extensions; using Elsa.Activities.Http.Models; using Elsa.Activities.Http.Parsers; +using Elsa.Activities.Http.Parsers.Request; using Elsa.Activities.Http.Services; using Elsa.Persistence; using Elsa.Persistence.Specifications.WorkflowInstances; diff --git a/src/activities/Elsa.Activities.Http/Parsers/DefaultHttpResponseBodyParser.cs b/src/activities/Elsa.Activities.Http/Parsers/DefaultHttpResponseBodyParser.cs deleted file mode 100644 index 495455cdd..000000000 --- a/src/activities/Elsa.Activities.Http/Parsers/DefaultHttpResponseBodyParser.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Elsa.Activities.Http.Services; - -namespace Elsa.Activities.Http.Parsers -{ - public class DefaultHttpResponseBodyParser : IHttpResponseBodyParser - { - public int Priority => -1; - public IEnumerable SupportedContentTypes => new[] { "", default }; - public async Task ParseAsync(HttpResponseMessage response, CancellationToken cancellationToken) => await response.Content.ReadAsStringAsync(); - } -} \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Http/Parsers/DefaultHttpRequestBodyParser.cs b/src/activities/Elsa.Activities.Http/Parsers/Request/DefaultHttpRequestBodyParser.cs similarity index 92% rename from src/activities/Elsa.Activities.Http/Parsers/DefaultHttpRequestBodyParser.cs rename to src/activities/Elsa.Activities.Http/Parsers/Request/DefaultHttpRequestBodyParser.cs index dd5dba9eb..971cb5559 100644 --- a/src/activities/Elsa.Activities.Http/Parsers/DefaultHttpRequestBodyParser.cs +++ b/src/activities/Elsa.Activities.Http/Parsers/Request/DefaultHttpRequestBodyParser.cs @@ -5,7 +5,7 @@ using Elsa.Activities.Http.Extensions; using Elsa.Activities.Http.Services; using Microsoft.AspNetCore.Http; -namespace Elsa.Activities.Http.Parsers +namespace Elsa.Activities.Http.Parsers.Request { public class DefaultHttpRequestBodyParser : IHttpRequestBodyParser { diff --git a/src/activities/Elsa.Activities.Http/Parsers/FormHttpRequestBodyParser.cs b/src/activities/Elsa.Activities.Http/Parsers/Request/FormHttpRequestBodyParser.cs similarity index 100% rename from src/activities/Elsa.Activities.Http/Parsers/FormHttpRequestBodyParser.cs rename to src/activities/Elsa.Activities.Http/Parsers/Request/FormHttpRequestBodyParser.cs diff --git a/src/activities/Elsa.Activities.Http/Parsers/JsonHttpRequestBodyParser.cs b/src/activities/Elsa.Activities.Http/Parsers/Request/JsonHttpRequestBodyParser.cs similarity index 100% rename from src/activities/Elsa.Activities.Http/Parsers/JsonHttpRequestBodyParser.cs rename to src/activities/Elsa.Activities.Http/Parsers/Request/JsonHttpRequestBodyParser.cs diff --git a/src/activities/Elsa.Activities.Http/Parsers/Response/DefaultHttpResponseContentReader.cs b/src/activities/Elsa.Activities.Http/Parsers/Response/DefaultHttpResponseContentReader.cs new file mode 100644 index 000000000..a3748bcd1 --- /dev/null +++ b/src/activities/Elsa.Activities.Http/Parsers/Response/DefaultHttpResponseContentReader.cs @@ -0,0 +1,14 @@ +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Activities.Http.Services; + +namespace Elsa.Activities.Http.Parsers.Response +{ + public class DefaultHttpResponseContentReader : IHttpResponseContentReader + { + public int Priority => -1; + public bool GetSupportsContentType(string contentType) => true; + public async Task ReadAsync(HttpResponseMessage response, CancellationToken cancellationToken) => await response.Content.ReadAsStringAsync(); + } +} \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Http/Parsers/Response/FileResponseContentReader.cs b/src/activities/Elsa.Activities.Http/Parsers/Response/FileResponseContentReader.cs new file mode 100644 index 000000000..22b846e5c --- /dev/null +++ b/src/activities/Elsa.Activities.Http/Parsers/Response/FileResponseContentReader.cs @@ -0,0 +1,21 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Elsa.Activities.Http.Services; + +namespace Elsa.Activities.Http.Parsers.Response +{ + public class FileResponseContentReader : IHttpResponseContentReader + { + public virtual int Priority => 5; + public virtual bool GetSupportsContentType(string contentType) + { + var types = new[] { "audio", "video", "application", "pdf" }; + return types.Any(x => contentType.Contains(x, StringComparison.OrdinalIgnoreCase)); + } + + public async Task ReadAsync(HttpResponseMessage response, CancellationToken cancellationToken) => await response.Content.ReadAsByteArrayAsync(); + } +} \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Http/Parsers/JsonHttpResponseBodyParser.cs b/src/activities/Elsa.Activities.Http/Parsers/Response/JsonHttpResponseContentReader.cs similarity index 58% rename from src/activities/Elsa.Activities.Http/Parsers/JsonHttpResponseBodyParser.cs rename to src/activities/Elsa.Activities.Http/Parsers/Response/JsonHttpResponseContentReader.cs index 43967453f..d6eb8b946 100644 --- a/src/activities/Elsa.Activities.Http/Parsers/JsonHttpResponseBodyParser.cs +++ b/src/activities/Elsa.Activities.Http/Parsers/Response/JsonHttpResponseContentReader.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System; using System.Dynamic; using System.Net.Http; using System.Threading; @@ -6,14 +6,14 @@ using System.Threading.Tasks; using Elsa.Activities.Http.Services; using Newtonsoft.Json; -namespace Elsa.Activities.Http.Parsers +namespace Elsa.Activities.Http.Parsers.Response { - public class JsonHttpResponseBodyParser : IHttpResponseBodyParser + public class JsonHttpResponseContentReader : IHttpResponseContentReader { - public int Priority => 0; - public IEnumerable SupportedContentTypes => new[] { "application/json", "text/json" }; + public int Priority => 10; + public bool GetSupportsContentType(string contentType) => contentType.Contains("/json", StringComparison.OrdinalIgnoreCase); - public async Task ParseAsync(HttpResponseMessage response, CancellationToken cancellationToken) + public async Task ReadAsync(HttpResponseMessage response, CancellationToken cancellationToken) { #if NET var json = (await response.Content.ReadAsStringAsync(cancellationToken)).Trim(); diff --git a/src/activities/Elsa.Activities.Http/Services/IHttpResponseBodyParser.cs b/src/activities/Elsa.Activities.Http/Services/IHttpResponseBodyParser.cs deleted file mode 100644 index eed5762f7..000000000 --- a/src/activities/Elsa.Activities.Http/Services/IHttpResponseBodyParser.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace Elsa.Activities.Http.Services -{ - public interface IHttpResponseBodyParser - { - int Priority { get; } - IEnumerable SupportedContentTypes { get; } - Task ParseAsync(HttpResponseMessage response, CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/src/activities/Elsa.Activities.Http/Services/IHttpResponseContentReader.cs b/src/activities/Elsa.Activities.Http/Services/IHttpResponseContentReader.cs new file mode 100644 index 000000000..236b6f15e --- /dev/null +++ b/src/activities/Elsa.Activities.Http/Services/IHttpResponseContentReader.cs @@ -0,0 +1,13 @@ +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Elsa.Activities.Http.Services +{ + public interface IHttpResponseContentReader + { + int Priority { get; } + bool GetSupportsContentType(string contentType); + Task ReadAsync(HttpResponseMessage response, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/scripting/Elsa.Scripting.JavaScript/Converters/Jint/ByteArrayConverter.cs b/src/scripting/Elsa.Scripting.JavaScript/Converters/Jint/ByteArrayConverter.cs new file mode 100644 index 000000000..9830c5de5 --- /dev/null +++ b/src/scripting/Elsa.Scripting.JavaScript/Converters/Jint/ByteArrayConverter.cs @@ -0,0 +1,24 @@ +using Jint; +using Jint.Native; +using Jint.Runtime.Interop; + +namespace Elsa.Scripting.JavaScript.Converters.Jint +{ + /// + /// Prevents Jint from returning byte[] as object[]. + /// + internal class ByteArrayConverter : IObjectConverter + { + public bool TryConvert(Engine engine, object value, out JsValue result) + { + result = JsValue.Null; + + if (value is not byte[] buffer) + return false; + + result = new ObjectWrapper(engine, buffer); + + return true; + } + } +} \ No newline at end of file diff --git a/src/scripting/Elsa.Scripting.JavaScript/Converters/TruncatingNumberJsonConverter.cs b/src/scripting/Elsa.Scripting.JavaScript/Converters/Json/TruncatingNumberJsonConverter.cs similarity index 97% rename from src/scripting/Elsa.Scripting.JavaScript/Converters/TruncatingNumberJsonConverter.cs rename to src/scripting/Elsa.Scripting.JavaScript/Converters/Json/TruncatingNumberJsonConverter.cs index a6569d2e2..c1c30757f 100644 --- a/src/scripting/Elsa.Scripting.JavaScript/Converters/TruncatingNumberJsonConverter.cs +++ b/src/scripting/Elsa.Scripting.JavaScript/Converters/Json/TruncatingNumberJsonConverter.cs @@ -1,7 +1,7 @@ using System; using Newtonsoft.Json; -namespace Elsa.Scripting.JavaScript.Converters +namespace Elsa.Scripting.JavaScript.Converters.Json { /// /// Ensures that whole numeric values are serialized without any decimal (e.g. 2 instead of 2.0) to ensure deserialization to models having int properties works. diff --git a/src/scripting/Elsa.Scripting.JavaScript/Services/EnumerableResultConverter.cs b/src/scripting/Elsa.Scripting.JavaScript/Services/EnumerableResultConverter.cs index 7c523c2b1..644d49100 100644 --- a/src/scripting/Elsa.Scripting.JavaScript/Services/EnumerableResultConverter.cs +++ b/src/scripting/Elsa.Scripting.JavaScript/Services/EnumerableResultConverter.cs @@ -8,42 +8,37 @@ namespace Elsa.Scripting.JavaScript.Services { public class EnumerableResultConverter : IConvertsJintEvaluationResult, IConvertsEnumerableToObject { - readonly IConvertsJintEvaluationResult? wrapped; + private readonly IConvertsJintEvaluationResult? _wrapped; + + public EnumerableResultConverter(IConvertsJintEvaluationResult? wrapped) => _wrapped = wrapped; public object? ConvertToDesiredType(object? evaluationResult, Type desiredType) { - if(evaluationResult is IEnumerable enumerable && !(evaluationResult is ExpandoObject)) + if (evaluationResult is IEnumerable enumerable && !(evaluationResult is ExpandoObject)) return ConvertEnumerable(enumerable, desiredType); - - return wrapped?.ConvertToDesiredType(evaluationResult, desiredType); + + return _wrapped?.ConvertToDesiredType(evaluationResult, desiredType); } - object? ConvertEnumerable(IEnumerable enumerable, Type? desiredType = null) + private static object? ConvertEnumerable(IEnumerable enumerable, Type? desiredType = null) { - if(enumerable is string) return enumerable; - if(enumerable is JObject) return enumerable; + if (enumerable is string) return enumerable; + if (enumerable is JObject) return enumerable; + if (enumerable is byte[]) return enumerable; var destinationType = GetDestinationType(desiredType); var json = JsonConvert.SerializeObject(enumerable); return JsonConvert.DeserializeObject(json, destinationType); } - static Type GetDestinationType(Type? desiredType) - { - return desiredType switch + private static Type GetDestinationType(Type? desiredType) => + desiredType switch { - Type t when t == typeof(object) => typeof(object[]), - null => typeof(object[]), - _ => desiredType, + { } t when t == typeof(object) => typeof(object[]), + null => typeof(object[]), + _ => desiredType, }; - } - object? IConvertsEnumerableToObject.ConvertEnumerable(IEnumerable enumerable) - => ConvertEnumerable(enumerable); - - public EnumerableResultConverter(IConvertsJintEvaluationResult? wrapped) - { - this.wrapped = wrapped; - } + object? IConvertsEnumerableToObject.ConvertEnumerable(IEnumerable enumerable) => ConvertEnumerable(enumerable); } } \ No newline at end of file diff --git a/src/scripting/Elsa.Scripting.JavaScript/Services/ExpandoObjectToDictionaryWhenNoDesiredTypeResultConverter.cs b/src/scripting/Elsa.Scripting.JavaScript/Services/ExpandoObjectToDictionaryWhenNoDesiredTypeResultConverter.cs index ee08242bf..ebb1e234c 100644 --- a/src/scripting/Elsa.Scripting.JavaScript/Services/ExpandoObjectToDictionaryWhenNoDesiredTypeResultConverter.cs +++ b/src/scripting/Elsa.Scripting.JavaScript/Services/ExpandoObjectToDictionaryWhenNoDesiredTypeResultConverter.cs @@ -9,8 +9,8 @@ namespace Elsa.Scripting.JavaScript.Services { public class ExpandoObjectToDictionaryWhenNoDesiredTypeResultConverter : IConvertsJintEvaluationResult { - readonly IConvertsJintEvaluationResult wrapped; - readonly IConvertsEnumerableToObject enumerableConverter; + private readonly IConvertsJintEvaluationResult wrapped; + private readonly IConvertsEnumerableToObject enumerableConverter; public object? ConvertToDesiredType(object? evaluationResult, Type desiredType) { @@ -20,7 +20,7 @@ namespace Elsa.Scripting.JavaScript.Services return wrapped.ConvertToDesiredType(evaluationResult, desiredType); } - object? RecursivelyPrepareExpandoObjectForReturn(ExpandoObject obj) + private object? RecursivelyPrepareExpandoObjectForReturn(ExpandoObject obj) { IDictionary ExpandoToDictionary(ExpandoObject expando) { diff --git a/src/scripting/Elsa.Scripting.JavaScript/Services/JObjectResultConverter.cs b/src/scripting/Elsa.Scripting.JavaScript/Services/JObjectResultConverter.cs deleted file mode 100644 index e7da5098f..000000000 --- a/src/scripting/Elsa.Scripting.JavaScript/Services/JObjectResultConverter.cs +++ /dev/null @@ -1,20 +0,0 @@ -// using System; -// using Newtonsoft.Json.Linq; -// -// namespace Elsa.Scripting.JavaScript.Services -// { -// public class JObjectResultConverter : IConvertsJintEvaluationResult -// { -// readonly IConvertsJintEvaluationResult _wrapped; -// -// public JObjectResultConverter(IConvertsJintEvaluationResult wrapped) => _wrapped = wrapped ?? throw new ArgumentNullException(nameof(wrapped)); -// -// public object? ConvertToDesiredType(object? evaluationResult, Type desiredType) -// { -// if(evaluationResult is JObject jObject) -// return JObjectExtensions.DeserializeState(jObject, desiredType); -// -// return _wrapped.ConvertToDesiredType(evaluationResult, desiredType); -// } -// } -// } \ No newline at end of file diff --git a/src/scripting/Elsa.Scripting.JavaScript/Services/JintEvaluationResultConverterFactory.cs b/src/scripting/Elsa.Scripting.JavaScript/Services/JintEvaluationResultConverterFactory.cs index 710b1b416..423af6a65 100644 --- a/src/scripting/Elsa.Scripting.JavaScript/Services/JintEvaluationResultConverterFactory.cs +++ b/src/scripting/Elsa.Scripting.JavaScript/Services/JintEvaluationResultConverterFactory.cs @@ -11,37 +11,30 @@ namespace Elsa.Scripting.JavaScript.Services public IConvertsJintEvaluationResult GetConverter() { - IConvertsJintEvaluationResult service; - // Builds a chain-of-responsibility service // Note: The order in which these classes execute is bottom-to-top - service = GetConvertChangeTypeService(); + var service = GetConvertChangeTypeService(); service = GetPlainObjectService(service); service = GetEnumerableConvertingService(service); service = GetExpandoConvertingService(service); - //service = GetJObjectService(service); service = GetTypeConverterConvertingService(service); service = GetNullConvertingService(service); return service; } - static IConvertsJintEvaluationResult GetConvertChangeTypeService() => new ConvertChangeTypeResultConverter(); + private static IConvertsJintEvaluationResult GetConvertChangeTypeService() => new ConvertChangeTypeResultConverter(); + private static IConvertsJintEvaluationResult GetPlainObjectService(IConvertsJintEvaluationResult wrapped) => new PlainObjectResultConverter(wrapped); + private IConvertsJintEvaluationResult GetEnumerableConvertingService(IConvertsJintEvaluationResult wrapped) => new EnumerableResultConverter(wrapped); - static IConvertsJintEvaluationResult GetPlainObjectService(IConvertsJintEvaluationResult wrapped) => new PlainObjectResultConverter(wrapped); - //static IConvertsJintEvaluationResult GetJObjectService(IConvertsJintEvaluationResult wrapped) => new JObjectResultConverter(wrapped); - - IConvertsJintEvaluationResult GetEnumerableConvertingService(IConvertsJintEvaluationResult wrapped) => new EnumerableResultConverter(wrapped); - - IConvertsJintEvaluationResult GetExpandoConvertingService(IConvertsJintEvaluationResult wrapped) + private IConvertsJintEvaluationResult GetExpandoConvertingService(IConvertsJintEvaluationResult wrapped) { var enumerableConverter = _serviceProvider.GetRequiredService(); return new ExpandoObjectToDictionaryWhenNoDesiredTypeResultConverter(enumerableConverter, wrapped); } - static IConvertsJintEvaluationResult GetTypeConverterConvertingService(IConvertsJintEvaluationResult wrapped) => new TypeConverterResultConverter(wrapped); - - static IConvertsJintEvaluationResult GetNullConvertingService(IConvertsJintEvaluationResult wrapped) => new NullResultConverter(wrapped); + private static IConvertsJintEvaluationResult GetTypeConverterConvertingService(IConvertsJintEvaluationResult wrapped) => new TypeConverterResultConverter(wrapped); + private static IConvertsJintEvaluationResult GetNullConvertingService(IConvertsJintEvaluationResult wrapped) => new NullResultConverter(wrapped); } } \ No newline at end of file diff --git a/src/scripting/Elsa.Scripting.JavaScript/Services/JintJavaScriptEvaluator.cs b/src/scripting/Elsa.Scripting.JavaScript/Services/JintJavaScriptEvaluator.cs index 36304c9eb..fd325466f 100644 --- a/src/scripting/Elsa.Scripting.JavaScript/Services/JintJavaScriptEvaluator.cs +++ b/src/scripting/Elsa.Scripting.JavaScript/Services/JintJavaScriptEvaluator.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Elsa.Scripting.JavaScript.Converters.Jint; using Elsa.Scripting.JavaScript.Messages; using Elsa.Scripting.JavaScript.Options; using Elsa.Services.Models; @@ -41,10 +42,11 @@ namespace Elsa.Scripting.JavaScript.Services return _resultConverter.ConvertToDesiredType(result, returnType); } - async Task GetConfiguredEngine(Action? configureEngine, ActivityExecutionContext context, CancellationToken cancellationToken) + private async Task GetConfiguredEngine(Action? configureEngine, ActivityExecutionContext context, CancellationToken cancellationToken) { var engine = new Engine(opts => { + opts.AddObjectConverter(); if (_scriptOptions.AllowClr) opts.AllowClr(); }); diff --git a/src/scripting/Elsa.Scripting.JavaScript/Services/NullResultConverter.cs b/src/scripting/Elsa.Scripting.JavaScript/Services/NullResultConverter.cs index 70f50bf53..51d1f5ec5 100644 --- a/src/scripting/Elsa.Scripting.JavaScript/Services/NullResultConverter.cs +++ b/src/scripting/Elsa.Scripting.JavaScript/Services/NullResultConverter.cs @@ -4,7 +4,7 @@ namespace Elsa.Scripting.JavaScript.Services { public class NullResultConverter : IConvertsJintEvaluationResult { - readonly IConvertsJintEvaluationResult wrapped; + private readonly IConvertsJintEvaluationResult wrapped; public object? ConvertToDesiredType(object? evaluationResult, Type desiredType) { diff --git a/src/scripting/Elsa.Scripting.JavaScript/Services/PlainObjectResultConverter.cs b/src/scripting/Elsa.Scripting.JavaScript/Services/PlainObjectResultConverter.cs index 4ba9638df..952f160a5 100644 --- a/src/scripting/Elsa.Scripting.JavaScript/Services/PlainObjectResultConverter.cs +++ b/src/scripting/Elsa.Scripting.JavaScript/Services/PlainObjectResultConverter.cs @@ -4,7 +4,7 @@ namespace Elsa.Scripting.JavaScript.Services { public class PlainObjectResultConverter : IConvertsJintEvaluationResult { - readonly IConvertsJintEvaluationResult wrapped; + private readonly IConvertsJintEvaluationResult wrapped; public object? ConvertToDesiredType(object? evaluationResult, Type desiredType) { diff --git a/src/scripting/Elsa.Scripting.JavaScript/Services/TypeConverterResultConverter.cs b/src/scripting/Elsa.Scripting.JavaScript/Services/TypeConverterResultConverter.cs index c9181216d..cb84096a6 100644 --- a/src/scripting/Elsa.Scripting.JavaScript/Services/TypeConverterResultConverter.cs +++ b/src/scripting/Elsa.Scripting.JavaScript/Services/TypeConverterResultConverter.cs @@ -5,7 +5,7 @@ namespace Elsa.Scripting.JavaScript.Services { public class TypeConverterResultConverter : IConvertsJintEvaluationResult { - readonly IConvertsJintEvaluationResult wrapped; + private readonly IConvertsJintEvaluationResult wrapped; public object? ConvertToDesiredType(object? evaluationResult, Type desiredType) {