Skip to content

Commit 669443e

Browse files
committed
Update API and add sample app
1 parent 1ff9b04 commit 669443e

26 files changed

+396
-478
lines changed

src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Microsoft.AspNetCore.Http.Metadata.IDisableValidationMetadata
55
Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.Description.get -> string?
66
Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.Description.set -> void
77
Microsoft.AspNetCore.Http.Metadata.IProducesResponseTypeMetadata.Description.get -> string?
8+
Microsoft.AspNetCore.Http.Validation.IValidatableInfo
9+
Microsoft.AspNetCore.Http.Validation.IValidatableInfo.ValidateAsync(object? value, Microsoft.AspNetCore.Http.Validation.ValidatableContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask
810
Microsoft.AspNetCore.Http.Validation.IValidatableInfoResolver
911
Microsoft.AspNetCore.Http.Validation.IValidatableInfoResolver.GetValidatableParameterInfo(System.Reflection.ParameterInfo! parameterInfo) -> Microsoft.AspNetCore.Http.Validation.ValidatableParameterInfo?
1012
Microsoft.AspNetCore.Http.Validation.IValidatableInfoResolver.GetValidatableTypeInfo(System.Type! type) -> Microsoft.AspNetCore.Http.Validation.ValidatableTypeInfo?
@@ -21,30 +23,13 @@ Microsoft.AspNetCore.Http.Validation.ValidatableContext.ValidationErrors.set ->
2123
Microsoft.AspNetCore.Http.Validation.ValidatableContext.ValidationOptions.get -> Microsoft.AspNetCore.Http.Validation.ValidationOptions!
2224
Microsoft.AspNetCore.Http.Validation.ValidatableContext.ValidationOptions.set -> void
2325
Microsoft.AspNetCore.Http.Validation.ValidatableParameterInfo
24-
Microsoft.AspNetCore.Http.Validation.ValidatableParameterInfo.DisplayName.get -> string!
25-
Microsoft.AspNetCore.Http.Validation.ValidatableParameterInfo.IsEnumerable.get -> bool
26-
Microsoft.AspNetCore.Http.Validation.ValidatableParameterInfo.IsNullable.get -> bool
27-
Microsoft.AspNetCore.Http.Validation.ValidatableParameterInfo.IsRequired.get -> bool
28-
Microsoft.AspNetCore.Http.Validation.ValidatableParameterInfo.Name.get -> string!
29-
Microsoft.AspNetCore.Http.Validation.ValidatableParameterInfo.ParameterType.get -> System.Type!
30-
Microsoft.AspNetCore.Http.Validation.ValidatableParameterInfo.ValidatableParameterInfo(System.Type! parameterType, string! name, string! displayName, bool isNullable, bool isRequired, bool isEnumerable) -> void
26+
Microsoft.AspNetCore.Http.Validation.ValidatableParameterInfo.ValidatableParameterInfo(System.Type! parameterType, string! name, string! displayName) -> void
3127
Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo
32-
Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo.DeclaringType.get -> System.Type!
33-
Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo.DisplayName.get -> string!
34-
Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo.HasValidatableType.get -> bool
35-
Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo.IsEnumerable.get -> bool
36-
Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo.IsNullable.get -> bool
3728
Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo.IsRequired.get -> bool
38-
Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo.Name.get -> string!
39-
Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo.PropertyType.get -> System.Type!
40-
Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo.ValidatablePropertyInfo(System.Type! declaringType, System.Type! propertyType, string! name, string! displayName, bool isEnumerable, bool isNullable, bool isRequired, bool hasValidatableType) -> void
29+
Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo.ValidatablePropertyInfo(System.Type! declaringType, System.Type! propertyType, string! name, string! displayName) -> void
4130
Microsoft.AspNetCore.Http.Validation.ValidatableTypeAttribute
4231
Microsoft.AspNetCore.Http.Validation.ValidatableTypeAttribute.ValidatableTypeAttribute() -> void
4332
Microsoft.AspNetCore.Http.Validation.ValidatableTypeInfo
44-
Microsoft.AspNetCore.Http.Validation.ValidatableTypeInfo.IsIValidatableObject.get -> bool
45-
Microsoft.AspNetCore.Http.Validation.ValidatableTypeInfo.Members.get -> System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo!>!
46-
Microsoft.AspNetCore.Http.Validation.ValidatableTypeInfo.Type.get -> System.Type!
47-
Microsoft.AspNetCore.Http.Validation.ValidatableTypeInfo.ValidatableSubTypes.get -> System.Collections.Generic.IReadOnlyList<System.Type!>?
4833
Microsoft.AspNetCore.Http.Validation.ValidatableTypeInfo.ValidatableTypeInfo(System.Type! type, System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo!>! members, bool implementsIValidatableObject, System.Collections.Generic.IReadOnlyList<System.Type!>? validatableSubTypes = null) -> void
4934
Microsoft.AspNetCore.Http.Validation.ValidationOptions
5035
Microsoft.AspNetCore.Http.Validation.ValidationOptions.MaxDepth.get -> int
@@ -55,6 +40,6 @@ Microsoft.AspNetCore.Http.Validation.ValidationOptions.TryGetValidatableTypeInfo
5540
Microsoft.AspNetCore.Http.Validation.ValidationOptions.ValidationOptions() -> void
5641
Microsoft.Extensions.DependencyInjection.ValidationServiceCollectionExtensions
5742
static Microsoft.Extensions.DependencyInjection.ValidationServiceCollectionExtensions.AddValidation(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action<Microsoft.AspNetCore.Http.Validation.ValidationOptions!>? configureOptions = null) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
58-
virtual Microsoft.AspNetCore.Http.Validation.ValidatableParameterInfo.Validate(object? value, Microsoft.AspNetCore.Http.Validation.ValidatableContext! context) -> System.Threading.Tasks.Task!
59-
virtual Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo.Validate(object! obj, Microsoft.AspNetCore.Http.Validation.ValidatableContext! context) -> System.Threading.Tasks.Task!
60-
virtual Microsoft.AspNetCore.Http.Validation.ValidatableTypeInfo.Validate(object? value, Microsoft.AspNetCore.Http.Validation.ValidatableContext! context) -> System.Threading.Tasks.Task!
43+
virtual Microsoft.AspNetCore.Http.Validation.ValidatableParameterInfo.ValidateAsync(object? value, Microsoft.AspNetCore.Http.Validation.ValidatableContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask
44+
virtual Microsoft.AspNetCore.Http.Validation.ValidatablePropertyInfo.ValidateAsync(object? value, Microsoft.AspNetCore.Http.Validation.ValidatableContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask
45+
virtual Microsoft.AspNetCore.Http.Validation.ValidatableTypeInfo.ValidateAsync(object? value, Microsoft.AspNetCore.Http.Validation.ValidatableContext! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Http.Validation;
5+
6+
/// <summary>
7+
/// Represents an interface for validating a value.
8+
/// </summary>
9+
public interface IValidatableInfo
10+
{
11+
/// <summary>
12+
/// Validates the specified value.
13+
/// </summary>
14+
/// <param name="value">The value to validate.</param>
15+
/// <param name="context">The validation context.</param>
16+
/// <param name="cancellationToken"></param>
17+
ValueTask ValidateAsync(object? value, ValidatableContext context, CancellationToken cancellationToken);
18+
}

src/Http/Http.Abstractions/src/Validation/RuntimeValidatableParameterInfoResolver.cs

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,10 @@ internal class RuntimeValidatableParameterInfoResolver : IValidatableInfoResolve
2020
parameterType: parameterInfo.ParameterType,
2121
name: parameterInfo.Name,
2222
displayName: GetDisplayName(parameterInfo),
23-
isNullable: IsNullable(parameterInfo),
24-
isRequired: validationAttributes.Any(a => a is RequiredAttribute),
25-
isEnumerable: IsEnumerable(parameterInfo),
2623
validationAttributes: validationAttributes
2724
);
2825
}
2926

30-
private static bool IsNullable(ParameterInfo parameterInfo)
31-
{
32-
if (parameterInfo.ParameterType.IsValueType)
33-
{
34-
return false;
35-
}
36-
37-
if (parameterInfo.ParameterType.IsGenericType &&
38-
parameterInfo.ParameterType.GetGenericTypeDefinition() == typeof(Nullable<>))
39-
{
40-
return true;
41-
}
42-
43-
return false;
44-
}
45-
4627
private static string GetDisplayName(ParameterInfo parameterInfo)
4728
{
4829
var displayAttribute = parameterInfo.GetCustomAttribute<DisplayAttribute>();
@@ -54,22 +35,6 @@ private static string GetDisplayName(ParameterInfo parameterInfo)
5435
return parameterInfo.Name!;
5536
}
5637

57-
private static bool IsEnumerable(ParameterInfo parameterInfo)
58-
{
59-
if (parameterInfo.ParameterType.IsGenericType &&
60-
parameterInfo.ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
61-
{
62-
return true;
63-
}
64-
65-
if (parameterInfo.ParameterType.IsArray)
66-
{
67-
return true;
68-
}
69-
70-
return false;
71-
}
72-
7338
public ValidatableTypeInfo? GetValidatableTypeInfo(Type type)
7439
{
7540
return null;
@@ -79,11 +44,8 @@ private class RuntimeValidatableParameterInfo(
7944
Type parameterType,
8045
string name,
8146
string displayName,
82-
bool isNullable,
83-
bool isRequired,
84-
bool isEnumerable,
8547
ValidationAttribute[] validationAttributes) :
86-
ValidatableParameterInfo(parameterType, name, displayName, isNullable, isRequired, isEnumerable)
48+
ValidatableParameterInfo(parameterType, name, displayName)
8749
{
8850
protected override ValidationAttribute[] GetValidationAttributes() => _validationAttributes;
8951

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections;
5+
using System.ComponentModel.DataAnnotations;
6+
using System.Diagnostics.CodeAnalysis;
7+
8+
namespace Microsoft.AspNetCore.Http.Validation;
9+
10+
internal static class TypeExtensions
11+
{
12+
public static bool IsEnumerable(this Type type)
13+
{
14+
// Check if type itself is an IEnumerable
15+
if (type.IsGenericType &&
16+
(type.GetGenericTypeDefinition() == typeof(IEnumerable<>) ||
17+
type.GetGenericTypeDefinition() == typeof(ICollection<>) ||
18+
type.GetGenericTypeDefinition() == typeof(List<>)))
19+
{
20+
return true;
21+
}
22+
23+
// Or an array
24+
if (type.IsArray)
25+
{
26+
return true;
27+
}
28+
29+
// Then evaluate if it implements IEnumerable and is not a string
30+
if (typeof(IEnumerable).IsAssignableFrom(type) &&
31+
type != typeof(string))
32+
{
33+
return true;
34+
}
35+
36+
return false;
37+
}
38+
39+
public static bool IsNullable(this Type type)
40+
{
41+
if (type.IsValueType)
42+
{
43+
return false;
44+
}
45+
46+
if (type.IsGenericType &&
47+
type.GetGenericTypeDefinition() == typeof(Nullable<>))
48+
{
49+
return true;
50+
}
51+
52+
return false;
53+
}
54+
55+
public static bool TryGetRequiredAttribute(this ValidationAttribute[] attributes, [NotNullWhen(true)] out RequiredAttribute? requiredAttribute)
56+
{
57+
foreach (var attribute in attributes)
58+
{
59+
if (attribute is RequiredAttribute requiredAttr)
60+
{
61+
requiredAttribute = requiredAttr;
62+
return true;
63+
}
64+
}
65+
66+
requiredAttribute = null;
67+
return false;
68+
}
69+
}

src/Http/Http.Abstractions/src/Validation/ValidatableParameterInfo.cs

Lines changed: 16 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,70 +4,46 @@
44
using System.Collections;
55
using System.ComponentModel.DataAnnotations;
66
using System.Diagnostics;
7-
using System.Linq;
87

98
namespace Microsoft.AspNetCore.Http.Validation;
109

1110
/// <summary>
1211
/// Contains validation information for a parameter.
1312
/// </summary>
14-
public abstract class ValidatableParameterInfo
13+
public abstract class ValidatableParameterInfo : IValidatableInfo
1514
{
16-
private ValidationAttribute? _requiredAttribute;
15+
private RequiredAttribute? _requiredAttribute;
16+
1717
/// <summary>
1818
/// Creates a new instance of <see cref="ValidatableParameterInfo"/>.
1919
/// </summary>
2020
/// <param name="parameterType">The <see cref="Type"/> associated with the parameter.</param>
2121
/// <param name="name">The parameter name.</param>
2222
/// <param name="displayName">The display name for the parameter.</param>
23-
/// <param name="isNullable">Whether the parameter is optional.</param>
24-
/// <param name="isRequired"></param>
25-
/// <param name="isEnumerable">Whether the parameter is enumerable.</param>
2623
public ValidatableParameterInfo(
2724
Type parameterType,
2825
string name,
29-
string displayName,
30-
bool isNullable,
31-
bool isRequired,
32-
bool isEnumerable)
26+
string displayName)
3327
{
3428
ParameterType = parameterType;
3529
Name = name;
3630
DisplayName = displayName;
37-
IsNullable = isNullable;
38-
IsRequired = isRequired;
39-
IsEnumerable = isEnumerable;
4031
}
4132

4233
/// <summary>
4334
/// Gets the parameter type.
4435
/// </summary>
45-
public Type ParameterType { get; }
36+
internal Type ParameterType { get; }
4637

4738
/// <summary>
4839
/// Gets the parameter name.
4940
/// </summary>
50-
public string Name { get; }
41+
internal string Name { get; }
5142

5243
/// <summary>
5344
/// Gets the display name for the parameter.
5445
/// </summary>
55-
public string DisplayName { get; }
56-
57-
/// <summary>
58-
/// Gets whether the parameter is optional.
59-
/// </summary>
60-
public bool IsNullable { get; }
61-
62-
/// <summary>
63-
/// Gets whether the parameter is annotated with the <see cref="RequiredAttribute"/>.
64-
/// </summary>
65-
public bool IsRequired { get; }
66-
67-
/// <summary>
68-
/// Gets whether the parameter is enumerable.
69-
/// </summary>
70-
public bool IsEnumerable { get; }
46+
internal string DisplayName { get; }
7147

7248
/// <summary>
7349
/// Gets the validation attributes for this parameter.
@@ -80,38 +56,36 @@ public ValidatableParameterInfo(
8056
/// </summary>
8157
/// <param name="value">The value to validate.</param>
8258
/// <param name="context">The context for the validation.</param>
59+
/// <param name="cancellationToken"></param>
8360
/// <returns>A task representing the asynchronous operation.</returns>
8461
/// <remarks>
8562
/// If the parameter is a collection, each item in the collection will be validated.
8663
/// If the parameter is not a collection but has a validatable type, the single value will be validated.
8764
/// </remarks>
88-
public virtual Task Validate(object? value, ValidatableContext context)
65+
public virtual async ValueTask ValidateAsync(object? value, ValidatableContext context, CancellationToken cancellationToken)
8966
{
9067
Debug.Assert(context.ValidationContext is not null);
9168

9269
// Skip validation if value is null and parameter is optional
93-
if (value == null && IsNullable && !IsRequired)
70+
if (value == null && ParameterType.IsNullable())
9471
{
95-
return Task.CompletedTask;
72+
return;
9673
}
9774

9875
context.ValidationContext.DisplayName = DisplayName;
9976
context.ValidationContext.MemberName = Name;
10077

10178
var validationAttributes = GetValidationAttributes();
10279

103-
if (IsRequired)
80+
if (_requiredAttribute is not null && validationAttributes.TryGetRequiredAttribute(out _requiredAttribute))
10481
{
105-
_requiredAttribute ??= validationAttributes.OfType<RequiredAttribute>()
106-
.FirstOrDefault();
107-
Debug.Assert(_requiredAttribute is not null, "RequiredAttribute should be present if IsRequired is true");
10882
var result = _requiredAttribute.GetValidationResult(value, context.ValidationContext);
10983

11084
if (result is not null && result != ValidationResult.Success)
11185
{
11286
var key = string.IsNullOrEmpty(context.Prefix) ? Name : $"{context.Prefix}.{Name}";
11387
context.AddValidationError(key, [result.ErrorMessage!]);
114-
return Task.CompletedTask;
88+
return;
11589
}
11690
}
11791

@@ -136,7 +110,7 @@ public virtual Task Validate(object? value, ValidatableContext context)
136110
}
137111

138112
// If the parameter is a collection, validate each item
139-
if (IsEnumerable && value is IEnumerable enumerable)
113+
if (ParameterType.IsEnumerable() && value is IEnumerable enumerable)
140114
{
141115
var index = 0;
142116
foreach (var item in enumerable)
@@ -149,7 +123,7 @@ public virtual Task Validate(object? value, ValidatableContext context)
149123

150124
if (context.ValidationOptions.TryGetValidatableTypeInfo(item.GetType(), out var validatableType))
151125
{
152-
validatableType.Validate(item, context);
126+
await validatableType.ValidateAsync(item, context, cancellationToken);
153127
}
154128
}
155129
index++;
@@ -161,10 +135,8 @@ public virtual Task Validate(object? value, ValidatableContext context)
161135
var valueType = value.GetType();
162136
if (context.ValidationOptions.TryGetValidatableTypeInfo(valueType, out var validatableType))
163137
{
164-
validatableType.Validate(value, context);
138+
await validatableType.ValidateAsync(value, context, cancellationToken);
165139
}
166140
}
167-
168-
return Task.CompletedTask;
169141
}
170142
}

0 commit comments

Comments
 (0)