From 98d749d03ccbc7c01b7aa6a4b38d70da39e56c2e Mon Sep 17 00:00:00 2001 From: jacalvar Date: Fri, 17 Oct 2014 13:13:14 -0700 Subject: [PATCH] [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. --- .../DictionaryBasedValueProvider.cs | 15 ++++- .../ModelBindingTests.cs | 27 +++++++- .../DictionaryBasedValueProviderTests.cs | 63 ++++++++++++++++++- .../Controllers/HomeController.cs | 12 ++++ 4 files changed, 114 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/DictionaryBasedValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/DictionaryBasedValueProvider.cs index 33679edf5b..1dedf508ce 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/DictionaryBasedValueProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/DictionaryBasedValueProvider.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.ModelBinding.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding { @@ -11,6 +12,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding where TBinderMarker : IValueBinderMarker { private readonly IDictionary _values; + private PrefixContainer _prefixContainer; public DictionaryBasedValueProvider(IDictionary values) { @@ -19,7 +21,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public override Task 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 GetValueAsync([NotNull] string key) diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs index 01ed7af2b9..f1d72fe24e 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs @@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); // 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)); var nameValueCollection = new List> { @@ -107,6 +107,31 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests 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(body); + Assert.NotNull(person); + Assert.Equal("Javier", person.Name); + Assert.Equal(26, person.Age); + } + [Fact] public async Task ModelBindCancellationTokenParameteres() { diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/DictionaryBasedValueProviderTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/DictionaryBasedValueProviderTests.cs index 386cc9a8e6..d76ef52f8b 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/DictionaryBasedValueProviderTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/DictionaryBasedValueProviderTests.cs @@ -8,7 +8,7 @@ using Xunit; namespace Microsoft.AspNet.Mvc.ModelBinding { - public class DictionaryBasedValueProviderTestss + public class DictionaryBasedValueProviderTests { [Fact] public async Task GetValueProvider_ReturnsNull_WhenKeyIsNotFound() @@ -63,6 +63,67 @@ namespace Microsoft.AspNet.Mvc.ModelBinding 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 + { + { "foo", 1 }, + { "bar.baz", 1 }, + }; + + var valueProvider = new DictionaryBasedValueProvider(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 + { + { "bar", 1 }, + { "bar.baz", 2 }, + }; + + var valueProvider = new DictionaryBasedValueProvider(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 + { + { "bar.baz", 2 }, + }; + + var valueProvider = new DictionaryBasedValueProvider(values); + + // Act + var result = await valueProvider.GetValueAsync("bar"); + + // Assert + Assert.Null(result); + } + [Fact] public async Task ContainsPrefixAsync_ReturnsFalse_IfKeyIsNotPresent() { diff --git a/test/WebSites/ModelBindingWebSite/Controllers/HomeController.cs b/test/WebSites/ModelBindingWebSite/Controllers/HomeController.cs index a6ff4da7f7..a4407e9592 100644 --- a/test/WebSites/ModelBindingWebSite/Controllers/HomeController.cs +++ b/test/WebSites/ModelBindingWebSite/Controllers/HomeController.cs @@ -37,6 +37,18 @@ namespace ModelBindingWebSite.Controllers 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 CreateValidationDictionary() { var result = new Dictionary();