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.
|
// 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.
|
// 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.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Microsoft.AspNetCore.Mvc.Internal;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
|
|
@ -12,11 +10,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An <see cref="IValueProvider"/> for jQuery formatted form data.
|
/// An <see cref="IValueProvider"/> for jQuery formatted form data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JQueryFormValueProvider : BindingSourceValueProvider, IEnumerableValueProvider
|
public class JQueryFormValueProvider : JQueryValueProvider
|
||||||
{
|
{
|
||||||
private readonly IDictionary<string, StringValues> _values;
|
|
||||||
private PrefixContainer _prefixContainer;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="JQueryFormValueProvider"/> class.
|
/// Initializes a new instance of the <see cref="JQueryFormValueProvider"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -27,60 +22,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
BindingSource bindingSource,
|
BindingSource bindingSource,
|
||||||
IDictionary<string, StringValues> values,
|
IDictionary<string, StringValues> values,
|
||||||
CultureInfo culture)
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Core;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A <see cref="IValueProviderFactory"/> for <see cref="JQueryFormValueProvider"/>.
|
/// An <see cref="IValueProviderFactory"/> for <see cref="JQueryFormValueProvider"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JQueryFormValueProviderFactory : IValueProviderFactory
|
public class JQueryFormValueProviderFactory : IValueProviderFactory
|
||||||
{
|
{
|
||||||
|
|
@ -38,97 +33,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
|
||||||
private static async Task AddValueProviderAsync(ValueProviderFactoryContext context)
|
private static async Task AddValueProviderAsync(ValueProviderFactoryContext context)
|
||||||
{
|
{
|
||||||
var request = context.ActionContext.HttpContext.Request;
|
var request = context.ActionContext.HttpContext.Request;
|
||||||
|
|
||||||
|
var formCollection = await request.ReadFormAsync();
|
||||||
|
|
||||||
var valueProvider = new JQueryFormValueProvider(
|
var valueProvider = new JQueryFormValueProvider(
|
||||||
BindingSource.Form,
|
BindingSource.Form,
|
||||||
await GetValueCollectionAsync(request),
|
JQueryKeyValuePairNormalizer.GetValues(formCollection, formCollection.Count),
|
||||||
CultureInfo.CurrentCulture);
|
CultureInfo.CurrentCulture);
|
||||||
|
|
||||||
context.ValueProviders.Add(valueProvider);
|
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));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
var valueProvider = new QueryStringValueProvider(
|
var query = context.ActionContext.HttpContext.Request.Query;
|
||||||
BindingSource.Query,
|
if (query != null && query.Count > 0)
|
||||||
context.ActionContext.HttpContext.Request.Query,
|
{
|
||||||
CultureInfo.InvariantCulture);
|
var valueProvider = new QueryStringValueProvider(
|
||||||
|
BindingSource.Query,
|
||||||
|
query,
|
||||||
|
CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
context.ValueProviders.Add(valueProvider);
|
context.ValueProviders.Add(valueProvider);
|
||||||
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,5 +28,9 @@
|
||||||
"TypeId": "public class Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinderProvider : Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderProvider",
|
"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)",
|
"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"
|
"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);
|
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)
|
private static ValueProviderFactoryContext CreateContext(string contentType, Dictionary<string, StringValues> formValues)
|
||||||
{
|
{
|
||||||
var context = new DefaultHttpContext();
|
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.Globalization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Internal;
|
||||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Moq;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Test
|
||||||
{
|
{
|
||||||
public class QueryStringValueProviderFactoryTest
|
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]
|
[Fact]
|
||||||
public async Task GetValueProvider_ReturnsQueryStringValueProviderInstanceWithInvariantCulture()
|
public async Task GetValueProvider_ReturnsQueryStringValueProviderInstanceWithInvariantCulture()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var request = new Mock<HttpRequest>();
|
var queryValues = new Dictionary<string, StringValues>();
|
||||||
request.SetupGet(f => f.Query).Returns(Mock.Of<IQueryCollection>());
|
queryValues.Add("foo", "bar");
|
||||||
var context = new Mock<HttpContext>();
|
var context = new DefaultHttpContext();
|
||||||
context.SetupGet(c => c.Items).Returns(new Dictionary<object, object>());
|
context.Request.Query = new QueryCollection(queryValues);
|
||||||
context.SetupGet(c => c.Request).Returns(request.Object);
|
var actionContext = new ActionContext(context, new RouteData(), new ActionDescriptor());
|
||||||
var actionContext = new ActionContext(context.Object, new RouteData(), new ActionDescriptor());
|
|
||||||
var factoryContext = new ValueProviderFactoryContext(actionContext);
|
var factoryContext = new ValueProviderFactoryContext(actionContext);
|
||||||
var factory = new QueryStringValueProviderFactory();
|
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