Skip to content

Commit c2347e4

Browse files
committed
Merge branch 'release' into dev
2 parents 7085e8a + 48f09d0 commit c2347e4

File tree

11 files changed

+749
-547
lines changed

11 files changed

+749
-547
lines changed

src/Microsoft.AspNet.Mvc.Core/ModelBinding/ComplexModelDto.cs

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/Microsoft.AspNet.Mvc.Core/ModelBinding/ComplexModelDtoModelBinder.cs

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/Microsoft.AspNet.Mvc.Core/ModelBinding/MutableObjectModelBinder.cs

Lines changed: 94 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,22 @@ public virtual async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBind
4040
return null;
4141
}
4242

43-
EnsureModel(bindingContext);
44-
var result = await CreateAndPopulateDto(bindingContext, mutableObjectBinderContext.PropertyMetadata);
43+
// Create model first (if necessary) to avoid reporting errors about properties when activation fails.
44+
var model = GetModel(bindingContext);
45+
46+
var results = await BindPropertiesAsync(bindingContext, mutableObjectBinderContext.PropertyMetadata);
4547

4648
var validationNode = new ModelValidationNode(
4749
bindingContext.ModelName,
4850
bindingContext.ModelMetadata,
49-
bindingContext.Model);
51+
model);
52+
53+
// Post-processing e.g. property setters and hooking up validation.
54+
bindingContext.Model = model;
55+
ProcessResults(bindingContext, results, validationNode);
5056

51-
// post-processing, e.g. property setters and hooking up validation
52-
ProcessDto(bindingContext, (ComplexModelDto)result.Model, validationNode);
5357
return new ModelBindingResult(
54-
bindingContext.Model,
58+
model,
5559
bindingContext.ModelName,
5660
isModelSet: true,
5761
validationNode: validationNode);
@@ -192,7 +196,8 @@ private async Task<bool> CanBindValue(ModelBindingContext bindingContext)
192196
var bindingSource = bindingContext.BindingSource;
193197
if (bindingSource != null && !bindingSource.IsGreedy)
194198
{
195-
var rootValueProvider = bindingContext.OperationBindingContext.ValueProvider as IBindingSourceValueProvider;
199+
var rootValueProvider =
200+
bindingContext.OperationBindingContext.ValueProvider as IBindingSourceValueProvider;
196201
if (rootValueProvider != null)
197202
{
198203
valueProvider = rootValueProvider.Filter(bindingSource);
@@ -225,12 +230,6 @@ private static bool CanBindType(ModelMetadata modelMetadata)
225230
return false;
226231
}
227232

228-
if (modelMetadata.ModelType == typeof(ComplexModelDto))
229-
{
230-
// forbidden type - will cause a stack overflow if we try binding this type
231-
return false;
232-
}
233-
234233
return true;
235234
}
236235

@@ -265,24 +264,50 @@ private static bool CanUpdateReadOnlyProperty(Type propertyType)
265264
return true;
266265
}
267266

268-
private async Task<ModelBindingResult> CreateAndPopulateDto(
267+
// Returned dictionary contains entries corresponding to properties against which binding was attempted. If
268+
// binding failed, the entry's value will have IsModelSet == false. Binding is attempted for all elements of
269+
// propertyMetadatas.
270+
private async Task<IDictionary<ModelMetadata, ModelBindingResult>> BindPropertiesAsync(
269271
ModelBindingContext bindingContext,
270272
IEnumerable<ModelMetadata> propertyMetadatas)
271273
{
272-
// create a DTO and call into the DTO binder
273-
var dto = new ComplexModelDto(bindingContext.ModelMetadata, propertyMetadatas);
274-
275-
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
276-
var dtoMetadata = metadataProvider.GetMetadataForType(typeof(ComplexModelDto));
274+
var results = new Dictionary<ModelMetadata, ModelBindingResult>();
275+
foreach (var propertyMetadata in propertyMetadatas)
276+
{
277+
var propertyModelName = ModelNames.CreatePropertyModelName(
278+
bindingContext.ModelName,
279+
propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName);
280+
var childContext = ModelBindingContext.GetChildModelBindingContext(
281+
bindingContext,
282+
propertyModelName,
283+
propertyMetadata);
284+
285+
// ModelBindingContext.Model property values may be non-null when invoked via TryUpdateModel(). Pass
286+
// complex (including collection) values down so that binding system does not unnecessarily recreate
287+
// instances or overwrite inner properties that are not bound. No need for this with simple values
288+
// because they will be overwritten if binding succeeds. Arrays are never reused because they cannot
289+
// be resized.
290+
//
291+
// ModelMetadata.PropertyGetter is not null safe; use it only if Model is non-null.
292+
if (bindingContext.Model != null &&
293+
propertyMetadata.PropertyGetter != null &&
294+
propertyMetadata.IsComplexType &&
295+
!propertyMetadata.ModelType.IsArray)
296+
{
297+
childContext.Model = propertyMetadata.PropertyGetter(bindingContext.Model);
298+
}
277299

278-
var childContext = ModelBindingContext.GetChildModelBindingContext(
279-
bindingContext,
280-
bindingContext.ModelName,
281-
dtoMetadata);
300+
var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childContext);
301+
if (result == null)
302+
{
303+
// Could not bind. Let ProcessResult() know explicitly.
304+
result = new ModelBindingResult(model: null, key: propertyModelName, isModelSet: false);
305+
}
282306

283-
childContext.Model = dto;
307+
results[propertyMetadata] = result;
308+
}
284309

285-
return await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childContext);
310+
return results;
286311
}
287312

