private void BuildAndAssignPropertyValues( string logicalPropertyPath, LinkedList <object> objectGraphLineage, Type explicitType, IDictionary <string, object> propertyValueConstraints, List <ValueBuildResult> results, BuildMode mode, CustomObjectBuildContext customObjectBuildContext) { // Get its public properties int i = 0; var writableProperties = // TODO: Need unit test for this behavior (from p in explicitType.GetPublicProperties() where p.CanWrite && !p.GetCustomAttributes <IgnoreDataMemberAttribute>().Any() select p) .OrderBy( p => { // Set scalar values first, then object references, then lists if (p.PropertyType.IsCustomClass()) { return(10000 + i++); } // Not a scalar, and not a custom object reference, then it's probably a list (sort last) if (!p.PropertyType.IsScalarProperty()) { return(20000 + i++); } return(i++); }) .ToList(); _logger.DebugFormat( "Processing custom object properties in the following order: {0}", string.Join(", ", writableProperties.Select(p => p.Name))); var containingInstance = objectGraphLineage.First.Value; while (writableProperties.Count > 0) { // Make note of the number of remaining writable properties we need to generate int lastWritableCount = writableProperties.Count; // Get a list of public, writable properties foreach (var property in writableProperties.ToArray()) { ValueBuildResult buildResult = null; object constraintValue; // Look for property name match using name and (if necessary) the "role name prefix" convention. if (propertyValueConstraints.TryGetValue(property.Name, out constraintValue) || propertyValueConstraints.TryGetValue(explicitType.Name + property.Name, out constraintValue)) { _logger.DebugFormat( "Setting property '{0}' to '{1}' from context.", logicalPropertyPath + "." + property.Name, constraintValue); buildResult = ValueBuildResult.WithValue(constraintValue, logicalPropertyPath + "." + property.Name); try { property.SetValue(containingInstance, buildResult.Value, null); } catch (Exception ex) { // TODO: Need unit test for this behavior of falling through to normal build on exception string valueTypeName = buildResult.Value == null ? "N/A" : buildResult.Value.GetType() .Name; // This can happen when context is incorrectly used to set a similarly named property and the types are different _logger.DebugFormat( "Normal value building will proceed for path '{0}' because the property '{1}' (of type '{2}') could not be set to value '{3}' (of type '{4}') on instance type '{5}' due to the following exception:\r\n{6}.", logicalPropertyPath, property.Name, property.PropertyType.Name, buildResult.Value ?? "[null]", valueTypeName, containingInstance.GetType() .Name, ex); // Clear the result, allow normal processing to proceed buildResult = null; } } if (buildResult == null) { // ----------------------------------------------------------------- // TODO: Need unit test for logic to not pass ALL context to children // ----------------------------------------------------------------- // TODO: This functionality/decision needs to be externalized from the Test Object Factory... as a "ContextFilter" (or something similar) // For example, IChildContextFilter with a NoContextFilter, and an EdOrgContextAllowedFilter (removes non EdOrg context from children) IDictionary <string, object> constraintsToPass; // Is this some sort of child list? if (property.PropertyType != typeof(string) && typeof(IEnumerable).IsAssignableFrom(property.PropertyType)) { // Filter the context passed to children. // Only pass EdOrg-related property value constraints to children var edOrgKeyValuePairs = from kvp in propertyValueConstraints.AsEnumerable() where kvp.Key.IsEducationOrganizationIdentifier() select kvp; constraintsToPass = new Dictionary <string, object>(StringComparer.InvariantCultureIgnoreCase); foreach (var kvp in edOrgKeyValuePairs) { constraintsToPass.Add(kvp); } } else { constraintsToPass = propertyValueConstraints; } // ----------------------------------------------------------------- var buildContext = new BuildContext( logicalPropertyPath + "." + property.Name, property.PropertyType, constraintsToPass, //propertyValueConstraints, explicitType, objectGraphLineage, mode); buildResult = BuildValue(results, buildContext, customObjectBuildContext); if (buildResult.ShouldDefer) { continue; } } if (buildResult.ShouldSetValue) { try { property.SetValue(containingInstance, buildResult.Value, null); } catch (Exception ex) { throw new Exception( string.Format( "Unable to set property '{0}' to value '{1}' on instance type '{2}'.", property.Name, buildResult.Value, containingInstance.GetType() .Name), ex); } } writableProperties.Remove(property); results.Add(buildResult); } // If we made no progress on the writeable properties, throw an exception now if (writableProperties.Count == lastWritableCount) { throw new Exception( string.Format( "The attempts to build the following properties have failed to make progress due to 'Defer' responses from the value builders:\r\n{0}.", string.Join(", ", writableProperties.Select(p => p.Name)))); } } }
private ValueBuildResult TryBuildCustomObject( List <ValueBuildResult> buildResults, BuildContext buildContext, CustomObjectBuildContext customObjectBuildContext) { if (customObjectBuildContext == null) { customObjectBuildContext = new CustomObjectBuildContext(); } Type targetType = buildContext.TargetType; if (targetType.IsValueType || targetType == typeof(string) || targetType.FullName.StartsWith("System.") || targetType.IsAbstract) { return(ValueBuildResult.NotHandled); } string logicalPropertyPath = buildContext.LogicalPropertyPath; var objectGraphLineage = buildContext.ObjectGraphLineage; var propertyValueConstraints = buildContext.PropertyValueConstraints; var buildMode = buildContext.BuildMode; // Start or augment logical property path if (string.IsNullOrEmpty(logicalPropertyPath)) { logicalPropertyPath = targetType.Name; } else { logicalPropertyPath += "." + targetType.Name; } // Initialize depth for type if it doesn't yet exist if (!customObjectBuildContext.CustomObjectDepthByType.ContainsKey(targetType)) { customObjectBuildContext.CustomObjectDepthByType[targetType] = 0; } // Initialize sequence for type if it doesn't yet exist if (!customObjectBuildContext.CustomObjectCollectionSequence.ContainsKey(targetType)) { customObjectBuildContext.CustomObjectCollectionSequence[targetType] = 0; } // Are we already building an instance of this type of object? int depth = customObjectBuildContext.CustomObjectDepthByType[targetType]; // Are we already building an instance in a collection of this type of object? int sequence = customObjectBuildContext.CustomObjectCollectionSequence[targetType]; // Don't go any deeper than 2 if (depth == MaximumHierarchyDepth) { return(ValueBuildResult.Skip(logicalPropertyPath)); } customObjectBuildContext.CustomObjectDepthByType[targetType] = depth + 1; try { // Attempt to create the object using a default constructor try { object instance = activator.CreateInstance(targetType); objectGraphLineage.AddFirst(instance); try { BuildAndAssignPropertyValues( logicalPropertyPath, objectGraphLineage, targetType, propertyValueConstraints, buildResults, buildMode, customObjectBuildContext); } finally { objectGraphLineage.RemoveFirst(); } return(ValueBuildResult.WithValue(instance, logicalPropertyPath)); } catch (Exception ex) { throw new Exception(string.Format("Unable to create type '{0}' at {1}.", targetType.FullName, logicalPropertyPath), ex); } } finally { customObjectBuildContext.CustomObjectDepthByType[targetType] = customObjectBuildContext.CustomObjectDepthByType[targetType] - 1; customObjectBuildContext.CustomObjectCollectionSequence[targetType] = sequence + 1; } }