Browse Source

Command handdlers redesigned.

pull/95/head
Sebastian Stehle 8 years ago
parent
commit
73c21f6c3a
  1. 13
      src/Squidex.Domain.Apps.Write/Apps/AppCommandHandler.cs
  2. 11
      src/Squidex.Domain.Apps.Write/Assets/AssetCommandHandler.cs
  3. 10
      src/Squidex.Domain.Apps.Write/Contents/ContentCommandHandler.cs
  4. 13
      src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandHandler.cs
  5. 8
      src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs
  6. 39
      src/Squidex.Infrastructure/CQRS/Commands/CommandContext.cs
  7. 5
      src/Squidex.Infrastructure/CQRS/Commands/CommandingExtensions.cs
  8. 8
      src/Squidex.Infrastructure/CQRS/Commands/EnrichWithTimestampCommandHandler.cs
  9. 3
      src/Squidex.Infrastructure/CQRS/Commands/ICommandHandler.cs
  10. 32
      src/Squidex.Infrastructure/CQRS/Commands/InMemoryCommandBus.cs
  11. 72
      src/Squidex.Infrastructure/CQRS/Commands/LogCommandHandler.cs
  12. 53
      src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs
  13. 37
      src/Squidex.Infrastructure/CQRS/Commands/LogExecutingHandler.cs
  14. 10
      src/Squidex/Config/Domain/WriteModule.cs
  15. 18
      src/Squidex/Pipeline/CommandHandlers/ETagCommandHandler.cs
  16. 12
      src/Squidex/Pipeline/CommandHandlers/EnrichWithActorCommandHandler.cs
  17. 9
      src/Squidex/Pipeline/CommandHandlers/EnrichWithAppIdCommandHandler.cs
  18. 8
      src/Squidex/Pipeline/CommandHandlers/EnrichWithSchemaIdCommandHandler.cs
  19. 36
      src/Squidex/Pipeline/CommandHandlers/SetVersionAsETagHandler.cs
  20. 79
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/CommandContextTests.cs
  21. 11
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/EnrichWithTimestampCommandHandlerTests.cs
  22. 42
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/InMemoryCommandBusTests.cs
  23. 47
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogCommandHandlerTests.cs
  24. 54
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExecutingHandlerTests.cs

13
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<AppDomainObject>(context, a => a.UpdateLanguage(command));
}
public Task<bool> HandleAsync(CommandContext context)
public async Task HandleAsync(CommandContext context, Func<Task> next)
{
return context.IsHandled ? TaskHelper.False : this.DispatchActionAsync(context.Command, context);
if (!await this.DispatchActionAsync(context.Command, context))
{
await next();
}
}
}
}

11
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<AssetDomainObject>(context, a => a.Delete(command));
}
public Task<bool> HandleAsync(CommandContext context)
public async Task HandleAsync(CommandContext context, Func<Task> next)
{
return context.IsHandled ? TaskHelper.False : this.DispatchActionAsync(context.Command, context);
if (!await this.DispatchActionAsync(context.Command, context))
{
await next();
}
}
}
}

10
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<ContentDomainObject>(context, c => c.Delete(command));
}
public Task<bool> HandleAsync(CommandContext context)
public async Task HandleAsync(CommandContext context, Func<Task> 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<string> message, bool enrich = false)

13
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<SchemaDomainObject>(context, s => s.Unpublish(command));
}
public Task<bool> HandleAsync(CommandContext context)
public async Task HandleAsync(CommandContext context, Func<Task> next)
{
return context.IsHandled ? TaskHelper.False : this.DispatchActionAsync(context.Command, context);
if (!await this.DispatchActionAsync(context.Command, context))
{
await next();
}
}
}
}

8
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<Guid>(aggregate.Id, aggregate.Version));
context.Complete(new EntityCreatedResult<Guid>(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;

39
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<object> 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<T>()
{
return (T)result?.Item1;

5
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);
}
}
}

