diff --git a/src/Sitko.FluentValidation/Graph/FluentGraphValidator.cs b/src/Sitko.FluentValidation/Graph/FluentGraphValidator.cs index 0328b17..d16d89f 100644 --- a/src/Sitko.FluentValidation/Graph/FluentGraphValidator.cs +++ b/src/Sitko.FluentValidation/Graph/FluentGraphValidator.cs @@ -14,6 +14,7 @@ public class FluentGraphValidator : IFluentGraphValidator private readonly ILogger logger; private readonly IOptions options; private readonly IServiceScope serviceScope; + private static readonly ConcurrentDictionary IgnoredPropertiesCache = new(); public FluentGraphValidator(IServiceProvider serviceProvider, ILogger logger, IOptions options) @@ -166,6 +167,13 @@ or char or uint or ulong or short or sbyte || foreach (var property in modelGraphValidationContext.Model.GetType().GetProperties()) { + var isSkippedProperty = IgnoredPropertiesCache.GetOrAdd($"{modelGraphValidationContext.Model.GetType()}_{property.Name}", _ => property.GetCustomAttributes(typeof(SkipGraphValidationAttribute), true) + .Cast() + .FirstOrDefault() != null); + if (isSkippedProperty) + { + continue; + } var propertyModel = property.GetValue(modelGraphValidationContext.Model); diff --git a/src/Sitko.FluentValidation/Sitko.FluentValidation.csproj b/src/Sitko.FluentValidation/Sitko.FluentValidation.csproj index 24e138f..b2fe813 100644 --- a/src/Sitko.FluentValidation/Sitko.FluentValidation.csproj +++ b/src/Sitko.FluentValidation/Sitko.FluentValidation.csproj @@ -1,7 +1,7 @@ - netstandard2.0;netstandard2.1;net6.0;net7.0 + netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0 enable enable latest @@ -22,11 +22,21 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/src/Sitko.FluentValidation/SkipGraphValidationAttribute.cs b/src/Sitko.FluentValidation/SkipGraphValidationAttribute.cs new file mode 100644 index 0000000..4d9cefb --- /dev/null +++ b/src/Sitko.FluentValidation/SkipGraphValidationAttribute.cs @@ -0,0 +1,4 @@ +namespace Sitko.FluentValidation; + +[AttributeUsage(AttributeTargets.Property)] +public class SkipGraphValidationAttribute : Attribute; diff --git a/tests/Sitko.FluentValidation.Tests/Data/BarModel.cs b/tests/Sitko.FluentValidation.Tests/Data/BarModel.cs index be3ffb8..915c93f 100644 --- a/tests/Sitko.FluentValidation.Tests/Data/BarModel.cs +++ b/tests/Sitko.FluentValidation.Tests/Data/BarModel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Sitko.FluentValidation.Tests.Data; @@ -7,4 +8,7 @@ public class BarModel public Guid TestGuid { get; set; } = Guid.Empty; public BarType Type { get; set; } = BarType.Bar; public int Val { get; set; } + + [SkipGraphValidation] + public List FooModels { get; set; } = new(); } diff --git a/tests/Sitko.FluentValidation.Tests/FluentGraphValidatorTests.cs b/tests/Sitko.FluentValidation.Tests/FluentGraphValidatorTests.cs index c97cfa0..9b02406 100644 --- a/tests/Sitko.FluentValidation.Tests/FluentGraphValidatorTests.cs +++ b/tests/Sitko.FluentValidation.Tests/FluentGraphValidatorTests.cs @@ -17,6 +17,19 @@ public FluentGraphValidatorTests(ITestOutputHelper testOutputHelper) : base(test { } + [Fact] + public async Task ValidateSkipAttribute() + { + var scope = await GetScopeAsync(); + var validator = scope.GetService(); + + var bar = new BarModel { Val = 0, FooModels = new List { new() { Id = Guid.NewGuid() } } }; + var foo = new FooModel { Id = Guid.NewGuid(), BarModels = new List { bar } }; + + var result = await validator.TryValidateModelAsync(foo); + result.Results.Where(r => r.Model.GetType() == typeof(FooModel)).Should().ContainSingle(); + } + [Fact] public async Task ValidateParent() { diff --git a/tests/Sitko.FluentValidation.Tests/Sitko.FluentValidation.Tests.csproj b/tests/Sitko.FluentValidation.Tests/Sitko.FluentValidation.Tests.csproj index a1f362d..6525827 100644 --- a/tests/Sitko.FluentValidation.Tests/Sitko.FluentValidation.Tests.csproj +++ b/tests/Sitko.FluentValidation.Tests/Sitko.FluentValidation.Tests.csproj @@ -1,13 +1,13 @@ - net6.0;net7.0 + net8.0 enable false - + diff --git a/tests/Sitko.FluentValidation.Tests/ValidationTestScope.cs b/tests/Sitko.FluentValidation.Tests/ValidationTestScope.cs index 43a339a..8071a4c 100644 --- a/tests/Sitko.FluentValidation.Tests/ValidationTestScope.cs +++ b/tests/Sitko.FluentValidation.Tests/ValidationTestScope.cs @@ -1,4 +1,4 @@ -using Sitko.Core.App; +using Microsoft.Extensions.Hosting; using Sitko.FluentValidation.Graph; namespace Sitko.FluentValidation.Tests; @@ -11,23 +11,21 @@ namespace Sitko.FluentValidation.Tests; [UsedImplicitly] public class ValidationTestScope : BaseTestScope { - protected override IServiceCollection ConfigureServices(IApplicationContext applicationContext, - IServiceCollection services, string name) + protected override IHostApplicationBuilder ConfigureServices(IHostApplicationBuilder builder, string name) { - base.ConfigureServices(applicationContext, services, name); - services.AddFluentValidationExtensions(); - services.AddValidatorsFromAssemblyContaining(); - return services; + base.ConfigureServices(builder, name); + builder.Services.AddFluentValidationExtensions(); + builder.Services.AddValidatorsFromAssemblyContaining(); + return builder; } } public class ValidationWithExcludedPrefixTestScope : ValidationTestScope { - protected override IServiceCollection ConfigureServices(IApplicationContext applicationContext, - IServiceCollection services, string name) + protected override IHostApplicationBuilder ConfigureServices(IHostApplicationBuilder builder, string name) { - base.ConfigureServices(applicationContext, services, name); - services.Configure(options => options.NamespacePrefixes.Add("Sitko")); - return services; + base.ConfigureServices(builder, name); + builder.Services.Configure(options => options.NamespacePrefixes.Add("Sitko")); + return builder; } }