diff --git a/Application/Pages/Index.cshtml.cs b/Application/Pages/Index.cshtml.cs index 806a114..f798ee6 100644 --- a/Application/Pages/Index.cshtml.cs +++ b/Application/Pages/Index.cshtml.cs @@ -1,4 +1,3 @@ -using Akka.Actor; using Microsoft.ApplicationInsights; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; diff --git a/Application/PlanTempus.Application.csproj b/Application/PlanTempus.Application.csproj index 17391d4..b0826ac 100644 --- a/Application/PlanTempus.Application.csproj +++ b/Application/PlanTempus.Application.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable diff --git a/Core/CommandQueries/Command.cs b/Core/CommandQueries/Command.cs new file mode 100644 index 0000000..ed8da3b --- /dev/null +++ b/Core/CommandQueries/Command.cs @@ -0,0 +1,7 @@ +namespace PlanTempus.Core.CommandQueries; + +public abstract class Command : ICommand +{ + public required Guid CorrelationId { get; set; } + public Guid TransactionId { get; set; } +} \ No newline at end of file diff --git a/Core/CommandQueries/CommandResponse.cs b/Core/CommandQueries/CommandResponse.cs new file mode 100644 index 0000000..1b0bac7 --- /dev/null +++ b/Core/CommandQueries/CommandResponse.cs @@ -0,0 +1,16 @@ +namespace PlanTempus.Core.CommandQueries; + +public class CommandResponse +{ + public Guid RequestId { get; } + public Guid CorrelationId { get; } + public Guid? TransactionId { get; } + public DateTime CreatedAt { get; } + + public CommandResponse(Guid correlationId) + { + CorrelationId = correlationId; + RequestId = Guid.CreateVersion7(); + CreatedAt = DateTime.Now; + } +} \ No newline at end of file diff --git a/Core/CommandQueries/ICommand.cs b/Core/CommandQueries/ICommand.cs new file mode 100644 index 0000000..cb5e2e1 --- /dev/null +++ b/Core/CommandQueries/ICommand.cs @@ -0,0 +1,7 @@ +namespace PlanTempus.Core.CommandQueries; + +public interface ICommand +{ + Guid CorrelationId { get; set; } + Guid TransactionId { get; set; } +} \ No newline at end of file diff --git a/Core/Database/DatabaseScope.cs b/Core/Database/DatabaseScope.cs index 40db7c4..2d5b5e5 100644 --- a/Core/Database/DatabaseScope.cs +++ b/Core/Database/DatabaseScope.cs @@ -15,6 +15,7 @@ public class DatabaseScope : IDisposable Connection = connection; _operation = operation; _operation.Telemetry.Success = true; + _operation.Telemetry.Timestamp = DateTimeOffset.UtcNow; _stopwatch = Stopwatch.StartNew(); } diff --git a/Core/ModuleRegistry/TelemetryModule.cs b/Core/ModuleRegistry/TelemetryModule.cs index cdef080..8b8e887 100644 --- a/Core/ModuleRegistry/TelemetryModule.cs +++ b/Core/ModuleRegistry/TelemetryModule.cs @@ -17,7 +17,7 @@ namespace PlanTempus.Core.ModuleRegistry var client = new Microsoft.ApplicationInsights.TelemetryClient(configuration); client.Context.GlobalProperties["Application"] = GetType().Namespace?.Split('.')[0]; client.Context.GlobalProperties["MachineName"] = Environment.MachineName; - client.Context.GlobalProperties["Version"] = Environment.Version.ToString(); + client.Context.GlobalProperties["CLRVersion"] = Environment.Version.ToString(); client.Context.GlobalProperties["ProcessorCount"] = Environment.ProcessorCount.ToString(); builder.Register(c => client).InstancePerLifetimeScope(); diff --git a/Core/PlanTempus.Core.csproj b/Core/PlanTempus.Core.csproj index 7fc76f2..e35b32e 100644 --- a/Core/PlanTempus.Core.csproj +++ b/Core/PlanTempus.Core.csproj @@ -1,30 +1,28 @@ - - + - - net8.0 - enable - + + net9.0 + enable + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - + + + + diff --git a/Database/PlanTempus.Database.csproj b/Database/PlanTempus.Database.csproj index 50d49a6..76de5e5 100644 --- a/Database/PlanTempus.Database.csproj +++ b/Database/PlanTempus.Database.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable diff --git a/PlanTempus.Components/CommandHandler.cs b/PlanTempus.Components/CommandHandler.cs index 85248e1..93c55cb 100644 --- a/PlanTempus.Components/CommandHandler.cs +++ b/PlanTempus.Components/CommandHandler.cs @@ -1,13 +1,14 @@ using Autofac; using PlanTempus.Components.Users.Create; +using PlanTempus.Core.CommandQueries; namespace PlanTempus.Components; public class CommandHandler(IComponentContext context) : ICommandHandler { - public async Task Handle(TCommand command) + public async Task Handle(TCommand command) where TCommand : ICommand { - var handler = context.Resolve>(); + var handler = context.Resolve>(); return await handler.Handle(command); } } \ No newline at end of file diff --git a/PlanTempus.Components/ICommandHandler.cs b/PlanTempus.Components/ICommandHandler.cs index 6eb35d7..8aa970c 100644 --- a/PlanTempus.Components/ICommandHandler.cs +++ b/PlanTempus.Components/ICommandHandler.cs @@ -1,13 +1,15 @@ -namespace PlanTempus.Components; +using PlanTempus.Core.CommandQueries; + +namespace PlanTempus.Components; public interface ICommandHandler { - Task Handle(TCommand command); + Task Handle(TCommand command) where TCommand : ICommand; } -public interface ICommandHandler +public interface ICommandHandler where TCommand : ICommand { - Task Handle(T input); + Task Handle(TCommand command); } public interface ICommandHandlerDecorator diff --git a/PlanTempus.Components/ModuleRegistry/CommandModule.cs b/PlanTempus.Components/ModuleRegistry/CommandModule.cs index 4f153ec..23a1579 100644 --- a/PlanTempus.Components/ModuleRegistry/CommandModule.cs +++ b/PlanTempus.Components/ModuleRegistry/CommandModule.cs @@ -1,5 +1,6 @@ using Autofac; using PlanTempus.Components.Users.Create; +using PlanTempus.Core.CommandQueries; using PlanTempus.Core.SeqLogging; namespace PlanTempus.Components.ModuleRegistry @@ -10,35 +11,27 @@ namespace PlanTempus.Components.ModuleRegistry protected override void Load(ContainerBuilder builder) { - builder.RegisterType() .As() .InstancePerLifetimeScope(); - - builder.RegisterAssemblyTypes(ThisAssembly) - .Where(t => !typeof(ICommandHandlerDecorator).IsAssignableFrom(t)) - .AsClosedTypesOf(typeof(ICommandHandler<,>)) - .InstancePerLifetimeScope(); - - - // Registrer alle handlers - // builder.RegisterAssemblyTypes(ThisAssembly) - // .AsClosedTypesOf(typeof(ICommandHandler<,>)) - // .InstancePerLifetimeScope(); builder.RegisterAssemblyTypes(ThisAssembly) - .As(); - - - builder.RegisterDecorator( - typeof(CreateUserHandlerDecorator), - typeof(ICommandHandler)); + .Where(t => !typeof(ICommandHandlerDecorator).IsAssignableFrom(t)) + .AsClosedTypesOf(typeof(ICommandHandler<>)) + .InstancePerLifetimeScope(); + + builder.RegisterAssemblyTypes(ThisAssembly) + .As(); + + + builder.RegisterGenericDecorator( + typeof(CommandHandlerDecorator<>), + typeof(ICommandHandler<>)); // // Registrer en decorator for alle ICommandHandler // builder.RegisterGenericDecorator( // typeof(CommandHandlerDecorator<,>), // typeof(ICommandHandler<,>)); - } } } \ No newline at end of file diff --git a/PlanTempus.Components/PlanTempus.Components.csproj b/PlanTempus.Components/PlanTempus.Components.csproj index d925cd4..2ea9caa 100644 --- a/PlanTempus.Components/PlanTempus.Components.csproj +++ b/PlanTempus.Components/PlanTempus.Components.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable diff --git a/PlanTempus.Components/Users/Create/CreateUserHandlerDecorator.cs b/PlanTempus.Components/Users/Create/CommandHandlerDecorator.cs similarity index 56% rename from PlanTempus.Components/Users/Create/CreateUserHandlerDecorator.cs rename to PlanTempus.Components/Users/Create/CommandHandlerDecorator.cs index 8f9289c..c2b7531 100644 --- a/PlanTempus.Components/Users/Create/CreateUserHandlerDecorator.cs +++ b/PlanTempus.Components/Users/Create/CommandHandlerDecorator.cs @@ -1,27 +1,30 @@ using System.Diagnostics; using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights.DataContracts; +using PlanTempus.Core.CommandQueries; namespace PlanTempus.Components.Users.Create; -public class CreateUserHandlerDecorator( - ICommandHandler decoratedHandler, - TelemetryClient telemetryClient) - : ICommandHandler, ICommandHandlerDecorator +public class CommandHandlerDecorator( + ICommandHandler decoratedHandler, + TelemetryClient telemetryClient) : ICommandHandler, ICommandHandlerDecorator where TCommand : ICommand { - public async Task Handle(CreateUserCommand command) + public async Task Handle(TCommand command) { // var correlationId = Activity.Current?.RootId ?? command.CorrelationId; using (var operation = - telemetryClient.StartOperation($"Handle {nameof(CreateUserCommand)}", + telemetryClient.StartOperation($"Handle {decoratedHandler.GetType().FullName}", command.CorrelationId.ToString())) { try { operation.Telemetry.Properties["CorrelationId"] = command.CorrelationId.ToString(); + operation.Telemetry.Properties["TransactionId"] = command.TransactionId.ToString(); + var result = await decoratedHandler.Handle(command); + operation.Telemetry.Properties["RequestId"] = result.RequestId.ToString(); operation.Telemetry.Success = true; return result; @@ -32,7 +35,9 @@ public class CreateUserHandlerDecorator( telemetryClient.TrackException(ex, new Dictionary { - ["CommandType"] = nameof(CreateUserCommand) + ["CorrelationId"] = command.CorrelationId.ToString(), + ["Command"] = command.GetType().Name, + ["CommandHandler"] = decoratedHandler.GetType().FullName }); throw; } diff --git a/PlanTempus.Components/Users/Create/CreateUserCommand.cs b/PlanTempus.Components/Users/Create/CreateUserCommand.cs index 7a69f53..5d0ef11 100644 --- a/PlanTempus.Components/Users/Create/CreateUserCommand.cs +++ b/PlanTempus.Components/Users/Create/CreateUserCommand.cs @@ -1,15 +1,11 @@ +using PlanTempus.Core.CommandQueries; + namespace PlanTempus.Components.Users.Create { - public interface ICommand - { - Guid CorrelationId { get; set; } - } - - public class CreateUserCommand : ICommand + public class CreateUserCommand : Command { public required string Email { get; set; } public required string Password { get; set; } public bool IsActive { get; set; } = true; - public required Guid CorrelationId { get; set; } } } \ No newline at end of file diff --git a/PlanTempus.Components/Users/Create/CreateUserController.cs b/PlanTempus.Components/Users/Create/CreateUserController.cs index 7ed1e0f..487b0dc 100644 --- a/PlanTempus.Components/Users/Create/CreateUserController.cs +++ b/PlanTempus.Components/Users/Create/CreateUserController.cs @@ -1,34 +1,31 @@ using Microsoft.AspNetCore.Mvc; +using PlanTempus.Core.CommandQueries; namespace PlanTempus.Components.Users.Create { - [ApiController] - [Route("api/users")] - public class CreateUserController(CreateUserHandler handler, CreateUserValidator validator) : ControllerBase - { - [HttpPost] - public async Task> Create([FromBody] CreateUserCommand command) - { - try - { - var validationResult = await validator.ValidateAsync(command); - if (!validationResult.IsValid) - { - return BadRequest(validationResult.Errors); - } + [ApiController] + [Route("api/users")] + public class CreateUserController(CreateUserHandler handler, CreateUserValidator validator) : ControllerBase + { + [HttpPost] + public async Task> Create([FromBody] CreateUserCommand command) + { + try + { + var validationResult = await validator.ValidateAsync(command); + if (!validationResult.IsValid) + { + return BadRequest(validationResult.Errors); + } - var result = await handler.Handle(command); - - return CreatedAtAction( - nameof(CreateUserCommand), - "GetUser", - new { id = result.Id }, - result); - } - catch (Exception ex) - { - return BadRequest(ex.Message); - } - } - } + var result = await handler.Handle(command); + + return Accepted($"/api/requests/{result.RequestId}", result); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + } } \ No newline at end of file diff --git a/PlanTempus.Components/Users/Create/CreateUserHandler.cs b/PlanTempus.Components/Users/Create/CreateUserHandler.cs index 39c63ff..651f899 100644 --- a/PlanTempus.Components/Users/Create/CreateUserHandler.cs +++ b/PlanTempus.Components/Users/Create/CreateUserHandler.cs @@ -3,17 +3,16 @@ using Microsoft.ApplicationInsights; using Npgsql; using PlanTempus.Components.Users.Exceptions; using PlanTempus.Core; +using PlanTempus.Core.CommandQueries; using PlanTempus.Core.Database; -using PlanTempus.Core.Telemetry; namespace PlanTempus.Components.Users.Create { public class CreateUserHandler( - TelemetryClient telemetryClient, IDatabaseOperations databaseOperations, - ISecureTokenizer secureTokenizer) : ICommandHandler + ISecureTokenizer secureTokenizer) : ICommandHandler { - public async Task Handle(CreateUserCommand command) + public async Task Handle(CreateUserCommand command) { using var db = databaseOperations.CreateScope(nameof(CreateUserHandler)); try @@ -28,20 +27,19 @@ namespace PlanTempus.Components.Users.Create var data = await db.Connection.QuerySqlAsync(sql, new { - Email = command.Email, + command.Email, PasswordHash = secureTokenizer.TokenizeText(command.Password), SecurityStamp = Guid.NewGuid().ToString("N"), EmailConfirmed = false, AccessFailedCount = 0, LockoutEnabled = false, - IsActive = command.IsActive, + command.IsActive, }); var result = data.First(); - - return result; + return new CommandResponse(command.CorrelationId); } catch (PostgresException ex) when (ex.SqlState == "23505") { diff --git a/PlanTempus.X.BDD/PlanTempus.X.BDD.csproj b/PlanTempus.X.BDD/PlanTempus.X.BDD.csproj index 8844c36..0b2c521 100644 --- a/PlanTempus.X.BDD/PlanTempus.X.BDD.csproj +++ b/PlanTempus.X.BDD/PlanTempus.X.BDD.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable false diff --git a/SetupInfrastructure/PlanTempus.SetupInfrastructure.csproj b/SetupInfrastructure/PlanTempus.SetupInfrastructure.csproj index a813b20..5c9236b 100644 --- a/SetupInfrastructure/PlanTempus.SetupInfrastructure.csproj +++ b/SetupInfrastructure/PlanTempus.SetupInfrastructure.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 enable diff --git a/Tests/CommandQueryHandlerTests/HandlerTest.cs b/Tests/CommandQueryHandlerTests/HandlerTest.cs index 4d3c262..b1c4742 100644 --- a/Tests/CommandQueryHandlerTests/HandlerTest.cs +++ b/Tests/CommandQueryHandlerTests/HandlerTest.cs @@ -2,6 +2,7 @@ using Autofac; using Insight.Database; using PlanTempus.Components; using PlanTempus.Components.Users.Create; +using PlanTempus.Core.CommandQueries; using PlanTempus.Core.Database; using PlanTempus.Core.Database.ConnectionFactory; using Shouldly; @@ -24,13 +25,13 @@ public class HandlerTest : TestFixture var command = new CreateUserCommand { - Email = "lloyd@dumbanddumber.com1", // Lloyd Christmas + Email = "lloyd@dumbanddumber.com3", // Lloyd Christmas Password = "1234AceVentura#LOL", // Ace Ventura IsActive = true, CorrelationId = Guid.NewGuid() }; - var result = await commandHandler.Handle(command); + var result = await commandHandler.Handle(command); result.ShouldNotBeNull(); } diff --git a/Tests/PlanTempus.X.TDD.csproj b/Tests/PlanTempus.X.TDD.csproj index ec3d895..0e69882 100644 --- a/Tests/PlanTempus.X.TDD.csproj +++ b/Tests/PlanTempus.X.TDD.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable false