Merge branch 'release/2.1' into dev

This commit is contained in:
Doug Bunting 2018-03-19 13:08:36 -07:00
commit 1aeaf69f87
No known key found for this signature in database
GPG Key ID: 888B4EB7822B32E9
12 changed files with 507 additions and 40 deletions

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc
/// A type that wraps either an <typeparamref name="TValue"/> instance or an <see cref="ActionResult"/>. /// A type that wraps either an <typeparamref name="TValue"/> instance or an <see cref="ActionResult"/>.
/// </summary> /// </summary>
/// <typeparam name="TValue">The type of the result.</typeparam> /// <typeparam name="TValue">The type of the result.</typeparam>
public class ActionResult<TValue> : IConvertToActionResult public sealed class ActionResult<TValue> : IConvertToActionResult
{ {
/// <summary> /// <summary>
/// Initializes a new instance of <see cref="ActionResult{TValue}"/> using the specified <paramref name="value"/>. /// Initializes a new instance of <see cref="ActionResult{TValue}"/> using the specified <paramref name="value"/>.

View File

@ -12,6 +12,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// </summary> /// </summary>
public class DefaultModelBindingContext : ModelBindingContext public class DefaultModelBindingContext : ModelBindingContext
{ {
private static readonly IValueProvider EmptyValueProvider = new CompositeValueProvider();
private IValueProvider _originalValueProvider; private IValueProvider _originalValueProvider;
private ActionContext _actionContext; private ActionContext _actionContext;
private ModelStateDictionary _modelState; private ModelStateDictionary _modelState;
@ -314,13 +316,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
return valueProvider; return valueProvider;
} }
var bindingSourceValueProvider = valueProvider as IBindingSourceValueProvider; if (!(valueProvider is IBindingSourceValueProvider bindingSourceValueProvider))
if (bindingSourceValueProvider == null)
{ {
return valueProvider; return valueProvider;
} }
return bindingSourceValueProvider.Filter(bindingSource) ?? new CompositeValueProvider(); return bindingSourceValueProvider.Filter(bindingSource) ?? EmptyValueProvider;
} }
private struct State private struct State

View File

@ -23,6 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
/// <typeparam name="TElement">Type of elements in the collection.</typeparam> /// <typeparam name="TElement">Type of elements in the collection.</typeparam>
public class CollectionModelBinder<TElement> : ICollectionModelBinder public class CollectionModelBinder<TElement> : ICollectionModelBinder
{ {
private static readonly IValueProvider EmptyValueProvider = new CompositeValueProvider();
private Func<object> _modelCreator; private Func<object> _modelCreator;
/// <summary> /// <summary>
@ -244,7 +245,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders
Logger.AttemptingToBindCollectionUsingIndices(bindingContext); Logger.AttemptingToBindCollectionUsingIndices(bindingContext);
var indexPropertyName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, "index"); var indexPropertyName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, "index");
var valueProviderResultIndex = bindingContext.ValueProvider.GetValue(indexPropertyName);
// Remove any value provider that may not use indexPropertyName as-is. Don't match e.g. Model[index].
var valueProvider = bindingContext.ValueProvider;
if (valueProvider is IKeyRewriterValueProvider keyRewriterValueProvider)
{
valueProvider = keyRewriterValueProvider.Filter() ?? EmptyValueProvider;
}
var valueProviderResultIndex = valueProvider.GetValue(indexPropertyName);
var indexNames = GetIndexNamesFromValueProviderResult(valueProviderResultIndex); var indexNames = GetIndexNamesFromValueProviderResult(valueProviderResultIndex);
return BindComplexCollectionFromIndexes(bindingContext, indexNames); return BindComplexCollectionFromIndexes(bindingContext, indexNames);

View File

