Change IValueProviderFactory.GetValueProvider to be synchronous.

This commit is contained in:
Pranav K 2014-05-12 15:08:33 -07:00
parent ec8d09dd65
commit d8ba998dec
11 changed files with 93 additions and 75 deletions

View File

@ -29,14 +29,12 @@ namespace Microsoft.AspNet.Mvc
_validatorProviders = validatorProviders; _validatorProviders = validatorProviders;
} }
public async Task<ActionBindingContext> GetActionBindingContextAsync(ActionContext actionContext) public Task<ActionBindingContext> GetActionBindingContextAsync(ActionContext actionContext)
{ {
var requestContext = new RequestContext(actionContext.HttpContext, actionContext.RouteValues); var requestContext = new RequestContext(actionContext.HttpContext, actionContext.RouteValues);
var valueProviders = await Task.WhenAll(_valueProviderFactories.Select(factory => factory.GetValueProviderAsync(requestContext))); var valueProviders = _valueProviderFactories.Select(factory => factory.GetValueProvider(requestContext))
valueProviders = valueProviders.Where(vp => vp != null) .Where(vp => vp != null);
.ToArray(); var context = new ActionBindingContext(
return new ActionBindingContext(
actionContext, actionContext,
_modelMetadataProvider, _modelMetadataProvider,
new CompositeModelBinder(_modelBinders), new CompositeModelBinder(_modelBinders),
@ -44,6 +42,8 @@ namespace Microsoft.AspNet.Mvc
_inputFormatterProvider, _inputFormatterProvider,
_validatorProviders _validatorProviders
); );
return Task.FromResult(context);
} }
} }
} }

View File

