Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
CR feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
rynowak committed May 22, 2015
1 parent cbadc75 commit 9f23ad7
Show file tree
Hide file tree
Showing 10 changed files with 596 additions and 242 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public override Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingCo
return base.BindModelAsync(bindingContext);
}

protected override object CreateEmptyCollection()
{
return new TElement[0];
}

/// <inheritdoc />
protected override object GetModel(IEnumerable<TElement> newCollection)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,31 @@ public virtual async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBind
{
ModelBindingHelper.ValidateBindingContext(bindingContext);

object model;

if (!await bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName))
{
// If this is the fallback case, and we failed to find data as a top-level model, then generate a
// default 'empty' model and return it.
var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
var hasExplicitAlias = bindingContext.BinderModelName != null;

if (isTopLevelObject && (hasExplicitAlias || bindingContext.ModelName == string.Empty))
{
model = CreateEmptyCollection();

var validationNode = new ModelValidationNode(
bindingContext.ModelName,
bindingContext.ModelMetadata,
model);

return new ModelBindingResult(
model,
bindingContext.ModelName,
isModelSet: true,
validationNode: validationNode);
}

return null;
}

Expand All @@ -46,7 +69,7 @@ public virtual async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBind
boundCollection = result.Model;
}

var model = bindingContext.Model;
model = bindingContext.Model;
if (model == null)
{
model = GetModel(boundCollection);
Expand All @@ -64,6 +87,12 @@ public virtual async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBind
validationNode: result?.ValidationNode);
}

// Called when we're creating a default 'empty' model for a top level bind.
protected virtual object CreateEmptyCollection()
{
return new List<TElement>();
}

