Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 2324b69

Browse files
authored
Merge pull request #1253 from json-api-dotnet/fix-modelstate-validation-non-required
Fixed: ModelState validation failed when [Range] does not include property default value
2 parents 5ffa4fb + 7d08a55 commit 2324b69

File tree

6 files changed

+27
-36
lines changed

6 files changed

+27
-36
lines changed

‎src/JsonApiDotNetCore/Configuration/JsonApiModelMetadataProvider.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,7 @@ public JsonApiModelMetadataProvider(ICompositeMetadataDetailsProvider detailsPro
3232
protected override ModelMetadata CreateModelMetadata(DefaultMetadataDetails entry)
3333
{
3434
var metadata = (DefaultModelMetadata)base.CreateModelMetadata(entry);
35-
36-
if (metadata.ValidationMetadata.IsRequired == true)
37-
{
38-
metadata.ValidationMetadata.PropertyValidationFilter = _jsonApiValidationFilter;
39-
}
35+
metadata.ValidationMetadata.PropertyValidationFilter = _jsonApiValidationFilter;
4036

4137
return metadata;
4238
}

‎src/JsonApiDotNetCore/Configuration/JsonApiValidationFilter.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using JsonApiDotNetCore.Middleware;
22
using JsonApiDotNetCore.Resources;
33
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
45
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
56
using Microsoft.Extensions.DependencyInjection;
67

@@ -23,15 +24,13 @@ public JsonApiValidationFilter(IHttpContextAccessor httpContextAccessor)
2324
/// <inheritdoc />
2425
public bool ShouldValidateEntry(ValidationEntry entry, ValidationEntry parentEntry)
2526
{
26-
IServiceProvider serviceProvider = GetScopedServiceProvider();
27-
28-
var request = serviceProvider.GetRequiredService<IJsonApiRequest>();
29-
30-
if (IsId(entry.Key))
27+
if (entry.Metadata.MetadataKind == ModelMetadataKind.Type || IsId(entry.Key))
3128
{
3229
return true;
3330
}
3431

32+
IServiceProvider serviceProvider = GetScopedServiceProvider();
33+
var request = serviceProvider.GetRequiredService<IJsonApiRequest>();
3534
bool isTopResourceInPrimaryRequest = string.IsNullOrEmpty(parentEntry.Key) && IsAtPrimaryEndpoint(request);
3635

3736
if (!isTopResourceInPrimaryRequest)

‎test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateFakers.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ internal sealed class ModelStateFakers : FakerContainer
1717
new Faker<SystemFile>()
1818
.UseSeed(GetFakerSeed())
1919
.RuleFor(systemFile => systemFile.FileName, faker => faker.System.FileName())
20+
.RuleFor(systemFile => systemFile.Attributes, faker => faker.Random.Enum(FileAttributes.Normal, FileAttributes.Hidden, FileAttributes.ReadOnly))
2021
.RuleFor(systemFile => systemFile.SizeInBytes, faker => faker.Random.Long(0, 1_000_000)));
2122

2223
private readonly Lazy<Faker<SystemDirectory>> _lazySystemDirectoryFaker = new(() =>
2324
new Faker<SystemDirectory>()
2425
.UseSeed(GetFakerSeed())
25-
.RuleFor(systemDirectory => systemDirectory.Name, faker => faker.Address.City())
26-
.RuleFor(systemDirectory => systemDirectory.IsCaseSensitive, faker => faker.Random.Bool())
27-
.RuleFor(systemDirectory => systemDirectory.SizeInBytes, faker => faker.Random.Long(0, 1_000_000)));
26+
.RuleFor(systemDirectory => systemDirectory.Name, faker => Path.GetFileNameWithoutExtension(faker.System.FileName()))
27+
.RuleFor(systemDirectory => systemDirectory.IsCaseSensitive, faker => faker.Random.Bool()));
2828

2929
public Faker<SystemVolume> SystemVolume => _lazySystemVolumeFaker.Value;
3030
public Faker<SystemFile> SystemFile => _lazySystemFileFaker.Value;

