Skip to content

Commit 475b75a

Browse files
authored
Merge pull request #125 from buildersoftio/v1.7/release
V1.7/release
2 parents 7312a6f + e62d39e commit 475b75a

17 files changed

Lines changed: 189 additions & 156 deletions

src/Cortex.Mediator/Behaviors/LoggingCommandBehavior.cs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Cortex.Mediator.Commands;
22
using Microsoft.Extensions.Logging;
33
using System;
4+
using System.Diagnostics;
45
using System.Threading;
56
using System.Threading.Tasks;
67

@@ -9,32 +10,45 @@ namespace Cortex.Mediator.Behaviors
910
/// <summary>
1011
/// Pipeline behavior for logging command/query execution.
1112
/// </summary>
12-
public class LoggingCommandBehavior<TCommand> : ICommandPipelineBehavior<TCommand>
13-
where TCommand : ICommand
13+
public sealed class LoggingCommandBehavior<TCommand, TResult> : ICommandPipelineBehavior<TCommand, TResult>
14+
where TCommand : ICommand<TResult>
1415
{
15-
private readonly ILogger<LoggingCommandBehavior<TCommand>> _logger;
16+
private readonly ILogger<LoggingCommandBehavior<TCommand, TResult>> _logger;
1617

17-
public LoggingCommandBehavior(ILogger<LoggingCommandBehavior<TCommand>> logger)
18+
public LoggingCommandBehavior(ILogger<LoggingCommandBehavior<TCommand, TResult>> logger)
1819
{
1920
_logger = logger;
2021
}
2122

22-
public async Task Handle(
23+
public async Task<TResult> Handle(
2324
TCommand command,
24-
CommandHandlerDelegate next,
25+
CommandHandlerDelegate<TResult> next,
2526
CancellationToken cancellationToken)
2627
{
2728
var commandName = typeof(TCommand).Name;
2829
_logger.LogInformation("Executing command {CommandName}", commandName);
2930

31+
var stopwatch = Stopwatch.StartNew(); // start timing
3032
try
3133
{
32-
await next();
33-
_logger.LogInformation("Command {CommandName} executed successfully", commandName);
34+
var result = await next();
35+
36+
stopwatch.Stop();
37+
_logger.LogInformation(
38+
"Command {CommandName} executed successfully in {ElapsedMilliseconds} ms",
39+
commandName,
40+
stopwatch.ElapsedMilliseconds);
41+
42+
return result;
3443
}
3544
catch (Exception ex)
3645
{
37-
_logger.LogError(ex, "Error executing command {CommandName}", commandName);
46+
stopwatch.Stop();
47+
_logger.LogError(
48+
ex,
49+
"Error executing command {CommandName} after {ElapsedMilliseconds} ms",
50+
commandName,
51+
stopwatch.ElapsedMilliseconds);
3852
throw;
3953
}
4054
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using Cortex.Mediator.Queries;
2+
using Microsoft.Extensions.Logging;
3+
using System;
4+
using System.Diagnostics;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
namespace Cortex.Mediator.Behaviors
9+
{
10+
/// <summary>
11+
/// Pipeline behavior for logging command/query execution.
12+
/// </summary>
13+
public sealed class LoggingQueryBehavior<TQuery, TResult> : IQueryPipelineBehavior<TQuery, TResult>
14+
where TQuery : IQuery<TResult>
15+
{
16+
private readonly ILogger<LoggingQueryBehavior<TQuery, TResult>> _logger;
17+
18+
public LoggingQueryBehavior(ILogger<LoggingQueryBehavior<TQuery, TResult>> logger)
19+
{
20+
_logger = logger;
21+
}
22+
23+
public async Task<TResult> Handle(
24+
TQuery command,
25+
QueryHandlerDelegate<TResult> next,
26+
CancellationToken cancellationToken)
27+
{
28+
var queryName = typeof(TQuery).Name;
29+
_logger.LogInformation("Executing query {QueryName}", queryName);
30+
31+
var stopwatch = Stopwatch.StartNew(); // start timing
32+
try
33+
{
34+
var result = await next();
35+
36+
stopwatch.Stop();
37+
_logger.LogInformation(
38+
"Query {QueryName} executed successfully in {ElapsedMilliseconds} ms",
39+
queryName,
40+
stopwatch.ElapsedMilliseconds);
41+
42+
return result;
43+
}
44+
catch (Exception ex)
45+
{
46+
stopwatch.Stop();
47+
_logger.LogError(
48+
ex,
49+
"Error executing query {QueryName} after {ElapsedMilliseconds} ms",
50+
queryName,
51+
stopwatch.ElapsedMilliseconds);
52+
throw;
53+
}
54+
}
55+
}
56+
}

src/Cortex.Mediator/Behaviors/TransactionCommandBehavior.cs

Lines changed: 0 additions & 39 deletions
This file was deleted.

src/Cortex.Mediator/Behaviors/ValidationCommandBehavior.cs

Lines changed: 0 additions & 50 deletions
This file was deleted.

src/Cortex.Mediator/Commands/ICommand.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
{
33
/// <summary>
44
/// Represents a command in the CQRS pattern.
5-
/// Commands are used to change the system state and do not return a value.
5+
/// Commands are used to change the system state and do return a value.
6+
/// Please note that this is not a common practice in CQRS, as commands typically do not return values.
67
/// </summary>
7-
public interface ICommand
8+
public interface ICommand<TResult>
89
{
910
}
1011
}

src/Cortex.Mediator/Commands/ICommandHandler.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ namespace Cortex.Mediator.Commands
77
/// Defines a handler for a command.
88
/// </summary>
99
/// <typeparam name="TCommand">The type of command being handled.</typeparam>
10-
public interface ICommandHandler<in TCommand>
11-
where TCommand : ICommand
10+
/// <typeparam name="TResult">The type of command that is being returned.</typeparam>
11+
public interface ICommandHandler<in TCommand, TResult>
12+
where TCommand : ICommand<TResult>
1213
{
1314
/// <summary>
1415
/// Handles the specified command.
1516
/// </summary>
1617
/// <param name="command">The command to handle.</param>
1718
/// <param name="cancellationToken">The cancellation token.</param>
18-
Task Handle(TCommand command, CancellationToken cancellationToken);
19+
Task<TResult> Handle(TCommand command, CancellationToken cancellationToken);
1920
}
2021
}

src/Cortex.Mediator/Commands/ICommandPipelineBehavior.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@ namespace Cortex.Mediator.Commands
77
/// Defines a pipeline behavior for wrapping command handlers.
88
/// </summary>
99
/// <typeparam name="TCommand">The type of command being handled.</typeparam>
10-
public interface ICommandPipelineBehavior<in TCommand>
11-
where TCommand : ICommand
10+
public interface ICommandPipelineBehavior<in TCommand, TResult>
11+
where TCommand : ICommand<TResult>
1212
{
1313
/// <summary>
1414
/// Handles the command and invokes the next behavior in the pipeline.
1515
/// </summary>
16-
Task Handle(
16+
Task<TResult> Handle(
1717
TCommand command,
18-
CommandHandlerDelegate next,
18+
CommandHandlerDelegate<TResult> next,
1919
CancellationToken cancellationToken);
2020
}
2121

2222
/// <summary>
2323
/// Represents a delegate that wraps the command handler execution.
2424
/// </summary>
25-
public delegate Task CommandHandlerDelegate();
25+
public delegate Task<TResult> CommandHandlerDelegate<TResult>();
2626
}

src/Cortex.Mediator/Common/Unit.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Cortex.Mediator
2+
{
3+
public readonly struct Unit
4+
{
5+
public static readonly Unit Value = new();
6+
public override string ToString() => "()";
7+
}
8+
}

src/Cortex.Mediator/Cortex.Mediator.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@
6161
</ItemGroup>
6262

6363
<ItemGroup>
64-
<PackageReference Include="FluentValidation" Version="11.11.0" />
65-
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.11.0" />
6664
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.3" />
6765
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
6866
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.3" />

src/Cortex.Mediator/DependencyInjection/MediatorOptions.cs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Cortex.Mediator.Commands;
1+
using Cortex.Mediator.Commands;
22
using Cortex.Mediator.Queries;
33
using System;
44
using System.Collections.Generic;
@@ -11,25 +11,64 @@ public class MediatorOptions
1111
internal List<Type> CommandBehaviors { get; } = new();
1212
internal List<Type> QueryBehaviors { get; } = new();
1313

14+
public bool OnlyPublicClasses { get; set; } = true;
15+
16+
17+
/// <summary>
18+
/// Register a *closed* command pipeline behavior.
19+
/// </summary>
1420
public MediatorOptions AddCommandPipelineBehavior<TBehavior>()
15-
where TBehavior : ICommandPipelineBehavior<ICommand> // Add constraint
21+
where TBehavior : class // Add constraint
1622
{
1723
var behaviorType = typeof(TBehavior);
24+
1825
if (behaviorType.IsGenericTypeDefinition)
1926
{
2027
throw new ArgumentException("Open generic types must be registered using AddOpenCommandPipelineBehavior");
2128
}
29+
30+
var implementsInterface = behaviorType
31+
.GetInterfaces()
32+
.Any(i => i.IsGenericType &&
33+
i.GetGenericTypeDefinition() == typeof(ICommandPipelineBehavior<,>));
34+
35+
if (!implementsInterface)
36+
{
37+
throw new ArgumentException("Type must implement ICommandPipelineBehavior<,>");
38+
}
39+
2240
CommandBehaviors.Add(behaviorType);
2341
return this;
2442
}
2543

44+
/// <summary>
45+
/// Register an *open generic* command pipeline behavior, e.g. typeof(LoggingCommandBehavior&lt;,&gt;).
46+
/// </summary>
2647
public MediatorOptions AddOpenCommandPipelineBehavior(Type openGenericBehaviorType)
2748
{
2849
if (!openGenericBehaviorType.IsGenericTypeDefinition)
2950
{
3051
throw new ArgumentException("Type must be an open generic type definition");
3152
}
3253

54+
var implementsInterface = openGenericBehaviorType
55+
.GetInterfaces()
56+
.Any(i => i.IsGenericType &&
57+
i.GetGenericTypeDefinition() == typeof(ICommandPipelineBehavior<,>));
58+
59+
// For open generics, interface might not appear in GetInterfaces() yet; check by definition instead.
60+
if (!implementsInterface &&
61+
!(openGenericBehaviorType.IsGenericTypeDefinition &&
62+
openGenericBehaviorType.GetGenericTypeDefinition() == openGenericBehaviorType))
63+
{
64+
// Fall back to checking generic arguments count to give a clear error
65+
var ok = openGenericBehaviorType.GetGenericArguments().Length == 2;
66+
if (!ok)
67+
{
68+
throw new ArgumentException("Type must implement ICommandPipelineBehavior<,>");
69+
}
70+
}
71+
3372
CommandBehaviors.Add(openGenericBehaviorType);
3473
return this;
3574
}

0 commit comments

Comments
 (0)