/// <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 Dictionary <string, object> ExpandBlindObject(object source, ContextType context, IEnumerable <PropertyReference> includes, HashSet <int> visited) { visited = UniqueVisit(source, visited); Type sourceType = source.GetType(); includes = ValidateIncludes(includes, sourceType, 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); } 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); }
/// <summary> /// Verify that we have the appropriate include list for a type, taking into account any requested, /// or otherwise defaults supplied. /// </summary> /// <param name="includes"></param> /// <param name="sourceType"></param> /// <param name="destType"></param> /// <returns></returns> private IEnumerable <PropertyReference> ConstructIncludes(IEnumerable <PropertyReference> includes, Type sourceType, Type destType) { // Out of the gate we want to first see if the only property to be included is a wildcard if ((includes.Count() == 1) && (includes.Any(i => i.PropertyName == "*"))) { var wildCardIncludes = new List <PropertyReference> { }; var mapDef = new MappingDefinition(); // Check to see if the object is to be blind expanded and make the destination the same as the source if it is if (destType == null) { destType = sourceType; } else // in the case that the object isn't to be blind expanded get the proper mapping { Mappings.TryGetValue(sourceType, out mapDef); } // Have all of the destination type properties set to be included foreach (PropertyInfo info in destType.GetTypeInfo().GetProperties()) { var matchingSourceProp = sourceType.GetTypeInfo().GetProperty(info.Name); // Make sure that the property isn't marked as InternalOnly on the sourceType // Which is only an issue if they marked the type to throw an error if it's requested if (matchingSourceProp != null) { if (matchingSourceProp.GetCustomAttributes().Any(att => att.GetType() == typeof(InternalOnlyAttribute))) { // Only add the property if it isn't marked InternalOnly continue; } } // Also make sure that the property exists on the destination type in some capacity // This will never be hit by a blindly expanded object as the source and destination type are identical if (matchingSourceProp == null) { try { // Check to see if there are any translators that would apply the object to the projection ultimately var transTest = mapDef.DefaultDestination().Translators[info.Name]; } catch (Exception) { // This property isn't known to the projection at all and thus should not be included continue; } } wildCardIncludes.Add(new PropertyReference { PropertyName = info.Name }); } return(wildCardIncludes); } if (includes.Any()) { return(includes); } if (destType == null) { // in the case of a blind object, default to source properties. This is a bit dangerous! includes = sourceType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance) .Select(p => new PropertyReference() { PropertyName = p.Name }); } // if this doesn't have any includes specified, use the default if (!includes.Any()) { includes = PropertyReference.Parse(Mappings[sourceType].DestinationForType(destType).DefaultIncludes); } // if this STILL doesn't have any includes, that means include everything if (!includes.Any()) { includes = destType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance) .Select(p => new PropertyReference() { PropertyName = p.Name }); } return(includes); }