diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiModelMetadataProvider.cs b/src/JsonApiDotNetCore/Configuration/JsonApiModelMetadataProvider.cs index 9ffe2f7641..0f9cbf1fd2 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiModelMetadataProvider.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiModelMetadataProvider.cs @@ -32,11 +32,7 @@ public JsonApiModelMetadataProvider(ICompositeMetadataDetailsProvider detailsPro protected override ModelMetadata CreateModelMetadata(DefaultMetadataDetails entry) { var metadata = (DefaultModelMetadata)base.CreateModelMetadata(entry); - - if (metadata.ValidationMetadata.IsRequired == true) - { - metadata.ValidationMetadata.PropertyValidationFilter = _jsonApiValidationFilter; - } + metadata.ValidationMetadata.PropertyValidationFilter = _jsonApiValidationFilter; return metadata; } diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs b/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs index a27acf8ebd..3fd29a9c65 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs @@ -1,6 +1,7 @@ using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.DependencyInjection; @@ -23,15 +24,13 @@ public JsonApiValidationFilter(IHttpContextAccessor httpContextAccessor) /// public bool ShouldValidateEntry(ValidationEntry entry, ValidationEntry parentEntry) { - IServiceProvider serviceProvider = GetScopedServiceProvider(); - - var request = serviceProvider.GetRequiredService(); - - if (IsId(entry.Key)) + if (entry.Metadata.MetadataKind == ModelMetadataKind.Type || IsId(entry.Key)) { return true; } + IServiceProvider serviceProvider = GetScopedServiceProvider(); + var request = serviceProvider.GetRequiredService(); bool isTopResourceInPrimaryRequest = string.IsNullOrEmpty(parentEntry.Key) && IsAtPrimaryEndpoint(request); if (!isTopResourceInPrimaryRequest) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateFakers.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateFakers.cs index 75183eaf9f..7d7d7d1acf 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateFakers.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateFakers.cs @@ -17,14 +17,14 @@ internal sealed class ModelStateFakers : FakerContainer new Faker() .UseSeed(GetFakerSeed()) .RuleFor(systemFile => systemFile.FileName, faker => faker.System.FileName()) + .RuleFor(systemFile => systemFile.Attributes, faker => faker.Random.Enum(FileAttributes.Normal, FileAttributes.Hidden, FileAttributes.ReadOnly)) .RuleFor(systemFile => systemFile.SizeInBytes, faker => faker.Random.Long(0, 1_000_000))); private readonly Lazy> _lazySystemDirectoryFaker = new(() => new Faker() .UseSeed(GetFakerSeed()) - .RuleFor(systemDirectory => systemDirectory.Name, faker => faker.Address.City()) - .RuleFor(systemDirectory => systemDirectory.IsCaseSensitive, faker => faker.Random.Bool()) - .RuleFor(systemDirectory => systemDirectory.SizeInBytes, faker => faker.Random.Long(0, 1_000_000))); + .RuleFor(systemDirectory => systemDirectory.Name, faker => Path.GetFileNameWithoutExtension(faker.System.FileName())) + .RuleFor(systemDirectory => systemDirectory.IsCaseSensitive, faker => faker.Random.Bool())); public Faker SystemVolume => _lazySystemVolumeFaker.Value; public Faker SystemFile => _lazySystemFileFaker.Value; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateValidationTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateValidationTests.cs index 7df5b15a74..b114962bd3 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateValidationTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateValidationTests.cs @@ -166,8 +166,6 @@ public async Task Cannot_create_resource_with_multiple_violations() type = "systemDirectories", attributes = new { - isCaseSensitive = false, - sizeInBytes = -1 } } }; @@ -192,9 +190,9 @@ public async Task Cannot_create_resource_with_multiple_violations() ErrorObject error2 = responseDocument.Errors[1]; error2.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error2.Title.Should().Be("Input validation failed."); - error2.Detail.Should().Be("The field SizeInBytes must be between 0 and 9223372036854775807."); + error2.Detail.Should().Be("The IsCaseSensitive field is required."); error2.Source.ShouldNotBeNull(); - error2.Source.Pointer.Should().Be("/data/attributes/sizeInBytes"); + error2.Source.Pointer.Should().Be("/data/attributes/isCaseSensitive"); } [Fact] @@ -205,15 +203,14 @@ public async Task Does_not_exceed_MaxModelValidationErrors() { data = new { - type = "systemDirectories", + type = "systemFiles", attributes = new { - sizeInBytes = -1 } } }; - const string route = "/systemDirectories"; + const string route = "/systemFiles"; // Act (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); @@ -232,16 +229,16 @@ public async Task Does_not_exceed_MaxModelValidationErrors() ErrorObject error2 = responseDocument.Errors[1]; error2.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error2.Title.Should().Be("Input validation failed."); - error2.Detail.Should().Be("The Name field is required."); + error2.Detail.Should().Be("The FileName field is required."); error2.Source.ShouldNotBeNull(); - error2.Source.Pointer.Should().Be("/data/attributes/directoryName"); + error2.Source.Pointer.Should().Be("/data/attributes/fileName"); ErrorObject error3 = responseDocument.Errors[2]; error3.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); error3.Title.Should().Be("Input validation failed."); - error3.Detail.Should().Be("The IsCaseSensitive field is required."); + error3.Detail.Should().Be("The Attributes field is required."); error3.Source.ShouldNotBeNull(); - error3.Source.Pointer.Should().Be("/data/attributes/isCaseSensitive"); + error3.Source.Pointer.Should().Be("/data/attributes/attributes"); } [Fact] @@ -360,13 +357,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_with_omitted_required_attribute_value() { // Arrange - SystemDirectory existingDirectory = _fakers.SystemDirectory.Generate(); + SystemFile existingFile = _fakers.SystemFile.Generate(); - long newSizeInBytes = _fakers.SystemDirectory.Generate().SizeInBytes; + long? newSizeInBytes = _fakers.SystemFile.Generate().SizeInBytes; await _testContext.RunOnDatabaseAsync(async dbContext => { - dbContext.Directories.Add(existingDirectory); + dbContext.Files.Add(existingFile); await dbContext.SaveChangesAsync(); }); @@ -374,8 +371,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => { data = new { - type = "systemDirectories", - id = existingDirectory.StringId, + type = "systemFiles", + id = existingFile.StringId, attributes = new { sizeInBytes = newSizeInBytes @@ -383,7 +380,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } }; - string route = $"/systemDirectories/{existingDirectory.StringId}"; + string route = $"/systemFiles/{existingFile.StringId}"; // Act (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/SystemDirectory.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/SystemDirectory.cs index 1712ad103e..4265dc688e 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/SystemDirectory.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/SystemDirectory.cs @@ -20,10 +20,6 @@ public sealed class SystemDirectory : Identifiable [Required] public bool? IsCaseSensitive { get; set; } - [Attr] - [Range(typeof(long), "0", "9223372036854775807")] - public long SizeInBytes { get; set; } - [HasMany] public ICollection Subdirectories { get; set; } = new List(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/SystemFile.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/SystemFile.cs index 56bd50d1e7..a5515922e4 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/SystemFile.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/SystemFile.cs @@ -15,6 +15,9 @@ public sealed class SystemFile : Identifiable [Attr] [Required] - [Range(typeof(long), "0", "9223372036854775807")] - public long? SizeInBytes { get; set; } + public FileAttributes? Attributes { get; set; } + + [Attr] + [Range(typeof(long), "1", "9223372036854775807")] + public long SizeInBytes { get; set; } }