Define activities (#78)

* Annotate activities with design-time attributes
This commit is contained in:
Sipke Schoorstra 2019-09-22 00:02:04 +02:00 committed by GitHub
parent 02be088cdb
commit e1da5f5f19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
62 changed files with 917 additions and 85 deletions

View file

@ -83,6 +83,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample15", "samples\Sample1
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.WorkflowHost.Web", "src\dashboard\Elsa.WorkflowHost.Web\Elsa.WorkflowHost.Web.csproj", "{1680B0F4-4FA3-4ED3-8DA1-23DF034D6009}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.WorkflowDesigner", "src\dashboard\Elsa.WorkflowDesigner\Elsa.WorkflowDesigner.csproj", "{944DAD02-4024-4D3E-8C75-AE313EA02A94}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -209,6 +211,10 @@ Global
{1680B0F4-4FA3-4ED3-8DA1-23DF034D6009}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1680B0F4-4FA3-4ED3-8DA1-23DF034D6009}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1680B0F4-4FA3-4ED3-8DA1-23DF034D6009}.Release|Any CPU.Build.0 = Release|Any CPU
{944DAD02-4024-4D3E-8C75-AE313EA02A94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{944DAD02-4024-4D3E-8C75-AE313EA02A94}.Debug|Any CPU.Build.0 = Debug|Any CPU
{944DAD02-4024-4D3E-8C75-AE313EA02A94}.Release|Any CPU.ActiveCfg = Release|Any CPU
{944DAD02-4024-4D3E-8C75-AE313EA02A94}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -249,6 +255,7 @@ Global
{5155F75B-38F2-4B5B-9C8F-6F6641BF986B} = {5E5E1E84-DDBC-40D6-B891-0D563A15A44A}
{5E5E1E84-DDBC-40D6-B891-0D563A15A44A} = {DA71CDAA-8DD3-4D5F-9FBD-8E4B37A2D925}
{1680B0F4-4FA3-4ED3-8DA1-23DF034D6009} = {0DF567E8-2A21-4A1C-90E8-025F1EC615F3}
{944DAD02-4024-4D3E-8C75-AE313EA02A94} = {0DF567E8-2A21-4A1C-90E8-025F1EC615F3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8B0975FD-7050-48B0-88C5-48C33378E158}

View file

@ -1,6 +1,7 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Results;
using Elsa.Services;
using Elsa.Services.Models;
@ -10,6 +11,15 @@ namespace Elsa.Activities.Console.Activities
/// <summary>
/// Reads input from the console.
/// </summary>
[ActivityDefinition(
Category = "Console",
Description = "Read text from standard in."
)]
[ActivityDefinitionDesigner(
Description =
"a => !!a.state.variableName ? `Read text from standard in and store into <strong>${ a.state.variableName }</strong>.` : 'Read text from standard in.'",
Outcomes = new[] { OutcomeNames.Done }
)]
public class ReadLine : Activity
{
private readonly TextReader input;
@ -23,13 +33,15 @@ namespace Elsa.Activities.Console.Activities
this.input = input;
}
[ActivityProperty(Hint = "The name of the variable to store the value into.")]
public string VariableName
{
get => GetState<string>();
set => SetState(value);
}
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext context,
protected override async Task<ActivityExecutionResult> OnExecuteAsync(
WorkflowExecutionContext context,
CancellationToken cancellationToken)
{
if (input == null)

View file

@ -1,6 +1,7 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Results;
@ -12,6 +13,15 @@ namespace Elsa.Activities.Console.Activities
/// <summary>
/// Writes a text string to the console.
/// </summary>
[ActivityDefinition(
Category = "Console",
Description = "Write text to standard out."
)]
[ActivityDefinitionDesigner(
Description =
"x => !!x.state.textExpression ? `Write <strong>${ x.state.textExpression.expression }</strong> to standard out.` : x.definition.description",
Outcomes = new[] { OutcomeNames.Done }
)]
public class WriteLine : Activity
{
public WriteLine(IWorkflowExpressionEvaluator evaluator)
@ -25,6 +35,7 @@ namespace Elsa.Activities.Console.Activities
this.output = output;
}
[ActivityProperty(Hint = "The text to write.")]
public WorkflowExpression<string> TextExpression
{
get => GetState(() => LiteralEvaluator.Expression<string>(null));

View file

@ -2,6 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Activities.Email.Options;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Results;
@ -11,6 +12,10 @@ using Microsoft.Extensions.Options;
namespace Elsa.Activities.Email.Activities
{
[ActivityDefinition(
Category = "Email",
Description = "Send an email message."
)]
public class SendEmail : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
@ -18,7 +23,7 @@ namespace Elsa.Activities.Email.Activities
private readonly SmtpClient smtpClient;
public SendEmail(
IWorkflowExpressionEvaluator expressionEvaluator,
IWorkflowExpressionEvaluator expressionEvaluator,
IOptions<SmtpOptions> options,
SmtpClient smtpClient)
{
@ -27,24 +32,28 @@ namespace Elsa.Activities.Email.Activities
this.smtpClient = smtpClient;
}
[ActivityProperty(Hint = "The sender's email address.")]
public WorkflowExpression<string> From
{
get => GetState(() => new WorkflowExpression<string>(LiteralEvaluator.SyntaxName, ""));
set => SetState(value);
}
[ActivityProperty(Hint = "The recipient's email address.")]
public WorkflowExpression<string> To
{
get => GetState(() => new WorkflowExpression<string>(LiteralEvaluator.SyntaxName, ""));
set => SetState(value);
}
[ActivityProperty(Hint = "The subject of the email message.")]
public WorkflowExpression<string> Subject
{
get => GetState(() => new WorkflowExpression<string>(LiteralEvaluator.SyntaxName, ""));
set => SetState(value);
}
[ActivityProperty(Hint = "The body of the email message.")]
public WorkflowExpression<string> Body
{
get => GetState(() => new WorkflowExpression<string>(LiteralEvaluator.SyntaxName, ""));
@ -54,7 +63,8 @@ namespace Elsa.Activities.Email.Activities
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext workflowContext,
CancellationToken cancellationToken)
{
var from = (await expressionEvaluator.EvaluateAsync(From, workflowContext, cancellationToken)) ?? options.Value.DefaultSender;
var from = (await expressionEvaluator.EvaluateAsync(From, workflowContext, cancellationToken)) ??
options.Value.DefaultSender;
var to = await expressionEvaluator.EvaluateAsync(To, workflowContext, cancellationToken);
var subject = await expressionEvaluator.EvaluateAsync(Subject, workflowContext, cancellationToken);
var body = await expressionEvaluator.EvaluateAsync(Body, workflowContext, cancellationToken);

View file

@ -8,16 +8,24 @@ using System.Threading;
using System.Threading.Tasks;
using Elsa.Activities.Http.Models;
using Elsa.Activities.Http.Services;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Results;
using Elsa.Services;
using Elsa.Services.Models;
using Elsa.WorkflowDesigner.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
namespace Elsa.Activities.Http.Activities
{
[ActivityDefinition(
Category = "HTTP",
DisplayName = "Send HTTP Request",
Description = "Send an HTTP request."
)]
[ActivityDefinitionDesigner(Outcomes = "x => !!x.state.supportedStatusCodes ? x.state.supportedStatusCodes : []")]
public class HttpRequestAction : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
@ -37,6 +45,7 @@ namespace Elsa.Activities.Http.Activities
/// <summary>
/// The URL to invoke.
/// </summary>
[ActivityProperty(Hint = "The URL to send the HTTP request to.")]
public WorkflowExpression<Uri> Url
{
get
@ -54,6 +63,11 @@ namespace Elsa.Activities.Http.Activities
/// <summary>
/// The HTTP method to use.
/// </summary>
[ActivityProperty(
Type = ActivityPropertyTypes.Select,
Hint = "The HTTP method to use when making the request."
)]
[SelectOptions("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD")]
public string Method
{
get => GetState(() => "GET");
@ -63,7 +77,8 @@ namespace Elsa.Activities.Http.Activities
/// <summary>
/// The body to send along with the request.
/// </summary>
public WorkflowExpression<string> Body
[ActivityProperty(Hint = "The HTTP content to send along with the request.")]
public WorkflowExpression<string> Content
{
get => GetState(() => new WorkflowExpression<string>(LiteralEvaluator.SyntaxName, ""));
set => SetState(value);
@ -72,6 +87,11 @@ namespace Elsa.Activities.Http.Activities
/// <summary>
/// The Content Type header to send along with the request body.
/// </summary>
[ActivityProperty(
Type = ActivityPropertyTypes.Select,
Hint = "The content type to send with the request."
)]
[SelectOptions("text/plain", "text/html", "application/json", "application/xml")]
public WorkflowExpression<string> ContentType
{
get => GetState(() => new WorkflowExpression<string>(LiteralEvaluator.SyntaxName, ""));
@ -81,6 +101,7 @@ namespace Elsa.Activities.Http.Activities
/// <summary>
/// The headers to send along with the request.
/// </summary>
[ActivityProperty(Hint = "The headers to send along with the request. One 'header: value' pair per line.")]
public WorkflowExpression<string> RequestHeaders
{
get => GetState(() => new WorkflowExpression<string>(LiteralEvaluator.SyntaxName, ""));
@ -90,6 +111,10 @@ namespace Elsa.Activities.Http.Activities
/// <summary>
/// A list of HTTP status codes this activity can handle.
/// </summary>
[ActivityProperty(
Type = ActivityPropertyTypes.List,
Hint = "A list of possible HTTP status codes to handle, comma-separated. Example: 200, 400, 404"
)]
public HashSet<int> SupportedStatusCodes
{
get => GetState(() => new HashSet<int> { 200 });
@ -102,14 +127,16 @@ namespace Elsa.Activities.Http.Activities
{
var request = await CreateRequestAsync(workflowContext, cancellationToken);
var response = await httpClient.SendAsync(request, cancellationToken);
var content = response.Content != null ? await response.Content.ReadAsStringAsync() : default(string);
var content = response.Content != null ? await response.Content.ReadAsStringAsync() : default;
var contentType = response.Content?.Headers.ContentType.MediaType;
var formatter = SelectContentFormatter(contentType);
var responseModel = new HttpResponseModel
{
StatusCode = response.StatusCode,
Headers = new HeaderDictionary(response.Headers.ToDictionary(x => x.Key, x => new StringValues(x.Value.ToArray()))),
Headers = new HeaderDictionary(
response.Headers.ToDictionary(x => x.Key, x => new StringValues(x.Value.ToArray()))
),
Content = content,
FormattedContent = await formatter.ParseAsync(content, contentType)
};
@ -123,10 +150,13 @@ namespace Elsa.Activities.Http.Activities
private IContentFormatter SelectContentFormatter(string contentType)
{
var formatters = contentFormatters.OrderByDescending(x => x.Priority).ToList();
return formatters.FirstOrDefault(x => x.SupportedContentTypes.Contains(contentType, StringComparer.OrdinalIgnoreCase)) ?? formatters.Last();
return formatters.FirstOrDefault(
x => x.SupportedContentTypes.Contains(contentType, StringComparer.OrdinalIgnoreCase)
) ?? formatters.Last();
}
private async Task<HttpRequestMessage> CreateRequestAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
private async Task<HttpRequestMessage> CreateRequestAsync(WorkflowExecutionContext workflowContext,
CancellationToken cancellationToken)
{
var methodSupportsBody = GetMethodSupportsBody(Method);
var uri = await expressionEvaluator.EvaluateAsync(Url, workflowContext, cancellationToken);
@ -135,8 +165,12 @@ namespace Elsa.Activities.Http.Activities
if (methodSupportsBody)
{
var body = await expressionEvaluator.EvaluateAsync(Body, workflowContext, cancellationToken);
var contentType = await expressionEvaluator.EvaluateAsync(ContentType, workflowContext, cancellationToken);
var body = await expressionEvaluator.EvaluateAsync(Content, workflowContext, cancellationToken);
var contentType = await expressionEvaluator.EvaluateAsync(
ContentType,
workflowContext,
cancellationToken
);
if (!string.IsNullOrWhiteSpace(body))
{
@ -152,9 +186,14 @@ namespace Elsa.Activities.Http.Activities
return request;
}
private async Task<IHeaderDictionary> ParseRequestHeadersAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
private async Task<IHeaderDictionary> ParseRequestHeadersAsync(WorkflowExecutionContext workflowContext,
CancellationToken cancellationToken)
{
var headersText = await expressionEvaluator.EvaluateAsync(RequestHeaders, workflowContext, cancellationToken);
var headersText = await expressionEvaluator.EvaluateAsync(
RequestHeaders,
workflowContext,
cancellationToken
);
var headers = new HeaderDictionary();
if (headersText != null)
@ -167,7 +206,11 @@ namespace Elsa.Activities.Http.Activities
foreach (var header in headersQuery)
{
var headerValueExpression = new WorkflowExpression<string>(RequestHeaders.Syntax, header.Value);
var headerValue = await expressionEvaluator.EvaluateAsync(headerValueExpression, workflowContext, cancellationToken);
var headerValue = await expressionEvaluator.EvaluateAsync(
headerValueExpression,
workflowContext,
cancellationToken
);
headers.Add(header.Key, headerValue);
}
}

View file

@ -7,27 +7,39 @@ using System.Threading.Tasks;
using Elsa.Activities.Http.Extensions;
using Elsa.Activities.Http.Models;
using Elsa.Activities.Http.Services;
using Elsa.Attributes;
using Elsa.Extensions;
using Elsa.Results;
using Elsa.Services;
using Elsa.Services.Models;
using Elsa.WorkflowDesigner.Models;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json.Linq;
namespace Elsa.Activities.Http.Activities
{
[ActivityDefinition(
Category = "HTTP",
DisplayName = "Receive HTTP Request",
Description = "Receive an incoming HTTP request."
)]
[ActivityDefinitionDesigner(
Description =
"x => !!x.state.path ? `Handle <strong>${ x.state.method } ${ x.state.path }</strong>.` : x.definition.description",
Outcomes = new[] { OutcomeNames.Done }
)]
public class HttpRequestEvent : Activity
{
public static Uri GetPath(JObject state)
{
return state.GetState<Uri>(nameof(Path));
}
public static string GetMethod(JObject state)
{
return state.GetState<string>(nameof(Method));
}
private readonly IHttpContextAccessor httpContextAccessor;
private readonly IEnumerable<IContentFormatter> contentFormatters;
@ -42,9 +54,7 @@ namespace Elsa.Activities.Http.Activities
/// <summary>
/// The path that triggers this activity.
/// </summary>
[Display(Description = "The relative path that triggers this activity.")]
[Required]
[UIHint("RelativePath")]
[ActivityProperty(Hint = "The relative path that triggers this activity.")]
public Uri Path
{
get => GetState<Uri>();
@ -54,10 +64,13 @@ namespace Elsa.Activities.Http.Activities
/// <summary>
/// The HTTP method that triggers this activity.
/// </summary>
[Display(Description = "The HTTP method that triggers this activity.")]
[Required]
[UIHint("Dropdown")]
public string Method {
[ActivityProperty(
Type = ActivityPropertyTypes.Select,
Hint = "The HTTP method that triggers this activity."
)]
[SelectOptions("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD")]
public string Method
{
get => GetState<string>();
set => SetState(value);
}
@ -66,7 +79,10 @@ namespace Elsa.Activities.Http.Activities
/// A value indicating whether the HTTP request content body should be read and stored as part of the HTTP request model.
/// The stored format depends on the content-type header.
/// </summary>
[Display(Description = "A value indicating whether the HTTP request content body should be read and stored as part of the HTTP request model. The stored format depends on the content-type header.")]
[ActivityProperty(
Hint =
"A value indicating whether the HTTP request content body should be read and stored as part of the HTTP request model. The stored format depends on the content-type header."
)]
public bool ReadContent
{
get => GetState<bool>();
@ -78,7 +94,8 @@ namespace Elsa.Activities.Http.Activities
return Halt(true);
}
protected override async Task<ActivityExecutionResult> OnResumeAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
protected override async Task<ActivityExecutionResult> OnResumeAsync(WorkflowExecutionContext workflowContext,
CancellationToken cancellationToken)
{
var request = httpContextAccessor.HttpContext.Request;
var model = new HttpRequestModel
@ -93,7 +110,10 @@ namespace Elsa.Activities.Http.Activities
{
if (request.HasFormContentType)
{
model.Form = (await request.ReadFormAsync(cancellationToken)).ToDictionary(x => x.Key, x => new StringValuesModel(x.Value));
model.Form = (await request.ReadFormAsync(cancellationToken)).ToDictionary(
x => x.Key,
x => new StringValuesModel(x.Value)
);
}
var parser = SelectContentParser(request.ContentType);
@ -111,7 +131,9 @@ namespace Elsa.Activities.Http.Activities
private IContentFormatter SelectContentParser(string contentType)
{
var formatters = contentFormatters.OrderByDescending(x => x.Priority).ToList();
return formatters.FirstOrDefault(x => x.SupportedContentTypes.Contains(contentType, StringComparer.OrdinalIgnoreCase)) ?? formatters.Last();
return formatters.FirstOrDefault(
x => x.SupportedContentTypes.Contains(contentType, StringComparer.OrdinalIgnoreCase)
) ?? formatters.Last();
}
}
}

View file

@ -1,32 +1,73 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Results;
using Elsa.Services;
using Elsa.Services.Models;
using Elsa.WorkflowDesigner.Models;
using Microsoft.AspNetCore.Http;
namespace Elsa.Activities.Http.Activities
{
[ActivityDefinition(
Category = "HTTP",
DisplayName = "Send HTTP Response",
Description = "Send an HTTP response."
)]
[ActivityDefinitionDesigner(
Description =
"x => !!x.state.statusCode ? `Send an HTTP <strong>${ x.state.statusCode }</strong><br/><br/> ${ x.state.contentType }</strong><br/>${ !!x.state.content ? x.state.content.expression ? x.state.content.expression.substr(0,100) + '...' : '' : '' }` : x.definition.description",
Outcomes = new[] { OutcomeNames.Done }
)]
public class HttpResponseAction : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
private readonly IHttpContextAccessor httpContextAccessor;
public HttpResponseAction(IWorkflowExpressionEvaluator expressionEvaluator, IHttpContextAccessor httpContextAccessor)
public static IEnumerable<SelectItem> GetStatusCodes()
{
return new[]
{
new SelectItem
{
Label = "2xx",
Options = new object[] { 200, 201, 202, 203, 204 }
},
new SelectItem
{
Label = "3xx",
Options = new object[] { 301, 302, 304, 307, 308 }
},
new SelectItem
{
Label = "4xx",
Options = new object[] { 400, 401, 402, 403, 404, 405, 409, 410, 412, 413, 415, 417, 418, 420, 428, 429 }
}
};
}
public HttpResponseAction(IWorkflowExpressionEvaluator expressionEvaluator,
IHttpContextAccessor httpContextAccessor)
{
this.expressionEvaluator = expressionEvaluator;
this.httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// The HTTP status code to return.
/// </summary>
[ActivityProperty(
Type = ActivityPropertyTypes.Select,
Hint = "The HTTP status code to write."
)]
[SelectOptions(nameof(GetStatusCodes), typeof(HttpResponseAction))]
public HttpStatusCode StatusCode
{
get => GetState(() => HttpStatusCode.OK);
@ -36,6 +77,8 @@ namespace Elsa.Activities.Http.Activities
/// <summary>
/// The content to send along with the response
/// </summary>
[ActivityProperty(Hint = "The HTTP content to write.")]
[ExpressionOptions(Multiline = true)]
public WorkflowExpression<string> Content
{
get => GetState(() => new WorkflowExpression<string>(LiteralEvaluator.SyntaxName, ""));
@ -45,29 +88,40 @@ namespace Elsa.Activities.Http.Activities
/// <summary>
/// The Content-Type header to send along with the response.
/// </summary>
[ActivityProperty(
Type = ActivityPropertyTypes.Select,
Hint = "The HTTP content type header to write."
)]
[SelectOptions("text/plain", "text/html", "application/json", "application/xml")]
public string ContentType
{
get => GetState<string>();
set => SetState(value);
}
/// <summary>
/// The headers to send along with the response. One 'header: value' pair per line.
/// </summary>
[ActivityProperty(Hint = "The headers to send along with the response. One 'header: value' pair per line.")]
public WorkflowExpression<string> ResponseHeaders
{
get => GetState(() => new WorkflowExpression<string>(LiteralEvaluator.SyntaxName, ""));
set => SetState(value);
}
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext workflowContext,
CancellationToken cancellationToken)
{
var response = httpContextAccessor.HttpContext.Response;
response.StatusCode = (int) StatusCode;
response.ContentType = ContentType;
var headersText = await expressionEvaluator.EvaluateAsync(ResponseHeaders, workflowContext, cancellationToken);
var headersText = await expressionEvaluator.EvaluateAsync(
ResponseHeaders,
workflowContext,
cancellationToken
);
if (headersText != null)
{
@ -79,7 +133,11 @@ namespace Elsa.Activities.Http.Activities
foreach (var header in headersQuery)
{
var headerValueExpression = new WorkflowExpression<string>(ResponseHeaders.Syntax, header.Value);
response.Headers[header.Key] = await expressionEvaluator.EvaluateAsync(headerValueExpression, workflowContext, cancellationToken);
response.Headers[header.Key] = await expressionEvaluator.EvaluateAsync(
headerValueExpression,
workflowContext,
cancellationToken
);
}
}

View file

@ -15,6 +15,7 @@
<ItemGroup>
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj" />
<ProjectReference Include="..\..\dashboard\Elsa.WorkflowDesigner\Elsa.WorkflowDesigner.csproj" />
</ItemGroup>
<ItemGroup>

View file

@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Extensions;
using Elsa.Results;
using Elsa.Services;
@ -9,6 +10,11 @@ using Newtonsoft.Json.Linq;
namespace Elsa.Activities.MassTransit.Activities
{
[ActivityDefinition(
Category = "MassTransit",
DisplayName = "Receive MassTransit Message",
Description = "Receive a message via MassTransit."
)]
public class ReceiveMassTransitMessage : Activity
{
public static Type GetMessageType(JObject state)
@ -16,7 +22,8 @@ namespace Elsa.Activities.MassTransit.Activities
var typeName = state.GetState<string>(nameof(MessageType));
return string.IsNullOrWhiteSpace(typeName) ? null : System.Type.GetType(typeName);
}
[ActivityProperty(Hint = "The assembly-qualified type name of the message to receive.")]
public Type MessageType
{
get => GetMessageType(State);
@ -27,7 +34,7 @@ namespace Elsa.Activities.MassTransit.Activities
{
var message = context.Workflow.Input["message"];
var messageType = MessageType;
return message != null && messageType != null && message.GetType() == messageType;
}
@ -36,7 +43,8 @@ namespace Elsa.Activities.MassTransit.Activities
return Halt(true);
}
protected override Task<ActivityExecutionResult> OnResumeAsync(WorkflowExecutionContext context, CancellationToken cancellationToken)
protected override Task<ActivityExecutionResult> OnResumeAsync(WorkflowExecutionContext context,
CancellationToken cancellationToken)
{
var message = context.Workflow.Input["message"];
context.SetLastResult(message);

View file

@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Results;
using Elsa.Services;
@ -9,6 +10,11 @@ using MassTransit;
namespace Elsa.Activities.MassTransit.Activities
{
[ActivityDefinition(
Category = "MassTransit",
DisplayName = "Send MassTransit Message",
Description = "Send a message via MassTransit."
)]
public class SendMassTransitMessage : Activity
{
private readonly ISendEndpointProvider sender;
@ -20,6 +26,7 @@ namespace Elsa.Activities.MassTransit.Activities
this.evaluator = evaluator;
}
[ActivityProperty(Hint = "The assembly-qualified type name of the message to send.")]
public Type MessageType
{
get
@ -30,6 +37,7 @@ namespace Elsa.Activities.MassTransit.Activities
set => SetState(value.AssemblyQualifiedName);
}
[ActivityProperty(Hint = "An expression that evaluates to the message to send.")]
public WorkflowExpression Message
{
get => GetState<WorkflowExpression>();
@ -41,7 +49,8 @@ namespace Elsa.Activities.MassTransit.Activities
return MessageType != null;
}
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext context, CancellationToken cancellationToken)
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext context,
CancellationToken cancellationToken)
{
var message = await evaluator.EvaluateAsync(Message, MessageType, context, cancellationToken);
await sender.Send(message, cancellationToken);

View file

@ -1,5 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Results;
@ -10,6 +11,10 @@ using NodaTime;
namespace Elsa.Activities.Timers.Activities
{
[ActivityDefinition(
Category = "Timers",
Description = "Triggers periodically based on a specified CRON expression."
)]
public class CronEvent : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
@ -21,6 +26,7 @@ namespace Elsa.Activities.Timers.Activities
this.clock = clock;
}
[ActivityProperty(Hint = "Specify a CRON expression. See https://crontab.guru/ for help.")]
public WorkflowExpression<string> CronExpression
{
get => GetState(() => new LiteralExpression("* * * * *"));
@ -39,16 +45,22 @@ namespace Elsa.Activities.Timers.Activities
return Halt();
}
protected override async Task<ActivityExecutionResult> OnResumeAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
protected override async Task<ActivityExecutionResult> OnResumeAsync(WorkflowExecutionContext workflowContext,
CancellationToken cancellationToken)
{
var isExpired = await IsExpiredAsync(workflowContext, cancellationToken);
return isExpired ? Done() : Halt();
}
private async Task<bool> IsExpiredAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
private async Task<bool> IsExpiredAsync(WorkflowExecutionContext workflowContext,
CancellationToken cancellationToken)
{
var cronExpression = await expressionEvaluator.EvaluateAsync(CronExpression, workflowContext, cancellationToken);
var cronExpression = await expressionEvaluator.EvaluateAsync(
CronExpression,
workflowContext,
cancellationToken
);
var schedule = CrontabSchedule.Parse(cronExpression);
var now = clock.GetCurrentInstant();
var startTime = StartTime ?? now;

View file

@ -1,5 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Results;
@ -12,6 +13,10 @@ namespace Elsa.Activities.Timers.Activities
/// <summary>
/// Triggers at a specific instant in the future.
/// </summary>
[ActivityDefinition(
Category = "Timers",
Description = "Triggers at a specified moment in time."
)]
public class InstantEvent : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
@ -26,6 +31,7 @@ namespace Elsa.Activities.Timers.Activities
/// <summary>
/// An expression that evaluates to an <see cref="Instant"/>
/// </summary>
[ActivityProperty(Hint = "An expression that evaluates to a NodaTime Instant")]
public WorkflowExpression<Instant> InstantExpression
{
get => GetState<WorkflowExpression<Instant>>();

View file

@ -2,6 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Activities.Workflows;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Results;
@ -11,6 +12,10 @@ using NodaTime;
namespace Elsa.Activities.Timers.Activities
{
[ActivityDefinition(
Category = "Timers",
Description = "Triggers at a specified interval."
)]
public class TimerEvent : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
@ -22,6 +27,7 @@ namespace Elsa.Activities.Timers.Activities
this.clock = clock;
}
[ActivityProperty(Hint = "An expression that evaluates to a TimeSpan value")]
public WorkflowExpression<TimeSpan> TimeoutExpression
{
get => GetState(() => new LiteralExpression<TimeSpan>("00:01:00"));

View file

@ -1,16 +1,26 @@
using System;
using System.Linq;
using Elsa.Attributes;
using Elsa.Results;
using Elsa.Services;
using Elsa.Services.Models;
using Elsa.WorkflowDesigner.Models;
namespace Elsa.Activities.UserTask.Activities
{
/// <summary>
/// Stores a set of possible user actions and halts the workflow until one of the actions has been performed.
/// </summary>
[ActivityDefinition(
Category = "User Tasks",
Description = "Triggers when a user action is received."
)]
public class UserTask : Activity
{
[ActivityProperty(
Type = ActivityPropertyTypes.List,
Hint = "Enter a comma-separated list of available actions"
)]
public string[] Actions
{
get => GetState(() => new string[0]);
@ -35,6 +45,7 @@ namespace Elsa.Activities.UserTask.Activities
return Outcome(userAction);
}
private string GetUserAction(WorkflowExecutionContext context) => (string) context.Workflow.Input.GetVariable("UserAction");
private string GetUserAction(WorkflowExecutionContext context) =>
(string) context.Workflow.Input.GetVariable("UserAction");
}
}

View file

@ -15,6 +15,7 @@
<ItemGroup>
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj" />
<ProjectReference Include="..\..\dashboard\Elsa.WorkflowDesigner\Elsa.WorkflowDesigner.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,13 @@
using System;
namespace Elsa.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public class ActivityDefinitionAttribute : Attribute
{
public string Type { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public string Category { get; set; }
}
}

View file

@ -0,0 +1,11 @@
using System;
namespace Elsa.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public class ActivityDefinitionDesignerAttribute : Attribute
{
public string Description { get; set; }
public object Outcomes { get; set; }
}
}

View file

@ -0,0 +1,13 @@
using System;
namespace Elsa.Attributes
{
[AttributeUsage(AttributeTargets.Property)]
public class ActivityPropertyAttribute : Attribute
{
public string Name { get; set; }
public string Label { get; set; }
public string Type { get; set; }
public string Hint { get; set; }
}
}

View file

@ -0,0 +1,10 @@
using System;
namespace Elsa.Attributes
{
[AttributeUsage(AttributeTargets.Property)]
public abstract class ActivityPropertyOptionsAttribute : Attribute
{
public abstract object GetOptions();
}
}

View file

@ -0,0 +1,15 @@
namespace Elsa.Attributes
{
public class ExpressionOptionsAttribute : ActivityPropertyOptionsAttribute
{
public bool Multiline { get; set; }
public override object GetOptions()
{
return new
{
Multiline
};
}
}
}

View file

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Elsa.Attributes
{
public class SelectOptionsAttribute : ActivityPropertyOptionsAttribute
{
public SelectOptionsAttribute()
{
}
public SelectOptionsAttribute(params string[] items)
{
Items = items.Cast<object>().ToArray();
}
public SelectOptionsAttribute(string optionsProviderMethod, Type providerType)
{
OptionsProviderMethod = optionsProviderMethod;
ProviderType = providerType;
}
public object[] Items { get; }
public string OptionsProviderMethod { get; }
public Type ProviderType { get; }
public override object GetOptions()
{
if (Items != null)
return new
{
Items
};
var selectItems = GetItemsFromProvider();
return new
{
Items = selectItems.Select(ToObject).ToArray()
};
}
private IEnumerable<object> GetItemsFromProvider()
{
var method = ProviderType.GetMethod(OptionsProviderMethod, BindingFlags.Public | BindingFlags.Static);
return (IEnumerable<object>) method.Invoke(null, null);
}
private object ToObject(object item)
{
if ((item is SelectItem selectItem) && selectItem.Options != null && selectItem.Label == null)
return selectItem.Options;
return item;
}
}
public class SelectItem
{
public string Label { get; set; }
public object[] Options { get; set; }
}
}

View file

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Results;
@ -9,6 +10,7 @@ using Elsa.Services.Models;
namespace Elsa.Activities.ControlFlow
{
[ActivityDefinition(Category = "Control Flow", Description = "Iterate over a collection.")]
public class ForEach : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
@ -18,12 +20,14 @@ namespace Elsa.Activities.ControlFlow
this.expressionEvaluator = expressionEvaluator;
}
[ActivityProperty(Hint = "Enter an expression that evaluates to an array of items to iterate over.")]
public WorkflowExpression<IList<object>> CollectionExpression
{
get => GetState(() => new JavaScriptExpression<IList<object>>("[]"));
set => SetState(value);
}
[ActivityProperty(Hint = "Enter a name for the iterator variable.")]
public string IteratorName
{
get => GetState(() => "CurrentValue");
@ -36,7 +40,9 @@ namespace Elsa.Activities.ControlFlow
set => SetState(value);
}
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext context, CancellationToken cancellationToken)
protected override async Task<ActivityExecutionResult> OnExecuteAsync(
WorkflowExecutionContext context,
CancellationToken cancellationToken)
{
var collection = await expressionEvaluator.EvaluateAsync(CollectionExpression, context, cancellationToken);
var index = CurrentIndex;
@ -55,7 +61,7 @@ namespace Elsa.Activities.ControlFlow
{
context.BeginScope();
}
context.CurrentScope.SetVariable(IteratorName, value);
return Outcome(OutcomeNames.Iterate);

View file

@ -1,18 +1,24 @@
using System.Collections.Generic;
using Elsa.Attributes;
using Elsa.Results;
using Elsa.Services;
using Elsa.Services.Models;
namespace Elsa.Activities.ControlFlow
{
[ActivityDefinition(Category = "Control Flow", Description = "Fork workflow execution into multiple branches.")]
[ActivityDefinitionDesigner(Outcomes = "x => x.state.branches")]
public class Fork : Activity
{
[ActivityProperty(
Hint = "Enter one or more names representing branches, separated with a comma. Example: Branch 1, Branch 2"
)]
public IList<string> Branches
{
get => GetState(() => new List<string>());
set => SetState(value);
}
protected override ActivityExecutionResult OnExecute(WorkflowExecutionContext workflowContext)
{
return Outcomes(Branches);

View file

@ -1,5 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Results;
@ -8,6 +9,16 @@ using Elsa.Services.Models;
namespace Elsa.Activities.ControlFlow
{
[ActivityDefinition(
DisplayName = "If/Else",
Category = "Control Flow",
Description = "Evaluate a Boolean expression and continue execution depending on the result."
)]
[ActivityDefinitionDesigner(
Description =
"x => !!x.state.expression ? `Evaluate <strong>${ x.state.expression.expression }</strong> and continue execution depending on the result.` : x.definition.description",
Outcomes = new[] { OutcomeNames.True, OutcomeNames.False }
)]
public class IfElse : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
@ -17,16 +28,22 @@ namespace Elsa.Activities.ControlFlow
this.expressionEvaluator = expressionEvaluator;
}
[ActivityProperty(Hint = "The expression to evaluate. The evaluated value will be used to switch on.")]
public WorkflowExpression<bool> ConditionExpression
{
get => GetState(() => new JavaScriptExpression<bool>("true"));
set => SetState(value);
}
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext workflowContext,
CancellationToken cancellationToken)
{
var result = await expressionEvaluator.EvaluateAsync(ConditionExpression, workflowContext, cancellationToken);
return Outcome(result ? OutcomeNames.True: OutcomeNames.False);
var result = await expressionEvaluator.EvaluateAsync(
ConditionExpression,
workflowContext,
cancellationToken
);
return Outcome(result ? OutcomeNames.True : OutcomeNames.False);
}
}
}

View file

@ -2,6 +2,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Results;
using Elsa.Services;
using Elsa.Services.Extensions;
@ -9,19 +10,29 @@ using Elsa.Services.Models;
namespace Elsa.Activities.ControlFlow
{
[ActivityDefinition(
Category = "Control Flow",
Description = "Merge workflow execution back into a single branch."
)]
[ActivityDefinitionDesigner(
Description =
"x => !!x.state.joinMode ? `Merge workflow execution back into a single branch using mode <strong>${ x.state.joinMode }</strong>` : x.definition.description",
Outcomes = new[] { OutcomeNames.Done }
)]
public class Join : Activity, IWorkflowEventHandler
{
public Join()
{
InboundTransitions = new List<string>().AsReadOnly();
}
public enum JoinMode
{
WaitAll,
WaitAny
}
[ActivityProperty(Hint = "Either 'WaitAll' or 'WaitAny'")]
public JoinMode Mode
{
get => GetState(() => JoinMode.WaitAll);
@ -33,7 +44,7 @@ namespace Elsa.Activities.ControlFlow
get => GetState<IReadOnlyCollection<string>>();
set => SetState(value);
}
protected override ActivityExecutionResult OnExecute(WorkflowExecutionContext workflowContext)
{
var recordedInboundTransitions = InboundTransitions ?? new List<string>();
@ -50,12 +61,13 @@ namespace Elsa.Activities.ControlFlow
done = inboundConnections.Any(x => recordedInboundTransitions.Contains(GetTransitionKey(x)));
break;
}
if (done)
{
// Remove any inbound blocking activities.
var ancestorActivityIds = workflow.GetInboundActivityPath(Id).ToList();
var blockingActivities = workflow.BlockingActivities.Where(x => ancestorActivityIds.Contains(x.Id)).ToList();
var blockingActivities =
workflow.BlockingActivities.Where(x => ancestorActivityIds.Contains(x.Id)).ToList();
foreach (var blockingActivity in blockingActivities)
{
@ -87,7 +99,10 @@ namespace Elsa.Activities.ControlFlow
{
var joinActivity = (Join) inboundConnection.Target.Activity;
var inboundTransitions = joinActivity.InboundTransitions ?? new List<string>();
joinActivity.InboundTransitions = inboundTransitions.Union(new[] { GetTransitionKey(inboundConnection) }).Distinct().ToList();
joinActivity.InboundTransitions = inboundTransitions
.Union(new[] { GetTransitionKey(inboundConnection) })
.Distinct()
.ToList();
}
}
@ -99,14 +114,18 @@ namespace Elsa.Activities.ControlFlow
return $"@{sourceActivityId}_{sourceOutcomeName}";
}
Task IWorkflowEventHandler.ActivityExecutedAsync(WorkflowExecutionContext workflowContext, IActivity activity, CancellationToken cancellationToken)
Task IWorkflowEventHandler.ActivityExecutedAsync(WorkflowExecutionContext workflowContext, IActivity activity,
CancellationToken cancellationToken)
{
RecordInboundTransitions(workflowContext, activity);
return Task.CompletedTask;
}
Task IWorkflowEventHandler.InvokingHaltedActivitiesAsync(WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken) => Task.CompletedTask;
Task IWorkflowEventHandler.WorkflowInvokedAsync(WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken) => Task.CompletedTask;
Task IWorkflowEventHandler.InvokingHaltedActivitiesAsync(WorkflowExecutionContext workflowExecutionContext,
CancellationToken cancellationToken) => Task.CompletedTask;
Task IWorkflowEventHandler.WorkflowInvokedAsync(WorkflowExecutionContext workflowExecutionContext,
CancellationToken cancellationToken) => Task.CompletedTask;
}
}

View file

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Results;
@ -9,29 +10,41 @@ using Elsa.Services.Models;
namespace Elsa.Activities.ControlFlow
{
[ActivityDefinition(
Category = "Control Flow",
Description = "Switch execution based on a given expression."
)]
[ActivityDefinitionDesigner(
Description =
"x => !!x.state.expression ? `Switch execution based on <strong>${ x.state.expression.expression }</strong>.` : x.definition.description",
Outcomes = "x => x.state.cases.map(c => c.toString())"
)]
public class Switch : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
public Switch(IWorkflowExpressionEvaluator expressionEvaluator)
{
this.expressionEvaluator = expressionEvaluator;
Cases = new List<string>();
}
[ActivityProperty(Hint = "The expression to evaluate. The evaluated value will be used to switch on.")]
public WorkflowExpression<string> Expression
{
get => GetState<WorkflowExpression<string>>();
set => SetState(value);
}
[ActivityProperty(Hint = "A comma-separated list of possible outcomes of the expression.")]
public IReadOnlyCollection<string> Cases
{
get => GetState<IReadOnlyCollection<string>>();
set => SetState(value);
}
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext workflowContext,
CancellationToken cancellationToken)
{
var result = await expressionEvaluator.EvaluateAsync(Expression, workflowContext, cancellationToken);
return Outcome(result);

View file

@ -1,13 +1,23 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Results;
using Elsa.Services;
using Elsa.Services.Models;
namespace Elsa.Activities.Primitives
{
[ActivityDefinition(
DisplayName = "Set Variable",
Description = "Set variable on the workflow.",
Category = "Primitives"
)]
[ActivityDefinitionDesigner(
Description =
"x => !!x.state.variableName ? `${x.state.expression.syntax}: <strong>${x.state.variableName}</strong> = <strong>${x.state.expression.expression}</strong>` : x.definition.description",
Outcomes = new[] { OutcomeNames.Done }
)]
public class SetVariable : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
@ -17,21 +27,30 @@ namespace Elsa.Activities.Primitives
this.expressionEvaluator = expressionEvaluator;
}
[ActivityProperty(Hint = "The name of the variable to store the value into.")]
public string VariableName
{
get => GetState<string>();
set => SetState(value);
}
[ActivityProperty(Hint = "An expression that evaluates to the value to store in the variable.")]
public WorkflowExpression ValueExpression
{
get => GetState<WorkflowExpression>();
set => SetState(value);
}
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
protected override async Task<ActivityExecutionResult> OnExecuteAsync(
WorkflowExecutionContext workflowContext,
CancellationToken cancellationToken)
{
var value = await expressionEvaluator.EvaluateAsync(ValueExpression, ValueExpression.Type, workflowContext, cancellationToken);
var value = await expressionEvaluator.EvaluateAsync(
ValueExpression,
ValueExpression.Type,
workflowContext,
cancellationToken
);
workflowContext.CurrentScope.SetVariable(VariableName, value);
return Done();
}

View file

@ -1,5 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Results;
@ -11,6 +12,10 @@ namespace Elsa.Activities.Workflows
/// <summary>
/// Sets the CorrelationId of the workflow to a given value.
/// </summary>
[ActivityDefinition(
Category = "Workflows",
Description = "Set the CorrelationId of the workflow to a given value."
)]
public class Correlate : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
@ -20,15 +25,18 @@ namespace Elsa.Activities.Workflows
this.expressionEvaluator = expressionEvaluator;
}
public WorkflowExpression<string> Expression
[ActivityProperty(Hint = "An expression that evaluates to the value to store as the correlation ID.")]
public WorkflowExpression<string> ValueExpression
{
get => GetState<WorkflowExpression<string>>();
set => SetState(value);
}
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
protected override async Task<ActivityExecutionResult> OnExecuteAsync(
WorkflowExecutionContext workflowContext,
CancellationToken cancellationToken)
{
var value = await expressionEvaluator.EvaluateAsync(Expression, workflowContext, cancellationToken);
var value = await expressionEvaluator.EvaluateAsync(ValueExpression, workflowContext, cancellationToken);
workflowContext.Workflow.CorrelationId = value;
return Done();
}

View file

@ -1,5 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Models;
@ -9,6 +10,10 @@ using Elsa.Services.Models;
namespace Elsa.Activities.Workflows
{
[ActivityDefinition(
Category = "Workflows",
Description = "Removes any blocking activities and sets the status of the workflow to Finished."
)]
public class Finish : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
@ -18,6 +23,7 @@ namespace Elsa.Activities.Workflows
this.expressionEvaluator = expressionEvaluator;
}
[ActivityProperty(Hint = "An expression that evaluates to a dictionary to be set as the workflow's output.'")]
public WorkflowExpression<Variables> WorkflowOutput
{
get => GetState(() => new JavaScriptExpression<Variables>("({})"));

View file

@ -1,5 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Results;
@ -11,6 +12,10 @@ namespace Elsa.Activities.Workflows
/// <summary>
/// Halts workflow execution until the specified signal is received.
/// </summary>
[ActivityDefinition(
Category = "Workflows",
Description = "Halt workflow execution until the specified signal is received."
)]
public class Signaled : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
@ -20,6 +25,7 @@ namespace Elsa.Activities.Workflows
this.expressionEvaluator = expressionEvaluator;
}
[ActivityProperty(Hint = "An expression that evaluates to the name of the signal to wait for.")]
public WorkflowExpression<string> Signal
{
get => GetState<WorkflowExpression<string>>();

View file

@ -1,9 +1,14 @@
using Elsa.Results;
using Elsa.Attributes;
using Elsa.Results;
using Elsa.Services;
using Elsa.Services.Models;
namespace Elsa.Activities.Workflows
{
[ActivityDefinition(
Category = "Workflows",
Description = "Useful when you need to invoke the workflow manually."
)]
public class Start : Activity
{
protected override ActivityExecutionResult OnExecute(WorkflowExecutionContext context)

View file

@ -1,5 +1,6 @@
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Models;
@ -12,6 +13,10 @@ namespace Elsa.Activities.Workflows
/// <summary>
/// Triggers the specified signal.
/// </summary>
[ActivityDefinition(
Category = "Workflows",
Description = "Trigger the specified signal."
)]
public class Trigger : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
@ -23,18 +28,23 @@ namespace Elsa.Activities.Workflows
this.workflowInvoker = workflowInvoker;
}
[ActivityProperty(Hint = "An expression that evaluates to the name of the signal to trigger.")]
public WorkflowExpression<string> Signal
{
get => GetState<WorkflowExpression<string>>();
set => SetState(value);
}
[ActivityProperty(
Hint = "An expression that evaluates to a dictionary to be provided as input when signaling."
)]
public WorkflowExpression<Variables> Input
{
get => GetState(() => new WorkflowExpression<Variables>(JavaScriptEvaluator.SyntaxName, "{}"));
set => SetState(value);
}
[ActivityProperty(Hint = "An expression that evaluates to the correlation ID to use when signaling.")]
public WorkflowExpression<string> CorrelationId
{
get => GetState(() => new WorkflowExpression<string>(LiteralEvaluator.SyntaxName, ""));
@ -57,7 +67,7 @@ namespace Elsa.Activities.Workflows
correlationId,
cancellationToken: cancellationToken
);
return Done();
}
}

View file

@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Extensions;
using Elsa.Models;
@ -10,6 +11,10 @@ using Elsa.Services.Models;
namespace Elsa.Activities.Workflows
{
[ActivityDefinition(
Category = "Workflows",
Description = "Trigger all workflows that start with or are blocked on the specified activity type."
)]
public class TriggerWorkflow : Activity
{
private readonly IWorkflowInvoker workflowInvoker;
@ -21,18 +26,23 @@ namespace Elsa.Activities.Workflows
this.expressionEvaluator = expressionEvaluator;
}
[ActivityProperty(Hint = "An expression that evaluates to the activity type to use when triggering workflows.")]
public WorkflowExpression<string> ActivityType
{
get => GetState(() => new LiteralExpression(""));
set => SetState(value);
}
[ActivityProperty(
Hint = "An expression that evaluates to a dictionary to be provided as input when triggering workflows."
)]
public WorkflowExpression<Variables> Input
{
get => GetState(() => new JavaScriptExpression<Variables>("{}"));
set => SetState(value);
}
[ActivityProperty(Hint = "An expression that evaluates to the correlation ID to use when triggering workflows.")]
public WorkflowExpression<string> CorrelationId
{
get => GetState(() => new LiteralExpression(Guid.NewGuid().ToString()));

View file

@ -0,0 +1,15 @@
using Elsa.Attributes;
using Elsa.Services;
namespace Elsa.Dashboard.Web.Activities
{
public class SampleActivity : Activity
{
[ActivityProperty]
public string FirstName
{
get => GetState<string>();
set => SetState(value);
}
}
}

View file

@ -16,6 +16,12 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\activities\Elsa.Activities.Console\Elsa.Activities.Console.csproj" />
<ProjectReference Include="..\..\activities\Elsa.Activities.Email\Elsa.Activities.Email.csproj" />
<ProjectReference Include="..\..\activities\Elsa.Activities.Http\Elsa.Activities.Http.csproj" />
<ProjectReference Include="..\..\activities\Elsa.Activities.MassTransit\Elsa.Activities.MassTransit.csproj" />
<ProjectReference Include="..\..\activities\Elsa.Activities.Timers\Elsa.Activities.Timers.csproj" />
<ProjectReference Include="..\..\activities\Elsa.Activities.UserTask\Elsa.Activities.UserTask.csproj" />
<ProjectReference Include="..\..\persistence\Elsa.Persistence.EntityFrameworkCore\Elsa.Persistence.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\Elsa.Dashboard\Elsa.Dashboard.csproj" />
</ItemGroup>

View file

@ -1,6 +1,7 @@
using System.IO;
using Elsa.Dashboard.Extensions;
using Elsa.Persistence.EntityFrameworkCore.Extensions;
using Elsa.Services.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
@ -27,6 +28,8 @@ namespace Elsa.Dashboard.Web
services
.AddMvc( /*options => options.Conventions.Add(new AddLocalhostFilterConvention())*/)
.SetCompatibilityVersion(CompatibilityVersion.Latest)
// This is entirely optional and only useful when developing the Elsa.Dashboard project.
.AddRazorOptions(
options =>
{
@ -47,7 +50,19 @@ namespace Elsa.Dashboard.Web
)
.AddEntityFrameworkCoreWorkflowDefinitionStore()
.AddEntityFrameworkCoreWorkflowInstanceStore()
.AddElsaDashboard();
.AddElsaDashboard(
options => options
// Add activity definitions from configuration.
.Bind(Configuration.GetSection("WorkflowDesigner"))
.Configure(
configureOptions => configureOptions.ActivityDefinitions
// Add all activities from all referenced assemblies.
.Discover(
selector => selector.FromApplicationDependencies()
.AddClasses(x => x.AssignableTo<IActivity>())
)
)
);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)

View file

@ -1,5 +1,31 @@
{
"ConnectionStrings": {
"Sqlite": "Data Source=c:\\data\\elsa.db;Cache=Shared"
},
"WorkflowDesigner": {
"ActivityDefinitions": [
{
"Type": "MyCustomActivity2",
"DisplayName": "My Custom Activity 2",
"Category": "Custom",
"Description": "Demonstrates adding custom activities to the designer",
"Properties": [
{
"Name": "Property1",
"Label": "Property 1",
"Type": "expression",
"Hint": "Specify any value you like.",
"Options": {
"Multiline": false
},
"Designer": {
"Outcomes": [
"Done"
]
}
}
]
}
]
}
}
}

View file

@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Elsa.Dashboard.Areas.Elsa.ViewModels;
using Elsa.Dashboard.Extensions;
using Elsa.Dashboard.Models;
using Elsa.Dashboard.Options;
using Elsa.Dashboard.Services;
using Elsa.Extensions;
using Elsa.Models;
@ -12,6 +13,9 @@ using Elsa.Serialization;
using Elsa.Serialization.Formatters;
using Elsa.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Elsa.Dashboard.Areas.Elsa.Controllers
{
@ -23,6 +27,7 @@ namespace Elsa.Dashboard.Areas.Elsa.Controllers
private readonly IWorkflowInstanceStore workflowInstanceStore;
private readonly IWorkflowPublisher publisher;
private readonly IWorkflowSerializer serializer;
private readonly IOptions<ElsaDashboardOptions> options;
private readonly IIdGenerator idGenerator;
private readonly INotifier notifier;
@ -31,6 +36,7 @@ namespace Elsa.Dashboard.Areas.Elsa.Controllers
IWorkflowInstanceStore workflowInstanceStore,
IWorkflowPublisher publisher,
IWorkflowSerializer serializer,
IOptions<ElsaDashboardOptions> options,
IIdGenerator idGenerator,
INotifier notifier)
{
@ -38,6 +44,7 @@ namespace Elsa.Dashboard.Areas.Elsa.Controllers
this.workflowDefinitionStore = workflowDefinitionStore;
this.workflowInstanceStore = workflowInstanceStore;
this.serializer = serializer;
this.options = options;
this.idGenerator = idGenerator;
this.notifier = notifier;
}
@ -73,7 +80,8 @@ namespace Elsa.Dashboard.Areas.Elsa.Controllers
var model = new WorkflowDefinitionEditModel
{
Name = workflow.Name,
Json = serializer.Serialize(workflow, JsonTokenFormatter.FormatName)
Json = serializer.Serialize(workflow, JsonTokenFormatter.FormatName),
ActivityDefinitions = options.Value.ActivityDefinitions.ToArray()
};
return View(model);
@ -110,7 +118,8 @@ namespace Elsa.Dashboard.Areas.Elsa.Controllers
Id = workflow.Id,
Name = workflow.Name,
Description = workflow.Description,
Json = serializer.Serialize(workflow, JsonTokenFormatter.FormatName)
Json = serializer.Serialize(workflow, JsonTokenFormatter.FormatName),
ActivityDefinitions = options.Value.ActivityDefinitions.ToArray()
};
return View(model);

View file

@ -1,3 +1,5 @@
using Elsa.WorkflowDesigner.Models;
namespace Elsa.Dashboard.Areas.Elsa.ViewModels
{
public class WorkflowDefinitionEditModel
@ -7,5 +9,6 @@ namespace Elsa.Dashboard.Areas.Elsa.ViewModels
public string SubmitAction { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ActivityDefinition[] ActivityDefinitions { get; set; }
}
}

View file

@ -24,7 +24,7 @@
<div class="card-body">
<form asp-action="@formAction">
<input asp-for="Json" id="workflowData" type="hidden"/>
<wf-designer-host id="designerHost" canvas-height="300vh"></wf-designer-host>
<vc:workflow-designer activity-definitions="@Model.ActivityDefinitions" />
<div class="text-center">
<button asp-for="SubmitAction" name="SubmitAction" type="submit" value="draft" class="btn btn-primary mt-4">Save Draft</button>
<button asp-for="SubmitAction" name="SubmitAction" type="submit" value="publish" class="btn btn-success mt-4">Publish</button>

View file

@ -32,7 +32,7 @@
<!-- CSS Files -->
<link href="~/assets/css/argon-dashboard.css?v=1.1.0" rel="stylesheet"/>
<link href="~/assets/css/elsa.css?v=1.1.0" rel="stylesheet"/>
<script src='https://unpkg.com/@@elsa-workflows/elsa-workflow-designer@0.0.41/dist/elsa-workflow-designer.js'></script>
<script src='https://unpkg.com/@@elsa-workflows/elsa-workflow-designer@0.0.43/dist/elsa-workflow-designer.js'></script>
@await RenderSectionAsync("HeadScripts", false)
</head>

View file

@ -2,3 +2,4 @@
@using Elsa.Dashboard.Areas.Elsa.ViewModels
@addTagHelper *, GravatarHelper.AspNetCore
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Elsa.WorkflowDesigner

View file

@ -19,6 +19,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="2.2.0" />
<PackageReference Include="Scrutor" Version="3.0.2.19" />
</ItemGroup>
<ItemGroup>
@ -26,20 +27,17 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\core\Elsa.Abstractions\Elsa.Abstractions.csproj" />
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj" />
<ProjectReference Include="..\..\core\Elsa.Abstractions\Elsa.Abstractions.csproj" />
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj" />
<ProjectReference Include="..\Elsa.WorkflowDesigner\Elsa.WorkflowDesigner.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Extensions.FileProviders.Physical, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
<HintPath>..\..\..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.fileproviders.physical\2.2.0\lib\netstandard2.0\Microsoft.Extensions.FileProviders.Physical.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="wwwroot\assets\css\argon-dashboard.css" />
<_ContentIncludedByDefault Remove="wwwroot\assets\css\argon-dashboard.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\assets\css\argon-dashboard.min.css" />
<_ContentIncludedByDefault Remove="wwwroot\assets\css\argon-dashboard.css" />
<_ContentIncludedByDefault Remove="wwwroot\assets\css\argon-dashboard.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\assets\css\argon-dashboard.min.css" />
<_ContentIncludedByDefault Remove="Views\Shared\Components\WorkflowDesigner\Default.cshtml" />
<_ContentIncludedByDefault Remove="Views\_ViewImports.cshtml" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,33 @@
using System;
using Elsa.Dashboard.Options;
using Elsa.Services.Models;
using Elsa.WorkflowDesigner;
using Microsoft.Extensions.DependencyInjection;
using Scrutor;
namespace Elsa.Dashboard.Extensions
{
public static class ActivityDefinitionListExtensions
{
public static ActivityDefinitionList Add<T>(this ActivityDefinitionList list) where T : IActivity
{
return list.Add(ActivityDescriber.Describe<T>());
}
public static ActivityDefinitionList Discover(this ActivityDefinitionList list, Action<ITypeSourceSelector> selector)
{
var typeSourceSelector = new TypeSourceSelector();
selector(typeSourceSelector);
var serviceCollection = new ServiceCollection();
typeSourceSelector.Populate(serviceCollection, RegistrationStrategy.Replace(ReplacementBehavior.All));
foreach (var service in serviceCollection)
{
list.Add(ActivityDescriber.Describe(service.ImplementationType));
}
return list;
}
}
}

View file

@ -0,0 +1,29 @@
using System.Collections;
using System.Collections.Generic;
using Elsa.WorkflowDesigner.Models;
namespace Elsa.Dashboard.Options
{
public class ActivityDefinitionList : IEnumerable<ActivityDefinition>
{
public ActivityDefinitionList()
{
Items = new Dictionary<string, ActivityDefinition>();
}
private IDictionary<string, ActivityDefinition> Items { get; }
public ActivityDefinitionList Add(ActivityDefinition item)
{
Items[item.Type] = item;
return this;
}
public IEnumerator<ActivityDefinition> GetEnumerator() => Items.Values.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View file

@ -4,6 +4,13 @@ namespace Elsa.Dashboard.Options
{
public class ElsaDashboardOptions
{
public ElsaDashboardOptions()
{
Scripts = new List<string>();
ActivityDefinitions = new ActivityDefinitionList();
}
public IList<string> Scripts { get; set; }
public ActivityDefinitionList ActivityDefinitions { get; set; }
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

View file

@ -0,0 +1,97 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Elsa.Attributes;
using Elsa.Expressions;
using Elsa.Services.Models;
using Elsa.WorkflowDesigner.Models;
using Humanizer;
namespace Elsa.WorkflowDesigner
{
public static class ActivityDescriber
{
public static ActivityDefinition Describe<T>() where T : IActivity
{
return Describe(typeof(T));
}
public static ActivityDefinition Describe(Type activityType)
{
var activityDefinitionAttribute = activityType.GetCustomAttribute<ActivityDefinitionAttribute>();
var activityDesignerAttribute = activityType.GetCustomAttribute<ActivityDefinitionDesignerAttribute>();
var typeName = activityDefinitionAttribute?.Type ?? activityType.Name;
var displayName =
activityDefinitionAttribute?.DisplayName ??
activityType.Name.Humanize(LetterCasing.Title);
var description = activityDefinitionAttribute?.Description;
var category = activityDefinitionAttribute?.Category ?? "Miscellaneous";
var designerDescription = activityDesignerAttribute?.Description;
var designerOutcomes = activityDesignerAttribute?.Outcomes ?? new[] { OutcomeNames.Done };
var properties = DescribeProperties(activityType);
return new ActivityDefinition
{
Type = typeName.Camelize(),
DisplayName = displayName,
Description = description,
Category = category,
Properties = properties.ToArray(),
Designer = new ActivityDesignerSettings
{
Description = designerDescription,
Outcomes = designerOutcomes
}
};
}
private static IEnumerable<ActivityPropertyDescriptor> DescribeProperties(Type activityType)
{
var properties = activityType.GetProperties();
foreach (var propertyInfo in properties)
{
var activityProperty = propertyInfo.GetCustomAttribute<ActivityPropertyAttribute>();
if (activityProperty == null)
yield break;
yield return new ActivityPropertyDescriptor
{
Name = (activityProperty.Name ?? propertyInfo.Name).Camelize(),
Label = activityProperty.Label ?? propertyInfo.Name.Humanize(LetterCasing.Title),
Type = (activityProperty.Type ?? DeterminePropertyType(propertyInfo)).Camelize(),
Hint = activityProperty.Hint,
Options = GetPropertyTypeOptions(propertyInfo)
};
}
}
private static object GetPropertyTypeOptions(PropertyInfo propertyInfo)
{
var optionsAttribute = propertyInfo.GetCustomAttribute<ActivityPropertyOptionsAttribute>();
return optionsAttribute?.GetOptions() ?? new object();
}
private static string DeterminePropertyType(PropertyInfo propertyInfo)
{
var type = propertyInfo.PropertyType;
if (typeof(IWorkflowExpression).IsAssignableFrom(type))
return ActivityPropertyTypes.Expression;
if (type == typeof(bool) || type == typeof(bool?))
return ActivityPropertyTypes.Boolean;
if (typeof(IEnumerable).IsAssignableFrom(type))
return ActivityPropertyTypes.List;
return ActivityPropertyTypes.Text;
}
}
}

View file

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<PackageVersion>1.0.0</PackageVersion>
<Authors>Sipke Schoorstra</Authors>
<Description>Elsa is a set of workflowing libraries and tools to enable super-fast workflowing capabilities in any .NET Core application.</Description>
<Copyright>2019</Copyright>
<PackageProjectUrl>https://github.com/elsa-workflows/elsa-core</PackageProjectUrl>
<RepositoryUrl>https://github.com/elsa-workflows/elsa-core</RepositoryUrl>
<RepositoryType>GitHub</RepositoryType>
<PackageTags>elsa, workflows</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Humanizer.Core" Version="2.7.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\core\Elsa.Abstractions\Elsa.Abstractions.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,17 @@
namespace Elsa.WorkflowDesigner.Models
{
public class ActivityDefinition
{
public ActivityDefinition()
{
Properties = new ActivityPropertyDescriptor[0];
}
public string Type { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public string Category { get; set; }
public ActivityPropertyDescriptor[] Properties { get; set; }
public ActivityDesignerSettings Designer { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Elsa.WorkflowDesigner.Models
{
public class ActivityDesignerSettings
{
public object Description { get; set; }
public object Outcomes { get; set; }
}
}

View file

@ -0,0 +1,11 @@
namespace Elsa.WorkflowDesigner.Models
{
public class ActivityPropertyDescriptor
{
public string Name { get; set; }
public string Type { get; set; }
public string Label { get; set; }
public string Hint { get; set; }
public object Options { get; set; }
}
}

View file

@ -0,0 +1,11 @@
namespace Elsa.WorkflowDesigner.Models
{
public static class ActivityPropertyTypes
{
public const string Text = "text";
public const string Boolean = "boolean";
public const string Expression = "expression";
public const string List = "list";
public const string Select = "select";
}
}

View file

@ -0,0 +1,7 @@
namespace Elsa.WorkflowDesigner.Models
{
public class WorkflowExpressionOptions
{
public bool Multiline { get; set; }
}
}

View file

@ -0,0 +1,32 @@
using Elsa.WorkflowDesigner.Models;
using Elsa.WorkflowDesigner.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Elsa.WorkflowDesigner.ViewComponents
{
public class WorkflowDesignerViewComponent : ViewComponent
{
public IViewComponentResult Invoke(ActivityDefinition[] activityDefinitions)
{
var model = new WorkflowDesignerViewComponentModel
{
ActivityDefinitionsJson = GetActivityDefinitionOptions(activityDefinitions)
};
return View(model);
}
private string GetActivityDefinitionOptions(ActivityDefinition[] activityDefinitions)
{
var definitions = activityDefinitions ?? new ActivityDefinition[0];
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore
};
return JsonConvert.SerializeObject(definitions, settings);
}
}
}

View file

@ -0,0 +1,7 @@
namespace Elsa.WorkflowDesigner.ViewModels
{
public class WorkflowDesignerViewComponentModel
{
public string ActivityDefinitionsJson { get; set; }
}
}

View file

@ -0,0 +1,3 @@
@model Elsa.WorkflowDesigner.ViewModels.WorkflowDesignerViewComponentModel
<wf-designer-host id="designerHost" canvas-height="300vh" activity-definitions="@Model.ActivityDefinitionsJson"></wf-designer-host>

View file

@ -0,0 +1,2 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Elsa.Dashboard