/// <summary>
        ///     Creates the type of the configuration object.
        /// </summary>
        /// <param name="settings">
        ///     The settings used to control how configuration objects are created and the features they support.
        /// </param>
        /// <typeparam name="TConfigurationObject">
        ///     The type of the interface to be implemented by the configuration object to create.
        /// </typeparam>
        /// <returns>
        ///     An implementation of the interface specified that can be used to interact with the configuration.
        /// </returns>
        /// <exception cref="InvalidOperationException">
        ///     Type specifies more than one 'Path' attribute and so cannot be processed. - or - Property specifies more
        ///     than one 'Path' attribute and so cannot be processed.
        /// </exception>
        internal static Type GenerateConfigurationObjectType<TConfigurationObject>(ConfigurationObjectSettings settings)
            where TConfigurationObject : IConfigurationObject
        {
            var type = typeof(TConfigurationObject);

            return GenerateConfigurationObjectType(type, settings);
        }
Exemple #2
0
        /// <summary>
        ///     Initializes a new instance of the <see cref="ConfigurationObjectTypeBuilder" /> class.
        /// </summary>
        /// <param name="interfaceType">
        ///     The type of the interface.
        /// </param>
        /// <param name="propertyDefs">
        ///     The definitions of the interface's properties.
        /// </param>
        /// <param name="settings">
        ///     Optional settings used to control how configuration objects are created and the features they support.
        /// </param>
        public ConfigurationObjectTypeBuilder(Type interfaceType, IEnumerable <PropertyDef> propertyDefs, ConfigurationObjectSettings settings)
        {
            // TODO: Make type builders cacheable.

            _interfaceType = interfaceType;
            _propertyDefs  = propertyDefs.ToArray();
            _baseClassType = typeof(ConfigurationObjectBase <>).MakeGenericType(_interfaceType);
            _settings      = settings;
            settings.ConfigureEnvironment();
        }
        /// <summary>
        ///     Creates the type of the configuration object.
        /// </summary>
        /// <param name="type">
        ///     The type of the interface to be implemented by the configuration object to create.
        /// </param>
        /// <param name="settings">
        ///     The settings used to control how configuration objects are created and the features they support.
        /// </param>
        /// <returns>
        ///     An implementation of the interface specified that can be used to interact with the configuration.
        /// </returns>
        /// <exception cref="InvalidOperationException">
        ///     Type specifies more than one 'Path' attribute and so cannot be processed. - or - Property specifies more
        ///     than one 'Path' attribute and so cannot be processed.
        /// </exception>
        internal static Type GenerateConfigurationObjectType(Type type, ConfigurationObjectSettings settings)
        {
            var propertyDefs = GetConfigurationObjectDefinition(type, settings);

            var builder = new ConfigurationObjectTypeBuilder(type, propertyDefs, settings);

            var generatedType = builder.Generate();

            return generatedType;
        }
        /// <summary>
        ///     Add a new kind of configuration reader that represents values taken directly from the
        ///     <see cref="IConfigurationRoot" /> object in the service collection.
        /// </summary>
        /// <typeparam name="TConfigurationObject">
        ///     The interface through which consumers will access the configuration. This must be derived from the
        ///     <see cref="IConfigurationObject" /> interface.
        /// </typeparam>
        /// <param name="serviceCollection">
        ///     The service collection to which to add the configuration reader. This must not be <see langword="null" />.
        /// </param>
        /// <param name="configureOptions">
        ///     A method or lambda that will configure the settings used to control how configuration objects are
        ///     created and the features they support.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="configureOptions" /> is <see langword="null" />.
        /// </exception>
        public static IServiceCollection AddConfigurationReader<TConfigurationObject>(this IServiceCollection serviceCollection, Action<ConfigurationObjectSettings> configureOptions)
            where TConfigurationObject : IConfigurationObject
        {
            configureOptions.Validate(nameof(configureOptions), ObjectIs.NotNull);

            var settings = new ConfigurationObjectSettings();

            configureOptions(settings);

            return AddConfigurationReader<TConfigurationObject>(serviceCollection, settings);
        }
        /// <summary>
        ///     Add a new kind of configuration reader that represents values taken directly from the
        ///     <see cref="IConfigurationRoot" /> object in the service collection.
        /// </summary>
        /// <typeparam name="TConfigurationObject">
        ///     The interface through which consumers will access the configuration. This must be derived from the
        ///     <see cref="IConfigurationObject" /> interface.
        /// </typeparam>
        /// <param name="serviceCollection">
        ///     The service collection to which to add the configuration reader. This must not be <see langword="null" />.
        /// </param>
        /// <param name="settings">
        ///     Optional settings used to control how configuration objects are created and the features they support.
        /// </param>
        public static IServiceCollection AddConfigurationReader<TConfigurationObject>(this IServiceCollection serviceCollection, ConfigurationObjectSettings? settings)
            where TConfigurationObject : IConfigurationObject
        {
            serviceCollection.Validate(nameof(serviceCollection), ObjectIs.NotNull);

            // Check to see if the collection has the relevant configuration reader registered, and if not, create and
            // add a new instance.

            if(ReferenceEquals(settings, null))
            {
                // Use default values.
                settings = new ConfigurationObjectSettings();
            }

            var serviceType = typeof(TConfigurationObject);

            if(serviceCollection.Any(d => d.ServiceType == serviceType))
            {
                return serviceCollection;
            }

            var implementationType = GenerateConfigurationObjectType<TConfigurationObject>(settings);

            var descriptor = new ServiceDescriptor(serviceType, (provider) =>
            {
                var configurationRoot = provider.GetService<IConfigurationRoot>();

                if(_validators.Count <= 0)
                {
                    foreach(var validator in provider.GetServices<IConfigurationObjectValidator>())
                    {
                        _validators.Add(validator);
                    }
                }

                var validators = GetValidatorsEx<TConfigurationObject>();

                var configurationObject = (TConfigurationObject)Activator.CreateInstance(implementationType, null, configurationRoot, null, settings, validators);

                configurationObject.Load();

                return configurationObject;
            }, ServiceLifetime.Singleton);

            serviceCollection.TryAdd(descriptor);

            return serviceCollection;
        }
        /// <summary>
        ///     Initializes a new instance of the <see cref="Implementation" /> class.
        /// </summary>
        /// <param name="underlyingType">
        ///     The underlying type of the property or collection represented.
        /// </param>
        /// <param name="settings">
        ///     The settings used to control how configuration objects are created and the features they support.
        /// </param>
        internal Implementation(Type underlyingType, ConfigurationObjectSettings settings)
        {
            ImplementationKind = GetImplementationKind(underlyingType, out var isReadOnlyColectionType);
            _settings          = settings;

            Type elementType;

            switch (ImplementationKind)
            {
            case ImplementationKind.ConfigurationObject:
                ImplementationType = ServiceCollectionExtensions.GenerateConfigurationObjectType(underlyingType, settings);
                Type = underlyingType;
                break;

            case ImplementationKind.ConfigurationCollection:
                elementType = underlyingType.GenericTypeArguments.First();
                if (isReadOnlyColectionType)
                {
                    ImplementationType = typeof(ReadOnlyConfigurationCollection <>).MakeGenericType(elementType);
                }
                else
                {
                    ImplementationType = typeof(ConfigurationCollection <>).MakeGenericType(elementType);
                }

                Type = elementType;
                break;

            case ImplementationKind.ConfigurationDictionary:
                elementType = underlyingType.GenericTypeArguments.First();
                if (isReadOnlyColectionType)
                {
                    ImplementationType = typeof(ReadOnlyConfigurationDictionary <>).MakeGenericType(elementType);
                }
                else
                {
                    ImplementationType = typeof(ConfigurationDictionary <>).MakeGenericType(elementType);
                }

                Type = elementType;
                break;

            default:
                ImplementationType = null;
                Type = underlyingType;
                break;
            }
        }
        /// <summary>
        ///     Gets the configuration object definition.
        /// </summary>
        /// <param name="type">
        ///     The type of the object to define.
        /// </param>
        /// <param name="settings">
        ///     The settings used to control how configuration objects are created and the features they support.
        /// </param>
        /// <returns>
        ///     A list of the property definitions for the type specified.
        /// </returns>
        /// <exception cref="InvalidOperationException">
        ///     Type '{type.Namespace}.{type.Name}' specifies more than one 'Path' attribute and so cannot be processed.
        /// </exception>
        internal static List<PropertyDef> GetConfigurationObjectDefinition(Type type, ConfigurationObjectSettings settings)
        {
            // TODO: Circular reference detection - for this version.

            var propertyDefs = new List<PropertyDef>();

            foreach(var property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                if(!property.CanRead)
                {
                    continue;
                }

                var propertyDef = new PropertyDef(property, settings);

                propertyDefs.Add(propertyDef);
            }

            return propertyDefs;
        }
