Permalink
Browse files

Performance improvements around simple parameters from the query stri…

…ng. This gets about a 2x win in the case of calling get(int,int,int) in an in-memory loop.

Reorder the value provider factory and model binder providers to put the most common at the top.
Put TypeConverter and QueryStringProvider at the top. If we only pull from the query string (and have no misses), this avoids even invoking the other model binders or the route value provider.

Per-request caching QueryString and RouteValue providers so that we can reuse them across parameters. We shouldn't be parsing the query string for every parameter in a request.
Use array instead of IEnumerable. Avoids expensive ToList with ValueProviderFactories. No downside here and it gets about a 15% improvement in scenario.

Avoid ToList() calls in composite model binder. If we already have the array, use that.
  • Loading branch information...
1 parent ec5dd4c commit a257938cd04948862e4af29f44aa45ffaea86592 MikeStall committed Apr 5, 2012
View
2 src/System.Web.Http/Controllers/HttpActionContext.cs
@@ -13,7 +13,7 @@ public class HttpActionContext
private readonly Dictionary<string, object> _operationArguments = new Dictionary<string, object>();
private HttpActionDescriptor _actionDescriptor;
private HttpControllerContext _controllerContext;
-
+
public HttpActionContext(HttpControllerContext controllerContext, HttpActionDescriptor actionDescriptor)
{
if (controllerContext == null)
View
8 src/System.Web.Http/ModelBinding/Binders/CompositeModelBinder.cs
@@ -18,11 +18,15 @@ namespace System.Web.Http.ModelBinding.Binders
public class CompositeModelBinder : IModelBinder
{
public CompositeModelBinder(IEnumerable<ModelBinderProvider> modelBinderProviders)
+ : this(modelBinderProviders.ToArray())
{
- Providers = modelBinderProviders.ToList();
+ }
+ public CompositeModelBinder(ModelBinderProvider[] modelBinderProviders)
+ {
+ Providers = modelBinderProviders;
}
- private List<ModelBinderProvider> Providers { get; set; }
+ private ModelBinderProvider[] Providers { get; set; }
public virtual bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
View
14 src/System.Web.Http/ModelBinding/Binders/CompositeModelBinderProvider.cs
@@ -24,16 +24,20 @@ public CompositeModelBinderProvider(IEnumerable<ModelBinderProvider> providers)
public IEnumerable<ModelBinderProvider> Providers
{
- get { return _providers; }
+ get { return _providers; }
}
public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
+ // Fast-path the case where we already have the providers.
+ if (_providers != null)
+ {
+ return new CompositeModelBinder(_providers);
+ }
+
// Extract all providers from the resolver except the the type of the executing one (else would cause recursion),
- // or use the set of providers we were given.
- IEnumerable<ModelBinderProvider> providers = _providers != null
- ? _providers
- : actionContext.ControllerContext.Configuration.Services.GetModelBinderProviders().Where((p) => !typeof(CompositeModelBinderProvider).IsAssignableFrom(p.GetType()));
+ // or use the set of providers we were given.
+ IEnumerable<ModelBinderProvider> providers = actionContext.ControllerContext.Configuration.Services.GetModelBinderProviders().Where((p) => !typeof(CompositeModelBinderProvider).IsAssignableFrom(p.GetType()));
return new CompositeModelBinder(providers);
}
View
13 src/System.Web.Http/ModelBinding/ModelBinderParameterBinding.cs
@@ -15,7 +15,7 @@ namespace System.Web.Http.ModelBinding
/// </summary>
public class ModelBinderParameterBinding : HttpParameterBinding
{
- private readonly IEnumerable<ValueProviderFactory> _valueProviderFactories;
+ private readonly ValueProviderFactory[] _valueProviderFactories;
private readonly ModelBinderProvider _modelBinderProvider;
// Cache information for ModelBindingContext.
@@ -37,7 +37,7 @@ public class ModelBinderParameterBinding : HttpParameterBinding
}
_modelBinderProvider = modelBinderProvider;
- _valueProviderFactories = valueProviderFactories;
+ _valueProviderFactories = valueProviderFactories.ToArray();
}
public IEnumerable<ValueProviderFactory> ValueProviderFactories
@@ -101,13 +101,14 @@ private ModelBindingContext GetModelBindingContext(ModelMetadataProvider metadat
}
// Instantiate the value providers for the given action context.
- private static IValueProvider CreateValueProvider(IEnumerable<ValueProviderFactory> factories, HttpActionContext actionContext)
+ private static IValueProvider CreateValueProvider(ValueProviderFactory[] factories, HttpActionContext actionContext)
{
- List<IValueProvider> providers = factories.Select<ValueProviderFactory, IValueProvider>(f => f.GetValueProvider(actionContext)).ToList();
- if (providers.Count == 1)
+ if (factories.Length == 1)
{
- return providers[0];
+ return factories[0].GetValueProvider(actionContext);
}
+
+ IValueProvider[] providers = Array.ConvertAll(factories, f => f.GetValueProvider(actionContext));
return new CompositeValueProvider(providers);
}
}
View
12 src/System.Web.Http/Services/DefaultServices.cs
@@ -103,16 +103,18 @@ public DefaultServices(HttpConfiguration configuration)
_defaultServices.Add(typeof(IHttpControllerTypeResolver), new List<object> { new DefaultHttpControllerTypeResolver() });
_defaultServices.Add(typeof(ITraceManager), new List<object> { new TraceManager() });
_defaultServices.Add(typeof(ITraceWriter), new List<object>());
+
+ // This is a priority list. So put the most common binders at the top.
_defaultServices.Add(typeof(ModelBinderProvider), new List<object>
{
+ new TypeConverterModelBinderProvider(),
new TypeMatchModelBinderProvider(),
new BinaryDataModelBinderProvider(),
new KeyValuePairModelBinderProvider(),
new ComplexModelDtoModelBinderProvider(),
new ArrayModelBinderProvider(),
new DictionaryModelBinderProvider(),
- new CollectionModelBinderProvider(),
- new TypeConverterModelBinderProvider(),
+ new CollectionModelBinderProvider(),
new MutableObjectModelBinderProvider()
});
_defaultServices.Add(typeof(ModelMetadataProvider), new List<object> { new CachedDataAnnotationsModelMetadataProvider() });
@@ -121,10 +123,12 @@ public DefaultServices(HttpConfiguration configuration)
new DataAnnotationsModelValidatorProvider(),
new DataMemberModelValidatorProvider()
});
+
+ // This is an ordered list,so put the most common providers at the top.
_defaultServices.Add(typeof(ValueProviderFactory), new List<object>
{
- new RouteDataValueProviderFactory(),
- new QueryStringValueProviderFactory()
+ new QueryStringValueProviderFactory(),
+ new RouteDataValueProviderFactory()
});
_serviceTypes = new HashSet<Type>(_defaultServices.Keys);
View
18 src/System.Web.Http/ValueProviders/Providers/QueryStringValueProviderFactory.cs
@@ -1,18 +1,32 @@
-using System.Globalization;
+using System.Collections.Generic;
+using System.Globalization;
using System.Web.Http.Controllers;
namespace System.Web.Http.ValueProviders.Providers
{
public class QueryStringValueProviderFactory : ValueProviderFactory, IUriValueProviderFactory
{
+ private const string RequestLocalStorageKey = "{8572540D-3BD9-46DA-B112-A1E6C9086003}";
+
public override IValueProvider GetValueProvider(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
- return new QueryStringValueProvider(actionContext, CultureInfo.InvariantCulture);
+ // Only parse the query string once-per request.
+
+ QueryStringValueProvider provider;
+ IDictionary<string, object> storage = actionContext.Request.Properties;
+
+ if (!storage.TryGetValue(RequestLocalStorageKey, out provider))
+ {
+ provider = new QueryStringValueProvider(actionContext, CultureInfo.InvariantCulture);
+ storage[RequestLocalStorageKey] = provider;
+ }
+
+ return provider;
}
}
}
View
17 src/System.Web.Http/ValueProviders/Providers/RouteDataValueProviderFactory.cs
@@ -1,18 +1,31 @@
-using System.Globalization;
+using System.Collections.Generic;
+using System.Globalization;
using System.Web.Http.Controllers;
namespace System.Web.Http.ValueProviders.Providers
{
public class RouteDataValueProviderFactory : ValueProviderFactory, IUriValueProviderFactory
{
+ private const string RequestLocalStorageKey = "{C0E50671-A1D4-429E-93C9-2AA63779924F}";
+
public override IValueProvider GetValueProvider(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
- return new RouteDataValueProvider(actionContext, CultureInfo.InvariantCulture);
+ // cache the route provider across requests so that we don't recompute on every parameter.
+ RouteDataValueProvider provider;
+ IDictionary<string, object> storage = actionContext.Request.Properties;
+
+ if (!storage.TryGetValue(RequestLocalStorageKey, out provider))
+ {
+ provider = new RouteDataValueProvider(actionContext, CultureInfo.InvariantCulture);
+ storage[RequestLocalStorageKey] = provider;
+ }
+
+ return provider;
}
}
}
View
22 test/System.Web.Http.Test/Services/DefaultServicesTests.cs
@@ -65,14 +65,14 @@ public void Constructor_DefaultServicesInContainer()
object[] modelBinderProviders = defaultServices.GetServices(typeof(ModelBinderProvider)).ToArray();
Assert.Equal(9, modelBinderProviders.Length);
- Assert.IsType<TypeMatchModelBinderProvider>(modelBinderProviders[0]);
- Assert.IsType<BinaryDataModelBinderProvider>(modelBinderProviders[1]);
- Assert.IsType<KeyValuePairModelBinderProvider>(modelBinderProviders[2]);
- Assert.IsType<ComplexModelDtoModelBinderProvider>(modelBinderProviders[3]);
- Assert.IsType<ArrayModelBinderProvider>(modelBinderProviders[4]);
- Assert.IsType<DictionaryModelBinderProvider>(modelBinderProviders[5]);
- Assert.IsType<CollectionModelBinderProvider>(modelBinderProviders[6]);
- Assert.IsType<TypeConverterModelBinderProvider>(modelBinderProviders[7]);
+ Assert.IsType<TypeConverterModelBinderProvider>(modelBinderProviders[0]);
+ Assert.IsType<TypeMatchModelBinderProvider>(modelBinderProviders[1]);
+ Assert.IsType<BinaryDataModelBinderProvider>(modelBinderProviders[2]);
+ Assert.IsType<KeyValuePairModelBinderProvider>(modelBinderProviders[3]);
+ Assert.IsType<ComplexModelDtoModelBinderProvider>(modelBinderProviders[4]);
+ Assert.IsType<ArrayModelBinderProvider>(modelBinderProviders[5]);
+ Assert.IsType<DictionaryModelBinderProvider>(modelBinderProviders[6]);
+ Assert.IsType<CollectionModelBinderProvider>(modelBinderProviders[7]);
Assert.IsType<MutableObjectModelBinderProvider>(modelBinderProviders[8]);
object[] validatorProviders = defaultServices.GetServices(typeof(ModelValidatorProvider)).ToArray();
@@ -81,9 +81,9 @@ public void Constructor_DefaultServicesInContainer()
Assert.IsType<DataMemberModelValidatorProvider>(validatorProviders[1]);
object[] valueProviderFactories = defaultServices.GetServices(typeof(ValueProviderFactory)).ToArray();
- Assert.Equal(2, valueProviderFactories.Length);
- Assert.IsType<RouteDataValueProviderFactory>(valueProviderFactories[0]);
- Assert.IsType<QueryStringValueProviderFactory>(valueProviderFactories[1]);
+ Assert.Equal(2, valueProviderFactories.Length);
+ Assert.IsType<QueryStringValueProviderFactory>(valueProviderFactories[0]);
+ Assert.IsType<RouteDataValueProviderFactory>(valueProviderFactories[1]);
}
// Add tests
View
4 test/System.Web.Http.Test/ValueProviders/Providers/QueryStringValueProviderFactoryTest.cs
@@ -1,4 +1,5 @@
using System.Globalization;
+using System.Net.Http;
using System.Web.Http.Controllers;
using Xunit;
using Assert = Microsoft.TestCommon.AssertEx;
@@ -18,7 +19,8 @@ public void GetValueProvider_WhenActionContextParameterIsNull_Throws()
[Fact]
public void GetValueProvider_ReturnsQueryStringValueProviderInstaceWithInvariantCulture()
{
- var context = new HttpActionContext();
+ var controllerContext = new HttpControllerContext() { Request = new HttpRequestMessage() };
+ var context = new HttpActionContext() { ControllerContext = controllerContext };
IValueProvider result = _factory.GetValueProvider(context);
View
4 test/System.Web.Http.Test/ValueProviders/Providers/RouteDataValueProviderFactoryTest.cs
@@ -1,4 +1,5 @@
using System.Globalization;
+using System.Net.Http;
using System.Web.Http.Controllers;
using Xunit;
using Assert = Microsoft.TestCommon.AssertEx;
@@ -18,7 +19,8 @@ public void GetValueProvider_WhenActionContextParameterIsNull_Throws()
[Fact]
public void GetValueProvider_ReturnsQueryStringValueProviderInstaceWithInvariantCulture()
{
- var context = new HttpActionContext();
+ var controllerContext = new HttpControllerContext() { Request = new HttpRequestMessage() };
+ var context = new HttpActionContext() { ControllerContext = controllerContext };
IValueProvider result = _factory.GetValueProvider(context);

0 comments on commit a257938

Please sign in to comment.