From eb43daed04331edbb362b9cf09ff3b07a3a4000e Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 16:30:57 +0200 Subject: [PATCH 01/23] Update code style, API usage, and middleware config - Set latest analysis level and adjust warning settings in Directory.Build.props - Add Scalar API reference and improve exception handling in MiddlewareConfig - Standardize cancellation token usage in endpoints - Pass cancellation token to Task.Delay in NoOpMediator --- Directory.Build.props | 3 +++ .../Configurations/MiddlewareConfig.cs | 10 +++++----- src/Clean.Architecture.Web/Contributors/Create.cs | 4 ++-- src/Clean.Architecture.Web/Contributors/List.cs | 8 ++++---- tests/Clean.Architecture.UnitTests/NoOpMediator.cs | 2 +- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 1b57a4a27..29bd78c81 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,10 @@ true true + latest + Recommended true + false net10.0 enable enable diff --git a/src/Clean.Architecture.Web/Configurations/MiddlewareConfig.cs b/src/Clean.Architecture.Web/Configurations/MiddlewareConfig.cs index 755847cf7..38f92d345 100644 --- a/src/Clean.Architecture.Web/Configurations/MiddlewareConfig.cs +++ b/src/Clean.Architecture.Web/Configurations/MiddlewareConfig.cs @@ -14,7 +14,7 @@ public static async Task UseAppMiddlewareAndSeedDatabase(th app.UseShowAllServicesMiddleware(); // see https://github.com/ardalis/AspNetCoreStartupServices } else - { + { app.UseDefaultExceptionHandler(); // from FastEndpoints app.UseHsts(); } @@ -32,7 +32,7 @@ public static async Task UseAppMiddlewareAndSeedDatabase(th settings.Path = "/swagger"; settings.DocumentPath = "/openapi/{documentName}.json"; }); - + app.MapScalarApiReference(options => { options.WithTitle("Clean Architecture API"); @@ -43,9 +43,9 @@ public static async Task UseAppMiddlewareAndSeedDatabase(th app.UseHttpsRedirection(); // Note this will drop Authorization headers // Run migrations and seed in Development or when explicitly requested via environment variable - var shouldMigrate = app.Environment.IsDevelopment() || + var shouldMigrate = app.Environment.IsDevelopment() || app.Configuration.GetValue("Database:ApplyMigrationsOnStartup"); - + if (shouldMigrate) { await MigrateDatabaseAsync(app); @@ -65,7 +65,7 @@ static async Task MigrateDatabaseAsync(WebApplication app) { logger.LogInformation("Applying database migrations..."); var context = services.GetRequiredService(); - + // For SQLite, use EnsureCreated instead of migrations (common for dev/local scenarios) // For SQL Server, use migrations (production scenario) if (context.Database.IsSqlite()) diff --git a/src/Clean.Architecture.Web/Contributors/Create.cs b/src/Clean.Architecture.Web/Contributors/Create.cs index e061d8fc4..6135a2935 100644 --- a/src/Clean.Architecture.Web/Contributors/Create.cs +++ b/src/Clean.Architecture.Web/Contributors/Create.cs @@ -49,9 +49,9 @@ public override void Configure() } public override async Task, ValidationProblem, ProblemHttpResult>> - ExecuteAsync(CreateContributorRequest request, CancellationToken cancellationToken) + ExecuteAsync(CreateContributorRequest request, CancellationToken ct) { - var result = await _mediator.Send(new CreateContributorCommand(ContributorName.From(request.Name!), request.PhoneNumber)); + var result = await _mediator.Send(new CreateContributorCommand(ContributorName.From(request.Name!), request.PhoneNumber), ct); return result.ToCreatedResult( id => $"/Contributors/{id}", diff --git a/src/Clean.Architecture.Web/Contributors/List.cs b/src/Clean.Architecture.Web/Contributors/List.cs index 6914695c5..53e35d610 100644 --- a/src/Clean.Architecture.Web/Contributors/List.cs +++ b/src/Clean.Architecture.Web/Contributors/List.cs @@ -46,12 +46,12 @@ public override void Configure() .ProducesProblem(400)); } - public override async Task HandleAsync(ListContributorsRequest request, CancellationToken cancellationToken) + public override async Task HandleAsync(ListContributorsRequest request, CancellationToken ct) { - var result = await _mediator.Send(new ListContributorsQuery(request.Page, request.PerPage)); + var result = await _mediator.Send(new ListContributorsQuery(request.Page, request.PerPage), ct); if (!result.IsSuccess) { - await Send.ErrorsAsync(statusCode: 400, cancellationToken); + await Send.ErrorsAsync(statusCode: 400, ct); return; } @@ -59,7 +59,7 @@ public override async Task HandleAsync(ListContributorsRequest request, Cancella AddLinkHeader(pagedResult.Page, pagedResult.PerPage, pagedResult.TotalPages); var response = Map.FromEntity(pagedResult); - await Send.OkAsync(response, cancellationToken); + await Send.OkAsync(response, ct); } private void AddLinkHeader(int page, int perPage, int totalPages) diff --git a/tests/Clean.Architecture.UnitTests/NoOpMediator.cs b/tests/Clean.Architecture.UnitTests/NoOpMediator.cs index 2358dab52..97d4e3c1a 100644 --- a/tests/Clean.Architecture.UnitTests/NoOpMediator.cs +++ b/tests/Clean.Architecture.UnitTests/NoOpMediator.cs @@ -4,7 +4,7 @@ public class NoOpMediator : IMediator { public async Task> CreateStream(IStreamQuery query, CancellationToken cancellationToken = default) { - await Task.Delay(1); + await Task.Delay(1, cancellationToken); return AsyncEnumerable.Empty(); } From 4d2b4a69c594cda530e0326c0e3fdaf7d2dbd32a Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 16:40:23 +0200 Subject: [PATCH 02/23] Override DisposeAsync and improve resource cleanup Changed DisposeAsync in CustomWebApplicationFactory from new to override, added call to base.DisposeAsync, and suppressed finalization to ensure proper resource disposal and .NET best practices. --- .../CustomWebApplicationFactory.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/Clean.Architecture.FunctionalTests/CustomWebApplicationFactory.cs b/tests/Clean.Architecture.FunctionalTests/CustomWebApplicationFactory.cs index 0a040d0df..be1658483 100644 --- a/tests/Clean.Architecture.FunctionalTests/CustomWebApplicationFactory.cs +++ b/tests/Clean.Architecture.FunctionalTests/CustomWebApplicationFactory.cs @@ -25,7 +25,7 @@ public async ValueTask InitializeAsync() } } - public new async ValueTask DisposeAsync() + public override async ValueTask DisposeAsync() { // Clean up environment variable Environment.SetEnvironmentVariable("USE_SQL_SERVER", null); @@ -33,6 +33,9 @@ public async ValueTask InitializeAsync() { await _dbContainer.DisposeAsync(); } + + await base.DisposeAsync(); + GC.SuppressFinalize(this); } /// From aa1127e48faf7140b746027ad7725bd897920cbe Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 16:50:53 +0200 Subject: [PATCH 03/23] Use InvariantCulture for route and log formatting Ensure all int-to-string conversions for routes and logging use CultureInfo.InvariantCulture for consistent, culture-agnostic output across LoggerConfigs and contributor request files. This improves reliability in globalized scenarios. --- src/Clean.Architecture.Web/Configurations/LoggerConfigs.cs | 5 +++-- .../Contributors/Delete.DeleteContributorRequest.cs | 6 ++++-- .../Contributors/GetById.GetContributorByIdRequest.cs | 6 ++++-- .../Contributors/Update.UpdateContributorRequest.cs | 3 ++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Clean.Architecture.Web/Configurations/LoggerConfigs.cs b/src/Clean.Architecture.Web/Configurations/LoggerConfigs.cs index d5c3ed9c4..cc12e64a1 100644 --- a/src/Clean.Architecture.Web/Configurations/LoggerConfigs.cs +++ b/src/Clean.Architecture.Web/Configurations/LoggerConfigs.cs @@ -1,4 +1,5 @@ -using Serilog; +using System.Globalization; +using Serilog; namespace Clean.Architecture.Web.Configurations; @@ -12,7 +13,7 @@ public static WebApplicationBuilder AddLoggerConfigs(this WebApplicationBuilder .ReadFrom.Configuration(builder.Configuration) .Enrich.FromLogContext() .Enrich.WithProperty("Application", builder.Environment.ApplicationName) - .WriteTo.Console() + .WriteTo.Console(formatProvider: CultureInfo.InvariantCulture) .CreateLogger()); return builder; diff --git a/src/Clean.Architecture.Web/Contributors/Delete.DeleteContributorRequest.cs b/src/Clean.Architecture.Web/Contributors/Delete.DeleteContributorRequest.cs index dbd7dca19..172345769 100644 --- a/src/Clean.Architecture.Web/Contributors/Delete.DeleteContributorRequest.cs +++ b/src/Clean.Architecture.Web/Contributors/Delete.DeleteContributorRequest.cs @@ -1,9 +1,11 @@ -namespace Clean.Architecture.Web.Contributors; +using System.Globalization; + +namespace Clean.Architecture.Web.Contributors; public record DeleteContributorRequest { public const string Route = "/Contributors/{ContributorId:int}"; - public static string BuildRoute(int contributorId) => Route.Replace("{ContributorId:int}", contributorId.ToString()); + public static string BuildRoute(int contributorId) => Route.Replace("{ContributorId:int}", contributorId.ToString(CultureInfo.InvariantCulture)); public int ContributorId { get; set; } } diff --git a/src/Clean.Architecture.Web/Contributors/GetById.GetContributorByIdRequest.cs b/src/Clean.Architecture.Web/Contributors/GetById.GetContributorByIdRequest.cs index e7dec507f..daf609c1c 100644 --- a/src/Clean.Architecture.Web/Contributors/GetById.GetContributorByIdRequest.cs +++ b/src/Clean.Architecture.Web/Contributors/GetById.GetContributorByIdRequest.cs @@ -1,9 +1,11 @@ -namespace Clean.Architecture.Web.Contributors; +using System.Globalization; + +namespace Clean.Architecture.Web.Contributors; public class GetContributorByIdRequest { public const string Route = "/Contributors/{ContributorId:int}"; - public static string BuildRoute(int contributorId) => Route.Replace("{ContributorId:int}", contributorId.ToString()); + public static string BuildRoute(int contributorId) => Route.Replace("{ContributorId:int}", contributorId.ToString(CultureInfo.InvariantCulture)); public int ContributorId { get; set; } } diff --git a/src/Clean.Architecture.Web/Contributors/Update.UpdateContributorRequest.cs b/src/Clean.Architecture.Web/Contributors/Update.UpdateContributorRequest.cs index a59341279..e2cbb367c 100644 --- a/src/Clean.Architecture.Web/Contributors/Update.UpdateContributorRequest.cs +++ b/src/Clean.Architecture.Web/Contributors/Update.UpdateContributorRequest.cs @@ -1,11 +1,12 @@ using System.ComponentModel.DataAnnotations; +using System.Globalization; namespace Clean.Architecture.Web.Contributors; public class UpdateContributorRequest { public const string Route = "/Contributors/{ContributorId:int}"; - public static string BuildRoute(int contributorId) => Route.Replace("{ContributorId:int}", contributorId.ToString()); + public static string BuildRoute(int contributorId) => Route.Replace("{ContributorId:int}", contributorId.ToString(CultureInfo.InvariantCulture)); public int ContributorId { get; set; } From 5c5606168fc7ddc0e533feb94249e4fef630ad19 Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 17:00:47 +0200 Subject: [PATCH 04/23] issue-736:: removed ca1307 warningss --- src/Clean.Architecture.ServiceDefaults/Extensions.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Clean.Architecture.ServiceDefaults/Extensions.cs b/src/Clean.Architecture.ServiceDefaults/Extensions.cs index 8ffe5aed0..226ccf45d 100644 --- a/src/Clean.Architecture.ServiceDefaults/Extensions.cs +++ b/src/Clean.Architecture.ServiceDefaults/Extensions.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.ServiceDiscovery; using OpenTelemetry; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; @@ -65,8 +64,8 @@ public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) w .AddAspNetCoreInstrumentation(tracing => // Exclude health check requests from tracing tracing.Filter = context => - !context.Request.Path.StartsWithSegments(HealthEndpointPath) - && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) + !context.Request.Path.StartsWithSegments(HealthEndpointPath, StringComparison.OrdinalIgnoreCase) + && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath, StringComparison.OrdinalIgnoreCase) ) // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) //.AddGrpcClientInstrumentation() From 54baf64ca444f06fa2c82ab5c2a1e4eb7cae19b2 Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 17:22:27 +0200 Subject: [PATCH 05/23] issue-736: disabled S125 because at tsat place S125 is wrong --- src/Clean.Architecture.ServiceDefaults/Extensions.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Clean.Architecture.ServiceDefaults/Extensions.cs b/src/Clean.Architecture.ServiceDefaults/Extensions.cs index 226ccf45d..22f5956b9 100644 --- a/src/Clean.Architecture.ServiceDefaults/Extensions.cs +++ b/src/Clean.Architecture.ServiceDefaults/Extensions.cs @@ -33,12 +33,13 @@ public static TBuilder AddServiceDefaults(this TBuilder builder) where // Turn on service discovery by default http.AddServiceDiscovery(); }); - +#pragma warning disable S125 // This template intentionally contains commented sample configuration. // Uncomment the following to restrict the allowed schemes for service discovery. // builder.Services.Configure(options => // { // options.AllowedSchemes = ["https"]; // }); +#pragma warning restore S125 return builder; } @@ -85,13 +86,14 @@ private static TBuilder AddOpenTelemetryExporters(this TBuilder builde { builder.Services.AddOpenTelemetry().UseOtlpExporter(); } - +#pragma warning disable S125 //this is there on purpose to show how to conditionally add exporters based on configuration, and to avoid confusion about the presence of multiple exporters in this template. // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) //{ // builder.Services.AddOpenTelemetry() // .UseAzureMonitor(); //} +#pragma warning restore return builder; } From 19ce16d7ad415b2aa2daccf81ad5c98b06bdf629 Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 17:40:46 +0200 Subject: [PATCH 06/23] issue-736: removed ca1707 warning & CA1727 hanged xml doc accordingly --- .editorconfig | 5 ++++- Clean.Architecture.slnx | 1 + .../Services/DeleteContributorService.cs | 21 ++++++++++++------- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/.editorconfig b/.editorconfig index 7ea9b6496..4a52dfc78 100644 --- a/.editorconfig +++ b/.editorconfig @@ -148,4 +148,7 @@ csharp_style_expression_bodied_local_functions = false:silent ############################### [*.vb] # Modifier preferences -visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion \ No newline at end of file +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion +# Test projects often use underscores in test method/class names for readability. +[tests/**/*.cs] +dotnet_diagnostic.CA1707.severity = none diff --git a/Clean.Architecture.slnx b/Clean.Architecture.slnx index 77b21ac68..27cf5bcf9 100644 --- a/Clean.Architecture.slnx +++ b/Clean.Architecture.slnx @@ -22,6 +22,7 @@ + diff --git a/src/Clean.Architecture.Core/Services/DeleteContributorService.cs b/src/Clean.Architecture.Core/Services/DeleteContributorService.cs index 153fb0a0b..5f71a920d 100644 --- a/src/Clean.Architecture.Core/Services/DeleteContributorService.cs +++ b/src/Clean.Architecture.Core/Services/DeleteContributorService.cs @@ -8,20 +8,27 @@ namespace Clean.Architecture.Core.Services; /// This is here mainly so there's an example of a domain service /// and also to demonstrate how to fire domain events from a service. /// -/// -/// -/// -public class DeleteContributorService(IRepository _repository, - IMediator _mediator, - ILogger _logger) : IDeleteContributorService +/// The repository used to load and delete contributors. +/// The mediator used to publish domain events. +/// The logger used by the service. +public class DeleteContributorService( + IRepository repository, + IMediator mediator, + ILogger logger) : IDeleteContributorService { + private readonly IRepository _repository = repository; + private readonly IMediator _mediator = mediator; + private readonly ILogger _logger = logger; + public async ValueTask DeleteContributor(ContributorId contributorId) { - _logger.LogInformation("Deleting Contributor {contributorId}", contributorId); + _logger.LogInformation("Deleting Contributor {ContributorId}", contributorId); + Contributor? aggregateToDelete = await _repository.GetByIdAsync(contributorId); if (aggregateToDelete == null) return Result.NotFound(); await _repository.DeleteAsync(aggregateToDelete); + var domainEvent = new ContributorDeletedEvent(contributorId); await _mediator.Publish(domainEvent); From f20a2b1c0585bf4772591c1f3e051197f2112594 Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 17:53:46 +0200 Subject: [PATCH 07/23] Refactor Constants class and update naming conventions Changed Constants to static and renamed fields to PascalCase. Updated all usages to match new names across queries, endpoints, DTOs, and validation to align with .NET standards. --- src/Clean.Architecture.UseCases/Constants.cs | 6 +++--- .../Contributors/List/ListContributorsHandler.cs | 2 +- .../Contributors/List/ListContributorsQuery.cs | 2 +- src/Clean.Architecture.Web/Contributors/List.cs | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Clean.Architecture.UseCases/Constants.cs b/src/Clean.Architecture.UseCases/Constants.cs index 22ba6c62e..07ba5eb8c 100644 --- a/src/Clean.Architecture.UseCases/Constants.cs +++ b/src/Clean.Architecture.UseCases/Constants.cs @@ -1,7 +1,7 @@ namespace Clean.Architecture.UseCases; -public class Constants +public static class Constants { - public const int DEFAULT_PAGE_SIZE = 10; - public const int MAX_PAGE_SIZE = 100; + public const int DefaultPageSize = 10; + public const int MaxPageSize = 100; } diff --git a/src/Clean.Architecture.UseCases/Contributors/List/ListContributorsHandler.cs b/src/Clean.Architecture.UseCases/Contributors/List/ListContributorsHandler.cs index 1dbcd8aa2..2a6321135 100644 --- a/src/Clean.Architecture.UseCases/Contributors/List/ListContributorsHandler.cs +++ b/src/Clean.Architecture.UseCases/Contributors/List/ListContributorsHandler.cs @@ -13,7 +13,7 @@ public async ValueTask>> Handle(ListContribut CancellationToken cancellationToken) { - var result = await _query.ListAsync(request.Page ?? 1, request.PerPage ?? Constants.DEFAULT_PAGE_SIZE); + var result = await _query.ListAsync(request.Page ?? 1, request.PerPage ?? Constants.DefaultPageSize); return Result.Success(result); } diff --git a/src/Clean.Architecture.UseCases/Contributors/List/ListContributorsQuery.cs b/src/Clean.Architecture.UseCases/Contributors/List/ListContributorsQuery.cs index b3dda9ee8..db3f7bc59 100644 --- a/src/Clean.Architecture.UseCases/Contributors/List/ListContributorsQuery.cs +++ b/src/Clean.Architecture.UseCases/Contributors/List/ListContributorsQuery.cs @@ -1,4 +1,4 @@ namespace Clean.Architecture.UseCases.Contributors.List; -public record ListContributorsQuery(int? Page = 1, int? PerPage = Constants.DEFAULT_PAGE_SIZE) +public record ListContributorsQuery(int? Page = 1, int? PerPage = Constants.DefaultPageSize) : IQuery>>; diff --git a/src/Clean.Architecture.Web/Contributors/List.cs b/src/Clean.Architecture.Web/Contributors/List.cs index 53e35d610..1d0315168 100644 --- a/src/Clean.Architecture.Web/Contributors/List.cs +++ b/src/Clean.Architecture.Web/Contributors/List.cs @@ -29,7 +29,7 @@ public override void Configure() // Document pagination parameters s.Params["page"] = "1-based page index (default 1)"; - s.Params["per_page"] = $"Page size 1–{UseCases.Constants.MAX_PAGE_SIZE} (default {UseCases.Constants.DEFAULT_PAGE_SIZE})"; + s.Params["per_page"] = $"Page size 1–{UseCases.Constants.MaxPageSize} (default {UseCases.Constants.DefaultPageSize})"; // Document possible responses s.Responses[200] = "Paginated list of contributors returned successfully"; @@ -92,7 +92,7 @@ public sealed class ListContributorsRequest // Bind to ?per_page= [BindFrom("per_page")] - public int PerPage { get; init; } = UseCases.Constants.DEFAULT_PAGE_SIZE; + public int PerPage { get; init; } = UseCases.Constants.DefaultPageSize; } public record ContributorListResponse : UseCases.PagedResult @@ -113,8 +113,8 @@ public ListContributorsValidator() .WithMessage("page must be >= 1"); RuleFor(x => x.PerPage) - .InclusiveBetween(1, UseCases.Constants.MAX_PAGE_SIZE) - .WithMessage($"per_page must be between 1 and {UseCases.Constants.MAX_PAGE_SIZE}"); + .InclusiveBetween(1, UseCases.Constants.MaxPageSize) + .WithMessage($"per_page must be between 1 and {UseCases.Constants.MaxPageSize}"); } } From 9abd6383b8e7d939745f0e1306920c06b3d5287f Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 18:04:56 +0200 Subject: [PATCH 08/23] Update log parameter names to PascalCase for consistency Changed structured logging placeholders from lowercase to PascalCase across multiple files to improve consistency and align with .NET logging conventions. --- .../Email/FakeEmailSender.cs | 2 +- .../Email/MimeKitEmailSender.cs | 8 ++++---- .../Configurations/MiddlewareConfig.cs | 4 ++-- .../CustomWebApplicationFactory.cs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Clean.Architecture.Infrastructure/Email/FakeEmailSender.cs b/src/Clean.Architecture.Infrastructure/Email/FakeEmailSender.cs index 945d002d6..2ed14d834 100644 --- a/src/Clean.Architecture.Infrastructure/Email/FakeEmailSender.cs +++ b/src/Clean.Architecture.Infrastructure/Email/FakeEmailSender.cs @@ -7,7 +7,7 @@ public class FakeEmailSender(ILogger logger) : IEmailSender private readonly ILogger _logger = logger; public Task SendEmailAsync(string to, string from, string subject, string body) { - _logger.LogInformation("Not actually sending an email to {to} from {from} with subject {subject}", to, from, subject); + _logger.LogInformation("Not actually sending an email to {To} from {From} with subject {Subject}", to, from, subject); return Task.CompletedTask; } } diff --git a/src/Clean.Architecture.Infrastructure/Email/MimeKitEmailSender.cs b/src/Clean.Architecture.Infrastructure/Email/MimeKitEmailSender.cs index c1846335a..d0e5cfd0e 100644 --- a/src/Clean.Architecture.Infrastructure/Email/MimeKitEmailSender.cs +++ b/src/Clean.Architecture.Infrastructure/Email/MimeKitEmailSender.cs @@ -10,10 +10,10 @@ public class MimeKitEmailSender(ILogger logger, public async Task SendEmailAsync(string to, string from, string subject, string body) { - _logger.LogWarning("Sending email to {to} from {from} with subject {subject} using {type}.", to, from, subject, this.ToString()); + _logger.LogWarning("Sending email to {To} from {From} with subject {Subject} using {Type}.", to, from, subject, this.ToString()); - using var client = new MailKit.Net.Smtp.SmtpClient(); - await client.ConnectAsync(_mailserverConfiguration.Hostname, + using var client = new MailKit.Net.Smtp.SmtpClient(); + await client.ConnectAsync(_mailserverConfiguration.Hostname, _mailserverConfiguration.Port, false); var message = new MimeMessage(); message.From.Add(new MailboxAddress(from, from)); @@ -23,7 +23,7 @@ await client.ConnectAsync(_mailserverConfiguration.Hostname, await client.SendAsync(message); - await client.DisconnectAsync(true, + await client.DisconnectAsync(true, new CancellationToken(canceled: true)); } } diff --git a/src/Clean.Architecture.Web/Configurations/MiddlewareConfig.cs b/src/Clean.Architecture.Web/Configurations/MiddlewareConfig.cs index 38f92d345..dc4210d7a 100644 --- a/src/Clean.Architecture.Web/Configurations/MiddlewareConfig.cs +++ b/src/Clean.Architecture.Web/Configurations/MiddlewareConfig.cs @@ -81,7 +81,7 @@ static async Task MigrateDatabaseAsync(WebApplication app) } catch (Exception ex) { - logger.LogError(ex, "An error occurred migrating the DB. {exceptionMessage}", ex.Message); + logger.LogError(ex, "An error occurred migrating the DB. {ExceptionMessage}", ex.Message); throw; // Re-throw to make startup fail if migrations fail } } @@ -101,7 +101,7 @@ static async Task SeedDatabaseAsync(WebApplication app) } catch (Exception ex) { - logger.LogError(ex, "An error occurred seeding the DB. {exceptionMessage}", ex.Message); + logger.LogError(ex, "An error occurred seeding the DB. {ExceptionMessage}", ex.Message); // Don't re-throw for seeding errors - it's not critical } } diff --git a/tests/Clean.Architecture.FunctionalTests/CustomWebApplicationFactory.cs b/tests/Clean.Architecture.FunctionalTests/CustomWebApplicationFactory.cs index be1658483..612538875 100644 --- a/tests/Clean.Architecture.FunctionalTests/CustomWebApplicationFactory.cs +++ b/tests/Clean.Architecture.FunctionalTests/CustomWebApplicationFactory.cs @@ -74,7 +74,7 @@ protected override IHost CreateHost(IHostBuilder builder) } catch (Exception ex) { - logger.LogError(ex, "An error occurred seeding the database with test messages. Error: {exceptionMessage}", ex.Message); + logger.LogError(ex, "An error occurred seeding the database with test messages. Error: {ExceptionMessage}", ex.Message); throw; } } From 4ec38fc716c747947c5f7cf450a42652b619864b Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 18:07:54 +0200 Subject: [PATCH 09/23] Update constant naming to PascalCase and cleanup usings Renamed constants to PascalCase for .NET convention compliance, updated all references, and removed an unused FastEndpoints using directive. --- .../Data/Config/DataSchemaConstants.cs | 2 +- src/Clean.Architecture.Infrastructure/Data/SeedData.cs | 4 ++-- .../Contributors/Update.UpdateContributorValidator.cs | 3 +-- .../ApiEndpoints/ContributorList.cs | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Clean.Architecture.Infrastructure/Data/Config/DataSchemaConstants.cs b/src/Clean.Architecture.Infrastructure/Data/Config/DataSchemaConstants.cs index 302816215..675c8101c 100644 --- a/src/Clean.Architecture.Infrastructure/Data/Config/DataSchemaConstants.cs +++ b/src/Clean.Architecture.Infrastructure/Data/Config/DataSchemaConstants.cs @@ -2,5 +2,5 @@ public static class DataSchemaConstants { - public const int DEFAULT_NAME_LENGTH = 100; + public const int DefaultNameLength = 100; } diff --git a/src/Clean.Architecture.Infrastructure/Data/SeedData.cs b/src/Clean.Architecture.Infrastructure/Data/SeedData.cs index 8e447d5d7..cde052242 100644 --- a/src/Clean.Architecture.Infrastructure/Data/SeedData.cs +++ b/src/Clean.Architecture.Infrastructure/Data/SeedData.cs @@ -4,7 +4,7 @@ namespace Clean.Architecture.Infrastructure.Data; public static class SeedData { - public const int NUMBER_OF_CONTRIBUTORS = 27; // including the 2 below + public const int NumberOfContributors = 27; // including the 2 below public static readonly ContributorName Contributor1Name = ContributorName.From("Ardalis"); public static readonly ContributorName Contributor2Name = ContributorName.From("Ilyana"); @@ -22,7 +22,7 @@ public static async Task PopulateTestDataAsync(AppDbContext dbContext) await dbContext.Database.ExecuteSqlInterpolatedAsync($"INSERT INTO [Contributors] ([Name], [Status]) VALUES ({Contributor2Name.Value}, {ContributorStatus.NotSet.Value})"); // Add a bunch more contributors to support demonstrating paging. - for (int i = 1; i <= NUMBER_OF_CONTRIBUTORS - 2; i++) + for (int i = 1; i <= NumberOfContributors - 2; i++) { await dbContext.Database.ExecuteSqlInterpolatedAsync($"INSERT INTO [Contributors] ([Name], [Status]) VALUES ({$"Contributor {i}"}, {ContributorStatus.NotSet.Value})"); } diff --git a/src/Clean.Architecture.Web/Contributors/Update.UpdateContributorValidator.cs b/src/Clean.Architecture.Web/Contributors/Update.UpdateContributorValidator.cs index c2e114edf..a9358b366 100644 --- a/src/Clean.Architecture.Web/Contributors/Update.UpdateContributorValidator.cs +++ b/src/Clean.Architecture.Web/Contributors/Update.UpdateContributorValidator.cs @@ -1,5 +1,4 @@ using Clean.Architecture.Infrastructure.Data.Config; -using FastEndpoints; using FluentValidation; namespace Clean.Architecture.Web.Contributors; @@ -15,7 +14,7 @@ public UpdateContributorValidator() .NotEmpty() .WithMessage("Name is required.") .MinimumLength(2) - .MaximumLength(DataSchemaConstants.DEFAULT_NAME_LENGTH); + .MaximumLength(DataSchemaConstants.DefaultNameLength); RuleFor(x => x.ContributorId) .Must((args, contributorId) => args.Id == contributorId) .WithMessage("Route and body Ids must match; cannot update Id of an existing resource."); diff --git a/tests/Clean.Architecture.FunctionalTests/ApiEndpoints/ContributorList.cs b/tests/Clean.Architecture.FunctionalTests/ApiEndpoints/ContributorList.cs index 548fce886..b17ebb568 100644 --- a/tests/Clean.Architecture.FunctionalTests/ApiEndpoints/ContributorList.cs +++ b/tests/Clean.Architecture.FunctionalTests/ApiEndpoints/ContributorList.cs @@ -13,7 +13,7 @@ public async Task ReturnsTwoContributors() { var result = await _client.GetAndDeserializeAsync("/Contributors"); - Assert.Equal(SeedData.NUMBER_OF_CONTRIBUTORS, result.TotalCount); + Assert.Equal(SeedData.NumberOfContributors, result.TotalCount); Assert.Contains(result.Items, i => i.Name == SeedData.Contributor1Name.Value); Assert.Contains(result.Items, i => i.Name == SeedData.Contributor2Name.Value); } From 3433d38b5dfb12d8fe55bbc4b6cd078c0b8047c9 Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 18:09:36 +0200 Subject: [PATCH 10/23] Update domain event filter to use Count instead of Any Changed entity filtering to use DomainEvents.Count != 0 instead of DomainEvents.Any() when dispatching events. This is a minor refactor with no functional impact. --- .../Data/EventDispatcherInterceptor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Clean.Architecture.Infrastructure/Data/EventDispatcherInterceptor.cs b/src/Clean.Architecture.Infrastructure/Data/EventDispatcherInterceptor.cs index 0d879cdae..fdf5fed3d 100644 --- a/src/Clean.Architecture.Infrastructure/Data/EventDispatcherInterceptor.cs +++ b/src/Clean.Architecture.Infrastructure/Data/EventDispatcherInterceptor.cs @@ -20,7 +20,7 @@ public override async ValueTask SavedChangesAsync(SaveChangesCompletedEvent // Retrieve all tracked entities that have domain events var entitiesWithEvents = appDbContext.ChangeTracker.Entries() .Select(e => e.Entity) - .Where(e => e.DomainEvents.Any()) + .Where(e => e.DomainEvents.Count != 0) .ToArray(); // Dispatch and clear domain events From 4681f78a959415ed06981dbfe46ce11ec859f25a Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 18:28:06 +0200 Subject: [PATCH 11/23] Update logging, DbContext access, and disposal patterns - Use PascalCase {ContributorId} in logging for consistency - Refactor BaseEfRepoTestFixture: private readonly _dbContext, add protected DbContext property - Implement IDisposable and IAsyncDisposable for proper DbContext disposal - Update EfRepositoryUpdate to use new DbContext property --- .../Handlers/ContributorDeletedHandler.cs | 2 +- ...butorNameUpdatedEmailNotificationHandler.cs | 2 +- .../Data/BaseEfRepoTestFixture.cs | 18 ++++++++++++++++-- .../Data/EfRepositoryUpdate.cs | 2 +- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/Clean.Architecture.Core/ContributorAggregate/Handlers/ContributorDeletedHandler.cs b/src/Clean.Architecture.Core/ContributorAggregate/Handlers/ContributorDeletedHandler.cs index 7562dbb51..d9cc68cc2 100644 --- a/src/Clean.Architecture.Core/ContributorAggregate/Handlers/ContributorDeletedHandler.cs +++ b/src/Clean.Architecture.Core/ContributorAggregate/Handlers/ContributorDeletedHandler.cs @@ -8,7 +8,7 @@ public class ContributorDeletedHandler(ILogger logger { public async ValueTask Handle(ContributorDeletedEvent domainEvent, CancellationToken cancellationToken) { - logger.LogInformation("Handling Contributed Deleted event for {contributorId}", domainEvent.ContributorId); + logger.LogInformation("Handling Contributed Deleted event for {ContributorId}", domainEvent.ContributorId); await emailSender.SendEmailAsync("to@test.com", "from@test.com", diff --git a/src/Clean.Architecture.Core/ContributorAggregate/Handlers/ContributorNameUpdatedEmailNotificationHandler.cs b/src/Clean.Architecture.Core/ContributorAggregate/Handlers/ContributorNameUpdatedEmailNotificationHandler.cs index f35f27e51..354733167 100644 --- a/src/Clean.Architecture.Core/ContributorAggregate/Handlers/ContributorNameUpdatedEmailNotificationHandler.cs +++ b/src/Clean.Architecture.Core/ContributorAggregate/Handlers/ContributorNameUpdatedEmailNotificationHandler.cs @@ -9,7 +9,7 @@ public class ContributorNameUpdatedEmailNotificationHandler( { public async ValueTask Handle(ContributorNameUpdatedEvent domainEvent, CancellationToken cancellationToken) { - logger.LogInformation("Handling Contributor Name Updated event for {contributorId}", domainEvent.Contributor.Id); + logger.LogInformation("Handling Contributor Name Updated event for {ContributorId}", domainEvent.Contributor.Id); await emailSender.SendEmailAsync("to@test.com", "from@test.com", diff --git a/tests/Clean.Architecture.IntegrationTests/Data/BaseEfRepoTestFixture.cs b/tests/Clean.Architecture.IntegrationTests/Data/BaseEfRepoTestFixture.cs index d5c7b56f1..ee48bcb15 100644 --- a/tests/Clean.Architecture.IntegrationTests/Data/BaseEfRepoTestFixture.cs +++ b/tests/Clean.Architecture.IntegrationTests/Data/BaseEfRepoTestFixture.cs @@ -3,9 +3,11 @@ namespace Clean.Architecture.IntegrationTests.Data; -public abstract class BaseEfRepoTestFixture +public abstract class BaseEfRepoTestFixture : IDisposable, IAsyncDisposable { - protected AppDbContext _dbContext; + private readonly AppDbContext _dbContext; + + protected AppDbContext DbContext => _dbContext; protected BaseEfRepoTestFixture() { @@ -40,4 +42,16 @@ protected EfRepository GetRepository() { return new EfRepository(_dbContext); } + + public void Dispose() + { + GC.SuppressFinalize(this); + _dbContext.Dispose(); + } + + public async ValueTask DisposeAsync() + { + GC.SuppressFinalize(this); + await _dbContext.DisposeAsync(); + } } diff --git a/tests/Clean.Architecture.IntegrationTests/Data/EfRepositoryUpdate.cs b/tests/Clean.Architecture.IntegrationTests/Data/EfRepositoryUpdate.cs index 7d099503a..d675317d8 100644 --- a/tests/Clean.Architecture.IntegrationTests/Data/EfRepositoryUpdate.cs +++ b/tests/Clean.Architecture.IntegrationTests/Data/EfRepositoryUpdate.cs @@ -16,7 +16,7 @@ public async Task UpdatesItemAfterAddingIt() await repository.AddAsync(Contributor, cancellationToken); // detach the item so we get a different instance - _dbContext.Entry(Contributor).State = EntityState.Detached; + DbContext.Entry(Contributor).State = EntityState.Detached; // fetch the item and update its title var newContributor = (await repository.ListAsync(cancellationToken)) From f872ca81a28cc312edade2d23cc0a7e8f4cc9950 Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 18:38:48 +0200 Subject: [PATCH 12/23] issues-736: renamed parameters fror IMailSender to no longer probok a warning brcaus to is a reserfved word --- src/Clean.Architecture.Core/Interfaces/IEmailSender.cs | 2 +- .../Email/FakeEmailSender.cs | 4 ++-- .../Email/MimeKitEmailSender.cs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Clean.Architecture.Core/Interfaces/IEmailSender.cs b/src/Clean.Architecture.Core/Interfaces/IEmailSender.cs index 68d689de2..ebb92acb3 100644 --- a/src/Clean.Architecture.Core/Interfaces/IEmailSender.cs +++ b/src/Clean.Architecture.Core/Interfaces/IEmailSender.cs @@ -2,5 +2,5 @@ public interface IEmailSender { - Task SendEmailAsync(string to, string from, string subject, string body); + Task SendEmailAsync(string recipientEmail, string senderEmail, string subject, string body); } diff --git a/src/Clean.Architecture.Infrastructure/Email/FakeEmailSender.cs b/src/Clean.Architecture.Infrastructure/Email/FakeEmailSender.cs index 2ed14d834..aa58f362b 100644 --- a/src/Clean.Architecture.Infrastructure/Email/FakeEmailSender.cs +++ b/src/Clean.Architecture.Infrastructure/Email/FakeEmailSender.cs @@ -5,9 +5,9 @@ namespace Clean.Architecture.Infrastructure.Email; public class FakeEmailSender(ILogger logger) : IEmailSender { private readonly ILogger _logger = logger; - public Task SendEmailAsync(string to, string from, string subject, string body) + public Task SendEmailAsync(string recipientEmail, string senderEmail, string subject, string body) { - _logger.LogInformation("Not actually sending an email to {To} from {From} with subject {Subject}", to, from, subject); + _logger.LogInformation("Not actually sending an email to {To} from {From} with subject {Subject}", recipientEmail, senderEmail, subject); return Task.CompletedTask; } } diff --git a/src/Clean.Architecture.Infrastructure/Email/MimeKitEmailSender.cs b/src/Clean.Architecture.Infrastructure/Email/MimeKitEmailSender.cs index d0e5cfd0e..1433ba3e5 100644 --- a/src/Clean.Architecture.Infrastructure/Email/MimeKitEmailSender.cs +++ b/src/Clean.Architecture.Infrastructure/Email/MimeKitEmailSender.cs @@ -8,16 +8,16 @@ public class MimeKitEmailSender(ILogger logger, private readonly ILogger _logger = logger; private readonly MailserverConfiguration _mailserverConfiguration = mailserverOptions.Value!; - public async Task SendEmailAsync(string to, string from, string subject, string body) + public async Task SendEmailAsync(string recipientEmail, string senderEmail, string subject, string body) { - _logger.LogWarning("Sending email to {To} from {From} with subject {Subject} using {Type}.", to, from, subject, this.ToString()); + _logger.LogWarning("Sending email to {To} from {From} with subject {Subject} using {Type}.", recipientEmail, senderEmail, subject, this.ToString()); using var client = new MailKit.Net.Smtp.SmtpClient(); await client.ConnectAsync(_mailserverConfiguration.Hostname, _mailserverConfiguration.Port, false); var message = new MimeMessage(); - message.From.Add(new MailboxAddress(from, from)); - message.To.Add(new MailboxAddress(to, to)); + message.From.Add(new MailboxAddress(senderEmail, senderEmail)); + message.To.Add(new MailboxAddress(recipientEmail, recipientEmail)); message.Subject = subject; message.Body = new TextPart("plain") { Text = body }; From 8439a2a8725a953c8aec53565be951495f24a17d Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 19:41:42 +0200 Subject: [PATCH 13/23] Disable CLS compliance rule CA1014 in .editorconfig CA1014 (CLS compliance) is now disabled by setting its severity to none, clarifying that CLS compliance is not required for this application or template repository. --- .editorconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.editorconfig b/.editorconfig index 4a52dfc78..8687c4b42 100644 --- a/.editorconfig +++ b/.editorconfig @@ -152,3 +152,5 @@ visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public # Test projects often use underscores in test method/class names for readability. [tests/**/*.cs] dotnet_diagnostic.CA1707.severity = none +# CLS compliance is not required for this application/template repository. +dotnet_diagnostic.CA1014.severity = none \ No newline at end of file From 1d846775058cc828d833ca3428c0fbfeb9b2daeb Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 20:44:38 +0200 Subject: [PATCH 14/23] Update editorconfig, gitignore, slnx, and add runsettings Expanded and reorganized .editorconfig for C#/VB conventions and .NET rules. Updated .gitignore for SonarQube and VS artifacts. Modified Clean.Architecture.slnx to include .gitignore and adjust test config references. Added .runsettings for parallel xUnit test execution. --- .editorconfig | 9 ++- .gitignore | 4 ++ Clean.Architecture.slnx | 3 +- tests/.editorconfig | 155 ++++++++++++++++++++++++++++++++++++++++ tests/.runsettings | 17 +++++ 5 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 tests/.editorconfig create mode 100644 tests/.runsettings diff --git a/.editorconfig b/.editorconfig index 8687c4b42..2a66ef130 100644 --- a/.editorconfig +++ b/.editorconfig @@ -143,14 +143,13 @@ csharp_style_prefer_top_level_statements = true:silent csharp_style_prefer_primary_constructors = true:suggestion csharp_style_expression_bodied_lambdas = true:silent csharp_style_expression_bodied_local_functions = false:silent + +dotnet_diagnostic.CA1707.severity = none +otnet_diagnostic.CA1404.severity = none + ############################### # VB Coding Conventions # ############################### [*.vb] # Modifier preferences visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion -# Test projects often use underscores in test method/class names for readability. -[tests/**/*.cs] -dotnet_diagnostic.CA1707.severity = none -# CLS compliance is not required for this application/template repository. -dotnet_diagnostic.CA1014.severity = none \ No newline at end of file diff --git a/.gitignore b/.gitignore index e3f7b12fe..2349d2d4c 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,11 @@ bld/ .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ +# Local SonarQube Scanner working directory +.sonarqube/ +# Visual Studio local workspace state +.vs/ # Hugo generated site output docs/public/ diff --git a/Clean.Architecture.slnx b/Clean.Architecture.slnx index 27cf5bcf9..f168350e2 100644 --- a/Clean.Architecture.slnx +++ b/Clean.Architecture.slnx @@ -6,6 +6,7 @@ + @@ -22,7 +23,7 @@ - + diff --git a/tests/.editorconfig b/tests/.editorconfig new file mode 100644 index 000000000..6cb547220 --- /dev/null +++ b/tests/.editorconfig @@ -0,0 +1,155 @@ +############################### +# Core EditorConfig Options # +############################### +root = false +# All files +[*] +indent_style = space + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 2 +insert_final_newline = true +charset = utf-8-bom +############################### +# .NET Coding Conventions # +############################### +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true +# this. preferences +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_readonly_field = true:suggestion +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +############################### +# Naming Conventions # +############################### +# Style Definitions +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# Use PascalCase for constant fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const +tab_width= 2 +dotnet_naming_rule.private_members_with_underscore.symbols = private_fields +dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore +dotnet_naming_rule.private_members_with_underscore.severity = suggestion +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private +dotnet_naming_style.prefix_underscore.capitalization = camel_case +dotnet_naming_style.prefix_underscore.required_prefix = _ +dotnet_style_operator_placement_when_wrapping = beginning_of_line +end_of_line = crlf +############################### +# C# Coding Conventions # +############################### +[*.cs] +# var preferences +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion +# Expression-level preferences +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +# Namespaces +csharp_style_namespace_declarations = file_scoped:warning +############################### +# C# Formatting Rules # +############################### +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true +csharp_using_directive_placement = outside_namespace:suggestion +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent + +dotnet_diagnostic.CA1707.severity = none +otnet_diagnostic.CA1404.severity = none + +############################### +# VB Coding Conventions # +############################### +[*.vb] +# Modifier preferences +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion diff --git a/tests/.runsettings b/tests/.runsettings new file mode 100644 index 000000000..6862ac743 --- /dev/null +++ b/tests/.runsettings @@ -0,0 +1,17 @@ + + + + + 0 + + false + + + + + true + true + 0 + + + From e7e6eb89d2812d68c0134c388991eff520280b7f Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 20:56:20 +0200 Subject: [PATCH 15/23] issue-736: removed ca1307 waqrnings --- .editorconfig | 2 +- .../Contributors/Get/GetContributorHandler.cs | 4 ++-- .../Contributors/Delete.DeleteContributorRequest.cs | 2 +- .../Contributors/GetById.GetContributorByIdRequest.cs | 2 +- .../Contributors/Update.UpdateContributorRequest.cs | 2 +- tests/.editorconfig | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.editorconfig b/.editorconfig index 2a66ef130..ccf454a32 100644 --- a/.editorconfig +++ b/.editorconfig @@ -145,7 +145,7 @@ csharp_style_expression_bodied_lambdas = true:silent csharp_style_expression_bodied_local_functions = false:silent dotnet_diagnostic.CA1707.severity = none -otnet_diagnostic.CA1404.severity = none +dotnet_diagnostic.CA1014.severity = none ############################### # VB Coding Conventions # diff --git a/src/Clean.Architecture.UseCases/Contributors/Get/GetContributorHandler.cs b/src/Clean.Architecture.UseCases/Contributors/Get/GetContributorHandler.cs index 9cdc4d598..4aa80314a 100644 --- a/src/Clean.Architecture.UseCases/Contributors/Get/GetContributorHandler.cs +++ b/src/Clean.Architecture.UseCases/Contributors/Get/GetContributorHandler.cs @@ -6,13 +6,13 @@ namespace Clean.Architecture.UseCases.Contributors.Get; /// /// Queries don't necessarily need to use repository methods, but they can if it's convenient /// -public class GetContributorHandler(IReadRepository _repository) +public class GetContributorHandler(IReadRepository repository) : IQueryHandler> { public async ValueTask> Handle(GetContributorQuery request, CancellationToken cancellationToken) { var spec = new ContributorByIdSpec(request.ContributorId); - var entity = await _repository.FirstOrDefaultAsync(spec, cancellationToken); + var entity = await repository.FirstOrDefaultAsync(spec, cancellationToken); if (entity == null) return Result.NotFound(); return new ContributorDto(entity.Id, entity.Name, entity.PhoneNumber ?? PhoneNumber.Unknown); diff --git a/src/Clean.Architecture.Web/Contributors/Delete.DeleteContributorRequest.cs b/src/Clean.Architecture.Web/Contributors/Delete.DeleteContributorRequest.cs index 172345769..984243e7a 100644 --- a/src/Clean.Architecture.Web/Contributors/Delete.DeleteContributorRequest.cs +++ b/src/Clean.Architecture.Web/Contributors/Delete.DeleteContributorRequest.cs @@ -5,7 +5,7 @@ namespace Clean.Architecture.Web.Contributors; public record DeleteContributorRequest { public const string Route = "/Contributors/{ContributorId:int}"; - public static string BuildRoute(int contributorId) => Route.Replace("{ContributorId:int}", contributorId.ToString(CultureInfo.InvariantCulture)); + public static string BuildRoute(int contributorId) => Route.Replace("{ContributorId:int}", contributorId.ToString(CultureInfo.InvariantCulture), StringComparison.InvariantCulture); public int ContributorId { get; set; } } diff --git a/src/Clean.Architecture.Web/Contributors/GetById.GetContributorByIdRequest.cs b/src/Clean.Architecture.Web/Contributors/GetById.GetContributorByIdRequest.cs index daf609c1c..c9fcd7790 100644 --- a/src/Clean.Architecture.Web/Contributors/GetById.GetContributorByIdRequest.cs +++ b/src/Clean.Architecture.Web/Contributors/GetById.GetContributorByIdRequest.cs @@ -5,7 +5,7 @@ namespace Clean.Architecture.Web.Contributors; public class GetContributorByIdRequest { public const string Route = "/Contributors/{ContributorId:int}"; - public static string BuildRoute(int contributorId) => Route.Replace("{ContributorId:int}", contributorId.ToString(CultureInfo.InvariantCulture)); + public static string BuildRoute(int contributorId) => Route.Replace("{ContributorId:int}", contributorId.ToString(CultureInfo.InvariantCulture), StringComparison.InvariantCulture); public int ContributorId { get; set; } } diff --git a/src/Clean.Architecture.Web/Contributors/Update.UpdateContributorRequest.cs b/src/Clean.Architecture.Web/Contributors/Update.UpdateContributorRequest.cs index e2cbb367c..b1783940d 100644 --- a/src/Clean.Architecture.Web/Contributors/Update.UpdateContributorRequest.cs +++ b/src/Clean.Architecture.Web/Contributors/Update.UpdateContributorRequest.cs @@ -6,7 +6,7 @@ namespace Clean.Architecture.Web.Contributors; public class UpdateContributorRequest { public const string Route = "/Contributors/{ContributorId:int}"; - public static string BuildRoute(int contributorId) => Route.Replace("{ContributorId:int}", contributorId.ToString(CultureInfo.InvariantCulture)); + public static string BuildRoute(int contributorId) => Route.Replace("{ContributorId:int}", contributorId.ToString(CultureInfo.InvariantCulture), StringComparison.InvariantCulture); public int ContributorId { get; set; } diff --git a/tests/.editorconfig b/tests/.editorconfig index 6cb547220..2982e3d6f 100644 --- a/tests/.editorconfig +++ b/tests/.editorconfig @@ -145,7 +145,7 @@ csharp_style_expression_bodied_lambdas = true:silent csharp_style_expression_bodied_local_functions = false:silent dotnet_diagnostic.CA1707.severity = none -otnet_diagnostic.CA1404.severity = none + ############################### # VB Coding Conventions # From a7d8341b006452e31fc73806caac565d3a1d5323 Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 21:31:11 +0200 Subject: [PATCH 16/23] Adopt LoggerMessage.Define and update code style rules .editorconfig updated to suppress CA1014 and CA1707, and set CA1848 as a suggestion. Refactored DeleteContributorService to use a static LoggerMessage.Define delegate for contributor deletion logs, replacing direct LogInformation calls. No functional changes made. --- .editorconfig | 4 ++++ .../Services/DeleteContributorService.cs | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index ccf454a32..b2904eff4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -145,7 +145,11 @@ csharp_style_expression_bodied_lambdas = true:silent csharp_style_expression_bodied_local_functions = false:silent dotnet_diagnostic.CA1707.severity = none + dotnet_diagnostic.CA1014.severity = none +// CA1848: Use 'LoggerMessage.Define' instead of 'LoggerMessage.Define' +// DO not force Logger Massages Delegates all everywhere +dotnet_diagnostic.CA1848.severity= suggestion ############################### # VB Coding Conventions # diff --git a/src/Clean.Architecture.Core/Services/DeleteContributorService.cs b/src/Clean.Architecture.Core/Services/DeleteContributorService.cs index 5f71a920d..10179574b 100644 --- a/src/Clean.Architecture.Core/Services/DeleteContributorService.cs +++ b/src/Clean.Architecture.Core/Services/DeleteContributorService.cs @@ -16,13 +16,19 @@ public class DeleteContributorService( IMediator mediator, ILogger logger) : IDeleteContributorService { + private static readonly Action LogDeletingContributor = + LoggerMessage.Define( + LogLevel.Information, + new EventId(1, nameof(DeleteContributor)), + "Deleting Contributor {ContributorId}"); + private readonly IRepository _repository = repository; private readonly IMediator _mediator = mediator; private readonly ILogger _logger = logger; public async ValueTask DeleteContributor(ContributorId contributorId) { - _logger.LogInformation("Deleting Contributor {ContributorId}", contributorId); + LogDeletingContributor(_logger, contributorId, null); Contributor? aggregateToDelete = await _repository.GetByIdAsync(contributorId); if (aggregateToDelete == null) return Result.NotFound(); @@ -31,7 +37,6 @@ public async ValueTask DeleteContributor(ContributorId contributorId) var domainEvent = new ContributorDeletedEvent(contributorId); await _mediator.Publish(domainEvent); - return Result.Success(); } } From 03ddfa42db528dbafd95acf2d830411cbb2a0fcf Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 21:32:37 +0200 Subject: [PATCH 17/23] Remove default null assignment from PhoneNumber property The PhoneNumber property in CreateContributorRequest is now a nullable string without an explicit default value assignment. This simplifies the property declaration. --- src/Clean.Architecture.Web/Contributors/Create.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Clean.Architecture.Web/Contributors/Create.cs b/src/Clean.Architecture.Web/Contributors/Create.cs index 6135a2935..45be6f63f 100644 --- a/src/Clean.Architecture.Web/Contributors/Create.cs +++ b/src/Clean.Architecture.Web/Contributors/Create.cs @@ -65,7 +65,7 @@ public class CreateContributorRequest [Required] public string Name { get; set; } = String.Empty; - public string? PhoneNumber { get; set; } = null; + public string? PhoneNumber { get; set; } } public class CreateContributorValidator : Validator From a35b340c411e447da96001c1d39f6931eaf727f0 Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 21:43:18 +0200 Subject: [PATCH 18/23] Clarify service docs and return Result on delete Updated DeleteContributorService summary and remarks to explain logging approach. Method now returns Result.Success() after deletion and event publication. --- .../Services/DeleteContributorService.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Clean.Architecture.Core/Services/DeleteContributorService.cs b/src/Clean.Architecture.Core/Services/DeleteContributorService.cs index 10179574b..514452e55 100644 --- a/src/Clean.Architecture.Core/Services/DeleteContributorService.cs +++ b/src/Clean.Architecture.Core/Services/DeleteContributorService.cs @@ -5,9 +5,14 @@ namespace Clean.Architecture.Core.Services; /// -/// This is here mainly so there's an example of a domain service +/// This service is here mainly so there's an example of a domain service /// and also to demonstrate how to fire domain events from a service. /// +/// +/// The logging call intentionally uses a precompiled delegate. +/// This avoids analyzer warning CA1848 and demonstrates the recommended high-performance +/// logging pattern without using LoggerExtensions.LogInformation directly. +/// /// The repository used to load and delete contributors. /// The mediator used to publish domain events. /// The logger used by the service. @@ -37,6 +42,7 @@ public async ValueTask DeleteContributor(ContributorId contributorId) var domainEvent = new ContributorDeletedEvent(contributorId); await _mediator.Publish(domainEvent); + return Result.Success(); } } From 8107c571e36e63bd3a513566df591a53ae48bc09 Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 21:53:26 +0200 Subject: [PATCH 19/23] isues-1746: Refactor GetContributorQuery namespace and usages Update GetContributorQuery and related files to use the new Clean.Architecture.UseCases.Contributors.GetContributor namespace. Adjust using directives and references in handler and endpoint files for consistency. --- .../Contributors/Get/GetContributorHandler.cs | 1 + .../Contributors/{Get => GetContributor}/GetContributorQuery.cs | 2 +- src/Clean.Architecture.Web/Contributors/GetById.cs | 2 +- src/Clean.Architecture.Web/Contributors/Update.cs | 1 - 4 files changed, 3 insertions(+), 3 deletions(-) rename src/Clean.Architecture.UseCases/Contributors/{Get => GetContributor}/GetContributorQuery.cs (69%) diff --git a/src/Clean.Architecture.UseCases/Contributors/Get/GetContributorHandler.cs b/src/Clean.Architecture.UseCases/Contributors/Get/GetContributorHandler.cs index 4aa80314a..3e29dd25c 100644 --- a/src/Clean.Architecture.UseCases/Contributors/Get/GetContributorHandler.cs +++ b/src/Clean.Architecture.UseCases/Contributors/Get/GetContributorHandler.cs @@ -1,5 +1,6 @@ using Clean.Architecture.Core.ContributorAggregate; using Clean.Architecture.Core.ContributorAggregate.Specifications; +using Clean.Architecture.UseCases.Contributors.GetContributor; namespace Clean.Architecture.UseCases.Contributors.Get; diff --git a/src/Clean.Architecture.UseCases/Contributors/Get/GetContributorQuery.cs b/src/Clean.Architecture.UseCases/Contributors/GetContributor/GetContributorQuery.cs similarity index 69% rename from src/Clean.Architecture.UseCases/Contributors/Get/GetContributorQuery.cs rename to src/Clean.Architecture.UseCases/Contributors/GetContributor/GetContributorQuery.cs index c27d2f49b..7f3df787c 100644 --- a/src/Clean.Architecture.UseCases/Contributors/Get/GetContributorQuery.cs +++ b/src/Clean.Architecture.UseCases/Contributors/GetContributor/GetContributorQuery.cs @@ -1,5 +1,5 @@ using Clean.Architecture.Core.ContributorAggregate; -namespace Clean.Architecture.UseCases.Contributors.Get; +namespace Clean.Architecture.UseCases.Contributors.GetContributor; public record GetContributorQuery(ContributorId ContributorId) : IQuery>; diff --git a/src/Clean.Architecture.Web/Contributors/GetById.cs b/src/Clean.Architecture.Web/Contributors/GetById.cs index 94ce0555f..1c60bb771 100644 --- a/src/Clean.Architecture.Web/Contributors/GetById.cs +++ b/src/Clean.Architecture.Web/Contributors/GetById.cs @@ -1,6 +1,6 @@ using Clean.Architecture.Core.ContributorAggregate; using Clean.Architecture.UseCases.Contributors; -using Clean.Architecture.UseCases.Contributors.Get; +using Clean.Architecture.UseCases.Contributors.GetContributor; using Clean.Architecture.Web.Extensions; using Microsoft.AspNetCore.Http.HttpResults; diff --git a/src/Clean.Architecture.Web/Contributors/Update.cs b/src/Clean.Architecture.Web/Contributors/Update.cs index 0f017c1ab..65c3a6bf3 100644 --- a/src/Clean.Architecture.Web/Contributors/Update.cs +++ b/src/Clean.Architecture.Web/Contributors/Update.cs @@ -1,6 +1,5 @@ using Clean.Architecture.Core.ContributorAggregate; using Clean.Architecture.UseCases.Contributors; -using Clean.Architecture.UseCases.Contributors.Get; using Clean.Architecture.UseCases.Contributors.Update; using Clean.Architecture.Web.Extensions; using Microsoft.AspNetCore.Http.HttpResults; From 395f13a7c240272aa007037ee615dc5aa08115f0 Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 21:57:43 +0200 Subject: [PATCH 20/23] Refactor token naming and make CreateStream static Renamed ct to cancellationToken in UpdateContributorHandler for clarity and consistency. Changed NoOpMediator.CreateStream to a static method. --- .../Contributors/Update/UpdateContributorHandler.cs | 8 ++++---- tests/Clean.Architecture.UnitTests/NoOpMediator.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Clean.Architecture.UseCases/Contributors/Update/UpdateContributorHandler.cs b/src/Clean.Architecture.UseCases/Contributors/Update/UpdateContributorHandler.cs index 6cb2278bf..74e5a4835 100644 --- a/src/Clean.Architecture.UseCases/Contributors/Update/UpdateContributorHandler.cs +++ b/src/Clean.Architecture.UseCases/Contributors/Update/UpdateContributorHandler.cs @@ -5,10 +5,10 @@ namespace Clean.Architecture.UseCases.Contributors.Update; public class UpdateContributorHandler(IRepository _repository) : ICommandHandler> { - public async ValueTask> Handle(UpdateContributorCommand command, - CancellationToken ct) + public async ValueTask> Handle(UpdateContributorCommand command, + CancellationToken cancellationToken) { - var existingContributor = await _repository.GetByIdAsync(command.ContributorId, ct); + var existingContributor = await _repository.GetByIdAsync(command.ContributorId, cancellationToken); if (existingContributor == null) { return Result.NotFound(); @@ -16,7 +16,7 @@ public async ValueTask> Handle(UpdateContributorCommand c existingContributor.UpdateName(command.NewName); - await _repository.UpdateAsync(existingContributor, ct); + await _repository.UpdateAsync(existingContributor, cancellationToken); return new ContributorDto(existingContributor.Id, existingContributor.Name, existingContributor.PhoneNumber ?? PhoneNumber.Unknown); diff --git a/tests/Clean.Architecture.UnitTests/NoOpMediator.cs b/tests/Clean.Architecture.UnitTests/NoOpMediator.cs index 97d4e3c1a..4d52ba997 100644 --- a/tests/Clean.Architecture.UnitTests/NoOpMediator.cs +++ b/tests/Clean.Architecture.UnitTests/NoOpMediator.cs @@ -2,7 +2,7 @@ public class NoOpMediator : IMediator { - public async Task> CreateStream(IStreamQuery query, CancellationToken cancellationToken = default) + public static async Task> CreateStream(IStreamQuery query, CancellationToken cancellationToken = default) { await Task.Delay(1, cancellationToken); return AsyncEnumerable.Empty(); From 204fa9912f860786df3dbda750b0cd9e953e4d80 Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 22:10:04 +0200 Subject: [PATCH 21/23] Disable CA1848/CA1873 analyzers, add .editorconfig to src .editorconfig updated to disable CA1848 and CA1873 rules and clarify LoggerMessage usage is not enforced. Solution file now includes .editorconfig in /src/ to apply coding conventions and analyzer settings to all source projects. --- .editorconfig | 4 +++- Clean.Architecture.slnx | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index b2904eff4..aa679d79f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -149,7 +149,9 @@ dotnet_diagnostic.CA1707.severity = none dotnet_diagnostic.CA1014.severity = none // CA1848: Use 'LoggerMessage.Define' instead of 'LoggerMessage.Define' // DO not force Logger Massages Delegates all everywhere -dotnet_diagnostic.CA1848.severity= suggestion +dotnet_diagnostic.CA1848.severity= none +# Avoid forcing LoggerMessage-style logging everywhere in this template/sample repository. +dotnet_diagnostic.CA1873.severity = none ############################### # VB Coding Conventions # diff --git a/Clean.Architecture.slnx b/Clean.Architecture.slnx index f168350e2..18d488761 100644 --- a/Clean.Architecture.slnx +++ b/Clean.Architecture.slnx @@ -13,6 +13,7 @@ + From 037a81d0ced874c47feee324919039670c11d23d Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Mon, 8 Jun 2026 22:13:18 +0200 Subject: [PATCH 22/23] issue-736: Seal VogenEfCoreConverters class for stricter encapsulation Changed VogenEfCoreConverters from internal partial to internal sealed partial, preventing inheritance and enforcing stricter encapsulation. --- .../Data/Config/VogenEfCoreConverters.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Clean.Architecture.Infrastructure/Data/Config/VogenEfCoreConverters.cs b/src/Clean.Architecture.Infrastructure/Data/Config/VogenEfCoreConverters.cs index b26a46854..3c74148d8 100644 --- a/src/Clean.Architecture.Infrastructure/Data/Config/VogenEfCoreConverters.cs +++ b/src/Clean.Architecture.Infrastructure/Data/Config/VogenEfCoreConverters.cs @@ -5,4 +5,4 @@ namespace Clean.Architecture.Infrastructure.Data.Config; [EfCoreConverter] [EfCoreConverter] -internal partial class VogenEfCoreConverters; +internal sealed partial class VogenEfCoreConverters; From 9857a3fefdb330b2ab1a46f00f819fabee5d2dda Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Jun 2026 22:04:37 +0000 Subject: [PATCH 23/23] fix: align Aspire tests target framework with Aspire host --- .../Clean.Architecture.AspireTests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Clean.Architecture.AspireTests/Clean.Architecture.AspireTests.csproj b/tests/Clean.Architecture.AspireTests/Clean.Architecture.AspireTests.csproj index 8c9210f4f..94c47bb2b 100644 --- a/tests/Clean.Architecture.AspireTests/Clean.Architecture.AspireTests.csproj +++ b/tests/Clean.Architecture.AspireTests/Clean.Architecture.AspireTests.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable false