예제 #1
0
        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))));
                }
            }
        }
예제 #2
0
        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;
            }
        }