From 0ad50629d522e66bb537706f3114e188f987edee Mon Sep 17 00:00:00 2001 From: "Collin M. Barrett" Date: Fri, 12 Nov 2021 18:21:30 -0600 Subject: [PATCH] =?UTF-8?q?feat(dir):=20=E2=9C=A8=20validate=20list=20deta?= =?UTF-8?q?ils=20query=20id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FilterLists.Directory.Api.csproj | 1 + .../ProblemDetailsConfigurationExtensions.cs | 29 +++++++++++++++++++ .../FilterLists.Directory.Api/Program.cs | 5 ++++ ...s.cs => SwaggerConfigurationExtensions.cs} | 2 +- .../ConfigurationExtensions.cs | 3 ++ .../FilterLists.Directory.Application.csproj | 1 + .../Queries/GetListDetails.cs | 10 +++++++ .../ValidatorPipelineBahavior.cs | 27 +++++++++++++++++ 8 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 services/Directory/FilterLists.Directory.Api/ProblemDetailsConfigurationExtensions.cs rename services/Directory/FilterLists.Directory.Api/{SwaggerExtensions.cs => SwaggerConfigurationExtensions.cs} (96%) create mode 100644 services/Directory/FilterLists.Directory.Application/ValidatorPipelineBahavior.cs diff --git a/services/Directory/FilterLists.Directory.Api/FilterLists.Directory.Api.csproj b/services/Directory/FilterLists.Directory.Api/FilterLists.Directory.Api.csproj index 59a959eb7..69ab728f6 100644 --- a/services/Directory/FilterLists.Directory.Api/FilterLists.Directory.Api.csproj +++ b/services/Directory/FilterLists.Directory.Api/FilterLists.Directory.Api.csproj @@ -28,6 +28,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/services/Directory/FilterLists.Directory.Api/ProblemDetailsConfigurationExtensions.cs b/services/Directory/FilterLists.Directory.Api/ProblemDetailsConfigurationExtensions.cs new file mode 100644 index 000000000..427447d6d --- /dev/null +++ b/services/Directory/FilterLists.Directory.Api/ProblemDetailsConfigurationExtensions.cs @@ -0,0 +1,29 @@ +using FluentValidation; +using Hellang.Middleware.ProblemDetails; + +namespace FilterLists.Directory.Api; + +internal static class ProblemDetailsConfigurationExtensions +{ + public static void ConfigureProblemDetails(this ProblemDetailsOptions options) + { + options.MapFluentValidationException(); + options.MapToStatusCode(StatusCodes.Status501NotImplemented); + options.MapToStatusCode(StatusCodes.Status503ServiceUnavailable); + options.MapToStatusCode(StatusCodes.Status500InternalServerError); + } + + private static void MapFluentValidationException(this ProblemDetailsOptions options) + { + options.Map((ctx, ex) => + { + var factory = ctx.RequestServices.GetRequiredService(); + var errors = ex.Errors + .GroupBy(f => f.PropertyName) + .ToDictionary( + g => g.Key, + g => g.Select(f => f.ErrorMessage).ToArray()); + return factory.CreateValidationProblemDetails(ctx, errors); + }); + } +} diff --git a/services/Directory/FilterLists.Directory.Api/Program.cs b/services/Directory/FilterLists.Directory.Api/Program.cs index 49a099233..398c5ea67 100644 --- a/services/Directory/FilterLists.Directory.Api/Program.cs +++ b/services/Directory/FilterLists.Directory.Api/Program.cs @@ -3,13 +3,17 @@ using FilterLists.Directory.Application; using FilterLists.Directory.Infrastructure.Persistence; using FilterLists.SharedKernel.Logging; +using Hellang.Middleware.ProblemDetails; +using Hellang.Middleware.ProblemDetails.Mvc; var builder = WebApplication.CreateBuilder(args); builder.Host.ConfigureApplication(); builder.Services.AddMemoryCache(); +builder.Services.AddProblemDetails(ProblemDetailsConfigurationExtensions.ConfigureProblemDetails); builder.Services.Configure(options => options.LowercaseUrls = true); builder.Services.AddControllers() + .AddProblemDetailsConventions() .AddJsonOptions(o => o.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); @@ -19,6 +23,7 @@ app.UseApplication(); app.UseExceptionHandler(app.Environment.IsDevelopment() ? "/error-local-development" : "/error"); +app.UseProblemDetails(); app.UseSwagger(); app.MapControllers(); diff --git a/services/Directory/FilterLists.Directory.Api/SwaggerExtensions.cs b/services/Directory/FilterLists.Directory.Api/SwaggerConfigurationExtensions.cs similarity index 96% rename from services/Directory/FilterLists.Directory.Api/SwaggerExtensions.cs rename to services/Directory/FilterLists.Directory.Api/SwaggerConfigurationExtensions.cs index ba68d1de4..6829174f8 100644 --- a/services/Directory/FilterLists.Directory.Api/SwaggerExtensions.cs +++ b/services/Directory/FilterLists.Directory.Api/SwaggerConfigurationExtensions.cs @@ -3,7 +3,7 @@ namespace FilterLists.Directory.Api; -internal static class SwaggerExtensions +internal static class SwaggerConfigurationExtensions { public static void AddSwaggerGen(this IServiceCollection services) { diff --git a/services/Directory/FilterLists.Directory.Application/ConfigurationExtensions.cs b/services/Directory/FilterLists.Directory.Application/ConfigurationExtensions.cs index 73304f209..e8e5e8b03 100644 --- a/services/Directory/FilterLists.Directory.Application/ConfigurationExtensions.cs +++ b/services/Directory/FilterLists.Directory.Application/ConfigurationExtensions.cs @@ -1,4 +1,5 @@ using FilterLists.Directory.Infrastructure; +using FluentValidation; using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; @@ -17,6 +18,8 @@ public static IHostBuilder ConfigureApplication(this IHostBuilder hostBuilder) public static void AddApplication(this IServiceCollection services, IConfiguration configuration) { services.AddMediatR(typeof(ConfigurationExtensions).Assembly); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidatorPipelineBehavior<,>)); + services.AddValidatorsFromAssembly(typeof(ConfigurationExtensions).Assembly, includeInternalTypes: true); services.AddAutoMapper(typeof(ConfigurationExtensions).Assembly); services.AddInfrastructure(configuration); } diff --git a/services/Directory/FilterLists.Directory.Application/FilterLists.Directory.Application.csproj b/services/Directory/FilterLists.Directory.Application/FilterLists.Directory.Application.csproj index 68617b8e7..f89c39246 100644 --- a/services/Directory/FilterLists.Directory.Application/FilterLists.Directory.Application.csproj +++ b/services/Directory/FilterLists.Directory.Application/FilterLists.Directory.Application.csproj @@ -18,6 +18,7 @@ + diff --git a/services/Directory/FilterLists.Directory.Application/Queries/GetListDetails.cs b/services/Directory/FilterLists.Directory.Application/Queries/GetListDetails.cs index ad1a9aa2b..30e592eae 100644 --- a/services/Directory/FilterLists.Directory.Application/Queries/GetListDetails.cs +++ b/services/Directory/FilterLists.Directory.Application/Queries/GetListDetails.cs @@ -3,6 +3,7 @@ using FilterLists.Directory.Api.Contracts.Models; using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities; +using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; @@ -12,6 +13,15 @@ public static class GetListDetails { public record Query(int Id) : IRequest; + internal class Validator : AbstractValidator + { + public Validator() + { + RuleFor(q => q.Id) + .GreaterThanOrEqualTo(0); + } + } + internal class Handler : IRequestHandler { private readonly IQueryContext _context; diff --git a/services/Directory/FilterLists.Directory.Application/ValidatorPipelineBahavior.cs b/services/Directory/FilterLists.Directory.Application/ValidatorPipelineBahavior.cs new file mode 100644 index 000000000..3c3e0c4e9 --- /dev/null +++ b/services/Directory/FilterLists.Directory.Application/ValidatorPipelineBahavior.cs @@ -0,0 +1,27 @@ +using FluentValidation; +using MediatR; + +namespace FilterLists.Directory.Application; + +internal class ValidatorPipelineBehavior : IPipelineBehavior + where TRequest : notnull +{ + private readonly IEnumerable> _validators; + + public ValidatorPipelineBehavior(IEnumerable> validators) + { + _validators = validators; + } + + public Task Handle(TRequest request, + CancellationToken cancellationToken, + RequestHandlerDelegate next) + { + foreach (var validator in _validators) + { + validator.ValidateAndThrow(request); + } + + return next(); + } +}