Skip to content

Commit 89b8900

Browse files
committed
Make subclassing DefaultBodyModelValidator more useful
- #9 - add `BodyModelValidatorContext` and `IBodyModelValidatorKeyBuilder`
1 parent 7674394 commit 89b8900

5 files changed

+180
-33
lines changed

src/System.Web.Http/System.Web.Http.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,9 @@
268268
<Compile Include="Tracing\Tracers\FormatterLoggerTraceWrapper.cs" />
269269
<Compile Include="Tracing\Tracers\DefaultHttpControllerTypeResolverTracer.cs" />
270270
<Compile Include="Tracing\Tracers\OverrideFilterTracer.cs" />
271+
<Compile Include="Validation\BodyModelValidatorContext.cs" />
271272
<Compile Include="Validation\IModelValidatorCache.cs" />
273+
<Compile Include="Validation\IBodyModelValidatorKeyBuilder.cs" />
272274
<Compile Include="Validation\ModelValidatorCache.cs" />
273275
<Compile Include="Controllers\ResponseMessageResultConverter.cs" />
274276
<Compile Include="Controllers\ValueResultConverter.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.Web.Http.Controllers;
6+
using System.Web.Http.Metadata;
7+
using System.Web.Http.ModelBinding;
8+
9+
namespace System.Web.Http.Validation
10+
{
11+
/// <summary>
12+
/// Context passed between <see cref="DefaultBodyModelValidator"/> methods.
13+
/// </summary>
14+
public class BodyModelValidatorContext
15+
{
16+
/// <summary>
17+
/// Gets or sets the <see cref="ModelMetadataProvider"/> used to provide the model metadata.
18+
/// </summary>
19+
public ModelMetadataProvider MetadataProvider { get; set; }
20+
21+
/// <summary>
22+
/// Gets or sets the <see cref="HttpActionContext"/> within which the model is being validated.
23+
/// </summary>
24+
public HttpActionContext ActionContext { get; set; }
25+
26+
/// <summary>
27+
/// Gets or sets the <see cref="IModelValidatorCache"/>.
28+
/// </summary>
29+
public IModelValidatorCache ValidatorCache { get; set; }
30+
31+
/// <summary>
32+
/// Gets or sets the current <see cref="ModelStateDictionary"/>.
33+
/// </summary>
34+
public ModelStateDictionary ModelState { get; set; }
35+
36+
/// <summary>
37+
/// Gets or sets the set of model objects visited in this validation. Includes the model being validated in the
38+
/// current scope.
39+
/// </summary>
40+
public HashSet<object> Visited { get; set; }
41+
42+
/// <summary>
43+
/// Gets or sets the stack of <see cref="IBodyModelValidatorKeyBuilder"/>s used in this validation. Includes
44+
/// the <see cref="IBodyModelValidatorKeyBuilder"/> to generate model state keys for the current scope.
45+
/// </summary>
46+
public Stack<IBodyModelValidatorKeyBuilder> KeyBuilders { get; set; }
47+
48+
/// <summary>
49+
/// Gets or sets the model state prefix for the root scope of this validation.
50+
/// </summary>
51+
public string RootPrefix { get; set; }
52+
}
53+
}

src/System.Web.Http/Validation/DefaultBodyModelValidator.cs

