1
1
using System ;
2
+ using System . Collections ;
3
+ using System . Collections . Generic ;
2
4
using System . ComponentModel . DataAnnotations ;
5
+ using System . Linq ;
6
+ using JsonApiDotNetCore . Configuration ;
3
7
using JsonApiDotNetCore . Middleware ;
4
8
using Microsoft . AspNetCore . Http ;
5
9
using Microsoft . Extensions . DependencyInjection ;
@@ -11,22 +15,104 @@ namespace JsonApiDotNetCore.Resources.Annotations
11
15
/// </summary>
12
16
public sealed class IsRequiredAttribute : RequiredAttribute
13
17
{
14
- private bool _isDisabled ;
18
+ private const string _isSelfReferencingResourceKey = "JsonApiDotNetCore_IsSelfReferencingResource" ;
15
19
16
- /// <inheritdoc />
17
- public override bool IsValid ( object value )
18
- {
19
- return _isDisabled || base . IsValid ( value ) ;
20
- }
20
+ public override bool RequiresValidationContext => true ;
21
21
22
22
/// <inheritdoc />
23
23
protected override ValidationResult IsValid ( object value , ValidationContext validationContext )
24
24
{
25
25
if ( validationContext == null ) throw new ArgumentNullException ( nameof ( validationContext ) ) ;
26
26
27
- var httpContextAccessor = ( IHttpContextAccessor ) validationContext . GetRequiredService ( typeof ( IHttpContextAccessor ) ) ;
28
- _isDisabled = httpContextAccessor . HttpContext . IsValidatorDisabled ( validationContext . MemberName , validationContext . ObjectType . Name ) ;
29
- return _isDisabled ? ValidationResult . Success : base . IsValid ( value , validationContext ) ;
27
+ var request = validationContext . GetRequiredService < IJsonApiRequest > ( ) ;
28
+ var httpContextAccessor = validationContext . GetRequiredService < IHttpContextAccessor > ( ) ;
29
+
30
+ if ( ShouldSkipValidationForResource ( validationContext , request , httpContextAccessor . HttpContext ) ||
31
+ ShouldSkipValidationForProperty ( validationContext , httpContextAccessor . HttpContext ) )
32
+ {
33
+ return ValidationResult . Success ;
34
+ }
35
+
36
+ return base . IsValid ( value , validationContext ) ;
37
+ }
38
+
39
+ private bool ShouldSkipValidationForResource ( ValidationContext validationContext , IJsonApiRequest request ,
40
+ HttpContext httpContext )
41
+ {
42
+ if ( request . Kind == EndpointKind . Primary )
43
+ {
44
+ // If there is a relationship included in the data of the POST or PATCH, then the 'IsRequired' attribute will be disabled for any
45
+ // property within that object. For instance, a new article is posted and has a relationship included to an author. In this case,
46
+ // the author name (which has the 'IsRequired' attribute) will not be included in the POST. Unless disabled, the POST will fail.
47
+
48
+ if ( validationContext . ObjectType != request . PrimaryResource . ResourceType )
49
+ {
50
+ return true ;
51
+ }
52
+
53
+ if ( validationContext . ObjectInstance is IIdentifiable identifiable )
54
+ {
55
+ if ( identifiable . StringId != request . PrimaryId )
56
+ {
57
+ return true ;
58
+ }
59
+
60
+ var isSelfReferencingResource = ( bool ? ) httpContext . Items [ _isSelfReferencingResourceKey ] ;
61
+
62
+ if ( isSelfReferencingResource == null )
63
+ {
64
+ // When processing a request, the first time we get here is for the top-level resource.
65
+ // Subsequent validations for related resources inspect the cache to know that their validation can be skipped.
66
+
67
+ isSelfReferencingResource = IsSelfReferencingResource ( identifiable , validationContext ) ;
68
+ httpContext . Items [ _isSelfReferencingResourceKey ] = isSelfReferencingResource ;
69
+ }
70
+
71
+ if ( isSelfReferencingResource . Value )
72
+ {
73
+ return true ;
74
+ }
75
+ }
76
+ }
77
+
78
+ return false ;
79
+ }
80
+
81
+ private bool IsSelfReferencingResource ( IIdentifiable identifiable , ValidationContext validationContext )
82
+ {
83
+ var provider = validationContext . GetRequiredService < IResourceContextProvider > ( ) ;
84
+ var relationships = provider . GetResourceContext ( validationContext . ObjectType ) . Relationships ;
85
+
86
+ foreach ( var relationship in relationships )
87
+ {
88
+ if ( relationship is HasOneAttribute hasOne )
89
+ {
90
+ var relationshipValue = ( IIdentifiable ) hasOne . GetValue ( identifiable ) ;
91
+ if ( IdentifiableComparer . Instance . Equals ( identifiable , relationshipValue ) )
92
+ {
93
+ return true ;
94
+ }
95
+ }
96
+
97
+ if ( relationship is HasManyAttribute hasMany )
98
+ {
99
+ var collection = ( IEnumerable ) hasMany . GetValue ( identifiable ) ;
100
+
101
+ if ( collection != null && collection . OfType < IIdentifiable > ( ) . Any ( resource =>
102
+ IdentifiableComparer . Instance . Equals ( identifiable , resource ) ) )
103
+ {
104
+ return true ;
105
+ }
106
+ }
107
+ }
108
+
109
+ return false ;
110
+ }
111
+
112
+ private bool ShouldSkipValidationForProperty ( ValidationContext validationContext , HttpContext httpContext )
113
+ {
114
+ return httpContext . IsRequiredValidatorDisabled ( validationContext . MemberName ,
115
+ validationContext . ObjectType . Name ) ;
30
116
}
31
117
}
32
118
}
0 commit comments