Exemple #8
0
        /// <summary>
        ///     Initializes a new instance of the <see cref="PropertyDef" /> class.
        /// </summary>
        /// <param name="propertyInfo">
        ///     The definition of the property.
        /// </param>
        /// <param name="settings">
        ///     The settings used to control how configuration objects are created and the features they support.
        /// </param>
        /// <exception cref="InvalidPropertyException">
        ///     'ConfigurationValue' attribute on property specifies that persistence should only save values (not
        ///     load), but no default value is provided.
        /// </exception>
        internal PropertyDef(PropertyInfo propertyInfo, ConfigurationObjectSettings settings)
        {
            PropertyInfo = propertyInfo;
            PropertyName = propertyInfo.Name;
            Type         = propertyInfo.PropertyType;
            Settings     = settings;

            if (Type.IsArray)
            {
                throw new InvalidPropertyException(PropertyName, $@"Property '{PropertyName}' is an array which is not supported.  Use 'IConfigurationCollection' or 'IReadOnlyConfigurationCollection' instead.");
            }

            UnderlyingType = GetUnderlyingType(propertyInfo.PropertyType);
            IsReadOnly     = !propertyInfo.CanWrite;

            var pathAttributes = propertyInfo.GetCustomAttributes(typeof(PathAttribute), true);

            if (pathAttributes.Length > 0)
            {
                var pathAttribute = ((PathAttribute)pathAttributes[0]);

                PathSection  = pathAttribute.Path;
                PathModifier = pathAttribute.Usage;
            }
            else
            {
                PathModifier = PathIs.Relative;
                PathSection  = PropertyName;
            }

            Implementation = new Implementation(UnderlyingType, Settings);
            switch (Implementation.ImplementationKind)
            {
            case ImplementationKind.ConfigurationCollection:
            case ImplementationKind.ConfigurationDictionary:
            case ImplementationKind.ConfigurationObject:
                ElementImplementation = new Implementation(Implementation.Type, Settings);
                break;
            }

            var configurationAttributes = propertyInfo.GetCustomAttributes <ConfigurationAttribute>(true).ToList();

            if (configurationAttributes.Count > 0)
            {
                var attribute = configurationAttributes[0];
                DefaultValue    = attribute.DefaultValue;
                HasDefaultValue = attribute.IsDefaultValueSet;
                Persistence     = attribute.Persistence;

                if (HasDefaultValue)
                {
                    if (ReferenceEquals(DefaultValue, null))
                    {
                        if (Type.IsValueType)
                        {
                            throw new ConfigurationException(null, PropertyName, string.Format(CultureInfo.InvariantCulture, "Property '{0}' has a default value of a type of 'null', which is incompatible with the type of the property.", PropertyName));
                        }
                    }
                    else
                    {
                        if (!Type.IsAssignableFrom(DefaultValue.GetType()))
                        {
                            throw new ConfigurationException(null, PropertyName, string.Format(CultureInfo.InvariantCulture, "Property '{0}' has a default value of a type incompatible with the type of the property.", PropertyName));
                        }
                    }
                }
            }
            else
            {
                Persistence = ConfigurationPersistenceActions.LoadAndSave;
            }

            if (!Persistence.HasFlag(ConfigurationPersistenceActions.LoadOnly) && !HasDefaultValue)
            {
                var propertyName = $"[{propertyInfo.DeclaringType.Namespace}.{propertyInfo.DeclaringType.Name}].{propertyInfo.Name}";
                throw new InvalidPropertyException(propertyName,
                                                   $"'ConfigurationValue' attribute on property \"{propertyName}\" specifies that persistence should only save or ignore values (not load), but no default value is provided.");
            }
        }
