Skip to content

Commit 85702f7

Browse files
authored
Dev (#13)
* - while handling minimal endpoints invalid requests, prioritize ProblemHttpResult type responses first since it utilizes customized problem details defined by IServiceCollection.AddProblemDetails * - add DoNotRegister attribute, to prevent targeted route group (including its children), endpoint or service endpoint request to be registered during application startup. - fix DeleteCustomer endpoint class name * - add DoNotRegister attribute information to README, also add a sample endpoint disabled with DoNotRegister attribute * - bump version
1 parent 7201de5 commit 85702f7

File tree

10 files changed

+95
-21
lines changed

10 files changed

+95
-21
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@
1818
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1919
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
2020

21-
<Version>0.6.3</Version>
21+
<Version>0.6.4</Version>
2222
</PropertyGroup>
2323
</Project>

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,33 @@ internal class CreateBook(ServiceDbContext db, ILocationStore location)
308308
}
309309
```
310310

311+
### Disabling components
312+
313+
DoNotRegister attribute can be used to prevent targeted route group (including its children) or endpoint to be registered during server application startup.
314+
315+
Also prevents targeted service endpoint request to be registered during service endpoint client application startup.
316+
317+
```csharp
318+
[MapToGroup<CustomersV1RouteGroup>()]
319+
[DoNotRegister]
320+
internal class DisabledCustomerFeature
321+
: MinimalEndpoint<IResult>
322+
{
323+
protected override void Configure(
324+
IServiceProvider serviceProvider,
325+
IRouteGroupConfigurator? parentRouteGroup)
326+
{
327+
MapGet("/disabled/");
328+
}
329+
330+
protected override Task<IResult> HandleAsync(
331+
CancellationToken ct)
332+
{
333+
throw new NotImplementedException();
334+
}
335+
}
336+
```
337+
311338
## Samples
312339

313340
[ShowcaseWebApi](https://github.com/modabas/ModEndpoints/tree/main/samples/ShowcaseWebApi) project demonstrates various kinds of endpoint implementations and configurations:

samples/ShowcaseWebApi/Features/Customers/CreateCustomer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public CreateCustomerRequestValidator()
2424

2525
[MapToGroup<CustomersV1RouteGroup>()]
2626
internal class CreateCustomer(ServiceDbContext db)
27-
: MinimalEndpoint<CreateCustomerRequest, Results<CreatedAtRoute<CreateCustomerResponse>, ValidationProblem>>
27+
: MinimalEndpoint<CreateCustomerRequest, Results<CreatedAtRoute<CreateCustomerResponse>, ValidationProblem, ProblemHttpResult>>
2828
{
2929
protected override void Configure(
3030
IServiceProvider serviceProvider,
@@ -33,7 +33,7 @@ protected override void Configure(
3333
MapPost("/");
3434
}
3535

36-
protected override async Task<Results<CreatedAtRoute<CreateCustomerResponse>, ValidationProblem>> HandleAsync(
36+
protected override async Task<Results<CreatedAtRoute<CreateCustomerResponse>, ValidationProblem, ProblemHttpResult>> HandleAsync(
3737
CreateCustomerRequest req,
3838
CancellationToken ct)
3939
{
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using ModEndpoints.Core;
2+
using ModEndpoints.RemoteServices.Core;
3+
using ShowcaseWebApi.Features.Customers.Configuration;
4+
5+
namespace ShowcaseWebApi.Features.Customers;
6+
7+
[MapToGroup<CustomersV1RouteGroup>()]
8+
[DoNotRegister]
9+
internal class DisabledCustomerFeature
10+
: MinimalEndpoint<IResult>
11+
{
12+
protected override void Configure(
13+
IServiceProvider serviceProvider,
14+
IRouteGroupConfigurator? parentRouteGroup)
15+
{
16+
MapGet("/disabled/");
17+
}
18+
19+
protected override Task<IResult> HandleAsync(
20+
CancellationToken ct)
21+
{
22+
throw new NotImplementedException();
23+
}
24+
}

samples/ShowcaseWebApi/Features/Customers/GetCustomerById.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public GetCustomerByIdRequestValidator()
2121

2222
[MapToGroup<CustomersV1RouteGroup>()]
2323
internal class GetCustomerById(ServiceDbContext db)
24-
: MinimalEndpoint<GetCustomerByIdRequest, Results<Ok<GetCustomerByIdResponse>, NotFound, ValidationProblem>>
24+
: MinimalEndpoint<GetCustomerByIdRequest, Results<Ok<GetCustomerByIdResponse>, NotFound, ValidationProblem, ProblemHttpResult>>
2525
{
2626
protected override void Configure(
2727
IServiceProvider serviceProvider,
@@ -30,7 +30,7 @@ protected override void Configure(
3030
MapGet("/{Id}");
3131
}
3232

33-
protected override async Task<Results<Ok<GetCustomerByIdResponse>, NotFound, ValidationProblem>> HandleAsync(
33+
protected override async Task<Results<Ok<GetCustomerByIdResponse>, NotFound, ValidationProblem, ProblemHttpResult>> HandleAsync(
3434
GetCustomerByIdRequest req,
3535
CancellationToken ct)
3636
{

src/ModEndpoints.Core/DependencyInjectionExtensions.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ private static IServiceCollection AddRouteGroupsCoreFromAssembly(
3838
.DefinedTypes
3939
.Where(type => type is { IsAbstract: false, IsInterface: false } &&
4040
type.IsAssignableTo(typeof(IRouteGroupConfigurator)) &&
41-
type != typeof(RootRouteGroup))
41+
type != typeof(RootRouteGroup) &&
42+
!type.GetCustomAttributes(typeof(DoNotRegisterAttribute)).Any())
4243
.Select(type => ServiceDescriptor.DescribeKeyed(typeof(IRouteGroupConfigurator), type, type, lifetime))
4344
.ToArray();
4445

@@ -55,7 +56,8 @@ public static IServiceCollection AddEndpointsCoreFromAssembly(
5556
var endpointTypes = assembly
5657
.DefinedTypes
5758
.Where(type => type is { IsAbstract: false, IsInterface: false } &&
58-
type.IsAssignableTo(typeof(IEndpointConfigurator)));
59+
type.IsAssignableTo(typeof(IEndpointConfigurator)) &&
60+
!type.GetCustomAttributes(typeof(DoNotRegisterAttribute)).Any());
5961

6062
CheckServiceEndpointRegistrations(endpointTypes);
6163

src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ protected virtual ValueTask<TResponse> HandleInvalidValidationResultAsync(
7070
responseType.Name.StartsWith("Results`") &&
7171
(responseType.Namespace?.Equals("Microsoft.AspNetCore.Http.HttpResults") ?? false))
7272
{
73+
if (TryUseImplicitOperatorFor<ProblemHttpResult>(
74+
responseType,
75+
validationResult,
76+
vr => vr.ToTypedProblem(),
77+
out var problem))
78+
{
79+
return new ValueTask<TResponse>(problem);
80+
}
7381
if (TryUseImplicitOperatorFor<ValidationProblem>(
7482
responseType,
7583
validationResult,
@@ -82,25 +90,17 @@ protected virtual ValueTask<TResponse> HandleInvalidValidationResultAsync(
8290
responseType,
8391
validationResult,
8492
vr => vr.ToTypedBadRequestWithValidationProblem(),
85-
out var badRequestWithValidationProblems))
93+
out var badRequestWithValidationProblem))
8694
{
87-
return new ValueTask<TResponse>(badRequestWithValidationProblems);
95+
return new ValueTask<TResponse>(badRequestWithValidationProblem);
8896
}
8997
if (TryUseImplicitOperatorFor<BadRequest<ProblemDetails>>(
9098
responseType,
9199
validationResult,
92100
vr => vr.ToTypedBadRequestWithProblem(),
93-
out var badRequestWithProblems))
101+
out var badRequestWithProblem))
94102
{
95-
return new ValueTask<TResponse>(badRequestWithProblems);
96-
}
97-
if (TryUseImplicitOperatorFor<ProblemHttpResult>(
98-
responseType,
99-
validationResult,
100-
vr => vr.ToTypedProblem(),
101-
out var problem))
102-
{
103-
return new ValueTask<TResponse>(problem);
103+
return new ValueTask<TResponse>(badRequestWithProblem);
104104
}
105105
if (TryUseImplicitOperatorFor<BadRequest>(
106106
responseType,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace ModEndpoints.RemoteServices.Core;
2+
3+
/// <summary>
4+
/// Attribute to prevent targeted route group (including its children) or endpoint to be registered during server application startup.
5+
/// Also prevents targeted service endpoint request to be registered during service endpoint client application startup.
6+
/// </summary>
7+
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
8+
public class DoNotRegisterAttribute : Attribute
9+
{
10+
}

src/ModEndpoints.RemoteServices/DependencyInjectionExtensions.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public static class DependencyInjectionExtensions
1010
private const string ClientDoesNotExist = "A client with name {0} does not exist.";
1111
private const string ChannelAlreadyRegistered = "A channel for request type {0} is already registered.";
1212
private const string ChannelCannotBeRegistered = "Channel couldn't be registered for request type {0} and client name {1}.";
13+
private const string RequestTypeFlaggedAsDoNotRegister = "Request type {0} is flagged as 'DoNotRegister'.";
1314

1415
/// <summary>
1516
/// Adds and configures a new client for specified ServiceEndpoint request.
@@ -163,7 +164,8 @@ public static IServiceCollection AddRemoteServicesWithNewClient(
163164
var requestTypes = fromAssembly
164165
.DefinedTypes
165166
.Where(type => type is { IsAbstract: false, IsInterface: false } &&
166-
type.IsAssignableTo(typeof(IServiceRequestMarker)));
167+
type.IsAssignableTo(typeof(IServiceRequestMarker)) &&
168+
!type.GetCustomAttributes(typeof(DoNotRegisterAttribute)).Any());
167169

168170
if (requestFilterPredicate is not null)
169171
{
@@ -206,7 +208,8 @@ public static IServiceCollection AddRemoteServicesToExistingClient(
206208
var requestTypes = fromAssembly
207209
.DefinedTypes
208210
.Where(type => type is { IsAbstract: false, IsInterface: false } &&
209-
type.IsAssignableTo(typeof(IServiceRequestMarker)));
211+
type.IsAssignableTo(typeof(IServiceRequestMarker)) &&
212+
!type.GetCustomAttributes(typeof(DoNotRegisterAttribute)).Any());
210213

211214
if (requestFilterPredicate is not null)
212215
{
@@ -229,6 +232,10 @@ private static IServiceCollection AddRemoteServiceWithNewClientInternal(
229232
Action<IServiceProvider, HttpClient> configureClient,
230233
Action<IHttpClientBuilder>? configureClientBuilder)
231234
{
235+
if (requestType.GetCustomAttributes(typeof(DoNotRegisterAttribute)).Any())
236+
{
237+
throw new InvalidOperationException(string.Format(RequestTypeFlaggedAsDoNotRegister, requestType));
238+
}
232239
if (ServiceChannelRegistry.Instance.IsRequestRegistered(requestType))
233240
{
234241
throw new InvalidOperationException(string.Format(ChannelAlreadyRegistered, requestType));
@@ -250,6 +257,10 @@ private static IServiceCollection AddRemoteServiceToExistingClientInternal(
250257
Type requestType,
251258
string clientName)
252259
{
260+
if (requestType.GetCustomAttributes(typeof(DoNotRegisterAttribute)).Any())
261+
{
262+
throw new InvalidOperationException(string.Format(RequestTypeFlaggedAsDoNotRegister, requestType));
263+
}
253264
if (ServiceChannelRegistry.Instance.IsRequestRegistered(requestType))
254265
{
255266
throw new InvalidOperationException(string.Format(ChannelAlreadyRegistered, requestType));

0 commit comments

Comments
 (0)