@ -25,7 +25,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public virtual async Task<bool> ContainsPrefixAsync(string prefix) public virtual async Task<bool> ContainsPrefixAsync(string prefix)
{ {
for (int i = 0; i < Count; i++) for (var i = 0; i < Count; i++)
{ {
if (await this[i].ContainsPrefixAsync(prefix)) if (await this[i].ContainsPrefixAsync(prefix))
{ {
@ -39,11 +39,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
// Performance-sensitive // Performance-sensitive
// Caching the count is faster for IList<T> // Caching the count is faster for IList<T>
int itemCount = Items.Count; var itemCount = Items.Count;
for (int i = 0; i < itemCount; i++) for (var i = 0; i < itemCount; i++)
{ {
IValueProvider vp = Items[i]; var valueProvider = Items[i];
ValueProviderResult result = await vp.GetValueAsync(key); var result = await valueProvider.GetValueAsync(key);
if (result != null) if (result != null)
{ {
return result; return result;
@ -52,11 +52,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return null; return null;
} }
public virtual IDictionary<string, string> GetKeysFromPrefix(string prefix) public virtual async Task<IDictionary<string, string>> GetKeysFromPrefixAsync(string prefix)
{ {
foreach (IValueProvider vp in this) foreach (var valueProvider in this)
{ {
IDictionary<string, string> result = GetKeysFromPrefixFromProvider(vp, prefix); var result = await GetKeysFromPrefixFromProvider(valueProvider, prefix);
if (result != null && result.Count > 0) if (result != null && result.Count > 0)
{ {
return result; return result;
@ -65,10 +65,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
} }
internal static IDictionary<string, string> GetKeysFromPrefixFromProvider(IValueProvider provider, string prefix) private static Task<IDictionary<string, string>> GetKeysFromPrefixFromProvider(IValueProvider provider,
string prefix)
{ {
IEnumerableValueProvider enumeratedProvider = provider as IEnumerableValueProvider; var enumeratedProvider = provider as IEnumerableValueProvider;
return (enumeratedProvider != null) ? enumeratedProvider.GetKeysFromPrefix(prefix) : null; return (enumeratedProvider != null) ? enumeratedProvider.GetKeysFromPrefixAsync(prefix) : null;
} }
protected override void InsertItem(int index, [NotNull] IValueProvider item) protected override void InsertItem(int index, [NotNull] IValueProvider item)

View File

@ -12,15 +12,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
private const string FormEncodedContentType = "application/x-www-form-urlencoded"; private const string FormEncodedContentType = "application/x-www-form-urlencoded";
public async Task<IValueProvider> GetValueProviderAsync(RequestContext requestContext) public IValueProvider GetValueProvider(RequestContext requestContext)
{ {
var request = requestContext.HttpContext.Request; var request = requestContext.HttpContext.Request;
if (IsSupportedContentType(request)) if (IsSupportedContentType(request))
{ {
var queryCollection = await request.GetFormAsync();
var culture = GetCultureInfo(request); var culture = GetCultureInfo(request);
return new ReadableStringCollectionValueProvider(queryCollection, culture); return new ReadableStringCollectionValueProvider(request.GetFormAsync, culture);
} }
return null; return null;

View File

@ -2,11 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
public interface IEnumerableValueProvider : IValueProvider public interface IEnumerableValueProvider : IValueProvider
{ {
IDictionary<string, string> GetKeysFromPrefix(string prefix); Task<IDictionary<string, string>> GetKeysFromPrefixAsync(string prefix);
} }
} }

View File

@ -12,6 +12,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary> /// </summary>
/// <param name="requestContext">RequestContext that value provider will populate from</param> /// <param name="requestContext">RequestContext that value provider will populate from</param>
/// <returns>a value provider instance or null</returns> /// <returns>a value provider instance or null</returns>
Task<IValueProvider> GetValueProviderAsync(RequestContext requestContext); IValueProvider GetValueProvider(RequestContext requestContext);
} }
} }

View File

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Globalization; using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
@ -11,7 +9,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
private static readonly object _cacheKey = new object(); private static readonly object _cacheKey = new object();
public Task<IValueProvider> GetValueProviderAsync([NotNull] RequestContext requestContext) public IValueProvider GetValueProvider([NotNull] RequestContext requestContext)
{ {
// Process the query collection once-per request. // Process the query collection once-per request.
var storage = requestContext.HttpContext.Items; var storage = requestContext.HttpContext.Items;
@ -27,7 +25,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
provider = (ReadableStringCollectionValueProvider)value; provider = (ReadableStringCollectionValueProvider)value;
} }
return Task.FromResult(provider); return provider;
} }
} }
} }

View File

@ -1,7 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // 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.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -14,7 +16,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
private readonly CultureInfo _culture; private readonly CultureInfo _culture;
private PrefixContainer _prefixContainer; private PrefixContainer _prefixContainer;
private readonly IReadableStringCollection _values; private readonly Func<Task<IReadableStringCollection>> _valuesFactory;
private IReadableStringCollection _values;
/// <summary> /// <summary>
/// Creates a NameValuePairsProvider wrapping an existing set of key value pairs. /// Creates a NameValuePairsProvider wrapping an existing set of key value pairs.
@ -27,6 +30,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
_culture = culture; _culture = culture;
} }
public ReadableStringCollectionValueProvider([NotNull] Func<Task<IReadableStringCollection>> valuesFactory,
CultureInfo culture)
{
_valuesFactory = valuesFactory;
_culture = culture;
}
public CultureInfo Culture public CultureInfo Culture
{ {
get get
@ -35,35 +45,24 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
} }
} }
private PrefixContainer PrefixContainer public virtual async Task<bool> ContainsPrefixAsync(string prefix)
{ {
get var prefixContainer = await GetPrefixContainerAsync();
{ return prefixContainer.ContainsPrefix(prefix);
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;
}
} }
public virtual Task<bool> ContainsPrefixAsync(string prefix) public virtual async Task<IDictionary<string, string>> GetKeysFromPrefixAsync([NotNull] string prefix)
{ {
return Task.FromResult(PrefixContainer.ContainsPrefix(prefix)); var prefixContainer = await GetPrefixContainerAsync();
return prefixContainer.GetKeysFromPrefix(prefix);
} }
public virtual IDictionary<string, string> GetKeysFromPrefix([NotNull] string prefix) public virtual async Task<ValueProviderResult> GetValueAsync([NotNull] string key)
{ {
return PrefixContainer.GetKeysFromPrefix(prefix); var collection = await GetValueCollectionAsync();
} var values = collection.GetValues(key);
public virtual Task<ValueProviderResult> GetValueAsync([NotNull] string key)
{
ValueProviderResult result; ValueProviderResult result;
var values = _values.GetValues(key);
if (values == null) if (values == null)
{ {
result = null; result = null;
@ -78,7 +77,31 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
result = new ValueProviderResult(values, _values.Get(key), _culture); result = new ValueProviderResult(values, _values.Get(key), _culture);
} }
return Task.FromResult(result); return result;
}
private async Task<IReadableStringCollection> GetValueCollectionAsync()
{
if (_values == null)
{
Contract.Assert(_valuesFactory != null);
_values = await _valuesFactory();
}
return _values;
}
private async Task<PrefixContainer> 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;
} }
} }
} }

