diff --git a/Directory.Build.props b/Directory.Build.props
index 250ad39..944379b 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -18,6 +18,6 @@
MIT
True
- 0.6.3
+ 0.6.4
\ No newline at end of file
diff --git a/README.md b/README.md
index 2ab446a..44e26c5 100644
--- a/README.md
+++ b/README.md
@@ -308,6 +308,33 @@ internal class CreateBook(ServiceDbContext db, ILocationStore location)
}
```
+### Disabling components
+
+DoNotRegister attribute can be used to prevent targeted route group (including its children) or endpoint to be registered during server application startup.
+
+Also prevents targeted service endpoint request to be registered during service endpoint client application startup.
+
+```csharp
+[MapToGroup()]
+[DoNotRegister]
+internal class DisabledCustomerFeature
+ : MinimalEndpoint
+{
+ protected override void Configure(
+ IServiceProvider serviceProvider,
+ IRouteGroupConfigurator? parentRouteGroup)
+ {
+ MapGet("/disabled/");
+ }
+
+ protected override Task HandleAsync(
+ CancellationToken ct)
+ {
+ throw new NotImplementedException();
+ }
+}
+```
+
## Samples
[ShowcaseWebApi](https://github.com/modabas/ModEndpoints/tree/main/samples/ShowcaseWebApi) project demonstrates various kinds of endpoint implementations and configurations:
diff --git a/samples/ShowcaseWebApi/Features/Customers/CreateCustomer.cs b/samples/ShowcaseWebApi/Features/Customers/CreateCustomer.cs
index 4f9eded..9ef5f88 100644
--- a/samples/ShowcaseWebApi/Features/Customers/CreateCustomer.cs
+++ b/samples/ShowcaseWebApi/Features/Customers/CreateCustomer.cs
@@ -24,7 +24,7 @@ public CreateCustomerRequestValidator()
[MapToGroup()]
internal class CreateCustomer(ServiceDbContext db)
- : MinimalEndpoint, ValidationProblem>>
+ : MinimalEndpoint, ValidationProblem, ProblemHttpResult>>
{
protected override void Configure(
IServiceProvider serviceProvider,
@@ -33,7 +33,7 @@ protected override void Configure(
MapPost("/");
}
- protected override async Task, ValidationProblem>> HandleAsync(
+ protected override async Task, ValidationProblem, ProblemHttpResult>> HandleAsync(
CreateCustomerRequest req,
CancellationToken ct)
{
diff --git a/samples/ShowcaseWebApi/Features/Customers/DeleteBook.cs b/samples/ShowcaseWebApi/Features/Customers/DeleteCustomer.cs
similarity index 100%
rename from samples/ShowcaseWebApi/Features/Customers/DeleteBook.cs
rename to samples/ShowcaseWebApi/Features/Customers/DeleteCustomer.cs
diff --git a/samples/ShowcaseWebApi/Features/Customers/DisabledCustomerFeature.cs b/samples/ShowcaseWebApi/Features/Customers/DisabledCustomerFeature.cs
new file mode 100644
index 0000000..a5efb15
--- /dev/null
+++ b/samples/ShowcaseWebApi/Features/Customers/DisabledCustomerFeature.cs
@@ -0,0 +1,24 @@
+using ModEndpoints.Core;
+using ModEndpoints.RemoteServices.Core;
+using ShowcaseWebApi.Features.Customers.Configuration;
+
+namespace ShowcaseWebApi.Features.Customers;
+
+[MapToGroup()]
+[DoNotRegister]
+internal class DisabledCustomerFeature
+ : MinimalEndpoint
+{
+ protected override void Configure(
+ IServiceProvider serviceProvider,
+ IRouteGroupConfigurator? parentRouteGroup)
+ {
+ MapGet("/disabled/");
+ }
+
+ protected override Task HandleAsync(
+ CancellationToken ct)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/samples/ShowcaseWebApi/Features/Customers/GetCustomerById.cs b/samples/ShowcaseWebApi/Features/Customers/GetCustomerById.cs
index ee9eb15..1dfe79c 100644
--- a/samples/ShowcaseWebApi/Features/Customers/GetCustomerById.cs
+++ b/samples/ShowcaseWebApi/Features/Customers/GetCustomerById.cs
@@ -21,7 +21,7 @@ public GetCustomerByIdRequestValidator()
[MapToGroup()]
internal class GetCustomerById(ServiceDbContext db)
- : MinimalEndpoint, NotFound, ValidationProblem>>
+ : MinimalEndpoint, NotFound, ValidationProblem, ProblemHttpResult>>
{
protected override void Configure(
IServiceProvider serviceProvider,
@@ -30,7 +30,7 @@ protected override void Configure(
MapGet("/{Id}");
}
- protected override async Task, NotFound, ValidationProblem>> HandleAsync(
+ protected override async Task, NotFound, ValidationProblem, ProblemHttpResult>> HandleAsync(
GetCustomerByIdRequest req,
CancellationToken ct)
{
diff --git a/src/ModEndpoints.Core/DependencyInjectionExtensions.cs b/src/ModEndpoints.Core/DependencyInjectionExtensions.cs
index 12d9d5d..10f908d 100644
--- a/src/ModEndpoints.Core/DependencyInjectionExtensions.cs
+++ b/src/ModEndpoints.Core/DependencyInjectionExtensions.cs
@@ -38,7 +38,8 @@ private static IServiceCollection AddRouteGroupsCoreFromAssembly(
.DefinedTypes
.Where(type => type is { IsAbstract: false, IsInterface: false } &&
type.IsAssignableTo(typeof(IRouteGroupConfigurator)) &&
- type != typeof(RootRouteGroup))
+ type != typeof(RootRouteGroup) &&
+ !type.GetCustomAttributes(typeof(DoNotRegisterAttribute)).Any())
.Select(type => ServiceDescriptor.DescribeKeyed(typeof(IRouteGroupConfigurator), type, type, lifetime))
.ToArray();
@@ -55,7 +56,8 @@ public static IServiceCollection AddEndpointsCoreFromAssembly(
var endpointTypes = assembly
.DefinedTypes
.Where(type => type is { IsAbstract: false, IsInterface: false } &&
- type.IsAssignableTo(typeof(IEndpointConfigurator)));
+ type.IsAssignableTo(typeof(IEndpointConfigurator)) &&
+ !type.GetCustomAttributes(typeof(DoNotRegisterAttribute)).Any());
CheckServiceEndpointRegistrations(endpointTypes);
diff --git a/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs b/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs
index 32ee4c0..62ddb0d 100644
--- a/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs
+++ b/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs
@@ -70,6 +70,14 @@ protected virtual ValueTask HandleInvalidValidationResultAsync(
responseType.Name.StartsWith("Results`") &&
(responseType.Namespace?.Equals("Microsoft.AspNetCore.Http.HttpResults") ?? false))
{
+ if (TryUseImplicitOperatorFor(
+ responseType,
+ validationResult,
+ vr => vr.ToTypedProblem(),
+ out var problem))
+ {
+ return new ValueTask(problem);
+ }
if (TryUseImplicitOperatorFor(
responseType,
validationResult,
@@ -82,25 +90,17 @@ protected virtual ValueTask HandleInvalidValidationResultAsync(
responseType,
validationResult,
vr => vr.ToTypedBadRequestWithValidationProblem(),
- out var badRequestWithValidationProblems))
+ out var badRequestWithValidationProblem))
{
- return new ValueTask(badRequestWithValidationProblems);
+ return new ValueTask(badRequestWithValidationProblem);
}
if (TryUseImplicitOperatorFor>(
responseType,
validationResult,
vr => vr.ToTypedBadRequestWithProblem(),
- out var badRequestWithProblems))
+ out var badRequestWithProblem))
{
- return new ValueTask(badRequestWithProblems);
- }
- if (TryUseImplicitOperatorFor(
- responseType,
- validationResult,
- vr => vr.ToTypedProblem(),
- out var problem))
- {
- return new ValueTask(problem);
+ return new ValueTask(badRequestWithProblem);
}
if (TryUseImplicitOperatorFor(
responseType,
diff --git a/src/ModEndpoints.RemoteServices.Core/DoNotRegisterAttribute.cs b/src/ModEndpoints.RemoteServices.Core/DoNotRegisterAttribute.cs
new file mode 100644
index 0000000..ec02681
--- /dev/null
+++ b/src/ModEndpoints.RemoteServices.Core/DoNotRegisterAttribute.cs
@@ -0,0 +1,10 @@
+namespace ModEndpoints.RemoteServices.Core;
+
+///
+/// Attribute to prevent targeted route group (including its children) or endpoint to be registered during server application startup.
+/// Also prevents targeted service endpoint request to be registered during service endpoint client application startup.
+///
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+public class DoNotRegisterAttribute : Attribute
+{
+}
diff --git a/src/ModEndpoints.RemoteServices/DependencyInjectionExtensions.cs b/src/ModEndpoints.RemoteServices/DependencyInjectionExtensions.cs
index 2594edb..bae2650 100644
--- a/src/ModEndpoints.RemoteServices/DependencyInjectionExtensions.cs
+++ b/src/ModEndpoints.RemoteServices/DependencyInjectionExtensions.cs
@@ -10,6 +10,7 @@ public static class DependencyInjectionExtensions
private const string ClientDoesNotExist = "A client with name {0} does not exist.";
private const string ChannelAlreadyRegistered = "A channel for request type {0} is already registered.";
private const string ChannelCannotBeRegistered = "Channel couldn't be registered for request type {0} and client name {1}.";
+ private const string RequestTypeFlaggedAsDoNotRegister = "Request type {0} is flagged as 'DoNotRegister'.";
///
/// Adds and configures a new client for specified ServiceEndpoint request.
@@ -163,7 +164,8 @@ public static IServiceCollection AddRemoteServicesWithNewClient(
var requestTypes = fromAssembly
.DefinedTypes
.Where(type => type is { IsAbstract: false, IsInterface: false } &&
- type.IsAssignableTo(typeof(IServiceRequestMarker)));
+ type.IsAssignableTo(typeof(IServiceRequestMarker)) &&
+ !type.GetCustomAttributes(typeof(DoNotRegisterAttribute)).Any());
if (requestFilterPredicate is not null)
{
@@ -206,7 +208,8 @@ public static IServiceCollection AddRemoteServicesToExistingClient(
var requestTypes = fromAssembly
.DefinedTypes
.Where(type => type is { IsAbstract: false, IsInterface: false } &&
- type.IsAssignableTo(typeof(IServiceRequestMarker)));
+ type.IsAssignableTo(typeof(IServiceRequestMarker)) &&
+ !type.GetCustomAttributes(typeof(DoNotRegisterAttribute)).Any());
if (requestFilterPredicate is not null)
{
@@ -229,6 +232,10 @@ private static IServiceCollection AddRemoteServiceWithNewClientInternal(
Action configureClient,
Action? configureClientBuilder)
{
+ if (requestType.GetCustomAttributes(typeof(DoNotRegisterAttribute)).Any())
+ {
+ throw new InvalidOperationException(string.Format(RequestTypeFlaggedAsDoNotRegister, requestType));
+ }
if (ServiceChannelRegistry.Instance.IsRequestRegistered(requestType))
{
throw new InvalidOperationException(string.Format(ChannelAlreadyRegistered, requestType));
@@ -250,6 +257,10 @@ private static IServiceCollection AddRemoteServiceToExistingClientInternal(
Type requestType,
string clientName)
{
+ if (requestType.GetCustomAttributes(typeof(DoNotRegisterAttribute)).Any())
+ {
+ throw new InvalidOperationException(string.Format(RequestTypeFlaggedAsDoNotRegister, requestType));
+ }
if (ServiceChannelRegistry.Instance.IsRequestRegistered(requestType))
{
throw new InvalidOperationException(string.Format(ChannelAlreadyRegistered, requestType));