288313
/// <summary>
@@ -298,16 +323,18 @@ protected virtual object CreateModel([NotNull] ModelBindingContext bindingContex
298323
}
299324

300325
/// <summary>
301-
/// Ensures <see cref="ModelBindingContext.Model"/> is not <c>null</c> in given
302-
/// <paramref name="bindingContext"/>.
326+
/// Get <see cref="ModelBindingContext.Model"/> if that property is not <c>null</c>. Otherwise activate a
327+
/// new instance of <see cref="ModelBindingContext.ModelType"/>.
303328
/// </summary>
304329
/// <param name="bindingContext">The <see cref="ModelBindingContext"/>.</param>
305-
protected virtual void EnsureModel([NotNull] ModelBindingContext bindingContext)
330+
protected virtual object GetModel([NotNull] ModelBindingContext bindingContext)
306331
{
307-
if (bindingContext.Model == null)
332+
if (bindingContext.Model != null)
308333
{
309-
bindingContext.Model = CreateModel(bindingContext);
334+
return bindingContext.Model;
310335
}
336+
337+
return CreateModel(bindingContext);
311338
}
312339

313340
/// <summary>
@@ -365,17 +392,18 @@ internal static PropertyValidationInfo GetPropertyValidationInfo(ModelBindingCon
365392
}
366393

