diff --git a/src/Squidex.Domain.Apps.Write/Apps/AppCommandHandler.cs b/src/Squidex.Domain.Apps.Write/Apps/AppCommandHandler.cs index e87f82be7..0fd12f68e 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/AppCommandHandler.cs +++ b/src/Squidex.Domain.Apps.Write/Apps/AppCommandHandler.cs @@ -6,6 +6,7 @@ // All rights reserved. // ========================================================================== +using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Read.Apps.Repositories; using Squidex.Domain.Apps.Read.Apps.Services; @@ -13,7 +14,6 @@ using Squidex.Domain.Apps.Write.Apps.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.Dispatching; -using Squidex.Infrastructure.Tasks; using Squidex.Shared.Users; // ReSharper disable InvertIf @@ -63,7 +63,7 @@ namespace Squidex.Domain.Apps.Write.Apps { a.Create(command); - context.Succeed(EntityCreatedResult.Create(a.Id, a.Version)); + context.Complete(EntityCreatedResult.Create(a.Id, a.Version)); }); } @@ -120,7 +120,7 @@ namespace Squidex.Domain.Apps.Write.Apps a.ChangePlan(command); } - context.Succeed(result); + context.Complete(result); } }); } @@ -160,9 +160,12 @@ namespace Squidex.Domain.Apps.Write.Apps return handler.UpdateAsync(context, a => a.UpdateLanguage(command)); } - public Task HandleAsync(CommandContext context) + public async Task HandleAsync(CommandContext context, Func next) { - return context.IsHandled ? TaskHelper.False : this.DispatchActionAsync(context.Command, context); + if (!await this.DispatchActionAsync(context.Command, context)) + { + await next(); + } } } } diff --git a/src/Squidex.Domain.Apps.Write/Assets/AssetCommandHandler.cs b/src/Squidex.Domain.Apps.Write/Assets/AssetCommandHandler.cs index 2fae5d038..c80b2b468 100644 --- a/src/Squidex.Domain.Apps.Write/Assets/AssetCommandHandler.cs +++ b/src/Squidex.Domain.Apps.Write/Assets/AssetCommandHandler.cs @@ -6,13 +6,13 @@ // All rights reserved. // ========================================================================== +using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Write.Assets.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.Dispatching; -using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Write.Assets { @@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Write.Assets await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), command.File.OpenRead()); - context.Succeed(EntityCreatedResult.Create(a.Id, a.Version)); + context.Complete(EntityCreatedResult.Create(a.Id, a.Version)); }); await assetStore.CopyTemporaryAsync(context.ContextId.ToString(), asset.Id.ToString(), asset.FileVersion, null); @@ -89,9 +89,12 @@ namespace Squidex.Domain.Apps.Write.Assets return handler.UpdateAsync(context, a => a.Delete(command)); } - public Task HandleAsync(CommandContext context) + public async Task HandleAsync(CommandContext context, Func next) { - return context.IsHandled ? TaskHelper.False : this.DispatchActionAsync(context.Command, context); + if (!await this.DispatchActionAsync(context.Command, context)) + { + await next(); + } } } } diff --git a/src/Squidex.Domain.Apps.Write/Contents/ContentCommandHandler.cs b/src/Squidex.Domain.Apps.Write/Contents/ContentCommandHandler.cs index eb55ac1ca..797f88f3b 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/ContentCommandHandler.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/ContentCommandHandler.cs @@ -20,7 +20,6 @@ using Squidex.Domain.Apps.Write.Contents.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.Dispatching; -using Squidex.Infrastructure.Tasks; // ReSharper disable ConvertToLambdaExpression @@ -63,7 +62,7 @@ namespace Squidex.Domain.Apps.Write.Contents { c.Create(command); - context.Succeed(EntityCreatedResult.Create(command.Data, c.Version)); + context.Complete(EntityCreatedResult.Create(command.Data, c.Version)); }); } @@ -96,9 +95,12 @@ namespace Squidex.Domain.Apps.Write.Contents return handler.UpdateAsync(context, c => c.Delete(command)); } - public Task HandleAsync(CommandContext context) + public async Task HandleAsync(CommandContext context, Func next) { - return context.IsHandled ? TaskHelper.False : this.DispatchActionAsync(context.Command, context); + if (!await this.DispatchActionAsync(context.Command, context)) + { + await next(); + } } private async Task ValidateAsync(ContentDataCommand command, Func message, bool enrich = false) diff --git a/src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandHandler.cs b/src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandHandler.cs index 6a10c3072..248bdc341 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandHandler.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandHandler.cs @@ -6,6 +6,7 @@ // All rights reserved. // ========================================================================== +using System; using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Read.Schemas.Services; @@ -13,7 +14,6 @@ using Squidex.Domain.Apps.Write.Schemas.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.Dispatching; -using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Write.Schemas { @@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Write.Schemas { s.Create(command); - context.Succeed(EntityCreatedResult.Create(s.Id, s.Version)); + context.Complete(EntityCreatedResult.Create(s.Id, s.Version)); }); } @@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Write.Schemas { s.AddField(command); - context.Succeed(EntityCreatedResult.Create(s.Schema.FieldsById.Values.First(x => x.Name == command.Name).Id, s.Version)); + context.Complete(EntityCreatedResult.Create(s.Schema.FieldsById.Values.First(x => x.Name == command.Name).Id, s.Version)); }); } @@ -125,9 +125,12 @@ namespace Squidex.Domain.Apps.Write.Schemas return handler.UpdateAsync(context, s => s.Unpublish(command)); } - public Task HandleAsync(CommandContext context) + public async Task HandleAsync(CommandContext context, Func next) { - return context.IsHandled ? TaskHelper.False : this.DispatchActionAsync(context.Command, context); + if (!await this.DispatchActionAsync(context.Command, context)) + { + await next(); + } } } } diff --git a/src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs b/src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs index f94f36a83..654a4f331 100644 --- a/src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs +++ b/src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs @@ -50,9 +50,9 @@ namespace Squidex.Infrastructure.CQRS.Commands await SaveAsync(aggregate); - if (!context.IsHandled) + if (!context.IsCompleted) { - context.Succeed(new EntityCreatedResult(aggregate.Id, aggregate.Version)); + context.Complete(new EntityCreatedResult(aggregate.Id, aggregate.Version)); } return aggregate; @@ -70,9 +70,9 @@ namespace Squidex.Infrastructure.CQRS.Commands await SaveAsync(aggregate); - if (!context.IsHandled) + if (!context.IsCompleted) { - context.Succeed(new EntitySavedResult(aggregate.Version)); + context.Complete(new EntitySavedResult(aggregate.Version)); } return aggregate; diff --git a/src/Squidex.Infrastructure/CQRS/Commands/CommandContext.cs b/src/Squidex.Infrastructure/CQRS/Commands/CommandContext.cs index 371d686af..1c89e9cf2 100644 --- a/src/Squidex.Infrastructure/CQRS/Commands/CommandContext.cs +++ b/src/Squidex.Infrastructure/CQRS/Commands/CommandContext.cs @@ -14,7 +14,6 @@ namespace Squidex.Infrastructure.CQRS.Commands { private readonly ICommand command; private readonly Guid contextId = Guid.NewGuid(); - private Exception exception; private Tuple result; public ICommand Command @@ -22,31 +21,16 @@ namespace Squidex.Infrastructure.CQRS.Commands get { return command; } } - public bool IsHandled - { - get { return IsSucceeded || IsFailed; } - } - - public bool IsFailed + public Guid ContextId { - get { return exception != null; } + get { return contextId; } } - public bool IsSucceeded + public bool IsCompleted { get { return result != null; } } - public Exception Exception - { - get { return exception; } - } - - public Guid ContextId - { - get { return contextId; } - } - public CommandContext(ICommand command) { Guard.NotNull(command, nameof(command)); @@ -54,26 +38,11 @@ namespace Squidex.Infrastructure.CQRS.Commands this.command = command; } - public void Succeed(object resultValue = null) + public void Complete(object resultValue = null) { - if (IsHandled) - { - return; - } - result = Tuple.Create(resultValue); } - public void Fail(Exception handlerException) - { - if (IsFailed) - { - return; - } - - exception = handlerException; - } - public T Result() { return (T)result?.Item1; diff --git a/src/Squidex.Infrastructure/CQRS/Commands/CommandingExtensions.cs b/src/Squidex.Infrastructure/CQRS/Commands/CommandingExtensions.cs index c07288c22..b515a8d2c 100644 --- a/src/Squidex.Infrastructure/CQRS/Commands/CommandingExtensions.cs +++ b/src/Squidex.Infrastructure/CQRS/Commands/CommandingExtensions.cs @@ -33,5 +33,10 @@ namespace Squidex.Infrastructure.CQRS.Commands return TaskHelper.Done; }); } + + public static Task HandleAsync(this ICommandHandler commandHandler, CommandContext context) + { + return commandHandler.HandleAsync(context, () => TaskHelper.Done); + } } } diff --git a/src/Squidex.Infrastructure/CQRS/Commands/EnrichWithTimestampHandler.cs b/src/Squidex.Infrastructure/CQRS/Commands/EnrichWithTimestampCommandHandler.cs similarity index 84% rename from src/Squidex.Infrastructure/CQRS/Commands/EnrichWithTimestampHandler.cs rename to src/Squidex.Infrastructure/CQRS/Commands/EnrichWithTimestampCommandHandler.cs index 855c11865..edbb01693 100644 --- a/src/Squidex.Infrastructure/CQRS/Commands/EnrichWithTimestampHandler.cs +++ b/src/Squidex.Infrastructure/CQRS/Commands/EnrichWithTimestampCommandHandler.cs @@ -1,14 +1,14 @@ // ========================================================================== -// EnrichWithTimestampHandler.cs +// EnrichWithTimestampCommandHandler.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== +using System; using System.Threading.Tasks; using NodaTime; -using Squidex.Infrastructure.Tasks; namespace Squidex.Infrastructure.CQRS.Commands { @@ -23,14 +23,14 @@ namespace Squidex.Infrastructure.CQRS.Commands this.clock = clock; } - public Task HandleAsync(CommandContext context) + public Task HandleAsync(CommandContext context, Func next) { if (context.Command is ITimestampCommand timestampCommand) { timestampCommand.Timestamp = clock.GetCurrentInstant(); } - return TaskHelper.False; + return next(); } } } diff --git a/src/Squidex.Infrastructure/CQRS/Commands/ICommandHandler.cs b/src/Squidex.Infrastructure/CQRS/Commands/ICommandHandler.cs index 497721b3c..ff0053330 100644 --- a/src/Squidex.Infrastructure/CQRS/Commands/ICommandHandler.cs +++ b/src/Squidex.Infrastructure/CQRS/Commands/ICommandHandler.cs @@ -6,12 +6,13 @@ // All rights reserved. // ========================================================================== +using System; using System.Threading.Tasks; namespace Squidex.Infrastructure.CQRS.Commands { public interface ICommandHandler { - Task HandleAsync(CommandContext context); + Task HandleAsync(CommandContext context, Func next); } } diff --git a/src/Squidex.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs b/src/Squidex.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs index 5985cd2aa..16483acbd 100644 --- a/src/Squidex.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs +++ b/src/Squidex.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs @@ -8,19 +8,21 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using Squidex.Infrastructure.Tasks; namespace Squidex.Infrastructure.CQRS.Commands { public sealed class InMemoryCommandBus : ICommandBus { - private readonly IEnumerable handlers; + private readonly List handlers; public InMemoryCommandBus(IEnumerable handlers) { Guard.NotNull(handlers, nameof(handlers)); - this.handlers = handlers; + this.handlers = handlers.Reverse().ToList(); } public async Task PublishAsync(ICommand command) @@ -29,29 +31,21 @@ namespace Squidex.Infrastructure.CQRS.Commands var context = new CommandContext(command); + var next = new Func(() => TaskHelper.Done); + foreach (var handler in handlers) { - try - { - var isHandled = await handler.HandleAsync(context); - - if (isHandled) - { - context.Succeed(); - } - } - catch (Exception ex) - { - context.Fail(ex); - } + next = Join(handler, context, next); } - if (context.Exception != null) - { - throw context.Exception; - } + await next(); return context; } + + private static Func Join(ICommandHandler handler, CommandContext context, Func next) + { + return () => handler.HandleAsync(context, next); + } } } \ No newline at end of file diff --git a/src/Squidex.Infrastructure/CQRS/Commands/LogCommandHandler.cs b/src/Squidex.Infrastructure/CQRS/Commands/LogCommandHandler.cs new file mode 100644 index 000000000..e1555503c --- /dev/null +++ b/src/Squidex.Infrastructure/CQRS/Commands/LogCommandHandler.cs @@ -0,0 +1,72 @@ +// ========================================================================== +// LogExecutingHandler.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Squidex.Infrastructure.Log; + +namespace Squidex.Infrastructure.CQRS.Commands +{ + public sealed class LogCommandHandler : ICommandHandler + { + private readonly ISemanticLog log; + + public LogCommandHandler(ISemanticLog log) + { + Guard.NotNull(log, nameof(log)); + + this.log = log; + } + + public async Task HandleAsync(CommandContext context, Func next) + { + try + { + log.LogInformation(w => w + .WriteProperty("action", "HandleCommand.") + .WriteProperty("actionId", context.ContextId.ToString()) + .WriteProperty("state", "Started") + .WriteProperty("commandType", context.Command.GetType().Name)); + + using (log.MeasureInformation(w => w + .WriteProperty("action", "HandleCommand.") + .WriteProperty("actionId", context.ContextId.ToString()) + .WriteProperty("state", "Completed") + .WriteProperty("commandType", context.Command.GetType().Name))) + { + await next(); + } + + log.LogInformation(w => w + .WriteProperty("action", "HandleCommand.") + .WriteProperty("actionId", context.ContextId.ToString()) + .WriteProperty("state", "Succeeded") + .WriteProperty("commandType", context.Command.GetType().Name)); + } + catch (Exception ex) + { + log.LogError(ex, w => w + .WriteProperty("action", "HandleCommand.") + .WriteProperty("actionId", context.ContextId.ToString()) + .WriteProperty("state", "Failed") + .WriteProperty("commandType", context.Command.GetType().Name)); + + throw; + } + + if (!context.IsCompleted) + { + log.LogFatal(w => w + .WriteProperty("action", "HandleCommand.") + .WriteProperty("actionId", context.ContextId.ToString()) + .WriteProperty("state", "Unhandled") + .WriteProperty("commandType", context.Command.GetType().Name)); + } + } + } +} diff --git a/src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs b/src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs deleted file mode 100644 index 80efac561..000000000 --- a/src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs +++ /dev/null @@ -1,53 +0,0 @@ -// ========================================================================== -// LogExceptionHandler.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Threading.Tasks; -using Squidex.Infrastructure.Log; -using Squidex.Infrastructure.Tasks; - -// ReSharper disable InvertIf - -namespace Squidex.Infrastructure.CQRS.Commands -{ - public sealed class LogExceptionHandler : ICommandHandler - { - private readonly ISemanticLog log; - - public LogExceptionHandler(ISemanticLog log) - { - Guard.NotNull(log, nameof(log)); - - this.log = log; - } - - public Task HandleAsync(CommandContext context) - { - var exception = context.Exception; - - if (exception != null) - { - log.LogError(exception, w => w - .WriteProperty("action", "HandleCommand.") - .WriteProperty("actionId", context.ContextId.ToString()) - .WriteProperty("state", "Failed") - .WriteProperty("commandType", context.Command.GetType().Name)); - } - - if (!context.IsHandled) - { - log.LogFatal(exception, w => w - .WriteProperty("action", "HandleCommand.") - .WriteProperty("actionId", context.ContextId.ToString()) - .WriteProperty("state", "Unhandled") - .WriteProperty("commandType", context.Command.GetType().Name)); - } - - return TaskHelper.False; - } - } -} diff --git a/src/Squidex.Infrastructure/CQRS/Commands/LogExecutingHandler.cs b/src/Squidex.Infrastructure/CQRS/Commands/LogExecutingHandler.cs deleted file mode 100644 index 646c97262..000000000 --- a/src/Squidex.Infrastructure/CQRS/Commands/LogExecutingHandler.cs +++ /dev/null @@ -1,37 +0,0 @@ -// ========================================================================== -// LogExecutingHandler.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Threading.Tasks; -using Squidex.Infrastructure.Log; -using Squidex.Infrastructure.Tasks; - -namespace Squidex.Infrastructure.CQRS.Commands -{ - public sealed class LogExecutingHandler : ICommandHandler - { - private readonly ISemanticLog log; - - public LogExecutingHandler(ISemanticLog log) - { - Guard.NotNull(log, nameof(log)); - - this.log = log; - } - - public Task HandleAsync(CommandContext context) - { - log.LogInformation(w => w - .WriteProperty("action", "HandleCommand.") - .WriteProperty("actionId", context.ContextId.ToString()) - .WriteProperty("state", "Started") - .WriteProperty("commandType", context.Command.GetType().Name)); - - return TaskHelper.False; - } - } -} diff --git a/src/Squidex/Config/Domain/WriteModule.cs b/src/Squidex/Config/Domain/WriteModule.cs index 93d9d4e6f..985d39fab 100644 --- a/src/Squidex/Config/Domain/WriteModule.cs +++ b/src/Squidex/Config/Domain/WriteModule.cs @@ -31,7 +31,7 @@ namespace Squidex.Config.Domain protected override void Load(ContainerBuilder builder) { - builder.RegisterType() + builder.RegisterType() .As() .SingleInstance(); @@ -39,15 +39,15 @@ namespace Squidex.Config.Domain .As() .SingleInstance(); - builder.RegisterType() + builder.RegisterType() .As() .SingleInstance(); - builder.RegisterType() + builder.RegisterType() .As() .SingleInstance(); - builder.RegisterType() + builder.RegisterType() .As() .SingleInstance(); @@ -71,7 +71,7 @@ namespace Squidex.Config.Domain .As() .SingleInstance(); - builder.RegisterType() + builder.RegisterType() .As() .SingleInstance(); diff --git a/src/Squidex/Pipeline/CommandHandlers/EnrichWithExpectedVersionHandler.cs b/src/Squidex/Pipeline/CommandHandlers/ETagCommandHandler.cs similarity index 66% rename from src/Squidex/Pipeline/CommandHandlers/EnrichWithExpectedVersionHandler.cs rename to src/Squidex/Pipeline/CommandHandlers/ETagCommandHandler.cs index 937cbd0c4..88a126094 100644 --- a/src/Squidex/Pipeline/CommandHandlers/EnrichWithExpectedVersionHandler.cs +++ b/src/Squidex/Pipeline/CommandHandlers/ETagCommandHandler.cs @@ -1,29 +1,30 @@ // ========================================================================== -// EnrichWithExpectedVersionHandler.cs +// ETagCommandHandler.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== +using System; using System.Globalization; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; using Squidex.Infrastructure.CQRS.Commands; -using Squidex.Infrastructure.Tasks; namespace Squidex.Pipeline.CommandHandlers { - public sealed class EnrichWithExpectedVersionHandler : ICommandHandler + public class ETagCommandHandler : ICommandHandler { private readonly IHttpContextAccessor httpContextAccessor; - public EnrichWithExpectedVersionHandler(IHttpContextAccessor httpContextAccessor) + public ETagCommandHandler(IHttpContextAccessor httpContextAccessor) { this.httpContextAccessor = httpContextAccessor; } - public Task HandleAsync(CommandContext context) + public async Task HandleAsync(CommandContext context, Func next) { var headers = httpContextAccessor.HttpContext.Request.Headers; var headerMatch = headers["If-Match"].ToString(); @@ -33,7 +34,12 @@ namespace Squidex.Pipeline.CommandHandlers context.Command.ExpectedVersion = expectedVersion; } - return TaskHelper.False; + await next(); + + if (context.Result() is EntitySavedResult result) + { + httpContextAccessor.HttpContext.Response.Headers["ETag"] = new StringValues(result.Version.ToString()); + } } } } diff --git a/src/Squidex/Pipeline/CommandHandlers/EnrichWithActorHandler.cs b/src/Squidex/Pipeline/CommandHandlers/EnrichWithActorCommandHandler.cs similarity index 85% rename from src/Squidex/Pipeline/CommandHandlers/EnrichWithActorHandler.cs rename to src/Squidex/Pipeline/CommandHandlers/EnrichWithActorCommandHandler.cs index 926987941..cea876f17 100644 --- a/src/Squidex/Pipeline/CommandHandlers/EnrichWithActorHandler.cs +++ b/src/Squidex/Pipeline/CommandHandlers/EnrichWithActorCommandHandler.cs @@ -1,11 +1,12 @@ // ========================================================================== -// EnrichWithActorHandler.cs +// EnrichWithActorCommandHandler.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== +using System; using System.Security; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -13,22 +14,21 @@ using Squidex.Domain.Apps.Write; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.Security; -using Squidex.Infrastructure.Tasks; // ReSharper disable InvertIf namespace Squidex.Pipeline.CommandHandlers { - public class EnrichWithActorHandler : ICommandHandler + public class EnrichWithActorCommandHandler : ICommandHandler { private readonly IHttpContextAccessor httpContextAccessor; - public EnrichWithActorHandler(IHttpContextAccessor httpContextAccessor) + public EnrichWithActorCommandHandler(IHttpContextAccessor httpContextAccessor) { this.httpContextAccessor = httpContextAccessor; } - public Task HandleAsync(CommandContext context) + public Task HandleAsync(CommandContext context, Func next) { if (context.Command is SquidexCommand squidexCommand && squidexCommand.Actor == null) { @@ -46,7 +46,7 @@ namespace Squidex.Pipeline.CommandHandlers squidexCommand.Actor = actorToken; } - return TaskHelper.False; + return next(); } private RefToken FindActorFromSubject() diff --git a/src/Squidex/Pipeline/CommandHandlers/EnrichWithAppIdHandler.cs b/src/Squidex/Pipeline/CommandHandlers/EnrichWithAppIdCommandHandler.cs similarity index 81% rename from src/Squidex/Pipeline/CommandHandlers/EnrichWithAppIdHandler.cs rename to src/Squidex/Pipeline/CommandHandlers/EnrichWithAppIdCommandHandler.cs index 99541301b..ef9542dfd 100644 --- a/src/Squidex/Pipeline/CommandHandlers/EnrichWithAppIdHandler.cs +++ b/src/Squidex/Pipeline/CommandHandlers/EnrichWithAppIdCommandHandler.cs @@ -12,22 +12,21 @@ using Microsoft.AspNetCore.Http; using Squidex.Domain.Apps.Write; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Commands; -using Squidex.Infrastructure.Tasks; // ReSharper disable InvertIf namespace Squidex.Pipeline.CommandHandlers { - public sealed class EnrichWithAppIdHandler : ICommandHandler + public sealed class EnrichWithAppIdCommandHandler : ICommandHandler { private readonly IHttpContextAccessor httpContextAccessor; - public EnrichWithAppIdHandler(IHttpContextAccessor httpContextAccessor) + public EnrichWithAppIdCommandHandler(IHttpContextAccessor httpContextAccessor) { this.httpContextAccessor = httpContextAccessor; } - public Task HandleAsync(CommandContext context) + public Task HandleAsync(CommandContext context, Func next) { if (context.Command is AppCommand appCommand && appCommand.AppId == null) { @@ -41,7 +40,7 @@ namespace Squidex.Pipeline.CommandHandlers appCommand.AppId = new NamedId(appFeature.App.Id, appFeature.App.Name); } - return TaskHelper.False; + return next(); } } } diff --git a/src/Squidex/Pipeline/CommandHandlers/EnrichWithSchemaIdHandler.cs b/src/Squidex/Pipeline/CommandHandlers/EnrichWithSchemaIdCommandHandler.cs similarity index 87% rename from src/Squidex/Pipeline/CommandHandlers/EnrichWithSchemaIdHandler.cs rename to src/Squidex/Pipeline/CommandHandlers/EnrichWithSchemaIdCommandHandler.cs index 0b59d46cb..bed6d1f92 100644 --- a/src/Squidex/Pipeline/CommandHandlers/EnrichWithSchemaIdHandler.cs +++ b/src/Squidex/Pipeline/CommandHandlers/EnrichWithSchemaIdCommandHandler.cs @@ -20,19 +20,19 @@ using Squidex.Infrastructure.CQRS.Commands; namespace Squidex.Pipeline.CommandHandlers { - public sealed class EnrichWithSchemaIdHandler : ICommandHandler + public sealed class EnrichWithSchemaIdCommandHandler : ICommandHandler { private readonly ISchemaProvider schemas; private readonly IActionContextAccessor actionContextAccessor; - public EnrichWithSchemaIdHandler(ISchemaProvider schemas, IActionContextAccessor actionContextAccessor) + public EnrichWithSchemaIdCommandHandler(ISchemaProvider schemas, IActionContextAccessor actionContextAccessor) { this.schemas = schemas; this.actionContextAccessor = actionContextAccessor; } - public async Task HandleAsync(CommandContext context) + public async Task HandleAsync(CommandContext context, Func next) { if (context.Command is SchemaCommand schemaCommand && schemaCommand.SchemaId == null) { @@ -62,7 +62,7 @@ namespace Squidex.Pipeline.CommandHandlers } } - return false; + await next(); } } } diff --git a/src/Squidex/Pipeline/CommandHandlers/SetVersionAsETagHandler.cs b/src/Squidex/Pipeline/CommandHandlers/SetVersionAsETagHandler.cs deleted file mode 100644 index 74d01c118..000000000 --- a/src/Squidex/Pipeline/CommandHandlers/SetVersionAsETagHandler.cs +++ /dev/null @@ -1,36 +0,0 @@ -// ========================================================================== -// SetVersionAsETagHandler.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; -using Squidex.Infrastructure.CQRS.Commands; -using Squidex.Infrastructure.Tasks; - -namespace Squidex.Pipeline.CommandHandlers -{ - public class SetVersionAsETagHandler : ICommandHandler - { - private readonly IHttpContextAccessor httpContextAccessor; - - public SetVersionAsETagHandler(IHttpContextAccessor httpContextAccessor) - { - this.httpContextAccessor = httpContextAccessor; - } - - public Task HandleAsync(CommandContext context) - { - if (context.Result() is EntitySavedResult result) - { - httpContextAccessor.HttpContext.Response.Headers["ETag"] = new StringValues(result.Version.ToString()); - } - - return TaskHelper.False; - } - } -} diff --git a/tests/Squidex.Infrastructure.Tests/CQRS/Commands/CommandContextTests.cs b/tests/Squidex.Infrastructure.Tests/CQRS/Commands/CommandContextTests.cs index 4120415b6..49b506c97 100644 --- a/tests/Squidex.Infrastructure.Tests/CQRS/Commands/CommandContextTests.cs +++ b/tests/Squidex.Infrastructure.Tests/CQRS/Commands/CommandContextTests.cs @@ -22,40 +22,8 @@ namespace Squidex.Infrastructure.CQRS.Commands var sut = new CommandContext(command); Assert.Equal(command, sut.Command); - Assert.Null(sut.Exception); - Assert.False(sut.IsHandled); - Assert.False(sut.IsSucceeded); - Assert.False(sut.IsFailed); - } - - [Fact] - public void Should_provide_exception_when_failed() - { - var exc = new InvalidOperationException(); - var sut = new CommandContext(command); - - sut.Fail(exc); - - Assert.Equal(exc, sut.Exception); - Assert.True(sut.IsHandled); - Assert.True(sut.IsFailed); - Assert.False(sut.IsSucceeded); - } - - [Fact] - public void Should_not_update_exception_when_failed() - { - var exc1 = new InvalidOperationException(); - var exc2 = new InvalidOperationException(); - var sut = new CommandContext(command); - - sut.Fail(exc1); - sut.Fail(exc2); - - Assert.Equal(exc1, sut.Exception); - Assert.True(sut.IsHandled); - Assert.True(sut.IsFailed); - Assert.False(sut.IsSucceeded); + Assert.False(sut.IsCompleted); + Assert.NotEqual(Guid.Empty, sut.ContextId); } [Fact] @@ -63,54 +31,17 @@ namespace Squidex.Infrastructure.CQRS.Commands { var sut = new CommandContext(command); - sut.Succeed(); + sut.Complete(); - Assert.Null(sut.Exception); - Assert.True(sut.IsSucceeded); - Assert.True(sut.IsHandled); - Assert.False(sut.IsFailed); - } - - [Fact] - public void Should_replace_status_when_already_succeeded() - { - var sut = new CommandContext(command); - - sut.Succeed(Guid.NewGuid()); - sut.Fail(new Exception()); - - Assert.NotNull(sut.Exception); - Assert.True(sut.IsHandled); - Assert.True(sut.IsFailed); - Assert.True(sut.IsSucceeded); + Assert.True(sut.IsCompleted); } [Fact] public void Should_provide_result_valid_when_succeeded_with_value() - { - var guid = Guid.NewGuid(); - var sut = new CommandContext(command); - - sut.Succeed(guid); - - Assert.Equal(guid, sut.Result()); - Assert.True(sut.IsHandled); - Assert.True(sut.IsSucceeded); - Assert.False(sut.IsFailed); - } - - [Fact] - public void Should_not_change_status_when_already_failed() { var sut = new CommandContext(command); - sut.Fail(new Exception()); - sut.Succeed(Guid.NewGuid()); - - Assert.NotNull(sut.Exception); - Assert.True(sut.IsHandled); - Assert.True(sut.IsFailed); - Assert.False(sut.IsSucceeded); + sut.Complete("RESULT"); } } } diff --git a/tests/Squidex.Infrastructure.Tests/CQRS/Commands/EnrichWithTimestampHandlerTests.cs b/tests/Squidex.Infrastructure.Tests/CQRS/Commands/EnrichWithTimestampCommandHandlerTests.cs similarity index 80% rename from tests/Squidex.Infrastructure.Tests/CQRS/Commands/EnrichWithTimestampHandlerTests.cs rename to tests/Squidex.Infrastructure.Tests/CQRS/Commands/EnrichWithTimestampCommandHandlerTests.cs index eb600a19f..1f3954797 100644 --- a/tests/Squidex.Infrastructure.Tests/CQRS/Commands/EnrichWithTimestampHandlerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/CQRS/Commands/EnrichWithTimestampCommandHandlerTests.cs @@ -1,5 +1,5 @@ // ========================================================================== -// EnrichWithTimestampHandlerTests.cs +// EnrichWithTimestampCommandHandlerTests.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -13,7 +13,7 @@ using Xunit; namespace Squidex.Infrastructure.CQRS.Commands { - public sealed class EnrichWithTimestampHandlerTests + public sealed class EnrichWithTimestampCommandHandlerTests { private sealed class MyTimestampCommand : ITimestampCommand { @@ -35,9 +35,8 @@ namespace Squidex.Infrastructure.CQRS.Commands var command = new MyTimestampCommand(); - var result = await sut.HandleAsync(new CommandContext(command)); + await sut.HandleAsync(new CommandContext(command)); - Assert.False(result); Assert.Equal(utc, command.Timestamp); } @@ -46,9 +45,7 @@ namespace Squidex.Infrastructure.CQRS.Commands { var sut = new EnrichWithTimestampHandler(clock); - var result = await sut.HandleAsync(new CommandContext(A.Dummy())); - - Assert.False(result); + await sut.HandleAsync(new CommandContext(A.Dummy())); A.CallTo(() => clock.GetCurrentInstant()).MustNotHaveHappened(); } diff --git a/tests/Squidex.Infrastructure.Tests/CQRS/Commands/InMemoryCommandBusTests.cs b/tests/Squidex.Infrastructure.Tests/CQRS/Commands/InMemoryCommandBusTests.cs index 70dc09276..ef9a538f6 100644 --- a/tests/Squidex.Infrastructure.Tests/CQRS/Commands/InMemoryCommandBusTests.cs +++ b/tests/Squidex.Infrastructure.Tests/CQRS/Commands/InMemoryCommandBusTests.cs @@ -22,10 +22,12 @@ namespace Squidex.Infrastructure.CQRS.Commands { public ICommand LastCommand; - public Task HandleAsync(CommandContext context) + public Task HandleAsync(CommandContext context, Func next) { LastCommand = context.Command; + context.Complete(true); + return TaskHelper.True; } } @@ -34,11 +36,11 @@ namespace Squidex.Infrastructure.CQRS.Commands { public ICommand LastCommand; - public Task HandleAsync(CommandContext context) + public Task HandleAsync(CommandContext context, Func next) { LastCommand = context.Command; - return TaskHelper.False; + return TaskHelper.Done; } } @@ -46,7 +48,7 @@ namespace Squidex.Infrastructure.CQRS.Commands { public ICommand LastCommand; - public Task HandleAsync(CommandContext context) + public Task HandleAsync(CommandContext context, Func next) { LastCommand = context.Command; @@ -54,25 +56,13 @@ namespace Squidex.Infrastructure.CQRS.Commands } } - private sealed class AfterThrowHandler : ICommandHandler - { - public Exception LastException; - - public Task HandleAsync(CommandContext context) - { - LastException = context.Exception; - - return TaskHelper.False; - } - } - [Fact] public async Task Should_not_set_handled_if_no_handler_registered() { var sut = new InMemoryCommandBus(new ICommandHandler[0]); var ctx = await sut.PublishAsync(command); - Assert.False(ctx.IsHandled); + Assert.False(ctx.IsCompleted); } [Fact] @@ -84,13 +74,11 @@ namespace Squidex.Infrastructure.CQRS.Commands var ctx = await sut.PublishAsync(command); Assert.Equal(command, handler.LastCommand); - Assert.False(ctx.IsSucceeded); - Assert.False(ctx.IsHandled); - Assert.Null(ctx.Exception); + Assert.False(ctx.IsCompleted); } [Fact] - public async Task Should_set_succeeded_if_handler_returns_true() + public async Task Should_set_succeeded_if_handler_marks_completed() { var handler = new HandledHandler(); @@ -98,23 +86,19 @@ namespace Squidex.Infrastructure.CQRS.Commands var ctx = await sut.PublishAsync(command); Assert.Equal(command, handler.LastCommand); - Assert.True(ctx.IsSucceeded); - Assert.True(ctx.IsHandled); - Assert.Null(ctx.Exception); + Assert.True(ctx.IsCompleted); } [Fact] public async Task Should_throw_and_call_all_handlers_if_first_handler_fails() { - var handler1 = new ThrowHandledHandler(); - var handler2 = new AfterThrowHandler(); + var handler = new ThrowHandledHandler(); - var sut = new InMemoryCommandBus(new ICommandHandler[] { handler1, handler2 }); + var sut = new InMemoryCommandBus(new ICommandHandler[] { handler }); await Assert.ThrowsAsync(async () => await sut.PublishAsync(command)); - Assert.Equal(command, handler1.LastCommand); - Assert.IsType(handler2.LastException); + Assert.Equal(command, handler.LastCommand); } } } diff --git a/tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExceptionHandlerTests.cs b/tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogCommandHandlerTests.cs similarity index 50% rename from tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExceptionHandlerTests.cs rename to tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogCommandHandlerTests.cs index 35a11d8f6..7b6c4210c 100644 --- a/tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExceptionHandlerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogCommandHandlerTests.cs @@ -8,10 +8,10 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using FakeItEasy; using Squidex.Infrastructure.Log; +using Squidex.Infrastructure.Tasks; using Xunit; namespace Squidex.Infrastructure.CQRS.Commands @@ -19,16 +19,19 @@ namespace Squidex.Infrastructure.CQRS.Commands public class LogExceptionHandlerTests { private readonly MyLog log = new MyLog(); - private readonly LogExceptionHandler sut; + private readonly LogCommandHandler sut; private readonly ICommand command = A.Dummy(); private sealed class MyLog : ISemanticLog { - public HashSet LogLevels { get; } = new HashSet(); + public int LogCount { get; private set; } + + public Dictionary LogLevels { get; } = new Dictionary(); public void Log(SemanticLogLevel logLevel, Action action) { - LogLevels.Add(logLevel); + LogCount++; + LogLevels[logLevel] = LogLevels.GetOrDefault(logLevel) + 1; } public ISemanticLog CreateScope(Action objectWriter) @@ -39,33 +42,38 @@ namespace Squidex.Infrastructure.CQRS.Commands public LogExceptionHandlerTests() { - sut = new LogExceptionHandler(log); + sut = new LogCommandHandler(log); } [Fact] - public async Task Should_do_nothing_if_command_is_succeeded() + public async Task Should_log_before_and_after_request() { var context = new CommandContext(command); - context.Succeed(); + await sut.HandleAsync(context, () => + { + context.Complete(true); - var isHandled = await sut.HandleAsync(context); + return TaskHelper.Done; + }); - Assert.False(isHandled); - Assert.Equal(0, log.LogLevels.Count); + Assert.Equal(3, log.LogCount); + Assert.Equal(3, log.LogLevels[SemanticLogLevel.Information]); } [Fact] - public async Task Should_log_if_command_failed() + public async Task Should_log_error_if_command_failed() { var context = new CommandContext(command); - context.Fail(new InvalidOperationException()); - - var isHandled = await sut.HandleAsync(context); + await Assert.ThrowsAsync(async () => + { + await sut.HandleAsync(context, () => throw new InvalidOperationException()); + }); - Assert.False(isHandled); - Assert.Equal(new[] { SemanticLogLevel.Error }, log.LogLevels.ToArray()); + Assert.Equal(3, log.LogCount); + Assert.Equal(2, log.LogLevels[SemanticLogLevel.Information]); + Assert.Equal(1, log.LogLevels[SemanticLogLevel.Error]); } [Fact] @@ -73,10 +81,11 @@ namespace Squidex.Infrastructure.CQRS.Commands { var context = new CommandContext(command); - var isHandled = await sut.HandleAsync(context); + await sut.HandleAsync(context, () => TaskHelper.Done); - Assert.False(isHandled); - Assert.Equal(new[] { SemanticLogLevel.Fatal }, log.LogLevels.ToArray()); + Assert.Equal(4, log.LogCount); + Assert.Equal(3, log.LogLevels[SemanticLogLevel.Information]); + Assert.Equal(1, log.LogLevels[SemanticLogLevel.Fatal]); } } } \ No newline at end of file diff --git a/tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExecutingHandlerTests.cs b/tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExecutingHandlerTests.cs deleted file mode 100644 index b24a3a591..000000000 --- a/tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExecutingHandlerTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -// ========================================================================== -// LogExecutingHandlerTests.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Threading.Tasks; -using FakeItEasy; -using Squidex.Infrastructure.Log; -using Xunit; - -namespace Squidex.Infrastructure.CQRS.Commands -{ - public class LogExecutingHandlerTests - { - private readonly MyLog log = new MyLog(); - private readonly LogExecutingHandler sut; - private readonly ICommand command = A.Dummy(); - - private sealed class MyLog : ISemanticLog - { - public int LogCount { get; private set; } - - public void Log(SemanticLogLevel logLevel, Action action) - { - LogCount++; - } - - public ISemanticLog CreateScope(Action objectWriter) - { - throw new NotSupportedException(); - } - } - - public LogExecutingHandlerTests() - { - sut = new LogExecutingHandler(log); - } - - [Fact] - public async Task Should_log_once() - { - var context = new CommandContext(command); - - var isHandled = await sut.HandleAsync(context); - - Assert.False(isHandled); - Assert.Equal(1, log.LogCount); - } - } -}