Skip to content

Commit

Permalink
Add ValidateAsync method to use FV async validation (#344)
Browse files Browse the repository at this point in the history
  • Loading branch information
jchannon authored May 18, 2024
1 parent 75bd50a commit 3a0729d
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 10 deletions.
4 changes: 2 additions & 2 deletions samples/CarterSample/Features/CastMembers/CastMemberModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ public class CastMemberModule : ICarterModule
{
public void AddRoutes(IEndpointRouteBuilder app)
{
app.MapPost("/castmembers", (HttpRequest req, CastMember castMember) =>
app.MapPost("/castmembers", async (HttpRequest req, CastMember castMember) =>
{
var result = req.Validate<CastMember>(castMember);
var result = await req.ValidateAsync<CastMember>(castMember);
if (!result.IsValid)
{
Expand Down
7 changes: 6 additions & 1 deletion samples/ValidatorOnlyProject/CastMemberValidator.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
namespace ValidatorOnlyProject
{
using System.Threading.Tasks;
using FluentValidation;

public class CastMemberValidator : AbstractValidator<CastMember>
{
public CastMemberValidator()
{
this.RuleFor(x => x.Name).NotEmpty();
this.RuleFor(x => x.Name).NotEmpty().MustAsync(async (name, cancellation) =>
{
await Task.Delay(50);
return true;
});
}
}
}
18 changes: 18 additions & 0 deletions src/Carter/ModelBinding/ValidationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Carter.ModelBinding;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

public static class ValidationExtensions
{
Expand All @@ -26,6 +27,23 @@ public static ValidationResult Validate<T>(this HttpRequest request, T model)
? throw new InvalidOperationException($"Cannot find validator for model of type '{typeof(T).Name}'")
: validator.Validate(new ValidationContext<T>(model));
}

/// <summary>
/// Performs validation on the specified <paramref name="model"/> instance
/// </summary>
/// <typeparam name="T">The type of the <paramref name="model"/> that is being validated</typeparam>
/// <param name="request">Current <see cref="HttpRequest"/></param>
/// <param name="model">The model instance that is being validated</param>
/// <returns><see cref="Task{ValidationResult}"/></returns>
public static async Task<ValidationResult> ValidateAsync<T>(this HttpRequest request, T model)
{
var validatorLocator = request.HttpContext.RequestServices.GetRequiredService<IValidatorLocator>();
var validator = validatorLocator.GetValidator<T>();

return validator == null
? throw new InvalidOperationException($"Cannot find validator for model of type '{typeof(T).Name}'")
: await validator.ValidateAsync(new ValidationContext<T>(model));
}

/// <summary>
/// Retrieve formatted validation errors
Expand Down
22 changes: 15 additions & 7 deletions test/Carter.Samples.Tests/FunctionalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Carter.Samples.Tests
using CarterSample.Features.FunctionalProgramming.UpdateDirector;
using Microsoft.AspNetCore.Mvc.Testing;
using Newtonsoft.Json;
using ValidatorOnlyProject;
using Xunit;

public class FunctionalTests
Expand All @@ -23,13 +24,7 @@ public class FunctionalTests
public FunctionalTests()
{
var server = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
//services.AddSingleton<IHelloService, MockHelloService>();
});
});
.WithWebHostBuilder(builder => { });

this.client = server.CreateClient();
}
Expand Down Expand Up @@ -192,5 +187,18 @@ public async Task Should_return_403_if_user_not_allowed_on_delete()
//Then
Assert.Equal(403, (int)res.StatusCode);
}

[Fact]
public async Task Should_return_422_for_blank_castmember_with_async_validation()
{

//When
var res = await this.client.PostAsync("/castmembers",
new StringContent(JsonConvert.SerializeObject(new CastMember{ Name = "" }), Encoding.UTF8,
"application/json"));

//Then
Assert.Equal(422, (int)res.StatusCode);
}
}
}

1 comment on commit 3a0729d

@jeffward01
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this, I would have done a PR request myself, but I wanted to see if there was a reason for it or not being implemented first. You are awesome for being so fast on this. Carter is really great. Thank you

Please sign in to comment.