diff --git a/src/apps/Elsa.Studio.Web/appsettings.json b/src/apps/Elsa.Studio.Web/appsettings.json new file mode 100644 index 000000000..7e26f0ec0 --- /dev/null +++ b/src/apps/Elsa.Studio.Web/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "ElsaServer": { + "Url": "https://localhost:5001/elsa/api" + }, + "Hosting": { + "BasePath": "" + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Expressions.JavaScript/Providers/VariableTypeDefinitionProvider.cs b/src/modules/Elsa.Expressions.JavaScript/Providers/VariableTypeDefinitionProvider.cs index f6bcd0b85..25f81738f 100644 --- a/src/modules/Elsa.Expressions.JavaScript/Providers/VariableTypeDefinitionProvider.cs +++ b/src/modules/Elsa.Expressions.JavaScript/Providers/VariableTypeDefinitionProvider.cs @@ -6,34 +6,32 @@ using Elsa.Expressions.JavaScript.TypeDefinitions.Models; using Elsa.Workflows.Management.Options; using JetBrains.Annotations; using Microsoft.Extensions.Options; - namespace Elsa.Expressions.JavaScript.Providers; - /// /// Produces s for variable types. /// -internal class VariableTypeDefinitionProvider(ITypeDescriber typeDescriber) : TypeDefinitionProvider +[UsedImplicitly] +internal class VariableTypeDefinitionProvider(ITypeDescriber typeDescriber, IOptions options) : TypeDefinitionProvider { protected override IEnumerable GetTypeDefinitions(TypeDefinitionContext context) { var excludedTypes = new Func[] { type => type == typeof(ExpandoObject), - type => typeof(IDictionary).IsAssignableFrom(type), - type => type == typeof(object) + type => type.IsPrimitive, + type => type.ContainsGenericParameters, + type => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>), + type => type == typeof(object), + type => type == typeof(string) }; - var variables = context.WorkflowGraph.Workflow.Variables; - - var variableTypeQuery = - from variable in variables - let variableType = variable.GetVariableType() - where (variableType.IsClass || variableType.IsInterface || variableType.IsEnum) && !variableType.IsPrimitive && !excludedTypes.Any(x => x(variableType)) + var variableTypes = + from variableDescriptor in options.Value.VariableDescriptors + let variableType = variableDescriptor.Type + where (variableType.IsClass || variableType.IsInterface || variableType.IsEnum) && !excludedTypes.Any(x => x(variableType)) select variableType; - var variableTypes = variableTypeQuery.Distinct(); - - foreach (var variableType in variableTypes) + foreach (var variableType in variableTypes.Distinct()) { yield return typeDescriber.DescribeType(variableType); } diff --git a/src/modules/Elsa.Expressions.JavaScript/TypeDefinitions/Services/TypeDefinitionDocumentRenderer.cs b/src/modules/Elsa.Expressions.JavaScript/TypeDefinitions/Services/TypeDefinitionDocumentRenderer.cs index fcc66000a..443886c65 100644 --- a/src/modules/Elsa.Expressions.JavaScript/TypeDefinitions/Services/TypeDefinitionDocumentRenderer.cs +++ b/src/modules/Elsa.Expressions.JavaScript/TypeDefinitions/Services/TypeDefinitionDocumentRenderer.cs @@ -1,9 +1,7 @@ using System.Text; using Elsa.Expressions.JavaScript.TypeDefinitions.Contracts; using Elsa.Expressions.JavaScript.TypeDefinitions.Models; - namespace Elsa.Expressions.JavaScript.TypeDefinitions.Services; - /// public class TypeDefinitionDocumentRenderer : ITypeDefinitionDocumentRenderer { @@ -11,19 +9,14 @@ public class TypeDefinitionDocumentRenderer : ITypeDefinitionDocumentRenderer public string Render(TypeDefinitionsDocument document) { var stringBuilder = new StringBuilder(); - foreach (var functionDefinition in document.Functions) Render(functionDefinition, stringBuilder); - foreach (var typeDefinition in document.Types) Render(typeDefinition, stringBuilder); - foreach (var variableDefinition in document.Variables) Render(variableDefinition, stringBuilder); - return stringBuilder.ToString(); } - private void Render(FunctionDefinition functionDefinition, StringBuilder output) { var returnType = functionDefinition.ReturnType != null ? $": {functionDefinition.ReturnType}" : ""; @@ -35,11 +28,9 @@ public class TypeDefinitionDocumentRenderer : ITypeDefinitionDocumentRenderer var returnType = functionDefinition.ReturnType != null ? $" => {functionDefinition.ReturnType}" : ""; output.AppendLine($"{functionDefinition.Name}: ({RenderParameters(functionDefinition.Parameters)}){returnType};"); } - private void Render(TypeDefinition typeDefinition, StringBuilder output) { output.AppendLine($"declare {typeDefinition.DeclarationKeyword} {typeDefinition.Name} {{"); - if (typeDefinition.DeclarationKeyword == "enum") { foreach (var property in typeDefinition.Properties) @@ -50,15 +41,13 @@ public class TypeDefinitionDocumentRenderer : ITypeDefinitionDocumentRenderer foreach (var property in typeDefinition.Properties) Render(property, output); } - foreach (var method in typeDefinition.Methods) RenderMethod(method, output); - output.AppendLine("}"); } private void Render(PropertyDefinition property, StringBuilder output) => output.AppendLine($"{property.Name}{(property.IsOptional ? "?" : "")}: {property.Type};"); - private void RenderEnumMember(PropertyDefinition property, StringBuilder output) => output.AppendLine($"{property.Name} = \"{property.Name}\";"); + private void RenderEnumMember(PropertyDefinition property, StringBuilder output) => output.AppendLine($"{property.Name} = \"{property.Name}\","); private void Render(VariableDefinition variable, StringBuilder output) => output.AppendLine($"declare var {variable.Name}: {variable.Type};"); string RenderParameter(ParameterDefinition parameter) => $"{parameter.Name}{(parameter.IsOptional ? "?" : "")}: {parameter.Type}"; string RenderParameters(IEnumerable parameters) => string.Join(", ", parameters.Select(RenderParameter)); diff --git a/src/modules/Elsa.Expressions.JavaScript/TypeDefinitions/Services/TypeDescriber.cs b/src/modules/Elsa.Expressions.JavaScript/TypeDefinitions/Services/TypeDescriber.cs index fc7079e98..9f4a141fb 100644 --- a/src/modules/Elsa.Expressions.JavaScript/TypeDefinitions/Services/TypeDescriber.cs +++ b/src/modules/Elsa.Expressions.JavaScript/TypeDefinitions/Services/TypeDescriber.cs @@ -1,17 +1,15 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Runtime.CompilerServices; using Elsa.Extensions; using Elsa.Expressions.JavaScript.Contracts; using Elsa.Expressions.JavaScript.TypeDefinitions.Contracts; using Elsa.Expressions.JavaScript.TypeDefinitions.Models; - namespace Elsa.Expressions.JavaScript.TypeDefinitions.Services; - /// public class TypeDescriber : ITypeDescriber { private readonly ITypeAliasRegistry _typeAliasRegistry; - /// /// Constructor. /// @@ -30,17 +28,18 @@ public class TypeDescriber : ITypeDescriber Properties = GetPropertyDefinitions(type).DistinctBy(x => x.Name).ToList(), Methods = GetMethodDefinitions(type).DistinctBy(x => x.Name).ToList() }; - return typeDefinition; } - private IEnumerable GetMethodDefinitions(Type type) { if(type.IsEnum) yield break; - + #pragma warning disable IL2070 - var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static).Where(x => !x.IsSpecialName).ToList(); + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) + .Where(x => !x.IsSpecialName) + .Where(x => x.GetCustomAttribute() == null) + .ToList(); #pragma warning restore IL2070 foreach (var method in methods) @@ -53,11 +52,9 @@ public class TypeDescriber : ITypeDescriber }; } } - private IEnumerable GetMethodParameters(MethodInfo method) { var parameters = method.GetParameters(); - foreach (var parameter in parameters) { yield return new ParameterDefinition @@ -68,7 +65,6 @@ public class TypeDescriber : ITypeDescriber }; } } - private IEnumerable GetPropertyDefinitions([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { // If the type is an enum, enumerate its members. @@ -83,12 +79,10 @@ public class TypeDescriber : ITypeDescriber IsOptional = false, }; } - yield break; } var properties = type.GetProperties(); - foreach (var property in properties) { yield return new PropertyDefinition @@ -99,7 +93,6 @@ public class TypeDescriber : ITypeDescriber }; } } - private static string GetDeclarationKeyword(Type type) => type switch { diff --git a/src/modules/Elsa.Http/Activities/SendHttpRequest.cs b/src/modules/Elsa.Http/Activities/SendHttpRequest.cs index 7b27571c4..6a3b150fe 100644 --- a/src/modules/Elsa.Http/Activities/SendHttpRequest.cs +++ b/src/modules/Elsa.Http/Activities/SendHttpRequest.cs @@ -1,9 +1,7 @@ using System.Runtime.CompilerServices; using Elsa.Workflows; using Elsa.Workflows.Attributes; - namespace Elsa.Http; - /// /// Send an HTTP request. /// @@ -14,7 +12,6 @@ public class SendHttpRequest : SendHttpRequestBase public SendHttpRequest([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } - /// /// A list of expected status codes to handle and the corresponding activity to execute when the status code matches. /// @@ -23,21 +20,21 @@ public class SendHttpRequest : SendHttpRequestBase UIHint = "http-status-codes" )] public ICollection ExpectedStatusCodes { get; set; } = new List(); - /// /// The activity to execute when the HTTP status code does not match any of the expected status codes. /// [Port] public IActivity? UnmatchedStatusCode { get; set; } - /// /// The activity to execute when the HTTP request fails to connect. /// + [Port] public IActivity? FailedToConnect { get; set; } - + /// /// The activity to execute when the HTTP request times out. /// + [Port] public IActivity? Timeout { get; set; } /// @@ -47,22 +44,18 @@ public class SendHttpRequest : SendHttpRequestBase var statusCode = (int)response.StatusCode; var matchingCase = expectedStatusCodes.FirstOrDefault(x => x.StatusCode == statusCode); var activity = matchingCase != null ? matchingCase.Activity : UnmatchedStatusCode; - await context.ScheduleActivityAsync(activity, OnChildActivityCompletedAsync); } - /// protected override async ValueTask HandleRequestExceptionAsync(ActivityExecutionContext context, HttpRequestException exception) { await context.ScheduleActivityAsync(FailedToConnect, OnChildActivityCompletedAsync); } - /// protected override async ValueTask HandleTaskCanceledExceptionAsync(ActivityExecutionContext context, TaskCanceledException exception) { await context.ScheduleActivityAsync(Timeout, OnChildActivityCompletedAsync); } - private async ValueTask OnChildActivityCompletedAsync(ActivityCompletedContext context) { await context.TargetContext.CompleteActivityAsync(); diff --git a/src/modules/Elsa.Http/Features/HttpFeature.cs b/src/modules/Elsa.Http/Features/HttpFeature.cs index 3940688e3..a12022186 100644 --- a/src/modules/Elsa.Http/Features/HttpFeature.cs +++ b/src/modules/Elsa.Http/Features/HttpFeature.cs @@ -26,9 +26,7 @@ using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; - namespace Elsa.Http.Features; - /// /// Installs services related to HTTP services and activities. /// @@ -38,32 +36,26 @@ public class HttpFeature(IModule module) : FeatureBase(module) { private Func _httpEndpointRouteProvider = sp => sp.GetRequiredService(); private Func _httpEndpointBasePathProvider = sp => sp.GetRequiredService(); - /// /// A delegate to configure . /// public Action? ConfigureHttpOptions { get; set; } - /// /// A delegate to configure . /// public Action? ConfigureHttpFileCacheOptions { get; set; } - /// /// A delegate that is invoked when authorizing an inbound HTTP request. /// public Func HttpEndpointAuthorizationHandler { get; set; } = sp => sp.GetRequiredService(); - /// /// A delegate that is invoked when an HTTP workflow faults. /// public Func HttpEndpointWorkflowFaultHandler { get; set; } = sp => sp.GetRequiredService(); - /// /// A delegate to configure the . /// public Func ContentTypeProvider { get; set; } = _ => new FileExtensionContentTypeProvider(); - /// /// A delegate to configure the . /// @@ -75,7 +67,7 @@ public class HttpFeature(IModule module) : FeatureBase(module) }; /// - /// A delegate to configure the used when by the activity. + /// A delegate to configure the used when by the and activities. /// public Action HttpClient { get; set; } = (_, _) => { }; @@ -83,7 +75,6 @@ public class HttpFeature(IModule module) : FeatureBase(module) /// A delegate to configure the for . /// public Action HttpClientBuilder { get; set; } = _ => { }; - /// /// A list of types to register with the service collection. /// @@ -92,7 +83,6 @@ public class HttpFeature(IModule module) : FeatureBase(module) typeof(HeaderHttpCorrelationIdSelector), typeof(QueryStringHttpCorrelationIdSelector) }; - /// /// A list of types to register with the service collection. /// @@ -101,12 +91,10 @@ public class HttpFeature(IModule module) : FeatureBase(module) typeof(HeaderHttpWorkflowInstanceIdSelector), typeof(QueryStringHttpWorkflowInstanceIdSelector) }; - public HttpFeature WithHttpEndpointRoutesProvider() where T : IHttpEndpointRoutesProvider { return WithHttpEndpointRoutesProvider(sp => sp.GetRequiredService()); } - public HttpFeature WithHttpEndpointRoutesProvider(Func httpEndpointRouteProvider) { _httpEndpointRouteProvider = httpEndpointRouteProvider; @@ -124,7 +112,6 @@ public class HttpFeature(IModule module) : FeatureBase(module) _httpEndpointBasePathProvider = httpEndpointBasePathProvider; return this; } - /// public override void Configure() { @@ -140,13 +127,10 @@ public class HttpFeature(IModule module) : FeatureBase(module) typeof(HttpFile), typeof(Downloadable) ], "HTTP"); - management.AddActivitiesFrom(); }); - Module.UseResilience(resilience => resilience.AddResilienceStrategyType()); } - /// public override void Apply() { @@ -155,15 +139,11 @@ public class HttpFeature(IModule module) : FeatureBase(module) options.BasePath = "/workflows"; options.BaseUrl = new Uri("http://localhost"); }); - var configureFileCacheOptions = ConfigureHttpFileCacheOptions ?? (options => { options.TimeToLive = TimeSpan.FromDays(7); }); - Services.Configure(configureOptions); Services.Configure(configureFileCacheOptions); - var httpClientBuilder = Services.AddHttpClient(HttpClient); HttpClientBuilder(httpClientBuilder); - Services .AddScoped() .AddScoped() @@ -172,31 +152,25 @@ public class HttpFeature(IModule module) : FeatureBase(module) .AddScoped() .AddScoped(ContentTypeProvider) .AddHttpContextAccessor() - // Handlers. .AddNotificationHandler() - // Content parsers. .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() - // HTTP content factories. .AddScoped() .AddScoped() .AddScoped() .AddScoped() - // Activity property options providers. .AddScoped() .AddScoped() .AddScoped(_httpEndpointBasePathProvider) - // Port resolvers. .AddScoped() - // HTTP endpoint handlers. .AddScoped() .AddScoped() @@ -209,7 +183,6 @@ public class HttpFeature(IModule module) : FeatureBase(module) // Startup tasks. .AddStartupTask() - // Downloadable content handlers. .AddScoped() .AddScoped() @@ -220,28 +193,21 @@ public class HttpFeature(IModule module) : FeatureBase(module) .AddScoped() .AddScoped() .AddScoped() - //Trigger payload validators. .AddTriggerPayloadValidator() - // File caches. .AddScoped(FileCache) .AddScoped() - // AuthenticationBasedHttpEndpointAuthorizationHandler requires Authorization services. // We could consider creating a separate module for installing authorization services. .AddAuthorization(); - // HTTP clients. Services.AddHttpClient(); - // Add selectors. foreach (var httpCorrelationIdSelectorType in HttpCorrelationIdSelectorTypes) Services.AddScoped(typeof(IHttpCorrelationIdSelector), httpCorrelationIdSelectorType); - foreach (var httpWorkflowInstanceIdSelectorType in HttpWorkflowInstanceIdSelectorTypes) Services.AddScoped(typeof(IHttpWorkflowInstanceIdSelector), httpWorkflowInstanceIdSelectorType); - Services.Configure(options => { options.AddTypeAlias("HttpRequest"); diff --git a/src/modules/Elsa.Http/PortResolvers/SendHttpRequestActivityPortResolver.cs b/src/modules/Elsa.Http/PortResolvers/SendHttpRequestActivityPortResolver.cs index 92dcd991d..decd7e90d 100644 --- a/src/modules/Elsa.Http/PortResolvers/SendHttpRequestActivityPortResolver.cs +++ b/src/modules/Elsa.Http/PortResolvers/SendHttpRequestActivityPortResolver.cs @@ -1,8 +1,6 @@ using Elsa.Workflows; using Elsa.Workflows.Models; - namespace Elsa.Http.PortResolvers; - /// /// Returns a list of outbound activities for a given activity's expected status codes. /// @@ -10,32 +8,25 @@ public class SendHttpRequestActivityResolver : IActivityResolver { /// public int Priority => 0; - /// public bool GetSupportsActivity(IActivity activity) => activity is SendHttpRequest; - - /// public ValueTask> GetActivityPortsAsync(IActivity activity, CancellationToken cancellationToken = default) { - IEnumerable ports = GetPortsInternal(activity); - return new ValueTask>(ports); + var ports = GetPortsInternal(activity); + return new(ports); } private IEnumerable GetPortsInternal(IActivity activity) { var sendHttpRequest = (SendHttpRequest)activity; var cases = sendHttpRequest.ExpectedStatusCodes.Where(x => x.Activity != null); - foreach (var @case in cases) yield return ActivityPort.FromActivity(@case.Activity!, @case.StatusCode.ToString()); - if (sendHttpRequest.Timeout != null) yield return ActivityPort.FromActivity(sendHttpRequest.Timeout, nameof(SendHttpRequest.Timeout)); - if (sendHttpRequest.FailedToConnect != null) yield return ActivityPort.FromActivity(sendHttpRequest.FailedToConnect, nameof(SendHttpRequest.FailedToConnect)); - if (sendHttpRequest.UnmatchedStatusCode != null) yield return ActivityPort.FromActivity(sendHttpRequest.UnmatchedStatusCode, nameof(SendHttpRequest.UnmatchedStatusCode)); } diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/Bookmarks/Resume/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/Bookmarks/Resume/Endpoint.cs index 832b60f59..2583e180b 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/Bookmarks/Resume/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/Bookmarks/Resume/Endpoint.cs @@ -4,14 +4,12 @@ using Elsa.Workflows.Runtime; using FastEndpoints; using JetBrains.Annotations; using Microsoft.AspNetCore.Http; - namespace Elsa.Workflows.Api.Endpoints.Bookmarks.Resume; - /// /// Resumes a bookmarked workflow instance with the bookmark ID specified in the provided SAS token. /// [PublicAPI] -internal class Resume(ITokenService tokenService, IBookmarkQueue bookmarkQueue, IPayloadSerializer serializer) : ElsaEndpoint +internal class Resume(ITokenService tokenService, IWorkflowResumer workflowResumer, IBookmarkQueue bookmarkQueue, IPayloadSerializer serializer) : ElsaEndpoint { /// public override void Configure() @@ -20,25 +18,27 @@ internal class Resume(ITokenService tokenService, IBookmarkQueue bookmarkQueue, Verbs(Http.GET, Http.POST); AllowAnonymous(); } - /// public override async Task HandleAsync(Request request, CancellationToken cancellationToken) { var token = Query("t")!; + var asynchronous = Query("async", false); if (!tokenService.TryDecryptToken(token, out var payload)) AddError("Invalid token."); - var input = HttpContext.Request.Method == HttpMethods.Post ? request.Input : GetInputFromQueryString(); - if (ValidationFailed) { await Send.ErrorsAsync(cancellation: cancellationToken); return; } - - await ResumeBookmarkedWorkflowAsync(payload, input, cancellationToken); - + + // Some clients, like Blazor, may prematurely cancel their request upon navigation away from the page. + // In this case, we don't want to cancel the workflow execution. + // We need to better understand the conditions that cause this. + var workflowCancellationToken = CancellationToken.None; + await ResumeBookmarkedWorkflowAsync(payload, input, asynchronous, workflowCancellationToken); + if (!HttpContext.Response.HasStarted) await Send.OkAsync(cancellationToken); } @@ -48,7 +48,6 @@ internal class Resume(ITokenService tokenService, IBookmarkQueue bookmarkQueue, var inputJson = Query("in", false); if (string.IsNullOrWhiteSpace(inputJson)) return null; - try { return serializer.Deserialize>(inputJson); @@ -59,21 +58,36 @@ internal class Resume(ITokenService tokenService, IBookmarkQueue bookmarkQueue, return null; } } - - private async Task ResumeBookmarkedWorkflowAsync(BookmarkTokenPayload tokenPayload, IDictionary? input, CancellationToken cancellationToken) + + private async Task ResumeBookmarkedWorkflowAsync(BookmarkTokenPayload tokenPayload, IDictionary? input, bool asynchronous, CancellationToken cancellationToken) { var bookmarkId = tokenPayload.BookmarkId; var workflowInstanceId = tokenPayload.WorkflowInstanceId; - var item = new NewBookmarkQueueItem + + if (asynchronous) + { + var item = new NewBookmarkQueueItem + { + BookmarkId = bookmarkId, + WorkflowInstanceId = workflowInstanceId, + Options = new() + { + Input = input + } + }; + + await bookmarkQueue.EnqueueAsync(item, cancellationToken); + return; + } + + var resumeRequest = new ResumeBookmarkRequest { BookmarkId = bookmarkId, WorkflowInstanceId = workflowInstanceId, - Options = new() - { - Input = input - } + Input = input }; - await bookmarkQueue.EnqueueAsync(item, cancellationToken); + + await workflowResumer.ResumeAsync(resumeRequest, cancellationToken); } } diff --git a/src/modules/Elsa.Workflows.Core/Exceptions/InputEvaluationException.cs b/src/modules/Elsa.Workflows.Core/Exceptions/InputEvaluationException.cs new file mode 100644 index 000000000..b067f53e2 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/Exceptions/InputEvaluationException.cs @@ -0,0 +1,6 @@ +namespace Elsa.Workflows.Exceptions; + +public class InputEvaluationException(string inputName, string message, Exception exception) : Exception(message, exception) +{ + public string InputName { get; } = inputName; +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.InputEvaluation.cs b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.InputEvaluation.cs index 093ede7f1..c10a9ac38 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.InputEvaluation.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.InputEvaluation.cs @@ -3,11 +3,11 @@ using Elsa.Expressions.Contracts; using Elsa.Expressions.Helpers; using Elsa.Expressions.Models; using Elsa.Workflows; +using Elsa.Workflows.Exceptions; using Elsa.Workflows.Models; // ReSharper disable once CheckNamespace namespace Elsa.Extensions; - public static partial class ActivityExecutionContextExtensions { /// @@ -17,14 +17,11 @@ public static partial class ActivityExecutionContextExtensions { var activityDescriptor = context.ActivityDescriptor; var inputDescriptors = activityDescriptor.Inputs.Where(x => x.AutoEvaluate).ToList(); - // Evaluate inputs. foreach (var inputDescriptor in inputDescriptors) await EvaluateInputPropertyAsync(context, activityDescriptor, inputDescriptor); - context.SetHasEvaluatedProperties(); } - /// /// Evaluates the specified input property of the activity. /// @@ -34,7 +31,6 @@ public static partial class ActivityExecutionContextExtensions var input = await EvaluateInputPropertyAsync(context, inputName); return input.ConvertTo(); } - /// /// Evaluates a specific input property of the activity. /// @@ -44,13 +40,10 @@ public static partial class ActivityExecutionContextExtensions var activityRegistryLookup = context.GetRequiredService(); var activityDescriptor = await activityRegistryLookup.FindAsync(activity.Type) ?? throw new Exception("Activity descriptor not found"); var inputDescriptor = activityDescriptor.GetWrappedInputPropertyDescriptor(activity, inputName); - if (inputDescriptor == null) throw new Exception($"No input with name {inputName} could be found"); - return await EvaluateInputPropertyAsync(context, activityDescriptor, inputDescriptor); } - /// /// Evaluates the specified input and sets the result in the activity execution context's memory space. /// @@ -66,19 +59,29 @@ public static partial class ActivityExecutionContextExtensions memoryBlockReference.Set(context, value); return value; } - + private static async Task EvaluateInputPropertyAsync(this ActivityExecutionContext context, ActivityDescriptor activityDescriptor, InputDescriptor inputDescriptor) + { + try + { + return await EvaluateInputPropertyCoreAsync(context, activityDescriptor, inputDescriptor); + } + catch (Exception e) + { + throw new InputEvaluationException(inputDescriptor.Name, $"Failed to evaluate activity input '{inputDescriptor.Name}'", e); + } + } + + private static async Task EvaluateInputPropertyCoreAsync(this ActivityExecutionContext context, ActivityDescriptor activityDescriptor, InputDescriptor inputDescriptor) { var activity = context.Activity; var defaultValue = inputDescriptor.DefaultValue; var value = defaultValue; var input = inputDescriptor.ValueGetter(activity); var identityGenerator = context.GetRequiredService(); - if (inputDescriptor.IsWrapped) { var wrappedInput = (Input?)input; - if (defaultValue != null && wrappedInput == null) { var typedInput = typeof(Input<>).MakeGenericType(inputDescriptor.Type); @@ -94,7 +97,6 @@ public static partial class ActivityExecutionContextExtensions var expressionEvaluator = context.GetRequiredService(); var expressionExecutionContext = context.ExpressionExecutionContext; var inputEvaluatorType = inputDescriptor.EvaluatorType ?? typeof(DefaultActivityInputEvaluator); - if (wrappedInput?.Expression != null) { var inputEvaluator = (IActivityInputEvaluator)context.GetRequiredService(inputEvaluatorType); @@ -102,9 +104,7 @@ public static partial class ActivityExecutionContextExtensions value = await inputEvaluator.EvaluateAsync(inputEvaluatorContext); } } - var memoryReference = wrappedInput?.MemoryBlockReference(); - if (memoryReference != null) { // When input is created from an activity provider, there may be no memory block reference ID. @@ -119,9 +119,7 @@ public static partial class ActivityExecutionContextExtensions { value = input; } - await StoreInputValueAsync(context, inputDescriptor, value!); - return value; } @@ -138,7 +136,6 @@ public static partial class ActivityExecutionContextExtensions // var filterResult = await manager.RunFiltersAsync(filterContext); context.ActivityState[inputDescriptor.Name] = value; } - return Task.CompletedTask; } } \ No newline at end of file