From aaab19509ebe43d75c8e04e7606c6823df56f7f7 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 15 Jan 2025 10:50:17 +0100 Subject: [PATCH 1/3] Fix array type handling in ArgumentJsonConverter Previously, the code incorrectly used `MakeCollectionType` for arrays. This has been corrected to use `MakeArrayType`, ensuring proper deserialization of array arguments. --- .../Elsa.Workflows.Api/Serialization/ArgumentJsonConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Elsa.Workflows.Api/Serialization/ArgumentJsonConverter.cs b/src/modules/Elsa.Workflows.Api/Serialization/ArgumentJsonConverter.cs index 0900930e8..81f5a0d42 100644 --- a/src/modules/Elsa.Workflows.Api/Serialization/ArgumentJsonConverter.cs +++ b/src/modules/Elsa.Workflows.Api/Serialization/ArgumentJsonConverter.cs @@ -44,7 +44,7 @@ public class ArgumentJsonConverter : JsonConverter var type = _wellKnownTypeRegistry.GetTypeOrDefault(typeName); if (isArray) - type = type.MakeCollectionType(); + type = type.MakeArrayType(); var newOptions = new JsonSerializerOptions(options); newOptions.Converters.RemoveWhere(x => x is ArgumentJsonConverterFactory); From 2c3843d5f5f3ed536299b085c50787a5473603b5 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 15 Jan 2025 10:50:34 +0100 Subject: [PATCH 2/3] Refactor variable mapping and improve type alias handling Updated `VariableMapper` for null assignment consistency and streamlined `VariableModel` instantiation. Improved `TypeJsonConverter` to handle list type aliases more explicitly, replacing ambiguous syntax with clearer format. --- .../Serialization/Converters/TypeJsonConverter.cs | 6 +++--- src/modules/Elsa.Workflows.Core/Services/VariableMapper.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Converters/TypeJsonConverter.cs b/src/modules/Elsa.Workflows.Core/Serialization/Converters/TypeJsonConverter.cs index 816b5fb5b..5fe74064e 100644 --- a/src/modules/Elsa.Workflows.Core/Serialization/Converters/TypeJsonConverter.cs +++ b/src/modules/Elsa.Workflows.Core/Serialization/Converters/TypeJsonConverter.cs @@ -40,9 +40,9 @@ public class TypeJsonConverter : JsonConverter } // Handle collection types. - if (typeAlias.EndsWith("()")) + if (typeAlias.StartsWith("List<") && typeAlias.EndsWith(">")) { - var elementTypeAlias = typeAlias[..^"()".Length]; + var elementTypeAlias = typeAlias[5..^1]; var elementType = _wellKnownTypeRegistry.TryGetType(elementTypeAlias, out var t) ? t : Type.GetType(elementTypeAlias)!; return typeof(List<>).MakeGenericType(elementType); } @@ -70,7 +70,7 @@ public class TypeJsonConverter : JsonConverter if (typedEnumerable.IsAssignableFrom(value) && _wellKnownTypeRegistry.TryGetAlias(elementType, out var elementTypeAlias)) { - writer.WriteStringValue($"{elementTypeAlias}()"); + writer.WriteStringValue($"List<{elementTypeAlias}>"); return; } } diff --git a/src/modules/Elsa.Workflows.Core/Services/VariableMapper.cs b/src/modules/Elsa.Workflows.Core/Services/VariableMapper.cs index 2524822c9..37899fc1b 100644 --- a/src/modules/Elsa.Workflows.Core/Services/VariableMapper.cs +++ b/src/modules/Elsa.Workflows.Core/Services/VariableMapper.cs @@ -59,7 +59,7 @@ public class VariableMapper .OnSuccess(value => variable.Value = value) .OnFailure(e => _logger.LogWarning("Failed to convert {SourceValue} to {TargetType}", source.Value, type.Name)); - variable.StorageDriverType = !string.IsNullOrEmpty(source.StorageDriverTypeName) ? Type.GetType(source.StorageDriverTypeName) : default; + variable.StorageDriverType = !string.IsNullOrEmpty(source.StorageDriverTypeName) ? Type.GetType(source.StorageDriverTypeName) : null; return variable; } @@ -76,6 +76,6 @@ public class VariableMapper var storageDriverTypeName = source.StorageDriverType?.GetSimpleAssemblyQualifiedName(); var serializedValue = value.Format(); - return new VariableModel(source.Id, source.Name, valueTypeAlias, serializedValue, storageDriverTypeName); + return new(source.Id, source.Name, valueTypeAlias, serializedValue, storageDriverTypeName); } } \ No newline at end of file From 18765cc386cd3b178548d80d74e134de23dffb46 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Wed, 15 Jan 2025 11:17:21 +0100 Subject: [PATCH 3/3] Refactor route normalization logic Simplified route normalization logic by removing unnecessary lowercase conversion and adjusted usages accordingly. These changes improve code readability, maintain consistency and fixes that route values are stored lower-cased instead of original-cased. --- .../Elsa.Http/Activities/HttpEndpoint.cs | 30 +++++++++---------- .../Elsa.Http/Extensions/RouteExtensions.cs | 4 +-- .../Middleware/HttpWorkflowsMiddleware.cs | 6 ++-- .../Elsa.Http/Services/RouteMatcher.cs | 6 ++-- .../WorkflowDefinitions/Post/Endpoint.cs | 2 +- 5 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/modules/Elsa.Http/Activities/HttpEndpoint.cs b/src/modules/Elsa.Http/Activities/HttpEndpoint.cs index dc148b15f..428d73adc 100644 --- a/src/modules/Elsa.Http/Activities/HttpEndpoint.cs +++ b/src/modules/Elsa.Http/Activities/HttpEndpoint.cs @@ -23,10 +23,10 @@ namespace Elsa.Http; public class HttpEndpoint : Trigger { internal const string HttpContextInputKey = "HttpContext"; - internal const string RequestPathInputKey = "RequestPath"; + internal const string PathInputKey = "Path"; /// - public HttpEndpoint([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public HttpEndpoint([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } @@ -38,7 +38,7 @@ public class HttpEndpoint : Trigger UIHint = InputUIHints.SingleLine, UIHandler = typeof(HttpEndpointPathUIHandler) )] - public Input Path { get; set; } = default!; + public Input Path { get; set; } = null!; /// /// The HTTP methods to accept. @@ -65,37 +65,37 @@ public class HttpEndpoint : Trigger /// The maximum time allowed to process the request. /// [Input(Description = "The maximum time allowed to process the request.", Category = "Upload")] - public Input RequestTimeout { get; set; } = default!; + public Input RequestTimeout { get; set; } = null!; /// /// The maximum request size allowed in bytes. /// [Input(Description = "The maximum request size allowed in bytes.", Category = "Upload")] - public Input RequestSizeLimit { get; set; } = default!; + public Input RequestSizeLimit { get; set; } = null!; /// /// The maximum request size allowed in bytes. /// [Input(Description = "The maximum file size allowed in bytes for an individual file.", Category = "Upload")] - public Input FileSizeLimit { get; set; } = default!; + public Input FileSizeLimit { get; set; } = null!; /// /// The allowed file extensions, /// [Input(Description = "Only file extensions in this list are allowed. Leave empty to allow all extensions", Category = "Upload", UIHint = InputUIHints.MultiText)] - public Input> AllowedFileExtensions { get; set; } = default!; + public Input> AllowedFileExtensions { get; set; } = null!; /// /// The allowed file extensions, /// [Input(Description = "File extensions in this list are forbidden. Leave empty to not block any extension.", Category = "Upload", UIHint = InputUIHints.MultiText)] - public Input> BlockedFileExtensions { get; set; } = default!; + public Input> BlockedFileExtensions { get; set; } = null!; /// /// The allowed file extensions, /// [Input(Description = "Only MIME types in this list are allowed. Leave empty to allow all types", Category = "Upload", UIHint = InputUIHints.MultiText)] - public Input> AllowedMimeTypes { get; set; } = default!; + public Input> AllowedMimeTypes { get; set; } = null!; /// /// A value indicating whether to expose the "Request too large" outcome. @@ -125,31 +125,31 @@ public class HttpEndpoint : Trigger /// The parsed request content, if any. /// [Output(Description = "The parsed request content, if any.")] - public Output ParsedContent { get; set; } = default!; + public Output ParsedContent { get; set; } = null!; /// /// The uploaded files, if any. /// [Output(Description = "The uploaded files, if any.", IsSerializable = false)] - public Output Files { get; set; } = default!; + public Output Files { get; set; } = null!; /// /// The parsed route data, if any. /// [Output(Description = "The parsed route data, if any.")] - public Output> RouteData { get; set; } = default!; + public Output> RouteData { get; set; } = null!; /// /// The querystring data, if any. /// [Output(Description = "The querystring data, if any.")] - public Output> QueryStringData { get; set; } = default!; + public Output> QueryStringData { get; set; } = null!; /// /// The headers, if any. /// [Output(Description = "The headers, if any.")] - public Output> Headers { get; set; } = default!; + public Output> Headers { get; set; } = null!; /// protected override IEnumerable GetTriggerPayloads(TriggerIndexingContext context) => GetBookmarkPayloads(context.ExpressionExecutionContext); @@ -205,7 +205,7 @@ public class HttpEndpoint : Trigger context.Set(Result, request); // Read route data, if any. - var path = context.GetWorkflowInput(RequestPathInputKey); + var path = context.GetWorkflowInput(PathInputKey); var routeData = GetRouteData(httpContext, path); var routeDictionary = routeData.Values.ToDictionary(route => route.Key, route => route.Value!); var queryStringDictionary = httpContext.Request.Query.ToObjectDictionary(); diff --git a/src/modules/Elsa.Http/Extensions/RouteExtensions.cs b/src/modules/Elsa.Http/Extensions/RouteExtensions.cs index a59e69013..79a9d0220 100644 --- a/src/modules/Elsa.Http/Extensions/RouteExtensions.cs +++ b/src/modules/Elsa.Http/Extensions/RouteExtensions.cs @@ -8,7 +8,7 @@ namespace Elsa.Extensions; public static class RouteExtensions { /// - /// Normalizes a route by ensuring a leading slash, removing any trailing slash and converting the path to lowercase. + /// Normalizes a route by ensuring a leading slash, removing any trailing slash. /// - public static string NormalizeRoute(this string path) => $"/{path.Trim('/').ToLowerInvariant()}"; + public static string NormalizeRoute(this string path) => $"/{path.Trim('/')}"; } \ No newline at end of file diff --git a/src/modules/Elsa.Http/Middleware/HttpWorkflowsMiddleware.cs b/src/modules/Elsa.Http/Middleware/HttpWorkflowsMiddleware.cs index bc0d81194..87cb34100 100644 --- a/src/modules/Elsa.Http/Middleware/HttpWorkflowsMiddleware.cs +++ b/src/modules/Elsa.Http/Middleware/HttpWorkflowsMiddleware.cs @@ -39,7 +39,7 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, ITenantAccessor tenan [RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Serialize(TValue, JsonSerializerOptions)")] public async Task InvokeAsync(HttpContext httpContext, IServiceProvider serviceProvider) { - var path = GetPath(httpContext); + var path = httpContext.Request.Path.Value!.NormalizeRoute(); var matchingPath = GetMatchingRoute(serviceProvider, path).Route; var basePath = options.Value.BasePath?.ToString().NormalizeRoute(); @@ -61,7 +61,7 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, ITenantAccessor tenan var input = new Dictionary { [HttpEndpoint.HttpContextInputKey] = true, - [HttpEndpoint.RequestPathInputKey] = path.NormalizeRoute() + [HttpEndpoint.PathInputKey] = path }; var cancellationToken = httpContext.RequestAborted; @@ -325,8 +325,6 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, ITenantAccessor tenan } } - private string GetPath(HttpContext httpContext) => httpContext.Request.Path.Value!.NormalizeRoute(); - [RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Serialize(TValue, JsonSerializerOptions)")] private async Task HandleMultipleWorkflowsFoundAsync(HttpContext httpContext, Func> workflowMatches, CancellationToken cancellationToken) { diff --git a/src/modules/Elsa.Http/Services/RouteMatcher.cs b/src/modules/Elsa.Http/Services/RouteMatcher.cs index b72e59d1b..4d9de3ae7 100644 --- a/src/modules/Elsa.Http/Services/RouteMatcher.cs +++ b/src/modules/Elsa.Http/Services/RouteMatcher.cs @@ -12,13 +12,11 @@ public class RouteMatcher : IRouteMatcher /// public RouteValueDictionary? Match(string routeTemplate, string route) { - var normalizedRoute = route.NormalizeRoute(); - var normalizedRouteTemplate = routeTemplate.NormalizeRoute(); - var template = TemplateParser.Parse(normalizedRouteTemplate); + var template = TemplateParser.Parse(routeTemplate); var matcher = new TemplateMatcher(template, GetDefaults(template)); var values = new RouteValueDictionary(); - return matcher.TryMatch(normalizedRoute, values) ? values : null; + return matcher.TryMatch(route, values) ? values : null; } private static RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate) diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs index b8ff04d48..4b1ba32f4 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs @@ -43,7 +43,7 @@ internal class Post( var draft = !string.IsNullOrWhiteSpace(definitionId) ? await workflowDefinitionPublisher.GetDraftAsync(definitionId, VersionOptions.Latest, cancellationToken) - : default; + : null; var isNew = draft == null;