Levi Broderick, a Senior Developer on
ASP.NET MVC
and all around smart dude, posted some details to an internal Microsoft mailing list today and I thought it was worth sharing. Levi agreed and I've expanded on it some.
Phil
blogged some about
Model Binding to a List last October
.
The default model binder will handle a number of collection types for you, if you play by the rules and make your HTML form elements follow a certain format.
If the method signature looks like this:
public ActionResult Blah(Person[] people) {
// ...
}
And we are given this input in our HTML:
<input type="text" name="people[0].FirstName" value="George" />
<input type="text" name="people[0].LastName" value="Washington" />
<input type="text" name="people[1].FirstName" value="Abraham" />
<input type="text" name="people[1].LastName" value="Lincoln" />
<input type="text" name="people[3].FirstName" value="Thomas" />
<input type="text" name="people[3].LastName" value="Jefferson" />
Which turns into this as a HTTP POST:
people%5B0%5D.FirstName=George&people%5B0%5D.LastName=Washington&people%5B1%5D.FirstName=Abraham&people%5B1%5D.LastName=Lincoln&people%5B3%5D.FirstName=Thomas&people%5B3%5D.LastName=Jefferson
Which is basically:
people[0].FirstName = "George"
people[0].LastName = "Washington"
people[1].FirstName = "Abraham"
people[1].LastName = "Lincoln"
people[3].FirstName = "Thomas"
people[3].LastName = "Jefferson"
Then it will be just as if we had written this in code:
people = new Person[] {
new Person() { FirstName = "George", LastName = "Washington" },
new Person() { FirstName = "Abraham", LastName = "Lincoln" }
};
The way that we read in the properties is by looking for
parameterName[index].PropertyName
. The index must be zero-based and unbroken. In the above example, because there was no people[2], we stop after Abraham Lincoln and don’t continue to Thomas Jefferson.
If the signature looks like this:
public ActionResult Blah(IDictionary<string, Company> stocks) {
// ...
}
And we are given this in HTML:
<input type="text" name="stocks[0].Key" value="MSFT" />
<input type="text" name="stocks[0].Value.CompanyName" value="Microsoft Corporation" />
<input type="text" name="stocks[0].Value.Industry" value="Computer Software" />
<input type="text" name="stocks[1].Key" value="AAPL" />
<input type="text" name="stocks[1].Value.CompanyName" value="Apple, Inc." />
<input type="text" name="stocks[1].Value.Industry" value="Consumer Devices" />
Which like this:
stocks[0].Key = "MSFT"
stocks[0].Value.CompanyName = "Microsoft Corporation"
stocks[0].Value.Industry = "Computer Software"
stocks[1].Key = "AAPL"
stocks[1].Value.CompanyName = "Apple, Inc."
stocks[1].Value.Industry = "Consumer Devices"
Then it will be just as if we had written:
stocks = new Dictionary<string, Company>() {
{ "MSFT", new Company() { CompanyName = "Microsoft Corporation", Industry = "Computer Software" } },
{ "AAPL", new Company() { CompanyName = "Apple, Inc.", Industry = "Consumer Devices" } }
};
The way that we read in the keys is by looking for
parameterName[index].Key
, and the way that we read in the values is by looking for
parameterName[index].Value
. If the key or value type is a complex type (like Company, in the above example), then we treat parameterName[index].Key or parameterName[index].Value as the entire field prefix and start appending the
.PropertyName
suffix to the end of it. The index must also be zero-based and unbroken, as mentioned previously.
Parameters of type
IEnumerable<T>, ICollection<T>, IList<T>, T[], Collection<T>, and List<T>
are bound using the first syntax. Parameters of type
IDictionary<TKey, TValue>
and
Dictionary<TKey, TValue>
are bound using the second syntax.
Of course, as with most of ASP.NET MVC, if you don't like this behavior you're welcome to change it by writing your own binders for specific types or by pulling the information from a FormCollection directly and doing your own thing.
Levi adds:
FWIW – you don’t need the bracket notation if you’re submitting simple types to the server. That is, if your request contains
key=foo&key=bar&key=baz
, we’ll correctly bind that to an
IEnumerable<T>, IList<T>, ICollection<T>, T[], Collection<T>
, or
List<T>
. In the first sentence in this paragraph, "simple type" means a type for which TypeDescriptor.GetConverter(typeof(T)).CanConvertFrom(typeof(string)) returns
true
. This makes a handful of cases simpler.
Thanks to Levi for the nitty gritty!