Add project files.
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
<Project>
|
||||
|
||||
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
|
||||
|
||||
<PropertyGroup Label="TargetFrameworks">
|
||||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Label="Files">
|
||||
<None Include="..\..\..\icon.png" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwait.Fody" PrivateAssets="All" />
|
||||
<PackageReference Include="Fody" PrivateAssets="All" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoWarn>$(NoWarn);CS0162;CS1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwait.Fody" PrivateAssets="All"/>
|
||||
<PackageReference Include="Fody" PrivateAssets="All"/>
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All"/>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Yarp.ReverseProxy"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
@@ -0,0 +1,5 @@
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
|
||||
var app = builder.Build();
|
||||
app.MapReverseProxy();
|
||||
await app.RunAsync();
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:28212",
|
||||
"sslPort": 44310
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5272",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7036;http://localhost:5272",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ReverseProxy": {
|
||||
"Routes": {
|
||||
"route1": {
|
||||
"ClusterId": "cluster1",
|
||||
"Match": {
|
||||
"Path": "{**catch-all}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Clusters": {
|
||||
"cluster1": {
|
||||
"LoadBalancingPolicy": "RoundRobin",
|
||||
"Destinations": {
|
||||
"cluster1/destination1": {
|
||||
"Address": "https://localhost:5001/"
|
||||
},
|
||||
"cluster1/destination2": {
|
||||
"Address": "https://localhost:5002/"
|
||||
},
|
||||
"cluster1/destination3": {
|
||||
"Address": "https://localhost:5003/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Activities.Flowchart.Attributes;
|
||||
using Elsa.Workflows.Attributes;
|
||||
|
||||
namespace Elsa.Server.Web.Activities;
|
||||
|
||||
[Activity("Elsa", "Authorization", "Authorizes a flow based on the configured policies.")]
|
||||
[FlowNode("Authorized", "Unauthorized", "Error")]
|
||||
public class AuthorizeFlow : Activity<string>
|
||||
{
|
||||
protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var httpContext = context.GetRequiredService<IHttpContextAccessor>().HttpContext;
|
||||
|
||||
if (httpContext == null)
|
||||
throw new InvalidOperationException("HttpContext is not available. Ensure that the activity is executed within an HTTP request context.");
|
||||
|
||||
var bookmark = context.CreateBookmark(new AuthorizeStimulus(), OnResumeAsync);
|
||||
var redirectUrl = context.ExpressionExecutionContext.GenerateBookmarkTriggerUrl(bookmark.Id);
|
||||
|
||||
Result.Set(context, redirectUrl);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
private async ValueTask OnResumeAsync(ActivityExecutionContext context)
|
||||
{
|
||||
if (!context.TryGetWorkflowInput<string>("Answer", out var response))
|
||||
{
|
||||
await context.CompleteActivityWithOutcomesAsync("Unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (response)
|
||||
{
|
||||
case "Authorized":
|
||||
await context.CompleteActivityWithOutcomesAsync("Authorized");
|
||||
return;
|
||||
case "Error":
|
||||
await context.CompleteActivityWithOutcomesAsync("Error");
|
||||
return;
|
||||
default:
|
||||
await context.CompleteActivityWithOutcomesAsync("Unauthorized");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record AuthorizeStimulus;
|
||||
@@ -0,0 +1,19 @@
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Attributes;
|
||||
using Elsa.Workflows.Models;
|
||||
using Elsa.Workflows.UIHints;
|
||||
using Elsa.Workflows.UIHints.CheckList;
|
||||
|
||||
namespace Elsa.Server.Web.Activities;
|
||||
|
||||
public class CheckedActivity : CodeActivity<string[]>
|
||||
{
|
||||
[Input(UIHint = InputUIHints.CheckList, UIHandler = typeof(CustomCheckListOptionsProvider))]
|
||||
public Input<string[]> CheckedItems { get; set; }
|
||||
|
||||
protected override void Execute(ActivityExecutionContext context)
|
||||
{
|
||||
context.SetResult(CheckedItems.GetOrDefault(context));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows;
|
||||
|
||||
namespace Elsa.Server.Web.Activities;
|
||||
|
||||
public class CustomDelay : Activity
|
||||
{
|
||||
protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
context.DelayFor(TimeSpan.FromSeconds(5), OnDelayElapsedAsync);
|
||||
return default;
|
||||
}
|
||||
|
||||
private async ValueTask OnDelayElapsedAsync(ActivityExecutionContext context)
|
||||
{
|
||||
Console.WriteLine("Delay elapsed");
|
||||
await context.CompleteActivityAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Runtime.Activities;
|
||||
|
||||
namespace Elsa.Server.Web.Activities;
|
||||
|
||||
public class CustomEvent : EventBase<object>
|
||||
{
|
||||
protected override string GetEventName(ExpressionExecutionContext context)
|
||||
{
|
||||
return "MyEvent";
|
||||
}
|
||||
|
||||
protected override void OnEventReceived(ActivityExecutionContext context, object? eventData)
|
||||
{
|
||||
Console.WriteLine("Event received with data: " + eventData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Elsa.Http;
|
||||
using Elsa.Http.Extensions;
|
||||
using Elsa.Workflows;
|
||||
|
||||
namespace Elsa.Server.Web.Activities;
|
||||
|
||||
public class CustomHttpEndpoint : HttpEndpointBase
|
||||
{
|
||||
protected override HttpEndpointOptions GetOptions()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Path = "my-path",
|
||||
Methods = [HttpMethods.Get],
|
||||
};
|
||||
}
|
||||
|
||||
protected override async ValueTask OnHttpRequestReceivedAsync(ActivityExecutionContext context, HttpContext httpContext)
|
||||
{
|
||||
httpContext.Response.StatusCode = 200;
|
||||
await httpContext.Response.WriteAsync("Hello World", context.CancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Scheduling.Activities;
|
||||
using Elsa.Workflows;
|
||||
|
||||
namespace Elsa.Server.Web.Activities;
|
||||
|
||||
public class CustomTimer : TimerBase
|
||||
{
|
||||
protected override TimeSpan GetInterval(ExpressionExecutionContext context)
|
||||
{
|
||||
return TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
protected override void OnTimerElapsed(ActivityExecutionContext context)
|
||||
{
|
||||
Console.WriteLine("Timer elapsed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Agents.Persistence.EntityFrameworkCore.MySql\Elsa.Agents.Persistence.EntityFrameworkCore.MySql.csproj" Condition=" '$(TargetFramework)' != 'net9.0' " />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Caching.Distributed.MassTransit\Elsa.Caching.Distributed.MassTransit.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Caching.Distributed.ProtoActor\Elsa.Caching.Distributed.ProtoActor.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.MySql\Elsa.EntityFrameworkCore.MySql.csproj" Condition=" '$(TargetFramework)' != 'net9.0' " />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.Oracle\Elsa.EntityFrameworkCore.Oracle.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.PostgreSql\Elsa.EntityFrameworkCore.PostgreSql.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Kafka\Elsa.Kafka.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.MassTransit.AzureServiceBus\Elsa.MassTransit.AzureServiceBus.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.OpenTelemetry\Elsa.OpenTelemetry.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Quartz.EntityFrameworkCore.MySql\Elsa.Quartz.EntityFrameworkCore.MySql.csproj" Condition=" '$(TargetFramework)' != 'net9.0' " />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Quartz.EntityFrameworkCore.PostgreSql\Elsa.Quartz.EntityFrameworkCore.PostgreSql.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Retention\Elsa.Retention.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Secrets.Api\Elsa.Secrets.Api.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql\Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite\Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer\Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Secrets.Scripting\Elsa.Secrets.Scripting.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Sql.MySql\Elsa.Sql.MySql.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Sql.PostgreSql\Elsa.Sql.PostgreSql.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Sql.Sqlite\Elsa.Sql.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Sql.SqlServer\Elsa.Sql.SqlServer.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Sql\Elsa.Sql.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Tenants.AspNetCore\Elsa.Tenants.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Webhooks\Elsa.Webhooks.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\common\Elsa.DropIns\Elsa.DropIns.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Alterations.MassTransit\Elsa.Alterations.MassTransit.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Alterations\Elsa.Alterations.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.CSharp\Elsa.CSharp.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.SqlServer\Elsa.EntityFrameworkCore.SqlServer.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.MassTransit.RabbitMq\Elsa.MassTransit.RabbitMq.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Python\Elsa.Python.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Quartz.EntityFrameworkCore.Sqlite\Elsa.Quartz.EntityFrameworkCore.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.FileStorage\Elsa.FileStorage.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Dapper.Migrations\Elsa.Dapper.Migrations.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Dapper\Elsa.Dapper.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Elasticsearch\Elsa.Elasticsearch.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Email\Elsa.Email.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Environments\Elsa.Environments.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.MassTransit\Elsa.MassTransit.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.MongoDb\Elsa.MongoDb.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Workflows.Runtime.ProtoActor\Elsa.Workflows.Runtime.ProtoActor.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Identity\Elsa.Identity.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.WorkflowProviders.BlobStorage\Elsa.WorkflowProviders.BlobStorage.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Hangfire\Elsa.Hangfire.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Http\Elsa.Http.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Labels\Elsa.Labels.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Liquid\Elsa.Liquid.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Quartz\Elsa.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.WorkflowContexts\Elsa.WorkflowContexts.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Bogus" />
|
||||
<PackageReference Include="DistributedLock.Postgres" />
|
||||
<PackageReference Include="DistributedLock.Redis" />
|
||||
<PackageReference Include="FluentStorage.Azure.Blobs" />
|
||||
<PackageReference Include="Hangfire.PostgreSql" />
|
||||
<PackageReference Include="OpenTelemetry" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.Console" />
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.SqlClient" />
|
||||
<PackageReference Include="Proto.Cluster.Kubernetes" />
|
||||
<PackageReference Include="Grpc.Net.Client" />
|
||||
<PackageReference Include="Proto.Persistence.Sqlite" />
|
||||
<PackageReference Include="Proto.Persistence.SqlServer" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="App_Data\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=enums/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Elsa.Server.Web;
|
||||
|
||||
public enum ApplicationRole
|
||||
{
|
||||
Default,
|
||||
Api,
|
||||
Worker,
|
||||
Monitor
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Elsa.Server.Web;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the transport options for distributed caching.
|
||||
/// </summary>
|
||||
public enum DistributedCachingTransport
|
||||
{
|
||||
None,
|
||||
Memory,
|
||||
MassTransit,
|
||||
ProtoActor
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Elsa.Server.Web;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the type of messaging broker used in MassTransit.
|
||||
/// </summary>
|
||||
public enum MassTransitBroker
|
||||
{
|
||||
Memory,
|
||||
AzureServiceBus,
|
||||
RabbitMq
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Elsa.Server.Web;
|
||||
|
||||
public enum PersistenceProvider
|
||||
{
|
||||
Memory,
|
||||
EntityFrameworkCore,
|
||||
MongoDb,
|
||||
Dapper
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Elsa.Server.Web;
|
||||
|
||||
public enum SqlDatabaseProvider
|
||||
{
|
||||
SqlServer,
|
||||
Sqlite,
|
||||
MySql,
|
||||
PostgreSql,
|
||||
Citus,
|
||||
YugabyteDb,
|
||||
Oracle,
|
||||
CockroachDb
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Elsa.Server.Web;
|
||||
|
||||
public enum WorkflowRuntime
|
||||
{
|
||||
Default,
|
||||
Distributed,
|
||||
ProtoActor
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using Elsa.Common.Multitenancy;
|
||||
using Elsa.EntityFrameworkCore.Extensions;
|
||||
|
||||
namespace Elsa.Server.Web.Extensions;
|
||||
|
||||
public static class MultitenantConnectionStringServiceProviderExtensions
|
||||
{
|
||||
public static string GetSqliteConnectionString(this IServiceProvider serviceProvider) => serviceProvider.GetConnectionString("Sqlite")!;
|
||||
public static string GetSqlServerConnectionString(this IServiceProvider serviceProvider) => serviceProvider.GetConnectionString("SqlServer")!;
|
||||
public static string GetMySqlConnectionString(this IServiceProvider serviceProvider) => serviceProvider.GetConnectionString("MySql")!;
|
||||
public static string GetPostgreSqlConnectionString(this IServiceProvider serviceProvider) => serviceProvider.GetConnectionString("PostgreSql")!;
|
||||
|
||||
public static string? GetConnectionString(this IServiceProvider serviceProvider, string name)
|
||||
{
|
||||
var tenantAccessor = serviceProvider.GetRequiredService<ITenantAccessor>();
|
||||
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
|
||||
var tenant = tenantAccessor.Tenant;
|
||||
var defaultConnectionString = configuration.GetConnectionString(name)!;
|
||||
var connectionString = tenant?.GetConnectionString(name) ?? defaultConnectionString;
|
||||
return connectionString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Elsa.Http;
|
||||
using Elsa.Workflows;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Server.Web.Filters;
|
||||
|
||||
/// <summary>
|
||||
/// Mask the value of the input if it is a HttpRequest and the input name is "Authorization".
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class HttpRequestAuthenticationHeaderFilter : ActivityStateFilterBase
|
||||
{
|
||||
protected override ActivityStateFilterResult OnExecute(ActivityStateFilterContext context)
|
||||
{
|
||||
var activityExecutionContext = context.ActivityExecutionContext;
|
||||
var activity = activityExecutionContext.Activity;
|
||||
var inputDescriptor = context.InputDescriptor;
|
||||
|
||||
if (activity is not SendHttpRequestBase || inputDescriptor.Name is not nameof(SendHttpRequestBase.Authorization))
|
||||
return ActivityStateFilterResult.Pass();
|
||||
|
||||
var contextValue = context.Value.GetString();
|
||||
|
||||
if (contextValue == null)
|
||||
return ActivityStateFilterResult.Pass();
|
||||
|
||||
var maskedValue = new string('*', contextValue.Length);
|
||||
return Filtered(maskedValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Elsa.Server.Web.Messages;
|
||||
|
||||
public class OrderReceived
|
||||
{
|
||||
public string OrderId { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,828 @@
|
||||
using System.Text.Encodings.Web;
|
||||
using Elsa.Alterations.Extensions;
|
||||
using Elsa.Alterations.MassTransit.Extensions;
|
||||
using Elsa.Caching.Options;
|
||||
using Elsa.Common.Codecs;
|
||||
using Elsa.Common.DistributedHosting.DistributedLocks;
|
||||
using Elsa.Common.RecurringTasks;
|
||||
using Elsa.Common.Serialization;
|
||||
using Elsa.Dapper.Extensions;
|
||||
using Elsa.Dapper.Services;
|
||||
using Elsa.DropIns.Extensions;
|
||||
using Elsa.EntityFrameworkCore.Extensions;
|
||||
using Elsa.EntityFrameworkCore.Modules.Alterations;
|
||||
using Elsa.EntityFrameworkCore.Modules.Identity;
|
||||
using Elsa.EntityFrameworkCore.Modules.Management;
|
||||
using Elsa.EntityFrameworkCore.Modules.Runtime;
|
||||
using Elsa.EntityFrameworkCore.Modules.Tenants;
|
||||
using Elsa.Expressions.Helpers;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Identity.Multitenancy;
|
||||
using Elsa.Kafka;
|
||||
using Elsa.Kafka.Factories;
|
||||
using Elsa.MassTransit.Extensions;
|
||||
using Elsa.MongoDb.Extensions;
|
||||
using Elsa.MongoDb.Modules.Alterations;
|
||||
using Elsa.MongoDb.Modules.Identity;
|
||||
using Elsa.MongoDb.Modules.Management;
|
||||
using Elsa.MongoDb.Modules.Runtime;
|
||||
using Elsa.MongoDb.Modules.Tenants;
|
||||
using Elsa.OpenTelemetry.Middleware;
|
||||
using Elsa.Retention.Extensions;
|
||||
using Elsa.Retention.Models;
|
||||
using Elsa.Secrets.Extensions;
|
||||
using Elsa.Secrets.Management.Tasks;
|
||||
using Elsa.Secrets.Persistence;
|
||||
using Elsa.Server.Web;
|
||||
using Elsa.Server.Web.Extensions;
|
||||
using Elsa.Server.Web.Filters;
|
||||
using Elsa.Server.Web.Messages;
|
||||
using Elsa.Sql.Extensions;
|
||||
using Elsa.Sql.MySql;
|
||||
using Elsa.Sql.PostgreSql;
|
||||
using Elsa.Sql.Sqlite;
|
||||
using Elsa.Sql.SqlServer;
|
||||
using Elsa.Tenants.AspNetCore;
|
||||
using Elsa.Tenants.Extensions;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Api;
|
||||
using Elsa.Workflows.CommitStates.Strategies;
|
||||
using Elsa.Workflows.IncidentStrategies;
|
||||
using Elsa.Workflows.LogPersistence;
|
||||
using Elsa.Workflows.Management;
|
||||
using Elsa.Workflows.Management.Stores;
|
||||
using Elsa.Workflows.Options;
|
||||
using Elsa.Workflows.Runtime.Distributed.Extensions;
|
||||
using Elsa.Workflows.Runtime.Options;
|
||||
using Elsa.Workflows.Runtime.Stores;
|
||||
using Elsa.Workflows.Runtime.Tasks;
|
||||
using Hangfire;
|
||||
using Hangfire.MemoryStorage;
|
||||
using Hangfire.PostgreSql;
|
||||
using Hangfire.PostgreSql.Factories;
|
||||
using Hangfire.SqlServer;
|
||||
using Hangfire.Storage.SQLite;
|
||||
using JetBrains.Annotations;
|
||||
using Medallion.Threading.FileSystem;
|
||||
using Medallion.Threading.Postgres;
|
||||
using Medallion.Threading.Redis;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Trace;
|
||||
using Proto.Cluster.Kubernetes;
|
||||
using Proto.Persistence.Sqlite;
|
||||
using Proto.Persistence.SqlServer;
|
||||
using Proto.Remote;
|
||||
using Proto.Remote.GrpcNet;
|
||||
using StackExchange.Redis;
|
||||
|
||||
// ReSharper disable RedundantAssignment
|
||||
const PersistenceProvider persistenceProvider = PersistenceProvider.EntityFrameworkCore;
|
||||
const bool useDbContextPooling = false;
|
||||
const bool useHangfire = false;
|
||||
const bool useQuartz = true;
|
||||
const bool useMassTransit = false;
|
||||
const bool useZipCompression = false;
|
||||
const bool runEFCoreMigrations = true;
|
||||
const bool useMemoryStores = false;
|
||||
const bool useCaching = true;
|
||||
const bool useKafka = false;
|
||||
const bool useReadOnlyMode = false;
|
||||
const bool useSignalR = false; // Disabled until Elsa Studio sends authenticated requests.
|
||||
const WorkflowRuntime workflowRuntime = WorkflowRuntime.Distributed;
|
||||
const DistributedCachingTransport distributedCachingTransport = DistributedCachingTransport.Memory;
|
||||
const MassTransitBroker massTransitBroker = MassTransitBroker.Memory;
|
||||
const bool useMultitenancy = false;
|
||||
const bool useTenantsFromConfiguration = true;
|
||||
const bool useSecrets = false;
|
||||
const bool disableVariableWrappers = false;
|
||||
const bool disableVariableCopying = false;
|
||||
const bool useManualOtelInstrumentation = false;
|
||||
|
||||
ObjectConverter.StrictMode = true;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var services = builder.Services;
|
||||
var configuration = builder.Configuration;
|
||||
var identitySection = configuration.GetSection("Identity");
|
||||
var identityTokenSection = identitySection.GetSection("Tokens");
|
||||
var sqliteConnectionString = configuration.GetConnectionString("Sqlite")!;
|
||||
var sqlServerConnectionString = configuration.GetConnectionString("SqlServer")!;
|
||||
var postgresConnectionString = configuration.GetConnectionString("PostgreSql")!;
|
||||
var citusConnectionString = configuration.GetConnectionString("Citus")!;
|
||||
var yugabyteDbConnectionString = configuration.GetConnectionString("YugabyteDb")!;
|
||||
var oracleConnectionString = configuration.GetConnectionString("Oracle")!;
|
||||
var mySqlConnectionString = configuration.GetConnectionString("MySql")!;
|
||||
var cockroachDbConnectionString = configuration.GetConnectionString("CockroachDb")!;
|
||||
var mongoDbConnectionString = configuration.GetConnectionString("MongoDb")!;
|
||||
var azureServiceBusConnectionString = configuration.GetConnectionString("AzureServiceBus")!;
|
||||
var rabbitMqConnectionString = configuration.GetConnectionString("RabbitMq")!;
|
||||
var redisConnectionString = configuration.GetConnectionString("Redis")!;
|
||||
var distributedLockProviderName = configuration.GetSection("Runtime:DistributedLocking")["Provider"];
|
||||
var appRole = Enum.Parse<ApplicationRole>(configuration["AppRole"] ?? "Default");
|
||||
var sqlDatabaseProvider = Enum.Parse<SqlDatabaseProvider>(configuration["DatabaseProvider"] ?? "Sqlite");
|
||||
|
||||
// Optionally create type aliases for easier configuration.
|
||||
TypeAliasRegistry.RegisterAlias("OrderReceivedProducerFactory", typeof(GenericProducerFactory<string, OrderReceived>));
|
||||
TypeAliasRegistry.RegisterAlias("OrderReceivedConsumerFactory", typeof(GenericConsumerFactory<string, OrderReceived>));
|
||||
|
||||
if (useManualOtelInstrumentation)
|
||||
{
|
||||
services.AddOpenTelemetry()
|
||||
.ConfigureResource(resource => resource.AddService("elsa-workflows", serviceVersion: "3.5.0").AddTelemetrySdk())
|
||||
.WithTracing(tracing =>
|
||||
{
|
||||
tracing
|
||||
.AddSource("*")
|
||||
.SetSampler(new AlwaysOnSampler())
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddHttpClientInstrumentation()
|
||||
.AddSqlClientInstrumentation()
|
||||
.AddConsoleExporter()
|
||||
.AddOtlpExporter()
|
||||
;
|
||||
})
|
||||
.WithMetrics(metrics =>
|
||||
{
|
||||
metrics
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddHttpClientInstrumentation()
|
||||
.AddConsoleExporter()
|
||||
.AddOtlpExporter()
|
||||
;
|
||||
});
|
||||
|
||||
// Enable OpenTelemetry Logging (optional)
|
||||
builder.Logging.AddOpenTelemetry(options =>
|
||||
{
|
||||
options.IncludeFormattedMessage = true;
|
||||
options.IncludeScopes = true;
|
||||
options.ParseStateValues = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Add Elsa services.
|
||||
services.AddScoped<IPropertyUIHandler, CustomCheckListOptionsProvider>();
|
||||
services
|
||||
.AddElsa(elsa =>
|
||||
{
|
||||
if (persistenceProvider == PersistenceProvider.MongoDb)
|
||||
elsa.UseMongoDb(mongoDbConnectionString);
|
||||
|
||||
if (persistenceProvider == PersistenceProvider.Dapper)
|
||||
elsa.UseDapper(dapper =>
|
||||
{
|
||||
dapper.UseMigrations(feature =>
|
||||
{
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer)
|
||||
feature.UseSqlServer();
|
||||
else
|
||||
feature.UseSqlite();
|
||||
});
|
||||
dapper.DbConnectionProvider = sp =>
|
||||
{
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer)
|
||||
return new SqlServerDbConnectionProvider(sqlServerConnectionString);
|
||||
else
|
||||
return new SqliteDbConnectionProvider(sqliteConnectionString);
|
||||
};
|
||||
});
|
||||
|
||||
if (useHangfire)
|
||||
{
|
||||
JobStorage jobStorage;
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql)
|
||||
{
|
||||
jobStorage = new PostgreSqlStorage(new NpgsqlConnectionFactory(postgresConnectionString, new()
|
||||
{
|
||||
QueuePollInterval = TimeSpan.FromSeconds(1)
|
||||
}));
|
||||
}
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.Sqlite)
|
||||
{
|
||||
jobStorage = new SQLiteStorage(sqliteConnectionString, new()
|
||||
{
|
||||
QueuePollInterval = TimeSpan.FromSeconds(1)
|
||||
});
|
||||
}
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer)
|
||||
{
|
||||
jobStorage = new SqlServerStorage(sqlServerConnectionString, new()
|
||||
{
|
||||
QueuePollInterval = TimeSpan.FromSeconds(1)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
jobStorage = new MemoryStorage();
|
||||
}
|
||||
|
||||
elsa.UseHangfire(hangfire => hangfire.UseJobStorage(jobStorage));
|
||||
}
|
||||
|
||||
elsa
|
||||
.AddActivitiesFrom<Program>()
|
||||
.AddWorkflowsFrom<Program>()
|
||||
.UseFluentStorageProvider()
|
||||
.UseFileStorage()
|
||||
.UseIdentity(identity =>
|
||||
{
|
||||
if (persistenceProvider == PersistenceProvider.MongoDb)
|
||||
identity.UseMongoDb();
|
||||
else if (persistenceProvider == PersistenceProvider.Dapper)
|
||||
identity.UseDapper();
|
||||
else
|
||||
identity.UseEntityFrameworkCore(ef =>
|
||||
{
|
||||
ef.UseContextPooling = useDbContextPooling;
|
||||
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer)
|
||||
ef.UseSqlServer(sqlServerConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql)
|
||||
ef.UsePostgreSql(postgresConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.Citus)
|
||||
ef.UsePostgreSql(citusConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.YugabyteDb)
|
||||
ef.UsePostgreSql(yugabyteDbConnectionString);
|
||||
#if !NET9_0
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.MySql)
|
||||
ef.UseMySql(mySqlConnectionString);
|
||||
#endif
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb)
|
||||
ef.UsePostgreSql(cockroachDbConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.Oracle)
|
||||
ef.UseOracle(oracleConnectionString, new()
|
||||
{
|
||||
SchemaName = "ELSA"
|
||||
});
|
||||
else
|
||||
ef.UseSqlite(sp => sp.GetSqliteConnectionString());
|
||||
|
||||
ef.RunMigrations = runEFCoreMigrations;
|
||||
});
|
||||
|
||||
identity.TokenOptions = options => identityTokenSection.Bind(options);
|
||||
identity.UseConfigurationBasedUserProvider(options => identitySection.Bind(options));
|
||||
identity.UseConfigurationBasedApplicationProvider(options => identitySection.Bind(options));
|
||||
identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options));
|
||||
})
|
||||
.UseDefaultAuthentication()
|
||||
.UseWorkflows(workflows =>
|
||||
{
|
||||
workflows.WithDefaultWorkflowExecutionPipeline(pipeline => pipeline.UseWorkflowExecutionTracing());
|
||||
workflows.WithDefaultActivityExecutionPipeline(pipeline => pipeline.UseActivityExecutionTracing());
|
||||
workflows.UseCommitStrategies(strategies =>
|
||||
{
|
||||
strategies.AddStandardStrategies();
|
||||
strategies.Add("Every 10 seconds", new PeriodicWorkflowStrategy(TimeSpan.FromSeconds(10)));
|
||||
});
|
||||
})
|
||||
.UseWorkflowManagement(management =>
|
||||
{
|
||||
if (persistenceProvider == PersistenceProvider.MongoDb)
|
||||
management.UseMongoDb();
|
||||
else if (persistenceProvider == PersistenceProvider.Dapper)
|
||||
management.UseDapper();
|
||||
else
|
||||
management.UseEntityFrameworkCore(ef =>
|
||||
{
|
||||
ef.UseContextPooling = useDbContextPooling;
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer)
|
||||
ef.UseSqlServer(sqlServerConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql)
|
||||
ef.UsePostgreSql(postgresConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.Citus)
|
||||
ef.UsePostgreSql(citusConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.YugabyteDb)
|
||||
ef.UsePostgreSql(yugabyteDbConnectionString);
|
||||
#if !NET9_0
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.MySql)
|
||||
ef.UseMySql(mySqlConnectionString);
|
||||
#endif
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb)
|
||||
ef.UsePostgreSql(cockroachDbConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.Oracle)
|
||||
ef.UseOracle(oracleConnectionString, new()
|
||||
{
|
||||
SchemaName = "ELSA"
|
||||
});
|
||||
else
|
||||
ef.UseSqlite(sp => sp.GetSqliteConnectionString());
|
||||
|
||||
ef.RunMigrations = runEFCoreMigrations;
|
||||
});
|
||||
|
||||
if (useZipCompression)
|
||||
management.SetCompressionAlgorithm(nameof(Zstd));
|
||||
|
||||
if (useMemoryStores)
|
||||
management.UseWorkflowInstances(feature => feature.WorkflowInstanceStore = sp => sp.GetRequiredService<MemoryWorkflowInstanceStore>());
|
||||
|
||||
if (useMassTransit)
|
||||
management.UseMassTransitDispatcher();
|
||||
|
||||
if (useCaching)
|
||||
management.UseCache();
|
||||
|
||||
management.SetDefaultLogPersistenceMode(LogPersistenceMode.Inherit);
|
||||
management.UseReadOnlyMode(useReadOnlyMode);
|
||||
management.AddVariableTypeAndAlias<OrderReceived>("Application");
|
||||
})
|
||||
.UseWorkflowRuntime(runtime =>
|
||||
{
|
||||
if (persistenceProvider == PersistenceProvider.MongoDb)
|
||||
runtime.UseMongoDb();
|
||||
else if (persistenceProvider == PersistenceProvider.Dapper)
|
||||
runtime.UseDapper();
|
||||
else
|
||||
runtime.UseEntityFrameworkCore(ef =>
|
||||
{
|
||||
ef.UseContextPooling = useDbContextPooling;
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer)
|
||||
{
|
||||
//ef.UseSqlServer(sqlServerConnectionString, new ElsaDbContextOptions);
|
||||
var migrationsAssembly = typeof(Elsa.EntityFrameworkCore.SqlServer.IdentityDbContextFactory).Assembly;
|
||||
var connectionString = sqlServerConnectionString;
|
||||
ef.DbContextOptionsBuilder = (_, db) => db.UseElsaSqlServer(migrationsAssembly, connectionString, null, configure => configure.CommandTimeout(60000));
|
||||
}
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql)
|
||||
ef.UsePostgreSql(postgresConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.Citus)
|
||||
ef.UsePostgreSql(citusConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.YugabyteDb)
|
||||
ef.UsePostgreSql(yugabyteDbConnectionString);
|
||||
#if !NET9_0
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.MySql)
|
||||
ef.UseMySql(mySqlConnectionString);
|
||||
#endif
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb)
|
||||
ef.UsePostgreSql(cockroachDbConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.Oracle)
|
||||
ef.UseOracle(oracleConnectionString, new()
|
||||
{
|
||||
SchemaName = "ELSA"
|
||||
});
|
||||
else
|
||||
ef.UseSqlite(sp => sp.GetSqliteConnectionString());
|
||||
|
||||
ef.RunMigrations = runEFCoreMigrations;
|
||||
});
|
||||
|
||||
if (workflowRuntime == WorkflowRuntime.Distributed)
|
||||
{
|
||||
runtime.UseDistributedRuntime();
|
||||
}
|
||||
|
||||
if (workflowRuntime == WorkflowRuntime.ProtoActor)
|
||||
{
|
||||
runtime.UseProtoActor();
|
||||
}
|
||||
|
||||
if (useMassTransit)
|
||||
runtime.UseMassTransitDispatcher();
|
||||
|
||||
runtime.WorkflowDispatcherOptions = options => configuration.GetSection("Runtime:WorkflowDispatcher").Bind(options);
|
||||
|
||||
if (useMemoryStores)
|
||||
{
|
||||
runtime.ActivityExecutionLogStore = sp => sp.GetRequiredService<MemoryActivityExecutionStore>();
|
||||
runtime.WorkflowExecutionLogStore = sp => sp.GetRequiredService<MemoryWorkflowExecutionLogStore>();
|
||||
}
|
||||
|
||||
if (useCaching)
|
||||
runtime.UseCache();
|
||||
|
||||
runtime.DistributedLockingOptions = options => configuration.GetSection("Runtime:DistributedLocking").Bind(options);
|
||||
|
||||
runtime.DistributedLockProvider = _ =>
|
||||
{
|
||||
switch (distributedLockProviderName)
|
||||
{
|
||||
case "Postgres":
|
||||
return new PostgresDistributedSynchronizationProvider(postgresConnectionString, options =>
|
||||
{
|
||||
options.KeepaliveCadence(TimeSpan.FromMinutes(5));
|
||||
options.UseMultiplexing();
|
||||
});
|
||||
case "Redis":
|
||||
{
|
||||
var connectionMultiplexer = ConnectionMultiplexer.Connect(redisConnectionString);
|
||||
var database = connectionMultiplexer.GetDatabase();
|
||||
return new RedisDistributedSynchronizationProvider(database);
|
||||
}
|
||||
case "File":
|
||||
return new FileDistributedSynchronizationProvider(new(Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "locks")));
|
||||
case "Noop":
|
||||
default:
|
||||
return new NoopDistributedSynchronizationProvider();
|
||||
}
|
||||
};
|
||||
})
|
||||
.UseEnvironments(environments => environments.EnvironmentsOptions = options => configuration.GetSection("Environments").Bind(options))
|
||||
.UseScheduling(scheduling =>
|
||||
{
|
||||
if (useHangfire)
|
||||
scheduling.UseHangfireScheduler();
|
||||
|
||||
if (useQuartz)
|
||||
scheduling.UseQuartzScheduler();
|
||||
})
|
||||
.UseWorkflowsApi(api =>
|
||||
{
|
||||
api.AddFastEndpointsAssembly<Program>();
|
||||
})
|
||||
.UseCSharp(options =>
|
||||
{
|
||||
options.DisableWrappers = disableVariableWrappers;
|
||||
options.AppendScript("string Greet(string name) => $\"Hello {name}!\";");
|
||||
options.AppendScript("string SayHelloWorld() => Greet(\"World\");");
|
||||
})
|
||||
.UseJavaScript(options =>
|
||||
{
|
||||
options.AllowClrAccess = true;
|
||||
options.DisableWrappers = disableVariableWrappers;
|
||||
options.DisableVariableCopying = disableVariableCopying;
|
||||
options.RegisterType<OrderReceived>();
|
||||
options.ConfigureEngine(engine =>
|
||||
{
|
||||
engine.Execute("function greet(name) { return `Hello ${name}!`; }");
|
||||
engine.Execute("function sayHelloWorld() { return greet('World'); }");
|
||||
});
|
||||
})
|
||||
.UsePython(python =>
|
||||
{
|
||||
python.PythonOptions += options =>
|
||||
{
|
||||
// Make sure to configure the path to the python DLL. E.g. /opt/homebrew/Cellar/python@3.11/3.11.6_1/Frameworks/Python.framework/Versions/3.11/bin/python3.11
|
||||
// alternatively, you can set the PYTHONNET_PYDLL environment variable.
|
||||
configuration.GetSection("Scripting:Python").Bind(options);
|
||||
|
||||
options.AddScript(sb =>
|
||||
{
|
||||
sb.AppendLine("def greet():");
|
||||
sb.AppendLine(" return \"Hello, welcome to Python!\"");
|
||||
});
|
||||
};
|
||||
})
|
||||
.UseLiquid(liquid => liquid.FluidOptions = options => options.Encoder = HtmlEncoder.Default)
|
||||
.UseHttp(http =>
|
||||
{
|
||||
http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options);
|
||||
|
||||
if (useCaching)
|
||||
http.UseCache();
|
||||
})
|
||||
.UseSql(options =>
|
||||
{
|
||||
options.Clients = client =>
|
||||
{
|
||||
client.Register<MySqlClient>("MySql");
|
||||
client.Register<PostgreSqlClient>("PostgreSql");
|
||||
client.Register<SqliteClient>("Sqlite");
|
||||
client.Register<SqlServerClient>("Sql Server");
|
||||
};
|
||||
})
|
||||
.UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options))
|
||||
.UseAlterations(alterations =>
|
||||
{
|
||||
if (persistenceProvider == PersistenceProvider.MongoDb)
|
||||
{
|
||||
alterations.UseMongoDb();
|
||||
}
|
||||
else if (persistenceProvider == PersistenceProvider.Dapper)
|
||||
{
|
||||
// TODO: alterations.UseDapper();
|
||||
}
|
||||
else
|
||||
{
|
||||
alterations.UseEntityFrameworkCore(ef =>
|
||||
{
|
||||
ef.UseContextPooling = useDbContextPooling;
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer)
|
||||
ef.UseSqlServer(sqlServerConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql)
|
||||
ef.UsePostgreSql(postgresConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.Citus)
|
||||
ef.UsePostgreSql(citusConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.YugabyteDb)
|
||||
ef.UsePostgreSql(yugabyteDbConnectionString);
|
||||
#if !NET9_0
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.MySql)
|
||||
ef.UseMySql(mySqlConnectionString);
|
||||
#endif
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb)
|
||||
ef.UsePostgreSql(cockroachDbConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.Oracle)
|
||||
ef.UseOracle(oracleConnectionString, new()
|
||||
{
|
||||
SchemaName = "ELSA"
|
||||
});
|
||||
else
|
||||
ef.UseSqlite(sp => sp.GetSqliteConnectionString());
|
||||
|
||||
ef.RunMigrations = runEFCoreMigrations;
|
||||
});
|
||||
}
|
||||
|
||||
if (useMassTransit)
|
||||
{
|
||||
alterations.UseMassTransitDispatcher();
|
||||
}
|
||||
})
|
||||
.UseOpenTelemetry(otel => otel.UseNewRootActivityForRemoteParent = true)
|
||||
.UseWorkflowContexts();
|
||||
|
||||
if (useQuartz)
|
||||
{
|
||||
elsa.UseQuartz(quartz =>
|
||||
{
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.Sqlite)
|
||||
quartz.UseSqlite(sqliteConnectionString);
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql)
|
||||
quartz.UsePostgreSql(postgresConnectionString);
|
||||
});
|
||||
}
|
||||
|
||||
if (useSignalR)
|
||||
{
|
||||
elsa.UseRealTimeWorkflows();
|
||||
}
|
||||
|
||||
if (useMassTransit)
|
||||
{
|
||||
elsa.UseMassTransit(massTransit =>
|
||||
{
|
||||
massTransit.DisableConsumers = appRole == ApplicationRole.Api;
|
||||
massTransit.AddMessageType<OrderReceived>();
|
||||
|
||||
if (massTransitBroker == MassTransitBroker.AzureServiceBus)
|
||||
{
|
||||
massTransit.UseAzureServiceBus(azureServiceBusConnectionString, serviceBusFeature => serviceBusFeature.ConfigureTransportBus = (context, bus) =>
|
||||
{
|
||||
bus.PrefetchCount = 50;
|
||||
bus.LockDuration = TimeSpan.FromMinutes(5);
|
||||
bus.MaxConcurrentCalls = 32;
|
||||
bus.MaxDeliveryCount = 8;
|
||||
// etc.
|
||||
});
|
||||
}
|
||||
|
||||
if (massTransitBroker == MassTransitBroker.RabbitMq)
|
||||
{
|
||||
massTransit.UseRabbitMq(rabbitMqConnectionString, rabbit => rabbit.ConfigureTransportBus = (context, bus) =>
|
||||
{
|
||||
bus.PrefetchCount = 50;
|
||||
bus.Durable = true;
|
||||
bus.AutoDelete = false;
|
||||
bus.ConcurrentMessageLimit = 32;
|
||||
// etc.
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (distributedCachingTransport != DistributedCachingTransport.None)
|
||||
{
|
||||
elsa.UseDistributedCache(distributedCaching =>
|
||||
{
|
||||
if (distributedCachingTransport == DistributedCachingTransport.MassTransit) distributedCaching.UseMassTransit();
|
||||
if (distributedCachingTransport == DistributedCachingTransport.ProtoActor) distributedCaching.UseProtoActor();
|
||||
});
|
||||
}
|
||||
|
||||
if (distributedCachingTransport == DistributedCachingTransport.ProtoActor || workflowRuntime == WorkflowRuntime.ProtoActor)
|
||||
{
|
||||
elsa.UseProtoActor(proto =>
|
||||
{
|
||||
proto
|
||||
.EnableMetrics()
|
||||
.EnableTracing();
|
||||
|
||||
proto.PersistenceProvider = _ =>
|
||||
{
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer)
|
||||
return new SqlServerProvider(sqlServerConnectionString, true, "", "proto_actor");
|
||||
return new SqliteProvider(new(sqliteConnectionString));
|
||||
};
|
||||
|
||||
if (configuration["KUBERNETES_SERVICE_HOST"] != null)
|
||||
{
|
||||
var kubernetesConfig = new KubernetesProviderConfig();
|
||||
var clusterProvider = new KubernetesProvider(kubernetesConfig);
|
||||
|
||||
var remoteConfig = GrpcNetRemoteConfig
|
||||
.BindToAllInterfaces(advertisedHost: configuration["ProtoActor:AdvertisedHost"]) // Environment variable to be provided by Kubernetes using pod.status.podIP.
|
||||
.WithLogLevelForDeserializationErrors(LogLevel.Critical)
|
||||
.WithRemoteDiagnostics(true);
|
||||
|
||||
proto.CreateClusterProvider = _ => clusterProvider;
|
||||
proto.ConfigureRemoteConfig = _ => remoteConfig;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (useKafka)
|
||||
{
|
||||
elsa.UseKafka(kafka =>
|
||||
{
|
||||
kafka.ConfigureOptions(options => configuration.GetSection("Kafka").Bind(options));
|
||||
});
|
||||
}
|
||||
|
||||
if (useSecrets)
|
||||
{
|
||||
elsa
|
||||
.UseSecrets()
|
||||
.UseSecretsManagement(management =>
|
||||
{
|
||||
management.ConfigureOptions(options => configuration.GetSection("Secrets:Management").Bind(options));
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer)
|
||||
management.UseEntityFrameworkCore(ef =>
|
||||
{
|
||||
ef.UseContextPooling = useDbContextPooling;
|
||||
ef.UseSqlServer(sqlServerConnectionString);
|
||||
});
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql)
|
||||
management.UseEntityFrameworkCore(ef =>
|
||||
{
|
||||
ef.UseContextPooling = useDbContextPooling;
|
||||
ef.UsePostgreSql(postgresConnectionString);
|
||||
});
|
||||
else
|
||||
management.UseEntityFrameworkCore(ef =>
|
||||
{
|
||||
ef.UseContextPooling = useDbContextPooling;
|
||||
ef.UseSqlite(sp => sp.GetSqliteConnectionString());
|
||||
});
|
||||
})
|
||||
.UseSecretsApi()
|
||||
.UseSecretsScripting()
|
||||
;
|
||||
}
|
||||
|
||||
elsa.UseRetention(r =>
|
||||
{
|
||||
r.SweepInterval = TimeSpan.FromHours(5);
|
||||
r.AddDeletePolicy("Delete all finished workflows", sp =>
|
||||
{
|
||||
var filter = new RetentionWorkflowInstanceFilter
|
||||
{
|
||||
WorkflowStatus = WorkflowStatus.Finished
|
||||
};
|
||||
return filter;
|
||||
});
|
||||
});
|
||||
|
||||
if (useMultitenancy)
|
||||
{
|
||||
elsa.UseTenants(tenants =>
|
||||
{
|
||||
tenants.ConfigureMultitenancy(options =>
|
||||
{
|
||||
options.TenantResolverPipelineBuilder
|
||||
.Append<HostTenantResolver>()
|
||||
.Append<RoutePrefixTenantResolver>()
|
||||
.Append<HeaderTenantResolver>()
|
||||
.Append<ClaimsTenantResolver>();
|
||||
});
|
||||
|
||||
if (useTenantsFromConfiguration)
|
||||
{
|
||||
tenants.UseConfigurationBasedTenantsProvider(options => configuration.GetSection("Multitenancy").Bind(options));
|
||||
}
|
||||
else
|
||||
{
|
||||
tenants.UseStoreBasedTenantsProvider();
|
||||
|
||||
tenants.UseTenantManagement(management =>
|
||||
{
|
||||
if (persistenceProvider == PersistenceProvider.MongoDb)
|
||||
management.UseMongoDb();
|
||||
if (persistenceProvider == PersistenceProvider.Dapper)
|
||||
throw new NotSupportedException("Dapper is not supported for tenant management.");
|
||||
if (persistenceProvider == PersistenceProvider.EntityFrameworkCore)
|
||||
{
|
||||
management.UseEntityFrameworkCore(ef =>
|
||||
{
|
||||
ef.UseContextPooling = useDbContextPooling;
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.Sqlite) ef.UseSqlite(sqliteConnectionString);
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer) ef.UseSqlServer(sqlServerConnectionString);
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) ef.UsePostgreSql(postgresConnectionString);
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.Citus) ef.UsePostgreSql(citusConnectionString);
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.YugabyteDb) ef.UsePostgreSql(yugabyteDbConnectionString);
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.Oracle)
|
||||
ef.UseOracle(oracleConnectionString, new()
|
||||
{
|
||||
SchemaName = "ELSA"
|
||||
});
|
||||
#if !NET9_0
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.MySql)
|
||||
ef.UseMySql(mySqlConnectionString);
|
||||
#endif
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb) ef.UsePostgreSql(cockroachDbConnectionString);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
tenants.UseTenantManagementEndpoints();
|
||||
}
|
||||
});
|
||||
|
||||
elsa.UseTenantHttpRouting(tenantHttpRouting =>
|
||||
{
|
||||
// Override the tenant header name with a custom one.
|
||||
tenantHttpRouting.WithTenantHeader("X-Tenant-ID");
|
||||
});
|
||||
}
|
||||
|
||||
elsa.UseWebhooks(webhooks => webhooks.ConfigureSinks += options => builder.Configuration.GetSection("Webhooks").Bind(options));
|
||||
elsa.InstallDropIns(options => options.DropInRootDirectory = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "DropIns"));
|
||||
elsa.AddSwagger();
|
||||
elsa.AddFastEndpointsAssembly<Program>();
|
||||
ConfigureForTest?.Invoke(elsa);
|
||||
});
|
||||
|
||||
// Obfuscate HTTP request headers.
|
||||
services.AddActivityStateFilter<HttpRequestAuthenticationHeaderFilter>();
|
||||
|
||||
// Optionally configure recurring tasks using alternative schedules.
|
||||
services.Configure<RecurringTaskOptions>(options =>
|
||||
{
|
||||
options.Schedule.ConfigureTask<TriggerBookmarkQueueRecurringTask>(TimeSpan.FromSeconds(300));
|
||||
options.Schedule.ConfigureTask<PurgeBookmarkQueueRecurringTask>(TimeSpan.FromSeconds(300));
|
||||
options.Schedule.ConfigureTask<UpdateExpiredSecretsRecurringTask>(TimeSpan.FromHours(4));
|
||||
options.Schedule.ConfigureTask<RestartInterruptedWorkflowsTask>(TimeSpan.FromSeconds(15));
|
||||
});
|
||||
|
||||
services.Configure<RuntimeOptions>(options => { options.InactivityThreshold = TimeSpan.FromSeconds(15); });
|
||||
services.Configure<BookmarkQueuePurgeOptions>(options => options.Ttl = TimeSpan.FromSeconds(10));
|
||||
services.Configure<CachingOptions>(options => options.CacheDuration = TimeSpan.FromDays(1));
|
||||
services.Configure<IncidentOptions>(options => options.DefaultIncidentStrategy = typeof(ContinueWithIncidentsStrategy));
|
||||
services.AddHealthChecks();
|
||||
services.AddControllers();
|
||||
services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().WithExposedHeaders("*")));
|
||||
|
||||
// Build the web application.
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
app.UseDeveloperExceptionPage();
|
||||
|
||||
// CORS.
|
||||
app.UseCors();
|
||||
|
||||
// Health checks.
|
||||
app.MapHealthChecks("/");
|
||||
|
||||
// Routing used for SignalR.
|
||||
app.UseRouting();
|
||||
|
||||
// Security.
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
// Multitenancy.
|
||||
if (useMultitenancy)
|
||||
app.UseTenants();
|
||||
|
||||
// Elsa API endpoints for designer.
|
||||
var routePrefix = app.Services.GetRequiredService<IOptions<ApiEndpointOptions>>().Value.RoutePrefix;
|
||||
app.UseWorkflowsApi(routePrefix);
|
||||
|
||||
// Captures unhandled exceptions and returns a JSON response.
|
||||
app.UseJsonSerializationErrorHandler();
|
||||
|
||||
// Elsa HTTP Endpoint activities.
|
||||
app.UseWorkflows();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
// Swagger API documentation.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
// SignalR.
|
||||
if (useSignalR)
|
||||
{
|
||||
app.UseWorkflowsSignalRHubs();
|
||||
}
|
||||
|
||||
// Run.
|
||||
await app.RunAsync();
|
||||
|
||||
/// <summary>
|
||||
/// The main entry point for the application made public for end to end testing.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public partial class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// Set by the test runner to configure the module for testing.
|
||||
/// </summary>
|
||||
public static Action<IModule>? ConfigureForTest { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:16508",
|
||||
"sslPort": 44323
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"Elsa.WorkflowServer.Web": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5072",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
# Server
|
||||
|
||||
This project represents an Elsa application that hosts workflows and exposes API endpoints to manage & execute workflows.
|
||||
|
||||
## Secrets
|
||||
The following are the secrets stored in hashed form in appsettings.json:
|
||||
|
||||
**API key**: `48587230567A646D394B435A6277734A-4802fa49-e91e-45e8-b00f-b5492377e20b`
|
||||
**Admin user**: `admin`
|
||||
**Admin password**: `password`
|
||||
|
||||
## OpenTelemetry (MacOS)
|
||||
|
||||
COR_ENABLE_PROFILING=1
|
||||
COR_PROFILER={918728DD-259F-4A6A-AC2B-B85E1B658318}
|
||||
CORECLR_PROFILER_PATH=$INSTALL_DIR/osx-x64/OpenTelemetry.AutoInstrumentation.Native.dylib
|
||||
DOTNET_ADDITIONAL_DEPS=$INSTALL_DIR/AdditionalDeps
|
||||
DOTNET_EnableDiagnostics=1
|
||||
DOTNET_SHARED_STORE=$INSTALL_DIR/store
|
||||
DOTNET_STARTUP_HOOKS=OpenTelemetry.AutoInstrumentation.StartupHook
|
||||
OTEL_DOTNET_AUTO_HOME=$INSTALL_DIR
|
||||
OTEL_DOTNET_AUTO_LOGS_CONSOLE_EXPORTER_ENABLED=true
|
||||
OTEL_DOTNET_AUTO_METRICS_CONSOLE_EXPORTER_ENABLED=true
|
||||
OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES=Proto.Actor,Elsa.Workflows
|
||||
OTEL_DOTNET_AUTO_TRACES_CONSOLE_EXPORTER_ENABLED=true
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
|
||||
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
|
||||
OTEL_RESOURCE_ATTRIBUTES=service.name=Elsa Server,service.version=3.3.0,service.instance.id=instance-123,deployment.environment=development
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Reflection;
|
||||
using Elsa.Workflows.UIHints.CheckList;
|
||||
|
||||
namespace Elsa.Server.Web;
|
||||
|
||||
/// <summary>
|
||||
/// Provides static drop-down options for a given property.
|
||||
/// </summary>
|
||||
public class CustomCheckListOptionsProvider : CheckListOptionsProviderBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override ValueTask<ICollection<CheckListItem>> GetItemsAsync(PropertyInfo propertyInfo, object? context, CancellationToken cancellationToken)
|
||||
{
|
||||
var items = new[]
|
||||
{
|
||||
"Apple", "Banana", "Cherry"
|
||||
};
|
||||
var selectListItems = items.Select(x => new CheckListItem(x, x)).ToList();
|
||||
|
||||
return new(selectListItems);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"HostBuilder": {
|
||||
"reloadConfigOnChange": true
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;",
|
||||
"SqlServer": "Server=localhost,1433;Initial Catalog=Elsa;User=sa;Password=!Elsa2025@;Encrypt=false;PersistSecurityInfo=false",
|
||||
"MySql": "Server=localhost;Database=elsa;Uid=admin;Pwd=password;",
|
||||
"PostgreSql": "Server=localhost;Username=elsa;Database=elsa;Port=5432;Password=elsa;SSLMode=Prefer;MaxPoolSize=2000;Timeout=60",
|
||||
"Citus": "Server=localhost;Username=citus;Database=citus;Port=9700;Password=citus;SSLMode=Prefer;MaxPoolSize=2000;Timeout=60",
|
||||
"YugabyteDb": "Host=localhost;Port=5433;Database=elsa;Username=yugabyte;Password=yugabyte;Pooling=true",
|
||||
"Oracle": "Data Source=(DESCRIPTION = (ADDRESS_LIST = (FAILOVER =ON) (LOAD_BALANCE = OFF) (ADDRESS = (PROTOCOL =TCP)(HOST=localhost)(PORT=1521))) (CONNECT_DATA = (SID= FREE) ));User Id=ELSA;Password=elsa;",
|
||||
"CockroachDb": "Host=localhost;Port=26257;Database=elsa;SslMode=Disable;Username=root;IncludeErrorDetail=true",
|
||||
"MongoDb": "mongodb://localhost:27017/elsa-workflows",
|
||||
"AzureServiceBus": "",
|
||||
"RabbitMq": "amqp://guest:guest@localhost:5672",
|
||||
"Redis": "localhost:6379,abortConnect=false"
|
||||
},
|
||||
"DatabaseProvider": "Sqlite",
|
||||
"Multitenancy": {
|
||||
"Tenants": [
|
||||
{
|
||||
"Id": null,
|
||||
"Name": "Default",
|
||||
"Configuration": {
|
||||
"Http": {
|
||||
"Prefix": "",
|
||||
"Host": "localhost:5001"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Id": "tenant-1",
|
||||
"Name": "Tenant 1",
|
||||
"Configuration": {
|
||||
"Http": {
|
||||
"Prefix": "tenant-1",
|
||||
"Host": "localhost:5001"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Smtp": {
|
||||
"Host": "localhost",
|
||||
"Port": 2525,
|
||||
"DefaultSender": "noreply@crmservices.com"
|
||||
},
|
||||
"Http": {
|
||||
"BaseUrl": "https://localhost:5001",
|
||||
"BasePath": "/workflows",
|
||||
"ApiRoutePrefix": "elsa/api",
|
||||
"AvailableContentTypes": [
|
||||
"application/json",
|
||||
"application/xml",
|
||||
"text/plain",
|
||||
"text/html"
|
||||
]
|
||||
},
|
||||
"Identity": {
|
||||
"Tokens": {
|
||||
"SigningKey": "sufficiently-large-secret-signing-key",
|
||||
"AccessTokenLifetime": "1:00:00:00",
|
||||
"RefreshTokenLifetime": "7:00:00:00"
|
||||
},
|
||||
"Roles": [
|
||||
{
|
||||
"Id": "admin",
|
||||
"Name": "Administrator",
|
||||
"Permissions": [
|
||||
"*"
|
||||
],
|
||||
"TenantId": "default"
|
||||
}
|
||||
],
|
||||
"Users": [
|
||||
{
|
||||
"Id": "a2323f46-42db-4e15-af8b-94238717d817",
|
||||
"Name": "admin",
|
||||
"HashedPassword": "TfKzh9RLix6FPcCNeHLkGrysFu3bYxqzGqduNdi8v1U=",
|
||||
"HashedPasswordSalt": "JEy9kBlhHCNsencitRHlGxmErmSgY+FVyMJulCH27Ds=",
|
||||
"Roles": [
|
||||
"admin"
|
||||
],
|
||||
"TenantId": ""
|
||||
},
|
||||
{
|
||||
"Id": "b0cd0e506e713a9d",
|
||||
"Name": "alice",
|
||||
"Roles": [
|
||||
"admin"
|
||||
],
|
||||
"HashedPassword": "8B0fFK/f/kk9GkVtzXfRJ2Y6cNyYVvLTfKouWcAcuPg=",
|
||||
"HashedPasswordSalt": "xlNWvEng8fRvo0McyJopbRJ2MJ9NIYV/4IY5dOZeiiw=",
|
||||
"TenantId": "tenant-2"
|
||||
},
|
||||
{
|
||||
"Id": "789d5056cb3bb8e9",
|
||||
"Name": "bob",
|
||||
"Roles": [
|
||||
"admin"
|
||||
],
|
||||
"HashedPassword": "GMHHvUw7RfIIBq+GAWZ/AlHR50TbgONYfGQ92Xge0EI=",
|
||||
"HashedPasswordSalt": "V4uHKg1PFBc1bsngCQoptibOu14faj9obSn5B23h1wE=",
|
||||
"TenantId": "tenant-1"
|
||||
}
|
||||
],
|
||||
"Applications": [
|
||||
{
|
||||
"Id": "d57030226341448daff5a2935aba2d3f",
|
||||
"Name": "Postman",
|
||||
"Roles": [
|
||||
"admin"
|
||||
],
|
||||
"ClientId": "HXr0Vzdm9KCZbwsJ",
|
||||
"ClientSecret": "a<~QGGHTEA%u4;CU&'Wga5ED:_&Gd1C)",
|
||||
"HashedApiKey": "Z5ClHs3mbzx8Pnw3+PxbMq8A/Y+VKMCCDTGYtax8JFM=",
|
||||
"HashedApiKeySalt": "kBisa1X8FwBfN2zmyGMFRgIVVBleghhQAJ4WGyTkaD0=",
|
||||
"HashedClientSecret": "jEv58d0SVbGQ3nBZM0lkzHghG4Y+lMKW80wipz+9vHk=",
|
||||
"HashedClientSecretSalt": "xRKy14Ok1/tU3kLf/8V1fcbLIegy9vcM90Peu2tzohU=",
|
||||
"TenantId": "default"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Environments": {
|
||||
"Environments": [
|
||||
{
|
||||
"Name": "Local",
|
||||
"ServerUrl": "https://localhost:5001/elsa/api"
|
||||
},
|
||||
{
|
||||
"Name": "Staging",
|
||||
"ServerUrl": "https://staging.acme.com/elsa/api"
|
||||
},
|
||||
{
|
||||
"Name": "Production",
|
||||
"ServerUrl": "https://production.acme.com/elsa/api"
|
||||
}
|
||||
]
|
||||
},
|
||||
"AppRole": "Default",
|
||||
"Runtime": {
|
||||
"DistributedLocking": {
|
||||
"Provider": "File",
|
||||
"LockAcquisitionTimeout": "00:00:10"
|
||||
},
|
||||
"DistributedLockProvider": "File",
|
||||
"WorkflowDispatcher": {
|
||||
"Channels": [
|
||||
{
|
||||
"Name": "Low"
|
||||
},
|
||||
{
|
||||
"Name": "Medium"
|
||||
},
|
||||
{
|
||||
"Name": "High"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Scripting": {
|
||||
"Python": {
|
||||
"PythonDllPath": "",
|
||||
"Scripts": [
|
||||
"def greet(name): return f'Hello {name}!'",
|
||||
"def say_hello_world(): return greet('World')"
|
||||
]
|
||||
}
|
||||
},
|
||||
"AzureServiceBus": {
|
||||
"Queues": [
|
||||
{
|
||||
"Name": "queue1"
|
||||
},
|
||||
{
|
||||
"Name": "queue2"
|
||||
}
|
||||
],
|
||||
"Topics": [
|
||||
{
|
||||
"Name": "topic1",
|
||||
"Subscriptions": [
|
||||
{
|
||||
"Name": "subscription1"
|
||||
},
|
||||
{
|
||||
"Name": "subscription2"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"Kafka": {
|
||||
"Topics": [
|
||||
{
|
||||
"Id": "topic-1",
|
||||
"Name": "topic-1"
|
||||
},
|
||||
{
|
||||
"Id": "topic-2",
|
||||
"Name": "topic-2"
|
||||
}
|
||||
],
|
||||
"SchemaRegistries": [
|
||||
{
|
||||
"Id": "schema-registry-1",
|
||||
"Name": "Schema Registry 1",
|
||||
"Config": {
|
||||
"Url": "http://localhost:8081"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Producers": [
|
||||
{
|
||||
"Id": "producer-1",
|
||||
"Name": "Producer 1",
|
||||
"FactoryType": "Elsa.Kafka.Factories.GenericProducerFactory`2[[System.String, System.Private.CoreLib], [Elsa.Server.Web.Messages.OrderReceived, Elsa.Server.Web]], Elsa.Kafka",
|
||||
"Config": {
|
||||
"BootstrapServers": "localhost:9092"
|
||||
},
|
||||
"SchemaRegistryId": "schema-registry-1"
|
||||
}
|
||||
],
|
||||
"Consumers": [
|
||||
{
|
||||
"Id": "consumer-1",
|
||||
"Name": "Consumer 1",
|
||||
"FactoryType": "Elsa.Kafka.Factories.GenericConsumerFactory`2[[System.String, System.Private.CoreLib], [Elsa.Server.Web.Messages.OrderReceived, Elsa.Server.Web]], Elsa.Kafka",
|
||||
"Config": {
|
||||
"BootstrapServers": "localhost:9092",
|
||||
"GroupId": "group-1",
|
||||
"AutoOffsetReset": "earliest",
|
||||
"EnableAutoCommit": "true"
|
||||
},
|
||||
"SchemaRegistryId": "schema-registry-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Secrets": {
|
||||
"Management": {
|
||||
"SweepInterval": "04:00:00"
|
||||
}
|
||||
},
|
||||
"Webhooks": {
|
||||
"Sinks": [
|
||||
{
|
||||
"Id": "1",
|
||||
"Name": "Run Task",
|
||||
"Filters": [
|
||||
{
|
||||
"EventType": "Elsa.RunTask"
|
||||
}
|
||||
],
|
||||
"Url": "https://localhost:6001/webhooks/run-task"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Resilience": {
|
||||
"Strategies": [
|
||||
{
|
||||
"$type": "HttpResilienceStrategy",
|
||||
"Id": "1",
|
||||
"DisplayName": "Retry 3 times, exponential",
|
||||
"MaxRetryAttempts": 3,
|
||||
"BackoffType": "Exponential"
|
||||
},
|
||||
{
|
||||
"$type": "HttpResilienceStrategy",
|
||||
"Id": "2",
|
||||
"DisplayName": "Retry 10 times, linear",
|
||||
"MaxRetryAttempts": 10,
|
||||
"BackoffType": "Linear"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows;
|
||||
|
||||
namespace Elsa.ServerAndStudio.Web.Activities;
|
||||
|
||||
public class CustomDelay : Activity
|
||||
{
|
||||
protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
context.DelayFor(TimeSpan.FromSeconds(5), FailAsync);
|
||||
context.DelayFor(TimeSpan.FromSeconds(500), OnDelayElapsedAsync);
|
||||
return default;
|
||||
}
|
||||
|
||||
private ValueTask FailAsync(ActivityExecutionContext context)
|
||||
{
|
||||
Console.WriteLine("Failing!");
|
||||
throw new("Failing!");
|
||||
}
|
||||
|
||||
private async ValueTask OnDelayElapsedAsync(ActivityExecutionContext context)
|
||||
{
|
||||
Console.WriteLine("Delay elapsed");
|
||||
await context.CompleteActivityAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Runtime.Activities;
|
||||
|
||||
namespace Elsa.ServerAndStudio.Web.Activities;
|
||||
|
||||
public class CustomEvent : EventBase<object>
|
||||
{
|
||||
protected override string GetEventName(ExpressionExecutionContext context)
|
||||
{
|
||||
return "MyEvent";
|
||||
}
|
||||
|
||||
protected override void OnEventReceived(ActivityExecutionContext context, object? eventData)
|
||||
{
|
||||
Console.WriteLine("Event received with data: " + eventData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Elsa.Http;
|
||||
using Elsa.Workflows;
|
||||
|
||||
namespace Elsa.ServerAndStudio.Web.Activities;
|
||||
|
||||
public class CustomHttpEndpoint : HttpEndpointBase
|
||||
{
|
||||
protected override HttpEndpointOptions GetOptions()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Path = "my-path",
|
||||
Methods = [HttpMethods.Get],
|
||||
};
|
||||
}
|
||||
|
||||
protected override async ValueTask OnHttpRequestReceivedAsync(ActivityExecutionContext context, HttpContext httpContext)
|
||||
{
|
||||
httpContext.Response.StatusCode = 200;
|
||||
await httpContext.Response.WriteAsync("Hello World", context.CancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Scheduling.Activities;
|
||||
using Elsa.Workflows;
|
||||
|
||||
namespace Elsa.ServerAndStudio.Web.Activities;
|
||||
|
||||
public class CustomTimer : TimerBase
|
||||
{
|
||||
protected override TimeSpan GetInterval(ExpressionExecutionContext context)
|
||||
{
|
||||
return TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
protected override void OnTimerElapsed(ActivityExecutionContext context)
|
||||
{
|
||||
Console.WriteLine("Timer elapsed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Agents.Persistence.EntityFrameworkCore.MySql\Elsa.Agents.Persistence.EntityFrameworkCore.MySql.csproj" Condition=" '$(TargetFramework)' != 'net9.0' " />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Connections.Api\Elsa.Connections.Api.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Connections.Core\Elsa.Connections.Core.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite\Elsa.Connections.Persistence.EntityFrameworkCore.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Connections.Persistence.EntityFrameworkCore\Elsa.Connections.Persistence.EntityFrameworkCore.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.PostgreSql\Elsa.EntityFrameworkCore.PostgreSql.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.SqlServer\Elsa.EntityFrameworkCore.SqlServer.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.IO.Http\Elsa.IO.Http.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.JavaScript.Libraries\Elsa.JavaScript.Libraries.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Quartz.EntityFrameworkCore.PostgreSql\Elsa.Quartz.EntityFrameworkCore.PostgreSql.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Quartz.EntityFrameworkCore.SqlServer\Elsa.Quartz.EntityFrameworkCore.SqlServer.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql\Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa\Elsa.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Caching.Distributed.MassTransit\Elsa.Caching.Distributed.MassTransit.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Caching.Distributed\Elsa.Caching.Distributed.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.CSharp\Elsa.CSharp.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Email\Elsa.Email.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.MySql\Elsa.EntityFrameworkCore.MySql.csproj" Condition=" '$(TargetFramework)' != 'net9.0' " />
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Hangfire\Elsa.Hangfire.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Http\Elsa.Http.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Identity\Elsa.Identity.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.IO.Compression\Elsa.IO.Compression.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Liquid\Elsa.Liquid.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.MassTransit.AzureServiceBus\Elsa.MassTransit.AzureServiceBus.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.MassTransit.RabbitMq\Elsa.MassTransit.RabbitMq.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.MassTransit\Elsa.MassTransit.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Workflows.Runtime.ProtoActor\Elsa.Workflows.Runtime.ProtoActor.csproj" />
|
||||
<ProjectReference Include="..\..\modules\Elsa.Python\Elsa.Python.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Quartz\Elsa.Quartz.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Webhooks\Elsa.Webhooks.csproj"/>
|
||||
<ProjectReference Include="..\..\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj"/>
|
||||
<ProjectReference Include="..\ElsaStudioWebAssembly\ElsaStudioWebAssembly.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server"/>
|
||||
<PackageReference Include="Proto.Persistence.Sqlite"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Elsa.Studio"/>
|
||||
<PackageReference Include="Elsa.Studio.Login.BlazorWasm"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Elsa.ServerAndStudio.Web.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the transport options for distributed caching.
|
||||
/// </summary>
|
||||
public enum DistributedCachingTransport
|
||||
{
|
||||
None,
|
||||
Memory,
|
||||
MassTransit
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Elsa.ServerAndStudio.Web.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the type of messaging broker used in MassTransit.
|
||||
/// </summary>
|
||||
public enum MassTransitBroker
|
||||
{
|
||||
Memory,
|
||||
AzureServiceBus,
|
||||
RabbitMq
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Elsa.ServerAndStudio.Web.Enums;
|
||||
|
||||
public enum SqlDatabaseProvider
|
||||
{
|
||||
SqlServer,
|
||||
Sqlite,
|
||||
MySql,
|
||||
PostgreSql,
|
||||
CockroachDb
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||
|
||||
namespace Elsa.ServerAndStudio.Web.Extensions;
|
||||
|
||||
public static class CorsOptionsExtensions
|
||||
{
|
||||
private static bool AllowAny(IList<string> values) => values.Count == 0 || values[0] == "*";
|
||||
|
||||
public static void Configure(this CorsOptions options, IConfigurationSection section)
|
||||
{
|
||||
var corsPolicy = section.Get<CorsPolicy>() ?? new CorsPolicy();
|
||||
options.AddDefaultPolicy(policy => {
|
||||
var headers = corsPolicy.Headers;
|
||||
var origins = corsPolicy.Origins;
|
||||
var methods = corsPolicy.Methods;
|
||||
|
||||
if (AllowAny(headers))
|
||||
policy.AllowAnyHeader();
|
||||
else
|
||||
policy.WithHeaders(headers.ToArray());
|
||||
|
||||
if (AllowAny(origins))
|
||||
policy.AllowAnyOrigin();
|
||||
else
|
||||
policy.WithOrigins(origins.ToArray());
|
||||
|
||||
if (AllowAny(methods))
|
||||
policy.AllowAnyMethod();
|
||||
else
|
||||
policy.WithMethods(methods.ToArray());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
@@ -0,0 +1,61 @@
|
||||
@page "/"
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@inject IConfiguration Configuration;
|
||||
@{
|
||||
var baseUrl = Configuration["Hosting:BaseUrl"] ?? Configuration["Http:BaseUrl"]!; // HttpBaseUrl is for backward compatibility.
|
||||
var apiUrl = $"{baseUrl.TrimEnd('/')}/{(Configuration["Hosting:ApiPrefix"] ?? "elsa/api").TrimStart('/')}";
|
||||
}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
|
||||
<title>Elsa Studio 3.5</title>
|
||||
<base href="/"/>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="@baseUrl/_content/Elsa.Studio.Shell/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="@baseUrl/_content/Elsa.Studio.Shell/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="@baseUrl/_content/Elsa.Studio.Shell/favicon-16x16.png">
|
||||
<link rel="manifest" href="@baseUrl/_content/Elsa.Studio.Shell/site.webmanifest">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Grandstander:wght@100&display=swap" rel="stylesheet">
|
||||
<link href="@baseUrl/_content/MudBlazor/MudBlazor.min.css" rel="stylesheet"/>
|
||||
<link href="@baseUrl/_content/CodeBeam.MudBlazor.Extensions/MudExtensions.min.css" rel="stylesheet"/>
|
||||
<link href="@baseUrl/_content/Radzen.Blazor/css/material-base.css" rel="stylesheet">
|
||||
<link href="@baseUrl/_content/Elsa.Studio.Shell/css/shell.css" rel="stylesheet">
|
||||
<link href="@baseUrl/_content/Elsa.Studio.Workflows.Designer/designer.css" rel="stylesheet">
|
||||
<link href="ElsaStudioWebAssembly.styles.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="loading-splash mud-container mud-container-maxwidth-false">
|
||||
<h5 class="mud-typography mud-typography-h5 mud-primary-text my-6">Loading...</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
<script src="@baseUrl/_content/BlazorMonaco/jsInterop.js"></script>
|
||||
<script src="@baseUrl/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
|
||||
<script src="@baseUrl/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
|
||||
<script src="@baseUrl/_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="@baseUrl/_content/CodeBeam.MudBlazor.Extensions/MudExtensions.min.js"></script>
|
||||
<script src="@baseUrl/_content/Radzen.Blazor/Radzen.Blazor.js"></script>
|
||||
<script>
|
||||
window.getClientConfig = function() { return {
|
||||
"apiUrl": "@apiUrl"
|
||||
} };
|
||||
</script>
|
||||
<script src="@baseUrl/_framework/blazor.webassembly.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,223 @@
|
||||
using Elsa.Agents;
|
||||
using Elsa.EntityFrameworkCore.Extensions;
|
||||
using Elsa.EntityFrameworkCore.Modules.Management;
|
||||
using Elsa.EntityFrameworkCore.Modules.Runtime;
|
||||
using Elsa.MassTransit.Options;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.JavaScript.Libraries.Extensions;
|
||||
using Elsa.ServerAndStudio.Web.Extensions;
|
||||
using Elsa.MassTransit.Extensions;
|
||||
using Elsa.ServerAndStudio.Web.Enums;
|
||||
using Medallion.Threading.FileSystem;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using WebhooksCore.Options;
|
||||
using Elsa.Connections.Middleware;
|
||||
using Elsa.IO.Http.Features;
|
||||
using Proto.Persistence.Sqlite;
|
||||
|
||||
const bool useMassTransit = true;
|
||||
const bool useProtoActor = true;
|
||||
const bool useCaching = true;
|
||||
const DistributedCachingTransport distributedCachingTransport = DistributedCachingTransport.MassTransit;
|
||||
const MassTransitBroker useMassTransitBroker = MassTransitBroker.Memory;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.WebHost.UseStaticWebAssets();
|
||||
var services = builder.Services;
|
||||
var configuration = builder.Configuration;
|
||||
var sqliteConnectionString = configuration.GetConnectionString("Sqlite")!;
|
||||
var mySqlConnectionString = configuration.GetConnectionString("MySql")!;
|
||||
var sqlServerConnectionString = configuration.GetConnectionString("SqlServer")!;
|
||||
var postgreSqlConnectionString = configuration.GetConnectionString("PostgreSql")!;
|
||||
var rabbitMqConnectionString = configuration.GetConnectionString("RabbitMq")!;
|
||||
var azureServiceBusConnectionString = configuration.GetConnectionString("AzureServiceBus")!;
|
||||
var identitySection = configuration.GetSection("Identity");
|
||||
var identityTokenSection = identitySection.GetSection("Tokens");
|
||||
var massTransitSection = configuration.GetSection("MassTransit");
|
||||
var massTransitDispatcherSection = configuration.GetSection("MassTransit.Dispatcher");
|
||||
var heartbeatSection = configuration.GetSection("Heartbeat");
|
||||
var sqlDatabaseProvider = Enum.Parse<SqlDatabaseProvider>(configuration["DatabaseProvider"] ?? "Sqlite");
|
||||
|
||||
services.Configure<MassTransitOptions>(massTransitSection);
|
||||
services.Configure<MassTransitWorkflowDispatcherOptions>(massTransitDispatcherSection);
|
||||
|
||||
// Add Elsa services.
|
||||
services
|
||||
.AddElsa(elsa =>
|
||||
{
|
||||
elsa
|
||||
.UseSasTokens()
|
||||
.UseIdentity(identity =>
|
||||
{
|
||||
identity.TokenOptions = options => identityTokenSection.Bind(options);
|
||||
identity.UseConfigurationBasedUserProvider(options => identitySection.Bind(options));
|
||||
identity.UseConfigurationBasedApplicationProvider(options => identitySection.Bind(options));
|
||||
identity.UseConfigurationBasedRoleProvider(options => identitySection.Bind(options));
|
||||
})
|
||||
.UseDefaultAuthentication()
|
||||
.UseApplicationCluster(x => x.HeartbeatOptions = settings => heartbeatSection.Bind(settings))
|
||||
.UseWorkflowManagement(management =>
|
||||
{
|
||||
if (useMassTransit)
|
||||
{
|
||||
management.UseMassTransitDispatcher();
|
||||
}
|
||||
|
||||
if (useCaching)
|
||||
management.UseCache();
|
||||
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer)
|
||||
management.UseEntityFrameworkCore(ef => ef.UseSqlServer(sqlServerConnectionString));
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql)
|
||||
management.UseEntityFrameworkCore(ef => ef.UsePostgreSql(postgreSqlConnectionString));
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.Sqlite)
|
||||
management.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString));
|
||||
#if !NET9_0
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.MySql)
|
||||
management.UseEntityFrameworkCore(ef => ef.UseMySql(mySqlConnectionString));
|
||||
#endif
|
||||
})
|
||||
.UseWorkflowRuntime(runtime =>
|
||||
{
|
||||
if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer)
|
||||
runtime.UseEntityFrameworkCore(ef => ef.UseSqlServer(sqlServerConnectionString));
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql)
|
||||
runtime.UseEntityFrameworkCore(ef => ef.UsePostgreSql(postgreSqlConnectionString));
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.Sqlite)
|
||||
runtime.UseEntityFrameworkCore(ef => ef.UseSqlite(sqliteConnectionString));
|
||||
#if !NET9_0
|
||||
else if (sqlDatabaseProvider == SqlDatabaseProvider.MySql)
|
||||
runtime.UseEntityFrameworkCore(ef => ef.UseMySql(mySqlConnectionString));
|
||||
#endif
|
||||
|
||||
if (useMassTransit)
|
||||
{
|
||||
runtime.UseMassTransitDispatcher();
|
||||
}
|
||||
|
||||
if (useProtoActor)
|
||||
{
|
||||
runtime.UseProtoActor();
|
||||
}
|
||||
|
||||
if (useCaching)
|
||||
runtime.UseCache();
|
||||
|
||||
runtime.DistributedLockProvider = _ => new FileDistributedSynchronizationProvider(new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "locks")));
|
||||
runtime.WorkflowInboxCleanupOptions = options => configuration.GetSection("Runtime:WorkflowInboxCleanup").Bind(options);
|
||||
runtime.WorkflowDispatcherOptions = options => configuration.GetSection("Runtime:WorkflowDispatcher").Bind(options);
|
||||
})
|
||||
.UseScheduling()
|
||||
.UseJavaScript(javaScriptFeature =>
|
||||
{
|
||||
javaScriptFeature
|
||||
.ConfigureJintOptions(jintOptions => jintOptions.AllowClrAccess = true)
|
||||
.UseLodashFp()
|
||||
.UseMoment();
|
||||
})
|
||||
.UseLiquid()
|
||||
.UseCSharp()
|
||||
.UsePython(python =>
|
||||
{
|
||||
python.PythonOptions += options =>
|
||||
{
|
||||
// Make sure to configure the path to the python DLL. E.g. /opt/homebrew/Cellar/python@3.11/3.11.6_1/Frameworks/Python.framework/Versions/3.11/bin/python3.11
|
||||
// alternatively, you can set the PYTHONNET_PYDLL environment variable.
|
||||
configuration.GetSection("Scripting:Python").Bind(options);
|
||||
};
|
||||
})
|
||||
.UseHttp(http =>
|
||||
{
|
||||
if (useCaching)
|
||||
http.UseCache();
|
||||
|
||||
http.ConfigureHttpOptions = options =>
|
||||
{
|
||||
options.BaseUrl = new Uri(configuration["Hosting:BaseUrl"] ?? configuration["Http:BaseUrl"]!); // HttpBaseUrl is for backward compatibility.
|
||||
options.BasePath = configuration["Http:BasePath"];
|
||||
};
|
||||
})
|
||||
.UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options))
|
||||
.UseWebhooks(webhooks => webhooks.ConfigureSinks = options => builder.Configuration.GetSection("Webhooks:Sinks").Bind(options))
|
||||
.UseWorkflowsApi()
|
||||
.UseCompression()
|
||||
.Use<IOHttpFeature>()
|
||||
.AddActivitiesFrom<Program>()
|
||||
.AddWorkflowsFrom<Program>();
|
||||
|
||||
if (useProtoActor)
|
||||
{
|
||||
elsa.UseProtoActor(proto => proto.PersistenceProvider = _ =>
|
||||
{
|
||||
return new SqliteProvider(new SqliteConnectionStringBuilder(sqliteConnectionString));
|
||||
});
|
||||
}
|
||||
|
||||
if (useMassTransit)
|
||||
{
|
||||
elsa.UseMassTransit(massTransit =>
|
||||
{
|
||||
switch (useMassTransitBroker)
|
||||
{
|
||||
case MassTransitBroker.AzureServiceBus:
|
||||
massTransit.UseAzureServiceBus(azureServiceBusConnectionString, asb =>
|
||||
{
|
||||
asb.SubscriptionCleanupOptions = options => options.Interval = TimeSpan.FromMinutes(5);
|
||||
asb.EnableAutomatedSubscriptionCleanup = true;
|
||||
});
|
||||
break;
|
||||
case MassTransitBroker.RabbitMq:
|
||||
massTransit.UseRabbitMq(rabbitMqConnectionString);
|
||||
break;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (distributedCachingTransport != DistributedCachingTransport.None)
|
||||
{
|
||||
elsa.UseDistributedCache(distributedCaching =>
|
||||
{
|
||||
if (distributedCachingTransport == DistributedCachingTransport.MassTransit) distributedCaching.UseMassTransit();
|
||||
});
|
||||
}
|
||||
|
||||
elsa.UseConnections(
|
||||
configure=> configure.AddConnectionsFrom<Program>());
|
||||
elsa.UseConnectionPersistence(ef=> ef.UseEntityFrameworkCore(f=>f.UseSqlite()));
|
||||
elsa.UseConnectionsApi();
|
||||
});
|
||||
|
||||
services.Configure<WebhookSinksOptions>(options => configuration.GetSection("Webhooks").Bind(options));
|
||||
|
||||
services.AddHealthChecks();
|
||||
|
||||
services.AddCors(cors => cors.Configure(configuration.GetSection("CorsPolicy")));
|
||||
|
||||
// Razor Pages.
|
||||
services.AddRazorPages(options => options.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute()));
|
||||
|
||||
// Configure middleware pipeline.
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error");
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.MapHealthChecks("/health");
|
||||
app.UseRouting();
|
||||
app.UseCors();
|
||||
app.UseStaticFiles();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseWorkflowsApi();
|
||||
app.UseWorkflows();
|
||||
app.MapFallbackToPage("/_Host");
|
||||
await app.RunAsync();
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:8080",
|
||||
"sslPort": 44363
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"Elsa.AllInOne.Web": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:8080",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Server
|
||||
|
||||
This project represents an Elsa application that hosts workflows and exposes API endpoints to manage & execute workflows.
|
||||
It also hosts the Elsa Studio Web Assembly project, which replaces the Stencil implementation.
|
||||
|
||||
## Secrets
|
||||
The following are the secrets stored in hashed form in appsettings.json:
|
||||
|
||||
**API key**: `48587230567A646D394B435A6277734A-4802fa49-e91e-45e8-b00f-b5492377e20b`
|
||||
**Admin user**: `admin`
|
||||
**Admin password**: `password`
|
||||
@@ -0,0 +1,135 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information",
|
||||
"OpenTelemetry": "Debug"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"Sqlite": "Data Source=elsa.sqlite.db;Cache=Shared;",
|
||||
"RabbitMq": "rabbitmq://guest:guest@localhost:5672",
|
||||
"MySql": "Server=localhost;Database=elsa;Uid=user;Pwd=password;",
|
||||
"SqlServer": "Server=.;Database=WorkflowDb;User Id=sa;Password=123456;TrustServerCertificate=True;",
|
||||
"PostgreSql": "Server=localhost;Username=elsa;Database=elsa;Port=5432;Password=elsa;SSLMode=Prefer;MaxPoolSize=2000;Timeout=60"
|
||||
},
|
||||
"DatabaseProvider": "Sqlite",
|
||||
"Hosting": {
|
||||
"BaseUrl": "https://localhost:8080",
|
||||
"ApiPrefix": "/elsa/api"
|
||||
},
|
||||
"Identity": {
|
||||
"Tokens": {
|
||||
"SigningKey": "c7dc81876a782d502084763fa322429fca015941eac90ce8ca7ad95fc8752035",
|
||||
"AccessTokenLifetime": "1:00:00:00",
|
||||
"RefreshTokenLifetime": "1:00:10:00"
|
||||
},
|
||||
"Roles": [
|
||||
{
|
||||
"Id": "admin",
|
||||
"Name": "Administrator",
|
||||
"Permissions": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Users": [
|
||||
{
|
||||
"Id": "a2323f46-42db-4e15-af8b-94238717d817",
|
||||
"Name": "admin",
|
||||
"HashedPassword": "TfKzh9RLix6FPcCNeHLkGrysFu3bYxqzGqduNdi8v1U=",
|
||||
"HashedPasswordSalt": "JEy9kBlhHCNsencitRHlGxmErmSgY+FVyMJulCH27Ds=",
|
||||
"Roles": [
|
||||
"admin"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Applications": [
|
||||
{
|
||||
"id": "d57030226341448daff5a2935aba2d3f",
|
||||
"name": "Postman",
|
||||
"roles": [
|
||||
"admin"
|
||||
],
|
||||
"clientId": "HXr0Vzdm9KCZbwsJ",
|
||||
"clientSecret": "a<~QGGHTEA%u4;CU&'Wga5ED:_&Gd1C)",
|
||||
"hashedApiKey": "Z5ClHs3mbzx8Pnw3+PxbMq8A/Y+VKMCCDTGYtax8JFM=",
|
||||
"hashedApiKeySalt": "kBisa1X8FwBfN2zmyGMFRgIVVBleghhQAJ4WGyTkaD0=",
|
||||
"hashedClientSecret": "jEv58d0SVbGQ3nBZM0lkzHghG4Y+lMKW80wipz+9vHk=",
|
||||
"hashedClientSecretSalt": "xRKy14Ok1/tU3kLf/8V1fcbLIegy9vcM90Peu2tzohU="
|
||||
}
|
||||
]
|
||||
},
|
||||
"Heartbeat": {
|
||||
"Interval": "00:00:01:00",
|
||||
"Timeout": "00:00:05:00"
|
||||
},
|
||||
"MassTransit": {
|
||||
"TemporaryQueueTtl": "00:00:05:00",
|
||||
"ConcurrentMessageLimit": 4,
|
||||
"PrefetchCount": 4,
|
||||
"MaxAutoRenewDuration": "00:00:00:30",
|
||||
"Dispatcher": {
|
||||
"ConcurrentMessageLimit": 4
|
||||
}
|
||||
},
|
||||
"Smtp": {
|
||||
"Host": "localhost",
|
||||
"Port": 2525,
|
||||
"DefaultSender": "noreply@crmservices.com"
|
||||
},
|
||||
"Http": {
|
||||
"BasePath": "/api/workflows"
|
||||
},
|
||||
"Webhooks": {
|
||||
"Sinks": []
|
||||
},
|
||||
"CorsPolicy": {
|
||||
"Origins": ["*"],
|
||||
"Methods": ["*"],
|
||||
"Headers": ["*"]
|
||||
},
|
||||
"Runtime": {
|
||||
"WorkflowInboxCleanup": {
|
||||
"SweepInterval": "00:00:10:00",
|
||||
"BatchSize": 1000
|
||||
},
|
||||
"WorkflowDispatcher": {
|
||||
"Channels": [
|
||||
{
|
||||
"Name": "Low"
|
||||
},
|
||||
{
|
||||
"Name": "Medium"
|
||||
},
|
||||
{
|
||||
"Name": "High"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Scripting": {
|
||||
"Python": {
|
||||
"PythonDllPath": "",
|
||||
"Scripts": [
|
||||
"def greet(name): return f'Hello {name}!'",
|
||||
"def say_hello_world(): return greet('World')"
|
||||
]
|
||||
}
|
||||
},
|
||||
"AzureServiceBus": {
|
||||
"ConnectionStringOrName": "AzureServiceBus",
|
||||
"Queues": [
|
||||
{
|
||||
"name": "order-created"
|
||||
},
|
||||
{
|
||||
"name": "order-completed"
|
||||
}
|
||||
],
|
||||
"Topics": [],
|
||||
"Subscriptions": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ElsaStudioWebAssembly\ElsaStudioWebAssembly.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Elsa.Studio" />
|
||||
<PackageReference Include="Elsa.Studio.Login.BlazorWasm" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
@@ -0,0 +1,67 @@
|
||||
@page "/"
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@inject IConfiguration Configuration;
|
||||
@{
|
||||
var baseUrl = Configuration["ElsaServer:Url"]!;
|
||||
var basePath = Configuration["Hosting:BasePath"]!;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(baseUrl))
|
||||
{
|
||||
baseUrl = "https://localhost:5001/elsa/api";
|
||||
}
|
||||
}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
|
||||
<title>Elsa Studio 3.5</title>
|
||||
<base href="/"/>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="@basePath/_content/Elsa.Studio.Shell/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="@basePath/_content/Elsa.Studio.Shell/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="@basePath/_content/Elsa.Studio.Shell/favicon-16x16.png">
|
||||
<link rel="manifest" href="@basePath/_content/Elsa.Studio.Shell/site.webmanifest">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Grandstander:wght@100&display=swap" rel="stylesheet">
|
||||
<link href="@basePath/_content/MudBlazor/MudBlazor.min.css" rel="stylesheet"/>
|
||||
<link href="@basePath/_content/CodeBeam.MudBlazor.Extensions/MudExtensions.min.css" rel="stylesheet"/>
|
||||
<link href="@basePath/_content/Radzen.Blazor/css/material-base.css" rel="stylesheet">
|
||||
<link href="@basePath/_content/Elsa.Studio.Shell/css/shell.css" rel="stylesheet">
|
||||
<link href="@basePath/_content/Elsa.Studio.Workflows.Designer/designer.css" rel="stylesheet">
|
||||
<link href="ElsaStudioWebAssembly.styles.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="loading-splash mud-container mud-container-maxwidth-false">
|
||||
<h5 class="mud-typography mud-typography-h5 mud-primary-text my-6">Loading...</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
<script src="@basePath/_content/BlazorMonaco/jsInterop.js"></script>
|
||||
<script src="@basePath/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
|
||||
<script src="@basePath/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
|
||||
<script src="@basePath/_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="@basePath/_content/CodeBeam.MudBlazor.Extensions/MudExtensions.min.js"></script>
|
||||
<script src="@basePath/_content/Radzen.Blazor/Radzen.Blazor.js"></script>
|
||||
<script>
|
||||
window.getClientConfig = function() { return {
|
||||
"apiUrl": "@baseUrl",
|
||||
"basePath": "@basePath"
|
||||
} };
|
||||
</script>
|
||||
<script src="@basePath/_framework/blazor.webassembly.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.WebHost.UseStaticWebAssets();
|
||||
var services = builder.Services;
|
||||
|
||||
// Razor Pages.
|
||||
services.AddRazorPages(options => options.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute()));
|
||||
|
||||
// Configure middleware pipeline.
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error");
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.UseRouting();
|
||||
app.UseStaticFiles();
|
||||
app.MapFallbackToPage("/_Host");
|
||||
await app.RunAsync();
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:64391",
|
||||
"sslPort": 44336
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "http://localhost:5161",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "https://localhost:7030;http://localhost:5161",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Elsa.Studio" />
|
||||
<PackageReference Include="Elsa.Studio.Core.BlazorWasm" />
|
||||
<PackageReference Include="Elsa.Studio.Login.BlazorWasm" />
|
||||
<PackageReference Include="Elsa.Studio.Agents" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
@@ -0,0 +1,5 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<main>
|
||||
@Body
|
||||
</main>
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Text.Json;
|
||||
using Elsa.Studio.Dashboard.Extensions;
|
||||
using Elsa.Studio.Shell;
|
||||
using Elsa.Studio.Shell.Extensions;
|
||||
using Elsa.Studio.Workflows.Extensions;
|
||||
using Elsa.Studio.Contracts;
|
||||
using Elsa.Studio.Core.BlazorWasm.Extensions;
|
||||
using Elsa.Studio.Extensions;
|
||||
using Elsa.Studio.Login.BlazorWasm.Extensions;
|
||||
using Elsa.Studio.Login.Extensions;
|
||||
using Elsa.Studio.Login.HttpMessageHandlers;
|
||||
using Elsa.Studio.Models;
|
||||
using Elsa.Studio.Options;
|
||||
using Elsa.Studio.Workflows.Designer.Extensions;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
// Build the host.
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
|
||||
// Register root components.
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
builder.RootComponents.RegisterCustomElsaStudioElements();
|
||||
|
||||
// Register shell services and modules.
|
||||
var backendApiConfig = new BackendApiConfig
|
||||
{
|
||||
ConfigureBackendOptions = options => builder.Configuration.GetSection("Backend").Bind(options),
|
||||
ConfigureHttpClientBuilder = options => options.AuthenticationHandler = typeof(AuthenticatingApiHttpMessageHandler)
|
||||
};
|
||||
|
||||
builder.Services.AddCore();
|
||||
builder.Services.AddShell();
|
||||
builder.Services.AddRemoteBackend(backendApiConfig);
|
||||
builder.Services.AddLoginModule();
|
||||
builder.Services.UseElsaIdentity();
|
||||
builder.Services.AddDashboardModule();
|
||||
builder.Services.AddWorkflowsModule();
|
||||
builder.Services.AddAgentsModule(backendApiConfig);
|
||||
|
||||
// Build the application.
|
||||
var app = builder.Build();
|
||||
|
||||
// Apply client config.
|
||||
var js = app.Services.GetRequiredService<IJSRuntime>();
|
||||
var clientConfig = await js.InvokeAsync<JsonElement>("getClientConfig");
|
||||
var apiUrl = clientConfig.GetProperty("apiUrl").GetString()!;
|
||||
app.Services.GetRequiredService<IOptions<BackendOptions>>().Value.Url = new Uri(apiUrl);
|
||||
|
||||
// Run each startup task.
|
||||
var startupTaskRunner = app.Services.GetRequiredService<IStartupTaskRunner>();
|
||||
await startupTaskRunner.RunStartupTasksAsync();
|
||||
|
||||
// Run the application.
|
||||
await app.RunAsync();
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:18109",
|
||||
"sslPort": 44371
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "http://localhost:5059",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "https://localhost:7020;http://localhost:5059",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Server
|
||||
|
||||
This project represents the Elsa Studio Blazor web assembly application and is hosted by Elsa.AllInOne.Web.
|
||||
@@ -0,0 +1,6 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Http
|
||||
@using Microsoft.JSInterop
|
||||
@@ -0,0 +1,56 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Api.Client.Extensions;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Api.Client.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Converts <see cref="Type"/> objects to and from their assembly-qualified name.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class TypeJsonConverter : JsonConverter<Type>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
{
|
||||
return typeToConvert == typeof(Type) || typeToConvert.FullName == "System.RuntimeType";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var typeName = reader.GetString()!;
|
||||
|
||||
// Handle collection types.
|
||||
if (typeName.EndsWith("[]"))
|
||||
{
|
||||
var elementTypeName = typeName[..^"[]".Length];
|
||||
var elementType = Type.GetType(elementTypeName)!;
|
||||
return typeof(List<>).MakeGenericType(elementType);
|
||||
}
|
||||
|
||||
return Type.GetType(typeName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options)
|
||||
{
|
||||
// Handle collection types.
|
||||
if (value is { IsGenericType: true, GenericTypeArguments.Length: 1 })
|
||||
{
|
||||
var elementType = value.GenericTypeArguments.First();
|
||||
var typedEnumerable = typeof(IEnumerable<>).MakeGenericType(elementType);
|
||||
|
||||
if (typedEnumerable.IsAssignableFrom(value))
|
||||
{
|
||||
var elementTypeName = value.GetSimpleAssemblyQualifiedName();
|
||||
JsonSerializer.Serialize(writer, $"{elementTypeName}[]", options);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var typeName = value.GetSimpleAssemblyQualifiedName();
|
||||
JsonSerializer.Serialize(writer, typeName, options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Api.Client.Shared.Models;
|
||||
|
||||
namespace Elsa.Api.Client.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// A JSON converter that serializes <see cref="VersionOptions"/>.
|
||||
/// </summary>
|
||||
public class VersionOptionsJsonConverter : JsonConverter<VersionOptions>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override VersionOptions Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var textValue = reader.GetString();
|
||||
return string.IsNullOrWhiteSpace(textValue) ? VersionOptions.Published : VersionOptions.FromString(textValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, VersionOptions value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using Elsa.Api.Client.Shared.Models;
|
||||
|
||||
namespace Elsa.Api.Client.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// A type converter that converts <see cref="VersionOptions"/> to and from strings.
|
||||
/// </summary>
|
||||
public class VersionOptionsTypeConverter : TypeConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) => sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) => value is string text ? VersionOptions.FromString(text) : base.ConvertFrom(context, culture, value)!;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>
|
||||
Provides HTTP clients for the Elsa Workflows REST API.
|
||||
</Description>
|
||||
<PackageTags>elsa api client</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Humanizer.Core" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" />
|
||||
<PackageReference Include="Polly" />
|
||||
<PackageReference Include="Polly.Extensions.Http" />
|
||||
<PackageReference Include="Refit" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,4 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=helpers/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=resources_005Clogpersistencestrategies_005Ccontracts/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=resources_005Clogpersistencestrategies_005Cmodels/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -0,0 +1,216 @@
|
||||
using System.Text.Json.Nodes;
|
||||
using Elsa.Api.Client.Resources.Resilience.Models;
|
||||
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
|
||||
using Elsa.Api.Client.Shared.Models;
|
||||
|
||||
namespace Elsa.Api.Client.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="JsonObject"/>.
|
||||
/// </summary>
|
||||
public static class ActivityExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the type name of the specified activity.
|
||||
/// </summary>
|
||||
public static string GetTypeName(this JsonObject activity) => activity.GetProperty<string>("type")!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type name of the specified activity.
|
||||
/// </summary>
|
||||
public static void SetTypeName(this JsonObject activity, string value) => activity.SetProperty(JsonValue.Create(value), "type");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version of the specified activity.
|
||||
/// </summary>
|
||||
public static int GetVersion(this JsonObject activity) => activity.GetProperty<int>("version");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version of the specified activity.
|
||||
/// </summary>
|
||||
public static void SetVersion(this JsonObject activity, int value) => activity.SetProperty(JsonValue.Create(value), "version");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the specified activity.
|
||||
/// </summary>
|
||||
public static string GetId(this JsonObject activity) => activity.GetProperty<string>("id")!;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the ID of the specified activity.
|
||||
/// </summary>
|
||||
public static void SetId(this JsonObject activity, string value) => activity.SetProperty(JsonValue.Create(value), "id");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the node ID of the specified activity.
|
||||
/// </summary>
|
||||
public static string GetNodeId(this JsonObject activity) => activity.GetProperty<string>("nodeId")!;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the node ID of the specified activity.
|
||||
/// </summary>
|
||||
public static void SetNodeId(this JsonObject activity, string value) => activity.SetProperty(JsonValue.Create(value), "nodeId");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the specified activity.
|
||||
/// </summary>
|
||||
public static string? GetName(this JsonObject activity) => activity.GetProperty<string>("name");
|
||||
|
||||
/// <summary>
|
||||
/// Sets the name of the specified activity.
|
||||
/// </summary>
|
||||
public static void SetName(this JsonObject activity, string? value) => activity.SetProperty(JsonValue.Create(value), "name");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the designer metadata for the specified activity.
|
||||
/// </summary>
|
||||
public static JsonObject? GetMetadata(this JsonObject activity) => activity.GetProperty("metadata")?.AsObject();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the designer metadata for the specified activity.
|
||||
/// </summary>
|
||||
public static void SetDesignerMetadata(this JsonObject activity, ActivityDesignerMetadata designerMetadata) => activity.SetProperty(designerMetadata.SerializeToNode(), "metadata", "designer");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the designer metadata for the specified activity.
|
||||
/// </summary>
|
||||
public static ActivityDesignerMetadata GetDesignerMetadata(this JsonObject activity) => activity.GetProperty<ActivityDesignerMetadata>("metadata", "designer") ?? new ActivityDesignerMetadata();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display text for the specified activity.
|
||||
/// </summary>
|
||||
public static string? GetDisplayText(this JsonObject activity) => activity.GetProperty("metadata", "displayText")?.GetValue<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the display text for the specified activity.
|
||||
/// </summary>
|
||||
public static void SetDisplayText(this JsonObject activity, string? value)
|
||||
{
|
||||
activity.SetProperty(JsonValue.Create(value), "metadata", "displayText");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description for the specified activity.
|
||||
/// </summary>
|
||||
public static string? GetDescription(this JsonObject activity) => activity.GetProperty("metadata", "description")?.GetValue<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the description for the specified activity.
|
||||
/// </summary>
|
||||
public static void SetDescription(this JsonObject activity, string value) => activity.SetProperty(JsonValue.Create(value), "metadata", "description");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the description for the specified activity should be shown.
|
||||
/// </summary>
|
||||
public static bool? GetShowDescription(this JsonObject activity) => activity.GetProperty<bool>("metadata", "showDescription");
|
||||
|
||||
/// <summary>
|
||||
/// Sets a value indicating whether the description for the specified activity should be shown.
|
||||
/// </summary>
|
||||
public static void SetShowDescription(this JsonObject activity, bool value) => activity.SetProperty(JsonValue.Create(value), "metadata", "showDescription");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the specified activity can trigger the workflow.
|
||||
/// </summary>
|
||||
public static bool? GetCanStartWorkflow(this JsonObject activity) => activity.GetProperty<bool>("customProperties", "canStartWorkflow");
|
||||
|
||||
/// <summary>
|
||||
/// Sets a value indicating whether the specified activity can trigger the workflow.
|
||||
/// </summary>
|
||||
public static void SetCanStartWorkflow(this JsonObject activity, bool value) => activity.SetProperty(JsonValue.Create(value), "customProperties", "canStartWorkflow");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the activities in the specified flowchart.
|
||||
/// </summary>
|
||||
public static IEnumerable<JsonObject> GetActivities(this JsonObject flowchart) => flowchart.GetProperty("activities")?.AsArray().AsEnumerable().Cast<JsonObject>() ?? Array.Empty<JsonObject>();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the activities in the specified flowchart.
|
||||
/// </summary>
|
||||
public static void SetActivities(this JsonObject flowchart, JsonArray activities) => flowchart.SetProperty(activities, "activities");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the connections in the specified flowchart.
|
||||
/// </summary>
|
||||
public static IEnumerable<Connection> GetConnections(this JsonObject flowchart) => flowchart.GetProperty<ICollection<Connection>>("connections") ?? new List<Connection>();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the connections in the specified flowchart.
|
||||
/// </summary>
|
||||
public static void SetConnections(this JsonObject flowchart, IEnumerable<Connection> connections) => flowchart.SetProperty(JsonValue.Create(connections), "connections");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root activity in the specified activity.
|
||||
/// </summary>
|
||||
public static JsonObject? GetRoot(this JsonObject activity) => activity.GetProperty("root") as JsonObject;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the root activity in the specified activity.
|
||||
/// </summary>
|
||||
public static void SetRoot(this JsonObject container, JsonObject root) => container.SetProperty(root, "root");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root activity in the specified activity.
|
||||
/// </summary>
|
||||
public static string? GetWorkflowDefinitionId(this JsonObject activity)
|
||||
{
|
||||
// Determine if this activity represents a workflow definition activity based on the presence of the following properties.
|
||||
var workflowDefinitionIdNode = activity.GetProperty("workflowDefinitionId");
|
||||
var workflowDefinitionVersionId = activity.GetProperty("workflowDefinitionVersionId");
|
||||
var latestAvailablePublishedVersion = activity.GetProperty("latestAvailablePublishedVersion");
|
||||
var latestAvailablePublishedVersionId = activity.GetProperty("latestAvailablePublishedVersionId");
|
||||
|
||||
if (workflowDefinitionIdNode == null || workflowDefinitionVersionId == null || latestAvailablePublishedVersion == null || latestAvailablePublishedVersionId == null)
|
||||
return null; // Not a workflow definition activity.
|
||||
|
||||
if (workflowDefinitionIdNode is JsonValue value)
|
||||
return value.ToString();
|
||||
|
||||
return null; // Not a workflow definition activity.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the given activity is a workflow definition activity.
|
||||
/// </summary>
|
||||
/// <param name="activity">The JsonObject representing the activity.</param>
|
||||
/// <returns>Returns true if the activity is a workflow definition activity; otherwise, false.</returns>
|
||||
public static bool GetIsWorkflowDefinitionActivity(this JsonObject activity) => activity.ContainsKey("workflowDefinitionId") && activity.ContainsKey("workflowDefinitionVersionId");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the specified activity can trigger the workflow.
|
||||
/// </summary>
|
||||
public static bool? GetRunAsynchronously(this JsonObject activity) => activity.GetProperty<bool>("customProperties", "runAsynchronously");
|
||||
|
||||
/// <summary>
|
||||
/// Sets a value indicating whether the specified activity can trigger the workflow.
|
||||
/// </summary>
|
||||
public static void SetRunAsynchronously(this JsonObject activity, bool value) => activity.SetProperty(JsonValue.Create(value), "customProperties", "runAsynchronously");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the commit state behavior for the specified activity.
|
||||
/// </summary>
|
||||
public static string? GetCommitStrategy(this JsonObject activity) => activity.TryGetProperty<string?>("customProperties", "commitStrategyName");
|
||||
|
||||
/// <summary>
|
||||
/// Sets the commit state behavior for the specified activity.
|
||||
/// </summary>
|
||||
public static void SetCommitStrategy(this JsonObject activity, string? name) => activity.SetProperty(JsonValue.Create(name), "customProperties", "commitStrategyName");
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the resilience strategy for the specified activity.
|
||||
/// </summary>
|
||||
public static ResilienceStrategyConfig? GetResilienceStrategy(this JsonObject activity)
|
||||
{
|
||||
var node = activity.GetProperty("customProperties", "resilienceStrategy");
|
||||
return ResilienceStrategyConfig.Deserialize(node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the resilience strategy for the specified activity.
|
||||
/// </summary>
|
||||
public static void SetResilienceStrategy(this JsonObject activity, ResilienceStrategyConfig? config)
|
||||
{
|
||||
var node = config?.SerializeToNode();
|
||||
activity.SetProperty(node, "customProperties", "resilienceStrategy");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Refit;
|
||||
|
||||
namespace Elsa.Api.Client.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="IApiResponse"/>.
|
||||
/// </summary>
|
||||
public static class ApiResponseExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the downloaded file name or the specified default file name.
|
||||
/// </summary>
|
||||
public static string GetDownloadedFileNameOrDefault(this IApiResponse response, string defaultFileName = "download.bin")
|
||||
{
|
||||
var fileName = defaultFileName;
|
||||
|
||||
if (response.Headers.TryGetValues("content-disposition", out var contentDispositionHeader)) // Only available if the Elsa Server exposes the "Content-Disposition" header.
|
||||
{
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
||||
var values = contentDispositionHeader.ToList() ?? [];
|
||||
|
||||
if (values.Count >= 2)
|
||||
fileName = values[1].Split('=')[1];
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
using Elsa.Api.Client.Options;
|
||||
using Elsa.Api.Client.Resources.ActivityDescriptorOptions.Contracts;
|
||||
using Elsa.Api.Client.Resources.ActivityDescriptors.Contracts;
|
||||
using Elsa.Api.Client.Resources.ActivityExecutions.Contracts;
|
||||
using Elsa.Api.Client.Resources.Alterations.Contracts;
|
||||
using Elsa.Api.Client.Resources.CommitStrategies.Contracts;
|
||||
using Elsa.Api.Client.Resources.Features.Contracts;
|
||||
using Elsa.Api.Client.Resources.Identity.Contracts;
|
||||
using Elsa.Api.Client.Resources.IncidentStrategies.Contracts;
|
||||
using Elsa.Api.Client.Resources.LogPersistenceStrategies;
|
||||
using Elsa.Api.Client.Resources.Resilience.Contracts;
|
||||
using Elsa.Api.Client.Resources.Scripting.Contracts;
|
||||
using Elsa.Api.Client.Resources.StorageDrivers.Contracts;
|
||||
using Elsa.Api.Client.Resources.Tasks.Contracts;
|
||||
using Elsa.Api.Client.Resources.VariableTypes.Contracts;
|
||||
using Elsa.Api.Client.Resources.WorkflowActivationStrategies.Contracts;
|
||||
using Elsa.Api.Client.Resources.WorkflowDefinitions.Contracts;
|
||||
using Elsa.Api.Client.Resources.WorkflowExecutionContexts.Contracts;
|
||||
using Elsa.Api.Client.Resources.WorkflowInstances.Contracts;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Refit;
|
||||
using static Elsa.Api.Client.RefitSettingsHelper;
|
||||
|
||||
namespace Elsa.Api.Client.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for dependency injection.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public static class DependencyInjectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds default Elsa API clients configured to use an API key.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddDefaultApiClientsUsingApiKey(this IServiceCollection services, Action<ElsaClientOptions> configureOptions)
|
||||
{
|
||||
var options = new ElsaClientOptions();
|
||||
configureOptions(options);
|
||||
|
||||
return services.AddDefaultApiClients(client =>
|
||||
{
|
||||
client.BaseAddress = options.BaseAddress;
|
||||
client.ApiKey = options.ApiKey;
|
||||
client.ConfigureHttpClient = options.ConfigureHttpClient;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds default Elsa API clients.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddDefaultApiClients(this IServiceCollection services, Action<ElsaClientBuilderOptions>? configureClient = null)
|
||||
{
|
||||
return services.AddApiClients(configureClient, builderOptions =>
|
||||
{
|
||||
var builderOptionsWithoutRetryPolicy = new ElsaClientBuilderOptions
|
||||
{
|
||||
ApiKey = builderOptions.ApiKey,
|
||||
AuthenticationHandler = builderOptions.AuthenticationHandler,
|
||||
BaseAddress = builderOptions.BaseAddress,
|
||||
ConfigureHttpClient = builderOptions.ConfigureHttpClient,
|
||||
ConfigureHttpClientBuilder = builderOptions.ConfigureHttpClientBuilder,
|
||||
ConfigureRetryPolicy = null
|
||||
};
|
||||
|
||||
services.AddApi<IWorkflowDefinitionsApi>(builderOptions);
|
||||
services.AddApi<IExecuteWorkflowApi>(builderOptionsWithoutRetryPolicy);
|
||||
services.AddApi<IWorkflowInstancesApi>(builderOptions);
|
||||
services.AddApi<IActivityDescriptorsApi>(builderOptions);
|
||||
services.AddApi<IActivityDescriptorOptionsApi>(builderOptions);
|
||||
services.AddApi<IActivityExecutionsApi>(builderOptions);
|
||||
services.AddApi<IStorageDriversApi>(builderOptions);
|
||||
services.AddApi<IVariableTypesApi>(builderOptions);
|
||||
services.AddApi<IWorkflowActivationStrategiesApi>(builderOptions);
|
||||
services.AddApi<IIncidentStrategiesApi>(builderOptions);
|
||||
services.AddApi<ILogPersistenceStrategiesApi>(builderOptions);
|
||||
services.AddApi<ICommitStrategiesApi>(builderOptions);
|
||||
services.AddApi<IResilienceStrategiesApi>(builderOptions);
|
||||
services.AddApi<IRetryAttemptsApi>(builderOptions);
|
||||
services.AddApi<ILoginApi>(builderOptions);
|
||||
services.AddApi<IFeaturesApi>(builderOptions);
|
||||
services.AddApi<IJavaScriptApi>(builderOptions);
|
||||
services.AddApi<IExpressionDescriptorsApi>(builderOptions);
|
||||
services.AddApi<IWorkflowContextProviderDescriptorsApi>(builderOptions);
|
||||
services.AddApi<IAlterationsApi>(builderOptions);
|
||||
services.AddApi<ITasksApi>(builderOptions);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an API client to the service collection. Requires AddElsaClient to be called exactly once.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddApiClient<T>(this IServiceCollection services, Action<ElsaClientBuilderOptions>? configureClient = null) where T : class
|
||||
{
|
||||
return services.AddApiClients(configureClient, builderOptions => services.AddApi<T>(builderOptions));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the Elsa client to the service collection.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddApiClients(this IServiceCollection services, Action<ElsaClientBuilderOptions>? configureClient = null, Action<ElsaClientBuilderOptions>? configureServices = null)
|
||||
{
|
||||
var builderOptions = new ElsaClientBuilderOptions();
|
||||
configureClient?.Invoke(builderOptions);
|
||||
builderOptions.ConfigureHttpClientBuilder += builder => builder.AddHttpMessageHandler(sp => (DelegatingHandler)sp.GetRequiredService(builderOptions.AuthenticationHandler));
|
||||
|
||||
services.TryAddScoped(builderOptions.AuthenticationHandler);
|
||||
|
||||
services.Configure<ElsaClientOptions>(options =>
|
||||
{
|
||||
options.BaseAddress = builderOptions.BaseAddress;
|
||||
options.ConfigureHttpClient = builderOptions.ConfigureHttpClient;
|
||||
options.ApiKey = builderOptions.ApiKey;
|
||||
});
|
||||
|
||||
configureServices?.Invoke(builderOptions);
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a refit client for the specified API type.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <param name="httpClientBuilderOptions">An options object that can be used to configure the HTTP client builder.</param>
|
||||
/// <typeparam name="T">The type representing the API.</typeparam>
|
||||
public static IServiceCollection AddApi<T>(this IServiceCollection services, ElsaClientBuilderOptions? httpClientBuilderOptions = default) where T : class
|
||||
{
|
||||
return services.AddApi(typeof(T), httpClientBuilderOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a refit client for the specified API type.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <param name="apiType">The type representing the API</param>
|
||||
/// <param name="httpClientBuilderOptions">An options object that can be used to configure the HTTP client builder.</param>
|
||||
public static IServiceCollection AddApi(this IServiceCollection services, Type apiType, ElsaClientBuilderOptions? httpClientBuilderOptions = default)
|
||||
{
|
||||
var builder = services.AddRefitClient(apiType, sp => CreateRefitSettings(sp, httpClientBuilderOptions?.ConfigureJsonSerializerOptions), apiType.Name).ConfigureHttpClient(ConfigureElsaApiHttpClient);
|
||||
httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder);
|
||||
httpClientBuilderOptions?.ConfigureRetryPolicy?.Invoke(builder);
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a refit client for the specified API type.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <param name="httpClientBuilderOptions">An options object that can be used to configure the HTTP client builder.</param>
|
||||
/// <typeparam name="T">The type representing the API.</typeparam>
|
||||
public static void AddApiWithoutRetryPolicy<T>(this IServiceCollection services, ElsaClientBuilderOptions? httpClientBuilderOptions = default) where T : class
|
||||
{
|
||||
var builder = services
|
||||
.AddRefitClient<T>(sp => CreateRefitSettings(sp), typeof(T).Name)
|
||||
.ConfigureHttpClient(ConfigureElsaApiHttpClient);
|
||||
httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an API client for the specified API type.
|
||||
/// </summary>
|
||||
public static T CreateApi<T>(this IServiceProvider serviceProvider, Uri baseAddress) where T : class
|
||||
{
|
||||
var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
|
||||
var httpClient = httpClientFactory.CreateClient(typeof(T).Name);
|
||||
httpClient.BaseAddress = baseAddress;
|
||||
return CreateApi<T>(serviceProvider, httpClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an API client for the specified API type.
|
||||
/// </summary>
|
||||
public static T CreateApi<T>(this IServiceProvider serviceProvider, HttpClient httpClient) where T : class
|
||||
{
|
||||
return RestService.For<T>(httpClient, CreateRefitSettings(serviceProvider));
|
||||
}
|
||||
|
||||
private static void ConfigureElsaApiHttpClient(IServiceProvider serviceProvider, HttpClient httpClient)
|
||||
{
|
||||
var options = serviceProvider.GetRequiredService<IOptions<ElsaClientOptions>>().Value;
|
||||
httpClient.BaseAddress = options.BaseAddress;
|
||||
options.ConfigureHttpClient?.Invoke(serviceProvider, httpClient);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Elsa.Api.Client.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="JsonElement"/>.
|
||||
/// </summary>
|
||||
public static class DictionaryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the value of the specified property if it exists, otherwise the default value.
|
||||
/// </summary>
|
||||
public static T TryGetValue<T>(this IDictionary<string, object> dictionary, string key, Func<T>? defaultValue = default, JsonSerializerOptions? serializerOptions = default)
|
||||
{
|
||||
var caseInsensitiveDictionary = ToCaseInsensitiveDictionary(dictionary);
|
||||
|
||||
if (caseInsensitiveDictionary.TryGetValue(key, out var value) && value is not JsonElement { ValueKind: JsonValueKind.Undefined })
|
||||
{
|
||||
var convertedValue = value.ConvertTo<T>(new ObjectConverterOptions(serializerOptions));
|
||||
|
||||
if (convertedValue != null)
|
||||
return convertedValue;
|
||||
}
|
||||
|
||||
if (defaultValue == null)
|
||||
return default!;
|
||||
|
||||
var defaultVal = defaultValue()!;
|
||||
dictionary[key] = defaultVal;
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of the specified property if it exists, otherwise the default value.
|
||||
/// </summary>
|
||||
public static object? TryGetValue(this IDictionary<string, object> dictionary, string key, Func<object>? defaultValue = default)
|
||||
{
|
||||
var caseInsensitiveDictionary = ToCaseInsensitiveDictionary(dictionary);
|
||||
|
||||
if (caseInsensitiveDictionary.TryGetValue(key, out var value) && value is not JsonElement { ValueKind: JsonValueKind.Undefined })
|
||||
return value;
|
||||
|
||||
if (defaultValue == null)
|
||||
return default;
|
||||
|
||||
var defaultVal = defaultValue()!;
|
||||
dictionary[key] = defaultVal;
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
// A method that de-duplicates the keys in the dictionary, taking into account that there may already be multiple keys that would be considered the same if the case was ignored.
|
||||
private static Dictionary<string, object> ToCaseInsensitiveDictionary(this IDictionary<string, object> dictionary)
|
||||
{
|
||||
var caseInsensitiveDictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var key in dictionary.Keys)
|
||||
{
|
||||
if (caseInsensitiveDictionary.ContainsKey(key))
|
||||
continue;
|
||||
|
||||
caseInsensitiveDictionary[key] = dictionary[key]!;
|
||||
}
|
||||
|
||||
return caseInsensitiveDictionary;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Elsa.Api.Client.Shared;
|
||||
|
||||
namespace Elsa.Api.Client.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Contains extension methods for the <see cref="HttpResponseMessage"/> class.
|
||||
/// </summary>
|
||||
public static class HttpResponseMessageExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the workflow instance ID from the response.
|
||||
/// </summary>
|
||||
public static string? GetWorkflowInstanceId(this HttpResponseMessage response) => response.Headers.TryGetValues(HeaderNames.WorkflowInstanceId, out var values) ? values.FirstOrDefault() : default;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Elsa.Api.Client.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="JsonElement"/>.
|
||||
/// </summary>
|
||||
public static class JsonElementExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the value of the specified property if it exists, otherwise the default value.
|
||||
/// </summary>
|
||||
public static bool TryGetPropertySafe(this JsonElement element, string propertyName, out JsonElement value)
|
||||
{
|
||||
value = default;
|
||||
|
||||
if(element.ValueKind != JsonValueKind.Object)
|
||||
return false;
|
||||
|
||||
if (!element.TryGetProperty(propertyName, out var property))
|
||||
return false;
|
||||
|
||||
value = property;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace Elsa.Api.Client.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="JsonObject"/>.
|
||||
/// </summary>
|
||||
public static class JsonObjectExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true if the specified <see cref="JsonObject"/> represents an activity.
|
||||
/// </summary>
|
||||
public static bool IsActivity(this JsonObject obj)
|
||||
{
|
||||
return obj.ContainsKey("type") && obj.ContainsKey("id") && obj.ContainsKey("version");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the specified value to a <see cref="JsonObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
|
||||
/// <returns>A <see cref="JsonObject"/> representing the specified value.</returns>
|
||||
public static JsonNode SerializeToNode(this object value, JsonSerializerOptions? options = null)
|
||||
{
|
||||
options ??= new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
|
||||
|
||||
return JsonSerializer.SerializeToNode(value, options)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the specified value to a <see cref="JsonArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
|
||||
/// <returns>A <see cref="JsonObject"/> representing the specified value.</returns>
|
||||
public static JsonArray SerializeToArray(this object value, JsonSerializerOptions? options = null)
|
||||
{
|
||||
options ??= new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
|
||||
|
||||
return JsonSerializer.SerializeToNode(value, options)!.AsArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the specified value to a <see cref="JsonArray"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
|
||||
/// <returns>A <see cref="JsonObject"/> representing the specified value.</returns>
|
||||
public static JsonArray SerializeToArray<T>(this IEnumerable<T> value, JsonSerializerOptions? options = null)
|
||||
{
|
||||
options ??= new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
|
||||
|
||||
return JsonSerializer.SerializeToNode(value, options)!.AsArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the specified <see cref="JsonNode"/> to the specified type.
|
||||
/// </summary>
|
||||
/// <param name="value">The <see cref="JsonNode"/> to deserialize.</param>
|
||||
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
|
||||
/// <typeparam name="T">The type to deserialize to.</typeparam>
|
||||
/// <returns>The deserialized value.</returns>
|
||||
public static T Deserialize<T>(this JsonNode value, JsonSerializerOptions? options = null)
|
||||
{
|
||||
options ??= new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
|
||||
|
||||
if (value is JsonObject jsonObject)
|
||||
return JsonSerializer.Deserialize<T>(jsonObject, options)!;
|
||||
|
||||
if (value is JsonArray jsonArray)
|
||||
return JsonSerializer.Deserialize<T>(jsonArray, options)!;
|
||||
|
||||
if (typeof(T).IsEnum || (Nullable.GetUnderlyingType(typeof(T))?.IsEnum ?? false))
|
||||
{
|
||||
if (value.GetValueKind() == JsonValueKind.Null)
|
||||
return default!;
|
||||
return (T)Enum.Parse(Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T), value.ToString());
|
||||
}
|
||||
|
||||
if (value is JsonValue jsonValue)
|
||||
return jsonValue.GetValue<T>();
|
||||
|
||||
throw new NotSupportedException($"Cannot deserialize {value.GetType()} to {typeof(T)}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the property value of the specified model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to set the property value on.</param>
|
||||
/// <param name="value">The value to set.</param>
|
||||
/// <param name="path">The path to the property.</param>
|
||||
public static void SetProperty(this JsonObject model, JsonNode? value, params string[] path)
|
||||
{
|
||||
model = GetPropertyContainer(model, path);
|
||||
model[path.Last()] = value?.SerializeToNode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the property value of the specified model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to set the property value on.</param>
|
||||
/// <param name="value">The value to set.</param>
|
||||
/// <param name="path">The path to the property.</param>
|
||||
public static void SetProperty(this JsonObject model, JsonArray? value, params string[] path)
|
||||
{
|
||||
model = GetPropertyContainer(model, path);
|
||||
model[path.Last()] = value?.SerializeToNode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the property value of the specified model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to set the property value on.</param>
|
||||
/// <param name="value">The value to set.</param>
|
||||
/// <param name="path">The path to the property.</param>
|
||||
public static void SetProperty(this JsonObject model, IEnumerable<JsonNode> value, params string[] path)
|
||||
{
|
||||
model = GetPropertyContainer(model, path);
|
||||
model[path.Last()] = new JsonArray(value.Select(x => x.SerializeToNode()).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value of the specified model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to get the property value from.</param>
|
||||
/// <param name="path">The path to the property.</param>
|
||||
/// <returns>The property value.</returns>
|
||||
public static JsonNode? GetProperty(this JsonObject model, params string[] path)
|
||||
{
|
||||
var currentModel = model;
|
||||
|
||||
foreach (var prop in path.SkipLast(1))
|
||||
{
|
||||
if (currentModel[prop] is not JsonObject value)
|
||||
return null;
|
||||
|
||||
currentModel = value;
|
||||
}
|
||||
|
||||
return currentModel[path.Last()];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value of the specified model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to get the property value from.</param>
|
||||
/// <param name="path">The path to the property.</param>
|
||||
/// <typeparam name="T">The type to deserialize to.</typeparam>
|
||||
/// <returns>The property value.</returns>
|
||||
public static T? TryGetProperty<T>(this JsonObject model, params string[] path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return model.GetProperty<T>(path);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value of the specified model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to get the property value from.</param>
|
||||
/// <param name="path">The path to the property.</param>
|
||||
/// <typeparam name="T">The type to deserialize to.</typeparam>
|
||||
/// <returns>The property value.</returns>
|
||||
public static T? GetProperty<T>(this JsonObject model, params string[] path)
|
||||
{
|
||||
var property = GetProperty(model, path);
|
||||
return property != null ? property.Deserialize<T>() : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value of the specified model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to get the property value from.</param>
|
||||
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use when deserializing.</param>
|
||||
/// <param name="path">The path to the property.</param>
|
||||
/// <typeparam name="T">The type to deserialize to.</typeparam>
|
||||
/// <returns>The property value.</returns>
|
||||
public static T? GetProperty<T>(this JsonObject model, JsonSerializerOptions options, params string[] path)
|
||||
{
|
||||
var property = GetProperty(model, path);
|
||||
return property != null ? property.Deserialize<T>(options) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the property container of the specified model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to set the property value on.</param>
|
||||
/// <param name="path">The path to the property.</param>
|
||||
private static JsonObject GetPropertyContainer(this JsonObject model, params string[] path)
|
||||
{
|
||||
foreach (var prop in path.SkipLast(1))
|
||||
{
|
||||
var property = model[prop] as JsonObject ?? new JsonObject();
|
||||
model[prop] = property;
|
||||
model = property;
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Dynamic;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Elsa.Api.Client.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides options to the conversion method.
|
||||
/// </summary>
|
||||
public record ObjectConverterOptions(JsonSerializerOptions? SerializerOptions = default)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ObjectConverterOptions(Action<JsonSerializerOptions> configureSerializerOptions) : this()
|
||||
{
|
||||
SerializerOptions = new JsonSerializerOptions();
|
||||
configureSerializerOptions(SerializerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A helper that attempts many strategies to try and convert the source value into the destination type.
|
||||
/// </summary>
|
||||
public static class ObjectConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to convert the source value into the destination type.
|
||||
/// </summary>
|
||||
public static T? ConvertTo<T>(this object? value, ObjectConverterOptions? serializerOptions = null) => value != null ? (T?)value.ConvertTo(typeof(T), serializerOptions) : default;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to convert the source value into the destination type.
|
||||
/// </summary>
|
||||
public static object? ConvertTo(this object? value, Type targetType, ObjectConverterOptions? converterOptions = null)
|
||||
{
|
||||
if (value == null)
|
||||
return default!;
|
||||
|
||||
var sourceType = value.GetType();
|
||||
|
||||
if (sourceType == targetType)
|
||||
return value;
|
||||
|
||||
var options = converterOptions?.SerializerOptions != null ? new JsonSerializerOptions(converterOptions.SerializerOptions) : new JsonSerializerOptions();
|
||||
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
|
||||
options.ReferenceHandler = ReferenceHandler.Preserve;
|
||||
options.PropertyNameCaseInsensitive = true;
|
||||
options.Converters.Add(new JsonStringEnumConverter());
|
||||
|
||||
var underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;
|
||||
var underlyingSourceType = Nullable.GetUnderlyingType(sourceType) ?? sourceType;
|
||||
|
||||
if (value is JsonElement { ValueKind: JsonValueKind.Number } jsonNumber && underlyingTargetType == typeof(string))
|
||||
return jsonNumber.ToString().ConvertTo(underlyingTargetType);
|
||||
|
||||
if (value is JsonElement jsonElement)
|
||||
{
|
||||
if (jsonElement.ValueKind == JsonValueKind.String && underlyingTargetType != typeof(string))
|
||||
return jsonElement.GetString().ConvertTo(underlyingTargetType);
|
||||
|
||||
return jsonElement.Deserialize(targetType, options);
|
||||
}
|
||||
|
||||
if (value is JsonNode jsonNode)
|
||||
return jsonNode.Deserialize(targetType, options);
|
||||
|
||||
if (underlyingSourceType == typeof(string) && !underlyingTargetType.IsPrimitive && underlyingTargetType != typeof(object))
|
||||
{
|
||||
var stringValue = (string)value;
|
||||
|
||||
try
|
||||
{
|
||||
var firstChar = stringValue.TrimStart().FirstOrDefault();
|
||||
|
||||
if (firstChar is '{' or '[')
|
||||
return JsonSerializer.Deserialize(stringValue, underlyingTargetType, options);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new Exception($"Failed to deserialize {stringValue} to {underlyingTargetType}");
|
||||
}
|
||||
}
|
||||
|
||||
if (targetType == typeof(object))
|
||||
return value;
|
||||
|
||||
if (underlyingTargetType.IsInstanceOfType(value))
|
||||
return value;
|
||||
|
||||
if (underlyingSourceType == underlyingTargetType)
|
||||
return value;
|
||||
|
||||
if (IsDateType(underlyingSourceType) && IsDateType(underlyingTargetType))
|
||||
return ConvertAnyDateType(value, underlyingTargetType);
|
||||
|
||||
if (typeof(IDictionary<string, object>).IsAssignableFrom(underlyingSourceType) && underlyingTargetType.IsClass)
|
||||
{
|
||||
if (typeof(ExpandoObject) == underlyingTargetType)
|
||||
{
|
||||
var expandoJson = JsonSerializer.Serialize(value);
|
||||
return ConvertTo(expandoJson, underlyingTargetType, converterOptions);
|
||||
}
|
||||
|
||||
if (typeof(IDictionary<string, object>).IsAssignableFrom(underlyingTargetType))
|
||||
return new Dictionary<string, object>((IDictionary<string, object>)value);
|
||||
|
||||
if (typeof(ExpandoObject) == underlyingSourceType)
|
||||
{
|
||||
// Parse ExpandoObject into target type.
|
||||
var expandoObject = (IDictionary<string, object>)value;
|
||||
var json = JsonSerializer.Serialize(expandoObject);
|
||||
return ConvertTo(json, underlyingTargetType, converterOptions);
|
||||
}
|
||||
}
|
||||
|
||||
var targetTypeConverter = TypeDescriptor.GetConverter(underlyingTargetType);
|
||||
|
||||
if (targetTypeConverter.CanConvertFrom(underlyingSourceType))
|
||||
return targetTypeConverter.IsValid(value)
|
||||
? targetTypeConverter.ConvertFrom(null!, CultureInfo.InvariantCulture, value)
|
||||
: targetType.GetDefaultValue();
|
||||
|
||||
var sourceTypeConverter = TypeDescriptor.GetConverter(underlyingSourceType);
|
||||
|
||||
if (sourceTypeConverter.CanConvertTo(underlyingTargetType))
|
||||
return sourceTypeConverter.ConvertTo(value, underlyingTargetType);
|
||||
|
||||
if (underlyingTargetType.IsEnum)
|
||||
{
|
||||
if (underlyingSourceType == typeof(string))
|
||||
return Enum.Parse(underlyingTargetType, (string)value);
|
||||
|
||||
if (underlyingSourceType == typeof(int))
|
||||
return Enum.ToObject(underlyingTargetType, value);
|
||||
|
||||
if (underlyingSourceType == typeof(double))
|
||||
return Enum.ToObject(underlyingTargetType, Convert.ChangeType(value, typeof(int), CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
if (value is string s)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(s))
|
||||
return null;
|
||||
|
||||
if (underlyingTargetType == typeof(Type))
|
||||
return Type.GetType(s);
|
||||
|
||||
// Perhaps it's a bit of a leap, but if the input is a string and the target type is IEnumerable<string>, then let's assume the string is a comma-separated list of strings.
|
||||
if (typeof(IEnumerable<string>).IsAssignableFrom(underlyingTargetType))
|
||||
return new[] { s };
|
||||
}
|
||||
|
||||
if (value is IEnumerable enumerable)
|
||||
{
|
||||
if (underlyingTargetType is { IsGenericType: true })
|
||||
{
|
||||
var desiredCollectionItemType = targetType.GenericTypeArguments[0];
|
||||
var desiredCollectionType = typeof(ICollection<>).MakeGenericType(desiredCollectionItemType);
|
||||
|
||||
if (underlyingTargetType.IsAssignableFrom(desiredCollectionType) || desiredCollectionType.IsAssignableFrom(underlyingTargetType))
|
||||
{
|
||||
var collectionType = typeof(List<>).MakeGenericType(desiredCollectionItemType);
|
||||
var collection = (IList)Activator.CreateInstance(collectionType)!;
|
||||
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
var convertedItem = ConvertTo(item, desiredCollectionItemType);
|
||||
collection.Add(convertedItem);
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Convert.ChangeType(value, underlyingTargetType, CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
throw new Exception($"Failed to convert an object of type {sourceType} to {underlyingTargetType}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified type is date-like type, false otherwise.
|
||||
/// </summary>
|
||||
private static bool IsDateType(Type type)
|
||||
{
|
||||
var dateTypes = new[]
|
||||
{
|
||||
typeof(DateTime),
|
||||
typeof(DateTimeOffset)
|
||||
};
|
||||
|
||||
return dateTypes.Contains(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts any date type to the specified target type.
|
||||
/// </summary>
|
||||
/// <param name="value">Any of <see cref="DateTime"/> or <see cref="DateTimeOffset"/>.</param>
|
||||
/// <param name="targetType">Any of <see cref="DateTime"/> or <see cref="DateTimeOffset"/>.</param>
|
||||
/// <returns>The converted value.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is not of type <see cref="DateTime"/> or <see cref="DateTimeOffset"/>.</exception>
|
||||
private static object ConvertAnyDateType(object value, Type targetType)
|
||||
{
|
||||
return targetType switch
|
||||
{
|
||||
{ } t when t == typeof(DateTime) => value switch
|
||||
{
|
||||
DateTime dateTime => dateTime,
|
||||
DateTimeOffset dateTimeOffset => dateTimeOffset.DateTime,
|
||||
_ => throw new ArgumentException("Invalid value type.")
|
||||
},
|
||||
{ } t when t == typeof(DateTimeOffset) => value switch
|
||||
{
|
||||
DateTime dateTime => new DateTimeOffset(dateTime),
|
||||
DateTimeOffset dateTimeOffset => dateTimeOffset,
|
||||
_ => throw new ArgumentException("Invalid value type.")
|
||||
},
|
||||
_ => throw new ArgumentException("Invalid target type.")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Text.Json;
|
||||
using Elsa.Api.Client.Shared.Models;
|
||||
|
||||
namespace Elsa.Api.Client.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for the PropertyBag class.
|
||||
/// </summary>
|
||||
public static class PropertyBagExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to retrieve a value from the PropertyBag based on the provided key.
|
||||
/// If the specified key does not exist in the PropertyBag, the method will return the default value obtained from the defaultValue function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to retrieve.</typeparam>
|
||||
/// <param name="propertyBag">The PropertyBag to retrieve the value from.</param>
|
||||
/// <param name="key">The key of the value to retrieve.</param>
|
||||
/// <param name="defaultValue">A function that returns the default value to be returned if the key does not exist in the PropertyBag.</param>
|
||||
/// <returns>The value associated with the key, or the default value if the key does not exist.</returns>
|
||||
public static T? TryGetValueOrDefault<T>(this PropertyBag propertyBag, string key, Func<T> defaultValue)
|
||||
{
|
||||
if (!propertyBag.TryGetValue(key, out var value))
|
||||
return defaultValue();
|
||||
|
||||
var json = value.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return defaultValue();
|
||||
|
||||
return JsonSerializer.Deserialize<T>(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a value in the PropertyBag based on the provided key.
|
||||
/// The value is serialized using JSON.
|
||||
/// </summary>
|
||||
/// <param name="propertyBag">The PropertyBag to set the value in.</param>
|
||||
/// <param name="key">The key to associate with the value.</param>
|
||||
/// <param name="value">The value to store in the PropertyBag.</param>
|
||||
public static void SetValue(this PropertyBag propertyBag, string key, object value)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(value);
|
||||
propertyBag[key] = json;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace Elsa.Api.Client.Extensions;
|
||||
|
||||
public static class ResilienceStrategyExtensions
|
||||
{
|
||||
public static string GetResilienceStrategyId(this JsonObject strategyJsonObject)
|
||||
{
|
||||
return strategyJsonObject.GetProperty<string>("id")!;
|
||||
}
|
||||
|
||||
public static string GetResilienceStrategyDisplayName(this JsonObject strategyJsonObject)
|
||||
{
|
||||
return strategyJsonObject.GetProperty<string>("displayName")!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Elsa.Api.Client.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Adds extension methods to <see cref="Type"/>.
|
||||
/// </summary>
|
||||
public static class TypeExtensions
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Type, string> SimpleAssemblyQualifiedTypeNameCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly-qualified name of the type, without any version info etc.
|
||||
/// E.g. "System.String, System.Private.CoreLib"
|
||||
/// </summary>
|
||||
public static string GetSimpleAssemblyQualifiedName(this Type type)
|
||||
{
|
||||
if (type is null) throw new ArgumentNullException(nameof(type));
|
||||
return SimpleAssemblyQualifiedTypeNameCache.GetOrAdd(type, BuildSimplifiedName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default value for the specified type.
|
||||
/// </summary>
|
||||
public static object? GetDefaultValue(this Type type) => type.IsClass ? null : Activator.CreateInstance(type);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the element type of the specified type representing an array or generic enumerable.
|
||||
/// </summary>
|
||||
public static Type GetEnumerableElementType(this Type type)
|
||||
{
|
||||
if (type.IsArray)
|
||||
return type.GetElementType()!;
|
||||
|
||||
var elementType = FindIEnumerable(type);
|
||||
return elementType == null ? type : elementType.GetGenericArguments()[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the first implemented IEnumerable interface in the given type hierarchy, and returns the generic type argument of the interface.
|
||||
/// </summary>
|
||||
/// <param name="sequenceType">The type to search for the IEnumerable interface.</param>
|
||||
/// <returns>The generic type argument of the first implemented IEnumerable interface found in the type hierarchy, or null if none is found.</returns>
|
||||
private static Type? FindIEnumerable(Type? sequenceType)
|
||||
{
|
||||
if (sequenceType == null || sequenceType == typeof(string))
|
||||
return null;
|
||||
|
||||
if (sequenceType.IsArray)
|
||||
return typeof(IEnumerable<>).MakeGenericType(sequenceType.GetElementType()!);
|
||||
|
||||
if (sequenceType.IsGenericType)
|
||||
{
|
||||
foreach (var arg in sequenceType.GetGenericArguments())
|
||||
{
|
||||
var enumerable = typeof(IEnumerable<>).MakeGenericType(arg);
|
||||
if (enumerable.IsAssignableFrom(sequenceType))
|
||||
return enumerable;
|
||||
}
|
||||
}
|
||||
|
||||
var interfaces = sequenceType.GetInterfaces();
|
||||
|
||||
if (interfaces is { Length: > 0 })
|
||||
{
|
||||
foreach (var interfaceType in interfaces)
|
||||
{
|
||||
var enumerable = FindIEnumerable(interfaceType);
|
||||
if (enumerable != null) return enumerable;
|
||||
}
|
||||
}
|
||||
if (sequenceType.BaseType != null && sequenceType.BaseType != typeof(object))
|
||||
return FindIEnumerable(sequenceType.BaseType);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string BuildSimplifiedName(Type type)
|
||||
{
|
||||
var assemblyName = type.Assembly.GetName().Name;
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var genericTypeName = type.GetGenericTypeDefinition().FullName!;
|
||||
var backtickIndex = genericTypeName.IndexOf('`');
|
||||
var typeNameWithoutArity = genericTypeName[..backtickIndex];
|
||||
var arity = genericTypeName[backtickIndex..];
|
||||
|
||||
var genericArguments = type.GetGenericArguments();
|
||||
var simplifiedGenericArguments = genericArguments.Select(BuildSimplifiedName);
|
||||
|
||||
return $"{typeNameWithoutArity}{arity}[[{string.Join("],[", simplifiedGenericArguments)}]], {assemblyName}";
|
||||
}
|
||||
|
||||
var typeName = type.FullName;
|
||||
return $"{typeName}, {assemblyName}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Elsa.Api.Client.Converters;
|
||||
using Refit;
|
||||
|
||||
namespace Elsa.Api.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Contains helper methods for configuring Refit settings.
|
||||
/// </summary>
|
||||
public static class RefitSettingsHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="RefitSettings"/> instance configured for Elsa.
|
||||
/// </summary>
|
||||
public static RefitSettings CreateRefitSettings(IServiceProvider serviceProvider, Action<IServiceProvider, JsonSerializerOptions>? configureJsonSerializerOptions = null)
|
||||
{
|
||||
var settings = new RefitSettings
|
||||
{
|
||||
ContentSerializer = new SystemTextJsonContentSerializer(CreateJsonSerializerOptions(serviceProvider, configureJsonSerializerOptions))
|
||||
};
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="JsonSerializerOptions"/> instance configured for Elsa.
|
||||
/// </summary>
|
||||
public static JsonSerializerOptions CreateJsonSerializerOptions(IServiceProvider serviceProvider, Action<IServiceProvider, JsonSerializerOptions>? configureJsonSerializerOptions = null)
|
||||
{
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
options.Converters.Add(new JsonStringEnumConverter());
|
||||
options.Converters.Add(new VersionOptionsJsonConverter());
|
||||
options.Converters.Add(new TypeJsonConverter());
|
||||
|
||||
configureJsonSerializerOptions?.Invoke(serviceProvider, options);
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Net.Http.Headers;
|
||||
using Elsa.Api.Client.Options;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Elsa.Api.Client.HttpMessageHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="HttpMessageHandler"/> that configures the outgoing HTTP request to use an API key as the authorization header.
|
||||
/// </summary>
|
||||
public class ApiKeyHttpMessageHandler : DelegatingHandler
|
||||
{
|
||||
private readonly ElsaClientOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ApiKeyHttpMessageHandler"/> class.
|
||||
/// </summary>
|
||||
public ApiKeyHttpMessageHandler(IOptions<ElsaClientOptions> options)
|
||||
{
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
var apiKey = _options.ApiKey;
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("ApiKey", apiKey);
|
||||
|
||||
return await base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace System.Runtime.CompilerServices;
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal sealed class IsExternalInit;
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Text.Json;
|
||||
using Elsa.Api.Client.HttpMessageHandlers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Polly;
|
||||
|
||||
namespace Elsa.Api.Client.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Represents options for building an Elsa API client.
|
||||
/// </summary>
|
||||
public class ElsaClientBuilderOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the base address of the Elsa server.
|
||||
/// </summary>
|
||||
public Uri BaseAddress { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the API key function to use when authenticating with the Elsa server.
|
||||
/// </summary>
|
||||
public string? ApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="DelegatingHandler"/> type that can be used to authenticate with the Elsa server.
|
||||
/// Defaults to <see cref="ApiKeyHttpMessageHandler"/>.
|
||||
/// </summary>
|
||||
public Type AuthenticationHandler { get; set; } = typeof(ApiKeyHttpMessageHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate that can be used to configure the HTTP client.
|
||||
/// </summary>
|
||||
public Action<IServiceProvider, HttpClient>? ConfigureHttpClient { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate that can be used to configure the HTTP client builder.
|
||||
/// </summary>
|
||||
public Action<IHttpClientBuilder> ConfigureHttpClientBuilder { get; set; } = _ => { };
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate that can be used to configure the retry policy.
|
||||
/// </summary>
|
||||
public Action<IHttpClientBuilder>? ConfigureRetryPolicy { get; set; } = builder => builder.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate that can be used to configure the JSON serializer options.
|
||||
/// </summary>
|
||||
public Action<IServiceProvider, JsonSerializerOptions>? ConfigureJsonSerializerOptions { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Elsa.Api.Client.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Represents options for the Elsa client.
|
||||
/// </summary>
|
||||
public class ElsaClientOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the base address of the Elsa server.
|
||||
/// </summary>
|
||||
public Uri BaseAddress { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the API key function to use when authenticating with the Elsa server.
|
||||
/// </summary>
|
||||
public string? ApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate that can be used to configure the HTTP client.
|
||||
/// </summary>
|
||||
public Action<IServiceProvider, HttpClient>? ConfigureHttpClient { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Elsa.Api.Client.Resources.ActivityExecutions.Models;
|
||||
|
||||
namespace Elsa.Api.Client.RealTime.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about the activity executed event.
|
||||
/// </summary>
|
||||
/// <param name="ActivityId">The ID of the activity.</param>
|
||||
/// <param name="Stats">Execution stats about the activity.</param>
|
||||
public record ActivityExecutedMessage(string ActivityId, ActivityExecutionStats Stats);
|
||||
@@ -0,0 +1,10 @@
|
||||
using Elsa.Api.Client.Resources.ActivityExecutions.Models;
|
||||
|
||||
namespace Elsa.Api.Client.RealTime.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about the activity executing event.
|
||||
/// </summary>
|
||||
/// <param name="ActivityId">The ID of the activity.</param>
|
||||
/// <param name="Stats">Execution stats about the activity.</param>
|
||||
public record ActivityExecutingMessage(string ActivityId, ActivityExecutionStats Stats);
|
||||
@@ -0,0 +1,9 @@
|
||||
using Elsa.Api.Client.Resources.ActivityExecutions.Models;
|
||||
|
||||
namespace Elsa.Api.Client.RealTime.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about the activity execution logs updated event.
|
||||
/// </summary>
|
||||
/// <param name="Stats">Execution stats about a set of activities.</param>
|
||||
public record ActivityExecutionLogUpdatedMessage(ICollection<ActivityExecutionStats> Stats);
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Elsa.Api.Client.RealTime.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a message that is sent when the workflow execution log is updated.
|
||||
/// </summary>
|
||||
public record WorkflowExecutionLogUpdatedMessage();
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Elsa.Api.Client.RealTime.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a message that is sent when the workflow instance is updated.
|
||||
/// </summary>
|
||||
/// <param name="WorkflowInstanceId">The ID of the workflow instance that was updated.</param>
|
||||
public record WorkflowInstanceUpdatedMessage(string WorkflowInstanceId);
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using Elsa.Api.Client.Resources.ActivityDescriptorOptions.Requests;
|
||||
using Elsa.Api.Client.Resources.ActivityDescriptorOptions.Responses;
|
||||
using Refit;
|
||||
|
||||
namespace Elsa.Api.Client.Resources.ActivityDescriptorOptions.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a client for the workflow definitions API.
|
||||
/// </summary>
|
||||
public interface IActivityDescriptorOptionsApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Lists activity descriptors options.
|
||||
/// </summary>
|
||||
/// <param name="activityTypeName">The TypeName of the activity </param>
|
||||
/// <param name="propertyName">The name of the property</param>
|
||||
/// <param name="request">The context request.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The response containing the activity descriptors.</returns>
|
||||
[Post("/descriptors/activities/{activityTypeName}/options/{propertyName}")]
|
||||
Task<GetActivityDescriptorOptionsResponse> GetAsync(string activityTypeName, string propertyName, [Body]GetActivityDescriptorOptionsRequest request, CancellationToken cancellationToken = default);
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
namespace Elsa.Api.Client.Resources.ActivityDescriptorOptions.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a request to Get Activity Descriptor Options.
|
||||
/// </summary>
|
||||
public class GetActivityDescriptorOptionsRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Object context use to pass custom information
|
||||
/// </summary>
|
||||
public object? Context { get; set; }
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace Elsa.Api.Client.Resources.ActivityDescriptorOptions.Responses;
|
||||
/// <summary>
|
||||
/// Represents a response from get activity descriptors Options.
|
||||
/// </summary>
|
||||
/// <param name="Items">The options elements.</param>
|
||||
public record GetActivityDescriptorOptionsResponse(IDictionary<string,object> Items);
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using Elsa.Api.Client.Resources.ActivityDescriptors.Requests;
|
||||
using Elsa.Api.Client.Resources.ActivityDescriptors.Responses;
|
||||
using Refit;
|
||||
|
||||
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a client for the workflow definitions API.
|
||||
/// </summary>
|
||||
public interface IActivityDescriptorsApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Lists activity descriptors.
|
||||
/// </summary>
|
||||
/// <param name="request">The request containing options for listing activity descriptors.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The response containing the activity descriptors.</returns>
|
||||
[Get("/descriptors/activities")]
|
||||
Task<ListActivityDescriptorsResponse> ListAsync([Query]ListActivityDescriptorsRequest request, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the kind of an activity, which affects how it is executed.
|
||||
/// </summary>
|
||||
public enum ActivityKind
|
||||
{
|
||||
/// <summary>
|
||||
/// Always run synchronously.
|
||||
/// </summary>
|
||||
Action,
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to trigger new workflows.
|
||||
/// </summary>
|
||||
Trigger,
|
||||
|
||||
/// <summary>
|
||||
/// Always run asynchronously.
|
||||
/// </summary>
|
||||
Job,
|
||||
|
||||
/// <summary>
|
||||
/// Run synchronously by default (like <see cref="Action"/>, but can be configured to run asynchronously (like <see cref="Job"/>).
|
||||
/// </summary>
|
||||
Task
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// The type of a port.
|
||||
/// </summary>
|
||||
public enum PortType
|
||||
{
|
||||
/// <summary>
|
||||
/// A port that is embedded in the activity.
|
||||
/// </summary>
|
||||
Embedded,
|
||||
|
||||
/// <summary>
|
||||
/// A flow port.
|
||||
/// </summary>
|
||||
Flow
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
using Elsa.Api.Client.Resources.ActivityDescriptors.Enums;
|
||||
|
||||
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Models;
|
||||
|
||||
/// <summary>
|
||||
/// A descriptor of an activity type.
|
||||
/// </summary>
|
||||
public record ActivityDescriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// The fully qualified name of the activity type.
|
||||
/// </summary>
|
||||
public string TypeName { get; init; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The namespace of the activity type.
|
||||
/// </summary>
|
||||
public string Namespace { get; init; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the activity type.
|
||||
/// </summary>
|
||||
public string Name { get; init; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The version of the activity type.
|
||||
/// </summary>
|
||||
public int Version { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The category of the activity type.
|
||||
/// </summary>
|
||||
public string Category { get; init; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The display name of the activity type.
|
||||
/// </summary>
|
||||
public string? DisplayName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The description of the activity type.
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The input properties of the activity type.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<InputDescriptor> Inputs { get; init; } = new List<InputDescriptor>();
|
||||
|
||||
/// <summary>
|
||||
/// The output properties of the activity type.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<OutputDescriptor> Outputs { get; init; } = new List<OutputDescriptor>();
|
||||
|
||||
/// <summary>
|
||||
/// The kind of activity.
|
||||
/// </summary>
|
||||
public ActivityKind Kind { get; set; } = ActivityKind.Action;
|
||||
|
||||
/// <summary>
|
||||
/// The ports of the activity type.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<Port> Ports { get; init; } = new List<Port>();
|
||||
|
||||
/// <summary>
|
||||
/// The custom properties of the activity type.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, object> CustomProperties { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
/// The properties to set when constructing an activity in the designer.
|
||||
/// </summary>
|
||||
public IDictionary<string, object> ConstructionProperties { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating whether this activity is a container of child activities.
|
||||
/// </summary>
|
||||
public bool IsContainer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this activity type is selectable from activity pickers.
|
||||
/// </summary>
|
||||
public bool IsBrowsable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this activity type is a start activity.
|
||||
/// </summary>
|
||||
public bool IsStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this activity type is a terminal activity.
|
||||
/// </summary>
|
||||
public bool IsTerminal { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Models;
|
||||
|
||||
/// <summary>
|
||||
/// A descriptor of an activity's input property.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public class InputDescriptor : PropertyDescriptor
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public InputDescriptor()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the property is wrapped with an <c>"Input{T}"</c> type, false otherwise.
|
||||
/// </summary>
|
||||
public bool IsWrapped { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A string value that hints at what UI control might be used to render in a UI tool.
|
||||
/// </summary>
|
||||
public string UIHint { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The category to which this input belongs. Can be used by UI to e.g. render different inputs in different tabs.
|
||||
/// </summary>
|
||||
public string? Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The default value.
|
||||
/// </summary>
|
||||
public object? DefaultValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The default syntax.
|
||||
/// </summary>
|
||||
public string? DefaultSyntax { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the input is readonly, false otherwise.
|
||||
/// </summary>
|
||||
public bool? IsReadOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The storage driver type to use for persistence.
|
||||
/// If no driver is specified, the referenced memory block will remain in memory for as long as the expression execution context exists.
|
||||
/// </summary>
|
||||
public string? StorageDriverType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary of UI specifications to be used by the UI.
|
||||
/// </summary>
|
||||
public IDictionary<string, object>? UISpecifications { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Models;
|
||||
|
||||
/// <summary>
|
||||
/// A descriptor of an activity's output property.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public class OutputDescriptor : PropertyDescriptor
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Elsa.Api.Client.Resources.ActivityDescriptors.Enums;
|
||||
|
||||
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a port on an activity.
|
||||
/// </summary>
|
||||
public class Port
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the port.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display name of the port.
|
||||
/// </summary>
|
||||
public string? DisplayName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the port.
|
||||
/// </summary>
|
||||
public PortType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the visibility of the port.
|
||||
/// </summary>
|
||||
public bool? IsBrowsable { get; set; } = true;
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Models;
|
||||
|
||||
/// <summary>
|
||||
/// A base type for <see cref="InputDescriptor"/> and <see cref="OutputDescriptor"/>.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public abstract class PropertyDescriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// The name.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The .NET type.
|
||||
/// </summary>
|
||||
[JsonPropertyName("typeName")]
|
||||
public string TypeName { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The user friendly name of the input. Used by UI tools.
|
||||
/// </summary>
|
||||
public string? DisplayName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user friendly description of the input. Used by UI tools.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The order in which this input should be displayed by UI tools.
|
||||
/// </summary>
|
||||
public float Order { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if this input should be displayed by UI tools, false otherwise.
|
||||
/// </summary>
|
||||
public bool? IsBrowsable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if this input property is synthetic, which means it does not exist physically on the activity's .NET type.
|
||||
/// </summary>
|
||||
public bool IsSynthetic { get; set; }
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
using Refit;
|
||||
|
||||
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a request to list activity descriptors.
|
||||
/// </summary>
|
||||
public class ListActivityDescriptorsRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to refresh the activity descriptors or not.
|
||||
/// </summary>
|
||||
[Query] public bool Refresh { get; set; }
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using Elsa.Api.Client.Resources.ActivityDescriptors.Models;
|
||||
|
||||
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Responses;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a response from listing activity descriptors.
|
||||
/// </summary>
|
||||
/// <param name="Items">The activity descriptors.</param>
|
||||
/// <param name="Count">The total number of activity descriptors.</param>
|
||||
public record ListActivityDescriptorsResponse(ICollection<ActivityDescriptor> Items, int Count);
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
using Elsa.Api.Client.Resources.ActivityExecutions.Models;
|
||||
using Elsa.Api.Client.Resources.ActivityExecutions.Requests;
|
||||
using Elsa.Api.Client.Shared.Models;
|
||||
using Refit;
|
||||
|
||||
namespace Elsa.Api.Client.Resources.ActivityExecutions.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a client for the activity executions API.
|
||||
/// </summary>
|
||||
public interface IActivityExecutionsApi
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a report of activity executions for a given workflow instance.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
/// <returns>The response containing the activity execution report.</returns>
|
||||
[Post("/activity-executions/report")]
|
||||
Task<ActivityExecutionReport> GetReportAsync([Body] GetActivityExecutionReportRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Lists activity executions for a given activity in a workflow instance.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
/// <returns>The response containing a list of activity executions.</returns>
|
||||
[Get("/activity-executions/list")]
|
||||
Task<ListResponse<ActivityExecutionRecord>> ListAsync(ListActivityExecutionsRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Lists activity execution summaries for a given activity in a workflow instance.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
/// <returns>The response containing a list of activity execution summaries.</returns>
|
||||
[Get("/activity-execution-summaries/list")]
|
||||
Task<ListResponse<ActivityExecutionRecordSummary>> ListSummariesAsync(ListActivityExecutionsRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a single activity execution by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the activity execution.</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
/// <returns>The activity execution.</returns>
|
||||
[Get("/activity-executions/{id}")]
|
||||
Task<ActivityExecutionRecord?> GetAsync(string id, CancellationToken cancellationToken = default);
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
using Elsa.Api.Client.Resources.WorkflowInstances.Models;
|
||||
using Elsa.Api.Client.Shared.Models;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Api.Client.Resources.ActivityExecutions.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single workflow execution, associated with an individual activity instance.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class ActivityExecutionRecord : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the workflow instance ID.
|
||||
/// </summary>
|
||||
public string WorkflowInstanceId { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the activity ID.
|
||||
/// </summary>
|
||||
public string ActivityId { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the activity node ID.
|
||||
/// </summary>
|
||||
public string ActivityNodeId { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The type of the activity.
|
||||
/// </summary>
|
||||
public string ActivityType { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The version of the activity type.
|
||||
/// </summary>
|
||||
public int ActivityTypeVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the activity.
|
||||
/// </summary>
|
||||
public string? ActivityName { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The state of the activity at the time this record is created or last updated.
|
||||
/// </summary>
|
||||
public IDictionary<string, object?>? ActivityState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Any additional payload associated with the log record.
|
||||
/// </summary>
|
||||
public IDictionary<string, object?>? Payload { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Any outputs provided by the activity.
|
||||
/// </summary>
|
||||
public IDictionary<string, object?>? Outputs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Any properties provided by the activity.
|
||||
/// </summary>
|
||||
public IDictionary<string, object?> Properties { get; set; } = new Dictionary<string, object?>();
|
||||
|
||||
/// <summary>
|
||||
/// Any metadata provided by the activity. In contrast to the <see cref="Properties"/> property, this metadata is also persisted as part of the activity execution summary record.
|
||||
/// It is therefore not suitable for larger payloads.
|
||||
/// </summary>
|
||||
public IDictionary<string, object?> Metadata { get; set; } = new Dictionary<string, object?>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception that occurred during the activity execution.
|
||||
/// </summary>
|
||||
public ExceptionState? Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time at which the activity execution began.
|
||||
/// </summary>
|
||||
public DateTimeOffset StartedAt { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the activity has any bookmarks.
|
||||
/// </summary>
|
||||
public bool HasBookmarks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status of the activity.
|
||||
/// </summary>
|
||||
public ActivityStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time at which the activity execution completed.
|
||||
/// </summary>
|
||||
public DateTimeOffset? CompletedAt { get; set; }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user