// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Web.Http.Controllers;
using System.Web.Http.Internal;
using System.Web.Http.Properties;
namespace System.Web.Http.ModelBinding.Binders
{
///
/// This class is an that delegates to one of a collection of
/// instances.
///
///
/// If no binder is available and the allows it,
/// this class tries to find a binder using an empty prefix.
///
public class CompositeModelBinder : IModelBinder
{
public CompositeModelBinder(IEnumerable modelBinderProviders)
: this(modelBinderProviders.ToArray())
{
}
public CompositeModelBinder(ModelBinderProvider[] modelBinderProviders)
{
Providers = modelBinderProviders;
}
private ModelBinderProvider[] Providers { get; set; }
public virtual bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
//// REVIEW: from MVC Futures
////CheckPropertyFilter(bindingContext);
ModelBindingContext newBindingContext = CreateNewBindingContext(bindingContext, bindingContext.ModelName);
IModelBinder binder = GetBinder(actionContext, newBindingContext);
if (binder == null && !String.IsNullOrEmpty(bindingContext.ModelName)
&& bindingContext.FallbackToEmptyPrefix)
{
// fallback to empty prefix?
newBindingContext = CreateNewBindingContext(bindingContext, String.Empty /* modelName */);
binder = GetBinder(actionContext, newBindingContext);
}
if (binder != null)
{
bool boundSuccessfully = binder.BindModel(actionContext, newBindingContext);
if (boundSuccessfully)
{
// run validation and return the model
// If we fell back to an empty prefix above and are dealing with simple types,
// propagate the non-blank model name through for user clarity in validation errors.
// Complex types will reveal their individual properties as model names and do not require this.
if (!newBindingContext.ModelMetadata.IsComplexType && String.IsNullOrEmpty(newBindingContext.ModelName))
{
newBindingContext.ValidationNode = new Validation.ModelValidationNode(newBindingContext.ModelMetadata, bindingContext.ModelName);
}
newBindingContext.ValidationNode.Validate(actionContext, null /* parentNode */);
bindingContext.Model = newBindingContext.Model;
return true;
}
}
return false; // something went wrong
}
//// REVIEW: from MVC Futures -- activate when Filters are in
////private static void CheckPropertyFilter(ModelBindingContext bindingContext)
////{
//// if (bindingContext.ModelType.GetProperties().Select(p => p.Name).Any(name => !bindingContext.PropertyFilter(name)))
//// {
//// throw Error.InvalidOperation(SRResources.ExtensibleModelBinderAdapter_PropertyFilterMustNotBeSet);
//// }
////}
private IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
Contract.Assert(actionContext != null);
Contract.Assert(bindingContext != null);
//// REVIEW: this was in MVC Futures
////EnsureNoBindAttribute(bindingContext.ModelType);
ModelBinderProvider providerFromAttr;
if (TryGetProviderFromAttributes(bindingContext.ModelType, out providerFromAttr))
{
return providerFromAttr.GetBinder(actionContext, bindingContext);
}
return (from provider in Providers
let binder = provider.GetBinder(actionContext, bindingContext)
where binder != null
select binder).FirstOrDefault();
}
private static ModelBindingContext CreateNewBindingContext(ModelBindingContext oldBindingContext, string modelName)
{
ModelBindingContext newBindingContext = new ModelBindingContext
{
ModelMetadata = oldBindingContext.ModelMetadata,
ModelName = modelName,
ModelState = oldBindingContext.ModelState,
ValueProvider = oldBindingContext.ValueProvider
};
// validation is expensive to create, so copy it over if we can
if (object.ReferenceEquals(modelName, oldBindingContext.ModelName))
{
newBindingContext.ValidationNode = oldBindingContext.ValidationNode;
}
return newBindingContext;
}
private static bool TryGetProviderFromAttributes(Type modelType, out ModelBinderProvider provider)
{
ModelBinderAttribute attr = TypeDescriptorHelper.Get(modelType).GetAttributes().OfType().FirstOrDefault();
if (attr == null)
{
provider = null;
return false;
}
// TODO, 386718, remove the following if statement when the bug is resolved
if (attr.BinderType == null)
{
provider = null;
return false;
}
if (typeof(ModelBinderProvider).IsAssignableFrom(attr.BinderType))
{
provider = (ModelBinderProvider)Activator.CreateInstance(attr.BinderType);
}
else
{
throw Error.InvalidOperation(SRResources.ModelBinderProviderCollection_InvalidBinderType, attr.BinderType.Name, typeof(ModelBinderProvider).Name, typeof(IModelBinder).Name);
}
return true;
}
}
}