@ -4,7 +4,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Mvc.ModelBinding namespace Microsoft.AspNetCore.Mvc.ModelBinding
@ -15,7 +14,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public class CompositeValueProvider : public class CompositeValueProvider :
Collection<IValueProvider>, Collection<IValueProvider>,
IEnumerableValueProvider, IEnumerableValueProvider,
IBindingSourceValueProvider IBindingSourceValueProvider,
IKeyRewriterValueProvider
{ {
/// <summary> /// <summary>
/// Initializes a new instance of <see cref="CompositeValueProvider"/>. /// Initializes a new instance of <see cref="CompositeValueProvider"/>.
@ -117,8 +117,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
foreach (var valueProvider in this) foreach (var valueProvider in this)
{ {
var enumeratedProvider = valueProvider as IEnumerableValueProvider; if (valueProvider is IEnumerableValueProvider enumeratedProvider)
if (enumeratedProvider != null)
{ {
var result = enumeratedProvider.GetKeysFromPrefix(prefix); var result = enumeratedProvider.GetKeysFromPrefix(prefix);
if (result != null && result.Count > 0) if (result != null && result.Count > 0)
@ -160,13 +159,34 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
throw new ArgumentNullException(nameof(bindingSource)); throw new ArgumentNullException(nameof(bindingSource));
} }
var filteredValueProviders = new List<IValueProvider>(); var shouldFilter = false;
foreach (var valueProvider in this.OfType<IBindingSourceValueProvider>()) for (var i = 0; i < Count; i++)
{ {
var result = valueProvider.Filter(bindingSource); var valueProvider = Items[i];
if (result != null) if (valueProvider is IBindingSourceValueProvider)
{ {
filteredValueProviders.Add(result); shouldFilter = true;
break;
}
}
if (!shouldFilter)
{
// No inner IBindingSourceValueProvider implementations. Result will be empty.
return null;
}
var filteredValueProviders = new List<IValueProvider>();
for (var i = 0; i < Count; i++)
{
var valueProvider = Items[i];
if (valueProvider is IBindingSourceValueProvider bindingSourceValueProvider)
{
var result = bindingSourceValueProvider.Filter(bindingSource);
if (result != null)
{
filteredValueProviders.Add(result);
}
} }
} }
@ -176,12 +196,58 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
return null; return null;
} }
if (filteredValueProviders.Count == Count) return new CompositeValueProvider(filteredValueProviders);
}
/// <inheritdoc />
/// <remarks>
/// Value providers are included by default. If a contained <see cref="IValueProvider"/> does not implement
/// <see cref="IKeyRewriterValueProvider"/>, <see cref="Filter()"/> will not remove it.
/// </remarks>
public IValueProvider Filter()
{
var shouldFilter = false;
for (var i = 0; i < Count; i++)
{ {
// No need for a new CompositeValueProvider. var valueProvider = Items[i];
if (valueProvider is IKeyRewriterValueProvider)
{
shouldFilter = true;
break;
}
}
if (!shouldFilter)
{
// No inner IKeyRewriterValueProvider implementations. Nothing to exclude.
return this; return this;
} }
var filteredValueProviders = new List<IValueProvider>();
for (var i = 0; i < Count; i++)
{
var valueProvider = Items[i];
if (valueProvider is IKeyRewriterValueProvider keyRewriterValueProvider)
{
var result = keyRewriterValueProvider.Filter();
if (result != null)
{
filteredValueProviders.Add(result);
}
}
else
{
// Assume value providers that aren't rewriter-aware do not rewrite their keys.
filteredValueProviders.Add(valueProvider);
}
}
if (filteredValueProviders.Count == 0)
{
// Do not create an empty CompositeValueProvider.
return null;
}
return new CompositeValueProvider(filteredValueProviders); return new CompositeValueProvider(filteredValueProviders);
} }
} }

View File

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Mvc.ModelBinding
{
/// <summary>
/// A value provider which can filter its contents to remove keys rewritten compared to the request data.
/// </summary>
public interface IKeyRewriterValueProvider : IValueProvider
{
/// <summary>
/// Filters the value provider to remove keys rewritten compared to the request data.
/// </summary>
/// <example>
/// If the request contains values with keys <c>Model.Property</c> and <c>Collection[index]</c>, the returned
/// <see cref="IValueProvider"/> will not match <c>Model[Property]</c> or <c>Collection.index</c>.
/// </example>
/// <returns>
/// The filtered value provider or <see langref="null"/> if the value provider only contains rewritten keys.
/// </returns>
IValueProvider Filter();
}
}

View File

@ -10,9 +10,12 @@ using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Mvc.ModelBinding namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
/// <summary> /// <summary>
/// An <see cref="IValueProvider"/> for jQuery formatted form data. /// An <see cref="IValueProvider"/> for jQuery formatted data.
/// </summary> /// </summary>
public abstract class JQueryValueProvider : BindingSourceValueProvider, IEnumerableValueProvider public abstract class JQueryValueProvider :
BindingSourceValueProvider,
IEnumerableValueProvider,
IKeyRewriterValueProvider
{ {
private readonly IDictionary<string, StringValues> _values; private readonly IDictionary<string, StringValues> _values;
private PrefixContainer _prefixContainer; private PrefixContainer _prefixContainer;
@ -85,5 +88,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
return ValueProviderResult.None; return ValueProviderResult.None;
} }
/// <inheritdoc />
/// <remarks>
/// Always returns <see langword="null"/> because <see cref="JQueryFormValueProviderFactory"/> creates this
/// <see cref="IValueProvider"/> with rewritten keys (if original contains brackets) or duplicate keys
/// (that <see cref="FormValueProvider"/> will match).
/// </remarks>
public IValueProvider Filter()
{
return null;
}
} }
} }