8
src/Squidex.Infrastructure/CQRS/Commands/EnrichWithTimestampHandler.cs → 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<bool> HandleAsync(CommandContext context)
public Task HandleAsync(CommandContext context, Func<Task> next)
{
if (context.Command is ITimestampCommand timestampCommand)
{
timestampCommand.Timestamp = clock.GetCurrentInstant();
}
return TaskHelper.False;
return next();
}
}
}

3
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<bool> HandleAsync(CommandContext context);
Task HandleAsync(CommandContext context, Func<Task> next);
}
}

32
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<ICommandHandler> handlers;
private readonly List<ICommandHandler> handlers;
public InMemoryCommandBus(IEnumerable<ICommandHandler> handlers)
{
Guard.NotNull(handlers, nameof(handlers));
this.handlers = handlers;
this.handlers = handlers.Reverse().ToList();
}
public async Task<CommandContext> PublishAsync(ICommand command)
@ -29,29 +31,21 @@ namespace Squidex.Infrastructure.CQRS.Commands
var context = new CommandContext(command);
var next = new Func<Task>(() => 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<Task> Join(ICommandHandler handler, CommandContext context, Func<Task> next)
{
return () => handler.HandleAsync(context, next);
}
}
}

72
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<Task> 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));
}
}
}
}

53
src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs

@ -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<bool> 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;
}
}
}

37
src/Squidex.Infrastructure/CQRS/Commands/LogExecutingHandler.cs

@ -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<bool> 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;
}
}
}

10
src/Squidex/Config/Domain/WriteModule.cs

@ -31,7 +31,7 @@ namespace Squidex.Config.Domain
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<EnrichWithExpectedVersionHandler>()
builder.RegisterType<ETagCommandHandler>()
.As<ICommandHandler>()
.SingleInstance();
@ -39,15 +39,15 @@ namespace Squidex.Config.Domain
.As<ICommandHandler>()
.SingleInstance();
builder.RegisterType<EnrichWithActorHandler>()
builder.RegisterType<EnrichWithActorCommandHandler>()
.As<ICommandHandler>()
.SingleInstance();
builder.RegisterType<EnrichWithAppIdHandler>()
builder.RegisterType<EnrichWithAppIdCommandHandler>()
.As<ICommandHandler>()
.SingleInstance();
builder.RegisterType<EnrichWithSchemaIdHandler>()
builder.RegisterType<EnrichWithSchemaIdCommandHandler>()
.As<ICommandHandler>()
.SingleInstance();
@ -71,7 +71,7 @@ namespace Squidex.Config.Domain
.As<ICommandHandler>()
.SingleInstance();
builder.RegisterType<SetVersionAsETagHandler>()
builder.RegisterType<ETagCommandHandler>()
.As<ICommandHandler>()
.SingleInstance();

18
src/Squidex/Pipeline/CommandHandlers/EnrichWithExpectedVersionHandler.cs → 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<bool> HandleAsync(CommandContext context)
public async Task HandleAsync(CommandContext context, Func<Task> 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<object>() is EntitySavedResult result)
{
httpContextAccessor.HttpContext.Response.Headers["ETag"] = new StringValues(result.Version.ToString());
}
}
}
}

12
src/Squidex/Pipeline/CommandHandlers/EnrichWithActorHandler.cs → 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<bool> HandleAsync(CommandContext context)
public Task HandleAsync(CommandContext context, Func<Task> 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()

9
src/Squidex/Pipeline/CommandHandlers/EnrichWithAppIdHandler.cs → 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<bool> HandleAsync(CommandContext context)
public Task HandleAsync(CommandContext context, Func<Task> next)
{
if (context.Command is AppCommand appCommand && appCommand.AppId == null)
{
@ -41,7 +40,7 @@ namespace Squidex.Pipeline.CommandHandlers
appCommand.AppId = new NamedId<Guid>(appFeature.App.Id, appFeature.App.Name);
}
return TaskHelper.False;
return next();
}
}
}

