using System; using System.Linq; using System.Collections.Specialized; using System.Web.Mvc; using Akua.Framework.Infrastructure.DI; using Akua.Framework.Model.NHibernate; using Akua.Framework.Web.MVC.Helpers; namespace Akua.Framework.Web.MVC.Controller.Binding { public class RouteQueryFormToModelFetcherAndBinder : CustomBinderBase { private FetchBehavior? _fetchBehavior; private readonly string _prefix; private readonly string _includeProperties; private readonly string _excludeProperties; private NHibernateEntityFetcher _entityFetcher; private NHibernateEntityFetcher _EntityFetcher { get { return _entityFetcher ?? (_entityFetcher = ServiceLocator.Resolve()); } } public RouteQueryFormToModelFetcherAndBinder() { } public RouteQueryFormToModelFetcherAndBinder(FetchBehavior fetchBehavior, string prefix, string includeProperties, string excludeProperties) { _fetchBehavior = fetchBehavior; _prefix = prefix; _includeProperties = includeProperties; _excludeProperties = excludeProperties; } public override ModelBinderResult BindModel(ModelBindingContext bindingContext) { VerifyBindingContextIsProvided(bindingContext); var hackedBindingContext = _Recreate_Binding_Context_Because_of_SEALED_BindAttribute_in_Beta(bindingContext); var binderResult = TryResolveAsExistingValueOrSimpleTypeFrom(hackedBindingContext); if (binderResult != null) return binderResult; var valuesToBind = GetValuesProviderFor(hackedBindingContext.Controller.ControllerContext).GetValues(); var fetchedModel = _FetchObject(hackedBindingContext.ModelType, valuesToBind, hackedBindingContext.ModelName); var bindingContextWithFetchedModel = _GetNewBindingContextWithFetchedModel(fetchedModel, hackedBindingContext); return base.BindModel(bindingContextWithFetchedModel); } // HACK: And HACK and HACK and again a HACK!!! until BindAttribute is sealed // and custom attribute cannot inherit from it!!!! private ModelBindingContext _Recreate_Binding_Context_Because_of_SEALED_BindAttribute_in_Beta(ModelBindingContext currentBindingContext) { return new ModelBindingContext(currentBindingContext, currentBindingContext.ValueProvider, currentBindingContext.ModelType, _GetPrefixOrModelName(currentBindingContext.ModelName), () => currentBindingContext.Model, currentBindingContext.ModelState, _IsPropertyAllowed); } private string _GetPrefixOrModelName(string currentModelName) { return (string.IsNullOrEmpty(_prefix)) ? currentModelName : _prefix; } private bool _IsPropertyAllowed(string propertyName) { var includeProperties = StringHelper.SplitString(_includeProperties); var excludeProperties = StringHelper.SplitString(_excludeProperties); // We allow a property to be bound if its both in the include list AND not in the exclude list. // An empty include list implies all properties are allowed. // An empty exclude list implies no properties are disallowed. bool includeProperty = (includeProperties == null) || (includeProperties.Length == 0) || includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase); bool excludeProperty = (excludeProperties != null) && excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase); return includeProperty && !excludeProperty; } private ModelBindingContext _GetNewBindingContextWithFetchedModel(object fetchedModel, ModelBindingContext currentBindingContext) { return new ModelBindingContext(currentBindingContext, currentBindingContext.ValueProvider, currentBindingContext.ModelType, currentBindingContext.ModelName, () => fetchedModel, currentBindingContext.ModelState, currentBindingContext.ShouldUpdateProperty); } private object _FetchObject(Type modelType, NameValueCollection dataToBind, string modelName) { if (_fetchBehavior.HasValue == false) _fetchBehavior = FetchBehavior.Nothing; var fetcherNamingStrategy = new BinderNamingStrategyForFetcher(modelName); return _EntityFetcher.Do(_fetchBehavior.Value) .NamingStrategy(fetcherNamingStrategy.StrategyForRootTypeIdentifierKey, fetcherNamingStrategy.StrategyForChildTypeIdentifierKey) .Fetch(modelType, dataToBind); } private class BinderNamingStrategyForFetcher { private const string _Prefix_To_Property_Delimiter = Constants.Prefix_To_Property_Delimiter_For_Name; private string _ProvidedPrefix { get; set; } public BinderNamingStrategyForFetcher(string providedPrefix) { _ProvidedPrefix = providedPrefix; } public string StrategyForChildTypeIdentifierKey(NameValueCollection keyPropertyValues, Type objectType, string objectPropertyNameForChild) { var assumedKey = (_GetBaseName(objectType) + _Prefix_To_Property_Delimiter + objectPropertyNameForChild).ToLower(); if (keyPropertyValues.AllKeys.Contains(assumedKey)) return assumedKey; // If regular key cannot be found, try to find if it was bound via Id property! return assumedKey + _Prefix_To_Property_Delimiter + "id"; } public string StrategyForRootTypeIdentifierKey(NameValueCollection keyPropertyValues, Type objectType) { var assumedKey = _GetBaseName(objectType).ToLower(); if (keyPropertyValues.AllKeys.Contains(assumedKey)) return assumedKey; // If regular key cannot be found, try to find if it was bound via Id property! return assumedKey + _Prefix_To_Property_Delimiter + "id"; } private string _GetBaseName(Type ofType) { return (string.IsNullOrEmpty(_ProvidedPrefix)) ? ofType.Name : _ProvidedPrefix; } } } }