feat(dir): validate list details query id

This commit is contained in:
Collin M. Barrett 2021-11-12 18:21:30 -06:00
parent 5775cc3274
commit 0ad50629d5
8 changed files with 77 additions and 1 deletions

View file

@ -28,6 +28,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View file

@ -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<NotImplementedException>(StatusCodes.Status501NotImplemented);
options.MapToStatusCode<HttpRequestException>(StatusCodes.Status503ServiceUnavailable);
options.MapToStatusCode<Exception>(StatusCodes.Status500InternalServerError);
}
private static void MapFluentValidationException(this ProblemDetailsOptions options)
{
options.Map<ValidationException>((ctx, ex) =>
{
var factory = ctx.RequestServices.GetRequiredService<ProblemDetailsFactory>();
var errors = ex.Errors
.GroupBy(f => f.PropertyName)
.ToDictionary(
g => g.Key,
g => g.Select(f => f.ErrorMessage).ToArray());
return factory.CreateValidationProblemDetails(ctx, errors);
});
}
}

View file

@ -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<RouteOptions>(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();

View file

@ -3,7 +3,7 @@
namespace FilterLists.Directory.Api;
internal static class SwaggerExtensions
internal static class SwaggerConfigurationExtensions
{
public static void AddSwaggerGen(this IServiceCollection services)
{

View file

@ -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);
}

View file

@ -18,6 +18,7 @@
<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="10.3.4" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
</ItemGroup>

View file

@ -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<ListDetailsVm?>;
internal class Validator : AbstractValidator<Query>
{
public Validator()
{
RuleFor(q => q.Id)
.GreaterThanOrEqualTo(0);
}
}
internal class Handler : IRequestHandler<Query, ListDetailsVm?>
{
private readonly IQueryContext _context;

View file

@ -0,0 +1,27 @@
using FluentValidation;
using MediatR;
namespace FilterLists.Directory.Application;
internal class ValidatorPipelineBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidatorPipelineBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request,
CancellationToken cancellationToken,
RequestHandlerDelegate<TResponse> next)
{
foreach (var validator in _validators)
{
validator.ValidateAndThrow(request);
}
return next();
}
}