/// <summary> /// Query if this is a collection of a mapped type /// </summary> /// <param name="sourceType"></param> /// <returns></returns> protected bool WillExpandCollection(Type sourceType) { var blindAssignment = BlindHandlers.Where(kv => kv.Key.IsAssignableFrom(sourceType)).Select(kv => kv.Value).FirstOrDefault(); if (this.ExpandBlindObjects && blindAssignment != null) { return(false); } // figure out if this is an expandable list, instead // Is this a list of items we need to project? if (sourceType.GetTypeInfo().GetInterfaces() .Any(t => t.IsConstructedGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable <>))) { var interfaceType = sourceType.GetTypeInfo().GetInterfaces() .First(t => t.IsConstructedGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable <>)); // Verify that the generic parameter is something we would expand var genericType = interfaceType.GenericTypeArguments[0]; if (this.WillExpandType(genericType)) { return(true); } } return(false); }
/// <summary> /// Query if this is a type that can be expanded with no projected type, i.e. blind /// </summary> /// <param name="sourceType"></param> /// <returns></returns> protected bool WillExpandBlind(Type sourceType) { if (!this.ExpandBlindObjects) { return(false); } var blindAssignment = BlindHandlers.Where(kv => kv.Key.IsAssignableFrom(sourceType)).Select(kv => kv.Value).FirstOrDefault(); if (blindAssignment != null) { return(true); } return(!WillExpandDirect(sourceType) && // False if will expand direct or collection !WillExpandCollection(sourceType) && sourceType.GetTypeInfo().IsClass && // False if a simple type (!(sourceType.GetTypeInfo().GetInterfaces() // False if the object doesn't have an IEnumerable interface .Any(t => t.IsConstructedGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable <>))))); }
/// <summary> /// Map a collection of mapped types /// </summary> /// <param name="originalValue"></param> /// <param name="destinationType"></param> /// <param name="context"></param> /// <param name="includes"></param> /// <param name="visited">todo: describe visited parameter on ExpandCollection</param> /// <returns></returns> protected IList ExpandCollection(object originalValue, Type destinationType, ContextType context, IEnumerable <PropertyReference> includes, HashSet <int> visited) { visited = UniqueVisit(originalValue, visited); var interfaceType = originalValue.GetType().GetTypeInfo().GetInterfaces() .First(t => t.IsConstructedGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable <>)); // Verify that the generic parameter is something we would expand var genericType = interfaceType.GenericTypeArguments[0]; // try to figure out the destination object type var destInterfaceType = destinationType.GetTypeInfo().GetInterfaces() .FirstOrDefault(t => t.IsConstructedGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable <>)); Type expandedType; var blindAssignment = BlindHandlers.Where(kv => kv.Key.IsAssignableFrom(genericType)).Select(kv => kv.Value).FirstOrDefault(); if (destInterfaceType != null) { expandedType = destInterfaceType.GenericTypeArguments[0]; } else if (this.Mappings.ContainsKey(genericType)) { expandedType = this.Mappings[genericType].DefaultDestinationType; } else if (this.ExpandBlindObjects && blindAssignment != null) { expandedType = blindAssignment.Item1; } else { expandedType = typeof(Dictionary <string, object>); } // Ok, now we need to try and instantiate the destination type (if it is concrete) or take a guess // at a concrete type if it is an interface /// @TODO IList is an easy way of being able to add, but not all collections implement IList (like hashset) /// really we want to work with ICollection<T>, although for some reason ICollection (non-generic) doesn't have Add var instantiatedDestinationType = destinationType.CreateDefaultObject() as IList; if (instantiatedDestinationType == null) { var concreteType = typeof(List <>).MakeGenericType(expandedType); instantiatedDestinationType = concreteType.CreateDefaultObject() as IList; } // try to assign the data item by item foreach (var item in (IEnumerable)originalValue) { /// If authorization indicates this should not in fact be authorized, skip it if (!AuthorizeValue(originalValue, context, item)) { continue; } instantiatedDestinationType.Add(this.Expand(item, context, includes, visited, expandedType)); } return(instantiatedDestinationType); }
/// <summary> /// Take a complex object, and transfer properties requested into a dictionary /// </summary> /// <param name="source"></param> /// <param name="context"></param> /// <param name="includes"></param> /// <param name="visited"></param> /// <returns></returns> protected object ExpandBlindObject(object source, ContextType context, IEnumerable <PropertyReference> includes, HashSet <int> visited) { visited = UniqueVisit(source, visited); Type sourceType = source.GetType(); var blindAssignment = BlindHandlers.Where(kv => kv.Key.IsAssignableFrom(sourceType)).Select(kv => kv.Value).FirstOrDefault(); if (blindAssignment != null) { return(blindAssignment.Item2(source, context)); } includes = ConstructIncludes(includes, sourceType, null); if (!includes.Any()) { return(null); } // Attempt to create a projection object we'll map the data into var destinationObject = new Dictionary <string, object>(); MappingDefinition mappingDefinition = null; // Allow any actions to run ahead of mapping if (Mappings.ContainsKey(sourceType)) { foreach (var action in Mappings[sourceType].BeforeExpansion) { action(destinationObject, source, context); } mappingDefinition = Mappings[source.GetType()]; } // Iterate over only the requested properties foreach (var propertyReference in includes) { string propertyName = propertyReference.PropertyName; if (mappingDefinition != null) { var preparers = mappingDefinition.PrepareProperties; // See if there's a propertyPreparer if (preparers.ContainsKey(propertyName)) { preparers[propertyName](destinationObject, null, source, context); } } // Transform the input value as needed object valueToAssign = GetSourceValue(source, context, propertyName, mappingDefinition?.DefaultDestination()?.Translators); /// If authorization indicates this should not in fact be authorized, skip it if (!AuthorizeValue(source, context, valueToAssign)) { continue; } if (WillExpand(valueToAssign)) { valueToAssign = Expand(valueToAssign, context, propertyReference.Children, visited); } if (valueToAssign != null) { destinationObject[propertyName] = valueToAssign; } } // Allow any actions to run after the mapping /// @Todo should this be in reverse order so we have a nested stack style FILO? if (Mappings.ContainsKey(sourceType)) { foreach (var action in Mappings[sourceType].DefaultDestination().AfterExpansion) { action(destinationObject, source, context); } } return(destinationObject); }