elsa-core icon indicating copy to clipboard operation
elsa-core copied to clipboard

Elsa v3: Custom Activities are Broken

Open adamfisher opened this issue 1 year ago • 2 comments

Attempted to add the simple Sum activity from the example in the docs but it fails:

Code:

public class Sum : CodeActivity<int>
{
    public Sum(Variable<int> a, Variable<int> b, Variable<int> result)
    {
        A = new(a);
        B = new(b);
        Result = new(result);
    }

    public Input<int> A { get; set; } = default!;
    public Input<int> B { get; set; } = default!;
    
    protected override void Execute(ActivityExecutionContext context)
    {
        var a = A.Get(context);
        var b = B.Get(context);
        
        var result = a + b;
        
        context.SetResult(result);
    }
}

Stacktrace:

System.InvalidOperationException: Each parameter in the deserialization constructor on type 'MyProject.Workflows.General.SumActivity' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. Fields are only considered when 'JsonSerializerOptions.IncludeFields' is enabled. The match can be case-insensitive.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_ConstructorParameterIncompleteBinding(Type parentType)
   at System.Text.Json.Serialization.Converters.ObjectWithParameterizedConstructorConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsObject(Utf8JsonReader& reader, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadFromSpanAsObject(ReadOnlySpan`1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable`1 actualByteCount)
   at System.Text.Json.JsonSerializer.Deserialize(JsonElement element, Type returnType, JsonSerializerOptions options)
   at Elsa.Workflows.Services.ActivityFactory.Create(Type type, ActivityConstructorContext context)
   at Elsa.Workflows.Services.ActivityDescriber.<>c__DisplayClass4_0.<DescribeActivityAsync>b__8(ActivityConstructorContext context)
   at Elsa.Workflows.Management.Serialization.Converters.ActivityJsonConverter.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.JsonCollectionConverter`2.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, TCollection& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo`1 jsonTypeInfo, Nullable`1 actualByteCount)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](JsonElement element, JsonSerializerOptions options)
   at Elsa.Workflows.Activities.Flowchart.Serialization.FlowchartJsonConverter.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsObject(Utf8JsonReader& reader, ReadStack& state)
   at System.Text.Json.JsonSerializer.ReadFromSpanAsObject(ReadOnlySpan`1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable`1 actualByteCount)
   at System.Text.Json.JsonSerializer.Deserialize(JsonElement element, Type returnType, JsonSerializerOptions options)
   at Elsa.Workflows.Services.ActivityFactory.Create(Type type, ActivityConstructorContext context)
   at Elsa.Workflows.Services.ActivityDescriber.<>c__DisplayClass4_0.<DescribeActivityAsync>b__8(ActivityConstructorContext context)
   at Elsa.Workflows.Management.Serialization.Converters.ActivityJsonConverter.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.ReadJsonAndSetMember(Object obj, ReadStack& state, Utf8JsonReader& reader)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
   at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
   at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.ContinueDeserialize(ReadBufferState& bufferState, JsonReaderState& jsonReaderState, ReadStack& readStack)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsync(Stream utf8Json, CancellationToken cancellationToken)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.DeserializeAsObjectAsync(Stream utf8Json, CancellationToken cancellationToken)
   at FastEndpoints.RequestBinder`1.BindJsonBody(HttpRequest httpRequest, JsonSerializerContext serializerCtx, CancellationToken cancellation)
   at FastEndpoints.RequestBinder`1.BindAsync(BinderContext ctx, CancellationToken cancellation)
   at FastEndpoints.Endpoint`2.BindRequestAsync(EndpointDefinition def, HttpContext ctx, List`1 failures, CancellationToken ct)
   at FastEndpoints.Endpoint`2.ExecAsync(CancellationToken ct)
   at FastEndpoints.Endpoint`2.ExecAsync(CancellationToken ct)
   at Elsa.Http.Middleware.WorkflowsMiddleware.InvokeAsync(HttpContext httpContext)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

adamfisher avatar Jan 18 '24 03:01 adamfisher

I had the same problem, just change it to below code, I think it is something to do with constructor parameters not being serializable

public class Sum : CodeActivity<int>
{
    public Input<int> A { get; set; } = default!;
    public Input<int> B { get; set; } = default!;

   protected override void Execute(ActivityExecutionContext context)
    {
        var input1 = A.Get(context);
        var input2 = B.Get(context);
        var result = input1 + input2;
        context.SetResult(result);
    }
}

osmanium avatar Feb 17 '24 21:02 osmanium

In general I've found the docs not to be great when persisting the workflows.

When deserialising the object, it must use one of the contructors to create it. The only information it has to deserialise it with is Input A and Input B (it doesn't have any Variables)

You can specify a private constructor that the Json serialiser should use with the information it has. By making it private, you're not changing the public interface to the class.

public class Sum : CodeActivity<int>
{
// --------------
    [JsonConstructor]
    private Sum(Input<int> a, Input<int> b){
        A = a;
        B = b;
    }
// ----- OR -----
    [JsonConstructor]
    private Sum() { }
// ---------------
    public Sum(Variable<int> a, Variable<int> b, Variable<int> result)
    {
        A = new(a);
        B = new(b);
        Result = new(result);
    }

    public Input<int> A { get; set; } = default!;
    public Input<int> B { get; set; } = default!;
    
    protected override void Execute(ActivityExecutionContext context)
    {
        var a = A.Get(context);
        var b = B.Get(context);
        
        var result = a + b;
        
        context.SetResult(result);
    }
}

Itzalive avatar Apr 16 '24 12:04 Itzalive