8
src/Squidex/Pipeline/CommandHandlers/EnrichWithSchemaIdHandler.cs → 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<bool> HandleAsync(CommandContext context)
public async Task HandleAsync(CommandContext context, Func<Task> next)
{
if (context.Command is SchemaCommand schemaCommand && schemaCommand.SchemaId == null)
{
@ -62,7 +62,7 @@ namespace Squidex.Pipeline.CommandHandlers
}
}
return false;
await next();
}
}
}

36
src/Squidex/Pipeline/CommandHandlers/SetVersionAsETagHandler.cs

@ -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<bool> HandleAsync(CommandContext context)
{
if (context.Result<object>() is EntitySavedResult result)
{
httpContextAccessor.HttpContext.Response.Headers["ETag"] = new StringValues(result.Version.ToString());
}
return TaskHelper.False;
}
}
}

79
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<Guid>());
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");
}
}
}

11
tests/Squidex.Infrastructure.Tests/CQRS/Commands/EnrichWithTimestampHandlerTests.cs → 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<ICommand>()));
Assert.False(result);
await sut.HandleAsync(new CommandContext(A.Dummy<ICommand>()));
A.CallTo(() => clock.GetCurrentInstant()).MustNotHaveHappened();
}

42
tests/Squidex.Infrastructure.Tests/CQRS/Commands/InMemoryCommandBusTests.cs

@ -22,10 +22,12 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
public ICommand LastCommand;
public Task<bool> HandleAsync(CommandContext context)
public Task HandleAsync(CommandContext context, Func<Task> next)
{
LastCommand = context.Command;
context.Complete(true);
return TaskHelper.True;
}
}
@ -34,11 +36,11 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
public ICommand LastCommand;
public Task<bool> HandleAsync(CommandContext context)
public Task HandleAsync(CommandContext context, Func<Task> next)
{
LastCommand = context.Command;
return TaskHelper.False;
return TaskHelper.Done;
}
}
@ -46,7 +48,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
public ICommand LastCommand;
public Task<bool> HandleAsync(CommandContext context)
public Task HandleAsync(CommandContext context, Func<Task> next)
{
LastCommand = context.Command;
@ -54,25 +56,13 @@ namespace Squidex.Infrastructure.CQRS.Commands
}
}
private sealed class AfterThrowHandler : ICommandHandler
{
public Exception LastException;
public Task<bool> 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<InvalidOperationException>(async () => await sut.PublishAsync(command));
Assert.Equal(command, handler1.LastCommand);
Assert.IsType<InvalidOperationException>(handler2.LastException);
Assert.Equal(command, handler.LastCommand);
}
}
}

47
tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExceptionHandlerTests.cs → 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<ICommand>();
private sealed class MyLog : ISemanticLog
{
public HashSet<SemanticLogLevel> LogLevels { get; } = new HashSet<SemanticLogLevel>();
public int LogCount { get; private set; }
public Dictionary<SemanticLogLevel, int> LogLevels { get; } = new Dictionary<SemanticLogLevel, int>();
public void Log(SemanticLogLevel logLevel, Action<IObjectWriter> action)
{
LogLevels.Add(logLevel);
LogCount++;
LogLevels[logLevel] = LogLevels.GetOrDefault(logLevel) + 1;
}
public ISemanticLog CreateScope(Action<IObjectWriter> 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<InvalidOperationException>(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]);
}
}
}

54
tests/Squidex.Infrastructure.Tests/CQRS/Commands/LogExecutingHandlerTests.cs

@ -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<ICommand>();
private sealed class MyLog : ISemanticLog
{
public int LogCount { get; private set; }
public void Log(SemanticLogLevel logLevel, Action<IObjectWriter> action)
{
LogCount++;
}
public ISemanticLog CreateScope(Action<IObjectWriter> 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);
}
}
}
Loading…
Cancel
Save