View File

@ -1,16 +1,13 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // 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 namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
public class RouteValueValueProviderFactory : IValueProviderFactory public class RouteValueValueProviderFactory : IValueProviderFactory
{ {
public Task<IValueProvider> GetValueProviderAsync(RequestContext requestContext) public IValueProvider GetValueProvider(RequestContext requestContext)
{ {
var valueProvider = new DictionaryBasedValueProvider(requestContext.RouteValues); return new DictionaryBasedValueProvider(requestContext.RouteValues);
return Task.FromResult<IValueProvider>(valueProvider);
} }
} }
} }

View File

@ -14,14 +14,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public class FormValueProviderFactoryTests public class FormValueProviderFactoryTests
{ {
[Fact] [Fact]
public async Task GetValueProvider_ReturnsNull_WhenContentTypeIsNotFormUrlEncoded() public void GetValueProvider_ReturnsNull_WhenContentTypeIsNotFormUrlEncoded()
{ {
// Arrange // Arrange
var requestContext = CreateRequestContext("some-content-type"); var requestContext = CreateRequestContext("some-content-type");
var factory = new FormValueProviderFactory(); var factory = new FormValueProviderFactory();
// Act // Act
var result = await factory.GetValueProviderAsync(requestContext); var result = factory.GetValueProvider(requestContext);
// Assert // Assert
Assert.Null(result); Assert.Null(result);
@ -30,14 +30,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
[Theory] [Theory]
[InlineData("application/x-www-form-urlencoded")] [InlineData("application/x-www-form-urlencoded")]
[InlineData("application/x-www-form-urlencoded;charset=utf-8")] [InlineData("application/x-www-form-urlencoded;charset=utf-8")]
public async Task GetValueProvider_ReturnsValueProviderInstaceWithInvariantCulture(string contentType) public void GetValueProvider_ReturnsValueProviderInstaceWithInvariantCulture(string contentType)
{ {
// Arrange // Arrange
var requestContext = CreateRequestContext(contentType); var requestContext = CreateRequestContext(contentType);
var factory = new FormValueProviderFactory(); var factory = new FormValueProviderFactory();
// Act // Act
var result = await factory.GetValueProviderAsync(requestContext); var result = factory.GetValueProvider(requestContext);
// Assert // Assert
var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider>(result); var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider>(result);

View File

@ -3,7 +3,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
#if NET45 #if NET45
using Moq; using Moq;
@ -18,7 +17,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
#if NET45 #if NET45
[Fact] [Fact]
public async Task GetValueProvider_ReturnsQueryStringValueProviderInstaceWithInvariantCulture() public void GetValueProvider_ReturnsQueryStringValueProviderInstaceWithInvariantCulture()
{ {
// Arrange // Arrange
var request = new Mock<HttpRequest>(); var request = new Mock<HttpRequest>();
@ -29,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var requestContext = new RequestContext(context.Object, new Dictionary<string, object>()); var requestContext = new RequestContext(context.Object, new Dictionary<string, object>());
// Act // Act
var result = await _factory.GetValueProviderAsync(requestContext); var result = _factory.GetValueProvider(requestContext);
// Assert // Assert
var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider>(result); var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider>(result);

View File

@ -24,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
[Fact] [Fact]
public async Task ContainsPrefix_WithEmptyCollection_ReturnsFalseForEmptyPrefix() public async Task ContainsPrefixAsync_WithEmptyCollection_ReturnsFalseForEmptyPrefix()
{ {
// Arrange // Arrange
var backingStore = new ReadableStringCollection(new Dictionary<string, string[]>()); var backingStore = new ReadableStringCollection(new Dictionary<string, string[]>());
@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
} }
[Fact] [Fact]
public async Task ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForEmptyPrefix() public async Task ContainsPrefixAsync_WithNonEmptyCollection_ReturnsTrueForEmptyPrefix()
{ {
// Arrange // Arrange
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
@ -51,7 +51,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
} }
[Fact] [Fact]
public async Task ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForKnownPrefixes() public async Task ContainsPrefixAsync_WithNonEmptyCollection_ReturnsTrueForKnownPrefixes()
{ {
// Arrange // Arrange
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
@ -63,7 +63,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
} }
[Fact] [Fact]
public async Task ContainsPrefix_WithNonEmptyCollection_ReturnsFalseForUnknownPrefix() public async Task ContainsPrefixAsync_WithNonEmptyCollection_ReturnsFalseForUnknownPrefix()
{ {
// Arrange // Arrange
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
@ -76,13 +76,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
} }
[Fact] [Fact]
public void GetKeysFromPrefix_EmptyPrefix_ReturnsAllPrefixes() public async Task GetKeysFromPrefixAsync_EmptyPrefix_ReturnsAllPrefixes()
{ {
// Arrange // Arrange
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
// Act // Act
IDictionary<string, string> result = valueProvider.GetKeysFromPrefix(""); var result = await valueProvider.GetKeysFromPrefixAsync("");
// Assert // Assert
Assert.Equal<KeyValuePair<string, string>>( Assert.Equal<KeyValuePair<string, string>>(
@ -91,35 +91,35 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
} }
[Fact] [Fact]
public void GetKeysFromPrefix_UnknownPrefix_ReturnsEmptyDictionary() public async Task GetKeysFromPrefixAsync_UnknownPrefix_ReturnsEmptyDictionary()
{ {
// Arrange // Arrange
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
// Act // Act
IDictionary<string, string> result = valueProvider.GetKeysFromPrefix("abc"); var result = await valueProvider.GetKeysFromPrefixAsync("abc");
// Assert // Assert
Assert.Empty(result); Assert.Empty(result);
} }
[Fact] [Fact]
public void GetKeysFromPrefix_KnownPrefix_ReturnsMatchingItems() public async Task GetKeysFromPrefixAsync_KnownPrefix_ReturnsMatchingItems()
{ {
// Arrange // Arrange
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
// Act // Act
IDictionary<string, string> result = valueProvider.GetKeysFromPrefix("bar"); var result = await valueProvider.GetKeysFromPrefixAsync("bar");
// Assert // Assert
KeyValuePair<string, string> kvp = Assert.Single(result); var kvp = Assert.Single(result);
Assert.Equal("baz", kvp.Key); Assert.Equal("baz", kvp.Key);
Assert.Equal("bar.baz", kvp.Value); Assert.Equal("bar.baz", kvp.Value);
} }
[Fact] [Fact]
public async Task GetValue_SingleValue() public async Task GetValueAsync_SingleValue()
{ {
// Arrange // Arrange
var culture = new CultureInfo("fr-FR"); var culture = new CultureInfo("fr-FR");
@ -136,7 +136,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
} }
[Fact] [Fact]
public async Task GetValue_MultiValue() public async Task GetValueAsync_MultiValue()
{ {
// Arrange // Arrange
var culture = new CultureInfo("fr-FR"); var culture = new CultureInfo("fr-FR");
@ -174,7 +174,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
//} //}
[Fact] [Fact]
public async Task GetValue_NullMultipleValue() public async Task GetValueAsync_NullMultipleValue()
{ {
// Arrange // Arrange
var backingStore = new ReadableStringCollection( var backingStore = new ReadableStringCollection(
@ -194,7 +194,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
} }
[Fact] [Fact]
public async Task GetValue_ReturnsNullIfKeyNotFound() public async Task GetValueAsync_ReturnsNullIfKeyNotFound()
{ {
// Arrange // Arrange
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);