diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/DefaultActionBindingContextProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/DefaultActionBindingContextProvider.cs index a2108af361..7917a51590 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/DefaultActionBindingContextProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/DefaultActionBindingContextProvider.cs @@ -29,14 +29,12 @@ namespace Microsoft.AspNet.Mvc _validatorProviders = validatorProviders; } - public async Task GetActionBindingContextAsync(ActionContext actionContext) + public Task GetActionBindingContextAsync(ActionContext actionContext) { var requestContext = new RequestContext(actionContext.HttpContext, actionContext.RouteValues); - var valueProviders = await Task.WhenAll(_valueProviderFactories.Select(factory => factory.GetValueProviderAsync(requestContext))); - valueProviders = valueProviders.Where(vp => vp != null) - .ToArray(); - - return new ActionBindingContext( + var valueProviders = _valueProviderFactories.Select(factory => factory.GetValueProvider(requestContext)) + .Where(vp => vp != null); + var context = new ActionBindingContext( actionContext, _modelMetadataProvider, new CompositeModelBinder(_modelBinders), @@ -44,6 +42,8 @@ namespace Microsoft.AspNet.Mvc _inputFormatterProvider, _validatorProviders ); + + return Task.FromResult(context); } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs index 216ef18888..153d9f2ca7 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public virtual async Task ContainsPrefixAsync(string prefix) { - for (int i = 0; i < Count; i++) + for (var i = 0; i < Count; i++) { if (await this[i].ContainsPrefixAsync(prefix)) { @@ -39,11 +39,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { // Performance-sensitive // Caching the count is faster for IList - int itemCount = Items.Count; - for (int i = 0; i < itemCount; i++) + var itemCount = Items.Count; + for (var i = 0; i < itemCount; i++) { - IValueProvider vp = Items[i]; - ValueProviderResult result = await vp.GetValueAsync(key); + var valueProvider = Items[i]; + var result = await valueProvider.GetValueAsync(key); if (result != null) { return result; @@ -52,11 +52,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return null; } - public virtual IDictionary GetKeysFromPrefix(string prefix) + public virtual async Task> GetKeysFromPrefixAsync(string prefix) { - foreach (IValueProvider vp in this) + foreach (var valueProvider in this) { - IDictionary result = GetKeysFromPrefixFromProvider(vp, prefix); + var result = await GetKeysFromPrefixFromProvider(valueProvider, prefix); if (result != null && result.Count > 0) { return result; @@ -65,10 +65,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return new Dictionary(StringComparer.OrdinalIgnoreCase); } - internal static IDictionary GetKeysFromPrefixFromProvider(IValueProvider provider, string prefix) + private static Task> GetKeysFromPrefixFromProvider(IValueProvider provider, + string prefix) { - IEnumerableValueProvider enumeratedProvider = provider as IEnumerableValueProvider; - return (enumeratedProvider != null) ? enumeratedProvider.GetKeysFromPrefix(prefix) : null; + var enumeratedProvider = provider as IEnumerableValueProvider; + return (enumeratedProvider != null) ? enumeratedProvider.GetKeysFromPrefixAsync(prefix) : null; } protected override void InsertItem(int index, [NotNull] IValueProvider item) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs index 29b56beb26..e8899eb980 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs @@ -12,15 +12,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { private const string FormEncodedContentType = "application/x-www-form-urlencoded"; - public async Task GetValueProviderAsync(RequestContext requestContext) + public IValueProvider GetValueProvider(RequestContext requestContext) { var request = requestContext.HttpContext.Request; if (IsSupportedContentType(request)) { - var queryCollection = await request.GetFormAsync(); var culture = GetCultureInfo(request); - return new ReadableStringCollectionValueProvider(queryCollection, culture); + return new ReadableStringCollectionValueProvider(request.GetFormAsync, culture); } return null; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IEnumerableValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IEnumerableValueProvider.cs index 2012334ec9..578c8a2629 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IEnumerableValueProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IEnumerableValueProvider.cs @@ -2,11 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Threading.Tasks; namespace Microsoft.AspNet.Mvc.ModelBinding { public interface IEnumerableValueProvider : IValueProvider { - IDictionary GetKeysFromPrefix(string prefix); + Task> GetKeysFromPrefixAsync(string prefix); } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IValueProviderFactory.cs index 2f5ec0fd2d..ac6dfb1a05 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IValueProviderFactory.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IValueProviderFactory.cs @@ -12,6 +12,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// /// RequestContext that value provider will populate from /// a value provider instance or null - Task GetValueProviderAsync(RequestContext requestContext); + IValueProvider GetValueProvider(RequestContext requestContext); } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs index 7b80fe0ec0..6362fc58b1 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Globalization; -using System.Threading.Tasks; -using Microsoft.AspNet.Mvc.ModelBinding.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding { @@ -11,7 +9,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { private static readonly object _cacheKey = new object(); - public Task GetValueProviderAsync([NotNull] RequestContext requestContext) + public IValueProvider GetValueProvider([NotNull] RequestContext requestContext) { // Process the query collection once-per request. var storage = requestContext.HttpContext.Items; @@ -27,7 +25,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { provider = (ReadableStringCollectionValueProvider)value; } - return Task.FromResult(provider); + return provider; } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs index d9fe02f7ef..b991b34a42 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using System.Threading.Tasks; @@ -14,7 +16,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { private readonly CultureInfo _culture; private PrefixContainer _prefixContainer; - private readonly IReadableStringCollection _values; + private readonly Func> _valuesFactory; + private IReadableStringCollection _values; /// /// Creates a NameValuePairsProvider wrapping an existing set of key value pairs. @@ -27,6 +30,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding _culture = culture; } + public ReadableStringCollectionValueProvider([NotNull] Func> valuesFactory, + CultureInfo culture) + { + _valuesFactory = valuesFactory; + _culture = culture; + } + public CultureInfo Culture { get @@ -35,35 +45,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } - private PrefixContainer PrefixContainer + public virtual async Task ContainsPrefixAsync(string prefix) { - get - { - if (_prefixContainer == null) - { - // Initialization race is OK providing data remains read-only and object identity is not significant - // TODO: Figure out if we can have IReadableStringCollection expose Keys, Count etc - - _prefixContainer = new PrefixContainer(_values.Select(v => v.Key).ToArray()); - } - return _prefixContainer; - } + var prefixContainer = await GetPrefixContainerAsync(); + return prefixContainer.ContainsPrefix(prefix); } - public virtual Task ContainsPrefixAsync(string prefix) + public virtual async Task> GetKeysFromPrefixAsync([NotNull] string prefix) { - return Task.FromResult(PrefixContainer.ContainsPrefix(prefix)); + var prefixContainer = await GetPrefixContainerAsync(); + return prefixContainer.GetKeysFromPrefix(prefix); } - public virtual IDictionary GetKeysFromPrefix([NotNull] string prefix) + public virtual async Task GetValueAsync([NotNull] string key) { - return PrefixContainer.GetKeysFromPrefix(prefix); - } + var collection = await GetValueCollectionAsync(); + var values = collection.GetValues(key); - public virtual Task GetValueAsync([NotNull] string key) - { ValueProviderResult result; - var values = _values.GetValues(key); if (values == null) { result = null; @@ -78,7 +77,31 @@ namespace Microsoft.AspNet.Mvc.ModelBinding result = new ValueProviderResult(values, _values.Get(key), _culture); } - return Task.FromResult(result); + return result; + } + + private async Task GetValueCollectionAsync() + { + if (_values == null) + { + Contract.Assert(_valuesFactory != null); + _values = await _valuesFactory(); + } + + return _values; + } + + private async Task GetPrefixContainerAsync() + { + if (_prefixContainer == null) + { + // Initialization race is OK providing data remains read-only and object identity is not significant + // TODO: Fix this once https://github.com/aspnet/HttpAbstractions/issues/3 is sorted out. + + var collection = await GetValueCollectionAsync(); + _prefixContainer = new PrefixContainer(collection.Select(v => v.Key).ToArray()); + } + return _prefixContainer; } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs index e7c712ef84..8c0ec2893c 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs @@ -1,16 +1,13 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Threading.Tasks; - namespace Microsoft.AspNet.Mvc.ModelBinding { public class RouteValueValueProviderFactory : IValueProviderFactory { - public Task GetValueProviderAsync(RequestContext requestContext) + public IValueProvider GetValueProvider(RequestContext requestContext) { - var valueProvider = new DictionaryBasedValueProvider(requestContext.RouteValues); - return Task.FromResult(valueProvider); + return new DictionaryBasedValueProvider(requestContext.RouteValues); } } } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs index bb6edbd789..0f3ab9a7de 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs @@ -14,14 +14,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public class FormValueProviderFactoryTests { [Fact] - public async Task GetValueProvider_ReturnsNull_WhenContentTypeIsNotFormUrlEncoded() + public void GetValueProvider_ReturnsNull_WhenContentTypeIsNotFormUrlEncoded() { // Arrange var requestContext = CreateRequestContext("some-content-type"); var factory = new FormValueProviderFactory(); // Act - var result = await factory.GetValueProviderAsync(requestContext); + var result = factory.GetValueProvider(requestContext); // Assert Assert.Null(result); @@ -30,14 +30,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test [Theory] [InlineData("application/x-www-form-urlencoded")] [InlineData("application/x-www-form-urlencoded;charset=utf-8")] - public async Task GetValueProvider_ReturnsValueProviderInstaceWithInvariantCulture(string contentType) + public void GetValueProvider_ReturnsValueProviderInstaceWithInvariantCulture(string contentType) { // Arrange var requestContext = CreateRequestContext(contentType); var factory = new FormValueProviderFactory(); // Act - var result = await factory.GetValueProviderAsync(requestContext); + var result = factory.GetValueProvider(requestContext); // Assert var valueProvider = Assert.IsType(result); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/QueryStringValueProviderFactoryTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/QueryStringValueProviderFactoryTest.cs index d36ae2096b..a93c78df71 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/QueryStringValueProviderFactoryTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/QueryStringValueProviderFactoryTest.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; -using System.Threading.Tasks; using Microsoft.AspNet.Http; #if NET45 using Moq; @@ -18,7 +17,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test #if NET45 [Fact] - public async Task GetValueProvider_ReturnsQueryStringValueProviderInstaceWithInvariantCulture() + public void GetValueProvider_ReturnsQueryStringValueProviderInstaceWithInvariantCulture() { // Arrange var request = new Mock(); @@ -29,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test var requestContext = new RequestContext(context.Object, new Dictionary()); // Act - var result = await _factory.GetValueProviderAsync(requestContext); + var result = _factory.GetValueProvider(requestContext); // Assert var valueProvider = Assert.IsType(result); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTests.cs index 2d1a72d4fa..9fdbed73a4 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTests.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test [Fact] - public async Task ContainsPrefix_WithEmptyCollection_ReturnsFalseForEmptyPrefix() + public async Task ContainsPrefixAsync_WithEmptyCollection_ReturnsFalseForEmptyPrefix() { // Arrange var backingStore = new ReadableStringCollection(new Dictionary()); @@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test } [Fact] - public async Task ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForEmptyPrefix() + public async Task ContainsPrefixAsync_WithNonEmptyCollection_ReturnsTrueForEmptyPrefix() { // Arrange var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); @@ -51,7 +51,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test } [Fact] - public async Task ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForKnownPrefixes() + public async Task ContainsPrefixAsync_WithNonEmptyCollection_ReturnsTrueForKnownPrefixes() { // Arrange var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); @@ -63,7 +63,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test } [Fact] - public async Task ContainsPrefix_WithNonEmptyCollection_ReturnsFalseForUnknownPrefix() + public async Task ContainsPrefixAsync_WithNonEmptyCollection_ReturnsFalseForUnknownPrefix() { // Arrange var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); @@ -76,13 +76,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test } [Fact] - public void GetKeysFromPrefix_EmptyPrefix_ReturnsAllPrefixes() + public async Task GetKeysFromPrefixAsync_EmptyPrefix_ReturnsAllPrefixes() { // Arrange var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act - IDictionary result = valueProvider.GetKeysFromPrefix(""); + var result = await valueProvider.GetKeysFromPrefixAsync(""); // Assert Assert.Equal>( @@ -91,35 +91,35 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test } [Fact] - public void GetKeysFromPrefix_UnknownPrefix_ReturnsEmptyDictionary() + public async Task GetKeysFromPrefixAsync_UnknownPrefix_ReturnsEmptyDictionary() { // Arrange var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act - IDictionary result = valueProvider.GetKeysFromPrefix("abc"); + var result = await valueProvider.GetKeysFromPrefixAsync("abc"); // Assert Assert.Empty(result); } [Fact] - public void GetKeysFromPrefix_KnownPrefix_ReturnsMatchingItems() + public async Task GetKeysFromPrefixAsync_KnownPrefix_ReturnsMatchingItems() { // Arrange var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act - IDictionary result = valueProvider.GetKeysFromPrefix("bar"); + var result = await valueProvider.GetKeysFromPrefixAsync("bar"); // Assert - KeyValuePair kvp = Assert.Single(result); + var kvp = Assert.Single(result); Assert.Equal("baz", kvp.Key); Assert.Equal("bar.baz", kvp.Value); } [Fact] - public async Task GetValue_SingleValue() + public async Task GetValueAsync_SingleValue() { // Arrange var culture = new CultureInfo("fr-FR"); @@ -136,7 +136,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test } [Fact] - public async Task GetValue_MultiValue() + public async Task GetValueAsync_MultiValue() { // Arrange var culture = new CultureInfo("fr-FR"); @@ -174,7 +174,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test //} [Fact] - public async Task GetValue_NullMultipleValue() + public async Task GetValueAsync_NullMultipleValue() { // Arrange var backingStore = new ReadableStringCollection( @@ -194,7 +194,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test } [Fact] - public async Task GetValue_ReturnsNullIfKeyNotFound() + public async Task GetValueAsync_ReturnsNullIfKeyNotFound() { // Arrange var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);