This repository has been archived by the owner on Dec 14, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
Copy pathCompositeModelBinder.cs
171 lines (154 loc) · 7.36 KB
/
CompositeModelBinder.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// 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.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
/// <summary>
/// Represents an <see cref="IModelBinder"/> that delegates to one of a collection of <see cref="IModelBinder"/>
/// instances.
/// </summary>
/// <remarks>
/// If no binder is available and the <see cref="ModelBindingContext"/> allows it,
/// this class tries to find a binder using an empty prefix.
/// </remarks>
public class CompositeModelBinder : ICompositeModelBinder
{
/// <summary>
/// Initializes a new instance of the CompositeModelBinder class.
/// </summary>
/// <param name="modelBinders">A collection of <see cref="IModelBinder"/> instances.</param>
public CompositeModelBinder(IEnumerable<IModelBinder> modelBinders)
{
if (modelBinders == null)
{
throw new ArgumentNullException(nameof(modelBinders));
}
ModelBinders = new List<IModelBinder>(modelBinders);
}
/// <inheritdoc />
public IReadOnlyList<IModelBinder> ModelBinders { get; }
public virtual Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var newBindingContext = CreateNewBindingContext(bindingContext);
if (newBindingContext == null)
{
// Unable to find a value provider for this binding source. Binding will fail.
return ModelBindingResult.NoResultAsync;
}
return RunModelBinders(newBindingContext);
}
private async Task<ModelBindingResult> RunModelBinders(ModelBindingContext bindingContext)
{
RuntimeHelpers.EnsureSufficientExecutionStack();
// Perf: Avoid allocations
for (var i = 0; i < ModelBinders.Count; i++)
{
var binder = ModelBinders[i];
var result = await binder.BindModelAsync(bindingContext);
if (result != ModelBindingResult.NoResult)
{
// This condition is necessary because the ModelState entry would never be validated if
// caller fell back to the empty prefix, leading to an possibly-incorrect !IsValid. In most
// (hopefully all) cases, the ModelState entry exists because some binders add errors before
// returning a result with !IsModelSet. Those binders often cannot run twice anyhow.
if (result.IsModelSet ||
bindingContext.ModelState.ContainsKey(bindingContext.ModelName))
{
if (bindingContext.IsTopLevelObject && result.Model != null)
{
ValidationStateEntry entry;
if (!bindingContext.ValidationState.TryGetValue(result.Model, out entry))
{
entry = new ValidationStateEntry();
bindingContext.ValidationState.Add(result.Model, entry);
}
entry.Key = entry.Key ?? result.Key;
entry.Metadata = entry.Metadata ?? bindingContext.ModelMetadata;
}
return result;
}
// Current binder should have been able to bind value but found nothing. Exit loop in a way that
// tells caller to fall back to the empty prefix, if appropriate. Do not return result because it
// means only "other binders are not applicable".
break;
}
}
// Either we couldn't find a binder, or the binder couldn't bind. Distinction is not important.
return ModelBindingResult.NoResult;
}
private static ModelBindingContext CreateNewBindingContext(ModelBindingContext oldBindingContext)
{
// If the property has a specified data binding sources, we need to filter the set of value providers
// to just those that match. We can skip filtering when IsGreedy == true, because that can't use
// value providers.
//
// We also want to base this filtering on the - top-level value provider in case the data source
// on this property doesn't intersect with the ambient data source.
//
// Ex:
//
// public class Person
// {
// [FromQuery]
// public int Id { get; set; }
// }
//
// public IActionResult UpdatePerson([FromForm] Person person) { }
//
// In this example, [FromQuery] overrides the ambient data source (form).
IValueProvider valueProvider = oldBindingContext.ValueProvider;
var bindingSource = oldBindingContext.BindingSource;
if (bindingSource != null && !bindingSource.IsGreedy)
{
var bindingSourceValueProvider = valueProvider as IBindingSourceValueProvider;
if (bindingSourceValueProvider != null)
{
valueProvider = bindingSourceValueProvider.Filter(bindingSource);
if (valueProvider == null)
{
// Unable to find a value provider for this binding source.
return null;
}
}
}
var newBindingContext = new ModelBindingContext
{
Model = oldBindingContext.Model,
ModelMetadata = oldBindingContext.ModelMetadata,
FieldName = oldBindingContext.FieldName,
ModelState = oldBindingContext.ModelState,
ValueProvider = valueProvider,
OperationBindingContext = oldBindingContext.OperationBindingContext,
PropertyFilter = oldBindingContext.PropertyFilter,
BinderModelName = oldBindingContext.BinderModelName,
BindingSource = oldBindingContext.BindingSource,
BinderType = oldBindingContext.BinderType,
IsTopLevelObject = oldBindingContext.IsTopLevelObject,
ValidationState = oldBindingContext.ValidationState,
};
if (bindingSource != null && bindingSource.IsGreedy)
{
newBindingContext.ModelName = oldBindingContext.ModelName;
}
else if (
!oldBindingContext.FallbackToEmptyPrefix ||
newBindingContext.ValueProvider.ContainsPrefix(oldBindingContext.ModelName))
{
newBindingContext.ModelName = oldBindingContext.ModelName;
}
else
{
newBindingContext.ModelName = string.Empty;
}
return newBindingContext;
}
}
}