Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework Workflow Context Feature #5861

Merged
merged 9 commits into from
Aug 6, 2024
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace Elsa.Samples.AspNet.WorkflowContexts.Entities;

public class Order{}
public class Order;
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,19 @@

namespace Elsa.Samples.AspNet.WorkflowContexts.Providers;

public class CustomerWorkflowContextProvider : WorkflowContextProvider<Customer>
public class CustomerWorkflowContextProvider(ICustomerStore customerStore) : WorkflowContextProvider<Customer>
{
private readonly ICustomerStore _customerStore;

public CustomerWorkflowContextProvider(ICustomerStore customerStore)
{
_customerStore = customerStore;
}

protected override async ValueTask<Customer?> LoadAsync(WorkflowExecutionContext workflowExecutionContext)
{
var customerId = workflowExecutionContext.GetCustomerId();
return customerId != null ? await _customerStore.GetAsync(customerId) : null;
return customerId != null ? await customerStore.GetAsync(customerId) : null;
}

protected override async ValueTask SaveAsync(WorkflowExecutionContext workflowExecutionContext, Customer? context)
{
if (context != null)
{
await _customerStore.SaveAsync(context);
await customerStore.SaveAsync(context);
workflowExecutionContext.SetCustomerId(context.Id);
}
}
Expand Down
10 changes: 9 additions & 1 deletion samples/aspnet/Elsa.Samples.AspNet.WorkflowContexts/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# Server

This project represents an Elsa application that hosts workflows and exposes API endpoints to manage & execute workflows.
This project demonstrates how to use Workflow Contexts.

A Workflow Context represents a custom, application-specific object provided to the workflow at runtime.
For example, if your workflow handles a Customer, a custom workflow context provider could provide this customer automatically to the workflow without the need for custom activities that load & persist updates to this customer.
Instead, the custom context provider would load the customer into memory once before the workflow starts and persists changes made to the customer (if any) when the workflow execution ends.

In this sample project, we handle two custom objects: Customer and Order.

To start the `CustomerCommunicationsWorkflow`, start it using the REST API or from Elsa Studio.

## Secrets
The following are the secrets stored in hashed form in appsettings.json:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@

namespace Elsa.Samples.AspNet.WorkflowContexts.Workflows;

/// <summary>
/// A workflow that sends annoying emails to customers.
/// </summary>
/// A workflow that sends helpful emails to customers.
public class CustomerCommunicationsWorkflow : WorkflowBase
{
private static string CustomerId = "2";

/// <inheritdoc />
protected override void Build(IWorkflowBuilder builder)
{
builder.AddWorkflowContextProvider<CustomerWorkflowContextProvider>();
Expand All @@ -23,35 +24,34 @@ protected override void Build(IWorkflowBuilder builder)
{
Activities =
{
SetWorkflowContextParameter.For<CustomerWorkflowContextProvider>(
context => context.GetInput<string>("CustomerId")!),
SetWorkflowContextParameter.For<CustomerWorkflowContextProvider>(CustomerId),
Delay.FromSeconds(5),
new SendEmail
{
Subject = new(context => $"Welcome to our family, {context.GetCustomer().Name}!"),
Body = new("Welcome aboard!"),
To = new(context => new[] { context.GetCustomer().Email })
To = new(context => [context.GetCustomer().Email])
},
Delay.FromSeconds(5),
new SendEmail
{
Subject = new(context => $"{context.GetCustomer().Name}, we got great deals for you!"),
Body = new("Get your creditcard ready!"),
To = new(context => new[] { context.GetCustomer().Email })
To = new(context => [context.GetCustomer().Email])
},
Delay.FromSeconds(5),
new SendEmail
{
Subject = new(context => $"{context.GetCustomer().Name}, you're missing out!"),
Body = new("Sale ends in 2 hours!"),
To = new(context => new[] { context.GetCustomer().Email })
To = new(context => [context.GetCustomer().Email])
},
Delay.FromSeconds(5),
new SendEmail
{
Subject = new(context => $"{context.GetCustomer().Name}, the clock is ticking!"),
Body = new("Tick tik tick!"),
To = new(context => new[] { context.GetCustomer().Email })
To = new(context => [context.GetCustomer().Email])
},
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Elsa.Mediator": "Warning",
"MassTransit": "Warning",
"Microsoft.Extensions.Http": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore": "Warning",
"Microsoft.AspNetCore": "Warning",
"System.Net.Http": "Warning"
"Default": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
Expand All @@ -17,7 +11,7 @@
},
"Identity": {
"Tokens": {
"SigningKey": "secret-signing-key",
"SigningKey": "or*bHQ26K9yjT4wsqk8k7h*!jCgvctzM89Zt@ENFGeuT-xZWQ9i.36u6YBhXBZut",
"AccessTokenLifetime": "1:00:00:00",
"RefreshTokenLifetime": "1:00:10:00"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,6 @@ public class WorkflowDefinition : LinkedEntity
/// </summary>
public IDictionary<string, object> CustomProperties { get; set; } = new Dictionary<string, object>();

/// <summary>
/// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow.
/// </summary>
public PropertyBag PropertyBag { get; set; } = new();

/// <summary>
/// The name of the workflow provider that created this workflow, if any.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,6 @@ public class WorkflowDefinitionModel : LinkedEntity
/// </summary>
public IDictionary<string, object>? CustomProperties { get; set; }

/// <summary>
/// Stores custom information about the workflow. Can be used to store application-specific properties to associate with the workflow.
/// </summary>
public PropertyBag PropertyBag { get; set; } = new();

/// <summary>
/// Gets or sets whether the workflow definition is read-only.
/// </summary>
Expand Down
67 changes: 0 additions & 67 deletions src/modules/Elsa.Common/Extensions/PropertyBagExtensions.cs

This file was deleted.

20 changes: 0 additions & 20 deletions src/modules/Elsa.Common/Models/PropertyBag.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ public void Configure(EntityTypeBuilder<WorkflowDefinition> builder)
builder.Ignore(x => x.Outputs);
builder.Ignore(x => x.Outcomes);
builder.Ignore(x => x.CustomProperties);
builder.Ignore(x => x.PropertyBag);
builder.Ignore(x => x.Options);
builder.Property<string>("Data");
builder.Property<bool?>("UsableAsActivity");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public async Task<bool> GetIsNameUnique(string name, string? definitionId = defa

private ValueTask OnSaveAsync(ManagementElsaDbContext managementElsaDbContext, WorkflowDefinition entity, CancellationToken cancellationToken)
{
var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties, entity.PropertyBag);
var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties);
var json = _payloadSerializer.Serialize(data);

managementElsaDbContext.Entry(entity).Property("Data").CurrentValue = json;
Expand All @@ -165,7 +165,7 @@ private ValueTask OnLoadAsync(ManagementElsaDbContext managementElsaDbContext, W
if (entity == null)
return ValueTask.CompletedTask;

var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties, entity.PropertyBag);
var data = new WorkflowDefinitionState(entity.Options, entity.Variables, entity.Inputs, entity.Outputs, entity.Outcomes, entity.CustomProperties);
var json = (string?)managementElsaDbContext.Entry(entity).Property("Data").CurrentValue;

if (!string.IsNullOrWhiteSpace(json))
Expand All @@ -177,7 +177,6 @@ private ValueTask OnLoadAsync(ManagementElsaDbContext managementElsaDbContext, W
entity.Outputs = data.Outputs;
entity.Outcomes = data.Outcomes;
entity.CustomProperties = data.CustomProperties;
entity.PropertyBag = data.PropertyBag;

return ValueTask.CompletedTask;
}
Expand Down Expand Up @@ -226,8 +225,7 @@ public WorkflowDefinitionState(
ICollection<InputDefinition> inputs,
ICollection<OutputDefinition> outputs,
ICollection<string> outcomes,
IDictionary<string, object> customProperties,
PropertyBag propertyBag
IDictionary<string, object> customProperties
)
{
Options = options;
Expand All @@ -236,18 +234,13 @@ PropertyBag propertyBag
Outputs = outputs;
Outcomes = outcomes;
CustomProperties = customProperties;
PropertyBag = propertyBag;
}

public WorkflowOptions Options { get; set; } = new();
public ICollection<Variable> Variables { get; set; } = new List<Variable>();
public ICollection<InputDefinition> Inputs { get; set; } = new List<InputDefinition>();
public ICollection<OutputDefinition> Outputs { get; set; } = new List<OutputDefinition>();
public ICollection<string> Outcomes { get; set; } = new List<string>();

[Obsolete("Use PropertyBag instead")]
public IDictionary<string, object> CustomProperties { get; set; } = new Dictionary<string, object>();

public PropertyBag PropertyBag { get; set; } = new();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
using Elsa.Workflows.Attributes;
using Elsa.Workflows.Models;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;

namespace Elsa.WorkflowContexts.Activities;

/// <summary>
/// Sets a workflow context parameter for a given workflow context provider.
/// </summary>
[Activity("Elsa", "Primitives", "Sets a workflow context parameter for a given workflow context provider.")]
[Activity("Elsa", "Workflow Context", "Sets a workflow context parameter for a given workflow context provider.")]
[PublicAPI]
public class SetWorkflowContextParameter : CodeActivity
{
Expand Down Expand Up @@ -100,13 +101,17 @@ public static SetWorkflowContextParameter For<T>(Func<ExpressionExecutionContext
public Input<object> ParameterValue { get; set; } = default!;

/// <inheritdoc />
protected override void Execute(ActivityExecutionContext context)
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
{
var providerType = ProviderType.Get(context);
var parameterName = ParameterName.GetOrDefault(context);
var scopedParameterName = providerType.GetScopedParameterName(parameterName);
var parameterValue = ParameterValue.Get(context);

// Update the parameter.
context.WorkflowExecutionContext.SetProperty(scopedParameterName, parameterValue);

// Load the context.
await context.WorkflowExecutionContext.LoadWorkflowContextAsync(providerType);
}
}
Loading