This bug occurs when using jQuery POST to send complex JavaScript objects to the server as encoded HTTP form data.
For example, if I need to post the following object:
```
{
singleProperty: 'hello',
arrayProperty: [
{
propertyOne: 1,
propertyTwo: 'Two',
},
{
propertyOne: 2,
propertyTwo: 'Four',
},
{
propertyOne: 3,
propertyTwo: 'Six',
}
]
}
```
Then when jQuery posts that object as form data, it posts the following (line breaks added for readability):
"singleProperty=hello&
arrayProperty[0][propertyOne]=1&
arrayProperty[0][propertyTwo]=Two&
arrayProperty[1][propertyOne]=2&
arrayProperty[1][propertyTwo]=Four&
arrayProperty[2][propertyOne]=3&
arrayProperty[2][propertyTwo]=Six"
But ASP.NET MVC is expecting this (line breaks added for readability):
"singleProperty=hello&
arrayProperty[0].propertyOne=1&
arrayProperty[0].propertyTwo=Two&
arrayProperty[1].propertyOne=2&
arrayProperty[1].propertyTwo=Four&
arrayProperty[2].propertyOne=3&
arrayProperty[2].propertyTwo=Six"
The result of this discrepancy is that the properties inside of each of the collection elements of the action parameter object are all defaulted.
I have attached a small project that exemplifies this behavior.
Comments: Essentially, I think this bug comes down to how the different technologies address named members of objects (e.g. properties). It appears that jQuery always uses bracket notation, whereas ASP.NET MVC only allows bracket notation when dealing with IDictionary<,> objects. I think that all of the value providers should adhere to a standard method of addressing members (items in collections and named members) and handle the mapping from what is provided to what is expected. This would possibly involve changing some code that has not been release as open source (e.g. the Form collections in RequestContext, etc.), and may be hindered by other dependent projects. I attempted to make a viable intermediate solution and was able to replace the default FormValueProvider with the following implementation in my solution and have it work as I expect (this does break the tests provided in the ASP.NET web stack solution, but they can fairly easily be re-written and expanded to include this scenario, which I may post in another comment). ``` public class FormValueProvider : IValueProvider { private const string BracketExpressionString = @"\[([A-Za-z]+)\]"; private static readonly Regex BracketExpression = new Regex(BracketExpressionString); private static IEnumerable<string> ParseKey(string key) { // for form values like data[key1][key2], as provided using jQuery $.post, we want // to also ensure that the form data.key1.key2 is in the dictionary to conform to // what ASP.NET MVC expects var result = new List<string> { key }; var str = key; while (BracketExpression.IsMatch(str)) { str = BracketExpression.Replace(str, @".$1", 1); result.Add(str); } return result; } private readonly IValueProvider _valueProvider; public FormValueProvider(ControllerContext controllerContext) : this(controllerContext.HttpContext.Request.Unvalidated.Form) { } public FormValueProvider(NameValueCollection formValues) { var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (string key in formValues) { var value = formValues.Get(key); if (value != null) { var keys = ParseKey(key); foreach (var k in keys) { values[k] = value; } } } _valueProvider = new DictionaryValueProvider<string>(values, CultureInfo.CurrentCulture); } public bool ContainsPrefix(string prefix) { var result = _valueProvider.ContainsPrefix(prefix); return result; } public ValueProviderResult GetValue(string key) { var result = _valueProvider.GetValue(key); return result; } } ``` Whether or not this is wholly correct, or fitting with the Microsoft team's desired direction remains to be seen.
For example, if I need to post the following object:
```
{
singleProperty: 'hello',
arrayProperty: [
{
propertyOne: 1,
propertyTwo: 'Two',
},
{
propertyOne: 2,
propertyTwo: 'Four',
},
{
propertyOne: 3,
propertyTwo: 'Six',
}
]
}
```
Then when jQuery posts that object as form data, it posts the following (line breaks added for readability):
"singleProperty=hello&
arrayProperty[0][propertyOne]=1&
arrayProperty[0][propertyTwo]=Two&
arrayProperty[1][propertyOne]=2&
arrayProperty[1][propertyTwo]=Four&
arrayProperty[2][propertyOne]=3&
arrayProperty[2][propertyTwo]=Six"
But ASP.NET MVC is expecting this (line breaks added for readability):
"singleProperty=hello&
arrayProperty[0].propertyOne=1&
arrayProperty[0].propertyTwo=Two&
arrayProperty[1].propertyOne=2&
arrayProperty[1].propertyTwo=Four&
arrayProperty[2].propertyOne=3&
arrayProperty[2].propertyTwo=Six"
The result of this discrepancy is that the properties inside of each of the collection elements of the action parameter object are all defaulted.
I have attached a small project that exemplifies this behavior.
Comments: Essentially, I think this bug comes down to how the different technologies address named members of objects (e.g. properties). It appears that jQuery always uses bracket notation, whereas ASP.NET MVC only allows bracket notation when dealing with IDictionary<,> objects. I think that all of the value providers should adhere to a standard method of addressing members (items in collections and named members) and handle the mapping from what is provided to what is expected. This would possibly involve changing some code that has not been release as open source (e.g. the Form collections in RequestContext, etc.), and may be hindered by other dependent projects. I attempted to make a viable intermediate solution and was able to replace the default FormValueProvider with the following implementation in my solution and have it work as I expect (this does break the tests provided in the ASP.NET web stack solution, but they can fairly easily be re-written and expanded to include this scenario, which I may post in another comment). ``` public class FormValueProvider : IValueProvider { private const string BracketExpressionString = @"\[([A-Za-z]+)\]"; private static readonly Regex BracketExpression = new Regex(BracketExpressionString); private static IEnumerable<string> ParseKey(string key) { // for form values like data[key1][key2], as provided using jQuery $.post, we want // to also ensure that the form data.key1.key2 is in the dictionary to conform to // what ASP.NET MVC expects var result = new List<string> { key }; var str = key; while (BracketExpression.IsMatch(str)) { str = BracketExpression.Replace(str, @".$1", 1); result.Add(str); } return result; } private readonly IValueProvider _valueProvider; public FormValueProvider(ControllerContext controllerContext) : this(controllerContext.HttpContext.Request.Unvalidated.Form) { } public FormValueProvider(NameValueCollection formValues) { var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (string key in formValues) { var value = formValues.Get(key); if (value != null) { var keys = ParseKey(key); foreach (var k in keys) { values[k] = value; } } } _valueProvider = new DictionaryValueProvider<string>(values, CultureInfo.CurrentCulture); } public bool ContainsPrefix(string prefix) { var result = _valueProvider.ContainsPrefix(prefix); return result; } public ValueProviderResult GetValue(string key) { var result = _valueProvider.GetValue(key); return result; } } ``` Whether or not this is wholly correct, or fitting with the Microsoft team's desired direction remains to be seen.