Added JQueryQueryStringValueProviderFactory and JQueryQueryStringValueProvider
[Fixes #6372] jQuery ajax request with complex data does not work with .net core 1.1 model bindings
This commit is contained in:
parent
4acdebc5be
commit
a952313f1c
|
|
@ -1,10 +1,8 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
|
|
@ -12,11 +10,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
/// <summary>
|
||||
/// An <see cref="IValueProvider"/> for jQuery formatted form data.
|
||||
/// </summary>
|
||||
public class JQueryFormValueProvider : BindingSourceValueProvider, IEnumerableValueProvider
|
||||
public class JQueryFormValueProvider : JQueryValueProvider
|
||||
{
|
||||
private readonly IDictionary<string, StringValues> _values;
|
||||
private PrefixContainer _prefixContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JQueryFormValueProvider"/> class.
|
||||
/// </summary>
|
||||
|
|
@ -27,60 +22,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
BindingSource bindingSource,
|
||||
IDictionary<string, StringValues> values,
|
||||
CultureInfo culture)
|
||||
: base(bindingSource)
|
||||
: base(bindingSource, values, culture)
|
||||
{
|
||||
if (bindingSource == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bindingSource));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
_values = values;
|
||||
Culture = culture;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal CultureInfo Culture { get; }
|
||||
|
||||
protected PrefixContainer PrefixContainer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_prefixContainer == null)
|
||||
{
|
||||
_prefixContainer = new PrefixContainer(_values.Keys);
|
||||
}
|
||||
|
||||
return _prefixContainer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool ContainsPrefix(string prefix)
|
||||
{
|
||||
return PrefixContainer.ContainsPrefix(prefix);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDictionary<string, string> GetKeysFromPrefix(string prefix)
|
||||
{
|
||||
return PrefixContainer.GetKeysFromPrefix(prefix);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ValueProviderResult GetValue(string key)
|
||||
{
|
||||
StringValues values;
|
||||
if (_values.TryGetValue(key, out values) && values.Count > 0)
|
||||
{
|
||||
return new ValueProviderResult(values, Culture);
|
||||
}
|
||||
|
||||
return ValueProviderResult.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,18 +2,13 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IValueProviderFactory"/> for <see cref="JQueryFormValueProvider"/>.
|
||||
/// An <see cref="IValueProviderFactory"/> for <see cref="JQueryFormValueProvider"/>.
|
||||
/// </summary>
|
||||
public class JQueryFormValueProviderFactory : IValueProviderFactory
|
||||
{
|
||||
|
|
@ -38,97 +33,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
private static async Task AddValueProviderAsync(ValueProviderFactoryContext context)
|
||||
{
|
||||
var request = context.ActionContext.HttpContext.Request;
|
||||
|
||||
var formCollection = await request.ReadFormAsync();
|
||||
|
||||
var valueProvider = new JQueryFormValueProvider(
|
||||
BindingSource.Form,
|
||||
await GetValueCollectionAsync(request),
|
||||
JQueryKeyValuePairNormalizer.GetValues(formCollection, formCollection.Count),
|
||||
CultureInfo.CurrentCulture);
|
||||
|
||||
context.ValueProviders.Add(valueProvider);
|
||||
}
|
||||
|
||||
private static async Task<IDictionary<string, StringValues>> GetValueCollectionAsync(HttpRequest request)
|
||||
{
|
||||
var formCollection = await request.ReadFormAsync();
|
||||
|
||||
var builder = new StringBuilder();
|
||||
var dictionary = new Dictionary<string, StringValues>(
|
||||
formCollection.Count,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var entry in formCollection)
|
||||
{
|
||||
var key = NormalizeJQueryToMvc(builder, entry.Key);
|
||||
builder.Clear();
|
||||
|
||||
dictionary[key] = entry.Value;
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
// This is a helper method for Model Binding over a JQuery syntax.
|
||||
// Normalize from JQuery to MVC keys. The model binding infrastructure uses MVC keys.
|
||||
// x[] --> x
|
||||
// [] --> ""
|
||||
// x[12] --> x[12]
|
||||
// x[field] --> x.field, where field is not a number
|
||||
private static string NormalizeJQueryToMvc(StringBuilder builder, string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var indexOpen = key.IndexOf('[');
|
||||
if (indexOpen == -1)
|
||||
{
|
||||
|
||||
// Fast path, no normalization needed.
|
||||
// This skips string conversion and allocating the string builder.
|
||||
return key;
|
||||
}
|
||||
|
||||
var position = 0;
|
||||
while (position < key.Length)
|
||||
{
|
||||
if (indexOpen == -1)
|
||||
{
|
||||
// No more brackets.
|
||||
builder.Append(key, position, key.Length - position);
|
||||
break;
|
||||
}
|
||||
|
||||
builder.Append(key, position, indexOpen - position); // everything up to "["
|
||||
|
||||
// Find closing bracket.
|
||||
var indexClose = key.IndexOf(']', indexOpen);
|
||||
if (indexClose == -1)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
message: Resources.FormatJQueryFormValueProviderFactory_MissingClosingBracket(key),
|
||||
paramName: nameof(key));
|
||||
}
|
||||
|
||||
if (indexClose == indexOpen + 1)
|
||||
{
|
||||
// Empty brackets signify an array. Just remove.
|
||||
}
|
||||
else if (char.IsDigit(key[indexOpen + 1]))
|
||||
{
|
||||
// Array index. Leave unchanged.
|
||||
builder.Append(key, indexOpen, indexClose - indexOpen + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Field name. Convert to dot notation.
|
||||
builder.Append('.');
|
||||
builder.Append(key, indexOpen + 1, indexClose - indexOpen - 1);
|
||||
}
|
||||
|
||||
position = indexClose + 1;
|
||||
indexOpen = key.IndexOf('[', position);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
// Normalizes keys, in a keyvaluepair collection, from jQuery format to a format that MVC understands.
|
||||
internal static class JQueryKeyValuePairNormalizer
|
||||
{
|
||||
public static IDictionary<string, StringValues> GetValues(
|
||||
IEnumerable<KeyValuePair<string, StringValues>> originalValues,
|
||||
int valueCount)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var dictionary = new Dictionary<string, StringValues>(
|
||||
valueCount,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var originalValue in originalValues)
|
||||
{
|
||||
var normalizedKey = NormalizeJQueryToMvc(builder, originalValue.Key);
|
||||
builder.Clear();
|
||||
|
||||
dictionary[normalizedKey] = originalValue.Value;
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
// This is a helper method for Model Binding over a JQuery syntax.
|
||||
// Normalize from JQuery to MVC keys. The model binding infrastructure uses MVC keys.
|
||||
// x[] --> x
|
||||
// [] --> ""
|
||||
// x[12] --> x[12]
|
||||
// x[field] --> x.field, where field is not a number
|
||||
private static string NormalizeJQueryToMvc(StringBuilder builder, string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var indexOpen = key.IndexOf('[');
|
||||
if (indexOpen == -1)
|
||||
{
|
||||
|
||||
// Fast path, no normalization needed.
|
||||
// This skips string conversion and allocating the string builder.
|
||||
return key;
|
||||
}
|
||||
|
||||
var position = 0;
|
||||
while (position < key.Length)
|
||||
{
|
||||
if (indexOpen == -1)
|
||||
{
|
||||
// No more brackets.
|
||||
builder.Append(key, position, key.Length - position);
|
||||
break;
|
||||
}
|
||||
|
||||
builder.Append(key, position, indexOpen - position); // everything up to "["
|
||||
|
||||
// Find closing bracket.
|
||||
var indexClose = key.IndexOf(']', indexOpen);
|
||||
if (indexClose == -1)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
message: Resources.FormatJQueryFormValueProviderFactory_MissingClosingBracket(key),
|
||||
paramName: nameof(key));
|
||||
}
|
||||
|
||||
if (indexClose == indexOpen + 1)
|
||||
{
|
||||
// Empty brackets signify an array. Just remove.
|
||||
}
|
||||
else if (char.IsDigit(key[indexOpen + 1]))
|
||||
{
|
||||
// Array index. Leave unchanged.
|
||||
builder.Append(key, indexOpen, indexClose - indexOpen + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Field name. Convert to dot notation.
|
||||
builder.Append('.');
|
||||
builder.Append(key, indexOpen + 1, indexClose - indexOpen - 1);
|
||||
}
|
||||
|
||||
position = indexClose + 1;
|
||||
indexOpen = key.IndexOf('[', position);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// 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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IValueProvider"/> for jQuery formatted query string data.
|
||||
/// </summary>
|
||||
public class JQueryQueryStringValueProvider : JQueryValueProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JQueryQueryStringValueProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="bindingSource">The <see cref="BindingSource"/> of the data.</param>
|
||||
/// <param name="values">The values.</param>
|
||||
/// <param name="culture">The culture to return with ValueProviderResult instances.</param>
|
||||
public JQueryQueryStringValueProvider(
|
||||
BindingSource bindingSource,
|
||||
IDictionary<string, StringValues> values,
|
||||
CultureInfo culture)
|
||||
: base(bindingSource, values, culture)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IValueProviderFactory"/> for <see cref="JQueryQueryStringValueProvider"/>.
|
||||
/// </summary>
|
||||
public class JQueryQueryStringValueProviderFactory : IValueProviderFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var query = context.ActionContext.HttpContext.Request.Query;
|
||||
if (query != null && query.Count > 0)
|
||||
{
|
||||
var valueProvider = new JQueryQueryStringValueProvider(
|
||||
BindingSource.Query,
|
||||
JQueryKeyValuePairNormalizer.GetValues(query, query.Count),
|
||||
CultureInfo.InvariantCulture);
|
||||
|
||||
context.ValueProviders.Add(valueProvider);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Mvc.Internal;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IValueProvider"/> for jQuery formatted form data.
|
||||
/// </summary>
|
||||
public abstract class JQueryValueProvider : BindingSourceValueProvider, IEnumerableValueProvider
|
||||
{
|
||||
private readonly IDictionary<string, StringValues> _values;
|
||||
private PrefixContainer _prefixContainer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JQueryValueProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="bindingSource">The <see cref="BindingSource"/> of the data.</param>
|
||||
/// <param name="values">The values.</param>
|
||||
/// <param name="culture">The culture to return with ValueProviderResult instances.</param>
|
||||
protected JQueryValueProvider(
|
||||
BindingSource bindingSource,
|
||||
IDictionary<string, StringValues> values,
|
||||
CultureInfo culture)
|
||||
: base(bindingSource)
|
||||
{
|
||||
if (bindingSource == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bindingSource));
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
|
||||
_values = values;
|
||||
Culture = culture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="CultureInfo"/> associated with the values.
|
||||
/// </summary>
|
||||
public CultureInfo Culture { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected PrefixContainer PrefixContainer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_prefixContainer == null)
|
||||
{
|
||||
_prefixContainer = new PrefixContainer(_values.Keys);
|
||||
}
|
||||
|
||||
return _prefixContainer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool ContainsPrefix(string prefix)
|
||||
{
|
||||
return PrefixContainer.ContainsPrefix(prefix);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDictionary<string, string> GetKeysFromPrefix(string prefix)
|
||||
{
|
||||
return PrefixContainer.GetKeysFromPrefix(prefix);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ValueProviderResult GetValue(string key)
|
||||
{
|
||||
StringValues values;
|
||||
if (_values.TryGetValue(key, out values) && values.Count > 0)
|
||||
{
|
||||
return new ValueProviderResult(values, Culture);
|
||||
}
|
||||
|
||||
return ValueProviderResult.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,12 +21,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
BindingSource.Query,
|
||||
context.ActionContext.HttpContext.Request.Query,
|
||||
CultureInfo.InvariantCulture);
|
||||
var query = context.ActionContext.HttpContext.Request.Query;
|
||||
if (query != null && query.Count > 0)
|
||||
{
|
||||
var valueProvider = new QueryStringValueProvider(
|
||||
BindingSource.Query,
|
||||
query,
|
||||
CultureInfo.InvariantCulture);
|
||||
|
||||
context.ValueProviders.Add(valueProvider);
|
||||
context.ValueProviders.Add(valueProvider);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,5 +28,9 @@
|
|||
"TypeId": "public class Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinderProvider : Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderProvider",
|
||||
"MemberId": "public .ctor(System.Collections.Generic.IList<Microsoft.AspNetCore.Mvc.Formatters.IInputFormatter> formatters, Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory readerFactory, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.Mvc.MvcOptions options)",
|
||||
"Kind": "Removal"
|
||||
},
|
||||
{
|
||||
"TypeId": "public class Microsoft.AspNetCore.Mvc.ModelBinding.JQueryFormValueProvider : Microsoft.AspNetCore.Mvc.ModelBinding.BindingSourceValueProvider, Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider",
|
||||
"Kind": "Removal"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -116,6 +116,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
|||
Assert.Equal("found", (string)result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreatesValueProvider_WithCurrentCulture()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext("application/x-www-form-urlencoded", formValues: _backingStore);
|
||||
var factory = new JQueryFormValueProviderFactory();
|
||||
|
||||
// Act
|
||||
await factory.CreateValueProviderAsync(context);
|
||||
|
||||
// Assert
|
||||
var valueProvider = Assert.Single(context.ValueProviders);
|
||||
var jqueryFormValueProvider = Assert.IsType<JQueryFormValueProvider>(valueProvider);
|
||||
Assert.Equal(CultureInfo.CurrentCulture, jqueryFormValueProvider.Culture);
|
||||
}
|
||||
|
||||
private static ValueProviderFactoryContext CreateContext(string contentType, Dictionary<string, StringValues> formValues)
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
// 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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||
{
|
||||
public class JQueryQueryStringValueProviderFactoryTest
|
||||
{
|
||||
private static readonly Dictionary<string, StringValues> _backingStore = new Dictionary<string, StringValues>
|
||||
{
|
||||
{ "[]", new[] { "found" } },
|
||||
{ "[]property1", new[] { "found" } },
|
||||
{ "property2[]", new[] { "found" } },
|
||||
{ "[]property3[]", new[] { "found" } },
|
||||
{ "property[]Value", new[] { "found" } },
|
||||
{ "[10]", new[] { "found" } },
|
||||
{ "[11]property", new[] { "found" } },
|
||||
{ "property4[10]", new[] { "found" } },
|
||||
{ "[12]property[][13]", new[] { "found" } },
|
||||
{ "[14][]property1[15]property2", new[] { "found" } },
|
||||
{ "prefix[11]property1", new[] { "found" } },
|
||||
{ "prefix[12][][property2]", new[] { "found" } },
|
||||
{ "prefix[property1][13]", new[] { "found" } },
|
||||
{ "prefix[14][][15]", new[] { "found" } },
|
||||
{ "[property5][]", new[] { "found" } },
|
||||
{ "[][property6]Value", new[] { "found" } },
|
||||
{ "prefix[property2]", new[] { "found" } },
|
||||
{ "prefix[][property]Value", new[] { "found" } },
|
||||
{ "[property7][property8]", new[] { "found" } },
|
||||
{ "[property9][][property10]Value", new[] { "found" } },
|
||||
};
|
||||
|
||||
public static TheoryData<string> SuccessDataSet
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<string>
|
||||
{
|
||||
string.Empty,
|
||||
"property1",
|
||||
"property2",
|
||||
"property3",
|
||||
"propertyValue",
|
||||
"[10]",
|
||||
"[11]property",
|
||||
"property4[10]",
|
||||
"[12]property[13]",
|
||||
"[14]property1[15]property2",
|
||||
"prefix.property1[13]",
|
||||
"prefix[14][15]",
|
||||
".property5",
|
||||
".property6Value",
|
||||
"prefix.property2",
|
||||
"prefix.propertyValue",
|
||||
".property7.property8",
|
||||
".property9.property10Value",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(SuccessDataSet))]
|
||||
public async Task GetValueProvider_ReturnsValueProvider_ContainingExpectedKeys(string key)
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(_backingStore);
|
||||
var factory = new JQueryQueryStringValueProviderFactory();
|
||||
|
||||
// Act
|
||||
await factory.CreateValueProviderAsync(context);
|
||||
|
||||
// Assert
|
||||
var valueProvider = Assert.Single(context.ValueProviders);
|
||||
var result = valueProvider.GetValue(key);
|
||||
Assert.Equal("found", (string)result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DoesNotCreateValueProvider_WhenQueryIsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(new Dictionary<string, StringValues>());
|
||||
var factory = new JQueryQueryStringValueProviderFactory();
|
||||
|
||||
// Act
|
||||
await factory.CreateValueProviderAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(context.ValueProviders);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreatesValueProvider_WithInvariantCulture()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(_backingStore);
|
||||
var factory = new JQueryQueryStringValueProviderFactory();
|
||||
|
||||
// Act
|
||||
await factory.CreateValueProviderAsync(context);
|
||||
|
||||
// Assert
|
||||
var valueProvider = Assert.Single(context.ValueProviders);
|
||||
var jqueryQueryStringValueProvider = Assert.IsType<JQueryQueryStringValueProvider>(valueProvider);
|
||||
Assert.Equal(CultureInfo.InvariantCulture, jqueryQueryStringValueProvider.Culture);
|
||||
}
|
||||
|
||||
private static ValueProviderFactoryContext CreateContext(Dictionary<string, StringValues> queryStringValues)
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
|
||||
context.Request.Query = new QueryCollection(queryStringValues);
|
||||
|
||||
var actionContext = new ActionContext(context, new RouteData(), new ActionDescriptor());
|
||||
return new ValueProviderFactoryContext(actionContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// 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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
{
|
||||
public class JQueryQueryStringValueProviderTest : EnumerableValueProviderTest
|
||||
{
|
||||
protected override IEnumerableValueProvider GetEnumerableValueProvider(
|
||||
BindingSource bindingSource,
|
||||
Dictionary<string, StringValues> values,
|
||||
CultureInfo culture)
|
||||
{
|
||||
return new JQueryQueryStringValueProvider(bindingSource, values, culture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,25 +5,40 @@ using System.Collections.Generic;
|
|||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Moq;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||
{
|
||||
public class QueryStringValueProviderFactoryTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task DoesNotCreateValueProvider_WhenQueryStringIsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
||||
var factoryContext = new ValueProviderFactoryContext(actionContext);
|
||||
var factory = new QueryStringValueProviderFactory();
|
||||
|
||||
// Act
|
||||
await factory.CreateValueProviderAsync(factoryContext);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(factoryContext.ValueProviders);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetValueProvider_ReturnsQueryStringValueProviderInstanceWithInvariantCulture()
|
||||
{
|
||||
// Arrange
|
||||
var request = new Mock<HttpRequest>();
|
||||
request.SetupGet(f => f.Query).Returns(Mock.Of<IQueryCollection>());
|
||||
var context = new Mock<HttpContext>();
|
||||
context.SetupGet(c => c.Items).Returns(new Dictionary<object, object>());
|
||||
context.SetupGet(c => c.Request).Returns(request.Object);
|
||||
var actionContext = new ActionContext(context.Object, new RouteData(), new ActionDescriptor());
|
||||
var queryValues = new Dictionary<string, StringValues>();
|
||||
queryValues.Add("foo", "bar");
|
||||
var context = new DefaultHttpContext();
|
||||
context.Request.Query = new QueryCollection(queryValues);
|
||||
var actionContext = new ActionContext(context, new RouteData(), new ActionDescriptor());
|
||||
var factoryContext = new ValueProviderFactoryContext(actionContext);
|
||||
var factory = new QueryStringValueProviderFactory();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
// 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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.IntegrationTests
|
||||
{
|
||||
public class JQueryFormatModelBindingIntegrationTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task BindsJQueryFormatData_FromQuery()
|
||||
{
|
||||
// Arrange
|
||||
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "parameter",
|
||||
ParameterType = typeof(Customer)
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(
|
||||
request =>
|
||||
{
|
||||
request.QueryString = new QueryString(
|
||||
"?Name=James&Address[0][City]=Redmond&Address[0][State][ShortName]=WA&Address[0][State][LongName]=Washington");
|
||||
},
|
||||
options =>
|
||||
{
|
||||
options.ValueProviderFactories.Add(new JQueryQueryStringValueProviderFactory());
|
||||
});
|
||||
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
var model = Assert.IsType<Customer>(modelBindingResult.Model);
|
||||
Assert.Equal("James", model.Name);
|
||||
Assert.NotNull(model.Address);
|
||||
var address = Assert.Single(model.Address);
|
||||
Assert.Equal("Redmond", address.City);
|
||||
Assert.NotNull(address.State);
|
||||
Assert.Equal("WA", address.State.ShortName);
|
||||
Assert.Equal("Washington", address.State.LongName);
|
||||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BindsJQueryFormatData_FromRequestBody()
|
||||
{
|
||||
// Arrange
|
||||
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
|
||||
var parameter = new ParameterDescriptor()
|
||||
{
|
||||
Name = "parameter",
|
||||
ParameterType = typeof(Customer)
|
||||
};
|
||||
|
||||
var testContext = ModelBindingTestHelper.GetTestContext(
|
||||
request =>
|
||||
{
|
||||
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(
|
||||
"Name=James&Address[0][City]=Redmond&Address[0][State][ShortName]=WA&Address[0][State][LongName]=Washington"));
|
||||
request.ContentType = "application/x-www-form-urlencoded;charset=utf-8";
|
||||
});
|
||||
|
||||
var modelState = testContext.ModelState;
|
||||
|
||||
// Act
|
||||
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(modelBindingResult.IsModelSet);
|
||||
|
||||
var model = Assert.IsType<Customer>(modelBindingResult.Model);
|
||||
Assert.Equal("James", model.Name);
|
||||
Assert.NotNull(model.Address);
|
||||
var address = Assert.Single(model.Address);
|
||||
Assert.Equal("Redmond", address.City);
|
||||
Assert.NotNull(address.State);
|
||||
Assert.Equal("WA", address.State.ShortName);
|
||||
Assert.Equal("Washington", address.State.LongName);
|
||||
Assert.True(modelState.IsValid);
|
||||
}
|
||||
|
||||
private class Customer
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public List<Address> Address { get; set; }
|
||||
}
|
||||
|
||||
private class Address
|
||||
{
|
||||
public string City { get; set; }
|
||||
public State State { get; set; }
|
||||
}
|
||||
|
||||
private class State
|
||||
{
|
||||
public string ShortName { get; set; }
|
||||
public string LongName { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue