Introduce FormValueProviderFactory to expose form data as a value provider

This commit is contained in:
Pranav K 2014-02-25 17:50:39 -08:00
parent 04c7b50726
commit d920003194
12 changed files with 154 additions and 56 deletions

View File

@ -0,0 +1,40 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class FormValueProviderFactory : IValueProviderFactory
{
private const string FormEncodedContentType = "application/x-www-form-urlencoded";
public async Task<IValueProvider> GetValueProviderAsync(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 null;
}
private bool IsSupportedContentType(HttpRequest request)
{
var contentType = request.Headers["Content-Type"];
return !String.IsNullOrEmpty(contentType) &&
contentType.Equals(FormEncodedContentType, StringComparison.OrdinalIgnoreCase);
}
private static CultureInfo GetCultureInfo(HttpRequest request)
{
// TODO: Tracked via https://github.com/aspnet/HttpAbstractions/issues/10. Determine what's the right way to
// map Accept-Language to culture.
return CultureInfo.CurrentCulture;
}
}
}

View File

@ -1,4 +1,5 @@

using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public interface IValueProviderFactory
@ -8,6 +9,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary>
/// <param name="requestContext">RequestContext that value provider will populate from</param>
/// <returns>a value provider instance or null</returns>
IValueProvider GetValueProvider(RequestContext requestContext);
Task<IValueProvider> GetValueProviderAsync(RequestContext requestContext);
}
}

View File

@ -1,13 +0,0 @@
using System.Globalization;
using Microsoft.AspNet.Abstractions;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class QueryStringValueProvider : NameValuePairsValueProvider
{
public QueryStringValueProvider(HttpContext context, CultureInfo culture)
: base(context.Request.Query, culture)
{
}
}
}

View File

@ -1,5 +1,5 @@
using System.Collections.Generic;
using System.Globalization;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
@ -8,24 +8,28 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
private static readonly object _cacheKey = new object();
public IValueProvider GetValueProvider(RequestContext requestContext)
public Task<IValueProvider> GetValueProviderAsync(RequestContext requestContext)
{
if (requestContext == null)
{
throw Error.ArgumentNull("requestContext");
}
// Process the query string once-per request.
IDictionary<object, object> storage = requestContext.HttpContext.Items;
// Process the query collection once-per request.
var storage = requestContext.HttpContext.Items;
object value;
IValueProvider provider;
if (!storage.TryGetValue(_cacheKey, out value))
{
var provider = new QueryStringValueProvider(requestContext.HttpContext, CultureInfo.InvariantCulture);
var queryCollection = requestContext.HttpContext.Request.Query;
provider = new ReadableStringCollectionValueProvider(queryCollection, CultureInfo.InvariantCulture);
storage[_cacheKey] = provider;
return provider;
}
return (QueryStringValueProvider)value;
else
{
provider = (ReadableStringCollectionValueProvider)value;
}
return Task.FromResult(provider);
}
}
}

View File