+98-30
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,10 @@
1616
namespace System.Web.Http.Validation
1717
{
1818
/// <summary>
19-
/// Recursively validate an object.
19+
/// Recursively validate an object.
2020
/// </summary>
2121
public class DefaultBodyModelValidator : IBodyModelValidator
2222
{
23-
private interface IKeyBuilder
24-
{
25-
string AppendTo(string prefix);
26-
}
27-
2823
/// <summary>
2924
/// Determines whether the <paramref name="model"/> is valid and adds any validation errors to the <paramref name="actionContext"/>'s <see cref="ModelStateDictionary"/>
3025
/// </summary>
@@ -64,14 +59,14 @@ public bool Validate(object model, Type type, ModelMetadataProvider metadataProv
6459
}
6560

6661
ModelMetadata metadata = metadataProvider.GetMetadataForType(() => model, type);
67-
ValidationContext validationContext = new ValidationContext()
62+
BodyModelValidatorContext validationContext = new BodyModelValidatorContext
6863
{
6964
MetadataProvider = metadataProvider,
7065
ActionContext = actionContext,
7166
ValidatorCache = actionContext.GetValidatorCache(),
7267
ModelState = actionContext.ModelState,
7368
Visited = new HashSet<object>(ReferenceEqualityComparer.Instance),
74-
KeyBuilders = new Stack<IKeyBuilder>(),
69+
KeyBuilders = new Stack<IBodyModelValidatorKeyBuilder>(),
7570
RootPrefix = keyPrefix
7671
};
7772
return ValidateNodeAndChildren(metadata, validationContext, container: null, validators: null);
@@ -87,12 +82,36 @@ public virtual bool ShouldValidateType(Type type)
8782
return !MediaTypeFormatterCollection.IsTypeExcludedFromValidation(type);
8883
}
8984

85+
/// <summary>
86+
/// Recursively validate the given <paramref name="metadata"/> and <paramref name="container"/>.
87+
/// </summary>
88+
/// <param name="metadata">The <see cref="ModelMetadata"/> for the object to validate.</param>
89+
/// <param name="validationContext">The <see cref="BodyModelValidatorContext"/>.</param>
90+
/// <param name="container">The object containing the object to validate.</param>
91+
/// <param name="validators">The collection of <see cref="ModelValidator"/>s.</param>
92+
/// <returns>
93+
/// <see langword="true"/> if validation succeeds for the given <paramref name="metadata"/>,
94+
/// <paramref name="container"/>, and child nodes; <see langword="false"/> otherwise.
95+
/// </returns>
9096
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "See comment below")]
91-
private bool ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext validationContext, object container, IEnumerable<ModelValidator> validators)
97+
protected virtual bool ValidateNodeAndChildren(
98+
ModelMetadata metadata,
99+
BodyModelValidatorContext validationContext,
100+
object container,
101+
IEnumerable<ModelValidator> validators)
92102
{
93103
// Recursion guard to avoid stack overflows
94104
RuntimeHelpers.EnsureSufficientExecutionStack();
95105

106+
if (metadata == null)
107+
{
108+
throw Error.ArgumentNull("metadata");
109+
}
110+
if (validationContext == null)
111+
{
112+
throw Error.ArgumentNull("validationContext");
113+
}
114+
96115
object model = null;
97116
try
98117
{
@@ -155,8 +174,26 @@ private bool ValidateNodeAndChildren(ModelMetadata metadata, ValidationContext v
155174
return isValid;
156175
}
157176

158-
private bool ValidateProperties(ModelMetadata metadata, ValidationContext validationContext)
177+
/// <summary>
178+
/// Recursively validate the properties of the given <paramref name="metadata"/>.
179+
/// </summary>
180+
/// <param name="metadata">The <see cref="ModelMetadata"/> for the object to validate.</param>
181+
/// <param name="validationContext">The <see cref="BodyModelValidatorContext"/>.</param>
182+
/// <returns>
183+
/// <see langword="true"/> if validation succeeds for all properties in <paramref name="metadata"/>;
184+
/// <see langword="false"/> otherwise.
185+
/// </returns>
186+
protected virtual bool ValidateProperties(ModelMetadata metadata, BodyModelValidatorContext validationContext)
159187
{
188+
if (metadata == null)
189+
{
190+
throw Error.ArgumentNull("metadata");
191+
}
192+
if (validationContext == null)
193+
{
194+
throw Error.ArgumentNull("validationContext");
195+
}
196+
160197
bool isValid = true;
161198
PropertyScope propertyScope = new PropertyScope();
162199
validationContext.KeyBuilders.Push(propertyScope);
@@ -172,8 +209,26 @@ private bool ValidateProperties(ModelMetadata metadata, ValidationContext valida
172209
return isValid;
173210
}
174211

175-
private bool ValidateElements(IEnumerable model, ValidationContext validationContext)
212+
/// <summary>
213+
/// Recursively validate the elements of the <paramref name="model"/> collection.
214+
/// </summary>
215+
/// <param name="model">The <see cref="IEnumerable"/> instance containing the elements to validate.</param>
216+
/// <param name="validationContext">The <see cref="BodyModelValidatorContext"/>.</param>
217+
/// <returns>
218+
/// <see langword="true"/> if validation succeeds for all elements of <paramref name="model"/>;
219+
/// <see langword="false"/> otherwise.
220+
/// </returns>
221+
protected virtual bool ValidateElements(IEnumerable model, BodyModelValidatorContext validationContext)
176222
{
223+
if (model == null)
224+
{
225+
throw Error.ArgumentNull("model");
226+
}
227+
if (validationContext == null)
228+
{
229+
throw Error.ArgumentNull("validationContext");
230+
}
231+
177232
bool isValid = true;
178233
Type elementType = GetElementType(model.GetType());
179234
ModelMetadata elementMetadata = validationContext.MetadataProvider.GetMetadataForType(null, elementType);
@@ -207,15 +262,39 @@ private bool ValidateElements(IEnumerable model, ValidationContext validationCon
207262
return isValid;
208263
}
209264

210-
// Validates a single node (not including children)
211-
// Returns true if validation passes successfully
212-
private static bool ShallowValidate(ModelMetadata metadata, ValidationContext validationContext, object container, IEnumerable<ModelValidator> validators)
265+
/// <summary>
266+
/// Validate a single node, not including its children.
267+
/// </summary>
268+
/// <param name="metadata">The <see cref="ModelMetadata"/>.</param>
269+
/// <param name="validationContext">The <see cref="BodyModelValidatorContext"/>.</param>
270+
/// <param name="container">The object to validate.</param>
271+
/// <param name="validators">The collection of <see cref="ModelValidator"/>s.</param>
272+
/// <returns>
273+
/// <see langword="true"/> if validation succeeds for the given <paramref name="metadata"/> and
274+
/// <paramref name="container"/>; <see langword="false"/> otherwise.
275+
/// </returns>
276+
protected virtual bool ShallowValidate(
277+
ModelMetadata metadata,
278+
BodyModelValidatorContext validationContext,
279+
object container,
280+
IEnumerable<ModelValidator> validators)
213281
{
282+
if (metadata == null)
283+
{
284+
throw Error.ArgumentNull("metadata");
285+
}
286+
if (validationContext == null)
287+
{
288+
throw Error.ArgumentNull("validationContext");
289+
}
290+
if (validators == null)
291+
{
292+
throw Error.ArgumentNull("validators");
293+
}
294+
214295
bool isValid = true;
215296
string modelKey = null;
216297

217-
Contract.Assert(validators != null);
218-
219298
// When the are no validators we bail quickly. This saves a GetEnumerator allocation.
220299
// In a large array (tens of thousands or more) scenario it's very significant.
221300
ICollection validatorsAsCollection = validators as ICollection;
@@ -231,7 +310,7 @@ private static bool ShallowValidate(ModelMetadata metadata, ValidationContext va
231310
if (modelKey == null)
232311
{
233312
modelKey = validationContext.RootPrefix;
234-
foreach (IKeyBuilder keyBuilder in validationContext.KeyBuilders.Reverse())
313+
foreach (IBodyModelValidatorKeyBuilder keyBuilder in validationContext.KeyBuilders.Reverse())
235314
{
236315
modelKey = keyBuilder.AppendTo(modelKey);
237316
}
@@ -263,7 +342,7 @@ private static Type GetElementType(Type type)
263342
return typeof(object);
264343
}
265344

266-
private class PropertyScope : IKeyBuilder
345+
private class PropertyScope : IBodyModelValidatorKeyBuilder
267346
{
268347
public string PropertyName { get; set; }
269348

@@ -273,7 +352,7 @@ public string AppendTo(string prefix)
273352
}
274353
}
275354

276-
private class ElementScope : IKeyBuilder
355+
private class ElementScope : IBodyModelValidatorKeyBuilder
277356
{
278357
public int Index { get; set; }
279358

@@ -282,16 +361,5 @@ public string AppendTo(string prefix)
282361
return ModelBindingHelper.CreateIndexModelName(prefix, Index);
283362
}
284363
}
285-
286-
private class ValidationContext
287-
{
288-
public ModelMetadataProvider MetadataProvider { get; set; }
289-
public HttpActionContext ActionContext { get; set; }
290-
public IModelValidatorCache ValidatorCache { get; set; }
291-
public ModelStateDictionary ModelState { get; set; }
292-
public HashSet<object> Visited { get; set; }
293-
public Stack<IKeyBuilder> KeyBuilders { get; set; }
294-
public string RootPrefix { get; set; }
295-
}
296364
}
297365
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace System.Web.Http.Validation
5+
{
6+
/// <summary>
7+
/// Abstraction for creating keys used in nested validation scopes. Intended for use in
8+
/// <see cref="IBodyModelValidator"/> implementations, especially <see cref="DefaultBodyModelValidator"/>.
9+
/// </summary>
10+
public interface IBodyModelValidatorKeyBuilder
11+
{
12+
/// <summary>
13+
/// Returns the key for a nested scope within the <paramref name="prefix"/> scope.
14+
/// </summary>
15+
/// <param name="prefix">Key for the current scope.</param>
16+
/// <returns>Key for a nested scope. Usually appends a property name to <paramref name="prefix"/>.</returns>
17+
string AppendTo(string prefix);
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System.Collections.Generic;
54
using System.Web.Http.Metadata;
65

76
namespace System.Web.Http.Validation
87
{
98
/// <summary>
10-
/// Defines a cache for <see cref="ModelValidator"/>s. This cache is keyed on the type or property that the metadata is associated with.
9+
/// Defines a cache for <see cref="ModelValidator"/>s. This cache is keyed on the type or property that the
10+
/// metadata is associated with.
1111
/// </summary>
12-
internal interface IModelValidatorCache
12+
public interface IModelValidatorCache
1313
{
14+
/// <summary>
15+
/// Returns the <see cref="ModelValidator"/>s for the given <paramref name="metadata"/>.
16+
/// </summary>
17+
/// <param name="metadata">The <see cref="ModelMetadata"/>.</param>
18+
/// <returns>An array of <see cref="ModelValidator"/>s for the given <paramref name="metadata"/>.</returns>
1419
ModelValidator[] GetValidators(ModelMetadata metadata);
1520
}
1621
}

0 commit comments

Comments
 (0)