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 namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
public interface IValueProviderFactory public interface IValueProviderFactory
@ -8,6 +9,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>
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; using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
@ -8,24 +8,28 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
private static readonly object _cacheKey = new object(); private static readonly object _cacheKey = new object();
public IValueProvider GetValueProvider(RequestContext requestContext) public Task<IValueProvider> GetValueProviderAsync(RequestContext requestContext)
{ {
if (requestContext == null) if (requestContext == null)
{ {
throw Error.ArgumentNull("requestContext"); throw Error.ArgumentNull("requestContext");
} }
// Process the query string once-per request. // Process the query collection once-per request.
IDictionary<object, object> storage = requestContext.HttpContext.Items; var storage = requestContext.HttpContext.Items;
object value; object value;
IValueProvider provider;
if (!storage.TryGetValue(_cacheKey, out value)) 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; storage[_cacheKey] = provider;
return provider;
} }
else
return (QueryStringValueProvider)value; {
provider = (ReadableStringCollectionValueProvider)value;
}
return Task.FromResult(provider);
} }
} }
} }

View File

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

View File

@ -1,11 +1,14 @@
 
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
public class RouteValueValueProviderFactory : IValueProviderFactory 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
@ -16,7 +17,7 @@ namespace Microsoft.AspNet.Mvc
_valueProviderFactory = valueProviderFactories; _valueProviderFactory = valueProviderFactories;
} }
public ActionDescriptor Select(RequestContext context) public async Task<ActionDescriptor> SelectAsync(RequestContext context)
{ {
if (context == null) if (context == null)
{ {
@ -36,7 +37,7 @@ namespace Microsoft.AspNet.Mvc
} }
else 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))); (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>(); var applicableCandiates = new List<ActionDescriptorCandidate>();
foreach (var action in candidates) 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 public interface IActionSelector
{ {
ActionDescriptor Select(RequestContext context); Task<ActionDescriptor> SelectAsync(RequestContext context);
bool Match(ActionDescriptor descriptor, 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 routeValues = context.GetFeature<IRouteValues>();
var requestContext = new RequestContext(context, routeValues.Values); var requestContext = new RequestContext(context, routeValues.Values);
var actionDescriptor = ActionSelector.Select(requestContext); var actionDescriptor = await ActionSelector.SelectAsync(requestContext);
if (actionDescriptor == null) 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.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Threading.Tasks;
using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.Abstractions;
using Moq; using Moq;
using Xunit; using Xunit;
@ -14,11 +15,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void GetValueProvider_WhenrequestContextParameterIsNull_Throws() public void GetValueProvider_WhenrequestContextParameterIsNull_Throws()
{ {
// Act and Assert // Act and Assert
ExceptionAssert.ThrowsArgumentNull(() => _factory.GetValueProvider(requestContext: null), "requestContext"); ExceptionAssert.ThrowsArgumentNull(() => _factory.GetValueProviderAsync(requestContext: null), "requestContext");
} }
[Fact] [Fact]
public void GetValueProvider_ReturnsQueryStringValueProviderInstaceWithInvariantCulture() public async Task GetValueProvider_ReturnsQueryStringValueProviderInstaceWithInvariantCulture()
{ {
// Arrange // Arrange
var request = new Mock<HttpRequest>(); 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>()); var requestContext = new RequestContext(context.Object, new Dictionary<string, object>());
// Act // Act
IValueProvider result = _factory.GetValueProvider(requestContext); var result = await _factory.GetValueProviderAsync(requestContext);
// Assert // Assert
var valueProvider = Assert.IsType<QueryStringValueProvider>(result); var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider>(result);
Assert.Equal(CultureInfo.InvariantCulture, valueProvider.Culture); Assert.Equal(CultureInfo.InvariantCulture, valueProvider.Culture);
} }
} }

View File

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