@ -6,7 +6,7 @@ using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class NameValuePairsValueProvider : IEnumerableValueProvider
public class ReadableStringCollectionValueProvider : IEnumerableValueProvider
{
private readonly CultureInfo _culture;
private PrefixContainer _prefixContainer;
@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// </summary>
/// <param name="values">The key value pairs to wrap.</param>
/// <param name="culture">The culture to return with ValueProviderResult instances.</param>
public NameValuePairsValueProvider(IReadableStringCollection values, CultureInfo culture)
public ReadableStringCollectionValueProvider(IReadableStringCollection values, CultureInfo culture)
{
if (values == null)
{

View File

@ -1,11 +1,14 @@

using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class RouteValueValueProviderFactory : IValueProviderFactory
{
public IValueProvider GetValueProvider(RequestContext requestContext)
public Task<IValueProvider> GetValueProviderAsync(RequestContext requestContext)
{
return new DictionaryBasedValueProvider(requestContext.RouteValues);
var valueProvider = new DictionaryBasedValueProvider(requestContext.RouteValues);
return Task.FromResult<IValueProvider>(valueProvider);
}
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
@ -16,7 +17,7 @@ namespace Microsoft.AspNet.Mvc
_valueProviderFactory = valueProviderFactories;
}
public ActionDescriptor Select(RequestContext context)
public async Task<ActionDescriptor> SelectAsync(RequestContext context)
{
if (context == null)
{
@ -36,7 +37,7 @@ namespace Microsoft.AspNet.Mvc
}
else
{
return SelectBestCandidate(context, matching);
return await SelectBestCandidate(context, matching);
}
}
@ -52,9 +53,10 @@ namespace Microsoft.AspNet.Mvc
(descriptor.DynamicConstraints == null || descriptor.DynamicConstraints.All(c => c.Accept(context)));
}
protected virtual ActionDescriptor SelectBestCandidate(RequestContext context, List<ActionDescriptor> candidates)
protected virtual async Task<ActionDescriptor> SelectBestCandidate(RequestContext context, List<ActionDescriptor> candidates)
{
var valueProviders = _valueProviderFactory.Select(vpf => vpf.GetValueProvider(context)).ToArray();
var valueProviders = await Task.WhenAll(_valueProviderFactory.Select(vpf => vpf.GetValueProviderAsync(context)));
valueProviders = valueProviders.Where(vp => vp != null).ToArray();
var applicableCandiates = new List<ActionDescriptorCandidate>();
foreach (var action in candidates)

View File

@ -1,8 +1,10 @@
namespace Microsoft.AspNet.Mvc
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
public interface IActionSelector
{
ActionDescriptor Select(RequestContext context);
Task<ActionDescriptor> SelectAsync(RequestContext context);
bool Match(ActionDescriptor descriptor, RequestContext context);
}

View File

@ -51,7 +51,7 @@ namespace Microsoft.AspNet.Mvc.Routing
var routeValues = context.GetFeature<IRouteValues>();
var requestContext = new RequestContext(context, routeValues.Values);
var actionDescriptor = ActionSelector.Select(requestContext);
var actionDescriptor = await ActionSelector.SelectAsync(requestContext);
if (actionDescriptor == null)
{

View File

@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNet.Abstractions;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
public class FormValueProviderFactoryTests
{
[Fact]
public async Task GetValueProvider_ReturnsNull_WhenContentTypeIsNotFormUrlEncoded()
{
// Arrange
var requestContext = CreateRequestContext("some-content-type");
var factory = new FormValueProviderFactory();
// Act
var result = await factory.GetValueProviderAsync(requestContext);
// Assert
Assert.Null(result);
}
[Fact]
public async Task GetValueProvider_ReturnsValueProviderInstaceWithInvariantCulture()
{
// Arrange
var requestContext = CreateRequestContext("application/x-www-form-urlencoded");
var factory = new FormValueProviderFactory();
// Act
var result = await factory.GetValueProviderAsync(requestContext);
// Assert
var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider>(result);
Assert.Equal(CultureInfo.CurrentCulture, valueProvider.Culture);
}
private static RequestContext CreateRequestContext(string contentType)
{
var collection = Mock.Of<IReadableStringCollection>();
var request = new Mock<HttpRequest>();
request.Setup(f => f.GetFormAsync()).Returns(Task.FromResult(collection));
var mockHeader = new Mock<IHeaderDictionary>();
mockHeader.Setup(h => h["Content-Type"]).Returns(contentType);
request.SetupGet(r => r.Headers).Returns(mockHeader.Object);
var context = new Mock<HttpContext>();
context.SetupGet(c => c.Request).Returns(request.Object);
var requestContext = new RequestContext(context.Object, new Dictionary<string, object>());
return requestContext;
}
}
}

View File

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNet.Abstractions;
using Moq;
using Xunit;
@ -14,11 +15,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void GetValueProvider_WhenrequestContextParameterIsNull_Throws()
{
// Act and Assert
ExceptionAssert.ThrowsArgumentNull(() => _factory.GetValueProvider(requestContext: null), "requestContext");
ExceptionAssert.ThrowsArgumentNull(() => _factory.GetValueProviderAsync(requestContext: null), "requestContext");
}
[Fact]
public void GetValueProvider_ReturnsQueryStringValueProviderInstaceWithInvariantCulture()
public async Task GetValueProvider_ReturnsQueryStringValueProviderInstaceWithInvariantCulture()
{
// Arrange
var request = new Mock<HttpRequest>();
@ -29,10 +30,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var requestContext = new RequestContext(context.Object, new Dictionary<string, object>());
// Act
IValueProvider result = _factory.GetValueProvider(requestContext);
var result = await _factory.GetValueProviderAsync(requestContext);
// Assert
var valueProvider = Assert.IsType<QueryStringValueProvider>(result);
var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider>(result);
Assert.Equal(CultureInfo.InvariantCulture, valueProvider.Culture);
}
}

View File

@ -7,7 +7,7 @@ using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
public class NameValuePairsValueProviderTest
public class ReadableStringCollectionValueProviderTest
{
private static readonly IReadableStringCollection _backingStore = new ReadableStringCollection(
new Dictionary<string, string[]>
@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
// Act & assert
ExceptionAssert.ThrowsArgumentNull(
() => new NameValuePairsValueProvider(values: null, culture: CultureInfo.InvariantCulture),
() => new ReadableStringCollectionValueProvider(values: null, culture: CultureInfo.InvariantCulture),
"values");
}
@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void ContainsPrefix_GuardClauses()
{
// Arrange
var valueProvider = new NameValuePairsValueProvider(_backingStore, null);
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
// Act & assert
ExceptionAssert.ThrowsArgumentNull(
@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
// Arrange
var backingStore = new ReadableStringCollection(new Dictionary<string, string[]>());
var valueProvider = new NameValuePairsValueProvider(backingStore, null);
var valueProvider = new ReadableStringCollectionValueProvider(backingStore, null);
// Act
bool result = valueProvider.ContainsPrefix("");
@ -57,7 +57,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForEmptyPrefix()
{
// Arrange
var valueProvider = new NameValuePairsValueProvider(_backingStore, null);
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
// Act
bool result = valueProvider.ContainsPrefix("");
@ -70,7 +70,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForKnownPrefixes()
{
// Arrange
var valueProvider = new NameValuePairsValueProvider(_backingStore, null);
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
// Act & Assert
Assert.True(valueProvider.ContainsPrefix("foo"));
@ -82,7 +82,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void ContainsPrefix_WithNonEmptyCollection_ReturnsFalseForUnknownPrefix()
{
// Arrange
var valueProvider = new NameValuePairsValueProvider(_backingStore, null);
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
// Act
bool result = valueProvider.ContainsPrefix("biff");
@ -95,7 +95,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void GetKeysFromPrefix_GuardClauses()
{
// Arrange
var valueProvider = new NameValuePairsValueProvider(_backingStore, null);
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
// Act & assert
ExceptionAssert.ThrowsArgumentNull(
@ -107,7 +107,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void GetKeysFromPrefix_EmptyPrefix_ReturnsAllPrefixes()
{
// Arrange
var valueProvider = new NameValuePairsValueProvider(_backingStore, null);
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
// Act
IDictionary<string, string> result = valueProvider.GetKeysFromPrefix("");
@ -122,7 +122,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void GetKeysFromPrefix_UnknownPrefix_ReturnsEmptyDictionary()
{
// Arrange
var valueProvider = new NameValuePairsValueProvider(_backingStore, null);
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
// Act
IDictionary<string, string> result = valueProvider.GetKeysFromPrefix("abc");
@ -135,7 +135,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void GetKeysFromPrefix_KnownPrefix_ReturnsMatchingItems()
{
// Arrange
var valueProvider = new NameValuePairsValueProvider(_backingStore, null);
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
// Act
IDictionary<string, string> result = valueProvider.GetKeysFromPrefix("bar");
@ -150,7 +150,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void GetValue_GuardClauses()
{
// Arrange
var valueProvider = new NameValuePairsValueProvider(_backingStore, null);
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
// Act & assert
ExceptionAssert.ThrowsArgumentNull(
@ -163,7 +163,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
// Arrange
var culture = CultureInfo.GetCultureInfo("fr-FR");
var valueProvider = new NameValuePairsValueProvider(_backingStore, culture);
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, culture);
// Act
ValueProviderResult vpResult = valueProvider.GetValue("bar.baz");
@ -180,7 +180,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{
// Arrange
var culture = CultureInfo.GetCultureInfo("fr-FR");
var valueProvider = new NameValuePairsValueProvider(_backingStore, culture);
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, culture);
// Act
ValueProviderResult vpResult = valueProvider.GetValue("foo");
@ -201,7 +201,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
//{
// // Arrange
// var culture = CultureInfo.GetCultureInfo("fr-FR");
// var valueProvider = new NameValuePairsValueProvider(_backingStore, culture);
// var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, culture);
// // Act
// ValueProviderResult vpResult = valueProvider.GetValue(key);
@ -223,7 +223,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
{ "key", new string[] { null, null, "value" } }
});
var culture = CultureInfo.GetCultureInfo("fr-FR");
var valueProvider = new NameValuePairsValueProvider(backingStore, culture);
var valueProvider = new ReadableStringCollectionValueProvider(backingStore, culture);
// Act
ValueProviderResult vpResult = valueProvider.GetValue("key");
@ -237,7 +237,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void GetValue_ReturnsNullIfKeyNotFound()
{
// Arrange
var valueProvider = new NameValuePairsValueProvider(_backingStore, null);
var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null);
// Act
ValueProviderResult vpResult = valueProvider.GetValue("bar");