Exemple #9
0
        /// <summary>
        ///     Initializes a new instance of the <see cref="ConfigurationObjectBase{TInterface}" /> class. This is the
        ///     interface used when creating the root instance for the service collection.
        /// </summary>
        /// <param name="propertyDef">
        ///     The definition of the property defined by this object. This can be <see lang="null" /> if this object is
        ///     the root of the hierarchy.
        /// </param>
        /// <param name="configurationRoot">
        ///     The configuration root from which to read and write values.
        /// </param>
        /// <param name="parent">
        ///     The parent object to which this one belongs. <see langword="null" /> if this is a root object.
        /// </param>
        /// <param name="settings">
        ///     The settings used to control how configuration objects are created and the features they support.
        /// </param>
        /// <param name="validators">
        ///     The validators that can be applied to configuration objects of this type.
        /// </param>
        protected ConfigurationObjectBase(IPropertyDef?propertyDef, IConfigurationRoot configurationRoot, IConfigurationParent parent, ConfigurationObjectSettings settings, IEnumerable <IConfigurationObjectValidator <TInterface> > validators) : base(propertyDef,
                                                                                                                                                                                                                                                          ServiceCollectionExtensions.GetConfigurationObjectDefinition(typeof(TInterface), settings), configurationRoot, parent)
        {
            _validators = validators?.ToArray() ?? Array.Empty <IConfigurationObjectValidator <TInterface> >();

            DisablePropertyChangedEvents();
            try
            {
                foreach (var value in Values)
                {
                    if (value.PropertyDef.HasDefaultValue)
                    {
                        value.Value = value.PropertyDef.DefaultValue;
                        value.Saved();
                    }
                }
            }
            finally
            {
                EnablePropertyChangedEvents();

                Validate();
            }
        }