From fedaf4262be82a0e2407f93edc33a593bc50d5ca Mon Sep 17 00:00:00 2001 From: Melih Odabas Date: Mon, 24 Feb 2025 11:29:16 +0300 Subject: [PATCH 1/5] - MinimalEndpoints: add support for invalid response message for MinimalEndpoints that use TypedResults - add Customers feature that use MinimalEndpoints to ShowcaseWebApi project --- .../ShowcaseWebApi/Data/ServiceDbContext.cs | 2 + .../Configuration/CustomersV1RouteGroup.cs | 16 ++++ .../Features/Customers/CreateCustomer.cs | 55 ++++++++++++++ .../Features/Customers/Data/CustomerEntity.cs | 10 +++ .../Features/Customers/DeleteBook.cs | 45 +++++++++++ .../Features/Customers/GetCustomerById.cs | 47 ++++++++++++ .../Features/Customers/ListCustomers.cs | 39 ++++++++++ .../Features/Customers/UpdateCustomer.cs | 62 +++++++++++++++ .../ValidationResultExtensions.cs | 31 +++++++- .../[Endpoints]/MinimalEndpoint.cs | 75 ++++++++++++++++++- 10 files changed, 379 insertions(+), 3 deletions(-) create mode 100644 samples/ShowcaseWebApi/Features/Customers/Configuration/CustomersV1RouteGroup.cs create mode 100644 samples/ShowcaseWebApi/Features/Customers/CreateCustomer.cs create mode 100644 samples/ShowcaseWebApi/Features/Customers/Data/CustomerEntity.cs create mode 100644 samples/ShowcaseWebApi/Features/Customers/DeleteBook.cs create mode 100644 samples/ShowcaseWebApi/Features/Customers/GetCustomerById.cs create mode 100644 samples/ShowcaseWebApi/Features/Customers/ListCustomers.cs create mode 100644 samples/ShowcaseWebApi/Features/Customers/UpdateCustomer.cs diff --git a/samples/ShowcaseWebApi/Data/ServiceDbContext.cs b/samples/ShowcaseWebApi/Data/ServiceDbContext.cs index 1297e5e..dc8a840 100644 --- a/samples/ShowcaseWebApi/Data/ServiceDbContext.cs +++ b/samples/ShowcaseWebApi/Data/ServiceDbContext.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using ShowcaseWebApi.Features.Books.Data; +using ShowcaseWebApi.Features.Customers.Data; using ShowcaseWebApi.Features.Stores.Data; namespace ShowcaseWebApi.Data; @@ -23,5 +24,6 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) #region DbSets public DbSet Books => Set(); public DbSet Stores => Set(); + public DbSet Customers => Set(); #endregion } diff --git a/samples/ShowcaseWebApi/Features/Customers/Configuration/CustomersV1RouteGroup.cs b/samples/ShowcaseWebApi/Features/Customers/Configuration/CustomersV1RouteGroup.cs new file mode 100644 index 0000000..6e41b9f --- /dev/null +++ b/samples/ShowcaseWebApi/Features/Customers/Configuration/CustomersV1RouteGroup.cs @@ -0,0 +1,16 @@ +using ModEndpoints.Core; + +namespace ShowcaseWebApi.Features.Customers.Configuration; + +[MapToGroup()] +internal class CustomersV1RouteGroup : RouteGroupConfigurator +{ + protected override void Configure( + IServiceProvider serviceProvider, + IRouteGroupConfigurator? parentRouteGroup) + { + MapGroup("/customers") + .MapToApiVersion(1) + .WithTags("/CustomersV1"); + } +} diff --git a/samples/ShowcaseWebApi/Features/Customers/CreateCustomer.cs b/samples/ShowcaseWebApi/Features/Customers/CreateCustomer.cs new file mode 100644 index 0000000..4f9eded --- /dev/null +++ b/samples/ShowcaseWebApi/Features/Customers/CreateCustomer.cs @@ -0,0 +1,55 @@ +using FluentValidation; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; +using ModEndpoints.Core; +using ShowcaseWebApi.Data; +using ShowcaseWebApi.Features.Customers.Configuration; +using ShowcaseWebApi.Features.Customers.Data; + +namespace ShowcaseWebApi.Features.Customers; +public record CreateCustomerRequest([FromBody] CreateCustomerRequestBody Body); + +public record CreateCustomerRequestBody(string FirstName, string? MiddleName, string LastName); + +public record CreateCustomerResponse(Guid Id); + +internal class CreateCustomerRequestValidator : AbstractValidator +{ + public CreateCustomerRequestValidator() + { + RuleFor(x => x.Body.FirstName).NotEmpty(); + RuleFor(x => x.Body.LastName).NotEmpty(); + } +} + +[MapToGroup()] +internal class CreateCustomer(ServiceDbContext db) + : MinimalEndpoint, ValidationProblem>> +{ + protected override void Configure( + IServiceProvider serviceProvider, + IRouteGroupConfigurator? parentRouteGroup) + { + MapPost("/"); + } + + protected override async Task, ValidationProblem>> HandleAsync( + CreateCustomerRequest req, + CancellationToken ct) + { + var customer = new CustomerEntity() + { + FirstName = req.Body.FirstName, + MiddleName = req.Body.MiddleName, + LastName = req.Body.LastName + }; + + db.Customers.Add(customer); + await db.SaveChangesAsync(ct); + + return TypedResults.CreatedAtRoute( + new CreateCustomerResponse(customer.Id), + typeof(GetCustomerById).FullName, + new { id = customer.Id }); + } +} diff --git a/samples/ShowcaseWebApi/Features/Customers/Data/CustomerEntity.cs b/samples/ShowcaseWebApi/Features/Customers/Data/CustomerEntity.cs new file mode 100644 index 0000000..ceb96b8 --- /dev/null +++ b/samples/ShowcaseWebApi/Features/Customers/Data/CustomerEntity.cs @@ -0,0 +1,10 @@ +using ShowcaseWebApi.Data; + +namespace ShowcaseWebApi.Features.Customers.Data; + +internal class CustomerEntity : BaseEntity +{ + public string FirstName { get; set; } = string.Empty; + public string? MiddleName { get; set; } + public string LastName { get; set; } = string.Empty; +} diff --git a/samples/ShowcaseWebApi/Features/Customers/DeleteBook.cs b/samples/ShowcaseWebApi/Features/Customers/DeleteBook.cs new file mode 100644 index 0000000..c1acdb9 --- /dev/null +++ b/samples/ShowcaseWebApi/Features/Customers/DeleteBook.cs @@ -0,0 +1,45 @@ +using FluentValidation; +using Microsoft.EntityFrameworkCore; +using ModEndpoints.Core; +using ShowcaseWebApi.Data; +using ShowcaseWebApi.Features.Customers.Configuration; + +namespace ShowcaseWebApi.Features.Customers; +public record DeleteCustomerRequest(Guid Id); + +internal class DeleteCustomerRequestValidator : AbstractValidator +{ + public DeleteCustomerRequestValidator() + { + RuleFor(x => x.Id).NotEmpty(); + } +} + +[MapToGroup()] +internal class DeleteCustomer(ServiceDbContext db) + : MinimalEndpoint +{ + protected override void Configure( + IServiceProvider serviceProvider, + IRouteGroupConfigurator? parentRouteGroup) + { + MapDelete("/{Id}") + .Produces(StatusCodes.Status204NoContent); + } + + protected override async Task HandleAsync( + DeleteCustomerRequest req, + CancellationToken ct) + { + var entity = await db.Customers.FirstOrDefaultAsync(b => b.Id == req.Id, ct); + + if (entity is null) + { + return Results.NotFound(); + } + + db.Customers.Remove(entity); + var deleted = await db.SaveChangesAsync(ct); + return deleted > 0 ? Results.NoContent() : Results.NotFound(); + } +} diff --git a/samples/ShowcaseWebApi/Features/Customers/GetCustomerById.cs b/samples/ShowcaseWebApi/Features/Customers/GetCustomerById.cs new file mode 100644 index 0000000..ee9eb15 --- /dev/null +++ b/samples/ShowcaseWebApi/Features/Customers/GetCustomerById.cs @@ -0,0 +1,47 @@ +using FluentValidation; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.EntityFrameworkCore; +using ModEndpoints.Core; +using ShowcaseWebApi.Data; +using ShowcaseWebApi.Features.Customers.Configuration; + +namespace ShowcaseWebApi.Features.Customers; + +public record GetCustomerByIdRequest(Guid Id); + +public record GetCustomerByIdResponse(Guid Id, string FirstName, string? MiddleName, string LastName); + +internal class GetCustomerByIdRequestValidator : AbstractValidator +{ + public GetCustomerByIdRequestValidator() + { + RuleFor(x => x.Id).NotEmpty(); + } +} + +[MapToGroup()] +internal class GetCustomerById(ServiceDbContext db) + : MinimalEndpoint, NotFound, ValidationProblem>> +{ + protected override void Configure( + IServiceProvider serviceProvider, + IRouteGroupConfigurator? parentRouteGroup) + { + MapGet("/{Id}"); + } + + protected override async Task, NotFound, ValidationProblem>> HandleAsync( + GetCustomerByIdRequest req, + CancellationToken ct) + { + var entity = await db.Customers.FirstOrDefaultAsync(b => b.Id == req.Id, ct); + + return entity is null ? + TypedResults.NotFound() : + TypedResults.Ok(new GetCustomerByIdResponse( + Id: entity.Id, + FirstName: entity.FirstName, + MiddleName: entity.MiddleName, + LastName: entity.LastName)); + } +} diff --git a/samples/ShowcaseWebApi/Features/Customers/ListCustomers.cs b/samples/ShowcaseWebApi/Features/Customers/ListCustomers.cs new file mode 100644 index 0000000..59f061a --- /dev/null +++ b/samples/ShowcaseWebApi/Features/Customers/ListCustomers.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore; +using ModEndpoints.Core; +using ShowcaseWebApi.Data; +using ShowcaseWebApi.Features.Customers.Configuration; + +namespace ShowcaseWebApi.Features.Customers; + +public record ListCustomersResponse(List Customers); +public record ListCustomersResponseItem( + Guid Id, + string FirstName, + string? MiddleName, + string LastName); + +[MapToGroup()] +internal class ListCustomers(ServiceDbContext db) + : MinimalEndpoint +{ + protected override void Configure( + IServiceProvider serviceProvider, + IRouteGroupConfigurator? parentRouteGroup) + { + MapGet("/"); + } + + protected override async Task HandleAsync( + CancellationToken ct) + { + var customers = await db.Customers + .Select(c => new ListCustomersResponseItem( + c.Id, + c.FirstName, + c.MiddleName, + c.LastName)) + .ToListAsync(ct); + + return new ListCustomersResponse(Customers: customers); + } +} diff --git a/samples/ShowcaseWebApi/Features/Customers/UpdateCustomer.cs b/samples/ShowcaseWebApi/Features/Customers/UpdateCustomer.cs new file mode 100644 index 0000000..f3aeab8 --- /dev/null +++ b/samples/ShowcaseWebApi/Features/Customers/UpdateCustomer.cs @@ -0,0 +1,62 @@ +using FluentValidation; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using ModEndpoints.Core; +using ShowcaseWebApi.Data; +using ShowcaseWebApi.Features.Customers.Configuration; + +namespace ShowcaseWebApi.Features.Customers; +public record UpdateCustomerRequest(Guid Id, [FromBody] UpdateCustomerRequestBody Body); + +public record UpdateCustomerRequestBody(string FirstName, string? MiddleName, string LastName); + +public record UpdateCustomerResponse(Guid Id, string FirstName, string? MiddleName, string LastName); + +internal class UpdateCustomerRequestValidator : AbstractValidator +{ + public UpdateCustomerRequestValidator() + { + RuleFor(x => x.Id).NotEmpty(); + RuleFor(x => x.Body.FirstName).NotEmpty(); + RuleFor(x => x.Body.LastName).NotEmpty(); + + } +} + +[MapToGroup()] +internal class UpdateCustomer(ServiceDbContext db) + : MinimalEndpoint +{ + protected override void Configure( + IServiceProvider serviceProvider, + IRouteGroupConfigurator? parentRouteGroup) + { + MapPut("/{Id}") + .Produces(); + } + + protected override async Task HandleAsync( + UpdateCustomerRequest req, + CancellationToken ct) + { + var entity = await db.Customers.FirstOrDefaultAsync(b => b.Id == req.Id, ct); + + if (entity is null) + { + return Results.NotFound(); + } + + entity.FirstName = req.Body.FirstName; + entity.MiddleName = req.Body.MiddleName; + entity.LastName = req.Body.LastName; + + var updated = await db.SaveChangesAsync(ct); + return updated > 0 ? + Results.Ok(new UpdateCustomerResponse( + Id: req.Id, + FirstName: req.Body.FirstName, + MiddleName: req.Body.MiddleName, + LastName: req.Body.LastName)) + : Results.NotFound(); + } +} diff --git a/src/ModEndpoints.Core/ValidationResultExtensions.cs b/src/ModEndpoints.Core/ValidationResultExtensions.cs index d5c251a..1663adb 100644 --- a/src/ModEndpoints.Core/ValidationResultExtensions.cs +++ b/src/ModEndpoints.Core/ValidationResultExtensions.cs @@ -1,15 +1,42 @@ using FluentValidation.Results; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; namespace ModEndpoints.Core; public static class ValidationResultExtensions { public static IResult ToMinimalApiResult(this ValidationResult validationResult) { - var errors = validationResult.Errors + var errors = GetErrors(validationResult); + return Results.ValidationProblem(errors); + } + public static ValidationProblem ToTypedValidationProblem(this ValidationResult validationResult) + { + var errors = GetErrors(validationResult); + return TypedResults.ValidationProblem(errors); + } + public static ProblemHttpResult ToTypedProblem(this ValidationResult validationResult) + { + var errors = GetErrors(validationResult); + return TypedResults.Problem(new HttpValidationProblemDetails(errors)); + } + public static BadRequest ToTypedBadRequestWithValidationProblem(this ValidationResult validationResult) + { + var errors = GetErrors(validationResult); + return TypedResults.BadRequest(new HttpValidationProblemDetails(errors)); + } + public static BadRequest ToTypedBadRequestWithProblem(this ValidationResult validationResult) + { + var errors = GetErrors(validationResult); + return TypedResults.BadRequest((ProblemDetails)new HttpValidationProblemDetails(errors)); + } + + private static Dictionary GetErrors(ValidationResult validationResult) + { + return validationResult.Errors .GroupBy(e => e.PropertyName) .Select(g => new { g.Key, Values = g.Select(e => e.ErrorMessage).ToArray() }) .ToDictionary(pair => pair.Key, pair => pair.Values); - return Results.ValidationProblem(errors); } } diff --git a/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs b/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs index 2990aff..fd8c178 100644 --- a/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs +++ b/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs @@ -1,6 +1,9 @@ -using FluentValidation; +using System.Diagnostics.CodeAnalysis; +using FluentValidation; using FluentValidation.Results; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; namespace ModEndpoints.Core; @@ -60,6 +63,55 @@ protected virtual ValueTask HandleInvalidValidationResultAsync( HttpContext context, CancellationToken ct) { + var responseType = typeof(TResponse); + + //Is using TypedResults + if (responseType.IsGenericType && + responseType.Name.StartsWith("Results`") && + (responseType.Namespace?.Equals("Microsoft.AspNetCore.Http.HttpResults") ?? false)) + { + if (TryUseImplicitOperatorFor( + responseType, + validationResult, + vr => vr.ToTypedValidationProblem(), + out var validationProblem)) + { + return new ValueTask(validationProblem); + } + if (TryUseImplicitOperatorFor>( + responseType, + validationResult, + vr => vr.ToTypedBadRequestWithValidationProblem(), + out var badRequestWithValidationProblems)) + { + return new ValueTask(badRequestWithValidationProblems); + } + if (TryUseImplicitOperatorFor>( + responseType, + validationResult, + vr => vr.ToTypedBadRequestWithProblem(), + out var badRequestWithProblems)) + { + return new ValueTask(badRequestWithProblems); + } + if (TryUseImplicitOperatorFor( + responseType, + validationResult, + vr => vr.ToTypedProblem(), + out var problem)) + { + return new ValueTask(problem); + } + if (TryUseImplicitOperatorFor( + responseType, + validationResult, + _ => TypedResults.BadRequest(), + out var badRequest)) + { + return new ValueTask(badRequest); + } + } + if (typeof(TResponse).IsAssignableFrom(typeof(IResult))) { return new ValueTask((TResponse)validationResult.ToMinimalApiResult()); @@ -69,6 +121,27 @@ protected virtual ValueTask HandleInvalidValidationResultAsync( throw new ValidationException(validationResult.Errors); } } + + private static bool TryUseImplicitOperatorFor( + Type responseType, + ValidationResult validationResult, + Func conversionFunc, + [NotNullWhen(true)] out TResponse? response) + { + var converter = responseType.GetMethod("op_Implicit", new[] { typeof(T) }); + + if (converter is not null) + { + var result = converter.Invoke(null, new[] { (object?)conversionFunc(validationResult) }); + if (result is not null) + { + response = (TResponse)result; + return true; + } + } + response = default; + return false; + } } public abstract class MinimalEndpoint From 93ec043f1ed9e71b9fc2f12637f4736529cba930 Mon Sep 17 00:00:00 2001 From: Melih Odabas Date: Mon, 24 Feb 2025 11:30:02 +0300 Subject: [PATCH 2/5] - code cleanup --- src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs b/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs index fd8c178..8aa1283 100644 --- a/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs +++ b/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs @@ -64,15 +64,15 @@ protected virtual ValueTask HandleInvalidValidationResultAsync( CancellationToken ct) { var responseType = typeof(TResponse); - + //Is using TypedResults if (responseType.IsGenericType && responseType.Name.StartsWith("Results`") && (responseType.Namespace?.Equals("Microsoft.AspNetCore.Http.HttpResults") ?? false)) { if (TryUseImplicitOperatorFor( - responseType, - validationResult, + responseType, + validationResult, vr => vr.ToTypedValidationProblem(), out var validationProblem)) { From 6ff2c5173536575235e1d38aea7b5aad567ed703 Mon Sep 17 00:00:00 2001 From: Melih Odabas Date: Mon, 24 Feb 2025 12:00:29 +0300 Subject: [PATCH 3/5] - seed customers table on application startup - update reaadme --- README.md | 16 +++++++++++++--- .../Extensions/WebApplicationExtensions.cs | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 373f557..2ab446a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ To make consuming a [ServiceEndpoint](#serviceendpoint) easier, which is a very specialized endpoint more suitable for internal services, a specific [client implementation](#serviceendpoint-clients) along with extensions required for client registration is implemented in ModEndpoints.RemoteServices package, and interfaces required for ServiceEndpoint request models are in ModEndpoints.RemoteServices.Core package. -[ShowcaseWebApi](https://github.com/modabas/ModEndpoints/tree/main/samples/ShowcaseWebApi) project demonstrates various kinds of endpoint implementations and configurations. [Client](https://github.com/modabas/ModEndpoints/tree/main/samples/Client) project is a sample ServiceEndpoint consumer. +Each of them are demonstrated in [sample projects](#samples). All endpoint abstractions are a structured approach to defining endpoints in ASP.NET Core applications. They extend the Minimal Api pattern with reusable, testable, and consistent components for request handling, validation, and response mapping. @@ -308,6 +308,16 @@ internal class CreateBook(ServiceDbContext db, ILocationStore location) } ``` +## Samples + +[ShowcaseWebApi](https://github.com/modabas/ModEndpoints/tree/main/samples/ShowcaseWebApi) project demonstrates various kinds of endpoint implementations and configurations: + - MinimalEnpoints samples are in [Customers](https://github.com/modabas/ModEndpoints/tree/main/samples/ShowcaseWebApi/Features/Customers) subfolder, + - WebResultEndpoints samples are in [Books](https://github.com/modabas/ModEndpoints/tree/main/samples/ShowcaseWebApi/Features/Books) subfolder, + - BusinessResultEndpoints samples are in [Stores](https://github.com/modabas/ModEndpoints/tree/main/samples/ShowcaseWebApi/Features/Stores) subfolder, + - ServiceEndpoints samples are in [StoresWithServiceEndpoint](https://github.com/modabas/ModEndpoints/tree/main/samples/ShowcaseWebApi/Features/StoresWithServiceEndpoint) subfolder. + +[ServiceEndpointClient](https://github.com/modabas/ModEndpoints/tree/main/samples/ServiceEndpointClient) project demonstrates how to consume ServiceEndpoints. + ## Performance WebResultEndpoints have a slight overhead (3-4%) over regular Minimal Apis on request/sec metric under load tests with 100 virtual users. @@ -326,9 +336,9 @@ MinimalEndpoint within ModEndpoints.Core package, is closest to barebones Minima - string - T (Any other type) -- Minimal Api IResult based +- Minimal Api IResult based (Including TypedResults with Results return value) -Other features described previously are common for all of them. +See (How to create responses in Minimal API apps)[https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/responses?view=aspnetcore-8.0] for detailed information. Other features described previously are common for all of them. Each type of endpoint has various implementations that accept a request model or not, that has a response model or not. diff --git a/samples/ShowcaseWebApi/Extensions/WebApplicationExtensions.cs b/samples/ShowcaseWebApi/Extensions/WebApplicationExtensions.cs index 2b12c49..20b0741 100644 --- a/samples/ShowcaseWebApi/Extensions/WebApplicationExtensions.cs +++ b/samples/ShowcaseWebApi/Extensions/WebApplicationExtensions.cs @@ -1,5 +1,6 @@ using ShowcaseWebApi.Data; using ShowcaseWebApi.Features.Books.Data; +using ShowcaseWebApi.Features.Customers.Data; using ShowcaseWebApi.Features.Stores.Data; namespace ShowcaseWebApi.Extensions; @@ -41,6 +42,23 @@ public static void SeedData(this WebApplication app) { Name = "Middling Evil Store" }); + db.Customers.Add(new CustomerEntity() + { + FirstName = "Willie", + MiddleName = "Jonathan", + LastName = "Normand" + }); + db.Customers.Add(new CustomerEntity() + { + FirstName = "Leslie", + MiddleName = "Lois", + LastName = "Coffman" + }); + db.Customers.Add(new CustomerEntity() + { + FirstName = "Oliver", + LastName = "Rogers" + }); db.SaveChanges(); } } From fbf98aee8de5a5af2716772c762c21d096a902aa Mon Sep 17 00:00:00 2001 From: Melih Odabas Date: Mon, 24 Feb 2025 12:04:43 +0300 Subject: [PATCH 4/5] - bump version --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 00eb0ba..250ad39 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,6 +18,6 @@ MIT True - 0.6.2 + 0.6.3 \ No newline at end of file From b2fcde09b7db034ef40905b7149dceb193e5e275 Mon Sep 17 00:00:00 2001 From: Melih Odabas Date: Mon, 24 Feb 2025 12:08:43 +0300 Subject: [PATCH 5/5] - use already resolved response type instead of calling typeof() again --- src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs b/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs index 8aa1283..32ee4c0 100644 --- a/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs +++ b/src/ModEndpoints.Core/[Endpoints]/MinimalEndpoint.cs @@ -112,7 +112,7 @@ protected virtual ValueTask HandleInvalidValidationResultAsync( } } - if (typeof(TResponse).IsAssignableFrom(typeof(IResult))) + if (responseType.IsAssignableFrom(typeof(IResult))) { return new ValueTask((TResponse)validationResult.ToMinimalApiResult()); }