Ignore empty keys in QueryStringValueProvider

Fixes 8484
This commit is contained in:
Pranav K 2018-09-27 09:33:50 -07:00
parent 1da7a89f59
commit d603735818
9 changed files with 126 additions and 11 deletions

View File

@ -13,7 +13,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// </summary> /// </summary>
public class FormValueProvider : BindingSourceValueProvider, IEnumerableValueProvider public class FormValueProvider : BindingSourceValueProvider, IEnumerableValueProvider
{ {
private readonly CultureInfo _culture;
private readonly IFormCollection _values; private readonly IFormCollection _values;
private PrefixContainer _prefixContainer; private PrefixContainer _prefixContainer;
@ -40,10 +39,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
} }
_values = values; _values = values;
_culture = culture; Culture = culture;
} }
public CultureInfo Culture => _culture; public CultureInfo Culture { get; }
protected PrefixContainer PrefixContainer protected PrefixContainer PrefixContainer
{ {
@ -83,6 +82,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
} }
if (key.Length == 0)
{
// Top level parameters will fall back to an empty prefix when the parameter name does not
// appear in any value provider. This would result in the parameter binding to a form parameter
// with a empty key (e.g. Request body looks like "=test") which isn't a scenario we want to support.
// Return a "None" result in this event.
return ValueProviderResult.None;
}
var values = _values[key]; var values = _values[key];
if (values.Count == 0) if (values.Count == 0)
{ {
@ -90,7 +98,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
} }
else else
{ {
return new ValueProviderResult(values, _culture); return new ValueProviderResult(values, Culture);
} }
} }
} }

View File

@ -79,6 +79,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// <inheritdoc /> /// <inheritdoc />
public override ValueProviderResult GetValue(string key) public override ValueProviderResult GetValue(string key)
{ {
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (_values.TryGetValue(key, out var values) && values.Count > 0) if (_values.TryGetValue(key, out var values) && values.Count > 0)
{ {
return new ValueProviderResult(values, Culture); return new ValueProviderResult(values, Culture);

View File

@ -13,7 +13,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// </summary> /// </summary>
public class QueryStringValueProvider : BindingSourceValueProvider, IEnumerableValueProvider public class QueryStringValueProvider : BindingSourceValueProvider, IEnumerableValueProvider
{ {
private readonly CultureInfo _culture;
private readonly IQueryCollection _values; private readonly IQueryCollection _values;
private PrefixContainer _prefixContainer; private PrefixContainer _prefixContainer;
@ -40,10 +39,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
} }
_values = values; _values = values;
_culture = culture; Culture = culture;
} }
public CultureInfo Culture => _culture; public CultureInfo Culture { get; }
protected PrefixContainer PrefixContainer protected PrefixContainer PrefixContainer
{ {
@ -83,6 +82,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
} }
if (key.Length == 0)
{
// Top level parameters will fall back to an empty prefix when the parameter name does not
// appear in any value provider. This would result in the parameter binding to a query string
// parameter with a empty key (e.g. /User?=test) which isn't a scenario we want to support.
// Return a "None" result in this event.
return ValueProviderResult.None;
}
var values = _values[key]; var values = _values[key];
if (values.Count == 0) if (values.Count == 0)
{ {
@ -90,7 +98,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
} }
else else
{ {
return new ValueProviderResult(values, _culture); return new ValueProviderResult(values, Culture);
} }
} }
} }

View File

@ -85,6 +85,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
} }
if (key.Length == 0)
{
// Top level parameters will fall back to an empty prefix when the parameter name does not
// appear in any value provider. This would result in the parameter binding to a route value
// an empty key which isn't a scenario we want to support.
// Return a "None" result in this event.
return ValueProviderResult.None;
}
if (_values.TryGetValue(key, out var value)) if (_values.TryGetValue(key, out var value))
{ {
var stringValue = value as string ?? Convert.ToString(value, Culture) ?? string.Empty; var stringValue = value as string ?? Convert.ToString(value, Culture) ?? string.Empty;

View File

@ -5,6 +5,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
using Moq; using Moq;
using Xunit; using Xunit;
@ -38,9 +40,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
Dictionary<string, StringValues> values, Dictionary<string, StringValues> values,
CultureInfo culture) CultureInfo culture)
{ {
var emptyValueProvider = var emptyValueProvider = new QueryStringValueProvider(bindingSource, new QueryCollection(), culture);
new JQueryFormValueProvider(bindingSource, new Dictionary<string, StringValues>(), culture); var valueProvider = new FormValueProvider(bindingSource, new FormCollection(values), culture);
var valueProvider = new JQueryFormValueProvider(bindingSource, values, culture);
return new CompositeValueProvider() { emptyValueProvider, valueProvider }; return new CompositeValueProvider() { emptyValueProvider, valueProvider };
} }

View File

@ -235,6 +235,23 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
Assert.Equal(ValueProviderResult.None, result); Assert.Equal(ValueProviderResult.None, result);
} }
[Fact]
public virtual void GetValue_EmptyKey()
{
// Arrange
var store = new Dictionary<string, StringValues>(BackingStore)
{
{ string.Empty, "some-value" },
};
var valueProvider = GetEnumerableValueProvider(BindingSource.Query, store, culture: null);
// Act
var result = valueProvider.GetValue(string.Empty);
// Assert
Assert.Equal(ValueProviderResult.None, result);
}
[Fact] [Fact]
public virtual void FilterInclude() public virtual void FilterInclude()
{ {

View File

@ -31,5 +31,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
// Assert // Assert
Assert.Null(result); Assert.Null(result);
} }
[Fact]
public override void GetValue_EmptyKey()
{
// Arrange
var store = new Dictionary<string, StringValues>(BackingStore)
{
{ string.Empty, "some-value" },
};
var valueProvider = GetEnumerableValueProvider(BindingSource.Query, store, culture: null);
// Act
var result = valueProvider.GetValue(string.Empty);
// Assert
Assert.Equal("some-value", (string)result);
}
} }
} }

View File

@ -34,5 +34,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
// Assert // Assert
Assert.Null(result); Assert.Null(result);
} }
[Fact]
public override void GetValue_EmptyKey()
{
// Arrange
var store = new Dictionary<string, StringValues>(BackingStore)
{
{ string.Empty, "some-value" },
};
var valueProvider = GetEnumerableValueProvider(BindingSource.Query, store, culture: null);
// Act
var result = valueProvider.GetValue(string.Empty);
// Assert
Assert.Equal("some-value", (string)result);
}
} }
} }

View File

@ -152,6 +152,39 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState); Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
} }
[Fact]
public async Task BindParameter_WithEmptyQueryStringKey_DoesNotGetBound()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameter = new ParameterDescriptor
{
Name = "Parameter1",
BindingInfo = new BindingInfo(),
ParameterType = typeof(string)
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString("?=someValue");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
// ModelBindingResult
Assert.False(modelBindingResult.IsModelSet);
// ModelState
Assert.True(modelState.IsValid);
Assert.Empty(modelState.Keys);
}
[Fact] [Fact]
[ReplaceCulture("en-GB", "en-GB")] [ReplaceCulture("en-GB", "en-GB")]
public async Task BindDecimalParameter_WithData_GetsBound() public async Task BindDecimalParameter_WithData_GetsBound()