Skip to content
Merged

Dev #13

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>

<Version>0.6.3</Version>
<Version>0.6.4</Version>
</PropertyGroup>
</Project>
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<CustomersV1RouteGroup>()]
[DoNotRegister]
internal class DisabledCustomerFeature
: MinimalEndpoint<IResult>
{
protected override void Configure(
IServiceProvider serviceProvider,
IRouteGroupConfigurator? parentRouteGroup)
{
MapGet("/disabled/");
}

protected override Task<IResult> 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:
Expand Down
4 changes: 2 additions & 2 deletions samples/ShowcaseWebApi/Features/Customers/CreateCustomer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public CreateCustomerRequestValidator()

[MapToGroup<CustomersV1RouteGroup>()]
internal class CreateCustomer(ServiceDbContext db)
: MinimalEndpoint<CreateCustomerRequest, Results<CreatedAtRoute<CreateCustomerResponse>, ValidationProblem>>
: MinimalEndpoint<CreateCustomerRequest, Results<CreatedAtRoute<CreateCustomerResponse>, ValidationProblem, ProblemHttpResult>>
{
protected override void Configure(
IServiceProvider serviceProvider,
Expand All @@ -33,7 +33,7 @@ protected override void Configure(
MapPost("/");
}

protected override async Task<Results<CreatedAtRoute<CreateCustomerResponse>, ValidationProblem>> HandleAsync(
protected override async Task<Results<CreatedAtRoute<CreateCustomerResponse>, ValidationProblem, ProblemHttpResult>> HandleAsync(
CreateCustomerRequest req,
CancellationToken ct)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using ModEndpoints.Core;
using ModEndpoints.RemoteServices.Core;
using ShowcaseWebApi.Features.Customers.Configuration;

namespace ShowcaseWebApi.Features.Customers;

[MapToGroup<CustomersV1RouteGroup>()]
[DoNotRegister]
internal class DisabledCustomerFeature
: MinimalEndpoint<IResult>
{
protected override void Configure(
IServiceProvider serviceProvider,
IRouteGroupConfigurator? parentRouteGroup)
{
MapGet("/disabled/");
}

protected override Task<IResult> HandleAsync(
CancellationToken ct)
{
throw new NotImplementedException();
}
}
4 changes: 2 additions & 2 deletions samples/ShowcaseWebApi/Features/Customers/GetCustomerById.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public GetCustomerByIdRequestValidator()

[MapToGroup<CustomersV1RouteGroup>()]
internal class GetCustomerById(ServiceDbContext db)
: MinimalEndpoint<GetCustomerByIdRequest, Results<Ok<GetCustomerByIdResponse>, NotFound, ValidationProblem>>
: MinimalEndpoint<GetCustomerByIdRequest, Results<Ok<GetCustomerByIdResponse>, NotFound, ValidationProblem, ProblemHttpResult>>
{
protected override void Configure(
IServiceProvider serviceProvider,
Expand All @@ -30,7 +30,7 @@ protected override void Configure(
MapGet("/{Id}");
}

protected override async Task<Results<Ok<GetCustomerByIdResponse>, NotFound, ValidationProblem>> HandleAsync(
protected override async Task<Results<Ok<GetCustomerByIdResponse>, NotFound, ValidationProblem, ProblemHttpResult>> HandleAsync(
GetCustomerByIdRequest req,
CancellationToken ct)
{
Expand Down
6 changes: 4 additions & 2 deletions src/ModEndpoints.Core/DependencyInjectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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);

Expand Down
24 changes: 12 additions & 12 deletions src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ protected virtual ValueTask<TResponse> HandleInvalidValidationResultAsync(
responseType.Name.StartsWith("Results`") &&
(responseType.Namespace?.Equals("Microsoft.AspNetCore.Http.HttpResults") ?? false))
{
if (TryUseImplicitOperatorFor<ProblemHttpResult>(
responseType,
validationResult,
vr => vr.ToTypedProblem(),
out var problem))
{
return new ValueTask<TResponse>(problem);
}
if (TryUseImplicitOperatorFor<ValidationProblem>(
responseType,
validationResult,
Expand All @@ -82,25 +90,17 @@ protected virtual ValueTask<TResponse> HandleInvalidValidationResultAsync(
responseType,
validationResult,
vr => vr.ToTypedBadRequestWithValidationProblem(),
out var badRequestWithValidationProblems))
out var badRequestWithValidationProblem))
{
return new ValueTask<TResponse>(badRequestWithValidationProblems);
return new ValueTask<TResponse>(badRequestWithValidationProblem);
}
if (TryUseImplicitOperatorFor<BadRequest<ProblemDetails>>(
responseType,
validationResult,
vr => vr.ToTypedBadRequestWithProblem(),
out var badRequestWithProblems))
out var badRequestWithProblem))
{
return new ValueTask<TResponse>(badRequestWithProblems);
}
if (TryUseImplicitOperatorFor<ProblemHttpResult>(
responseType,
validationResult,
vr => vr.ToTypedProblem(),
out var problem))
{
return new ValueTask<TResponse>(problem);
return new ValueTask<TResponse>(badRequestWithProblem);
}
if (TryUseImplicitOperatorFor<BadRequest>(
responseType,
Expand Down
10 changes: 10 additions & 0 deletions src/ModEndpoints.RemoteServices.Core/DoNotRegisterAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace ModEndpoints.RemoteServices.Core;

/// <summary>
/// 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.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class DoNotRegisterAttribute : Attribute
{
}
15 changes: 13 additions & 2 deletions src/ModEndpoints.RemoteServices/DependencyInjectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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'.";

/// <summary>
/// Adds and configures a new client for specified ServiceEndpoint request.
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand All @@ -229,6 +232,10 @@ private static IServiceCollection AddRemoteServiceWithNewClientInternal(
Action<IServiceProvider, HttpClient> configureClient,
Action<IHttpClientBuilder>? 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));
Expand All @@ -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));
Expand Down