diff --git a/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs b/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs index 9533f4652..919d45b38 100644 --- a/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs +++ b/src/modules/Elsa.Workflows.Core/Services/ActivityDescriber.cs @@ -22,11 +22,12 @@ public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolve { var activityAttr = activityType.GetCustomAttribute(); var ns = activityAttr?.Namespace ?? ActivityTypeNameHelper.GenerateNamespace(activityType) ?? "Elsa"; - var typeName = activityAttr?.Type ?? activityType.Name; + var friendlyName = GetFriendlyActivityName(activityType); + var typeName = activityAttr?.Type ?? friendlyName; var typeVersion = activityAttr?.Version ?? 1; var fullTypeName = ActivityTypeNameHelper.GenerateTypeName(activityType); var displayNameAttr = activityType.GetCustomAttribute(); - var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? typeName.Humanize(LetterCasing.Title); + var displayName = displayNameAttr?.DisplayName ?? activityAttr?.DisplayName ?? friendlyName.Humanize(LetterCasing.Title); var categoryAttr = activityType.GetCustomAttribute(); var category = categoryAttr?.Category ?? activityAttr?.Category ?? ActivityTypeNameHelper.GetCategoryFromNamespace(ns) ?? "Miscellaneous"; var descriptionAttr = activityType.GetCustomAttribute(); @@ -42,16 +43,19 @@ public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolve Name = portAttr?.Name ?? prop.Name, DisplayName = portAttr?.DisplayName ?? portAttr?.Name ?? prop.Name, Type = PortType.Embedded, - IsBrowsable = portAttr != null && (portBrowsableAttr == null || portBrowsableAttr.Browsable) + IsBrowsable = portAttr != null && (portBrowsableAttr == null || portBrowsableAttr.Browsable), }; var flowNodeAttr = activityType.GetCustomAttribute(); - var flowPorts = flowNodeAttr?.Outcomes.Select(x => new Port - { - Type = PortType.Flow, - Name = x, - DisplayName = x - }).ToDictionary(x => x.Name) ?? new Dictionary(); + var flowPorts = + flowNodeAttr + ?.Outcomes.Select(x => new Port + { + Type = PortType.Flow, + Name = x, + DisplayName = x, + }) + .ToDictionary(x => x.Name) ?? new Dictionary(); var allPorts = embeddedPorts.Concat(flowPorts.Values); var inputProperties = GetInputProperties(activityType).ToList(); @@ -90,7 +94,7 @@ public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolve var activity = activityFactory.Create(activityType, context); activity.Type = fullTypeName; return activity; - } + }, }; // If the activity has a default output, set its IsSerializable property to the value of the OutputAttribute.IsSerializable property. @@ -106,12 +110,10 @@ public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolve } /// - public IEnumerable GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType) => - activityType.GetProperties().Where(x => typeof(Input).IsAssignableFrom(x.PropertyType) || x.GetCustomAttribute() != null).DistinctBy(x => x.Name); + public IEnumerable GetInputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType) => activityType.GetProperties().Where(x => typeof(Input).IsAssignableFrom(x.PropertyType) || x.GetCustomAttribute() != null).DistinctBy(x => x.Name); /// - public IEnumerable GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType) => - activityType.GetProperties().Where(x => typeof(Output).IsAssignableFrom(x.PropertyType)).DistinctBy(x => x.Name).ToList(); + public IEnumerable GetOutputProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type activityType) => activityType.GetProperties().Where(x => typeof(Output).IsAssignableFrom(x.PropertyType)).DistinctBy(x => x.Name).ToList(); /// public Task DescribeOutputPropertyAsync(PropertyInfo propertyInfo, CancellationToken cancellationToken = default) @@ -121,18 +123,7 @@ public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolve var typeArgs = propertyInfo.PropertyType.GenericTypeArguments; var wrappedPropertyType = typeArgs.Any() ? typeArgs[0] : typeof(object); - return Task.FromResult(new OutputDescriptor - ( - (outputAttribute?.Name ?? propertyInfo.Name).Pascalize(), - outputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title), - wrappedPropertyType, - propertyInfo.GetValue, - propertyInfo.SetValue, - propertyInfo, - descriptionAttribute?.Description ?? outputAttribute?.Description, - outputAttribute?.IsBrowsable ?? true, - outputAttribute?.IsSerializable - )); + return Task.FromResult(new OutputDescriptor((outputAttribute?.Name ?? propertyInfo.Name).Pascalize(), outputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title), wrappedPropertyType, propertyInfo.GetValue, propertyInfo.SetValue, propertyInfo, descriptionAttribute?.Description ?? outputAttribute?.Description, outputAttribute?.IsBrowsable ?? true, outputAttribute?.IsSerializable)); } /// @@ -150,8 +141,7 @@ public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolve var uiSpecification = await propertyUIHandlerResolver.GetUIPropertiesAsync(propertyInfo, null, cancellationToken); - return new InputDescriptor - ( + return new InputDescriptor( inputAttribute?.Name ?? propertyInfo.Name, wrappedPropertyType, propertyInfo.GetValue, @@ -214,6 +204,15 @@ public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolve return InputUIHints.SingleLine; } + private static string GetFriendlyActivityName(Type t) + { + if (!t.IsGenericType) + return t.Name; + var baseName = t.Name.Substring(0, t.Name.IndexOf('`')); + var argNames = string.Join(", ", t.GetGenericArguments().Select(a => a.Name)); + return $"{baseName}<{argNames}>"; + } + private async Task> DescribeInputPropertiesAsync(IEnumerable properties, CancellationToken cancellationToken = default) { return await Task.WhenAll(properties.Select(async x => await DescribeInputPropertyAsync(x, cancellationToken))); @@ -223,4 +222,4 @@ public class ActivityDescriber(IPropertyDefaultValueResolver defaultValueResolve { return await Task.WhenAll(properties.Select(async x => await DescribeOutputPropertyAsync(x, cancellationToken))); } -} \ No newline at end of file +} diff --git a/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs b/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs index fb9c48f14..a2a545646 100644 --- a/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs +++ b/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs @@ -31,7 +31,7 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor IsSystem = workflowExecutionContext.Workflow.IsSystem, CreatedAt = workflowExecutionContext.CreatedAt, UpdatedAt = workflowExecutionContext.UpdatedAt, - FinishedAt = workflowExecutionContext.FinishedAt + FinishedAt = workflowExecutionContext.FinishedAt, }; ExtractProperties(state, workflowExecutionContext); @@ -64,12 +64,13 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor ApplyScheduledActivities(state, workflowExecutionContext); return workflowExecutionContext; } - + private void ApplyInput(WorkflowState state, WorkflowExecutionContext workflowExecutionContext) { // Only add input from state if the input doesn't already exist on the workflow execution context. foreach (var inputItem in state.Input) - if (!workflowExecutionContext.Input.ContainsKey(inputItem.Key)) workflowExecutionContext.Input.Add(inputItem.Key, inputItem.Value); + if (!workflowExecutionContext.Input.ContainsKey(inputItem.Key)) + workflowExecutionContext.Input.Add(inputItem.Key, inputItem.Value); } private IDictionary GetPersistableInput(WorkflowExecutionContext workflowExecutionContext) @@ -102,24 +103,23 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor private static async Task ApplyActivityExecutionContextsAsync(WorkflowState state, WorkflowExecutionContext workflowExecutionContext) { - var activityExecutionContexts = (await Task.WhenAll( - state.ActivityExecutionContexts.Select(async item => await CreateActivityExecutionContextAsync(item)))) - .Where(x => x != null) - .Select(x => x!) - .ToList(); + var activityExecutionContexts = (await Task.WhenAll(state.ActivityExecutionContexts.Select(async item => await CreateActivityExecutionContextAsync(item)))).Where(x => x != null).Select(x => x!).ToList(); var lookup = activityExecutionContexts.ToDictionary(x => x.Id); // Reconstruct hierarchy. foreach (var contextState in state.ActivityExecutionContexts.Where(x => !string.IsNullOrWhiteSpace(x.ParentContextId))) { - var parentContext = lookup[contextState.ParentContextId!]; - var contextId = contextState.Id; - - if (lookup.TryGetValue(contextId, out var context)) + if (lookup.ContainsKey(contextState.ParentContextId)) { - context.ExpressionExecutionContext.ParentContext = parentContext.ExpressionExecutionContext; - context.ParentActivityExecutionContext = parentContext; + var parentContext = lookup[contextState.ParentContextId!]; + var contextId = contextState.Id; + + if (lookup.TryGetValue(contextId, out var context)) + { + context.ExpressionExecutionContext.ParentContext = parentContext.ExpressionExecutionContext; + context.ParentActivityExecutionContext = parentContext; + } } } @@ -144,10 +144,10 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor var activityExecutionContext = await workflowExecutionContext.CreateActivityExecutionContextAsync(activity); activityExecutionContext.Id = activityExecutionContextState.Id; activityExecutionContext.Properties.Merge(properties); - - if(activityExecutionContextState.ActivityState != null) + + if (activityExecutionContextState.ActivityState != null) activityExecutionContext.ActivityState.Merge(activityExecutionContextState.ActivityState); - + activityExecutionContext.TransitionTo(activityExecutionContextState.Status); activityExecutionContext.IsExecuting = activityExecutionContextState.IsExecuting; activityExecutionContext.AggregateFaultCount = activityExecutionContextState.FaultCount; @@ -164,16 +164,19 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor { foreach (var completionCallbackEntry in state.CompletionCallbacks) { - var ownerActivityExecutionContext = workflowExecutionContext.ActivityExecutionContexts.First(x => x.Id == completionCallbackEntry.OwnerInstanceId); - var childNode = workflowExecutionContext.FindNodeById(completionCallbackEntry.ChildNodeId); + var ownerActivityExecutionContext = workflowExecutionContext.ActivityExecutionContexts.FirstOrDefault(x => x.Id == completionCallbackEntry.OwnerInstanceId); + if (ownerActivityExecutionContext != null) + { + var childNode = workflowExecutionContext.FindNodeById(completionCallbackEntry.ChildNodeId); - if (childNode == null) - continue; + if (childNode == null) + continue; - var callbackName = completionCallbackEntry.MethodName; - var callbackDelegate = !string.IsNullOrEmpty(callbackName) ? ownerActivityExecutionContext.Activity.GetActivityCompletionCallback(callbackName) : default; - var tag = completionCallbackEntry.Tag; - workflowExecutionContext.AddCompletionCallback(ownerActivityExecutionContext, childNode, callbackDelegate, tag); + var callbackName = completionCallbackEntry.MethodName; + var callbackDelegate = !string.IsNullOrEmpty(callbackName) ? ownerActivityExecutionContext.Activity.GetActivityCompletionCallback(callbackName) : default; + var tag = completionCallbackEntry.Tag; + workflowExecutionContext.AddCompletionCallback(ownerActivityExecutionContext, childNode, callbackDelegate, tag); + } } } @@ -208,9 +211,7 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor throw new("Lost an owner context"); } - var completionCallbacks = workflowExecutionContext - .CompletionCallbacks - .Select(x => new CompletionCallbackState(x.Owner.Id, x.Child.NodeId, x.CompletionCallback?.Method.Name, x.Tag)); + var completionCallbacks = workflowExecutionContext.CompletionCallbacks.Select(x => new CompletionCallbackState(x.Owner.Id, x.Child.NodeId, x.CompletionCallback?.Method.Name, x.Tag)); state.CompletionCallbacks = completionCallbacks.ToList(); } @@ -243,7 +244,7 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor StartedAt = activityExecutionContext.StartedAt, CompletedAt = activityExecutionContext.CompletedAt, Tag = activityExecutionContext.Tag, - DynamicVariables = activityExecutionContext.DynamicVariables + DynamicVariables = activityExecutionContext.DynamicVariables, }; return activityExecutionContextState; } @@ -263,9 +264,9 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor Tag = x.Tag, Variables = x.Variables?.ToList(), ExistingActivityExecutionContextId = x.ExistingActivityExecutionContext?.Id, - Input = x.Input + Input = x.Input, }); state.ScheduledActivities = scheduledActivities.ToList(); } -} \ No newline at end of file +}