View File

@ -4,6 +4,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Moq; using Moq;
using Xunit; using Xunit;
@ -12,6 +14,26 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
public class CompositeValueProviderTest : EnumerableValueProviderTest public class CompositeValueProviderTest : EnumerableValueProviderTest
{ {
[Fact]
public override void FilterInclude()
{
// Arrange
var provider = GetBindingSourceValueProvider(BindingSource.Query, BackingStore, culture: null);
var originalProviders = ((CompositeValueProvider)provider).ToArray();
var bindingSource = new BindingSource(
BindingSource.Query.Id,
displayName: null,
isGreedy: true,
isFromRequest: true);
// Act
var result = provider.Filter(bindingSource);
// Assert (does not change inner providers)
var newProvider = Assert.IsType<CompositeValueProvider>(result);
Assert.Equal(originalProviders, newProvider, ReferenceEqualityComparer.Instance);
}
protected override IEnumerableValueProvider GetEnumerableValueProvider( protected override IEnumerableValueProvider GetEnumerableValueProvider(
BindingSource bindingSource, BindingSource bindingSource,
Dictionary<string, StringValues> values, Dictionary<string, StringValues> values,
@ -96,7 +118,113 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
Assert.Same(valueProvider1.Object, filteredProvider); Assert.Same(valueProvider1.Object, filteredProvider);
} }
private Mock<IBindingSourceValueProvider> GetMockValueProvider(string bindingSourceId) public static TheoryData<CompositeValueProvider> Filter_ReturnsProviderData
{
get
{
// None filter themselves out.
var noneRewrite = new[]
{
GetValueProvider(rewritesKeys: false),
GetValueProvider(rewritesKeys: false),
};
// None implement IKeyRewriterValueProvider.
var noneImplement = new[] { GetMockValueProvider("One").Object, GetMockValueProvider("Two").Object };
return new TheoryData<CompositeValueProvider>
{
// Starts empty
new CompositeValueProvider(),
new CompositeValueProvider(noneRewrite),
new CompositeValueProvider(noneImplement),
};
}
}
[Theory]
[MemberData(nameof(Filter_ReturnsProviderData))]
public void Filter_ReturnsProvider(CompositeValueProvider provider)
{
// Arrange
var originalProviders = provider.ToArray();
// Act
var result = provider.Filter();
// Assert (does not change inner providers)
var newProvider = Assert.IsType<CompositeValueProvider>(result);
Assert.Equal(originalProviders, newProvider, ReferenceEqualityComparer.Instance);
}
[Fact]
public void Filter_ReturnsNull()
{
// Arrange
var allRewrite = new[] { GetValueProvider(rewritesKeys: true), GetValueProvider(rewritesKeys: true) };
var provider = new CompositeValueProvider(allRewrite);
// Act
var result = provider.Filter();
// Assert
Assert.Null(result);
}
[Fact]
public void Filter_RemovesThoseThatRewrite()
{
// Arrange
var doesNotRewrite1 = GetValueProvider(rewritesKeys: false);
var doesNotRewrite2 = GetValueProvider(rewritesKeys: false);
var doesNotImplement1 = GetMockValueProvider("One").Object;
var doesNotImplement2 = GetMockValueProvider("Two").Object;
var rewrites1 = GetValueProvider(rewritesKeys: true);
var rewrites2 = GetValueProvider(rewritesKeys: true);
var providers = new IValueProvider[]
{
doesNotRewrite1,
doesNotImplement1,
rewrites1,
doesNotRewrite2,
doesNotImplement2,
rewrites2,
};
var expectedProviders = new IValueProvider[]
{
doesNotRewrite1,
doesNotImplement1,
doesNotRewrite2,
doesNotImplement2,
};
var provider = new CompositeValueProvider(providers);
// Act
var result = provider.Filter();
// Assert
Assert.NotSame(provider, result);
var newProvider = Assert.IsType<CompositeValueProvider>(result);
Assert.Equal(expectedProviders, newProvider, ReferenceEqualityComparer.Instance);
}
private static IKeyRewriterValueProvider GetValueProvider(bool rewritesKeys)
{
var valueProvider = new Mock<IKeyRewriterValueProvider>(MockBehavior.Strict);
if (rewritesKeys)
{
valueProvider.Setup(vp => vp.Filter()).Returns<IValueProvider>(null);
}
else
{
valueProvider.Setup(vp => vp.Filter()).Returns(valueProvider.Object);
}
return valueProvider.Object;
}
private static Mock<IBindingSourceValueProvider> GetMockValueProvider(string bindingSourceId)
{ {
var valueProvider = new Mock<IBindingSourceValueProvider>(MockBehavior.Strict); var valueProvider = new Mock<IBindingSourceValueProvider>(MockBehavior.Strict);

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
public abstract class EnumerableValueProviderTest public abstract class EnumerableValueProviderTest
{ {
private static readonly Dictionary<string, StringValues> _backingStore = new Dictionary<string, StringValues> protected static Dictionary<string, StringValues> BackingStore { get; } = new Dictionary<string, StringValues>
{ {
{ "some", new[] { "someValue1", "someValue2" } }, { "some", new[] { "someValue1", "someValue2" } },
{ "null_value", StringValues.Empty }, { "null_value", StringValues.Empty },
@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForEmptyPrefix() public void ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForEmptyPrefix()
{ {
// Arrange // Arrange
var valueProvider = GetEnumerableValueProvider(BindingSource.Query, _backingStore, culture: null); var valueProvider = GetEnumerableValueProvider(BindingSource.Query, BackingStore, culture: null);
// Act // Act
var result = valueProvider.ContainsPrefix(string.Empty); var result = valueProvider.ContainsPrefix(string.Empty);
@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForKnownPrefixes(string prefix) public void ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForKnownPrefixes(string prefix)
{ {
// Arrange // Arrange
var valueProvider = GetEnumerableValueProvider(BindingSource.Query, _backingStore, culture: null); var valueProvider = GetEnumerableValueProvider(BindingSource.Query, BackingStore, culture: null);
// Act & Assert // Act & Assert
Assert.True(valueProvider.ContainsPrefix(prefix)); Assert.True(valueProvider.ContainsPrefix(prefix));
@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void ContainsPrefix_WithNonEmptyCollection_ReturnsFalseForUnknownPrefix() public void ContainsPrefix_WithNonEmptyCollection_ReturnsFalseForUnknownPrefix()
{ {
// Arrange // Arrange
var valueProvider = GetEnumerableValueProvider(BindingSource.Query, _backingStore, culture: null); var valueProvider = GetEnumerableValueProvider(BindingSource.Query, BackingStore, culture: null);
// Act // Act
var result = valueProvider.ContainsPrefix("biff"); var result = valueProvider.ContainsPrefix("biff");
@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ "prefix", "prefix" }, { "prefix", "prefix" },
{ "some", "some" }, { "some", "some" },
}; };
var valueProvider = GetEnumerableValueProvider(BindingSource.Query, _backingStore, culture: null); var valueProvider = GetEnumerableValueProvider(BindingSource.Query, BackingStore, culture: null);
// Act // Act
var result = valueProvider.GetKeysFromPrefix(string.Empty); var result = valueProvider.GetKeysFromPrefix(string.Empty);
@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void GetKeysFromPrefix_UnknownPrefix_ReturnsEmptyDictionary() public void GetKeysFromPrefix_UnknownPrefix_ReturnsEmptyDictionary()
{ {
// Arrange // Arrange
var valueProvider = GetEnumerableValueProvider(BindingSource.Query, _backingStore, culture: null); var valueProvider = GetEnumerableValueProvider(BindingSource.Query, BackingStore, culture: null);
// Act // Act
var result = valueProvider.GetKeysFromPrefix("abc"); var result = valueProvider.GetKeysFromPrefix("abc");
@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ "index1", "prefix[index1]" }, { "index1", "prefix[index1]" },
{ "index2", "prefix[index2]" }, { "index2", "prefix[index2]" },
}; };
var valueProvider = GetEnumerableValueProvider(BindingSource.Query, _backingStore, culture: null); var valueProvider = GetEnumerableValueProvider(BindingSource.Query, BackingStore, culture: null);
// Act // Act
var result = valueProvider.GetKeysFromPrefix("prefix"); var result = valueProvider.GetKeysFromPrefix("prefix");
@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ "property", "[index].property" }, { "property", "[index].property" },
{ "anotherIndex", "[index][anotherIndex]" } { "anotherIndex", "[index][anotherIndex]" }
}; };
var valueProvider = GetEnumerableValueProvider(BindingSource.Query, _backingStore, culture: null); var valueProvider = GetEnumerableValueProvider(BindingSource.Query, BackingStore, culture: null);
// Act // Act
var result = valueProvider.GetKeysFromPrefix("[index]"); var result = valueProvider.GetKeysFromPrefix("[index]");
@ -161,7 +161,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
// Arrange // Arrange
var culture = new CultureInfo("fr-FR"); var culture = new CultureInfo("fr-FR");
var valueProvider = GetEnumerableValueProvider(BindingSource.Query, _backingStore, culture); var valueProvider = GetEnumerableValueProvider(BindingSource.Query, BackingStore, culture);
// Act // Act
var result = valueProvider.GetValue("prefix.name"); var result = valueProvider.GetValue("prefix.name");
@ -176,7 +176,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
// Arrange // Arrange
var culture = new CultureInfo("fr-FR"); var culture = new CultureInfo("fr-FR");
var valueProvider = GetEnumerableValueProvider(BindingSource.Query, _backingStore, culture); var valueProvider = GetEnumerableValueProvider(BindingSource.Query, BackingStore, culture);
// Act // Act
var result = valueProvider.GetValue("some"); var result = valueProvider.GetValue("some");
@ -194,7 +194,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
// Arrange // Arrange
var culture = new CultureInfo("fr-FR"); var culture = new CultureInfo("fr-FR");
var valueProvider = GetEnumerableValueProvider(BindingSource.Query, _backingStore, culture); var valueProvider = GetEnumerableValueProvider(BindingSource.Query, BackingStore, culture);
// Act // Act
var result = valueProvider.GetValue(key); var result = valueProvider.GetValue(key);
@ -226,7 +226,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void GetValue_ReturnsNullIfKeyNotFound() public void GetValue_ReturnsNullIfKeyNotFound()
{ {
// Arrange // Arrange
var valueProvider = GetEnumerableValueProvider(BindingSource.Query, _backingStore, culture: null); var valueProvider = GetEnumerableValueProvider(BindingSource.Query, BackingStore, culture: null);
// Act // Act
var result = valueProvider.GetValue("prefix"); var result = valueProvider.GetValue("prefix");
@ -236,10 +236,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
} }
[Fact] [Fact]
public void FilterInclude() public virtual void FilterInclude()
{ {
// Arrange // Arrange
var provider = GetBindingSourceValueProvider(BindingSource.Query, _backingStore, culture: null); var provider = GetBindingSourceValueProvider(BindingSource.Query, BackingStore, culture: null);
var bindingSource = new BindingSource( var bindingSource = new BindingSource(
BindingSource.Query.Id, BindingSource.Query.Id,
@ -259,7 +259,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void FilterExclude() public void FilterExclude()
{ {
// Arrange // Arrange
var provider = GetBindingSourceValueProvider(BindingSource.Query, _backingStore, culture: null); var provider = GetBindingSourceValueProvider(BindingSource.Query, BackingStore, culture: null);
var bindingSource = new BindingSource( var bindingSource = new BindingSource(
"Test", "Test",
@ -274,7 +274,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
Assert.Null(result); Assert.Null(result);
} }
private IBindingSourceValueProvider GetBindingSourceValueProvider( protected IBindingSourceValueProvider GetBindingSourceValueProvider(
BindingSource bindingSource, BindingSource bindingSource,
Dictionary<string, StringValues> values, Dictionary<string, StringValues> values,
CultureInfo culture) CultureInfo culture)

View File

@ -4,6 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ModelBinding namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
@ -16,5 +17,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
return new JQueryFormValueProvider(bindingSource, values, culture); return new JQueryFormValueProvider(bindingSource, values, culture);
} }
[Fact]
public void Filter_ExcludesItself()
{
// Arrange
var dictionary = new Dictionary<string, StringValues>();
var provider = new JQueryFormValueProvider(BindingSource.Form, dictionary, CultureInfo.CurrentCulture);
// Act
var result = provider.Filter();
// Assert
Assert.Null(result);
}
} }
} }

View File

@ -4,6 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ModelBinding namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
@ -16,5 +17,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
{ {
return new JQueryQueryStringValueProvider(bindingSource, values, culture); return new JQueryQueryStringValueProvider(bindingSource, values, culture);
} }
[Fact]
public void Filter_ExcludesItself()
{
// Arrange
var dictionary = new Dictionary<string, StringValues>();
var provider = new JQueryQueryStringValueProvider(
BindingSource.Form,
dictionary,
CultureInfo.CurrentCulture);
// Act
var result = provider.Filter();
// Assert
Assert.Null(result);
}
} }
} }

View File

@ -63,6 +63,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
[Theory] [Theory]
[InlineData("?prefix[0]=10&prefix[1]=11")] [InlineData("?prefix[0]=10&prefix[1]=11")]
[InlineData("?prefix.index=low&prefix.index=high&prefix[low]=10&prefix[high]=11")] [InlineData("?prefix.index=low&prefix.index=high&prefix[low]=10&prefix[high]=11")]
[InlineData("?prefix.index=index&prefix.index=indexer&prefix[index]=10&prefix[indexer]=11")]
[InlineData("?prefix.index=index&prefix.index=indexer&prefix[index]=10&prefix[indexer]=11&prefix[extra]=12")]
public async Task CollectionModelBinder_BindsListOfSimpleType_WithExplicitPrefix_Success(string queryString) public async Task CollectionModelBinder_BindsListOfSimpleType_WithExplicitPrefix_Success(string queryString)
{ {
// Arrange // Arrange
@ -101,6 +103,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
[Theory] [Theory]
[InlineData("?[0]=10&[1]=11")] [InlineData("?[0]=10&[1]=11")]
[InlineData("?index=low&index=high&[high]=11&[low]=10")] [InlineData("?index=low&index=high&[high]=11&[low]=10")]
[InlineData("?index=index&index=indexer&[indexer]=11&[index]=10")]
[InlineData("?index=index&index=indexer&[indexer]=11&[index]=10&[extra]=12")]
public async Task CollectionModelBinder_BindsCollectionOfSimpleType_EmptyPrefix_Success(string queryString) public async Task CollectionModelBinder_BindsCollectionOfSimpleType_EmptyPrefix_Success(string queryString)
{ {
// Arrange // Arrange
@ -172,6 +176,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
[InlineData("?index=low&index=high&[low].Id=10&[high].Id=11")] [InlineData("?index=low&index=high&[low].Id=10&[high].Id=11")]
[InlineData("?parameter[0].Id=10&parameter[1].Id=11")] [InlineData("?parameter[0].Id=10&parameter[1].Id=11")]
[InlineData("?parameter.index=low&parameter.index=high&parameter[low].Id=10&parameter[high].Id=11")] [InlineData("?parameter.index=low&parameter.index=high&parameter[low].Id=10&parameter[high].Id=11")]
[InlineData("?parameter.index=index&parameter.index=indexer&parameter[index].Id=10&parameter[indexer].Id=11")]
public async Task CollectionModelBinder_BindsListOfComplexType_ImpliedPrefix_Success(string queryString) public async Task CollectionModelBinder_BindsListOfComplexType_ImpliedPrefix_Success(string queryString)
{ {
// Arrange // Arrange
@ -207,6 +212,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
[Theory] [Theory]
[InlineData("?prefix[0].Id=10&prefix[1].Id=11")] [InlineData("?prefix[0].Id=10&prefix[1].Id=11")]
[InlineData("?prefix.index=low&prefix.index=high&prefix[high].Id=11&prefix[low].Id=10")] [InlineData("?prefix.index=low&prefix.index=high&prefix[high].Id=11&prefix[low].Id=10")]
[InlineData("?prefix.index=index&prefix.index=indexer&prefix[indexer].Id=11&prefix[index].Id=10")]
public async Task CollectionModelBinder_BindsListOfComplexType_ExplicitPrefix_Success(string queryString) public async Task CollectionModelBinder_BindsListOfComplexType_ExplicitPrefix_Success(string queryString)
{ {
// Arrange // Arrange
@ -683,6 +689,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
[InlineData("?index=low&[low].Street=LongStreet")] [InlineData("?index=low&[low].Street=LongStreet")]
[InlineData("?parameter[0].Street=LongStreet")] [InlineData("?parameter[0].Street=LongStreet")]
[InlineData("?parameter.index=low&parameter[low].Street=LongStreet")] [InlineData("?parameter.index=low&parameter[low].Street=LongStreet")]
[InlineData("?parameter.index=index&parameter[index].Street=LongStreet")]
public async Task CollectionModelBinder_BindsCollectionOfComplexType_ImpliedPrefix_FindsValidationErrors( public async Task CollectionModelBinder_BindsCollectionOfComplexType_ImpliedPrefix_FindsValidationErrors(
string queryString) string queryString)
{ {

View File

@ -9,6 +9,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Primitives;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Mvc.IntegrationTests namespace Microsoft.AspNetCore.Mvc.IntegrationTests
@ -141,6 +142,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
[InlineData("?prefix[key0]=10")] [InlineData("?prefix[key0]=10")]
[InlineData("?prefix[0].Key=key0&prefix[0].Value=10")] [InlineData("?prefix[0].Key=key0&prefix[0].Value=10")]
[InlineData("?prefix.index=low&prefix[low].Key=key0&prefix[low].Value=10")] [InlineData("?prefix.index=low&prefix[low].Key=key0&prefix[low].Value=10")]
[InlineData("?prefix.index=index&prefix[index].Key=key0&prefix[index].Value=10")]
[InlineData("?prefix.index=index&prefix[index].Key=key0&prefix[index].Value=10&prefix[extra].Key=key4&prefix[extra].Value=5")]
public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_WithExplicitPrefix_Success( public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_WithExplicitPrefix_Success(
string queryString) string queryString)
{ {
@ -181,6 +184,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
[InlineData("?[key0]=10")] [InlineData("?[key0]=10")]
[InlineData("?[0].Key=key0&[0].Value=10")] [InlineData("?[0].Key=key0&[0].Value=10")]
[InlineData("?index=low&[low].Key=key0&[low].Value=10")] [InlineData("?index=low&[low].Key=key0&[low].Value=10")]
[InlineData("?index=index&[index].Key=key0&[index].Value=10")]
[InlineData("?index=index&[index].Key=key0&[index].Value=10&[extra].Key=key4&[extra].Value=5")]
public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_EmptyPrefix_Success(string queryString) public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_EmptyPrefix_Success(string queryString)
{ {
// Arrange // Arrange
@ -212,6 +217,186 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.True(modelState.IsValid); Assert.True(modelState.IsValid);
} }
public static TheoryData<Action<HttpRequest>> ThreeEntryTestData
{
get
{
var impliedPrefixQueryString = "?parameter[archive]=1&parameter[correlation]=2&parameter[index]=3";
var noPrefixQueryString = "?[archive]=1&[correlation]=2&[index]=3";
var reversedNoPrefixQueryString = "?[index]=3&[correlation]=2&[archive]=1";
var impliedPrefixDictionary = new Dictionary<string, StringValues>
{
{ "parameter[archive]", "1" },
{ "parameter[correlation]", "2" },
{ "parameter[index]", "3" },
};
var reversedImpliedPrefixDictionary = new Dictionary<string, StringValues>
{
{ "parameter[index]", "3" },
{ "parameter[correlation]", "2" },
{ "parameter[archive]", "1" },
};
var longFormDictionary = new Dictionary<string, StringValues>
{
{ "parameter[0].Key", "archive" },
{ "parameter[0].Value", "1" },
{ "parameter[1].Key", "correlation" },
{ "parameter[1].Value", "2" },
{ "parameter[2].Key", "index" },
{ "parameter[2].Value", "3" },
};
var longerFormDictionary = new Dictionary<string, StringValues>
{
{ "parameter[indexer].Key", "archive" },
{ "parameter[indexer].Value", "1" },
{ "parameter[index].Key", "correlation" },
{ "parameter.index", new[] { "indexer", "index", "indexes" } },
{ "parameter[index].Value", "2" },
{ "parameter[indexes].Key", "index" },
{ "parameter[indexes].Value", "3" },
};
var longestFormDictionary = new Dictionary<string, StringValues>
{
{ "parameter[indexer].Key", "archive" },
{ "parameter[indexer].Value", "1" },
{ "parameter[index].Key", "correlation" },
{ "parameter[extra].Key", "index" },
{ "parameter[extra].Value", "4" },
{ "parameter.index", new[] { "indexer", "index", "indexes" } },
{ "parameter[index].Value", "2" },
{ "parameter[indexes].Key", "index" },
{ "parameter[indexes].Value", "3" },
{ "parameter[another].Key", "index" },
{ "parameter[another].Value", "5" },
};
var noPrefixDictionary = new Dictionary<string, StringValues>
{
{ "[archive]", "1" },
{ "[correlation]", "2" },
{ "[index]", "3" },
};
var reversedNoPrefixDictionary = new Dictionary<string, StringValues>
{
{ "[index]", "3" },
{ "[correlation]", "2" },
{ "[archive]", "1" },
};
return new TheoryData<Action<HttpRequest>>
{
request => request.QueryString = new QueryString(impliedPrefixQueryString),
request => request.QueryString = new QueryString(noPrefixQueryString),
request => request.QueryString = new QueryString(reversedNoPrefixQueryString),
request =>
{
request.ContentType = "application/x-www-form-urlencoded";
request.Form = new FormCollection(impliedPrefixDictionary);
},
request =>
{
request.ContentType = "application/x-www-form-urlencoded";
request.Form = new FormCollection(reversedImpliedPrefixDictionary);
},
request =>
{
request.ContentType = "application/x-www-form-urlencoded";
request.Form = new FormCollection(longFormDictionary);
},
request =>
{
request.ContentType = "application/x-www-form-urlencoded";
request.Form = new FormCollection(longerFormDictionary);
},
request =>
{
request.ContentType = "application/x-www-form-urlencoded";
request.Form = new FormCollection(longestFormDictionary);
},
request =>
{
request.ContentType = "application/x-www-form-urlencoded";
request.Form = new FormCollection(noPrefixDictionary);
},
request =>
{
request.ContentType = "application/x-www-form-urlencoded";
request.Form = new FormCollection(reversedNoPrefixDictionary);
},
};
}
}
[Theory]
[MemberData(nameof(ThreeEntryTestData))]
public async Task DictionaryModelBinder_Binds3EntriesOfSimpleType(Action<HttpRequest> updateRequest)
{
// Arrange
var expectedDictionary = new Dictionary<string, int>
{
{ "archive", 1 },
{ "correlation", 2 },
{ "index", 3 },
};
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var testContext = ModelBindingTestHelper.GetTestContext(updateRequest);
var modelState = testContext.ModelState;
var parameter = new ParameterDescriptor
{
Name = "parameter",
ParameterType = typeof(Dictionary<string, int>),
};
// Act
var result = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(result.IsModelSet);
var model = Assert.IsType<Dictionary<string, int>>(result.Model);
Assert.Equal(expectedDictionary, model);
Assert.NotEmpty(modelState);
Assert.True(modelState.IsValid);
}
[Theory]
[MemberData(nameof(ThreeEntryTestData))]
public async Task DictionaryModelBinder_Binds3EntriesOfSimpleType_WithJQueryQueryString(
Action<HttpRequest> updateRequest)
{
// Arrange
var expectedDictionary = new Dictionary<string, int>
{
{ "archive", 1 },
{ "correlation", 2 },
{ "index", 3 },
};
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var testContext = ModelBindingTestHelper.GetTestContext(
updateRequest,
options => options.ValueProviderFactories.Add(new JQueryQueryStringValueProviderFactory()));
var modelState = testContext.ModelState;
var parameter = new ParameterDescriptor
{
Name = "parameter",
ParameterType = typeof(Dictionary<string, int>),
};
// Act
var result = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(result.IsModelSet);
var model = Assert.IsType<Dictionary<string, int>>(result.Model);
Assert.Equal(expectedDictionary, model);
Assert.NotEmpty(modelState);
Assert.True(modelState.IsValid);
}
[Fact] [Fact]
public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_NoData() public async Task DictionaryModelBinder_BindsDictionaryOfSimpleType_NoData()
{ {
@ -251,9 +436,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
var other = obj as Person; return obj is Person other && Id == other.Id;
return other != null && Id == other.Id;
} }
public override int GetHashCode() public override int GetHashCode()
@ -274,6 +457,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
[InlineData("?parameter[key0].Id=10")] [InlineData("?parameter[key0].Id=10")]
[InlineData("?parameter[0].Key=key0&parameter[0].Value.Id=10")] [InlineData("?parameter[0].Key=key0&parameter[0].Value.Id=10")]
[InlineData("?parameter.index=low&parameter[low].Key=key0&parameter[low].Value.Id=10")] [InlineData("?parameter.index=low&parameter[low].Key=key0&parameter[low].Value.Id=10")]
[InlineData("?parameter.index=index&parameter[index].Key=key0&parameter[index].Value.Id=10")]
public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_ImpliedPrefix_Success(string queryString) public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_ImpliedPrefix_Success(string queryString)
{ {
// Arrange // Arrange
@ -309,6 +493,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
[InlineData("?prefix[key0].Id=10")] [InlineData("?prefix[key0].Id=10")]
[InlineData("?prefix[0].Key=key0&prefix[0].Value.Id=10")] [InlineData("?prefix[0].Key=key0&prefix[0].Value.Id=10")]
[InlineData("?prefix.index=low&prefix[low].Key=key0&prefix[low].Value.Id=10")] [InlineData("?prefix.index=low&prefix[low].Key=key0&prefix[low].Value.Id=10")]
[InlineData("?prefix.index=index&prefix[index].Key=key0&prefix[index].Value.Id=10")]
public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_ExplicitPrefix_Success( public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_ExplicitPrefix_Success(
string queryString) string queryString)
{ {
@ -352,6 +537,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
[InlineData("?parameter[key0].Id=100")] [InlineData("?parameter[key0].Id=100")]
[InlineData("?parameter[0].Key=key0&parameter[0].Value.Id=100")] [InlineData("?parameter[0].Key=key0&parameter[0].Value.Id=100")]
[InlineData("?parameter.index=low&parameter[low].Key=key0&parameter[low].Value.Id=100")] [InlineData("?parameter.index=low&parameter[low].Key=key0&parameter[low].Value.Id=100")]
[InlineData("?parameter.index=index&parameter[index].Key=key0&parameter[index].Value.Id=100")]
public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_ImpliedPrefix_FindsValidationErrors( public async Task DictionaryModelBinder_BindsDictionaryOfComplexType_ImpliedPrefix_FindsValidationErrors(
string queryString) string queryString)
{ {