138 lines
5.3 KiB
C#
138 lines
5.3 KiB
C#
// Copyright (c) Microsoft Open Technologies, Inc. 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.Diagnostics;
|
|
using System.Reflection;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNet.Mvc.ModelBinding.Internal;
|
|
using Microsoft.Framework.DependencyInjection;
|
|
|
|
namespace Microsoft.AspNet.Mvc.ModelBinding
|
|
{
|
|
public class GenericModelBinder : IModelBinder
|
|
{
|
|
private readonly ITypeActivator _activator;
|
|
private readonly IServiceProvider _serviceProvider;
|
|
|
|
public GenericModelBinder(IServiceProvider serviceProvider, ITypeActivator activator)
|
|
{
|
|
_serviceProvider = serviceProvider;
|
|
_activator = activator;
|
|
}
|
|
|
|
public async Task<bool> BindModelAsync(ModelBindingContext bindingContext)
|
|
{
|
|
var binderType = ResolveBinderType(bindingContext.ModelType);
|
|
if (binderType != null)
|
|
{
|
|
var binder = (IModelBinder)_activator.CreateInstance(_serviceProvider, binderType);
|
|
await binder.BindModelAsync(bindingContext);
|
|
|
|
// Was able to resolve a binder type, hence we should tell the model binding system to return
|
|
// true so that none of the other model binders participate.
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static Type ResolveBinderType(Type modelType)
|
|
{
|
|
return GetArrayBinder(modelType) ??
|
|
GetCollectionBinder(modelType) ??
|
|
GetDictionaryBinder(modelType) ??
|
|
GetKeyValuePairBinder(modelType);
|
|
}
|
|
|
|
private static Type GetArrayBinder(Type modelType)
|
|
{
|
|
if (modelType.IsArray)
|
|
{
|
|
var elementType = modelType.GetElementType();
|
|
return typeof(ArrayModelBinder<>).MakeGenericType(elementType);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static Type GetCollectionBinder(Type modelType)
|
|
{
|
|
return GetGenericBinderType(
|
|
typeof(ICollection<>),
|
|
typeof(List<>),
|
|
typeof(CollectionModelBinder<>),
|
|
modelType);
|
|
}
|
|
|
|
private static Type GetDictionaryBinder(Type modelType)
|
|
{
|
|
return GetGenericBinderType(
|
|
typeof(IDictionary<,>),
|
|
typeof(Dictionary<,>),
|
|
typeof(DictionaryModelBinder<,>),
|
|
modelType);
|
|
}
|
|
|
|
private static Type GetKeyValuePairBinder(Type modelType)
|
|
{
|
|
return ModelBindingHelper.GetPossibleBinderInstanceType(
|
|
closedModelType: modelType,
|
|
openModelType: typeof(KeyValuePair<,>),
|
|
openBinderType: typeof(KeyValuePairModelBinder<,>));
|
|
}
|
|
|
|
/// <remarks>
|
|
/// Example: <c>GetGenericBinderType(typeof(IList<T>), typeof(List<T>),
|
|
/// typeof(ListBinder<T>), ...)</c> means that the <c>ListBinder<T></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<T></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);
|
|
Debug.Assert(modelType != null);
|
|
|
|
var modelTypeArguments = GetGenericBinderTypeArgs(supportedInterfaceType, modelType);
|
|
|
|
if (modelTypeArguments == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var closedNewInstanceType = newInstanceType.MakeGenericType(modelTypeArguments);
|
|
if (!modelType.GetTypeInfo().IsAssignableFrom(closedNewInstanceType.GetTypeInfo()))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return openBinderType.MakeGenericType(modelTypeArguments);
|
|
}
|
|
|
|
// Get the generic arguments for the binder, based on the model type. Or null if not compatible.
|
|
private static Type[] GetGenericBinderTypeArgs(Type supportedInterfaceType, Type modelType)
|
|
{
|
|
var modelTypeInfo = modelType.GetTypeInfo();
|
|
if (!modelTypeInfo.IsGenericType || modelTypeInfo.IsGenericTypeDefinition)
|
|
{
|
|
// not a closed generic type
|
|
return null;
|
|
}
|
|
|
|
var modelTypeArguments = modelTypeInfo.GenericTypeArguments;
|
|
if (modelTypeArguments.Length != supportedInterfaceType.GetTypeInfo().GenericTypeParameters.Length)
|
|
{
|
|
// wrong number of generic type arguments
|
|
return null;
|
|
}
|
|
|
|
return modelTypeArguments;
|
|
}
|
|
}
|
|
}
|