‎test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateValidationTests.cs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,6 @@ public async Task Cannot_create_resource_with_multiple_violations()
166166
type = "systemDirectories",
167167
attributes = new
168168
{
169-
isCaseSensitive = false,
170-
sizeInBytes = -1
171169
}
172170
}
173171
};
@@ -192,9 +190,9 @@ public async Task Cannot_create_resource_with_multiple_violations()
192190
ErrorObject error2 = responseDocument.Errors[1];
193191
error2.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
194192
error2.Title.Should().Be("Input validation failed.");
195-
error2.Detail.Should().Be("The field SizeInBytes must be between 0 and 9223372036854775807.");
193+
error2.Detail.Should().Be("The IsCaseSensitive field is required.");
196194
error2.Source.ShouldNotBeNull();
197-
error2.Source.Pointer.Should().Be("/data/attributes/sizeInBytes");
195+
error2.Source.Pointer.Should().Be("/data/attributes/isCaseSensitive");
198196
}
199197

200198
[Fact]
@@ -205,15 +203,14 @@ public async Task Does_not_exceed_MaxModelValidationErrors()
205203
{
206204
data = new
207205
{
208-
type = "systemDirectories",
206+
type = "systemFiles",
209207
attributes = new
210208
{
211-
sizeInBytes = -1
212209
}
213210
}
214211
};
215212

216-
const string route = "/systemDirectories";
213+
const string route = "/systemFiles";
217214

218215
// Act
219216
(HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync<Document>(route, requestBody);
@@ -232,16 +229,16 @@ public async Task Does_not_exceed_MaxModelValidationErrors()
232229
ErrorObject error2 = responseDocument.Errors[1];
233230
error2.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
234231
error2.Title.Should().Be("Input validation failed.");
235-
error2.Detail.Should().Be("The Name field is required.");
232+
error2.Detail.Should().Be("The FileName field is required.");
236233
error2.Source.ShouldNotBeNull();
237-
error2.Source.Pointer.Should().Be("/data/attributes/directoryName");
234+
error2.Source.Pointer.Should().Be("/data/attributes/fileName");
238235

239236
ErrorObject error3 = responseDocument.Errors[2];
240237
error3.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
241238
error3.Title.Should().Be("Input validation failed.");
242-
error3.Detail.Should().Be("The IsCaseSensitive field is required.");
239+
error3.Detail.Should().Be("The Attributes field is required.");
243240
error3.Source.ShouldNotBeNull();
244-
error3.Source.Pointer.Should().Be("/data/attributes/isCaseSensitive");
241+
error3.Source.Pointer.Should().Be("/data/attributes/attributes");
245242
}
246243

247244
[Fact]
@@ -360,30 +357,30 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
360357
public async Task Can_update_resource_with_omitted_required_attribute_value()
361358
{
362359
// Arrange
363-
SystemDirectoryexistingDirectory = _fakers.SystemDirectory.Generate();
360+
SystemFileexistingFile = _fakers.SystemFile.Generate();
364361

365-
long newSizeInBytes = _fakers.SystemDirectory.Generate().SizeInBytes;
362+
long? newSizeInBytes = _fakers.SystemFile.Generate().SizeInBytes;
366363

367364
await _testContext.RunOnDatabaseAsync(async dbContext =>
368365
{
369-
dbContext.Directories.Add(existingDirectory);
366+
dbContext.Files.Add(existingFile);
370367
await dbContext.SaveChangesAsync();
371368
});
372369

373370
var requestBody = new
374371
{
375372
data = new
376373
{
377-
type = "systemDirectories",
378-
id = existingDirectory.StringId,
374+
type = "systemFiles",
375+
id = existingFile.StringId,
379376
attributes = new
380377
{
381378
sizeInBytes = newSizeInBytes
382379
}
383380
}
384381
};
385382

386-
string route = $"/systemDirectories/{existingDirectory.StringId}";
383+
string route = $"/systemFiles/{existingFile.StringId}";
387384

388385
// Act
389386
(HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync<string>(route, requestBody);

‎test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/SystemDirectory.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,6 @@ public sealed class SystemDirectory : Identifiable<int>
2020
[Required]
2121
public bool? IsCaseSensitive { get; set; }
2222

23-
[Attr]
24-
[Range(typeof(long), "0", "9223372036854775807")]
25-
public long SizeInBytes { get; set; }
26-
2723
[HasMany]
2824
public ICollection<SystemDirectory> Subdirectories { get; set; } = new List<SystemDirectory>();
2925

‎test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/SystemFile.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ public sealed class SystemFile : Identifiable<int>
1515

1616
[Attr]
1717
[Required]
18-
[Range(typeof(long), "0", "9223372036854775807")]
19-
public long? SizeInBytes { get; set; }
18+
public FileAttributes? Attributes { get; set; }
19+
20+
[Attr]
21+
[Range(typeof(long), "1", "9223372036854775807")]
22+
public long SizeInBytes { get; set; }
2023
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /