From 2f6c8da348c667c39ffbbd4eed0da41bc8c347a3 Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Tue, 24 Oct 2023 10:20:38 +0200 Subject: [PATCH 1/8] feat: Add IEndpointAndServiceDefinition --- .../Abstractions/IEndpointAndServiceDefinition.cs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/Atc.Rest.MinimalApi/Abstractions/IEndpointAndServiceDefinition.cs diff --git a/src/Atc.Rest.MinimalApi/Abstractions/IEndpointAndServiceDefinition.cs b/src/Atc.Rest.MinimalApi/Abstractions/IEndpointAndServiceDefinition.cs new file mode 100644 index 0000000..e690857 --- /dev/null +++ b/src/Atc.Rest.MinimalApi/Abstractions/IEndpointAndServiceDefinition.cs @@ -0,0 +1,7 @@ +namespace Atc.Rest.MinimalApi.Abstractions; + +public interface IEndpointAndServiceDefinition : IEndpointDefinition +{ + void DefineServices( + IServiceCollection services); +} \ No newline at end of file From d6fbe33a619e28c7daf670711131f60da51eba8f Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Tue, 24 Oct 2023 10:20:58 +0200 Subject: [PATCH 2/8] feat: Add AddEndpointAndServiceDefinitions method --- .../EndpointDefinitionExtensions.cs | 56 +++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/src/Atc.Rest.MinimalApi/Extensions/EndpointDefinitionExtensions.cs b/src/Atc.Rest.MinimalApi/Extensions/EndpointDefinitionExtensions.cs index f1ea47e..cfd75f8 100644 --- a/src/Atc.Rest.MinimalApi/Extensions/EndpointDefinitionExtensions.cs +++ b/src/Atc.Rest.MinimalApi/Extensions/EndpointDefinitionExtensions.cs @@ -33,9 +33,46 @@ public static void AddEndpointDefinitions( services.AddSingleton(endpointDefinitions as IReadOnlyCollection); } + /// + /// Adds the endpoint definitions to the specified service collection by scanning the assemblies of the provided marker types. + /// This method looks for types that implement the interface and are neither abstract nor an interface, + /// and adds them to the service collection as a single instance of + /// and . + /// + /// The to add the endpoint definitions to. + /// The types used as markers to find the assemblies to scan for endpoint definitions. + /// Thrown if is null. + public static void AddEndpointAndServiceDefinitions( + this IServiceCollection services, + params Type[] scanMarkers) + { + ArgumentNullException.ThrowIfNull(scanMarkers); + + services.AddEndpointDefinitions(scanMarkers); + + var endpointDefinitions = new List(); + + foreach (var marker in scanMarkers) + { + endpointDefinitions.AddRange( + marker.Assembly.ExportedTypes + .Where(x => typeof(IEndpointAndServiceDefinition).IsAssignableFrom(x) && + x is { IsInterface: false, IsAbstract: false }) + .Select(Activator.CreateInstance).Cast()); + } + + foreach (var endpointDefinition in endpointDefinitions) + { + endpointDefinition.DefineServices(services); + } + + services.AddSingleton(endpointDefinitions as IReadOnlyCollection); + } + /// /// Applies the endpoint definitions to the specified web application. - /// This method retrieves the registered endpoint definitions from the application's services and invokes their method. + /// This method retrieves the registered endpoint definitions from the application's services and invokes + /// their and/or method. /// /// The to which the endpoint definitions are applied. /// Thrown if is null. @@ -44,11 +81,22 @@ public static void UseEndpointDefinitions( { ArgumentNullException.ThrowIfNull(app); - var definitions = app.Services.GetRequiredService>(); + var definitions = app.Services.GetService>(); + if (definitions is not null) + { + foreach (var endpointDefinition in definitions) + { + endpointDefinition.DefineEndpoints(app); + } + } - foreach (var endpointDefinition in definitions) + var definitionWithDefineServices = app.Services.GetService>(); + if (definitionWithDefineServices is not null) { - endpointDefinition.DefineEndpoints(app); + foreach (var endpointDefinition in definitionWithDefineServices) + { + endpointDefinition.DefineEndpoints(app); + } } } } \ No newline at end of file From d1ea260ec318b8f372d61dfd3e59fa1d354fbb1b Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Tue, 24 Oct 2023 10:23:23 +0200 Subject: [PATCH 3/8] docs: Update readme with new section "Automatic endpoint discovery and registration with services" --- README.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cfc17bb..9cd6c3b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Whether you're building a brand-new project or seeking to enhance an existing on - [Atc.Rest.MinimalApi](#atcrestminimalapi) - [Table of Contents](#table-of-contents) - [Automatic endpoint discovery and registration](#automatic-endpoint-discovery-and-registration) +- [Automatic endpoint discovery and registration with services](#automatic-endpoint-discovery-and-registration-with-services) - [SwaggerFilters](#swaggerfilters) - [`SwaggerDefaultValues`](#swaggerdefaultvalues) - [`SwaggerEnumDescriptionsDocumentFilter`](#swaggerenumdescriptionsdocumentfilter) @@ -50,7 +51,8 @@ builder.Services.AddEndpointDefinitions(typeof(IApiContractAssemblyMarker)); var app = builder.Build(); /// Applies the endpoint definitions to the specified web application. -/// This method retrieves the registered endpoint definitions from the application's services and invokes their "IEndpointDefinition.DefineEndpoints" method. +/// This method retrieves the registered endpoint definitions from the application's services and invokes +/// their and/or method. app.UseEndpointDefinitions(); ``` @@ -82,6 +84,72 @@ public sealed class UsersEndpointDefinition : IEndpointDefinition } ``` +# Automatic endpoint discovery and registration with services + +An alternative approach is using the interface `IEndpointAndServiceDefinition`. + +```csharp +public interface IEndpointAndServiceDefinition : IEndpointDefinition +{ + void DefineServices( + IServiceCollection services); +} +``` + +Upon defining all endpoints and ensuring they inherit from the specified interface, the process of automatic registration can be systematically orchestrated and configured in the subsequent manner. + +```csharp +var builder = WebApplication.CreateBuilder(args); + +/// Adds the endpoint definitions to the specified service collection by scanning the assemblies of the provided marker types. +/// In this example the empty assembly marker interface IApiContractAssemblyMarker defined in the project where EndpointDefinitions reside. +/// This method looks for types that implement the IEndpointDefinition interface and are neither abstract nor an interface, +/// and adds them to the service collection as a single instance of IReadOnlyCollection{IEndpointDefinition} +/// and . +builder.Services.AddEndpointAndServiceDefinitions(typeof(IApiContractAssemblyMarker)); + +var app = builder.Build(); + +/// Applies the endpoint definitions to the specified web application. +/// This method retrieves the registered endpoint definitions from the application's services and invokes +/// their and/or method. +app.UseEndpointDefinitions(); +``` + +An example of how to configure an endpoint upon inheriting from the specified interface. + +```csharp +public sealed class UsersEndpointDefinition : IEndpointAndServiceDefinition +{ + internal const string ApiRouteBase = "/api/users"; + + public void DefineServices( + IServiceCollection services) + { + services.AddScoped(); + } + + public void DefineEndpoints( + WebApplication app) + { + var users = app.NewVersionedApi("Users"); + + var usersV1 = users + .MapGroup(ApiRouteBase) + .HasApiVersion(1.0); + + usersV1 + .MapGet("/", GetAllUsers) + .WithName("GetAllUsers"); + } + + internal Task>> GetAllUsers( + [FromServices] IUserService userService, + CancellationToken cancellationToken) + => userService.GetAllUsers(cancellationToken); +} +``` + # SwaggerFilters In the development of RESTful APIs, filters play an essential role in shaping the output and behavior of the system. Whether it's providing detailed descriptions for enumerations or handling default values and response formats, filters like those in the Atc.Rest.MinimalApi.Filters.Swagger namespace enhance the API's functionality and documentation, aiding in both development and integration. From e411cf6252f27f3d05f149d1665b9e2204f6d20e Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Tue, 24 Oct 2023 10:36:59 +0200 Subject: [PATCH 4/8] chore: Update nuget packages --- Directory.Build.props | 8 ++++---- sample/Directory.Build.props | 6 +++--- .../src/Demo.Api.Contracts/Demo.Api.Contracts.csproj | 6 +++--- sample/src/Demo.Api/Demo.Api.csproj | 2 +- sample/src/Demo.Domain/Demo.Domain.csproj | 12 ++++++------ .../Demo.Api.Contracts.Tests.csproj | 10 +++++++--- .../Demo.Api.IntegrationTests.csproj | 12 ++++++++---- .../test/Demo.Domain.Tests/Demo.Domain.Tests.csproj | 10 +++++++--- src/Atc.Rest.MinimalApi/Atc.Rest.MinimalApi.csproj | 6 +++--- .../Atc.Rest.MinimalApi.Tests.csproj | 12 ++++++++---- 10 files changed, 50 insertions(+), 34 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 7e65f1f..4c8c955 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -38,7 +38,7 @@ true - + true @@ -47,10 +47,10 @@ - + - - + + \ No newline at end of file diff --git a/sample/Directory.Build.props b/sample/Directory.Build.props index bf1634b..dbe670b 100644 --- a/sample/Directory.Build.props +++ b/sample/Directory.Build.props @@ -47,10 +47,10 @@ - + - - + + \ No newline at end of file diff --git a/sample/src/Demo.Api.Contracts/Demo.Api.Contracts.csproj b/sample/src/Demo.Api.Contracts/Demo.Api.Contracts.csproj index dbf2a07..d5fa035 100644 --- a/sample/src/Demo.Api.Contracts/Demo.Api.Contracts.csproj +++ b/sample/src/Demo.Api.Contracts/Demo.Api.Contracts.csproj @@ -11,11 +11,11 @@ - - + + - + diff --git a/sample/src/Demo.Api/Demo.Api.csproj b/sample/src/Demo.Api/Demo.Api.csproj index 7b3994e..5f16b8e 100644 --- a/sample/src/Demo.Api/Demo.Api.csproj +++ b/sample/src/Demo.Api/Demo.Api.csproj @@ -5,7 +5,7 @@ - + diff --git a/sample/src/Demo.Domain/Demo.Domain.csproj b/sample/src/Demo.Domain/Demo.Domain.csproj index 83646e2..40b6f8d 100644 --- a/sample/src/Demo.Domain/Demo.Domain.csproj +++ b/sample/src/Demo.Domain/Demo.Domain.csproj @@ -5,18 +5,18 @@ - - + + - + - + - + - + diff --git a/sample/test/Demo.Api.Contracts.Tests/Demo.Api.Contracts.Tests.csproj b/sample/test/Demo.Api.Contracts.Tests/Demo.Api.Contracts.Tests.csproj index 7a5312e..8caa57a 100644 --- a/sample/test/Demo.Api.Contracts.Tests/Demo.Api.Contracts.Tests.csproj +++ b/sample/test/Demo.Api.Contracts.Tests/Demo.Api.Contracts.Tests.csproj @@ -6,9 +6,9 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -22,4 +22,8 @@ + + + + diff --git a/sample/test/Demo.Api.IntegrationTests/Demo.Api.IntegrationTests.csproj b/sample/test/Demo.Api.IntegrationTests/Demo.Api.IntegrationTests.csproj index 743dab1..e271f81 100644 --- a/sample/test/Demo.Api.IntegrationTests/Demo.Api.IntegrationTests.csproj +++ b/sample/test/Demo.Api.IntegrationTests/Demo.Api.IntegrationTests.csproj @@ -6,10 +6,10 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -23,4 +23,8 @@ + + + + diff --git a/sample/test/Demo.Domain.Tests/Demo.Domain.Tests.csproj b/sample/test/Demo.Domain.Tests/Demo.Domain.Tests.csproj index 887c9cf..8cd1c06 100644 --- a/sample/test/Demo.Domain.Tests/Demo.Domain.Tests.csproj +++ b/sample/test/Demo.Domain.Tests/Demo.Domain.Tests.csproj @@ -6,9 +6,9 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -22,4 +22,8 @@ + + + + diff --git a/src/Atc.Rest.MinimalApi/Atc.Rest.MinimalApi.csproj b/src/Atc.Rest.MinimalApi/Atc.Rest.MinimalApi.csproj index 2338095..aec433a 100644 --- a/src/Atc.Rest.MinimalApi/Atc.Rest.MinimalApi.csproj +++ b/src/Atc.Rest.MinimalApi/Atc.Rest.MinimalApi.csproj @@ -13,10 +13,10 @@ - - + + - + diff --git a/test/Atc.Rest.MinimalApi.Tests/Atc.Rest.MinimalApi.Tests.csproj b/test/Atc.Rest.MinimalApi.Tests/Atc.Rest.MinimalApi.Tests.csproj index 240daf2..0e1f5e8 100644 --- a/test/Atc.Rest.MinimalApi.Tests/Atc.Rest.MinimalApi.Tests.csproj +++ b/test/Atc.Rest.MinimalApi.Tests/Atc.Rest.MinimalApi.Tests.csproj @@ -7,11 +7,11 @@ - - + + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -25,4 +25,8 @@ + + + + From 3a050378e2e1108fa5afec95fd343f64550d84b6 Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Tue, 24 Oct 2023 10:37:31 +0200 Subject: [PATCH 5/8] fix: Suppress SA1402 for demo project --- sample/.editorconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sample/.editorconfig b/sample/.editorconfig index 75a664b..83de8d9 100644 --- a/sample/.editorconfig +++ b/sample/.editorconfig @@ -517,4 +517,6 @@ dotnet_diagnostic.CA2016.severity = none # Forward the CancellationTo dotnet_diagnostic.MA0004.severity = none # Consider calling ConfigureAwait. dotnet_diagnostic.MA0040.severity = none # Forward the CancellationToken parameter to methods that take one - False-Positive +dotnet_diagnostic.SA1402.severity = none # File may only contains a single type - not relevant for multi records in single file by design + dotnet_diagnostic.S3267.severity = none # Loop could be simplified. LINQ Expression. \ No newline at end of file From c5b26626d29d20357abcf0bb819dabdf515920e1 Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Tue, 24 Oct 2023 10:38:18 +0200 Subject: [PATCH 6/8] fix: Suppress CS1574 - false/positive for xml-cref --- .editorconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.editorconfig b/.editorconfig index f5a69b4..939acbe 100644 --- a/.editorconfig +++ b/.editorconfig @@ -520,6 +520,8 @@ dotnet_diagnostic.CA1812.severity = none # Internal class that is app dotnet_diagnostic.CA2007.severity = none # Consider calling ConfigureAwait on the awaited task dotnet_diagnostic.CA2227.severity = none # Collection properties should be read only +dotnet_diagnostic.CS1574.severity = none # XML comment has cref attribute '[attribute]' that could not be resolved + dotnet_diagnostic.MA0004.severity = none # Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed dotnet_diagnostic.S1172.severity = suggestion # Temporary, due to false positive in latest version of the analyzer package From 8623d097a30ed7b121c69adb6ecc6d9cba5d2167 Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Tue, 24 Oct 2023 11:00:16 +0200 Subject: [PATCH 7/8] fix: Update nuget package Atc.Test in Directory.Build.props --- .../Demo.Api.Contracts.Tests/Demo.Api.Contracts.Tests.csproj | 4 ---- .../Demo.Api.IntegrationTests.csproj | 4 ---- sample/test/Demo.Domain.Tests/Demo.Domain.Tests.csproj | 4 ---- sample/test/Directory.Build.props | 2 +- .../Atc.Rest.MinimalApi.Tests.csproj | 4 ---- test/Directory.Build.props | 2 +- 6 files changed, 2 insertions(+), 18 deletions(-) diff --git a/sample/test/Demo.Api.Contracts.Tests/Demo.Api.Contracts.Tests.csproj b/sample/test/Demo.Api.Contracts.Tests/Demo.Api.Contracts.Tests.csproj index 8caa57a..36f1206 100644 --- a/sample/test/Demo.Api.Contracts.Tests/Demo.Api.Contracts.Tests.csproj +++ b/sample/test/Demo.Api.Contracts.Tests/Demo.Api.Contracts.Tests.csproj @@ -22,8 +22,4 @@ - - - - diff --git a/sample/test/Demo.Api.IntegrationTests/Demo.Api.IntegrationTests.csproj b/sample/test/Demo.Api.IntegrationTests/Demo.Api.IntegrationTests.csproj index e271f81..8a364ab 100644 --- a/sample/test/Demo.Api.IntegrationTests/Demo.Api.IntegrationTests.csproj +++ b/sample/test/Demo.Api.IntegrationTests/Demo.Api.IntegrationTests.csproj @@ -23,8 +23,4 @@ - - - - diff --git a/sample/test/Demo.Domain.Tests/Demo.Domain.Tests.csproj b/sample/test/Demo.Domain.Tests/Demo.Domain.Tests.csproj index 8cd1c06..6db3627 100644 --- a/sample/test/Demo.Domain.Tests/Demo.Domain.Tests.csproj +++ b/sample/test/Demo.Domain.Tests/Demo.Domain.Tests.csproj @@ -22,8 +22,4 @@ - - - - diff --git a/sample/test/Directory.Build.props b/sample/test/Directory.Build.props index afd059a..b8ec10d 100644 --- a/sample/test/Directory.Build.props +++ b/sample/test/Directory.Build.props @@ -11,7 +11,7 @@ - + diff --git a/test/Atc.Rest.MinimalApi.Tests/Atc.Rest.MinimalApi.Tests.csproj b/test/Atc.Rest.MinimalApi.Tests/Atc.Rest.MinimalApi.Tests.csproj index 0e1f5e8..517593d 100644 --- a/test/Atc.Rest.MinimalApi.Tests/Atc.Rest.MinimalApi.Tests.csproj +++ b/test/Atc.Rest.MinimalApi.Tests/Atc.Rest.MinimalApi.Tests.csproj @@ -25,8 +25,4 @@ - - - - diff --git a/test/Directory.Build.props b/test/Directory.Build.props index afd059a..b8ec10d 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -11,7 +11,7 @@ - + From 87b295815a0618ca7e3dccb87df1d3948f1e98c6 Mon Sep 17 00:00:00 2001 From: David Kallesen Date: Tue, 24 Oct 2023 11:03:36 +0200 Subject: [PATCH 8/8] docs: Update --- README.md | 6 +++--- .../Extensions/EndpointDefinitionExtensions.cs | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9cd6c3b..bdcaf74 100644 --- a/README.md +++ b/README.md @@ -102,9 +102,9 @@ Upon defining all endpoints and ensuring they inherit from the specified interfa var builder = WebApplication.CreateBuilder(args); /// Adds the endpoint definitions to the specified service collection by scanning the assemblies of the provided marker types. -/// In this example the empty assembly marker interface IApiContractAssemblyMarker defined in the project where EndpointDefinitions reside. -/// This method looks for types that implement the IEndpointDefinition interface and are neither abstract nor an interface, -/// and adds them to the service collection as a single instance of IReadOnlyCollection{IEndpointDefinition} +/// This method looks for types that implement the and +/// interface and are neither abstract nor an interface, +/// and adds them to the service collection as a single instance of /// and . builder.Services.AddEndpointAndServiceDefinitions(typeof(IApiContractAssemblyMarker)); diff --git a/src/Atc.Rest.MinimalApi/Extensions/EndpointDefinitionExtensions.cs b/src/Atc.Rest.MinimalApi/Extensions/EndpointDefinitionExtensions.cs index cfd75f8..734a2ed 100644 --- a/src/Atc.Rest.MinimalApi/Extensions/EndpointDefinitionExtensions.cs +++ b/src/Atc.Rest.MinimalApi/Extensions/EndpointDefinitionExtensions.cs @@ -35,7 +35,8 @@ public static void AddEndpointDefinitions( /// /// Adds the endpoint definitions to the specified service collection by scanning the assemblies of the provided marker types. - /// This method looks for types that implement the interface and are neither abstract nor an interface, + /// This method looks for types that implement the and + /// interface and are neither abstract nor an interface, /// and adds them to the service collection as a single instance of /// and . ///