Fix access to container properties and improve object type conversion

This commit is contained in:
Sipke Schoorstra 2021-01-03 11:06:11 +01:00
parent f14c22d5d4
commit 67be790c7d
11 changed files with 79 additions and 16 deletions

View file

@ -1,4 +1,5 @@
using System.ComponentModel;
using System;
using System.ComponentModel;
using NodaTime;
using NodaTime.Text;
@ -21,7 +22,10 @@ namespace Elsa
return (T?)((object?)DurationPattern.JsonRoundtrip.Parse(value!.ToString()).Value)!;
var converter = TypeDescriptor.GetConverter(typeof(T));
return converter.CanConvertFrom(value.GetType()) ? (T?) converter.ConvertFrom(value) : default;
if (converter.CanConvertFrom(value.GetType()))
return (T?)converter.ConvertFrom(value);
return (T?)Convert.ChangeType(value, typeof(T));
}
}
}

View file

@ -31,10 +31,18 @@ namespace Elsa.Services.Models
public IServiceScope ServiceScope { get; }
public IActivityBlueprint ActivityBlueprint { get; }
public ActivityInstance ActivityInstance { get; }
public ActivityInstance? ParentActivityInstance => WorkflowExecutionContext.WorkflowInstance.Activities.FirstOrDefault(x => x.Id == ActivityBlueprint.Parent?.Id);
public IReadOnlyCollection<string> Outcomes { get; set; }
public object? Input { get; }
public CancellationToken CancellationToken { get; }
public JObject Data => ActivityInstance.Data;
public JObject? ParentData => ParentActivityInstance?.Data;
public T? GetParentState<T>(string key)
{
var parentData = ParentData;
return parentData == null ? default : parentData.GetState<T>(key);
}
public object? Output
{

View file

@ -12,6 +12,7 @@ namespace Elsa.Activities.ControlFlow
public static IActivityBuilder Finish(this IBuilder builder, Func<ActivityExecutionContext, ValueTask<object?>> output) => builder.Finish(activity => activity.WithOutput(output));
public static IActivityBuilder Finish(this IBuilder builder, Func<ActivityExecutionContext, object?> output) => builder.Finish(activity => activity.WithOutput(output));
public static IActivityBuilder Finish(this IBuilder builder, Func<object?> output) => builder.Finish(activity => activity.WithOutput(output));
public static IActivityBuilder Finish(this IBuilder builder, object? output) => builder.Finish(activity => activity.WithOutput(output));
public static IActivityBuilder Finish(this IBuilder builder, Func<ActivityExecutionContext, ValueTask<string?>> outcome) => builder.Finish(activity => activity.WithOutcome(outcome));
public static IActivityBuilder Finish(this IBuilder builder, Func<ActivityExecutionContext, string?> outcome) => builder.Finish(activity => activity.WithOutcome(outcome));

View file

@ -48,7 +48,7 @@ namespace Elsa.Activities.ControlFlow
if (loop)
{
CurrentValue = currentValue + Step;
return Combine(Schedule(Id), Output(currentValue), Outcome(OutcomeNames.Iterate));
return Outcome(OutcomeNames.Iterate, currentValue);
}
CurrentValue = null;

View file

@ -141,6 +141,11 @@ namespace Elsa.Builders
var activityBlueprintDictionary = activityBlueprints.ToDictionary(x => x.Id);
connections.AddRange(ConnectionBuilders.Select(x => new Connection(activityBlueprintDictionary[x.Source().ActivityId], activityBlueprintDictionary[x.Target().ActivityId], x.Outcome)));
compositeActivityBlueprint.Connections = connections;
compositeActivityBlueprint.Activities = activityBlueprints;
compositeActivityBlueprint.ActivityPropertyProviders = new ActivityPropertyProviders(activityPropertyProviders);
// Build composite activities.
var compositeActivityBuilders = activityBuilders.Where(x => typeof(CompositeActivity).IsAssignableFrom(x.ActivityType));
BuildCompositeActivities(compositeActivityBuilders, activityBlueprints, connections, activityPropertyProviders);
@ -150,10 +155,6 @@ namespace Elsa.Builders
.Select(x => (x.ActivityId, x.PropertyValueProviders))
.ToDictionary(x => x.ActivityId!, x => x.PropertyValueProviders!));
compositeActivityBlueprint.Connections = connections;
compositeActivityBlueprint.Activities = activityBlueprints;
compositeActivityBlueprint.ActivityPropertyProviders = new ActivityPropertyProviders(activityPropertyProviders);
return compositeActivityBlueprint;
}

View file

@ -6,7 +6,7 @@ namespace Elsa.Services
{
public class ActivityFactory : IActivityFactory
{
public ActivityInstance Instantiate(IActivityBlueprint activityBlueprint) => new ActivityInstance(
public ActivityInstance Instantiate(IActivityBlueprint activityBlueprint) => new(
activityBlueprint.Id,
activityBlueprint.Type,
null,

View file

@ -4,6 +4,7 @@ using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Elsa.ActivityProviders;
using Elsa.Exceptions;
using Elsa.Services.Models;
namespace Elsa.Services
@ -19,8 +20,16 @@ namespace Elsa.Services
}
public async ValueTask<IEnumerable<ActivityType>> GetActivityTypesAsync(CancellationToken cancellationToken) => (await GetDictionaryAsync(cancellationToken)).Values;
public async ValueTask<ActivityType> GetActivityTypeAsync(string type, CancellationToken cancellationToken) => (await GetDictionaryAsync(cancellationToken))[type];
public async ValueTask<ActivityType> GetActivityTypeAsync(string type, CancellationToken cancellationToken)
{
var dictionary = await GetDictionaryAsync(cancellationToken);
if (!dictionary.ContainsKey(type))
throw new WorkflowException($"The activity type '{type}' has not been registered. Did you forget to register it with ElsaOptions?");
return dictionary[type];
}
public async ValueTask<RuntimeActivityInstance> ActivateActivityAsync(IActivityBlueprint activityBlueprint, CancellationToken cancellationToken = default)
{
var type = await GetActivityTypeAsync(activityBlueprint.Type, cancellationToken);

View file

@ -0,0 +1,36 @@
using Elsa.Activities.Console;
using Elsa.Activities.ControlFlow;
using Elsa.Activities.Timers;
using Elsa.Attributes;
using Elsa.Builders;
using Elsa.Services;
using Elsa.Services.Models;
using NodaTime;
namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole.Activities
{
/// <summary>
/// Custom activities that inherit from <seealso cref="CompositeActivity"/> declare their own mini-workflow.
/// </summary>
public class CountdownActivity : CompositeActivity
{
// Exposed properties on composite activities must store their values in the State property bag so that it gets persisted & available to child activities.
// Activities are scheduled one by one, which means that when the child activities are executed, the container activity (such as this one) will not be in memory - the default value will be captured when the property is used directly.
[ActivityProperty(Hint = "The start number.")]
public int Start
{
get => GetState<int>();
set => SetState(value);
}
public override void Build(ICompositeActivityBuilder activity)
{
activity
.For(context => context.GetParentState<int>(nameof(Start)), _ => 0, _ => -1, iterate =>
{
iterate.WriteLine(context => $"{context.GetInput<int>()}...");
}, Operator.GreaterThan)
.WriteLine("Happy New Year!");
}
}
}

View file

@ -12,7 +12,7 @@ namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole.Activities
/// Custom activities that inherit from <seealso cref="CompositeActivity"/> declare their own mini-workflow.
/// </summary>
[Action(Outcomes = new[] { "Left", "Right" })]
public class CountDownActivity : CompositeActivity
public class NavigateActivity : CompositeActivity
{
public override void Build(ICompositeActivityBuilder activity)
{

View file

@ -3,6 +3,7 @@ using Elsa.Samples.ProgrammaticCompositeActivitiesConsole.Activities;
using Elsa.Samples.ProgrammaticCompositeActivitiesConsole.Workflows;
using Elsa.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole
{
@ -12,9 +13,11 @@ namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole
{
// Create a service container with Elsa services.
var services = new ServiceCollection()
.AddLogging(logging => logging.AddConsole())
.AddElsa(options => options
.AddConsoleActivities()
.AddActivity<CountDownActivity>()
.AddActivity<NavigateActivity>()
.AddActivity<CountdownActivity>()
.AddWorkflow<CompositionWorkflow>())
.BuildServiceProvider();

View file

@ -11,14 +11,15 @@ namespace Elsa.Samples.ProgrammaticCompositeActivitiesConsole.Workflows
{
public void Build(IWorkflowBuilder workflow) => workflow
.WriteLine("Welcome to the Composite Activities demo workflow!")
// A custom, composite activity
.Then<CountDownActivity>(countDown =>
.WriteLine("=Navigation demo=")
.Then<NavigateActivity>(countDown =>
{
countDown.When("Left").WriteLine("Where going left!");
countDown.When("Right").WriteLine("Where going right!");
})
.WriteLine("Done")
;
.WriteLine("=Countdown demo=")
.Then<CountdownActivity>(activity => activity.Set(x => x.Start, 10));
}
}