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/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/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 +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.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..a46f191ed 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; 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/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/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/ValidateWorkflowRequest.cs similarity index 50% rename from src/modules/Elsa.Workflows.Management/Requests/ValidateWorkflowRequest.cs rename to src/modules/Elsa.Workflows.Management/Notifications/ValidateWorkflowRequest.cs index 13eeb3899..967c56686 100644 --- a/src/modules/Elsa.Workflows.Management/Requests/ValidateWorkflowRequest.cs +++ b/src/modules/Elsa.Workflows.Management/Notifications/ValidateWorkflowRequest.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/WorkflowDefinitionPublisher.cs b/src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs index 0cdc9491e..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 = 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/WorkflowValidator.cs b/src/modules/Elsa.Workflows.Management/Services/WorkflowValidator.cs index b8c4b7efa..ed43c2bad 100644 --- a/src/modules/Elsa.Workflows.Management/Services/WorkflowValidator.cs +++ b/src/modules/Elsa.Workflows.Management/Services/WorkflowValidator.cs @@ -1,27 +1,20 @@ using Elsa.Mediator.Contracts; using Elsa.Workflows.Activities; using Elsa.Workflows.Management.Models; +using Elsa.Workflows.Management.Notifications; using Elsa.Workflows.Management.Requests; 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