diff --git a/.editorconfig b/.editorconfig index 31a97b083..ae542588f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,5 @@ -# Remove the line below if you want to inherit .editorconfig settings from higher directories root = true +# Remove the line below if you want to inherit .editorconfig settings from higher directories # C# files [*.cs] @@ -76,9 +76,9 @@ dotnet_style_allow_statement_immediately_after_block_experimental = true #### C# Coding Conventions #### # var preferences -csharp_style_var_elsewhere = false -csharp_style_var_for_built_in_types = false -csharp_style_var_when_type_is_apparent = false +csharp_style_var_elsewhere = true +csharp_style_var_for_built_in_types = true +csharp_style_var_when_type_is_apparent = true # Expression-bodied members csharp_style_expression_bodied_accessors = true diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c7878d823..78a907e38 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,8 +1,8 @@ --- name: Bug report -about: Create a report to help us improve -title: "[BUG]" -labels: bug +about: Create a bug report to help us improve +title: "" +type: Bug assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/doc.md b/.github/ISSUE_TEMPLATE/documentation.md similarity index 95% rename from .github/ISSUE_TEMPLATE/doc.md rename to .github/ISSUE_TEMPLATE/documentation.md index 46aca55d7..96e21f160 100644 --- a/.github/ISSUE_TEMPLATE/doc.md +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -1,8 +1,8 @@ --- -name: Documentation Improvement +name: Documentation about: Suggest improvements to the documentation for this project -title: "[DOC] " -labels: documentation +title: "" +Type: Documentation assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bd29571db..9a4ea5fb3 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,8 +1,8 @@ --- name: Feature request about: Suggest an idea for this project -title: "[FEAT]" -labels: enhancement +title: "" +type: Feature assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/improvement.md similarity index 97% rename from .github/ISSUE_TEMPLATE/enhancement.md rename to .github/ISSUE_TEMPLATE/improvement.md index 72c1f00d2..e72b4864c 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.md +++ b/.github/ISSUE_TEMPLATE/improvement.md @@ -1,8 +1,8 @@ --- -name: Enhancement +name: Improvement about: Suggest an enhancement to an existing feature for this project -title: "[ENH] " -labels: enhancement +title: "" +type: Improvement assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/performance.md b/.github/ISSUE_TEMPLATE/performance.md index 34ee8f5a4..f66241a5a 100644 --- a/.github/ISSUE_TEMPLATE/performance.md +++ b/.github/ISSUE_TEMPLATE/performance.md @@ -1,8 +1,8 @@ --- -name: Performance Improvement +name: Performance about: Suggest a performance enhancement for this project -title: "[PERF] " -labels: performance +title: "" +type: Performance assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/chore.md b/.github/ISSUE_TEMPLATE/task.md similarity index 90% rename from .github/ISSUE_TEMPLATE/chore.md rename to .github/ISSUE_TEMPLATE/task.md index b6d83591a..430bf669c 100644 --- a/.github/ISSUE_TEMPLATE/chore.md +++ b/.github/ISSUE_TEMPLATE/task.md @@ -1,13 +1,13 @@ --- -name: Maintenance Task -about: Suggest a maintenance task for this project -title: "[CHORE] " -labels: maintenance +name: Task +about: Suggest a task for this project +title: "" +type: Task assignees: '' --- -## Maintenance Task Request +## Task Request ### Task Overview **What maintenance task do you propose? Please describe.** diff --git a/.github/ISSUE_TEMPLATE/test.md b/.github/ISSUE_TEMPLATE/test.md deleted file mode 100644 index d6f778748..000000000 --- a/.github/ISSUE_TEMPLATE/test.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -name: Test Improvement -about: Suggest improvements or additions to tests for this project -title: "[TEST] " -labels: test -assignees: '' - ---- - -## Test Improvement Request - -### Test Issue Overview -**Is your test improvement related to a specific problem? Please describe.** -Provide a detailed explanation of the testing issue, such as missing coverage, flaky tests, or inefficient testing processes. For example, "We currently have no tests covering our user authentication flow..." - -### Proposed Test Improvements -**Describe the improvements you'd like** -Explain in detail what testing improvements or new tests you would like to see implemented. Describe how these tests should work and why they are necessary. Include any particular aspects like integration tests, unit tests, or end-to-end tests. - -### Existing Tests -**Describe any existing tests and their limitations** -Detail the current testing setup and its limitations. This could include gaps in coverage, outdated tests, or tests that frequently fail under certain conditions. - -### Impact of Improvements -**Explain the potential impact** -How would these test improvements benefit the development process, product reliability, or deployment cycles? Detail the anticipated benefits to help stakeholders understand the value of improving testing. - -### Additional Context -**Add any other context** -Include any other information that might help clarify your request. This could be technical considerations, links to failed builds, or any other details that could inform the improvement process. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..d8b5df869 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "NuGet" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index f77924633..b661aee6f 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -5,10 +5,12 @@ on: branches: - 'main' - 'bug/*' + - 'perf/*' + - 'patch/*' release: types: [ prereleased, published ] env: - base_version: '3.3.0' + base_version: '3.4.0' feedz_feed_source: 'https://f.feedz.io/elsa-workflows/elsa-3/nuget/index.json' nuget_feed_source: 'https://api.nuget.org/v3/index.json' @@ -99,27 +101,12 @@ jobs: - name: Publish to feedz.io run: dotnet nuget push *.nupkg -k ${{ secrets.FEEDZ_API_KEY }} -s ${{ env.feedz_feed_source }} --skip-duplicate - publish_preview_nuget: - name: Publish preview to nuget.org - needs: build - runs-on: ubuntu-latest - timeout-minutes: 10 - if: ${{ github.event_name == 'prereleased' && github.event.action == 'published' }} - steps: - - name: Download Packages - uses: actions/download-artifact@v4.1.7 - with: - name: elsa-nuget-packages - - - name: Publish to nuget.org - run: dotnet nuget push *.nupkg -k ${{ secrets.NUGET_API_KEY }} -s ${{ env.nuget_feed_source }} --skip-duplicate - publish_nuget: name: Publish release to nuget.org needs: build runs-on: ubuntu-latest timeout-minutes: 10 - if: ${{ github.event_name == 'release' && github.event.action == 'published' }} + if: ${{ github.event.action == 'published' }} steps: - name: Download Packages uses: actions/download-artifact@v4.1.7 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index bb879d54b..d99b63e7f 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -5,7 +5,7 @@ # # - To turn off auto-generation set: # -# [GitHubActions (AutoGenerate = false)] +# [CustomGitHubActions (AutoGenerate = false)] # # - To trigger manual generation invoke: # @@ -32,9 +32,10 @@ jobs: name: ubuntu-latest runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4 with: - dotnet-version: 9.x + dotnet-version: | + 9.x + - uses: actions/checkout@v4 - name: 'Run: Compile, Test, Pack' run: ./build.cmd Compile Test Pack diff --git a/.gitignore b/.gitignore index f772d8ea0..d63bbd6bb 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,5 @@ unlist.sh # build artifacts /artifacts + +/docker/data/ diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 64c8bccf1..839100960 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -1,28 +1,5 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "properties": { - "AnalyseCode": { - "type": "boolean" - }, - "Configuration": { - "type": "string", - "enum": [ - "Debug", - "Release" - ] - }, - "IgnoreFailedSources": { - "type": "boolean", - "description": "Ignore unreachable sources during Restore" - }, - "Solution": { - "type": "string", - "description": "Path to a solution file that is automatically loaded" - }, - "Version": { - "type": "string" - } - }, "definitions": { "Host": { "type": "string", @@ -122,5 +99,34 @@ } } }, - "$ref": "#/definitions/NukeBuild" + "allOf": [ + { + "properties": { + "AnalyseCode": { + "type": "boolean" + }, + "Configuration": { + "type": "string", + "enum": [ + "Debug", + "Release" + ] + }, + "IgnoreFailedSources": { + "type": "boolean", + "description": "Ignore unreachable sources during Restore" + }, + "Solution": { + "type": "string", + "description": "Path to a solution file that is automatically loaded" + }, + "Version": { + "type": "string" + } + } + }, + { + "$ref": "#/definitions/NukeBuild" + } + ] } diff --git a/Directory.Build.props b/Directory.Build.props index 5911ae4e2..eb59aca7f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -37,6 +37,6 @@ $(NoWarn);IL2026;IL2046;IL2057;IL2067;IL2070;IL2072;IL2075;IL2087;IL2091 - 3.3.0-preview.666 + 3.3.0-rc4 \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index a69ea2529..8d70a9957 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,234 +1,197 @@ - - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Elsa.sln b/Elsa.sln index 286627371..3bd4eac22 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -1,999 +1,1018 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.7.34003.232 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{61017E64-6D00-49CB-9E81-5002DC8F7D5F}" - ProjectSection(SolutionItems) = preProject - src\Directory.Build.props = src\Directory.Build.props - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{C6658DE0-2B2F-47F0-BB61-2CA66D435C09}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{7D21EB5E-D6B4-48A5-A9BC-A26387A65F0B}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - .gitignore = .gitignore - Directory.Build.props = Directory.Build.props - Directory.Packages.props = Directory.Packages.props - icon.png = icon.png - NuGet.Config = NuGet.Config - README.md = README.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{0354F050-3992-4DD4-B0EE-5FBA04AC72B6}" - ProjectSection(SolutionItems) = preProject - docs\bounty-footer.md = docs\bounty-footer.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Quartz", "src\modules\Elsa.Quartz\Elsa.Quartz.csproj", "{7D5A49B4-9A9B-496E-803B-DEB85B2C3132}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Http", "src\modules\Elsa.Http\Elsa.Http.csproj", "{82E26BF9-5F3A-4365-899A-AB1FFD54AA45}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Scheduling", "src\modules\Elsa.Scheduling\Elsa.Scheduling.csproj", "{ACD65CE5-3CC2-47B1-BFAC-72443D764F6E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.AzureServiceBus", "src\modules\Elsa.AzureServiceBus\Elsa.AzureServiceBus.csproj", "{24C7095B-5CDD-4369-9F34-5565A019B195}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.JavaScript", "src\modules\Elsa.JavaScript\Elsa.JavaScript.csproj", "{D31581AB-A6C1-4B73-AB63-45667F6C82AE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Hangfire", "src\modules\Elsa.Hangfire\Elsa.Hangfire.csproj", "{0601A2A6-2C62-418B-9104-8CDE497E5283}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.WorkflowContexts", "src\modules\Elsa.WorkflowContexts\Elsa.WorkflowContexts.csproj", "{302BFC43-ED2F-43AE-8AD4-FCD481B0AC67}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.MassTransit", "src\modules\Elsa.MassTransit\Elsa.MassTransit.csproj", "{BB983D0B-A939-4008-B9E3-C7C172BCF83A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{90031D64-CA0F-46D0-9AF4-8DC023A5FFCD}" - ProjectSection(SolutionItems) = preProject - test\Directory.Build.props = test\Directory.Build.props - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Labels", "src\modules\Elsa.Labels\Elsa.Labels.csproj", "{80FF5821-E831-450D-AA0C-C76D07D878F4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Liquid", "src\modules\Elsa.Liquid\Elsa.Liquid.csproj", "{8E5BD3D9-3529-48BC-B741-CFB7A69DB233}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Runtime.ProtoActor", "src\modules\Elsa.Workflows.Runtime.ProtoActor\Elsa.Workflows.Runtime.ProtoActor.csproj", "{B2049499-D384-46DF-8837-F1180107DD54}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Dsl", "src\modules\Elsa.Dsl\Elsa.Dsl.csproj", "{78DAB791-8595-4612-8E9E-2248AF5B1862}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Api", "src\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj", "{C9539BD8-D2AE-4A8D-8281-71A05B3FBF31}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Core", "src\modules\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj", "{169E2C9B-6687-427F-A278-30BF849BFEDC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Management", "src\modules\Elsa.Workflows.Management\Elsa.Workflows.Management.csproj", "{2DD5D66B-85E9-4AF9-911C-C9F963234159}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Runtime", "src\modules\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj", "{8A050229-DB79-4E0B-9AFF-7565E87F2954}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Features", "src\common\Elsa.Features\Elsa.Features.csproj", "{C237BA1A-3A7D-4AB2-BE09-2696F3C082A4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Expressions", "src\modules\Elsa.Expressions\Elsa.Expressions.csproj", "{80529478-A383-4FEA-B744-C71264969E9A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Api.Common", "src\common\Elsa.Api.Common\Elsa.Api.Common.csproj", "{39CD855E-83B1-4A96-93F7-01608211EBE3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Identity", "src\modules\Elsa.Identity\Elsa.Identity.csproj", "{30B49E42-AE61-4F7C-981F-923F07BB3986}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Common", "src\modules\Elsa.Common\Elsa.Common.csproj", "{D229105F-6879-4452-9189-75DE060C0F4C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore", "src\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj", "{7CC95512-7F44-45C3-A669-95B7048D4730}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Telnyx", "src\modules\Elsa.Telnyx\Elsa.Telnyx.csproj", "{F09B30F7-AF33-44D7-B835-099D1F72A37E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Email", "src\modules\Elsa.Email\Elsa.Email.csproj", "{1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{986E5482-0482-448C-B9E4-EC67A9474B85}" - ProjectSection(SolutionItems) = preProject - docker\.dockerignore = docker\.dockerignore - docker\docker-compose-datadog.yml = docker\docker-compose-datadog.yml - docker\docker-compose.yml = docker\docker-compose.yml - docker\ElsaServer-Datadog.Dockerfile = docker\ElsaServer-Datadog.Dockerfile - docker\ElsaServer.Dockerfile = docker\ElsaServer.Dockerfile - docker\ElsaServerAndStudio.Dockerfile = docker\ElsaServerAndStudio.Dockerfile - docker\ElsaStudio.Dockerfile = docker\ElsaStudio.Dockerfile - docker\init-db.sh = docker\init-db.sh - docker\otel-collector-config.yaml = docker\otel-collector-config.yaml - docker\docker-compose-kafka.yml = docker\docker-compose-kafka.yml - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Elasticsearch", "src\modules\Elsa.Elasticsearch\Elsa.Elasticsearch.csproj", "{3246883E-2FA7-4B4A-BDC5-99039A2869BC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Webhooks", "src\modules\Elsa.Webhooks\Elsa.Webhooks.csproj", "{2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unit", "unit", "{18453B51-25EB-4317-A4B3-B10518252E92}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integration", "integration", "{1B8D5897-902E-4632-8698-E89CAF3DDF54}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "component", "component", "{08B41FFA-CEE3-46A7-B5C0-3EB65D37A16C}" - ProjectSection(SolutionItems) = preProject - test\component\Directory.Build.props = test\component\Directory.Build.props - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.IntegrationTests", "test\integration\Elsa.Workflows.IntegrationTests\Elsa.Workflows.IntegrationTests.csproj", "{E9652738-2B3D-4357-B84B-54F0EA161382}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "clients", "clients", "{89608AA5-5ADE-4832-AC7B-871C4AE64210}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Api.Client", "src\clients\Elsa.Api.Client\Elsa.Api.Client.csproj", "{1FCB2200-28B8-4703-8E89-73241AAED047}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Environments", "src\modules\Elsa.Environments\Elsa.Environments.csproj", "{B5CDF747-8066-40D5-9BAE-CBE397BC9B51}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "persistence", "persistence", "{9B4F139F-7D26-435C-A561-89E65A67A8E5}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripting", "scripting", "{6EF07978-A6D2-40EB-891D-7D70C5F37E76}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "servicebus", "servicebus", "{DD089B8B-DA73-492A-9010-F772D1C178DA}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "security", "security", "{5948B0A5-7873-4DBB-BA03-EB283D6EA91B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{B08B4E00-C2AB-48F3-8389-449F42AEF179}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "timers", "timers", "{AB797AF0-C12C-46DE-A157-7E25625C6200}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "runtimes", "runtimes", "{58C59255-281C-4595-8732-808158F1DC6E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Dapper", "src\modules\Elsa.Dapper\Elsa.Dapper.csproj", "{4D8F3BB2-709D-481A-8FD3-0DA9762B366D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Dapper.Migrations", "src\modules\Elsa.Dapper.Migrations\Elsa.Dapper.Migrations.csproj", "{A86DF0F0-24E9-470E-984B-9E7332544972}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.WorkflowProviders.BlobStorage", "src\modules\Elsa.WorkflowProviders.BlobStorage\Elsa.WorkflowProviders.BlobStorage.csproj", "{A702DD84-B55C-4807-9C28-D8F5AC3CCA78}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.MongoDb", "src\modules\Elsa.MongoDb\Elsa.MongoDb.csproj", "{3A377A6A-F735-4010-9E00-72E8BEB8F1F2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Mediator", "src\common\Elsa.Mediator\Elsa.Mediator.csproj", "{28818676-F6AF-4203-8B65-BD33A50CB9A2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Core.UnitTests", "test\unit\Elsa.Workflows.Core.UnitTests\Elsa.Workflows.Core.UnitTests.csproj", "{DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.DropIns.Core", "src\common\Elsa.DropIns.Core\Elsa.DropIns.Core.csproj", "{1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.DropIns", "src\common\Elsa.DropIns\Elsa.DropIns.csproj", "{4E7F15D1-729E-4DA7-992A-C3A2C6054B37}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.SasTokens", "src\modules\Elsa.SasTokens\Elsa.SasTokens.csproj", "{3095C95A-F1F9-487A-A983-1B100A33E4C7}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "alterations", "alterations", "{CF3DA5F1-F98D-49C9-A980-27E570B38B8D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Alterations.Core", "src\modules\Elsa.Alterations.Core\Elsa.Alterations.Core.csproj", "{8DBEC8F3-908D-46B3-95CA-47EA138AB1A4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Alterations", "src\modules\Elsa.Alterations\Elsa.Alterations.csproj", "{CC7DE119-736D-4FC5-85DC-70A6770749F6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.Sqlite", "src\modules\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj", "{EFE4A814-F58D-4C19-8F7F-B95F4EE343F0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.SqlServer", "src\modules\Elsa.EntityFrameworkCore.SqlServer\Elsa.EntityFrameworkCore.SqlServer.csproj", "{1C84408D-A0A7-4513-AD9A-1BA137ABE62C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.MySql", "src\modules\Elsa.EntityFrameworkCore.MySql\Elsa.EntityFrameworkCore.MySql.csproj", "{E4300D97-1D53-4029-9435-483FB7EC9621}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.PostgreSql", "src\modules\Elsa.EntityFrameworkCore.PostgreSql\Elsa.EntityFrameworkCore.PostgreSql.csproj", "{84AB9951-3DBF-43A4-8F5D-AEED1102CE88}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Alterations.MassTransit", "src\modules\Elsa.Alterations.MassTransit\Elsa.Alterations.MassTransit.csproj", "{1F621382-08FB-45EA-B94C-96A7FECB9489}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.Common", "src\modules\Elsa.EntityFrameworkCore.Common\Elsa.EntityFrameworkCore.Common.csproj", "{FBA14F8A-9F14-4364-ABDB-B8AE81A0D755}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Quartz.EntityFrameworkCore.Sqlite", "src\modules\Elsa.Quartz.EntityFrameworkCore.Sqlite\Elsa.Quartz.EntityFrameworkCore.Sqlite.csproj", "{A71557F2-D5FB-4772-9391-F7D18A3B6C44}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Quartz.EntityFrameworkCore.SqlServer", "src\modules\Elsa.Quartz.EntityFrameworkCore.SqlServer\Elsa.Quartz.EntityFrameworkCore.SqlServer.csproj", "{05F3B23E-CF28-467B-AD3C-595EA4ED6B96}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Quartz.EntityFrameworkCore.MySql", "src\modules\Elsa.Quartz.EntityFrameworkCore.MySql\Elsa.Quartz.EntityFrameworkCore.MySql.csproj", "{74ACD9D0-8B4C-42FA-A582-E93BF0075023}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Quartz.EntityFrameworkCore.PostgreSql", "src\modules\Elsa.Quartz.EntityFrameworkCore.PostgreSql\Elsa.Quartz.EntityFrameworkCore.PostgreSql.csproj", "{CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.Oracle", "src\modules\Elsa.EntityFrameworkCore.Oracle\Elsa.EntityFrameworkCore.Oracle.csproj", "{49D8D2CF-AF68-40FA-951F-018247C97443}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.FileStorage", "src\modules\Elsa.FileStorage\Elsa.FileStorage.csproj", "{732BF088-6AD7-4C4D-9A48-8074253596D4}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "storage", "storage", "{B818988E-639C-4E6E-85C1-B231BCAD9DAB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Tenants", "src\modules\Elsa.Tenants\Elsa.Tenants.csproj", "{29638A67-E79F-44FE-AC05-DA499EBA929E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.CSharp", "src\modules\Elsa.CSharp\Elsa.CSharp.csproj", "{24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Python", "src\modules\Elsa.Python\Elsa.Python.csproj", "{790E94F2-5393-47DF-AC52-D9247F5B243A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.MassTransit.RabbitMq", "src\modules\Elsa.MassTransit.RabbitMq\Elsa.MassTransit.RabbitMq.csproj", "{169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pipelines", "pipelines", "{B789B743-0FBE-4BBC-9F45-84C20C8D4814}" - ProjectSection(SolutionItems) = preProject - .github\workflows\bounty.yml = .github\workflows\bounty.yml - .github\workflows\elsa-server-and-studio.yml = .github\workflows\elsa-server-and-studio.yml - .github\workflows\elsa-server.yml = .github\workflows\elsa-server.yml - .github\workflows\elsa-studio.yml = .github\workflows\elsa-studio.yml - .github\workflows\packages.yml = .github\workflows\packages.yml - .github\workflows\pr.yml = .github\workflows\pr.yml - .github\workflows\stale.yml = .github\workflows\stale.yml - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{99F2B1DA-2F69-4D70-A2A3-AC985AD91EC4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.MassTransit.AzureServiceBus", "src\modules\Elsa.MassTransit.AzureServiceBus\Elsa.MassTransit.AzureServiceBus.csproj", "{AFEB799E-82C3-4D02-9D5C-766BB8DEF004}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{C80C8231-D35C-4ACC-9ED6-9F3DB221535E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Alterations.IntegrationTests", "test\integration\Elsa.Alterations.IntegrationTests\Elsa.Alterations.IntegrationTests.csproj", "{F50336DA-42D1-4DD1-A107-67AFEB8A33EE}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "hosting", "hosting", "{A516931E-EDBB-4FC3-BB94-1BB824D5BC61}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Hosting.Management", "src\modules\Elsa.Hosting.Management\Elsa.Hosting.Management.csproj", "{BBCE36D1-6767-4ED1-B3E8-84D2567A962A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.KeyValues", "src\modules\Elsa.KeyValues\Elsa.KeyValues.csproj", "{73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Runtime.UnitTests", "test\unit\Elsa.Workflows.Runtime.UnitTests\Elsa.Workflows.Runtime.UnitTests.csproj", "{B0946844-DE0E-4C59-9391-C0EB649DBF4E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Caching", "src\modules\Elsa.Caching\Elsa.Caching.csproj", "{A1FE2CA0-8EF3-4EDB-9055-DD282E374E16}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "caching", "caching", "{EB3A7401-0DE3-476F-9E6F-057F1F4590FB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Caching.Distributed.MassTransit", "src\modules\Elsa.Caching.Distributed.MassTransit\Elsa.Caching.Distributed.MassTransit.csproj", "{BF4CDE28-F0CC-482D-8392-C1C9BB4C368F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Caching.Distributed", "src\modules\Elsa.Caching.Distributed\Elsa.Caching.Distributed.csproj", "{7EB0CFB8-B668-4F3F-898D-80411095BC2F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.ComponentTests", "test\component\Elsa.Workflows.ComponentTests\Elsa.Workflows.ComponentTests.csproj", "{34BC9836-681D-43A5-BC39-E4FEE17FC528}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "performance", "performance", "{CBB515F3-A0EF-43B5-A907-FD4E652DD66E}" - ProjectSection(SolutionItems) = preProject - test\performance\Directory.Build.props = test\performance\Directory.Build.props - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.PerformanceTests", "test\performance\Elsa.Workflows.PerformanceTests\Elsa.Workflows.PerformanceTests.csproj", "{90CD37A9-C866-4D90-A3B1-8C87F53B845E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "retention", "retention", "{3F20899B-C5E1-485F-808B-ADB1877EB3FD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Retention", "src\modules\Elsa.Retention\Elsa.Retention.csproj", "{3D42FCA2-5BD0-4624-9FCA-F4BF935B27AE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.MongoDb.UnitTests", "test\unit\Elsa.MongoDb.UnitTests\Elsa.MongoDb.UnitTests.csproj", "{56CAA9F2-1882-4EFA-BAC0-9C3D804553F1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Runtime.Distributed", "src\modules\Elsa.Workflows.Runtime.Distributed\Elsa.Workflows.Runtime.Distributed.csproj", "{0D4A88A1-8AEE-49ED-BC2E-ED68C96F60AD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Testing.Shared", "src\common\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj", "{5CBCBEA4-6038-48CA-B45D-C1D0F6B4ACA4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Testing.Shared.Integration", "src\common\Elsa.Testing.Shared.Integration\Elsa.Testing.Shared.Integration.csproj", "{94A61AD7-2A2B-40DB-81F3-C59D596958A8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Testing.Shared.Component", "src\common\Elsa.Testing.Shared.Component\Elsa.Testing.Shared.Component.csproj", "{4B598AF7-BD7D-4544-A274-2CDDD98F4167}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Activities.IntegrationTests", "test\integration\Elsa.Activities.IntegrationTests\Elsa.Activities.IntegrationTests.csproj", "{99B171E6-0248-4402-836D-98947CD63772}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.JavaScript.IntegrationTests", "test\integration\Elsa.JavaScript.IntegrationTests\Elsa.JavaScript.IntegrationTests.csproj", "{4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.AzureServiceBus.ComponentTests", "test\component\Elsa.AzureServiceBus.ComponentTests\Elsa.AzureServiceBus.ComponentTests.csproj", "{7F5A304F-7DD9-4A64-8FCD-9B1FF06735B9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "apps", "apps", "{D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1}" - ProjectSection(SolutionItems) = preProject - src\apps\Directory.Build.props = src\apps\Directory.Build.props - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Server.Web", "src\apps\Elsa.Server.Web\Elsa.Server.Web.csproj", "{5ADDDFB1-E59B-4097-97B7-8C24E2D60463}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa", "src\modules\Elsa\Elsa.csproj", "{97C7E531-9D5F-43FD-AA19-BF24DA13B612}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Server.LoadBalancer", "src\apps\Elsa.Server.LoadBalancer\Elsa.Server.LoadBalancer.csproj", "{65F2AD97-3ECF-4BA6-8AAA-1E5882FDCE68}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElsaStudioWebAssembly", "src\apps\ElsaStudioWebAssembly\ElsaStudioWebAssembly.csproj", "{690B0274-291F-4D9E-BA76-54EFF7D3E4BC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.ServerAndStudio.Web", "src\apps\Elsa.ServerAndStudio.Web\Elsa.ServerAndStudio.Web.csproj", "{060FD0BA-BD78-48E1-A8A7-4906A5AD5E39}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Studio.Web", "src\apps\Elsa.Studio.Web\Elsa.Studio.Web.csproj", "{169A82A5-2DB3-40EA-801E-14C08D743DF7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.OpenTelemetry", "src\modules\Elsa.OpenTelemetry\Elsa.OpenTelemetry.csproj", "{2CDF3E1C-267D-4198-B1C7-7E1F548FC120}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.ProtoActor.Core", "src\modules\Elsa.ProtoActor.Core\Elsa.ProtoActor.Core.csproj", "{01B96BB9-35E8-4364-ACB8-6D12A14D8DBA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Caching.Distributed.ProtoActor", "src\modules\Elsa.Caching.Distributed.ProtoActor\Elsa.Caching.Distributed.ProtoActor.csproj", "{47FBCB04-0C2D-453C-BE2F-7052CAC22524}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "k8s", "k8s", "{B32DB9B2-AD6C-48A5-8682-4373CB045185}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "elsa-server", "elsa-server", "{454652D5-E1BB-4D4B-9B21-9CEFC900C4FB}" - ProjectSection(SolutionItems) = preProject - scripts\k8s\elsa-server\deployment.yaml = scripts\k8s\elsa-server\deployment.yaml - scripts\k8s\elsa-server\role-binding.yaml = scripts\k8s\elsa-server\role-binding.yaml - scripts\k8s\elsa-server\role.yaml = scripts\k8s\elsa-server\role.yaml - scripts\k8s\elsa-server\service-account.yml = scripts\k8s\elsa-server\service-account.yml - scripts\k8s\elsa-server\service.yaml = scripts\k8s\elsa-server\service.yaml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "postgres", "postgres", "{31089E79-694B-4F45-97AF-86D34A7B231E}" - ProjectSection(SolutionItems) = preProject - scripts\k8s\postgres\config-map.yaml = scripts\k8s\postgres\config-map.yaml - scripts\k8s\postgres\deployment.yaml = scripts\k8s\postgres\deployment.yaml - scripts\k8s\postgres\service.yml = scripts\k8s\postgres\service.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plant-uml", "plant-uml", "{6DDB9ECF-D454-4894-A262-A6BB3026E61E}" - ProjectSection(SolutionItems) = preProject - scripts\k8s\plant-uml\deployment.yaml = scripts\k8s\plant-uml\deployment.yaml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "trace-lens", "trace-lens", "{16B570BC-E293-4A19-B20E-5C97BAF8476B}" - ProjectSection(SolutionItems) = preProject - scripts\k8s\trace-lens\deployment.yaml = scripts\k8s\trace-lens\deployment.yaml - scripts\k8s\trace-lens\service.yaml = scripts\k8s\trace-lens\service.yaml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integrations", "integrations", "{EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.OrchardCore", "src\modules\Elsa.OrchardCore\Elsa.OrchardCore.csproj", "{B0460856-7E6D-4B1E-B243-8E20662FC32E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "agents", "agents", "{50470834-4CD8-479A-8B58-0A1869BA5D37}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Core", "src\modules\Elsa.Agents.Core\Elsa.Agents.Core.csproj", "{5BD671F0-9603-4C2F-8364-54B02986EAA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Api", "src\modules\Elsa.Agents.Api\Elsa.Agents.Api.csproj", "{154525CD-3726-45B7-9A9C-553BBC60EB10}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Activities", "src\modules\Elsa.Agents.Activities\Elsa.Agents.Activities.csproj", "{F620D7DC-B58D-4BD0-81A2-E88A2C1E4833}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Persistence.EntityFrameworkCore", "src\modules\Elsa.Agents.Persistence.EntityFrameworkCore\Elsa.Agents.Persistence.EntityFrameworkCore.csproj", "{6FE60BB4-DE6A-485C-A38A-1317C56AF2DA}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuke", "nuke", "{57326431-9C6C-4BAB-AA51-15131C21ADCC}" - ProjectSection(SolutionItems) = preProject - scripts\nuke\build.cmd = scripts\nuke\build.cmd - scripts\nuke\build.ps1 = scripts\nuke\build.ps1 - scripts\nuke\build.sh = scripts\nuke\build.sh - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Core", "src\modules\Elsa.Secrets.Core\Elsa.Secrets.Core.csproj", "{34FAB60D-7259-47B9-B50E-B533C7C3CFEC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "secrets", "secrets", "{8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Management", "src\modules\Elsa.Secrets.Management\Elsa.Secrets.Management.csproj", "{19329441-2A59-4BAC-A1BE-BBAB70C93CA0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Api", "src\modules\Elsa.Secrets.Api\Elsa.Secrets.Api.csproj", "{37D16EAF-ACD8-4105-8BD3-B2745819CFF8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Persistence", "src\modules\Elsa.Agents.Persistence\Elsa.Agents.Persistence.csproj", "{CFEC357F-0863-4458-A6C4-5D7AA0E44759}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Persistence.EntityFrameworkCore.Sqlite", "src\modules\Elsa.Agents.Persistence.EntityFrameworkCore.Sqlite\Elsa.Agents.Persistence.EntityFrameworkCore.Sqlite.csproj", "{E75762D2-FD12-4165-810B-628C28CB2562}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "migrations", "migrations", "{3666CF13-1353-4620-9472-506670214A6A}" - ProjectSection(SolutionItems) = preProject - scripts\migrations\efcore-3.0.sh = scripts\migrations\efcore-3.0.sh - scripts\migrations\efcore-3.1-sql.sh = scripts\migrations\efcore-3.1-sql.sh - scripts\migrations\efcore-3.1.sh = scripts\migrations\efcore-3.1.sh - scripts\migrations\efcore-3.2.sh = scripts\migrations\efcore-3.2.sh - scripts\migrations\efcore-3.3.sh = scripts\migrations\efcore-3.3.sh - scripts\migrations\README.md = scripts\migrations\README.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{AA5ACF15-8647-4AC9-B9F5-73FCB92BE7E3}" - ProjectSection(SolutionItems) = preProject - scripts\docker\build-and-run-all-in-one-web-docker.sh = scripts\docker\build-and-run-all-in-one-web-docker.sh - scripts\docker\docker-compose.yml = scripts\docker\docker-compose.yml - scripts\docker\docker-run-all-in-one-web.ps1 = scripts\docker\docker-run-all-in-one-web.ps1 - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "elsa-studio", "elsa-studio", "{DFFE6199-B5A7-4BA8-A5E1-77D0426C6468}" - ProjectSection(SolutionItems) = preProject - scripts\k8s\elsa-studio\deployment.yaml = scripts\k8s\elsa-studio\deployment.yaml - scripts\k8s\elsa-studio\service.yaml = scripts\k8s\elsa-studio\service.yaml - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Models", "src\modules\Elsa.Agents.Models\Elsa.Agents.Models.csproj", "{052C4F97-5909-4D88-AA49-4B0D9058A0A9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Persistence.EntityFrameworkCore", "src\modules\Elsa.Secrets.Persistence.EntityFrameworkCore\Elsa.Secrets.Persistence.EntityFrameworkCore.csproj", "{9A6D67DE-51B1-456D-B25D-19E0356C8F60}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite", "src\modules\Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite\Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite.csproj", "{44A03CF6-4EB6-434C-90D1-FF8142FE394E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Persistence.EntityFrameworkCore.SqlServer", "src\modules\Elsa.Agents.Persistence.EntityFrameworkCore.SqlServer\Elsa.Agents.Persistence.EntityFrameworkCore.SqlServer.csproj", "{A9140976-DFF9-432A-B1DC-E188A07C49E5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Persistence.EntityFrameworkCore.MySql", "src\modules\Elsa.Agents.Persistence.EntityFrameworkCore.MySql\Elsa.Agents.Persistence.EntityFrameworkCore.MySql.csproj", "{B3046301-6F00-4885-8B01-080BD489055C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Models", "src\modules\Elsa.Secrets.Models\Elsa.Secrets.Models.csproj", "{29D12ADC-55E9-40D0-9E4C-F0EBB6E098EC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Scripting", "src\modules\Elsa.Secrets.Scripting\Elsa.Secrets.Scripting.csproj", "{6C606FEB-9A1F-4816-ABE4-22AFA8CEE771}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.JavaScript.Libraries", "src\modules\Elsa.JavaScript.Libraries\Elsa.JavaScript.Libraries.csproj", "{5F80663C-B072-49DE-9A67-FD804BAD787E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql", "src\modules\Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql\Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql.csproj", "{630E2D93-7F7D-4A8A-BB52-D8095A1F24D9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer", "src\modules\Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer\Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer.csproj", "{59E7614A-3630-4C39-9C7F-9E12BC95D37E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "multitenancy", "multitenancy", "{2F3E1026-5054-4E1F-899B-F1A7F70F9912}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Tenants.AspNetCore", "src\modules\Elsa.Tenants.AspNetCore\Elsa.Tenants.AspNetCore.csproj", "{D5720DBC-8C2B-42D5-9D9F-2FF6EAD4001C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Agents.Persistence.EntityFrameworkCore.PostgreSql", "src\modules\Elsa.Agents.Persistence.EntityFrameworkCore.PostgreSql\Elsa.Agents.Persistence.EntityFrameworkCore.PostgreSql.csproj", "{2B939AC9-03A4-479E-AA0D-CB58F4A7F480}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Kafka", "src\modules\Elsa.Kafka\Elsa.Kafka.csproj", "{BF934627-F531-44FB-BEC2-ECA801FF31E7}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "connection", "connection", "{FFC2EE94-ABD1-4D22-A325-A53F80CC097E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Core", "src\modules\Elsa.Connections.Core\Elsa.Connections.Core.csproj", "{7F620A88-E1CE-4D73-A21A-DE5A5B538BA8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Api", "src\modules\Elsa.Connections.Api\Elsa.Connections.Api.csproj", "{FD22E2B0-E164-4881-9DD4-4E429D178FB9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Connections.Persistence", "src\modules\Elsa.Connections.Persistence\Elsa.Connections.Persistence.csproj", "{9C2764B2-8BA4-4184-9B75-C426A593C815}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7D5A49B4-9A9B-496E-803B-DEB85B2C3132}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7D5A49B4-9A9B-496E-803B-DEB85B2C3132}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7D5A49B4-9A9B-496E-803B-DEB85B2C3132}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7D5A49B4-9A9B-496E-803B-DEB85B2C3132}.Release|Any CPU.Build.0 = Release|Any CPU - {82E26BF9-5F3A-4365-899A-AB1FFD54AA45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {82E26BF9-5F3A-4365-899A-AB1FFD54AA45}.Debug|Any CPU.Build.0 = Debug|Any CPU - {82E26BF9-5F3A-4365-899A-AB1FFD54AA45}.Release|Any CPU.ActiveCfg = Release|Any CPU - {82E26BF9-5F3A-4365-899A-AB1FFD54AA45}.Release|Any CPU.Build.0 = Release|Any CPU - {ACD65CE5-3CC2-47B1-BFAC-72443D764F6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ACD65CE5-3CC2-47B1-BFAC-72443D764F6E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ACD65CE5-3CC2-47B1-BFAC-72443D764F6E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ACD65CE5-3CC2-47B1-BFAC-72443D764F6E}.Release|Any CPU.Build.0 = Release|Any CPU - {24C7095B-5CDD-4369-9F34-5565A019B195}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {24C7095B-5CDD-4369-9F34-5565A019B195}.Debug|Any CPU.Build.0 = Debug|Any CPU - {24C7095B-5CDD-4369-9F34-5565A019B195}.Release|Any CPU.ActiveCfg = Release|Any CPU - {24C7095B-5CDD-4369-9F34-5565A019B195}.Release|Any CPU.Build.0 = Release|Any CPU - {D31581AB-A6C1-4B73-AB63-45667F6C82AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D31581AB-A6C1-4B73-AB63-45667F6C82AE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D31581AB-A6C1-4B73-AB63-45667F6C82AE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D31581AB-A6C1-4B73-AB63-45667F6C82AE}.Release|Any CPU.Build.0 = Release|Any CPU - {0601A2A6-2C62-418B-9104-8CDE497E5283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0601A2A6-2C62-418B-9104-8CDE497E5283}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0601A2A6-2C62-418B-9104-8CDE497E5283}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0601A2A6-2C62-418B-9104-8CDE497E5283}.Release|Any CPU.Build.0 = Release|Any CPU - {302BFC43-ED2F-43AE-8AD4-FCD481B0AC67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {302BFC43-ED2F-43AE-8AD4-FCD481B0AC67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {302BFC43-ED2F-43AE-8AD4-FCD481B0AC67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {302BFC43-ED2F-43AE-8AD4-FCD481B0AC67}.Release|Any CPU.Build.0 = Release|Any CPU - {BB983D0B-A939-4008-B9E3-C7C172BCF83A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BB983D0B-A939-4008-B9E3-C7C172BCF83A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BB983D0B-A939-4008-B9E3-C7C172BCF83A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BB983D0B-A939-4008-B9E3-C7C172BCF83A}.Release|Any CPU.Build.0 = Release|Any CPU - {80FF5821-E831-450D-AA0C-C76D07D878F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {80FF5821-E831-450D-AA0C-C76D07D878F4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {80FF5821-E831-450D-AA0C-C76D07D878F4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {80FF5821-E831-450D-AA0C-C76D07D878F4}.Release|Any CPU.Build.0 = Release|Any CPU - {8E5BD3D9-3529-48BC-B741-CFB7A69DB233}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8E5BD3D9-3529-48BC-B741-CFB7A69DB233}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8E5BD3D9-3529-48BC-B741-CFB7A69DB233}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8E5BD3D9-3529-48BC-B741-CFB7A69DB233}.Release|Any CPU.Build.0 = Release|Any CPU - {B2049499-D384-46DF-8837-F1180107DD54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B2049499-D384-46DF-8837-F1180107DD54}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B2049499-D384-46DF-8837-F1180107DD54}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B2049499-D384-46DF-8837-F1180107DD54}.Release|Any CPU.Build.0 = Release|Any CPU - {78DAB791-8595-4612-8E9E-2248AF5B1862}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {78DAB791-8595-4612-8E9E-2248AF5B1862}.Debug|Any CPU.Build.0 = Debug|Any CPU - {78DAB791-8595-4612-8E9E-2248AF5B1862}.Release|Any CPU.ActiveCfg = Release|Any CPU - {78DAB791-8595-4612-8E9E-2248AF5B1862}.Release|Any CPU.Build.0 = Release|Any CPU - {C9539BD8-D2AE-4A8D-8281-71A05B3FBF31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C9539BD8-D2AE-4A8D-8281-71A05B3FBF31}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C9539BD8-D2AE-4A8D-8281-71A05B3FBF31}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C9539BD8-D2AE-4A8D-8281-71A05B3FBF31}.Release|Any CPU.Build.0 = Release|Any CPU - {169E2C9B-6687-427F-A278-30BF849BFEDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {169E2C9B-6687-427F-A278-30BF849BFEDC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {169E2C9B-6687-427F-A278-30BF849BFEDC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {169E2C9B-6687-427F-A278-30BF849BFEDC}.Release|Any CPU.Build.0 = Release|Any CPU - {2DD5D66B-85E9-4AF9-911C-C9F963234159}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2DD5D66B-85E9-4AF9-911C-C9F963234159}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2DD5D66B-85E9-4AF9-911C-C9F963234159}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2DD5D66B-85E9-4AF9-911C-C9F963234159}.Release|Any CPU.Build.0 = Release|Any CPU - {8A050229-DB79-4E0B-9AFF-7565E87F2954}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A050229-DB79-4E0B-9AFF-7565E87F2954}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A050229-DB79-4E0B-9AFF-7565E87F2954}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A050229-DB79-4E0B-9AFF-7565E87F2954}.Release|Any CPU.Build.0 = Release|Any CPU - {C237BA1A-3A7D-4AB2-BE09-2696F3C082A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C237BA1A-3A7D-4AB2-BE09-2696F3C082A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C237BA1A-3A7D-4AB2-BE09-2696F3C082A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C237BA1A-3A7D-4AB2-BE09-2696F3C082A4}.Release|Any CPU.Build.0 = Release|Any CPU - {80529478-A383-4FEA-B744-C71264969E9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {80529478-A383-4FEA-B744-C71264969E9A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {80529478-A383-4FEA-B744-C71264969E9A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {80529478-A383-4FEA-B744-C71264969E9A}.Release|Any CPU.Build.0 = Release|Any CPU - {39CD855E-83B1-4A96-93F7-01608211EBE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {39CD855E-83B1-4A96-93F7-01608211EBE3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {39CD855E-83B1-4A96-93F7-01608211EBE3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {39CD855E-83B1-4A96-93F7-01608211EBE3}.Release|Any CPU.Build.0 = Release|Any CPU - {30B49E42-AE61-4F7C-981F-923F07BB3986}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {30B49E42-AE61-4F7C-981F-923F07BB3986}.Debug|Any CPU.Build.0 = Debug|Any CPU - {30B49E42-AE61-4F7C-981F-923F07BB3986}.Release|Any CPU.ActiveCfg = Release|Any CPU - {30B49E42-AE61-4F7C-981F-923F07BB3986}.Release|Any CPU.Build.0 = Release|Any CPU - {D229105F-6879-4452-9189-75DE060C0F4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D229105F-6879-4452-9189-75DE060C0F4C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D229105F-6879-4452-9189-75DE060C0F4C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D229105F-6879-4452-9189-75DE060C0F4C}.Release|Any CPU.Build.0 = Release|Any CPU - {7CC95512-7F44-45C3-A669-95B7048D4730}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7CC95512-7F44-45C3-A669-95B7048D4730}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7CC95512-7F44-45C3-A669-95B7048D4730}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7CC95512-7F44-45C3-A669-95B7048D4730}.Release|Any CPU.Build.0 = Release|Any CPU - {F09B30F7-AF33-44D7-B835-099D1F72A37E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F09B30F7-AF33-44D7-B835-099D1F72A37E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F09B30F7-AF33-44D7-B835-099D1F72A37E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F09B30F7-AF33-44D7-B835-099D1F72A37E}.Release|Any CPU.Build.0 = Release|Any CPU - {1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Release|Any CPU.Build.0 = Release|Any CPU - {3246883E-2FA7-4B4A-BDC5-99039A2869BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3246883E-2FA7-4B4A-BDC5-99039A2869BC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3246883E-2FA7-4B4A-BDC5-99039A2869BC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3246883E-2FA7-4B4A-BDC5-99039A2869BC}.Release|Any CPU.Build.0 = Release|Any CPU - {2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}.Release|Any CPU.Build.0 = Release|Any CPU - {E9652738-2B3D-4357-B84B-54F0EA161382}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E9652738-2B3D-4357-B84B-54F0EA161382}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E9652738-2B3D-4357-B84B-54F0EA161382}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E9652738-2B3D-4357-B84B-54F0EA161382}.Release|Any CPU.Build.0 = Release|Any CPU - {1FCB2200-28B8-4703-8E89-73241AAED047}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1FCB2200-28B8-4703-8E89-73241AAED047}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1FCB2200-28B8-4703-8E89-73241AAED047}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1FCB2200-28B8-4703-8E89-73241AAED047}.Release|Any CPU.Build.0 = Release|Any CPU - {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Release|Any CPU.Build.0 = Release|Any CPU - {4D8F3BB2-709D-481A-8FD3-0DA9762B366D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4D8F3BB2-709D-481A-8FD3-0DA9762B366D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4D8F3BB2-709D-481A-8FD3-0DA9762B366D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4D8F3BB2-709D-481A-8FD3-0DA9762B366D}.Release|Any CPU.Build.0 = Release|Any CPU - {A86DF0F0-24E9-470E-984B-9E7332544972}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A86DF0F0-24E9-470E-984B-9E7332544972}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A86DF0F0-24E9-470E-984B-9E7332544972}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A86DF0F0-24E9-470E-984B-9E7332544972}.Release|Any CPU.Build.0 = Release|Any CPU - {A702DD84-B55C-4807-9C28-D8F5AC3CCA78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A702DD84-B55C-4807-9C28-D8F5AC3CCA78}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A702DD84-B55C-4807-9C28-D8F5AC3CCA78}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A702DD84-B55C-4807-9C28-D8F5AC3CCA78}.Release|Any CPU.Build.0 = Release|Any CPU - {3A377A6A-F735-4010-9E00-72E8BEB8F1F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3A377A6A-F735-4010-9E00-72E8BEB8F1F2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3A377A6A-F735-4010-9E00-72E8BEB8F1F2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3A377A6A-F735-4010-9E00-72E8BEB8F1F2}.Release|Any CPU.Build.0 = Release|Any CPU - {28818676-F6AF-4203-8B65-BD33A50CB9A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {28818676-F6AF-4203-8B65-BD33A50CB9A2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {28818676-F6AF-4203-8B65-BD33A50CB9A2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {28818676-F6AF-4203-8B65-BD33A50CB9A2}.Release|Any CPU.Build.0 = Release|Any CPU - {DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}.Release|Any CPU.Build.0 = Release|Any CPU - {1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}.Release|Any CPU.Build.0 = Release|Any CPU - {4E7F15D1-729E-4DA7-992A-C3A2C6054B37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4E7F15D1-729E-4DA7-992A-C3A2C6054B37}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4E7F15D1-729E-4DA7-992A-C3A2C6054B37}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4E7F15D1-729E-4DA7-992A-C3A2C6054B37}.Release|Any CPU.Build.0 = Release|Any CPU - {3095C95A-F1F9-487A-A983-1B100A33E4C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3095C95A-F1F9-487A-A983-1B100A33E4C7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3095C95A-F1F9-487A-A983-1B100A33E4C7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3095C95A-F1F9-487A-A983-1B100A33E4C7}.Release|Any CPU.Build.0 = Release|Any CPU - {8DBEC8F3-908D-46B3-95CA-47EA138AB1A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8DBEC8F3-908D-46B3-95CA-47EA138AB1A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8DBEC8F3-908D-46B3-95CA-47EA138AB1A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8DBEC8F3-908D-46B3-95CA-47EA138AB1A4}.Release|Any CPU.Build.0 = Release|Any CPU - {CC7DE119-736D-4FC5-85DC-70A6770749F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CC7DE119-736D-4FC5-85DC-70A6770749F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CC7DE119-736D-4FC5-85DC-70A6770749F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CC7DE119-736D-4FC5-85DC-70A6770749F6}.Release|Any CPU.Build.0 = Release|Any CPU - {EFE4A814-F58D-4C19-8F7F-B95F4EE343F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EFE4A814-F58D-4C19-8F7F-B95F4EE343F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EFE4A814-F58D-4C19-8F7F-B95F4EE343F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EFE4A814-F58D-4C19-8F7F-B95F4EE343F0}.Release|Any CPU.Build.0 = Release|Any CPU - {1C84408D-A0A7-4513-AD9A-1BA137ABE62C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1C84408D-A0A7-4513-AD9A-1BA137ABE62C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1C84408D-A0A7-4513-AD9A-1BA137ABE62C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1C84408D-A0A7-4513-AD9A-1BA137ABE62C}.Release|Any CPU.Build.0 = Release|Any CPU - {E4300D97-1D53-4029-9435-483FB7EC9621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E4300D97-1D53-4029-9435-483FB7EC9621}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E4300D97-1D53-4029-9435-483FB7EC9621}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E4300D97-1D53-4029-9435-483FB7EC9621}.Release|Any CPU.Build.0 = Release|Any CPU - {84AB9951-3DBF-43A4-8F5D-AEED1102CE88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {84AB9951-3DBF-43A4-8F5D-AEED1102CE88}.Debug|Any CPU.Build.0 = Debug|Any CPU - {84AB9951-3DBF-43A4-8F5D-AEED1102CE88}.Release|Any CPU.ActiveCfg = Release|Any CPU - {84AB9951-3DBF-43A4-8F5D-AEED1102CE88}.Release|Any CPU.Build.0 = Release|Any CPU - {1F621382-08FB-45EA-B94C-96A7FECB9489}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1F621382-08FB-45EA-B94C-96A7FECB9489}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1F621382-08FB-45EA-B94C-96A7FECB9489}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1F621382-08FB-45EA-B94C-96A7FECB9489}.Release|Any CPU.Build.0 = Release|Any CPU - {FBA14F8A-9F14-4364-ABDB-B8AE81A0D755}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FBA14F8A-9F14-4364-ABDB-B8AE81A0D755}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FBA14F8A-9F14-4364-ABDB-B8AE81A0D755}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FBA14F8A-9F14-4364-ABDB-B8AE81A0D755}.Release|Any CPU.Build.0 = Release|Any CPU - {A71557F2-D5FB-4772-9391-F7D18A3B6C44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A71557F2-D5FB-4772-9391-F7D18A3B6C44}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A71557F2-D5FB-4772-9391-F7D18A3B6C44}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A71557F2-D5FB-4772-9391-F7D18A3B6C44}.Release|Any CPU.Build.0 = Release|Any CPU - {05F3B23E-CF28-467B-AD3C-595EA4ED6B96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {05F3B23E-CF28-467B-AD3C-595EA4ED6B96}.Debug|Any CPU.Build.0 = Debug|Any CPU - {05F3B23E-CF28-467B-AD3C-595EA4ED6B96}.Release|Any CPU.ActiveCfg = Release|Any CPU - {05F3B23E-CF28-467B-AD3C-595EA4ED6B96}.Release|Any CPU.Build.0 = Release|Any CPU - {74ACD9D0-8B4C-42FA-A582-E93BF0075023}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {74ACD9D0-8B4C-42FA-A582-E93BF0075023}.Debug|Any CPU.Build.0 = Debug|Any CPU - {74ACD9D0-8B4C-42FA-A582-E93BF0075023}.Release|Any CPU.ActiveCfg = Release|Any CPU - {74ACD9D0-8B4C-42FA-A582-E93BF0075023}.Release|Any CPU.Build.0 = Release|Any CPU - {CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}.Release|Any CPU.Build.0 = Release|Any CPU - {49D8D2CF-AF68-40FA-951F-018247C97443}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {49D8D2CF-AF68-40FA-951F-018247C97443}.Debug|Any CPU.Build.0 = Debug|Any CPU - {49D8D2CF-AF68-40FA-951F-018247C97443}.Release|Any CPU.ActiveCfg = Release|Any CPU - {49D8D2CF-AF68-40FA-951F-018247C97443}.Release|Any CPU.Build.0 = Release|Any CPU - {732BF088-6AD7-4C4D-9A48-8074253596D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {732BF088-6AD7-4C4D-9A48-8074253596D4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {732BF088-6AD7-4C4D-9A48-8074253596D4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {732BF088-6AD7-4C4D-9A48-8074253596D4}.Release|Any CPU.Build.0 = Release|Any CPU - {29638A67-E79F-44FE-AC05-DA499EBA929E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29638A67-E79F-44FE-AC05-DA499EBA929E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29638A67-E79F-44FE-AC05-DA499EBA929E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29638A67-E79F-44FE-AC05-DA499EBA929E}.Release|Any CPU.Build.0 = Release|Any CPU - {24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC}.Release|Any CPU.Build.0 = Release|Any CPU - {790E94F2-5393-47DF-AC52-D9247F5B243A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {790E94F2-5393-47DF-AC52-D9247F5B243A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {790E94F2-5393-47DF-AC52-D9247F5B243A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {790E94F2-5393-47DF-AC52-D9247F5B243A}.Release|Any CPU.Build.0 = Release|Any CPU - {169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6}.Release|Any CPU.Build.0 = Release|Any CPU - {99F2B1DA-2F69-4D70-A2A3-AC985AD91EC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99F2B1DA-2F69-4D70-A2A3-AC985AD91EC4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AFEB799E-82C3-4D02-9D5C-766BB8DEF004}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AFEB799E-82C3-4D02-9D5C-766BB8DEF004}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AFEB799E-82C3-4D02-9D5C-766BB8DEF004}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AFEB799E-82C3-4D02-9D5C-766BB8DEF004}.Release|Any CPU.Build.0 = Release|Any CPU - {F50336DA-42D1-4DD1-A107-67AFEB8A33EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F50336DA-42D1-4DD1-A107-67AFEB8A33EE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F50336DA-42D1-4DD1-A107-67AFEB8A33EE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F50336DA-42D1-4DD1-A107-67AFEB8A33EE}.Release|Any CPU.Build.0 = Release|Any CPU - {BBCE36D1-6767-4ED1-B3E8-84D2567A962A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BBCE36D1-6767-4ED1-B3E8-84D2567A962A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BBCE36D1-6767-4ED1-B3E8-84D2567A962A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BBCE36D1-6767-4ED1-B3E8-84D2567A962A}.Release|Any CPU.Build.0 = Release|Any CPU - {73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3}.Release|Any CPU.Build.0 = Release|Any CPU - {B0946844-DE0E-4C59-9391-C0EB649DBF4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B0946844-DE0E-4C59-9391-C0EB649DBF4E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B0946844-DE0E-4C59-9391-C0EB649DBF4E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B0946844-DE0E-4C59-9391-C0EB649DBF4E}.Release|Any CPU.Build.0 = Release|Any CPU - {A1FE2CA0-8EF3-4EDB-9055-DD282E374E16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1FE2CA0-8EF3-4EDB-9055-DD282E374E16}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1FE2CA0-8EF3-4EDB-9055-DD282E374E16}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1FE2CA0-8EF3-4EDB-9055-DD282E374E16}.Release|Any CPU.Build.0 = Release|Any CPU - {BF4CDE28-F0CC-482D-8392-C1C9BB4C368F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BF4CDE28-F0CC-482D-8392-C1C9BB4C368F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BF4CDE28-F0CC-482D-8392-C1C9BB4C368F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BF4CDE28-F0CC-482D-8392-C1C9BB4C368F}.Release|Any CPU.Build.0 = Release|Any CPU - {7EB0CFB8-B668-4F3F-898D-80411095BC2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EB0CFB8-B668-4F3F-898D-80411095BC2F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EB0CFB8-B668-4F3F-898D-80411095BC2F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EB0CFB8-B668-4F3F-898D-80411095BC2F}.Release|Any CPU.Build.0 = Release|Any CPU - {34BC9836-681D-43A5-BC39-E4FEE17FC528}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {34BC9836-681D-43A5-BC39-E4FEE17FC528}.Debug|Any CPU.Build.0 = Debug|Any CPU - {34BC9836-681D-43A5-BC39-E4FEE17FC528}.Release|Any CPU.ActiveCfg = Release|Any CPU - {34BC9836-681D-43A5-BC39-E4FEE17FC528}.Release|Any CPU.Build.0 = Release|Any CPU - {90CD37A9-C866-4D90-A3B1-8C87F53B845E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {90CD37A9-C866-4D90-A3B1-8C87F53B845E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {90CD37A9-C866-4D90-A3B1-8C87F53B845E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {90CD37A9-C866-4D90-A3B1-8C87F53B845E}.Release|Any CPU.Build.0 = Release|Any CPU - {3D42FCA2-5BD0-4624-9FCA-F4BF935B27AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3D42FCA2-5BD0-4624-9FCA-F4BF935B27AE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3D42FCA2-5BD0-4624-9FCA-F4BF935B27AE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3D42FCA2-5BD0-4624-9FCA-F4BF935B27AE}.Release|Any CPU.Build.0 = Release|Any CPU - {56CAA9F2-1882-4EFA-BAC0-9C3D804553F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {56CAA9F2-1882-4EFA-BAC0-9C3D804553F1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {56CAA9F2-1882-4EFA-BAC0-9C3D804553F1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {56CAA9F2-1882-4EFA-BAC0-9C3D804553F1}.Release|Any CPU.Build.0 = Release|Any CPU - {0D4A88A1-8AEE-49ED-BC2E-ED68C96F60AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0D4A88A1-8AEE-49ED-BC2E-ED68C96F60AD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0D4A88A1-8AEE-49ED-BC2E-ED68C96F60AD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0D4A88A1-8AEE-49ED-BC2E-ED68C96F60AD}.Release|Any CPU.Build.0 = Release|Any CPU - {5CBCBEA4-6038-48CA-B45D-C1D0F6B4ACA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5CBCBEA4-6038-48CA-B45D-C1D0F6B4ACA4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5CBCBEA4-6038-48CA-B45D-C1D0F6B4ACA4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5CBCBEA4-6038-48CA-B45D-C1D0F6B4ACA4}.Release|Any CPU.Build.0 = Release|Any CPU - {94A61AD7-2A2B-40DB-81F3-C59D596958A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {94A61AD7-2A2B-40DB-81F3-C59D596958A8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {94A61AD7-2A2B-40DB-81F3-C59D596958A8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {94A61AD7-2A2B-40DB-81F3-C59D596958A8}.Release|Any CPU.Build.0 = Release|Any CPU - {4B598AF7-BD7D-4544-A274-2CDDD98F4167}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4B598AF7-BD7D-4544-A274-2CDDD98F4167}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4B598AF7-BD7D-4544-A274-2CDDD98F4167}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4B598AF7-BD7D-4544-A274-2CDDD98F4167}.Release|Any CPU.Build.0 = Release|Any CPU - {99B171E6-0248-4402-836D-98947CD63772}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99B171E6-0248-4402-836D-98947CD63772}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99B171E6-0248-4402-836D-98947CD63772}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99B171E6-0248-4402-836D-98947CD63772}.Release|Any CPU.Build.0 = Release|Any CPU - {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Release|Any CPU.Build.0 = Release|Any CPU - {7F5A304F-7DD9-4A64-8FCD-9B1FF06735B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7F5A304F-7DD9-4A64-8FCD-9B1FF06735B9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7F5A304F-7DD9-4A64-8FCD-9B1FF06735B9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7F5A304F-7DD9-4A64-8FCD-9B1FF06735B9}.Release|Any CPU.Build.0 = Release|Any CPU - {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Release|Any CPU.Build.0 = Release|Any CPU - {97C7E531-9D5F-43FD-AA19-BF24DA13B612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {97C7E531-9D5F-43FD-AA19-BF24DA13B612}.Debug|Any CPU.Build.0 = Debug|Any CPU - {97C7E531-9D5F-43FD-AA19-BF24DA13B612}.Release|Any CPU.ActiveCfg = Release|Any CPU - {97C7E531-9D5F-43FD-AA19-BF24DA13B612}.Release|Any CPU.Build.0 = Release|Any CPU - {65F2AD97-3ECF-4BA6-8AAA-1E5882FDCE68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {65F2AD97-3ECF-4BA6-8AAA-1E5882FDCE68}.Debug|Any CPU.Build.0 = Debug|Any CPU - {65F2AD97-3ECF-4BA6-8AAA-1E5882FDCE68}.Release|Any CPU.ActiveCfg = Release|Any CPU - {65F2AD97-3ECF-4BA6-8AAA-1E5882FDCE68}.Release|Any CPU.Build.0 = Release|Any CPU - {690B0274-291F-4D9E-BA76-54EFF7D3E4BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {690B0274-291F-4D9E-BA76-54EFF7D3E4BC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {690B0274-291F-4D9E-BA76-54EFF7D3E4BC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {690B0274-291F-4D9E-BA76-54EFF7D3E4BC}.Release|Any CPU.Build.0 = Release|Any CPU - {060FD0BA-BD78-48E1-A8A7-4906A5AD5E39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {060FD0BA-BD78-48E1-A8A7-4906A5AD5E39}.Debug|Any CPU.Build.0 = Debug|Any CPU - {060FD0BA-BD78-48E1-A8A7-4906A5AD5E39}.Release|Any CPU.ActiveCfg = Release|Any CPU - {060FD0BA-BD78-48E1-A8A7-4906A5AD5E39}.Release|Any CPU.Build.0 = Release|Any CPU - {169A82A5-2DB3-40EA-801E-14C08D743DF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {169A82A5-2DB3-40EA-801E-14C08D743DF7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {169A82A5-2DB3-40EA-801E-14C08D743DF7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {169A82A5-2DB3-40EA-801E-14C08D743DF7}.Release|Any CPU.Build.0 = Release|Any CPU - {2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Release|Any CPU.Build.0 = Release|Any CPU - {01B96BB9-35E8-4364-ACB8-6D12A14D8DBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {01B96BB9-35E8-4364-ACB8-6D12A14D8DBA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {01B96BB9-35E8-4364-ACB8-6D12A14D8DBA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {01B96BB9-35E8-4364-ACB8-6D12A14D8DBA}.Release|Any CPU.Build.0 = Release|Any CPU - {47FBCB04-0C2D-453C-BE2F-7052CAC22524}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {47FBCB04-0C2D-453C-BE2F-7052CAC22524}.Debug|Any CPU.Build.0 = Debug|Any CPU - {47FBCB04-0C2D-453C-BE2F-7052CAC22524}.Release|Any CPU.ActiveCfg = Release|Any CPU - {47FBCB04-0C2D-453C-BE2F-7052CAC22524}.Release|Any CPU.Build.0 = Release|Any CPU - {B0460856-7E6D-4B1E-B243-8E20662FC32E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B0460856-7E6D-4B1E-B243-8E20662FC32E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B0460856-7E6D-4B1E-B243-8E20662FC32E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B0460856-7E6D-4B1E-B243-8E20662FC32E}.Release|Any CPU.Build.0 = Release|Any CPU - {5BD671F0-9603-4C2F-8364-54B02986EAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5BD671F0-9603-4C2F-8364-54B02986EAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5BD671F0-9603-4C2F-8364-54B02986EAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5BD671F0-9603-4C2F-8364-54B02986EAA2}.Release|Any CPU.Build.0 = Release|Any CPU - {154525CD-3726-45B7-9A9C-553BBC60EB10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {154525CD-3726-45B7-9A9C-553BBC60EB10}.Debug|Any CPU.Build.0 = Debug|Any CPU - {154525CD-3726-45B7-9A9C-553BBC60EB10}.Release|Any CPU.ActiveCfg = Release|Any CPU - {154525CD-3726-45B7-9A9C-553BBC60EB10}.Release|Any CPU.Build.0 = Release|Any CPU - {F620D7DC-B58D-4BD0-81A2-E88A2C1E4833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F620D7DC-B58D-4BD0-81A2-E88A2C1E4833}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F620D7DC-B58D-4BD0-81A2-E88A2C1E4833}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F620D7DC-B58D-4BD0-81A2-E88A2C1E4833}.Release|Any CPU.Build.0 = Release|Any CPU - {6FE60BB4-DE6A-485C-A38A-1317C56AF2DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6FE60BB4-DE6A-485C-A38A-1317C56AF2DA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6FE60BB4-DE6A-485C-A38A-1317C56AF2DA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6FE60BB4-DE6A-485C-A38A-1317C56AF2DA}.Release|Any CPU.Build.0 = Release|Any CPU - {34FAB60D-7259-47B9-B50E-B533C7C3CFEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {34FAB60D-7259-47B9-B50E-B533C7C3CFEC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {34FAB60D-7259-47B9-B50E-B533C7C3CFEC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {34FAB60D-7259-47B9-B50E-B533C7C3CFEC}.Release|Any CPU.Build.0 = Release|Any CPU - {19329441-2A59-4BAC-A1BE-BBAB70C93CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {19329441-2A59-4BAC-A1BE-BBAB70C93CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {19329441-2A59-4BAC-A1BE-BBAB70C93CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {19329441-2A59-4BAC-A1BE-BBAB70C93CA0}.Release|Any CPU.Build.0 = Release|Any CPU - {37D16EAF-ACD8-4105-8BD3-B2745819CFF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {37D16EAF-ACD8-4105-8BD3-B2745819CFF8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {37D16EAF-ACD8-4105-8BD3-B2745819CFF8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {37D16EAF-ACD8-4105-8BD3-B2745819CFF8}.Release|Any CPU.Build.0 = Release|Any CPU - {CFEC357F-0863-4458-A6C4-5D7AA0E44759}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CFEC357F-0863-4458-A6C4-5D7AA0E44759}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CFEC357F-0863-4458-A6C4-5D7AA0E44759}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CFEC357F-0863-4458-A6C4-5D7AA0E44759}.Release|Any CPU.Build.0 = Release|Any CPU - {E75762D2-FD12-4165-810B-628C28CB2562}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E75762D2-FD12-4165-810B-628C28CB2562}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E75762D2-FD12-4165-810B-628C28CB2562}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E75762D2-FD12-4165-810B-628C28CB2562}.Release|Any CPU.Build.0 = Release|Any CPU - {052C4F97-5909-4D88-AA49-4B0D9058A0A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {052C4F97-5909-4D88-AA49-4B0D9058A0A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {052C4F97-5909-4D88-AA49-4B0D9058A0A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {052C4F97-5909-4D88-AA49-4B0D9058A0A9}.Release|Any CPU.Build.0 = Release|Any CPU - {9A6D67DE-51B1-456D-B25D-19E0356C8F60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9A6D67DE-51B1-456D-B25D-19E0356C8F60}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9A6D67DE-51B1-456D-B25D-19E0356C8F60}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9A6D67DE-51B1-456D-B25D-19E0356C8F60}.Release|Any CPU.Build.0 = Release|Any CPU - {44A03CF6-4EB6-434C-90D1-FF8142FE394E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {44A03CF6-4EB6-434C-90D1-FF8142FE394E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {44A03CF6-4EB6-434C-90D1-FF8142FE394E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {44A03CF6-4EB6-434C-90D1-FF8142FE394E}.Release|Any CPU.Build.0 = Release|Any CPU - {A9140976-DFF9-432A-B1DC-E188A07C49E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A9140976-DFF9-432A-B1DC-E188A07C49E5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A9140976-DFF9-432A-B1DC-E188A07C49E5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A9140976-DFF9-432A-B1DC-E188A07C49E5}.Release|Any CPU.Build.0 = Release|Any CPU - {B3046301-6F00-4885-8B01-080BD489055C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B3046301-6F00-4885-8B01-080BD489055C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B3046301-6F00-4885-8B01-080BD489055C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B3046301-6F00-4885-8B01-080BD489055C}.Release|Any CPU.Build.0 = Release|Any CPU - {29D12ADC-55E9-40D0-9E4C-F0EBB6E098EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29D12ADC-55E9-40D0-9E4C-F0EBB6E098EC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29D12ADC-55E9-40D0-9E4C-F0EBB6E098EC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29D12ADC-55E9-40D0-9E4C-F0EBB6E098EC}.Release|Any CPU.Build.0 = Release|Any CPU - {6C606FEB-9A1F-4816-ABE4-22AFA8CEE771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6C606FEB-9A1F-4816-ABE4-22AFA8CEE771}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6C606FEB-9A1F-4816-ABE4-22AFA8CEE771}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6C606FEB-9A1F-4816-ABE4-22AFA8CEE771}.Release|Any CPU.Build.0 = Release|Any CPU - {5F80663C-B072-49DE-9A67-FD804BAD787E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5F80663C-B072-49DE-9A67-FD804BAD787E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5F80663C-B072-49DE-9A67-FD804BAD787E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5F80663C-B072-49DE-9A67-FD804BAD787E}.Release|Any CPU.Build.0 = Release|Any CPU - {630E2D93-7F7D-4A8A-BB52-D8095A1F24D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {630E2D93-7F7D-4A8A-BB52-D8095A1F24D9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {630E2D93-7F7D-4A8A-BB52-D8095A1F24D9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {630E2D93-7F7D-4A8A-BB52-D8095A1F24D9}.Release|Any CPU.Build.0 = Release|Any CPU - {59E7614A-3630-4C39-9C7F-9E12BC95D37E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {59E7614A-3630-4C39-9C7F-9E12BC95D37E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {59E7614A-3630-4C39-9C7F-9E12BC95D37E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {59E7614A-3630-4C39-9C7F-9E12BC95D37E}.Release|Any CPU.Build.0 = Release|Any CPU - {D5720DBC-8C2B-42D5-9D9F-2FF6EAD4001C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5720DBC-8C2B-42D5-9D9F-2FF6EAD4001C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5720DBC-8C2B-42D5-9D9F-2FF6EAD4001C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5720DBC-8C2B-42D5-9D9F-2FF6EAD4001C}.Release|Any CPU.Build.0 = Release|Any CPU - {2B939AC9-03A4-479E-AA0D-CB58F4A7F480}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2B939AC9-03A4-479E-AA0D-CB58F4A7F480}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2B939AC9-03A4-479E-AA0D-CB58F4A7F480}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2B939AC9-03A4-479E-AA0D-CB58F4A7F480}.Release|Any CPU.Build.0 = Release|Any CPU - {BF934627-F531-44FB-BEC2-ECA801FF31E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BF934627-F531-44FB-BEC2-ECA801FF31E7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BF934627-F531-44FB-BEC2-ECA801FF31E7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BF934627-F531-44FB-BEC2-ECA801FF31E7}.Release|Any CPU.Build.0 = Release|Any CPU - {7F620A88-E1CE-4D73-A21A-DE5A5B538BA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7F620A88-E1CE-4D73-A21A-DE5A5B538BA8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7F620A88-E1CE-4D73-A21A-DE5A5B538BA8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7F620A88-E1CE-4D73-A21A-DE5A5B538BA8}.Release|Any CPU.Build.0 = Release|Any CPU - {FD22E2B0-E164-4881-9DD4-4E429D178FB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FD22E2B0-E164-4881-9DD4-4E429D178FB9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FD22E2B0-E164-4881-9DD4-4E429D178FB9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FD22E2B0-E164-4881-9DD4-4E429D178FB9}.Release|Any CPU.Build.0 = Release|Any CPU - {9C2764B2-8BA4-4184-9B75-C426A593C815}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9C2764B2-8BA4-4184-9B75-C426A593C815}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9C2764B2-8BA4-4184-9B75-C426A593C815}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9C2764B2-8BA4-4184-9B75-C426A593C815}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} - {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} - {7D5A49B4-9A9B-496E-803B-DEB85B2C3132} = {AB797AF0-C12C-46DE-A157-7E25625C6200} - {82E26BF9-5F3A-4365-899A-AB1FFD54AA45} = {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} - {ACD65CE5-3CC2-47B1-BFAC-72443D764F6E} = {AB797AF0-C12C-46DE-A157-7E25625C6200} - {24C7095B-5CDD-4369-9F34-5565A019B195} = {DD089B8B-DA73-492A-9010-F772D1C178DA} - {D31581AB-A6C1-4B73-AB63-45667F6C82AE} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} - {0601A2A6-2C62-418B-9104-8CDE497E5283} = {AB797AF0-C12C-46DE-A157-7E25625C6200} - {302BFC43-ED2F-43AE-8AD4-FCD481B0AC67} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} - {BB983D0B-A939-4008-B9E3-C7C172BCF83A} = {DD089B8B-DA73-492A-9010-F772D1C178DA} - {80FF5821-E831-450D-AA0C-C76D07D878F4} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {8E5BD3D9-3529-48BC-B741-CFB7A69DB233} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} - {B2049499-D384-46DF-8837-F1180107DD54} = {58C59255-281C-4595-8732-808158F1DC6E} - {78DAB791-8595-4612-8E9E-2248AF5B1862} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} - {C9539BD8-D2AE-4A8D-8281-71A05B3FBF31} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} - {169E2C9B-6687-427F-A278-30BF849BFEDC} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} - {2DD5D66B-85E9-4AF9-911C-C9F963234159} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} - {8A050229-DB79-4E0B-9AFF-7565E87F2954} = {58C59255-281C-4595-8732-808158F1DC6E} - {C237BA1A-3A7D-4AB2-BE09-2696F3C082A4} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} - {80529478-A383-4FEA-B744-C71264969E9A} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} - {39CD855E-83B1-4A96-93F7-01608211EBE3} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} - {30B49E42-AE61-4F7C-981F-923F07BB3986} = {5948B0A5-7873-4DBB-BA03-EB283D6EA91B} - {D229105F-6879-4452-9189-75DE060C0F4C} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {7CC95512-7F44-45C3-A669-95B7048D4730} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} - {F09B30F7-AF33-44D7-B835-099D1F72A37E} = {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} - {1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF} = {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} - {3246883E-2FA7-4B4A-BDC5-99039A2869BC} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} - {2BBFDE36-28A7-4875-8EBE-CC25C76B97FF} = {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} - {18453B51-25EB-4317-A4B3-B10518252E92} = {90031D64-CA0F-46D0-9AF4-8DC023A5FFCD} - {1B8D5897-902E-4632-8698-E89CAF3DDF54} = {90031D64-CA0F-46D0-9AF4-8DC023A5FFCD} - {08B41FFA-CEE3-46A7-B5C0-3EB65D37A16C} = {90031D64-CA0F-46D0-9AF4-8DC023A5FFCD} - {E9652738-2B3D-4357-B84B-54F0EA161382} = {1B8D5897-902E-4632-8698-E89CAF3DDF54} - {89608AA5-5ADE-4832-AC7B-871C4AE64210} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} - {1FCB2200-28B8-4703-8E89-73241AAED047} = {89608AA5-5ADE-4832-AC7B-871C4AE64210} - {B5CDF747-8066-40D5-9BAE-CBE397BC9B51} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {9B4F139F-7D26-435C-A561-89E65A67A8E5} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {6EF07978-A6D2-40EB-891D-7D70C5F37E76} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {DD089B8B-DA73-492A-9010-F772D1C178DA} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {5948B0A5-7873-4DBB-BA03-EB283D6EA91B} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {B08B4E00-C2AB-48F3-8389-449F42AEF179} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {AB797AF0-C12C-46DE-A157-7E25625C6200} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {58C59255-281C-4595-8732-808158F1DC6E} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {4D8F3BB2-709D-481A-8FD3-0DA9762B366D} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} - {A86DF0F0-24E9-470E-984B-9E7332544972} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} - {A702DD84-B55C-4807-9C28-D8F5AC3CCA78} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} - {3A377A6A-F735-4010-9E00-72E8BEB8F1F2} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} - {28818676-F6AF-4203-8B65-BD33A50CB9A2} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} - {DC9CCAD0-7363-4691-B964-FF5B3AEA3F95} = {18453B51-25EB-4317-A4B3-B10518252E92} - {1B794B9C-9B8A-4D5A-9848-B51274BD1FC5} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} - {4E7F15D1-729E-4DA7-992A-C3A2C6054B37} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} - {3095C95A-F1F9-487A-A983-1B100A33E4C7} = {5948B0A5-7873-4DBB-BA03-EB283D6EA91B} - {CF3DA5F1-F98D-49C9-A980-27E570B38B8D} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {8DBEC8F3-908D-46B3-95CA-47EA138AB1A4} = {CF3DA5F1-F98D-49C9-A980-27E570B38B8D} - {CC7DE119-736D-4FC5-85DC-70A6770749F6} = {CF3DA5F1-F98D-49C9-A980-27E570B38B8D} - {EFE4A814-F58D-4C19-8F7F-B95F4EE343F0} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} - {1C84408D-A0A7-4513-AD9A-1BA137ABE62C} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} - {E4300D97-1D53-4029-9435-483FB7EC9621} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} - {84AB9951-3DBF-43A4-8F5D-AEED1102CE88} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} - {1F621382-08FB-45EA-B94C-96A7FECB9489} = {CF3DA5F1-F98D-49C9-A980-27E570B38B8D} - {FBA14F8A-9F14-4364-ABDB-B8AE81A0D755} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} - {A71557F2-D5FB-4772-9391-F7D18A3B6C44} = {AB797AF0-C12C-46DE-A157-7E25625C6200} - {05F3B23E-CF28-467B-AD3C-595EA4ED6B96} = {AB797AF0-C12C-46DE-A157-7E25625C6200} - {74ACD9D0-8B4C-42FA-A582-E93BF0075023} = {AB797AF0-C12C-46DE-A157-7E25625C6200} - {CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D} = {AB797AF0-C12C-46DE-A157-7E25625C6200} - {49D8D2CF-AF68-40FA-951F-018247C97443} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} - {732BF088-6AD7-4C4D-9A48-8074253596D4} = {B818988E-639C-4E6E-85C1-B231BCAD9DAB} - {B818988E-639C-4E6E-85C1-B231BCAD9DAB} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {29638A67-E79F-44FE-AC05-DA499EBA929E} = {2F3E1026-5054-4E1F-899B-F1A7F70F9912} - {24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} - {790E94F2-5393-47DF-AC52-D9247F5B243A} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} - {169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6} = {DD089B8B-DA73-492A-9010-F772D1C178DA} - {AFEB799E-82C3-4D02-9D5C-766BB8DEF004} = {DD089B8B-DA73-492A-9010-F772D1C178DA} - {F50336DA-42D1-4DD1-A107-67AFEB8A33EE} = {1B8D5897-902E-4632-8698-E89CAF3DDF54} - {A516931E-EDBB-4FC3-BB94-1BB824D5BC61} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {BBCE36D1-6767-4ED1-B3E8-84D2567A962A} = {A516931E-EDBB-4FC3-BB94-1BB824D5BC61} - {73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {B0946844-DE0E-4C59-9391-C0EB649DBF4E} = {18453B51-25EB-4317-A4B3-B10518252E92} - {A1FE2CA0-8EF3-4EDB-9055-DD282E374E16} = {EB3A7401-0DE3-476F-9E6F-057F1F4590FB} - {EB3A7401-0DE3-476F-9E6F-057F1F4590FB} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {BF4CDE28-F0CC-482D-8392-C1C9BB4C368F} = {EB3A7401-0DE3-476F-9E6F-057F1F4590FB} - {7EB0CFB8-B668-4F3F-898D-80411095BC2F} = {EB3A7401-0DE3-476F-9E6F-057F1F4590FB} - {34BC9836-681D-43A5-BC39-E4FEE17FC528} = {08B41FFA-CEE3-46A7-B5C0-3EB65D37A16C} - {CBB515F3-A0EF-43B5-A907-FD4E652DD66E} = {90031D64-CA0F-46D0-9AF4-8DC023A5FFCD} - {90CD37A9-C866-4D90-A3B1-8C87F53B845E} = {CBB515F3-A0EF-43B5-A907-FD4E652DD66E} - {3F20899B-C5E1-485F-808B-ADB1877EB3FD} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {3D42FCA2-5BD0-4624-9FCA-F4BF935B27AE} = {3F20899B-C5E1-485F-808B-ADB1877EB3FD} - {56CAA9F2-1882-4EFA-BAC0-9C3D804553F1} = {18453B51-25EB-4317-A4B3-B10518252E92} - {0D4A88A1-8AEE-49ED-BC2E-ED68C96F60AD} = {58C59255-281C-4595-8732-808158F1DC6E} - {5CBCBEA4-6038-48CA-B45D-C1D0F6B4ACA4} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} - {94A61AD7-2A2B-40DB-81F3-C59D596958A8} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} - {4B598AF7-BD7D-4544-A274-2CDDD98F4167} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} - {99B171E6-0248-4402-836D-98947CD63772} = {1B8D5897-902E-4632-8698-E89CAF3DDF54} - {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D} = {1B8D5897-902E-4632-8698-E89CAF3DDF54} - {7F5A304F-7DD9-4A64-8FCD-9B1FF06735B9} = {08B41FFA-CEE3-46A7-B5C0-3EB65D37A16C} - {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} - {5ADDDFB1-E59B-4097-97B7-8C24E2D60463} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} - {97C7E531-9D5F-43FD-AA19-BF24DA13B612} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {65F2AD97-3ECF-4BA6-8AAA-1E5882FDCE68} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} - {690B0274-291F-4D9E-BA76-54EFF7D3E4BC} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} - {060FD0BA-BD78-48E1-A8A7-4906A5AD5E39} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} - {169A82A5-2DB3-40EA-801E-14C08D743DF7} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} - {01B96BB9-35E8-4364-ACB8-6D12A14D8DBA} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {47FBCB04-0C2D-453C-BE2F-7052CAC22524} = {EB3A7401-0DE3-476F-9E6F-057F1F4590FB} - {B32DB9B2-AD6C-48A5-8682-4373CB045185} = {C80C8231-D35C-4ACC-9ED6-9F3DB221535E} - {454652D5-E1BB-4D4B-9B21-9CEFC900C4FB} = {B32DB9B2-AD6C-48A5-8682-4373CB045185} - {31089E79-694B-4F45-97AF-86D34A7B231E} = {B32DB9B2-AD6C-48A5-8682-4373CB045185} - {6DDB9ECF-D454-4894-A262-A6BB3026E61E} = {B32DB9B2-AD6C-48A5-8682-4373CB045185} - {16B570BC-E293-4A19-B20E-5C97BAF8476B} = {B32DB9B2-AD6C-48A5-8682-4373CB045185} - {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {B0460856-7E6D-4B1E-B243-8E20662FC32E} = {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} - {50470834-4CD8-479A-8B58-0A1869BA5D37} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {5BD671F0-9603-4C2F-8364-54B02986EAA2} = {50470834-4CD8-479A-8B58-0A1869BA5D37} - {154525CD-3726-45B7-9A9C-553BBC60EB10} = {50470834-4CD8-479A-8B58-0A1869BA5D37} - {F620D7DC-B58D-4BD0-81A2-E88A2C1E4833} = {50470834-4CD8-479A-8B58-0A1869BA5D37} - {6FE60BB4-DE6A-485C-A38A-1317C56AF2DA} = {50470834-4CD8-479A-8B58-0A1869BA5D37} - {57326431-9C6C-4BAB-AA51-15131C21ADCC} = {C80C8231-D35C-4ACC-9ED6-9F3DB221535E} - {34FAB60D-7259-47B9-B50E-B533C7C3CFEC} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} - {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {19329441-2A59-4BAC-A1BE-BBAB70C93CA0} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} - {37D16EAF-ACD8-4105-8BD3-B2745819CFF8} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} - {CFEC357F-0863-4458-A6C4-5D7AA0E44759} = {50470834-4CD8-479A-8B58-0A1869BA5D37} - {E75762D2-FD12-4165-810B-628C28CB2562} = {50470834-4CD8-479A-8B58-0A1869BA5D37} - {3666CF13-1353-4620-9472-506670214A6A} = {C80C8231-D35C-4ACC-9ED6-9F3DB221535E} - {AA5ACF15-8647-4AC9-B9F5-73FCB92BE7E3} = {C80C8231-D35C-4ACC-9ED6-9F3DB221535E} - {DFFE6199-B5A7-4BA8-A5E1-77D0426C6468} = {B32DB9B2-AD6C-48A5-8682-4373CB045185} - {052C4F97-5909-4D88-AA49-4B0D9058A0A9} = {50470834-4CD8-479A-8B58-0A1869BA5D37} - {9A6D67DE-51B1-456D-B25D-19E0356C8F60} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} - {44A03CF6-4EB6-434C-90D1-FF8142FE394E} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} - {A9140976-DFF9-432A-B1DC-E188A07C49E5} = {50470834-4CD8-479A-8B58-0A1869BA5D37} - {B3046301-6F00-4885-8B01-080BD489055C} = {50470834-4CD8-479A-8B58-0A1869BA5D37} - {29D12ADC-55E9-40D0-9E4C-F0EBB6E098EC} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} - {6C606FEB-9A1F-4816-ABE4-22AFA8CEE771} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} - {5F80663C-B072-49DE-9A67-FD804BAD787E} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} - {630E2D93-7F7D-4A8A-BB52-D8095A1F24D9} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} - {59E7614A-3630-4C39-9C7F-9E12BC95D37E} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} - {2F3E1026-5054-4E1F-899B-F1A7F70F9912} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {D5720DBC-8C2B-42D5-9D9F-2FF6EAD4001C} = {2F3E1026-5054-4E1F-899B-F1A7F70F9912} - {2B939AC9-03A4-479E-AA0D-CB58F4A7F480} = {50470834-4CD8-479A-8B58-0A1869BA5D37} +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34003.232 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{61017E64-6D00-49CB-9E81-5002DC8F7D5F}" + ProjectSection(SolutionItems) = preProject + src\Directory.Build.props = src\Directory.Build.props + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{C6658DE0-2B2F-47F0-BB61-2CA66D435C09}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{7D21EB5E-D6B4-48A5-A9BC-A26387A65F0B}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitignore = .gitignore + Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props + icon.png = icon.png + NuGet.Config = NuGet.Config + README.md = README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{0354F050-3992-4DD4-B0EE-5FBA04AC72B6}" + ProjectSection(SolutionItems) = preProject + docs\bounty-footer.md = docs\bounty-footer.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Quartz", "src\modules\Elsa.Quartz\Elsa.Quartz.csproj", "{7D5A49B4-9A9B-496E-803B-DEB85B2C3132}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Http", "src\modules\Elsa.Http\Elsa.Http.csproj", "{82E26BF9-5F3A-4365-899A-AB1FFD54AA45}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Scheduling", "src\modules\Elsa.Scheduling\Elsa.Scheduling.csproj", "{ACD65CE5-3CC2-47B1-BFAC-72443D764F6E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.AzureServiceBus", "src\modules\Elsa.AzureServiceBus\Elsa.AzureServiceBus.csproj", "{24C7095B-5CDD-4369-9F34-5565A019B195}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.JavaScript", "src\modules\Elsa.JavaScript\Elsa.JavaScript.csproj", "{D31581AB-A6C1-4B73-AB63-45667F6C82AE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Hangfire", "src\modules\Elsa.Hangfire\Elsa.Hangfire.csproj", "{0601A2A6-2C62-418B-9104-8CDE497E5283}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.WorkflowContexts", "src\modules\Elsa.WorkflowContexts\Elsa.WorkflowContexts.csproj", "{302BFC43-ED2F-43AE-8AD4-FCD481B0AC67}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.MassTransit", "src\modules\Elsa.MassTransit\Elsa.MassTransit.csproj", "{BB983D0B-A939-4008-B9E3-C7C172BCF83A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{90031D64-CA0F-46D0-9AF4-8DC023A5FFCD}" + ProjectSection(SolutionItems) = preProject + test\Directory.Build.props = test\Directory.Build.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Labels", "src\modules\Elsa.Labels\Elsa.Labels.csproj", "{80FF5821-E831-450D-AA0C-C76D07D878F4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Liquid", "src\modules\Elsa.Liquid\Elsa.Liquid.csproj", "{8E5BD3D9-3529-48BC-B741-CFB7A69DB233}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Runtime.ProtoActor", "src\modules\Elsa.Workflows.Runtime.ProtoActor\Elsa.Workflows.Runtime.ProtoActor.csproj", "{B2049499-D384-46DF-8837-F1180107DD54}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Dsl", "src\modules\Elsa.Dsl\Elsa.Dsl.csproj", "{78DAB791-8595-4612-8E9E-2248AF5B1862}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Api", "src\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj", "{C9539BD8-D2AE-4A8D-8281-71A05B3FBF31}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Core", "src\modules\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj", "{169E2C9B-6687-427F-A278-30BF849BFEDC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Management", "src\modules\Elsa.Workflows.Management\Elsa.Workflows.Management.csproj", "{2DD5D66B-85E9-4AF9-911C-C9F963234159}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Runtime", "src\modules\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj", "{8A050229-DB79-4E0B-9AFF-7565E87F2954}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Features", "src\common\Elsa.Features\Elsa.Features.csproj", "{C237BA1A-3A7D-4AB2-BE09-2696F3C082A4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Expressions", "src\modules\Elsa.Expressions\Elsa.Expressions.csproj", "{80529478-A383-4FEA-B744-C71264969E9A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Api.Common", "src\common\Elsa.Api.Common\Elsa.Api.Common.csproj", "{39CD855E-83B1-4A96-93F7-01608211EBE3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Identity", "src\modules\Elsa.Identity\Elsa.Identity.csproj", "{30B49E42-AE61-4F7C-981F-923F07BB3986}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Common", "src\modules\Elsa.Common\Elsa.Common.csproj", "{D229105F-6879-4452-9189-75DE060C0F4C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore", "src\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj", "{7CC95512-7F44-45C3-A669-95B7048D4730}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Telnyx", "src\modules\Elsa.Telnyx\Elsa.Telnyx.csproj", "{F09B30F7-AF33-44D7-B835-099D1F72A37E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Email", "src\modules\Elsa.Email\Elsa.Email.csproj", "{1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{986E5482-0482-448C-B9E4-EC67A9474B85}" + ProjectSection(SolutionItems) = preProject + docker\.dockerignore = docker\.dockerignore + docker\docker-compose-datadog.yml = docker\docker-compose-datadog.yml + docker\docker-compose.yml = docker\docker-compose.yml + docker\ElsaServer-Datadog.Dockerfile = docker\ElsaServer-Datadog.Dockerfile + docker\ElsaServer.Dockerfile = docker\ElsaServer.Dockerfile + docker\ElsaServerAndStudio.Dockerfile = docker\ElsaServerAndStudio.Dockerfile + docker\ElsaStudio.Dockerfile = docker\ElsaStudio.Dockerfile + docker\otel-collector-config.yaml = docker\otel-collector-config.yaml + docker\init-db-postgres.sh = docker\init-db-postgres.sh + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Elasticsearch", "src\modules\Elsa.Elasticsearch\Elsa.Elasticsearch.csproj", "{3246883E-2FA7-4B4A-BDC5-99039A2869BC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Webhooks", "src\modules\Elsa.Webhooks\Elsa.Webhooks.csproj", "{2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unit", "unit", "{18453B51-25EB-4317-A4B3-B10518252E92}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integration", "integration", "{1B8D5897-902E-4632-8698-E89CAF3DDF54}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "component", "component", "{08B41FFA-CEE3-46A7-B5C0-3EB65D37A16C}" + ProjectSection(SolutionItems) = preProject + test\component\Directory.Build.props = test\component\Directory.Build.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.IntegrationTests", "test\integration\Elsa.Workflows.IntegrationTests\Elsa.Workflows.IntegrationTests.csproj", "{E9652738-2B3D-4357-B84B-54F0EA161382}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "clients", "clients", "{89608AA5-5ADE-4832-AC7B-871C4AE64210}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Api.Client", "src\clients\Elsa.Api.Client\Elsa.Api.Client.csproj", "{1FCB2200-28B8-4703-8E89-73241AAED047}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Environments", "src\modules\Elsa.Environments\Elsa.Environments.csproj", "{B5CDF747-8066-40D5-9BAE-CBE397BC9B51}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "persistence", "persistence", "{9B4F139F-7D26-435C-A561-89E65A67A8E5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripting", "scripting", "{6EF07978-A6D2-40EB-891D-7D70C5F37E76}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "servicebus", "servicebus", "{DD089B8B-DA73-492A-9010-F772D1C178DA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "security", "security", "{5948B0A5-7873-4DBB-BA03-EB283D6EA91B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{B08B4E00-C2AB-48F3-8389-449F42AEF179}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "timers", "timers", "{AB797AF0-C12C-46DE-A157-7E25625C6200}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "runtimes", "runtimes", "{58C59255-281C-4595-8732-808158F1DC6E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Dapper", "src\modules\Elsa.Dapper\Elsa.Dapper.csproj", "{4D8F3BB2-709D-481A-8FD3-0DA9762B366D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Dapper.Migrations", "src\modules\Elsa.Dapper.Migrations\Elsa.Dapper.Migrations.csproj", "{A86DF0F0-24E9-470E-984B-9E7332544972}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.WorkflowProviders.BlobStorage", "src\modules\Elsa.WorkflowProviders.BlobStorage\Elsa.WorkflowProviders.BlobStorage.csproj", "{A702DD84-B55C-4807-9C28-D8F5AC3CCA78}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.MongoDb", "src\modules\Elsa.MongoDb\Elsa.MongoDb.csproj", "{3A377A6A-F735-4010-9E00-72E8BEB8F1F2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Mediator", "src\common\Elsa.Mediator\Elsa.Mediator.csproj", "{28818676-F6AF-4203-8B65-BD33A50CB9A2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Core.UnitTests", "test\unit\Elsa.Workflows.Core.UnitTests\Elsa.Workflows.Core.UnitTests.csproj", "{DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.DropIns.Core", "src\common\Elsa.DropIns.Core\Elsa.DropIns.Core.csproj", "{1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.DropIns", "src\common\Elsa.DropIns\Elsa.DropIns.csproj", "{4E7F15D1-729E-4DA7-992A-C3A2C6054B37}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.SasTokens", "src\modules\Elsa.SasTokens\Elsa.SasTokens.csproj", "{3095C95A-F1F9-487A-A983-1B100A33E4C7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "alterations", "alterations", "{CF3DA5F1-F98D-49C9-A980-27E570B38B8D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Alterations.Core", "src\modules\Elsa.Alterations.Core\Elsa.Alterations.Core.csproj", "{8DBEC8F3-908D-46B3-95CA-47EA138AB1A4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Alterations", "src\modules\Elsa.Alterations\Elsa.Alterations.csproj", "{CC7DE119-736D-4FC5-85DC-70A6770749F6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.Sqlite", "src\modules\Elsa.EntityFrameworkCore.Sqlite\Elsa.EntityFrameworkCore.Sqlite.csproj", "{EFE4A814-F58D-4C19-8F7F-B95F4EE343F0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.SqlServer", "src\modules\Elsa.EntityFrameworkCore.SqlServer\Elsa.EntityFrameworkCore.SqlServer.csproj", "{1C84408D-A0A7-4513-AD9A-1BA137ABE62C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.MySql", "src\modules\Elsa.EntityFrameworkCore.MySql\Elsa.EntityFrameworkCore.MySql.csproj", "{E4300D97-1D53-4029-9435-483FB7EC9621}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.PostgreSql", "src\modules\Elsa.EntityFrameworkCore.PostgreSql\Elsa.EntityFrameworkCore.PostgreSql.csproj", "{84AB9951-3DBF-43A4-8F5D-AEED1102CE88}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Alterations.MassTransit", "src\modules\Elsa.Alterations.MassTransit\Elsa.Alterations.MassTransit.csproj", "{1F621382-08FB-45EA-B94C-96A7FECB9489}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.Common", "src\modules\Elsa.EntityFrameworkCore.Common\Elsa.EntityFrameworkCore.Common.csproj", "{FBA14F8A-9F14-4364-ABDB-B8AE81A0D755}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Quartz.EntityFrameworkCore.Sqlite", "src\modules\Elsa.Quartz.EntityFrameworkCore.Sqlite\Elsa.Quartz.EntityFrameworkCore.Sqlite.csproj", "{A71557F2-D5FB-4772-9391-F7D18A3B6C44}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Quartz.EntityFrameworkCore.SqlServer", "src\modules\Elsa.Quartz.EntityFrameworkCore.SqlServer\Elsa.Quartz.EntityFrameworkCore.SqlServer.csproj", "{05F3B23E-CF28-467B-AD3C-595EA4ED6B96}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Quartz.EntityFrameworkCore.MySql", "src\modules\Elsa.Quartz.EntityFrameworkCore.MySql\Elsa.Quartz.EntityFrameworkCore.MySql.csproj", "{74ACD9D0-8B4C-42FA-A582-E93BF0075023}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Quartz.EntityFrameworkCore.PostgreSql", "src\modules\Elsa.Quartz.EntityFrameworkCore.PostgreSql\Elsa.Quartz.EntityFrameworkCore.PostgreSql.csproj", "{CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore.Oracle", "src\modules\Elsa.EntityFrameworkCore.Oracle\Elsa.EntityFrameworkCore.Oracle.csproj", "{49D8D2CF-AF68-40FA-951F-018247C97443}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.FileStorage", "src\modules\Elsa.FileStorage\Elsa.FileStorage.csproj", "{732BF088-6AD7-4C4D-9A48-8074253596D4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "storage", "storage", "{B818988E-639C-4E6E-85C1-B231BCAD9DAB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Tenants", "src\modules\Elsa.Tenants\Elsa.Tenants.csproj", "{29638A67-E79F-44FE-AC05-DA499EBA929E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.CSharp", "src\modules\Elsa.CSharp\Elsa.CSharp.csproj", "{24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Python", "src\modules\Elsa.Python\Elsa.Python.csproj", "{790E94F2-5393-47DF-AC52-D9247F5B243A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.MassTransit.RabbitMq", "src\modules\Elsa.MassTransit.RabbitMq\Elsa.MassTransit.RabbitMq.csproj", "{169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pipelines", "pipelines", "{B789B743-0FBE-4BBC-9F45-84C20C8D4814}" + ProjectSection(SolutionItems) = preProject + .github\workflows\bounty.yml = .github\workflows\bounty.yml + .github\workflows\elsa-server-and-studio.yml = .github\workflows\elsa-server-and-studio.yml + .github\workflows\elsa-server.yml = .github\workflows\elsa-server.yml + .github\workflows\elsa-studio.yml = .github\workflows\elsa-studio.yml + .github\workflows\packages.yml = .github\workflows\packages.yml + .github\workflows\pr.yml = .github\workflows\pr.yml + .github\workflows\stale.yml = .github\workflows\stale.yml + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{99F2B1DA-2F69-4D70-A2A3-AC985AD91EC4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.MassTransit.AzureServiceBus", "src\modules\Elsa.MassTransit.AzureServiceBus\Elsa.MassTransit.AzureServiceBus.csproj", "{AFEB799E-82C3-4D02-9D5C-766BB8DEF004}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{C80C8231-D35C-4ACC-9ED6-9F3DB221535E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Alterations.IntegrationTests", "test\integration\Elsa.Alterations.IntegrationTests\Elsa.Alterations.IntegrationTests.csproj", "{F50336DA-42D1-4DD1-A107-67AFEB8A33EE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "hosting", "hosting", "{A516931E-EDBB-4FC3-BB94-1BB824D5BC61}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Hosting.Management", "src\modules\Elsa.Hosting.Management\Elsa.Hosting.Management.csproj", "{BBCE36D1-6767-4ED1-B3E8-84D2567A962A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.KeyValues", "src\modules\Elsa.KeyValues\Elsa.KeyValues.csproj", "{73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Runtime.UnitTests", "test\unit\Elsa.Workflows.Runtime.UnitTests\Elsa.Workflows.Runtime.UnitTests.csproj", "{B0946844-DE0E-4C59-9391-C0EB649DBF4E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Caching", "src\modules\Elsa.Caching\Elsa.Caching.csproj", "{A1FE2CA0-8EF3-4EDB-9055-DD282E374E16}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "caching", "caching", "{EB3A7401-0DE3-476F-9E6F-057F1F4590FB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Caching.Distributed.MassTransit", "src\modules\Elsa.Caching.Distributed.MassTransit\Elsa.Caching.Distributed.MassTransit.csproj", "{BF4CDE28-F0CC-482D-8392-C1C9BB4C368F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Caching.Distributed", "src\modules\Elsa.Caching.Distributed\Elsa.Caching.Distributed.csproj", "{7EB0CFB8-B668-4F3F-898D-80411095BC2F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.ComponentTests", "test\component\Elsa.Workflows.ComponentTests\Elsa.Workflows.ComponentTests.csproj", "{34BC9836-681D-43A5-BC39-E4FEE17FC528}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "performance", "performance", "{CBB515F3-A0EF-43B5-A907-FD4E652DD66E}" + ProjectSection(SolutionItems) = preProject + test\performance\Directory.Build.props = test\performance\Directory.Build.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.PerformanceTests", "test\performance\Elsa.Workflows.PerformanceTests\Elsa.Workflows.PerformanceTests.csproj", "{90CD37A9-C866-4D90-A3B1-8C87F53B845E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "retention", "retention", "{3F20899B-C5E1-485F-808B-ADB1877EB3FD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Retention", "src\modules\Elsa.Retention\Elsa.Retention.csproj", "{3D42FCA2-5BD0-4624-9FCA-F4BF935B27AE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.MongoDb.UnitTests", "test\unit\Elsa.MongoDb.UnitTests\Elsa.MongoDb.UnitTests.csproj", "{56CAA9F2-1882-4EFA-BAC0-9C3D804553F1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Workflows.Runtime.Distributed", "src\modules\Elsa.Workflows.Runtime.Distributed\Elsa.Workflows.Runtime.Distributed.csproj", "{0D4A88A1-8AEE-49ED-BC2E-ED68C96F60AD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Testing.Shared", "src\common\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj", "{5CBCBEA4-6038-48CA-B45D-C1D0F6B4ACA4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Testing.Shared.Integration", "src\common\Elsa.Testing.Shared.Integration\Elsa.Testing.Shared.Integration.csproj", "{94A61AD7-2A2B-40DB-81F3-C59D596958A8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Testing.Shared.Component", "src\common\Elsa.Testing.Shared.Component\Elsa.Testing.Shared.Component.csproj", "{4B598AF7-BD7D-4544-A274-2CDDD98F4167}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Activities.IntegrationTests", "test\integration\Elsa.Activities.IntegrationTests\Elsa.Activities.IntegrationTests.csproj", "{99B171E6-0248-4402-836D-98947CD63772}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.JavaScript.IntegrationTests", "test\integration\Elsa.JavaScript.IntegrationTests\Elsa.JavaScript.IntegrationTests.csproj", "{4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.AzureServiceBus.ComponentTests", "test\component\Elsa.AzureServiceBus.ComponentTests\Elsa.AzureServiceBus.ComponentTests.csproj", "{7F5A304F-7DD9-4A64-8FCD-9B1FF06735B9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "apps", "apps", "{D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1}" + ProjectSection(SolutionItems) = preProject + src\apps\Directory.Build.props = src\apps\Directory.Build.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Server.Web", "src\apps\Elsa.Server.Web\Elsa.Server.Web.csproj", "{5ADDDFB1-E59B-4097-97B7-8C24E2D60463}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa", "src\modules\Elsa\Elsa.csproj", "{97C7E531-9D5F-43FD-AA19-BF24DA13B612}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Server.LoadBalancer", "src\apps\Elsa.Server.LoadBalancer\Elsa.Server.LoadBalancer.csproj", "{65F2AD97-3ECF-4BA6-8AAA-1E5882FDCE68}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElsaStudioWebAssembly", "src\apps\ElsaStudioWebAssembly\ElsaStudioWebAssembly.csproj", "{690B0274-291F-4D9E-BA76-54EFF7D3E4BC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.ServerAndStudio.Web", "src\apps\Elsa.ServerAndStudio.Web\Elsa.ServerAndStudio.Web.csproj", "{060FD0BA-BD78-48E1-A8A7-4906A5AD5E39}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Studio.Web", "src\apps\Elsa.Studio.Web\Elsa.Studio.Web.csproj", "{169A82A5-2DB3-40EA-801E-14C08D743DF7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.OpenTelemetry", "src\modules\Elsa.OpenTelemetry\Elsa.OpenTelemetry.csproj", "{2CDF3E1C-267D-4198-B1C7-7E1F548FC120}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.ProtoActor.Core", "src\modules\Elsa.ProtoActor.Core\Elsa.ProtoActor.Core.csproj", "{01B96BB9-35E8-4364-ACB8-6D12A14D8DBA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Caching.Distributed.ProtoActor", "src\modules\Elsa.Caching.Distributed.ProtoActor\Elsa.Caching.Distributed.ProtoActor.csproj", "{47FBCB04-0C2D-453C-BE2F-7052CAC22524}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "k8s", "k8s", "{B32DB9B2-AD6C-48A5-8682-4373CB045185}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "elsa-server", "elsa-server", "{454652D5-E1BB-4D4B-9B21-9CEFC900C4FB}" + ProjectSection(SolutionItems) = preProject + scripts\k8s\elsa-server\deployment.yaml = scripts\k8s\elsa-server\deployment.yaml + scripts\k8s\elsa-server\role-binding.yaml = scripts\k8s\elsa-server\role-binding.yaml + scripts\k8s\elsa-server\role.yaml = scripts\k8s\elsa-server\role.yaml + scripts\k8s\elsa-server\service-account.yml = scripts\k8s\elsa-server\service-account.yml + scripts\k8s\elsa-server\service.yaml = scripts\k8s\elsa-server\service.yaml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "postgres", "postgres", "{31089E79-694B-4F45-97AF-86D34A7B231E}" + ProjectSection(SolutionItems) = preProject + scripts\k8s\postgres\config-map.yaml = scripts\k8s\postgres\config-map.yaml + scripts\k8s\postgres\deployment.yaml = scripts\k8s\postgres\deployment.yaml + scripts\k8s\postgres\service.yml = scripts\k8s\postgres\service.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plant-uml", "plant-uml", "{6DDB9ECF-D454-4894-A262-A6BB3026E61E}" + ProjectSection(SolutionItems) = preProject + scripts\k8s\plant-uml\deployment.yaml = scripts\k8s\plant-uml\deployment.yaml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "trace-lens", "trace-lens", "{16B570BC-E293-4A19-B20E-5C97BAF8476B}" + ProjectSection(SolutionItems) = preProject + scripts\k8s\trace-lens\deployment.yaml = scripts\k8s\trace-lens\deployment.yaml + scripts\k8s\trace-lens\service.yaml = scripts\k8s\trace-lens\service.yaml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integrations", "integrations", "{EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.OrchardCore", "src\modules\Elsa.OrchardCore\Elsa.OrchardCore.csproj", "{B0460856-7E6D-4B1E-B243-8E20662FC32E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "agents", "agents", "{50470834-4CD8-479A-8B58-0A1869BA5D37}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Core", "src\modules\Elsa.Agents.Core\Elsa.Agents.Core.csproj", "{5BD671F0-9603-4C2F-8364-54B02986EAA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Api", "src\modules\Elsa.Agents.Api\Elsa.Agents.Api.csproj", "{154525CD-3726-45B7-9A9C-553BBC60EB10}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Activities", "src\modules\Elsa.Agents.Activities\Elsa.Agents.Activities.csproj", "{F620D7DC-B58D-4BD0-81A2-E88A2C1E4833}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Persistence.EntityFrameworkCore", "src\modules\Elsa.Agents.Persistence.EntityFrameworkCore\Elsa.Agents.Persistence.EntityFrameworkCore.csproj", "{6FE60BB4-DE6A-485C-A38A-1317C56AF2DA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuke", "nuke", "{57326431-9C6C-4BAB-AA51-15131C21ADCC}" + ProjectSection(SolutionItems) = preProject + scripts\nuke\build.cmd = scripts\nuke\build.cmd + scripts\nuke\build.ps1 = scripts\nuke\build.ps1 + scripts\nuke\build.sh = scripts\nuke\build.sh + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Core", "src\modules\Elsa.Secrets.Core\Elsa.Secrets.Core.csproj", "{34FAB60D-7259-47B9-B50E-B533C7C3CFEC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "secrets", "secrets", "{8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Management", "src\modules\Elsa.Secrets.Management\Elsa.Secrets.Management.csproj", "{19329441-2A59-4BAC-A1BE-BBAB70C93CA0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Api", "src\modules\Elsa.Secrets.Api\Elsa.Secrets.Api.csproj", "{37D16EAF-ACD8-4105-8BD3-B2745819CFF8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Persistence", "src\modules\Elsa.Agents.Persistence\Elsa.Agents.Persistence.csproj", "{CFEC357F-0863-4458-A6C4-5D7AA0E44759}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Persistence.EntityFrameworkCore.Sqlite", "src\modules\Elsa.Agents.Persistence.EntityFrameworkCore.Sqlite\Elsa.Agents.Persistence.EntityFrameworkCore.Sqlite.csproj", "{E75762D2-FD12-4165-810B-628C28CB2562}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "migrations", "migrations", "{3666CF13-1353-4620-9472-506670214A6A}" + ProjectSection(SolutionItems) = preProject + scripts\migrations\efcore-3.0.sh = scripts\migrations\efcore-3.0.sh + scripts\migrations\efcore-3.1-sql.sh = scripts\migrations\efcore-3.1-sql.sh + scripts\migrations\efcore-3.1.sh = scripts\migrations\efcore-3.1.sh + scripts\migrations\efcore-3.2.sh = scripts\migrations\efcore-3.2.sh + scripts\migrations\efcore-3.3.sh = scripts\migrations\efcore-3.3.sh + scripts\migrations\README.md = scripts\migrations\README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{AA5ACF15-8647-4AC9-B9F5-73FCB92BE7E3}" + ProjectSection(SolutionItems) = preProject + scripts\docker\build-and-run-all-in-one-web-docker.sh = scripts\docker\build-and-run-all-in-one-web-docker.sh + scripts\docker\docker-compose.yml = scripts\docker\docker-compose.yml + scripts\docker\docker-run-all-in-one-web.ps1 = scripts\docker\docker-run-all-in-one-web.ps1 + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "elsa-studio", "elsa-studio", "{DFFE6199-B5A7-4BA8-A5E1-77D0426C6468}" + ProjectSection(SolutionItems) = preProject + scripts\k8s\elsa-studio\deployment.yaml = scripts\k8s\elsa-studio\deployment.yaml + scripts\k8s\elsa-studio\service.yaml = scripts\k8s\elsa-studio\service.yaml + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Models", "src\modules\Elsa.Agents.Models\Elsa.Agents.Models.csproj", "{052C4F97-5909-4D88-AA49-4B0D9058A0A9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Persistence.EntityFrameworkCore", "src\modules\Elsa.Secrets.Persistence.EntityFrameworkCore\Elsa.Secrets.Persistence.EntityFrameworkCore.csproj", "{9A6D67DE-51B1-456D-B25D-19E0356C8F60}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite", "src\modules\Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite\Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite.csproj", "{44A03CF6-4EB6-434C-90D1-FF8142FE394E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Persistence.EntityFrameworkCore.SqlServer", "src\modules\Elsa.Agents.Persistence.EntityFrameworkCore.SqlServer\Elsa.Agents.Persistence.EntityFrameworkCore.SqlServer.csproj", "{A9140976-DFF9-432A-B1DC-E188A07C49E5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Agents.Persistence.EntityFrameworkCore.MySql", "src\modules\Elsa.Agents.Persistence.EntityFrameworkCore.MySql\Elsa.Agents.Persistence.EntityFrameworkCore.MySql.csproj", "{B3046301-6F00-4885-8B01-080BD489055C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Models", "src\modules\Elsa.Secrets.Models\Elsa.Secrets.Models.csproj", "{29D12ADC-55E9-40D0-9E4C-F0EBB6E098EC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Scripting", "src\modules\Elsa.Secrets.Scripting\Elsa.Secrets.Scripting.csproj", "{6C606FEB-9A1F-4816-ABE4-22AFA8CEE771}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.JavaScript.Libraries", "src\modules\Elsa.JavaScript.Libraries\Elsa.JavaScript.Libraries.csproj", "{5F80663C-B072-49DE-9A67-FD804BAD787E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql", "src\modules\Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql\Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql.csproj", "{630E2D93-7F7D-4A8A-BB52-D8095A1F24D9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer", "src\modules\Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer\Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer.csproj", "{59E7614A-3630-4C39-9C7F-9E12BC95D37E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "multitenancy", "multitenancy", "{2F3E1026-5054-4E1F-899B-F1A7F70F9912}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Tenants.AspNetCore", "src\modules\Elsa.Tenants.AspNetCore\Elsa.Tenants.AspNetCore.csproj", "{D5720DBC-8C2B-42D5-9D9F-2FF6EAD4001C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Agents.Persistence.EntityFrameworkCore.PostgreSql", "src\modules\Elsa.Agents.Persistence.EntityFrameworkCore.PostgreSql\Elsa.Agents.Persistence.EntityFrameworkCore.PostgreSql.csproj", "{2B939AC9-03A4-479E-AA0D-CB58F4A7F480}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Kafka", "src\modules\Elsa.Kafka\Elsa.Kafka.csproj", "{BF934627-F531-44FB-BEC2-ECA801FF31E7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sql", "sql", "{A0DC5F8E-5D7F-4E8A-A5DF-B1FC31F7336E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Sql", "src\modules\Elsa.Sql\Elsa.Sql.csproj", "{FD3CD5A8-E9B3-467F-90EB-2B7D5B83F348}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Sql.MySql", "src\modules\Elsa.Sql.MySql\Elsa.Sql.MySql.csproj", "{3BED411B-79B5-4CCC-BD46-9549A427B908}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Sql.PostgreSql", "src\modules\Elsa.Sql.PostgreSql\Elsa.Sql.PostgreSql.csproj", "{6CC5FBC7-D3D7-4FE3-AD08-C67939BDB24D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Sql.Sqlite", "src\modules\Elsa.Sql.Sqlite\Elsa.Sql.Sqlite.csproj", "{FA5E857F-B173-4B5D-8049-B817A210DEF5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Sql.SqlServer", "src\modules\Elsa.Sql.SqlServer\Elsa.Sql.SqlServer.csproj", "{A51F9683-DA9F-45E7-82DE-1E261ACD6D68}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "oracle-setup", "oracle-setup", "{66E2E2CF-967F-4564-89E8-F46FA973C99B}" + ProjectSection(SolutionItems) = preProject + docker\oracle-setup\setup.sql = docker\oracle-setup\setup.sql + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7D5A49B4-9A9B-496E-803B-DEB85B2C3132}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D5A49B4-9A9B-496E-803B-DEB85B2C3132}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D5A49B4-9A9B-496E-803B-DEB85B2C3132}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D5A49B4-9A9B-496E-803B-DEB85B2C3132}.Release|Any CPU.Build.0 = Release|Any CPU + {82E26BF9-5F3A-4365-899A-AB1FFD54AA45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {82E26BF9-5F3A-4365-899A-AB1FFD54AA45}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82E26BF9-5F3A-4365-899A-AB1FFD54AA45}.Release|Any CPU.ActiveCfg = Release|Any CPU + {82E26BF9-5F3A-4365-899A-AB1FFD54AA45}.Release|Any CPU.Build.0 = Release|Any CPU + {ACD65CE5-3CC2-47B1-BFAC-72443D764F6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACD65CE5-3CC2-47B1-BFAC-72443D764F6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACD65CE5-3CC2-47B1-BFAC-72443D764F6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACD65CE5-3CC2-47B1-BFAC-72443D764F6E}.Release|Any CPU.Build.0 = Release|Any CPU + {24C7095B-5CDD-4369-9F34-5565A019B195}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24C7095B-5CDD-4369-9F34-5565A019B195}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24C7095B-5CDD-4369-9F34-5565A019B195}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24C7095B-5CDD-4369-9F34-5565A019B195}.Release|Any CPU.Build.0 = Release|Any CPU + {D31581AB-A6C1-4B73-AB63-45667F6C82AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D31581AB-A6C1-4B73-AB63-45667F6C82AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D31581AB-A6C1-4B73-AB63-45667F6C82AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D31581AB-A6C1-4B73-AB63-45667F6C82AE}.Release|Any CPU.Build.0 = Release|Any CPU + {0601A2A6-2C62-418B-9104-8CDE497E5283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0601A2A6-2C62-418B-9104-8CDE497E5283}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0601A2A6-2C62-418B-9104-8CDE497E5283}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0601A2A6-2C62-418B-9104-8CDE497E5283}.Release|Any CPU.Build.0 = Release|Any CPU + {302BFC43-ED2F-43AE-8AD4-FCD481B0AC67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {302BFC43-ED2F-43AE-8AD4-FCD481B0AC67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {302BFC43-ED2F-43AE-8AD4-FCD481B0AC67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {302BFC43-ED2F-43AE-8AD4-FCD481B0AC67}.Release|Any CPU.Build.0 = Release|Any CPU + {BB983D0B-A939-4008-B9E3-C7C172BCF83A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB983D0B-A939-4008-B9E3-C7C172BCF83A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB983D0B-A939-4008-B9E3-C7C172BCF83A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB983D0B-A939-4008-B9E3-C7C172BCF83A}.Release|Any CPU.Build.0 = Release|Any CPU + {80FF5821-E831-450D-AA0C-C76D07D878F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80FF5821-E831-450D-AA0C-C76D07D878F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80FF5821-E831-450D-AA0C-C76D07D878F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80FF5821-E831-450D-AA0C-C76D07D878F4}.Release|Any CPU.Build.0 = Release|Any CPU + {8E5BD3D9-3529-48BC-B741-CFB7A69DB233}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E5BD3D9-3529-48BC-B741-CFB7A69DB233}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E5BD3D9-3529-48BC-B741-CFB7A69DB233}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E5BD3D9-3529-48BC-B741-CFB7A69DB233}.Release|Any CPU.Build.0 = Release|Any CPU + {B2049499-D384-46DF-8837-F1180107DD54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2049499-D384-46DF-8837-F1180107DD54}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2049499-D384-46DF-8837-F1180107DD54}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2049499-D384-46DF-8837-F1180107DD54}.Release|Any CPU.Build.0 = Release|Any CPU + {78DAB791-8595-4612-8E9E-2248AF5B1862}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78DAB791-8595-4612-8E9E-2248AF5B1862}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78DAB791-8595-4612-8E9E-2248AF5B1862}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78DAB791-8595-4612-8E9E-2248AF5B1862}.Release|Any CPU.Build.0 = Release|Any CPU + {C9539BD8-D2AE-4A8D-8281-71A05B3FBF31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9539BD8-D2AE-4A8D-8281-71A05B3FBF31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9539BD8-D2AE-4A8D-8281-71A05B3FBF31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9539BD8-D2AE-4A8D-8281-71A05B3FBF31}.Release|Any CPU.Build.0 = Release|Any CPU + {169E2C9B-6687-427F-A278-30BF849BFEDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {169E2C9B-6687-427F-A278-30BF849BFEDC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {169E2C9B-6687-427F-A278-30BF849BFEDC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {169E2C9B-6687-427F-A278-30BF849BFEDC}.Release|Any CPU.Build.0 = Release|Any CPU + {2DD5D66B-85E9-4AF9-911C-C9F963234159}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DD5D66B-85E9-4AF9-911C-C9F963234159}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DD5D66B-85E9-4AF9-911C-C9F963234159}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DD5D66B-85E9-4AF9-911C-C9F963234159}.Release|Any CPU.Build.0 = Release|Any CPU + {8A050229-DB79-4E0B-9AFF-7565E87F2954}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A050229-DB79-4E0B-9AFF-7565E87F2954}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A050229-DB79-4E0B-9AFF-7565E87F2954}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A050229-DB79-4E0B-9AFF-7565E87F2954}.Release|Any CPU.Build.0 = Release|Any CPU + {C237BA1A-3A7D-4AB2-BE09-2696F3C082A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C237BA1A-3A7D-4AB2-BE09-2696F3C082A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C237BA1A-3A7D-4AB2-BE09-2696F3C082A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C237BA1A-3A7D-4AB2-BE09-2696F3C082A4}.Release|Any CPU.Build.0 = Release|Any CPU + {80529478-A383-4FEA-B744-C71264969E9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80529478-A383-4FEA-B744-C71264969E9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80529478-A383-4FEA-B744-C71264969E9A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80529478-A383-4FEA-B744-C71264969E9A}.Release|Any CPU.Build.0 = Release|Any CPU + {39CD855E-83B1-4A96-93F7-01608211EBE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39CD855E-83B1-4A96-93F7-01608211EBE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39CD855E-83B1-4A96-93F7-01608211EBE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39CD855E-83B1-4A96-93F7-01608211EBE3}.Release|Any CPU.Build.0 = Release|Any CPU + {30B49E42-AE61-4F7C-981F-923F07BB3986}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {30B49E42-AE61-4F7C-981F-923F07BB3986}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30B49E42-AE61-4F7C-981F-923F07BB3986}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30B49E42-AE61-4F7C-981F-923F07BB3986}.Release|Any CPU.Build.0 = Release|Any CPU + {D229105F-6879-4452-9189-75DE060C0F4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D229105F-6879-4452-9189-75DE060C0F4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D229105F-6879-4452-9189-75DE060C0F4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D229105F-6879-4452-9189-75DE060C0F4C}.Release|Any CPU.Build.0 = Release|Any CPU + {7CC95512-7F44-45C3-A669-95B7048D4730}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CC95512-7F44-45C3-A669-95B7048D4730}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CC95512-7F44-45C3-A669-95B7048D4730}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CC95512-7F44-45C3-A669-95B7048D4730}.Release|Any CPU.Build.0 = Release|Any CPU + {F09B30F7-AF33-44D7-B835-099D1F72A37E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F09B30F7-AF33-44D7-B835-099D1F72A37E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F09B30F7-AF33-44D7-B835-099D1F72A37E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F09B30F7-AF33-44D7-B835-099D1F72A37E}.Release|Any CPU.Build.0 = Release|Any CPU + {1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Release|Any CPU.Build.0 = Release|Any CPU + {3246883E-2FA7-4B4A-BDC5-99039A2869BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3246883E-2FA7-4B4A-BDC5-99039A2869BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3246883E-2FA7-4B4A-BDC5-99039A2869BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3246883E-2FA7-4B4A-BDC5-99039A2869BC}.Release|Any CPU.Build.0 = Release|Any CPU + {2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2BBFDE36-28A7-4875-8EBE-CC25C76B97FF}.Release|Any CPU.Build.0 = Release|Any CPU + {E9652738-2B3D-4357-B84B-54F0EA161382}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9652738-2B3D-4357-B84B-54F0EA161382}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9652738-2B3D-4357-B84B-54F0EA161382}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9652738-2B3D-4357-B84B-54F0EA161382}.Release|Any CPU.Build.0 = Release|Any CPU + {1FCB2200-28B8-4703-8E89-73241AAED047}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FCB2200-28B8-4703-8E89-73241AAED047}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FCB2200-28B8-4703-8E89-73241AAED047}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FCB2200-28B8-4703-8E89-73241AAED047}.Release|Any CPU.Build.0 = Release|Any CPU + {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5CDF747-8066-40D5-9BAE-CBE397BC9B51}.Release|Any CPU.Build.0 = Release|Any CPU + {4D8F3BB2-709D-481A-8FD3-0DA9762B366D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D8F3BB2-709D-481A-8FD3-0DA9762B366D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D8F3BB2-709D-481A-8FD3-0DA9762B366D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D8F3BB2-709D-481A-8FD3-0DA9762B366D}.Release|Any CPU.Build.0 = Release|Any CPU + {A86DF0F0-24E9-470E-984B-9E7332544972}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A86DF0F0-24E9-470E-984B-9E7332544972}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A86DF0F0-24E9-470E-984B-9E7332544972}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A86DF0F0-24E9-470E-984B-9E7332544972}.Release|Any CPU.Build.0 = Release|Any CPU + {A702DD84-B55C-4807-9C28-D8F5AC3CCA78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A702DD84-B55C-4807-9C28-D8F5AC3CCA78}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A702DD84-B55C-4807-9C28-D8F5AC3CCA78}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A702DD84-B55C-4807-9C28-D8F5AC3CCA78}.Release|Any CPU.Build.0 = Release|Any CPU + {3A377A6A-F735-4010-9E00-72E8BEB8F1F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A377A6A-F735-4010-9E00-72E8BEB8F1F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A377A6A-F735-4010-9E00-72E8BEB8F1F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A377A6A-F735-4010-9E00-72E8BEB8F1F2}.Release|Any CPU.Build.0 = Release|Any CPU + {28818676-F6AF-4203-8B65-BD33A50CB9A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28818676-F6AF-4203-8B65-BD33A50CB9A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28818676-F6AF-4203-8B65-BD33A50CB9A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28818676-F6AF-4203-8B65-BD33A50CB9A2}.Release|Any CPU.Build.0 = Release|Any CPU + {DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC9CCAD0-7363-4691-B964-FF5B3AEA3F95}.Release|Any CPU.Build.0 = Release|Any CPU + {1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B794B9C-9B8A-4D5A-9848-B51274BD1FC5}.Release|Any CPU.Build.0 = Release|Any CPU + {4E7F15D1-729E-4DA7-992A-C3A2C6054B37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E7F15D1-729E-4DA7-992A-C3A2C6054B37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E7F15D1-729E-4DA7-992A-C3A2C6054B37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E7F15D1-729E-4DA7-992A-C3A2C6054B37}.Release|Any CPU.Build.0 = Release|Any CPU + {3095C95A-F1F9-487A-A983-1B100A33E4C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3095C95A-F1F9-487A-A983-1B100A33E4C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3095C95A-F1F9-487A-A983-1B100A33E4C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3095C95A-F1F9-487A-A983-1B100A33E4C7}.Release|Any CPU.Build.0 = Release|Any CPU + {8DBEC8F3-908D-46B3-95CA-47EA138AB1A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DBEC8F3-908D-46B3-95CA-47EA138AB1A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DBEC8F3-908D-46B3-95CA-47EA138AB1A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DBEC8F3-908D-46B3-95CA-47EA138AB1A4}.Release|Any CPU.Build.0 = Release|Any CPU + {CC7DE119-736D-4FC5-85DC-70A6770749F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC7DE119-736D-4FC5-85DC-70A6770749F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC7DE119-736D-4FC5-85DC-70A6770749F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC7DE119-736D-4FC5-85DC-70A6770749F6}.Release|Any CPU.Build.0 = Release|Any CPU + {EFE4A814-F58D-4C19-8F7F-B95F4EE343F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EFE4A814-F58D-4C19-8F7F-B95F4EE343F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EFE4A814-F58D-4C19-8F7F-B95F4EE343F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EFE4A814-F58D-4C19-8F7F-B95F4EE343F0}.Release|Any CPU.Build.0 = Release|Any CPU + {1C84408D-A0A7-4513-AD9A-1BA137ABE62C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C84408D-A0A7-4513-AD9A-1BA137ABE62C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C84408D-A0A7-4513-AD9A-1BA137ABE62C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C84408D-A0A7-4513-AD9A-1BA137ABE62C}.Release|Any CPU.Build.0 = Release|Any CPU + {E4300D97-1D53-4029-9435-483FB7EC9621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4300D97-1D53-4029-9435-483FB7EC9621}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4300D97-1D53-4029-9435-483FB7EC9621}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4300D97-1D53-4029-9435-483FB7EC9621}.Release|Any CPU.Build.0 = Release|Any CPU + {84AB9951-3DBF-43A4-8F5D-AEED1102CE88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84AB9951-3DBF-43A4-8F5D-AEED1102CE88}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84AB9951-3DBF-43A4-8F5D-AEED1102CE88}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84AB9951-3DBF-43A4-8F5D-AEED1102CE88}.Release|Any CPU.Build.0 = Release|Any CPU + {1F621382-08FB-45EA-B94C-96A7FECB9489}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F621382-08FB-45EA-B94C-96A7FECB9489}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F621382-08FB-45EA-B94C-96A7FECB9489}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F621382-08FB-45EA-B94C-96A7FECB9489}.Release|Any CPU.Build.0 = Release|Any CPU + {FBA14F8A-9F14-4364-ABDB-B8AE81A0D755}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBA14F8A-9F14-4364-ABDB-B8AE81A0D755}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBA14F8A-9F14-4364-ABDB-B8AE81A0D755}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBA14F8A-9F14-4364-ABDB-B8AE81A0D755}.Release|Any CPU.Build.0 = Release|Any CPU + {A71557F2-D5FB-4772-9391-F7D18A3B6C44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A71557F2-D5FB-4772-9391-F7D18A3B6C44}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A71557F2-D5FB-4772-9391-F7D18A3B6C44}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A71557F2-D5FB-4772-9391-F7D18A3B6C44}.Release|Any CPU.Build.0 = Release|Any CPU + {05F3B23E-CF28-467B-AD3C-595EA4ED6B96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05F3B23E-CF28-467B-AD3C-595EA4ED6B96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05F3B23E-CF28-467B-AD3C-595EA4ED6B96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05F3B23E-CF28-467B-AD3C-595EA4ED6B96}.Release|Any CPU.Build.0 = Release|Any CPU + {74ACD9D0-8B4C-42FA-A582-E93BF0075023}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74ACD9D0-8B4C-42FA-A582-E93BF0075023}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74ACD9D0-8B4C-42FA-A582-E93BF0075023}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74ACD9D0-8B4C-42FA-A582-E93BF0075023}.Release|Any CPU.Build.0 = Release|Any CPU + {CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D}.Release|Any CPU.Build.0 = Release|Any CPU + {49D8D2CF-AF68-40FA-951F-018247C97443}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {49D8D2CF-AF68-40FA-951F-018247C97443}.Debug|Any CPU.Build.0 = Debug|Any CPU + {49D8D2CF-AF68-40FA-951F-018247C97443}.Release|Any CPU.ActiveCfg = Release|Any CPU + {49D8D2CF-AF68-40FA-951F-018247C97443}.Release|Any CPU.Build.0 = Release|Any CPU + {732BF088-6AD7-4C4D-9A48-8074253596D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {732BF088-6AD7-4C4D-9A48-8074253596D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {732BF088-6AD7-4C4D-9A48-8074253596D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {732BF088-6AD7-4C4D-9A48-8074253596D4}.Release|Any CPU.Build.0 = Release|Any CPU + {29638A67-E79F-44FE-AC05-DA499EBA929E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29638A67-E79F-44FE-AC05-DA499EBA929E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29638A67-E79F-44FE-AC05-DA499EBA929E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29638A67-E79F-44FE-AC05-DA499EBA929E}.Release|Any CPU.Build.0 = Release|Any CPU + {24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC}.Release|Any CPU.Build.0 = Release|Any CPU + {790E94F2-5393-47DF-AC52-D9247F5B243A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {790E94F2-5393-47DF-AC52-D9247F5B243A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {790E94F2-5393-47DF-AC52-D9247F5B243A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {790E94F2-5393-47DF-AC52-D9247F5B243A}.Release|Any CPU.Build.0 = Release|Any CPU + {169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6}.Release|Any CPU.Build.0 = Release|Any CPU + {99F2B1DA-2F69-4D70-A2A3-AC985AD91EC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99F2B1DA-2F69-4D70-A2A3-AC985AD91EC4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFEB799E-82C3-4D02-9D5C-766BB8DEF004}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFEB799E-82C3-4D02-9D5C-766BB8DEF004}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFEB799E-82C3-4D02-9D5C-766BB8DEF004}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFEB799E-82C3-4D02-9D5C-766BB8DEF004}.Release|Any CPU.Build.0 = Release|Any CPU + {F50336DA-42D1-4DD1-A107-67AFEB8A33EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F50336DA-42D1-4DD1-A107-67AFEB8A33EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F50336DA-42D1-4DD1-A107-67AFEB8A33EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F50336DA-42D1-4DD1-A107-67AFEB8A33EE}.Release|Any CPU.Build.0 = Release|Any CPU + {BBCE36D1-6767-4ED1-B3E8-84D2567A962A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BBCE36D1-6767-4ED1-B3E8-84D2567A962A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BBCE36D1-6767-4ED1-B3E8-84D2567A962A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BBCE36D1-6767-4ED1-B3E8-84D2567A962A}.Release|Any CPU.Build.0 = Release|Any CPU + {73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3}.Release|Any CPU.Build.0 = Release|Any CPU + {B0946844-DE0E-4C59-9391-C0EB649DBF4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0946844-DE0E-4C59-9391-C0EB649DBF4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0946844-DE0E-4C59-9391-C0EB649DBF4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0946844-DE0E-4C59-9391-C0EB649DBF4E}.Release|Any CPU.Build.0 = Release|Any CPU + {A1FE2CA0-8EF3-4EDB-9055-DD282E374E16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1FE2CA0-8EF3-4EDB-9055-DD282E374E16}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1FE2CA0-8EF3-4EDB-9055-DD282E374E16}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1FE2CA0-8EF3-4EDB-9055-DD282E374E16}.Release|Any CPU.Build.0 = Release|Any CPU + {BF4CDE28-F0CC-482D-8392-C1C9BB4C368F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF4CDE28-F0CC-482D-8392-C1C9BB4C368F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF4CDE28-F0CC-482D-8392-C1C9BB4C368F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF4CDE28-F0CC-482D-8392-C1C9BB4C368F}.Release|Any CPU.Build.0 = Release|Any CPU + {7EB0CFB8-B668-4F3F-898D-80411095BC2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EB0CFB8-B668-4F3F-898D-80411095BC2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EB0CFB8-B668-4F3F-898D-80411095BC2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EB0CFB8-B668-4F3F-898D-80411095BC2F}.Release|Any CPU.Build.0 = Release|Any CPU + {34BC9836-681D-43A5-BC39-E4FEE17FC528}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34BC9836-681D-43A5-BC39-E4FEE17FC528}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34BC9836-681D-43A5-BC39-E4FEE17FC528}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34BC9836-681D-43A5-BC39-E4FEE17FC528}.Release|Any CPU.Build.0 = Release|Any CPU + {90CD37A9-C866-4D90-A3B1-8C87F53B845E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90CD37A9-C866-4D90-A3B1-8C87F53B845E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90CD37A9-C866-4D90-A3B1-8C87F53B845E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90CD37A9-C866-4D90-A3B1-8C87F53B845E}.Release|Any CPU.Build.0 = Release|Any CPU + {3D42FCA2-5BD0-4624-9FCA-F4BF935B27AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D42FCA2-5BD0-4624-9FCA-F4BF935B27AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D42FCA2-5BD0-4624-9FCA-F4BF935B27AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D42FCA2-5BD0-4624-9FCA-F4BF935B27AE}.Release|Any CPU.Build.0 = Release|Any CPU + {56CAA9F2-1882-4EFA-BAC0-9C3D804553F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56CAA9F2-1882-4EFA-BAC0-9C3D804553F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56CAA9F2-1882-4EFA-BAC0-9C3D804553F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56CAA9F2-1882-4EFA-BAC0-9C3D804553F1}.Release|Any CPU.Build.0 = Release|Any CPU + {0D4A88A1-8AEE-49ED-BC2E-ED68C96F60AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D4A88A1-8AEE-49ED-BC2E-ED68C96F60AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D4A88A1-8AEE-49ED-BC2E-ED68C96F60AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D4A88A1-8AEE-49ED-BC2E-ED68C96F60AD}.Release|Any CPU.Build.0 = Release|Any CPU + {5CBCBEA4-6038-48CA-B45D-C1D0F6B4ACA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5CBCBEA4-6038-48CA-B45D-C1D0F6B4ACA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5CBCBEA4-6038-48CA-B45D-C1D0F6B4ACA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5CBCBEA4-6038-48CA-B45D-C1D0F6B4ACA4}.Release|Any CPU.Build.0 = Release|Any CPU + {94A61AD7-2A2B-40DB-81F3-C59D596958A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94A61AD7-2A2B-40DB-81F3-C59D596958A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94A61AD7-2A2B-40DB-81F3-C59D596958A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94A61AD7-2A2B-40DB-81F3-C59D596958A8}.Release|Any CPU.Build.0 = Release|Any CPU + {4B598AF7-BD7D-4544-A274-2CDDD98F4167}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B598AF7-BD7D-4544-A274-2CDDD98F4167}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B598AF7-BD7D-4544-A274-2CDDD98F4167}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B598AF7-BD7D-4544-A274-2CDDD98F4167}.Release|Any CPU.Build.0 = Release|Any CPU + {99B171E6-0248-4402-836D-98947CD63772}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99B171E6-0248-4402-836D-98947CD63772}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99B171E6-0248-4402-836D-98947CD63772}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99B171E6-0248-4402-836D-98947CD63772}.Release|Any CPU.Build.0 = Release|Any CPU + {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D}.Release|Any CPU.Build.0 = Release|Any CPU + {7F5A304F-7DD9-4A64-8FCD-9B1FF06735B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F5A304F-7DD9-4A64-8FCD-9B1FF06735B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F5A304F-7DD9-4A64-8FCD-9B1FF06735B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F5A304F-7DD9-4A64-8FCD-9B1FF06735B9}.Release|Any CPU.Build.0 = Release|Any CPU + {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5ADDDFB1-E59B-4097-97B7-8C24E2D60463}.Release|Any CPU.Build.0 = Release|Any CPU + {97C7E531-9D5F-43FD-AA19-BF24DA13B612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97C7E531-9D5F-43FD-AA19-BF24DA13B612}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97C7E531-9D5F-43FD-AA19-BF24DA13B612}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97C7E531-9D5F-43FD-AA19-BF24DA13B612}.Release|Any CPU.Build.0 = Release|Any CPU + {65F2AD97-3ECF-4BA6-8AAA-1E5882FDCE68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65F2AD97-3ECF-4BA6-8AAA-1E5882FDCE68}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65F2AD97-3ECF-4BA6-8AAA-1E5882FDCE68}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65F2AD97-3ECF-4BA6-8AAA-1E5882FDCE68}.Release|Any CPU.Build.0 = Release|Any CPU + {690B0274-291F-4D9E-BA76-54EFF7D3E4BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {690B0274-291F-4D9E-BA76-54EFF7D3E4BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {690B0274-291F-4D9E-BA76-54EFF7D3E4BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {690B0274-291F-4D9E-BA76-54EFF7D3E4BC}.Release|Any CPU.Build.0 = Release|Any CPU + {060FD0BA-BD78-48E1-A8A7-4906A5AD5E39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {060FD0BA-BD78-48E1-A8A7-4906A5AD5E39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {060FD0BA-BD78-48E1-A8A7-4906A5AD5E39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {060FD0BA-BD78-48E1-A8A7-4906A5AD5E39}.Release|Any CPU.Build.0 = Release|Any CPU + {169A82A5-2DB3-40EA-801E-14C08D743DF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {169A82A5-2DB3-40EA-801E-14C08D743DF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {169A82A5-2DB3-40EA-801E-14C08D743DF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {169A82A5-2DB3-40EA-801E-14C08D743DF7}.Release|Any CPU.Build.0 = Release|Any CPU + {2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CDF3E1C-267D-4198-B1C7-7E1F548FC120}.Release|Any CPU.Build.0 = Release|Any CPU + {01B96BB9-35E8-4364-ACB8-6D12A14D8DBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01B96BB9-35E8-4364-ACB8-6D12A14D8DBA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01B96BB9-35E8-4364-ACB8-6D12A14D8DBA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01B96BB9-35E8-4364-ACB8-6D12A14D8DBA}.Release|Any CPU.Build.0 = Release|Any CPU + {47FBCB04-0C2D-453C-BE2F-7052CAC22524}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47FBCB04-0C2D-453C-BE2F-7052CAC22524}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47FBCB04-0C2D-453C-BE2F-7052CAC22524}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47FBCB04-0C2D-453C-BE2F-7052CAC22524}.Release|Any CPU.Build.0 = Release|Any CPU + {B0460856-7E6D-4B1E-B243-8E20662FC32E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0460856-7E6D-4B1E-B243-8E20662FC32E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0460856-7E6D-4B1E-B243-8E20662FC32E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0460856-7E6D-4B1E-B243-8E20662FC32E}.Release|Any CPU.Build.0 = Release|Any CPU + {5BD671F0-9603-4C2F-8364-54B02986EAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BD671F0-9603-4C2F-8364-54B02986EAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5BD671F0-9603-4C2F-8364-54B02986EAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5BD671F0-9603-4C2F-8364-54B02986EAA2}.Release|Any CPU.Build.0 = Release|Any CPU + {154525CD-3726-45B7-9A9C-553BBC60EB10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {154525CD-3726-45B7-9A9C-553BBC60EB10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {154525CD-3726-45B7-9A9C-553BBC60EB10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {154525CD-3726-45B7-9A9C-553BBC60EB10}.Release|Any CPU.Build.0 = Release|Any CPU + {F620D7DC-B58D-4BD0-81A2-E88A2C1E4833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F620D7DC-B58D-4BD0-81A2-E88A2C1E4833}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F620D7DC-B58D-4BD0-81A2-E88A2C1E4833}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F620D7DC-B58D-4BD0-81A2-E88A2C1E4833}.Release|Any CPU.Build.0 = Release|Any CPU + {6FE60BB4-DE6A-485C-A38A-1317C56AF2DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6FE60BB4-DE6A-485C-A38A-1317C56AF2DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FE60BB4-DE6A-485C-A38A-1317C56AF2DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6FE60BB4-DE6A-485C-A38A-1317C56AF2DA}.Release|Any CPU.Build.0 = Release|Any CPU + {34FAB60D-7259-47B9-B50E-B533C7C3CFEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34FAB60D-7259-47B9-B50E-B533C7C3CFEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34FAB60D-7259-47B9-B50E-B533C7C3CFEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34FAB60D-7259-47B9-B50E-B533C7C3CFEC}.Release|Any CPU.Build.0 = Release|Any CPU + {19329441-2A59-4BAC-A1BE-BBAB70C93CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19329441-2A59-4BAC-A1BE-BBAB70C93CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19329441-2A59-4BAC-A1BE-BBAB70C93CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19329441-2A59-4BAC-A1BE-BBAB70C93CA0}.Release|Any CPU.Build.0 = Release|Any CPU + {37D16EAF-ACD8-4105-8BD3-B2745819CFF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37D16EAF-ACD8-4105-8BD3-B2745819CFF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37D16EAF-ACD8-4105-8BD3-B2745819CFF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37D16EAF-ACD8-4105-8BD3-B2745819CFF8}.Release|Any CPU.Build.0 = Release|Any CPU + {CFEC357F-0863-4458-A6C4-5D7AA0E44759}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFEC357F-0863-4458-A6C4-5D7AA0E44759}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFEC357F-0863-4458-A6C4-5D7AA0E44759}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFEC357F-0863-4458-A6C4-5D7AA0E44759}.Release|Any CPU.Build.0 = Release|Any CPU + {E75762D2-FD12-4165-810B-628C28CB2562}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E75762D2-FD12-4165-810B-628C28CB2562}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E75762D2-FD12-4165-810B-628C28CB2562}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E75762D2-FD12-4165-810B-628C28CB2562}.Release|Any CPU.Build.0 = Release|Any CPU + {052C4F97-5909-4D88-AA49-4B0D9058A0A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {052C4F97-5909-4D88-AA49-4B0D9058A0A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {052C4F97-5909-4D88-AA49-4B0D9058A0A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {052C4F97-5909-4D88-AA49-4B0D9058A0A9}.Release|Any CPU.Build.0 = Release|Any CPU + {9A6D67DE-51B1-456D-B25D-19E0356C8F60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A6D67DE-51B1-456D-B25D-19E0356C8F60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A6D67DE-51B1-456D-B25D-19E0356C8F60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A6D67DE-51B1-456D-B25D-19E0356C8F60}.Release|Any CPU.Build.0 = Release|Any CPU + {44A03CF6-4EB6-434C-90D1-FF8142FE394E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44A03CF6-4EB6-434C-90D1-FF8142FE394E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44A03CF6-4EB6-434C-90D1-FF8142FE394E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44A03CF6-4EB6-434C-90D1-FF8142FE394E}.Release|Any CPU.Build.0 = Release|Any CPU + {A9140976-DFF9-432A-B1DC-E188A07C49E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9140976-DFF9-432A-B1DC-E188A07C49E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9140976-DFF9-432A-B1DC-E188A07C49E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9140976-DFF9-432A-B1DC-E188A07C49E5}.Release|Any CPU.Build.0 = Release|Any CPU + {B3046301-6F00-4885-8B01-080BD489055C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B3046301-6F00-4885-8B01-080BD489055C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B3046301-6F00-4885-8B01-080BD489055C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B3046301-6F00-4885-8B01-080BD489055C}.Release|Any CPU.Build.0 = Release|Any CPU + {29D12ADC-55E9-40D0-9E4C-F0EBB6E098EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29D12ADC-55E9-40D0-9E4C-F0EBB6E098EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29D12ADC-55E9-40D0-9E4C-F0EBB6E098EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29D12ADC-55E9-40D0-9E4C-F0EBB6E098EC}.Release|Any CPU.Build.0 = Release|Any CPU + {6C606FEB-9A1F-4816-ABE4-22AFA8CEE771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C606FEB-9A1F-4816-ABE4-22AFA8CEE771}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C606FEB-9A1F-4816-ABE4-22AFA8CEE771}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C606FEB-9A1F-4816-ABE4-22AFA8CEE771}.Release|Any CPU.Build.0 = Release|Any CPU + {5F80663C-B072-49DE-9A67-FD804BAD787E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F80663C-B072-49DE-9A67-FD804BAD787E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F80663C-B072-49DE-9A67-FD804BAD787E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F80663C-B072-49DE-9A67-FD804BAD787E}.Release|Any CPU.Build.0 = Release|Any CPU + {630E2D93-7F7D-4A8A-BB52-D8095A1F24D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {630E2D93-7F7D-4A8A-BB52-D8095A1F24D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {630E2D93-7F7D-4A8A-BB52-D8095A1F24D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {630E2D93-7F7D-4A8A-BB52-D8095A1F24D9}.Release|Any CPU.Build.0 = Release|Any CPU + {59E7614A-3630-4C39-9C7F-9E12BC95D37E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59E7614A-3630-4C39-9C7F-9E12BC95D37E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59E7614A-3630-4C39-9C7F-9E12BC95D37E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59E7614A-3630-4C39-9C7F-9E12BC95D37E}.Release|Any CPU.Build.0 = Release|Any CPU + {D5720DBC-8C2B-42D5-9D9F-2FF6EAD4001C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5720DBC-8C2B-42D5-9D9F-2FF6EAD4001C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5720DBC-8C2B-42D5-9D9F-2FF6EAD4001C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5720DBC-8C2B-42D5-9D9F-2FF6EAD4001C}.Release|Any CPU.Build.0 = Release|Any CPU + {2B939AC9-03A4-479E-AA0D-CB58F4A7F480}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B939AC9-03A4-479E-AA0D-CB58F4A7F480}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B939AC9-03A4-479E-AA0D-CB58F4A7F480}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B939AC9-03A4-479E-AA0D-CB58F4A7F480}.Release|Any CPU.Build.0 = Release|Any CPU + {BF934627-F531-44FB-BEC2-ECA801FF31E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF934627-F531-44FB-BEC2-ECA801FF31E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF934627-F531-44FB-BEC2-ECA801FF31E7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF934627-F531-44FB-BEC2-ECA801FF31E7}.Release|Any CPU.Build.0 = Release|Any CPU + {FD3CD5A8-E9B3-467F-90EB-2B7D5B83F348}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD3CD5A8-E9B3-467F-90EB-2B7D5B83F348}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD3CD5A8-E9B3-467F-90EB-2B7D5B83F348}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD3CD5A8-E9B3-467F-90EB-2B7D5B83F348}.Release|Any CPU.Build.0 = Release|Any CPU + {3BED411B-79B5-4CCC-BD46-9549A427B908}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3BED411B-79B5-4CCC-BD46-9549A427B908}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3BED411B-79B5-4CCC-BD46-9549A427B908}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3BED411B-79B5-4CCC-BD46-9549A427B908}.Release|Any CPU.Build.0 = Release|Any CPU + {6CC5FBC7-D3D7-4FE3-AD08-C67939BDB24D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CC5FBC7-D3D7-4FE3-AD08-C67939BDB24D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CC5FBC7-D3D7-4FE3-AD08-C67939BDB24D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CC5FBC7-D3D7-4FE3-AD08-C67939BDB24D}.Release|Any CPU.Build.0 = Release|Any CPU + {FA5E857F-B173-4B5D-8049-B817A210DEF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA5E857F-B173-4B5D-8049-B817A210DEF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA5E857F-B173-4B5D-8049-B817A210DEF5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA5E857F-B173-4B5D-8049-B817A210DEF5}.Release|Any CPU.Build.0 = Release|Any CPU + {A51F9683-DA9F-45E7-82DE-1E261ACD6D68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A51F9683-DA9F-45E7-82DE-1E261ACD6D68}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A51F9683-DA9F-45E7-82DE-1E261ACD6D68}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A51F9683-DA9F-45E7-82DE-1E261ACD6D68}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {7D5A49B4-9A9B-496E-803B-DEB85B2C3132} = {AB797AF0-C12C-46DE-A157-7E25625C6200} + {82E26BF9-5F3A-4365-899A-AB1FFD54AA45} = {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} + {ACD65CE5-3CC2-47B1-BFAC-72443D764F6E} = {AB797AF0-C12C-46DE-A157-7E25625C6200} + {24C7095B-5CDD-4369-9F34-5565A019B195} = {DD089B8B-DA73-492A-9010-F772D1C178DA} + {D31581AB-A6C1-4B73-AB63-45667F6C82AE} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} + {0601A2A6-2C62-418B-9104-8CDE497E5283} = {AB797AF0-C12C-46DE-A157-7E25625C6200} + {302BFC43-ED2F-43AE-8AD4-FCD481B0AC67} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} + {BB983D0B-A939-4008-B9E3-C7C172BCF83A} = {DD089B8B-DA73-492A-9010-F772D1C178DA} + {80FF5821-E831-450D-AA0C-C76D07D878F4} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {8E5BD3D9-3529-48BC-B741-CFB7A69DB233} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} + {B2049499-D384-46DF-8837-F1180107DD54} = {58C59255-281C-4595-8732-808158F1DC6E} + {78DAB791-8595-4612-8E9E-2248AF5B1862} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} + {C9539BD8-D2AE-4A8D-8281-71A05B3FBF31} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} + {169E2C9B-6687-427F-A278-30BF849BFEDC} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} + {2DD5D66B-85E9-4AF9-911C-C9F963234159} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} + {8A050229-DB79-4E0B-9AFF-7565E87F2954} = {58C59255-281C-4595-8732-808158F1DC6E} + {C237BA1A-3A7D-4AB2-BE09-2696F3C082A4} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + {80529478-A383-4FEA-B744-C71264969E9A} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} + {39CD855E-83B1-4A96-93F7-01608211EBE3} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + {30B49E42-AE61-4F7C-981F-923F07BB3986} = {5948B0A5-7873-4DBB-BA03-EB283D6EA91B} + {D229105F-6879-4452-9189-75DE060C0F4C} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {7CC95512-7F44-45C3-A669-95B7048D4730} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} + {F09B30F7-AF33-44D7-B835-099D1F72A37E} = {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} + {1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF} = {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} + {3246883E-2FA7-4B4A-BDC5-99039A2869BC} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} + {2BBFDE36-28A7-4875-8EBE-CC25C76B97FF} = {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} + {18453B51-25EB-4317-A4B3-B10518252E92} = {90031D64-CA0F-46D0-9AF4-8DC023A5FFCD} + {1B8D5897-902E-4632-8698-E89CAF3DDF54} = {90031D64-CA0F-46D0-9AF4-8DC023A5FFCD} + {08B41FFA-CEE3-46A7-B5C0-3EB65D37A16C} = {90031D64-CA0F-46D0-9AF4-8DC023A5FFCD} + {E9652738-2B3D-4357-B84B-54F0EA161382} = {1B8D5897-902E-4632-8698-E89CAF3DDF54} + {89608AA5-5ADE-4832-AC7B-871C4AE64210} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {1FCB2200-28B8-4703-8E89-73241AAED047} = {89608AA5-5ADE-4832-AC7B-871C4AE64210} + {B5CDF747-8066-40D5-9BAE-CBE397BC9B51} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {9B4F139F-7D26-435C-A561-89E65A67A8E5} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {6EF07978-A6D2-40EB-891D-7D70C5F37E76} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {DD089B8B-DA73-492A-9010-F772D1C178DA} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {5948B0A5-7873-4DBB-BA03-EB283D6EA91B} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {B08B4E00-C2AB-48F3-8389-449F42AEF179} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {AB797AF0-C12C-46DE-A157-7E25625C6200} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {58C59255-281C-4595-8732-808158F1DC6E} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {4D8F3BB2-709D-481A-8FD3-0DA9762B366D} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} + {A86DF0F0-24E9-470E-984B-9E7332544972} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} + {A702DD84-B55C-4807-9C28-D8F5AC3CCA78} = {B08B4E00-C2AB-48F3-8389-449F42AEF179} + {3A377A6A-F735-4010-9E00-72E8BEB8F1F2} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} + {28818676-F6AF-4203-8B65-BD33A50CB9A2} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + {DC9CCAD0-7363-4691-B964-FF5B3AEA3F95} = {18453B51-25EB-4317-A4B3-B10518252E92} + {1B794B9C-9B8A-4D5A-9848-B51274BD1FC5} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + {4E7F15D1-729E-4DA7-992A-C3A2C6054B37} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + {3095C95A-F1F9-487A-A983-1B100A33E4C7} = {5948B0A5-7873-4DBB-BA03-EB283D6EA91B} + {CF3DA5F1-F98D-49C9-A980-27E570B38B8D} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {8DBEC8F3-908D-46B3-95CA-47EA138AB1A4} = {CF3DA5F1-F98D-49C9-A980-27E570B38B8D} + {CC7DE119-736D-4FC5-85DC-70A6770749F6} = {CF3DA5F1-F98D-49C9-A980-27E570B38B8D} + {EFE4A814-F58D-4C19-8F7F-B95F4EE343F0} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} + {1C84408D-A0A7-4513-AD9A-1BA137ABE62C} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} + {E4300D97-1D53-4029-9435-483FB7EC9621} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} + {84AB9951-3DBF-43A4-8F5D-AEED1102CE88} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} + {1F621382-08FB-45EA-B94C-96A7FECB9489} = {CF3DA5F1-F98D-49C9-A980-27E570B38B8D} + {FBA14F8A-9F14-4364-ABDB-B8AE81A0D755} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} + {A71557F2-D5FB-4772-9391-F7D18A3B6C44} = {AB797AF0-C12C-46DE-A157-7E25625C6200} + {05F3B23E-CF28-467B-AD3C-595EA4ED6B96} = {AB797AF0-C12C-46DE-A157-7E25625C6200} + {74ACD9D0-8B4C-42FA-A582-E93BF0075023} = {AB797AF0-C12C-46DE-A157-7E25625C6200} + {CCCCEF8C-7D96-4BEA-B9D0-E91EDF08E65D} = {AB797AF0-C12C-46DE-A157-7E25625C6200} + {49D8D2CF-AF68-40FA-951F-018247C97443} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} + {732BF088-6AD7-4C4D-9A48-8074253596D4} = {B818988E-639C-4E6E-85C1-B231BCAD9DAB} + {B818988E-639C-4E6E-85C1-B231BCAD9DAB} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {29638A67-E79F-44FE-AC05-DA499EBA929E} = {2F3E1026-5054-4E1F-899B-F1A7F70F9912} + {24331E82-D7AF-45B1-ACF0-CA6C3B0B77DC} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} + {790E94F2-5393-47DF-AC52-D9247F5B243A} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} + {169BEA3D-2A81-47EE-A6C1-3F8719EEC1F6} = {DD089B8B-DA73-492A-9010-F772D1C178DA} + {AFEB799E-82C3-4D02-9D5C-766BB8DEF004} = {DD089B8B-DA73-492A-9010-F772D1C178DA} + {F50336DA-42D1-4DD1-A107-67AFEB8A33EE} = {1B8D5897-902E-4632-8698-E89CAF3DDF54} + {A516931E-EDBB-4FC3-BB94-1BB824D5BC61} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {BBCE36D1-6767-4ED1-B3E8-84D2567A962A} = {A516931E-EDBB-4FC3-BB94-1BB824D5BC61} + {73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {B0946844-DE0E-4C59-9391-C0EB649DBF4E} = {18453B51-25EB-4317-A4B3-B10518252E92} + {A1FE2CA0-8EF3-4EDB-9055-DD282E374E16} = {EB3A7401-0DE3-476F-9E6F-057F1F4590FB} + {EB3A7401-0DE3-476F-9E6F-057F1F4590FB} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {BF4CDE28-F0CC-482D-8392-C1C9BB4C368F} = {EB3A7401-0DE3-476F-9E6F-057F1F4590FB} + {7EB0CFB8-B668-4F3F-898D-80411095BC2F} = {EB3A7401-0DE3-476F-9E6F-057F1F4590FB} + {34BC9836-681D-43A5-BC39-E4FEE17FC528} = {08B41FFA-CEE3-46A7-B5C0-3EB65D37A16C} + {CBB515F3-A0EF-43B5-A907-FD4E652DD66E} = {90031D64-CA0F-46D0-9AF4-8DC023A5FFCD} + {90CD37A9-C866-4D90-A3B1-8C87F53B845E} = {CBB515F3-A0EF-43B5-A907-FD4E652DD66E} + {3F20899B-C5E1-485F-808B-ADB1877EB3FD} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {3D42FCA2-5BD0-4624-9FCA-F4BF935B27AE} = {3F20899B-C5E1-485F-808B-ADB1877EB3FD} + {56CAA9F2-1882-4EFA-BAC0-9C3D804553F1} = {18453B51-25EB-4317-A4B3-B10518252E92} + {0D4A88A1-8AEE-49ED-BC2E-ED68C96F60AD} = {58C59255-281C-4595-8732-808158F1DC6E} + {5CBCBEA4-6038-48CA-B45D-C1D0F6B4ACA4} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + {94A61AD7-2A2B-40DB-81F3-C59D596958A8} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + {4B598AF7-BD7D-4544-A274-2CDDD98F4167} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09} + {99B171E6-0248-4402-836D-98947CD63772} = {1B8D5897-902E-4632-8698-E89CAF3DDF54} + {4332A6BC-434A-4AF5-A075-F1BBCDD28F5D} = {1B8D5897-902E-4632-8698-E89CAF3DDF54} + {7F5A304F-7DD9-4A64-8FCD-9B1FF06735B9} = {08B41FFA-CEE3-46A7-B5C0-3EB65D37A16C} + {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F} + {5ADDDFB1-E59B-4097-97B7-8C24E2D60463} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} + {97C7E531-9D5F-43FD-AA19-BF24DA13B612} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {65F2AD97-3ECF-4BA6-8AAA-1E5882FDCE68} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} + {690B0274-291F-4D9E-BA76-54EFF7D3E4BC} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} + {060FD0BA-BD78-48E1-A8A7-4906A5AD5E39} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} + {169A82A5-2DB3-40EA-801E-14C08D743DF7} = {D92BEAB2-60D6-4BB4-885A-6BA681C6CCF1} {2CDF3E1C-267D-4198-B1C7-7E1F548FC120} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {BF934627-F531-44FB-BEC2-ECA801FF31E7} = {DD089B8B-DA73-492A-9010-F772D1C178DA} - {FFC2EE94-ABD1-4D22-A325-A53F80CC097E} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} - {7F620A88-E1CE-4D73-A21A-DE5A5B538BA8} = {FFC2EE94-ABD1-4D22-A325-A53F80CC097E} - {FD22E2B0-E164-4881-9DD4-4E429D178FB9} = {FFC2EE94-ABD1-4D22-A325-A53F80CC097E} - {9C2764B2-8BA4-4184-9B75-C426A593C815} = {FFC2EE94-ABD1-4D22-A325-A53F80CC097E} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E} - EndGlobalSection -EndGlobal + {01B96BB9-35E8-4364-ACB8-6D12A14D8DBA} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {47FBCB04-0C2D-453C-BE2F-7052CAC22524} = {EB3A7401-0DE3-476F-9E6F-057F1F4590FB} + {B32DB9B2-AD6C-48A5-8682-4373CB045185} = {C80C8231-D35C-4ACC-9ED6-9F3DB221535E} + {454652D5-E1BB-4D4B-9B21-9CEFC900C4FB} = {B32DB9B2-AD6C-48A5-8682-4373CB045185} + {31089E79-694B-4F45-97AF-86D34A7B231E} = {B32DB9B2-AD6C-48A5-8682-4373CB045185} + {6DDB9ECF-D454-4894-A262-A6BB3026E61E} = {B32DB9B2-AD6C-48A5-8682-4373CB045185} + {16B570BC-E293-4A19-B20E-5C97BAF8476B} = {B32DB9B2-AD6C-48A5-8682-4373CB045185} + {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {B0460856-7E6D-4B1E-B243-8E20662FC32E} = {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} + {50470834-4CD8-479A-8B58-0A1869BA5D37} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {5BD671F0-9603-4C2F-8364-54B02986EAA2} = {50470834-4CD8-479A-8B58-0A1869BA5D37} + {154525CD-3726-45B7-9A9C-553BBC60EB10} = {50470834-4CD8-479A-8B58-0A1869BA5D37} + {F620D7DC-B58D-4BD0-81A2-E88A2C1E4833} = {50470834-4CD8-479A-8B58-0A1869BA5D37} + {6FE60BB4-DE6A-485C-A38A-1317C56AF2DA} = {50470834-4CD8-479A-8B58-0A1869BA5D37} + {57326431-9C6C-4BAB-AA51-15131C21ADCC} = {C80C8231-D35C-4ACC-9ED6-9F3DB221535E} + {34FAB60D-7259-47B9-B50E-B533C7C3CFEC} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} + {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {19329441-2A59-4BAC-A1BE-BBAB70C93CA0} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} + {37D16EAF-ACD8-4105-8BD3-B2745819CFF8} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} + {CFEC357F-0863-4458-A6C4-5D7AA0E44759} = {50470834-4CD8-479A-8B58-0A1869BA5D37} + {E75762D2-FD12-4165-810B-628C28CB2562} = {50470834-4CD8-479A-8B58-0A1869BA5D37} + {3666CF13-1353-4620-9472-506670214A6A} = {C80C8231-D35C-4ACC-9ED6-9F3DB221535E} + {AA5ACF15-8647-4AC9-B9F5-73FCB92BE7E3} = {C80C8231-D35C-4ACC-9ED6-9F3DB221535E} + {DFFE6199-B5A7-4BA8-A5E1-77D0426C6468} = {B32DB9B2-AD6C-48A5-8682-4373CB045185} + {052C4F97-5909-4D88-AA49-4B0D9058A0A9} = {50470834-4CD8-479A-8B58-0A1869BA5D37} + {9A6D67DE-51B1-456D-B25D-19E0356C8F60} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} + {44A03CF6-4EB6-434C-90D1-FF8142FE394E} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} + {A9140976-DFF9-432A-B1DC-E188A07C49E5} = {50470834-4CD8-479A-8B58-0A1869BA5D37} + {B3046301-6F00-4885-8B01-080BD489055C} = {50470834-4CD8-479A-8B58-0A1869BA5D37} + {29D12ADC-55E9-40D0-9E4C-F0EBB6E098EC} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} + {6C606FEB-9A1F-4816-ABE4-22AFA8CEE771} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} + {5F80663C-B072-49DE-9A67-FD804BAD787E} = {6EF07978-A6D2-40EB-891D-7D70C5F37E76} + {630E2D93-7F7D-4A8A-BB52-D8095A1F24D9} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} + {59E7614A-3630-4C39-9C7F-9E12BC95D37E} = {8CEEC194-820A-4C8D-AB9E-E51E6D3E9CC1} + {2F3E1026-5054-4E1F-899B-F1A7F70F9912} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {D5720DBC-8C2B-42D5-9D9F-2FF6EAD4001C} = {2F3E1026-5054-4E1F-899B-F1A7F70F9912} + {2B939AC9-03A4-479E-AA0D-CB58F4A7F480} = {50470834-4CD8-479A-8B58-0A1869BA5D37} + {BF934627-F531-44FB-BEC2-ECA801FF31E7} = {DD089B8B-DA73-492A-9010-F772D1C178DA} + {A0DC5F8E-5D7F-4E8A-A5DF-B1FC31F7336E} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} + {FD3CD5A8-E9B3-467F-90EB-2B7D5B83F348} = {A0DC5F8E-5D7F-4E8A-A5DF-B1FC31F7336E} + {3BED411B-79B5-4CCC-BD46-9549A427B908} = {A0DC5F8E-5D7F-4E8A-A5DF-B1FC31F7336E} + {6CC5FBC7-D3D7-4FE3-AD08-C67939BDB24D} = {A0DC5F8E-5D7F-4E8A-A5DF-B1FC31F7336E} + {FA5E857F-B173-4B5D-8049-B817A210DEF5} = {A0DC5F8E-5D7F-4E8A-A5DF-B1FC31F7336E} + {A51F9683-DA9F-45E7-82DE-1E261ACD6D68} = {A0DC5F8E-5D7F-4E8A-A5DF-B1FC31F7336E} + {66E2E2CF-967F-4564-89E8-F46FA973C99B} = {986E5482-0482-448C-B9E4-EC67A9474B85} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md index 383c3a8aa..eefa67ecd 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Stack Overflow questions](https://img.shields.io/badge/stackoverflow-elsa_workflows-orange.svg)]( http://stackoverflow.com/questions/tagged/elsa-workflows ) [![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Elsa%20Guru-006BFF)](https://gurubase.io/g/elsa) -### [For Elsa 2 Click Here](https://github.com/elsa-workflows/elsa-core/tree/2.x) +### [For Elsa 2, Click Here](https://github.com/elsa-workflows/elsa-core/tree/2.x) ## Introduction Elsa is a powerful workflow library that enables workflow execution within any .NET application. Elsa allows you to define workflows in various ways, including: @@ -55,16 +55,13 @@ By default, you can access http://localhost:13000 and log in with: ## Documentation -For comprehensive documentation and to get started with Elsa, please visit the [Elsa Documentation Website](https://v3.elsaworkflows.io/). +[Elsa Documentation Website](https://docs.elsaworkflows.io/). ## Known Issues and Limitations Elsa is continually evolving, and while it offers powerful capabilities, there are some known limitations and ongoing work: - Documentation is still a work in progress. -- The designer is not yet fully embeddable in other applications; this feature is planned for a future release. -- C# and Python expressions are not yet fully tested. -- Bulk Dispatch Workflows is a new activity and not yet fully tested. - Input/Output is not yet implemented in the Workflow Instance Viewer. - Starting workflows from the designer is currently supported only for workflows that do not require input and do not start with a trigger; this is planned for a future release. - The designer currently only supports Flowchart activities. Support for Sequence and StateMachine activities is planned for a future release. @@ -90,15 +87,7 @@ Elsa offers a wide range of features for building and executing workflows, inclu ## Roadmap -The following features are planned for future releases of Elsa: - -- [ ] Multi-tenancy -- [ ] State Machine activity -- [ ] Designer support for Sequence activity & StateMachine activity -- [ ] BPMN 2.0 support -- [ ] DMN support -- [ ] Workflow migration to new versions via UI -- [ ] Capsules ("hot" deployable workflow packages containing activities and configuration) +See #3232 ## Use Cases @@ -109,7 +98,7 @@ Elsa can be used in a variety of scenarios, including: - Scheduled workflows such as sending daily reports. - Event-driven workflows such as sending welcome emails when a user signs up. -## Programmatic Workflows +## Coding Workflows Elsa allows you to define workflows in code using C#. The following example shows how to receive HTTP requests and send an email in response: @@ -141,13 +130,12 @@ public class SendEmailWorkflow : WorkflowBase } ``` -## Designed Workflows +## Designing Workflows Elsa allows you to define workflows using a visual designer. The following example shows how to receive HTTP requests and send an email in response: ![Elsa ships with a powerful visual designer](./design/screenshots/http-send-email-workflow-designer.png) - ## Contributing We welcome contributions from the community and are pleased that you are interested in helping to improve the Elsa Workflow project! Here are the steps to contribute to our project: diff --git a/build/Build.CI.GitHubActions.cs b/build/Build.CI.GitHubActions.cs index 7b3097658..1d2847d54 100644 --- a/build/Build.CI.GitHubActions.cs +++ b/build/Build.CI.GitHubActions.cs @@ -1,7 +1,11 @@ +using System.Collections.Generic; using Nuke.Common.CI.GitHubActions; +using Nuke.Common.CI.GitHubActions.Configuration; +using Nuke.Common.Execution; +using Nuke.Common.Utilities; using Nuke.Components; -[GitHubActions( +[CustomGitHubActions( "pr", GitHubActionsImage.UbuntuLatest, OnPullRequestBranches = ["main"], @@ -12,4 +16,55 @@ using Nuke.Components; ConcurrencyCancelInProgress = true ) ] -public partial class Build; \ No newline at end of file +public partial class Build; + +class CustomGitHubActionsAttribute : GitHubActionsAttribute +{ + public CustomGitHubActionsAttribute(string name, GitHubActionsImage image, params GitHubActionsImage[] images) : base(name, image, images) + { + } + + protected override GitHubActionsJob GetJobs(GitHubActionsImage image, IReadOnlyCollection relevantTargets) + { + var job = base.GetJobs(image, relevantTargets); + + var newSteps = new List(job.Steps); + + // only need to list the ones that are missing from default image + newSteps.Insert(0, new GitHubActionsSetupDotNetStep(["9.x"])); + + job.Steps = newSteps.ToArray(); + return job; + } +} + +class GitHubActionsSetupDotNetStep : GitHubActionsStep +{ + public GitHubActionsSetupDotNetStep(string[] versions) + { + Versions = versions; + } + + string[] Versions { get; } + + public override void Write(CustomFileWriter writer) + { + writer.WriteLine("- uses: actions/setup-dotnet@v4"); + + using (writer.Indent()) + { + writer.WriteLine("with:"); + using (writer.Indent()) + { + writer.WriteLine("dotnet-version: |"); + using (writer.Indent()) + { + foreach (var version in Versions) + { + writer.WriteLine(version); + } + } + } + } + } +} \ No newline at end of file diff --git a/build/_build.csproj b/build/_build.csproj index 66629bd30..acca9bc0d 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -19,7 +19,7 @@ - + diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index d8e421916..137a6cc77 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -13,7 +13,7 @@ - "5432:5432" mysql: - image: mysql:9.1.0 + image: mysql:8.0 container_name: mysql environment: MYSQL_ROOT_PASSWORD: password @@ -23,7 +23,19 @@ ports: - "3306:3306" volumes: - - mysql_data:/var/lib/mysql + - mysql_data2:/var/lib/mysql + + oracle: + image: container-registry.oracle.com/database/free:latest + container_name: oracle-db + environment: + ORACLE_PWD: elsa + ports: + - "1521:1521" + - "5500:5500" + volumes: + - ./data/oracle-data:/opt/oracle/oradata + - ./setup/oracle-setup:/opt/oracle/scripts/setup mongodb: image: mongo:latest @@ -72,6 +84,17 @@ environment: - PlantUml__RemoteUrl= - ConnectionStrings__DefaultConnection=USER ID=tracelens;PASSWORD=tracelenspass;HOST=postgres;PORT=5432;DATABASE=tracelens;POOLING=true; + + smtp4dev: # Mock SMTP server + image: rnwood/smtp4dev + container_name: smtp4dev + restart: always + ports: + - "3000:80" # Web interface + - "2525:25" # SMTP port + environment: + - ASPNETCORE_URLS=http://+:80 + - Logging__LogLevel__Default=Information elsa-server: build: @@ -104,6 +127,7 @@ volumes: postgres-data: - mysql_data: + oracle-data-free1: + mysql_data2: cockroachdb-data: mongodb_data: diff --git a/docker/init-db.sh b/docker/init-db-postgres.sh similarity index 100% rename from docker/init-db.sh rename to docker/init-db-postgres.sh diff --git a/docker/setup/oracle-setup/setup.sql b/docker/setup/oracle-setup/setup.sql new file mode 100755 index 000000000..311441d40 --- /dev/null +++ b/docker/setup/oracle-setup/setup.sql @@ -0,0 +1,3 @@ +alter session set "_ORACLE_SCRIPT"=true; +CREATE USER ELSA IDENTIFIED BY elsa; +GRANT ALL PRIVILEGES TO ELSA; \ No newline at end of file diff --git a/scripts/migrations/efcore-3.1.sh b/scripts/migrations/efcore-3.1.sh index 158880741..b9357f071 100755 --- a/scripts/migrations/efcore-3.1.sh +++ b/scripts/migrations/efcore-3.1.sh @@ -16,6 +16,6 @@ for module in "${mods[@]}"; do echo "Updating migrations for $provider..." echo "Provider path: ${providerPath:?}/${migrationsPath}" echo "Migrations path: $migrationsPath" - ef-migration-runtime-schema --interface Elsa.EntityFrameworkCore.Common.Contracts.IElsaDbContextSchema --efOptions "migrations add V3_1 -c ""$module""ElsaDbContext -p ""$providerPath"" -o ""$migrationsPath""" + ef-migration-runtime-schema --interface Elsa.EntityFrameworkCore.IElsaDbContextSchema --efOptions "migrations add V3_1 -c ""$module""ElsaDbContext -p ""$providerPath"" -o ""$migrationsPath""" done done diff --git a/scripts/migrations/efcore-3.2.sh b/scripts/migrations/efcore-3.2.sh index 0a2edd654..c9fc38e44 100755 --- a/scripts/migrations/efcore-3.2.sh +++ b/scripts/migrations/efcore-3.2.sh @@ -16,6 +16,6 @@ for module in "${mods[@]}"; do echo "Updating migrations for $provider..." echo "Provider path: ${providerPath:?}/${migrationsPath}" echo "Migrations path: $migrationsPath" - ef-migration-runtime-schema --interface Elsa.EntityFrameworkCore.Common.Contracts.IElsaDbContextSchema --efOptions "migrations add V3_2 -c ""$module""ElsaDbContext -p ""$providerPath"" -o ""$migrationsPath""" + ef-migration-runtime-schema --interface Elsa.EntityFrameworkCore.IElsaDbContextSchema --efOptions "migrations add V3_2 -c ""$module""ElsaDbContext -p ""$providerPath"" -o ""$migrationsPath""" done done diff --git a/src/Directory.Build.props b/src/Directory.Build.props index cb5db9709..84212213e 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,7 +3,7 @@ - net6.0;net8.0;net9.0 + net8.0;net9.0 diff --git a/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj b/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj index bf4d53171..7ac6ef99a 100644 --- a/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj +++ b/src/apps/Elsa.Server.Web/Elsa.Server.Web.csproj @@ -1,4 +1,4 @@ - + @@ -9,58 +9,76 @@ - + + - + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + + + + + + + - - + + + + + + diff --git a/src/apps/Elsa.Server.Web/Enums/SqlDatabaseProvider.cs b/src/apps/Elsa.Server.Web/Enums/SqlDatabaseProvider.cs index cc2988042..651163385 100644 --- a/src/apps/Elsa.Server.Web/Enums/SqlDatabaseProvider.cs +++ b/src/apps/Elsa.Server.Web/Enums/SqlDatabaseProvider.cs @@ -6,5 +6,6 @@ public enum SqlDatabaseProvider Sqlite, MySql, PostgreSql, + Oracle, CockroachDb } \ No newline at end of file diff --git a/src/apps/Elsa.Server.Web/Program.cs b/src/apps/Elsa.Server.Web/Program.cs index b1427d8cd..32fdbfc5c 100644 --- a/src/apps/Elsa.Server.Web/Program.cs +++ b/src/apps/Elsa.Server.Web/Program.cs @@ -2,12 +2,14 @@ using System.Text.Encodings.Web; using Elsa.Agents; using Elsa.Alterations.Extensions; using Elsa.Alterations.MassTransit.Extensions; +using Elsa.Caching.Options; 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; using Elsa.EntityFrameworkCore.Extensions; using Elsa.EntityFrameworkCore.Modules.Alterations; using Elsa.EntityFrameworkCore.Modules.Identity; @@ -27,6 +29,8 @@ 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; @@ -34,9 +38,16 @@ 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.LogPersistence; using Elsa.Workflows.Management; using Elsa.Workflows.Management.Compression; @@ -45,6 +56,12 @@ 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; @@ -75,10 +92,11 @@ const WorkflowRuntime workflowRuntime = WorkflowRuntime.Distributed; const DistributedCachingTransport distributedCachingTransport = DistributedCachingTransport.MassTransit; const MassTransitBroker massTransitBroker = MassTransitBroker.Memory; const bool useMultitenancy = false; -const bool useTenantsFromConfiguration = false; +const bool useTenantsFromConfiguration = true; const bool useAgents = false; -const bool useSecrets = true; +const bool useSecrets = false; const bool disableVariableWrappers = false; +const bool disableVariableCopying = false; var builder = WebApplication.CreateBuilder(args); var services = builder.Services; @@ -88,6 +106,7 @@ var identityTokenSection = identitySection.GetSection("Tokens"); var sqliteConnectionString = configuration.GetConnectionString("Sqlite")!; var sqlServerConnectionString = configuration.GetConnectionString("SqlServer")!; var postgresConnectionString = configuration.GetConnectionString("PostgreSql")!; +var oracleConnectionString = configuration.GetConnectionString("Oracle")!; var mySqlConnectionString = configuration.GetConnectionString("MySql")!; var cockroachDbConnectionString = configuration.GetConnectionString("CockroachDb")!; var mongoDbConnectionString = configuration.GetConnectionString("MongoDb")!; @@ -129,7 +148,36 @@ services }); if (useHangfire) - elsa.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() @@ -150,11 +198,13 @@ services else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) ef.UsePostgreSql(postgresConnectionString!); #if !NET9_0 - else if (sqlDatabaseProvider == SqlDatabaseProvider.MySql) + 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 ElsaDbContextOptions{ SchemaName = "ELSA"}); else ef.UseSqlite(sp => sp.GetSqliteConnectionString()); @@ -171,6 +221,11 @@ services { 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 => { @@ -191,6 +246,8 @@ services #endif else if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb) ef.UsePostgreSql(cockroachDbConnectionString!); + else if (sqlDatabaseProvider == SqlDatabaseProvider.Oracle) + ef.UseOracle(oracleConnectionString, new ElsaDbContextOptions{ SchemaName = "ELSA"}); else ef.UseSqlite(sp => sp.GetSqliteConnectionString()); @@ -236,7 +293,9 @@ services ef.UseMySql(mySqlConnectionString); #endif else if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb) - ef.UsePostgreSql(cockroachDbConnectionString!); + ef.UsePostgreSql(cockroachDbConnectionString); + else if (sqlDatabaseProvider == SqlDatabaseProvider.Oracle) + ef.UseOracle(oracleConnectionString, new ElsaDbContextOptions{ SchemaName = "ELSA"}); else ef.UseSqlite(sp => sp.GetSqliteConnectionString()); @@ -316,6 +375,7 @@ services { options.AllowClrAccess = true; options.DisableWrappers = disableVariableWrappers; + options.DisableVariableCopying = disableVariableCopying; options.RegisterType(); options.ConfigureEngine(engine => { @@ -330,6 +390,12 @@ services // 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) @@ -340,6 +406,16 @@ services if (useCaching) http.UseCache(); }) + .UseSql(options => + { + options.Clients = client => + { + client.Register("MySql"); + client.Register("PostgreSql"); + client.Register("Sqlite"); + client.Register("Sql Server"); + }; + }) .UseEmail(email => email.ConfigureOptions = options => configuration.GetSection("Smtp").Bind(options)) .UseAlterations(alterations => { @@ -364,7 +440,9 @@ services ef.UseMySql(mySqlConnectionString); #endif else if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb) - ef.UsePostgreSql(cockroachDbConnectionString!); + ef.UsePostgreSql(cockroachDbConnectionString); + else if (sqlDatabaseProvider == SqlDatabaseProvider.Oracle) + ef.UseOracle(oracleConnectionString, new ElsaDbContextOptions{ SchemaName = "ELSA"}); else ef.UseSqlite(sp => sp.GetSqliteConnectionString()); @@ -381,7 +459,13 @@ services if (useQuartz) { - elsa.UseQuartz(quartz => { quartz.UseSqlite(sqliteConnectionString); }); + elsa.UseQuartz(quartz => + { + if (sqlDatabaseProvider == SqlDatabaseProvider.Sqlite) + quartz.UseSqlite(sqliteConnectionString); + else if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) + quartz.UsePostgreSql(postgresConnectionString); + }); } if (useSignalR) @@ -514,6 +598,19 @@ services ; } + 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 => @@ -548,8 +645,10 @@ services if (sqlDatabaseProvider == SqlDatabaseProvider.Sqlite) ef.UseSqlite(sqliteConnectionString); if (sqlDatabaseProvider == SqlDatabaseProvider.SqlServer) ef.UseSqlServer(sqlServerConnectionString); if (sqlDatabaseProvider == SqlDatabaseProvider.PostgreSql) ef.UsePostgreSql(postgresConnectionString); + if (sqlDatabaseProvider == SqlDatabaseProvider.Oracle) ef.UseOracle(oracleConnectionString, new ElsaDbContextOptions{ SchemaName = "ELSA"}); #if !NET9_0 - if (sqlDatabaseProvider == SqlDatabaseProvider.MySql) ef.UseMySql(mySqlConnectionString); + if (sqlDatabaseProvider == SqlDatabaseProvider.MySql) + ef.UseMySql(mySqlConnectionString); #endif if (sqlDatabaseProvider == SqlDatabaseProvider.CockroachDb) ef.UsePostgreSql(cockroachDbConnectionString); }); @@ -560,7 +659,11 @@ services } }); - elsa.UseTenantHttpRouting(); + elsa.UseTenantHttpRouting(tenantHttpRouting => + { + // Override the tenant header name with a custom one. + tenantHttpRouting.WithTenantHeader("X-Company-Id"); + }); } elsa.InstallDropIns(options => options.DropInRootDirectory = Path.Combine(Directory.GetCurrentDirectory(), "App_Data", "DropIns")); @@ -582,7 +685,7 @@ services.Configure(options => services.Configure(options => options.Ttl = TimeSpan.FromSeconds(10)); -//services.Configure(options => options.CacheDuration = TimeSpan.FromDays(1)); +services.Configure(options => options.CacheDuration = TimeSpan.FromDays(1)); services.AddHealthChecks(); services.AddControllers(); services.AddCors(cors => cors.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().WithExposedHeaders("*"))); diff --git a/src/apps/Elsa.Server.Web/SampleWorkflow.cs b/src/apps/Elsa.Server.Web/SampleWorkflow.cs new file mode 100644 index 000000000..4f031c47d --- /dev/null +++ b/src/apps/Elsa.Server.Web/SampleWorkflow.cs @@ -0,0 +1,25 @@ +using Elsa.Workflows; +using Elsa.Workflows.Activities; +using Elsa.Extensions; +using Elsa.Workflows.CommitStates.Strategies; + +namespace Elsa.Server.Web; + +public class SampleWorkflow : WorkflowBase +{ + protected override void Build(IWorkflowBuilder builder) + { + builder.WorkflowOptions.CommitStrategyName = "Every 10 seconds"; + builder.Root = new Sequence + { + Activities = + { + new WriteLine("Commit before executing").SetCommitStrategy(nameof(ExecutingActivityStrategy)), + new WriteLine("Commit after executing").SetCommitStrategy(nameof(ExecutedActivityStrategy)), + new WriteLine("Commit before & after executing").SetCommitStrategy(nameof(CommitAlwaysActivityStrategy)), + new WriteLine("Commit only based on the workflow commit options").SetCommitStrategy(nameof(DefaultActivityStrategy)), + new WriteLine("Never commit the workflow when this activity is about to execute or has executed").SetCommitStrategy(nameof(CommitNeverActivityStrategy)), + } + }; + } +} \ No newline at end of file diff --git a/src/apps/Elsa.Server.Web/appsettings.json b/src/apps/Elsa.Server.Web/appsettings.json index 1f6fd76cd..a1d35303b 100644 --- a/src/apps/Elsa.Server.Web/appsettings.json +++ b/src/apps/Elsa.Server.Web/appsettings.json @@ -2,14 +2,7 @@ "Logging": { "LogLevel": { "Default": "Warning", - "Elsa": "Warning", - "MassTransit": "Warning", - "Microsoft.Extensions.Http": "Warning", - "Microsoft.Hosting.Lifetime": "Information", - "Microsoft.EntityFrameworkCore": "Warning", - "Microsoft.AspNetCore": "Warning", - "Quartz": "Warning", - "System.Net.Http": "Warning" + "Microsoft.Hosting.Lifetime": "Information" } }, "HostBuilder": { @@ -18,7 +11,9 @@ "AllowedHosts": "*", "ConnectionStrings": { "Sqlite": "Data Source=App_Data/elsa.sqlite.db;Cache=Shared;", + "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", + "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": "", diff --git a/src/apps/Elsa.ServerAndStudio.Web/Pages/_Host.cshtml b/src/apps/Elsa.ServerAndStudio.Web/Pages/_Host.cshtml index c2217d30d..81e0c82bc 100644 --- a/src/apps/Elsa.ServerAndStudio.Web/Pages/_Host.cshtml +++ b/src/apps/Elsa.ServerAndStudio.Web/Pages/_Host.cshtml @@ -2,9 +2,8 @@ @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @inject IConfiguration Configuration; @{ - var baseUrl = Configuration["Hosting:BaseUrl"]!; - var apiUrl = baseUrl + Url.Content("~/elsa/api"); - var basePath = Configuration["Hosting:BasePath"]!; + var baseUrl = Configuration["Hosting:BaseUrl"] ?? Configuration["Http:BaseUrl"]!; // HttpBaseUrl is for backward compatibility. + var apiUrl = $"{baseUrl.TrimEnd('/')}/{(Configuration["Hosting:ApiPrefix"] ?? "elsa/api").TrimStart('/')}"; } @@ -15,20 +14,20 @@ Elsa Studio 3.0 - - - - + + + + - - - - + + + + @@ -44,19 +43,18 @@ Reload 🗙 - - - - - - + + + + + + - + \ No newline at end of file diff --git a/src/apps/Elsa.ServerAndStudio.Web/Program.cs b/src/apps/Elsa.ServerAndStudio.Web/Program.cs index d5e13c05c..85af86498 100644 --- a/src/apps/Elsa.ServerAndStudio.Web/Program.cs +++ b/src/apps/Elsa.ServerAndStudio.Web/Program.cs @@ -131,7 +131,11 @@ services if (useCaching) http.UseCache(); - http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options); + 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)) diff --git a/src/apps/Elsa.ServerAndStudio.Web/appsettings.json b/src/apps/Elsa.ServerAndStudio.Web/appsettings.json index e95f0771c..4a3ccd5db 100644 --- a/src/apps/Elsa.ServerAndStudio.Web/appsettings.json +++ b/src/apps/Elsa.ServerAndStudio.Web/appsettings.json @@ -15,10 +15,10 @@ "SqlServer": "Server=localhost;Database=elsa;User Id=sa;Password=Password12!", "PostgreSql": "Server=localhost;Username=elsa;Database=elsa;Port=5432;Password=elsa;SSLMode=Prefer;MaxPoolSize=2000;Timeout=60" }, - "DatabaseProvider": "PostgreSql", + "DatabaseProvider": "Sqlite", "Hosting": { "BaseUrl": "https://localhost:8080", - "BasePath": "" + "ApiPrefix": "/elsa/api" }, "Identity": { "Tokens": { @@ -81,7 +81,6 @@ "DefaultSender": "noreply@crmservices.com" }, "Http": { - "BaseUrl": "https://localhost:5001", "BasePath": "/api/workflows" }, "Webhooks": { diff --git a/src/clients/Elsa.Api.Client/Extensions/ActivityExtensions.cs b/src/clients/Elsa.Api.Client/Extensions/ActivityExtensions.cs index 6ae48eab8..4990ea81a 100644 --- a/src/clients/Elsa.Api.Client/Extensions/ActivityExtensions.cs +++ b/src/clients/Elsa.Api.Client/Extensions/ActivityExtensions.cs @@ -1,4 +1,5 @@ using System.Text.Json.Nodes; +using Elsa.Api.Client.Resources.WorkflowDefinitions.Models; using Elsa.Api.Client.Shared.Models; namespace Elsa.Api.Client.Extensions; @@ -182,4 +183,14 @@ public static class ActivityExtensions /// Sets a value indicating whether the specified activity can trigger the workflow. /// public static void SetRunAsynchronously(this JsonObject activity, bool value) => activity.SetProperty(JsonValue.Create(value), "customProperties", "runAsynchronously"); + + /// + /// Gets the commit state behavior for the specified activity. + /// + public static string? GetCommitStrategy(this JsonObject activity) => activity.TryGetProperty("customProperties", "commitStrategyName"); + + /// + /// Sets the commit state behavior for the specified activity. + /// + public static void SetCommitStrategy(this JsonObject activity, string? name) => activity.SetProperty(JsonValue.Create(name), "customProperties", "commitStrategyName"); } \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs index e8bb1afe3..3e23b2e45 100644 --- a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs +++ b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs @@ -2,6 +2,7 @@ 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.CommitStrategies.Contracts; using Elsa.Api.Client.Resources.Features.Contracts; using Elsa.Api.Client.Resources.Identity.Contracts; using Elsa.Api.Client.Resources.IncidentStrategies.Contracts; @@ -73,6 +74,7 @@ public static class DependencyInjectionExtensions services.AddApi(builderOptions); services.AddApi(builderOptions); services.AddApi(builderOptions); + services.AddApi(builderOptions); services.AddApi(builderOptions); services.AddApi(builderOptions); services.AddApi(builderOptions); diff --git a/src/clients/Elsa.Api.Client/Extensions/JsonObjectExtensions.cs b/src/clients/Elsa.Api.Client/Extensions/JsonObjectExtensions.cs index a1612f9c2..b65fcf394 100644 --- a/src/clients/Elsa.Api.Client/Extensions/JsonObjectExtensions.cs +++ b/src/clients/Elsa.Api.Client/Extensions/JsonObjectExtensions.cs @@ -15,52 +15,43 @@ public static class JsonObjectExtensions { return obj.ContainsKey("type") && obj.ContainsKey("id") && obj.ContainsKey("version"); } - + /// /// Serializes the specified value to a . /// /// The value to serialize. /// The to use. /// A representing the specified value. - public static JsonNode SerializeToNode(this object value, JsonSerializerOptions? options = default) + public static JsonNode SerializeToNode(this object value, JsonSerializerOptions? options = null) { - options ??= new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - + options ??= new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + return JsonSerializer.SerializeToNode(value, options)!; } - + /// /// Serializes the specified value to a . /// /// The value to serialize. /// The to use. /// A representing the specified value. - public static JsonArray SerializeToArray(this object value, JsonSerializerOptions? options = default) + public static JsonArray SerializeToArray(this object value, JsonSerializerOptions? options = null) { - options ??= new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - + options ??= new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + return JsonSerializer.SerializeToNode(value, options)!.AsArray(); } - + /// /// Serializes the specified value to a . /// /// The value to serialize. /// The to use. /// A representing the specified value. - public static JsonArray SerializeToArray(this IEnumerable value, JsonSerializerOptions? options = default) + public static JsonArray SerializeToArray(this IEnumerable value, JsonSerializerOptions? options = null) { - options ??= new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - + options ??= new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + return JsonSerializer.SerializeToNode(value, options)!.AsArray(); } @@ -71,19 +62,23 @@ public static class JsonObjectExtensions /// The to use. /// The type to deserialize to. /// The deserialized value. - public static T Deserialize(this JsonNode value, JsonSerializerOptions? options = default) + public static T Deserialize(this JsonNode value, JsonSerializerOptions? options = null) { - options ??= new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - + options ??= new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + if (value is JsonObject jsonObject) return JsonSerializer.Deserialize(jsonObject, options)!; if (value is JsonArray jsonArray) return JsonSerializer.Deserialize(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(); @@ -101,7 +96,7 @@ public static class JsonObjectExtensions model = GetPropertyContainer(model, path); model[path.Last()] = value?.SerializeToNode(); } - + /// /// Sets the property value of the specified model. /// @@ -113,7 +108,7 @@ public static class JsonObjectExtensions model = GetPropertyContainer(model, path); model[path.Last()] = value?.SerializeToNode(); } - + /// /// Sets the property value of the specified model. /// @@ -125,7 +120,7 @@ public static class JsonObjectExtensions model = GetPropertyContainer(model, path); model[path.Last()] = new JsonArray(value.Select(x => x.SerializeToNode()).ToArray()); } - + /// /// Gets the property value of the specified model. /// @@ -139,7 +134,7 @@ public static class JsonObjectExtensions foreach (var prop in path.SkipLast(1)) { if (currentModel[prop] is not JsonObject value) - return default; + return null; currentModel = value; } @@ -147,6 +142,25 @@ public static class JsonObjectExtensions return currentModel[path.Last()]; } + /// + /// Gets the property value of the specified model. + /// + /// The model to get the property value from. + /// The path to the property. + /// The type to deserialize to. + /// The property value. + public static T? TryGetProperty(this JsonObject model, params string[] path) + { + try + { + return model.GetProperty(path); + } + catch (Exception e) + { + return default; + } + } + /// /// Gets the property value of the specified model. /// @@ -173,7 +187,7 @@ public static class JsonObjectExtensions var property = GetProperty(model, path); return property != null ? property.Deserialize(options) : default; } - + /// /// Returns the property container of the specified model. /// @@ -190,5 +204,4 @@ public static class JsonObjectExtensions return model; } - } \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Contracts/IAlteration.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Contracts/IAlteration.cs new file mode 100644 index 000000000..0ce1f7a50 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Contracts/IAlteration.cs @@ -0,0 +1,6 @@ +namespace Elsa.Api.Client.Resources.Alterations.Contracts; + +/// +/// Marker interface for all alteration classes +/// +public interface IAlteration; \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Contracts/IAlterationsApi.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Contracts/IAlterationsApi.cs new file mode 100644 index 000000000..6e212ec36 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Contracts/IAlterationsApi.cs @@ -0,0 +1,52 @@ +using Elsa.Api.Client.Resources.Alterations.Models; +using Elsa.Api.Client.Resources.Alterations.Requests; +using Elsa.Api.Client.Resources.Alterations.Responses; +using Refit; + +namespace Elsa.Api.Client.Resources.Alterations.Contracts; + +/// +/// Represents a client for the alterations API. Requires the Elsa.Alterations feature. +/// +public interface IAlterationsApi +{ + /// + /// Returns an alteration plan and its associated jobs. + /// + /// The ID of the alteration plan to return. + /// The cancellation token. + [Get("/alterations/{id}")] + Task GetAsync(string id, CancellationToken cancellationToken = default); + + /// + /// Determines which workflow instances a "Submit" request would target without actually running an alteration + /// + /// The requested workflow filter to dry run + /// The cancellation token. + [Post("/alterations/dry-run")] + Task DryRun(AlterationWorkflowInstanceFilter request, CancellationToken cancellationToken = default); + + /// + /// Submits an alteration plan and a filter for workflows instances to be executed against + /// + /// The alterations and filter to submit + /// The cancellation token. + [Post("/alterations/submit")] + Task Submit(AlterationPlanParams request, CancellationToken cancellationToken = default); + + /// + /// Runs an alteration plan and a list of workflow Instance Ids to be executed against + /// + /// The alterations and workflowInstanceIds to execute + /// The cancellation token. + [Post("/alterations/run")] + Task Run(RunRequest request, CancellationToken cancellationToken = default); + + /// + /// Retries the specified workflow instances. + /// + /// The request containing the selection of workflow instances to retry. + /// The cancellation token. + [Post("/alterations/workflows/retry")] + Task BulkRetryAsync(BulkRetryRequest request, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Enums/ActivityStatus.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Enums/ActivityStatus.cs new file mode 100644 index 000000000..a4693e6ae --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Enums/ActivityStatus.cs @@ -0,0 +1,32 @@ +namespace Elsa.Api.Client.Resources.Alterations.Enums; + +/// +/// Represents the status of an activity. +/// +public enum ActivityStatus +{ + /// + /// The activity is in the Pending state. + /// + Pending, + + /// + /// The activity is in the Running state. Note that event if an activity is running, it may not be executing. + /// + Running, + + /// + /// The activity is in the Completed state. + /// + Completed, + + /// + /// The activity is in the Canceled state. + /// + Canceled, + + /// + /// The activity is in the Faulted state. + /// + Faulted +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Enums/AlterationJobStatus.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Enums/AlterationJobStatus.cs new file mode 100644 index 000000000..49e65a99a --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Enums/AlterationJobStatus.cs @@ -0,0 +1,27 @@ +namespace Elsa.Api.Client.Resources.Alterations.Enums; + +/// +/// The status of an alteration plan for a workflow instance. +/// +public enum AlterationJobStatus +{ + /// + /// The plan is pending execution. + /// + Pending, + + /// + /// The plan is currently being executed. + /// + Running, + + /// + /// The plan has been completed. + /// + Completed, + + /// + /// The job has failed. + /// + Failed +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Enums/AlterationPlanStatus.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Enums/AlterationPlanStatus.cs new file mode 100644 index 000000000..80ded4c2e --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Enums/AlterationPlanStatus.cs @@ -0,0 +1,37 @@ +namespace Elsa.Api.Client.Resources.Alterations.Enums; + +/// +/// The status of an alteration plan. +/// +public enum AlterationPlanStatus +{ + /// + /// The plan is pending execution. + /// + Pending, + + /// + /// The plan is currently generating jobs. + /// + Generating, + + /// + /// The plan is currently dispatching jobs. + /// + Dispatching, + + /// + /// The plan is currently being executed. + /// + Running, + + /// + /// The plan has been completed. + /// + Completed, + + /// + /// The plan has failed. + /// + Failed +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/ActivityFilter.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/ActivityFilter.cs new file mode 100644 index 000000000..37fdec5ee --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/ActivityFilter.cs @@ -0,0 +1,34 @@ +using Elsa.Api.Client.Resources.Alterations.Enums; + +namespace Elsa.Api.Client.Resources.Alterations.Models; + +/// +/// A filter for activities within a workflow instance +/// +public class ActivityFilter +{ + /// + /// The ID of the activity. + /// + public string? ActivityId { get; set; } + + /// + /// The ID of the activity instance. + /// + public string? ActivityInstanceId { get; set; } + + /// + /// The node ID of the activity. + /// + public string? NodeId { get; set; } + + /// + /// The name of the activity. + /// + public string? Name { get; set; } + + /// + /// The status of the activity. + /// + public ActivityStatus? Status { get; set; } +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationBase.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationBase.cs new file mode 100644 index 000000000..18615eca9 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationBase.cs @@ -0,0 +1,8 @@ +using Elsa.Api.Client.Resources.Alterations.Contracts; + +namespace Elsa.Api.Client.Resources.Alterations.Models; + +/// +/// A base class for all IAlterations. +/// +public abstract class AlterationBase : IAlteration; \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationJob.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationJob.cs new file mode 100644 index 000000000..d756c23d7 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationJob.cs @@ -0,0 +1,45 @@ +using Elsa.Api.Client.Resources.Alterations.Enums; +using Elsa.Api.Client.Shared.Models; + +namespace Elsa.Api.Client.Resources.Alterations.Models; + +/// +/// Represents the execution of the plan for an individual workflow instance. +/// +public class AlterationJob : Entity +{ + /// + /// The ID of the plan that this job belongs to. + /// + public string PlanId { get; set; } = default!; + + /// + /// The ID of the workflow instance that this job applies to. + /// + public string WorkflowInstanceId { get; set; } = default!; + + /// + /// The status of the job. + /// + public AlterationJobStatus Status { get; set; } + + /// + /// The serialized log of the job. + /// + public ICollection? Log { get; set; } = new List(); + + /// + /// The date and time at which the job was created. + /// + public DateTimeOffset CreatedAt { get; set; } + + /// + /// The date and time at which the job was started. + /// + public DateTimeOffset? StartedAt { get; set; } + + /// + /// The date and time at which the job was completed. + /// + public DateTimeOffset? CompletedAt { get; set; } +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationLog.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationLog.cs new file mode 100644 index 000000000..c39d44c12 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationLog.cs @@ -0,0 +1,13 @@ +namespace Elsa.Api.Client.Resources.Alterations.Models; + +/// +/// Represents a log of alterations. +/// +public class AlterationLog +{ + + /// + /// The log entries. + /// + public ICollection LogEntries { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationLogEntry.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationLogEntry.cs new file mode 100644 index 000000000..9c661275b --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationLogEntry.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Logging; + +namespace Elsa.Api.Client.Resources.Alterations.Models; + +/// +/// An individual log entry about an alteration +/// +/// +/// +/// +/// +public record AlterationLogEntry(string Message, LogLevel LogLevel, DateTimeOffset Timestamp, string? EventName = null); \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationPlan.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationPlan.cs new file mode 100644 index 000000000..993858cf5 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationPlan.cs @@ -0,0 +1,41 @@ +using Elsa.Api.Client.Resources.Alterations.Contracts; +using Elsa.Api.Client.Resources.Alterations.Enums; +using Elsa.Api.Client.Shared.Models; + +namespace Elsa.Api.Client.Resources.Alterations.Models; + +/// +/// A plan that contains a list of alterations to be applied to a set of workflow instances. +/// +public class AlterationPlan : Entity +{ + /// + /// The alterations to be applied. + /// + public ICollection Alterations { get; set; } = new List(); + + /// + /// The IDs of the workflow instances that this plan applies to. + /// + public AlterationWorkflowInstanceFilter WorkflowInstanceFilter { get; set; } = new(); + + /// + /// The status of the plan. + /// + public AlterationPlanStatus Status { get; set; } + + /// + /// The date and time at which the plan was created. + /// + public DateTimeOffset CreatedAt { get; set; } + + /// + /// The date and time at which the plan was started. + /// + public DateTimeOffset? StartedAt { get; set; } + + /// + /// The date and time at which the plan was completed. + /// + public DateTimeOffset? CompletedAt { get; set; } +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationPlanParams.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationPlanParams.cs new file mode 100644 index 000000000..f9fc5a1c1 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationPlanParams.cs @@ -0,0 +1,24 @@ +using Elsa.Api.Client.Resources.Alterations.Contracts; + +namespace Elsa.Api.Client.Resources.Alterations.Models; + +/// +/// Represents the execution of an alteration plan against a set of workflow instances defined by the given filter +/// +public class AlterationPlanParams +{ + /// + /// The unique identifier for the alteration plan. If not specified, a new ID will be generated. + /// + public string? Id { get; set; } + + /// + /// The alterations to be applied. + /// + public ICollection Alterations { get; set; } = new List(); + + /// + /// The IDs of the workflow instances that this plan applies to. + /// + public AlterationWorkflowInstanceFilter Filter { get; set; } = new(); +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationWorkflowInstanceFilter.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationWorkflowInstanceFilter.cs new file mode 100644 index 000000000..6f4419114 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/AlterationWorkflowInstanceFilter.cs @@ -0,0 +1,45 @@ +using Elsa.Api.Client.Shared.Models; + +namespace Elsa.Api.Client.Resources.Alterations.Models; + +/// +/// Represents a filter for workflow instances. +/// +public class AlterationWorkflowInstanceFilter +{ + /// + /// The IDs of the workflow instances that this plan applies to. + /// + public IEnumerable? WorkflowInstanceIds { get; set; } + + /// + /// The correlation IDs of the workflow instances that this plan applies to. + /// + public IEnumerable? CorrelationIds { get; set; } + + /// + /// A collection of timestamp filters used for filtering data based on specified timestamp columns and operators. + /// + public IEnumerable? TimestampFilters { get; set; } + + /// + /// The IDs of the workflow definitions that this plan applies to. + /// + public IEnumerable? DefinitionVersionIds { get; set; } + + /// + /// Whether the workflow instances to match have incidents. + /// + public bool? HasIncidents { get; set; } + + /// + /// Whether the workflow instances to match are system workflows. Defaults to false. + /// + public bool? IsSystem { get; set; } = false; + + /// + /// Represents a collection of filters for activities. + /// + public IEnumerable? ActivityFilters { get; set; } + +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/CancelActivity.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/CancelActivity.cs new file mode 100644 index 000000000..2500e7d97 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/CancelActivity.cs @@ -0,0 +1,17 @@ +namespace Elsa.Api.Client.Resources.Alterations.Models; + +/// +/// Cancels a workflow instance activity during an alteration +/// +public class CancelActivity : AlterationBase +{ + /// + /// The ID of the activity to be cancelled. If not specified, the activity instance ID will be used. + /// + public string? ActivityId { get; set; } + + /// + /// The ID of the activity instance to be cancelled. If specified, overrides . + /// + public string? ActivityInstanceId { get; set; } +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/Migrate.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/Migrate.cs new file mode 100644 index 000000000..1a4dc3b69 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/Migrate.cs @@ -0,0 +1,12 @@ +namespace Elsa.Api.Client.Resources.Alterations.Models; + +/// +/// Migrates a workflow instance to a newer version in an alteration. +/// +public class Migrate : AlterationBase +{ + /// + /// The target version to upgrade to. + /// + public int TargetVersion { get; set; } +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/ModifyVariable.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/ModifyVariable.cs new file mode 100644 index 000000000..60ab9f6ba --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/ModifyVariable.cs @@ -0,0 +1,18 @@ +namespace Elsa.Api.Client.Resources.Alterations.Models; + +/// +/// Modifies a variable in a workflow instance alteration +/// +public class ModifyVariable : AlterationBase +{ + /// + /// The ID of the variable to modify. + /// + public string VariableId { get; set; } = default!; + + /// + /// The new value of the variable. + /// + public object? Value { get; set; } + +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/RunAlterationsResult.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/RunAlterationsResult.cs new file mode 100644 index 000000000..756240be2 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/RunAlterationsResult.cs @@ -0,0 +1,27 @@ +namespace Elsa.Api.Client.Resources.Alterations.Models; + +/// +/// The result of running a series of alterations. +/// +public class RunAlterationsResult +{ + /// + /// The ID of the workflow instance that was altered. + /// + public string WorkflowInstanceId { get; set; } = string.Empty; + + /// + /// A log of the alterations that were run. + /// + public AlterationLog Log { get; set; } = new(); + + /// + /// A flag indicating whether the workflow has scheduled work. + /// + public bool WorkflowHasScheduledWork { get; set; } + + /// + /// A flag indicating whether the alterations have succeeded. + /// + public bool IsSuccessful { get; set; } +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Models/ScheduleActivity.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/ScheduleActivity.cs new file mode 100644 index 000000000..2b0f77f6d --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Models/ScheduleActivity.cs @@ -0,0 +1,17 @@ +namespace Elsa.Api.Client.Resources.Alterations.Models; + +/// +/// Schedules an activity for execution in an alteration. +/// +public class ScheduleActivity : AlterationBase +{ + /// + /// The ID of the next activity to be scheduled. If not specified, the ActivityInstanceId will be used. + /// + public string? ActivityId { get; set; } + + /// + /// The ID of the activity instance to be scheduled. If not specified, the ActivityId will be used. + /// + public string? ActivityInstanceId { get; set; } +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Requests/BulkRetryRequest.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Requests/BulkRetryRequest.cs new file mode 100644 index 000000000..2fbc9fdef --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Requests/BulkRetryRequest.cs @@ -0,0 +1,17 @@ +namespace Elsa.Api.Client.Resources.Alterations.Requests; + +/// +/// Represents a request to bulk retry workflow instances. +/// +public class BulkRetryRequest +{ + /// + /// The IDs of the workflow instances that have incidents to be retried. + /// + public ICollection WorkflowInstanceIds { get; set; } = new List(); + + /// + /// An optional list of explicitly specified activity IDs to retry. If omitted, all faulted activities will be retried. + /// + public ICollection? ActivityIds { get; set; } +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/BulkRetryResponse.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/BulkRetryResponse.cs new file mode 100644 index 000000000..532e517c9 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/BulkRetryResponse.cs @@ -0,0 +1,14 @@ +using Elsa.Api.Client.Resources.Alterations.Models; + +namespace Elsa.Api.Client.Resources.Alterations.Responses; + +/// +/// Represents a response to bulk retry workflow instances. +/// +public class BulkRetryResponse +{ + /// + /// The alterations that resulted from the bulk retry request + /// + public ICollection Results { get;set; } = new List(); +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/DryRunResponse.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/DryRunResponse.cs new file mode 100644 index 000000000..6eb0d2bea --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/DryRunResponse.cs @@ -0,0 +1,12 @@ +namespace Elsa.Api.Client.Resources.Alterations.Responses; + +/// +/// The response to the DryRun request +/// +public class DryRunResponse +{ + /// + /// The list of workflow instance IDs that would be affected by a "Submit" request + /// + public ICollection WorkflowInstanceIds { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/GetAlterationPlanResponse.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/GetAlterationPlanResponse.cs new file mode 100644 index 000000000..c3fc2fb51 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/GetAlterationPlanResponse.cs @@ -0,0 +1,19 @@ +using Elsa.Api.Client.Resources.Alterations.Models; + +namespace Elsa.Api.Client.Resources.Alterations.Responses; + +/// +/// The response from the "Get" alteration plan endpoint +/// +public class GetAlterationPlanResponse +{ + /// + /// The alteration plan mathching the provided ID + /// + public AlterationPlan Plan { get; set; } = new(); + + /// + /// The list of jobs that exist for that AlterationPlan + /// + public ICollection Jobs { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/RunRequest.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/RunRequest.cs new file mode 100644 index 000000000..fdaf341e8 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/RunRequest.cs @@ -0,0 +1,19 @@ +using Elsa.Api.Client.Resources.Alterations.Contracts; + +namespace Elsa.Api.Client.Resources.Alterations.Responses; + +/// +/// A plan that contains a list of alterations to be applied to a set of workflow instances. +/// +public class RunRequest +{ + /// + /// The alterations to be applied. + /// + public ICollection Alterations { get; set; } = new List(); + + /// + /// The IDs of the workflow instances that this plan applies to. + /// + public ICollection WorkflowInstanceIds { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/RunResponse.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/RunResponse.cs new file mode 100644 index 000000000..0d65d891e --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/RunResponse.cs @@ -0,0 +1,14 @@ +using Elsa.Api.Client.Resources.Alterations.Models; + +namespace Elsa.Api.Client.Resources.Alterations.Responses; + +/// +/// The response to the Run endpoint +/// +public class RunResponse +{ + /// + /// The alteration results of a Run request + /// + private ICollection Results { get; set; } = new List(); +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/SubmitResponse.cs b/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/SubmitResponse.cs new file mode 100644 index 000000000..82c5cd1e3 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/Alterations/Responses/SubmitResponse.cs @@ -0,0 +1,12 @@ +namespace Elsa.Api.Client.Resources.Alterations.Responses; + +/// +/// The response to the "Submit" endpoint +/// +public class SubmitResponse +{ + /// + /// The ID of the alteration plan created as part of the Submit request + /// + public string PlanId { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/CommitStrategies/Contracts/IIncidentStrategiesApi.cs b/src/clients/Elsa.Api.Client/Resources/CommitStrategies/Contracts/IIncidentStrategiesApi.cs new file mode 100644 index 000000000..cfee71fcc --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/CommitStrategies/Contracts/IIncidentStrategiesApi.cs @@ -0,0 +1,25 @@ +using Elsa.Api.Client.Resources.CommitStrategies.Models; +using Elsa.Api.Client.Shared.Models; +using Refit; + +namespace Elsa.Api.Client.Resources.CommitStrategies.Contracts; + +/// +/// Represents a client for the commit strategies API. +/// +public interface ICommitStrategiesApi +{ + /// + /// Lists workflow commit strategies. + /// + /// A list response containing activity commit strategy descriptors and their count. + [Get("/descriptors/commit-strategies/workflows")] + Task> ListWorkflowStrategiesAsync(CancellationToken cancellationToken = default); + + /// + /// Lists activity commit strategies. + /// + /// A list response containing activity commit strategy descriptors and their count. + [Get("/descriptors/commit-strategies/activities")] + Task> ListActivityStrategiesAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/CommitStrategies/Models/CommitStrategyDescriptor.cs b/src/clients/Elsa.Api.Client/Resources/CommitStrategies/Models/CommitStrategyDescriptor.cs new file mode 100644 index 000000000..ad63d5931 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/CommitStrategies/Models/CommitStrategyDescriptor.cs @@ -0,0 +1,7 @@ +namespace Elsa.Api.Client.Resources.CommitStrategies.Models; + +/// +/// Represents a descriptor for a commit strategy, containing information such as its technical name, +/// display name, and description. +/// +public record CommitStrategyDescriptor(string Name, string DisplayName, string Description); \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowCommitStateOptions.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowCommitStateOptions.cs new file mode 100644 index 000000000..8e14345c6 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowCommitStateOptions.cs @@ -0,0 +1,19 @@ +namespace Elsa.Api.Client.Resources.WorkflowDefinitions.Models; + +public class WorkflowCommitStateOptions +{ + /// + /// Commit workflow state before the workflow starts. + /// + public bool Starting { get; set; } + + /// + /// Commit workflow state before an activity executes, unless the activity is configured to not commit state. + /// + public bool ActivityExecuting { get; set; } + + /// + /// Commit workflow state after an activity executes, unless the activity is configured to not commit state. + /// + public bool ActivityExecuted { get; set; } +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowOptions.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowOptions.cs index ec28ac197..07b5c882d 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowOptions.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Models/WorkflowOptions.cs @@ -29,4 +29,9 @@ public class WorkflowOptions /// The type of IIncidentStrategy to use when a fault occurs in the workflow. /// public string? IncidentStrategyType { get; set; } + + /// + /// The options for committing workflow state. + /// + public string? CommitStrategyName { get; set; } } \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/ActivityIncident.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/ActivityIncident.cs new file mode 100644 index 000000000..078275008 --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/ActivityIncident.cs @@ -0,0 +1,51 @@ +using System.Text.Json.Serialization; + +namespace Elsa.Api.Client.Resources.WorkflowInstances.Models; + +/// +/// Holds information about an activity incident. +/// +public class ActivityIncident +{ + /// + /// Initializes a new instance of the class. + /// + [JsonConstructor] + public ActivityIncident() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The ID of the activity that caused the incident. + /// The type of the activity that caused the incident. + /// The message of the incident. + /// The exception that caused the incident. + /// The timestamp of the incident. + public ActivityIncident(string activityId, string activityType, string message, ExceptionState? exception, DateTimeOffset timestamp) + { + ActivityId = activityId; + ActivityType = activityType; + Message = message; + Exception = exception; + Timestamp = timestamp; + } + + /// The ID of the activity that caused the incident. + public string ActivityId { get; init; } = default!; + + /// The type of the activity that caused the incident. + public string ActivityType { get; init; } = default!; + + /// The message of the incident. + public string Message { get; init; } = default!; + + /// The exception that caused the incident. + public ExceptionState? Exception { get; init; } + + /// + /// The timestamp of the incident. + /// + public DateTimeOffset Timestamp { get; init; } +} diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/WorkflowState.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/WorkflowState.cs index 1a7c2a7b4..48f1391d8 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/WorkflowState.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowInstances/Models/WorkflowState.cs @@ -38,6 +38,11 @@ public class WorkflowState : Entity /// public ICollection Bookmarks { get; set; } = new List(); + /// + /// A collection of incidents that may have occurred during execution. + /// + public ICollection Incidents { get; set; } = new List(); + /// /// The serialized workflow state, if any. /// diff --git a/src/common/Elsa.Mediator/Contracts/IRequestSender.cs b/src/common/Elsa.Mediator/Contracts/IRequestSender.cs index 37d4288f9..de23cd440 100644 --- a/src/common/Elsa.Mediator/Contracts/IRequestSender.cs +++ b/src/common/Elsa.Mediator/Contracts/IRequestSender.cs @@ -12,5 +12,5 @@ public interface IRequestSender /// The cancellation token. /// The type of the response. /// The response. - Task> SendAsync(IRequest request, CancellationToken cancellationToken = default); + Task SendAsync(IRequest request, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/common/Elsa.Mediator/Middleware/Request/Components/RequestHandlerInvokerMiddleware.cs b/src/common/Elsa.Mediator/Middleware/Request/Components/RequestHandlerInvokerMiddleware.cs index 930d324f9..2055515f1 100644 --- a/src/common/Elsa.Mediator/Middleware/Request/Components/RequestHandlerInvokerMiddleware.cs +++ b/src/common/Elsa.Mediator/Middleware/Request/Components/RequestHandlerInvokerMiddleware.cs @@ -6,24 +6,10 @@ namespace Elsa.Mediator.Middleware.Request.Components; /// /// A middleware component that invokes request handlers. /// -public class RequestHandlerInvokerMiddleware : IRequestMiddleware +public class RequestHandlerInvokerMiddleware( + RequestMiddlewareDelegate next, + IEnumerable requestHandlers) : IRequestMiddleware { - private readonly RequestMiddlewareDelegate _next; - private readonly IEnumerable _requestHandlers; - - /// - /// Initializes a new instance of the class. - /// - /// The next middleware in the pipeline. - /// - public RequestHandlerInvokerMiddleware( - RequestMiddlewareDelegate next, - IEnumerable requestHandlers) - { - _next = next; - _requestHandlers = requestHandlers; - } - /// public async ValueTask InvokeAsync(RequestContext context) { @@ -33,22 +19,26 @@ public class RequestHandlerInvokerMiddleware : IRequestMiddleware var requestType = request.GetType(); var responseType = context.ResponseType; var handlerType = typeof(IRequestHandler<,>).MakeGenericType(requestType, responseType); - var handlers = _requestHandlers.Where(x => handlerType.IsInstanceOfType(x)).ToArray(); + var handlers = requestHandlers.Where(x => handlerType.IsInstanceOfType(x)).ToArray(); + + if (handlers.Length == 0) + throw new InvalidOperationException($"There is no handler to handle the {requestType.FullName} request"); - foreach (var handler in handlers) - { - var handleMethod = handlerType.GetMethod("HandleAsync")!; - var cancellationToken = context.CancellationToken; - var task = (Task)handleMethod.Invoke(handler, new object?[] { request, cancellationToken })!; - await task; + if (handlers.Length > 1) + throw new InvalidOperationException($"Multiple handlers were found to handle the {requestType.FullName} request"); - // Get result of task. - var taskWithReturnType = typeof(Task<>).MakeGenericType(responseType); - var resultProperty = taskWithReturnType.GetProperty(nameof(Task.Result))!; - context.Responses.Add(resultProperty.GetValue(task)!); - } + var handler = handlers.First(); + var handleMethod = handlerType.GetMethod("HandleAsync")!; + var cancellationToken = context.CancellationToken; + var task = (Task)handleMethod.Invoke(handler, [request, cancellationToken])!; + await task; + + // Get result of task. + var taskWithReturnType = typeof(Task<>).MakeGenericType(responseType); + var resultProperty = taskWithReturnType.GetProperty(nameof(Task.Result))!; + context.Response = resultProperty.GetValue(task)!; // Invoke next middleware. - await _next(context); + await next(context); } } \ No newline at end of file diff --git a/src/common/Elsa.Mediator/Middleware/Request/RequestContext.cs b/src/common/Elsa.Mediator/Middleware/Request/RequestContext.cs index 8aa4a5232..b6093cb68 100644 --- a/src/common/Elsa.Mediator/Middleware/Request/RequestContext.cs +++ b/src/common/Elsa.Mediator/Middleware/Request/RequestContext.cs @@ -36,7 +36,7 @@ public class RequestContext public CancellationToken CancellationToken { get; init; } /// - /// Gets the responses from each request handler. + /// Gets the response the request handler. /// - public ICollection Responses { get; set; } = new List(); + public object? Response { get; set; } } \ No newline at end of file diff --git a/src/common/Elsa.Mediator/Services/DefaultMediator.cs b/src/common/Elsa.Mediator/Services/DefaultMediator.cs index 7665624e6..21b3c36c0 100644 --- a/src/common/Elsa.Mediator/Services/DefaultMediator.cs +++ b/src/common/Elsa.Mediator/Services/DefaultMediator.cs @@ -41,23 +41,20 @@ public class DefaultMediator : IMediator } /// - public async Task> SendAsync(IRequest request, CancellationToken cancellationToken = default) + public async Task SendAsync(IRequest request, CancellationToken cancellationToken = default) { var responseType = typeof(T); var context = new RequestContext(request, responseType, cancellationToken); await _requestPipeline.ExecuteAsync(context); - if (context.Responses.All(x => x is T)) - return context.Responses.Cast().AsEnumerable(); - - throw new InvalidCastException($"Unable to cast objects in Responses property to type {typeof(T)}"); + return (T?)context.Response; } /// public async Task SendAsync(ICommand command, CancellationToken cancellationToken = default) => await SendAsync(command, _defaultCommandStrategy, cancellationToken); /// - public async Task SendAsync(ICommand command, ICommandStrategy? strategy = default, CancellationToken cancellationToken = default) + public async Task SendAsync(ICommand command, ICommandStrategy? strategy = null, CancellationToken cancellationToken = default) { var resultType = typeof(Unit); strategy ??= _defaultCommandStrategy; @@ -69,7 +66,7 @@ public class DefaultMediator : IMediator public async Task SendAsync(ICommand command, CancellationToken cancellationToken = default) => await SendAsync(command, _defaultCommandStrategy, cancellationToken); /// - public async Task SendAsync(ICommand command, ICommandStrategy strategy, CancellationToken cancellationToken = default) + public async Task SendAsync(ICommand command, ICommandStrategy? strategy, CancellationToken cancellationToken = default) { var resultType = typeof(T); strategy ??= _defaultCommandStrategy; @@ -83,7 +80,7 @@ public class DefaultMediator : IMediator public async Task SendAsync(INotification notification, CancellationToken cancellationToken = default) => await SendAsync(notification, _defaultPublishingStrategy, cancellationToken); /// - public async Task SendAsync(INotification notification, IEventPublishingStrategy? strategy = default, CancellationToken cancellationToken = default) + public async Task SendAsync(INotification notification, IEventPublishingStrategy? strategy = null, CancellationToken cancellationToken = default) { strategy ??= _defaultPublishingStrategy; var context = new NotificationContext(notification, strategy, cancellationToken); diff --git a/src/common/Elsa.Testing.Shared.Component/Services/SignalManager.cs b/src/common/Elsa.Testing.Shared.Component/Services/SignalManager.cs index 94ce581af..b9b585f66 100644 --- a/src/common/Elsa.Testing.Shared.Component/Services/SignalManager.cs +++ b/src/common/Elsa.Testing.Shared.Component/Services/SignalManager.cs @@ -25,7 +25,7 @@ public class SignalManager await Task.WhenAny(taskCompletionSource.Task, Task.Delay(millisecondsTimeout, cancellationTokenSource.Token)); cancellationTokenSource.Token.ThrowIfCancellationRequested(); _signals.TryRemove(signal, out _); - return taskCompletionSource.Task.Result; + return await taskCompletionSource.Task; } catch (OperationCanceledException) { diff --git a/src/modules/Elsa.Agents.Persistence.EntityFrameworkCore.MySql/Elsa.Agents.Persistence.EntityFrameworkCore.MySql.csproj b/src/modules/Elsa.Agents.Persistence.EntityFrameworkCore.MySql/Elsa.Agents.Persistence.EntityFrameworkCore.MySql.csproj index 04d22bca5..4ef508dba 100644 --- a/src/modules/Elsa.Agents.Persistence.EntityFrameworkCore.MySql/Elsa.Agents.Persistence.EntityFrameworkCore.MySql.csproj +++ b/src/modules/Elsa.Agents.Persistence.EntityFrameworkCore.MySql/Elsa.Agents.Persistence.EntityFrameworkCore.MySql.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0 + net8.0;net9.0 Provides an EF Core migrations for MySQL for the Agents Persistence module. elsa module agents semantic kernel llm ai persistence efcore entity framework core mysql diff --git a/src/modules/Elsa.Agents.Persistence.EntityFrameworkCore/Feature.cs b/src/modules/Elsa.Agents.Persistence.EntityFrameworkCore/Feature.cs index d5c99065e..67f4d0240 100644 --- a/src/modules/Elsa.Agents.Persistence.EntityFrameworkCore/Feature.cs +++ b/src/modules/Elsa.Agents.Persistence.EntityFrameworkCore/Feature.cs @@ -34,6 +34,5 @@ public class EFCoreAgentPersistenceFeature(IModule module) : PersistenceFeatureB AddEntityStore(); AddEntityStore(); AddEntityStore(); - Services.AddScoped(); } } \ No newline at end of file diff --git a/src/modules/Elsa.Agents.Persistence.EntityFrameworkCore/SetupForOracle.cs b/src/modules/Elsa.Agents.Persistence.EntityFrameworkCore/SetupForOracle.cs deleted file mode 100644 index 2f5444792..000000000 --- a/src/modules/Elsa.Agents.Persistence.EntityFrameworkCore/SetupForOracle.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Elsa.Agents.Persistence.Entities; -using Elsa.EntityFrameworkCore; -using Elsa.EntityFrameworkCore.Extensions; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Elsa.Agents.Persistence.EntityFrameworkCore; - -/// -/// Represents a class that handles entity model creation for SQLite databases. -/// -public class SetupForOracle : IEntityModelCreatingHandler -{ - /// - public void Handle(ElsaDbContextBase dbContext, ModelBuilder modelBuilder, IMutableEntityType entityType) - { - if(!dbContext.Database.IsOracle()) - return; - - // In order to use data more than 2000 char we have to use NCLOB. - // In Oracle, we have to explicitly say the column is NCLOB otherwise it would be considered nvarchar(2000). - modelBuilder.Entity().Property("SerializedSettings").HasColumnType("NCLOB"); - modelBuilder.Entity().Property("SerializedAgentConfig").HasColumnType("NCLOB"); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Alterations.Core/Models/AlterationPlanParams.cs b/src/modules/Elsa.Alterations.Core/Models/AlterationPlanParams.cs index dec58d038..08c266c4c 100644 --- a/src/modules/Elsa.Alterations.Core/Models/AlterationPlanParams.cs +++ b/src/modules/Elsa.Alterations.Core/Models/AlterationPlanParams.cs @@ -18,7 +18,7 @@ public class AlterationPlanParams public ICollection Alterations { get; set; } = new List(); /// - /// The IDs of the workflow instances that this plan applies to. + /// The filter used to determine which workflow instances that this plan applies to. /// public AlterationWorkflowInstanceFilter Filter { get; set; } = new(); } \ No newline at end of file diff --git a/src/modules/Elsa.Alterations/Endpoints/Alterations/DryRun/Endpoint.cs b/src/modules/Elsa.Alterations/Endpoints/Alterations/DryRun/Endpoint.cs index eb9461dff..cbf0d5471 100644 --- a/src/modules/Elsa.Alterations/Endpoints/Alterations/DryRun/Endpoint.cs +++ b/src/modules/Elsa.Alterations/Endpoints/Alterations/DryRun/Endpoint.cs @@ -6,7 +6,7 @@ using JetBrains.Annotations; namespace Elsa.Alterations.Endpoints.Alterations.DryRun; /// -/// Executes an alteration plan. +/// Determines which workflow instances a "Submit" request would target without actually running an alteration. /// [PublicAPI] public class DryRun(IWorkflowInstanceFinder workflowInstanceFinder) : ElsaEndpoint diff --git a/src/modules/Elsa.Alterations/Endpoints/Alterations/Get/Endpoint.cs b/src/modules/Elsa.Alterations/Endpoints/Alterations/Get/Endpoint.cs index c1615636f..7c4be8ebe 100644 --- a/src/modules/Elsa.Alterations/Endpoints/Alterations/Get/Endpoint.cs +++ b/src/modules/Elsa.Alterations/Endpoints/Alterations/Get/Endpoint.cs @@ -6,7 +6,7 @@ using JetBrains.Annotations; namespace Elsa.Alterations.Endpoints.Alterations.Get; /// -/// Executes an alteration plan. +/// Gets an alteration plan and its associated jobs. /// [PublicAPI] public class Get : ElsaEndpointWithoutRequest diff --git a/src/modules/Elsa.Alterations/Endpoints/Alterations/Run/Endpoint.cs b/src/modules/Elsa.Alterations/Endpoints/Alterations/Run/Endpoint.cs index 4301682db..a1d090e5e 100644 --- a/src/modules/Elsa.Alterations/Endpoints/Alterations/Run/Endpoint.cs +++ b/src/modules/Elsa.Alterations/Endpoints/Alterations/Run/Endpoint.cs @@ -5,7 +5,7 @@ using JetBrains.Annotations; namespace Elsa.Alterations.Endpoints.Alterations.Run; /// -/// Executes an alteration plan. +/// Executes an alteration plan by targeting workflow instances by ID. /// [PublicAPI] public class Run : ElsaEndpoint diff --git a/src/modules/Elsa.Alterations/Endpoints/Alterations/Submit/Endpoint.cs b/src/modules/Elsa.Alterations/Endpoints/Alterations/Submit/Endpoint.cs index 1fbcb9db4..e7ba1233f 100644 --- a/src/modules/Elsa.Alterations/Endpoints/Alterations/Submit/Endpoint.cs +++ b/src/modules/Elsa.Alterations/Endpoints/Alterations/Submit/Endpoint.cs @@ -8,7 +8,7 @@ using JetBrains.Annotations; namespace Elsa.Alterations.Endpoints.Alterations.Submit; /// -/// Executes an alteration plan. +/// Submits an alteration plan to be executed targeting workflow instances by a filter. /// [PublicAPI] public class Submit : ElsaEndpoint diff --git a/src/modules/Elsa.CSharp/Handlers/GenerateWorkflowInputAccessors.cs b/src/modules/Elsa.CSharp/Handlers/GenerateWorkflowInputAccessors.cs index 1fcf00b72..b8702fdc9 100644 --- a/src/modules/Elsa.CSharp/Handlers/GenerateWorkflowInputAccessors.cs +++ b/src/modules/Elsa.CSharp/Handlers/GenerateWorkflowInputAccessors.cs @@ -49,7 +49,8 @@ public class GenerateWorkflowInputAccessors(IOptions options) : I } sb.AppendLine("}"); - sb.AppendLine("var Inputs = new WorkflowInputsProxy(ExecutionContext);"); + sb.AppendLine("var Inputs = new WorkflowInputsProxy(ExecutionContext);"); // Obsolete; use Input instead. + sb.AppendLine("var Input = Inputs;"); notification.AppendScript(sb.ToString()); return Task.CompletedTask; } diff --git a/src/modules/Elsa.CSharp/Handlers/GenerateWorkflowVariableAccessors.cs b/src/modules/Elsa.CSharp/Handlers/GenerateWorkflowVariableAccessors.cs index e63ed99d5..c7f03d0b1 100644 --- a/src/modules/Elsa.CSharp/Handlers/GenerateWorkflowVariableAccessors.cs +++ b/src/modules/Elsa.CSharp/Handlers/GenerateWorkflowVariableAccessors.cs @@ -48,7 +48,8 @@ public class GenerateWorkflowVariableAccessors(IOptions options) } sb.AppendLine("}"); - sb.AppendLine("var Variables = new WorkflowVariablesProxy(ExecutionContext);"); + sb.AppendLine("var Variables = new WorkflowVariablesProxy(ExecutionContext);"); // Obsolete; use Variable instead. + sb.AppendLine("var Variable = Variables;"); notification.AppendScript(sb.ToString()); return Task.CompletedTask; } diff --git a/src/modules/Elsa.CSharp/Models/Globals.cs b/src/modules/Elsa.CSharp/Models/Globals.cs index 86997a7f7..f86b9693d 100644 --- a/src/modules/Elsa.CSharp/Models/Globals.cs +++ b/src/modules/Elsa.CSharp/Models/Globals.cs @@ -16,7 +16,6 @@ public partial class Globals ExpressionExecutionContext = expressionExecutionContext; Arguments = arguments; ExecutionContext = new ExecutionContextProxy(expressionExecutionContext); - Input = new InputProxy(expressionExecutionContext); Output = new OutputProxy(expressionExecutionContext); Outcome = new OutcomeProxy(expressionExecutionContext); } @@ -31,11 +30,6 @@ public partial class Globals /// public OutputProxy Output { get; set; } - /// - /// Provides access to workflow inputs. - /// - public InputProxy Input { get; set; } - /// /// Gets the current execution context. /// diff --git a/src/modules/Elsa.CSharp/Models/InputProxy.cs b/src/modules/Elsa.CSharp/Models/InputProxy.cs deleted file mode 100644 index 98b16df40..000000000 --- a/src/modules/Elsa.CSharp/Models/InputProxy.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Elsa.Expressions.Models; -using Elsa.Extensions; - -namespace Elsa.CSharp.Models; - -/// -/// Provides access to workflow inputs. -/// -public class InputProxy -{ - private readonly ExpressionExecutionContext _expressionExecutionContext; - - /// - /// Initializes a new instance of the class. - /// - public InputProxy(ExpressionExecutionContext expressionExecutionContext) - { - _expressionExecutionContext = expressionExecutionContext; - } - - /// - /// Gets the value of the specified input. - /// - public object? Get(string name) => _expressionExecutionContext.GetInput(name); - - /// - /// Gets the value of the specified input. - /// - public T? Get(string name) => _expressionExecutionContext.GetInput(name); -} \ No newline at end of file diff --git a/src/modules/Elsa.CSharp/Models/OutputProxy.cs b/src/modules/Elsa.CSharp/Models/OutputProxy.cs index fc7f45ceb..b641aedd5 100644 --- a/src/modules/Elsa.CSharp/Models/OutputProxy.cs +++ b/src/modules/Elsa.CSharp/Models/OutputProxy.cs @@ -25,7 +25,7 @@ public class OutputProxy /// The ID or name of the activity that produced the output. /// The name of the output. /// The value of the output. - public object? From(string activityIdOrName, string? outputName = default) => _expressionExecutionContext.GetOutput(activityIdOrName, outputName); + public object? From(string activityIdOrName, string? outputName = null) => _expressionExecutionContext.GetOutput(activityIdOrName, outputName); /// /// Gets the value of the specified output from the specified activity. @@ -33,7 +33,7 @@ public class OutputProxy /// The ID or name of the activity that produced the output. /// The name of the output. /// The value of the output. - public T? From(string activityIdOrName, string? outputName = default) => Get(activityIdOrName, outputName).ConvertTo(); + public T? From(string activityIdOrName, string? outputName = null) => From(activityIdOrName, outputName).ConvertTo(); /// /// Gets the value of the specified output. @@ -42,7 +42,7 @@ public class OutputProxy /// The name of the output. /// The value of the output. [Obsolete("Use From instead.")] - public object? Get(string activityIdOrName, string? outputName = default) => From(activityIdOrName, outputName); + public object? Get(string activityIdOrName, string? outputName = null) => From(activityIdOrName, outputName); /// /// Gets the value of the specified output. @@ -51,7 +51,7 @@ public class OutputProxy /// The name of the output. /// The value of the output. [Obsolete("Use From instead.")] - public T? Get(string activityIdOrName, string? outputName = default) => From(activityIdOrName, outputName); + public T? Get(string activityIdOrName, string? outputName = null) => From(activityIdOrName, outputName); /// /// Gets the result of the last activity that executed. diff --git a/src/modules/Elsa.Common/Elsa.Common.csproj b/src/modules/Elsa.Common/Elsa.Common.csproj index 7899635c9..654e988e5 100644 --- a/src/modules/Elsa.Common/Elsa.Common.csproj +++ b/src/modules/Elsa.Common/Elsa.Common.csproj @@ -21,8 +21,4 @@ - - - - diff --git a/src/modules/Elsa.Common/Extensions/ServiceCollectionExtensions.cs b/src/modules/Elsa.Common/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..c5088a123 --- /dev/null +++ b/src/modules/Elsa.Common/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,37 @@ +namespace Elsa.Extensions; + +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Linq; + +public static class ServiceCollectionExtensions +{ + /// + /// Adds the service with a specific implementation type only if the combination + /// of service and implementation does not already exist in the service collection. + /// + public static IServiceCollection TryAddScopedImplementation( + this IServiceCollection services) + where TService : class + where TImplementation : class, TService + { + if (!services.Any(sd => sd.ServiceType == typeof(TService) && sd.ImplementationType == typeof(TImplementation))) + services.AddScoped(); + + return services; + } + + /// + /// Adds the service with a specific implementation factory only if the combination + /// of service and implementation already doesn't exist. + /// + public static IServiceCollection TryAddScopedImplementation( + this IServiceCollection services, Func implementationFactory) + where TService : class + { + if (services.All(sd => sd.ServiceType != typeof(TService))) + services.AddScoped(implementationFactory); + + return services; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Common/Features/MultitenancyFeature.cs b/src/modules/Elsa.Common/Features/MultitenancyFeature.cs index 84facff7b..640e6183e 100644 --- a/src/modules/Elsa.Common/Features/MultitenancyFeature.cs +++ b/src/modules/Elsa.Common/Features/MultitenancyFeature.cs @@ -2,7 +2,6 @@ using Elsa.Common.Multitenancy; using Elsa.Common.Multitenancy.EventHandlers; using Elsa.Common.Multitenancy.HostedServices; using Elsa.Common.RecurringTasks; -using Elsa.Extensions; using Elsa.Features.Abstractions; using Elsa.Features.Services; using Microsoft.Extensions.DependencyInjection; @@ -41,6 +40,9 @@ public class MultitenancyFeature(IModule module) : FeatureBase(module) .AddSingleton() .AddSingleton() + // Order is important: Startup task first, then background and recurring tasks. + .AddSingleton() + .AddSingleton() .AddSingleton(sp => sp.GetRequiredService()) .AddSingleton(sp => sp.GetRequiredService()) @@ -49,10 +51,8 @@ public class MultitenancyFeature(IModule module) : FeatureBase(module) .AddSingleton(sp => sp.GetRequiredService()) .AddSingleton(sp => sp.GetRequiredService()) - .AddSingleton() .AddSingleton() .AddSingleton() - .AddStartupTask() .AddScoped() .AddScoped() .AddScoped() diff --git a/src/modules/Elsa.Common/Multitenancy/EventHandlers/StartRecurringTasks.cs b/src/modules/Elsa.Common/Multitenancy/EventHandlers/StartRecurringTasks.cs index c89c79c70..f83479af9 100644 --- a/src/modules/Elsa.Common/Multitenancy/EventHandlers/StartRecurringTasks.cs +++ b/src/modules/Elsa.Common/Multitenancy/EventHandlers/StartRecurringTasks.cs @@ -7,7 +7,7 @@ namespace Elsa.Common.Multitenancy.EventHandlers; public class StartRecurringTasks(RecurringTaskScheduleManager scheduleManager, ILogger logger) : ITenantActivatedEvent, ITenantDeactivatedEvent { private readonly ICollection _scheduledTimers = new List(); - private CancellationTokenSource _cancellationTokenSource = default!; + private CancellationTokenSource _cancellationTokenSource = null!; public async Task TenantActivatedAsync(TenantActivatedEventArgs args) { @@ -16,7 +16,7 @@ public class StartRecurringTasks(RecurringTaskScheduleManager scheduleManager, I var tenantScope = args.TenantScope; var tasks = tenantScope.ServiceProvider.GetServices().ToList(); var taskExecutor = tenantScope.ServiceProvider.GetRequiredService(); - + foreach (var task in tasks) { var schedule = scheduleManager.GetScheduleFor(task.GetType()); diff --git a/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantAccessor.cs b/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantAccessor.cs index fd8a699ba..146dfd451 100644 --- a/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantAccessor.cs +++ b/src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantAccessor.cs @@ -11,7 +11,7 @@ public class DefaultTenantAccessor : ITenantAccessor public Tenant? Tenant { get => CurrentTenantField.Value; - internal set => CurrentTenantField.Value = value; + private set => CurrentTenantField.Value = value; } public IDisposable PushContext(Tenant? tenant) diff --git a/src/modules/Elsa.Common/RecurringTasks/ConfigureRecurringTasksScheduleStartupTask.cs b/src/modules/Elsa.Common/RecurringTasks/ConfigureRecurringTasksScheduleStartupTask.cs deleted file mode 100644 index a376f3b8e..000000000 --- a/src/modules/Elsa.Common/RecurringTasks/ConfigureRecurringTasksScheduleStartupTask.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Cronos; -using JetBrains.Annotations; -using Microsoft.Extensions.Options; - -namespace Elsa.Common.RecurringTasks; - -[UsedImplicitly] -public class ConfigureRecurringTasksScheduleStartupTask(IOptions options, ISystemClock systemClock, RecurringTaskScheduleManager recurringTaskScheduleManager) : IStartupTask -{ - public Task ExecuteAsync(CancellationToken cancellationToken) - { - foreach(var entry in options.Value.Schedule.ScheduledTasks) - { - var taskType = entry.Key; - var intervalExpression = entry.Value; - var schedule = intervalExpression.Type switch - { - IntervalExpressionType.Cron => (ISchedule)new CronSchedule(systemClock, CronExpression.Parse(intervalExpression.Expression)), - IntervalExpressionType.Interval => new IntervalSchedule(TimeSpan.Parse(intervalExpression.Expression)), - _ => throw new NotSupportedException($"Interval expression type '{intervalExpression.Type}' is not supported.") - }; - recurringTaskScheduleManager.ConfigureScheduledTask(taskType, schedule); - } - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Common/RecurringTasks/RecurringTaskScheduleManager.cs b/src/modules/Elsa.Common/RecurringTasks/RecurringTaskScheduleManager.cs index b5418383f..fbb286d56 100644 --- a/src/modules/Elsa.Common/RecurringTasks/RecurringTaskScheduleManager.cs +++ b/src/modules/Elsa.Common/RecurringTasks/RecurringTaskScheduleManager.cs @@ -1,21 +1,31 @@ +using Cronos; +using Microsoft.Extensions.Options; + namespace Elsa.Common.RecurringTasks; -public class RecurringTaskScheduleManager +public class RecurringTaskScheduleManager(IOptions options, ISystemClock systemClock) { public IDictionary ScheduledTasks { get; set; } = new Dictionary(); - public void ConfigureScheduledTask(ISchedule schedule) where T : IRecurringTask - { - ConfigureScheduledTask(typeof(T), schedule); - } - - public void ConfigureScheduledTask(Type recurringTaskType, ISchedule schedule) - { - ScheduledTasks[recurringTaskType] = schedule; - } - public ISchedule GetScheduleFor(Type taskType) { - return ScheduledTasks.TryGetValue(taskType, out var schedule) ? schedule : new IntervalSchedule(TimeSpan.FromMinutes(1)); + if (!ScheduledTasks.TryGetValue(taskType, out var schedule)) + { + var intervalExpression = options.Value.Schedule.ScheduledTasks.TryGetValue(taskType, out var expr) ? expr : null; + schedule = intervalExpression != null ? CreateSchedule(intervalExpression) : new IntervalSchedule(TimeSpan.FromMinutes(1)); + ScheduledTasks[taskType] = schedule; + } + + return schedule; + } + + private ISchedule CreateSchedule(IntervalExpression intervalExpression) + { + return intervalExpression.Type switch + { + IntervalExpressionType.Cron => new CronSchedule(systemClock, CronExpression.Parse(intervalExpression.Expression)), + IntervalExpressionType.Interval => new IntervalSchedule(TimeSpan.Parse(intervalExpression.Expression)), + _ => throw new NotSupportedException($"Interval expression type '{intervalExpression.Type}' is not supported.") + }; } } \ No newline at end of file diff --git a/src/modules/Elsa.Common/Serialization/TypeTypeConverter.cs b/src/modules/Elsa.Common/Serialization/TypeTypeConverter.cs index 4d89b6c5e..63e1b210e 100644 --- a/src/modules/Elsa.Common/Serialization/TypeTypeConverter.cs +++ b/src/modules/Elsa.Common/Serialization/TypeTypeConverter.cs @@ -18,7 +18,7 @@ public class TypeTypeConverter : TypeConverter { if (TypeAliasRegistry.GetType(stringValue) is { } type) return type; - return Type.GetType(stringValue); + return Type.GetType(stringValue) ?? throw new InvalidOperationException($"Type '{stringValue}' not found."); } return base.ConvertFrom(context, culture, value); } diff --git a/src/modules/Elsa.Dapper.Migrations/Runtime/V3_3.cs b/src/modules/Elsa.Dapper.Migrations/Runtime/V3_3.cs index 2cbb9a16f..bbe82ae9f 100644 --- a/src/modules/Elsa.Dapper.Migrations/Runtime/V3_3.cs +++ b/src/modules/Elsa.Dapper.Migrations/Runtime/V3_3.cs @@ -14,12 +14,12 @@ public class V3_3 : Migration /// public override void Up() { - Delete.Table("WorkflowInboxMessages"); Alter.Table("Triggers").AddColumn("TenantId").AsString().Nullable(); Alter.Table("Bookmarks").AddColumn("TenantId").AsString().Nullable(); Alter.Table("WorkflowExecutionLogRecords").AddColumn("TenantId").AsString().Nullable(); Alter.Table("ActivityExecutionRecords").AddColumn("TenantId").AsString().Nullable(); Alter.Table("KeyValuePairs").AddColumn("TenantId").AsString().Nullable(); + Rename.Column("Key").OnTable("KeyValuePairs").To("Id"); IfDatabase("SqlServer", "Oracle", "MySql", "Postgres") .Create @@ -56,37 +56,7 @@ public class V3_3 : Migration Delete.Column("TenantId").FromTable("WorkflowExecutionLogRecords"); Delete.Column("TenantId").FromTable("ActivityExecutionRecords"); Delete.Column("TenantId").FromTable("KeyValuePairs"); - - IfDatabase("SqlServer", "Oracle", "MySql", "Postgres") - .Create - .Table("WorkflowInboxMessages") - .WithColumn("Id").AsString().PrimaryKey() - .WithColumn("ActivityTypeName").AsString().NotNullable().Indexed() - .WithColumn("WorkflowInstanceId").AsString().Nullable().Indexed() - .WithColumn("ActivityInstanceId").AsString().Nullable().Indexed() - .WithColumn("CorrelationId").AsString().Nullable().Indexed() - .WithColumn("Hash").AsString().NotNullable().Indexed() - .WithColumn("SerializedBookmarkPayload").AsString(MaxValue) - .WithColumn("SerializedInput").AsString(MaxValue).Nullable() - .WithColumn("CreatedAt").AsDateTimeOffset().Indexed() - .WithColumn("ExpiresAt").AsDateTimeOffset().Indexed() - ; - - IfDatabase("Sqlite") - .Create - .Table("WorkflowInboxMessages") - .WithColumn("Id").AsString().PrimaryKey() - .WithColumn("ActivityTypeName").AsString().NotNullable().Indexed() - .WithColumn("WorkflowInstanceId").AsString().Nullable().Indexed() - .WithColumn("ActivityInstanceId").AsString().Nullable().Indexed() - .WithColumn("CorrelationId").AsString().Nullable().Indexed() - .WithColumn("Hash").AsString().NotNullable().Indexed() - .WithColumn("SerializedBookmarkPayload").AsString(MaxValue) - .WithColumn("SerializedInput").AsString(MaxValue).Nullable() - .WithColumn("CreatedAt").AsDateTime2().Indexed() - .WithColumn("ExpiresAt").AsDateTime2().Indexed() - ; - + Rename.Column("Id").OnTable("KeyValuePairs").To("Key"); Delete.Table("BookmarkQueueItems"); } } \ No newline at end of file diff --git a/src/modules/Elsa.Email/Activities/SendEmail.cs b/src/modules/Elsa.Email/Activities/SendEmail.cs index c1e88afcb..db68773ca 100644 --- a/src/modules/Elsa.Email/Activities/SendEmail.cs +++ b/src/modules/Elsa.Email/Activities/SendEmail.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Dynamic; using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; @@ -84,8 +85,7 @@ public class SendEmail : Activity /// /// The activity to execute when an error occurs while trying to send the email. /// - [Port] - public IActivity? Error { get; set; } + [Port] public IActivity? Error { get; set; } /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) @@ -99,7 +99,10 @@ public class SendEmail : Activity message.From.Add(MailboxAddress.Parse(from)); message.Subject = Subject.GetOrDefault(context) ?? ""; - var bodyBuilder = new BodyBuilder { HtmlBody = Body.GetOrDefault(context) }; + var bodyBuilder = new BodyBuilder + { + HtmlBody = Body.GetOrDefault(context) + }; await AddAttachmentsAsync(context, bodyBuilder, cancellationToken); message.Body = bodyBuilder.ToMessageBody(); @@ -119,7 +122,10 @@ public class SendEmail : Activity catch (Exception e) { logger.LogWarning(e, "Error while sending email message"); - context.AddExecutionLogEntry("Error", e.Message, payload: new { e.StackTrace }); + context.AddExecutionLogEntry("Error", e.Message, payload: new + { + e.StackTrace + }); await context.ScheduleActivityAsync(Error, OnErrorCompletedAsync); } } @@ -155,38 +161,54 @@ public class SendEmail : Activity await AttachLocalFileAsync(bodyBuilder, path, cancellationToken); break; case byte[] bytes: - { - var fileName = $"Attachment-{++index}"; - bodyBuilder.Attachments.Add(fileName, bytes, ContentType.Parse("application/binary")); - break; - } + { + var fileName = $"Attachment-{++index}"; + bodyBuilder.Attachments.Add(fileName, bytes, ContentType.Parse("application/binary")); + break; + } case Stream stream: - { - var fileName = $"Attachment-{++index}"; - await bodyBuilder.Attachments.AddAsync(fileName, stream, ContentType.Parse("application/binary"), cancellationToken); - break; - } + { + var fileName = $"Attachment-{++index}"; + await bodyBuilder.Attachments.AddAsync(fileName, stream, ContentType.Parse("application/binary"), cancellationToken); + break; + } case EmailAttachment emailAttachment: - { - var fileName = emailAttachment.FileName ?? $"Attachment-{++index}"; - var contentType = emailAttachment.ContentType ?? "application/binary"; - var parsedContentType = ContentType.Parse(contentType); + { + var fileName = emailAttachment.FileName ?? $"Attachment-{++index}"; + var contentType = emailAttachment.ContentType ?? "application/binary"; + var parsedContentType = ContentType.Parse(contentType); - if (emailAttachment.Content is byte[] bytes) - bodyBuilder.Attachments.Add(fileName, bytes, parsedContentType); + if (emailAttachment.Content is byte[] bytes) + bodyBuilder.Attachments.Add(fileName, bytes, parsedContentType); - else if (emailAttachment.Content is Stream stream) - await bodyBuilder.Attachments.AddAsync(fileName, stream, parsedContentType, cancellationToken); + else if (emailAttachment.Content is Stream stream) + await bodyBuilder.Attachments.AddAsync(fileName, stream, parsedContentType, cancellationToken); - break; - } + break; + } + case ExpandoObject expandoObject: + { + var dictionary = new Dictionary(expandoObject, StringComparer.OrdinalIgnoreCase); + var fileName = dictionary.GetValue("FileName") ?? $"Attachment-{++index}"; + var contentType = dictionary.GetValue("ContentType") ?? "application/binary"; + var parsedContentType = ContentType.Parse(contentType); + var content = dictionary.GetValue("Content"); + + if (content is byte[] bytes) + bodyBuilder.Attachments.Add(fileName, bytes, parsedContentType); + + else if (content is Stream stream) + await bodyBuilder.Attachments.AddAsync(fileName, stream, parsedContentType, cancellationToken); + + break; + } default: - { - var json = JsonSerializer.Serialize(attachmentObject); - var fileName = $"Attachment-{++index}"; - bodyBuilder.Attachments.Add(fileName, Encoding.UTF8.GetBytes(json), ContentType.Parse("application/json")); - break; - } + { + var json = JsonSerializer.Serialize(attachmentObject); + var fileName = $"Attachment-{++index}"; + bodyBuilder.Attachments.Add(fileName, Encoding.UTF8.GetBytes(json), ContentType.Parse("application/json")); + break; + } } } } @@ -203,7 +225,24 @@ public class SendEmail : Activity await bodyBuilder.Attachments.AddAsync(fileName, contentStream, ContentType.Parse(contentType), cancellationToken); } - private IEnumerable InterpretAttachmentsModel(object attachments) => attachments is string text ? new[] { text } : attachments is IEnumerable enumerable ? enumerable : new[] { attachments }; + private IEnumerable InterpretAttachmentsModel(object attachments) + { + if (attachments is byte[] bytes) + return new[] + { + bytes + }; + + return attachments is string text + ? new[] + { + text + } + : attachments as IEnumerable ?? new[] + { + attachments + }; + } private void SetRecipientsEmailAddresses(InternetAddressList list, IEnumerable? addresses) { diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/Abstractions/DesignTimeDbContextFactoryBase.cs b/src/modules/Elsa.EntityFrameworkCore.Common/Abstractions/DesignTimeDbContextFactoryBase.cs index fe1951e6e..df23d655f 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Common/Abstractions/DesignTimeDbContextFactoryBase.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Common/Abstractions/DesignTimeDbContextFactoryBase.cs @@ -27,7 +27,7 @@ public abstract class DesignTimeDbContextFactoryBase : IDesignTimeDb ConfigureBuilder(builder, connectionString); - return (TDbContext)Activator.CreateInstance(typeof(TDbContext), builder.Options, serviceProvider)!; + return (TDbContext)ActivatorUtilities.CreateInstance(serviceProvider, typeof(TDbContext), builder.Options); } /// diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/CommonPersistenceFeature.cs b/src/modules/Elsa.EntityFrameworkCore.Common/CommonPersistenceFeature.cs index 42f681a5d..835574f21 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Common/CommonPersistenceFeature.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Common/CommonPersistenceFeature.cs @@ -13,6 +13,5 @@ public class CommonPersistenceFeature(IModule module) : FeatureBase(module) { Services.AddScoped(); Services.AddScoped(); - Services.AddScoped(); } } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/DbSchemaAwareMigrationAssembly.cs b/src/modules/Elsa.EntityFrameworkCore.Common/DbSchemaAwareMigrationAssembly.cs index 21ab52188..5e31b710e 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Common/DbSchemaAwareMigrationAssembly.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Common/DbSchemaAwareMigrationAssembly.cs @@ -10,17 +10,14 @@ namespace Elsa.EntityFrameworkCore; /// /// Class That enable Schema change for Migration /// -public class DbSchemaAwareMigrationAssembly : MigrationsAssembly +public class DbSchemaAwareMigrationAssembly( + ICurrentDbContext currentContext, + IDbContextOptions options, + IMigrationsIdGenerator idGenerator, + IDiagnosticsLogger logger) + : MigrationsAssembly(currentContext, options, idGenerator, logger) { - private readonly DbContext _context; - - public DbSchemaAwareMigrationAssembly(ICurrentDbContext currentContext, - IDbContextOptions options, IMigrationsIdGenerator idGenerator, - IDiagnosticsLogger logger) - : base(currentContext, options, idGenerator, logger) - { - _context = currentContext.Context; - } + private readonly DbContext _context = currentContext.Context; public override Migration CreateMigration(TypeInfo migrationClass, string activeProvider) { diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/Elsa.EntityFrameworkCore.Common.csproj b/src/modules/Elsa.EntityFrameworkCore.Common/Elsa.EntityFrameworkCore.Common.csproj index 723566266..e3f4635d9 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Common/Elsa.EntityFrameworkCore.Common.csproj +++ b/src/modules/Elsa.EntityFrameworkCore.Common/Elsa.EntityFrameworkCore.Common.csproj @@ -12,7 +12,6 @@ - diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/ElsaDbContextBase.cs b/src/modules/Elsa.EntityFrameworkCore.Common/ElsaDbContextBase.cs index eef02af6e..5b9b7557b 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Common/ElsaDbContextBase.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Common/ElsaDbContextBase.cs @@ -2,6 +2,7 @@ using Elsa.Common.Entities; using Elsa.Common.Multitenancy; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.DependencyInjection; namespace Elsa.EntityFrameworkCore; @@ -16,8 +17,9 @@ public abstract class ElsaDbContextBase : DbContext, IElsaDbContextSchema EntityState.Added, EntityState.Modified, }; - - protected readonly IServiceProvider ServiceProvider; + + protected IServiceProvider ServiceProvider { get; } + private readonly ElsaDbContextOptions? _elsaDbContextOptions; public string? TenantId { get; set; } /// @@ -39,18 +41,18 @@ public abstract class ElsaDbContextBase : DbContext, IElsaDbContextSchema protected ElsaDbContextBase(DbContextOptions options, IServiceProvider serviceProvider) : base(options) { ServiceProvider = serviceProvider; - var elsaDbContextOptions = options.FindExtension()?.Options; - - // ReSharper disable once VirtualMemberCallInConstructor - Schema = !string.IsNullOrWhiteSpace(elsaDbContextOptions?.SchemaName) ? elsaDbContextOptions.SchemaName : ElsaSchema; + _elsaDbContextOptions = options.FindExtension()?.Options; + // ReSharper disable once VirtualMemberCallInConstructor + Schema = !string.IsNullOrWhiteSpace(_elsaDbContextOptions?.SchemaName) ? _elsaDbContextOptions.SchemaName : ElsaSchema; + var tenantAccessor = serviceProvider.GetService(); var tenantId = tenantAccessor?.Tenant?.Id; - - if(!string.IsNullOrWhiteSpace(tenantId)) + + if (!string.IsNullOrWhiteSpace(tenantId)) TenantId = tenantId; } - + /// public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) { @@ -58,26 +60,33 @@ public abstract class ElsaDbContextBase : DbContext, IElsaDbContextSchema return await base.SaveChangesAsync(cancellationToken); } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.EnableSensitiveDataLogging(); +#if NET9_0_OR_GREATER + optionsBuilder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); +#endif + } + /// protected override void OnModelCreating(ModelBuilder modelBuilder) { - if (!string.IsNullOrWhiteSpace(Schema)) - { - if (!Database.IsSqlite()) - modelBuilder.HasDefaultSchema(Schema); - } + if (!string.IsNullOrWhiteSpace(Schema)) + modelBuilder.HasDefaultSchema(Schema); + + var additionalConfigurations = _elsaDbContextOptions?.GetModelConfigurations(this); + additionalConfigurations?.Invoke(modelBuilder); + var entityTypeHandlers = ServiceProvider.GetServices().ToList(); - foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + foreach (var entityType in modelBuilder.Model.GetEntityTypes().ToList()) { - foreach (var handler in entityTypeHandlers) - { + foreach (var handler in entityTypeHandlers) handler.Handle(this, modelBuilder, entityType); - } } } - + private async Task OnBeforeSavingAsync(CancellationToken cancellationToken) { var handlers = ServiceProvider.GetServices().ToList(); diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/ElsaDbContextOptions.cs b/src/modules/Elsa.EntityFrameworkCore.Common/ElsaDbContextOptions.cs index ad185a396..cd6af7271 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Common/ElsaDbContextOptions.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Common/ElsaDbContextOptions.cs @@ -1,4 +1,5 @@ using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; namespace Elsa.EntityFrameworkCore; @@ -22,4 +23,30 @@ public class ElsaDbContextOptions /// The assembly name containing the migrations. /// public string? MigrationsAssemblyName { get; set; } + + public IDictionary> ProviderSpecificConfigurations { get; set; } = new Dictionary>(); + + public void ConfigureModel(Action configure) where TDbContext : DbContext + { + ConfigureModel(typeof(TDbContext), configure); + } + + public void ConfigureModel(Type dbContextType, Action configure) + { + if (!ProviderSpecificConfigurations.TryGetValue(dbContextType, out var configurations)) + ProviderSpecificConfigurations[dbContextType] = configurations = _ => { }; + + configurations += configure; + ProviderSpecificConfigurations[dbContextType] = configurations; + } + + public Action GetModelConfigurations(DbContext dbContext) + { + return GetModelConfigurations(dbContext.GetType()); + } + + public Action GetModelConfigurations(Type dbContextType) + { + return ProviderSpecificConfigurations.TryGetValue(dbContextType, out var providerConfigurations) ? providerConfigurations : _ => { }; + } } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/PersistenceFeatureBase.cs b/src/modules/Elsa.EntityFrameworkCore.Common/PersistenceFeatureBase.cs index 031f69397..5df4f3a07 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Common/PersistenceFeatureBase.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Common/PersistenceFeatureBase.cs @@ -39,11 +39,7 @@ public abstract class PersistenceFeatureBase : FeatureBase /// /// Gets or sets the callback used to configure the . /// - public Action DbContextOptionsBuilder = (_, options) => options - .UseElsaDbContextOptions(default) - .UseSqlite("Data Source=elsa.sqlite.db;Cache=Shared;", sqlite => sqlite - .MigrationsAssembly("Elsa.EntityFrameworkCore.Sqlite") - .MigrationsHistoryTable(ElsaDbContextBase.MigrationsHistoryTable, ElsaDbContextBase.ElsaSchema)); + public virtual Action DbContextOptionsBuilder { get; set; } = null!; public override void ConfigureHostedServices() { @@ -54,6 +50,9 @@ public abstract class PersistenceFeatureBase : FeatureBase /// public override void Apply() { + if(DbContextOptionsBuilder == null) + throw new InvalidOperationException("The DbContextOptionsBuilder must be configured."); + if (UseContextPooling) Services.AddPooledDbContextFactory(DbContextOptionsBuilder); else diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsStartupTask.cs b/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsStartupTask.cs index f55e3b647..45f9c4a5e 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsStartupTask.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Common/RunMigrationsStartupTask.cs @@ -16,7 +16,7 @@ public class RunMigrationsStartupTask(IDbContextFactory /// (IDbContextFactory dbContextF Func? onSaving = default, CancellationToken cancellationToken = default) { - await using var dbContext = await CreateDbContextAsync(cancellationToken); var entityList = entities.ToList(); + if (entityList.Count == 0) + return; + + await using var dbContext = await CreateDbContextAsync(cancellationToken); + if (onSaving != null) { var savingTasks = entityList.Select(entity => onSaving(dbContext, entity, cancellationToken).AsTask()).ToList(); @@ -162,9 +166,13 @@ public class Store(IDbContextFactory dbContextF Func? onSaving = default, CancellationToken cancellationToken = default) { - await using var dbContext = await CreateDbContextAsync(cancellationToken); var entityList = entities.ToList(); + if (entityList.Count == 0) + return; + + await using var dbContext = await CreateDbContextAsync(cancellationToken); + if (onSaving != null) { var savingTasks = entityList.Select(entity => onSaving(dbContext, entity, cancellationToken).AsTask()).ToList(); diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/DbContextOptionsBuilder.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/DbContextOptionsBuilder.cs index 8e64723ff..403d09f77 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/DbContextOptionsBuilder.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/DbContextOptionsBuilder.cs @@ -14,8 +14,12 @@ public static class DbContextOptionsBuilderExtensions /// /// Configures Entity Framework Core with MySQL. /// - public static DbContextOptionsBuilder UseElsaMySql(this DbContextOptionsBuilder builder, Assembly migrationsAssembly, string connectionString, - ElsaDbContextOptions? options = default, ServerVersion? serverVersion = default, Action? configure = default) => + public static DbContextOptionsBuilder UseElsaMySql(this DbContextOptionsBuilder builder, + Assembly migrationsAssembly, + string connectionString, + ElsaDbContextOptions? options = null, + ServerVersion? serverVersion = null, + Action? configure = null) => builder .UseElsaDbContextOptions(options) .UseMySql(connectionString, serverVersion ?? ServerVersion.AutoDetect(connectionString), db => @@ -23,7 +27,12 @@ public static class DbContextOptionsBuilderExtensions db .MigrationsAssembly(options.GetMigrationsAssemblyName(migrationsAssembly)) .MigrationsHistoryTable(options.GetMigrationsHistoryTableName(), options.GetSchemaName()) - .SchemaBehavior(MySqlSchemaBehavior.Ignore); + .SchemaBehavior(MySqlSchemaBehavior.Ignore) + .EnablePrimitiveCollectionsSupport() +#if NET9_0 + .TranslateParameterizedCollectionsToConstants() +#endif + ; configure?.Invoke(db); }); diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Elsa.EntityFrameworkCore.MySql.csproj b/src/modules/Elsa.EntityFrameworkCore.MySql/Elsa.EntityFrameworkCore.MySql.csproj index c8ad4abb8..97efa0a73 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Elsa.EntityFrameworkCore.MySql.csproj +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Elsa.EntityFrameworkCore.MySql.csproj @@ -1,7 +1,7 @@ - net6.0;net8.0 + net8.0;net9.0 Provides MySQL EF Core migrations for various modules. diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20231024160940_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20231024160940_Initial.cs index 29faf3e35..cf3eb698a 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20231024160940_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20231024160940_Initial.cs @@ -1,4 +1,4 @@ - +using Elsa.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240329200651_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240329200651_V3_1.cs index dcda40b23..0092d23c1 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240329200651_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240329200651_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime /// public partial class V3_1 : Migration { - private readonly IElsaDbContextSchema _schema; + private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; /// - public V3_1(IElsaDbContextSchema schema) + public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240610184652_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240610184652_V3_2.cs index 891b4ba00..978783615 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240610184652_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20240610184652_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime /// public partial class V3_2 : Migration { - private readonly IElsaDbContextSchema _schema; + private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; /// - public V3_2(IElsaDbContextSchema schema) + public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241212211632_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20250116192733_V3_3.Designer.cs similarity index 98% rename from src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241212211632_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20250116192733_V3_3.Designer.cs index 16473bb4d..cd2cae34e 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241212211632_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20250116192733_V3_3.Designer.cs @@ -3,6 +3,7 @@ using System; using Elsa.EntityFrameworkCore.Modules.Runtime; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -11,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime { [DbContext(typeof(RuntimeElsaDbContext))] - [Migration("20241212211632_V3_3")] + [Migration("20250116192733_V3_3")] partial class V3_3 { /// @@ -20,18 +21,16 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.11") .HasAnnotation("Relational:MaxIdentifierLength", 64); + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + modelBuilder.Entity("Elsa.KeyValues.Entities.SerializedKeyValuePair", b => { b.Property("Id") .HasColumnType("varchar(255)"); - b.Property("Key") - .IsRequired() - .HasColumnType("longtext"); - b.Property("SerializedValue") .IsRequired() .HasColumnType("longtext"); @@ -213,10 +212,6 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime .IsRequired() .HasColumnType("varchar(255)"); - b.Property("BookmarkId") - .IsRequired() - .HasColumnType("longtext"); - b.Property("CorrelationId") .HasColumnType("longtext"); diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241212211632_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20250116192733_V3_3.cs similarity index 72% rename from src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241212211632_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20250116192733_V3_3.cs index 1bd14cf16..d1dc13b38 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20241212211632_V3_3.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/20250116192733_V3_3.cs @@ -19,15 +19,17 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime /// protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.DropPrimaryKey( - name: "PK_KeyValuePairs", + migrationBuilder.RenameColumn( + name: "Key", schema: _schema.Schema, - table: "KeyValuePairs"); + table: "KeyValuePairs", + newName: "Id"); - migrationBuilder.DropPrimaryKey( - name: "PK_Bookmarks", + migrationBuilder.RenameColumn( + name: "BookmarkId", schema: _schema.Schema, - table: "Bookmarks"); + table: "Bookmarks", + newName: "Id"); migrationBuilder.AddColumn( name: "TenantId", @@ -53,26 +55,6 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime nullable: true) .Annotation("MySql:CharSet", "utf8mb4"); - migrationBuilder.AlterColumn( - name: "Key", - schema: _schema.Schema, - table: "KeyValuePairs", - type: "longtext", - nullable: false, - oldClrType: typeof(string), - oldType: "varchar(255)") - .Annotation("MySql:CharSet", "utf8mb4") - .OldAnnotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.AddColumn( - name: "Id", - schema: _schema.Schema, - table: "KeyValuePairs", - type: "varchar(255)", - nullable: false, - defaultValue: "") - .Annotation("MySql:CharSet", "utf8mb4"); - migrationBuilder.AddColumn( name: "TenantId", schema: _schema.Schema, @@ -81,26 +63,6 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime nullable: true) .Annotation("MySql:CharSet", "utf8mb4"); - migrationBuilder.AlterColumn( - name: "BookmarkId", - schema: _schema.Schema, - table: "Bookmarks", - type: "longtext", - nullable: false, - oldClrType: typeof(string), - oldType: "varchar(255)") - .Annotation("MySql:CharSet", "utf8mb4") - .OldAnnotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.AddColumn( - name: "Id", - schema: _schema.Schema, - table: "Bookmarks", - type: "varchar(255)", - nullable: false, - defaultValue: "") - .Annotation("MySql:CharSet", "utf8mb4"); - migrationBuilder.AddColumn( name: "TenantId", schema: _schema.Schema, @@ -117,18 +79,6 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime nullable: true) .Annotation("MySql:CharSet", "utf8mb4"); - migrationBuilder.AddPrimaryKey( - name: "PK_KeyValuePairs", - schema: _schema.Schema, - table: "KeyValuePairs", - column: "Id"); - - migrationBuilder.AddPrimaryKey( - name: "PK_Bookmarks", - schema: _schema.Schema, - table: "Bookmarks", - column: "Id"); - migrationBuilder.CreateTable( name: "BookmarkQueueItems", schema: _schema.Schema, @@ -256,21 +206,11 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime schema: _schema.Schema, table: "Triggers"); - migrationBuilder.DropPrimaryKey( - name: "PK_KeyValuePairs", - schema: _schema.Schema, - table: "KeyValuePairs"); - migrationBuilder.DropIndex( name: "IX_SerializedKeyValuePair_TenantId", schema: _schema.Schema, table: "KeyValuePairs"); - migrationBuilder.DropPrimaryKey( - name: "PK_Bookmarks", - schema: _schema.Schema, - table: "Bookmarks"); - migrationBuilder.DropIndex( name: "IX_StoredBookmark_TenantId", schema: _schema.Schema, @@ -296,21 +236,11 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime schema: _schema.Schema, table: "Triggers"); - migrationBuilder.DropColumn( - name: "Id", - schema: _schema.Schema, - table: "KeyValuePairs"); - migrationBuilder.DropColumn( name: "TenantId", schema: _schema.Schema, table: "KeyValuePairs"); - migrationBuilder.DropColumn( - name: "Id", - schema: _schema.Schema, - table: "Bookmarks"); - migrationBuilder.DropColumn( name: "TenantId", schema: _schema.Schema, @@ -321,39 +251,17 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime schema: _schema.Schema, table: "ActivityExecutionRecords"); - migrationBuilder.AlterColumn( - name: "Key", + migrationBuilder.RenameColumn( + name: "Id", schema: _schema.Schema, table: "KeyValuePairs", - type: "varchar(255)", - nullable: false, - oldClrType: typeof(string), - oldType: "longtext") - .Annotation("MySql:CharSet", "utf8mb4") - .OldAnnotation("MySql:CharSet", "utf8mb4"); + newName: "Key"); - migrationBuilder.AlterColumn( - name: "BookmarkId", + migrationBuilder.RenameColumn( + name: "Id", schema: _schema.Schema, table: "Bookmarks", - type: "varchar(255)", - nullable: false, - oldClrType: typeof(string), - oldType: "longtext") - .Annotation("MySql:CharSet", "utf8mb4") - .OldAnnotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.AddPrimaryKey( - name: "PK_KeyValuePairs", - schema: _schema.Schema, - table: "KeyValuePairs", - column: "Key"); - - migrationBuilder.AddPrimaryKey( - name: "PK_Bookmarks", - schema: _schema.Schema, - table: "Bookmarks", - column: "BookmarkId"); + newName: "BookmarkId"); } } } diff --git a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs index b82e19325..e384a181b 100644 --- a/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs +++ b/src/modules/Elsa.EntityFrameworkCore.MySql/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs @@ -3,6 +3,7 @@ using System; using Elsa.EntityFrameworkCore.Modules.Runtime; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable @@ -17,18 +18,16 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.11") .HasAnnotation("Relational:MaxIdentifierLength", 64); + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + modelBuilder.Entity("Elsa.KeyValues.Entities.SerializedKeyValuePair", b => { b.Property("Id") .HasColumnType("varchar(255)"); - b.Property("Key") - .IsRequired() - .HasColumnType("longtext"); - b.Property("SerializedValue") .IsRequired() .HasColumnType("longtext"); @@ -210,10 +209,6 @@ namespace Elsa.EntityFrameworkCore.MySql.Migrations.Runtime .IsRequired() .HasColumnType("varchar(255)"); - b.Property("BookmarkId") - .IsRequired() - .HasColumnType("longtext"); - b.Property("CorrelationId") .HasColumnType("longtext"); diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Configurations/Management.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Configurations/Management.cs new file mode 100644 index 000000000..6f9d6e195 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Configurations/Management.cs @@ -0,0 +1,26 @@ +using Elsa.Workflows.Management.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Elsa.EntityFrameworkCore.Oracle.Configurations; + +internal class Management : IEntityTypeConfiguration, IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + // In order to use data more than 2000 char we have to use NCLOB. + // In Oracle, we have to explicitly say the column is NCLOB otherwise it would be considered nvarchar(2000). + builder.Property("StringData").HasColumnType("NCLOB"); + builder.Property("Data").HasColumnType("NCLOB"); + builder.Property(x => x.Description).HasColumnType("NCLOB"); + builder.Property(x => x.MaterializerContext).HasColumnType("NCLOB"); + builder.Property(x => x.BinaryData).HasColumnType("BLOB"); + } + + public void Configure(EntityTypeBuilder builder) + { + // In order to use data more than 2000 char we have to use NCLOB. + // In Oracle, we have to explicitly say the column is NCLOB otherwise it would be considered nvarchar(2000). + builder.Property("Data").HasColumnType("NCLOB"); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Configurations/Runtime.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Configurations/Runtime.cs new file mode 100644 index 000000000..20eecc6f5 --- /dev/null +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Configurations/Runtime.cs @@ -0,0 +1,63 @@ +using Elsa.Workflows.Runtime.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Elsa.EntityFrameworkCore.Oracle.Configurations; + +public class Runtime : + IEntityTypeConfiguration, + IEntityTypeConfiguration, + IEntityTypeConfiguration, + IEntityTypeConfiguration, + IEntityTypeConfiguration +{ + /// + public void Configure(EntityTypeBuilder builder) + { + // To use data more than 2000 char we have to use NCLOB. + // In Oracle, we have to explicitly say the column is NCLOB otherwise it would be considered nvarchar(2000). + builder.Property("SerializedActivityState").HasColumnType("NCLOB"); + builder.Property("SerializedException").HasColumnType("NCLOB"); + builder.Property("SerializedPayload").HasColumnType("NCLOB"); + builder.Property("SerializedOutputs").HasColumnType("NCLOB"); + builder.Property("SerializedProperties").HasColumnType("NCLOB"); + } + + /// + public void Configure(EntityTypeBuilder builder) + { + // To use data more than 2000 char we have to use NCLOB. + // In Oracle, we have to explicitly say the column is NCLOB otherwise it would be considered nvarchar(2000). + // modelBuilder.Entity().Ignore(x => x.Payload); + // modelBuilder.Entity().Ignore(x => x.Metadata); + builder.Property("SerializedPayload").HasColumnType("NCLOB"); + builder.Property("SerializedMetadata").HasColumnType("NCLOB"); + } + + /// + public void Configure(EntityTypeBuilder builder) + { + // To use data more than 2000 char we have to use NCLOB. + // In Oracle, we have to explicitly say the column is NCLOB otherwise it would be considered nvarchar(2000). + builder.Property("SerializedPayload").HasColumnType("NCLOB"); + } + + /// + public void Configure(EntityTypeBuilder builder) + { + // To use data more than 2000 char we have to use NCLOB. + // In Oracle, we have to explicitly say the column is NCLOB otherwise it would be considered nvarchar(2000). + builder.Property("SerializedActivityState").HasColumnType("NCLOB"); + builder.Property("SerializedPayload").HasColumnType("NCLOB"); + } + + public void Configure(EntityTypeBuilder builder) + { + // To use data more than 2000 char we have to use NCLOB. + // In Oracle, we have to explicitly say the column is NCLOB otherwise it would be considered nvarchar(2000). + builder.Ignore(x => x.Input); + builder.Ignore(x => x.BookmarkPayload); + builder.Property("SerializedInput").HasColumnType("NCLOB"); + builder.Property("SerializedBookmarkPayload").HasColumnType("NCLOB"); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/DbContextFactories.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/DbContextFactories.cs index 6270c9d91..d2fa8f28f 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/DbContextFactories.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/DbContextFactories.cs @@ -35,6 +35,7 @@ public class OracleDesignTimeDbContextFactory : DesignTimeDbContextF { protected override void ConfigureBuilder(DbContextOptionsBuilder builder, string connectionString) { - builder.UseElsaOracle(GetType().Assembly, connectionString); + var options = new ElsaDbContextOptions().Configure(); + builder.UseElsaOracle(GetType().Assembly, connectionString, options); } } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/DbContextOptionsBuilder.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/DbContextOptionsBuilder.cs index 1aa7e716d..f35133910 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/DbContextOptionsBuilder.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/DbContextOptionsBuilder.cs @@ -13,7 +13,7 @@ public static class DbContextOptionsBuilderExtensions /// /// Configures Entity Framework Core with Oracle. /// - public static DbContextOptionsBuilder UseElsaOracle(this DbContextOptionsBuilder builder, Assembly migrationsAssembly, string connectionString, ElsaDbContextOptions? options = default, Action? configure = default) => + public static DbContextOptionsBuilder UseElsaOracle(this DbContextOptionsBuilder builder, Assembly migrationsAssembly, string connectionString, ElsaDbContextOptions? options = null, Action? configure = null) => builder .UseElsaDbContextOptions(options) .UseOracle(connectionString, db => diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241212211620_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20250131233442_V3_3.Designer.cs similarity index 98% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241212211620_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20250131233442_V3_3.Designer.cs index 812a1ef52..c16d8aac2 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241212211620_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20250131233442_V3_3.Designer.cs @@ -12,7 +12,7 @@ using Oracle.EntityFrameworkCore.Metadata; namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Alterations { [DbContext(typeof(AlterationsElsaDbContext))] - [Migration("20241212211620_V3_3")] + [Migration("20250131233442_V3_3")] partial class V3_3 { /// @@ -21,7 +21,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Alterations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 128); OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241212211620_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20250131233442_V3_3.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241212211620_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20250131233442_V3_3.cs index 97bd8dd6c..fb7daa451 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20241212211620_V3_3.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/20250131233442_V3_3.cs @@ -20,7 +20,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Alterations protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.EnsureSchema( - name: "Elsa"); + name: _schema.Schema); migrationBuilder.CreateTable( name: "AlterationJobs", diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/AlterationsElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/AlterationsElsaDbContextModelSnapshot.cs index 7a7b4f739..617db1282 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/AlterationsElsaDbContextModelSnapshot.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Alterations/AlterationsElsaDbContextModelSnapshot.cs @@ -18,7 +18,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Alterations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 128); OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241212211936_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20250131233455_V3_3.Designer.cs similarity index 98% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241212211936_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20250131233455_V3_3.Designer.cs index 5948fc317..9b9a8c12d 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241212211936_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20250131233455_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Oracle.EntityFrameworkCore.Metadata; namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Identity { [DbContext(typeof(IdentityElsaDbContext))] - [Migration("20241212211936_V3_3")] + [Migration("20250131233455_V3_3")] partial class V3_3 { /// @@ -20,7 +20,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Identity #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 128); OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241212211936_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20250131233455_V3_3.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241212211936_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20250131233455_V3_3.cs index baed52c99..37d23e329 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20241212211936_V3_3.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/20250131233455_V3_3.cs @@ -19,7 +19,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Identity protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.EnsureSchema( - name: "Elsa"); + _schema.Schema); migrationBuilder.CreateTable( name: "Applications", diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/IdentityElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/IdentityElsaDbContextModelSnapshot.cs index 6b6a40e5a..aabcce6cb 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/IdentityElsaDbContextModelSnapshot.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Identity/IdentityElsaDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Identity #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 128); OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241212212100_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20250131233459_V3_3.Designer.cs similarity index 97% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241212212100_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20250131233459_V3_3.Designer.cs index d87cdc60c..8f95ac067 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241212212100_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20250131233459_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Oracle.EntityFrameworkCore.Metadata; namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Labels { [DbContext(typeof(LabelsElsaDbContext))] - [Migration("20241212212100_V3_3")] + [Migration("20250131233459_V3_3")] partial class V3_3 { /// @@ -20,7 +20,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Labels #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 128); OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241212212100_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20250131233459_V3_3.cs similarity index 99% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241212212100_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20250131233459_V3_3.cs index dbfb65ccd..414471594 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20241212212100_V3_3.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/20250131233459_V3_3.cs @@ -19,7 +19,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Labels protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.EnsureSchema( - name: "Elsa"); + _schema.Schema); migrationBuilder.CreateTable( name: "Labels", diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/LabelsElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/LabelsElsaDbContextModelSnapshot.cs index 8ff391a49..6d050f7c2 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/LabelsElsaDbContextModelSnapshot.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Labels/LabelsElsaDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Labels #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 128); OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241212211817_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20250131233451_V3_3.Designer.cs similarity index 91% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241212211817_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20250131233451_V3_3.Designer.cs index 018b9a4d1..1e05309c4 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241212211817_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20250131233451_V3_3.Designer.cs @@ -12,7 +12,7 @@ using Oracle.EntityFrameworkCore.Metadata; namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management { [DbContext(typeof(ManagementElsaDbContext))] - [Migration("20241212211817_V3_3")] + [Migration("20250131233451_V3_3")] partial class V3_3 { /// @@ -21,7 +21,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 128); OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -32,35 +32,35 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management .HasColumnType("NVARCHAR2(450)"); b.Property("BinaryData") - .HasColumnType("RAW(2000)"); + .HasColumnType("BLOB"); b.Property("CreatedAt") .HasColumnType("TIMESTAMP(7) WITH TIME ZONE"); b.Property("Data") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("DefinitionId") .IsRequired() .HasColumnType("NVARCHAR2(450)"); b.Property("Description") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("IsLatest") - .HasColumnType("NUMBER(1)"); + .HasColumnType("BOOLEAN"); b.Property("IsPublished") - .HasColumnType("NUMBER(1)"); + .HasColumnType("BOOLEAN"); b.Property("IsReadonly") - .HasColumnType("NUMBER(1)"); + .HasColumnType("BOOLEAN"); b.Property("IsSystem") - .HasColumnType("NUMBER(1)"); + .HasColumnType("BOOLEAN"); b.Property("MaterializerContext") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("MaterializerName") .IsRequired() @@ -73,7 +73,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management .HasColumnType("NVARCHAR2(2000)"); b.Property("StringData") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("TenantId") .HasColumnType("NVARCHAR2(450)"); @@ -82,7 +82,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management .HasColumnType("NVARCHAR2(2000)"); b.Property("UsableAsActivity") - .HasColumnType("NUMBER(1)"); + .HasColumnType("BOOLEAN"); b.Property("Version") .HasColumnType("NUMBER(10)"); @@ -129,7 +129,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management .HasColumnType("TIMESTAMP(7) WITH TIME ZONE"); b.Property("Data") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("DataCompressionAlgorithm") .HasColumnType("NVARCHAR2(2000)"); @@ -149,7 +149,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management .HasColumnType("NUMBER(10)"); b.Property("IsSystem") - .HasColumnType("NUMBER(1)"); + .HasColumnType("BOOLEAN"); b.Property("Name") .HasColumnType("NVARCHAR2(450)"); diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241212211817_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20250131233451_V3_3.cs similarity index 90% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241212211817_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20250131233451_V3_3.cs index 57217614d..aa899af2c 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20241212211817_V3_3.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/20250131233451_V3_3.cs @@ -20,7 +20,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.EnsureSchema( - name: "Elsa"); + name: _schema.Schema); migrationBuilder.CreateTable( name: "WorkflowDefinitions", @@ -30,22 +30,22 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management Id = table.Column(type: "NVARCHAR2(450)", nullable: false), DefinitionId = table.Column(type: "NVARCHAR2(450)", nullable: false), Name = table.Column(type: "NVARCHAR2(450)", nullable: true), - Description = table.Column(type: "NVARCHAR2(2000)", nullable: true), + Description = table.Column(type: "NCLOB", nullable: true), ToolVersion = table.Column(type: "NVARCHAR2(2000)", nullable: true), ProviderName = table.Column(type: "NVARCHAR2(2000)", nullable: true), MaterializerName = table.Column(type: "NVARCHAR2(2000)", nullable: false), - MaterializerContext = table.Column(type: "NVARCHAR2(2000)", nullable: true), - StringData = table.Column(type: "NVARCHAR2(2000)", nullable: true), - BinaryData = table.Column(type: "RAW(2000)", nullable: true), - IsReadonly = table.Column(type: "NUMBER(1)", nullable: false), - IsSystem = table.Column(type: "NUMBER(1)", nullable: false), - Data = table.Column(type: "NVARCHAR2(2000)", nullable: true), - UsableAsActivity = table.Column(type: "NUMBER(1)", nullable: true), + MaterializerContext = table.Column(type: "NCLOB", nullable: true), + StringData = table.Column(type: "NCLOB", nullable: true), + BinaryData = table.Column(type: "BLOB", nullable: true), + IsReadonly = table.Column(type: "BOOLEAN", nullable: false), + IsSystem = table.Column(type: "BOOLEAN", nullable: false), + Data = table.Column(type: "NCLOB", nullable: true), + UsableAsActivity = table.Column(type: "BOOLEAN", nullable: true), TenantId = table.Column(type: "NVARCHAR2(450)", nullable: true), CreatedAt = table.Column(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: false), Version = table.Column(type: "NUMBER(10)", nullable: false), - IsLatest = table.Column(type: "NUMBER(1)", nullable: false), - IsPublished = table.Column(type: "NUMBER(1)", nullable: false) + IsLatest = table.Column(type: "BOOLEAN", nullable: false), + IsPublished = table.Column(type: "BOOLEAN", nullable: false) }, constraints: table => { @@ -67,11 +67,11 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management CorrelationId = table.Column(type: "NVARCHAR2(450)", nullable: true), Name = table.Column(type: "NVARCHAR2(450)", nullable: true), IncidentCount = table.Column(type: "NUMBER(10)", nullable: false), - IsSystem = table.Column(type: "NUMBER(1)", nullable: false), + IsSystem = table.Column(type: "BOOLEAN", nullable: false), CreatedAt = table.Column(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: false), UpdatedAt = table.Column(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: false), FinishedAt = table.Column(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: true), - Data = table.Column(type: "NVARCHAR2(2000)", nullable: true), + Data = table.Column(type: "NCLOB", nullable: true), DataCompressionAlgorithm = table.Column(type: "NVARCHAR2(2000)", nullable: true), TenantId = table.Column(type: "NVARCHAR2(450)", nullable: true) }, diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/ManagementElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/ManagementElsaDbContextModelSnapshot.cs index d28aebf43..325fa4f68 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/ManagementElsaDbContextModelSnapshot.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Management/ManagementElsaDbContextModelSnapshot.cs @@ -18,7 +18,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 128); OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -29,35 +29,35 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management .HasColumnType("NVARCHAR2(450)"); b.Property("BinaryData") - .HasColumnType("RAW(2000)"); + .HasColumnType("BLOB"); b.Property("CreatedAt") .HasColumnType("TIMESTAMP(7) WITH TIME ZONE"); b.Property("Data") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("DefinitionId") .IsRequired() .HasColumnType("NVARCHAR2(450)"); b.Property("Description") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("IsLatest") - .HasColumnType("NUMBER(1)"); + .HasColumnType("BOOLEAN"); b.Property("IsPublished") - .HasColumnType("NUMBER(1)"); + .HasColumnType("BOOLEAN"); b.Property("IsReadonly") - .HasColumnType("NUMBER(1)"); + .HasColumnType("BOOLEAN"); b.Property("IsSystem") - .HasColumnType("NUMBER(1)"); + .HasColumnType("BOOLEAN"); b.Property("MaterializerContext") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("MaterializerName") .IsRequired() @@ -70,7 +70,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management .HasColumnType("NVARCHAR2(2000)"); b.Property("StringData") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("TenantId") .HasColumnType("NVARCHAR2(450)"); @@ -79,7 +79,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management .HasColumnType("NVARCHAR2(2000)"); b.Property("UsableAsActivity") - .HasColumnType("NUMBER(1)"); + .HasColumnType("BOOLEAN"); b.Property("Version") .HasColumnType("NUMBER(10)"); @@ -126,7 +126,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management .HasColumnType("TIMESTAMP(7) WITH TIME ZONE"); b.Property("Data") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("DataCompressionAlgorithm") .HasColumnType("NVARCHAR2(2000)"); @@ -146,7 +146,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Management .HasColumnType("NUMBER(10)"); b.Property("IsSystem") - .HasColumnType("NUMBER(1)"); + .HasColumnType("BOOLEAN"); b.Property("Name") .HasColumnType("NVARCHAR2(450)"); diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241212211722_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20250131233446_V3_3.Designer.cs similarity index 94% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241212211722_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20250131233446_V3_3.Designer.cs index 340dce4a6..d7fd0359e 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241212211722_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20250131233446_V3_3.Designer.cs @@ -12,7 +12,7 @@ using Oracle.EntityFrameworkCore.Metadata; namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime { [DbContext(typeof(RuntimeElsaDbContext))] - [Migration("20241212211722_V3_3")] + [Migration("20250131233446_V3_3")] partial class V3_3 { /// @@ -21,7 +21,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 128); OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -31,10 +31,6 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime b.Property("Id") .HasColumnType("NVARCHAR2(450)"); - b.Property("Key") - .IsRequired() - .HasColumnType("NVARCHAR2(2000)"); - b.Property("SerializedValue") .IsRequired() .HasColumnType("NVARCHAR2(2000)"); @@ -76,25 +72,25 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime .HasColumnType("TIMESTAMP(7) WITH TIME ZONE"); b.Property("HasBookmarks") - .HasColumnType("NUMBER(1)"); + .HasColumnType("BOOLEAN"); b.Property("SerializedActivityState") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("SerializedActivityStateCompressionAlgorithm") .HasColumnType("NVARCHAR2(2000)"); b.Property("SerializedException") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("SerializedOutputs") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("SerializedPayload") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("SerializedProperties") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("StartedAt") .HasColumnType("TIMESTAMP(7) WITH TIME ZONE"); @@ -216,10 +212,6 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime .IsRequired() .HasColumnType("NVARCHAR2(450)"); - b.Property("BookmarkId") - .IsRequired() - .HasColumnType("NVARCHAR2(2000)"); - b.Property("CorrelationId") .HasColumnType("NVARCHAR2(2000)"); @@ -231,10 +223,10 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime .HasColumnType("NVARCHAR2(450)"); b.Property("SerializedMetadata") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("SerializedPayload") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("TenantId") .HasColumnType("NVARCHAR2(450)"); @@ -281,7 +273,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime .HasColumnType("NVARCHAR2(450)"); b.Property("SerializedPayload") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("TenantId") .HasColumnType("NVARCHAR2(450)"); @@ -354,10 +346,10 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime .HasColumnType("NUMBER(19)"); b.Property("SerializedActivityState") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("SerializedPayload") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("Source") .HasColumnType("NVARCHAR2(2000)"); @@ -465,10 +457,10 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime .HasColumnType("NVARCHAR2(450)"); b.Property("SerializedBookmarkPayload") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("SerializedInput") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("TenantId") .HasColumnType("NVARCHAR2(2000)"); diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241212211722_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20250131233446_V3_3.cs similarity index 96% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241212211722_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20250131233446_V3_3.cs index 672dc5645..c470348e0 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20241212211722_V3_3.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/20250131233446_V3_3.cs @@ -20,7 +20,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.EnsureSchema( - name: "Elsa"); + name: _schema.Schema); migrationBuilder.CreateTable( name: "ActivityExecutionRecords", @@ -35,15 +35,15 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime ActivityTypeVersion = table.Column(type: "NUMBER(10)", nullable: false), ActivityName = table.Column(type: "NVARCHAR2(450)", nullable: true), StartedAt = table.Column(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: false), - HasBookmarks = table.Column(type: "NUMBER(1)", nullable: false), + HasBookmarks = table.Column(type: "BOOLEAN", nullable: false), Status = table.Column(type: "NVARCHAR2(450)", nullable: false), CompletedAt = table.Column(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: true), - SerializedActivityState = table.Column(type: "NVARCHAR2(2000)", nullable: true), + SerializedActivityState = table.Column(type: "NCLOB", nullable: true), SerializedActivityStateCompressionAlgorithm = table.Column(type: "NVARCHAR2(2000)", nullable: true), - SerializedException = table.Column(type: "NVARCHAR2(2000)", nullable: true), - SerializedOutputs = table.Column(type: "NVARCHAR2(2000)", nullable: true), - SerializedPayload = table.Column(type: "NVARCHAR2(2000)", nullable: true), - SerializedProperties = table.Column(type: "NVARCHAR2(2000)", nullable: true), + SerializedException = table.Column(type: "NCLOB", nullable: true), + SerializedOutputs = table.Column(type: "NCLOB", nullable: true), + SerializedPayload = table.Column(type: "NCLOB", nullable: true), + SerializedProperties = table.Column(type: "NCLOB", nullable: true), TenantId = table.Column(type: "NVARCHAR2(450)", nullable: true) }, constraints: table => @@ -78,15 +78,14 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime columns: table => new { Id = table.Column(type: "NVARCHAR2(450)", nullable: false), - BookmarkId = table.Column(type: "NVARCHAR2(2000)", nullable: false), ActivityTypeName = table.Column(type: "NVARCHAR2(450)", nullable: false), Hash = table.Column(type: "NVARCHAR2(450)", nullable: false), WorkflowInstanceId = table.Column(type: "NVARCHAR2(450)", nullable: false), ActivityInstanceId = table.Column(type: "NVARCHAR2(450)", nullable: true), CorrelationId = table.Column(type: "NVARCHAR2(2000)", nullable: true), CreatedAt = table.Column(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: false), - SerializedMetadata = table.Column(type: "NVARCHAR2(2000)", nullable: true), - SerializedPayload = table.Column(type: "NVARCHAR2(2000)", nullable: true), + SerializedMetadata = table.Column(type: "NCLOB", nullable: true), + SerializedPayload = table.Column(type: "NCLOB", nullable: true), TenantId = table.Column(type: "NVARCHAR2(450)", nullable: true) }, constraints: table => @@ -100,7 +99,6 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime columns: table => new { Id = table.Column(type: "NVARCHAR2(450)", nullable: false), - Key = table.Column(type: "NVARCHAR2(2000)", nullable: false), SerializedValue = table.Column(type: "NVARCHAR2(2000)", nullable: false), TenantId = table.Column(type: "NVARCHAR2(450)", nullable: true) }, @@ -120,7 +118,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime Name = table.Column(type: "NVARCHAR2(450)", nullable: false), ActivityId = table.Column(type: "NVARCHAR2(2000)", nullable: false), Hash = table.Column(type: "NVARCHAR2(450)", nullable: true), - SerializedPayload = table.Column(type: "NVARCHAR2(2000)", nullable: true), + SerializedPayload = table.Column(type: "NCLOB", nullable: true), TenantId = table.Column(type: "NVARCHAR2(450)", nullable: true) }, constraints: table => @@ -150,8 +148,8 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime EventName = table.Column(type: "NVARCHAR2(450)", nullable: true), Message = table.Column(type: "NVARCHAR2(2000)", nullable: true), Source = table.Column(type: "NVARCHAR2(2000)", nullable: true), - SerializedActivityState = table.Column(type: "NVARCHAR2(2000)", nullable: true), - SerializedPayload = table.Column(type: "NVARCHAR2(2000)", nullable: true), + SerializedActivityState = table.Column(type: "NCLOB", nullable: true), + SerializedPayload = table.Column(type: "NCLOB", nullable: true), TenantId = table.Column(type: "NVARCHAR2(450)", nullable: true) }, constraints: table => @@ -172,8 +170,8 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime ActivityInstanceId = table.Column(type: "NVARCHAR2(450)", nullable: true), CreatedAt = table.Column(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: false), ExpiresAt = table.Column(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: false), - SerializedBookmarkPayload = table.Column(type: "NVARCHAR2(2000)", nullable: true), - SerializedInput = table.Column(type: "NVARCHAR2(2000)", nullable: true), + SerializedBookmarkPayload = table.Column(type: "NCLOB", nullable: true), + SerializedInput = table.Column(type: "NCLOB", nullable: true), TenantId = table.Column(type: "NVARCHAR2(2000)", nullable: true) }, constraints: table => diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs index cad99cefa..e0adad632 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs @@ -18,7 +18,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 128); OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -28,10 +28,6 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime b.Property("Id") .HasColumnType("NVARCHAR2(450)"); - b.Property("Key") - .IsRequired() - .HasColumnType("NVARCHAR2(2000)"); - b.Property("SerializedValue") .IsRequired() .HasColumnType("NVARCHAR2(2000)"); @@ -73,25 +69,25 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime .HasColumnType("TIMESTAMP(7) WITH TIME ZONE"); b.Property("HasBookmarks") - .HasColumnType("NUMBER(1)"); + .HasColumnType("BOOLEAN"); b.Property("SerializedActivityState") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("SerializedActivityStateCompressionAlgorithm") .HasColumnType("NVARCHAR2(2000)"); b.Property("SerializedException") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("SerializedOutputs") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("SerializedPayload") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("SerializedProperties") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("StartedAt") .HasColumnType("TIMESTAMP(7) WITH TIME ZONE"); @@ -213,10 +209,6 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime .IsRequired() .HasColumnType("NVARCHAR2(450)"); - b.Property("BookmarkId") - .IsRequired() - .HasColumnType("NVARCHAR2(2000)"); - b.Property("CorrelationId") .HasColumnType("NVARCHAR2(2000)"); @@ -228,10 +220,10 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime .HasColumnType("NVARCHAR2(450)"); b.Property("SerializedMetadata") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("SerializedPayload") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("TenantId") .HasColumnType("NVARCHAR2(450)"); @@ -278,7 +270,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime .HasColumnType("NVARCHAR2(450)"); b.Property("SerializedPayload") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("TenantId") .HasColumnType("NVARCHAR2(450)"); @@ -351,10 +343,10 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime .HasColumnType("NUMBER(19)"); b.Property("SerializedActivityState") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("SerializedPayload") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("Source") .HasColumnType("NVARCHAR2(2000)"); @@ -462,10 +454,10 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Runtime .HasColumnType("NVARCHAR2(450)"); b.Property("SerializedBookmarkPayload") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("SerializedInput") - .HasColumnType("NVARCHAR2(2000)"); + .HasColumnType("NCLOB"); b.Property("TenantId") .HasColumnType("NVARCHAR2(2000)"); diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20241212212227_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20250131233503_V3_3.Designer.cs similarity index 94% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20241212212227_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20250131233503_V3_3.Designer.cs index a02ded2ad..447cd1eac 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20241212212227_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20250131233503_V3_3.Designer.cs @@ -11,7 +11,7 @@ using Oracle.EntityFrameworkCore.Metadata; namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Tenants { [DbContext(typeof(TenantsElsaDbContext))] - [Migration("20241212212227_V3_3")] + [Migration("20250131233503_V3_3")] partial class V3_3 { /// @@ -20,7 +20,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Tenants #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 128); OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20241212212227_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20250131233503_V3_3.cs similarity index 98% rename from src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20241212212227_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20250131233503_V3_3.cs index 8ca9f1711..1860609ac 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20241212212227_V3_3.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/20250131233503_V3_3.cs @@ -19,7 +19,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Tenants protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.EnsureSchema( - name: "Elsa"); + name: _schema.Schema); migrationBuilder.CreateTable( name: "Tenants", diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs index 464fe8f3f..79041524c 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/Migrations/Tenants/TenantsElsaDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace Elsa.EntityFrameworkCore.Oracle.Migrations.Tenants #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.12") .HasAnnotation("Relational:MaxIdentifierLength", 128); OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder); diff --git a/src/modules/Elsa.EntityFrameworkCore.Oracle/OracleProvidersExtensions.cs b/src/modules/Elsa.EntityFrameworkCore.Oracle/OracleProvidersExtensions.cs index 002b87541..8d800c577 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Oracle/OracleProvidersExtensions.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Oracle/OracleProvidersExtensions.cs @@ -1,4 +1,9 @@ using System.Reflection; +using Elsa.EntityFrameworkCore.Modules.Management; +using Elsa.EntityFrameworkCore.Modules.Runtime; +using Elsa.EntityFrameworkCore.Oracle.Configurations; +using Elsa.Workflows.Management.Entities; +using Elsa.Workflows.Runtime.Entities; using JetBrains.Annotations; using Oracle.EntityFrameworkCore.Infrastructure; @@ -12,50 +17,71 @@ namespace Elsa.EntityFrameworkCore.Extensions; public static class OracleProvidersExtensions { private static Assembly Assembly => typeof(OracleProvidersExtensions).Assembly; - - public static TFeature UseOracle(this PersistenceFeatureBase feature, - string connectionString, - ElsaDbContextOptions? options = null, - Action? configure = null) + + public static TFeature UseOracle(this PersistenceFeatureBase feature, + string connectionString, + ElsaDbContextOptions? options = null, + Action? configure = null) where TDbContext : ElsaDbContextBase where TFeature : PersistenceFeatureBase { return feature.UseOracle(Assembly, connectionString, options, configure); } - - public static TFeature UseOracle(this PersistenceFeatureBase feature, + + public static TFeature UseOracle(this PersistenceFeatureBase feature, Func connectionStringFunc, ElsaDbContextOptions? options = null, Action? configure = null - ) + ) where TDbContext : ElsaDbContextBase where TFeature : PersistenceFeatureBase { return feature.UseOracle(Assembly, connectionStringFunc, options, configure); } - - public static TFeature UseOracle(this PersistenceFeatureBase feature, - Assembly migrationsAssembly, + + public static TFeature UseOracle(this PersistenceFeatureBase feature, + Assembly migrationsAssembly, string connectionString, ElsaDbContextOptions? options = null, Action? configure = null - ) + ) where TDbContext : ElsaDbContextBase where TFeature : PersistenceFeatureBase { return feature.UseOracle(migrationsAssembly, _ => connectionString, options, configure); } - - public static TFeature UseOracle(this PersistenceFeatureBase feature, - Assembly migrationsAssembly, + + public static TFeature UseOracle(this PersistenceFeatureBase feature, + Assembly migrationsAssembly, Func connectionStringFunc, ElsaDbContextOptions? options = null, Action? configure = null - ) + ) where TDbContext : ElsaDbContextBase where TFeature : PersistenceFeatureBase { + options ??= new(); + options.Configure(); feature.DbContextOptionsBuilder = (sp, db) => db.UseElsaOracle(migrationsAssembly, connectionStringFunc(sp), options, configure: configure); return (TFeature)feature; } + + public static ElsaDbContextOptions Configure(this ElsaDbContextOptions options) + { + var management = new Management(); + var runtime = new Runtime(); + + options.ConfigureModel(modelBuilder => modelBuilder + .ApplyConfiguration(management) + .ApplyConfiguration(management)); + + options.ConfigureModel(modelBuilder => modelBuilder + .ApplyConfiguration(runtime) + .ApplyConfiguration(runtime) + .ApplyConfiguration(runtime) + .ApplyConfiguration(runtime) + .ApplyConfiguration(runtime)); + + return options; + } } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20231024160952_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20231024160952_Initial.cs index 64226b886..8412c641b 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20231024160952_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20231024160952_Initial.cs @@ -1,4 +1,4 @@ - +using Elsa.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240329200711_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240329200711_V3_1.cs index 00dfc44e8..821d29f2e 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240329200711_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240329200711_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime /// public partial class V3_1 : Migration { - private readonly IElsaDbContextSchema _schema; + private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; /// - public V3_1(IElsaDbContextSchema schema) + public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240610184748_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240610184748_V3_2.cs index 830c75445..d61e4aec4 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240610184748_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20240610184748_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime /// public partial class V3_2 : Migration { - private readonly IElsaDbContextSchema _schema; + private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; /// - public V3_2(IElsaDbContextSchema schema) + public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241212211710_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20250116192950_V3_3.Designer.cs similarity index 98% rename from src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241212211710_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20250116192950_V3_3.Designer.cs index 516ec8a2a..05fe77430 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241212211710_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20250116192950_V3_3.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime { [DbContext(typeof(RuntimeElsaDbContext))] - [Migration("20241212211710_V3_3")] + [Migration("20250116192950_V3_3")] partial class V3_3 { /// @@ -21,7 +21,7 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.11") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -31,10 +31,6 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime b.Property("Id") .HasColumnType("text"); - b.Property("Key") - .IsRequired() - .HasColumnType("text"); - b.Property("SerializedValue") .IsRequired() .HasColumnType("text"); @@ -216,10 +212,6 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime .IsRequired() .HasColumnType("text"); - b.Property("BookmarkId") - .IsRequired() - .HasColumnType("text"); - b.Property("CorrelationId") .HasColumnType("text"); diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241212211710_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20250116192950_V3_3.cs similarity index 82% rename from src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241212211710_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20250116192950_V3_3.cs index 17e35fbe8..f9b793865 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20241212211710_V3_3.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/20250116192950_V3_3.cs @@ -19,15 +19,17 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime /// protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.DropPrimaryKey( - name: "PK_KeyValuePairs", + migrationBuilder.RenameColumn( + name: "Key", schema: _schema.Schema, - table: "KeyValuePairs"); + table: "KeyValuePairs", + newName: "Id"); - migrationBuilder.DropPrimaryKey( - name: "PK_Bookmarks", + migrationBuilder.RenameColumn( + name: "BookmarkId", schema: _schema.Schema, - table: "Bookmarks"); + table: "Bookmarks", + newName: "Id"); migrationBuilder.AddColumn( name: "TenantId", @@ -50,14 +52,6 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime type: "text", nullable: true); - migrationBuilder.AddColumn( - name: "Id", - schema: _schema.Schema, - table: "KeyValuePairs", - type: "text", - nullable: false, - defaultValue: ""); - migrationBuilder.AddColumn( name: "TenantId", schema: _schema.Schema, @@ -65,14 +59,6 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime type: "text", nullable: true); - migrationBuilder.AddColumn( - name: "Id", - schema: _schema.Schema, - table: "Bookmarks", - type: "text", - nullable: false, - defaultValue: ""); - migrationBuilder.AddColumn( name: "TenantId", schema: _schema.Schema, @@ -87,18 +73,6 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime type: "text", nullable: true); - migrationBuilder.AddPrimaryKey( - name: "PK_KeyValuePairs", - schema: _schema.Schema, - table: "KeyValuePairs", - column: "Id"); - - migrationBuilder.AddPrimaryKey( - name: "PK_Bookmarks", - schema: _schema.Schema, - table: "Bookmarks", - column: "Id"); - migrationBuilder.CreateTable( name: "BookmarkQueueItems", schema: _schema.Schema, @@ -216,21 +190,11 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime schema: _schema.Schema, table: "Triggers"); - migrationBuilder.DropPrimaryKey( - name: "PK_KeyValuePairs", - schema: _schema.Schema, - table: "KeyValuePairs"); - migrationBuilder.DropIndex( name: "IX_SerializedKeyValuePair_TenantId", schema: _schema.Schema, table: "KeyValuePairs"); - migrationBuilder.DropPrimaryKey( - name: "PK_Bookmarks", - schema: _schema.Schema, - table: "Bookmarks"); - migrationBuilder.DropIndex( name: "IX_StoredBookmark_TenantId", schema: _schema.Schema, @@ -256,21 +220,11 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime schema: _schema.Schema, table: "Triggers"); - migrationBuilder.DropColumn( - name: "Id", - schema: _schema.Schema, - table: "KeyValuePairs"); - migrationBuilder.DropColumn( name: "TenantId", schema: _schema.Schema, table: "KeyValuePairs"); - migrationBuilder.DropColumn( - name: "Id", - schema: _schema.Schema, - table: "Bookmarks"); - migrationBuilder.DropColumn( name: "TenantId", schema: _schema.Schema, @@ -281,17 +235,17 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime schema: _schema.Schema, table: "ActivityExecutionRecords"); - migrationBuilder.AddPrimaryKey( - name: "PK_KeyValuePairs", + migrationBuilder.RenameColumn( + name: "Id", schema: _schema.Schema, table: "KeyValuePairs", - column: "Key"); + newName: "Key"); - migrationBuilder.AddPrimaryKey( - name: "PK_Bookmarks", + migrationBuilder.RenameColumn( + name: "Id", schema: _schema.Schema, table: "Bookmarks", - column: "BookmarkId"); + newName: "BookmarkId"); } } } diff --git a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs index a16cbd662..01066d7e1 100644 --- a/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs +++ b/src/modules/Elsa.EntityFrameworkCore.PostgreSql/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs @@ -18,7 +18,7 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.11") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -28,10 +28,6 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime b.Property("Id") .HasColumnType("text"); - b.Property("Key") - .IsRequired() - .HasColumnType("text"); - b.Property("SerializedValue") .IsRequired() .HasColumnType("text"); @@ -213,10 +209,6 @@ namespace Elsa.EntityFrameworkCore.PostgreSql.Migrations.Runtime .IsRequired() .HasColumnType("text"); - b.Property("BookmarkId") - .IsRequired() - .HasColumnType("text"); - b.Property("CorrelationId") .HasColumnType("text"); diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20231024160944_Initial.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20231024160944_Initial.cs index af48889a8..85ae54f44 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20231024160944_Initial.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20231024160944_Initial.cs @@ -1,4 +1,4 @@ - +using Elsa.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240329200657_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240329200657_V3_1.cs index abaac578e..342469eec 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240329200657_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240329200657_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime /// public partial class V3_1 : Migration { - private readonly IElsaDbContextSchema _schema; + private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; /// - public V3_1(IElsaDbContextSchema schema) + public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240610184719_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240610184719_V3_2.cs index 67ba36b5f..4f0f4c3ef 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240610184719_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20240610184719_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime /// public partial class V3_2 : Migration { - private readonly IElsaDbContextSchema _schema; + private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; /// - public V3_2(IElsaDbContextSchema schema) + public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241212211646_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20250116192837_V3_3.Designer.cs similarity index 98% rename from src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241212211646_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20250116192837_V3_3.Designer.cs index 117ee983b..6f59df17b 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241212211646_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20250116192837_V3_3.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime { [DbContext(typeof(RuntimeElsaDbContext))] - [Migration("20241212211646_V3_3")] + [Migration("20250116192837_V3_3")] partial class V3_3 { /// @@ -21,7 +21,7 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.11") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -31,10 +31,6 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime b.Property("Id") .HasColumnType("nvarchar(450)"); - b.Property("Key") - .IsRequired() - .HasColumnType("nvarchar(max)"); - b.Property("SerializedValue") .IsRequired() .HasColumnType("nvarchar(max)"); @@ -216,10 +212,6 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime .IsRequired() .HasColumnType("nvarchar(450)"); - b.Property("BookmarkId") - .IsRequired() - .HasColumnType("nvarchar(max)"); - b.Property("CorrelationId") .HasColumnType("nvarchar(max)"); diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241212211646_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20250116192837_V3_3.cs similarity index 73% rename from src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241212211646_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20250116192837_V3_3.cs index b16e9c9ea..ea7495e61 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20241212211646_V3_3.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/20250116192837_V3_3.cs @@ -19,15 +19,17 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime /// protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.DropPrimaryKey( - name: "PK_KeyValuePairs", + migrationBuilder.RenameColumn( + name: "Key", schema: _schema.Schema, - table: "KeyValuePairs"); + table: "KeyValuePairs", + newName: "Id"); - migrationBuilder.DropPrimaryKey( - name: "PK_Bookmarks", + migrationBuilder.RenameColumn( + name: "BookmarkId", schema: _schema.Schema, - table: "Bookmarks"); + table: "Bookmarks", + newName: "Id"); migrationBuilder.AddColumn( name: "TenantId", @@ -50,23 +52,6 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime type: "nvarchar(450)", nullable: true); - migrationBuilder.AlterColumn( - name: "Key", - schema: _schema.Schema, - table: "KeyValuePairs", - type: "nvarchar(max)", - nullable: false, - oldClrType: typeof(string), - oldType: "nvarchar(450)"); - - migrationBuilder.AddColumn( - name: "Id", - schema: _schema.Schema, - table: "KeyValuePairs", - type: "nvarchar(450)", - nullable: false, - defaultValue: ""); - migrationBuilder.AddColumn( name: "TenantId", schema: _schema.Schema, @@ -74,23 +59,6 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime type: "nvarchar(450)", nullable: true); - migrationBuilder.AlterColumn( - name: "BookmarkId", - schema: _schema.Schema, - table: "Bookmarks", - type: "nvarchar(max)", - nullable: false, - oldClrType: typeof(string), - oldType: "nvarchar(450)"); - - migrationBuilder.AddColumn( - name: "Id", - schema: _schema.Schema, - table: "Bookmarks", - type: "nvarchar(450)", - nullable: false, - defaultValue: ""); - migrationBuilder.AddColumn( name: "TenantId", schema: _schema.Schema, @@ -105,18 +73,6 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime type: "nvarchar(450)", nullable: true); - migrationBuilder.AddPrimaryKey( - name: "PK_KeyValuePairs", - schema: _schema.Schema, - table: "KeyValuePairs", - column: "Id"); - - migrationBuilder.AddPrimaryKey( - name: "PK_Bookmarks", - schema: _schema.Schema, - table: "Bookmarks", - column: "Id"); - migrationBuilder.CreateTable( name: "BookmarkQueueItems", schema: _schema.Schema, @@ -234,21 +190,11 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime schema: _schema.Schema, table: "Triggers"); - migrationBuilder.DropPrimaryKey( - name: "PK_KeyValuePairs", - schema: _schema.Schema, - table: "KeyValuePairs"); - migrationBuilder.DropIndex( name: "IX_SerializedKeyValuePair_TenantId", schema: _schema.Schema, table: "KeyValuePairs"); - migrationBuilder.DropPrimaryKey( - name: "PK_Bookmarks", - schema: _schema.Schema, - table: "Bookmarks"); - migrationBuilder.DropIndex( name: "IX_StoredBookmark_TenantId", schema: _schema.Schema, @@ -274,21 +220,11 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime schema: _schema.Schema, table: "Triggers"); - migrationBuilder.DropColumn( - name: "Id", - schema: _schema.Schema, - table: "KeyValuePairs"); - migrationBuilder.DropColumn( name: "TenantId", schema: _schema.Schema, table: "KeyValuePairs"); - migrationBuilder.DropColumn( - name: "Id", - schema: _schema.Schema, - table: "Bookmarks"); - migrationBuilder.DropColumn( name: "TenantId", schema: _schema.Schema, @@ -299,35 +235,17 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime schema: _schema.Schema, table: "ActivityExecutionRecords"); - migrationBuilder.AlterColumn( - name: "Key", + migrationBuilder.RenameColumn( + name: "Id", schema: _schema.Schema, table: "KeyValuePairs", - type: "nvarchar(450)", - nullable: false, - oldClrType: typeof(string), - oldType: "nvarchar(max)"); + newName: "Key"); - migrationBuilder.AlterColumn( - name: "BookmarkId", + migrationBuilder.RenameColumn( + name: "Id", schema: _schema.Schema, table: "Bookmarks", - type: "nvarchar(450)", - nullable: false, - oldClrType: typeof(string), - oldType: "nvarchar(max)"); - - migrationBuilder.AddPrimaryKey( - name: "PK_KeyValuePairs", - schema: _schema.Schema, - table: "KeyValuePairs", - column: "Key"); - - migrationBuilder.AddPrimaryKey( - name: "PK_Bookmarks", - schema: _schema.Schema, - table: "Bookmarks", - column: "BookmarkId"); + newName: "BookmarkId"); } } } diff --git a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs index 752f26c97..2d936a1c8 100644 --- a/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs +++ b/src/modules/Elsa.EntityFrameworkCore.SqlServer/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs @@ -18,7 +18,7 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("Elsa") - .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("ProductVersion", "8.0.11") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -28,10 +28,6 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime b.Property("Id") .HasColumnType("nvarchar(450)"); - b.Property("Key") - .IsRequired() - .HasColumnType("nvarchar(max)"); - b.Property("SerializedValue") .IsRequired() .HasColumnType("nvarchar(max)"); @@ -213,10 +209,6 @@ namespace Elsa.EntityFrameworkCore.SqlServer.Migrations.Runtime .IsRequired() .HasColumnType("nvarchar(450)"); - b.Property("BookmarkId") - .IsRequired() - .HasColumnType("nvarchar(max)"); - b.Property("CorrelationId") .HasColumnType("nvarchar(max)"); diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240329200705_V3_1.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240329200705_V3_1.cs index ddea13893..c6e73b527 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240329200705_V3_1.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240329200705_V3_1.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime /// public partial class V3_1 : Migration { - private readonly IElsaDbContextSchema _schema; + private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; /// - public V3_1(IElsaDbContextSchema schema) + public V3_1(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240610184731_V3_2.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240610184731_V3_2.cs index af8aed20a..d84cfc43f 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240610184731_V3_2.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20240610184731_V3_2.cs @@ -7,10 +7,10 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime /// public partial class V3_2 : Migration { - private readonly IElsaDbContextSchema _schema; + private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema; /// - public V3_2(IElsaDbContextSchema schema) + public V3_2(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema) { _schema = schema; } diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241212211658_V3_3.Designer.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20250116192907_V3_3.Designer.cs similarity index 98% rename from src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241212211658_V3_3.Designer.cs rename to src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20250116192907_V3_3.Designer.cs index 097593805..d92a8513f 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241212211658_V3_3.Designer.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20250116192907_V3_3.Designer.cs @@ -11,24 +11,20 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime { [DbContext(typeof(RuntimeElsaDbContext))] - [Migration("20241212211658_V3_3")] + [Migration("20250116192907_V3_3")] partial class V3_3 { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "7.0.20"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.11"); modelBuilder.Entity("Elsa.KeyValues.Entities.SerializedKeyValuePair", b => { b.Property("Id") .HasColumnType("TEXT"); - b.Property("Key") - .IsRequired() - .HasColumnType("TEXT"); - b.Property("SerializedValue") .IsRequired() .HasColumnType("TEXT"); @@ -210,10 +206,6 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime .IsRequired() .HasColumnType("TEXT"); - b.Property("BookmarkId") - .IsRequired() - .HasColumnType("TEXT"); - b.Property("CorrelationId") .HasColumnType("TEXT"); diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241212211658_V3_3.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20250116192907_V3_3.cs similarity index 82% rename from src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241212211658_V3_3.cs rename to src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20250116192907_V3_3.cs index 193c3a8dc..4316a2459 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20241212211658_V3_3.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/20250116192907_V3_3.cs @@ -19,13 +19,15 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime /// protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.DropPrimaryKey( - name: "PK_KeyValuePairs", - table: "KeyValuePairs"); + migrationBuilder.RenameColumn( + name: "Key", + table: "KeyValuePairs", + newName: "Id"); - migrationBuilder.DropPrimaryKey( - name: "PK_Bookmarks", - table: "Bookmarks"); + migrationBuilder.RenameColumn( + name: "BookmarkId", + table: "Bookmarks", + newName: "Id"); migrationBuilder.AddColumn( name: "TenantId", @@ -45,26 +47,12 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime type: "TEXT", nullable: true); - migrationBuilder.AddColumn( - name: "Id", - table: "KeyValuePairs", - type: "TEXT", - nullable: false, - defaultValue: ""); - migrationBuilder.AddColumn( name: "TenantId", table: "KeyValuePairs", type: "TEXT", nullable: true); - migrationBuilder.AddColumn( - name: "Id", - table: "Bookmarks", - type: "TEXT", - nullable: false, - defaultValue: ""); - migrationBuilder.AddColumn( name: "TenantId", table: "Bookmarks", @@ -77,16 +65,6 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime type: "TEXT", nullable: true); - migrationBuilder.AddPrimaryKey( - name: "PK_KeyValuePairs", - table: "KeyValuePairs", - column: "Id"); - - migrationBuilder.AddPrimaryKey( - name: "PK_Bookmarks", - table: "Bookmarks", - column: "Id"); - migrationBuilder.CreateTable( name: "BookmarkQueueItems", columns: table => new @@ -187,18 +165,10 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime name: "IX_StoredTrigger_TenantId", table: "Triggers"); - migrationBuilder.DropPrimaryKey( - name: "PK_KeyValuePairs", - table: "KeyValuePairs"); - migrationBuilder.DropIndex( name: "IX_SerializedKeyValuePair_TenantId", table: "KeyValuePairs"); - migrationBuilder.DropPrimaryKey( - name: "PK_Bookmarks", - table: "Bookmarks"); - migrationBuilder.DropIndex( name: "IX_StoredBookmark_TenantId", table: "Bookmarks"); @@ -219,18 +189,10 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime name: "TenantId", table: "Triggers"); - migrationBuilder.DropColumn( - name: "Id", - table: "KeyValuePairs"); - migrationBuilder.DropColumn( name: "TenantId", table: "KeyValuePairs"); - migrationBuilder.DropColumn( - name: "Id", - table: "Bookmarks"); - migrationBuilder.DropColumn( name: "TenantId", table: "Bookmarks"); @@ -239,15 +201,15 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime name: "TenantId", table: "ActivityExecutionRecords"); - migrationBuilder.AddPrimaryKey( - name: "PK_KeyValuePairs", + migrationBuilder.RenameColumn( + name: "Id", table: "KeyValuePairs", - column: "Key"); + newName: "Key"); - migrationBuilder.AddPrimaryKey( - name: "PK_Bookmarks", + migrationBuilder.RenameColumn( + name: "Id", table: "Bookmarks", - column: "BookmarkId"); + newName: "BookmarkId"); } } } diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs index 16fbb9bcd..de19a039a 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/Migrations/Runtime/RuntimeElsaDbContextModelSnapshot.cs @@ -15,17 +15,13 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "7.0.20"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.11"); modelBuilder.Entity("Elsa.KeyValues.Entities.SerializedKeyValuePair", b => { b.Property("Id") .HasColumnType("TEXT"); - b.Property("Key") - .IsRequired() - .HasColumnType("TEXT"); - b.Property("SerializedValue") .IsRequired() .HasColumnType("TEXT"); @@ -207,10 +203,6 @@ namespace Elsa.EntityFrameworkCore.Sqlite.Migrations.Runtime .IsRequired() .HasColumnType("TEXT"); - b.Property("BookmarkId") - .IsRequired() - .HasColumnType("TEXT"); - b.Property("CorrelationId") .HasColumnType("TEXT"); diff --git a/src/modules/Elsa.EntityFrameworkCore.Common/EntityHandlers/SetupForSqlite.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/SetupForSqlite.cs similarity index 95% rename from src/modules/Elsa.EntityFrameworkCore.Common/EntityHandlers/SetupForSqlite.cs rename to src/modules/Elsa.EntityFrameworkCore.Sqlite/SetupForSqlite.cs index 500c6f176..8d60ac962 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Common/EntityHandlers/SetupForSqlite.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/SetupForSqlite.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -namespace Elsa.EntityFrameworkCore.EntityHandlers; +namespace Elsa.EntityFrameworkCore.Sqlite; /// /// Represents a class that handles entity model creation for SQLite databases. diff --git a/src/modules/Elsa.EntityFrameworkCore.Sqlite/SqliteProvidersExtensions.cs b/src/modules/Elsa.EntityFrameworkCore.Sqlite/SqliteProvidersExtensions.cs index 635abfac5..561b35428 100644 --- a/src/modules/Elsa.EntityFrameworkCore.Sqlite/SqliteProvidersExtensions.cs +++ b/src/modules/Elsa.EntityFrameworkCore.Sqlite/SqliteProvidersExtensions.cs @@ -1,7 +1,10 @@ using System.Reflection; using Elsa.EntityFrameworkCore.EntityHandlers; +using Elsa.EntityFrameworkCore.Sqlite; +using Elsa.Extensions; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; // ReSharper disable once CheckNamespace namespace Elsa.EntityFrameworkCore.Extensions; @@ -65,7 +68,8 @@ public static class SqliteProvidersExtensions where TDbContext : ElsaDbContextBase where TFeature : PersistenceFeatureBase { - feature.Module.Services.AddScoped(); + + feature.Module.Services.TryAddScopedImplementation(); feature.DbContextOptionsBuilder = (sp, db) => db.UseElsaSqlite(migrationsAssembly, connectionStringFunc(sp), options, configure: configure); return (TFeature)feature; } diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Alterations/Feature.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Alterations/Feature.cs index f05d8442d..de79b7815 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Alterations/Feature.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Alterations/Feature.cs @@ -28,6 +28,5 @@ public class EFCoreAlterationsPersistenceFeature(IModule module) : PersistenceFe base.Apply(); AddEntityStore(); AddEntityStore(); - Services.AddScoped(); } } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Alterations/SetupForOracle.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Alterations/SetupForOracle.cs deleted file mode 100644 index a9b7703bc..000000000 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Alterations/SetupForOracle.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Elsa.Alterations.Core.Entities; -using Elsa.EntityFrameworkCore.Extensions; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Elsa.EntityFrameworkCore.Modules.Alterations; - -/// -/// Represents a class that handles entity model creation for SQLite databases. -/// -public class SetupForOracle : IEntityModelCreatingHandler -{ - /// - public void Handle(ElsaDbContextBase dbContext, ModelBuilder modelBuilder, IMutableEntityType entityType) - { - if(!dbContext.Database.IsOracle()) - return; - - // In order to use data more than 2000 char we have to use NCLOB. - // In Oracle, we have to explicitly say the column is NCLOB otherwise it would be considered nvarchar(2000). - modelBuilder.Entity().Property("SerializedAlterations").HasColumnType("NCLOB"); - modelBuilder.Entity().Property("SerializedWorkflowInstanceIds").HasColumnType("NCLOB"); - modelBuilder.Entity().Property("SerializedLog").HasColumnType("NCLOB"); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Identity/DbContext.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Identity/DbContext.cs index c6a2ccf06..21e5a43fc 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Identity/DbContext.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Identity/DbContext.cs @@ -31,11 +31,11 @@ public class IdentityElsaDbContext : ElsaDbContextBase /// protected override void OnModelCreating(ModelBuilder modelBuilder) { - base.OnModelCreating(modelBuilder); - var config = new Configurations(); modelBuilder.ApplyConfiguration(config); modelBuilder.ApplyConfiguration(config); modelBuilder.ApplyConfiguration(config); + + base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/DbContext.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/DbContext.cs index f0ae46821..79ab138fb 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/DbContext.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/DbContext.cs @@ -28,12 +28,13 @@ public class ManagementElsaDbContext : ElsaDbContextBase /// protected override void OnModelCreating(ModelBuilder modelBuilder) { - base.OnModelCreating(modelBuilder); modelBuilder.Ignore(); modelBuilder.Ignore(); var config = new Configurations(); modelBuilder.ApplyConfiguration(config); modelBuilder.ApplyConfiguration(config); + + base.OnModelCreating(modelBuilder); } } diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/SetupForOracle.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/SetupForOracle.cs deleted file mode 100644 index c14e6f4e1..000000000 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/SetupForOracle.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Elsa.EntityFrameworkCore.Extensions; -using Elsa.Workflows.Management.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Elsa.EntityFrameworkCore.Modules.Management; - -/// -/// Represents a class that handles entity model creation for SQLite databases. -/// -public class SetupForOracle : IEntityModelCreatingHandler -{ - /// - public void Handle(ElsaDbContextBase dbContext, ModelBuilder modelBuilder, IMutableEntityType entityType) - { - if(!dbContext.Database.IsOracle()) - return; - - // In order to use data more than 2000 char we have to use NCLOB. - // In Oracle, we have to explicitly say the column is NCLOB otherwise it would be considered nvarchar(2000). - modelBuilder.Entity().Property("Data").HasColumnType("NCLOB"); - modelBuilder.Entity().Property("StringData").HasColumnType("NCLOB"); - modelBuilder.Entity().Property("Data").HasColumnType("NCLOB"); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionPersistenceFeature.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionPersistenceFeature.cs index b84f3ef23..3b808a942 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionPersistenceFeature.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionPersistenceFeature.cs @@ -10,6 +10,7 @@ namespace Elsa.EntityFrameworkCore.Modules.Management; /// Configures the feature with an Entity Framework Core persistence provider. /// [DependsOn(typeof(WorkflowManagementFeature))] +[DependsOn(typeof(WorkflowDefinitionsFeature))] public class EFCoreWorkflowDefinitionPersistenceFeature(IModule module) : PersistenceFeatureBase(module) { /// diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs index 02e5286c1..226bb40e9 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowDefinitionStore.cs @@ -206,7 +206,7 @@ public class EFCoreWorkflowDefinitionStore(EntityStore x.Name == filter.Name); if (filter.Names != null) queryable = queryable.Where(x => filter.Names.Contains(x.Name!)); if (filter.UsableAsActivity != null) queryable = queryable.Where(x => EF.Property(x, "UsableAsActivity") == filter.UsableAsActivity); - if (!string.IsNullOrWhiteSpace(filter.SearchTerm)) queryable = queryable.Where(x => x.Name!.Contains(filter.SearchTerm) || x.Description!.Contains(filter.SearchTerm) || x.Id.Contains(filter.SearchTerm) || x.DefinitionId.Contains(filter.SearchTerm)); + if (!string.IsNullOrWhiteSpace(filter.SearchTerm)) queryable = queryable.Where(x => x.Name!.ToLower().Contains(filter.SearchTerm.ToLower()) || x.Description!.ToLower().Contains(filter.SearchTerm.ToLower()) || x.Id.Contains(filter.SearchTerm) || x.DefinitionId.Contains(filter.SearchTerm)); // TEMP: IsSystem may be null when upgrading from older versions of Elsa to 3.2. See issue #5366. // In a future version, we should remove this check and simply do queryable.Where(x => x.IsSystem == filter.IsSystem). diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowInstancePersistenceFeature.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowInstancePersistenceFeature.cs index 8858c9757..de019e808 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowInstancePersistenceFeature.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowInstancePersistenceFeature.cs @@ -10,6 +10,7 @@ namespace Elsa.EntityFrameworkCore.Modules.Management; /// Configures the feature with an Entity Framework Core persistence provider. /// [DependsOn(typeof(WorkflowManagementFeature))] +[DependsOn(typeof(WorkflowInstancesFeature))] public class EFCoreWorkflowInstancePersistenceFeature(IModule module) : PersistenceFeatureBase(module) { /// @@ -23,6 +24,5 @@ public class EFCoreWorkflowInstancePersistenceFeature(IModule module) : Persiste { base.Apply(); AddEntityStore(); - Services.AddScoped(); } } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowManagementPersistenceFeature.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowManagementPersistenceFeature.cs index 5699442c6..09e9e20b2 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowManagementPersistenceFeature.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Management/WorkflowManagementPersistenceFeature.cs @@ -1,33 +1,27 @@ using Elsa.Features.Attributes; using Elsa.Features.Services; -using Elsa.Workflows.Management.Entities; using Elsa.Workflows.Management.Features; using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.EntityFrameworkCore; namespace Elsa.EntityFrameworkCore.Modules.Management; /// /// Configures the and features with an Entity Framework Core persistence provider. /// -[DependsOn(typeof(WorkflowManagementFeature))] -[DependsOn(typeof(WorkflowInstancesFeature))] -[DependsOn(typeof(WorkflowDefinitionsFeature))] +[DependsOn(typeof(EFCoreWorkflowInstancePersistenceFeature))] +[DependsOn(typeof(EFCoreWorkflowDefinitionPersistenceFeature))] [PublicAPI] public class WorkflowManagementPersistenceFeature(IModule module) : PersistenceFeatureBase(module) { - /// - public override void Configure() + public override Action DbContextOptionsBuilder { - Module.Configure(feature => feature.WorkflowInstanceStore = sp => sp.GetRequiredService()); - Module.Configure(feature => feature.WorkflowDefinitionStore = sp => sp.GetRequiredService()); - } - - /// - public override void Apply() - { - base.Apply(); - AddEntityStore(); - AddEntityStore(); + get => base.DbContextOptionsBuilder; + set + { + base.DbContextOptionsBuilder = value; + Module.Configure(x => x.DbContextOptionsBuilder = value); + Module.Configure(x => x.DbContextOptionsBuilder = value); + } } } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/Configurations.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/Configurations.cs index 1e097aefa..140e84dab 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/Configurations.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/Configurations.cs @@ -68,6 +68,7 @@ public class Configurations : public void Configure(EntityTypeBuilder builder) { builder.HasKey(x => x.Id); + builder.Ignore(x => x.Key); builder.HasIndex(x => x.TenantId, $"IX_{nameof(SerializedKeyValuePair)}_{nameof(SerializedKeyValuePair.TenantId)}"); } diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/DbContext.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/DbContext.cs index 8977b797f..c03030bf1 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/DbContext.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/DbContext.cs @@ -57,8 +57,6 @@ public class RuntimeElsaDbContext : ElsaDbContextBase /// protected override void OnModelCreating(ModelBuilder modelBuilder) { - base.OnModelCreating(modelBuilder); - var config = new Configurations(); modelBuilder.ApplyConfiguration(config); modelBuilder.ApplyConfiguration(config); @@ -67,5 +65,7 @@ public class RuntimeElsaDbContext : ElsaDbContextBase modelBuilder.ApplyConfiguration(config); modelBuilder.ApplyConfiguration(config); modelBuilder.ApplyConfiguration(config); + + base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/SetupForOracle.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/SetupForOracle.cs deleted file mode 100644 index 2a4351a38..000000000 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/SetupForOracle.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Elsa.EntityFrameworkCore.Extensions; -using Elsa.KeyValues.Entities; -using Elsa.Workflows.Runtime.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Elsa.EntityFrameworkCore.Modules.Runtime; - -/// -/// Represents a class that handles entity model creation for SQLite databases. -/// -public class SetupForOracle : IEntityModelCreatingHandler -{ - /// - public void Handle(ElsaDbContextBase dbContext, ModelBuilder modelBuilder, IMutableEntityType entityType) - { - if (!dbContext.Database.IsOracle()) - return; - - // To use data more than 2000 char we have to use NCLOB. - // In Oracle, we have to explicitly say the column is NCLOB otherwise it would be considered nvarchar(2000). - modelBuilder.Entity().Property("SerializedPayload").HasColumnType("NCLOB"); - - modelBuilder.Entity().Property("SerializedActivityState").HasColumnType("NCLOB"); - modelBuilder.Entity().Property("SerializedPayload").HasColumnType("NCLOB"); - - modelBuilder.Entity().Property("SerializedActivityState").HasColumnType("NCLOB"); - modelBuilder.Entity().Property("SerializedException").HasColumnType("NCLOB"); - modelBuilder.Entity().Property("SerializedPayload").HasColumnType("NCLOB"); - modelBuilder.Entity().Property("SerializedOutputs").HasColumnType("NCLOB"); - - modelBuilder.Entity().Property("SerializedPayload").HasColumnType("NCLOB"); - modelBuilder.Entity().Property("SerializedMetadata").HasColumnType("NCLOB"); - - modelBuilder.Entity().Property("SerializedInput").HasColumnType("NCLOB"); - modelBuilder.Entity().Property("SerializedBookmarkPayload").HasColumnType("NCLOB"); - - modelBuilder.Entity().Property("SerializedValue").HasColumnType("NCLOB"); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/TriggerStore.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/TriggerStore.cs index 2b2e26913..05ab1d03b 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/TriggerStore.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/TriggerStore.cs @@ -37,8 +37,14 @@ public class EFCoreTriggerStore(EntityStore /// public async ValueTask ReplaceAsync(IEnumerable removed, IEnumerable added, CancellationToken cancellationToken = default) { - var filter = new TriggerFilter { Ids = removed.Select(r => r.Id).ToList() }; - await DeleteManyAsync(filter, cancellationToken); + var removedList = removed.ToList(); + + if(removedList.Count > 0) + { + var filter = new TriggerFilter { Ids = removedList.Select(r => r.Id).ToList() }; + await DeleteManyAsync(filter, cancellationToken); + } + await store.SaveManyAsync(added, OnSaveAsync, cancellationToken); } diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowRuntimePersistenceFeature.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowRuntimePersistenceFeature.cs index f7b5a00be..a3ab463db 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowRuntimePersistenceFeature.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Runtime/WorkflowRuntimePersistenceFeature.cs @@ -41,6 +41,5 @@ public class EFCoreWorkflowRuntimePersistenceFeature(IModule module) : Persisten AddEntityStore(); AddEntityStore(); AddStore(); - Services.AddScoped(); } } \ No newline at end of file diff --git a/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/DbContext.cs b/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/DbContext.cs index 90d4dad28..aaa6b23ac 100644 --- a/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/DbContext.cs +++ b/src/modules/Elsa.EntityFrameworkCore/Modules/Tenants/DbContext.cs @@ -19,7 +19,7 @@ public class TenantsElsaDbContext : ElsaDbContextBase /// /// The alteration plans. /// - public DbSet Tenants { get; set; } = default!; + public DbSet Tenants { get; set; } = null!; /// protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/src/modules/Elsa.Expressions/Elsa.Expressions.csproj b/src/modules/Elsa.Expressions/Elsa.Expressions.csproj index d696bff63..66d1a6e5e 100644 --- a/src/modules/Elsa.Expressions/Elsa.Expressions.csproj +++ b/src/modules/Elsa.Expressions/Elsa.Expressions.csproj @@ -15,6 +15,7 @@ + diff --git a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs index 85238b016..899a84ddc 100644 --- a/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs +++ b/src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs @@ -40,11 +40,11 @@ public static class ObjectConverter try { var convertedValue = value.ConvertTo(targetType, converterOptions); - return new Result(true, convertedValue, null); + return new(true, convertedValue, null); } catch (Exception e) { - return new Result(false, null, e); + return new(false, null, e); } } @@ -52,20 +52,23 @@ public static class ObjectConverter /// Attempts to convert the source value into the destination type. /// public static T? ConvertTo(this object? value, ObjectConverterOptions? converterOptions = null) => value != null ? (T?)value.ConvertTo(typeof(T), converterOptions) : default; - + private static JsonSerializerOptions? _defaultSerializerOptions; private static JsonSerializerOptions? _internalSerializerOptions; - - private static JsonSerializerOptions DefaultSerializerOptions => _defaultSerializerOptions ??= new JsonSerializerOptions + + private static JsonSerializerOptions DefaultSerializerOptions => _defaultSerializerOptions ??= new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true, ReferenceHandler = ReferenceHandler.Preserve, - Converters = { new JsonStringEnumConverter() }, + Converters = + { + new JsonStringEnumConverter() + }, Encoder = JavaScriptEncoder.Create(UnicodeRanges.All) }; - - private static JsonSerializerOptions InternalSerializerOptions => _internalSerializerOptions ??= new JsonSerializerOptions + + private static JsonSerializerOptions InternalSerializerOptions => _internalSerializerOptions ??= new() { Encoder = JavaScriptEncoder.Create(UnicodeRanges.All) }; @@ -81,7 +84,7 @@ public static class ObjectConverter var sourceType = value.GetType(); - if (sourceType == targetType) + if (targetType.IsAssignableFrom(sourceType)) return value; var serializerOptions = converterOptions?.SerializerOptions ?? DefaultSerializerOptions; @@ -101,24 +104,39 @@ public static class ObjectConverter if (value is JsonNode jsonNode) { - return underlyingTargetType switch + if (jsonNode is not JsonArray jsonArray) { - { } t when t == typeof(string) => jsonNode.ToString(), - { } t when t != typeof(object) || converterOptions?.DeserializeJsonObjectToObject == true => jsonNode.Deserialize(targetType, serializerOptions), - _ => jsonNode - }; + return underlyingTargetType switch + { + { } t when t == typeof(string) => jsonNode.ToString(), + { } t when t == typeof(ExpandoObject) && jsonNode.GetValueKind() == JsonValueKind.Object => JsonSerializer.Deserialize(jsonNode.ToJsonString()), + { } t when t != typeof(object) || converterOptions?.DeserializeJsonObjectToObject == true => jsonNode.Deserialize(targetType, serializerOptions), + _ => jsonNode + }; + } + + // Convert to target type if target type is an array or a generic collection. + if (targetType.IsArray || targetType.IsCollectionType()) + { + // The element type of the source array is JsonObject. If the element type of the target array is Object then return the source array as an array of JsonObjects. + // Deserializing normally would return an array of JsonElement instead of JsonObject, but we want to keep JsonObject elements: + var targetElementType = targetType.IsArray ? targetType.GetElementType()! : targetType.GenericTypeArguments[0]; + + if (targetElementType != typeof(object)) + return jsonArray.Deserialize(targetType, serializerOptions); + } } if (underlyingSourceType == typeof(string) && !underlyingTargetType.IsPrimitive && underlyingTargetType != typeof(object)) { var stringValue = (string)value; - + if (underlyingTargetType == typeof(byte[])) { // Byte arrays are serialized to base64, so in this case, we convert the string back to the requested target type of byte[]. return Convert.FromBase64String(stringValue); } - + try { var firstChar = stringValue.TrimStart().FirstOrDefault(); @@ -145,7 +163,7 @@ public static class ObjectConverter return ConvertAnyDateType(value, underlyingTargetType); var internalSerializerOptions = InternalSerializerOptions; - + if (typeof(IDictionary).IsAssignableFrom(underlyingSourceType) && underlyingTargetType.IsClass) { if (typeof(ExpandoObject) == underlyingTargetType) @@ -188,7 +206,7 @@ public static class ObjectConverter if (underlyingSourceType == typeof(double)) return Enum.ToObject(underlyingTargetType, Convert.ChangeType(value, typeof(int), CultureInfo.InvariantCulture)); - + if (underlyingSourceType == typeof(long)) return Enum.ToObject(underlyingTargetType, Convert.ChangeType(value, typeof(int), CultureInfo.InvariantCulture)); } @@ -203,7 +221,10 @@ public static class ObjectConverter // Perhaps it's a bit of a leap, but if the input is a string and the target type is IEnumerable, then let's assume the string is a comma-separated list of strings. if (typeof(IEnumerable).IsAssignableFrom(underlyingTargetType)) - return new[] { s }; + return new[] + { + s + }; } if (value is IEnumerable enumerable) @@ -246,9 +267,7 @@ public static class ObjectConverter { var dateTypes = new[] { - typeof(DateTime), - typeof(DateTimeOffset), - typeof(DateOnly) + typeof(DateTime), typeof(DateTimeOffset), typeof(DateOnly) }; return dateTypes.Contains(type); @@ -269,20 +288,20 @@ public static class ObjectConverter { DateTime dateTime => dateTime, DateTimeOffset dateTimeOffset => dateTimeOffset.DateTime, - DateOnly date => new DateTime(date.Year, date.Month, date.Day), + DateOnly date => new(date.Year, date.Month, date.Day), _ => throw new ArgumentException("Invalid value type.") }, { } t when t == typeof(DateTimeOffset) => value switch { - DateTime dateTime => new DateTimeOffset(dateTime), + DateTime dateTime => new(dateTime), DateTimeOffset dateTimeOffset => dateTimeOffset, - DateOnly date => new DateTimeOffset(date.Year, date.Month, date.Day, 0, 0, 0, TimeSpan.Zero), + DateOnly date => new(date.Year, date.Month, date.Day, 0, 0, 0, TimeSpan.Zero), _ => throw new ArgumentException("Invalid value type.") }, { } t when t == typeof(DateOnly) => value switch { - DateTime dateTime => new DateOnly(dateTime.Year, dateTime.Month, dateTime.Day), - DateTimeOffset dateTimeOffset => new DateOnly(dateTimeOffset.Year, dateTimeOffset.Month, dateTimeOffset.Day), + DateTime dateTime => new(dateTime.Year, dateTime.Month, dateTime.Day), + DateTimeOffset dateTimeOffset => new(dateTimeOffset.Year, dateTimeOffset.Month, dateTimeOffset.Day), DateOnly date => date, _ => throw new ArgumentException("Invalid value type.") }, diff --git a/src/modules/Elsa.Expressions/Models/ExpressionExecutionContext.cs b/src/modules/Elsa.Expressions/Models/ExpressionExecutionContext.cs index 1c5ff2806..3ca25e693 100644 --- a/src/modules/Elsa.Expressions/Models/ExpressionExecutionContext.cs +++ b/src/modules/Elsa.Expressions/Models/ExpressionExecutionContext.cs @@ -9,8 +9,9 @@ namespace Elsa.Expressions.Models; public class ExpressionExecutionContext( IServiceProvider serviceProvider, MemoryRegister memory, - ExpressionExecutionContext? parentContext = default, - IDictionary? transientProperties = default, + ExpressionExecutionContext? parentContext = null, + IDictionary? transientProperties = null, + Action? onChange = null, CancellationToken cancellationToken = default) { /// @@ -54,7 +55,7 @@ public class ExpressionExecutionContext( public bool TryGetBlock(MemoryBlockReference blockReference, out MemoryBlock block) { var b = GetBlockInternal(blockReference); - block = b ?? default!; + block = b ?? null!; return b != null; } @@ -79,7 +80,7 @@ public class ExpressionExecutionContext( return true; } - value = default; + value = null; return false; } @@ -96,16 +97,17 @@ public class ExpressionExecutionContext( /// /// Sets the value of the memory block pointed to by the specified memory block reference. /// - public void Set(Func blockReference, object? value, Action? configure = default) => Set(blockReference(), value, configure); + public void Set(Func blockReference, object? value, Action? configure = null) => Set(blockReference(), value, configure); /// /// Sets the value of the memory block pointed to by the specified memory block reference. /// - public void Set(MemoryBlockReference blockReference, object? value, Action? configure = default) + public void Set(MemoryBlockReference blockReference, object? value, Action? configure = null) { var block = GetBlockInternal(blockReference) ?? Memory.Declare(blockReference); block.Value = value; configure?.Invoke(block); + onChange?.Invoke(); } /// diff --git a/src/modules/Elsa.Hangfire/Elsa.Hangfire.csproj b/src/modules/Elsa.Hangfire/Elsa.Hangfire.csproj index 8b940ae22..2474602ce 100644 --- a/src/modules/Elsa.Hangfire/Elsa.Hangfire.csproj +++ b/src/modules/Elsa.Hangfire/Elsa.Hangfire.csproj @@ -8,14 +8,14 @@ - - - + + + - - + + diff --git a/src/modules/Elsa.Hangfire/Extensions/JobStorageExtensions.cs b/src/modules/Elsa.Hangfire/Extensions/JobStorageExtensions.cs index 64ed51057..5036635eb 100644 --- a/src/modules/Elsa.Hangfire/Extensions/JobStorageExtensions.cs +++ b/src/modules/Elsa.Hangfire/Extensions/JobStorageExtensions.cs @@ -23,7 +23,7 @@ public static class JobStorageExtensions { scheduledJobs = api.ScheduledJobs(skip, take); - var jobs = scheduledJobs.FindAll(x => x.Value.Job.Type == typeof(RunWorkflowJob) || x.Value.Job.Type == typeof(ResumeWorkflowJob)); + var jobs = scheduledJobs.FindAll(x => x.Value.Job?.Type == typeof(RunWorkflowJob) || x.Value.Job?.Type == typeof(ResumeWorkflowJob)); foreach (var job in jobs.Where(x => (string)x.Value.Job.Args[0] == name)) yield return job; @@ -45,7 +45,7 @@ public static class JobStorageExtensions { enqueuedJobs = api.EnqueuedJobs(queueName, skip, take); - var jobs = enqueuedJobs.FindAll(x => x.Value.Job.Type == typeof(RunWorkflowJob) || x.Value.Job.Type == typeof(ResumeWorkflowJob)); + var jobs = enqueuedJobs.FindAll(x => x.Value.Job?.Type == typeof(RunWorkflowJob) || x.Value.Job?.Type == typeof(ResumeWorkflowJob)); foreach (var job in jobs.Where(x => (string)x.Value.Job.Args[0] == taskName)) yield return job; diff --git a/src/modules/Elsa.Hangfire/Extensions/ModuleExtensions.cs b/src/modules/Elsa.Hangfire/Extensions/ModuleExtensions.cs index ab493f267..b06060b46 100644 --- a/src/modules/Elsa.Hangfire/Extensions/ModuleExtensions.cs +++ b/src/modules/Elsa.Hangfire/Extensions/ModuleExtensions.cs @@ -25,6 +25,7 @@ public static class ModuleExtensions /// /// Configures Hangfire to use SQL Server storage. Only use this feature if you are not configuring Hangfire yourself. /// + [Obsolete("Configure storage directly on the HangfireFeature.")] public static HangfireFeature UseSqlServerStorage(this HangfireFeature feature, Action configure) { feature.Module.Use(configure); @@ -34,6 +35,7 @@ public static class ModuleExtensions /// /// Configures Hangfire to use SQLite storage. Only use this feature if you are not configuring Hangfire yourself. /// + [Obsolete("Configure storage directly on the HangfireFeature.")] public static HangfireFeature UseSqliteStorage(this HangfireFeature feature, Action configure) { feature.Module.Use(configure); diff --git a/src/modules/Elsa.Hangfire/Features/HangfireFeature.cs b/src/modules/Elsa.Hangfire/Features/HangfireFeature.cs index 25c359cfe..0162ccd11 100644 --- a/src/modules/Elsa.Hangfire/Features/HangfireFeature.cs +++ b/src/modules/Elsa.Hangfire/Features/HangfireFeature.cs @@ -1,5 +1,7 @@ using Elsa.Features.Abstractions; +using Elsa.Features.Attributes; using Elsa.Features.Services; +using Elsa.Workflows.Runtime.Features; using Hangfire; using Hangfire.MemoryStorage; using Newtonsoft.Json; @@ -9,35 +11,63 @@ namespace Elsa.Hangfire.Features; /// /// Sets up Hangfire. If you're setting up Hangfire yourself, then you should not enable this feature. /// -public class HangfireFeature : FeatureBase +[DependsOn(typeof(WorkflowRuntimeFeature))] // Ensure that the workflow runtime feature's hosted services have executed before Hangfire Server starts. +public class HangfireFeature(IModule module) : FeatureBase(module) { - /// - public HangfireFeature(IModule module) : base(module) - { - } - /// /// A delegate that configures Hangfire. /// - public Action ConfigureHangfire { get; set; } = (_, cfg) => cfg.UseMemoryStorage(); + private Action _configureHangfire = (_, _) => { }; /// /// A delegate that configures Hangfire's background job server options. /// - public Action ConfigureBackgroundServerOptions { get; set; } = (_, _) => { }; + private Action _configureBackgroundServerOptions = (_, _) => { }; /// /// A delegate that creates a job storage instance. /// - public Func CreateJobStorage { get; set; } = () => new MemoryStorage(); + private Func _createJobStorage = () => new MemoryStorage(); + + /// + /// Configures Hangfire. + /// + public HangfireFeature ConfigureHangfire(Action configure) + { + _configureHangfire += configure; + return this; + } + + /// + /// Configures Hangfire's background job server options. + /// + public HangfireFeature ConfigureBackgroundServerOptions(Action configure) + { + _configureBackgroundServerOptions += configure; + return this; + } + + public HangfireFeature UseMemoryStorage() + { + return UseJobStorage(new MemoryStorage()); + } + + public HangfireFeature UseJobStorage(JobStorage storage) + { + _createJobStorage = () => storage; + return this; + } /// public override void Apply() { + var jobStorage = _createJobStorage(); + Action configAction = (sp, cfg) => { cfg.UseSimpleAssemblyNameTypeSerializer(); cfg.UseRecommendedSerializerSettings(json => json.TypeNameHandling = TypeNameHandling.Objects); + cfg.UseStorage(jobStorage); }; Action serverOptionsAction = (sp, options) => @@ -46,10 +76,10 @@ public class HangfireFeature : FeatureBase options.SchedulePollingInterval = TimeSpan.FromSeconds(1); }; - configAction += ConfigureHangfire; - serverOptionsAction += ConfigureBackgroundServerOptions; + configAction += _configureHangfire; + serverOptionsAction += _configureBackgroundServerOptions; Services.AddHangfire(configAction); - Services.AddHangfireServer(serverOptionsAction, CreateJobStorage()); + Services.AddHangfireServer(serverOptionsAction, jobStorage); } } \ No newline at end of file diff --git a/src/modules/Elsa.Hangfire/Features/HangfireSqlServerStorageFeature.cs b/src/modules/Elsa.Hangfire/Features/HangfireSqlServerStorageFeature.cs index 480a5a3bf..fafb92528 100644 --- a/src/modules/Elsa.Hangfire/Features/HangfireSqlServerStorageFeature.cs +++ b/src/modules/Elsa.Hangfire/Features/HangfireSqlServerStorageFeature.cs @@ -11,18 +11,14 @@ namespace Elsa.Hangfire.Features; /// Configures the Hangfire feature to use SQL Server storage. If you're setting up Hangfire yourself, then you should not enable this feature. /// [DependsOn(typeof(HangfireFeature))] -public class HangfireSqlServerStorageFeature : FeatureBase +[Obsolete("Configure storage directly on the HangfireFeature.")] +public class HangfireSqlServerStorageFeature(IModule module) : FeatureBase(module) { - /// - public HangfireSqlServerStorageFeature(IModule module) : base(module) - { - } - /// /// The connection string to use when connecting to SQL Server, or the name of the connection string. /// - public string NameOrConnectionString { get; set; } = default!; - + public string NameOrConnectionString { get; set; } = null!; + /// /// Configures the SQL Server storage options. /// @@ -41,13 +37,11 @@ public class HangfireSqlServerStorageFeature : FeatureBase UseRecommendedIsolationLevel = true }; ConfigureSqlServerStorageOptions(storageOptions); - - hangfireFeature.ConfigureHangfire = (_, cfg) => + + hangfireFeature.ConfigureHangfire((_, cfg) => { cfg.UseSqlServerStorage(NameOrConnectionString, storageOptions); - }; - - hangfireFeature.CreateJobStorage = () => new SqlServerStorage(NameOrConnectionString, storageOptions); + }); }); } } \ No newline at end of file diff --git a/src/modules/Elsa.Hangfire/Features/HangfireSqliteStorageFeature.cs b/src/modules/Elsa.Hangfire/Features/HangfireSqliteStorageFeature.cs index 7982a078b..dcd7353df 100644 --- a/src/modules/Elsa.Hangfire/Features/HangfireSqliteStorageFeature.cs +++ b/src/modules/Elsa.Hangfire/Features/HangfireSqliteStorageFeature.cs @@ -10,17 +10,13 @@ namespace Elsa.Hangfire.Features; /// Configures the Hangfire feature to use SQLite storage. If you're setting up Hangfire yourself, then you should not enable this feature. /// [DependsOn(typeof(HangfireFeature))] -public class HangfireSqliteStorageFeature : FeatureBase +[Obsolete("Configure storage directly on the HangfireFeature.")] +public class HangfireSqliteStorageFeature(IModule module) : FeatureBase(module) { - /// - public HangfireSqliteStorageFeature(IModule module) : base(module) - { - } - /// /// The connection string to use when connecting to SQL Server, or the name of the connection string. /// - public string NameOrConnectionString { get; set; } = default!; + public string NameOrConnectionString { get; set; } = null!; /// /// Configures the SQL Server storage options. @@ -38,12 +34,10 @@ public class HangfireSqliteStorageFeature : FeatureBase }; ConfigureSqlServerStorageOptions(storageOptions); - hangfireFeature.ConfigureHangfire = (_, cfg) => + hangfireFeature.ConfigureHangfire((_, cfg) => { cfg.UseSQLiteStorage(NameOrConnectionString, storageOptions); - }; - - hangfireFeature.CreateJobStorage = () => new SQLiteStorage(NameOrConnectionString, storageOptions); + }); }); } } \ No newline at end of file diff --git a/src/modules/Elsa.Hangfire/Jobs/ExecuteBackgroundActivityJob.cs b/src/modules/Elsa.Hangfire/Jobs/ExecuteBackgroundActivityJob.cs index 5a9f2622c..8af4fb8da 100644 --- a/src/modules/Elsa.Hangfire/Jobs/ExecuteBackgroundActivityJob.cs +++ b/src/modules/Elsa.Hangfire/Jobs/ExecuteBackgroundActivityJob.cs @@ -1,28 +1,32 @@ +using Elsa.Common.Multitenancy; using Elsa.Workflows.Runtime; +using JetBrains.Annotations; namespace Elsa.Hangfire.Jobs; /// /// A job that executes a background activity. /// -public class ExecuteBackgroundActivityJob +[UsedImplicitly] +public class ExecuteBackgroundActivityJob(IBackgroundActivityInvoker backgroundActivityInvoker, ITenantFinder tenantFinder, ITenantAccessor tenantAccessor) { - private readonly IBackgroundActivityInvoker _backgroundActivityInvoker; - /// - /// Initializes a new instance of the class. + /// Executes the job. /// - /// - public ExecuteBackgroundActivityJob(IBackgroundActivityInvoker backgroundActivityInvoker) + public async Task ExecuteAsync(ScheduledBackgroundActivity scheduledBackgroundActivity, string? tenantId, CancellationToken cancellationToken = default) { - _backgroundActivityInvoker = backgroundActivityInvoker; + var tenant = tenantId != null ? await tenantFinder.FindByIdAsync(tenantId, cancellationToken) : null; + using var scope = tenantAccessor.PushContext(tenant); + await backgroundActivityInvoker.ExecuteAsync(scheduledBackgroundActivity, cancellationToken); } /// /// Executes the job. /// + [Obsolete("Use the other overload.")] + [UsedImplicitly] public async Task ExecuteAsync(ScheduledBackgroundActivity scheduledBackgroundActivity, CancellationToken cancellationToken = default) { - await _backgroundActivityInvoker.ExecuteAsync(scheduledBackgroundActivity, cancellationToken); + await backgroundActivityInvoker.ExecuteAsync(scheduledBackgroundActivity, cancellationToken); } } \ No newline at end of file diff --git a/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs b/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs index c0bc31706..aac9941e2 100644 --- a/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs +++ b/src/modules/Elsa.Hangfire/Jobs/ResumeWorkflowJob.cs @@ -1,21 +1,44 @@ -using Elsa.Scheduling; +using Elsa.Common.Multitenancy; +using Elsa.Scheduling; using Elsa.Workflows.Runtime; using Elsa.Workflows.Runtime.Messages; +using Hangfire; +using JetBrains.Annotations; namespace Elsa.Hangfire.Jobs; /// /// A job that resumes a workflow. /// -public class ResumeWorkflowJob(IWorkflowRuntime workflowRuntime) +public class ResumeWorkflowJob(IWorkflowRuntime workflowRuntime, ITenantFinder tenantFinder, ITenantAccessor tenantAccessor) { /// /// Executes the job. /// - /// The name of the job. + /// A unique name for this job. /// The workflow request. + /// The ID of the current tenant scheduling this job. /// The cancellation token. - public async Task ExecuteAsync(string name, ScheduleExistingWorkflowInstanceRequest request, CancellationToken cancellationToken) + // ReSharper disable once UnusedParameter.Global + [AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Fail)] + public async Task ExecuteAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, string? tenantId, CancellationToken cancellationToken) + { + var tenant = tenantId != null ? await tenantFinder.FindByIdAsync(tenantId, cancellationToken) : null; + using var scope = tenantAccessor.PushContext(tenant); + var client = await workflowRuntime.CreateClientAsync(request.WorkflowInstanceId, cancellationToken); + var runRequest = new RunWorkflowInstanceRequest + { + BookmarkId = request.BookmarkId, + ActivityHandle = request.ActivityHandle, + Input = request.Input, + Properties = request.Properties + }; + await client.RunInstanceAsync(runRequest, cancellationToken); + } + + [Obsolete("Use the other overload.")] + [UsedImplicitly] + public async Task ExecuteAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, CancellationToken cancellationToken) { var client = await workflowRuntime.CreateClientAsync(request.WorkflowInstanceId, cancellationToken); var runRequest = new RunWorkflowInstanceRequest diff --git a/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs b/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs index 708d75a44..94a979e28 100644 --- a/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs +++ b/src/modules/Elsa.Hangfire/Jobs/RunWorkflowJob.cs @@ -1,21 +1,46 @@ +using Elsa.Common.Multitenancy; using Elsa.Scheduling; using Elsa.Workflows.Runtime; using Elsa.Workflows.Runtime.Messages; +using Hangfire; +using JetBrains.Annotations; namespace Elsa.Hangfire.Jobs; /// /// A job that resumes a workflow. /// -public class RunWorkflowJob(IWorkflowRuntime workflowRuntime) +public class RunWorkflowJob(IWorkflowRuntime workflowRuntime, ITenantFinder tenantFinder, ITenantAccessor tenantAccessor) { /// /// Executes the job. /// - /// The name of the job. + /// A unique name for this job. /// The workflow request. + /// The ID of the current tenant scheduling this job. /// The cancellation token. - public async Task ExecuteAsync(string name, ScheduleNewWorkflowInstanceRequest request, CancellationToken cancellationToken) + // ReSharper disable once UnusedParameter.Global + [AutomaticRetry(OnAttemptsExceeded = AttemptsExceededAction.Fail)] + public async Task ExecuteAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, string? tenantId, CancellationToken cancellationToken) + { + var tenant = tenantId != null ? await tenantFinder.FindByIdAsync(tenantId, cancellationToken) : null; + using var scope = tenantAccessor.PushContext(tenant); + var client = await workflowRuntime.CreateClientAsync(cancellationToken); + var createAndRunRequest = new CreateAndRunWorkflowInstanceRequest + { + WorkflowDefinitionHandle = request.WorkflowDefinitionHandle, + TriggerActivityId = request.TriggerActivityId, + CorrelationId = request.CorrelationId, + Input = request.Input, + Properties = request.Properties, + ParentId = request.ParentId + }; + await client.CreateAndRunInstanceAsync(createAndRunRequest, cancellationToken); + } + + [Obsolete("Use the other overload.")] + [UsedImplicitly] + public async Task ExecuteAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, CancellationToken cancellationToken) { var client = await workflowRuntime.CreateClientAsync(cancellationToken); var createAndRunRequest = new CreateAndRunWorkflowInstanceRequest diff --git a/src/modules/Elsa.Hangfire/Services/HangfireBackgroundActivityScheduler.cs b/src/modules/Elsa.Hangfire/Services/HangfireBackgroundActivityScheduler.cs index 798d6cfd2..ab3ac2089 100644 --- a/src/modules/Elsa.Hangfire/Services/HangfireBackgroundActivityScheduler.cs +++ b/src/modules/Elsa.Hangfire/Services/HangfireBackgroundActivityScheduler.cs @@ -1,3 +1,4 @@ +using Elsa.Common.Multitenancy; using Elsa.Hangfire.Jobs; using Elsa.Hangfire.States; using Elsa.Workflows.Runtime; @@ -9,11 +10,12 @@ namespace Elsa.Hangfire.Services; /// /// Invokes activities from a background worker within the context of its workflow instance using Hangfire. /// -public class HangfireBackgroundActivityScheduler(IBackgroundJobClient backgroundJobClient) : IBackgroundActivityScheduler +public class HangfireBackgroundActivityScheduler(IBackgroundJobClient backgroundJobClient, ITenantAccessor tenantAccessor) : IBackgroundActivityScheduler { public Task CreateAsync(ScheduledBackgroundActivity scheduledBackgroundActivity, CancellationToken cancellationToken = default) { - var jobId = backgroundJobClient.Create(x => x.ExecuteAsync(scheduledBackgroundActivity, CancellationToken.None), new PendingState()); + var tenantId = tenantAccessor.Tenant?.Id; + var jobId = backgroundJobClient.Create(x => x.ExecuteAsync(scheduledBackgroundActivity, tenantId, CancellationToken.None), new PendingState()); return Task.FromResult(jobId); } @@ -28,7 +30,8 @@ public class HangfireBackgroundActivityScheduler(IBackgroundJobClient background /// public Task ScheduleAsync(ScheduledBackgroundActivity scheduledBackgroundActivity, CancellationToken cancellationToken = default) { - var jobId = backgroundJobClient.Enqueue(x => x.ExecuteAsync(scheduledBackgroundActivity, CancellationToken.None)); + var tenantId = tenantAccessor.Tenant?.Id; + var jobId = backgroundJobClient.Enqueue(x => x.ExecuteAsync(scheduledBackgroundActivity, tenantId, CancellationToken.None)); return Task.FromResult(jobId); } diff --git a/src/modules/Elsa.Hangfire/Services/HangfireWorkflowScheduler.cs b/src/modules/Elsa.Hangfire/Services/HangfireWorkflowScheduler.cs index 802555b3c..743af48e1 100644 --- a/src/modules/Elsa.Hangfire/Services/HangfireWorkflowScheduler.cs +++ b/src/modules/Elsa.Hangfire/Services/HangfireWorkflowScheduler.cs @@ -1,3 +1,4 @@ +using Elsa.Common.Multitenancy; using Elsa.Hangfire.Extensions; using Elsa.Hangfire.Jobs; using Elsa.Scheduling; @@ -9,33 +10,25 @@ namespace Elsa.Hangfire.Services; /// /// An implementation of that uses Hangfire. /// -public class HangfireWorkflowScheduler : IWorkflowScheduler +public class HangfireWorkflowScheduler( + IBackgroundJobClient backgroundJobClient, + IRecurringJobManager recurringJobManager, + ITenantAccessor tenantAccessor, + JobStorage jobStorage) : IWorkflowScheduler { - private readonly IBackgroundJobClient _backgroundJobClient; - private readonly IRecurringJobManager _recurringJobManager; - private readonly JobStorage _jobStorage; - - /// - /// Initializes a new instance of the class. - /// - public HangfireWorkflowScheduler(IBackgroundJobClient backgroundJobClient, IRecurringJobManager recurringJobManager, JobStorage jobStorage) - { - _backgroundJobClient = backgroundJobClient; - _recurringJobManager = recurringJobManager; - _jobStorage = jobStorage; - } - /// public ValueTask ScheduleAtAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, DateTimeOffset at, CancellationToken cancellationToken = default) { - _backgroundJobClient.Schedule(job => job.ExecuteAsync(taskName, request, CancellationToken.None), at); + var tenantId = tenantAccessor.Tenant?.Id; + backgroundJobClient.Schedule(job => job.ExecuteAsync(taskName, request, tenantId, CancellationToken.None), at); return ValueTask.CompletedTask; } /// public ValueTask ScheduleAtAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, DateTimeOffset at, CancellationToken cancellationToken = default) { - _backgroundJobClient.Schedule(job => job.ExecuteAsync(taskName, request, CancellationToken.None), at); + var tenantId = tenantAccessor.Tenant?.Id; + backgroundJobClient.Schedule(job => job.ExecuteAsync(taskName, request, tenantId, CancellationToken.None), at); return ValueTask.CompletedTask; } @@ -54,14 +47,16 @@ public class HangfireWorkflowScheduler : IWorkflowScheduler /// public ValueTask ScheduleCronAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, string cronExpression, CancellationToken cancellationToken = default) { - _recurringJobManager.AddOrUpdate(taskName, job => job.ExecuteAsync(taskName, request, CancellationToken.None), cronExpression); + var tenantId = tenantAccessor.Tenant?.Id; + recurringJobManager.AddOrUpdate(taskName, job => job.ExecuteAsync(taskName, request, tenantId, CancellationToken.None), cronExpression); return ValueTask.CompletedTask; } /// public ValueTask ScheduleCronAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, string cronExpression, CancellationToken cancellationToken = default) { - _recurringJobManager.AddOrUpdate(taskName, job => job.ExecuteAsync(taskName, request, CancellationToken.None), cronExpression); + var tenantId = tenantAccessor.Tenant?.Id; + recurringJobManager.AddOrUpdate(taskName, job => job.ExecuteAsync(taskName, request, tenantId, CancellationToken.None), cronExpression); return ValueTask.CompletedTask; } @@ -75,18 +70,18 @@ public class HangfireWorkflowScheduler : IWorkflowScheduler private void DeleteJobByTaskName(string taskName) { var scheduledJobIds = GetScheduledJobIds(taskName); - foreach (var jobId in scheduledJobIds) _backgroundJobClient.Delete(jobId); + foreach (var jobId in scheduledJobIds) backgroundJobClient.Delete(jobId); var queuedJobsIds = GetQueuedJobIds(taskName); - foreach (var jobId in queuedJobsIds) _backgroundJobClient.Delete(jobId); + foreach (var jobId in queuedJobsIds) backgroundJobClient.Delete(jobId); var recurringJobIds = GetRecurringJobIds(taskName); - foreach (var jobId in recurringJobIds) _recurringJobManager.RemoveIfExists(jobId); + foreach (var jobId in recurringJobIds) recurringJobManager.RemoveIfExists(jobId); } private IEnumerable GetScheduledJobIds(string taskName) { - return _jobStorage.EnumerateScheduledJobs(taskName) + return jobStorage.EnumerateScheduledJobs(taskName) .Select(x => x.Key) .Distinct() .ToList(); @@ -94,7 +89,7 @@ public class HangfireWorkflowScheduler : IWorkflowScheduler private IEnumerable GetQueuedJobIds(string taskName) { - return _jobStorage.EnumerateQueuedJobs("default", taskName) + return jobStorage.EnumerateQueuedJobs("default", taskName) .Select(x => x.Key) .Distinct() .ToList(); @@ -102,7 +97,7 @@ public class HangfireWorkflowScheduler : IWorkflowScheduler private IEnumerable GetRecurringJobIds(string taskName) { - using var connection = _jobStorage.GetConnection(); + using var connection = jobStorage.GetConnection(); var jobs = connection.GetRecurringJobs().Where(x => x.Job.Type == typeof(TJob) && (string)x.Job.Args[0] == taskName); return jobs.Select(x => x.Id).Distinct().ToList(); } diff --git a/src/modules/Elsa.Http/Activities/HttpEndpoint.cs b/src/modules/Elsa.Http/Activities/HttpEndpoint.cs index dc148b15f..428d73adc 100644 --- a/src/modules/Elsa.Http/Activities/HttpEndpoint.cs +++ b/src/modules/Elsa.Http/Activities/HttpEndpoint.cs @@ -23,10 +23,10 @@ namespace Elsa.Http; public class HttpEndpoint : Trigger { internal const string HttpContextInputKey = "HttpContext"; - internal const string RequestPathInputKey = "RequestPath"; + internal const string PathInputKey = "Path"; /// - public HttpEndpoint([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public HttpEndpoint([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } @@ -38,7 +38,7 @@ public class HttpEndpoint : Trigger UIHint = InputUIHints.SingleLine, UIHandler = typeof(HttpEndpointPathUIHandler) )] - public Input Path { get; set; } = default!; + public Input Path { get; set; } = null!; /// /// The HTTP methods to accept. @@ -65,37 +65,37 @@ public class HttpEndpoint : Trigger /// The maximum time allowed to process the request. /// [Input(Description = "The maximum time allowed to process the request.", Category = "Upload")] - public Input RequestTimeout { get; set; } = default!; + public Input RequestTimeout { get; set; } = null!; /// /// The maximum request size allowed in bytes. /// [Input(Description = "The maximum request size allowed in bytes.", Category = "Upload")] - public Input RequestSizeLimit { get; set; } = default!; + public Input RequestSizeLimit { get; set; } = null!; /// /// The maximum request size allowed in bytes. /// [Input(Description = "The maximum file size allowed in bytes for an individual file.", Category = "Upload")] - public Input FileSizeLimit { get; set; } = default!; + public Input FileSizeLimit { get; set; } = null!; /// /// The allowed file extensions, /// [Input(Description = "Only file extensions in this list are allowed. Leave empty to allow all extensions", Category = "Upload", UIHint = InputUIHints.MultiText)] - public Input> AllowedFileExtensions { get; set; } = default!; + public Input> AllowedFileExtensions { get; set; } = null!; /// /// The allowed file extensions, /// [Input(Description = "File extensions in this list are forbidden. Leave empty to not block any extension.", Category = "Upload", UIHint = InputUIHints.MultiText)] - public Input> BlockedFileExtensions { get; set; } = default!; + public Input> BlockedFileExtensions { get; set; } = null!; /// /// The allowed file extensions, /// [Input(Description = "Only MIME types in this list are allowed. Leave empty to allow all types", Category = "Upload", UIHint = InputUIHints.MultiText)] - public Input> AllowedMimeTypes { get; set; } = default!; + public Input> AllowedMimeTypes { get; set; } = null!; /// /// A value indicating whether to expose the "Request too large" outcome. @@ -125,31 +125,31 @@ public class HttpEndpoint : Trigger /// The parsed request content, if any. /// [Output(Description = "The parsed request content, if any.")] - public Output ParsedContent { get; set; } = default!; + public Output ParsedContent { get; set; } = null!; /// /// The uploaded files, if any. /// [Output(Description = "The uploaded files, if any.", IsSerializable = false)] - public Output Files { get; set; } = default!; + public Output Files { get; set; } = null!; /// /// The parsed route data, if any. /// [Output(Description = "The parsed route data, if any.")] - public Output> RouteData { get; set; } = default!; + public Output> RouteData { get; set; } = null!; /// /// The querystring data, if any. /// [Output(Description = "The querystring data, if any.")] - public Output> QueryStringData { get; set; } = default!; + public Output> QueryStringData { get; set; } = null!; /// /// The headers, if any. /// [Output(Description = "The headers, if any.")] - public Output> Headers { get; set; } = default!; + public Output> Headers { get; set; } = null!; /// protected override IEnumerable GetTriggerPayloads(TriggerIndexingContext context) => GetBookmarkPayloads(context.ExpressionExecutionContext); @@ -205,7 +205,7 @@ public class HttpEndpoint : Trigger context.Set(Result, request); // Read route data, if any. - var path = context.GetWorkflowInput(RequestPathInputKey); + var path = context.GetWorkflowInput(PathInputKey); var routeData = GetRouteData(httpContext, path); var routeDictionary = routeData.Values.ToDictionary(route => route.Key, route => route.Value!); var queryStringDictionary = httpContext.Request.Query.ToObjectDictionary(); diff --git a/src/modules/Elsa.Http/Elsa.Http.csproj b/src/modules/Elsa.Http/Elsa.Http.csproj index 9f1f9fda3..aaa37a25c 100644 --- a/src/modules/Elsa.Http/Elsa.Http.csproj +++ b/src/modules/Elsa.Http/Elsa.Http.csproj @@ -13,7 +13,6 @@ - diff --git a/src/modules/Elsa.Http/Extensions/RouteExtensions.cs b/src/modules/Elsa.Http/Extensions/RouteExtensions.cs index a59e69013..79a9d0220 100644 --- a/src/modules/Elsa.Http/Extensions/RouteExtensions.cs +++ b/src/modules/Elsa.Http/Extensions/RouteExtensions.cs @@ -8,7 +8,7 @@ namespace Elsa.Extensions; public static class RouteExtensions { /// - /// Normalizes a route by ensuring a leading slash, removing any trailing slash and converting the path to lowercase. + /// Normalizes a route by ensuring a leading slash, removing any trailing slash. /// - public static string NormalizeRoute(this string path) => $"/{path.Trim('/').ToLowerInvariant()}"; + public static string NormalizeRoute(this string path) => $"/{path.Trim('/')}"; } \ No newline at end of file diff --git a/src/modules/Elsa.Http/Features/HttpFeature.cs b/src/modules/Elsa.Http/Features/HttpFeature.cs index 3115b1681..65c0e42d8 100644 --- a/src/modules/Elsa.Http/Features/HttpFeature.cs +++ b/src/modules/Elsa.Http/Features/HttpFeature.cs @@ -16,7 +16,6 @@ using Elsa.Http.Tasks; using Elsa.Http.UIHints; using Elsa.Workflows; using Elsa.Workflows.Management.Requests; -using Elsa.Workflows.Management.Responses; using FluentStorage; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.StaticFiles; @@ -168,7 +167,7 @@ public class HttpFeature(IModule module) : FeatureBase(module) .AddHttpContextAccessor() // Handlers. - .AddRequestHandler() + .AddNotificationHandler() .AddNotificationHandler() // Content parsers. diff --git a/src/modules/Elsa.Http/Handlers/ValidateWorkflowRequestHandler.cs b/src/modules/Elsa.Http/Handlers/ValidateWorkflowRequestHandler.cs index b1f706ef4..fb51064e7 100644 --- a/src/modules/Elsa.Http/Handlers/ValidateWorkflowRequestHandler.cs +++ b/src/modules/Elsa.Http/Handlers/ValidateWorkflowRequestHandler.cs @@ -2,9 +2,7 @@ using Elsa.Extensions; using Elsa.Http.Bookmarks; using Elsa.Mediator.Contracts; using Elsa.Workflows.Helpers; -using Elsa.Workflows.Management.Models; -using Elsa.Workflows.Management.Requests; -using Elsa.Workflows.Management.Responses; +using Elsa.Workflows.Management.Notifications; using Elsa.Workflows.Runtime; using Elsa.Workflows.Runtime.Filters; using JetBrains.Annotations; @@ -15,7 +13,7 @@ namespace Elsa.Http.Handlers; /// A handler that validates a workflow path and return any errors. /// [UsedImplicitly] -public class ValidateWorkflowRequestHandler : IRequestHandler +public class ValidateWorkflowRequestHandler : INotificationHandler { private readonly ITriggerStore _triggerStore; private readonly ITriggerIndexer _triggerIndexer; @@ -28,14 +26,17 @@ public class ValidateWorkflowRequestHandler : IRequestHandler - public async Task HandleAsync(ValidateWorkflowRequest request, CancellationToken cancellationToken) + + public async Task HandleAsync(WorkflowDefinitionValidating notification, CancellationToken cancellationToken) { - var workflow = request.Workflow; + var workflow = notification.Workflow; var httpEndpointTriggers = (await _triggerIndexer.GetTriggersAsync(workflow, cancellationToken)).Where(x => x.Payload is HttpEndpointBookmarkPayload).ToList(); - var publishedWorkflowsTriggers = (await _triggerStore.FindManyAsync(new TriggerFilter { Name = ActivityTypeNameHelper.GenerateTypeName(typeof(HttpEndpoint)) }, cancellationToken)).ToList(); - var validationErrors = new List(); + var filter = new TriggerFilter + { + Name = ActivityTypeNameHelper.GenerateTypeName(typeof(HttpEndpoint)) + }; + var publishedWorkflowsTriggers = (await _triggerStore.FindManyAsync(filter, cancellationToken)).ToList(); + var validationErrors = notification.ValidationErrors; foreach (var httpEndpointTrigger in httpEndpointTriggers) { @@ -53,9 +54,7 @@ public class ValidateWorkflowRequestHandler : IRequestHandler(TValue, JsonSerializerOptions)")] public async Task InvokeAsync(HttpContext httpContext, IServiceProvider serviceProvider) { - var path = GetPath(httpContext); + var path = httpContext.Request.Path.Value!.NormalizeRoute(); var matchingPath = GetMatchingRoute(serviceProvider, path).Route; var basePath = options.Value.BasePath?.ToString().NormalizeRoute(); @@ -61,7 +61,7 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, ITenantAccessor tenan var input = new Dictionary { [HttpEndpoint.HttpContextInputKey] = true, - [HttpEndpoint.RequestPathInputKey] = path.NormalizeRoute() + [HttpEndpoint.PathInputKey] = path }; var cancellationToken = httpContext.RequestAborted; @@ -325,8 +325,6 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, ITenantAccessor tenan } } - private string GetPath(HttpContext httpContext) => httpContext.Request.Path.Value!.NormalizeRoute(); - [RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Serialize(TValue, JsonSerializerOptions)")] private async Task HandleMultipleWorkflowsFoundAsync(HttpContext httpContext, Func> workflowMatches, CancellationToken cancellationToken) { diff --git a/src/modules/Elsa.Http/Services/RouteMatcher.cs b/src/modules/Elsa.Http/Services/RouteMatcher.cs index b72e59d1b..4d9de3ae7 100644 --- a/src/modules/Elsa.Http/Services/RouteMatcher.cs +++ b/src/modules/Elsa.Http/Services/RouteMatcher.cs @@ -12,13 +12,11 @@ public class RouteMatcher : IRouteMatcher /// public RouteValueDictionary? Match(string routeTemplate, string route) { - var normalizedRoute = route.NormalizeRoute(); - var normalizedRouteTemplate = routeTemplate.NormalizeRoute(); - var template = TemplateParser.Parse(normalizedRouteTemplate); + var template = TemplateParser.Parse(routeTemplate); var matcher = new TemplateMatcher(template, GetDefaults(template)); var values = new RouteValueDictionary(); - return matcher.TryMatch(normalizedRoute, values) ? values : null; + return matcher.TryMatch(route, values) ? values : null; } private static RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate) diff --git a/src/modules/Elsa.JavaScript/Extensions/EngineExtensions.cs b/src/modules/Elsa.JavaScript/Extensions/EngineExtensions.cs index b12b9f449..7219e79d5 100644 --- a/src/modules/Elsa.JavaScript/Extensions/EngineExtensions.cs +++ b/src/modules/Elsa.JavaScript/Extensions/EngineExtensions.cs @@ -24,12 +24,12 @@ public static class EngineExtensions internal static void SyncVariablesContainer(this Engine engine, IOptions options, string name, object? value) { - if (!options.Value.DisableWrappers) - { - // To ensure both variable accessor syntaxes work, we need to update the variables container in the engine as well as the context to keep them in sync. - var variablesContainer = (IDictionary)engine.GetValue("variables").ToObject()!; - variablesContainer[name] = ObjectConverterHelper.ProcessVariableValue(engine, value); - engine.SetValue("variables", variablesContainer); - } + if (options.Value.DisableWrappers || options.Value.DisableVariableCopying) + return; + + // To ensure both variable accessor syntaxes work, we need to update the variables container in the engine as well as the context to keep them in sync. + var variablesContainer = (IDictionary)engine.GetValue("variables").ToObject()!; + variablesContainer[name] = ObjectConverterHelper.ProcessVariableValue(engine, value); + engine.SetValue("variables", variablesContainer); } } \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Handlers/ConfigureEngineWithVariables.cs b/src/modules/Elsa.JavaScript/Handlers/ConfigureEngineWithVariables.cs index 417c4f5eb..3e1d4f6c9 100644 --- a/src/modules/Elsa.JavaScript/Handlers/ConfigureEngineWithVariables.cs +++ b/src/modules/Elsa.JavaScript/Handlers/ConfigureEngineWithVariables.cs @@ -1,4 +1,5 @@ using System.Dynamic; +using System.Text.RegularExpressions; using Elsa.Expressions.Models; using Elsa.Extensions; using Elsa.JavaScript.Extensions; @@ -8,7 +9,6 @@ using Elsa.JavaScript.Options; using Elsa.Mediator.Contracts; using Elsa.Workflows.Activities; using JetBrains.Annotations; -using Jint; using Jint.Native; using Microsoft.Extensions.Options; @@ -18,12 +18,14 @@ namespace Elsa.JavaScript.Handlers; /// A handler that configures the Jint engine with workflow variables. /// [UsedImplicitly] -public class ConfigureEngineWithVariables(IOptions options) : INotificationHandler, INotificationHandler +public partial class ConfigureEngineWithVariables(IOptions options) : INotificationHandler, INotificationHandler { + private bool IsEnabled => options.Value is { DisableWrappers: false, DisableVariableCopying: false }; + /// public Task HandleAsync(EvaluatingJavaScript notification, CancellationToken cancellationToken) { - if (options.Value.DisableWrappers) + if (!IsEnabled) return Task.CompletedTask; CopyVariablesIntoEngine(notification); @@ -32,7 +34,7 @@ public class ConfigureEngineWithVariables(IOptions options) : INoti public Task HandleAsync(EvaluatedJavaScript notification, CancellationToken cancellationToken) { - if (options.Value.DisableWrappers) + if (!IsEnabled) return Task.CompletedTask; CopyVariablesIntoWorkflowExecutionContext(notification); @@ -60,7 +62,8 @@ public class ConfigureEngineWithVariables(IOptions options) : INoti { var engine = notification.Engine; var context = notification.Context; - var variableNames = context.GetVariableNamesInScope().FilterInvalidVariableNames().ToList(); + var expression = notification.Expression; + var variableNames = GetUsedVariableNames(context, expression).ToList(); var variablesContainer = (IDictionary)new ExpandoObject(); foreach (var variableName in variableNames) @@ -73,6 +76,17 @@ public class ConfigureEngineWithVariables(IOptions options) : INoti engine.SetValue("variables", variablesContainer); } + private IEnumerable GetUsedVariableNames(ExpressionExecutionContext context, string expression) + { + var variableNames = context.GetVariableNamesInScope().FilterInvalidVariableNames(); + + var variableNamesInScript = ExtractVariableNamesRegex().Matches(expression) + .Select(m => m.Groups[1].Value) + .ToList(); + + return variableNames.Where(x => variableNamesInScript.Contains(x)); + } + private IEnumerable GetInputNames(ExpressionExecutionContext context) { var activityExecutionContext = context.TryGetActivityExecutionContext(out var aec) ? aec : null; @@ -95,4 +109,7 @@ public class ConfigureEngineWithVariables(IOptions options) : INoti activityExecutionContext = activityExecutionContext.ParentActivityExecutionContext; } } + + [GeneratedRegex(@"variables\.(\w+)(?:\.\w+)*")] + private static partial Regex ExtractVariableNamesRegex(); } \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/ObjectConverters/JsonElementConverter.cs b/src/modules/Elsa.JavaScript/ObjectConverters/JsonElementConverter.cs new file mode 100644 index 000000000..499f1a990 --- /dev/null +++ b/src/modules/Elsa.JavaScript/ObjectConverters/JsonElementConverter.cs @@ -0,0 +1,37 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Nodes; +using Jint; +using Jint.Native; +using Jint.Runtime.Interop; + +namespace Elsa.JavaScript.ObjectConverters; + +internal class JsonElementConverter : IObjectConverter +{ + public bool TryConvert(Engine engine, object value, [NotNullWhen(true)] out JsValue? result) + { + if (value is JsonElement jsonElement) + { + result = ConvertJsonElementToJsValue(engine, jsonElement); + return true; + } + + result = JsValue.Null; + return false; + } + + private static JsValue ConvertJsonElementToJsValue(Engine engine, JsonElement element) => + element.ValueKind switch + { + JsonValueKind.Object => JsValue.FromObject(engine, JsonObject.Create(element)), + JsonValueKind.Array => JsValue.FromObject(engine, JsonArray.Create(element)), + JsonValueKind.String => JsValue.FromObject(engine, element.GetString()), + JsonValueKind.Number => element.TryGetInt32(out var intValue) ? JsNumber.Create(intValue) : JsNumber.Create(element.GetDouble()), + JsonValueKind.True => JsBoolean.True, + JsonValueKind.False => JsBoolean.False, + JsonValueKind.Undefined => JsValue.Undefined, + JsonValueKind.Null => JsValue.Null, + _ => throw new InvalidOperationException($"Unsupported JsonValueKind: {element.ValueKind}") + }; +} \ No newline at end of file diff --git a/src/modules/Elsa.JavaScript/Options/JintOptions.cs b/src/modules/Elsa.JavaScript/Options/JintOptions.cs index 2cff888b7..ad611cc72 100644 --- a/src/modules/Elsa.JavaScript/Options/JintOptions.cs +++ b/src/modules/Elsa.JavaScript/Options/JintOptions.cs @@ -47,6 +47,12 @@ public class JintOptions /// public bool DisableWrappers { get; set; } + /// + /// Disables copying workflow variables into the Jint engine and copying them back into the workflow execution context. + /// Disabling this option will increase performance but will also prevent you from accessing workflow variables from within JavaScript expressions using the variables.MyVariable syntax. + /// + public bool DisableVariableCopying { get; set; } + /// /// Configures the Jint engine options. /// diff --git a/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs b/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs index 0f16cab66..94fdf51b6 100644 --- a/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs +++ b/src/modules/Elsa.JavaScript/Services/JintJavaScriptEvaluator.cs @@ -91,7 +91,7 @@ public class JintJavaScriptEvaluator(IConfiguration configuration, INotification private void ConfigureObjectConverters(Jint.Options options) { - options.Interop.ObjectConverters.AddRange([new ByteArrayConverter(), new EnumToStringConverter()]); + options.Interop.ObjectConverters.AddRange([new ByteArrayConverter(), new EnumToStringConverter(), new JsonElementConverter()]); } private void ConfigureArgumentGetters(Engine engine, ExpressionEvaluatorOptions options) diff --git a/src/modules/Elsa.Kafka/Implementations/WorkerManager.cs b/src/modules/Elsa.Kafka/Implementations/WorkerManager.cs index b3383d123..364ffd6a7 100644 --- a/src/modules/Elsa.Kafka/Implementations/WorkerManager.cs +++ b/src/modules/Elsa.Kafka/Implementations/WorkerManager.cs @@ -165,6 +165,10 @@ public class WorkerManager(IHasher hasher, IServiceScopeFactory scopeFactory) : private async Task CreateWorkerAsync(IServiceProvider serviceProvider, ConsumerDefinition consumerDefinition, CancellationToken cancellationToken) { var factoryType = consumerDefinition.FactoryType; + + if(factoryType == null!) + throw new InvalidOperationException("Worker factory type not specified."); + var consumerFactory = ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider, factoryType) as IConsumerFactory; if (consumerFactory == null) diff --git a/src/modules/Elsa.MassTransit/Activities/MessageReceived.cs b/src/modules/Elsa.MassTransit/Activities/MessageReceived.cs index d0a32890a..905bc9d53 100644 --- a/src/modules/Elsa.MassTransit/Activities/MessageReceived.cs +++ b/src/modules/Elsa.MassTransit/Activities/MessageReceived.cs @@ -32,20 +32,32 @@ public class MessageReceived : Trigger protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { // If we did not receive external input, it means we are just now encountering this activity and we need to block execution by creating a bookmark. - if (!context.TryGetWorkflowInput(InputKey, out var message)) + if (!context.TryGetWorkflowInput(InputKey, out var message) || message.GetType() != MessageType) { // Create bookmarks for when we receive the expected HTTP request. - context.CreateBookmark(GetBookmarkPayload(context.ExpressionExecutionContext)); + context.CreateBookmark(GetBookmarkPayload(context.ExpressionExecutionContext), ResumeAsync, includeActivityInstanceId: false); return; } // Provide the received message as output. context.Set(Result, message); + // Remove the input to prevent it from being passed to the next activity. + context.WorkflowInput.Remove(InputKey); + // Complete. await context.CompleteActivityAsync(); } + private ValueTask ResumeAsync(ActivityExecutionContext context) + { + // Remove the input to prevent it from being passed to the next activity. + context.WorkflowInput.Remove(InputKey); + + // Complete. + return context.CompleteActivityAsync(); + } + private object GetBookmarkPayload(ExpressionExecutionContext context) { // Generate bookmark data for message type. diff --git a/src/modules/Elsa.MassTransit/Services/MassTransitWorkflowDispatcher.cs b/src/modules/Elsa.MassTransit/Services/MassTransitWorkflowDispatcher.cs index 472950649..e7b7d64ce 100644 --- a/src/modules/Elsa.MassTransit/Services/MassTransitWorkflowDispatcher.cs +++ b/src/modules/Elsa.MassTransit/Services/MassTransitWorkflowDispatcher.cs @@ -30,12 +30,12 @@ public class MassTransitWorkflowDispatcher( : IWorkflowDispatcher { /// - public async Task DispatchAsync(DispatchWorkflowDefinitionRequest request, DispatchWorkflowOptions? options = default, CancellationToken cancellationToken = default) + public async Task DispatchAsync(DispatchWorkflowDefinitionRequest request, DispatchWorkflowOptions? options = null, CancellationToken cancellationToken = default) { var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(request.DefinitionVersionId, cancellationToken); if (workflowGraph == null) - throw new Exception($"Workflow definition version with ID '{request.DefinitionVersionId}' not found"); + throw new($"Workflow definition version with ID '{request.DefinitionVersionId}' not found"); var workflowInstanceOptions = new WorkflowInstanceOptions { @@ -51,7 +51,7 @@ public class MassTransitWorkflowDispatcher( } /// - public async Task DispatchAsync(DispatchWorkflowInstanceRequest request, DispatchWorkflowOptions? options = default, CancellationToken cancellationToken = default) + public async Task DispatchAsync(DispatchWorkflowInstanceRequest request, DispatchWorkflowOptions? options = null, CancellationToken cancellationToken = default) { var sendEndpoint = await GetSendEndpointAsync(options); var serializedInput = SerializeInput(request.Input); @@ -68,7 +68,7 @@ public class MassTransitWorkflowDispatcher( } /// - public async Task DispatchAsync(DispatchTriggerWorkflowsRequest request, DispatchWorkflowOptions? options = default, CancellationToken cancellationToken = default) + public async Task DispatchAsync(DispatchTriggerWorkflowsRequest request, DispatchWorkflowOptions? options = null, CancellationToken cancellationToken = default) { await DispatchTriggersAsync(request, options, cancellationToken); await DispatchBookmarksAsync(request, options, cancellationToken); @@ -76,7 +76,7 @@ public class MassTransitWorkflowDispatcher( } /// - public async Task DispatchAsync(DispatchResumeWorkflowsRequest request, DispatchWorkflowOptions? options = default, CancellationToken cancellationToken = default) + public async Task DispatchAsync(DispatchResumeWorkflowsRequest request, DispatchWorkflowOptions? options = null, CancellationToken cancellationToken = default) { var hash = stimulusHasher.Hash(request.ActivityTypeName, request.BookmarkPayload, request.ActivityInstanceId); var correlationId = request.CorrelationId; @@ -94,7 +94,7 @@ public class MassTransitWorkflowDispatcher( return DispatchWorkflowResponse.Success(); } - private async Task DispatchTriggersAsync(DispatchTriggerWorkflowsRequest request, DispatchWorkflowOptions? options = default, CancellationToken cancellationToken = default) + private async Task DispatchTriggersAsync(DispatchTriggerWorkflowsRequest request, DispatchWorkflowOptions? options = null, CancellationToken cancellationToken = default) { var triggerHash = stimulusHasher.Hash(request.ActivityTypeName, request.BookmarkPayload); var triggerFilter = new TriggerFilter @@ -127,13 +127,13 @@ public class MassTransitWorkflowDispatcher( private async Task DispatchWorkflowAsync(Workflow workflow, WorkflowInstanceOptions? workflowInstanceOptions, string? triggerActivityId, DispatchWorkflowOptions? options, CancellationToken cancellationToken) { - var workflowInstance = await workflowInstanceManager.CreateWorkflowInstanceAsync(workflow, workflowInstanceOptions, cancellationToken); + var workflowInstance = await workflowInstanceManager.CreateAndCommitWorkflowInstanceAsync(workflow, workflowInstanceOptions, cancellationToken); var sendEndpoint = await GetSendEndpointAsync(options); var message = DispatchWorkflowDefinition.DispatchExistingWorkflowInstance(workflowInstance.Id, triggerActivityId); await sendEndpoint.Send(message, cancellationToken); } - private async Task DispatchBookmarksAsync(DispatchTriggerWorkflowsRequest request, DispatchWorkflowOptions? options = default, CancellationToken cancellationToken = default) + private async Task DispatchBookmarksAsync(DispatchTriggerWorkflowsRequest request, DispatchWorkflowOptions? options = null, CancellationToken cancellationToken = default) { var correlationId = request.CorrelationId; var workflowInstanceId = request.WorkflowInstanceId; @@ -169,10 +169,10 @@ public class MassTransitWorkflowDispatcher( } } - private async Task GetSendEndpointAsync(DispatchWorkflowOptions? options = default) + private async Task GetSendEndpointAsync(DispatchWorkflowOptions? options = null) { var endpointName = endpointChannelFormatter.FormatEndpointName(options?.Channel); - var sendEndpoint = await bus.GetSendEndpoint(new Uri($"queue:{endpointName}")); + var sendEndpoint = await bus.GetSendEndpoint(new($"queue:{endpointName}")); return sendEndpoint; } diff --git a/src/modules/Elsa.MongoDb/Common/MongoDbStore.cs b/src/modules/Elsa.MongoDb/Common/MongoDbStore.cs index c070fd188..9c5c3b674 100644 --- a/src/modules/Elsa.MongoDb/Common/MongoDbStore.cs +++ b/src/modules/Elsa.MongoDb/Common/MongoDbStore.cs @@ -405,7 +405,7 @@ public class MongoDbStore(IMongoCollection collection, ITe if (typeof(Entity).IsAssignableFrom(typeof(TDocument))) { var tenant = tenantAccessor.Tenant; - var tenantId = tenant?.Id; + var tenantId = tenant?.Id.EmptyToNull(); queryable = queryable.Where(x => (x as Entity)!.TenantId == tenantId); } @@ -418,7 +418,7 @@ public class MongoDbStore(IMongoCollection collection, ITe var tenantId = tenant?.Id; if (document is Entity tenantDocument) - tenantDocument.TenantId = tenantId; + tenantDocument.TenantId = tenantId.EmptyToNull(); } private void ApplyTenantId(IEnumerable documents) @@ -429,7 +429,7 @@ public class MongoDbStore(IMongoCollection collection, ITe foreach (var document in documents) { if (document is Entity tenantDocument) - tenantDocument.TenantId = tenantId; + tenantDocument.TenantId = tenantId.EmptyToNull(); } } } \ No newline at end of file diff --git a/src/modules/Elsa.MongoDb/Extensions/StringExtensions.cs b/src/modules/Elsa.MongoDb/Extensions/StringExtensions.cs new file mode 100644 index 000000000..cc806f04a --- /dev/null +++ b/src/modules/Elsa.MongoDb/Extensions/StringExtensions.cs @@ -0,0 +1,6 @@ +namespace Elsa.MongoDb.Extensions; + +public static class StringExtensions +{ + public static string? EmptyToNull(this string? value) => string.IsNullOrWhiteSpace(value) ? null : value; +} \ No newline at end of file diff --git a/src/modules/Elsa.Quartz.EntityFrameworkCore.MySql/Elsa.Quartz.EntityFrameworkCore.MySql.csproj b/src/modules/Elsa.Quartz.EntityFrameworkCore.MySql/Elsa.Quartz.EntityFrameworkCore.MySql.csproj index e9362407a..422ff83a5 100644 --- a/src/modules/Elsa.Quartz.EntityFrameworkCore.MySql/Elsa.Quartz.EntityFrameworkCore.MySql.csproj +++ b/src/modules/Elsa.Quartz.EntityFrameworkCore.MySql/Elsa.Quartz.EntityFrameworkCore.MySql.csproj @@ -1,7 +1,7 @@ - net6.0;net8.0 + net8.0;net9.0 Provides EF Core migrations for Quartz.NET. diff --git a/src/modules/Elsa.Quartz/Elsa.Quartz.csproj b/src/modules/Elsa.Quartz/Elsa.Quartz.csproj index d58faa3c6..1d9418298 100644 --- a/src/modules/Elsa.Quartz/Elsa.Quartz.csproj +++ b/src/modules/Elsa.Quartz/Elsa.Quartz.csproj @@ -1,7 +1,7 @@ - net6.0;net8.0;net9.0 + net8.0;net9.0 Provides integration with the Quartz.NET library and provide am implementation of Elsa's IJobScheduler using Quartz.NET. diff --git a/src/modules/Elsa.Retention/Jobs/CleanupJob.cs b/src/modules/Elsa.Retention/Jobs/CleanupJob.cs index 330e9d41e..764e21395 100644 --- a/src/modules/Elsa.Retention/Jobs/CleanupJob.cs +++ b/src/modules/Elsa.Retention/Jobs/CleanupJob.cs @@ -4,7 +4,6 @@ using Elsa.Retention.Contracts; using Elsa.Retention.Options; using Elsa.Workflows.Management; using Elsa.Workflows.Management.Entities; -using Elsa.Workflows.Management.Filters; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -31,7 +30,6 @@ public class CleanupJob( /// public async Task ExecuteAsync(CancellationToken cancellationToken = default) { - Console.WriteLine(DateTime.Now.ToLongTimeString()); var collectors = GetServices(typeof(IRelatedEntityCollector), typeof(IRelatedEntityCollector<>)); var deletedWorkflowInstances = 0L; diff --git a/src/modules/Elsa.Scheduling/Features/SchedulingFeature.cs b/src/modules/Elsa.Scheduling/Features/SchedulingFeature.cs index 9cbd1a11c..7ef3dfdec 100644 --- a/src/modules/Elsa.Scheduling/Features/SchedulingFeature.cs +++ b/src/modules/Elsa.Scheduling/Features/SchedulingFeature.cs @@ -45,9 +45,7 @@ public class SchedulingFeature : FeatureBase .AddSingleton(CronParser) .AddScoped() .AddScoped() - .AddSingleton() .AddScoped() - .AddSingleton() .AddSingleton(CronParser) .AddScoped(WorkflowScheduler) .AddBackgroundTask() diff --git a/src/modules/Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql/efcore-3.3.sh b/src/modules/Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql/efcore-3.3.sh index 69fc0c617..dff83d4a2 100644 --- a/src/modules/Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql/efcore-3.3.sh +++ b/src/modules/Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql/efcore-3.3.sh @@ -1 +1 @@ -ef-migration-runtime-schema --interface Elsa.EntityFrameworkCore.Common.Contracts.IElsaDbContextSchema --efOptions "migrations add V3_3 -c SecretsDbContext -o Migrations" \ No newline at end of file +ef-migration-runtime-schema --interface Elsa.EntityFrameworkCore.IElsaDbContextSchema --efOptions "migrations add V3_3 -c SecretsDbContext -o Migrations" \ No newline at end of file diff --git a/src/modules/Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer/efcore-3.3.sh b/src/modules/Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer/efcore-3.3.sh index 69fc0c617..dff83d4a2 100644 --- a/src/modules/Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer/efcore-3.3.sh +++ b/src/modules/Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer/efcore-3.3.sh @@ -1 +1 @@ -ef-migration-runtime-schema --interface Elsa.EntityFrameworkCore.Common.Contracts.IElsaDbContextSchema --efOptions "migrations add V3_3 -c SecretsDbContext -o Migrations" \ No newline at end of file +ef-migration-runtime-schema --interface Elsa.EntityFrameworkCore.IElsaDbContextSchema --efOptions "migrations add V3_3 -c SecretsDbContext -o Migrations" \ No newline at end of file diff --git a/src/modules/Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite/efcore-3.3.sh b/src/modules/Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite/efcore-3.3.sh index 69fc0c617..dff83d4a2 100644 --- a/src/modules/Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite/efcore-3.3.sh +++ b/src/modules/Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite/efcore-3.3.sh @@ -1 +1 @@ -ef-migration-runtime-schema --interface Elsa.EntityFrameworkCore.Common.Contracts.IElsaDbContextSchema --efOptions "migrations add V3_3 -c SecretsDbContext -o Migrations" \ No newline at end of file +ef-migration-runtime-schema --interface Elsa.EntityFrameworkCore.IElsaDbContextSchema --efOptions "migrations add V3_3 -c SecretsDbContext -o Migrations" \ No newline at end of file diff --git a/src/modules/Elsa.Sql.MySql/Elsa.Sql.MySql.csproj b/src/modules/Elsa.Sql.MySql/Elsa.Sql.MySql.csproj new file mode 100644 index 000000000..54c6148dc --- /dev/null +++ b/src/modules/Elsa.Sql.MySql/Elsa.Sql.MySql.csproj @@ -0,0 +1,18 @@ + + + + + Provides client implementations for interacting with MySql databases. + + elsa module activities sql mysql + + + + + + + + + + + diff --git a/src/modules/Elsa.Sql.MySql/FodyWeavers.xml b/src/modules/Elsa.Sql.MySql/FodyWeavers.xml new file mode 100644 index 000000000..00e1d9a1c --- /dev/null +++ b/src/modules/Elsa.Sql.MySql/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/modules/Elsa.Sql.MySql/MySqlClient.cs b/src/modules/Elsa.Sql.MySql/MySqlClient.cs new file mode 100644 index 000000000..60d84fd3e --- /dev/null +++ b/src/modules/Elsa.Sql.MySql/MySqlClient.cs @@ -0,0 +1,55 @@ +using MySql.Data.MySqlClient; +using Elsa.Sql.Client; +using System.Data; + +namespace Elsa.Sql.MySql; + +public class MySqlClient : BaseSqlClient, ISqlClient +{ + private string? _connectionString; + + /// + /// MySql client implimentation. + /// + /// + public MySqlClient(string? connectionString) => _connectionString = connectionString; + + /// + /// + /// + public async Task ExecuteCommandAsync(string sqlCommand) + { + using var connection = new MySqlConnection(_connectionString); + connection.Open(); + var command = new MySqlCommand(sqlCommand, connection); + + var result = await command.ExecuteNonQueryAsync(); + return result; + } + + /// + /// + /// + public async Task ExecuteScalarAsync(string sqlQuery) + { + using var connection = new MySqlConnection(_connectionString); + connection.Open(); + var command = new MySqlCommand(sqlQuery, connection); + + var result = await command.ExecuteScalarAsync(); + return result; + } + + /// + /// + /// + public async Task ExecuteQueryAsync(string sqlQuery) + { + using var connection = new MySqlConnection(_connectionString); + connection.Open(); + var command = new MySqlCommand(sqlQuery, connection); + + using var reader = await command.ExecuteReaderAsync(); + return await Task.FromResult(ReadAsDataSet(reader)); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql.PostgreSql/Elsa.Sql.PostgreSql.csproj b/src/modules/Elsa.Sql.PostgreSql/Elsa.Sql.PostgreSql.csproj new file mode 100644 index 000000000..82a290850 --- /dev/null +++ b/src/modules/Elsa.Sql.PostgreSql/Elsa.Sql.PostgreSql.csproj @@ -0,0 +1,18 @@ + + + + + Provides client implementations for interacting with PostgreSql databases. + + elsa module activities sql postgresql + + + + + + + + + + + diff --git a/src/modules/Elsa.Sql.PostgreSql/FodyWeavers.xml b/src/modules/Elsa.Sql.PostgreSql/FodyWeavers.xml new file mode 100644 index 000000000..00e1d9a1c --- /dev/null +++ b/src/modules/Elsa.Sql.PostgreSql/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/modules/Elsa.Sql.PostgreSql/PostgreSqlClient.cs b/src/modules/Elsa.Sql.PostgreSql/PostgreSqlClient.cs new file mode 100644 index 000000000..495439f30 --- /dev/null +++ b/src/modules/Elsa.Sql.PostgreSql/PostgreSqlClient.cs @@ -0,0 +1,55 @@ +using Npgsql; +using Elsa.Sql.Client; +using System.Data; + +namespace Elsa.Sql.PostgreSql; + +public class PostgreSqlClient : BaseSqlClient, ISqlClient +{ + private string? _connectionString; + + /// + /// PostgreSQL client implimentation. + /// + /// + public PostgreSqlClient(string? connectionString) => _connectionString = connectionString; + + /// + /// + /// + public async Task ExecuteCommandAsync(string sqlCommand) + { + using var connection = new NpgsqlConnection(_connectionString); + connection.Open(); + var command = new NpgsqlCommand(sqlCommand, connection); + + var result = await command.ExecuteNonQueryAsync(); + return result; + } + + /// + /// + /// + public async Task ExecuteScalarAsync(string sqlQuery) + { + using var connection = new NpgsqlConnection(_connectionString); + connection.Open(); + var command = new NpgsqlCommand(sqlQuery, connection); + + var result = await command.ExecuteScalarAsync(); + return result; + } + + /// + /// + /// + public async Task ExecuteQueryAsync(string sqlQuery) + { + using var connection = new NpgsqlConnection(_connectionString); + connection.Open(); + var command = new NpgsqlCommand(sqlQuery, connection); + + using var reader = await command.ExecuteReaderAsync(); + return await Task.FromResult(ReadAsDataSet(reader)); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql.SqlServer/Elsa.Sql.SqlServer.csproj b/src/modules/Elsa.Sql.SqlServer/Elsa.Sql.SqlServer.csproj new file mode 100644 index 000000000..cab38ce95 --- /dev/null +++ b/src/modules/Elsa.Sql.SqlServer/Elsa.Sql.SqlServer.csproj @@ -0,0 +1,18 @@ + + + + + Provides client implementations for interacting with Microsoft SQL Server databases. + + elsa module activities sql mssqlserver sqlserver + + + + + + + + + + + diff --git a/src/modules/Elsa.Sql.SqlServer/FodyWeavers.xml b/src/modules/Elsa.Sql.SqlServer/FodyWeavers.xml new file mode 100644 index 000000000..00e1d9a1c --- /dev/null +++ b/src/modules/Elsa.Sql.SqlServer/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/modules/Elsa.Sql.SqlServer/SqlServerClient.cs b/src/modules/Elsa.Sql.SqlServer/SqlServerClient.cs new file mode 100644 index 000000000..f5b57c263 --- /dev/null +++ b/src/modules/Elsa.Sql.SqlServer/SqlServerClient.cs @@ -0,0 +1,55 @@ +using System.Data; +using Elsa.Sql.Client; +using Microsoft.Data.SqlClient; + +namespace Elsa.Sql.SqlServer; + +public class SqlServerClient : BaseSqlClient, ISqlClient +{ + private string? _connectionString; + + /// + /// Microsoft SQL server client implimentation. + /// + /// + public SqlServerClient(string? connectionString) => _connectionString = connectionString; + + /// + /// + /// + public async Task ExecuteCommandAsync(string sqlCommand) + { + using var connection = new SqlConnection(_connectionString); + connection.Open(); + var command = new SqlCommand(sqlCommand, connection); + + var result = await command.ExecuteNonQueryAsync(); + return result; + } + + /// + /// + /// + public async Task ExecuteScalarAsync(string sqlQuery) + { + using var connection = new SqlConnection(_connectionString); + connection.Open(); + var command = new SqlCommand(sqlQuery, connection); + + var result = await command.ExecuteScalarAsync(); + return result; + } + + /// + /// + /// + public async Task ExecuteQueryAsync(string sqlQuery) + { + using var connection = new SqlConnection(_connectionString); + connection.Open(); + var command = new SqlCommand(sqlQuery, connection); + + using var reader = await command.ExecuteReaderAsync(); + return await Task.FromResult(ReadAsDataSet(reader)); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql.Sqlite/Elsa.Sql.Sqlite.csproj b/src/modules/Elsa.Sql.Sqlite/Elsa.Sql.Sqlite.csproj new file mode 100644 index 000000000..43bf2bf5a --- /dev/null +++ b/src/modules/Elsa.Sql.Sqlite/Elsa.Sql.Sqlite.csproj @@ -0,0 +1,18 @@ + + + + + Provides client implementations for interacting with Sqlite databases. + + elsa module activities sql sqlite + + + + + + + + + + + diff --git a/src/modules/Elsa.Sql.Sqlite/FodyWeavers.xml b/src/modules/Elsa.Sql.Sqlite/FodyWeavers.xml new file mode 100644 index 000000000..00e1d9a1c --- /dev/null +++ b/src/modules/Elsa.Sql.Sqlite/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/modules/Elsa.Sql.Sqlite/SqliteClient.cs b/src/modules/Elsa.Sql.Sqlite/SqliteClient.cs new file mode 100644 index 000000000..d67e69342 --- /dev/null +++ b/src/modules/Elsa.Sql.Sqlite/SqliteClient.cs @@ -0,0 +1,55 @@ +using System.Data; +using Elsa.Sql.Client; +using Microsoft.Data.Sqlite; + +namespace Elsa.Sql.Sqlite; + +public class SqliteClient : BaseSqlClient, ISqlClient +{ + private string? _connectionString; + + /// + /// Sqlite client implimentation. + /// + /// + public SqliteClient(string? connectionString) => _connectionString = connectionString; + + /// + /// + /// + public async Task ExecuteCommandAsync(string sqlCommand) + { + using var connection = new SqliteConnection(_connectionString); + connection.Open(); + var command = new SqliteCommand(sqlCommand, connection); + + var result = await command.ExecuteNonQueryAsync(); + return result; + } + + /// + /// + /// + public async Task ExecuteScalarAsync(string sqlQuery) + { + using var connection = new SqliteConnection(_connectionString); + connection.Open(); + var command = new SqliteCommand(sqlQuery, connection); + + var result = await command.ExecuteScalarAsync(); + return result; + } + + /// + /// + /// + public async Task ExecuteQueryAsync(string sqlQuery) + { + using var connection = new SqliteConnection(_connectionString); + connection.Open(); + var command = new SqliteCommand(sqlQuery, connection); + + using var reader = await command.ExecuteReaderAsync(); + return await Task.FromResult(ReadAsDataSet(reader)); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql/Activities/SqlCodeOptionsProvider.cs b/src/modules/Elsa.Sql/Activities/SqlCodeOptionsProvider.cs new file mode 100644 index 000000000..f61bad42f --- /dev/null +++ b/src/modules/Elsa.Sql/Activities/SqlCodeOptionsProvider.cs @@ -0,0 +1,10 @@ +using System.Reflection; +using Elsa.Workflows.UIHints.CodeEditor; + +// ReSharper disable once CheckNamespace +namespace Elsa.Sql.Activities; + +internal class SqlCodeOptionsProvider : CodeEditorOptionsProviderBase +{ + protected override string GetLanguage(PropertyInfo propertyInfo, object? context) => "sql"; +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql/Activities/SqlCommand.cs b/src/modules/Elsa.Sql/Activities/SqlCommand.cs new file mode 100644 index 000000000..da287a5db --- /dev/null +++ b/src/modules/Elsa.Sql/Activities/SqlCommand.cs @@ -0,0 +1,73 @@ +using System.Runtime.CompilerServices; +using Elsa.Extensions; +using Elsa.Sql.Contracts; +using Elsa.Sql.UIHints; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using Elsa.Workflows.UIHints; + +namespace Elsa.Sql.Activities; + +/// +/// Execute given SQL command and returns the number of rows affected. +/// +[Activity("Elsa", "SQL", "Execute given SQL command and returns the number of rows affected.", DisplayName = "SQL Command", Kind = ActivityKind.Task)] +public class SqlCommand : Activity +{ + /// + /// + /// + public SqlCommand([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base (source, line) + { + } + + /// + /// Database client to connect with. + /// + [Input( + Description = "Database client.", + UIHint = InputUIHints.DropDown, + UIHandler = typeof(SqlClientsDropDownProvider))] + public Input Client { get; set; } = default!; + + /// + /// Connection string. + /// + [Input( + Description = "Connection string.", + CanContainSecrets = true)] + public Input ConnectionString { get; set; } = default!; + + /// + /// Command to run against the database. + /// + [Input( + Description = "Command to run against the database.", + UIHint = InputUIHints.CodeEditor, + UIHandler = typeof(SqlCodeOptionsProvider) + )] + public Input Command { get; set; } = default!; + + + /// + /// The number of affected rows. + /// + [Output( + Description = "The number of rows affected.")] + public Output Result { get; set; } = default!; + + /// + /// + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var factory = context.GetRequiredService(); + var client = factory.CreateClient(Client.GetOrDefault(context), ConnectionString.GetOrDefault(context)); + + var result = await client.ExecuteCommandAsync(Command.GetOrDefault(context)); + context.Set(Result, result); + + await CompleteAsync(context); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql/Activities/SqlQuery.cs b/src/modules/Elsa.Sql/Activities/SqlQuery.cs new file mode 100644 index 000000000..27d9c34a1 --- /dev/null +++ b/src/modules/Elsa.Sql/Activities/SqlQuery.cs @@ -0,0 +1,75 @@ +using System.Data; +using System.Runtime.CompilerServices; +using Elsa.Extensions; +using Elsa.Sql.Contracts; +using Elsa.Sql.UIHints; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using Elsa.Workflows.UIHints; + +namespace Elsa.Sql.Activities; + +/// +/// Execute given SQL query and return the resulting data. +/// +[Activity("Elsa", "SQL", "Execute given SQL query and return the resulting data.", DisplayName = "SQL Query", Kind = ActivityKind.Task)] +public class SqlQuery : Activity +{ + /// + /// + /// + public SqlQuery([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + { + } + + /// + /// Database client to connect with. + /// + [Input( + Description = "Database client.", + UIHint = InputUIHints.DropDown, + UIHandler = typeof(SqlClientsDropDownProvider))] + public Input Client { get; set; } = default!; + + /// + /// Connection string. + /// + [Input( + Description = "Connection string.", + CanContainSecrets = true)] + public Input ConnectionString { get; set; } = default!; + + /// + /// Query to run against the database. + /// + [Input( + Description = "Query to run against the database.", + UIHint = InputUIHints.CodeEditor, + UIHandler = typeof(SqlCodeOptionsProvider) + )] + public Input Query { get; set; } = default!; + + + /// + /// of queried results. + /// + [Output( + Description = "DataSet of queried results.", + IsSerializable = false)] + public Output Results { get; set; } = default!; + + /// + /// + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var factory = context.GetRequiredService(); + var client = factory.CreateClient(Client.GetOrDefault(context), ConnectionString.GetOrDefault(context)); + + var results = await client.ExecuteQueryAsync(Query.GetOrDefault(context)); + context.Set(Results, results); + + await CompleteAsync(context); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql/Activities/SqlSingleValue.cs b/src/modules/Elsa.Sql/Activities/SqlSingleValue.cs new file mode 100644 index 000000000..0c5909bd9 --- /dev/null +++ b/src/modules/Elsa.Sql/Activities/SqlSingleValue.cs @@ -0,0 +1,73 @@ +using System.Runtime.CompilerServices; +using Elsa.Extensions; +using Elsa.Sql.Contracts; +using Elsa.Sql.UIHints; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using Elsa.Workflows.UIHints; + +namespace Elsa.Sql.Activities; + +/// +/// Execute given SQL command and return a single result. +/// +[Activity("Elsa", "SQL", "Execute given SQL command and return a single result.", DisplayName = "SQL Single Value", Kind = ActivityKind.Task)] +public class SqlSingleValue : Activity +{ + /// + /// + /// + public SqlSingleValue([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + { + } + + /// + /// Database client to connect with. + /// + [Input( + Description = "Database client.", + UIHint = InputUIHints.DropDown, + UIHandler = typeof(SqlClientsDropDownProvider))] + public Input Client { get; set; } = default!; + + /// + /// Connection string. + /// + [Input( + Description = "Connection string.", + CanContainSecrets = true)] + public Input ConnectionString { get; set; } = default!; + + /// + /// Query to run against the database. + /// + [Input( + Description = "Query to run against the database.", + UIHint = InputUIHints.CodeEditor, + UIHandler = typeof(SqlCodeOptionsProvider) + )] + public Input Query { get; set; } = default!; + + + /// + /// Command result. + /// + [Output( + Description = "Command result.")] + public Output Result { get; set; } = default!; + + /// + /// + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var factory = context.GetRequiredService(); + var client = factory.CreateClient(Client.GetOrDefault(context), ConnectionString.GetOrDefault(context)); + + var result = await client.ExecuteScalarAsync(Query.GetOrDefault(context)); + context.Set(Result, result); + + await CompleteAsync(context); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql/Client/BaseSqlClient.cs b/src/modules/Elsa.Sql/Client/BaseSqlClient.cs new file mode 100644 index 000000000..6ed7dbfd9 --- /dev/null +++ b/src/modules/Elsa.Sql/Client/BaseSqlClient.cs @@ -0,0 +1,46 @@ +using System.Data; + +namespace Elsa.Sql.Client; + +public abstract class BaseSqlClient +{ + /// + /// Returns data as a . + /// + /// Reader to return data from. + /// of data. + protected static DataSet ReadAsDataSet(IDataReader reader) + { + var dataSet = new DataSet("dataset"); + dataSet.Tables.Add(ReadAsDataTable(reader)); + return dataSet; + } + + /// + /// Returns data as a . + /// + /// Reader to return data from. + /// of data. + protected static DataTable ReadAsDataTable(IDataReader reader) + { + var data = new DataTable(); + var schemaTable =reader.GetSchemaTable(); + + foreach (DataRow row in schemaTable.Rows) + { + string colName = row.Field("ColumnName"); + Type t = row.Field("DataType"); + data.Columns.Add(colName, t); + } + + while (reader.Read()) + { + var newRow = data.Rows.Add(); + foreach (DataColumn col in data.Columns) + { + newRow[col.ColumnName] = reader[col.ColumnName]; + } + } + return data; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql/Client/ISqlClient.cs b/src/modules/Elsa.Sql/Client/ISqlClient.cs new file mode 100644 index 000000000..8e4e3fe43 --- /dev/null +++ b/src/modules/Elsa.Sql/Client/ISqlClient.cs @@ -0,0 +1,27 @@ +using System.Data; + +namespace Elsa.Sql.Client; + +public interface ISqlClient +{ + /// + /// Asyncronously executes a Transact-SQL statement against the connection and returns the number of rows affected. + /// + /// The command to execute + /// The number of rows affected. + public Task ExecuteCommandAsync(string sqlCommand); + + /// + /// Asyncronously executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored. + /// + /// The query to execute + /// The first column of the first row in the result set, or a null reference if the result set is empty. Returns a maximum of 2033 characters. + public Task ExecuteScalarAsync(string sqlQuery); + + /// + /// Asyncronously executes the query, and returns a dataset of data returned by the query. + /// + /// Query to execute + /// DataSet of the quiried data + public Task ExecuteQueryAsync(string sqlQuery); +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql/Contracts/ISqlClientFactory.cs b/src/modules/Elsa.Sql/Contracts/ISqlClientFactory.cs new file mode 100644 index 000000000..de2d7771c --- /dev/null +++ b/src/modules/Elsa.Sql/Contracts/ISqlClientFactory.cs @@ -0,0 +1,14 @@ +using Elsa.Sql.Client; + +namespace Elsa.Sql.Contracts; + +public interface ISqlClientFactory +{ + /// + /// Create an instance of the registered client. + /// + /// The name of the registered client to create. This can either be clientName used during registration or the default nameof(client) itself. + /// Connection string. + /// + public ISqlClient CreateClient(string clientName, string connectionString); +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql/Contracts/ISqlClientNamesProvider.cs b/src/modules/Elsa.Sql/Contracts/ISqlClientNamesProvider.cs new file mode 100644 index 000000000..b1b48eef4 --- /dev/null +++ b/src/modules/Elsa.Sql/Contracts/ISqlClientNamesProvider.cs @@ -0,0 +1,11 @@ +namespace Elsa.Sql.Contracts; + +public interface ISqlClientNamesProvider +{ + /// + /// Returns a dictionary of registered clients. + /// + /// A token to monitor cancellation requests. + /// A of registered client names their . + Task> GetRegisteredSqlClientNamesAsync(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql/Elsa.Sql.csproj b/src/modules/Elsa.Sql/Elsa.Sql.csproj new file mode 100644 index 000000000..bfb24be1e --- /dev/null +++ b/src/modules/Elsa.Sql/Elsa.Sql.csproj @@ -0,0 +1,15 @@ + + + + + Provides activities to interact with sql databases. + + elsa module activities sql + + + + + + + + diff --git a/src/modules/Elsa.Sql/Extensions/ModuleExtensions.cs b/src/modules/Elsa.Sql/Extensions/ModuleExtensions.cs new file mode 100644 index 000000000..de787d172 --- /dev/null +++ b/src/modules/Elsa.Sql/Extensions/ModuleExtensions.cs @@ -0,0 +1,22 @@ +using Elsa.Features.Services; +using Elsa.Sql.Features; + +namespace Elsa.Sql.Extensions; + +/// +/// Provides methods to install and configure SQL client features. +/// +public static class ModuleExtensions +{ + /// + /// Adds the feature to the system. + /// + /// + /// + /// + public static IModule UseSql(this IModule configuration, Action? configure= default) + { + configuration.Configure(configure); + return configuration; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql/Factory/SqlClientFactory.cs b/src/modules/Elsa.Sql/Factory/SqlClientFactory.cs new file mode 100644 index 000000000..4422b099b --- /dev/null +++ b/src/modules/Elsa.Sql/Factory/SqlClientFactory.cs @@ -0,0 +1,43 @@ +using Elsa.Sql.Client; +using Elsa.Sql.Contracts; +using Elsa.Sql.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Sql.Factory; + +/// +/// SQL client factory +/// +public class SqlClientFactory : ISqlClientFactory +{ + private readonly IServiceProvider _serviceProvider; + + public SqlClientFactory(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + + /// + /// + /// + public ISqlClient CreateClient(string clientName, string connectionString) + { + if (string.IsNullOrEmpty(clientName)) + { + throw new ArgumentException($"Client name can not be empty or null.", nameof(clientName)); + } + if (string.IsNullOrEmpty(connectionString)) + { + throw new ArgumentException($"Connection string can not be empty or null.", nameof(connectionString)); + } + if (_serviceProvider.GetRequiredService().Clients.TryGetValue(clientName, out var clientType)) + { + try + { + return ActivatorUtilities.CreateInstance(_serviceProvider, clientType, connectionString) as ISqlClient; + } + catch (Exception ex) + { + throw new InvalidOperationException($"Unable to create instance of '{clientName}' of type '{clientType}'.", ex); + } + } + throw new ArgumentException($"No registered SQL client provider for '{clientName}'."); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql/Features/SqlFeature.cs b/src/modules/Elsa.Sql/Features/SqlFeature.cs new file mode 100644 index 000000000..e2593b641 --- /dev/null +++ b/src/modules/Elsa.Sql/Features/SqlFeature.cs @@ -0,0 +1,61 @@ +using Elsa.Extensions; +using Elsa.Features.Abstractions; +using Elsa.Features.Services; +using Elsa.Sql.Activities; +using Elsa.Sql.Contracts; +using Elsa.Sql.Factory; +using Elsa.Sql.Implimentations; +using Elsa.Sql.Services; +using Elsa.Sql.UIHints; +using Elsa.Workflows; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Sql.Features; + +/// +/// Setup SQL client features +/// +public class SqlFeature : FeatureBase +{ + /// + /// Set a callback to configure . + /// + public Action Clients { get; set; } = _ => { }; + + /// + /// + /// + /// + public SqlFeature(IModule module) : base(module) + { + } + + /// + /// + /// + public override void Configure() + { + Module.AddActivitiesFrom(); + } + + /// + /// + /// + public override void Apply() + { + Services + .AddSingleton(provider => + { + ClientStore clientRegistry = new(); + Clients.Invoke(clientRegistry); + return clientRegistry; + }) + .AddSingleton() + + // Providers + .AddScoped() + .AddScoped() + .AddScoped(); + + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql/FodyWeavers.xml b/src/modules/Elsa.Sql/FodyWeavers.xml new file mode 100644 index 000000000..00e1d9a1c --- /dev/null +++ b/src/modules/Elsa.Sql/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/modules/Elsa.Sql/Implimentations/SqlClientNamesProvider.cs b/src/modules/Elsa.Sql/Implimentations/SqlClientNamesProvider.cs new file mode 100644 index 000000000..5c2cba45c --- /dev/null +++ b/src/modules/Elsa.Sql/Implimentations/SqlClientNamesProvider.cs @@ -0,0 +1,23 @@ +using Elsa.Sql.Contracts; +using Elsa.Sql.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Sql.Implimentations; + +/// +/// Returns registered client names +/// +public class SqlClientNamesProvider : ISqlClientNamesProvider +{ + private readonly IServiceProvider _serviceProvider; + + public SqlClientNamesProvider(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + + /// + /// + /// + public Task> GetRegisteredSqlClientNamesAsync(CancellationToken cancellationToken) + { + return Task.FromResult(_serviceProvider.GetRequiredService().Clients); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql/Services/ClientStore.cs b/src/modules/Elsa.Sql/Services/ClientStore.cs new file mode 100644 index 000000000..d76727aea --- /dev/null +++ b/src/modules/Elsa.Sql/Services/ClientStore.cs @@ -0,0 +1,36 @@ +using Elsa.Sql.Client; + +namespace Elsa.Sql.Services; +public class ClientStore +{ + private readonly Dictionary clients = new(); + + /// + /// Dictionary of registered clients and their type. + /// + public IReadOnlyDictionary Clients => clients; + + /// + /// Registers the specified client type with the store. + /// The client type must inherit from . + /// + /// + /// The type of the client to be registered. The client must be a class that implements the interface. + /// + /// + /// The name of the client to register. If not provided, the name defaults to nameof(TClient). + /// This value is used as a key to identify the client in the store. + /// + /// + /// Thrown when a client with the same name is already registered in the store. + /// + /// + /// This method registers a client type to the store using a unique key. The key is either the provided or the default name derived from . + /// + public void Register(string? name) where TClient : class, ISqlClient + { + var key = string.IsNullOrEmpty(name) ? nameof(TClient) : name; + if (clients.ContainsKey(key)) { throw new InvalidOperationException($"Client with key '{name}' is already registered."); } + clients.Add(key, typeof(TClient)); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Sql/UIHints/SqlClientsDropDownProvider.cs b/src/modules/Elsa.Sql/UIHints/SqlClientsDropDownProvider.cs new file mode 100644 index 000000000..019271df6 --- /dev/null +++ b/src/modules/Elsa.Sql/UIHints/SqlClientsDropDownProvider.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using Elsa.Sql.Contracts; +using Elsa.Workflows.UIHints.Dropdown; + +namespace Elsa.Sql.UIHints; + +/// +/// Provides registered clients for the Client input field. +/// +/// +public class SqlClientsDropDownProvider(ISqlClientNamesProvider sqlClientNamesProvider) : DropDownOptionsProviderBase +{ + protected override async ValueTask> GetItemsAsync(PropertyInfo propertyInfo, object? context, CancellationToken cancellationToken) + { + var clients = await sqlClientNamesProvider.GetRegisteredSqlClientNamesAsync(cancellationToken); + return clients.Select(x => new SelectListItem(x.Key, x.Key)).ToList(); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants.AspNetCore/Features/MultitenantHttpRoutingFeature.cs b/src/modules/Elsa.Tenants.AspNetCore/Features/MultitenantHttpRoutingFeature.cs index 2def1e4d6..bdf0df3b8 100644 --- a/src/modules/Elsa.Tenants.AspNetCore/Features/MultitenantHttpRoutingFeature.cs +++ b/src/modules/Elsa.Tenants.AspNetCore/Features/MultitenantHttpRoutingFeature.cs @@ -3,6 +3,7 @@ using Elsa.Features.Abstractions; using Elsa.Features.Attributes; using Elsa.Features.Services; using Elsa.Http.Features; +using Elsa.Tenants.AspNetCore.Options; using Elsa.Tenants.AspNetCore.Services; using Elsa.Tenants.Features; using Microsoft.Extensions.DependencyInjection; @@ -13,6 +14,26 @@ namespace Elsa.Tenants.AspNetCore.Features; [DependencyOf(typeof(TenantsFeature))] public class MultitenantHttpRoutingFeature(IModule module) : FeatureBase(module) { + private Action _configureMultitenancyHttpOptions = _ => { }; + + /// + /// Configures the MultitenantHttpRoutingFeature to use a specific tenant header name. + /// + /// The name of the HTTP header used to identify the tenant. + /// The current instance of for fluent configuration. + public MultitenantHttpRoutingFeature WithTenantHeader(string headerName) => WithMultitenancyHttpOptions(options => options.TenantHeaderName = headerName); + + /// + /// Configures the MultitenantHttpRoutingFeature with custom multitenancy HTTP options. + /// + /// The action to configure . + /// The current instance of for fluent configuration. + public MultitenantHttpRoutingFeature WithMultitenancyHttpOptions(Action configure) + { + _configureMultitenancyHttpOptions = configure; + return this; + } + public override void Configure() { Module.Configure(feature => @@ -24,6 +45,9 @@ public class MultitenantHttpRoutingFeature(IModule module) : FeatureBase(module) public override void Apply() { + // Multitenancy HTTP options. + Services.Configure(_configureMultitenancyHttpOptions); + // Tenant resolvers. Services .AddScoped() diff --git a/src/modules/Elsa.Tenants.AspNetCore/Options/MultitenancyHttpOptions.cs b/src/modules/Elsa.Tenants.AspNetCore/Options/MultitenancyHttpOptions.cs new file mode 100644 index 000000000..877400eb6 --- /dev/null +++ b/src/modules/Elsa.Tenants.AspNetCore/Options/MultitenancyHttpOptions.cs @@ -0,0 +1,6 @@ +namespace Elsa.Tenants.AspNetCore.Options; + +public class MultitenancyHttpOptions +{ + public string TenantHeaderName { get; set; } = "X-Tenant-Id"; +} \ No newline at end of file diff --git a/src/modules/Elsa.Tenants.AspNetCore/Resolvers/HeaderTenantResolver.cs b/src/modules/Elsa.Tenants.AspNetCore/Resolvers/HeaderTenantResolver.cs index 06d36e387..697049285 100644 --- a/src/modules/Elsa.Tenants.AspNetCore/Resolvers/HeaderTenantResolver.cs +++ b/src/modules/Elsa.Tenants.AspNetCore/Resolvers/HeaderTenantResolver.cs @@ -1,12 +1,14 @@ using Elsa.Common.Multitenancy; +using Elsa.Tenants.AspNetCore.Options; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; namespace Elsa.Tenants.AspNetCore; /// /// Resolves the tenant based on the header in the request. /// -public class HeaderTenantResolver(IHttpContextAccessor httpContextAccessor) : TenantResolverBase +public class HeaderTenantResolver(IHttpContextAccessor httpContextAccessor, IOptions options) : TenantResolverBase { protected override TenantResolverResult Resolve(TenantResolverContext context) { @@ -15,7 +17,8 @@ public class HeaderTenantResolver(IHttpContextAccessor httpContextAccessor) : Te if (httpContext == null) return Unresolved(); - var tenantId = httpContext.Request.Headers["X-Tenant-Id"].FirstOrDefault(); + var headerName = options.Value.TenantHeaderName; + var tenantId = httpContext.Request.Headers[headerName].FirstOrDefault(); return AutoResolve(tenantId); } diff --git a/src/modules/Elsa.Workflows.Api/Elsa.Workflows.Api.csproj b/src/modules/Elsa.Workflows.Api/Elsa.Workflows.Api.csproj index 4fff95dc9..94828c0a3 100644 --- a/src/modules/Elsa.Workflows.Api/Elsa.Workflows.Api.csproj +++ b/src/modules/Elsa.Workflows.Api/Elsa.Workflows.Api.csproj @@ -15,4 +15,8 @@ + + + + diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/CommitStrategies/Activities/List/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/CommitStrategies/Activities/List/Endpoint.cs new file mode 100644 index 000000000..85fdcdcd0 --- /dev/null +++ b/src/modules/Elsa.Workflows.Api/Endpoints/CommitStrategies/Activities/List/Endpoint.cs @@ -0,0 +1,29 @@ +using Elsa.Abstractions; +using Elsa.Models; +using Elsa.Workflows.CommitStates; + +namespace Elsa.Workflows.Api.Endpoints.CommitStrategies.Activities.List; + +/// +/// Represents an API endpoint that provides a list of registered workflow commit strategies. +/// +/// +/// This class is an implementation of an endpoint that retrieves a collection of workflow commit strategy registrations +/// from a provided registry and returns them in a unified response. +/// +internal class List(ICommitStrategyRegistry registry) : ElsaEndpointWithoutRequest> +{ + public override void Configure() + { + Get("/descriptors/commit-strategies/activities"); + ConfigurePermissions("read:commit-strategies"); + } + + public override Task> ExecuteAsync(CancellationToken cancellationToken) + { + var registrations = registry.ListActivityStrategyRegistrations().ToList(); + var descriptors = CommitStrategyDescriptor.FromStrategyMetadata(registrations.Select(x => x.Metadata)).ToList(); + var response =new ListResponse(descriptors); + return Task.FromResult(response); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/CommitStrategies/Models.cs b/src/modules/Elsa.Workflows.Api/Endpoints/CommitStrategies/Models.cs new file mode 100644 index 000000000..0af9b5e50 --- /dev/null +++ b/src/modules/Elsa.Workflows.Api/Endpoints/CommitStrategies/Models.cs @@ -0,0 +1,16 @@ +using Elsa.Workflows.CommitStates; + +namespace Elsa.Workflows.Api.Endpoints.CommitStrategies; + +internal record CommitStrategyDescriptor(string Name, string DisplayName, string Description) +{ + public static CommitStrategyDescriptor FromStrategyMetadata(CommitStrategyMetadata metadata) + { + return new(metadata.Name, metadata.DisplayName, metadata.Description); + } + + public static IEnumerable FromStrategyMetadata(IEnumerable metadata) + { + return metadata.Select(FromStrategyMetadata); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/CommitStrategies/Workflows/List/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/CommitStrategies/Workflows/List/Endpoint.cs new file mode 100644 index 000000000..e7f3fc327 --- /dev/null +++ b/src/modules/Elsa.Workflows.Api/Endpoints/CommitStrategies/Workflows/List/Endpoint.cs @@ -0,0 +1,29 @@ +using Elsa.Abstractions; +using Elsa.Models; +using Elsa.Workflows.CommitStates; + +namespace Elsa.Workflows.Api.Endpoints.CommitStrategies.Workflows.List; + +/// +/// Represents an API endpoint that provides a list of registered workflow commit strategies. +/// +/// +/// This class is an implementation of an endpoint that retrieves a collection of workflow commit strategy registrations +/// from a provided registry and returns them in a unified response. +/// +internal class List(ICommitStrategyRegistry registry) : ElsaEndpointWithoutRequest> +{ + public override void Configure() + { + Get("/descriptors/commit-strategies/workflows"); + ConfigurePermissions("read:commit-strategies"); + } + + public override Task> ExecuteAsync(CancellationToken cancellationToken) + { + var registrations = registry.ListWorkflowStrategyRegistrations().ToList(); + var descriptors = CommitStrategyDescriptor.FromStrategyMetadata(registrations.Select(x => x.Metadata)).ToList(); + var response =new ListResponse(descriptors); + return Task.FromResult(response); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkDelete/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkDelete/Endpoint.cs index bce5264cf..1da5ff771 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkDelete/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkDelete/Endpoint.cs @@ -19,9 +19,9 @@ internal class BulkDelete(IWorkflowDefinitionManager workflowDefinitionManager, public override async Task ExecuteAsync(Request request, CancellationToken cancellationToken) { - var authorizationResult = authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(), AuthorizationPolicies.NotReadOnlyPolicy); + var authorizationResult = await authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(), AuthorizationPolicies.NotReadOnlyPolicy); - if (!authorizationResult.Result.Succeeded) + if (!authorizationResult.Succeeded) { await SendForbiddenAsync(cancellationToken); return null!; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkDeleteVersions/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkDeleteVersions/Endpoint.cs index a56f49821..11648b4ef 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkDeleteVersions/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkDeleteVersions/Endpoint.cs @@ -19,9 +19,9 @@ internal class BulkDeleteVersions(IWorkflowDefinitionManager workflowDefinitionM public override async Task ExecuteAsync(Request request, CancellationToken cancellationToken) { - var authorizationResult = authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(), AuthorizationPolicies.NotReadOnlyPolicy); + var authorizationResult = await authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(), AuthorizationPolicies.NotReadOnlyPolicy); - if (!authorizationResult.Result.Succeeded) + if (!authorizationResult.Succeeded) { await SendForbiddenAsync(cancellationToken); return null!; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs index 3893a32d1..309ff4c9d 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkPublish/Endpoint.cs @@ -21,9 +21,9 @@ internal class BulkPublish(IWorkflowDefinitionStore store, IWorkflowDefinitionPu public override async Task ExecuteAsync(Request request, CancellationToken cancellationToken) { - var authorizationResult = authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(), AuthorizationPolicies.NotReadOnlyPolicy); + var authorizationResult = await authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(), AuthorizationPolicies.NotReadOnlyPolicy); - if (!authorizationResult.Result.Succeeded) + if (!authorizationResult.Succeeded) { await SendForbiddenAsync(cancellationToken); return null!; @@ -70,6 +70,6 @@ internal class BulkPublish(IWorkflowDefinitionStore store, IWorkflowDefinitionPu updatedConsumers.AddRange(result.AffectedWorkflows.WorkflowDefinitions.Select(x => x.DefinitionId)); } - return new Response(published, alreadyPublished, notFound, skipped, updatedConsumers); + return new(published, alreadyPublished, notFound, skipped, updatedConsumers); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkRetract/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkRetract/Endpoint.cs index 0b9129147..e57162059 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkRetract/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/BulkRetract/Endpoint.cs @@ -21,9 +21,9 @@ internal class BulkRetract(IWorkflowDefinitionStore store, IWorkflowDefinitionPu public override async Task ExecuteAsync(Request request, CancellationToken cancellationToken) { - var authorizationResult = authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(), AuthorizationPolicies.NotReadOnlyPolicy); + var authorizationResult = await authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(), AuthorizationPolicies.NotReadOnlyPolicy); - if (!authorizationResult.Result.Succeeded) + if (!authorizationResult.Succeeded) { await SendForbiddenAsync(cancellationToken); return null!; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Delete/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Delete/Endpoint.cs index 33ddca4ba..a3c5bfde3 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Delete/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Delete/Endpoint.cs @@ -33,9 +33,9 @@ internal class Delete(IWorkflowDefinitionManager workflowDefinitionManager, IAut return; } - var authorizationResult = authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(definition), AuthorizationPolicies.NotReadOnlyPolicy); + var authorizationResult = await authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(definition), AuthorizationPolicies.NotReadOnlyPolicy); - if (!authorizationResult.Result.Succeeded) + if (!authorizationResult.Succeeded) { await SendForbiddenAsync(cancellationToken); return; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/DeleteVersion/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/DeleteVersion/Endpoint.cs index 932bbe04e..c29dc96f7 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/DeleteVersion/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/DeleteVersion/Endpoint.cs @@ -19,9 +19,9 @@ internal class DeleteVersion(IWorkflowDefinitionManager workflowDefinitionManage public override async Task HandleAsync(Request request, CancellationToken cancellationToken) { - var authorizationResult = authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(), AuthorizationPolicies.NotReadOnlyPolicy); + var authorizationResult = await authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(), AuthorizationPolicies.NotReadOnlyPolicy); - if (!authorizationResult.Result.Succeeded) + if (!authorizationResult.Succeeded) { await SendForbiddenAsync(cancellationToken); return; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/EndpointBase.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/EndpointBase.cs index c22a50f7e..d5a77fc6a 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/EndpointBase.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Execute/EndpointBase.cs @@ -49,7 +49,9 @@ internal abstract class EndpointBase( }; var startResponse = await workflowStarter.StartWorkflowAsync(startRequest, cancellationToken); - HttpContext.Response.Headers.Append("x-elsa-workflow-cannot-start", startResponse.CannotStart.ToString()); + + if(!HttpContext.Response.HasStarted) + HttpContext.Response.Headers.Append("x-elsa-workflow-cannot-start", startResponse.CannotStart.ToString()); if (startResponse.CannotStart) { @@ -61,8 +63,9 @@ internal abstract class EndpointBase( // Write the workflow instance ID to the response header. // This allows clients to read the header even if the workflow writes a response body - // (in which case, we can't transmit a JSON body that includes the instance ID). - HttpContext.Response.Headers.Append("x-elsa-workflow-instance-id", instanceId); + // (in which case, we can't transmit a JSON body that includes the instance ID). + if(!HttpContext.Response.HasStarted) + HttpContext.Response.Headers.Append("x-elsa-workflow-instance-id", instanceId); var workflowClient = await workflowRuntime.CreateClientAsync(instanceId, cancellationToken); @@ -84,7 +87,7 @@ internal abstract class EndpointBase( if (HttpContext.Response.StatusCode == StatusCodes.Status200OK) { var workflowState = await workflowClient.ExportStateAsync(cancellationToken); - await SendOkAsync(new Response(workflowState), cancellationToken); + await SendOkAsync(new(workflowState), cancellationToken); } } } diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetByDefinitionId/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetByDefinitionId/Endpoint.cs index 696f5ef68..7f87e67f1 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetByDefinitionId/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/GetByDefinitionId/Endpoint.cs @@ -1,14 +1,13 @@ using Elsa.Abstractions; -using Elsa.Common.Entities; using Elsa.Common.Models; -using Elsa.Workflows.Management; -using Elsa.Workflows.Management.Filters; +using Elsa.Mediator.Contracts; +using Elsa.Workflows.Management.Requests; using JetBrains.Annotations; namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.GetByDefinitionId; [PublicAPI] -internal class GetByDefinitionId(IWorkflowDefinitionStore store, IWorkflowDefinitionLinker linker) : ElsaEndpoint +internal class GetByDefinitionId(IMediator mediator, IWorkflowDefinitionLinker linker) : ElsaEndpoint { public override void Configure() { @@ -19,16 +18,9 @@ internal class GetByDefinitionId(IWorkflowDefinitionStore store, IWorkflowDefini public override async Task HandleAsync(Request request, CancellationToken cancellationToken) { var versionOptions = request.VersionOptions != null ? VersionOptions.FromString(request.VersionOptions) : VersionOptions.Latest; - - var filter = new WorkflowDefinitionFilter - { - DefinitionId = request.DefinitionId, - VersionOptions = versionOptions - }; - - var order = new WorkflowDefinitionOrder(x => x.Version, OrderDirection.Descending); - var definition = (await store.FindManyAsync(filter, order, cancellationToken: cancellationToken)).FirstOrDefault(); - + var findRequest = new FindWorkflowDefinitionRequest(request.DefinitionId, versionOptions); + var definition = await mediator.SendAsync(findRequest, cancellationToken); + if (definition == null) { await SendNotFoundAsync(cancellationToken); diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Import/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Import/Endpoint.cs index 23517a900..72e792c17 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Import/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Import/Endpoint.cs @@ -45,9 +45,9 @@ internal class Import : ElsaEndpoint var result = await ImportSingleWorkflowDefinitionAsync(model, cancellationToken); var definition = result.WorkflowDefinition; - var authorizationResult = _authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(definition), AuthorizationPolicies.NotReadOnlyPolicy); + var authorizationResult = await _authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(definition), AuthorizationPolicies.NotReadOnlyPolicy); - if (!authorizationResult.Result.Succeeded) + if (!authorizationResult.Succeeded) { await SendForbiddenAsync(cancellationToken); return; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/ImportFiles/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/ImportFiles/Endpoint.cs index b02a123e0..f0d0eee81 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/ImportFiles/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/ImportFiles/Endpoint.cs @@ -49,9 +49,9 @@ internal class ImportFiles : ElsaEndpoint /// public override async Task HandleAsync(WorkflowDefinitionModel model, CancellationToken cancellationToken) { - var authorizationResult = _authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(), AuthorizationPolicies.NotReadOnlyPolicy); + var authorizationResult = await _authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(), AuthorizationPolicies.NotReadOnlyPolicy); - if (!authorizationResult.Result.Succeeded) + if (!authorizationResult.Succeeded) { await SendForbiddenAsync(cancellationToken); return; @@ -108,7 +108,7 @@ internal class ImportFiles : ElsaEndpoint private async Task ImportJsonStreamAsync(Stream jsonStream, CancellationToken cancellationToken) { - var json = await new StreamReader(jsonStream).ReadToEndAsync(); + var json = await new StreamReader(jsonStream).ReadToEndAsync(cancellationToken); var model = _apiSerializer.Deserialize(json); await ImportSingleWorkflowDefinitionAsync(model, cancellationToken); } diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs index 230460257..77cf9deae 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/List/Endpoint.cs @@ -33,7 +33,7 @@ internal class List(IWorkflowDefinitionStore store, IWorkflowDefinitionLinker li { var versionOptions = string.IsNullOrWhiteSpace(request.VersionOptions) ? default(VersionOptions?) : VersionOptions.FromString(request.VersionOptions); - return new WorkflowDefinitionFilter + return new() { IsSystem = request.IsSystem, VersionOptions = versionOptions, diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs index 0746c037d..4b1ba32f4 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Post/Endpoint.cs @@ -43,7 +43,7 @@ internal class Post( var draft = !string.IsNullOrWhiteSpace(definitionId) ? await workflowDefinitionPublisher.GetDraftAsync(definitionId, VersionOptions.Latest, cancellationToken) - : default; + : null; var isNew = draft == null; @@ -56,9 +56,9 @@ internal class Post( draft.DefinitionId = definitionId; } - var authorizationResult = authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(draft), AuthorizationPolicies.NotReadOnlyPolicy); + var authorizationResult = await authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(draft), AuthorizationPolicies.NotReadOnlyPolicy); - if (!authorizationResult.Result.Succeeded) + if (!authorizationResult.Succeeded) { await SendForbiddenAsync(cancellationToken); return; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs index 0a28f964d..55d63a06b 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Publish/Endpoint.cs @@ -35,9 +35,9 @@ internal class Publish(IWorkflowDefinitionStore store, IWorkflowDefinitionPublis return; } - var authorizationResult = authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(definition), AuthorizationPolicies.NotReadOnlyPolicy); + var authorizationResult = await authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(definition), AuthorizationPolicies.NotReadOnlyPolicy); - if (!authorizationResult.Result.Succeeded) + if (!authorizationResult.Succeeded) { await SendForbiddenAsync(cancellationToken); return; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Retract/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Retract/Endpoint.cs index 9c2b1484f..7a52e7506 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Retract/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Retract/Endpoint.cs @@ -36,9 +36,9 @@ internal class Retract(IWorkflowDefinitionStore store, IWorkflowDefinitionPublis return; } - var authorizationResult = authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(definition), AuthorizationPolicies.NotReadOnlyPolicy); + var authorizationResult = await authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(definition), AuthorizationPolicies.NotReadOnlyPolicy); - if (!authorizationResult.Result.Succeeded) + if (!authorizationResult.Succeeded) { await SendForbiddenAsync(cancellationToken); return; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/UpdateReferences/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/UpdateReferences/Endpoint.cs index ca94da972..a510186c7 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/UpdateReferences/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/UpdateReferences/Endpoint.cs @@ -36,9 +36,9 @@ internal class UpdateReferences(IWorkflowReferenceUpdater workflowReferenceUpdat return; } - var authorizationResult = authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(definition), AuthorizationPolicies.NotReadOnlyPolicy); + var authorizationResult = await authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(definition), AuthorizationPolicies.NotReadOnlyPolicy); - if (!authorizationResult.Result.Succeeded) + if (!authorizationResult.Succeeded) { await SendForbiddenAsync(cancellationToken); return; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Delete.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Delete.cs index a1cda6181..b6a4d0930 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Delete.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Delete.cs @@ -42,9 +42,9 @@ public class DeleteVersion(IWorkflowDefinitionManager workflowDefinitionManager, return; } - var authorizationResult = authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(definition), AuthorizationPolicies.NotReadOnlyPolicy); + var authorizationResult = await authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(definition), AuthorizationPolicies.NotReadOnlyPolicy); - if (!authorizationResult.Result.Succeeded) + if (!authorizationResult.Succeeded) { await SendForbiddenAsync(cancellationToken); return; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs index 0d7aef8df..4dcd78dc9 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowDefinitions/Version/Revert.cs @@ -39,9 +39,9 @@ internal class RevertVersion(IWorkflowDefinitionManager workflowDefinitionManage return; } - var authorizationResult = authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(definition), AuthorizationPolicies.NotReadOnlyPolicy); + var authorizationResult = await authorizationService.AuthorizeAsync(User, new NotReadOnlyResource(definition), AuthorizationPolicies.NotReadOnlyPolicy); - if (!authorizationResult.Result.Succeeded) + if (!authorizationResult.Succeeded) { await SendForbiddenAsync(cancellationToken); return; diff --git a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/List/Endpoint.cs b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/List/Endpoint.cs index a69047659..785de7b1d 100644 --- a/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/List/Endpoint.cs +++ b/src/modules/Elsa.Workflows.Api/Endpoints/WorkflowInstances/List/Endpoint.cs @@ -38,6 +38,7 @@ internal class List(IWorkflowInstanceStore store) : ElsaEndpoint { _wellKnownTypeRegistry = wellKnownTypeRegistry; } - + /// public override void Write(Utf8JsonWriter writer, ArgumentDefinition value, JsonSerializerOptions options) { var newOptions = new JsonSerializerOptions(options); newOptions.Converters.RemoveWhere(x => x is ArgumentJsonConverterFactory); - - var jsonObject = (JsonObject)JsonSerializer.SerializeToNode(value, value.GetType(), newOptions)!; - var isArray = value.Type.IsCollectionType(); - jsonObject["isArray"] = isArray; - jsonObject["type"] = _wellKnownTypeRegistry.GetAliasOrDefault(isArray ? value.Type.GetCollectionElementType() : value.Type); + var jsonObject = (JsonObject)JsonSerializer.SerializeToNode(value, value.GetType(), newOptions)!; + var typeName = value.Type; + var typeAlias = _wellKnownTypeRegistry.TryGetAlias(typeName, out var alias) ? alias : null; + var isArray = typeName.IsArray; + var isCollection = typeName.IsCollectionType(); + var elementTypeName = isArray ? typeName.GetElementType() : isCollection ? typeName.GenericTypeArguments[0] : typeName; + var elementTypeAlias = _wellKnownTypeRegistry.GetAliasOrDefault(elementTypeName); + var isAliasedArray = (isArray || isCollection) && typeAlias != null; + var finalTypeAlias = isArray || isCollection ? typeAlias ?? elementTypeAlias : elementTypeAlias; + + if (isArray && !isAliasedArray) jsonObject["isArray"] = isArray; + if (isCollection) jsonObject["isCollection"] = isCollection; + + jsonObject["type"] = finalTypeAlias; JsonSerializer.Serialize(writer, jsonObject, newOptions); } @@ -40,11 +49,12 @@ public class ArgumentJsonConverter : JsonConverter { var jsonObject = (JsonObject)JsonNode.Parse(ref reader)!; var isArray = jsonObject["isArray"]?.GetValue() ?? false; - var typeName = jsonObject["type"]!.GetValue(); - var type = _wellKnownTypeRegistry.GetTypeOrDefault(typeName); + var isCollection = jsonObject["isCollection"]?.GetValue() ?? false; + var typeAlias = jsonObject["type"]!.GetValue(); + var type = _wellKnownTypeRegistry.GetTypeOrDefault(typeAlias); - if (isArray) - type = type.MakeCollectionType(); + if (isArray) type = type.MakeArrayType(); + if (isCollection) type = type.MakeGenericType(type.GenericTypeArguments[0]); var newOptions = new JsonSerializerOptions(options); newOptions.Converters.RemoveWhere(x => x is ArgumentJsonConverterFactory); diff --git a/src/modules/Elsa.Workflows.Core/Abstractions/Activity.cs b/src/modules/Elsa.Workflows.Core/Abstractions/Activity.cs index ad52a70a3..3be660125 100644 --- a/src/modules/Elsa.Workflows.Core/Abstractions/Activity.cs +++ b/src/modules/Elsa.Workflows.Core/Abstractions/Activity.cs @@ -73,6 +73,13 @@ public abstract class Activity : IActivity, ISignalHandler get => this.GetRunAsynchronously(); set => this.SetRunAsynchronously(value); } + + [JsonIgnore] + public string? CommitStrategy + { + get => this.GetCommitStrategy(); + set => this.SetCommitStrategy(value); + } /// [JsonConverter(typeof(PolymorphicObjectConverterFactory))] diff --git a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.cs b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.cs index 4ee602a37..3fa0921ea 100644 --- a/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.cs +++ b/src/modules/Elsa.Workflows.Core/Activities/Flowchart/Activities/Flowchart.cs @@ -31,9 +31,7 @@ public class Flowchart : Container /// /// The activity to execute when the flowchart starts. /// - [Port] - [Browsable(false)] - public IActivity? Start { get; set; } + [Port] [Browsable(false)] public IActivity? Start { get; set; } /// /// A list of connections between activities. @@ -98,8 +96,8 @@ public class Flowchart : Container { var workflowExecutionContext = context.WorkflowExecutionContext; var activityIds = Activities.Select(x => x.Id).ToList(); - var descendantContexts = context.GetDescendents().Where(x => x.ParentActivityExecutionContext == context).ToList(); - var activityExecutionContexts = descendantContexts.Where(x => activityIds.Contains(x.Activity.Id)).ToList(); + var children = context.Children; + var hasRunningActivityInstances = children.Where(x => activityIds.Contains(x.Activity.Id)).Any(x => x.Status == ActivityStatus.Running); var hasPendingWork = workflowExecutionContext.Scheduler.List().Any(workItem => { @@ -117,8 +115,6 @@ public class Flowchart : Container return ancestors.Any(x => x == context); }); - var hasRunningActivityInstances = activityExecutionContexts.Any(x => x.Status == ActivityStatus.Running); - return hasRunningActivityInstances || hasPendingWork; } @@ -186,7 +182,7 @@ public class Flowchart : Container var executionCount = scope.GetExecutionCount(activity); var haveInboundActivitiesExecuted = inboundActivities.All(x => scope.GetExecutionCount(x) > executionCount); - if (haveInboundActivitiesExecuted) + if (haveInboundActivitiesExecuted) await flowchartContext.ScheduleActivityAsync(activity, OnChildCompletedAsync); } else @@ -231,7 +227,7 @@ public class Flowchart : Container if (!hasPendingWork) { - var hasFaultedActivities = context.GetActiveChildren().Any(x => x.Status == ActivityStatus.Faulted); + var hasFaultedActivities = context.Children.Any(x => x.Status == ActivityStatus.Faulted); if (!hasFaultedActivities) { diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/CommitStrategiesFeature.cs b/src/modules/Elsa.Workflows.Core/CommitStates/CommitStrategiesFeature.cs new file mode 100644 index 000000000..0e43d71f5 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/CommitStrategiesFeature.cs @@ -0,0 +1,104 @@ +using Elsa.Extensions; +using Elsa.Features.Abstractions; +using Elsa.Features.Services; +using Elsa.Workflows.CommitStates.Strategies; +using Elsa.Workflows.CommitStates.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Workflows.CommitStates; + +public class CommitStrategiesFeature(IModule module) : FeatureBase(module) +{ + public void AddStandardStrategies() + { + // Workflow commit strategies. + Add(new DefaultWorkflowStrategy()); + Add(new WorkflowExecutingWorkflowStrategy()); + Add(new WorkflowExecutedWorkflowStrategy()); + Add(new ActivityExecutingWorkflowStrategy()); + Add(new ActivityExecutedWorkflowStrategy()); + + // Activity commit strategies. + Add(new DefaultActivityStrategy()); + Add(new CommitAlwaysActivityStrategy()); + Add(new CommitNeverActivityStrategy()); + Add(new ExecutingActivityStrategy()); + Add(new ExecutedActivityStrategy()); + } + + public void Add(IWorkflowCommitStrategy strategy) + { + var registration = ObjectRegistrationFactory.Describe(strategy); + Add(registration); + } + + public void Add(string displayName, IWorkflowCommitStrategy strategy) + { + var registration = ObjectRegistrationFactory.Describe(strategy); + registration.Metadata.DisplayName = displayName; + Add(registration); + } + + public void Add(string displayName, string description, IWorkflowCommitStrategy strategy) + { + var registration = ObjectRegistrationFactory.Describe(strategy); + registration.Metadata.DisplayName = displayName; + registration.Metadata.Description = description; + Add(registration); + } + + public void Add(string name, string displayName, string description, IWorkflowCommitStrategy strategy) + { + var registration = ObjectRegistrationFactory.Describe(strategy); + registration.Metadata.Name = name; + registration.Metadata.DisplayName = displayName; + registration.Metadata.Description = description; + Add(registration); + } + + public void Add(WorkflowCommitStrategyRegistration registration) + { + Services.Configure(options => options.WorkflowCommitStrategies[registration.Metadata.Name] = registration); + } + + public void Add(IActivityCommitStrategy strategy) + { + var registration = ObjectRegistrationFactory.Describe(strategy); + Add(registration); + } + + public void Add(string displayName, IActivityCommitStrategy strategy) + { + var registration = ObjectRegistrationFactory.Describe(strategy); + registration.Metadata.DisplayName = displayName; + Add(registration); + } + + public void Add(string displayName, string description, IActivityCommitStrategy strategy) + { + var registration = ObjectRegistrationFactory.Describe(strategy); + registration.Metadata.DisplayName = displayName; + registration.Metadata.Description = description; + Add(registration); + } + + public void Add(string name, string displayName, string description, IActivityCommitStrategy strategy) + { + var registration = ObjectRegistrationFactory.Describe(strategy); + registration.Metadata.Name = name; + registration.Metadata.DisplayName = displayName; + registration.Metadata.Description = description; + Add(registration); + } + + public void Add(ActivityCommitStrategyRegistration registration) + { + Services.Configure(options => options.ActivityCommitStrategies[registration.Metadata.Name] = registration); + } + + public override void Apply() + { + Services.AddSingleton(); + Services.AddStartupTask(); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Contracts/IActivityCommitStrategy.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Contracts/IActivityCommitStrategy.cs new file mode 100644 index 000000000..b648a4670 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Contracts/IActivityCommitStrategy.cs @@ -0,0 +1,6 @@ +namespace Elsa.Workflows.CommitStates; + +public interface IActivityCommitStrategy +{ + CommitAction ShouldCommit(ActivityCommitStateStrategyContext context); +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Contracts/ICommitStateHandler.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Contracts/ICommitStateHandler.cs similarity index 57% rename from src/modules/Elsa.Workflows.Core/Contracts/ICommitStateHandler.cs rename to src/modules/Elsa.Workflows.Core/CommitStates/Contracts/ICommitStateHandler.cs index 3d24ee6fd..175f42828 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/ICommitStateHandler.cs +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Contracts/ICommitStateHandler.cs @@ -1,8 +1,9 @@ using Elsa.Workflows.State; -namespace Elsa.Workflows; +namespace Elsa.Workflows.CommitStates; public interface ICommitStateHandler { + Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken = default); Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, WorkflowState workflowState, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Contracts/ICommitStrategyRegistry.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Contracts/ICommitStrategyRegistry.cs new file mode 100644 index 000000000..92a57376c --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Contracts/ICommitStrategyRegistry.cs @@ -0,0 +1,11 @@ +namespace Elsa.Workflows.CommitStates; + +public interface ICommitStrategyRegistry +{ + IEnumerable ListWorkflowStrategyRegistrations(); + IEnumerable ListActivityStrategyRegistrations(); + void RegisterStrategy(WorkflowCommitStrategyRegistration registration); + void RegisterStrategy(ActivityCommitStrategyRegistration registration); + IWorkflowCommitStrategy? FindWorkflowStrategy(string name); + IActivityCommitStrategy? FindActivityStrategy(string name); +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Contracts/IWorkflowCommitStrategy.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Contracts/IWorkflowCommitStrategy.cs new file mode 100644 index 000000000..074d4c857 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Contracts/IWorkflowCommitStrategy.cs @@ -0,0 +1,6 @@ +namespace Elsa.Workflows.CommitStates; + +public interface IWorkflowCommitStrategy +{ + CommitAction ShouldCommit(WorkflowCommitStateStrategyContext context); +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Extensions/ModuleExtensions.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Extensions/ModuleExtensions.cs new file mode 100644 index 000000000..9d2bf94c5 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Extensions/ModuleExtensions.cs @@ -0,0 +1,15 @@ +using Elsa.Workflows.CommitStates; +using Elsa.Workflows.Features; + +// ReSharper disable once CheckNamespace +namespace Elsa.Extensions; + +public static class WorkflowsFeatureCommitStateExtensions +{ + public static WorkflowsFeature UseCommitStrategies(this WorkflowsFeature workflowsFeature, Action? configure = null) + { + workflowsFeature.Module.Use(configure); + return workflowsFeature; + } + +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Helpers/ObjectMetadataDescriber.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Helpers/ObjectMetadataDescriber.cs new file mode 100644 index 000000000..817ae0908 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Helpers/ObjectMetadataDescriber.cs @@ -0,0 +1,23 @@ +using System.ComponentModel; +using System.Reflection; +using Humanizer; + +namespace Elsa.Workflows.CommitStates; + +public static class ObjectMetadataDescriber +{ + public static CommitStrategyMetadata Describe(Type strategyType) + { + var suffix = strategyType.IsAssignableTo(typeof(IActivityCommitStrategy)) ? "ActivityStrategy" : "WorkflowStrategy"; + var name = strategyType.Name.Replace(suffix, ""); + var displayName = strategyType.GetCustomAttribute()?.DisplayName ?? strategyType.Name.Replace("CommitStrategy", "").Humanize(); + var description = strategyType.GetCustomAttribute()?.Description ?? string.Empty; + + return new() + { + Name = name, + DisplayName = displayName, + Description = description + }; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Helpers/ObjectRegistrationFactory.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Helpers/ObjectRegistrationFactory.cs new file mode 100644 index 000000000..d06a8ae04 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Helpers/ObjectRegistrationFactory.cs @@ -0,0 +1,16 @@ +namespace Elsa.Workflows.CommitStates; + +public static class ObjectRegistrationFactory +{ + public static WorkflowCommitStrategyRegistration Describe(IWorkflowCommitStrategy strategy) + { + var metadata = ObjectMetadataDescriber.Describe(strategy.GetType()); + return new(strategy, metadata); + } + + public static ActivityCommitStrategyRegistration Describe(IActivityCommitStrategy strategy) + { + var metadata = ObjectMetadataDescriber.Describe(strategy.GetType()); + return new(strategy, metadata); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Models/ActivityCommitStateStrategyContext.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Models/ActivityCommitStateStrategyContext.cs new file mode 100644 index 000000000..bde323f1e --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Models/ActivityCommitStateStrategyContext.cs @@ -0,0 +1,3 @@ +namespace Elsa.Workflows.CommitStates; + +public record ActivityCommitStateStrategyContext(ActivityExecutionContext ActivityExecutionContext, ActivityLifetimeEvent LifetimeEvent); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Models/ActivityCommitStrategyRegistration.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Models/ActivityCommitStrategyRegistration.cs new file mode 100644 index 000000000..a5bb7b997 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Models/ActivityCommitStrategyRegistration.cs @@ -0,0 +1,14 @@ +namespace Elsa.Workflows.CommitStates; + +public class ActivityCommitStrategyRegistration : ObjectRegistration +{ + public ActivityCommitStrategyRegistration() + { + } + + public ActivityCommitStrategyRegistration(IActivityCommitStrategy strategy, CommitStrategyMetadata metadata) + { + Strategy = strategy; + Metadata = metadata; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Models/ActivityLifetimeEvent.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Models/ActivityLifetimeEvent.cs new file mode 100644 index 000000000..9a2643416 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Models/ActivityLifetimeEvent.cs @@ -0,0 +1,7 @@ +namespace Elsa.Workflows.CommitStates; + +public enum ActivityLifetimeEvent +{ + ActivityExecuting, + ActivityExecuted +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Models/ActivityStrategyDescriptor.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Models/ActivityStrategyDescriptor.cs new file mode 100644 index 000000000..8b1c266cc --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Models/ActivityStrategyDescriptor.cs @@ -0,0 +1,3 @@ +namespace Elsa.Workflows.CommitStates; + +public record ActivityStrategyDescriptor(string Name, string Description, IActivityCommitStrategy Strategy); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Models/CommitAction.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Models/CommitAction.cs new file mode 100644 index 000000000..cd2878354 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Models/CommitAction.cs @@ -0,0 +1,8 @@ +namespace Elsa.Workflows.CommitStates; + +public enum CommitAction +{ + Default, + Commit, + Skip +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Models/CommitStrategyMetadata.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Models/CommitStrategyMetadata.cs new file mode 100644 index 000000000..da7a3763a --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Models/CommitStrategyMetadata.cs @@ -0,0 +1,8 @@ +namespace Elsa.Workflows.CommitStates; + +public class CommitStrategyMetadata +{ + public string Name { get; set; } = null!; + public string DisplayName { get; set; } = null!; + public string Description { get; set; } = null!; +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Models/ObjectRegistration.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Models/ObjectRegistration.cs new file mode 100644 index 000000000..fb98ac5c4 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Models/ObjectRegistration.cs @@ -0,0 +1,7 @@ +namespace Elsa.Workflows.CommitStates; + +public class ObjectRegistration +{ + public T Strategy { get; set; } = default!; + public TMeta Metadata { get; set; } = default!; +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Models/WorkflowCommitStateStrategyContext.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Models/WorkflowCommitStateStrategyContext.cs new file mode 100644 index 000000000..d649e08f1 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Models/WorkflowCommitStateStrategyContext.cs @@ -0,0 +1,3 @@ +namespace Elsa.Workflows.CommitStates; + +public record WorkflowCommitStateStrategyContext(WorkflowExecutionContext WorkflowExecutionContext, WorkflowLifetimeEvent LifetimeEvent); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Models/WorkflowCommitStrategyRegistration.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Models/WorkflowCommitStrategyRegistration.cs new file mode 100644 index 000000000..6842a0114 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Models/WorkflowCommitStrategyRegistration.cs @@ -0,0 +1,14 @@ +namespace Elsa.Workflows.CommitStates; + +public class WorkflowCommitStrategyRegistration : ObjectRegistration +{ + public WorkflowCommitStrategyRegistration() + { + } + + public WorkflowCommitStrategyRegistration(IWorkflowCommitStrategy strategy, CommitStrategyMetadata metadata) + { + Strategy = strategy; + Metadata = metadata; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Models/WorkflowLifetimeEvent.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Models/WorkflowLifetimeEvent.cs new file mode 100644 index 000000000..1819083c1 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Models/WorkflowLifetimeEvent.cs @@ -0,0 +1,9 @@ +namespace Elsa.Workflows.CommitStates; + +public enum WorkflowLifetimeEvent +{ + WorkflowExecuting, + ActivityExecuting, + ActivityExecuted, + WorkflowExecuted +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Models/WorkflowStrategyDescriptor.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Models/WorkflowStrategyDescriptor.cs new file mode 100644 index 000000000..1aa38e723 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Models/WorkflowStrategyDescriptor.cs @@ -0,0 +1,3 @@ +namespace Elsa.Workflows.CommitStates; + +public record WorkflowStrategyDescriptor(string Name, string Description); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Options/CommitStateOptions.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Options/CommitStateOptions.cs new file mode 100644 index 000000000..cf607f986 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Options/CommitStateOptions.cs @@ -0,0 +1,7 @@ +namespace Elsa.Workflows.CommitStates; + +public class CommitStateOptions +{ + public IDictionary WorkflowCommitStrategies { get; set; } = new Dictionary(); + public IDictionary ActivityCommitStrategies { get; set; } = new Dictionary(); +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Services/DefaultCommitStrategyRegistry.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Services/DefaultCommitStrategyRegistry.cs new file mode 100644 index 000000000..ff72157d1 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Services/DefaultCommitStrategyRegistry.cs @@ -0,0 +1,38 @@ +namespace Elsa.Workflows.CommitStates; + +public class DefaultCommitStrategyRegistry : ICommitStrategyRegistry +{ + private readonly IDictionary _workflowStrategies = new Dictionary(); + private readonly IDictionary _activityStrategies = new Dictionary(); + + + public IEnumerable ListWorkflowStrategyRegistrations() + { + return _workflowStrategies.Values; + } + + public IEnumerable ListActivityStrategyRegistrations() + { + return _activityStrategies.Values; + } + + public void RegisterStrategy(WorkflowCommitStrategyRegistration registration) + { + _workflowStrategies[registration.Metadata.Name] = registration; + } + + public void RegisterStrategy(ActivityCommitStrategyRegistration registration) + { + _activityStrategies[registration.Metadata.Name] = registration; + } + + public IWorkflowCommitStrategy? FindWorkflowStrategy(string name) + { + return _workflowStrategies.TryGetValue(name, out var registration) ? registration.Strategy : null; + } + + public IActivityCommitStrategy? FindActivityStrategy(string name) + { + return _activityStrategies.TryGetValue(name, out var registration) ? registration.Strategy : null; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/CommitAlwaysActivityStrategy.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/CommitAlwaysActivityStrategy.cs new file mode 100644 index 000000000..c803a371a --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/CommitAlwaysActivityStrategy.cs @@ -0,0 +1,17 @@ +using System.ComponentModel; + +namespace Elsa.Workflows.CommitStates.Strategies; + +/// +/// Represents a strategy that always commits the workflow whenever the associated activity is about to execute or has executed. +/// This strategy ensures that the workflow's state is persisted at both pre-execution and post-execution stages of the activity. +/// +[DisplayName("Always Commit")] +[Description("Always commit the workflow state when the activity is about to execute or has executed.")] +public class CommitAlwaysActivityStrategy : IActivityCommitStrategy +{ + public CommitAction ShouldCommit(ActivityCommitStateStrategyContext context) + { + return CommitAction.Commit; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/CommitNeverActivityStrategy.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/CommitNeverActivityStrategy.cs new file mode 100644 index 000000000..cdc42514a --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/CommitNeverActivityStrategy.cs @@ -0,0 +1,18 @@ +using System.ComponentModel; + +namespace Elsa.Workflows.CommitStates.Strategies; + +/// +/// A strategy implementation of that specifies +/// the workflow should never commit when the associated activity is executed or has executed. +/// This overrides any workflow-level commit strategy. +/// +[DisplayName("Never Commit")] +[Description("Never commit the workflow state when the activity is about to execute or has executed.")] +public class CommitNeverActivityStrategy : IActivityCommitStrategy +{ + public CommitAction ShouldCommit(ActivityCommitStateStrategyContext context) + { + return CommitAction.Skip; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/DefaultActivityStrategy.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/DefaultActivityStrategy.cs new file mode 100644 index 000000000..5eeaedc69 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/DefaultActivityStrategy.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace Elsa.Workflows.CommitStates.Strategies; + +/// +/// Represents the default activity commit strategy for workflow activities. +/// +/// +/// This strategy determines whether a workflow should commit changes during the execution of an activity. +/// By default, it delegates the commit behavior to the workflow's global commit options or other overriding strategies. +/// +[DisplayName("Default")] +[Description("The default activity commit strategy.")] +public class DefaultActivityStrategy : IActivityCommitStrategy +{ + public CommitAction ShouldCommit(ActivityCommitStateStrategyContext context) + { + return CommitAction.Default; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/ExecutedActivityStrategy.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/ExecutedActivityStrategy.cs new file mode 100644 index 000000000..d4c1ec976 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/ExecutedActivityStrategy.cs @@ -0,0 +1,21 @@ +using System.ComponentModel; + +namespace Elsa.Workflows.CommitStates.Strategies; + +/// +/// Represents an activity commit strategy that determines whether a commit action should occur +/// after an activity has executed within a workflow. +/// +/// +/// This strategy evaluates the activity's execution events, specifically committing only if the +/// activity's lifetime event is "ActivityExecuted". +/// +[DisplayName("Executed")] +[Description("Commit the workflow state after the activity has executed.")] +public class ExecutedActivityStrategy : IActivityCommitStrategy +{ + public CommitAction ShouldCommit(ActivityCommitStateStrategyContext context) + { + return context.LifetimeEvent == ActivityLifetimeEvent.ActivityExecuted ? CommitAction.Commit : CommitAction.Default; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/ExecutingActivityStrategy.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/ExecutingActivityStrategy.cs new file mode 100644 index 000000000..a16ea900a --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Activities/ExecutingActivityStrategy.cs @@ -0,0 +1,23 @@ +using System.ComponentModel; + +namespace Elsa.Workflows.CommitStates.Strategies; + +/// +/// Represents a commit strategy that evaluates whether a workflow commit should occur +/// when an activity is in the "Executing" state. +/// +/// +/// This strategy determines commit behavior based on the activity's lifecycle event. +/// Specifically, it commits the workflow if the activity is currently executing +/// (i.e., the lifetime event is `ActivityLifetimeEvent.ActivityExecuting`). +/// For all other states, it defaults to no specific commit action. +/// +[DisplayName("Executing")] +[Description("Commit the workflow state when the activity is executing.")] +public class ExecutingActivityStrategy : IActivityCommitStrategy +{ + public CommitAction ShouldCommit(ActivityCommitStateStrategyContext context) + { + return context.LifetimeEvent == ActivityLifetimeEvent.ActivityExecuting ? CommitAction.Commit : CommitAction.Default; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/ActivityExecutedWorkflowStrategy.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/ActivityExecutedWorkflowStrategy.cs new file mode 100644 index 000000000..d35187ff9 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/ActivityExecutedWorkflowStrategy.cs @@ -0,0 +1,22 @@ +using System.ComponentModel; + +namespace Elsa.Workflows.CommitStates.Strategies; + +/// +/// Represents a commit strategy that determines whether a workflow state should be committed +/// based on the "ActivityExecuted" lifetime event of an activity. +/// +/// +/// This strategy evaluates the provided during execution +/// and returns a action if the activity's lifetime event corresponds +/// to . Otherwise, it defaults to . +/// +[DisplayName("Activity Executed")] +[Description("Determines whether a workflow state should be committed if the current activity has executed.")] +public class ActivityExecutedWorkflowStrategy : IWorkflowCommitStrategy +{ + public CommitAction ShouldCommit(WorkflowCommitStateStrategyContext context) + { + return context.LifetimeEvent == WorkflowLifetimeEvent.ActivityExecuted ? CommitAction.Commit : CommitAction.Default; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/ActivityExecutingWorkflowStrategy.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/ActivityExecutingWorkflowStrategy.cs new file mode 100644 index 000000000..5f891a525 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/ActivityExecutingWorkflowStrategy.cs @@ -0,0 +1,22 @@ +using System.ComponentModel; + +namespace Elsa.Workflows.CommitStates.Strategies; + +/// +/// Represents a workflow commit strategy that determines whether a commit should occur +/// during the ActivityExecuting lifecycle event of an activity. +/// +/// +/// This strategy evaluates the workflow execution context and checks if the current lifecycle event is +/// . If the condition is met, the strategy indicates +/// that a commit action should be performed. Otherwise, a default action will be returned. +/// +[DisplayName("Activity Executing")] +[Description("Determines whether a workflow state should be committed if the current activity is executing.")] +public class ActivityExecutingWorkflowStrategy : IWorkflowCommitStrategy +{ + public CommitAction ShouldCommit(WorkflowCommitStateStrategyContext context) + { + return context.LifetimeEvent == WorkflowLifetimeEvent.ActivityExecuting ? CommitAction.Commit : CommitAction.Default; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/DefaultWorkflowStrategy.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/DefaultWorkflowStrategy.cs new file mode 100644 index 000000000..505f32788 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/DefaultWorkflowStrategy.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace Elsa.Workflows.CommitStates.Strategies; + +/// +/// Represents the default strategy for determining whether a workflow should commit its state. +/// +/// +/// This strategy always returns the default commit action as defined by the enum, +/// ensuring that workflows adhere to the standard behavior unless overridden by custom strategies. +/// +[DisplayName("Default")] +[Description("The default workflow commit strategy.")] +public class DefaultWorkflowStrategy : IWorkflowCommitStrategy +{ + public CommitAction ShouldCommit(WorkflowCommitStateStrategyContext context) + { + return CommitAction.Default; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/PeriodicWorkflowStrategy.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/PeriodicWorkflowStrategy.cs new file mode 100644 index 000000000..62f481667 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/PeriodicWorkflowStrategy.cs @@ -0,0 +1,29 @@ +using System.ComponentModel; +using Elsa.Common; + +namespace Elsa.Workflows.CommitStates.Strategies; + +/// +/// Implements a periodic workflow commit strategy based on a specified time interval. +/// This strategy determines if a workflow should commit by comparing the elapsed time +/// since the last commit with the configured interval. +/// +[DisplayName("Periodic")] +[Description("Determines whether a workflow state should be committed based on a specified time interval.")] +public class PeriodicWorkflowStrategy(TimeSpan interval) : IWorkflowCommitStrategy +{ + private static readonly object LastCommitPropertyKey = new(); + public TimeSpan Interval { get; } = interval; + + public CommitAction ShouldCommit(WorkflowCommitStateStrategyContext context) + { + var lastCommit = context.WorkflowExecutionContext.TransientProperties.TryGetValue(LastCommitPropertyKey, out var value) ? (DateTimeOffset?)value : null; + var now = context.WorkflowExecutionContext.GetRequiredService().UtcNow; + var shouldCommit = lastCommit == null || (now - lastCommit.Value).TotalMilliseconds > Interval.TotalMilliseconds; + + if (shouldCommit) + context.WorkflowExecutionContext.TransientProperties[LastCommitPropertyKey] = now; + + return shouldCommit ? CommitAction.Commit : CommitAction.Default; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/WorkflowExecutedWorkflowStrategy.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/WorkflowExecutedWorkflowStrategy.cs new file mode 100644 index 000000000..8be2a361a --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/WorkflowExecutedWorkflowStrategy.cs @@ -0,0 +1,16 @@ +using System.ComponentModel; + +namespace Elsa.Workflows.CommitStates.Strategies; + +/// +/// Represents a workflow commit strategy that commits changes when the workflow has been executed. +/// +[DisplayName("Workflow Executed")] +[Description("Commit the workflow state when the workflow has been executed.")] +public class WorkflowExecutedWorkflowStrategy : IWorkflowCommitStrategy +{ + public CommitAction ShouldCommit(WorkflowCommitStateStrategyContext context) + { + return context.LifetimeEvent == WorkflowLifetimeEvent.WorkflowExecuted ? CommitAction.Commit : CommitAction.Default; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/WorkflowExecutingWorkflowStrategy.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/WorkflowExecutingWorkflowStrategy.cs new file mode 100644 index 000000000..91162258e --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Strategies/Workflows/WorkflowExecutingWorkflowStrategy.cs @@ -0,0 +1,22 @@ +using System.ComponentModel; + +namespace Elsa.Workflows.CommitStates.Strategies; + +/// +/// Implements a commit strategy that determines whether the workflow state +/// should be committed when the workflow is in the "executing" lifetime event. +/// +/// +/// This strategy checks the current context's `LifetimeEvent` and commits +/// the workflow state if it corresponds to the `WorkflowExecuting` event. +/// Otherwise, it defaults to no explicit commit action. +/// +[DisplayName("Workflow Executing")] +[Description("Commit the workflow state when the workflow is executing.")] +public class WorkflowExecutingWorkflowStrategy : IWorkflowCommitStrategy +{ + public CommitAction ShouldCommit(WorkflowCommitStateStrategyContext context) + { + return context.LifetimeEvent == WorkflowLifetimeEvent.WorkflowExecuting ? CommitAction.Commit : CommitAction.Default; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/CommitStates/Tasks/PopulateCommitStrategyRegistry.cs b/src/modules/Elsa.Workflows.Core/CommitStates/Tasks/PopulateCommitStrategyRegistry.cs new file mode 100644 index 000000000..2bd9b962d --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/CommitStates/Tasks/PopulateCommitStrategyRegistry.cs @@ -0,0 +1,16 @@ +using Elsa.Common; +using JetBrains.Annotations; +using Microsoft.Extensions.Options; + +namespace Elsa.Workflows.CommitStates.Tasks; + +[UsedImplicitly] +public class PopulateCommitStrategyRegistry(ICommitStrategyRegistry registry, IOptions options) : IStartupTask +{ + public Task ExecuteAsync(CancellationToken cancellationToken) + { + foreach (var strategy in options.Value.WorkflowCommitStrategies.Values) registry.RegisterStrategy(strategy); + foreach (var strategy in options.Value.ActivityCommitStrategies.Values) registry.RegisterStrategy(strategy); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.Complete.cs b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.Complete.cs index 978c64d91..1a278d1db 100644 --- a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.Complete.cs +++ b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.Complete.cs @@ -9,7 +9,7 @@ public partial class ActivityExecutionContext /// /// Complete the current activity. This should only be called by activities that explicitly suppress automatic-completion. /// - public async ValueTask CompleteActivityAsync(object? result = default) + public async ValueTask CompleteActivityAsync(object? result = null) { var outcomes = result as Outcomes; @@ -28,8 +28,8 @@ public partial class ActivityExecutionContext return; // Cancel any non-completed child activities. - var childContexts = WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == this && x.CanCancelActivity()).ToList(); - + var childContexts = Children.Where(x => x.CanCancelActivity()).ToList(); + foreach (var childContext in childContexts) await childContext.CancelActivityAsync(); diff --git a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.ExecutionLogEntry.cs b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.ExecutionLogEntry.cs index e05a262c6..260733811 100644 --- a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.ExecutionLogEntry.cs +++ b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.ExecutionLogEntry.cs @@ -13,7 +13,7 @@ public partial class ActivityExecutionContext /// The source of the activity. For example, the source file name and line number in case of composite activities. /// Any contextual data related to this event. /// Returns the created . - public WorkflowExecutionLogEntry AddExecutionLogEntry(string eventName, string? message = default, string? source = default, object? payload = default) + public WorkflowExecutionLogEntry AddExecutionLogEntry(string eventName, string? message = null, string? source = null, object? payload = null) { var logEntry = new WorkflowExecutionLogEntry( Id, diff --git a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs index f545b0881..667faa22a 100644 --- a/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs +++ b/src/modules/Elsa.Workflows.Core/Contexts/ActivityExecutionContext.cs @@ -20,7 +20,10 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable { private readonly ISystemClock _systemClock; private readonly List _bookmarks = []; + private ActivityStatus _status; + private Exception? _exception; private long _executionCount; + private ActivityExecutionContext? _parentActivityExecutionContext; /// /// Initializes a new instance of the class. @@ -29,7 +32,6 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable string id, WorkflowExecutionContext workflowExecutionContext, ActivityExecutionContext? parentActivityExecutionContext, - ExpressionExecutionContext expressionExecutionContext, IActivity activity, ActivityDescriptor activityDescriptor, DateTimeOffset startedAt, @@ -38,9 +40,15 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable CancellationToken cancellationToken) { _systemClock = systemClock; + Properties = new ChangeTrackingDictionary(Taint); + ActivityState = new ChangeTrackingDictionary(Taint); + ActivityInput = new ChangeTrackingDictionary(Taint); WorkflowExecutionContext = workflowExecutionContext; - ParentActivityExecutionContext = parentActivityExecutionContext; - ExpressionExecutionContext = expressionExecutionContext; + _parentActivityExecutionContext = parentActivityExecutionContext; + var expressionExecutionContextProps = ExpressionExecutionContextExtensions.CreateActivityExecutionContextPropertiesFrom(workflowExecutionContext, workflowExecutionContext.Input); + expressionExecutionContextProps[ExpressionExecutionContextExtensions.ActivityKey] = activity; + ExpressionExecutionContext = new(workflowExecutionContext.ServiceProvider, new(), parentActivityExecutionContext?.ExpressionExecutionContext ?? workflowExecutionContext.ExpressionExecutionContext, expressionExecutionContextProps, Taint, CancellationToken); + ; Activity = activity; ActivityDescriptor = activityDescriptor; StartedAt = startedAt; @@ -84,7 +92,15 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// /// The parent activity execution context, if any. /// - public ActivityExecutionContext? ParentActivityExecutionContext { get; internal set; } + public ActivityExecutionContext? ParentActivityExecutionContext + { + get => _parentActivityExecutionContext; + internal set + { + _parentActivityExecutionContext = value; + _parentActivityExecutionContext?.Children.Add(this); + } + } /// /// The expression execution context. @@ -125,7 +141,18 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// /// The current status of the activity. /// - public ActivityStatus Status { get; private set; } + public ActivityStatus Status + { + get => _status; + private set + { + if (value == _status) + return; + + _status = value; + Taint(); + } + } /// /// Sets the current status of the activity. @@ -138,10 +165,18 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// /// Gets or sets the exception that occurred during the activity execution, if any. /// - public Exception? Exception { get; set; } + public Exception? Exception + { + get => _exception; + set + { + _exception = value; + Taint(); + } + } /// - public IDictionary Properties { get; set; } = new Dictionary(); + public IDictionary Properties { get; private set; } /// /// A transient dictionary of values that can be associated with this activity execution context. @@ -160,6 +195,8 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// As of tool version 3.0, all activity Ids are already unique, so there's no need to construct a hierarchical ID public string NodeId => ActivityNode.NodeId; + public ISet Children { get; } = new HashSet(); + /// /// A list of bookmarks created by the current activity. /// @@ -178,18 +215,23 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// /// A dictionary of inputs for the current activity. /// - public IDictionary ActivityInput { get; set; } = new Dictionary(); + public IDictionary ActivityInput { get; private set; } /// /// Journal data will be added to the workflow execution log for the "Executed" event. /// // ReSharper disable once CollectionNeverQueried.Global - public IDictionary JournalData { get; } = new Dictionary(); + public IDictionary JournalData { get; } = new Dictionary(); /// /// Stores the evaluated inputs, serialized, for the current activity for historical purposes. /// - public IDictionary ActivityState { get; set; } = new Dictionary(); + public IDictionary ActivityState { get; } + + /// + /// Indicates whether the state of the current activity execution context has been modified. + /// + public bool IsDirty { get; private set; } /// /// Schedules the specified activity to be executed. @@ -198,7 +240,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// An optional callback to invoke when the activity completes. /// An optional tag to associate with the activity execution. /// An optional list of variables to declare with the activity execution. - public ValueTask ScheduleActivityAsync(IActivity? activity, ActivityCompletionCallback? completionCallback, object? tag = default, IEnumerable? variables = default) + public ValueTask ScheduleActivityAsync(IActivity? activity, ActivityCompletionCallback? completionCallback, object? tag = null, IEnumerable? variables = null) { var options = new ScheduleWorkOptions { @@ -214,7 +256,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// /// The activity to schedule. /// The options used to schedule the activity. - public async ValueTask ScheduleActivityAsync(IActivity? activity, ScheduleWorkOptions? options = default) + public async ValueTask ScheduleActivityAsync(IActivity? activity, ScheduleWorkOptions? options = null) { await ScheduleActivityAsync(activity, this, options); } @@ -225,7 +267,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// The activity to schedule. /// The activity execution context that owns the scheduled activity. /// The options used to schedule the activity. - public async ValueTask ScheduleActivityAsync(IActivity? activity, ActivityExecutionContext? owner, ScheduleWorkOptions? options = default) + public async ValueTask ScheduleActivityAsync(IActivity? activity, ActivityExecutionContext? owner, ScheduleWorkOptions? options = null) { var activityNode = activity != null ? WorkflowExecutionContext.FindNodeByActivity(activity) ?? throw new InvalidOperationException("The specified activity is not part of the workflow.") @@ -239,7 +281,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// The activity node to schedule. /// The activity execution context that owns the scheduled activity. /// The options used to schedule the activity. - public async ValueTask ScheduleActivityAsync(ActivityNode? activityNode, ActivityExecutionContext? owner = default, ScheduleWorkOptions? options = default) + public async ValueTask ScheduleActivityAsync(ActivityNode? activityNode, ActivityExecutionContext? owner = null, ScheduleWorkOptions? options = null) { if (this.GetIsBackgroundExecution()) { @@ -261,7 +303,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable Variables = options?.Variables?.ToList(), Input = options?.Input } - : default + : null }; var scheduledActivities = this.GetBackgroundScheduledActivities().ToList(); @@ -303,7 +345,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// The callback to invoke when the activities complete. /// An optional tag to associate with the activity execution. /// An optional list of variables to declare with the activity execution. - public ValueTask ScheduleActivities(IEnumerable activities, ActivityCompletionCallback? completionCallback, object? tag = default, IEnumerable? variables = default) + public ValueTask ScheduleActivities(IEnumerable activities, ActivityCompletionCallback? completionCallback, object? tag = null, IEnumerable? variables = null) { var options = new ScheduleWorkOptions { @@ -319,7 +361,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// /// The activities to schedule. /// The options used to schedule the activities. - public async ValueTask ScheduleActivities(IEnumerable activities, ScheduleWorkOptions? options = default) + public async ValueTask ScheduleActivities(IEnumerable activities, ScheduleWorkOptions? options = null) { foreach (var activity in activities) await ScheduleActivityAsync(activity, options); @@ -331,7 +373,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// The payloads to create bookmarks for. /// An optional callback that is invoked when the bookmark is resumed. /// Whether or not the activity instance ID should be included in the bookmark payload. - public void CreateBookmarks(IEnumerable payloads, ExecuteActivityDelegate? callback = default, bool includeActivityInstanceId = true) + public void CreateBookmarks(IEnumerable payloads, ExecuteActivityDelegate? callback = null, bool includeActivityInstanceId = true) { foreach (var payload in payloads) CreateBookmark(new CreateBookmarkArgs @@ -346,13 +388,21 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// Adds each bookmark to the list of bookmarks. /// /// The bookmarks to add. - public void AddBookmarks(IEnumerable bookmarks) => _bookmarks.AddRange(bookmarks); + public void AddBookmarks(IEnumerable bookmarks) + { + _bookmarks.AddRange(bookmarks); + Taint(); + } /// /// Adds a bookmark to the list of bookmarks. /// /// The bookmark to add. - public void AddBookmark(Bookmark bookmark) => _bookmarks.Add(bookmark); + public void AddBookmark(Bookmark bookmark) + { + _bookmarks.Add(bookmark); + Taint(); + } /// /// Creates a bookmark so that this activity can be resumed at a later time. @@ -360,7 +410,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// An optional callback that is invoked when the bookmark is resumed. /// Custom properties to associate with the bookmark. /// The created bookmark. - public Bookmark CreateBookmark(ExecuteActivityDelegate callback, IDictionary? metadata = default) + public Bookmark CreateBookmark(ExecuteActivityDelegate callback, IDictionary? metadata = null) { return CreateBookmark(new CreateBookmarkArgs { @@ -377,7 +427,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// Whether or not the activity instance ID should be included in the bookmark payload. /// Custom properties to associate with the bookmark. /// The created bookmark. - public Bookmark CreateBookmark(object stimulus, ExecuteActivityDelegate callback, bool includeActivityInstanceId = true, IDictionary? customProperties = default) + public Bookmark CreateBookmark(object stimulus, ExecuteActivityDelegate callback, bool includeActivityInstanceId = true, IDictionary? customProperties = null) { return CreateBookmark(new CreateBookmarkArgs { @@ -395,7 +445,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// Specifies whether to include the activity instance ID in the bookmark information. Defaults to true. /// Additional custom properties to associate with the bookmark. Defaults to null. /// The created bookmark. - public Bookmark CreateBookmark(object stimulus, bool includeActivityInstanceId, IDictionary? customProperties = default) + public Bookmark CreateBookmark(object stimulus, bool includeActivityInstanceId, IDictionary? customProperties = null) { return CreateBookmark(new CreateBookmarkArgs { @@ -411,9 +461,9 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// The payload to associate with the bookmark. /// Custom properties to associate with the bookmark. /// The created bookmark. - public Bookmark CreateBookmark(object stimulus, IDictionary? metadata = default) + public Bookmark CreateBookmark(object stimulus, IDictionary? metadata = null) { - return CreateBookmark(new CreateBookmarkArgs + return CreateBookmark(new() { Stimulus = stimulus, Metadata = metadata @@ -424,7 +474,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// Creates a bookmark so that this activity can be resumed at a later time. /// Creating a bookmark will automatically suspend the workflow after all pending activities have executed. /// - public Bookmark CreateBookmark(CreateBookmarkArgs? options = default) + public Bookmark CreateBookmark(CreateBookmarkArgs? options = null) { var payload = options?.Stimulus; var callback = options?.Callback; @@ -456,7 +506,11 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// /// Clear all bookmarks. /// - public void ClearBookmarks() => _bookmarks.Clear(); + public void ClearBookmarks() + { + _bookmarks.Clear(); + Taint(); + } /// /// Returns a property value associated with the current activity context. @@ -569,7 +623,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// /// The output. /// The output value. - public object? Get(Output? output) => output == null ? default : Get(output.MemoryBlockReference()); + public object? Get(Output? output) => output == null ? null : Get(output.MemoryBlockReference()); /// /// Gets the value of the specified memory block. @@ -593,7 +647,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable public T? Get(MemoryBlockReference blockReference) { var value = Get(blockReference); - return value != default ? value.ConvertTo() : default; + return value != null ? value.ConvertTo() : default; } /// @@ -628,7 +682,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// The memory block reference. /// The value to set. /// An optional callback that can be used to configure the memory block. - public void Set(MemoryBlockReference blockReference, object? value, Action? configure = default) => ExpressionExecutionContext.Set(blockReference, value, configure); + public void Set(MemoryBlockReference blockReference, object? value, Action? configure = null) => ExpressionExecutionContext.Set(blockReference, value, configure); /// /// Sets a value at the specified output. @@ -637,7 +691,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// The value to set. /// The name of the output. /// The type of the output. - public void Set(Output? output, T? value, [CallerArgumentExpression("output")] string? outputName = default) => Set((Output?)output, value, outputName); + public void Set(Output? output, T? value, [CallerArgumentExpression("output")] string? outputName = null) => Set((Output?)output, value, outputName); /// /// Sets a value at the specified output. @@ -645,7 +699,7 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable /// The output. /// The value to set. /// The name of the output. - public void Set(Output? output, object? value, [CallerArgumentExpression("output")] string? outputName = default) + public void Set(Output? output, object? value, [CallerArgumentExpression("output")] string? outputName = null) { // Store the value in the expression execution memory block. ExpressionExecutionContext.Set(output, value); @@ -663,11 +717,26 @@ public partial class ActivityExecutionContext : IExecutionContext, IDisposable WorkflowExecutionContext.RemoveCompletionCallbacks(entriesToRemove); } - internal void IncrementExecutionCount() => _executionCount++; + public void Taint() + { + if (!IsDirty) + IsDirty = true; + } + + public void ClearTaint() + { + if (IsDirty) + IsDirty = false; + } + + internal void IncrementExecutionCount() + { + _executionCount++; + } private MemoryBlock? GetMemoryBlock(MemoryBlockReference locationBlockReference) { - return ExpressionExecutionContext.TryGetBlock(locationBlockReference, out var memoryBlock) ? memoryBlock : default; + return ExpressionExecutionContext.TryGetBlock(locationBlockReference, out var memoryBlock) ? memoryBlock : null; } void IDisposable.Dispose() diff --git a/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs index bd8623ce8..345295d32 100644 --- a/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs +++ b/src/modules/Elsa.Workflows.Core/Contexts/WorkflowExecutionContext.cs @@ -4,6 +4,7 @@ using Elsa.Expressions.Helpers; using Elsa.Expressions.Models; using Elsa.Extensions; using Elsa.Workflows.Activities; +using Elsa.Workflows.CommitStates; using Elsa.Workflows.Exceptions; using Elsa.Workflows.Helpers; using Elsa.Workflows.Memory; @@ -22,7 +23,7 @@ namespace Elsa.Workflows; /// The child being scheduled. /// The delegate to invoke when the scheduled activity completes. /// An optional tag. -public record ActivityCompletionCallbackEntry(ActivityExecutionContext Owner, ActivityNode Child, ActivityCompletionCallback? CompletionCallback, object? Tag = default); +public record ActivityCompletionCallbackEntry(ActivityExecutionContext Owner, ActivityNode Child, ActivityCompletionCallback? CompletionCallback, object? Tag = null); /// /// Provides context to the currently executing workflow. @@ -37,6 +38,7 @@ public partial class WorkflowExecutionContext : IExecutionContext private readonly IList _completionCallbackEntries = new List(); private IList _activityExecutionContexts; private readonly IHasher _hasher; + private readonly ICommitStateHandler _commitStateHandler; /// /// Initializes a new instance of . @@ -61,6 +63,7 @@ public partial class WorkflowExecutionContext : IExecutionContext ActivityRegistry = serviceProvider.GetRequiredService(); ActivityRegistryLookup = serviceProvider.GetRequiredService(); _hasher = serviceProvider.GetRequiredService(); + _commitStateHandler = serviceProvider.GetRequiredService(); SubStatus = WorkflowSubStatus.Pending; Id = id; CorrelationId = correlationId; @@ -239,7 +242,7 @@ public partial class WorkflowExecutionContext : IExecutionContext public WorkflowSubStatus SubStatus { get; internal set; } /// The root associated with the execution context. - public MemoryRegister MemoryRegister { get; private set; } = default!; + public MemoryRegister MemoryRegister { get; private set; } = null!; /// A unique ID of the execution context. public string Id { get; set; } @@ -346,7 +349,7 @@ public partial class WorkflowExecutionContext : IExecutionContext /// public IReadOnlyCollection ActivityExecutionContexts { - get => _activityExecutionContexts.ToList(); + get => _activityExecutionContexts.AsReadOnly(); internal set => _activityExecutionContexts = value.ToList(); } @@ -406,7 +409,7 @@ public partial class WorkflowExecutionContext : IExecutionContext /// /// Registers a completion callback for the specified activity. /// - internal void AddCompletionCallback(ActivityExecutionContext owner, ActivityNode child, ActivityCompletionCallback? completionCallback = default, object? tag = default) + internal void AddCompletionCallback(ActivityExecutionContext owner, ActivityNode child, ActivityCompletionCallback? completionCallback = null, object? tag = null) { var entry = new ActivityCompletionCallbackEntry(owner, child, completionCallback, tag); _completionCallbackEntries.Add(entry); @@ -420,7 +423,7 @@ public partial class WorkflowExecutionContext : IExecutionContext var entry = _completionCallbackEntries.FirstOrDefault(x => x.Owner == owner && x.Child == child); if (entry == null) - return default; + return null; RemoveCompletionCallback(entry); return entry; @@ -449,25 +452,25 @@ public partial class WorkflowExecutionContext : IExecutionContext ? FindActivityByInstanceId(handle.ActivityInstanceId) : handle.ActivityHash != null ? FindActivityByHash(handle.ActivityHash) - : default; + : null; } /// /// Returns the with the specified activity ID from the workflow graph. /// - public ActivityNode? FindNodeById(string nodeId) => NodeIdLookup.TryGetValue(nodeId, out var node) ? node : default; + public ActivityNode? FindNodeById(string nodeId) => NodeIdLookup.TryGetValue(nodeId, out var node) ? node : null; /// /// Returns the with the specified hash of the activity node ID from the workflow graph. /// /// The hash of the activity node ID. /// The with the specified hash of the activity node ID. - public ActivityNode? FindNodeByHash(string hash) => NodeHashLookup.TryGetValue(hash, out var node) ? node : default; + public ActivityNode? FindNodeByHash(string hash) => NodeHashLookup.TryGetValue(hash, out var node) ? node : null; /// Returns the containing the specified activity from the workflow graph. public ActivityNode? FindNodeByActivity(IActivity activity) { - return NodeActivityLookup.TryGetValue(activity, out var node) ? node : default; + return NodeActivityLookup.TryGetValue(activity, out var node) ? node : null; } /// Returns the associated with the specified activity ID. @@ -510,41 +513,36 @@ public partial class WorkflowExecutionContext : IExecutionContext internal void TransitionTo(WorkflowSubStatus subStatus) { if (!ValidateStatusTransition()) - throw new Exception($"Cannot transition from {SubStatus} to {subStatus}"); + throw new($"Cannot transition from {SubStatus} to {subStatus}"); SubStatus = subStatus; UpdatedAt = SystemClock.UtcNow; if (Status == WorkflowStatus.Finished) FinishedAt = UpdatedAt; - + if (Status == WorkflowStatus.Finished || SubStatus == WorkflowSubStatus.Suspended) { - foreach (var registration in _cancellationRegistrations) + foreach (var registration in _cancellationRegistrations) registration.Dispose(); } } /// Creates a new for the specified activity. - public async Task CreateActivityExecutionContextAsync(IActivity activity, ActivityInvocationOptions? options = default) + public async Task CreateActivityExecutionContextAsync(IActivity activity, ActivityInvocationOptions? options = null) { var activityDescriptor = await ActivityRegistryLookup.FindAsync(activity) ?? throw new ActivityNotFoundException(activity.Type); var tag = options?.Tag; var parentContext = options?.Owner; - var parentExpressionExecutionContext = parentContext?.ExpressionExecutionContext ?? ExpressionExecutionContext; - var properties = ExpressionExecutionContextExtensions.CreateActivityExecutionContextPropertiesFrom(this, Input); - properties[ExpressionExecutionContextExtensions.ActivityKey] = activity; - var memory = new MemoryRegister(); var now = SystemClock.UtcNow; - var expressionExecutionContext = new ExpressionExecutionContext(ServiceProvider, memory, parentExpressionExecutionContext, properties, CancellationToken); var id = IdentityGenerator.GenerateId(); - var activityExecutionContext = new ActivityExecutionContext(id, this, parentContext, expressionExecutionContext, activity, activityDescriptor, now, tag, SystemClock, CancellationToken); + var activityExecutionContext = new ActivityExecutionContext(id, this, parentContext, activity, activityDescriptor, now, tag, SystemClock, CancellationToken); var variablesToDeclare = options?.Variables ?? Array.Empty(); var variableContainer = new[] { activityExecutionContext.ActivityNode }.Concat(activityExecutionContext.ActivityNode.Ancestors()).FirstOrDefault(x => x.Activity is IVariableContainer)?.Activity as IVariableContainer; - expressionExecutionContext.TransientProperties[ExpressionExecutionContextExtensions.ActivityExecutionContextKey] = activityExecutionContext; + activityExecutionContext.ExpressionExecutionContext.TransientProperties[ExpressionExecutionContextExtensions.ActivityExecutionContextKey] = activityExecutionContext; if (variableContainer != null) { @@ -555,11 +553,12 @@ public partial class WorkflowExecutionContext : IExecutionContext activityExecutionContext.DynamicVariables.Add(variable); // Assign the variable to the expression execution context. - expressionExecutionContext.CreateVariable(variable.Name, variable.Value); + activityExecutionContext.ExpressionExecutionContext.CreateVariable(variable.Name, variable.Value); } } - activityExecutionContext.ActivityInput = options?.Input ?? new Dictionary(StringComparer.OrdinalIgnoreCase); + var activityInput = options?.Input ?? new Dictionary(StringComparer.OrdinalIgnoreCase); + activityExecutionContext.ActivityInput.Merge(activityInput); return activityExecutionContext; } @@ -568,17 +567,42 @@ public partial class WorkflowExecutionContext : IExecutionContext public ActivityOutputRegister GetActivityOutputRegister() => TransientProperties.GetOrAdd(ActivityOutputRegistryKey, () => new ActivityOutputRegister()); /// Returns the last activity result. - public object? GetLastActivityResult() => TransientProperties.TryGetValue(LastActivityResultKey, out var value) ? value : default; + public object? GetLastActivityResult() => TransientProperties.TryGetValue(LastActivityResultKey, out var value) ? value : null; /// Adds the specified to the workflow execution context. public void AddActivityExecutionContext(ActivityExecutionContext context) => _activityExecutionContexts.Add(context); /// Removes the specified from the workflow execution context. - public void RemoveActivityExecutionContext(ActivityExecutionContext context) => _activityExecutionContexts.Remove(context); + public void RemoveActivityExecutionContext(ActivityExecutionContext context) + { + _activityExecutionContexts.Remove(context); + context.ParentActivityExecutionContext?.Children.Remove(context); + } /// Removes the specified from the workflow execution context. /// The predicate used to filter the activity execution contexts to remove. - public void RemoveActivityExecutionContext(Func predicate) => _activityExecutionContexts.RemoveWhere(predicate); + public void RemoveActivityExecutionContexts(Func predicate) + { + var itemsToRemove = _activityExecutionContexts.Where(predicate).ToList(); + foreach (var item in itemsToRemove) + RemoveActivityExecutionContext(item); + } + + /// + /// Removes all completed activity execution contexts that have a parent activity execution context. + /// + public void ClearCompletedActivityExecutionContexts() + { + RemoveActivityExecutionContexts(x => x is { IsCompleted: true, ParentActivityExecutionContext: not null }); + } + + public IEnumerable GetActiveActivityExecutionContexts() + { + // Filter out completed activity execution contexts, except for the root Workflow activity context, which stores workflow-level variables. + // This will currently break scripts accessing activity output directly, but there's a workaround for that via variable capturing. + // We may ultimately restore direct output access, but in a different way. + return ActivityExecutionContexts.Where(x => !x.IsCompleted || x.ParentActivityExecutionContext == null); + } /// /// Records the output of the specified activity into the current workflow execution context. @@ -607,11 +631,16 @@ public partial class WorkflowExecutionContext : IExecutionContext WorkflowSubStatus.Suspended => WorkflowStatus.Running, _ => throw new ArgumentOutOfRangeException(nameof(subStatus), subStatus, null) }; - + // TODO: Check if we should not use the target subStatus here instead. private bool ValidateStatusTransition() { var currentMainStatus = GetMainStatus(SubStatus); return currentMainStatus != WorkflowStatus.Finished; } + + public Task CommitAsync() + { + return _commitStateHandler.CommitAsync(this, CancellationToken); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IExecutionContext.cs b/src/modules/Elsa.Workflows.Core/Contracts/IExecutionContext.cs index 09ddfc5d9..41f6d9cfd 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/IExecutionContext.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/IExecutionContext.cs @@ -31,5 +31,5 @@ public interface IExecutionContext /// /// A dictionary of values that can be associated with this activity execution context. /// - public IDictionary Properties { get; set; } + public IDictionary Properties { get; } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Contracts/IVariablePersistenceManager.cs b/src/modules/Elsa.Workflows.Core/Contracts/IVariablePersistenceManager.cs index aa4105e3b..bb61acd67 100644 --- a/src/modules/Elsa.Workflows.Core/Contracts/IVariablePersistenceManager.cs +++ b/src/modules/Elsa.Workflows.Core/Contracts/IVariablePersistenceManager.cs @@ -19,7 +19,12 @@ public interface IVariablePersistenceManager Task SaveVariablesAsync(WorkflowExecutionContext context); /// - /// Deletes the specified variables from the . + /// Deletes the specified variables from the . /// Task DeleteVariablesAsync(ActivityExecutionContext context); + + /// + /// Deletes the specified variables from the . + /// + Task DeleteVariablesAsync(WorkflowExecutionContext context); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj.DotSettings b/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj.DotSettings index f05e636df..63e48fa9c 100644 --- a/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj.DotSettings +++ b/src/modules/Elsa.Workflows.Core/Elsa.Workflows.Core.csproj.DotSettings @@ -7,6 +7,14 @@ True True True + True + True + True + True + True + True + True + True True True True diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs index a0fc4e7ad..59a69c68d 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ActivityExecutionContextExtensions.cs @@ -25,13 +25,13 @@ public static partial class ActivityExecutionContextExtensions /// /// Attempts to get a value from the input provided via . If a value was found, an attempt is made to convert it into the specified type T. /// - public static bool TryGetWorkflowInput(this ActivityExecutionContext context, string key, out T value, JsonSerializerOptions? serializerOptions = default) + public static bool TryGetWorkflowInput(this ActivityExecutionContext context, string key, out T value, JsonSerializerOptions? serializerOptions = null) { var wellKnownTypeRegistry = context.GetRequiredService(); if (context.WorkflowInput.TryGetValue(key, out var v)) { - value = v.ConvertTo(new ObjectConverterOptions(serializerOptions, wellKnownTypeRegistry))!; + value = v.ConvertTo(new(serializerOptions, wellKnownTypeRegistry))!; return true; } @@ -42,15 +42,15 @@ public static partial class ActivityExecutionContextExtensions /// /// Gets a value from the input provided via . If a value was found, an attempt is made to convert it into the specified type T. /// - public static T GetWorkflowInput(this ActivityExecutionContext context, JsonSerializerOptions? serializerOptions = default) => context.GetWorkflowInput(typeof(T).Name, serializerOptions); + public static T GetWorkflowInput(this ActivityExecutionContext context, JsonSerializerOptions? serializerOptions = null) => context.GetWorkflowInput(typeof(T).Name, serializerOptions); /// /// Gets a value from the input provided via . If a value was found, an attempt is made to convert it into the specified type T. /// - public static T GetWorkflowInput(this ActivityExecutionContext context, string key, JsonSerializerOptions? serializerOptions = default) + public static T GetWorkflowInput(this ActivityExecutionContext context, string key, JsonSerializerOptions? serializerOptions = null) { var wellKnownTypeRegistry = context.GetRequiredService(); - return context.WorkflowInput[key].ConvertTo(new ObjectConverterOptions(serializerOptions, wellKnownTypeRegistry))!; + return context.WorkflowInput[key].ConvertTo(new(serializerOptions, wellKnownTypeRegistry))!; } /// @@ -61,7 +61,7 @@ public static partial class ActivityExecutionContextExtensions /// Thrown when the specified activity does not implement . public static void SetResult(this ActivityExecutionContext context, object? value) { - var activity = context.Activity as IActivityWithResult ?? throw new Exception($"Cannot set result on activity {context.Activity.Id} because it does not implement {nameof(IActivityWithResult)}."); + var activity = context.Activity as IActivityWithResult ?? throw new($"Cannot set result on activity {context.Activity.Id} because it does not implement {nameof(IActivityWithResult)}."); context.Set(activity.Result, value, "Result"); } @@ -79,7 +79,7 @@ public static partial class ActivityExecutionContextExtensions /// The type of storage driver to use for the variable. /// A callback to configure the memory block. /// The created . - public static Variable CreateVariable(this ActivityExecutionContext context, string name, object? value, Type? storageDriverType = default, Action? configure = default) => + public static Variable CreateVariable(this ActivityExecutionContext context, string name, object? value, Type? storageDriverType = null, Action? configure = null) => context.ExpressionExecutionContext.CreateVariable(name, value, storageDriverType, configure); /// @@ -90,7 +90,7 @@ public static partial class ActivityExecutionContextExtensions /// The value of the variable. /// A callback to configure the memory block. /// The created . - public static Variable SetVariable(this ActivityExecutionContext context, string name, object? value, Action? configure = default) => + public static Variable SetVariable(this ActivityExecutionContext context, string name, object? value, Action? configure = null) => context.ExpressionExecutionContext.SetVariable(name, value, configure); /// @@ -183,64 +183,6 @@ public static partial class ActivityExecutionContextExtensions return portProperty.GetCustomAttribute()?.Name ?? portProperty.Name; } - /// - /// Returns a flattened list of the current context's ancestors. - /// - public static IEnumerable GetAncestors(this ActivityExecutionContext context) - { - var current = context.ParentActivityExecutionContext; - - while (current != null) - { - yield return current; - current = current.ParentActivityExecutionContext; - } - } - - /// - /// Returns a flattened list of the current context's descendants. - /// - public static IEnumerable GetDescendents(this ActivityExecutionContext context) - { - var children = context.WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == context).ToList(); - - foreach (var child in children) - { - yield return child; - - foreach (var descendent in GetDescendents(child)) - yield return descendent; - } - } - - /// - /// Returns a flattened list of the current context's immediate active children. - /// - public static IEnumerable GetActiveChildren(this ActivityExecutionContext context) => - context.WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == context); - - /// - /// Returns a flattened list of the current context's immediate children. - /// - public static IEnumerable GetChildren(this ActivityExecutionContext context) => - context.WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == context); - - /// - /// Returns a flattened list of the current context's descendants. - /// - public static IEnumerable GetDescendants(this ActivityExecutionContext context) - { - var children = context.WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == context).ToList(); - - foreach (var child in children) - { - yield return child; - - foreach (var descendant in child.GetDescendants()) - yield return descendant; - } - } - /// /// Send a signal up the current hierarchy of ancestors. /// @@ -358,6 +300,68 @@ public static partial class ActivityExecutionContextExtensions return null; } + + /// + /// Returns a flattened list of the current context's ancestors. + /// + public static IEnumerable GetAncestors(this ActivityExecutionContext context) + { + var current = context.ParentActivityExecutionContext; + + while (current != null) + { + yield return current; + current = current.ParentActivityExecutionContext; + } + } + + /// + /// Returns a flattened list of the current context's descendants. + /// + public static IEnumerable GetDescendents(this ActivityExecutionContext context) + { + var children = context.WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == context).ToList(); + + foreach (var child in children) + { + yield return child; + + foreach (var descendent in GetDescendents(child)) + yield return descendent; + } + } + + /// + /// Returns a flattened list of the current context's immediate active children. + /// + public static IEnumerable GetActiveChildren(this ActivityExecutionContext context) + { + return context.WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == context); + } + + /// + /// Returns a flattened list of the current context's immediate children. + /// + public static IEnumerable GetChildren(this ActivityExecutionContext context) + { + return context.WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == context); + } + + /// + /// Returns a flattened list of the current context's descendants. + /// + public static IEnumerable GetDescendants(this ActivityExecutionContext context) + { + var children = context.WorkflowExecutionContext.ActivityExecutionContexts.Where(x => x.ParentActivityExecutionContext == context).ToList(); + + foreach (var child in children) + { + yield return child; + + foreach (var descendant in child.GetDescendants()) + yield return descendant; + } + } internal static bool GetHasEvaluatedProperties(this ActivityExecutionContext context) => context.TransientProperties.TryGetValue("HasEvaluatedProperties", out var value) && value; internal static void SetHasEvaluatedProperties(this ActivityExecutionContext context) => context.TransientProperties["HasEvaluatedProperties"] = true; diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ActivityPropertyExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ActivityPropertyExtensions.cs index 4d657080a..ff9bee7c5 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ActivityPropertyExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ActivityPropertyExtensions.cs @@ -11,6 +11,7 @@ public static class ActivityPropertyExtensions private static readonly string[] CanStartWorkflowPropertyName = ["canStartWorkflow", "CanStartWorkflow"]; private static readonly string[] RunAsynchronouslyPropertyName = ["runAsynchronously", "RunAsynchronously"]; private static readonly string[] SourcePropertyName = ["source", "Source"]; + private static readonly string[] CommitStrategyName = ["commitStrategyName", "CommitStrategyName"]; /// /// Gets a flag indicating whether this activity can be used for starting a workflow. @@ -47,6 +48,23 @@ public static class ActivityPropertyExtensions /// public static void SetSource(this IActivity activity, string value) => activity.CustomProperties[SourcePropertyName[0]] = value; + /// + /// Gets the commit state behavior for the specified activity. + /// + public static string? GetCommitStrategy(this IActivity activity) => activity.CustomProperties.GetValueOrDefault(CommitStrategyName, () => null); + + /// + /// Sets the commit state behavior for the specified activity. + /// + public static TActivity SetCommitStrategy(this TActivity activity, string? name) where TActivity : IActivity + { + if (string.IsNullOrWhiteSpace(name)) + activity.CustomProperties.Remove(CommitStrategyName[0]); + else + activity.CustomProperties[CommitStrategyName[0]] = name; + return activity; + } + /// /// Sets the source file and line number where this activity was instantiated, if any. /// @@ -58,22 +76,22 @@ public static class ActivityPropertyExtensions var source = $"{Path.GetFileName(sourceFile)}:{lineNumber}"; activity.SetSource(source); } - + /// /// Gets the display text for the specified activity. /// - public static string? GetDisplayText(this IActivity activity) => activity.Metadata.TryGetValue("displayText", out var value) ? value.ToString() : default; - + public static string? GetDisplayText(this IActivity activity) => activity.Metadata.TryGetValue("displayText", out var value) ? value.ToString() : null; + /// /// Sets the display text for the specified activity. /// public static void SetDisplayText(this IActivity activity, string value) => activity.Metadata["displayText"] = value; - + /// /// Gets the description for the specified activity. /// - public static string? GetDescription(this IActivity activity) => activity.Metadata.TryGetValue("description", out var value) ? value.ToString() : default; - + public static string? GetDescription(this IActivity activity) => activity.Metadata.TryGetValue("description", out var value) ? value.ToString() : null; + /// /// Sets the description for the specified activity. /// diff --git a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs index a88e84498..70f5381d2 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/ExpressionExecutionContextExtensions.cs @@ -119,7 +119,7 @@ public static class ExpressionExecutionContextExtensions public static Variable? GetVariable(this ExpressionExecutionContext context, string name, bool localScopeOnly = false) { var block = context.GetVariableBlock(name, localScopeOnly); - return block?.Metadata is VariableBlockMetadata metadata ? metadata.Variable : default; + return block?.Metadata is VariableBlockMetadata metadata ? metadata.Variable : null; } private static MemoryBlock? GetVariableBlock(this ExpressionExecutionContext context, string name, bool localScopeOnly = false) @@ -138,12 +138,12 @@ public static class ExpressionExecutionContextExtensions /// Creates a named variable in the context. /// public static Variable CreateVariable(this ExpressionExecutionContext context, string name, T? value, Type? storageDriverType = null, - Action? configure = default) + Action? configure = null) { var existingVariable = context.GetVariable(name, localScopeOnly: true); if (existingVariable != null) - throw new Exception($"Variable {name} already exists in the context."); + throw new($"Variable {name} already exists in the context."); var variable = new Variable(name, value) { @@ -173,7 +173,7 @@ public static class ExpressionExecutionContextExtensions /// /// Sets the value of a named variable in the context. /// - public static Variable SetVariable(this ExpressionExecutionContext context, string name, T? value, Action? configure = default) + public static Variable SetVariable(this ExpressionExecutionContext context, string name, T? value, Action? configure = null) { var variable = context.GetVariable(name); @@ -193,7 +193,7 @@ public static class ExpressionExecutionContextExtensions /// /// Sets the output to the specified value. /// - public static void Set(this ExpressionExecutionContext context, Output? output, object? value, Action? configure = default) + public static void Set(this ExpressionExecutionContext context, Output? output, object? value, Action? configure = null) { if (output != null) { @@ -412,11 +412,11 @@ public static class ExpressionExecutionContextExtensions // Otherwise, return the input. var workflowExecutionContext = context.GetWorkflowExecutionContext(); var input = workflowExecutionContext.Input; - return input.TryGetValue(name, out var value) ? value : default; + return input.TryGetValue(name, out var value) ? value : null; } /// - /// Returns the value of the specified input. + /// Returns the value of the specified output. /// /// /// The ID or name of the activity. @@ -433,9 +433,9 @@ public static class ExpressionExecutionContextExtensions throw new InvalidOperationException("Activity not found."); var outputRegister = workflowExecutionContext.GetActivityOutputRegister(); - var outputRecordCandidates = outputRegister.FindMany(x => x.ActivityId == activity.Id && x.OutputName == outputName).ToList(); + var outputRecordCandidates = outputRegister.FindMany(activity.Id, outputName); var containerIds = activityExecutionContext.GetAncestors().Select(x => x.Id).ToList(); - var filteredOutputRecordCandidates = outputRecordCandidates.Where(x => containerIds.Contains(x.ContainerId)).ToList(); + var filteredOutputRecordCandidates = outputRecordCandidates.Where(x => containerIds.Contains(x.ContainerId)); var outputRecord = filteredOutputRecordCandidates.FirstOrDefault(); return outputRecord?.Value; } @@ -464,7 +464,7 @@ public static class ExpressionExecutionContextExtensions foreach (var output in activityDescriptor.Outputs) { var outputPascalName = output.Name.Pascalize(); - yield return new ActivityOutputs(activity.Id, activityIdPascalName, [ + yield return new(activity.Id, activityIdPascalName, [ outputPascalName ]); } @@ -508,7 +508,7 @@ public static class ExpressionExecutionContextExtensions { var inputPascalName = inputEntry.Key.Pascalize(); var inputValue = inputEntry.Value; - yield return new WorkflowInput(inputPascalName, inputValue); + yield return new(inputPascalName, inputValue); } } else @@ -522,7 +522,7 @@ public static class ExpressionExecutionContextExtensions var variable = variableBlockMetadata.Variable; var variablePascalName = variable.Name.Pascalize(); - yield return new WorkflowInput(variablePascalName, block.Value); + yield return new(variablePascalName, block.Value); } } } diff --git a/src/modules/Elsa.Workflows.Core/Extensions/TriggerExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/TriggerExtensions.cs index a6de0a24f..87a051e0f 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/TriggerExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/TriggerExtensions.cs @@ -50,7 +50,7 @@ public static class TriggerExtensions var expressionInput = new Dictionary(); var applicationProperties = ExpressionExecutionContextExtensions.CreateTriggerIndexingPropertiesFrom(context.Workflow, expressionInput); applicationProperties[ExpressionExecutionContextExtensions.ActivityKey] = trigger; - var expressionExecutionContext = new ExpressionExecutionContext(serviceProvider, register, default, applicationProperties, cancellationToken); + var expressionExecutionContext = new ExpressionExecutionContext(serviceProvider, register, null, applicationProperties, null, cancellationToken); // Evaluate activity inputs before requesting trigger data. foreach (var namedInput in assignedInputs) diff --git a/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs b/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs index 044abe9b2..6745c6004 100644 --- a/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs +++ b/src/modules/Elsa.Workflows.Core/Extensions/WorkflowExecutionContextExtensions.cs @@ -88,7 +88,7 @@ public static class WorkflowExecutionContextExtensions : WorkflowExecutionContext.Noop; // Store the bookmark to resume in the context. - workflowExecutionContext.ResumedBookmarkContext = new ResumedBookmarkContext(bookmark); + workflowExecutionContext.ResumedBookmarkContext = new(bookmark); logger.LogDebug("Scheduled activity {ActivityId} to resume from bookmark {BookmarkId}", bookmarkedActivity.Id, bookmark.Id); return workItem; diff --git a/src/modules/Elsa.Workflows.Core/Features/WorkflowsFeature.cs b/src/modules/Elsa.Workflows.Core/Features/WorkflowsFeature.cs index 67e5cb4a6..ffafd87cb 100644 --- a/src/modules/Elsa.Workflows.Core/Features/WorkflowsFeature.cs +++ b/src/modules/Elsa.Workflows.Core/Features/WorkflowsFeature.cs @@ -1,7 +1,6 @@ using Elsa.Common; using Elsa.Common.Features; using Elsa.Common.Serialization; -using Elsa.CSharp.Activities; using Elsa.Expressions.Features; using Elsa.Extensions; using Elsa.Features.Abstractions; @@ -9,6 +8,7 @@ using Elsa.Features.Attributes; using Elsa.Features.Services; using Elsa.Workflows.ActivationValidators; using Elsa.Workflows.Builders; +using Elsa.Workflows.CommitStates; using Elsa.Workflows.IncidentStrategies; using Elsa.Workflows.LogPersistence; using Elsa.Workflows.LogPersistence.Strategies; @@ -36,6 +36,7 @@ namespace Elsa.Workflows.Features; [DependsOn(typeof(MediatorFeature))] [DependsOn(typeof(DefaultFormattersFeature))] [DependsOn(typeof(MultitenancyFeature))] +[DependsOn(typeof(CommitStrategiesFeature))] public class WorkflowsFeature : FeatureBase { /// diff --git a/src/modules/Elsa.Workflows.Core/Middleware/Activities/DefaultActivityInvokerMiddleware.cs b/src/modules/Elsa.Workflows.Core/Middleware/Activities/DefaultActivityInvokerMiddleware.cs index 65652ea04..9b5ef9cd9 100644 --- a/src/modules/Elsa.Workflows.Core/Middleware/Activities/DefaultActivityInvokerMiddleware.cs +++ b/src/modules/Elsa.Workflows.Core/Middleware/Activities/DefaultActivityInvokerMiddleware.cs @@ -1,5 +1,6 @@ using Elsa.Extensions; using Elsa.Workflows.Activities; +using Elsa.Workflows.CommitStates; using Elsa.Workflows.Pipelines.ActivityExecution; using Microsoft.Extensions.Logging; @@ -19,19 +20,19 @@ public static class ActivityInvokerMiddlewareExtensions /// /// A default activity execution middleware component that evaluates the current activity's properties, executes the activity and adds any produced bookmarks to the workflow execution context. /// -public class DefaultActivityInvokerMiddleware(ActivityMiddlewareDelegate next, ILogger logger) +public class DefaultActivityInvokerMiddleware(ActivityMiddlewareDelegate next, ICommitStrategyRegistry commitStrategyRegistry, ILogger logger) : IActivityExecutionMiddleware { /// public async ValueTask InvokeAsync(ActivityExecutionContext context) { context.CancellationToken.ThrowIfCancellationRequested(); - + var workflowExecutionContext = context.WorkflowExecutionContext; // Evaluate input properties. await EvaluateInputPropertiesAsync(context); - + // Prevent the activity from being started if cancellation is requested. if (context.CancellationToken.IsCancellationRequested) { @@ -39,7 +40,7 @@ public class DefaultActivityInvokerMiddleware(ActivityMiddlewareDelegate next, I context.AddExecutionLogEntry("Activity cancelled"); return; } - + // Check if the activity can be executed. if (!await context.Activity.CanExecuteAsync(context)) { @@ -48,6 +49,10 @@ public class DefaultActivityInvokerMiddleware(ActivityMiddlewareDelegate next, I return; } + // Conditionally commit the workflow state. + if (ShouldCommit(context, ActivityLifetimeEvent.ActivityExecuting)) + await context.WorkflowExecutionContext.CommitAsync(); + context.TransitionTo(ActivityStatus.Running); // Execute activity. @@ -78,6 +83,10 @@ public class DefaultActivityInvokerMiddleware(ActivityMiddlewareDelegate next, I workflowExecutionContext.Bookmarks.AddRange(context.Bookmarks); logger.LogDebug("Added {BookmarkCount} bookmarks to the workflow execution context", context.Bookmarks.Count); } + + // Conditionally commit the workflow state. + if (ShouldCommit(context, ActivityLifetimeEvent.ActivityExecuted)) + await context.WorkflowExecutionContext.CommitAsync(); } /// @@ -111,4 +120,41 @@ public class DefaultActivityInvokerMiddleware(ActivityMiddlewareDelegate next, I // Evaluate input properties. await context.EvaluateInputPropertiesAsync(); } + + private bool ShouldCommit(ActivityExecutionContext context, ActivityLifetimeEvent lifetimeEvent) + { + var strategyName = context.Activity.GetCommitStrategy(); + var strategy = string.IsNullOrWhiteSpace(strategyName) ? null : commitStrategyRegistry.FindActivityStrategy(strategyName); + var commitAction = CommitAction.Default; + + if (strategy != null) + { + var strategyContext = new ActivityCommitStateStrategyContext(context, lifetimeEvent); + commitAction = strategy.ShouldCommit(strategyContext); + } + + switch (commitAction) + { + case CommitAction.Skip: + return false; + case CommitAction.Commit: + return true; + case CommitAction.Default: + { + var workflowStrategyName = context.WorkflowExecutionContext.Workflow.Options.CommitStrategyName; + var workflowStrategy = string.IsNullOrWhiteSpace(workflowStrategyName) ? null : commitStrategyRegistry.FindWorkflowStrategy(workflowStrategyName); + + if(workflowStrategy == null) + return false; + + var workflowLifetimeEvent = lifetimeEvent == ActivityLifetimeEvent.ActivityExecuting ? WorkflowLifetimeEvent.ActivityExecuting : WorkflowLifetimeEvent.ActivityExecuted; + var workflowCommitStateStrategyContext = new WorkflowCommitStateStrategyContext(context.WorkflowExecutionContext, workflowLifetimeEvent); + commitAction = workflowStrategy.ShouldCommit(workflowCommitStateStrategyContext); + + return commitAction == CommitAction.Commit; + } + default: + throw new ArgumentOutOfRangeException(nameof(commitAction), commitAction, "Unknown commit action"); + } + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Middleware/Activities/ExceptionHandlingMiddleware.cs b/src/modules/Elsa.Workflows.Core/Middleware/Activities/ExceptionHandlingMiddleware.cs index b26210782..38d5963a2 100644 --- a/src/modules/Elsa.Workflows.Core/Middleware/Activities/ExceptionHandlingMiddleware.cs +++ b/src/modules/Elsa.Workflows.Core/Middleware/Activities/ExceptionHandlingMiddleware.cs @@ -1,4 +1,5 @@ using Elsa.Common; +using Elsa.Extensions; using Elsa.Workflows.Models; using Elsa.Workflows.Pipelines.ActivityExecution; using Elsa.Workflows.State; @@ -20,45 +21,47 @@ public static class ExceptionHandlingMiddlewareExtensions /// /// Catches any exceptions thrown by downstream components and transitions the workflow into the faulted state. /// -public class ExceptionHandlingMiddleware : IActivityExecutionMiddleware +public class ExceptionHandlingMiddleware(ActivityMiddlewareDelegate next, IIncidentStrategyResolver incidentStrategyResolver, ISystemClock systemClock, ILogger logger) + : IActivityExecutionMiddleware { - private readonly ActivityMiddlewareDelegate _next; - private readonly IIncidentStrategyResolver _incidentStrategyResolver; - private readonly ISystemClock _systemClock; - private readonly ILogger _logger; - - /// - /// Constructor. - /// - public ExceptionHandlingMiddleware(ActivityMiddlewareDelegate next, IIncidentStrategyResolver incidentStrategyResolver, ISystemClock systemClock, ILogger logger) - { - _next = next; - _incidentStrategyResolver = incidentStrategyResolver; - _systemClock = systemClock; - _logger = logger; - } - /// public async ValueTask InvokeAsync(ActivityExecutionContext context) { try { - await _next(context); + await next(context); } catch (Exception e) { - _logger.LogWarning(e, "An exception was caught from a downstream middleware component"); - context.Exception = e; - context.TransitionTo(ActivityStatus.Faulted); - - var activity = context.Activity; - var exceptionState = ExceptionState.FromException(e); - var now = _systemClock.UtcNow; - var incident = new ActivityIncident(activity.Id, activity.Type, e.Message, exceptionState, now); - context.WorkflowExecutionContext.Incidents.Add(incident); - - var strategy = await _incidentStrategyResolver.ResolveStrategyAsync(context); - strategy.HandleIncident(context); + logger.LogWarning(e, "An exception was caught from a downstream middleware component"); + LogExceptionAndTransition(context, e); + FaultAncestors(context); + await HandleIncidentAsync(context); } } + + private void LogExceptionAndTransition(ActivityExecutionContext context, Exception e) + { + context.Exception = e; + context.TransitionTo(ActivityStatus.Faulted); + var activity = context.Activity; + var exceptionState = ExceptionState.FromException(e); + var now = systemClock.UtcNow; + var incident = new ActivityIncident(activity.Id, activity.Type, e.Message, exceptionState, now); + context.WorkflowExecutionContext.Incidents.Add(incident); + } + + private async Task HandleIncidentAsync(ActivityExecutionContext context) + { + var strategy = await incidentStrategyResolver.ResolveStrategyAsync(context); + strategy.HandleIncident(context); + } + + private static void FaultAncestors(ActivityExecutionContext context) + { + var ancestors = context.GetAncestors(); + + foreach (var ancestor in ancestors) + ancestor.TransitionTo(ActivityStatus.Faulted); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Middleware/Workflows/DefaultActivitySchedulerMiddleware.cs b/src/modules/Elsa.Workflows.Core/Middleware/Workflows/DefaultActivitySchedulerMiddleware.cs index f9d1645d9..200f8c203 100644 --- a/src/modules/Elsa.Workflows.Core/Middleware/Workflows/DefaultActivitySchedulerMiddleware.cs +++ b/src/modules/Elsa.Workflows.Core/Middleware/Workflows/DefaultActivitySchedulerMiddleware.cs @@ -1,4 +1,5 @@ using Elsa.Extensions; +using Elsa.Workflows.CommitStates; using Elsa.Workflows.Models; using Elsa.Workflows.Options; using Elsa.Workflows.Pipelines.WorkflowExecution; @@ -19,16 +20,8 @@ public static class UseActivitySchedulerMiddlewareExtensions /// /// A workflow execution middleware component that executes scheduled work items. /// -public class DefaultActivitySchedulerMiddleware : WorkflowExecutionMiddleware +public class DefaultActivitySchedulerMiddleware(WorkflowMiddlewareDelegate next, IActivityInvoker activityInvoker, ICommitStrategyRegistry commitStrategyRegistry) : WorkflowExecutionMiddleware(next) { - private readonly IActivityInvoker _activityInvoker; - - /// - public DefaultActivitySchedulerMiddleware(WorkflowMiddlewareDelegate next, IActivityInvoker activityInvoker) : base(next) - { - _activityInvoker = activityInvoker; - } - /// public override async ValueTask InvokeAsync(WorkflowExecutionContext context) { @@ -36,6 +29,8 @@ public class DefaultActivitySchedulerMiddleware : WorkflowExecutionMiddleware context.TransitionTo(WorkflowSubStatus.Executing); + await ConditionallyCommitStateAsync(context, WorkflowLifetimeEvent.WorkflowExecuting); + while (scheduler.HasAny) { // Do not start a workflow if cancellation has been requested. @@ -63,6 +58,21 @@ public class DefaultActivitySchedulerMiddleware : WorkflowExecutionMiddleware Input = workItem.Input }; - await _activityInvoker.InvokeAsync(context, workItem.Activity, options); + await activityInvoker.InvokeAsync(context, workItem.Activity, options); + } + + private async Task ConditionallyCommitStateAsync(WorkflowExecutionContext context, WorkflowLifetimeEvent lifetimeEvent) + { + var strategyName = context.Workflow.Options.CommitStrategyName; + var strategy = string.IsNullOrWhiteSpace(strategyName) ? null : commitStrategyRegistry.FindWorkflowStrategy(strategyName); + + if(strategy == null) + return; + + var strategyContext = new WorkflowCommitStateStrategyContext(context, lifetimeEvent); + var commitAction = strategy.ShouldCommit(strategyContext); + + if (commitAction is CommitAction.Commit) + await context.CommitAsync(); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Models/ActivityNode.cs b/src/modules/Elsa.Workflows.Core/Models/ActivityNode.cs index 44e39ac00..3768857cb 100644 --- a/src/modules/Elsa.Workflows.Core/Models/ActivityNode.cs +++ b/src/modules/Elsa.Workflows.Core/Models/ActivityNode.cs @@ -5,6 +5,10 @@ namespace Elsa.Workflows.Models; /// public class ActivityNode { + private readonly List _parents = new(); + private readonly List _children = new(); + private string? _nodeId; + /// /// Initializes a new instance of the class. /// @@ -23,8 +27,13 @@ public class ActivityNode { get { - var ancestorIds = Ancestors().Reverse().Select(x => x.Activity.Id).ToList(); - return ancestorIds.Any() ? $"{string.Join(":", ancestorIds)}:{Activity.Id}" : Activity.Id; + if (_nodeId == null) + { + var ancestorIds = Ancestors().Reverse().Select(x => x.Activity.Id).ToList(); + _nodeId = ancestorIds.Any() ? $"{string.Join(":", ancestorIds)}:{Activity.Id}" : Activity.Id; + } + + return _nodeId; } } @@ -41,12 +50,23 @@ public class ActivityNode /// /// Gets the parents of this node. /// - public ICollection Parents { get; set; } = new List(); - + public IReadOnlyCollection Parents => _parents.AsReadOnly(); + /// /// Gets the children of this node. /// - public ICollection Children { get; set; } = new List(); + public ICollection Children => _children.AsReadOnly(); + + public void AddParent(ActivityNode parent) + { + _parents.Add(parent); + _nodeId = null; + } + + public void AddChild(ActivityNode child) + { + _children.Add(child); + } /// /// Gets the descendants of this node. @@ -85,7 +105,7 @@ public class ActivityNode /// Gets the siblings of this node. /// public IEnumerable Siblings() => Parents.SelectMany(parent => parent.Children); - + /// /// Gets the siblings and cousins of this node. /// diff --git a/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs b/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs index 8b120af83..181aee62f 100644 --- a/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs +++ b/src/modules/Elsa.Workflows.Core/Models/ActivityOutputRegister.cs @@ -5,7 +5,8 @@ namespace Elsa.Workflows.Models; /// public class ActivityOutputRegister { - private readonly ICollection _records = new List(); + private readonly Dictionary> _recordsByActivityIdAndOutputName = new(); + private readonly Dictionary _recordsByActivityInstanceIdAndOutputName = new(); /// /// The default output name. @@ -19,7 +20,7 @@ public class ActivityOutputRegister /// The output value. public void Record(ActivityExecutionContext activityExecutionContext, object? outputValue) { - Record(activityExecutionContext, default, outputValue); + Record(activityExecutionContext, null, outputValue); } /// @@ -39,19 +40,33 @@ public class ActivityOutputRegister // Inspect the output descriptor to see if the specified output name matches any PropertyInfo's name. // If so, use that descriptor's name instead. var outputDescriptor = activityExecutionContext.ActivityDescriptor.Outputs.FirstOrDefault(x => x.PropertyInfo?.Name == outputName); - + if (outputDescriptor != null) outputName = outputDescriptor.Name; var record = new ActivityOutputRecord(containerId, activityId, activityInstanceId, outputName, outputValue); - _records.Add(record); + _recordsByActivityInstanceIdAndOutputName[CreateActivityInstanceIdLookupKey(activityInstanceId, outputName)] = record; + + var scopedRecordsKey = CreateActivityIdLookupKey(activityId, outputName); + + if (!_recordsByActivityIdAndOutputName.TryGetValue(scopedRecordsKey, out var scopedRecords)) + { + scopedRecords = new(); + _recordsByActivityIdAndOutputName[scopedRecordsKey] = scopedRecords; + } + + scopedRecords.Add(record); } /// - /// Finds all output records matching the specified predicate. + /// Finds all output records for the specified activity ID and output name. /// - public IEnumerable FindMany(Func predicate) => _records.Where(predicate); + public IEnumerable FindMany(string activityId, string? outputName = null) + { + var key = CreateActivityIdLookupKey(activityId, outputName); + return _recordsByActivityIdAndOutputName.TryGetValue(key, out var records) ? records : Enumerable.Empty(); + } /// /// Gets the output value for the specified activity ID. @@ -59,10 +74,12 @@ public class ActivityOutputRegister /// The activity ID. /// Name of the output. /// The output value. - public object? FindOutputByActivityId(string activityId, string? outputName = default) + public object? FindOutputByActivityId(string activityId, string? outputName = null) { - var record = _records.LastOrDefault(x => x.ActivityId == activityId && x.OutputName == (outputName ?? DefaultOutputName)); - return record?.Value; + var key = CreateActivityIdLookupKey(activityId, outputName); + return !_recordsByActivityIdAndOutputName.TryGetValue(key, out var records) + ? null + : records.LastOrDefault()?.Value; // Always return the last value. } /// @@ -71,9 +88,14 @@ public class ActivityOutputRegister /// The activity instance ID. /// /// The output value. - public object? FindOutputByActivityInstanceId(string activityInstanceId, string? outputName = default) + public object? FindOutputByActivityInstanceId(string activityInstanceId, string? outputName = null) { - var record = _records.LastOrDefault(x => x.ActivityInstanceId == activityInstanceId && x.OutputName == (outputName ?? DefaultOutputName)); - return record?.Value; + var key = CreateActivityInstanceIdLookupKey(activityInstanceId, outputName); + return !_recordsByActivityInstanceIdAndOutputName.TryGetValue(key, out var record) + ? null + : record.Value; } + + private string CreateActivityIdLookupKey(string activityId, string? outputName) => $"{activityId}:{outputName ?? DefaultOutputName}"; + private string CreateActivityInstanceIdLookupKey(string activityInstanceId, string? outputName) => $"{activityInstanceId}:{outputName ?? DefaultOutputName}"; } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Models/Bookmark.cs b/src/modules/Elsa.Workflows.Core/Models/Bookmark.cs index 9d062b336..e2ba868f8 100644 --- a/src/modules/Elsa.Workflows.Core/Models/Bookmark.cs +++ b/src/modules/Elsa.Workflows.Core/Models/Bookmark.cs @@ -26,13 +26,13 @@ public record Bookmark( string? ActivityInstanceId, DateTimeOffset CreatedAt, bool AutoBurn = true, - string? CallbackMethodName = default, + string? CallbackMethodName = null, bool AutoComplete = true, - IDictionary? Metadata = default) + IDictionary? Metadata = null) { /// [JsonConstructor] - public Bookmark() : this("", "", "", null, "", "", "", default, default) + public Bookmark() : this("", "", "", null, "", "", "", default, false) { } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Models/ChangeTrackingDictionary.cs b/src/modules/Elsa.Workflows.Core/Models/ChangeTrackingDictionary.cs new file mode 100644 index 000000000..e4a18a189 --- /dev/null +++ b/src/modules/Elsa.Workflows.Core/Models/ChangeTrackingDictionary.cs @@ -0,0 +1,27 @@ +namespace Elsa.Workflows; + +public class ChangeTrackingDictionary(Action onChange) : Dictionary where TKey : notnull +{ + public new void Add(TKey key, TValue value) + { + base.Add(key, value); + onChange(); + } + + public new bool Remove(TKey key) + { + var result = base.Remove(key); + if (result) onChange(); + return result; + } + + public new TValue this[TKey key] + { + get => base[key]; + set + { + base[key] = value; + onChange(); + } + } +} diff --git a/src/modules/Elsa.Workflows.Core/Models/PropertyDescriptor.cs b/src/modules/Elsa.Workflows.Core/Models/PropertyDescriptor.cs index dc6e8ec54..24ed114b9 100644 --- a/src/modules/Elsa.Workflows.Core/Models/PropertyDescriptor.cs +++ b/src/modules/Elsa.Workflows.Core/Models/PropertyDescriptor.cs @@ -11,13 +11,13 @@ public abstract class PropertyDescriptor /// /// The name. /// - public string Name { get; set; } = default!; + public string Name { get; set; } = null!; /// /// The .NET type. /// [JsonPropertyName("typeName")] - public Type Type { get; set; } = default!; + public Type Type { get; set; } = null!; /// /// The user friendly name of the input. Used by UI tools. @@ -53,13 +53,13 @@ public abstract class PropertyDescriptor /// Returns the value of the input property for the specified activity. /// [JsonIgnore] - public Func ValueGetter { get; set; } = default!; + public Func ValueGetter { get; set; } = null!; /// /// Sets the value of the input property for the specified activity. /// [JsonIgnore] - public Action ValueSetter { get; set; } = default!; + public Action ValueSetter { get; set; } = null!; /// /// The source of the property, if any. diff --git a/src/modules/Elsa.Workflows.Core/Models/WorkflowOptions.cs b/src/modules/Elsa.Workflows.Core/Models/WorkflowOptions.cs index e4b82b116..4b688f914 100644 --- a/src/modules/Elsa.Workflows.Core/Models/WorkflowOptions.cs +++ b/src/modules/Elsa.Workflows.Core/Models/WorkflowOptions.cs @@ -29,4 +29,9 @@ public class WorkflowOptions /// The type of to use when a fault occurs in the workflow. /// public Type? IncidentStrategyType { get; set; } + + /// + /// The options for committing workflow state. + /// + public string? CommitStrategyName { get; set; } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Converters/TypeJsonConverter.cs b/src/modules/Elsa.Workflows.Core/Serialization/Converters/TypeJsonConverter.cs index c8a4e9a3d..5fe74064e 100644 --- a/src/modules/Elsa.Workflows.Core/Serialization/Converters/TypeJsonConverter.cs +++ b/src/modules/Elsa.Workflows.Core/Serialization/Converters/TypeJsonConverter.cs @@ -31,10 +31,18 @@ public class TypeJsonConverter : JsonConverter { var typeAlias = reader.GetString()!; - // Handle collection types. + // Handle array types. if (typeAlias.EndsWith("[]")) { - var elementTypeAlias = typeAlias[..^"[]".Length]; + var elementTypeAlias = typeAlias[..^2]; + var elementType = _wellKnownTypeRegistry.TryGetType(elementTypeAlias, out var t) ? t : Type.GetType(elementTypeAlias)!; + return elementType.MakeArrayType(); + } + + // Handle collection types. + if (typeAlias.StartsWith("List<") && typeAlias.EndsWith(">")) + { + var elementTypeAlias = typeAlias[5..^1]; var elementType = _wellKnownTypeRegistry.TryGetType(elementTypeAlias, out var t) ? t : Type.GetType(elementTypeAlias)!; return typeof(List<>).MakeGenericType(elementType); } @@ -45,6 +53,15 @@ public class TypeJsonConverter : JsonConverter /// public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options) { + // Handle array types. + if (value.IsArray) + { + var elementType = value.GetElementType()!; + var elementTypeAlias = _wellKnownTypeRegistry.TryGetAlias(elementType, out var elementTypeAliasValue) ? elementTypeAliasValue : elementType.GetSimpleAssemblyQualifiedName(); + writer.WriteStringValue($"{elementTypeAlias}[]"); + return; + } + // Handle collection types. if (value is { IsGenericType: true, GenericTypeArguments.Length: 1 }) { @@ -53,7 +70,7 @@ public class TypeJsonConverter : JsonConverter if (typedEnumerable.IsAssignableFrom(value) && _wellKnownTypeRegistry.TryGetAlias(elementType, out var elementTypeAlias)) { - writer.WriteStringValue($"{elementTypeAlias}[]"); + writer.WriteStringValue($"List<{elementTypeAlias}>"); return; } } diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonPayloadSerializer.cs b/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonPayloadSerializer.cs index 26b811d89..c5ab81a8f 100644 --- a/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonPayloadSerializer.cs +++ b/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonPayloadSerializer.cs @@ -74,7 +74,7 @@ public class JsonPayloadSerializer : IPayloadSerializer { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; options.Converters.Add(new JsonStringEnumConverter()); diff --git a/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonWorkflowStateSerializer.cs b/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonWorkflowStateSerializer.cs index 90093d688..d23869f4a 100644 --- a/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonWorkflowStateSerializer.cs +++ b/src/modules/Elsa.Workflows.Core/Serialization/Serializers/JsonWorkflowStateSerializer.cs @@ -1,7 +1,5 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; -using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; using Elsa.Common.Serialization; using Elsa.Expressions.Contracts; using Elsa.Workflows.Serialization.Converters; @@ -129,29 +127,18 @@ public class JsonWorkflowStateSerializer : ConfigurableSerializer, IWorkflowStat /// public override JsonSerializerOptions GetOptions() { - // Bypass cached options to ensure that the reference handler is always fresh. - return GetOptionsInternal(); - } - - /// - protected override void Configure(JsonSerializerOptions options) - { - var referenceHandler = new CrossScopedReferenceHandler(); - - options.ReferenceHandler = referenceHandler; - options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; - options.PropertyNameCaseInsensitive = true; - options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + var options = base.GetOptions(); + return new JsonSerializerOptions(options) + { + ReferenceHandler = new CrossScopedReferenceHandler() + }; } /// protected override void AddConverters(JsonSerializerOptions options) { - options.Converters.Add(new JsonStringEnumConverter()); options.Converters.Add(new TypeJsonConverter(_wellKnownTypeRegistry)); - options.Converters.Add(JsonMetadataServices.TimeSpanConverter); options.Converters.Add(new PolymorphicObjectConverterFactory(_wellKnownTypeRegistry)); - options.Converters.Add(new TypeJsonConverter(_wellKnownTypeRegistry)); options.Converters.Add(new VariableConverterFactory(_wellKnownTypeRegistry, _loggerFactory)); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Services/ActivityInvoker.cs b/src/modules/Elsa.Workflows.Core/Services/ActivityInvoker.cs index b180f61e7..a7d86aa5d 100644 --- a/src/modules/Elsa.Workflows.Core/Services/ActivityInvoker.cs +++ b/src/modules/Elsa.Workflows.Core/Services/ActivityInvoker.cs @@ -12,7 +12,7 @@ public class ActivityInvoker( { /// - public async Task InvokeAsync(WorkflowExecutionContext workflowExecutionContext, IActivity activity, ActivityInvocationOptions? options = default) + public async Task InvokeAsync(WorkflowExecutionContext workflowExecutionContext, IActivity activity, ActivityInvocationOptions? options = null) { // Setup an activity execution context, potentially reusing an existing one if requested. var existingActivityExecutionContext = options?.ExistingActivityExecutionContext; @@ -20,12 +20,13 @@ public class ActivityInvoker( // Perform a lookup to make sure the activity execution context is part of the workflow execution context. var activityExecutionContext = existingActivityExecutionContext != null ? workflowExecutionContext.ActivityExecutionContexts.FirstOrDefault(x => x.Id == existingActivityExecutionContext.Id) - : default; + : null; if (activityExecutionContext == null) { // Create a new activity execution context. activityExecutionContext = await workflowExecutionContext.CreateActivityExecutionContextAsync(activity, options); + activityExecutionContext.Taint(); // Add the activity context to the workflow context. workflowExecutionContext.AddActivityExecutionContext(activityExecutionContext); diff --git a/src/modules/Elsa.Workflows.Core/Services/ActivityLoggerStateGenerator.cs b/src/modules/Elsa.Workflows.Core/Services/ActivityLoggerStateGenerator.cs index 959e6726c..eb2bd5f6f 100644 --- a/src/modules/Elsa.Workflows.Core/Services/ActivityLoggerStateGenerator.cs +++ b/src/modules/Elsa.Workflows.Core/Services/ActivityLoggerStateGenerator.cs @@ -12,7 +12,7 @@ public class ActivityLoggerStateGenerator : ILoggerStateGeneratorA containing the state related to the . public Dictionary GenerateLoggerState(ActivityExecutionContext activityExecutionContext) { - return new Dictionary + return new() { ["ActivityInstanceId"] = activityExecutionContext.Id }; diff --git a/src/modules/Elsa.Workflows.Core/Services/ActivityVisitor.cs b/src/modules/Elsa.Workflows.Core/Services/ActivityVisitor.cs index fcdb66519..c95c7beec 100644 --- a/src/modules/Elsa.Workflows.Core/Services/ActivityVisitor.cs +++ b/src/modules/Elsa.Workflows.Core/Services/ActivityVisitor.cs @@ -71,12 +71,12 @@ public class ActivityVisitor : IActivityVisitor if (childNode == null) { - childNode = new ActivityNode(activity, activityPort.PortName); + childNode = new(activity, activityPort.PortName); collectedNodes.Add(childNode); } - childNode.Parents.Add(pair.Node); - pair.Node.Children.Add(childNode); + childNode.AddParent(pair.Node); + pair.Node.AddChild(childNode); collectedActivities.Add(activity); await VisitRecursiveAsync((childNode, activity), visitorContext, cancellationToken); } diff --git a/src/modules/Elsa.Workflows.Core/Services/NoopCommitStateHandler.cs b/src/modules/Elsa.Workflows.Core/Services/NoopCommitStateHandler.cs index 8503bddb7..b6b55d6bd 100644 --- a/src/modules/Elsa.Workflows.Core/Services/NoopCommitStateHandler.cs +++ b/src/modules/Elsa.Workflows.Core/Services/NoopCommitStateHandler.cs @@ -1,9 +1,15 @@ +using Elsa.Workflows.CommitStates; using Elsa.Workflows.State; namespace Elsa.Workflows; public class NoopCommitStateHandler : ICommitStateHandler { + public Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + public Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, WorkflowState workflowState, CancellationToken cancellationToken = default) { return Task.CompletedTask; diff --git a/src/modules/Elsa.Workflows.Core/Services/QueueBasedActivityScheduler.cs b/src/modules/Elsa.Workflows.Core/Services/QueueBasedActivityScheduler.cs index b13584b05..47b56e7fb 100644 --- a/src/modules/Elsa.Workflows.Core/Services/QueueBasedActivityScheduler.cs +++ b/src/modules/Elsa.Workflows.Core/Services/QueueBasedActivityScheduler.cs @@ -21,7 +21,7 @@ public class QueueBasedActivityScheduler : IActivityScheduler public ActivityWorkItem Take() => _queue.Dequeue(); /// - public IEnumerable List() => _queue.ToList(); + public IEnumerable List() => _queue; /// public bool Any(Func predicate) => _queue.Any(predicate); diff --git a/src/modules/Elsa.Workflows.Core/Services/VariableMapper.cs b/src/modules/Elsa.Workflows.Core/Services/VariableMapper.cs index 2524822c9..37899fc1b 100644 --- a/src/modules/Elsa.Workflows.Core/Services/VariableMapper.cs +++ b/src/modules/Elsa.Workflows.Core/Services/VariableMapper.cs @@ -59,7 +59,7 @@ public class VariableMapper .OnSuccess(value => variable.Value = value) .OnFailure(e => _logger.LogWarning("Failed to convert {SourceValue} to {TargetType}", source.Value, type.Name)); - variable.StorageDriverType = !string.IsNullOrEmpty(source.StorageDriverTypeName) ? Type.GetType(source.StorageDriverTypeName) : default; + variable.StorageDriverType = !string.IsNullOrEmpty(source.StorageDriverTypeName) ? Type.GetType(source.StorageDriverTypeName) : null; return variable; } @@ -76,6 +76,6 @@ public class VariableMapper var storageDriverTypeName = source.StorageDriverType?.GetSimpleAssemblyQualifiedName(); var serializedValue = value.Format(); - return new VariableModel(source.Id, source.Name, valueTypeAlias, serializedValue, storageDriverTypeName); + return new(source.Id, source.Name, valueTypeAlias, serializedValue, storageDriverTypeName); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Services/VariablePersistenceManager.cs b/src/modules/Elsa.Workflows.Core/Services/VariablePersistenceManager.cs index 3b351eddf..1e19a9d88 100644 --- a/src/modules/Elsa.Workflows.Core/Services/VariablePersistenceManager.cs +++ b/src/modules/Elsa.Workflows.Core/Services/VariablePersistenceManager.cs @@ -40,18 +40,26 @@ public class VariablePersistenceManager(IStorageDriverManager storageDriverManag continue; var id = GetStateId(variable); - var value = await driver.ReadAsync(id, storageDriverContext); - if (value == null) continue; - register.Declare(variable); - - if (!variable.TryParseValue(value, out var parsedValue)) + try { - logger.LogWarning("Failed to parse value for variable {VariableId} of type {VariableType} with value {Value}", variable.Id, variable.GetVariableType().FullName, value); - continue; - } + var value = await driver.ReadAsync(id, storageDriverContext); + if (value == null) continue; - variable.Set(register, parsedValue); + register.Declare(variable); + + if (!variable.TryParseValue(value, out var parsedValue)) + { + logger.LogWarning("Failed to parse value for variable {VariableId} of type {VariableType} with value {Value}", variable.Id, variable.GetVariableType().FullName, value); + continue; + } + + variable.Set(register, parsedValue); + } + catch (Exception e) + { + logger.LogError(e, "Failed to read variable {VariableId} from storage driver {StorageDriverType}", variable.Id, driver.GetType().FullName); + } } } } @@ -112,6 +120,17 @@ public class VariablePersistenceManager(IStorageDriverManager storageDriverManag } } + /// + public async Task DeleteVariablesAsync(WorkflowExecutionContext context) + { + var activityContexts = context.ActivityExecutionContexts.ToList(); + + foreach (var activityContext in activityContexts) + { + await DeleteVariablesAsync(activityContext); + } + } + private IEnumerable GetLocalVariables(IExecutionContext context) => context.Variables; private MemoryBlock EnsureBlock(MemoryRegister register, Variable variable) diff --git a/src/modules/Elsa.Workflows.Core/Services/WorkflowRunner.cs b/src/modules/Elsa.Workflows.Core/Services/WorkflowRunner.cs index 69cdf8785..a79842799 100644 --- a/src/modules/Elsa.Workflows.Core/Services/WorkflowRunner.cs +++ b/src/modules/Elsa.Workflows.Core/Services/WorkflowRunner.cs @@ -1,6 +1,7 @@ using Elsa.Extensions; using Elsa.Mediator.Contracts; using Elsa.Workflows.Activities; +using Elsa.Workflows.CommitStates; using Elsa.Workflows.Models; using Elsa.Workflows.Notifications; using Elsa.Workflows.Options; @@ -43,7 +44,7 @@ public class WorkflowRunner( public async Task> RunAsync(WorkflowBase workflow, RunWorkflowOptions? options = default, CancellationToken cancellationToken = default) { var result = await RunAsync((IWorkflow)workflow, options, cancellationToken); - return new RunWorkflowResult(result.WorkflowState, result.Workflow, (TResult)result.Result!); + return new(result.WorkflowState, result.Workflow, (TResult)result.Result!); } /// @@ -141,7 +142,7 @@ public class WorkflowRunner( if (!string.IsNullOrEmpty(activityHandle.ActivityInstanceId)) { var activityExecutionContext = workflowExecutionContext.ActivityExecutionContexts.FirstOrDefault(x => x.Id == activityHandle.ActivityInstanceId) - ?? throw new Exception("No activity execution context found with the specified ID."); + ?? throw new("No activity execution context found with the specified ID."); workflowExecutionContext.ScheduleActivityExecutionContext(activityExecutionContext); } else @@ -191,6 +192,6 @@ public class WorkflowRunner( var result = workflow.ResultVariable?.Get(workflowExecutionContext.MemoryRegister); await notificationSender.SendAsync(new WorkflowExecuted(workflow, workflowState, workflowExecutionContext), cancellationToken); await commitStateHandler.CommitAsync(workflowExecutionContext, workflowState, cancellationToken); - return new RunWorkflowResult(workflowState, workflowExecutionContext.Workflow, result); + return new(workflowState, workflowExecutionContext.Workflow, result); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs b/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs index 2767dc099..99a11a214 100644 --- a/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs +++ b/src/modules/Elsa.Workflows.Core/Services/WorkflowStateExtractor.cs @@ -139,8 +139,11 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor var properties = activityExecutionContextState.Properties; var activityExecutionContext = await workflowExecutionContext.CreateActivityExecutionContextAsync(activity); activityExecutionContext.Id = activityExecutionContextState.Id; - activityExecutionContext.Properties = properties; - activityExecutionContext.ActivityState = activityExecutionContextState.ActivityState ?? new Dictionary(); + activityExecutionContext.Properties.Merge(properties); + + if(activityExecutionContextState.ActivityState != null) + activityExecutionContext.ActivityState.Merge(activityExecutionContextState.ActivityState); + activityExecutionContext.TransitionTo(activityExecutionContextState.Status); activityExecutionContext.StartedAt = activityExecutionContextState.StartedAt; activityExecutionContext.CompletedAt = activityExecutionContextState.CompletedAt; @@ -189,14 +192,14 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor private static void ExtractCompletionCallbacks(WorkflowState state, WorkflowExecutionContext workflowExecutionContext) { - // Assert all referenced owner contexts exist. - var activeContexts = GetActiveActivityExecutionContexts(workflowExecutionContext.ActivityExecutionContexts).ToList(); + // Assert that all referenced owner contexts exist. + var activeContexts = workflowExecutionContext.GetActiveActivityExecutionContexts().ToList(); foreach (var completionCallback in workflowExecutionContext.CompletionCallbacks) { var ownerContext = activeContexts.FirstOrDefault(x => x == completionCallback.Owner); if (ownerContext == null) - throw new Exception("Lost an owner context"); + throw new("Lost an owner context"); } var completionCallbacks = workflowExecutionContext @@ -217,7 +220,7 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor var parentContext = activityExecutionContext.WorkflowExecutionContext.ActivityExecutionContexts.FirstOrDefault(x => x.Id == parentId); if (parentContext == null) - throw new Exception("We lost a context. This could indicate a bug in a parent activity that completed before (some of) its child activities."); + throw new("We lost a context. This could indicate a bug in a parent activity that completed before (some of) its child activities."); } var activityExecutionContextState = new ActivityExecutionContextState @@ -238,7 +241,7 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor } // Only persist non-completed contexts. - state.ActivityExecutionContexts = GetActiveActivityExecutionContexts(workflowExecutionContext.ActivityExecutionContexts).Reverse().Select(CreateActivityExecutionContextState).ToList(); + state.ActivityExecutionContexts = workflowExecutionContext.GetActiveActivityExecutionContexts().Reverse().Select(CreateActivityExecutionContextState).ToList(); } private void ExtractScheduledActivities(WorkflowState state, WorkflowExecutionContext workflowExecutionContext) @@ -257,12 +260,4 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor state.ScheduledActivities = scheduledActivities.ToList(); } - - private static IEnumerable GetActiveActivityExecutionContexts(IEnumerable activityExecutionContexts) - { - // Filter out completed activity execution contexts, except for the root Workflow activity context, which stores workflow-level variables. - // This will currently break scripts accessing activity output directly, but there's a workaround for that via variable capturing. - // We may ultimately restore direct output access, but in a different way. - return activityExecutionContexts.Where(x => !x.IsCompleted || x.ParentActivityExecutionContext == null).ToList(); - } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Core/UIHints/JsonEditor/JsonCodeOptionsProvider.cs b/src/modules/Elsa.Workflows.Core/UIHints/JsonEditor/JsonCodeOptionsProvider.cs index efebacc1c..c847644b3 100644 --- a/src/modules/Elsa.Workflows.Core/UIHints/JsonEditor/JsonCodeOptionsProvider.cs +++ b/src/modules/Elsa.Workflows.Core/UIHints/JsonEditor/JsonCodeOptionsProvider.cs @@ -2,7 +2,7 @@ using System.Reflection; using Elsa.Workflows.UIHints.CodeEditor; // ReSharper disable once CheckNamespace -namespace Elsa.CSharp.Activities; +namespace Elsa.Workflows.UIHints.JsonEditor; internal class JsonCodeOptionsProvider : CodeEditorOptionsProviderBase { diff --git a/src/modules/Elsa.Workflows.Core/UIHints/JsonEditor/JsonEditorUIHintHandler.cs b/src/modules/Elsa.Workflows.Core/UIHints/JsonEditor/JsonEditorUIHintHandler.cs index d8033383a..2d4e6e403 100644 --- a/src/modules/Elsa.Workflows.Core/UIHints/JsonEditor/JsonEditorUIHintHandler.cs +++ b/src/modules/Elsa.Workflows.Core/UIHints/JsonEditor/JsonEditorUIHintHandler.cs @@ -1,5 +1,4 @@ using System.Reflection; -using Elsa.CSharp.Activities; namespace Elsa.Workflows.UIHints.JsonEditor; diff --git a/src/modules/Elsa.Workflows.Management/Contracts/IWorkflowInstanceManager.cs b/src/modules/Elsa.Workflows.Management/Contracts/IWorkflowInstanceManager.cs index e54a08a28..886160157 100644 --- a/src/modules/Elsa.Workflows.Management/Contracts/IWorkflowInstanceManager.cs +++ b/src/modules/Elsa.Workflows.Management/Contracts/IWorkflowInstanceManager.cs @@ -20,7 +20,12 @@ public interface IWorkflowInstanceManager /// Finds the first workflow instance that matches the specified filter. /// Task FindAsync(WorkflowInstanceFilter filter, CancellationToken cancellationToken = default); - + + /// + /// Determines whether a workflow instance with the specified ID exists. + /// + Task ExistsAsync(string instanceId, CancellationToken cancellationToken = default); + /// /// Saves the specified workflow instance. /// @@ -105,5 +110,10 @@ public interface IWorkflowInstanceManager /// /// Instantiates and saves a new workflow instance. /// - Task CreateWorkflowInstanceAsync(Workflow workflow, WorkflowInstanceOptions? options = null, CancellationToken cancellationToken = default); + Task CreateAndCommitWorkflowInstanceAsync(Workflow workflow, WorkflowInstanceOptions? options = null, CancellationToken cancellationToken = default); + + /// + /// Instantiates a new workflow instance. + /// + WorkflowInstance CreateWorkflowInstance(Workflow workflow, WorkflowInstanceOptions? options = null); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs b/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs index 5f15fbdc1..5eb87a812 100644 --- a/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs +++ b/src/modules/Elsa.Workflows.Management/Entities/WorkflowDefinition.cs @@ -12,7 +12,7 @@ public class WorkflowDefinition : VersionedEntity /// /// The logical ID of the workflow. This ID is the same across versions. /// - public string DefinitionId { get; set; } = default!; + public string DefinitionId { get; set; } = null!; /// /// The name of the workflow. @@ -67,13 +67,13 @@ public class WorkflowDefinition : VersionedEntity /// /// The name of the workflow materializer to interpret the or . /// - public string MaterializerName { get; set; } = default!; + public string MaterializerName { get; set; } = null!; /// /// Provider-specific data. /// public string? MaterializerContext { get; set; } - + /// /// A textual representation of the workflow. The data is to be interpreted by the configured materializer. /// diff --git a/src/modules/Elsa.Workflows.Management/Extensions/ModuleExtensions.cs b/src/modules/Elsa.Workflows.Management/Extensions/ModuleExtensions.cs index 6c7306f07..dc6a1dc46 100644 --- a/src/modules/Elsa.Workflows.Management/Extensions/ModuleExtensions.cs +++ b/src/modules/Elsa.Workflows.Management/Extensions/ModuleExtensions.cs @@ -14,7 +14,7 @@ public static class ModuleExtensions /// /// Adds the workflow management feature to the specified module. /// - public static IModule UseWorkflowManagement(this IModule module, Action? configure = default) + public static IModule UseWorkflowManagement(this IModule module, Action? configure = null) { module.Configure(management => { @@ -27,7 +27,7 @@ public static class ModuleExtensions /// /// Adds the default workflow management feature to the specified module. /// - public static WorkflowManagementFeature UseWorkflowDefinitions(this WorkflowManagementFeature feature, Action? configure = default) + public static WorkflowManagementFeature UseWorkflowDefinitions(this WorkflowManagementFeature feature, Action? configure = null) { feature.Module.Configure(configure); return feature; @@ -36,7 +36,7 @@ public static class ModuleExtensions /// /// Adds the workflow instance feature to workflow management module. /// - public static WorkflowManagementFeature UseWorkflowInstances(this WorkflowManagementFeature feature, Action? configure = default) + public static WorkflowManagementFeature UseWorkflowInstances(this WorkflowManagementFeature feature, Action? configure = null) { feature.Module.Configure(configure); return feature; @@ -45,7 +45,7 @@ public static class ModuleExtensions /// /// Adds the Elsa DSL integration feature. /// - public static WorkflowManagementFeature UseDslIntegration(this WorkflowManagementFeature feature, Action? configure = default) + public static WorkflowManagementFeature UseDslIntegration(this WorkflowManagementFeature feature, Action? configure = null) { feature.Module.Configure(configure); return feature; @@ -84,7 +84,7 @@ public static class ModuleExtensions /// /// Adds caching stores feature to the workflow management feature. /// - public static WorkflowManagementFeature UseCache(this WorkflowManagementFeature feature, Action? configure = default) + public static WorkflowManagementFeature UseCache(this WorkflowManagementFeature feature, Action? configure = null) { feature.Module.Configure(configure); return feature; diff --git a/src/modules/Elsa.Workflows.Management/Features/CachingWorkflowDefinitionsFeature.cs b/src/modules/Elsa.Workflows.Management/Features/CachingWorkflowDefinitionsFeature.cs index a36ed798c..d9d666b75 100644 --- a/src/modules/Elsa.Workflows.Management/Features/CachingWorkflowDefinitionsFeature.cs +++ b/src/modules/Elsa.Workflows.Management/Features/CachingWorkflowDefinitionsFeature.cs @@ -1,6 +1,7 @@ using Elsa.Features.Abstractions; using Elsa.Features.Services; using Elsa.Workflows.Management.Handlers; +using Elsa.Workflows.Management.Handlers.Notification; using Elsa.Workflows.Management.Services; using Elsa.Workflows.Management.Stores; using Microsoft.Extensions.DependencyInjection; diff --git a/src/modules/Elsa.Workflows.Management/Features/WorkflowDefinitionsFeature.cs b/src/modules/Elsa.Workflows.Management/Features/WorkflowDefinitionsFeature.cs index a29d9ed1f..916ad3c67 100644 --- a/src/modules/Elsa.Workflows.Management/Features/WorkflowDefinitionsFeature.cs +++ b/src/modules/Elsa.Workflows.Management/Features/WorkflowDefinitionsFeature.cs @@ -1,5 +1,9 @@ using Elsa.Features.Abstractions; using Elsa.Features.Services; +using Elsa.Mediator.Contracts; +using Elsa.Workflows.Management.Entities; +using Elsa.Workflows.Management.Handlers.Request; +using Elsa.Workflows.Management.Requests; using Elsa.Workflows.Management.Stores; using Microsoft.Extensions.DependencyInjection; @@ -19,10 +23,14 @@ public class WorkflowDefinitionsFeature : FeatureBase /// The factory to create new instances of . /// public Func WorkflowDefinitionStore { get; set; } = sp => sp.GetRequiredService(); + public Func FindWorkflowDefinitionHandler { get; set; } = () => typeof(FindWorkflowDefinitionHandler); /// public override void Apply() { - Services.AddScoped(WorkflowDefinitionStore); + Services + .AddScoped(WorkflowDefinitionStore) + .AddScoped(typeof(IRequestHandler), FindWorkflowDefinitionHandler()) + ; } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs b/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs index a81d8fd47..016bdb488 100644 --- a/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs +++ b/src/modules/Elsa.Workflows.Management/Features/WorkflowManagementFeature.cs @@ -18,6 +18,7 @@ using Elsa.Workflows.Management.Compression; using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Entities; using Elsa.Workflows.Management.Handlers; +using Elsa.Workflows.Management.Handlers.Notification; using Elsa.Workflows.Management.Mappers; using Elsa.Workflows.Management.Materializers; using Elsa.Workflows.Management.Models; @@ -240,6 +241,7 @@ public class WorkflowManagementFeature : FeatureBase .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() + .AddNotificationHandler() ; Services.Configure(options => diff --git a/src/modules/Elsa.Workflows.Management/Filters/WorkflowDefinitionFilter.cs b/src/modules/Elsa.Workflows.Management/Filters/WorkflowDefinitionFilter.cs index b69f486ad..9b6d6d16f 100644 --- a/src/modules/Elsa.Workflows.Management/Filters/WorkflowDefinitionFilter.cs +++ b/src/modules/Elsa.Workflows.Management/Filters/WorkflowDefinitionFilter.cs @@ -102,7 +102,7 @@ public class WorkflowDefinitionFilter if (Name != null) queryable = queryable.Where(x => x.Name == Name); if (Names != null) queryable = queryable.Where(x => Names.Contains(x.Name!)); if (UsableAsActivity != null) queryable = queryable.Where(x => x.Options.UsableAsActivity == UsableAsActivity); - if (!string.IsNullOrWhiteSpace(SearchTerm)) queryable = queryable.Where(x => x.Name!.Contains(SearchTerm) || x.Description!.Contains(SearchTerm) || x.Id.Contains(SearchTerm) || x.DefinitionId.Contains(SearchTerm)); + if (!string.IsNullOrWhiteSpace(SearchTerm)) queryable = queryable.Where(x => x.Name!.ToLower().Contains(SearchTerm.ToLower()) || x.Description!.ToLower().Contains(SearchTerm.ToLower()) || x.Id.Contains(SearchTerm) || x.DefinitionId.Contains(SearchTerm)); if (IsSystem != null) queryable = queryable.Where(x => x.IsSystem == IsSystem); if (IsReadonly != null) queryable = queryable.Where(x => x.IsReadonly == IsReadonly); diff --git a/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs b/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs index e578468c5..f279df52a 100644 --- a/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs +++ b/src/modules/Elsa.Workflows.Management/Filters/WorkflowInstanceFilter.cs @@ -25,6 +25,11 @@ public class WorkflowInstanceFilter /// Filter workflow instances that match the specified search term. /// public string? SearchTerm { get; set; } + + /// + /// Filter workflow instances that match the specified name. + /// + public string? Name { get; set; } /// /// Filter workflow instances by definition ID. @@ -125,6 +130,7 @@ public class WorkflowInstanceFilter if (filter.WorkflowSubStatuses != null) query = query.Where(x => filter.WorkflowSubStatuses.Contains(x.SubStatus)); if (filter.HasIncidents != null) query = filter.HasIncidents == true ? query.Where(x => x.IncidentCount > 0) : query.Where(x => x.IncidentCount == 0); if (filter.IsSystem != null) query = query.Where(x => x.IsSystem == filter.IsSystem); + if (filter.Name != null) query = query.Where(x => x.Name!.ToLower().Contains(filter.Name.ToLower())); if (TimestampFilters != null) { @@ -160,7 +166,7 @@ public class WorkflowInstanceFilter { query = from instance in query - where instance.Name!.Contains(searchTerm) + where instance.Name!.ToLower().Contains(searchTerm.ToLower()) || instance.DefinitionVersionId.Contains(searchTerm) || instance.DefinitionId.Contains(searchTerm) || instance.Id.Contains(searchTerm) diff --git a/src/modules/Elsa.Workflows.Management/Handlers/DeleteWorkflowInstances.cs b/src/modules/Elsa.Workflows.Management/Handlers/Notification/DeleteWorkflowInstances.cs similarity index 97% rename from src/modules/Elsa.Workflows.Management/Handlers/DeleteWorkflowInstances.cs rename to src/modules/Elsa.Workflows.Management/Handlers/Notification/DeleteWorkflowInstances.cs index 01cd75881..01429d33a 100644 --- a/src/modules/Elsa.Workflows.Management/Handlers/DeleteWorkflowInstances.cs +++ b/src/modules/Elsa.Workflows.Management/Handlers/Notification/DeleteWorkflowInstances.cs @@ -3,7 +3,7 @@ using Elsa.Workflows.Management.Filters; using Elsa.Workflows.Management.Notifications; using JetBrains.Annotations; -namespace Elsa.Workflows.Management.Handlers; +namespace Elsa.Workflows.Management.Handlers.Notification; /// /// Deletes workflow instances when a workflow definition or version is deleted. diff --git a/src/modules/Elsa.Workflows.Management/Handlers/EvictWorkflowDefinitionServiceCache.cs b/src/modules/Elsa.Workflows.Management/Handlers/Notification/EvictWorkflowDefinitionServiceCache.cs similarity index 97% rename from src/modules/Elsa.Workflows.Management/Handlers/EvictWorkflowDefinitionServiceCache.cs rename to src/modules/Elsa.Workflows.Management/Handlers/Notification/EvictWorkflowDefinitionServiceCache.cs index 5c1e4dec9..3cc7f15da 100644 --- a/src/modules/Elsa.Workflows.Management/Handlers/EvictWorkflowDefinitionServiceCache.cs +++ b/src/modules/Elsa.Workflows.Management/Handlers/Notification/EvictWorkflowDefinitionServiceCache.cs @@ -2,7 +2,7 @@ using Elsa.Mediator.Contracts; using Elsa.Workflows.Management.Notifications; using JetBrains.Annotations; -namespace Elsa.Workflows.Management.Handlers; +namespace Elsa.Workflows.Management.Handlers.Notification; /// /// A workflow definition notifications handler for evicting the cache of the workflow definition service. diff --git a/src/modules/Elsa.Workflows.Management/Handlers/RefreshActivityRegistry.cs b/src/modules/Elsa.Workflows.Management/Handlers/Notification/RefreshActivityRegistry.cs similarity index 98% rename from src/modules/Elsa.Workflows.Management/Handlers/RefreshActivityRegistry.cs rename to src/modules/Elsa.Workflows.Management/Handlers/Notification/RefreshActivityRegistry.cs index f6fe2b7bd..bad83d829 100644 --- a/src/modules/Elsa.Workflows.Management/Handlers/RefreshActivityRegistry.cs +++ b/src/modules/Elsa.Workflows.Management/Handlers/Notification/RefreshActivityRegistry.cs @@ -5,7 +5,7 @@ using Elsa.Workflows.Management.Entities; using Elsa.Workflows.Management.Notifications; using JetBrains.Annotations; -namespace Elsa.Workflows.Management.Handlers; +namespace Elsa.Workflows.Management.Handlers.Notification; /// /// Refreshes the for the provider whenever an is published, retracted or deleted. diff --git a/src/modules/Elsa.Workflows.Management/Handlers/UpdateConsumingWorkflows.cs b/src/modules/Elsa.Workflows.Management/Handlers/Notification/UpdateConsumingWorkflows.cs similarity index 89% rename from src/modules/Elsa.Workflows.Management/Handlers/UpdateConsumingWorkflows.cs rename to src/modules/Elsa.Workflows.Management/Handlers/Notification/UpdateConsumingWorkflows.cs index 39082829c..1e3819d4b 100644 --- a/src/modules/Elsa.Workflows.Management/Handlers/UpdateConsumingWorkflows.cs +++ b/src/modules/Elsa.Workflows.Management/Handlers/Notification/UpdateConsumingWorkflows.cs @@ -1,9 +1,8 @@ using Elsa.Extensions; using Elsa.Mediator.Contracts; -using Elsa.Workflows.Management.Contracts; using Elsa.Workflows.Management.Notifications; -namespace Elsa.Workflows.Management.Handlers; +namespace Elsa.Workflows.Management.Handlers.Notification; /// /// Updates consuming workflows when a workflow definition is published. diff --git a/src/modules/Elsa.Workflows.Management/Handlers/Notification/ValidateWorkflow.cs b/src/modules/Elsa.Workflows.Management/Handlers/Notification/ValidateWorkflow.cs new file mode 100644 index 000000000..4e3ed5258 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Handlers/Notification/ValidateWorkflow.cs @@ -0,0 +1,31 @@ +using Elsa.Mediator.Contracts; +using Elsa.Workflows.Management.Models; +using Elsa.Workflows.Management.Notifications; +using Elsa.Workflows.Models; + +namespace Elsa.Workflows.Management.Handlers.Notification; + +public class ValidateWorkflow : INotificationHandler +{ + public Task HandleAsync(WorkflowDefinitionValidating notification, CancellationToken cancellationToken) + { + var workflow = notification.Workflow; + var inputs = workflow.Inputs; + var outputs = workflow.Outputs; + + ValidateUniqueNames(inputs, "inputs", notification.ValidationErrors); + ValidateUniqueNames(outputs, "outputs", notification.ValidationErrors); + + return Task.CompletedTask; + } + + private void ValidateUniqueNames(IEnumerable variables, string variableType, ICollection validationErrors) + { + var duplicateNames = variables.GroupBy(x => x.Name).Where(x => x.Count() > 1).Select(x => x.Key).ToList(); + if (duplicateNames.Any()) + { + var message = $"The following {variableType} are defined more than once: {string.Join(", ", duplicateNames)}"; + validationErrors.Add(new(message)); + } + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Handlers/Request/FindWorkflowDefinitionHandler.cs b/src/modules/Elsa.Workflows.Management/Handlers/Request/FindWorkflowDefinitionHandler.cs new file mode 100644 index 000000000..0febd0442 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Handlers/Request/FindWorkflowDefinitionHandler.cs @@ -0,0 +1,23 @@ +using Elsa.Common.Entities; +using Elsa.Mediator.Contracts; +using Elsa.Workflows.Management.Entities; +using Elsa.Workflows.Management.Filters; +using Elsa.Workflows.Management.Requests; + +namespace Elsa.Workflows.Management.Handlers.Request; + +public class FindWorkflowDefinitionHandler(IWorkflowDefinitionStore store) : IRequestHandler +{ + public async Task HandleAsync(FindWorkflowDefinitionRequest request, CancellationToken cancellationToken) + { + var filter = new WorkflowDefinitionFilter + { + DefinitionId = request.DefinitionId, + VersionOptions = request.VersionOptions + }; + + var order = new WorkflowDefinitionOrder(x => x.Version, OrderDirection.Descending); + var definition = (await store.FindManyAsync(filter, order, cancellationToken: cancellationToken)).FirstOrDefault(); + return definition; + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs b/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs index 787c5d341..79a7d8ec6 100644 --- a/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs +++ b/src/modules/Elsa.Workflows.Management/Mappers/VariableDefinitionMapper.cs @@ -4,42 +4,50 @@ using Elsa.Expressions.Helpers; using Elsa.Extensions; using Elsa.Workflows.Memory; using Elsa.Workflows.Models; +using Microsoft.Extensions.Logging; namespace Elsa.Workflows.Management.Mappers; /// /// Maps s to s and vice versa. /// -public class VariableDefinitionMapper +public class VariableDefinitionMapper(IWellKnownTypeRegistry wellKnownTypeRegistry, ILogger logger) { - private readonly IWellKnownTypeRegistry _wellKnownTypeRegistry; - - /// - /// Constructor. - /// - public VariableDefinitionMapper(IWellKnownTypeRegistry wellKnownTypeRegistry) - { - _wellKnownTypeRegistry = wellKnownTypeRegistry; - } - /// /// Maps a to a . /// public Variable? Map(VariableDefinition source) { - if (!_wellKnownTypeRegistry.TryGetTypeOrDefault(source.TypeName, out var type)) - return null; + var aliasedType = wellKnownTypeRegistry.TryGetType(source.TypeName, out var aliasedTypeValue) ? aliasedTypeValue : null; + var type = aliasedType ?? Type.GetType(source.TypeName); - var valueType = source.IsArray ? typeof(ICollection<>).MakeGenericType(type) : type; + if (type == null) + { + logger.LogWarning("Failed to resolve the type {TypeName} of variable {VariableName}. Variable will not be mapped.", source.TypeName, source.Name); + return null; + } + + var valueType = aliasedType is { IsArray: true } ? source.IsArray ? aliasedType.MakeArrayType() : aliasedType : source.IsArray ? type.MakeArrayType() : type; var variableGenericType = typeof(Variable<>).MakeGenericType(valueType); var variable = (Variable)Activator.CreateInstance(variableGenericType)!; - if(!string.IsNullOrEmpty(source.Id)) + if (!string.IsNullOrEmpty(source.Id)) variable.Id = source.Id; - + variable.Name = source.Name; - variable.Value = source.Value.ConvertTo(valueType); - variable.StorageDriverType = !string.IsNullOrEmpty(source.StorageDriverTypeName) ? Type.GetType(source.StorageDriverTypeName) : default; + + if (!string.IsNullOrWhiteSpace(source.Value)) + { + source.Value?.TryConvertTo(valueType).OnSuccess(value => + { + variable.Value = value; + }).OnFailure(ex => + { + logger.LogWarning(ex, "Failed to convert the default value {DefaultValue} of variable {VariableName} to its type {VariableType}. Default value will not be set.", source.Value, source.Name, valueType); + }); + } + + variable.StorageDriverType = !string.IsNullOrEmpty(source.StorageDriverTypeName) ? Type.GetType(source.StorageDriverTypeName) : null; return variable; } @@ -52,7 +60,7 @@ public class VariableDefinitionMapper .Select(Map) .Where(x => x != null) .Select(x => x!) - ?? Enumerable.Empty(); + ?? []; /// /// Maps a to a . @@ -61,19 +69,25 @@ public class VariableDefinitionMapper { var variableType = source.GetType(); var valueType = variableType.IsConstructedGenericType ? variableType.GetGenericArguments().FirstOrDefault() ?? typeof(object) : typeof(object); - var isArray = valueType.IsCollectionType(); - var elementValueType = isArray ? valueType.GenericTypeArguments[0] : valueType; + var valueTypeAlias = wellKnownTypeRegistry.TryGetAlias(valueType, out var alias) ? alias : null; var value = source.Value; - - var valueTypeAlias = _wellKnownTypeRegistry.GetAliasOrDefault(elementValueType); - var storageDriverTypeName = source.StorageDriverType?.GetSimpleAssemblyQualifiedName(); var serializedValue = value.Format(); + var storageDriverTypeName = source.StorageDriverType?.GetSimpleAssemblyQualifiedName(); - return new VariableDefinition(source.Id, source.Name, valueTypeAlias, isArray, serializedValue, storageDriverTypeName); + // Handles the case where an alias exists for an array or collection type. E.g. byte[] -> ByteArray. + if (valueTypeAlias != null && (valueType.IsArray || valueType.IsCollectionType())) + return new(source.Id, source.Name, valueTypeAlias, false, serializedValue, storageDriverTypeName); + + var isArray = valueType.IsArray; + var isCollection = valueType.IsCollectionType(); + var elementValueType = isArray ? valueType.GetElementType() : isCollection ? valueType.GenericTypeArguments[0] : valueType; + var elementTypeAlias = wellKnownTypeRegistry.GetAliasOrDefault(elementValueType); + + return new(source.Id, source.Name, elementTypeAlias, isArray, serializedValue, storageDriverTypeName); } /// /// Maps a list of s to a list of s. /// - public IEnumerable Map(IEnumerable? source) => source?.Select(Map) ?? Enumerable.Empty(); + public IEnumerable Map(IEnumerable? source) => source?.Select(Map) ?? []; } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs b/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs index bc941ce70..cd22cc0a4 100644 --- a/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs +++ b/src/modules/Elsa.Workflows.Management/Mappers/WorkflowDefinitionMapper.cs @@ -1,5 +1,6 @@ using Elsa.Workflows.Activities; using Elsa.Workflows.Management.Entities; +using Elsa.Workflows.Management.Materializers; using Elsa.Workflows.Management.Models; using Elsa.Workflows.Models; @@ -34,9 +35,9 @@ public class WorkflowDefinitionMapper var root = _activitySerializer.Deserialize(source.StringData!); return new( - new WorkflowIdentity(source.DefinitionId, source.Version, source.Id, source.TenantId), - new WorkflowPublication(source.IsLatest, source.IsPublished), - new WorkflowMetadata(source.Name, source.Description, source.CreatedAt, source.ToolVersion), + new(source.DefinitionId, source.Version, source.Id, source.TenantId), + new(source.IsLatest, source.IsPublished), + new(source.Name, source.Description, source.CreatedAt, source.ToolVersion), source.Options, root, source.Variables, @@ -66,9 +67,9 @@ public class WorkflowDefinitionMapper #pragma warning restore CS0618 return new( - new WorkflowIdentity(source.DefinitionId, source.Version, source.Id, source.TenantId), - new WorkflowPublication(source.IsLatest, source.IsPublished), - new WorkflowMetadata(source.Name, source.Description, source.CreatedAt, source.ToolVersion), + new(source.DefinitionId, source.Version, source.Id, source.TenantId), + new(source.IsLatest, source.IsPublished), + new(source.Name, source.Description, source.CreatedAt, source.ToolVersion), options, root, variables, @@ -79,6 +80,38 @@ public class WorkflowDefinitionMapper source.IsReadonly, source.IsSystem); } + + public WorkflowDefinition MapToWorkflowDefinition(WorkflowDefinitionModel source) + { + var root = source.Root!; + var variables = _variableDefinitionMapper.Map(source.Variables).ToList(); + var options = source.Options ?? new WorkflowOptions(); + var stringData = _activitySerializer.Serialize(root); + + return new() + { + IsPublished = source.IsPublished, + Description = source.Description, + Id = source.Id, + Inputs = source.Inputs ?? [], + Name = source.Name, + Options = options, + Outcomes = source.Outcomes ?? [], + Outputs = source.Outputs ?? [], + Variables = variables, + Version = source.Version, + CreatedAt = source.CreatedAt, + CustomProperties = source.CustomProperties ?? new Dictionary(), + DefinitionId = source.DefinitionId, + IsLatest = source.IsLatest, + IsReadonly = source.IsReadonly, + IsSystem = source.IsSystem, + TenantId = source.TenantId, + ToolVersion = source.ToolVersion, + StringData = stringData, + MaterializerName = JsonWorkflowMaterializer.MaterializerName + }; + } /// /// Maps many s to many s. @@ -86,8 +119,10 @@ public class WorkflowDefinitionMapper /// The source s. /// An optional cancellation token. /// The mapped s. - public async Task> MapAsync(IEnumerable source, CancellationToken cancellationToken = default) => - await Task.WhenAll(source.Select(async x => await MapAsync(x, cancellationToken))); + public async Task> MapAsync(IEnumerable source, CancellationToken cancellationToken = default) + { + return await Task.WhenAll(source.Select(async x => await MapAsync(x, cancellationToken))); + } /// /// Maps a to a . diff --git a/src/modules/Elsa.Workflows.Management/Materializers/JsonWorkflowMaterializer.cs b/src/modules/Elsa.Workflows.Management/Materializers/JsonWorkflowMaterializer.cs index 0d24fccf5..29477d1e0 100644 --- a/src/modules/Elsa.Workflows.Management/Materializers/JsonWorkflowMaterializer.cs +++ b/src/modules/Elsa.Workflows.Management/Materializers/JsonWorkflowMaterializer.cs @@ -31,7 +31,7 @@ public class JsonWorkflowMaterializer : IWorkflowMaterializer public ValueTask MaterializeAsync(WorkflowDefinition definition, CancellationToken cancellationToken) { var workflow = ToWorkflow(definition); - return new ValueTask(workflow); + return new(workflow); } private Workflow ToWorkflow(WorkflowDefinition definition) => _workflowDefinitionMapper.Map(definition); diff --git a/src/modules/Elsa.Workflows.Management/Models/WorkflowValidationError.cs b/src/modules/Elsa.Workflows.Management/Models/WorkflowValidationError.cs index b33564cbf..108090c20 100644 --- a/src/modules/Elsa.Workflows.Management/Models/WorkflowValidationError.cs +++ b/src/modules/Elsa.Workflows.Management/Models/WorkflowValidationError.cs @@ -5,4 +5,4 @@ namespace Elsa.Workflows.Management.Models; /// /// The error message. /// The Id of the activity that caused the error, if any. -public record WorkflowValidationError(string Message, string? ActivityId = default); \ No newline at end of file +public record WorkflowValidationError(string Message, string? ActivityId = null); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Requests/ValidateWorkflowRequest.cs b/src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionValidating.cs similarity index 50% rename from src/modules/Elsa.Workflows.Management/Requests/ValidateWorkflowRequest.cs rename to src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionValidating.cs index 13eeb3899..967c56686 100644 --- a/src/modules/Elsa.Workflows.Management/Requests/ValidateWorkflowRequest.cs +++ b/src/modules/Elsa.Workflows.Management/Notifications/WorkflowDefinitionValidating.cs @@ -1,11 +1,11 @@ using Elsa.Mediator.Contracts; using Elsa.Workflows.Activities; -using Elsa.Workflows.Management.Responses; +using Elsa.Workflows.Management.Models; -namespace Elsa.Workflows.Management.Requests; +namespace Elsa.Workflows.Management.Notifications; /// /// A request to validate a workflow definition. /// /// The workflow materialized from the definition. -public record ValidateWorkflowRequest(Workflow Workflow) : IRequest; \ No newline at end of file +public record WorkflowDefinitionValidating(Workflow Workflow, ICollection ValidationErrors) : INotification; \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Requests/FindWorkflowDefinitionRequest.cs b/src/modules/Elsa.Workflows.Management/Requests/FindWorkflowDefinitionRequest.cs new file mode 100644 index 000000000..a539d5aa5 --- /dev/null +++ b/src/modules/Elsa.Workflows.Management/Requests/FindWorkflowDefinitionRequest.cs @@ -0,0 +1,12 @@ +using Elsa.Common.Models; +using Elsa.Mediator.Contracts; +using Elsa.Workflows.Management.Entities; + +namespace Elsa.Workflows.Management.Requests; + +/// +/// A request to find a workflow definition. +/// +/// The ID of the workflow definition. +/// The version options. +public record FindWorkflowDefinitionRequest(string DefinitionId, VersionOptions VersionOptions) : IRequest; \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Responses/ValidateWorkflowResponse.cs b/src/modules/Elsa.Workflows.Management/Responses/ValidateWorkflowResponse.cs deleted file mode 100644 index c87d447af..000000000 --- a/src/modules/Elsa.Workflows.Management/Responses/ValidateWorkflowResponse.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Elsa.Workflows.Management.Models; -using Elsa.Workflows.Management.Requests; - -namespace Elsa.Workflows.Management.Responses; - -/// -/// Provides a response to a . -/// -/// The validation errors, if any. -public record ValidateWorkflowResponse(ICollection ValidationErrors); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionImporter.cs b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionImporter.cs index 536a1a834..b63260be4 100644 --- a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionImporter.cs +++ b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionImporter.cs @@ -36,7 +36,7 @@ namespace Elsa.Workflows.Management.Services // Get a workflow draft version. var draft = !string.IsNullOrWhiteSpace(definitionId) ? await _workflowDefinitionPublisher.GetDraftAsync(definitionId, VersionOptions.Latest, cancellationToken) - : default; + : null; var isNew = draft == null; diff --git a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs index 75a20e2d4..ab60f6558 100644 --- a/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs +++ b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs @@ -14,54 +14,33 @@ using Elsa.Workflows.Management.Requests; namespace Elsa.Workflows.Management.Services; /// -public class WorkflowDefinitionPublisher : IWorkflowDefinitionPublisher +public class WorkflowDefinitionPublisher( + IWorkflowDefinitionService workflowDefinitionService, + IWorkflowDefinitionStore workflowDefinitionStore, + IWorkflowValidator workflowValidator, + INotificationSender notificationSender, + IIdentityGenerator identityGenerator, + IActivitySerializer activitySerializer, + ISystemClock systemClock) + : IWorkflowDefinitionPublisher { - private readonly IWorkflowDefinitionService _workflowDefinitionService; - private readonly IWorkflowDefinitionStore _workflowDefinitionStore; - private readonly INotificationSender _notificationSender; - private readonly IIdentityGenerator _identityGenerator; - private readonly IActivitySerializer _activitySerializer; - private readonly IRequestSender _requestSender; - private readonly ISystemClock _systemClock; - - /// - /// Constructor. - /// - public WorkflowDefinitionPublisher( - IWorkflowDefinitionService workflowDefinitionService, - IWorkflowDefinitionStore workflowDefinitionStore, - INotificationSender notificationSender, - IIdentityGenerator identityGenerator, - IActivitySerializer activitySerializer, - IRequestSender requestSender, - ISystemClock systemClock) - { - _workflowDefinitionService = workflowDefinitionService; - _workflowDefinitionStore = workflowDefinitionStore; - _notificationSender = notificationSender; - _identityGenerator = identityGenerator; - _activitySerializer = activitySerializer; - _requestSender = requestSender; - _systemClock = systemClock; - } - /// - public WorkflowDefinition New(IActivity? root = default) + public WorkflowDefinition New(IActivity? root = null) { root ??= new Sequence(); - var id = _identityGenerator.GenerateId(); - var definitionId = _identityGenerator.GenerateId(); + var id = identityGenerator.GenerateId(); + var definitionId = identityGenerator.GenerateId(); const int version = 1; - return new WorkflowDefinition + return new() { Id = id, DefinitionId = definitionId, Version = version, IsLatest = true, IsPublished = false, - CreatedAt = _systemClock.UtcNow, - StringData = _activitySerializer.Serialize(root), + CreatedAt = systemClock.UtcNow, + StringData = activitySerializer.Serialize(root), MaterializerName = JsonWorkflowMaterializer.MaterializerName }; } @@ -69,14 +48,18 @@ public class WorkflowDefinitionPublisher : IWorkflowDefinitionPublisher /// public async Task PublishAsync(string definitionId, CancellationToken cancellationToken = default) { - var filter = new WorkflowDefinitionFilter { DefinitionId = definitionId, VersionOptions = VersionOptions.Latest }; - var definition = await _workflowDefinitionStore.FindAsync(filter, cancellationToken); + var filter = new WorkflowDefinitionFilter + { + DefinitionId = definitionId, + VersionOptions = VersionOptions.Latest + }; + var definition = await workflowDefinitionStore.FindAsync(filter, cancellationToken); if (definition == null) - return new PublishWorkflowDefinitionResult(false, new List + return new(false, new List { new("Workflow definition not found.") - }, null); + }, new([])); return await PublishAsync(definition, cancellationToken); } @@ -84,47 +67,53 @@ public class WorkflowDefinitionPublisher : IWorkflowDefinitionPublisher /// public async Task PublishAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default) { - var workflowGraph = await _workflowDefinitionService.MaterializeWorkflowAsync(definition, cancellationToken); - var responses = await _requestSender.SendAsync(new ValidateWorkflowRequest(workflowGraph.Workflow), cancellationToken); - var validationErrors = responses.SelectMany(r => r.ValidationErrors).ToList(); + var workflowGraph = await workflowDefinitionService.MaterializeWorkflowAsync(definition, cancellationToken); + var validationErrors = (await workflowValidator.ValidateAsync(workflowGraph.Workflow, cancellationToken)).ToList(); if (validationErrors.Any()) - return new PublishWorkflowDefinitionResult(false, validationErrors, null); - - await _notificationSender.SendAsync(new WorkflowDefinitionPublishing(definition), cancellationToken); + return new(false, validationErrors, new([])); + await notificationSender.SendAsync(new WorkflowDefinitionPublishing(definition), cancellationToken); var definitionId = definition.DefinitionId; // Reset current latest and published definitions. - var filter = new WorkflowDefinitionFilter { DefinitionId = definitionId, VersionOptions = VersionOptions.LatestOrPublished }; - var publishedWorkflows = await _workflowDefinitionStore.FindManyAsync(filter, cancellationToken); + var filter = new WorkflowDefinitionFilter + { + DefinitionId = definitionId, + VersionOptions = VersionOptions.LatestOrPublished + }; + var publishedWorkflows = await workflowDefinitionStore.FindManyAsync(filter, cancellationToken); foreach (var publishedAndOrLatestWorkflow in publishedWorkflows) { - var isPublished = publishedAndOrLatestWorkflow.IsPublished; + var isPublished = publishedAndOrLatestWorkflow.IsPublished; publishedAndOrLatestWorkflow.IsPublished = false; publishedAndOrLatestWorkflow.IsLatest = false; - await _workflowDefinitionStore.SaveAsync(publishedAndOrLatestWorkflow, cancellationToken); - + await workflowDefinitionStore.SaveAsync(publishedAndOrLatestWorkflow, cancellationToken); + if (isPublished) - await _notificationSender.SendAsync(new WorkflowDefinitionVersionRetracted(publishedAndOrLatestWorkflow), cancellationToken); + await notificationSender.SendAsync(new WorkflowDefinitionVersionRetracted(publishedAndOrLatestWorkflow), cancellationToken); } // Save the new published definition. definition.IsPublished = true; definition = Initialize(definition); - await _workflowDefinitionStore.SaveAsync(definition, cancellationToken); + await workflowDefinitionStore.SaveAsync(definition, cancellationToken); var affectedWorkflows = new AffectedWorkflows(new List()); - await _notificationSender.SendAsync(new WorkflowDefinitionPublished(definition, affectedWorkflows), cancellationToken); - return new PublishWorkflowDefinitionResult(true, validationErrors, affectedWorkflows); + await notificationSender.SendAsync(new WorkflowDefinitionPublished(definition, affectedWorkflows), cancellationToken); + return new(true, validationErrors, affectedWorkflows); } /// public async Task RetractAsync(string definitionId, CancellationToken cancellationToken = default) { - var filter = new WorkflowDefinitionFilter { DefinitionId = definitionId, VersionOptions = VersionOptions.Published }; - var definition = await _workflowDefinitionStore.FindAsync(filter, cancellationToken); + var filter = new WorkflowDefinitionFilter + { + DefinitionId = definitionId, + VersionOptions = VersionOptions.Published + }; + var definition = await workflowDefinitionStore.FindAsync(filter, cancellationToken); if (definition == null) return null; @@ -140,19 +129,26 @@ public class WorkflowDefinitionPublisher : IWorkflowDefinitionPublisher definition.IsPublished = false; - await _notificationSender.SendAsync(new WorkflowDefinitionRetracting(definition), cancellationToken); - await _workflowDefinitionStore.SaveAsync(definition, cancellationToken); - await _notificationSender.SendAsync(new WorkflowDefinitionRetracted(definition), cancellationToken); + await notificationSender.SendAsync(new WorkflowDefinitionRetracting(definition), cancellationToken); + await workflowDefinitionStore.SaveAsync(definition, cancellationToken); + await notificationSender.SendAsync(new WorkflowDefinitionRetracted(definition), cancellationToken); return definition; } /// public async Task GetDraftAsync(string definitionId, VersionOptions versionOptions, CancellationToken cancellationToken = default) { - var filter = new WorkflowDefinitionFilter { DefinitionId = definitionId, VersionOptions = versionOptions }; + var filter = new WorkflowDefinitionFilter + { + DefinitionId = definitionId, + VersionOptions = versionOptions + }; var order = new WorkflowDefinitionOrder(x => x.Version, OrderDirection.Descending); - var lastVersion = await _workflowDefinitionStore.FindLastVersionAsync(new WorkflowDefinitionFilter { DefinitionId = definitionId }, cancellationToken); - var definition = await _workflowDefinitionStore.FindAsync(filter, order, cancellationToken) ?? lastVersion; + var lastVersion = await workflowDefinitionStore.FindLastVersionAsync(new() + { + DefinitionId = definitionId + }, cancellationToken); + var definition = await workflowDefinitionStore.FindAsync(filter, order, cancellationToken) ?? lastVersion; if (definition == null!) return null; @@ -163,8 +159,8 @@ public class WorkflowDefinitionPublisher : IWorkflowDefinitionPublisher var draft = definition.ShallowClone(); draft.Version = lastVersion?.Version + 1 ?? 1; - draft.CreatedAt = _systemClock.UtcNow; - draft.Id = _identityGenerator.GenerateId(); + draft.CreatedAt = systemClock.UtcNow; + draft.Id = identityGenerator.GenerateId(); draft.IsLatest = true; draft.IsPublished = false; @@ -176,24 +172,27 @@ public class WorkflowDefinitionPublisher : IWorkflowDefinitionPublisher { var draft = definition; var definitionId = definition.DefinitionId; - var filter = new WorkflowDefinitionFilter { DefinitionId = definitionId }; - var lastVersion = await _workflowDefinitionStore.FindLastVersionAsync(filter, cancellationToken); + var filter = new WorkflowDefinitionFilter + { + DefinitionId = definitionId + }; + var lastVersion = await workflowDefinitionStore.FindLastVersionAsync(filter, cancellationToken); draft.Version = draft.Id == lastVersion?.Id ? lastVersion.Version : lastVersion?.Version + 1 ?? 1; draft.IsLatest = true; draft = Initialize(draft); - await _workflowDefinitionStore.SaveAsync(draft, cancellationToken); + await workflowDefinitionStore.SaveAsync(draft, cancellationToken); if (lastVersion is null) { - await _notificationSender.SendAsync(new WorkflowDefinitionCreated(definition), cancellationToken); + await notificationSender.SendAsync(new WorkflowDefinitionCreated(definition), cancellationToken); } if (lastVersion is { IsPublished: true, IsLatest: true }) { lastVersion.IsLatest = false; - await _workflowDefinitionStore.SaveAsync(lastVersion, cancellationToken); + await workflowDefinitionStore.SaveAsync(lastVersion, cancellationToken); } return draft; @@ -202,10 +201,10 @@ public class WorkflowDefinitionPublisher : IWorkflowDefinitionPublisher private WorkflowDefinition Initialize(WorkflowDefinition definition) { if (definition.Id == null!) - definition.Id = _identityGenerator.GenerateId(); + definition.Id = identityGenerator.GenerateId(); if (definition.DefinitionId == null!) - definition.DefinitionId = _identityGenerator.GenerateId(); + definition.DefinitionId = identityGenerator.GenerateId(); if (definition.Version == 0) definition.Version = 1; diff --git a/src/modules/Elsa.Workflows.Management/Services/WorkflowInstanceManager.cs b/src/modules/Elsa.Workflows.Management/Services/WorkflowInstanceManager.cs index 16fbb109b..f33e43a6d 100644 --- a/src/modules/Elsa.Workflows.Management/Services/WorkflowInstanceManager.cs +++ b/src/modules/Elsa.Workflows.Management/Services/WorkflowInstanceManager.cs @@ -33,6 +33,16 @@ public class WorkflowInstanceManager( return await store.FindAsync(filter, cancellationToken); } + public async Task ExistsAsync(string instanceId, CancellationToken cancellationToken = default) + { + var filter = new WorkflowInstanceFilter + { + Id = instanceId + }; + var count = await store.CountAsync(filter, cancellationToken); + return count > 0; + } + /// public async Task SaveAsync(WorkflowInstance workflowInstance, CancellationToken cancellationToken = default) { @@ -130,10 +140,16 @@ public class WorkflowInstanceManager( } /// - public async Task CreateWorkflowInstanceAsync(Workflow workflow, WorkflowInstanceOptions? options = null, CancellationToken cancellationToken = default) + public async Task CreateAndCommitWorkflowInstanceAsync(Workflow workflow, WorkflowInstanceOptions? options = null, CancellationToken cancellationToken = default) { - var workflowInstance = workflowInstanceFactory.CreateWorkflowInstance(workflow, options); + var workflowInstance = CreateWorkflowInstance(workflow, options); await SaveAsync(workflowInstance, cancellationToken); return workflowInstance; } + + /// + public WorkflowInstance CreateWorkflowInstance(Workflow workflow, WorkflowInstanceOptions? options = null) + { + return workflowInstanceFactory.CreateWorkflowInstance(workflow, options); + } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Services/WorkflowValidator.cs b/src/modules/Elsa.Workflows.Management/Services/WorkflowValidator.cs index b8c4b7efa..666a60856 100644 --- a/src/modules/Elsa.Workflows.Management/Services/WorkflowValidator.cs +++ b/src/modules/Elsa.Workflows.Management/Services/WorkflowValidator.cs @@ -1,27 +1,19 @@ using Elsa.Mediator.Contracts; using Elsa.Workflows.Activities; using Elsa.Workflows.Management.Models; -using Elsa.Workflows.Management.Requests; +using Elsa.Workflows.Management.Notifications; namespace Elsa.Workflows.Management.Services; /// -public class WorkflowValidator : IWorkflowValidator +public class WorkflowValidator(INotificationSender notificationSender) : IWorkflowValidator { - private readonly IRequestSender _requestSender; - - /// - /// Initializes a new instance of the class. - /// - public WorkflowValidator(IRequestSender requestSender) - { - _requestSender = requestSender; - } - /// public async Task> ValidateAsync(Workflow workflow, CancellationToken cancellationToken = default) { - var responses = await _requestSender.SendAsync(new ValidateWorkflowRequest(workflow), cancellationToken); - return responses.SelectMany(r => r.ValidationErrors).ToList(); + var validationErrors = new List(); + var notification = new WorkflowDefinitionValidating(workflow, validationErrors); + await notificationSender.SendAsync(notification, cancellationToken); + return validationErrors; } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Management/Stores/CachingWorkflowDefinitionStore.cs b/src/modules/Elsa.Workflows.Management/Stores/CachingWorkflowDefinitionStore.cs index 876e52168..796a55755 100644 --- a/src/modules/Elsa.Workflows.Management/Stores/CachingWorkflowDefinitionStore.cs +++ b/src/modules/Elsa.Workflows.Management/Stores/CachingWorkflowDefinitionStore.cs @@ -144,7 +144,7 @@ public class CachingWorkflowDefinitionStore(IWorkflowDefinitionStore decoratedSt { var invalidationRequestToken = cacheManager.GetToken(CacheInvalidationTokenKey); entry.AddExpirationToken(invalidationRequestToken); - entry.SetAbsoluteExpiration(cacheManager.CachingOptions.Value.CacheDuration); + entry.SetSlidingExpiration(cacheManager.CachingOptions.Value.CacheDuration); return await factory(); }); } diff --git a/src/modules/Elsa.Workflows.Runtime.Distributed/Features/DistributedRuntimeFeature.cs b/src/modules/Elsa.Workflows.Runtime.Distributed/Features/DistributedRuntimeFeature.cs index 49a6526fe..0e05eca4a 100644 --- a/src/modules/Elsa.Workflows.Runtime.Distributed/Features/DistributedRuntimeFeature.cs +++ b/src/modules/Elsa.Workflows.Runtime.Distributed/Features/DistributedRuntimeFeature.cs @@ -2,8 +2,8 @@ using Elsa.Extensions; using Elsa.Features.Abstractions; using Elsa.Features.Attributes; using Elsa.Features.Services; -using Elsa.Workflows.Runtime.Distributed.Handlers; using Elsa.Workflows.Runtime.Features; +using Elsa.Workflows.Runtime.Handlers; using Microsoft.Extensions.DependencyInjection; namespace Elsa.Workflows.Runtime.Distributed.Features; diff --git a/src/modules/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowClient.cs b/src/modules/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowClient.cs index 497086f51..a9468d11d 100644 --- a/src/modules/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowClient.cs +++ b/src/modules/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowClient.cs @@ -11,7 +11,8 @@ public class DistributedWorkflowClient( string workflowInstanceId, IDistributedLockProvider distributedLockProvider, IOptions distributedLockingOptions, - IServiceProvider serviceProvider) : IWorkflowClient + IServiceProvider serviceProvider) + : IWorkflowClient { private readonly LocalWorkflowClient _localWorkflowClient = ActivatorUtilities.CreateInstance(serviceProvider, workflowInstanceId); @@ -30,7 +31,7 @@ public class DistributedWorkflowClient( public async Task CreateAndRunInstanceAsync(CreateAndRunWorkflowInstanceRequest request, CancellationToken cancellationToken = default) { - var result = await WithLockAsync(async () => await _localWorkflowClient.CreateAndRunInstanceAsync(request, cancellationToken)); + var result = await _localWorkflowClient.CreateAndRunInstanceAsync(request, cancellationToken); return result; } @@ -49,6 +50,11 @@ public class DistributedWorkflowClient( await _localWorkflowClient.ImportStateAsync(workflowState, cancellationToken); } + public async Task InstanceExistsAsync(CancellationToken cancellationToken = default) + { + return await _localWorkflowClient.InstanceExistsAsync(cancellationToken); + } + private async Task WithLockAsync(Func> func) { var lockKey = $"workflow-instance:{WorkflowInstanceId}"; diff --git a/src/modules/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowRuntime.Obsolete.cs b/src/modules/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowRuntime.Obsolete.cs new file mode 100644 index 000000000..26b73aaa8 --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowRuntime.Obsolete.cs @@ -0,0 +1,32 @@ +using Elsa.Workflows.Runtime.Entities; +using Elsa.Workflows.Runtime.Filters; +using Elsa.Workflows.Runtime.Matches; +using Elsa.Workflows.Runtime.Options; +using Elsa.Workflows.Runtime.Parameters; +using Elsa.Workflows.Runtime.Params; +using Elsa.Workflows.Runtime.Requests; +using Elsa.Workflows.Runtime.Results; +using Elsa.Workflows.State; + +namespace Elsa.Workflows.Runtime.Distributed; + +public partial class DistributedWorkflowRuntime +{ + private readonly Lazy _obsoleteApi; + private ObsoleteWorkflowRuntime ObsoleteApi => _obsoleteApi.Value; + + public Task CanStartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = null) => ObsoleteApi.CanStartWorkflowAsync(definitionId, options); + public Task StartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = null) => ObsoleteApi.StartWorkflowAsync(definitionId, options); + public Task> StartWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = null) => ObsoleteApi.StartWorkflowsAsync(activityTypeName, bookmarkPayload, options); + public Task TryStartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = null) => ObsoleteApi.TryStartWorkflowAsync(definitionId, options); + public Task ResumeWorkflowAsync(string workflowInstanceId, ResumeWorkflowRuntimeParams? options = null) => ObsoleteApi.ResumeWorkflowAsync(workflowInstanceId, options); + public Task> ResumeWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = null) => ObsoleteApi.ResumeWorkflowsAsync(activityTypeName, bookmarkPayload, options); + public Task TriggerWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = null) => ObsoleteApi.TriggerWorkflowsAsync(activityTypeName, bookmarkPayload, options); + public Task ExecuteWorkflowAsync(WorkflowMatch match, ExecuteWorkflowParams? options = default) => ObsoleteApi.ExecuteWorkflowAsync(match, options); + public Task CancelWorkflowAsync(string workflowInstanceId, CancellationToken cancellationToken = default) => ObsoleteApi.CancelWorkflowAsync(workflowInstanceId, cancellationToken); + public Task> FindWorkflowsAsync(WorkflowsFilter filter, CancellationToken cancellationToken = default) => ObsoleteApi.FindWorkflowsAsync(filter, cancellationToken); + public Task ExportWorkflowStateAsync(string workflowInstanceId, CancellationToken cancellationToken = default) => ObsoleteApi.ExportWorkflowStateAsync(workflowInstanceId, cancellationToken); + public Task ImportWorkflowStateAsync(WorkflowState workflowState, CancellationToken cancellationToken = default) => ObsoleteApi.ImportWorkflowStateAsync(workflowState, cancellationToken); + public Task UpdateBookmarkAsync(StoredBookmark bookmark, CancellationToken cancellationToken = default) => ObsoleteApi.UpdateBookmarkAsync(bookmark, cancellationToken); + public Task CountRunningWorkflowsAsync(CountRunningWorkflowsRequest request, CancellationToken cancellationToken = default) => ObsoleteApi.CountRunningWorkflowsAsync(request, cancellationToken); +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowRuntime.cs b/src/modules/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowRuntime.cs index 8069d0587..6c932fe69 100644 --- a/src/modules/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowRuntime.cs +++ b/src/modules/Elsa.Workflows.Runtime.Distributed/Services/DistributedWorkflowRuntime.cs @@ -5,8 +5,21 @@ namespace Elsa.Workflows.Runtime.Distributed; /// /// Represents a distributed workflow runtime that can create instances connected to a workflow instance. /// -public class DistributedWorkflowRuntime(IServiceProvider serviceProvider, IIdentityGenerator identityGenerator) : IWorkflowRuntime +public partial class DistributedWorkflowRuntime : IWorkflowRuntime { + private readonly IServiceProvider _serviceProvider; + private readonly IIdentityGenerator _identityGenerator; + + /// + /// Represents a distributed workflow runtime that can create instances connected to a workflow instance. + /// + public DistributedWorkflowRuntime(IServiceProvider serviceProvider, IIdentityGenerator identityGenerator) + { + _serviceProvider = serviceProvider; + _identityGenerator = identityGenerator; + _obsoleteApi = new(() => ObsoleteWorkflowRuntime.Create(serviceProvider, CreateClientAsync)); + } + /// public async ValueTask CreateClientAsync(CancellationToken cancellationToken = default) { @@ -16,8 +29,8 @@ public class DistributedWorkflowRuntime(IServiceProvider serviceProvider, IIdent /// public ValueTask CreateClientAsync(string? workflowInstanceId, CancellationToken cancellationToken = default) { - workflowInstanceId ??= identityGenerator.GenerateId(); - var client = (IWorkflowClient)ActivatorUtilities.CreateInstance(serviceProvider, typeof(DistributedWorkflowClient), workflowInstanceId); + workflowInstanceId ??= _identityGenerator.GenerateId(); + var client = (IWorkflowClient)ActivatorUtilities.CreateInstance(_serviceProvider, typeof(DistributedWorkflowClient), workflowInstanceId); return new(client); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime.ProtoActor/Actors/WorkflowInstance.cs b/src/modules/Elsa.Workflows.Runtime.ProtoActor/Actors/WorkflowInstance.cs index b89314b12..748197690 100644 --- a/src/modules/Elsa.Workflows.Runtime.ProtoActor/Actors/WorkflowInstance.cs +++ b/src/modules/Elsa.Workflows.Runtime.ProtoActor/Actors/WorkflowInstance.cs @@ -41,7 +41,7 @@ internal class WorkflowInstance( public override Task OnStarted() { - _linkedTokenSource = new CancellationTokenSource(); + _linkedTokenSource = new(); _linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(Context.CancellationToken, _linkedTokenSource.Token).Token; return Task.CompletedTask; } @@ -61,7 +61,7 @@ internal class WorkflowInstance( if (result.IsFaulted) onError(result.Exception.Message); else - respond(new CreateWorkflowInstanceResponse()); + respond(new()); }); return Task.CompletedTask; @@ -161,14 +161,16 @@ internal class WorkflowInstance( await using var scope = scopeFactory.CreateAsyncScope(); var serviceProvider = scope.ServiceProvider; var workflowCanceler = serviceProvider.GetRequiredService(); - _workflowState = await workflowCanceler.CancelWorkflowAsync(WorkflowGraph, WorkflowState, Context.CancellationToken); + WorkflowState = await workflowCanceler.CancelWorkflowAsync(WorkflowGraph, WorkflowState, Context.CancellationToken); + var workflowInstanceManager = scope.ServiceProvider.GetRequiredService(); + await workflowInstanceManager.SaveAsync(WorkflowState, Context.CancellationToken); } public override async Task ExportState() { await EnsureStateAsync(); var json = mappers.WorkflowStateJsonMapper.Map(WorkflowState); - return new ExportWorkflowStateResponse + return new() { SerializedWorkflowState = json }; @@ -179,6 +181,18 @@ internal class WorkflowInstance( var workflowState = mappers.WorkflowStateJsonMapper.Map(request.SerializedWorkflowState); await EnsureStateAsync(); WorkflowState = workflowState; + await using var scope = scopeFactory.CreateAsyncScope(); + var workflowInstanceManager = scope.ServiceProvider.GetRequiredService(); + await workflowInstanceManager.SaveAsync(WorkflowState, Context.CancellationToken); + } + + public override Task InstanceExists() + { + var exists = _workflowInstanceId != null; + return Task.FromResult(new InstanceExistsResponse + { + Exists = exists + }); } private async Task RunAsync(RunWorkflowOptions runWorkflowOptions) @@ -186,7 +200,7 @@ internal class WorkflowInstance( if (_isRunning) { _queuedRunWorkflowOptions.Enqueue(runWorkflowOptions); - return new RunWorkflowResult(null!, null!, null); + return new(null!, null!, null); } _isRunning = true; @@ -211,6 +225,8 @@ internal class WorkflowInstance( var workflowRunner = scope.ServiceProvider.GetRequiredService(); var workflowResult = await workflowRunner.RunAsync(WorkflowGraph, WorkflowState, runWorkflowOptions, _linkedCancellationToken); WorkflowState = workflowResult.WorkflowState; + var workflowInstanceManager = scope.ServiceProvider.GetRequiredService(); + await workflowInstanceManager.SaveAsync(WorkflowState, Context.CancellationToken); return workflowResult; } @@ -255,7 +271,7 @@ internal class WorkflowInstance( await using var scope = scopeFactory.CreateAsyncScope(); var workflowInstanceManager = scope.ServiceProvider.GetRequiredService(); - var workflowInstance = await workflowInstanceManager.CreateWorkflowInstanceAsync(workflowGraph.Workflow, workflowInstanceOptions, cancellationToken); + var workflowInstance = await workflowInstanceManager.CreateAndCommitWorkflowInstanceAsync(workflowGraph.Workflow, workflowInstanceOptions, cancellationToken); var workflowState = workflowInstance.WorkflowState; _workflowInstanceId = workflowState.Id; WorkflowGraph = workflowGraph; diff --git a/src/modules/Elsa.Workflows.Runtime.ProtoActor/Proto/WorkflowInstance.Messages.proto b/src/modules/Elsa.Workflows.Runtime.ProtoActor/Proto/WorkflowInstance.Messages.proto index 5eeaa3699..719572c28 100644 --- a/src/modules/Elsa.Workflows.Runtime.ProtoActor/Proto/WorkflowInstance.Messages.proto +++ b/src/modules/Elsa.Workflows.Runtime.ProtoActor/Proto/WorkflowInstance.Messages.proto @@ -85,3 +85,7 @@ message ExportWorkflowStateResponse { message ImportWorkflowStateRequest { Json SerializedWorkflowState = 1; } + +message InstanceExistsResponse { + bool Exists = 1; +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime.ProtoActor/Proto/WorkflowInstance.proto b/src/modules/Elsa.Workflows.Runtime.ProtoActor/Proto/WorkflowInstance.proto index a23ca9ab7..ded6d1d68 100644 --- a/src/modules/Elsa.Workflows.Runtime.ProtoActor/Proto/WorkflowInstance.proto +++ b/src/modules/Elsa.Workflows.Runtime.ProtoActor/Proto/WorkflowInstance.proto @@ -15,4 +15,5 @@ service WorkflowInstance { rpc Cancel (Empty) returns (Empty); rpc ExportState(Empty) returns (ExportWorkflowStateResponse); rpc ImportState(ImportWorkflowStateRequest) returns (Empty); + rpc InstanceExists(Empty) returns (InstanceExistsResponse); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime.ProtoActor/Services/ProtoActorWorkflowClient.cs b/src/modules/Elsa.Workflows.Runtime.ProtoActor/Services/ProtoActorWorkflowClient.cs index 7576d136f..a37cb11b3 100644 --- a/src/modules/Elsa.Workflows.Runtime.ProtoActor/Services/ProtoActorWorkflowClient.cs +++ b/src/modules/Elsa.Workflows.Runtime.ProtoActor/Services/ProtoActorWorkflowClient.cs @@ -85,6 +85,11 @@ public class ProtoActorWorkflowClient : IWorkflowClient await _actorClient.ImportState(request, CreateHeaders(), cancellationToken); } + public Task InstanceExistsAsync(CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + private IDictionary CreateHeaders() { var headers = new Dictionary(); diff --git a/src/modules/Elsa.Workflows.Runtime.ProtoActor/Services/ProtoActorWorkflowRuntime.Obsolete.cs b/src/modules/Elsa.Workflows.Runtime.ProtoActor/Services/ProtoActorWorkflowRuntime.Obsolete.cs new file mode 100644 index 000000000..bf5593756 --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime.ProtoActor/Services/ProtoActorWorkflowRuntime.Obsolete.cs @@ -0,0 +1,32 @@ +using Elsa.Workflows.Runtime.Entities; +using Elsa.Workflows.Runtime.Filters; +using Elsa.Workflows.Runtime.Matches; +using Elsa.Workflows.Runtime.Options; +using Elsa.Workflows.Runtime.Parameters; +using Elsa.Workflows.Runtime.Params; +using Elsa.Workflows.Runtime.Requests; +using Elsa.Workflows.Runtime.Results; +using Elsa.Workflows.State; + +namespace Elsa.Workflows.Runtime.ProtoActor.Services; + +public partial class ProtoActorWorkflowRuntime +{ + private readonly Lazy _obsoleteApi; + private ObsoleteWorkflowRuntime ObsoleteApi => _obsoleteApi.Value; + + public Task CanStartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = null) => ObsoleteApi.CanStartWorkflowAsync(definitionId, options); + public Task StartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = null) => ObsoleteApi.StartWorkflowAsync(definitionId, options); + public Task> StartWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = null) => ObsoleteApi.StartWorkflowsAsync(activityTypeName, bookmarkPayload, options); + public Task TryStartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = null) => ObsoleteApi.TryStartWorkflowAsync(definitionId, options); + public Task ResumeWorkflowAsync(string workflowInstanceId, ResumeWorkflowRuntimeParams? options = null) => ObsoleteApi.ResumeWorkflowAsync(workflowInstanceId, options); + public Task> ResumeWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = null) => ObsoleteApi.ResumeWorkflowsAsync(activityTypeName, bookmarkPayload, options); + public Task TriggerWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = null) => ObsoleteApi.TriggerWorkflowsAsync(activityTypeName, bookmarkPayload, options); + public Task ExecuteWorkflowAsync(WorkflowMatch match, ExecuteWorkflowParams? options = default) => ObsoleteApi.ExecuteWorkflowAsync(match, options); + public Task CancelWorkflowAsync(string workflowInstanceId, CancellationToken cancellationToken = default) => ObsoleteApi.CancelWorkflowAsync(workflowInstanceId, cancellationToken); + public Task> FindWorkflowsAsync(WorkflowsFilter filter, CancellationToken cancellationToken = default) => ObsoleteApi.FindWorkflowsAsync(filter, cancellationToken); + public Task ExportWorkflowStateAsync(string workflowInstanceId, CancellationToken cancellationToken = default) => ObsoleteApi.ExportWorkflowStateAsync(workflowInstanceId, cancellationToken); + public Task ImportWorkflowStateAsync(WorkflowState workflowState, CancellationToken cancellationToken = default) => ObsoleteApi.ImportWorkflowStateAsync(workflowState, cancellationToken); + public Task UpdateBookmarkAsync(StoredBookmark bookmark, CancellationToken cancellationToken = default) => ObsoleteApi.UpdateBookmarkAsync(bookmark, cancellationToken); + public Task CountRunningWorkflowsAsync(CountRunningWorkflowsRequest request, CancellationToken cancellationToken = default) => ObsoleteApi.CountRunningWorkflowsAsync(request, cancellationToken); +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime.ProtoActor/Services/ProtoActorWorkflowRuntime.cs b/src/modules/Elsa.Workflows.Runtime.ProtoActor/Services/ProtoActorWorkflowRuntime.cs index 8ba6c1bd8..e97ca0037 100644 --- a/src/modules/Elsa.Workflows.Runtime.ProtoActor/Services/ProtoActorWorkflowRuntime.cs +++ b/src/modules/Elsa.Workflows.Runtime.ProtoActor/Services/ProtoActorWorkflowRuntime.cs @@ -5,8 +5,22 @@ namespace Elsa.Workflows.Runtime.ProtoActor.Services; /// /// Represents a Proto.Actor implementation of the workflows runtime. /// -public class ProtoActorWorkflowRuntime(IServiceProvider serviceProvider, IIdentityGenerator identityGenerator) : IWorkflowRuntime +public partial class ProtoActorWorkflowRuntime : IWorkflowRuntime { + private readonly IServiceProvider _serviceProvider; + private readonly IIdentityGenerator _identityGenerator; + + /// + /// Represents a Proto.Actor implementation of the workflows runtime. + /// + public ProtoActorWorkflowRuntime(IServiceProvider serviceProvider, + IIdentityGenerator identityGenerator) + { + _serviceProvider = serviceProvider; + _identityGenerator = identityGenerator; + _obsoleteApi = new(() => ObsoleteWorkflowRuntime.Create(serviceProvider, CreateClientAsync)); + } + /// public async ValueTask CreateClientAsync(CancellationToken cancellationToken = default) { @@ -16,8 +30,8 @@ public class ProtoActorWorkflowRuntime(IServiceProvider serviceProvider, IIdenti /// public ValueTask CreateClientAsync(string? workflowInstanceId, CancellationToken cancellationToken = default) { - workflowInstanceId ??= identityGenerator.GenerateId(); - var client = (IWorkflowClient)ActivatorUtilities.CreateInstance(serviceProvider, typeof(ProtoActorWorkflowClient), workflowInstanceId); + workflowInstanceId ??= _identityGenerator.GenerateId(); + var client = (IWorkflowClient)ActivatorUtilities.CreateInstance(_serviceProvider, typeof(ProtoActorWorkflowClient), workflowInstanceId); return new(client); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs b/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs index df335a24d..8074be822 100644 --- a/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs +++ b/src/modules/Elsa.Workflows.Runtime/Activities/BulkDispatchWorkflows.cs @@ -104,19 +104,18 @@ public class BulkDispatchWorkflows : Activity protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { var waitForCompletion = WaitForCompletion.GetOrDefault(context); - var items = context.GetItemSource(Items); - var dispatchedInstancesCount = 0; + var items = await context.GetItemSource(Items).ToListAsync(context.CancellationToken); + var count = items.Count; - await foreach (var item in items) - { - await DispatchChildWorkflowAsync(context, item); - dispatchedInstancesCount++; - } + // Dispatch the child workflows. + foreach (var item in items) + await DispatchChildWorkflowAsync(context, item, waitForCompletion); - context.SetProperty(DispatchedInstancesCountKey, dispatchedInstancesCount); + // Store the number of dispatched instances for tracking. + context.SetProperty(DispatchedInstancesCountKey, count); // If we need to wait for the child workflows to complete (if any), create a bookmark. - if (waitForCompletion && dispatchedInstancesCount > 0) + if (waitForCompletion && count > 0) { var workflowInstanceId = context.WorkflowExecutionContext.Id; var bookmarkOptions = new CreateBookmarkArgs @@ -125,11 +124,12 @@ public class BulkDispatchWorkflows : Activity Stimulus = new BulkDispatchWorkflowsStimulus(workflowInstanceId) { ParentInstanceId = context.WorkflowExecutionContext.Id, - ScheduledInstanceIdsCount = dispatchedInstancesCount + ScheduledInstanceIdsCount = count }, IncludeActivityInstanceId = false, AutoBurn = false, }; + context.CreateBookmark(bookmarkOptions); } else @@ -139,9 +139,15 @@ public class BulkDispatchWorkflows : Activity } } - private async ValueTask DispatchChildWorkflowAsync(ActivityExecutionContext context, object item) + private async ValueTask DispatchChildWorkflowAsync(ActivityExecutionContext context, object item, bool waitForCompletion) { var workflowDefinitionId = WorkflowDefinitionId.Get(context); + var workflowDefinitionService = context.GetRequiredService(); + var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(workflowDefinitionId, VersionOptions.Published); + + if (workflowGraph == null) + throw new($"No published version of workflow definition with ID {workflowDefinitionId} found."); + var parentInstanceId = context.WorkflowExecutionContext.Id; var input = Input.GetOrDefault(context) ?? new Dictionary(); var channelName = ChannelName.GetOrDefault(context); @@ -151,6 +157,9 @@ public class BulkDispatchWorkflows : Activity ["ParentInstanceId"] = parentInstanceId }; + if (waitForCompletion) + properties["WaitForCompletion"] = true; + var itemDictionary = new Dictionary { [defaultInputItemKey] = item @@ -168,12 +177,6 @@ public class BulkDispatchWorkflows : Activity var workflowDispatcher = context.GetRequiredService(); var identityGenerator = context.GetRequiredService(); var evaluator = context.GetRequiredService(); - var workflowDefinitionService = context.GetRequiredService(); - var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(workflowDefinitionId, VersionOptions.Published); - - if (workflowGraph == null) - throw new Exception($"No published version of workflow definition with ID {workflowDefinitionId} found."); - var correlationId = CorrelationIdFunction != null ? await evaluator.EvaluateAsync(CorrelationIdFunction!, context.ExpressionExecutionContext, evaluatorOptions) : null; var instanceId = identityGenerator.GenerateId(); var request = new DispatchWorkflowDefinitionRequest(workflowGraph.Workflow.Identity.Id) diff --git a/src/modules/Elsa.Workflows.Runtime/Activities/DispatchWorkflow.cs b/src/modules/Elsa.Workflows.Runtime/Activities/DispatchWorkflow.cs index fa424c91d..566c79937 100644 --- a/src/modules/Elsa.Workflows.Runtime/Activities/DispatchWorkflow.cs +++ b/src/modules/Elsa.Workflows.Runtime/Activities/DispatchWorkflow.cs @@ -72,7 +72,7 @@ public class DispatchWorkflow : Activity var waitForCompletion = WaitForCompletion.GetOrDefault(context); // Dispatch the child workflow. - var instanceId = await DispatchChildWorkflowAsync(context); + var instanceId = await DispatchChildWorkflowAsync(context, waitForCompletion); // If we need to wait for the child workflow to complete, create a bookmark. if (waitForCompletion) @@ -92,28 +92,38 @@ public class DispatchWorkflow : Activity } } - private async ValueTask DispatchChildWorkflowAsync(ActivityExecutionContext context) + private async ValueTask DispatchChildWorkflowAsync(ActivityExecutionContext context, bool waitForCompletion) { var workflowDefinitionId = WorkflowDefinitionId.Get(context); + var workflowDefinitionService = context.GetRequiredService(); + var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(workflowDefinitionId, VersionOptions.Published, context.CancellationToken); + + if (workflowGraph == null) + throw new($"No published version of workflow definition with ID {workflowDefinitionId} found."); + var input = Input.GetOrDefault(context) ?? new Dictionary(); var channelName = ChannelName.GetOrDefault(context); + var parentInstanceId = context.WorkflowExecutionContext.Id; + var properties = new Dictionary + { + ["ParentInstanceId"] = parentInstanceId + }; - input["ParentInstanceId"] = context.WorkflowExecutionContext.Id; + // If we need to wait for the child workflow to complete, set the property. This will be used by the ResumeDispatchWorkflowActivity handler. + if (waitForCompletion) + properties["WaitForCompletion"] = true; + + input["ParentInstanceId"] = parentInstanceId; var correlationId = CorrelationId.GetOrDefault(context); var workflowDispatcher = context.GetRequiredService(); var identityGenerator = context.GetRequiredService(); - var workflowDefinitionService = context.GetRequiredService(); - var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(workflowDefinitionId, VersionOptions.Published, context.CancellationToken); - - if (workflowGraph == null) - throw new Exception($"No published version of workflow definition with ID {workflowDefinitionId} found."); - var instanceId = identityGenerator.GenerateId(); var request = new DispatchWorkflowDefinitionRequest(workflowGraph.Workflow.Identity.Id) { - ParentWorkflowInstanceId = context.WorkflowExecutionContext.Id, + ParentWorkflowInstanceId = parentInstanceId, Input = input, + Properties = properties, CorrelationId = correlationId, InstanceId = instanceId, }; diff --git a/src/modules/Elsa.Workflows.Runtime/Activities/ExecuteWorkflow.cs b/src/modules/Elsa.Workflows.Runtime/Activities/ExecuteWorkflow.cs index 7aaf05a5b..4fda5e880 100644 --- a/src/modules/Elsa.Workflows.Runtime/Activities/ExecuteWorkflow.cs +++ b/src/modules/Elsa.Workflows.Runtime/Activities/ExecuteWorkflow.cs @@ -19,7 +19,7 @@ namespace Elsa.Workflows.Runtime.Activities; public class ExecuteWorkflow : Activity { /// - public ExecuteWorkflow([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) + public ExecuteWorkflow([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line) { } @@ -31,7 +31,7 @@ public class ExecuteWorkflow : Activity Description = "The definition ID of the workflow to execute.", UIHint = InputUIHints.WorkflowDefinitionPicker )] - public Input WorkflowDefinitionId { get; set; } = default!; + public Input WorkflowDefinitionId { get; set; } = null!; /// /// The correlation ID to associate the workflow with. @@ -40,33 +40,33 @@ public class ExecuteWorkflow : Activity DisplayName = "Correlation ID", Description = "The correlation ID to associate the workflow with." )] - public Input CorrelationId { get; set; } = default!; + public Input CorrelationId { get; set; } = null!; /// /// The input to send to the workflow. /// [Input(Description = "The input to send to the workflow.")] - public Input?> Input { get; set; } = default!; - + public Input?> Input { get; set; } = null!; + /// /// True to wait for the child workflow to complete before completing this activity. If not set, the child workflow will be executed until it either completes or goes idle before this activity completes. /// [Input(Description = "Wait for the child workflow to complete before completing this activity.")] - public Input WaitForCompletion { get; set; } = default!; + public Input WaitForCompletion { get; set; } = null!; /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { - var result = await ExecuteWorkflowAsync(context); var waitForCompletion = WaitForCompletion.Get(context); - - if(!waitForCompletion || result.Status == WorkflowStatus.Finished) + var result = await ExecuteWorkflowAsync(context, waitForCompletion); + + if (!waitForCompletion || result.Status == WorkflowStatus.Finished) { context.SetResult(result); await context.CompleteActivityAsync(); return; } - + // Since the child workflow is still running, we need to wait for it to complete using a bookmark. var bookmarkOptions = new CreateBookmarkArgs { @@ -77,23 +77,36 @@ public class ExecuteWorkflow : Activity context.CreateBookmark(bookmarkOptions); } - private async ValueTask ExecuteWorkflowAsync(ActivityExecutionContext context) + private async ValueTask ExecuteWorkflowAsync(ActivityExecutionContext context, bool waitForCompletion) { var workflowDefinitionId = WorkflowDefinitionId.Get(context); - var input = Input.GetOrDefault(context) ?? new Dictionary(); - var correlationId = CorrelationId.GetOrDefault(context); - var workflowInvoker = context.GetRequiredService(); - var identityGenerator = context.GetRequiredService(); var workflowDefinitionService = context.GetRequiredService(); var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(workflowDefinitionId, VersionOptions.Published, context.CancellationToken); if (workflowGraph == null) - throw new Exception($"No published version of workflow definition with ID {workflowDefinitionId} found."); + throw new($"No published version of workflow definition with ID {workflowDefinitionId} found."); + + var parentInstanceId = context.WorkflowExecutionContext.Id; + var input = Input.GetOrDefault(context) ?? new Dictionary(); + var correlationId = CorrelationId.GetOrDefault(context); + var workflowInvoker = context.GetRequiredService(); + var identityGenerator = context.GetRequiredService(); + var properties = new Dictionary + { + ["ParentInstanceId"] = parentInstanceId + }; + + // If we need to wait for the child workflow to complete, set the property. This will be used by the ResumeExecuteWorkflowActivity to resume the parent workflow. + if (waitForCompletion) + properties["WaitForCompletion"] = true; + + input["ParentInstanceId"] = parentInstanceId; var options = new RunWorkflowOptions { - ParentWorkflowInstanceId = context.WorkflowExecutionContext.Id, + ParentWorkflowInstanceId = parentInstanceId, Input = input, + Properties = properties, CorrelationId = correlationId, WorkflowInstanceId = identityGenerator.GenerateId() }; @@ -109,7 +122,7 @@ public class ExecuteWorkflow : Activity return info; } - + private async ValueTask OnChildWorkflowCompletedAsync(ActivityExecutionContext context) { var input = context.WorkflowInput; diff --git a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowClient.cs b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowClient.cs index aecfafdd3..8753a76bd 100644 --- a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowClient.cs +++ b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowClient.cs @@ -42,4 +42,6 @@ public interface IWorkflowClient /// Imports the specified . /// Task ImportStateAsync(WorkflowState workflowState, CancellationToken cancellationToken = default); + + Task InstanceExistsAsync(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowRuntime.cs b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowRuntime.cs index cbef32bcc..5ef752118 100644 --- a/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowRuntime.cs +++ b/src/modules/Elsa.Workflows.Runtime/Contracts/IWorkflowRuntime.cs @@ -1,3 +1,13 @@ +using Elsa.Workflows.Runtime.Entities; +using Elsa.Workflows.Runtime.Filters; +using Elsa.Workflows.Runtime.Matches; +using Elsa.Workflows.Runtime.Options; +using Elsa.Workflows.Runtime.Parameters; +using Elsa.Workflows.Runtime.Params; +using Elsa.Workflows.Runtime.Requests; +using Elsa.Workflows.Runtime.Results; +using Elsa.Workflows.State; + namespace Elsa.Workflows.Runtime; /// @@ -21,4 +31,101 @@ public interface IWorkflowRuntime /// A new instance. /// The workflow instance itself doesn't have to exist yet. ValueTask CreateClientAsync(string? workflowInstanceId, CancellationToken cancellationToken = default); + + + /// + /// Returns a value whether the specified workflow definition can create a new instance. + /// + [Obsolete("Use the client API instead, retrieved from CreateClientAsync")] + Task CanStartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = default); + + /// + /// Creates a new workflow instance of the specified definition ID and executes it. + /// + /// The workflow definition ID to run. + /// Options for starting the workflow. + [Obsolete("Use the client API instead, retrieved from CreateClientAsync")] + Task StartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = default); + + /// + /// Starts all workflows with triggers matching the specified activity type and bookmark payload. + /// + [Obsolete("Use the client API instead, retrieved from CreateClientAsync")] + Task> StartWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = default); + + /// + /// Tries to start a workflow and returns the result if successful. + /// + [Obsolete("Use the client API instead, retrieved from CreateClientAsync")] + Task TryStartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = default); + + /// + /// Resumes an existing workflow instance. + /// + /// The ID of the workflow instance to resume. + /// Options for resuming the workflow. + [Obsolete("Use the client API instead, retrieved from CreateClientAsync")] + Task ResumeWorkflowAsync(string workflowInstanceId, ResumeWorkflowRuntimeParams? options = default); + + /// + /// Resumes all workflows that are bookmarked on the specified activity type. + /// + [Obsolete("Use the client API instead, retrieved from CreateClientAsync")] + Task> ResumeWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = default); + + /// + /// Starts all workflows and resumes existing workflow instances based on the specified activity type and bookmark payload. + /// + [Obsolete("Use the client API instead, retrieved from CreateClientAsync")] + Task TriggerWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = default); + + /// + /// Executes a pending workflow. + /// + /// A workflow match to execute. + /// Options for executing the workflow. + [Obsolete("Use the client API instead, retrieved from CreateClientAsync")] + Task ExecuteWorkflowAsync(WorkflowMatch match, ExecuteWorkflowParams? options = default); + + /// + /// Cancels the execution of a workflow. + /// + /// The ID of the workflow instance to cancel. + /// + [Obsolete("Use the client API instead, retrieved from CreateClientAsync")] + Task CancelWorkflowAsync(string workflowInstanceId, CancellationToken cancellationToken = default); + + /// + /// Finds all the workflows that can be started or resumed based on a query model. + /// + /// + /// + [Obsolete("Use the client API instead, retrieved from CreateClientAsync")] + Task> FindWorkflowsAsync(WorkflowsFilter filter, CancellationToken cancellationToken = default); + + /// + /// Exports the of the specified workflow instance. + /// + [Obsolete("Use the client API instead, retrieved from CreateClientAsync")] + Task ExportWorkflowStateAsync(string workflowInstanceId, CancellationToken cancellationToken = default); + + /// + /// Imports the specified . + /// + [Obsolete("Use the client API instead, retrieved from CreateClientAsync")] + Task ImportWorkflowStateAsync(WorkflowState workflowState, CancellationToken cancellationToken = default); + + /// + /// Updates the specified bookmark. + /// + /// The bookmark to update. + /// The cancellation token. + [Obsolete("Use the client API instead, retrieved from CreateClientAsync")] + Task UpdateBookmarkAsync(StoredBookmark bookmark, CancellationToken cancellationToken = default); + + /// + /// Counts the number of workflow instances based on the provided query args. + /// + [Obsolete("Use the client API instead, retrieved from CreateClientAsync")] + Task CountRunningWorkflowsAsync(CountRunningWorkflowsRequest request, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Elsa.Workflows.Runtime.csproj.DotSettings b/src/modules/Elsa.Workflows.Runtime/Elsa.Workflows.Runtime.csproj.DotSettings index 9b4d77e2f..41b655e3e 100644 --- a/src/modules/Elsa.Workflows.Runtime/Elsa.Workflows.Runtime.csproj.DotSettings +++ b/src/modules/Elsa.Workflows.Runtime/Elsa.Workflows.Runtime.csproj.DotSettings @@ -1,4 +1,5 @@  + True True True True diff --git a/src/modules/Elsa.Workflows.Runtime/Entities/BookmarkQueueItem.cs b/src/modules/Elsa.Workflows.Runtime/Entities/BookmarkQueueItem.cs index a39d1feae..f7a867f73 100644 --- a/src/modules/Elsa.Workflows.Runtime/Entities/BookmarkQueueItem.cs +++ b/src/modules/Elsa.Workflows.Runtime/Entities/BookmarkQueueItem.cs @@ -54,7 +54,7 @@ public class BookmarkQueueItem : Entity /// public BookmarkFilter CreateBookmarkFilter() { - return new BookmarkFilter + return new() { WorkflowInstanceId = WorkflowInstanceId, CorrelationId = CorrelationId, diff --git a/src/modules/Elsa.Workflows.Runtime/Entities/StoredBookmark.cs b/src/modules/Elsa.Workflows.Runtime/Entities/StoredBookmark.cs index 8c44881ff..b34658e51 100644 --- a/src/modules/Elsa.Workflows.Runtime/Entities/StoredBookmark.cs +++ b/src/modules/Elsa.Workflows.Runtime/Entities/StoredBookmark.cs @@ -7,12 +7,6 @@ namespace Elsa.Workflows.Runtime.Entities; /// public class StoredBookmark : Entity { - [Obsolete("Use Id instead.")] public string BookmarkId - { - get => Id; - set => Id = value; - } - /// /// The name of the activity type associated with the bookmark. /// diff --git a/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowExecutionPipelineBuilderExtensions.cs b/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowExecutionPipelineBuilderExtensions.cs index cd48e66f6..9affb2058 100644 --- a/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowExecutionPipelineBuilderExtensions.cs +++ b/src/modules/Elsa.Workflows.Runtime/Extensions/WorkflowExecutionPipelineBuilderExtensions.cs @@ -18,9 +18,6 @@ public static class WorkflowExecutionPipelineBuilderExtensions pipelineBuilder .Reset() .UseEngineExceptionHandling() - .UseBookmarkPersistence() - .UseActivityExecutionLogPersistence() - .UseWorkflowExecutionLogPersistence() .UsePersistentVariables() .UseExceptionHandling() .UseDefaultActivityScheduler(); @@ -33,15 +30,18 @@ public static class WorkflowExecutionPipelineBuilderExtensions /// /// Installs middleware that persists bookmarks after workflow execution. /// + [Obsolete("This middleware is no longer used and will be removed in a future version. Bookmarks are now persisted through the commit state handler.")] public static IWorkflowExecutionPipelineBuilder UseBookmarkPersistence(this IWorkflowExecutionPipelineBuilder pipelineBuilder) => pipelineBuilder.UseMiddleware(); /// /// Installs middleware that persists the workflow execution journal. /// + [Obsolete("This middleware is no longer used and will be removed in a future version. Execution logs are now persisted through the commit state handler.")] public static IWorkflowExecutionPipelineBuilder UseWorkflowExecutionLogPersistence(this IWorkflowExecutionPipelineBuilder pipelineBuilder) => pipelineBuilder.UseMiddleware(); /// /// Installs middleware that persists activity execution records. /// + [Obsolete("This middleware is no longer used and will be removed in a future version. Activity state is now persisted through the commit state handler.")] public static IWorkflowExecutionPipelineBuilder UseActivityExecutionLogPersistence(this IWorkflowExecutionPipelineBuilder pipelineBuilder) => pipelineBuilder.UseMiddleware(); } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Features/DefaultWorkflowRuntimeFeature.cs b/src/modules/Elsa.Workflows.Runtime/Features/DefaultWorkflowRuntimeFeature.cs index 41de318ac..6adf87b86 100644 --- a/src/modules/Elsa.Workflows.Runtime/Features/DefaultWorkflowRuntimeFeature.cs +++ b/src/modules/Elsa.Workflows.Runtime/Features/DefaultWorkflowRuntimeFeature.cs @@ -8,15 +8,4 @@ namespace Elsa.Workflows.Runtime.Features; /// Installs the default runtime services. /// [DependsOn(typeof(WorkflowRuntimeFeature))] -public class DefaultWorkflowRuntimeFeature : FeatureBase -{ - /// - public DefaultWorkflowRuntimeFeature(IModule module) : base(module) - { - } - - /// - public override void Apply() - { - } -} \ No newline at end of file +public class DefaultWorkflowRuntimeFeature(IModule module) : FeatureBase(module); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs index 91f0592b0..5ee379d74 100644 --- a/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs +++ b/src/modules/Elsa.Workflows.Runtime/Features/WorkflowRuntimeFeature.cs @@ -193,7 +193,7 @@ public class WorkflowRuntimeFeature : FeatureBase Module.AddActivitiesFrom(); Module.Configure(workflows => { - workflows.CommitStateHandler = sp => sp.GetRequiredService(); + workflows.CommitStateHandler = sp => sp.GetRequiredService(); }); Services.Configure(options => @@ -263,11 +263,10 @@ public class WorkflowRuntimeFeature : FeatureBase .AddScoped() .AddScoped() .AddScoped() - .AddScoped, ActivityExecutionRecordExtractor>() .AddScoped, WorkflowExecutionLogRecordExtractor>() .AddScoped() - .AddScoped() + .AddScoped() // Deprecated services. .AddScoped() @@ -310,6 +309,7 @@ public class WorkflowRuntimeFeature : FeatureBase // Domain handlers. .AddCommandHandler() + .AddCommandHandler() .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() diff --git a/src/modules/Elsa.Workflows.Runtime/Filters/WorkflowsFilter.cs b/src/modules/Elsa.Workflows.Runtime/Filters/WorkflowsFilter.cs new file mode 100644 index 000000000..a6492d081 --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Filters/WorkflowsFilter.cs @@ -0,0 +1,12 @@ +using Elsa.Workflows.Runtime.Options; + +namespace Elsa.Workflows.Runtime.Filters; + +/// +/// A filter for finding workflows to trigger. +/// +/// The activity type name to trigger workflows for. +/// The bookmark payload to trigger workflows for. +/// The options to use when triggering workflows. +[Obsolete("This type is obsolete. Use the new CreateClientAsync methods of IWorkflowRuntime instead.")] +public record WorkflowsFilter(string ActivityTypeName, object BookmarkPayload, TriggerWorkflowsOptions Options); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime.Distributed/Handlers/CancelWorkflowsCommandHandler.cs b/src/modules/Elsa.Workflows.Runtime/Handlers/CancelWorkflowsCommandHandler.cs similarity index 92% rename from src/modules/Elsa.Workflows.Runtime.Distributed/Handlers/CancelWorkflowsCommandHandler.cs rename to src/modules/Elsa.Workflows.Runtime/Handlers/CancelWorkflowsCommandHandler.cs index 0f1f080bf..776db86c9 100644 --- a/src/modules/Elsa.Workflows.Runtime.Distributed/Handlers/CancelWorkflowsCommandHandler.cs +++ b/src/modules/Elsa.Workflows.Runtime/Handlers/CancelWorkflowsCommandHandler.cs @@ -2,7 +2,7 @@ using Elsa.Mediator.Contracts; using Elsa.Mediator.Models; using Elsa.Workflows.Runtime.Commands; -namespace Elsa.Workflows.Runtime.Distributed.Handlers; +namespace Elsa.Workflows.Runtime.Handlers; /// /// Handles the . diff --git a/src/modules/Elsa.Workflows.Runtime/Handlers/ResumeBulkDispatchWorkflowActivity.cs b/src/modules/Elsa.Workflows.Runtime/Handlers/ResumeBulkDispatchWorkflowActivity.cs index 6b683305b..7532d376e 100644 --- a/src/modules/Elsa.Workflows.Runtime/Handlers/ResumeBulkDispatchWorkflowActivity.cs +++ b/src/modules/Elsa.Workflows.Runtime/Handlers/ResumeBulkDispatchWorkflowActivity.cs @@ -21,11 +21,12 @@ internal class ResumeBulkDispatchWorkflowActivity(IBookmarkQueue bookmarkQueue, if (workflowState.Status != WorkflowStatus.Finished) return; - var parentInstanceId = workflowState.Properties.TryGetValue("ParentInstanceId", out var parentInstanceIdValue) ? parentInstanceIdValue.ToString() : default; - - if (string.IsNullOrWhiteSpace(parentInstanceId)) + var waitForCompletion = workflowState.Properties.TryGetValue("WaitForCompletion", out var waitForCompletionValue) && (bool)waitForCompletionValue; + + if (!waitForCompletion) return; - + + var parentInstanceId = (string)workflowState.Properties["ParentInstanceId"]; var activityTypeName = ActivityTypeNameHelper.GenerateTypeName(); var stimulus = new BulkDispatchWorkflowsStimulus(parentInstanceId); var stimulusHash = stimulusHasher.Hash(activityTypeName, stimulus); diff --git a/src/modules/Elsa.Workflows.Runtime/Handlers/ResumeDispatchWorkflowActivity.cs b/src/modules/Elsa.Workflows.Runtime/Handlers/ResumeDispatchWorkflowActivity.cs index 607edf1f6..37b95583a 100644 --- a/src/modules/Elsa.Workflows.Runtime/Handlers/ResumeDispatchWorkflowActivity.cs +++ b/src/modules/Elsa.Workflows.Runtime/Handlers/ResumeDispatchWorkflowActivity.cs @@ -2,7 +2,6 @@ using Elsa.Mediator.Contracts; using Elsa.Workflows.Helpers; using Elsa.Workflows.Notifications; using Elsa.Workflows.Runtime.Activities; -using Elsa.Workflows.Runtime.Options; using Elsa.Workflows.Runtime.Stimuli; using JetBrains.Annotations; using Microsoft.Extensions.Logging; @@ -16,33 +15,35 @@ namespace Elsa.Workflows.Runtime.Handlers; internal class ResumeDispatchWorkflowActivity(IBookmarkQueue bookmarkQueue, IStimulusHasher stimulusHasher, ILogger logger) : INotificationHandler { private static readonly string ActivityTypeName = ActivityTypeNameHelper.GenerateTypeName(); - + public async Task HandleAsync(WorkflowExecuted notification, CancellationToken cancellationToken) { var workflowState = notification.WorkflowState; - - logger.LogDebug("Handling workflow executed notification for workflow {WorkflowInstanceId}", notification.WorkflowState.Id); if (workflowState.Status != WorkflowStatus.Finished) - { - logger.LogDebug("Workflow {WorkflowInstanceId} is not in a finished state. Skipping resumption of any blocking DispatchWorkflow activities", notification.WorkflowState.Id); return; - } + var props = workflowState.Properties; + var waitForCompletion = props.TryGetValue("WaitForCompletion", out var waitForCompletionValue) && (bool)waitForCompletionValue; + + if (!waitForCompletion) + return; + + var parentInstanceId = (string)props["ParentInstanceId"]; var stimulus = new DispatchWorkflowStimulus(notification.WorkflowState.Id); var input = workflowState.Output; var bookmarkQueueItem = new NewBookmarkQueueItem { + WorkflowInstanceId = parentInstanceId, ActivityTypeName = ActivityTypeName, StimulusHash = stimulusHasher.Hash(ActivityTypeName, stimulus), - Options = new ResumeBookmarkOptions + Options = new() { Input = input } }; - - logger.LogDebug("Resuming any blocking DispatchWorkflow activities for workflow {WorkflowInstanceId} using stimulus hash {StimulusHash}", notification.WorkflowState.Id, bookmarkQueueItem.StimulusHash); + await bookmarkQueue.EnqueueAsync(bookmarkQueueItem, cancellationToken); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Handlers/ResumeExecuteWorkflowActivity.cs b/src/modules/Elsa.Workflows.Runtime/Handlers/ResumeExecuteWorkflowActivity.cs index 256621e0b..da9ffefd9 100644 --- a/src/modules/Elsa.Workflows.Runtime/Handlers/ResumeExecuteWorkflowActivity.cs +++ b/src/modules/Elsa.Workflows.Runtime/Handlers/ResumeExecuteWorkflowActivity.cs @@ -2,7 +2,6 @@ using Elsa.Mediator.Contracts; using Elsa.Workflows.Helpers; using Elsa.Workflows.Notifications; using Elsa.Workflows.Runtime.Activities; -using Elsa.Workflows.Runtime.Options; using Elsa.Workflows.Runtime.Stimuli; using JetBrains.Annotations; @@ -11,7 +10,7 @@ namespace Elsa.Workflows.Runtime.Handlers; /// /// Resumes any blocking activities when its child workflow completes. /// -[PublicAPI] +[UsedImplicitly] internal class ResumeExecuteWorkflowActivity(IBookmarkQueue bookmarkQueue, IStimulusHasher stimulusHasher) : INotificationHandler { private static readonly string ActivityTypeName = ActivityTypeNameHelper.GenerateTypeName(); @@ -23,14 +22,22 @@ internal class ResumeExecuteWorkflowActivity(IBookmarkQueue bookmarkQueue, IStim if (workflowState.Status != WorkflowStatus.Finished) return; + var props = workflowState.Properties; + var waitForCompletion = props.TryGetValue("WaitForCompletion", out var waitForCompletionValue) && (bool)waitForCompletionValue; + + if (!waitForCompletion) + return; + + var parentInstanceId = (string)props["ParentInstanceId"]; var stimulus = new ExecuteWorkflowStimulus(notification.WorkflowState.Id); var input = workflowState.Output; var bookmarkQueueItem = new NewBookmarkQueueItem { + WorkflowInstanceId = parentInstanceId, ActivityTypeName = ActivityTypeName, StimulusHash = stimulusHasher.Hash(ActivityTypeName, stimulus), - Options = new ResumeBookmarkOptions + Options = new() { Input = input } diff --git a/src/modules/Elsa.Workflows.Runtime/Matches/ResumableWorkflowMatch.cs b/src/modules/Elsa.Workflows.Runtime/Matches/ResumableWorkflowMatch.cs new file mode 100644 index 000000000..ba6f7669b --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Matches/ResumableWorkflowMatch.cs @@ -0,0 +1,6 @@ +using Elsa.Workflows.Management.Entities; + +namespace Elsa.Workflows.Runtime.Matches; + +public record ResumableWorkflowMatch(string WorkflowInstanceId, string? CorrelationId, string? BookmarkId, object? Payload) + : WorkflowMatch(CorrelationId, Payload); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Matches/StartableWorkflowMatch.cs b/src/modules/Elsa.Workflows.Runtime/Matches/StartableWorkflowMatch.cs new file mode 100644 index 000000000..3103b8c52 --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Matches/StartableWorkflowMatch.cs @@ -0,0 +1,4 @@ +namespace Elsa.Workflows.Runtime.Matches; + +public record StartableWorkflowMatch(string? CorrelationId, string? ActivityId, string? DefinitionId, object? Payload) + : WorkflowMatch(CorrelationId, Payload); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Matches/WorkflowMatch.cs b/src/modules/Elsa.Workflows.Runtime/Matches/WorkflowMatch.cs new file mode 100644 index 000000000..541a691c9 --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Matches/WorkflowMatch.cs @@ -0,0 +1,5 @@ +using Elsa.Workflows.Management.Entities; + +namespace Elsa.Workflows.Runtime.Matches; + +public record WorkflowMatch(string? CorrelationId, object? Payload); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Messages/RunWorkflowInstanceResponse.cs b/src/modules/Elsa.Workflows.Runtime/Messages/RunWorkflowInstanceResponse.cs index 73d1a9596..8a49e2273 100644 --- a/src/modules/Elsa.Workflows.Runtime/Messages/RunWorkflowInstanceResponse.cs +++ b/src/modules/Elsa.Workflows.Runtime/Messages/RunWorkflowInstanceResponse.cs @@ -22,6 +22,8 @@ public record RunWorkflowInstanceResponse /// public WorkflowSubStatus SubStatus { get; set; } + public ICollection Bookmarks { get; set; } = new List(); + /// /// Any incidents that occurred during the execution of the workflow instance. /// diff --git a/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs b/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs index e0ed60f80..640a07744 100644 --- a/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs +++ b/src/modules/Elsa.Workflows.Runtime/Middleware/Activities/BackgroundActivityInvokerMiddleware.cs @@ -1,5 +1,6 @@ using System.Text.Json; using Elsa.Extensions; +using Elsa.Workflows.CommitStates; using Elsa.Workflows.Middleware.Activities; using Elsa.Workflows.Models; using Elsa.Workflows.Options; @@ -18,8 +19,9 @@ public class BackgroundActivityInvokerMiddleware( ActivityMiddlewareDelegate next, ILogger logger, IIdentityGenerator identityGenerator, - IBackgroundActivityScheduler backgroundActivityScheduler) - : DefaultActivityInvokerMiddleware(next, logger) + IBackgroundActivityScheduler backgroundActivityScheduler, + ICommitStrategyRegistry commitStrategyRegistry) + : DefaultActivityInvokerMiddleware(next, commitStrategyRegistry, logger) { internal static string GetBackgroundActivityOutputKey(string activityNodeId) => $"__BackgroundActivityOutput:{activityNodeId}"; internal static string GetBackgroundActivityOutcomesKey(string activityNodeId) => $"__BackgroundActivityOutcomes:{activityNodeId}"; diff --git a/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistActivityExecutionLogMiddleware.cs b/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistActivityExecutionLogMiddleware.cs index e81f53fd7..b659c8635 100644 --- a/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistActivityExecutionLogMiddleware.cs +++ b/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistActivityExecutionLogMiddleware.cs @@ -1,17 +1,19 @@ using Elsa.Workflows.Pipelines.WorkflowExecution; -using Elsa.Workflows.Runtime.Entities; namespace Elsa.Workflows.Runtime.Middleware.Workflows; /// /// Creates and updates activity execution records from activity execution contexts. /// -public class PersistActivityExecutionLogMiddleware(WorkflowMiddlewareDelegate next, ILogRecordSink sink) : WorkflowExecutionMiddleware(next) +[Obsolete("This middleware is no longer used and will be removed in a future version. Activity state is now persisted through the commit state handler")] +public class PersistActivityExecutionLogMiddleware(WorkflowMiddlewareDelegate next) : WorkflowExecutionMiddleware(next) { /// public override async ValueTask InvokeAsync(WorkflowExecutionContext context) { await Next(context); - await sink.PersistExecutionLogsAsync(context); + + // Not used anymore. + //await sink.PersistExecutionLogsAsync(context); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistBookmarkMiddleware.cs b/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistBookmarkMiddleware.cs index 0319baf07..c44b255ab 100644 --- a/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistBookmarkMiddleware.cs +++ b/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistBookmarkMiddleware.cs @@ -1,26 +1,20 @@ using Elsa.Workflows.Pipelines.WorkflowExecution; -using Elsa.Workflows.Runtime.Requests; namespace Elsa.Workflows.Runtime.Middleware.Workflows; /// /// Takes care of loading and persisting bookmarks. /// -public class PersistBookmarkMiddleware : WorkflowExecutionMiddleware +[Obsolete("This middleware is no longer used and will be removed in a future version. Bookmarks are now persisted through the commit state handler")] +public class PersistBookmarkMiddleware(WorkflowMiddlewareDelegate next) : WorkflowExecutionMiddleware(next) { - private readonly IBookmarksPersister _bookmarksPersister; - - /// - public PersistBookmarkMiddleware(WorkflowMiddlewareDelegate next, IBookmarksPersister bookmarksPersister) : base(next) - { - _bookmarksPersister = bookmarksPersister; - } - /// public override async ValueTask InvokeAsync(WorkflowExecutionContext context) { await Next(context); - var bookmarkRequest = new UpdateBookmarksRequest(context, context.BookmarksDiff, context.CorrelationId); - await _bookmarksPersister.PersistBookmarksAsync(bookmarkRequest); + + // Not used anymore. + // var bookmarkRequest = new UpdateBookmarksRequest(context, context.BookmarksDiff, context.CorrelationId); + // await _bookmarksPersister.PersistBookmarksAsync(bookmarkRequest); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistWorkflowExecutionLogMiddleware.cs b/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistWorkflowExecutionLogMiddleware.cs index 25d863054..4c2d39534 100644 --- a/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistWorkflowExecutionLogMiddleware.cs +++ b/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistWorkflowExecutionLogMiddleware.cs @@ -1,12 +1,12 @@ using Elsa.Workflows.Pipelines.WorkflowExecution; -using Elsa.Workflows.Runtime.Entities; namespace Elsa.Workflows.Runtime.Middleware.Workflows; /// /// Takes care of persisting workflow execution log entries. /// -public class PersistWorkflowExecutionLogMiddleware(WorkflowMiddlewareDelegate next, ILogRecordSink sink) : WorkflowExecutionMiddleware(next) +[Obsolete("This middleware is no longer used and will be removed in a future version. Execution logs are now persisted through the commit state handler.")] +public class PersistWorkflowExecutionLogMiddleware(WorkflowMiddlewareDelegate next) : WorkflowExecutionMiddleware(next) { /// public override async ValueTask InvokeAsync(WorkflowExecutionContext context) @@ -14,6 +14,7 @@ public class PersistWorkflowExecutionLogMiddleware(WorkflowMiddlewareDelegate ne // Invoke next middleware. await Next(context); - await sink.PersistExecutionLogsAsync(context); + // Not used anymore. + //await sink.PersistExecutionLogsAsync(context); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistentVariablesMiddleware.cs b/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistentVariablesMiddleware.cs index b743983da..ab464717d 100644 --- a/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistentVariablesMiddleware.cs +++ b/src/modules/Elsa.Workflows.Runtime/Middleware/Workflows/PersistentVariablesMiddleware.cs @@ -5,28 +5,17 @@ namespace Elsa.Workflows.Runtime.Middleware.Workflows; /// /// Takes care of loading and persisting workflow variables. /// -public class PersistentVariablesMiddleware : WorkflowExecutionMiddleware +public class PersistentVariablesMiddleware(WorkflowMiddlewareDelegate next, IVariablePersistenceManager variablePersistenceManager) : WorkflowExecutionMiddleware(next) { - private readonly IVariablePersistenceManager _variablePersistenceManager; - - /// - /// Constructor. - /// - public PersistentVariablesMiddleware(WorkflowMiddlewareDelegate next, IVariablePersistenceManager variablePersistenceManager) : base(next) - { - _variablePersistenceManager = variablePersistenceManager; - } - /// public override async ValueTask InvokeAsync(WorkflowExecutionContext context) { // Load variables into the workflow execution context. - await _variablePersistenceManager.LoadVariablesAsync(context); + await variablePersistenceManager.LoadVariablesAsync(context); // Invoke next middleware. await Next(context); - // Persist variables. - await _variablePersistenceManager.SaveVariablesAsync(context); + // Variables are persisted through the commit state handler. } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Notifications/ActivityExecutionLogUpdated.cs b/src/modules/Elsa.Workflows.Runtime/Notifications/ActivityExecutionLogUpdated.cs index f662d238d..9766cf884 100644 --- a/src/modules/Elsa.Workflows.Runtime/Notifications/ActivityExecutionLogUpdated.cs +++ b/src/modules/Elsa.Workflows.Runtime/Notifications/ActivityExecutionLogUpdated.cs @@ -8,4 +8,4 @@ namespace Elsa.Workflows.Runtime.Notifications; /// /// The workflow execution context. /// The activity execution records. -public record ActivityExecutionLogUpdated(WorkflowExecutionContext WorkflowExecutionContext, List Records) : INotification; \ No newline at end of file +public record ActivityExecutionLogUpdated(WorkflowExecutionContext WorkflowExecutionContext, ICollection Records) : INotification; \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Options/TriggerWorkflowsOptions.cs b/src/modules/Elsa.Workflows.Runtime/Options/TriggerWorkflowsOptions.cs index 5ac0f1837..15a784823 100644 --- a/src/modules/Elsa.Workflows.Runtime/Options/TriggerWorkflowsOptions.cs +++ b/src/modules/Elsa.Workflows.Runtime/Options/TriggerWorkflowsOptions.cs @@ -3,6 +3,7 @@ namespace Elsa.Workflows.Runtime.Options; /// /// Options for triggering workflows. /// +[Obsolete("This type is obsolete. Use the new CreateClientAsync methods of IWorkflowRuntime instead.")] public class TriggerWorkflowsOptions { public string? CorrelationId { get; set; } diff --git a/src/modules/Elsa.Workflows.Runtime/Parameters/ResumeWorkflowRuntimeParams.cs b/src/modules/Elsa.Workflows.Runtime/Parameters/ResumeWorkflowRuntimeParams.cs index 6836fcc0b..b1b744071 100644 --- a/src/modules/Elsa.Workflows.Runtime/Parameters/ResumeWorkflowRuntimeParams.cs +++ b/src/modules/Elsa.Workflows.Runtime/Parameters/ResumeWorkflowRuntimeParams.cs @@ -5,6 +5,7 @@ namespace Elsa.Workflows.Runtime.Parameters; /// /// Options for resuming workflows. /// +[Obsolete("This type is obsolete. Use the new CreateClientAsync methods of IWorkflowRuntime instead.")] public class ResumeWorkflowRuntimeParams { public string? CorrelationId { get; set; } diff --git a/src/modules/Elsa.Workflows.Runtime/Parameters/StartWorkflowRuntimeParams.cs b/src/modules/Elsa.Workflows.Runtime/Parameters/StartWorkflowRuntimeParams.cs index d8e337c73..79a1dd72d 100644 --- a/src/modules/Elsa.Workflows.Runtime/Parameters/StartWorkflowRuntimeParams.cs +++ b/src/modules/Elsa.Workflows.Runtime/Parameters/StartWorkflowRuntimeParams.cs @@ -5,6 +5,7 @@ namespace Elsa.Workflows.Runtime.Parameters; /// /// Represents parameters for starting a workflow. /// +[Obsolete("This type is obsolete. Use the new CreateClientAsync methods of IWorkflowRuntime instead.")] public class StartWorkflowRuntimeParams { public string? CorrelationId { get; set; } diff --git a/src/modules/Elsa.Workflows.Runtime/Params/ExecuteWorkflowParams.cs b/src/modules/Elsa.Workflows.Runtime/Params/ExecuteWorkflowParams.cs index e6c72ad91..f9d4ab40c 100644 --- a/src/modules/Elsa.Workflows.Runtime/Params/ExecuteWorkflowParams.cs +++ b/src/modules/Elsa.Workflows.Runtime/Params/ExecuteWorkflowParams.cs @@ -2,6 +2,7 @@ using Elsa.Workflows.Models; namespace Elsa.Workflows.Runtime.Params; +[Obsolete("This type is obsolete.")] public class ExecuteWorkflowParams { public string? CorrelationId { get; set; } @@ -11,4 +12,5 @@ public class ExecuteWorkflowParams public IDictionary? Properties { get; set; } public string? TriggerActivityId { get; set; } public string? ParentWorkflowInstanceId { get; set; } + public CancellationToken CancellationToken { get; set; } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Params/WorkflowInboxMessageDeliveryParams.cs b/src/modules/Elsa.Workflows.Runtime/Params/WorkflowInboxMessageDeliveryParams.cs index 5f800b381..b275ac64d 100644 --- a/src/modules/Elsa.Workflows.Runtime/Params/WorkflowInboxMessageDeliveryParams.cs +++ b/src/modules/Elsa.Workflows.Runtime/Params/WorkflowInboxMessageDeliveryParams.cs @@ -3,6 +3,7 @@ namespace Elsa.Workflows.Runtime.Params; /// /// Options for delivering a workflow inbox message. /// +[Obsolete("This type is obsolete. Use the new IBookmarkQueue service instead.")] public class WorkflowInboxMessageDeliveryParams { /// diff --git a/src/modules/Elsa.Workflows.Runtime/Requests/CountRunningWorkflowsRequest.cs b/src/modules/Elsa.Workflows.Runtime/Requests/CountRunningWorkflowsRequest.cs new file mode 100644 index 000000000..3d4cec8ac --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Requests/CountRunningWorkflowsRequest.cs @@ -0,0 +1,23 @@ +namespace Elsa.Workflows.Runtime.Requests; + +/// +/// Contains arguments to use for counting the number of workflow instances. +/// +[Obsolete("This type is obsolete. Use the new CreateClientAsync methods of IWorkflowRuntime instead.")] +public class CountRunningWorkflowsRequest +{ + /// + /// The workflow definition ID to include in the query. + /// + public string? DefinitionId { get; set; } + + /// + /// The workflow definition version to include in the query. + /// + public int? Version { get; set; } + + /// + /// The correlation ID to include in the query. + /// + public string? CorrelationId { get; set; } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Responses/StartWorkflowResponse.cs b/src/modules/Elsa.Workflows.Runtime/Responses/StartWorkflowResponse.cs index e3472732f..6745e54f7 100644 --- a/src/modules/Elsa.Workflows.Runtime/Responses/StartWorkflowResponse.cs +++ b/src/modules/Elsa.Workflows.Runtime/Responses/StartWorkflowResponse.cs @@ -24,6 +24,8 @@ public record StartWorkflowResponse /// The sub-status of the workflow instance. /// public WorkflowSubStatus? SubStatus { get; set; } + + public ICollection Bookmarks { get; set; } = new List(); /// /// Any incidents that occurred during the execution of the workflow instance. diff --git a/src/modules/Elsa.Workflows.Runtime/Results/CancellationResult.cs b/src/modules/Elsa.Workflows.Runtime/Results/CancellationResult.cs index 86092adb7..f40bf1a0b 100644 --- a/src/modules/Elsa.Workflows.Runtime/Results/CancellationResult.cs +++ b/src/modules/Elsa.Workflows.Runtime/Results/CancellationResult.cs @@ -5,4 +5,5 @@ namespace Elsa.Workflows.Runtime.Results; /// /// True if the operation was successful; otherwise, false. /// The reason for the failure, if any. -public record CancellationResult(bool Success, CancellationFailureReason? Reason = default); \ No newline at end of file +[Obsolete("This type is obsolete. Use the new CreateClientAsync methods of IWorkflowRuntime instead.")] +public record CancellationResult(bool Success, CancellationFailureReason? Reason = null); \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/ActivityExecutionRecordExtractor.cs b/src/modules/Elsa.Workflows.Runtime/Services/ActivityExecutionRecordExtractor.cs deleted file mode 100644 index 293601250..000000000 --- a/src/modules/Elsa.Workflows.Runtime/Services/ActivityExecutionRecordExtractor.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Elsa.Workflows.Runtime.Entities; - -namespace Elsa.Workflows.Runtime; - -/// -/// Extracts activity execution log records. -/// -public class ActivityExecutionRecordExtractor(IActivityExecutionMapper activityExecutionMapper) : ILogRecordExtractor -{ - /// - public async Task> ExtractLogRecordsAsync(WorkflowExecutionContext context) - { - var activityExecutionContexts = context.ActivityExecutionContexts; - var tasks = activityExecutionContexts.Select(activityExecutionMapper.MapAsync).ToList(); - return await Task.WhenAll(tasks); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/BackgroundActivityInvoker.cs b/src/modules/Elsa.Workflows.Runtime/Services/BackgroundActivityInvoker.cs index c2541fe79..b5082642f 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/BackgroundActivityInvoker.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/BackgroundActivityInvoker.cs @@ -29,10 +29,10 @@ public class BackgroundActivityInvoker( { var workflowInstanceId = scheduledBackgroundActivity.WorkflowInstanceId; var workflowInstance = await workflowInstanceManager.FindByIdAsync(workflowInstanceId, cancellationToken); - if (workflowInstance == null) throw new Exception("Workflow instance not found"); + if (workflowInstance == null) throw new("Workflow instance not found"); var workflowState = workflowInstance.WorkflowState; var workflow = await workflowDefinitionService.FindWorkflowGraphAsync(workflowInstance.DefinitionVersionId, cancellationToken); - if (workflow == null) throw new Exception("Workflow definition not found"); + if (workflow == null) throw new("Workflow definition not found"); var workflowExecutionContext = await WorkflowExecutionContext.CreateAsync(serviceProvider, workflow, workflowState, cancellationToken: cancellationToken); var activityNodeId = scheduledBackgroundActivity.ActivityNodeId; var activityExecutionContext = workflowExecutionContext.ActivityExecutionContexts.First(x => x.NodeId == activityNodeId); diff --git a/src/modules/Elsa.Workflows.Runtime/Services/BookmarkUpdater.cs b/src/modules/Elsa.Workflows.Runtime/Services/BookmarkUpdater.cs index d42bfab61..d7a9be23e 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/BookmarkUpdater.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/BookmarkUpdater.cs @@ -11,12 +11,15 @@ public class BookmarkUpdater(IBookmarkManager bookmarkManager, IBookmarkStore bo public async Task UpdateBookmarksAsync(UpdateBookmarksRequest request, CancellationToken cancellationToken = default) { var instanceId = request.WorkflowExecutionContext.Id; - await RemoveBookmarksAsync(instanceId, request.Diff.Removed, cancellationToken); - await StoreBookmarksAsync(request.WorkflowExecutionContext, request.Diff.Added, cancellationToken); + await RemoveBookmarksAsync(instanceId, request.Diff.Removed.ToList(), cancellationToken); + await StoreBookmarksAsync(request.WorkflowExecutionContext, request.Diff.Added.ToList(), cancellationToken); } - - private async Task RemoveBookmarksAsync(string workflowInstanceId, IEnumerable bookmarks, CancellationToken cancellationToken) + + private async Task RemoveBookmarksAsync(string workflowInstanceId, ICollection bookmarks, CancellationToken cancellationToken) { + if (bookmarks.Count == 0) + return; + var matchingIds = bookmarks.Select(x => x.Id).ToList(); var filter = new BookmarkFilter { @@ -25,9 +28,12 @@ public class BookmarkUpdater(IBookmarkManager bookmarkManager, IBookmarkStore bo }; await bookmarkManager.DeleteManyAsync(filter, cancellationToken); } - - private async Task StoreBookmarksAsync(WorkflowExecutionContext context, IEnumerable bookmarks, CancellationToken cancellationToken) + + private async Task StoreBookmarksAsync(WorkflowExecutionContext context, ICollection bookmarks, CancellationToken cancellationToken) { + if (bookmarks.Count == 0) + return; + foreach (var bookmark in bookmarks) { var storedBookmark = context.MapBookmark(bookmark); diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultActivityExecutionMapper.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultActivityExecutionMapper.cs index 0e1dd85de..7278e4fa1 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/DefaultActivityExecutionMapper.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultActivityExecutionMapper.cs @@ -115,7 +115,7 @@ public class DefaultActivityExecutionMapper : IActivityExecutionMapper ActivityTypeVersion = source.Activity.Version, StartedAt = source.StartedAt, HasBookmarks = source.Bookmarks.Any(), - Status = GetAggregateStatus(source), + Status = source.Status, CompletedAt = source.CompletedAt }; } @@ -220,20 +220,9 @@ public class DefaultActivityExecutionMapper : IActivityExecutionMapper } } - private static ActivityStatus GetAggregateStatus(ActivityExecutionContext context) - { - // If any child activity is faulted, the aggregate status is faulted. - var descendantContexts = context.GetDescendants().ToList(); - - if (descendantContexts.Any(x => x.Status == ActivityStatus.Faulted)) - return ActivityStatus.Faulted; - - return context.Status; - } - private static IDictionary GetPayload(ActivityExecutionContext source) { - var outcomes = source.JournalData.TryGetValue("Outcomes", out var resultValue) ? resultValue as string[] : default; + var outcomes = source.JournalData.TryGetValue("Outcomes", out var resultValue) ? resultValue as string[] : null; var payload = new Dictionary(); if (outcomes != null) @@ -256,13 +245,13 @@ public class DefaultActivityExecutionMapper : IActivityExecutionMapper var cachedValue = activity.GetOutput(expressionExecutionContext, x.Name); - if (cachedValue != default) + if (cachedValue != null) return cachedValue; if (x.ValueGetter(activity) is Output output && source.TryGet(output.MemoryBlockReference(), out var outputValue)) return outputValue; - return default; + return null; }); return outputs; diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultCommitStateHandler.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultCommitStateHandler.cs new file mode 100644 index 000000000..3df97e11c --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultCommitStateHandler.cs @@ -0,0 +1,34 @@ +using Elsa.Workflows.CommitStates; +using Elsa.Workflows.Management; +using Elsa.Workflows.Runtime.Entities; +using Elsa.Workflows.Runtime.Requests; +using Elsa.Workflows.State; + +namespace Elsa.Workflows.Runtime; + +public class DefaultCommitStateHandler( + IWorkflowInstanceManager workflowInstanceManager, + IBookmarksPersister bookmarkPersister, + IVariablePersistenceManager variablePersistenceManager, + ILogRecordSink activityExecutionLogRecordSink, + ILogRecordSink workflowExecutionLogRecordSink) : ICommitStateHandler +{ + public async Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken = default) + { + var workflowState = workflowInstanceManager.ExtractWorkflowState(workflowExecutionContext); + await CommitAsync(workflowExecutionContext, workflowState, cancellationToken); + } + + public async Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, WorkflowState workflowState, CancellationToken cancellationToken = default) + { + var updateBookmarksRequest = new UpdateBookmarksRequest(workflowExecutionContext, workflowExecutionContext.BookmarksDiff, workflowExecutionContext.CorrelationId); + await bookmarkPersister.PersistBookmarksAsync(updateBookmarksRequest); + await activityExecutionLogRecordSink.PersistExecutionLogsAsync(workflowExecutionContext, cancellationToken); + await workflowExecutionLogRecordSink.PersistExecutionLogsAsync(workflowExecutionContext, cancellationToken); + await variablePersistenceManager.SaveVariablesAsync(workflowExecutionContext); + await workflowInstanceManager.SaveAsync(workflowState, cancellationToken); + workflowExecutionContext.ExecutionLog.Clear(); + workflowExecutionContext.ClearCompletedActivityExecutionContexts(); + await workflowExecutionContext.ExecuteDeferredTasksAsync(); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowStarter.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowStarter.cs index 11d861160..db557bbbc 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowStarter.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowStarter.cs @@ -12,14 +12,14 @@ public class DefaultWorkflowStarter(IWorkflowDefinitionService workflowDefinitio { var workflow = await GetWorkflowAsync(request, cancellationToken); - var canStart = await workflowActivationStrategyEvaluator.CanStartWorkflowAsync(new WorkflowActivationStrategyEvaluationContext + var canStart = await workflowActivationStrategyEvaluator.CanStartWorkflowAsync(new() { Workflow = workflow, CorrelationId = request.CorrelationId }); if (!canStart) - return new StartWorkflowResponse + return new() { CannotStart = true }; @@ -31,16 +31,19 @@ public class DefaultWorkflowStarter(IWorkflowDefinitionService workflowDefinitio CorrelationId = request.CorrelationId, Input = request.Input, TriggerActivityId = request.TriggerActivityId, - ActivityHandle = request.ActivityHandle + ActivityHandle = request.ActivityHandle, + Properties = request.Properties, + ParentId = request.ParentId }; var runWorkflowResponse = await workflowClient.CreateAndRunInstanceAsync(createWorkflowInstanceRequest, cancellationToken); - return new StartWorkflowResponse + return new() { CannotStart = false, WorkflowInstanceId = runWorkflowResponse.WorkflowInstanceId, Status = runWorkflowResponse.Status, SubStatus = runWorkflowResponse.SubStatus, + Bookmarks = runWorkflowResponse.Bookmarks, Incidents = runWorkflowResponse.Incidents }; } @@ -56,7 +59,7 @@ public class DefaultWorkflowStarter(IWorkflowDefinitionService workflowDefinitio var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(request.WorkflowDefinitionHandle, cancellationToken); if (workflowGraph == null) - throw new WorkflowGraphNotFoundException($"Workflow definition not found.", request.WorkflowDefinitionHandle); + throw new WorkflowGraphNotFoundException("Workflow definition not found.", request.WorkflowDefinitionHandle); return workflowGraph.Workflow; } diff --git a/src/modules/Elsa.Workflows.Runtime/Services/LocalWorkflowClient.cs b/src/modules/Elsa.Workflows.Runtime/Services/LocalWorkflowClient.cs index 1747e31f3..8497c99dc 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/LocalWorkflowClient.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/LocalWorkflowClient.cs @@ -29,8 +29,7 @@ public class LocalWorkflowClient( public async Task CreateInstanceAsync(CreateWorkflowInstanceRequest request, CancellationToken cancellationToken = default) { var workflowDefinitionHandle = request.WorkflowDefinitionHandle; - var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(workflowDefinitionHandle, cancellationToken); - if (workflowGraph == null) throw new InvalidOperationException($"Workflow with version ID {workflowDefinitionHandle} not found."); + var workflowGraph = await GetWorkflowGraphAsync(workflowDefinitionHandle, cancellationToken); var options = new WorkflowInstanceOptions { @@ -41,48 +40,15 @@ public class LocalWorkflowClient( Properties = request.Properties }; - await workflowInstanceManager.CreateWorkflowInstanceAsync(workflowGraph.Workflow, options, cancellationToken); - return new CreateWorkflowInstanceResponse(); + await workflowInstanceManager.CreateAndCommitWorkflowInstanceAsync(workflowGraph.Workflow, options, cancellationToken); + return new(); } /// public async Task RunInstanceAsync(RunWorkflowInstanceRequest request, CancellationToken cancellationToken = default) { var workflowInstance = await GetWorkflowInstanceAsync(cancellationToken); - var workflowState = workflowInstance.WorkflowState; - - if (workflowInstance.Status != WorkflowStatus.Running) - { - logger.LogWarning("Attempt to resume workflow {WorkflowInstanceId} that is not in the Running state. The actual state is {ActualWorkflowStatus}", workflowState.Id, workflowState.Status); - return new RunWorkflowInstanceResponse - { - WorkflowInstanceId = WorkflowInstanceId, - Status = workflowInstance.Status, - SubStatus = workflowInstance.SubStatus - }; - } - - var runWorkflowOptions = new RunWorkflowOptions - { - Input = request.Input, - Properties = request.Properties, - BookmarkId = request.BookmarkId, - TriggerActivityId = request.TriggerActivityId, - ActivityHandle = request.ActivityHandle, - }; - - var workflowGraph = await GetWorkflowGraphAsync(cancellationToken); - var workflowResult = await workflowRunner.RunAsync(workflowGraph, workflowState, runWorkflowOptions, cancellationToken); - - workflowState = workflowResult.WorkflowState; - - return new RunWorkflowInstanceResponse - { - WorkflowInstanceId = WorkflowInstanceId, - Status = workflowState.Status, - SubStatus = workflowState.SubStatus, - Incidents = workflowState.Incidents - }; + return await RunInstanceAsync(workflowInstance, request, cancellationToken); } /// @@ -96,8 +62,8 @@ public class LocalWorkflowClient( WorkflowDefinitionHandle = request.WorkflowDefinitionHandle, ParentId = request.ParentId }; - await CreateInstanceAsync(createRequest, cancellationToken); - return await RunInstanceAsync(new RunWorkflowInstanceRequest + var workflowInstance = await CreateInstanceInternalAsync(createRequest, cancellationToken); + return await RunInstanceAsync(workflowInstance, new() { Input = request.Input, Properties = request.Properties, @@ -111,7 +77,7 @@ public class LocalWorkflowClient( { var workflowInstance = await GetWorkflowInstanceAsync(cancellationToken); if (workflowInstance.Status != WorkflowStatus.Running) return; - var workflowGraph = await GetWorkflowGraphAsync(cancellationToken); + var workflowGraph = await GetWorkflowGraphAsync(workflowInstance, cancellationToken); var workflowState = await workflowCanceler.CancelWorkflowAsync(workflowGraph, workflowInstance.WorkflowState, cancellationToken); await workflowInstanceManager.SaveAsync(workflowState, cancellationToken); } @@ -130,6 +96,66 @@ public class LocalWorkflowClient( await workflowInstanceManager.SaveAsync(workflowInstance, cancellationToken); } + public Task InstanceExistsAsync(CancellationToken cancellationToken = default) + { + return workflowInstanceManager.ExistsAsync(workflowInstanceId, cancellationToken); + } + + private async Task RunInstanceAsync(WorkflowInstance workflowInstance, RunWorkflowInstanceRequest request, CancellationToken cancellationToken = default) + { + var workflowState = workflowInstance.WorkflowState; + + if (workflowInstance.Status != WorkflowStatus.Running) + { + logger.LogWarning("Attempt to resume workflow {WorkflowInstanceId} that is not in the Running state. The actual state is {ActualWorkflowStatus}", workflowState.Id, workflowState.Status); + return new() + { + WorkflowInstanceId = WorkflowInstanceId, + Status = workflowInstance.Status, + SubStatus = workflowInstance.SubStatus + }; + } + + var runWorkflowOptions = new RunWorkflowOptions + { + Input = request.Input, + Properties = request.Properties, + BookmarkId = request.BookmarkId, + TriggerActivityId = request.TriggerActivityId, + ActivityHandle = request.ActivityHandle, + }; + + var workflowGraph = await GetWorkflowGraphAsync(workflowInstance, cancellationToken); + var workflowResult = await workflowRunner.RunAsync(workflowGraph, workflowState, runWorkflowOptions, cancellationToken); + + workflowState = workflowResult.WorkflowState; + + return new() + { + WorkflowInstanceId = WorkflowInstanceId, + Status = workflowState.Status, + SubStatus = workflowState.SubStatus, + Incidents = workflowState.Incidents + }; + } + + public async Task CreateInstanceInternalAsync(CreateWorkflowInstanceRequest request, CancellationToken cancellationToken = default) + { + var workflowDefinitionHandle = request.WorkflowDefinitionHandle; + var workflowGraph = await GetWorkflowGraphAsync(workflowDefinitionHandle, cancellationToken); + + var options = new WorkflowInstanceOptions + { + WorkflowInstanceId = WorkflowInstanceId, + CorrelationId = request.CorrelationId, + ParentWorkflowInstanceId = request.ParentId, + Input = request.Input, + Properties = request.Properties + }; + + return workflowInstanceManager.CreateWorkflowInstance(workflowGraph.Workflow, options); + } + private async Task GetWorkflowInstanceAsync(CancellationToken cancellationToken) { var workflowInstance = await workflowInstanceManager.FindByIdAsync(WorkflowInstanceId, cancellationToken); @@ -137,11 +163,16 @@ public class LocalWorkflowClient( return workflowInstance; } - private async Task GetWorkflowGraphAsync(CancellationToken cancellationToken) + private async Task GetWorkflowGraphAsync(WorkflowInstance workflowInstance, CancellationToken cancellationToken) { - var workflowInstance = await GetWorkflowInstanceAsync(cancellationToken); - var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(workflowInstance.DefinitionVersionId, cancellationToken); - if (workflowGraph == null) throw new InvalidOperationException($"Workflow graph with version ID {workflowInstance.DefinitionVersionId} not found."); + var handle = WorkflowDefinitionHandle.ByDefinitionVersionId(workflowInstance.DefinitionVersionId); + return await GetWorkflowGraphAsync(handle, cancellationToken); + } + + private async Task GetWorkflowGraphAsync(WorkflowDefinitionHandle definitionHandle, CancellationToken cancellationToken) + { + var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(definitionHandle, cancellationToken); + if (workflowGraph == null) throw new InvalidOperationException($"Workflow graph with handle {definitionHandle} not found."); return workflowGraph; } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/LocalWorkflowRuntime.Obsolete.cs b/src/modules/Elsa.Workflows.Runtime/Services/LocalWorkflowRuntime.Obsolete.cs new file mode 100644 index 000000000..0c929966e --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Services/LocalWorkflowRuntime.Obsolete.cs @@ -0,0 +1,32 @@ +using Elsa.Workflows.Runtime.Entities; +using Elsa.Workflows.Runtime.Filters; +using Elsa.Workflows.Runtime.Matches; +using Elsa.Workflows.Runtime.Options; +using Elsa.Workflows.Runtime.Parameters; +using Elsa.Workflows.Runtime.Params; +using Elsa.Workflows.Runtime.Requests; +using Elsa.Workflows.Runtime.Results; +using Elsa.Workflows.State; + +namespace Elsa.Workflows.Runtime; + +public partial class LocalWorkflowRuntime +{ + private readonly Lazy _obsoleteApi; + private ObsoleteWorkflowRuntime ObsoleteApi => _obsoleteApi.Value; + + public Task CanStartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = null) => ObsoleteApi.CanStartWorkflowAsync(definitionId, options); + public Task StartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = null) => ObsoleteApi.StartWorkflowAsync(definitionId, options); + public Task> StartWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = null) => ObsoleteApi.StartWorkflowsAsync(activityTypeName, bookmarkPayload, options); + public Task TryStartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = null) => ObsoleteApi.TryStartWorkflowAsync(definitionId, options); + public Task ResumeWorkflowAsync(string workflowInstanceId, ResumeWorkflowRuntimeParams? options = null) => ObsoleteApi.ResumeWorkflowAsync(workflowInstanceId, options); + public Task> ResumeWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = null) => ObsoleteApi.ResumeWorkflowsAsync(activityTypeName, bookmarkPayload, options); + public Task TriggerWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = null) => ObsoleteApi.TriggerWorkflowsAsync(activityTypeName, bookmarkPayload, options); + public Task ExecuteWorkflowAsync(WorkflowMatch match, ExecuteWorkflowParams? options = default) => ObsoleteApi.ExecuteWorkflowAsync(match, options); + public Task CancelWorkflowAsync(string workflowInstanceId, CancellationToken cancellationToken = default) => ObsoleteApi.CancelWorkflowAsync(workflowInstanceId, cancellationToken); + public Task> FindWorkflowsAsync(WorkflowsFilter filter, CancellationToken cancellationToken = default) => ObsoleteApi.FindWorkflowsAsync(filter, cancellationToken); + public Task ExportWorkflowStateAsync(string workflowInstanceId, CancellationToken cancellationToken = default) => ObsoleteApi.ExportWorkflowStateAsync(workflowInstanceId, cancellationToken); + public Task ImportWorkflowStateAsync(WorkflowState workflowState, CancellationToken cancellationToken = default) => ObsoleteApi.ImportWorkflowStateAsync(workflowState, cancellationToken); + public Task UpdateBookmarkAsync(StoredBookmark bookmark, CancellationToken cancellationToken = default) => ObsoleteApi.UpdateBookmarkAsync(bookmark, cancellationToken); + public Task CountRunningWorkflowsAsync(CountRunningWorkflowsRequest request, CancellationToken cancellationToken = default) => ObsoleteApi.CountRunningWorkflowsAsync(request, cancellationToken); +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/LocalWorkflowRuntime.cs b/src/modules/Elsa.Workflows.Runtime/Services/LocalWorkflowRuntime.cs index dcfa181c7..d20b48e17 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/LocalWorkflowRuntime.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/LocalWorkflowRuntime.cs @@ -7,8 +7,23 @@ namespace Elsa.Workflows.Runtime; /// It does not support clustering and is intended for single-node deployments only. /// For distributed deployments, use Proto.Actor or another distributed runtime. /// -public class LocalWorkflowRuntime(IServiceProvider serviceProvider, IIdentityGenerator identityGenerator) : IWorkflowRuntime +public partial class LocalWorkflowRuntime : IWorkflowRuntime { + private readonly IServiceProvider _serviceProvider; + private readonly IIdentityGenerator _identityGenerator; + + /// + /// Represents a local implementation of the distributed runtime for running workflows. + /// It does not support clustering and is intended for single-node deployments only. + /// For distributed deployments, use Proto.Actor or another distributed runtime. + /// + public LocalWorkflowRuntime(IServiceProvider serviceProvider, IIdentityGenerator identityGenerator) + { + _serviceProvider = serviceProvider; + _identityGenerator = identityGenerator; + _obsoleteApi = new(() => ObsoleteWorkflowRuntime.Create(serviceProvider, CreateClientAsync)); + } + /// public async ValueTask CreateClientAsync(CancellationToken cancellationToken = default) { @@ -18,8 +33,8 @@ public class LocalWorkflowRuntime(IServiceProvider serviceProvider, IIdentityGen /// public ValueTask CreateClientAsync(string? workflowInstanceId, CancellationToken cancellationToken = default) { - workflowInstanceId ??= identityGenerator.GenerateId(); - var client = (IWorkflowClient)ActivatorUtilities.CreateInstance(serviceProvider, typeof(LocalWorkflowClient), workflowInstanceId); + workflowInstanceId ??= _identityGenerator.GenerateId(); + var client = (IWorkflowClient)ActivatorUtilities.CreateInstance(_serviceProvider, typeof(LocalWorkflowClient), workflowInstanceId); return new(client); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/ObsoleteWorkflowRuntime.cs b/src/modules/Elsa.Workflows.Runtime/Services/ObsoleteWorkflowRuntime.cs new file mode 100644 index 000000000..aa26779bc --- /dev/null +++ b/src/modules/Elsa.Workflows.Runtime/Services/ObsoleteWorkflowRuntime.cs @@ -0,0 +1,256 @@ +using Elsa.Common.Models; +using Elsa.Workflows.Management; +using Elsa.Workflows.Management.Filters; +using Elsa.Workflows.Models; +using Elsa.Workflows.Runtime.Entities; +using Elsa.Workflows.Runtime.Filters; +using Elsa.Workflows.Runtime.Matches; +using Elsa.Workflows.Runtime.Messages; +using Elsa.Workflows.Runtime.Options; +using Elsa.Workflows.Runtime.Parameters; +using Elsa.Workflows.Runtime.Params; +using Elsa.Workflows.Runtime.Requests; +using Elsa.Workflows.Runtime.Results; +using Elsa.Workflows.State; +using Microsoft.Extensions.DependencyInjection; +using Open.Linq.AsyncExtensions; + +namespace Elsa.Workflows.Runtime; + +/// +/// Implements the now deprecated workflow runtime API methods. +/// +public class ObsoleteWorkflowRuntime( + Func> createClientAsync, + IWorkflowDefinitionService workflowDefinitionService, + IWorkflowActivationStrategyEvaluator workflowActivationStrategyEvaluator, + IStimulusSender stimulusSender, + IStimulusHasher stimulusHasher, + IBookmarkStore bookmarkStore, + IWorkflowInstanceStore workflowInstanceStore, + ITriggerBoundWorkflowService triggerBoundWorkflowService, + IBookmarkBoundWorkflowService bookmarkBoundWorkflowService) +{ + public static ObsoleteWorkflowRuntime Create(IServiceProvider serviceProvider, Func> createClientAsync) + { + return ActivatorUtilities.CreateInstance(serviceProvider, createClientAsync); + } + + public async Task CanStartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = null) + { + var cancellationToken = options?.CancellationToken ?? CancellationToken.None; + var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(definitionId, options?.VersionOptions ?? VersionOptions.Published, cancellationToken); + var workflow = workflowGraph!.Workflow; + + var canStart = await workflowActivationStrategyEvaluator.CanStartWorkflowAsync(new() + { + Workflow = workflow, + CorrelationId = options?.CorrelationId, + CancellationToken = cancellationToken + }); + + return new(null, canStart); + } + + public async Task StartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = null) + { + var cancellationToken = options?.CancellationToken ?? CancellationToken.None; + var client = await createClientAsync(options?.InstanceId, cancellationToken); + var createRequest = new CreateAndRunWorkflowInstanceRequest + { + Properties = options?.Properties, + CorrelationId = options?.CorrelationId, + Input = options?.Input, + WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(definitionId, options?.VersionOptions ?? VersionOptions.Published), + ParentId = options?.ParentWorkflowInstanceId, + TriggerActivityId = options?.TriggerActivityId + }; + var response = await client.CreateAndRunInstanceAsync(createRequest, cancellationToken); + return new(response.WorkflowInstanceId, response.Status, response.SubStatus, response.Bookmarks, response.Incidents); + } + + public async Task> StartWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = null) + { + var cancellationToken = options?.CancellationToken ?? CancellationToken.None; + var metadata = new StimulusMetadata + { + CorrelationId = options?.CorrelationId, + WorkflowInstanceId = options?.WorkflowInstanceId, + Properties = options?.Properties, + ActivityInstanceId = options?.ActivityInstanceId, + Input = options?.Input + }; + var result = await stimulusSender.SendAsync(activityTypeName, bookmarkPayload, metadata, cancellationToken); + var results = result.WorkflowInstanceResponses.Select(x => new WorkflowExecutionResult(x.WorkflowInstanceId, x.Status, x.SubStatus, x.Bookmarks, x.Incidents)).ToList(); + return results; + } + + public async Task TryStartWorkflowAsync(string definitionId, StartWorkflowRuntimeParams? options = null) + { + return await StartWorkflowAsync(definitionId, options); + } + + public async Task ResumeWorkflowAsync(string workflowInstanceId, ResumeWorkflowRuntimeParams? options = null) + { + var cancellationToken = options?.CancellationToken ?? CancellationToken.None; + var workflowClient = await createClientAsync(workflowInstanceId, cancellationToken); + var exists = await workflowClient.InstanceExistsAsync(cancellationToken); + + if (!exists) + return null; + + var runWorkflowRequest = new RunWorkflowInstanceRequest + { + Input = options?.Input, + Properties = options?.Properties, + ActivityHandle = options?.ActivityHandle, + BookmarkId = options?.BookmarkId + }; + + var response = await workflowClient.RunInstanceAsync(runWorkflowRequest, cancellationToken); + + return new(response.WorkflowInstanceId, response.Status, response.SubStatus, response.Bookmarks, response.Incidents); + } + + public async Task> ResumeWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = null) + { + var cancellationToken = options?.CancellationToken ?? CancellationToken.None; + var metadata = new StimulusMetadata + { + CorrelationId = options?.CorrelationId, + WorkflowInstanceId = options?.WorkflowInstanceId, + Properties = options?.Properties, + ActivityInstanceId = options?.ActivityInstanceId, + Input = options?.Input + }; + var result = await stimulusSender.SendAsync(activityTypeName, bookmarkPayload, metadata, cancellationToken); + var results = result.WorkflowInstanceResponses.Select(x => new WorkflowExecutionResult(x.WorkflowInstanceId, x.Status, x.SubStatus, x.Bookmarks, x.Incidents)).ToList(); + return results; + } + + public async Task TriggerWorkflowsAsync(string activityTypeName, object bookmarkPayload, TriggerWorkflowsOptions? options = null) + { + var cancellationToken = options?.CancellationToken ?? CancellationToken.None; + var metadata = new StimulusMetadata + { + CorrelationId = options?.CorrelationId, + WorkflowInstanceId = options?.WorkflowInstanceId, + Properties = options?.Properties, + ActivityInstanceId = options?.ActivityInstanceId, + Input = options?.Input + }; + var result = await stimulusSender.SendAsync(activityTypeName, bookmarkPayload, metadata, cancellationToken); + var results = result.WorkflowInstanceResponses.Select(x => new WorkflowExecutionResult(x.WorkflowInstanceId, x.Status, x.SubStatus, x.Bookmarks, x.Incidents)).ToList(); + return new(results); + } + + public async Task ExecuteWorkflowAsync(WorkflowMatch match, ExecuteWorkflowParams? options = default) + { + var cancellationToken = options?.CancellationToken ?? CancellationToken.None; + if (match is StartableWorkflowMatch collectedStartableWorkflow) + { + var startOptions = new StartWorkflowRuntimeParams + { + CorrelationId = collectedStartableWorkflow.CorrelationId, + Input = options?.Input, + Properties = options?.Properties, + VersionOptions = VersionOptions.Published, + TriggerActivityId = collectedStartableWorkflow.ActivityId, + CancellationToken = cancellationToken + }; + + var startResult = await StartWorkflowAsync(collectedStartableWorkflow.DefinitionId!, startOptions); + return startResult with + { + TriggeredActivityId = collectedStartableWorkflow.ActivityId + }; + } + + var collectedResumableWorkflow = (match as ResumableWorkflowMatch)!; + var runtimeOptions = new ResumeWorkflowRuntimeParams + { + CorrelationId = collectedResumableWorkflow.CorrelationId, + BookmarkId = collectedResumableWorkflow.BookmarkId, + Input = options?.Input, + Properties = options?.Properties, + CancellationToken = cancellationToken, + }; + + return (await ResumeWorkflowAsync(collectedResumableWorkflow.WorkflowInstanceId, runtimeOptions))!; + } + + public async Task CancelWorkflowAsync(string workflowInstanceId, CancellationToken cancellationToken = default) + { + var client = await createClientAsync(workflowInstanceId, cancellationToken); + await client.CancelAsync(cancellationToken); + return new(true); + } + + public async Task> FindWorkflowsAsync(WorkflowsFilter filter, CancellationToken cancellationToken = default) + { + var startableWorkflows = await FindStartableWorkflowsAsync(filter, cancellationToken); + var resumableWorkflows = await FindResumableWorkflowsAsync(filter, cancellationToken); + var results = startableWorkflows.Concat(resumableWorkflows).ToList(); + return results; + } + + public async Task ExportWorkflowStateAsync(string workflowInstanceId, CancellationToken cancellationToken = default) + { + var client = await createClientAsync(workflowInstanceId, cancellationToken); + return await client.ExportStateAsync(cancellationToken); + } + + public async Task ImportWorkflowStateAsync(WorkflowState workflowState, CancellationToken cancellationToken = default) + { + var client = await createClientAsync(workflowState.Id, cancellationToken); + await client.ImportStateAsync(workflowState, cancellationToken); + } + + public async Task UpdateBookmarkAsync(StoredBookmark bookmark, CancellationToken cancellationToken = default) + { + await bookmarkStore.SaveAsync(bookmark, cancellationToken); + } + + public async Task CountRunningWorkflowsAsync(CountRunningWorkflowsRequest request, CancellationToken cancellationToken = default) + { + var filter = new WorkflowInstanceFilter + { + DefinitionId = request.DefinitionId, + Version = request.Version, + CorrelationId = request.CorrelationId, + WorkflowStatus = WorkflowStatus.Running + }; + return await workflowInstanceStore.CountAsync(filter, cancellationToken); + } + + private async Task> FindStartableWorkflowsAsync(WorkflowsFilter filter, CancellationToken cancellationToken = default) + { + var stimulusHash = stimulusHasher.Hash(filter.ActivityTypeName, filter.BookmarkPayload, filter.Options.ActivityInstanceId); + var triggerBoundWorkflows = await triggerBoundWorkflowService.FindManyAsync(stimulusHash, cancellationToken).ToList(); + var correlationId = filter.Options.CorrelationId; + + var query = + from triggerBoundWorkflow in triggerBoundWorkflows + from trigger in triggerBoundWorkflow.Triggers + select new StartableWorkflowMatch(correlationId, trigger.ActivityId, triggerBoundWorkflow.WorkflowGraph.Workflow.Identity.DefinitionId, filter.BookmarkPayload); + + return query.ToList(); + } + + private async Task> FindResumableWorkflowsAsync(WorkflowsFilter filter, CancellationToken cancellationToken) + { + var bookmarkOptions = new FindBookmarkOptions + { + CorrelationId = filter.Options.CorrelationId, + WorkflowInstanceId = filter.Options.WorkflowInstanceId, + ActivityInstanceId = filter.Options.ActivityInstanceId + }; + var bookmarkBoundWorkflows = await bookmarkBoundWorkflowService.FindManyAsync(filter.ActivityTypeName, filter.BookmarkPayload, bookmarkOptions, cancellationToken).ToList(); + + return ( + from bookmarkBoundWorkflow in bookmarkBoundWorkflows + from bookmark in bookmarkBoundWorkflow.Bookmarks + select new ResumableWorkflowMatch(bookmarkBoundWorkflow.WorkflowInstanceId, bookmark.CorrelationId, bookmark.Id, bookmark.Payload)) + .ToList(); + } +} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/StimulusSender.cs b/src/modules/Elsa.Workflows.Runtime/Services/StimulusSender.cs index 7ded4d3a3..58894281c 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/StimulusSender.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/StimulusSender.cs @@ -14,7 +14,6 @@ public class StimulusSender( IBookmarkBoundWorkflowService bookmarkBoundWorkflowService, IBookmarkQueue bookmarkQueue, IWorkflowRuntime workflowRuntime, - IWorkflowStarter workflowStarter, ITriggerInvoker triggerInvoker, ILogger logger) : IStimulusSender { @@ -38,7 +37,7 @@ public class StimulusSender( var resumed = await ResumeExistingWorkflowsAsync(stimulusHash, metadata, cancellationToken); responses.AddRange(resumed); - return new SendStimulusResult(responses); + return new(responses); } private async Task> TriggerNewWorkflowsAsync(string stimulusHash, StimulusMetadata? metadata = null, CancellationToken cancellationToken = default) @@ -129,7 +128,7 @@ public class StimulusSender( WorkflowInstanceId = workflowInstanceId, BookmarkId = metadata?.BookmarkId, StimulusHash = stimulusHash, - Options = new ResumeBookmarkOptions + Options = new() { Input = input, Properties = properties diff --git a/src/modules/Elsa.Workflows.Runtime/Services/StoreActivityExecutionLogSink.cs b/src/modules/Elsa.Workflows.Runtime/Services/StoreActivityExecutionLogSink.cs index 2c280a22c..a1fda06ea 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/StoreActivityExecutionLogSink.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/StoreActivityExecutionLogSink.cs @@ -1,21 +1,35 @@ using Elsa.Mediator.Contracts; using Elsa.Workflows.Runtime.Entities; using Elsa.Workflows.Runtime.Notifications; -using Open.Linq.AsyncExtensions; namespace Elsa.Workflows.Runtime.Services; /// /// This implementation saves directly through the store. /// -public class StoreActivityExecutionLogSink(IActivityExecutionStore activityExecutionStore, ILogRecordExtractor extractor, INotificationSender notificationSender) +public class StoreActivityExecutionLogSink( + IActivityExecutionStore activityExecutionStore, + IActivityExecutionMapper mapper, + INotificationSender notificationSender) : ILogRecordSink { /// public async Task PersistExecutionLogsAsync(WorkflowExecutionContext context, CancellationToken cancellationToken = default) { - var records = await extractor.ExtractLogRecordsAsync(context).ToList(); + // Select tainted activity execution contexts to avoid saving untainted ones multiple times. + var activityExecutionContexts = context.ActivityExecutionContexts.Where(x => x.IsDirty).ToList(); + + if (activityExecutionContexts.Count == 0) + return; + + var tasks = activityExecutionContexts.Select(mapper.MapAsync).ToList(); + var records = await Task.WhenAll(tasks); await activityExecutionStore.SaveManyAsync(records, cancellationToken); + + // Untaint activity execution contexts. + foreach (var activityExecutionContext in activityExecutionContexts) + activityExecutionContext.ClearTaint(); + await notificationSender.SendAsync(new ActivityExecutionLogUpdated(context, records), cancellationToken); } } \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/StoreCommitStateHandler.cs b/src/modules/Elsa.Workflows.Runtime/Services/StoreCommitStateHandler.cs deleted file mode 100644 index 8f3d33466..000000000 --- a/src/modules/Elsa.Workflows.Runtime/Services/StoreCommitStateHandler.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Elsa.Workflows.Management; -using Elsa.Workflows.State; - -namespace Elsa.Workflows.Runtime; - -public class StoreCommitStateHandler(IWorkflowInstanceManager workflowInstanceManager) : ICommitStateHandler -{ - public async Task CommitAsync(WorkflowExecutionContext workflowExecutionContext, WorkflowState workflowState, CancellationToken cancellationToken = default) - { - await workflowInstanceManager.SaveAsync(workflowState, cancellationToken); - await workflowExecutionContext.ExecuteDeferredTasksAsync(); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Workflows.Runtime/Services/StoreWorkflowExecutionLogSink.cs b/src/modules/Elsa.Workflows.Runtime/Services/StoreWorkflowExecutionLogSink.cs index a91667858..72bce2ebb 100644 --- a/src/modules/Elsa.Workflows.Runtime/Services/StoreWorkflowExecutionLogSink.cs +++ b/src/modules/Elsa.Workflows.Runtime/Services/StoreWorkflowExecutionLogSink.cs @@ -14,6 +14,10 @@ public class StoreWorkflowExecutionLogSink(IWorkflowExecutionLogStore store, ILo public async Task PersistExecutionLogsAsync(WorkflowExecutionContext context, CancellationToken cancellationToken) { var records = await extractor.ExtractLogRecordsAsync(context).ToList(); + + if(records.Count == 0) + return; + await store.AddManyAsync(records, context.CancellationToken); await notificationSender.SendAsync(new WorkflowExecutionLogUpdated(context), context.CancellationToken); } diff --git a/test/integration/Elsa.JavaScript.IntegrationTests/JsonElementConverterTests.cs b/test/integration/Elsa.JavaScript.IntegrationTests/JsonElementConverterTests.cs new file mode 100644 index 000000000..b7203e554 --- /dev/null +++ b/test/integration/Elsa.JavaScript.IntegrationTests/JsonElementConverterTests.cs @@ -0,0 +1,155 @@ +using System.Text.Json; +using Elsa.Expressions.Models; +using Elsa.JavaScript.Contracts; +using Elsa.Testing.Shared; +using Elsa.Workflows.Memory; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; + +namespace Elsa.JavaScript.IntegrationTests; + +public class JsonElementConverterTests(ITestOutputHelper testOutputHelper) +{ + private readonly IServiceProvider _serviceProvider = new TestApplicationBuilder(testOutputHelper).Build(); + + [Fact(DisplayName = "JsonElement JsonObject can be passed to JavaScript")] + public async Task TestJsonObjectPassedAsJsonElement() + { + var javaScriptEvaluator = _serviceProvider.GetRequiredService(); + var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new MemoryRegister()); + var jsonVariable = new Variable + { + Name = "JsonVariable" + }; + + var jsonString = "{\"name\": \"John\", \"age\": 30}"; + var jsonElement = JsonSerializer.Deserialize(jsonString); + + jsonVariable.Set(expressionExecutionContext, jsonElement); + var script = "getVariable('JsonVariable').age"; + var result = await javaScriptEvaluator.EvaluateAsync(script, typeof(int), expressionExecutionContext); + Assert.Equal(30, result); + } + + [Fact(DisplayName = "JsonElement JsonArray can be passed to JavaScript")] + public async Task TestJsonArrayPassedAsJsonElement() + { + var javaScriptEvaluator = _serviceProvider.GetRequiredService(); + var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new MemoryRegister()); + var jsonVariable = new Variable + { + Name = "JsonVariable" + }; + + var jsonString = "[1, 2, 3, 4, 5, 6]"; + var jsonElement = JsonSerializer.Deserialize(jsonString); + + jsonVariable.Set(expressionExecutionContext, jsonElement); + var script = "getVariable('JsonVariable')[3]"; + var result = await javaScriptEvaluator.EvaluateAsync(script, typeof(int), expressionExecutionContext); + Assert.Equal(4, result); + } + + [Fact(DisplayName = "JsonElement string can be passed to JavaScript")] + public async Task TestStringPassedAsJsonElement() + { + var javaScriptEvaluator = _serviceProvider.GetRequiredService(); + var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new MemoryRegister()); + var jsonVariable = new Variable + { + Name = "JsonVariable" + }; + + var jsonString = "\"I'm just a string\""; + var jsonElement = JsonSerializer.Deserialize(jsonString); + + jsonVariable.Set(expressionExecutionContext, jsonElement); + var script = "getVariable('JsonVariable')"; + var result = await javaScriptEvaluator.EvaluateAsync(script, typeof(string), expressionExecutionContext); + Assert.Equal("I'm just a string", result); + } + + [Fact(DisplayName = "JsonElement boolean can be passed to JavaScript")] + public async Task TestBooleanPassedAsJsonElement() + { + var javaScriptEvaluator = _serviceProvider.GetRequiredService(); + var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new MemoryRegister()); + var jsonVariable = new Variable + { + Name = "JsonVariable" + }; + + var jsonString = "false"; + var jsonElement = JsonSerializer.Deserialize(jsonString); + + jsonVariable.Set(expressionExecutionContext, jsonElement); + var script = "getVariable('JsonVariable')"; + var result = await javaScriptEvaluator.EvaluateAsync(script, typeof(bool), expressionExecutionContext); + Assert.Equal(false, result); + } + + [Fact(DisplayName = "JsonElement containing nested Json objects and arrays be passed to JavaScript")] + public async Task TestNestedJsonPassedAsJsonElement() + { + var javaScriptEvaluator = _serviceProvider.GetRequiredService(); + var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new MemoryRegister()); + var jsonVariable = new Variable + { + Name = "JsonVariable" + }; + + var jsonString = @" + { + ""name"": ""John Doe"", + ""age"": 35, + ""address"": { + ""street"": ""123 Main St"", + ""city"": ""Springfield"", + ""zip"": ""12345"", + ""coordinates"": { ""lat"": 40.7128, ""lng"": -74.006 } + }, + ""skills"": [ + { ""name"": ""Programming"", ""level"": ""Advanced"" }, + { ""name"": ""Writing"", ""level"": ""Intermediate"" } + ], + ""projects"": [ + { + ""title"": ""Project A"", + ""status"": ""Completed"", + ""team"": [""Alice"", ""Bob""] + }, + { + ""title"": ""Project B"", + ""status"": ""In Progress"", + ""team"": [""Charlie"", ""David"", ""Eve""] + } + ], + ""isEmployed"": true, + ""contact"": { ""email"": ""john.doe@example.com"", ""phone"": ""555-1234"" } + }"; + var jsonElement = JsonSerializer.Deserialize(jsonString); + + jsonVariable.Set(expressionExecutionContext, jsonElement); + + var script = "getVariable('JsonVariable').projects[1].team[0]"; + var result = await javaScriptEvaluator.EvaluateAsync(script, typeof(string), expressionExecutionContext); + Assert.Equal("Charlie", result); + + var script2 = "getVariable('JsonVariable').isEmployed"; + var result2 = await javaScriptEvaluator.EvaluateAsync(script2, typeof(bool), expressionExecutionContext); + Assert.Equal(true, result2); + + var script3 = "getVariable('JsonVariable').skills[0].level"; + var result3 = await javaScriptEvaluator.EvaluateAsync(script3, typeof(string), expressionExecutionContext); + Assert.Equal("Advanced", result3); + + var script4 = "getVariable('JsonVariable').address.coordinates.lat"; + var result4 = await javaScriptEvaluator.EvaluateAsync(script4, typeof(double), expressionExecutionContext); + Assert.Equal(40.7128, result4); + + var script5 = "getVariable('JsonVariable').age"; + var result5 = await javaScriptEvaluator.EvaluateAsync(script5, typeof(int), expressionExecutionContext); + Assert.Equal(35, result5); + } +} \ No newline at end of file diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ActivityOutputs/LoopingWorkflow.cs b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ActivityOutputs/LoopingWorkflow.cs new file mode 100644 index 000000000..a24f0e5d2 --- /dev/null +++ b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ActivityOutputs/LoopingWorkflow.cs @@ -0,0 +1,34 @@ +using Elsa.Extensions; +using Elsa.Workflows.Activities; + +namespace Elsa.Workflows.IntegrationTests.Scenarios.ActivityOutputs; + +public class LoopingWorkflow : WorkflowBase +{ + protected override void Build(IWorkflowBuilder builder) + { + var readCurrentValue = new Inline(context => context.GetVariable("CurrentValue")!); + + builder.Root = new ForEach( + [ + "Item 1", + "Item 2", + "Item 3" + ]) + { + Body = new Sequence + { + Activities = + [ + readCurrentValue, + new WriteLine(context => + { + var currentValue = context.GetVariable("CurrentValue"); + var activityResult = context.GetActivityExecutionContext().GetResult(readCurrentValue); + return $"Current value: {currentValue}, Activity result: {activityResult}"; + }) + ] + } + }; + } +} \ No newline at end of file diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ActivityOutputs/Tests.cs b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ActivityOutputs/Tests.cs index ce1d1bde4..285966ae7 100644 --- a/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ActivityOutputs/Tests.cs +++ b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/ActivityOutputs/Tests.cs @@ -24,8 +24,21 @@ public class Tests var lines = _capturingTextWriter.Lines.ToList(); Assert.Equal(new[] { - "The result of 4 and 6 is 10.", - "The last result is 10." + "The result of 4 and 6 is 10.", "The last result is 10." + }, lines); + } + + [Fact(DisplayName = "The last activity output is returned.")] + public async Task Test2() + { + await _services.PopulateRegistriesAsync(); + await _workflowRunner.RunAsync(); + var lines = _capturingTextWriter.Lines.ToList(); + Assert.Equal(new[] + { + "Current value: Item 1, Activity result: Item 1", + "Current value: Item 2, Activity result: Item 2", + "Current value: Item 3, Activity result: Item 3" }, lines); } } \ No newline at end of file diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/Serialization/Tests.cs b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/Serialization/Tests.cs new file mode 100644 index 000000000..786c7a8ba --- /dev/null +++ b/test/integration/Elsa.Workflows.IntegrationTests/Scenarios/Serialization/Tests.cs @@ -0,0 +1,31 @@ +using Elsa.Testing.Shared; +using Elsa.Workflows.Management; +using Microsoft.Extensions.DependencyInjection; +using Xunit.Abstractions; + +namespace Elsa.Workflows.IntegrationTests.Scenarios.Serialization; + +public class Tests +{ + private readonly CapturingTextWriter _capturingTextWriter = new(); + private readonly IServiceProvider _services; + private readonly IWorkflowDefinitionPublisher _publisher; + private readonly IActivitySerializer _serializer; + + public Tests(ITestOutputHelper testOutputHelper) + { + _services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build(); + _services.GetRequiredService(); + _publisher = _services.GetRequiredService(); + _serializer = _services.GetRequiredService(); + } + + [Fact(DisplayName = "Can serialize newly created workflow definition")] + public async Task Test1() + { + await _services.PopulateRegistriesAsync(); + var workflowDefinition = _publisher.New(); + var root = _serializer.Deserialize(workflowDefinition.StringData); + Assert.NotNull(root); + } +} \ No newline at end of file diff --git a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/Tests.cs b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/Tests.cs index 1a6ff38d5..f4a853f28 100644 --- a/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/Tests.cs +++ b/test/integration/Elsa.Workflows.IntegrationTests/Serialization/JsonSerialization/Tests.cs @@ -74,7 +74,8 @@ public class SerializationTests(ITestOutputHelper testOutputHelper) { var dict = new Dictionary { - { "Content", new List() + { + "Content", new List() { new() { @@ -94,9 +95,10 @@ public class SerializationTests(ITestOutputHelper testOutputHelper) { var dict = new Dictionary { - { "Content", new List + { + "Content", new List { - Guid.NewGuid() + Guid.NewGuid() } } }; @@ -106,6 +108,24 @@ public class SerializationTests(ITestOutputHelper testOutputHelper) Assert.Equal(typeof(List), result.GetType()); } + [Fact] + public void RoundtripPrimitiveArrays() + { + var dict = new Dictionary + { + { + "Content", new[] + { + Guid.NewGuid() + } + } + }; + var jsonSerialized = SerializeUsingPayloadSerializer(dict); + var transformationModel = DeSerializeDictionaryUsingPayloadSerializer(jsonSerialized); + var result = transformationModel["Content"]; + Assert.Equal(typeof(Guid[]), result.GetType()); + } + private string SerializeUsingPayloadSerializer(object obj) { var payloadSerializer = _services.GetRequiredService(); @@ -134,10 +154,11 @@ public class SerializationTests(ITestOutputHelper testOutputHelper) var dict = new Dictionary { - { "StatusCode", "Created" }, - { "Content",isArray ? - (type == typeof(JArray)? JArray.Parse(jsonContent):JsonArray.Parse(jsonContent)): - (type == typeof(JObject)? JObject.Parse(jsonContent):JsonObject.Parse(jsonContent)) + { + "StatusCode", "Created" + }, + { + "Content", isArray ? (type == typeof(JArray) ? JArray.Parse(jsonContent) : JsonArray.Parse(jsonContent)) : (type == typeof(JObject) ? JObject.Parse(jsonContent) : JsonObject.Parse(jsonContent)) } }; return dict; diff --git a/test/performance/Directory.Build.props b/test/performance/Directory.Build.props index 0e0af60ea..674d5ca9b 100644 --- a/test/performance/Directory.Build.props +++ b/test/performance/Directory.Build.props @@ -1,6 +1,4 @@ - - diff --git a/test/performance/Elsa.Workflows.PerformanceTests/Config.cs b/test/performance/Elsa.Workflows.PerformanceTests/Config.cs new file mode 100644 index 000000000..307deecce --- /dev/null +++ b/test/performance/Elsa.Workflows.PerformanceTests/Config.cs @@ -0,0 +1,14 @@ +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Exporters; + +namespace Elsa.Workflows.PerformanceTests; + +public class Config : ManualConfig +{ + public Config() + { + AddExporter(MarkdownExporter.GitHub); + AddDiagnoser(MemoryDiagnoser.Default); + } +} \ No newline at end of file diff --git a/test/performance/Elsa.Workflows.PerformanceTests/ConsoleActivitiesBenchmark.cs b/test/performance/Elsa.Workflows.PerformanceTests/ConsoleActivitiesBenchmark.cs new file mode 100644 index 000000000..9f214cf1b --- /dev/null +++ b/test/performance/Elsa.Workflows.PerformanceTests/ConsoleActivitiesBenchmark.cs @@ -0,0 +1,32 @@ +using BenchmarkDotNet.Attributes; +using Elsa.Extensions; +using Elsa.Workflows.Activities; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Workflows.PerformanceTests; + +[Config(typeof(Config))] +public class ConsoleActivitiesBenchmark +{ + private WriteLine _writeLineWorkflow; + private IWorkflowRunner _workflowRunner; + private ServiceProvider _serviceProvider; + + [GlobalSetup] + public void GlobalSetup() + { + var services = new ServiceCollection(); + services.AddElsa(); + + _serviceProvider = services.BuildServiceProvider(); + _workflowRunner = _serviceProvider.GetRequiredService(); + + _writeLineWorkflow = new WriteLine("Hello, World!"); + } + + [Benchmark] + public async Task WriteLine() => await _workflowRunner.RunAsync(_writeLineWorkflow); + + [GlobalCleanup] + public void GlobalCleanup() => _serviceProvider.Dispose(); +} \ No newline at end of file diff --git a/test/performance/Elsa.Workflows.PerformanceTests/Elsa.Workflows.PerformanceTests.csproj b/test/performance/Elsa.Workflows.PerformanceTests/Elsa.Workflows.PerformanceTests.csproj index fc1b99f93..ee58b4574 100644 --- a/test/performance/Elsa.Workflows.PerformanceTests/Elsa.Workflows.PerformanceTests.csproj +++ b/test/performance/Elsa.Workflows.PerformanceTests/Elsa.Workflows.PerformanceTests.csproj @@ -6,11 +6,11 @@ enable false - true + Exe - + diff --git a/test/performance/Elsa.Workflows.PerformanceTests/Program.cs b/test/performance/Elsa.Workflows.PerformanceTests/Program.cs new file mode 100644 index 000000000..af752d90e --- /dev/null +++ b/test/performance/Elsa.Workflows.PerformanceTests/Program.cs @@ -0,0 +1,4 @@ +using BenchmarkDotNet.Running; + +BenchmarkSwitcher switcher = new(typeof(Program).Assembly); +switcher.Run(args); \ No newline at end of file diff --git a/test/performance/Elsa.Workflows.PerformanceTests/UnitTest1.cs b/test/performance/Elsa.Workflows.PerformanceTests/UnitTest1.cs deleted file mode 100644 index 5eecbccb1..000000000 --- a/test/performance/Elsa.Workflows.PerformanceTests/UnitTest1.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Elsa.Workflows.PerformanceTests; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - } -} \ No newline at end of file diff --git a/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Person.cs b/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Person.cs new file mode 100644 index 000000000..b5583db8b --- /dev/null +++ b/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Person.cs @@ -0,0 +1,9 @@ +namespace Elsa.Workflows.Core.UnitTests.ObjectConversion +{ + public class Person + { + public double? Age { get; set; } + + public string? Name { get; set; } + } +} diff --git a/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Tests.cs b/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Tests.cs new file mode 100644 index 000000000..c245cb5cc --- /dev/null +++ b/test/unit/Elsa.Workflows.Core.UnitTests/ObjectConversion/Tests.cs @@ -0,0 +1,303 @@ +using System.Dynamic; +using System.Text.Json; +using System.Text.Json.Nodes; +using Elsa.Expressions.Exceptions; +using Elsa.Expressions.Helpers; + +namespace Elsa.Workflows.Core.UnitTests.ObjectConversion; + +public class Tests +{ + [Fact] + public void TryConvertTo_SameType_ReturnsSuccess() + { + // Arrange + var value = 42; + + // Act + var result = value.TryConvertTo(); + + // Assert + Assert.True(result.Success); + Assert.Equal(42, result.Value); + } + + [Fact] + public void TryConvertTo_DifferentType_ReturnsConvertedValue() + { + // Arrange + var value = "42"; + + // Act + var result = value.TryConvertTo(); + + // Assert + Assert.True(result.Success); + Assert.Equal(42, result.Value); + } + + [Fact] + public void TryConvertTo_InvalidConversion_ReturnsFailure() + { + // Arrange + var value = "invalid"; + + // Act + var result = value.TryConvertTo(); + + // Assert + + // I would have expected this conversion to not be successful. It seems there are many cases like this + //Assert.False(result.Success); + //Assert.NotNull(result.Exception); + + Assert.True(result.Success); + Assert.Equal(0, result.Value); + } + + [Fact] + public void TryConvertTo_InvalidJsonString_ReturnsFailure() + { + // Arrange + var value = "{ invalid json }"; + + // Act + var result = value.TryConvertTo>(); + + // Assert + Assert.False(result.Success); + Assert.NotNull(result.Exception); + } + + [Fact] + public void ConvertTo_NullValue_ReturnsDefault() + { + // Arrange + object? value = null; + + // Act + var result = value.ConvertTo(); + + // Assert + Assert.Equal(0, result); + } + + [Fact] + public void ConvertTo_JsonElementNumberToString_ReturnsString() + { + // Arrange + var jsonElement = JsonNode.Parse("42").AsValue(); + + // Act + var result = jsonElement.ConvertTo(); + + // Assert + Assert.Equal("42", result); + } + + [Fact] + public void ConvertTo_JsonNodeToExpandoObject_ReturnsExpandoObject() + { + // Arrange + var jsonNode = JsonNode.Parse("{ \"key\": \"value\" }"); + + // Act + var result = jsonNode.ConvertTo(); + + // Assert + dynamic expando = Assert.IsType(result); + object value = expando.key; + + // This is not the result I expect, I would have expected the JsonNode to have been recursively converted + + Assert.True(value is JsonElement); + Assert.Equal(JsonValueKind.String, ((JsonElement)value).ValueKind); + Assert.Equal("value", ((JsonElement)value).GetString()); + } + + [Fact] + public void ConvertTo_StringToDateTime_ReturnsDateTime() + { + // Arrange + var value = "2023-01-01T00:00:00"; + + // Act + var result = value.ConvertTo(); + + // Assert + Assert.Equal(new(2023, 1, 1, 0, 0, 0), result); + } + + [Fact] + public void ConvertTo_StringToEnum_ReturnsEnum() + { + // Arrange + var value = "Monday"; + + // Act + var result = value.ConvertTo(); + + // Assert + Assert.Equal(DayOfWeek.Monday, result); + } + + [Fact] + public void ConvertTo_StringToByteArray_ReturnsByteArray() + { + // Arrange + var value = Convert.ToBase64String(new byte[] + { + 1, 2, 3 + }); + + // Act + var result = value.ConvertTo(); + + // Assert + Assert.Equal(new byte[] + { + 1, 2, 3 + }, result); + } + + [Fact] + public void ConvertTo_InvalidJsonString_ThrowsException() + { + // Arrange + var value = "{ invalid json }"; + + // Act & Assert + Assert.Throws(() => value.ConvertTo>()); + } + + [Fact] + public void ConvertTo_EnumerableToList_ReturnsConvertedList() + { + // Arrange + var value = new[] + { + "1", "2", "3" + }; + + // Act + var result = value.ConvertTo>(); + + // Assert + Assert.Equal(new() + { + 1, + 2, + 3 + }, result); + } + + [Fact] + public void ConvertTo_DateTimeToDateOnly_ReturnsDateOnly() + { + // Arrange + var value = new DateTime(2023, 1, 1); + + // Act + var result = value.ConvertTo(); + + // Assert + Assert.Equal(new(2023, 1, 1), result); + } + + [Fact] + public void ConvertTo_DateOnlyToDateTime_ReturnsDateTime() + { + // Arrange + var value = new DateOnly(2023, 1, 1); + + // Act + var result = value.ConvertTo(); + + // Assert + Assert.Equal(new(2023, 1, 1, 0, 0, 0), result); + } + + [Fact] + public void ConvertTo_UnknownConversion_ThrowsInvalidCastException() + { + // Arrange + var value = new object(); + + // Act & Assert + Assert.Throws(() => value.ConvertTo()); + } + + [Fact] + public void ConvertTo_JsonArrayToListOfObject_ReturnsListOfJsonObject() + { + // Arrange + var jsonArrayString = "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]"; + var jsonArray = JsonNode.Parse(jsonArrayString); + var options = new ObjectConverterOptions(); + + // Act + var result = jsonArray.ConvertTo>(options); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count); + Assert.IsType(result[0]); + Assert.IsType(result[1]); + + var firstElement = result[0] as JsonObject; + var secondElement = result[1] as JsonObject; + + Assert.NotNull(firstElement); + Assert.Equal("Alice", firstElement["name"]?.ToString()); + Assert.Equal("30", firstElement["age"]?.ToString()); + + Assert.NotNull(secondElement); + Assert.Equal("Bob", secondElement["name"]?.ToString()); + Assert.Equal("25", secondElement["age"]?.ToString()); + } + + [Fact] + public void ConvertTo_JsonArrayToICollectionOfObject_ReturnsCollectionOfJsonObject() + { + // Arrange + var jsonArrayString = "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]"; + var jsonArray = JsonNode.Parse(jsonArrayString); + var options = new ObjectConverterOptions(); + + // Act + var result = jsonArray.ConvertTo>(options); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count); + Assert.All(result, item => Assert.IsType(item)); + + var firstElement = result.First() as JsonObject; + var secondElement = result.Last() as JsonObject; + + Assert.NotNull(firstElement); + Assert.Equal("Alice", firstElement["name"]?.ToString()); + Assert.Equal("30", firstElement["age"]?.ToString()); + + Assert.NotNull(secondElement); + Assert.Equal("Bob", secondElement["name"]?.ToString()); + Assert.Equal("25", secondElement["age"]?.ToString()); + } + + [Fact] + public void ConvertFrom_JsonArrayToArrayOfComplextType_ReturnsArrayOfComplexType() + { + // Arrange + var jsonArrayString = "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]"; + var options = new ObjectConverterOptions(); + + // Act + var result = jsonArrayString.ConvertTo(options); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Length); + Assert.Equal("Alice", result[0].Name); + Assert.Equal("Bob", result[1].Name); + } +} \ No newline at end of file