367394
// Internal for testing.
368-
internal ModelValidationNode ProcessDto(
395+
internal ModelValidationNode ProcessResults(
369396
ModelBindingContext bindingContext,
370-
ComplexModelDto dto,
397+
IDictionary<ModelMetadata, ModelBindingResult> results,
371398
ModelValidationNode validationNode)
372399
{
373400
var metadataProvider = bindingContext.OperationBindingContext.MetadataProvider;
374-
var modelExplorer = metadataProvider.GetModelExplorerForType(bindingContext.ModelType, bindingContext.Model);
401+
var modelExplorer =
402+
metadataProvider.GetModelExplorerForType(bindingContext.ModelType, bindingContext.Model);
375403
var validationInfo = GetPropertyValidationInfo(bindingContext);
376404

377405
// Eliminate provided properties from RequiredProperties; leaving just *missing* required properties.
378-
var boundProperties = dto.Results.Where(p => p.Value.IsModelSet).Select(p => p.Key.PropertyName);
406+
var boundProperties = results.Where(p => p.Value.IsModelSet).Select(p => p.Key.PropertyName);
379407
validationInfo.RequiredProperties.ExceptWith(boundProperties);
380408

381409
foreach (var missingRequiredProperty in validationInfo.RequiredProperties)
@@ -389,25 +417,25 @@ internal ModelValidationNode ProcessDto(
389417
Resources.FormatModelBinding_MissingBindRequiredMember(propertyName));
390418
}
391419

392-
// For each property that ComplexModelDtoModelBinder attempted to bind, call the setter, recording
420+
// For each property that BindPropertiesAsync() attempted to bind, call the setter, recording
393421
// exceptions as necessary.
394-
foreach (var entry in dto.Results)
422+
foreach (var entry in results)
395423
{
396-
var dtoResult = entry.Value;
397-
if (dtoResult != null)
424+
var result = entry.Value;
425+
if (result != null)
398426
{
399427
var propertyMetadata = entry.Key;
400-
SetProperty(bindingContext, modelExplorer, propertyMetadata, dtoResult);
428+
SetProperty(bindingContext, modelExplorer, propertyMetadata, result);
401429

402-
var dtoValidationNode = dtoResult.ValidationNode;
403-
if (dtoValidationNode == null)
430+
var propertyValidationNode = result.ValidationNode;
431+
if (propertyValidationNode == null)
404432
{
405-
// Make sure that irrespective of if the properties of the model were bound with a value,
433+
// Make sure that irrespective of whether the properties of the model were bound with a value,
406434
// create a validation node so that these get validated.
407-
dtoValidationNode = new ModelValidationNode(dtoResult.Key, entry.Key, dtoResult.Model);
435+
propertyValidationNode = new ModelValidationNode(result.Key, entry.Key, result.Model);
408436
}
409437

410-
validationNode.ChildNodes.Add(dtoValidationNode);
438+
validationNode.ChildNodes.Add(propertyValidationNode);
411439
}
412440
}
413441

@@ -422,71 +450,69 @@ internal ModelValidationNode ProcessDto(
422450
/// The <see cref="ModelExplorer"/> for the model containing property to set.
423451
/// </param>
424452
/// <param name="propertyMetadata">The <see cref="ModelMetadata"/> for the property to set.</param>
425-
/// <param name="dtoResult">The <see cref="ModelBindingResult"/> for the property's new value.</param>
453+
/// <param name="result">The <see cref="ModelBindingResult"/> for the property's new value.</param>
426454
/// <remarks>Should succeed in all cases that <see cref="CanUpdateProperty"/> returns <c>true</c>.</remarks>
427455
protected virtual void SetProperty(
428456
[NotNull] ModelBindingContext bindingContext,
429457
[NotNull] ModelExplorer modelExplorer,
430458
[NotNull] ModelMetadata propertyMetadata,
431-
[NotNull] ModelBindingResult dtoResult)
459+
[NotNull] ModelBindingResult result)
432460
{
433461
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase;
434-
var property = bindingContext.ModelType.GetProperty(
435-
propertyMetadata.PropertyName,
436-
bindingFlags);
462+
var property = bindingContext.ModelType.GetProperty(propertyMetadata.PropertyName, bindingFlags);
437463

438464
if (property == null)
439465
{
440466
// Nothing to do if property does not exist.
441467
return;
442468
}
443469

444-
if (!property.CanWrite)
470+
if (!result.IsModelSet)
445471
{
446-
// Try to handle as a collection if property exists but is not settable.
447-
AddToProperty(bindingContext, modelExplorer, property, dtoResult);
472+
// If we don't have a value, don't set it on the model and trounce a pre-initialized value.
448473
return;
449474
}
450475

451-
object value = null;
452-
if (dtoResult.IsModelSet)
453-
{
454-
value = dtoResult.Model;
455-
}
456-
457-
if (!dtoResult.IsModelSet)
476+
if (!property.CanWrite)
458477
{
459-
// If we don't have a value, don't set it on the model and trounce a pre-initialized
460-
// value.
478+
// Try to handle as a collection if property exists but is not settable.
479+
AddToProperty(bindingContext, modelExplorer, property, result);
461480
return;
462481
}
463482

483+
var value = result.Model;
464484
try
465485
{
466486
propertyMetadata.PropertySetter(bindingContext.Model, value);
467487
}
468488
catch (Exception exception)
469489
{
470-
AddModelError(exception, bindingContext, dtoResult);
490+
AddModelError(exception, bindingContext, result);
471491
}
472492
}
473493

474494
private void AddToProperty(
475495
ModelBindingContext bindingContext,
476496
ModelExplorer modelExplorer,
477497
PropertyInfo property,
478-
ModelBindingResult dtoResult)
498+
ModelBindingResult result)
479499
{
480500
var propertyExplorer = modelExplorer.GetExplorerForProperty(property.Name);
481501

482502
var target = propertyExplorer.Model;
483-
var source = dtoResult.Model;
484-
if (!dtoResult.IsModelSet || target == null || source == null)
503+
var source = result.Model;
504+
if (target == null || source == null)
485505
{
486506
// Cannot copy to or from a null collection.
487507
return;
488508
}
489509

510+
if (target == source)
511+
{
512+
// Added to the target collection in BindPropertiesAsync().
513+
return;
514+
}
515+
490516
// Determine T if this is an ICollection<T> property. No need for a T[] case because CanUpdateProperty()
491517
// ensures property is either settable or not an array. Underlying assumption is that CanUpdateProperty()
492518
// and SetProperty() are overridden together.
@@ -507,7 +533,7 @@ private void AddToProperty(
507533
}
508534
catch (Exception exception)
509535
{
510-
AddModelError(exception, bindingContext, dtoResult);
536+
AddModelError(exception, bindingContext, result);
511537
}
512538
}
513539

@@ -529,7 +555,7 @@ private static void CallPropertyAddRange<TElement>(object target, object source)
529555
private static void AddModelError(
530556
Exception exception,
531557
ModelBindingContext bindingContext,
532-
ModelBindingResult dtoResult)
558+
ModelBindingResult result)
533559
{
534560
var targetInvocationException = exception as TargetInvocationException;
535561
if (targetInvocationException != null && targetInvocationException.InnerException != null)
@@ -539,7 +565,7 @@ private static void AddModelError(
539565

540566
// Do not add an error message if a binding error has already occurred for this property.
541567
var modelState = bindingContext.ModelState;
542-
var modelStateKey = dtoResult.Key;
568+
var modelStateKey = result.Key;
543569
var validationState = modelState.GetFieldValidationState(modelStateKey);
544570
if (validationState == ModelValidationState.Unvalidated)
545571
{

0 commit comments

Comments
 (0)