Skip to content

Commit 5dfc0aa

Browse files
author
MikeStall
committed
Performance improvement on validate. Cache expensive parts of the modelbinding context across requests, specifically validation nodes. Also need to ensure the cached nodes get propagated down to usage point when we clone a context.
1 parent cf10ff8 commit 5dfc0aa

File tree

3 files changed

+54
-9
lines changed

3 files changed

+54
-9
lines changed

src/System.Web.Http/ModelBinding/Binders/CompositeModelBinder.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ private static ModelBindingContext CreateNewBindingContext(ModelBindingContext o
102102
ValueProvider = oldBindingContext.ValueProvider
103103
};
104104

105+
// validation is expensive to create, so copy it over if we can
106+
if (object.ReferenceEquals(modelName, oldBindingContext.ModelName))
107+
{
108+
newBindingContext.ValidationNode = oldBindingContext.ValidationNode;
109+
}
110+
105111
return newBindingContext;
106112
}
107113

src/System.Web.Http/ModelBinding/ModelBinderParameterBinding.cs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Threading.Tasks;
55
using System.Web.Http.Controllers;
66
using System.Web.Http.Metadata;
7+
using System.Web.Http.Validation;
78
using System.Web.Http.ValueProviders;
89
using System.Web.Http.ValueProviders.Providers;
910

@@ -17,6 +18,10 @@ public class ModelBinderParameterBinding : HttpParameterBinding
1718
private readonly IEnumerable<ValueProviderFactory> _valueProviderFactories;
1819
private readonly ModelBinderProvider _modelBinderProvider;
1920

21+
// Cache information for ModelBindingContext.
22+
private ModelMetadata _metadataCache;
23+
private ModelValidationNode _validationNodeCache;
24+
2025
public ModelBinderParameterBinding(HttpParameterDescriptor descriptor,
2126
ModelBinderProvider modelBinderProvider,
2227
IEnumerable<ValueProviderFactory> valueProviderFactories)
@@ -46,6 +51,21 @@ public ModelBinderProvider ModelBinderProvider
4651
}
4752

4853
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
54+
{
55+
string name = Descriptor.ParameterName;
56+
57+
ModelBindingContext ctx = GetModelBindingContext(metadataProvider, actionContext);
58+
59+
IModelBinder binder = this._modelBinderProvider.GetBinder(actionContext, ctx);
60+
61+
bool haveResult = binder.BindModel(actionContext, ctx);
62+
object model = haveResult ? ctx.Model : Descriptor.DefaultValue;
63+
actionContext.ActionArguments.Add(name, model);
64+
65+
return TaskHelpers.Completed();
66+
}
67+
68+
private ModelBindingContext GetModelBindingContext(ModelMetadataProvider metadataProvider, HttpActionContext actionContext)
4969
{
5070
string name = Descriptor.ParameterName;
5171
Type type = Descriptor.ParameterType;
@@ -54,22 +74,30 @@ public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
5474

5575
IValueProvider vp = CreateValueProvider(this._valueProviderFactories, actionContext);
5676

77+
if (_metadataCache == null)
78+
{
79+
Interlocked.Exchange(ref _metadataCache, metadataProvider.GetMetadataForType(null, type));
80+
}
81+
5782
ModelBindingContext ctx = new ModelBindingContext()
5883
{
5984
ModelName = prefix ?? name,
6085
FallbackToEmptyPrefix = prefix == null, // only fall back if prefix not specified
61-
ModelMetadata = metadataProvider.GetMetadataForType(null, type),
86+
ModelMetadata = _metadataCache,
6287
ModelState = actionContext.ModelState,
6388
ValueProvider = vp
6489
};
90+
91+
if (_validationNodeCache == null)
92+
{
93+
Interlocked.Exchange(ref _validationNodeCache, ctx.ValidationNode);
94+
}
95+
else
96+
{
97+
ctx.ValidationNode = _validationNodeCache;
98+
}
6599

66-
IModelBinder binder = this._modelBinderProvider.GetBinder(actionContext, ctx);
67-
68-
bool haveResult = binder.BindModel(actionContext, ctx);
69-
object model = haveResult ? ctx.Model : Descriptor.DefaultValue;
70-
actionContext.ActionArguments.Add(name, model);
71-
72-
return TaskHelpers.Completed();
100+
return ctx;
73101
}
74102

75103
// Instantiate the value providers for the given action context.

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using System.Linq;
3+
using System.Threading;
34
using System.Web.Http.Controllers;
45
using System.Web.Http.Metadata;
56
using System.Web.Http.ModelBinding;
@@ -9,6 +10,11 @@ namespace System.Web.Http.Validation
910
{
1011
public sealed class ModelValidationNode
1112
{
13+
// This is a cache of the validators.
14+
// Use an array instead of IEnumerable to ensure that we've actually computed the result.
15+
// Array also reduces the memory footprint of the cache compared to other collections.
16+
private ModelValidator[] _validators;
17+
1218
public ModelValidationNode(ModelMetadata modelMetadata, string modelStateKey)
1319
: this(modelMetadata, modelStateKey, null)
1420
{
@@ -191,8 +197,13 @@ private void ValidateThis(HttpActionContext actionContext, ModelValidationNode p
191197
return;
192198
}
193199

200+
if (_validators == null)
201+
{
202+
Interlocked.Exchange(ref _validators, ModelMetadata.GetValidators(actionContext.GetValidatorProviders()).ToArray());
203+
}
204+
194205
object container = TryConvertContainerToMetadataType(parentNode);
195-
foreach (ModelValidator validator in ModelMetadata.GetValidators(actionContext.GetValidatorProviders()))
206+
foreach (ModelValidator validator in _validators)
196207
{
197208
foreach (ModelValidationResult validationResult in validator.Validate(container))
198209
{

0 commit comments

Comments
 (0)