// Used when the ValueProvider contains the collection to be bound as a single element, e.g. the raw value
// is [ "1", "2" ] and needs to be converted to an int[].
internal async Task<CollectionResult> BindSimpleCollection(
Expand Down Expand Up @@ -165,8 +194,6 @@ internal async Task<CollectionResult> BindComplexCollectionFromIndexes(
var didBind = false;
object boundValue = null;

var modelType = bindingContext.ModelType;

var result =
await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(childBindingContext);
if (result != null && result.IsModelSet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,10 @@ protected override object GetModel(IEnumerable<KeyValuePair<TKey, TValue>> newCo
{
return newCollection?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}

protected override object CreateEmptyCollection()
{
return new Dictionary<TKey, TValue>();
}
}
}
144 changes: 43 additions & 101 deletions src/Microsoft.AspNet.Mvc.Core/ModelBinding/GenericModelBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,129 +13,83 @@ public class GenericModelBinder : IModelBinder
{
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{
var bindingInfo = ResolveGenericBindingInfo(bindingContext.ModelType);
if (bindingInfo != null)
var binderType = ResolveBinderType(bindingContext.ModelType);
if (binderType != null)
{
var binder = (IModelBinder)Activator.CreateInstance(bindingInfo.ModelBinderType);
var binder = (IModelBinder)Activator.CreateInstance(binderType);
var result = await binder.BindModelAsync(bindingContext);

if (result != null && result.IsModelSet)
{
// Success - propagate the values returned by the model binder.
return result;
}

// If this is the fallback case, and we didn't bind as a top-level model, then generate a
// default 'empty' model and return it.
var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
var hasExplicitAlias = bindingContext.BinderModelName != null;

if (isTopLevelObject && (hasExplicitAlias || bindingContext.ModelName == string.Empty))
{
object model;
if (bindingInfo.UnderlyingModelType.IsArray)
{
model = Array.CreateInstance(bindingInfo.UnderlyingModelType.GetElementType(), 0);
}
else
{
model = Activator.CreateInstance(bindingInfo.UnderlyingModelType);
}

var validationNode = new ModelValidationNode(
bindingContext.ModelName,
bindingContext.ModelMetadata,
model);

return new ModelBindingResult(model, bindingContext.ModelName, true, validationNode);
}

// We always want to return a result for model types that we handle; tell the model binding
// system to skip other model binders by returning non-null.
return new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false);
var modelBindingResult = result != null ?
new ModelBindingResult(result.Model, result.Key, result.IsModelSet, result.ValidationNode) :
new ModelBindingResult(model: null, key: bindingContext.ModelName, isModelSet: false);

// Were able to resolve a binder type.
// Always tell the model binding system to skip other model binders i.e. return non-null.
return modelBindingResult;
}

return null;
}

private static GenericModelBindingInfo ResolveGenericBindingInfo(Type modelType)
private static Type ResolveBinderType(Type modelType)
{
return GetArrayBinderInfo(modelType) ??
GetCollectionBinderInfo(modelType) ??
GetDictionaryBinderInfo(modelType) ??
GetKeyValuePairBinderInfo(modelType);
return GetArrayBinder(modelType) ??
GetCollectionBinder(modelType) ??
GetDictionaryBinder(modelType) ??
GetKeyValuePairBinder(modelType);
}

private static GenericModelBindingInfo GetArrayBinderInfo(Type modelType)
private static Type GetArrayBinder(Type modelType)
{
if (modelType.IsArray)
{
var elementType = modelType.GetElementType();
var modelBinderType = typeof(ArrayModelBinder<>).MakeGenericType(elementType);

return new GenericModelBindingInfo()
{
ModelBinderType = modelBinderType,
UnderlyingModelType = modelType,
};
return typeof(ArrayModelBinder<>).MakeGenericType(elementType);
}

return null;
}

private static GenericModelBindingInfo GetCollectionBinderInfo(Type modelType)
private static Type GetCollectionBinder(Type modelType)
{
return GetGenericModelBindingInfo(
typeof(ICollection<>),
typeof(List<>),
typeof(CollectionModelBinder<>),
modelType);
return GetGenericBinderType(
typeof(ICollection<>),
typeof(List<>),
typeof(CollectionModelBinder<>),
modelType);
}

private static GenericModelBindingInfo GetDictionaryBinderInfo(Type modelType)
private static Type GetDictionaryBinder(Type modelType)
{
return GetGenericModelBindingInfo(
typeof(IDictionary<,>),
typeof(Dictionary<,>),
typeof(DictionaryModelBinder<,>),
modelType);
return GetGenericBinderType(
typeof(IDictionary<,>),
typeof(Dictionary<,>),
typeof(DictionaryModelBinder<,>),
modelType);
}

private static GenericModelBindingInfo GetKeyValuePairBinderInfo(Type modelType)
private static Type GetKeyValuePairBinder(Type modelType)
{
var modelTypeInfo = modelType.GetTypeInfo();
if (modelTypeInfo.IsGenericType &&
modelTypeInfo.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
{
var modelBinderType = typeof(KeyValuePairModelBinder<,>)
.MakeGenericType(modelTypeInfo.GenericTypeArguments);

return new GenericModelBindingInfo()
{
ModelBinderType = modelBinderType,
UnderlyingModelType = modelType,
};
return typeof(KeyValuePairModelBinder<,>).MakeGenericType(modelTypeInfo.GenericTypeArguments);
}

return null;
}

//
// Example:
// GetGenericBinderType(typeof(IList<T>), typeof(List<T>), typeof(ListBinder<T&>), ...)
//
// This means that the ListBinder<T> type can work with models that implement IList<T>, and if there is no
// existing model instance, the binder will create a List{T}.
//
// This method will return null if the given model type isn't compatible with the combination of
// supportedInterfaceType and modelType. If supportedInterfaceType and modelType are compatible, then
// it will return the closed-generic newInstanceType and closed-generic openBinderType.
// </remarks>
private static GenericModelBindingInfo GetGenericModelBindingInfo(
Type supportedInterfaceType,
Type newInstanceType,
Type openBinderType,
Type modelType)
/// <remarks>
/// Example: <c>GetGenericBinderType(typeof(IList&lt;T&gt;), typeof(List&lt;T&gt;),
/// typeof(ListBinder&lt;T&gt;), ...)</c> means that the <c>ListBinder&lt;T&gt;</c> type can update models that
/// implement <see cref="IList{T}"/>, and if for some reason the existing model instance is not updatable the
/// binder will create a <see cref="List{T}"/> object and bind to that instead. This method will return
/// <c>ListBinder&lt;T&gt;</c> or <c>null</c>, depending on whether the type and updatability checks succeed.
/// </remarks>
private static Type GetGenericBinderType(Type supportedInterfaceType,
Type newInstanceType,
Type openBinderType,
Type modelType)
{
Debug.Assert(supportedInterfaceType != null);
Debug.Assert(openBinderType != null);
Expand All @@ -154,11 +108,7 @@ private static GenericModelBindingInfo GetGenericModelBindingInfo(
return null;
}

return new GenericModelBindingInfo()
{
ModelBinderType = openBinderType.MakeGenericType(modelTypeArguments),
UnderlyingModelType = closedNewInstanceType,
};
return openBinderType.MakeGenericType(modelTypeArguments);
}

// Get the generic arguments for the binder, based on the model type. Or null if not compatible.
Expand All @@ -180,13 +130,5 @@ private static Type[] GetGenericBinderTypeArgs(Type supportedInterfaceType, Type

return modelTypeArguments;
}

private class GenericModelBindingInfo
{
public Type ModelBinderType { get; set; }

// The concrete type that should be created if needed
public Type UnderlyingModelType { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,27 @@ public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext binding
}
else
{
// Caller (GenericModelBinder) was able to resolve a binder type and will create a ModelBindingResult
// that exits current ModelBinding loop.
// If this is the fallback case, and we failed to find data as a top-level model, then generate a
// default 'empty' model and return it.
var isTopLevelObject = bindingContext.ModelMetadata.ContainerType == null;
var hasExplicitAlias = bindingContext.BinderModelName != null;

if (isTopLevelObject && (hasExplicitAlias || bindingContext.ModelName == string.Empty))
{
var model = new KeyValuePair<TKey, TValue>();

var validationNode = new ModelValidationNode(
bindingContext.ModelName,
bindingContext.ModelMetadata,
model);

return new ModelBindingResult(
model,
bindingContext.ModelName,
isModelSet: true,
validationNode: validationNode);
}

return null;
}
}
Expand Down
Loading

0 comments on commit 9f23ad7

Please sign in to comment.