[Fixes #1389] Can't bind complex type data from route parameters.

Changed DictionaryBasedValueProvider to do a prefix check instead of just checking if the underlying
dictionary contains the key.
This commit is contained in:
jacalvar 2014-10-17 13:13:14 -07:00
parent 66f626b828
commit 98d749d03c
4 changed files with 114 additions and 3 deletions

View File

@ -4,6 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
@ -11,6 +12,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
where TBinderMarker : IValueBinderMarker where TBinderMarker : IValueBinderMarker
{ {
private readonly IDictionary<string, object> _values; private readonly IDictionary<string, object> _values;
private PrefixContainer _prefixContainer;
public DictionaryBasedValueProvider(IDictionary<string, object> values) public DictionaryBasedValueProvider(IDictionary<string, object> values)
{ {
@ -19,7 +21,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public override Task<bool> ContainsPrefixAsync(string key) public override Task<bool> ContainsPrefixAsync(string key)
{ {
return Task.FromResult(_values.ContainsKey(key)); var prefixContainer = GetOrCreatePrefixContainer();
return Task.FromResult(prefixContainer.ContainsPrefix(key));
}
private PrefixContainer GetOrCreatePrefixContainer()
{
if (_prefixContainer == null)
{
_prefixContainer = new PrefixContainer(_values.Keys);
}
return _prefixContainer;
} }
public override Task<ValueProviderResult> GetValueAsync([NotNull] string key) public override Task<ValueProviderResult> GetValueAsync([NotNull] string key)

View File

@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var client = server.CreateClient(); var client = server.CreateClient();
// Provide all three values, it should bind based on the attribute on the action method. // Provide all three values, it should bind based on the attribute on the action method.
var request = new HttpRequestMessage(HttpMethod.Post, var request = new HttpRequestMessage(HttpMethod.Post,
string.Format("http://localhost/CompositeTest/{0}/valueFromRoute?param=valueFromQuery", actionName)); string.Format("http://localhost/CompositeTest/{0}/valueFromRoute?param=valueFromQuery", actionName));
var nameValueCollection = new List<KeyValuePair<string, string>> var nameValueCollection = new List<KeyValuePair<string, string>>
{ {
@ -107,6 +107,31 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal(12, person.Age); Assert.Equal(12, person.Age);
} }
[Theory]
[InlineData("http://localhost/Home/ActionWithPersonFromUrlWithPrefix/Javier/26")]
[InlineData("http://localhost/Home/ActionWithPersonFromUrlWithoutPrefix/Javier/26")]
public async Task CanBind_ComplexData_FromRouteData(string url)
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
// Act
var response = await
client.GetAsync(url);
//Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.NotNull(body);
var person = JsonConvert.DeserializeObject<Person>(body);
Assert.NotNull(person);
Assert.Equal("Javier", person.Name);
Assert.Equal(26, person.Age);
}
[Fact] [Fact]
public async Task ModelBindCancellationTokenParameteres() public async Task ModelBindCancellationTokenParameteres()
{ {

View File

@ -8,7 +8,7 @@ using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
public class DictionaryBasedValueProviderTestss public class DictionaryBasedValueProviderTests
{ {
[Fact] [Fact]
public async Task GetValueProvider_ReturnsNull_WhenKeyIsNotFound() public async Task GetValueProvider_ReturnsNull_WhenKeyIsNotFound()
@ -63,6 +63,67 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Null(result.AttemptedValue); Assert.Null(result.AttemptedValue);
} }
[Theory]
[InlineData("foo")]
[InlineData("bar")]
[InlineData("bar.baz")]
public async Task ContainsPrefixAsync_ReturnsTrue_ForKnownPrefixes(string prefix)
{
// Arrange
var values = new Dictionary<string, object>
{
{ "foo", 1 },
{ "bar.baz", 1 },
};
var valueProvider = new DictionaryBasedValueProvider<TestValueBinderMarker>(values);
// Act
var result = await valueProvider.ContainsPrefixAsync(prefix);
// Assert
Assert.True(result);
}
[Theory]
[InlineData("bar", "1")]
[InlineData("bar.baz", "2")]
public async Task GetValueAsync_ReturnsCorrectValue_ForKnownKeys(string prefix, string expectedValue)
{
// Arrange
var values = new Dictionary<string, object>
{
{ "bar", 1 },
{ "bar.baz", 2 },
};
var valueProvider = new DictionaryBasedValueProvider<TestValueBinderMarker>(values);
// Act
var result = await valueProvider.GetValueAsync(prefix);
// Assert
Assert.Equal(expectedValue, (string)result.AttemptedValue);
}
[Fact]
public async Task GetValueAsync_DoesNotReturnAValue_ForAKeyPrefix()
{
// Arrange
var values = new Dictionary<string, object>
{
{ "bar.baz", 2 },
};
var valueProvider = new DictionaryBasedValueProvider<TestValueBinderMarker>(values);
// Act
var result = await valueProvider.GetValueAsync("bar");
// Assert
Assert.Null(result);
}
[Fact] [Fact]
public async Task ContainsPrefixAsync_ReturnsFalse_IfKeyIsNotPresent() public async Task ContainsPrefixAsync_ReturnsFalse_IfKeyIsNotPresent()
{ {

View File

@ -37,6 +37,18 @@ namespace ModelBindingWebSite.Controllers
return wrapper.CancellationToken == ActionContext.HttpContext.RequestAborted; return wrapper.CancellationToken == ActionContext.HttpContext.RequestAborted;
} }
[HttpGet("Home/ActionWithPersonFromUrlWithPrefix/{person.name}/{person.age}")]
public Person ActionWithPersonFromUrlWithPrefix([FromRoute] Person person)
{
return person;
}
[HttpGet("Home/ActionWithPersonFromUrlWithoutPrefix/{name}/{age}")]
public Person ActionWithPersonFromUrlWithoutPrefix([FromRoute] Person person)
{
return person;
}
private Dictionary<string, string> CreateValidationDictionary() private Dictionary<string, string> CreateValidationDictionary()
{ {
var result = new Dictionary<string, string>(); var result = new Dictionary<string, string>();