diff --git a/Elsa.sln b/Elsa.sln
index 7682d09c5..07e55704e 100644
--- a/Elsa.sln
+++ b/Elsa.sln
@@ -18,12 +18,6 @@ EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{0354F050-3992-4DD4-B0EE-5FBA04AC72B6}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "architecture", "architecture", "{A34FD350-79C5-492A-8811-6DBD80830F8B}"
-ProjectSection(SolutionItems) = preProject
- docs\architecture\Layer1.md = docs\architecture\Layer1.md
- docs\architecture\Layer2.md = docs\architecture\Layer2.md
-EndProjectSection
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "console", "console", "{873BFC3E-63C2-4495-A503-5EC05DCD84E4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "aspnet", "aspnet", "{56C2FFB8-EA54-45B5-A095-4A78142EB4B5}"
@@ -211,7 +205,6 @@ Global
{155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
{C6658DE0-2B2F-47F0-BB61-2CA66D435C09} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
{6FA9ED7D-2D32-46BC-B47F-E503B2A4C0A9} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
- {A34FD350-79C5-492A-8811-6DBD80830F8B} = {0354F050-3992-4DD4-B0EE-5FBA04AC72B6}
{873BFC3E-63C2-4495-A503-5EC05DCD84E4} = {155227F0-A33B-40AA-A4B4-06F813EB921B}
{B44A9DDA-8091-490A-BC0B-2EAE4A601B09} = {873BFC3E-63C2-4495-A503-5EC05DCD84E4}
{56C2FFB8-EA54-45B5-A095-4A78142EB4B5} = {155227F0-A33B-40AA-A4B4-06F813EB921B}
diff --git a/src/core/Elsa.Core/Activities/Console/WriteLine.cs b/src/core/Elsa.Core/Activities/Console/WriteLine.cs
index d41206188..eb3d5bd93 100644
--- a/src/core/Elsa.Core/Activities/Console/WriteLine.cs
+++ b/src/core/Elsa.Core/Activities/Console/WriteLine.cs
@@ -1,4 +1,5 @@
using System.ComponentModel;
+using Elsa.Attributes;
using Elsa.Models;
namespace Elsa.Activities.Console;
diff --git a/src/core/Elsa.Core/Attributes/ActivityAttribute.cs b/src/core/Elsa.Core/Attributes/ActivityAttribute.cs
index 434fe631a..98c36630f 100644
--- a/src/core/Elsa.Core/Attributes/ActivityAttribute.cs
+++ b/src/core/Elsa.Core/Attributes/ActivityAttribute.cs
@@ -3,13 +3,29 @@ namespace Elsa.Attributes;
[AttributeUsage(AttributeTargets.Class)]
public class ActivityAttribute : Attribute
{
- public ActivityAttribute(string? typeName = default, string? description = default, string? category = default)
+ public ActivityAttribute(string @namespace, string? category, string? description = default)
{
+ Namespace = @namespace;
+ Description = description;
+ Category = category;
+ }
+
+ public ActivityAttribute(string @namespace, string? description = default)
+ {
+ Namespace = @namespace;
+ Description = description;
+ Category = @namespace;
+ }
+
+ public ActivityAttribute(string @namespace, string? typeName, string? description = default, string? category = default)
+ {
+ Namespace = @namespace;
TypeName = typeName;
Description = description;
Category = category;
}
-
+
+ public string? Namespace { get; }
public string? TypeName { get; }
public string? Description { get; }
public string? Category { get; }
diff --git a/src/core/Elsa.Core/Attributes/InputAttribute.cs b/src/core/Elsa.Core/Attributes/InputAttribute.cs
index 40fd569d7..b67c8e8dd 100644
--- a/src/core/Elsa.Core/Attributes/InputAttribute.cs
+++ b/src/core/Elsa.Core/Attributes/InputAttribute.cs
@@ -25,7 +25,7 @@ public class InputAttribute : Attribute
// ///
// /// A brief description about this property for workflow tooling to use when displaying activity editors.
// ///
- // public string? Description { get; set; }
+ public string? Description { get; set; }
///
/// A category to group this property with.
@@ -68,7 +68,7 @@ public class InputAttribute : Attribute
public string[]? SupportedSyntaxes { get; set; }
///
- /// A value indicating whether this property should be displayed but as read-only.
+ /// A value indicating whether this property should be displayed as read-only.
///
public bool IsReadOnly { get; set; }
diff --git a/src/core/Elsa.Core/Extensions/ActivityExecutionContextExtensions.cs b/src/core/Elsa.Core/Extensions/ActivityExecutionContextExtensions.cs
index a7766e94c..59b8f2c46 100644
--- a/src/core/Elsa.Core/Extensions/ActivityExecutionContextExtensions.cs
+++ b/src/core/Elsa.Core/Extensions/ActivityExecutionContextExtensions.cs
@@ -1,9 +1,24 @@
-using System.Text.Json;
+using Elsa.Contracts;
using Elsa.Models;
namespace Elsa.Extensions;
public static class ActivityExecutionContextExtensions
{
- public static bool TryGetInput(this ActivityExecutionContext context, string key, out T value) => context.Input.TryGetValue(key, out value);
+ public static bool TryGetInput(this ActivityExecutionContext context, string key, out T value) => context.Input.TryGetValue(key, out value!);
+ public static T GetInput(this ActivityExecutionContext context) => context.GetInput(typeof(T).Name);
+ public static T GetInput(this ActivityExecutionContext context, string key) => (T)context.Input[key];
+
+ public static WorkflowExecutionLogEntry AddExecutionLogEntry(this ActivityExecutionContext context, string eventName, string? message = default, object? payload = default) =>
+ context.AddExecutionLogEntry(eventName, message, default, payload);
+
+ public static WorkflowExecutionLogEntry AddExecutionLogEntry(this ActivityExecutionContext context, string eventName, string? message = default, string? source = default, object? payload = default)
+ {
+ var activity = context.Activity;
+ var workflowExecutionContext = context.WorkflowExecutionContext;
+ var now = context.GetRequiredService().UtcNow;
+ var logEntry = new WorkflowExecutionLogEntry(activity.Id, activity.TypeName, now, eventName, message, source, payload);
+ workflowExecutionContext.ExecutionLog.Add(logEntry);
+ return logEntry;
+ }
}
\ No newline at end of file
diff --git a/src/core/Elsa.Core/Models/ActivityExecutionContext.cs b/src/core/Elsa.Core/Models/ActivityExecutionContext.cs
index 7737690e8..db89525ae 100644
--- a/src/core/Elsa.Core/Models/ActivityExecutionContext.cs
+++ b/src/core/Elsa.Core/Models/ActivityExecutionContext.cs
@@ -62,7 +62,7 @@ public class ActivityExecutionContext
/// A dictionary of received inputs.
///
public IReadOnlyDictionary Input => WorkflowExecutionContext.Input;
-
+
///
/// Journal data will be added to the workflow execution log for the "Executed" event.
///
@@ -98,10 +98,12 @@ public class ActivityExecutionContext
CreateBookmark(bookmarkDatum, callback);
}
- public void CreateBookmarks(IEnumerable bookmarks) => _bookmarks.AddRange(bookmarks);
- public void CreateBookmark(Bookmark bookmark) => _bookmarks.Add(bookmark);
+ public void AddBookmarks(IEnumerable bookmarks) => _bookmarks.AddRange(bookmarks);
+ public void AddBookmark(Bookmark bookmark) => _bookmarks.Add(bookmark);
- public void CreateBookmark(object? bookmarkDatum, ExecuteActivityDelegate? callback = default)
+ public Bookmark CreateBookmark(ExecuteActivityDelegate callback) => CreateBookmark(default, callback);
+
+ public Bookmark CreateBookmark(object? bookmarkDatum = default, ExecuteActivityDelegate? callback = default)
{
var hasher = GetRequiredService();
var identityGenerator = GetRequiredService();
@@ -109,17 +111,20 @@ public class ActivityExecutionContext
var bookmarkDatumJson = bookmarkDatum != null ? bookmarkDataSerializer.Serialize(bookmarkDatum) : default;
var hash = bookmarkDatumJson != null ? hasher.Hash(bookmarkDatumJson) : default;
- CreateBookmark(new Bookmark(
+ var bookmark = new Bookmark(
identityGenerator.GenerateId(),
Activity.TypeName,
hash,
bookmarkDatumJson,
Activity.Id,
Id,
- callback?.Method.Name));
+ callback?.Method.Name);
+
+ AddBookmark(bookmark);
+ return bookmark;
}
- public T? GetProperty(string key) => Properties.TryGetValue(key, out var value) ? value : default;
+ public T? GetProperty(string key) => Properties!.TryGetValue(key, out var value) ? value : default;
public void SetProperty(string key, T value) where T : notnull => Properties[key] = value;
public T UpdateProperty(string key, Func updater) where T : notnull
diff --git a/src/core/Elsa.Core/Models/ExpressionExecutionContext.cs b/src/core/Elsa.Core/Models/ExpressionExecutionContext.cs
index e86e9cbd0..ef3faa107 100644
--- a/src/core/Elsa.Core/Models/ExpressionExecutionContext.cs
+++ b/src/core/Elsa.Core/Models/ExpressionExecutionContext.cs
@@ -26,7 +26,24 @@ public class ExpressionExecutionContext
public object Get(RegisterLocationReference locationReference) => GetLocation(locationReference).Value!;
public T Get(RegisterLocationReference locationReference) => (T)Get(locationReference);
public T? Get(Input? input) => input != null ? (T?)GetLocation(input.LocationReference).Value : default;
- public T? GetVariable(string name, object? defaultValue = default) => Get(new Variable(name, defaultValue));
+ public T? GetVariable(string name) => (T?)GetVariable(name);
+ public T? GetVariable() => (T?)GetVariable(typeof(T).Name);
+
+ public object? GetVariable(string name)
+ {
+ var variable = Workflow.Variables.FirstOrDefault(x => x.Name == name);
+ return variable?.Get(Register);
+ }
+
+ public Variable SetVariable(T? value) => SetVariable(typeof(T).Name, value);
+ public Variable SetVariable(string name, T? value) => SetVariable(name, (object?)value);
+
+ public Variable SetVariable(string name, object? value)
+ {
+ var variable = Workflow.Variables.FirstOrDefault(x => x.Name == name) ?? new Variable(name, value);
+ variable.Set(Register, value);
+ return variable;
+ }
public void Set(RegisterLocationReference locationReference, object? value)
{
diff --git a/src/core/Elsa.Core/Models/RegisterLocationReference.cs b/src/core/Elsa.Core/Models/RegisterLocationReference.cs
index fa8d54672..361df7e6d 100644
--- a/src/core/Elsa.Core/Models/RegisterLocationReference.cs
+++ b/src/core/Elsa.Core/Models/RegisterLocationReference.cs
@@ -10,10 +10,13 @@ public abstract class RegisterLocationReference
public string Id { get; set; } = default!;
public abstract RegisterLocation Declare();
+ public object? Get(Register register) => GetLocation(register).Value;
+ public T? Get(Register register) => (T?)Get(register);
public object? Get(ExpressionExecutionContext context) => context.Get(this);
public object? Get(ActivityExecutionContext context) => Get(context.ExpressionExecutionContext);
public T? Get(ExpressionExecutionContext context) => (T?)Get(context);
public T? Get(ActivityExecutionContext context) => (T?)Get(context.ExpressionExecutionContext);
+ public void Set(Register register, object? value) => GetLocation(register).Value = value;
public void Set(ExpressionExecutionContext context, object? value) => context.Set(this, value);
public void Set(ActivityExecutionContext context, object? value) => Set(context.ExpressionExecutionContext, value);
diff --git a/src/core/Elsa.Core/Models/WorkflowExecutionContext.cs b/src/core/Elsa.Core/Models/WorkflowExecutionContext.cs
index 77a9e29f2..0ce6ae4c7 100644
--- a/src/core/Elsa.Core/Models/WorkflowExecutionContext.cs
+++ b/src/core/Elsa.Core/Models/WorkflowExecutionContext.cs
@@ -37,10 +37,12 @@ public class WorkflowExecutionContext
CancellationToken = cancellationToken;
NodeIdLookup = _nodes.ToDictionary(x => x.NodeId);
NodeActivityLookup = _nodes.ToDictionary(x => x.Activity);
+ Register = workflow.CreateRegister();
}
public Workflow Workflow { get; }
- public ActivityNode Graph { get; set; }
+ public ActivityNode Graph { get; }
+ public Register Register { get; }
public string Id { get; set; }
public IReadOnlyCollection Nodes => new ReadOnlyCollection(_nodes);
public IDictionary NodeIdLookup { get; }
@@ -48,18 +50,18 @@ public class WorkflowExecutionContext
public IActivityScheduler Scheduler { get; }
public Bookmark? Bookmark { get; }
public IReadOnlyDictionary Input { get; }
-
+
///
/// A dictionary that can be used by application code and activities to store information. Values need to be serializable, since this dictionary will be persisted alongside the workflow instance.
///
public IDictionary Properties { get; set; } = new Dictionary();
-
+
///
/// A dictionary that can be used by application code and middleware to store information and even services. Values do not need to be serializable, since this dictionary will not be persisted.
/// All data will be gone once workflow execution completes.
///
public IDictionary TransientProperties { get; set; } = new Dictionary();
-
+
public ExecuteActivityDelegate? ExecuteDelegate { get; set; }
public CancellationToken CancellationToken { get; }
public IReadOnlyCollection Bookmarks => new ReadOnlyCollection(_bookmarks);
@@ -99,7 +101,7 @@ public class WorkflowExecutionContext
RemoveCompletionCallback(entry);
return entry.CompletionCallback;
}
-
+
public void RemoveCompletionCallback(ActivityCompletionCallbackEntry entry) => _completionCallbackEntries.Remove(entry);
public ActivityNode FindNodeById(string nodeId) => NodeIdLookup[nodeId];
@@ -141,8 +143,27 @@ public class WorkflowExecutionContext
var activityInvoker = GetRequiredService();
var workItem = new ActivityWorkItem(bookmarkedActivity.Id, async () => await activityInvoker.InvokeAsync(bookmarkedActivityContext));
Scheduler.Push(workItem);
-
+
// If no resumption point was specified, use Noop to prevent the regular "ExecuteAsync" method to be invoked.
ExecuteDelegate = bookmark.CallbackMethodName != null ? bookmarkedActivity.GetResumeActivityDelegate(bookmark.CallbackMethodName) : Noop;
}
+
+ public T? GetVariable(string name) => (T?)GetVariable(name);
+ public T? GetVariable() => (T?)GetVariable(typeof(T).Name);
+
+ public object? GetVariable(string name)
+ {
+ var variable = Workflow.Variables.FirstOrDefault(x => x.Name == name);
+ return variable?.Get(Register);
+ }
+
+ public Variable SetVariable(T? value) => SetVariable(typeof(T).Name, value);
+ public Variable SetVariable(string name, T? value) => SetVariable(name, (object?)value);
+
+ public Variable SetVariable(string name, object? value)
+ {
+ var variable = Workflow.Variables.FirstOrDefault(x => x.Name == name) ?? new Variable(name, value);
+ variable.Set(Register, value);
+ return variable;
+ }
}
\ No newline at end of file
diff --git a/src/core/Elsa.Core/Pipelines/ActivityExecution/Components/ActivityInvokerMiddleware.cs b/src/core/Elsa.Core/Pipelines/ActivityExecution/Components/ActivityInvokerMiddleware.cs
index b8597f091..e0c65b7c2 100644
--- a/src/core/Elsa.Core/Pipelines/ActivityExecution/Components/ActivityInvokerMiddleware.cs
+++ b/src/core/Elsa.Core/Pipelines/ActivityExecution/Components/ActivityInvokerMiddleware.cs
@@ -64,13 +64,7 @@ public class ActivityInvokerMiddleware : IActivityExecutionMiddleware
await CompleteParentsAsync(context);
}
- private void LogExecutionRecord(ActivityExecutionContext context, string eventName, string? message = default, string? source = default, object? payload = default)
- {
- var activity = context.Activity;
- var workflowExecutionContext = context.WorkflowExecutionContext;
- var logEntry = new WorkflowExecutionLogEntry(activity.Id, activity.TypeName, _clock.UtcNow, eventName, message, source, payload);
- workflowExecutionContext.ExecutionLog.Add(logEntry);
- }
+ private void LogExecutionRecord(ActivityExecutionContext context, string eventName, string? message = default, string? source = default, object? payload = default) => context.AddExecutionLogEntry(eventName, message, source, payload);
private async Task EvaluateInputPropertiesAsync(ActivityExecutionContext context)
{
diff --git a/src/core/Elsa.Core/Services/ActivityInvoker.cs b/src/core/Elsa.Core/Services/ActivityInvoker.cs
index 11a2b1f81..9c6f9eed6 100644
--- a/src/core/Elsa.Core/Services/ActivityInvoker.cs
+++ b/src/core/Elsa.Core/Services/ActivityInvoker.cs
@@ -26,8 +26,7 @@ public class ActivityInvoker : IActivityInvoker
var parentActivityExecutionContext = owner;
// Setup an activity execution context.
- var register = workflowExecutionContext.Workflow.CreateRegister();
-
+ var register = workflowExecutionContext.Register;
var workflow = workflowExecutionContext.Workflow;
var transientProperties = workflowExecutionContext.TransientProperties;
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, register, workflow, transientProperties, parentActivityExecutionContext?.ExpressionExecutionContext, cancellationToken);
diff --git a/src/core/Elsa.Management/Services/ActivityDescriber.cs b/src/core/Elsa.Management/Services/ActivityDescriber.cs
index 84056ed89..c5add7090 100644
--- a/src/core/Elsa.Management/Services/ActivityDescriber.cs
+++ b/src/core/Elsa.Management/Services/ActivityDescriber.cs
@@ -88,13 +88,13 @@ public class ActivityDescriber : IActivityDescriber
wrappedPropertyType,
GetUIHint(wrappedPropertyType, inputAttribute),
inputAttribute?.DisplayName ?? propertyInfo.Name.Humanize(LetterCasing.Title),
- descriptionAttribute?.Description,
+ descriptionAttribute?.Description ?? inputAttribute?.Description,
_optionsResolver.GetOptions(propertyInfo),
inputAttribute?.Category,
inputAttribute?.Order ?? 0,
_defaultValueResolver.GetDefaultValue(propertyInfo),
inputAttribute?.DefaultSyntax,
- //inputAttribute?.SupportedSyntaxes, TODO: Come up with a different way to specify support languages for activity inputs. By default, maybe all pros should support all registered scripting languages?
+ //inputAttribute?.SupportedSyntaxes, TODO: Come up with a different way to specify support languages for activity inputs. By default, maybe all props should support all registered scripting languages?
inputAttribute?.IsReadOnly ?? false,
inputAttribute?.IsBrowsable ?? true
);
diff --git a/src/modules/Elsa.Modules.WorkflowContexts/Extensions/WorkflowExecutionContextExtensions.cs b/src/modules/Elsa.Modules.WorkflowContexts/Extensions/WorkflowExecutionContextExtensions.cs
new file mode 100644
index 000000000..2c4bfe843
--- /dev/null
+++ b/src/modules/Elsa.Modules.WorkflowContexts/Extensions/WorkflowExecutionContextExtensions.cs
@@ -0,0 +1,27 @@
+using Elsa.Extensions;
+using Elsa.Models;
+using Elsa.Modules.WorkflowContexts.Models;
+
+namespace Elsa.Modules.WorkflowContexts.Extensions;
+
+public static class WorkflowExecutionContextExtensions
+{
+ public static void SetWorkflowContext(this WorkflowExecutionContext workflowExecutionContext, WorkflowContext workflowContext, object? value) => workflowExecutionContext.TransientProperties.SetWorkflowContext(workflowContext, value);
+ public static void SetWorkflowContext(this ExpressionExecutionContext expressionExecutionContext, WorkflowContext workflowContext, object? value) => expressionExecutionContext.TransientProperties.SetWorkflowContext(workflowContext, value);
+ public static T? GetWorkflowContext(this WorkflowExecutionContext workflowExecutionContext) => (T?)workflowExecutionContext.GetWorkflowContext(new WorkflowContext(typeof(TProvider)));
+ public static T? GetWorkflowContext(this ExpressionExecutionContext expressionExecutionContext) => (T?)expressionExecutionContext.TransientProperties.GetWorkflowContext(new WorkflowContext(typeof(TProvider)));
+ public static object? GetWorkflowContext(this WorkflowExecutionContext workflowExecutionContext, WorkflowContext workflowContext) => workflowExecutionContext.TransientProperties.GetWorkflowContext(workflowContext);
+ public static object? GetWorkflowContext(this ExpressionExecutionContext expressionExecutionContext, WorkflowContext workflowContext) => expressionExecutionContext.TransientProperties.GetWorkflowContext(workflowContext);
+
+ private static void SetWorkflowContext(this IDictionary transientProperties, WorkflowContext workflowContext, object? value)
+ {
+ var contextDictionary = transientProperties.GetOrAdd("WorkflowContexts", () => new Dictionary());
+ contextDictionary[workflowContext.ProviderType] = value;
+ }
+
+ private static object? GetWorkflowContext(this IDictionary transientProperties, WorkflowContext workflowContext)
+ {
+ var contextDictionary = transientProperties.GetOrAdd("WorkflowContexts", () => new Dictionary());
+ return contextDictionary[workflowContext.ProviderType];
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Modules.WorkflowContexts/Middleware/WorkflowContextMiddleware.cs b/src/modules/Elsa.Modules.WorkflowContexts/Middleware/WorkflowContextMiddleware.cs
index 04e05b926..9796ef7fa 100644
--- a/src/modules/Elsa.Modules.WorkflowContexts/Middleware/WorkflowContextMiddleware.cs
+++ b/src/modules/Elsa.Modules.WorkflowContexts/Middleware/WorkflowContextMiddleware.cs
@@ -1,6 +1,7 @@
using Elsa.Extensions;
using Elsa.Models;
using Elsa.Modules.WorkflowContexts.Contracts;
+using Elsa.Modules.WorkflowContexts.Extensions;
using Elsa.Modules.WorkflowContexts.Models;
using Elsa.Pipelines.WorkflowExecution;
using Elsa.Pipelines.WorkflowExecution.Components;
@@ -36,8 +37,7 @@ public class WorkflowContextMiddleware : WorkflowExecutionMiddleware
var value = await provider.LoadAsync(context);
// Store the loaded value into the workflow execution context.
- var contextDictionary = context.TransientProperties.GetOrAdd("WorkflowContexts", () => new Dictionary());
- contextDictionary.Add(workflowContext, value);
+ context.SetWorkflowContext(workflowContext, value);
}
// Invoke the next middleware.
@@ -47,8 +47,7 @@ public class WorkflowContextMiddleware : WorkflowExecutionMiddleware
foreach (var workflowContext in workflowContexts!)
{
// Get the loaded value from the workflow execution context.
- var contextDictionary = context.TransientProperties.GetOrAdd("WorkflowContexts", () => new Dictionary());
- var value = contextDictionary[workflowContext];
+ var value = context.GetWorkflowContext(workflowContext);
var provider = (IWorkflowContextProvider)ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, workflowContext.ProviderType);
await provider.SaveAsync(context, value);
diff --git a/src/persistence/Elsa.Persistence.Abstractions/Requests/FindWorkflowBookmark.cs b/src/persistence/Elsa.Persistence.Abstractions/Requests/FindWorkflowBookmark.cs
new file mode 100644
index 000000000..db0c5866e
--- /dev/null
+++ b/src/persistence/Elsa.Persistence.Abstractions/Requests/FindWorkflowBookmark.cs
@@ -0,0 +1,6 @@
+using Elsa.Mediator.Contracts;
+using Elsa.Persistence.Entities;
+
+namespace Elsa.Persistence.Requests;
+
+public record FindWorkflowBookmark(string BookmarkId) : IRequest;
\ No newline at end of file
diff --git a/src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Requests/FindWorkflowBookmarkHandler.cs b/src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Requests/FindWorkflowBookmarkHandler.cs
new file mode 100644
index 000000000..f4e51e778
--- /dev/null
+++ b/src/persistence/Elsa.Persistence.EntityFrameworkCore/Handlers/Requests/FindWorkflowBookmarkHandler.cs
@@ -0,0 +1,13 @@
+using Elsa.Mediator.Contracts;
+using Elsa.Persistence.Entities;
+using Elsa.Persistence.EntityFrameworkCore.Contracts;
+using Elsa.Persistence.Requests;
+
+namespace Elsa.Persistence.EntityFrameworkCore.Handlers.Requests;
+
+public class FindWorkflowBookmarkHandler : IRequestHandler
+{
+ private readonly IStore _store;
+ public FindWorkflowBookmarkHandler(IStore store) => _store = store;
+ public async Task HandleAsync(FindWorkflowBookmark request, CancellationToken cancellationToken) => await _store.FindAsync(x => x.Id == request.BookmarkId, cancellationToken);
+}
\ No newline at end of file
diff --git a/src/persistence/Elsa.Persistence.InMemory/Handlers/Requests/FindWorkflowBookmarkHandler.cs b/src/persistence/Elsa.Persistence.InMemory/Handlers/Requests/FindWorkflowBookmarkHandler.cs
new file mode 100644
index 000000000..86d7c7e7c
--- /dev/null
+++ b/src/persistence/Elsa.Persistence.InMemory/Handlers/Requests/FindWorkflowBookmarkHandler.cs
@@ -0,0 +1,16 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Elsa.Mediator.Contracts;
+using Elsa.Persistence.Entities;
+using Elsa.Persistence.InMemory.Services;
+using Elsa.Persistence.Requests;
+
+namespace Elsa.Persistence.InMemory.Handlers.Requests;
+
+public class FindWorkflowBookmarkHandler : IRequestHandler
+{
+ private readonly InMemoryStore _store;
+ public FindWorkflowBookmarkHandler(InMemoryStore store) => _store = store;
+ public Task HandleAsync(FindWorkflowBookmark request, CancellationToken cancellationToken) => Task.FromResult(FindById(request.BookmarkId));
+ private WorkflowBookmark? FindById(string id) => _store.Find(x => x.Id == id);
+}
\ No newline at end of file
diff --git a/src/samples/aspnet/Elsa.Samples.Web1/Workflows/WorkflowContextsWorkflow.cs b/src/samples/aspnet/Elsa.Samples.Web1/Workflows/WorkflowContextsWorkflow.cs
index 3b423e342..ee8f2e85d 100644
--- a/src/samples/aspnet/Elsa.Samples.Web1/Workflows/WorkflowContextsWorkflow.cs
+++ b/src/samples/aspnet/Elsa.Samples.Web1/Workflows/WorkflowContextsWorkflow.cs
@@ -4,7 +4,6 @@ using Elsa.Contracts;
using Elsa.Models;
using Elsa.Modules.WorkflowContexts.Abstractions;
using Elsa.Modules.WorkflowContexts.Extensions;
-using Elsa.Modules.WorkflowContexts.Models;
using Elsa.Runtime.Contracts;
namespace Elsa.Samples.Web1.Workflows;