From 234c46aab8091bf65e5e108fcd6242544eb3c30c Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 18 Mar 2026 09:56:08 +0800 Subject: [PATCH 1/4] Fix FluentValidation not working with ConventionalControllers When Application Services are registered via ConventionalControllers.Create(), their types are added to DynamicProxyIgnoreTypes, which disables the ValidationInterceptor. The AbpValidationActionFilter only checked ModelState (DataAnnotations), so FluentValidation rules were never executed. Add IValidationEnabled check in AbpValidationActionFilter to call IMethodInvocationValidator for conventional controllers, enabling FluentValidation support without duplicating validation for regular controllers. Resolve #23457 --- .../api-development/auto-controllers.md | 4 +- .../Validation/AbpValidationActionFilter.cs | 22 +++++++ .../Volo.Abp.AspNetCore.Mvc.Tests.csproj | 1 + .../Mvc/AbpAspNetCoreMvcTestModule.cs | 4 +- .../FluentValidationTestAppService_Tests.cs | 64 +++++++++++++++++++ .../FluentValidationTestInputValidator.cs | 12 ++++ .../Validation/ValidationTestController.cs | 11 ++++ .../ValidationTestController_Tests.cs | 18 ++++++ .../FluentValidationTestAppService.cs | 17 +++++ 9 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestAppService_Tests.cs create mode 100644 framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestInputValidator.cs create mode 100644 framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/FluentValidationTestAppService.cs diff --git a/docs/en/framework/api-development/auto-controllers.md b/docs/en/framework/api-development/auto-controllers.md index b40718b079..2c6f0ee39a 100644 --- a/docs/en/framework/api-development/auto-controllers.md +++ b/docs/en/framework/api-development/auto-controllers.md @@ -70,7 +70,7 @@ Route is calculated based on some conventions: * Continues with a **route path**. Default value is '**/app**' and can be configured as like below: ````csharp -Configure(options => +PreConfigure(options => { options.ConventionalControllers .Create(typeof(BookStoreApplicationModule).Assembly, opts => @@ -149,7 +149,7 @@ public class PersonAppService : ApplicationService You can further filter classes to become an API controller by providing the `TypePredicate` option: ````csharp -services.Configure(options => +PreConfigure(options => { options.ConventionalControllers .Create(typeof(BookStoreApplicationModule).Assembly, opts => diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs index f866c85585..ba2f0828cc 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs @@ -60,6 +60,28 @@ public class AbpValidationActionFilter : IAsyncActionFilter, IAbpFilter, ITransi } context.GetRequiredService().Validate(context.ModelState); + + if (context.Controller is IValidationEnabled) + { + await ValidateActionArgumentsAsync(context); + } + await next(); } + + protected virtual async Task ValidateActionArgumentsAsync(ActionExecutingContext context) + { + var methodInfo = context.ActionDescriptor.GetMethodInfo(); + var parameterValues = methodInfo.GetParameters() + .Select(p => context.ActionArguments.TryGetValue(p.Name!, out var value) ? value : null) + .ToArray(); + + await context.GetRequiredService().ValidateAsync( + new MethodInvocationValidationContext( + context.Controller, + methodInfo, + parameterValues + ) + ); + } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj index 347da6f0bd..8a929dd38a 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo.Abp.AspNetCore.Mvc.Tests.csproj @@ -26,6 +26,7 @@ + diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs index 1c705d8df3..9154608680 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs @@ -23,6 +23,7 @@ using Volo.Abp.TestApp; using Volo.Abp.TestApp.Application; using Volo.Abp.Threading; using Volo.Abp.Validation.Localization; +using Volo.Abp.FluentValidation; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.AspNetCore.Mvc; @@ -31,7 +32,8 @@ namespace Volo.Abp.AspNetCore.Mvc; typeof(AbpAspNetCoreTestBaseModule), typeof(AbpMemoryDbTestModule), typeof(AbpAspNetCoreMvcModule), - typeof(AbpAutofacModule) + typeof(AbpAutofacModule), + typeof(AbpFluentValidationModule) )] public class AbpAspNetCoreMvcTestModule : AbpModule { diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestAppService_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestAppService_Tests.cs new file mode 100644 index 0000000000..b1d1f56ed5 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestAppService_Tests.cs @@ -0,0 +1,64 @@ +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Http; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Validation; + +public class FluentValidationTestAppService_Tests : AspNetCoreMvcTestBase +{ + [Fact] + public async Task Should_Validate_With_FluentValidation_On_ConventionalController() + { + // Name is "A" which is less than 3 characters, should fail FluentValidation + var response = await PostAsync("{\"name\": \"A\"}"); + response.StatusCode.ShouldBe(HttpStatusCode.BadRequest); + } + + [Fact] + public async Task Should_Validate_With_FluentValidation_On_ConventionalController_EmptyName() + { + // Empty name should fail FluentValidation (NotEmpty rule) + var response = await PostAsync("{\"name\": \"\"}"); + response.StatusCode.ShouldBe(HttpStatusCode.BadRequest); + } + + [Fact] + public async Task Should_Validate_With_FluentValidation_On_ConventionalController_MaxLength() + { + // Name exceeds 10 characters, should fail FluentValidation (MaximumLength rule) + var response = await PostAsync("{\"name\": \"12345678901\"}"); + response.StatusCode.ShouldBe(HttpStatusCode.BadRequest); + } + + [Fact] + public async Task Should_Return_Validation_Errors_With_Details() + { + var response = await PostAsync("{\"name\": \"A\"}"); + response.StatusCode.ShouldBe(HttpStatusCode.BadRequest); + + var content = await response.Content.ReadAsStringAsync(); + content.ShouldContain("Name"); + content.ShouldContain("validationErrors"); + } + + [Fact] + public async Task Should_Pass_Validation_With_Valid_Input() + { + var response = await PostAsync("{\"name\": \"Hello\"}"); + response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + + private async Task PostAsync(string jsonContent) + { + var request = new HttpRequestMessage(HttpMethod.Post, "/api/app/fluent-validation-test") + { + Content = new StringContent(jsonContent, Encoding.UTF8, "application/json") + }; + return await Client.SendAsync(request); + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestInputValidator.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestInputValidator.cs new file mode 100644 index 0000000000..d99ee32179 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestInputValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; +using Volo.Abp.TestApp.Application; + +namespace Volo.Abp.AspNetCore.Mvc.Validation; + +public class FluentValidationTestInputValidator : AbstractValidator +{ + public FluentValidationTestInputValidator() + { + RuleFor(x => x.Name).NotEmpty().MinimumLength(3).MaximumLength(10); + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs index 3a4fe19fc8..72fb5ccb7d 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Shouldly; using Volo.Abp.DependencyInjection; +using Volo.Abp.TestApp.Application; using Volo.Abp.Validation; namespace Volo.Abp.AspNetCore.Mvc.Validation; @@ -120,6 +121,16 @@ public class ValidationTestController : AbpController } } + [HttpPost] + [Route("fluent-validation-action")] + public Task FluentValidationAction([FromBody] FluentValidationTestInput input) + { + // This action uses a DTO that has a FluentValidator registered, + // but since this is a regular AbpController (not IValidationEnabled), + // FluentValidation should NOT be triggered by AbpValidationActionFilter. + return Task.FromResult(input.Name); + } + public class CustomValidateModel : IValidatableObject { public string Value1 { get; set; } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs index 409a804fa2..a0d0040cc9 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs @@ -1,4 +1,6 @@ using System.Net; +using System.Net.Http; +using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Shouldly; @@ -112,6 +114,22 @@ public class ValidationTestController_Tests : AspNetCoreMvcTestBase var result = await GetResponseAsStringAsync("/api/validation-test/object-result-action2"); result.ShouldBe("ModelState.IsValid: false"); } + + [Fact] + public async Task Should_Not_Trigger_FluentValidation_On_Regular_Controller() + { + // FluentValidationTestInput has a FluentValidator with MinimumLength(3) rule, + // but this is a regular AbpController (not IValidationEnabled), + // so FluentValidation should NOT be triggered. + var request = new HttpRequestMessage(HttpMethod.Post, "/api/validation-test/fluent-validation-action") + { + Content = new StringContent("{\"name\": \"A\"}", Encoding.UTF8, "application/json") + }; + var response = await Client.SendAsync(request); + + // Should return OK because FluentValidation is not triggered for regular controllers + response.StatusCode.ShouldBe(HttpStatusCode.OK); + } } public class DisableAutoModelValidationTestController_Tests : AspNetCoreMvcTestBase diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/FluentValidationTestAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/FluentValidationTestAppService.cs new file mode 100644 index 0000000000..ea4fcf7480 --- /dev/null +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/FluentValidationTestAppService.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace Volo.Abp.TestApp.Application; + +public class FluentValidationTestAppService : ApplicationService +{ + public virtual Task CreateAsync(FluentValidationTestInput input) + { + return Task.FromResult(input.Name); + } +} + +public class FluentValidationTestInput +{ + public string Name { get; set; } +} From d3d6915ddff6eef9eef2c14c19bc60a46a7da242 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 18 Mar 2026 10:25:07 +0800 Subject: [PATCH 2/4] Address Copilot review: extract GetEffectiveMethodInfo helper and fix method resolution in ValidateActionArgumentsAsync Extract override method resolution logic into a reusable GetEffectiveMethodInfo helper to avoid duplication. Use the resolved override method in ValidateActionArgumentsAsync so that IMethodInvocationValidator validates against the concrete method on the controller type, not the base method from ActionDescriptor. Remove unused imports in FluentValidationTestAppService_Tests. --- .../Validation/AbpValidationActionFilter.cs | 40 +++++++++++-------- .../FluentValidationTestAppService_Tests.cs | 2 - 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs index ba2f0828cc..bcbeb2fa48 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; @@ -39,23 +40,13 @@ public class AbpValidationActionFilter : IAsyncActionFilter, IAbpFilter, ITransi return; } - if (context.ActionDescriptor.GetMethodInfo().DeclaringType != context.Controller.GetType()) + var effectiveMethod = GetEffectiveMethodInfo(context); + if (effectiveMethod != null) { - var baseMethod = context.ActionDescriptor.GetMethodInfo(); - - var overrideMethod = context.Controller.GetType().GetMethods().FirstOrDefault(x => - x.DeclaringType == context.Controller.GetType() && - x.Name == baseMethod.Name && - x.ReturnType == baseMethod.ReturnType && - x.GetParameters().Select(p => p.ToString()).SequenceEqual(baseMethod.GetParameters().Select(p => p.ToString()))); - - if (overrideMethod != null) + if (ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(effectiveMethod) != null) { - if (ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(overrideMethod) != null) - { - await next(); - return; - } + await next(); + return; } } @@ -69,9 +60,26 @@ public class AbpValidationActionFilter : IAsyncActionFilter, IAbpFilter, ITransi await next(); } + protected virtual MethodInfo? GetEffectiveMethodInfo(ActionExecutingContext context) + { + var baseMethod = context.ActionDescriptor.GetMethodInfo(); + if (baseMethod.DeclaringType == context.Controller.GetType()) + { + return null; + } + + return context.Controller.GetType().GetMethods().FirstOrDefault(x => + x.DeclaringType == context.Controller.GetType() && + x.Name == baseMethod.Name && + x.ReturnType == baseMethod.ReturnType && + x.GetParameters().Select(p => p.ToString()).SequenceEqual(baseMethod.GetParameters().Select(p => p.ToString()))); + } + protected virtual async Task ValidateActionArgumentsAsync(ActionExecutingContext context) { - var methodInfo = context.ActionDescriptor.GetMethodInfo(); + var baseMethod = context.ActionDescriptor.GetMethodInfo(); + var methodInfo = GetEffectiveMethodInfo(context) ?? baseMethod; + var parameterValues = methodInfo.GetParameters() .Select(p => context.ActionArguments.TryGetValue(p.Name!, out var value) ? value : null) .ToArray(); diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestAppService_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestAppService_Tests.cs index b1d1f56ed5..563c68a039 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestAppService_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestAppService_Tests.cs @@ -1,10 +1,8 @@ -using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Shouldly; -using Volo.Abp.Http; using Xunit; namespace Volo.Abp.AspNetCore.Mvc.Validation; From 86b42e26134e86ce0087242e2314b5838c505c3a Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 18 Mar 2026 10:35:47 +0800 Subject: [PATCH 3/4] Avoid double reflection call by passing effectiveMethod into ValidateActionArgumentsAsync --- .../AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs index bcbeb2fa48..ad1f7dc1f7 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs @@ -54,7 +54,7 @@ public class AbpValidationActionFilter : IAsyncActionFilter, IAbpFilter, ITransi if (context.Controller is IValidationEnabled) { - await ValidateActionArgumentsAsync(context); + await ValidateActionArgumentsAsync(context, effectiveMethod); } await next(); @@ -75,10 +75,9 @@ public class AbpValidationActionFilter : IAsyncActionFilter, IAbpFilter, ITransi x.GetParameters().Select(p => p.ToString()).SequenceEqual(baseMethod.GetParameters().Select(p => p.ToString()))); } - protected virtual async Task ValidateActionArgumentsAsync(ActionExecutingContext context) + protected virtual async Task ValidateActionArgumentsAsync(ActionExecutingContext context, MethodInfo? effectiveMethod = null) { - var baseMethod = context.ActionDescriptor.GetMethodInfo(); - var methodInfo = GetEffectiveMethodInfo(context) ?? baseMethod; + var methodInfo = effectiveMethod ?? context.ActionDescriptor.GetMethodInfo(); var parameterValues = methodInfo.GetParameters() .Select(p => context.ActionArguments.TryGetValue(p.Name!, out var value) ? value : null) From b21747c63bd7b57a5f70c5e651b4d00ee17da94d Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 18 Mar 2026 10:46:34 +0800 Subject: [PATCH 4/4] Dispose HttpRequestMessage in tests --- .../Mvc/Validation/FluentValidationTestAppService_Tests.cs | 2 +- .../AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestAppService_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestAppService_Tests.cs index 563c68a039..24360b1254 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestAppService_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/FluentValidationTestAppService_Tests.cs @@ -53,7 +53,7 @@ public class FluentValidationTestAppService_Tests : AspNetCoreMvcTestBase private async Task PostAsync(string jsonContent) { - var request = new HttpRequestMessage(HttpMethod.Post, "/api/app/fluent-validation-test") + using var request = new HttpRequestMessage(HttpMethod.Post, "/api/app/fluent-validation-test") { Content = new StringContent(jsonContent, Encoding.UTF8, "application/json") }; diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs index a0d0040cc9..651a2650dc 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs @@ -121,7 +121,7 @@ public class ValidationTestController_Tests : AspNetCoreMvcTestBase // FluentValidationTestInput has a FluentValidator with MinimumLength(3) rule, // but this is a regular AbpController (not IValidationEnabled), // so FluentValidation should NOT be triggered. - var request = new HttpRequestMessage(HttpMethod.Post, "/api/validation-test/fluent-validation-action") + using var request = new HttpRequestMessage(HttpMethod.Post, "/api/validation-test/fluent-validation-action") { Content = new StringContent("{\"name\": \"A\"}", Encoding.UTF8, "application/json") };