Skip to content

Commit

Permalink
Merge pull request #40 from SharpGrip/35-improve-mvc-endpoint-detection
Browse files Browse the repository at this point in the history
add check on controller attribute
  • Loading branch information
mvdgun authored Dec 4, 2024
2 parents f4dc05a + f97434d commit af92c92
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System.Linq;
using System;
using System.Linq;
using System.Threading.Tasks;
using FluentValidation;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
Expand All @@ -22,17 +24,15 @@ public class FluentValidationAutoValidationActionFilter : IAsyncActionFilter
private readonly IFluentValidationAutoValidationResultFactory fluentValidationAutoValidationResultFactory;
private readonly AutoValidationMvcConfiguration autoValidationMvcConfiguration;

public FluentValidationAutoValidationActionFilter(
IFluentValidationAutoValidationResultFactory fluentValidationAutoValidationResultFactory,
IOptions<AutoValidationMvcConfiguration> autoValidationMvcConfiguration)
public FluentValidationAutoValidationActionFilter(IFluentValidationAutoValidationResultFactory fluentValidationAutoValidationResultFactory, IOptions<AutoValidationMvcConfiguration> autoValidationMvcConfiguration)
{
this.fluentValidationAutoValidationResultFactory = fluentValidationAutoValidationResultFactory;
this.autoValidationMvcConfiguration = autoValidationMvcConfiguration.Value;
}

public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingContext, ActionExecutionDelegate next)

Check warning on line 33 in FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs

View workflow job for this annotation

GitHub Actions / build

Refactor this method to reduce its Cognitive Complexity from 50 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)

Check warning on line 33 in FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs

View workflow job for this annotation

GitHub Actions / build

Rename parameter 'actionExecutingContext' to 'context' to match the interface declaration. (https://rules.sonarsource.com/csharp/RSPEC-927)

Check warning on line 33 in FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs

View workflow job for this annotation

GitHub Actions / build

Refactor this method to reduce its Cognitive Complexity from 50 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)

Check warning on line 33 in FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs

View workflow job for this annotation

GitHub Actions / build

Rename parameter 'actionExecutingContext' to 'context' to match the interface declaration. (https://rules.sonarsource.com/csharp/RSPEC-927)
{
if (actionExecutingContext.Controller is ControllerBase controllerBase)
if (IsValidController(actionExecutingContext.Controller))
{
var endpoint = actionExecutingContext.HttpContext.GetEndpoint();
var controllerActionDescriptor = (ControllerActionDescriptor) actionExecutingContext.ActionDescriptor;
Expand Down Expand Up @@ -108,7 +108,8 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC

if (!actionExecutingContext.ModelState.IsValid)
{
var validationProblemDetails = controllerBase.ProblemDetailsFactory.CreateValidationProblemDetails(actionExecutingContext.HttpContext, actionExecutingContext.ModelState);
var problemDetailsFactory = serviceProvider.GetRequiredService<ProblemDetailsFactory>();
var validationProblemDetails = problemDetailsFactory.CreateValidationProblemDetails(actionExecutingContext.HttpContext, actionExecutingContext.ModelState);

actionExecutingContext.Result = fluentValidationAutoValidationResultFactory.CreateActionResult(actionExecutingContext, validationProblemDetails);

Expand All @@ -119,6 +120,21 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC
await next();
}

private bool IsValidController(object controller)

Check warning on line 123 in FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs

View workflow job for this annotation

GitHub Actions / build

Make 'IsValidController' a static method. (https://rules.sonarsource.com/csharp/RSPEC-2325)

Check warning on line 123 in FluentValidation.AutoValidation.Mvc/src/Filters/FluentValidationAutoValidationActionFilter.cs

View workflow job for this annotation

GitHub Actions / build

Make 'IsValidController' a static method. (https://rules.sonarsource.com/csharp/RSPEC-2325)
{
var controllerType = controller.GetType();

if (controllerType.HasCustomAttribute<NonControllerAttribute>())
{
return false;
}

return controller is ControllerBase ||
controllerType.HasCustomAttribute<ControllerAttribute>() ||
controllerType.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ||
controllerType.InheritsFromTypeWithNameEndingIn("Controller");
}

private bool HasValidBindingSource(BindingSource? bindingSource)
{
return (autoValidationMvcConfiguration.EnableBodyBindingSourceAutomaticValidation && bindingSource == BindingSource.Body) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,20 @@ public static bool HasCustomAttribute<TAttribute>(this Type type) where TAttribu
{
return type.CustomAttributes.Any(attribute => attribute.AttributeType == typeof(TAttribute));
}

public static bool InheritsFromTypeWithNameEndingIn(this Type type, string name)
{
while (type.BaseType != null)
{
type = type.BaseType;

if (type.Name.EndsWith(name, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}

return false;
}
}
}
16 changes: 8 additions & 8 deletions Tests/FluentValidation.AutoValidation.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,26 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.16">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.17">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit" Version="2.6.4" />
<PackageReference Include="xunit.runner.console" Version="2.6.4">
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.console" Version="2.9.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.runner.msbuild" Version="2.6.4">
<PackageReference Include="xunit.runner.msbuild" Version="2.9.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@ public async Task TestOnActionExecutionAsync()

serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(typeof(TestModel))).Returns(new TestValidator());
serviceProvider.GetService(typeof(IGlobalValidationInterceptor)).Returns(new GlobalValidationInterceptor());
serviceProvider.GetService(typeof(ProblemDetailsFactory)).Returns(problemDetailsFactory);

problemDetailsFactory.CreateValidationProblemDetails(httpContext, modelStateDictionary).Returns(validationProblemDetails);
fluentValidationAutoValidationResultFactory.CreateActionResult(actionExecutingContext, validationProblemDetails).Returns(new BadRequestObjectResult(validationProblemDetails));
httpContext.RequestServices.Returns(serviceProvider);
controller.ProblemDetailsFactory = problemDetailsFactory;
actionExecutingContext.Controller.Returns(controller);
actionExecutingContext.ActionDescriptor = controllerActionDescriptor;
actionExecutingContext.ActionArguments.Returns(actionArguments);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Microsoft.AspNetCore.Mvc;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes;
using SharpGrip.FluentValidation.AutoValidation.Shared.Extensions;
using Xunit;
Expand Down Expand Up @@ -46,11 +47,38 @@ public void Test_HasCustomAttribute()
Assert.False(typeof(TestModelRecord).HasCustomAttribute<AutoValidationAttribute>());
}

[Fact]
public void Test_InheritsFromTypeWithNameEndingIn()
{
Assert.True(typeof(TestInherits1).InheritsFromTypeWithNameEndingIn("Controller"));
Assert.True(typeof(TestInherits1).InheritsFromTypeWithNameEndingIn("controller"));
Assert.True(typeof(TestInherits2).InheritsFromTypeWithNameEndingIn("Controller"));
Assert.True(typeof(TestInherits2).InheritsFromTypeWithNameEndingIn("controller"));
Assert.False(typeof(TestInherits3).InheritsFromTypeWithNameEndingIn("Controller"));
Assert.False(typeof(TestInherits3).InheritsFromTypeWithNameEndingIn("controller"));
Assert.False(typeof(TestInherits4).InheritsFromTypeWithNameEndingIn("Controller"));
Assert.False(typeof(TestInherits4).InheritsFromTypeWithNameEndingIn("controller"));
Assert.False(typeof(TestInherits5).InheritsFromTypeWithNameEndingIn("Controller"));
Assert.False(typeof(TestInherits5).InheritsFromTypeWithNameEndingIn("controller"));
}

[AutoValidation]
private class TestModelClass;

[AutoValidateNever]
private record TestModelRecord;

private enum TestModelEnum;

private class TestInherits1 : Controller;

private class TestInherits2 : CustomControllerBase;

private class TestInherits3 : ControllerBase;

private class TestInherits4 : ActionContext;

private class TestInherits5 : object;

private class CustomControllerBase : Controller;
}

0 comments on commit af92c92

Please sign in to comment.