Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Performance improvement on validate. Cache expensive parts of the mod…

…elbinding context across requests, specifically validation nodes. Also need to ensure the cached nodes get propagated down to usage point when we clone a context.
  • Loading branch information...
commit 5dfc0aa293fcc2d1d2fc08d09e80cd2870eb0b96 1 parent cf10ff8
MikeStall authored
View
6 src/System.Web.Http/ModelBinding/Binders/CompositeModelBinder.cs
@@ -102,6 +102,12 @@ private static ModelBindingContext CreateNewBindingContext(ModelBindingContext o
ValueProvider = oldBindingContext.ValueProvider
};
+ // validation is expensive to create, so copy it over if we can
+ if (object.ReferenceEquals(modelName, oldBindingContext.ModelName))
+ {
+ newBindingContext.ValidationNode = oldBindingContext.ValidationNode;
+ }
+
return newBindingContext;
}
View
44 src/System.Web.Http/ModelBinding/ModelBinderParameterBinding.cs
@@ -4,6 +4,7 @@
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
+using System.Web.Http.Validation;
using System.Web.Http.ValueProviders;
using System.Web.Http.ValueProviders.Providers;
@@ -17,6 +18,10 @@ public class ModelBinderParameterBinding : HttpParameterBinding
private readonly IEnumerable<ValueProviderFactory> _valueProviderFactories;
private readonly ModelBinderProvider _modelBinderProvider;
+ // Cache information for ModelBindingContext.
+ private ModelMetadata _metadataCache;
+ private ModelValidationNode _validationNodeCache;
+
public ModelBinderParameterBinding(HttpParameterDescriptor descriptor,
ModelBinderProvider modelBinderProvider,
IEnumerable<ValueProviderFactory> valueProviderFactories)
@@ -48,28 +53,51 @@ public ModelBinderProvider ModelBinderProvider
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
string name = Descriptor.ParameterName;
+
+ ModelBindingContext ctx = GetModelBindingContext(metadataProvider, actionContext);
+
+ IModelBinder binder = this._modelBinderProvider.GetBinder(actionContext, ctx);
+
+ bool haveResult = binder.BindModel(actionContext, ctx);
+ object model = haveResult ? ctx.Model : Descriptor.DefaultValue;
+ actionContext.ActionArguments.Add(name, model);
+
+ return TaskHelpers.Completed();
+ }
+
+ private ModelBindingContext GetModelBindingContext(ModelMetadataProvider metadataProvider, HttpActionContext actionContext)
+ {
+ string name = Descriptor.ParameterName;
Type type = Descriptor.ParameterType;
string prefix = Descriptor.Prefix;
IValueProvider vp = CreateValueProvider(this._valueProviderFactories, actionContext);
+ if (_metadataCache == null)
+ {
+ Interlocked.Exchange(ref _metadataCache, metadataProvider.GetMetadataForType(null, type));
+ }
+
ModelBindingContext ctx = new ModelBindingContext()
{
ModelName = prefix ?? name,
FallbackToEmptyPrefix = prefix == null, // only fall back if prefix not specified
- ModelMetadata = metadataProvider.GetMetadataForType(null, type),
+ ModelMetadata = _metadataCache,
ModelState = actionContext.ModelState,
ValueProvider = vp
};
+
+ if (_validationNodeCache == null)
+ {
+ Interlocked.Exchange(ref _validationNodeCache, ctx.ValidationNode);
+ }
+ else
+ {
+ ctx.ValidationNode = _validationNodeCache;
+ }
- IModelBinder binder = this._modelBinderProvider.GetBinder(actionContext, ctx);
-
- bool haveResult = binder.BindModel(actionContext, ctx);
- object model = haveResult ? ctx.Model : Descriptor.DefaultValue;
- actionContext.ActionArguments.Add(name, model);
-
- return TaskHelpers.Completed();
+ return ctx;
}
// Instantiate the value providers for the given action context.
View
13 src/System.Web.Http/Validation/ModelValidationNode.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
using System.Web.Http.ModelBinding;
@@ -9,6 +10,11 @@ namespace System.Web.Http.Validation
{
public sealed class ModelValidationNode
{
+ // This is a cache of the validators.
+ // Use an array instead of IEnumerable to ensure that we've actually computed the result.
+ // Array also reduces the memory footprint of the cache compared to other collections.
+ private ModelValidator[] _validators;
+
public ModelValidationNode(ModelMetadata modelMetadata, string modelStateKey)
: this(modelMetadata, modelStateKey, null)
{
@@ -191,8 +197,13 @@ private void ValidateThis(HttpActionContext actionContext, ModelValidationNode p
return;
}
+ if (_validators == null)
+ {
+ Interlocked.Exchange(ref _validators, ModelMetadata.GetValidators(actionContext.GetValidatorProviders()).ToArray());
+ }
+
object container = TryConvertContainerToMetadataType(parentNode);
- foreach (ModelValidator validator in ModelMetadata.GetValidators(actionContext.GetValidatorProviders()))
+ foreach (ModelValidator validator in _validators)
{
foreach (ModelValidationResult validationResult in validator.Validate(container))
{
Please sign in to comment.
Something went wrong with that request. Please try again.