diff --git a/src/Umbraco.Core/Configuration/CaseInsensitiveEnumConfigConverter.cs b/src/Umbraco.Core/Configuration/CaseInsensitiveEnumConfigConverter.cs new file mode 100644 index 0000000000..64fbfb49a9 --- /dev/null +++ b/src/Umbraco.Core/Configuration/CaseInsensitiveEnumConfigConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Configuration; + +namespace Umbraco.Core.Configuration +{ + /// + /// A case-insensitive configuration converter for enumerations. + /// + /// The type of the enumeration. + internal class CaseInsensitiveEnumConfigConverter : ConfigurationConverterBase + where T : struct + { + public override object ConvertFrom(ITypeDescriptorContext ctx, CultureInfo ci, object data) + { + if (data == null) + throw new ArgumentNullException("data"); + + //return Enum.Parse(typeof(T), (string)data, true); + + T value; + if (Enum.TryParse((string)data, true, out value)) + return value; + + throw new Exception(string.Format("\"{0}\" is not valid {1} value. Valid values are: {2}.", + data, typeof(T).Name, + string.Join(", ", Enum.GetValues(typeof(T)).Cast()))); + } + } +} diff --git a/src/Umbraco.Core/Configuration/ConfigurationKeyAttribute.cs b/src/Umbraco.Core/Configuration/ConfigurationKeyAttribute.cs new file mode 100644 index 0000000000..d0804f0443 --- /dev/null +++ b/src/Umbraco.Core/Configuration/ConfigurationKeyAttribute.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Core.Configuration +{ + /// + /// Indicates the configuration key for a section or a group. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + internal sealed class ConfigurationKeyAttribute : Attribute + { + /// + /// Initializes a new instance of the class with a configuration key. + /// + /// The configurationkey. + /// The default configuration key type is Umbraco. + public ConfigurationKeyAttribute(string configurationKey) + : this(configurationKey, ConfigurationKeyType.Umbraco) + { } + + /// + /// Initializes a new instance of the class with a configuration key and a key type. + /// + /// The configurationkey. + /// The key type. + public ConfigurationKeyAttribute(string configurationKey, ConfigurationKeyType keyType) + { + ConfigurationKey = configurationKey; + KeyType = keyType; + } + + /// + /// Gets or sets the configuration key. + /// + public string ConfigurationKey { get; private set; } + + /// + /// Gets or sets the configuration key type. + /// + public ConfigurationKeyType KeyType { get; private set; } + } +} diff --git a/src/Umbraco.Core/Configuration/ConfigurationKeyType.cs b/src/Umbraco.Core/Configuration/ConfigurationKeyType.cs new file mode 100644 index 0000000000..53c7472e94 --- /dev/null +++ b/src/Umbraco.Core/Configuration/ConfigurationKeyType.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Core.Configuration +{ + /// + /// Indicates the type of configuration section keys. + /// + internal enum ConfigurationKeyType + { + /// + /// An Umbraco section ie with path "/umbraco/sectionKey". + /// + Umbraco, + + /// + /// An Umbraco plugins section ie with path "/umbraco.plugins/sectionKey". + /// + Plugins, + + /// + /// A raw section ie with path "/sectionKey". + /// + Raw + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoConfigurationSection.cs b/src/Umbraco.Core/Configuration/UmbracoConfigurationSection.cs new file mode 100644 index 0000000000..3143658001 --- /dev/null +++ b/src/Umbraco.Core/Configuration/UmbracoConfigurationSection.cs @@ -0,0 +1,33 @@ +using System.Configuration; + +namespace Umbraco.Core.Configuration +{ + // note - still must work on how to support read-only and ResetSection for collections + // note - still must work on how to spread config over files (aka DeepConfig in v5) + + /// + /// Represents an Umbraco section within the configuration file. + /// + /// + /// The requirement for these sections is to be read-only. + /// However for unit tests purposes it is internally possible to override some values, and + /// then calling >ResetSection should cancel these changes and bring the section back to + /// what it was originally. + /// The UmbracoSettings.For{T} method will return a section, either one that + /// is in the configuration file, or a section that was created with default values. + /// + internal abstract class UmbracoConfigurationSection : ConfigurationSection + { + /// + /// Gets a value indicating whether the section actually is in the configuration file. + /// + protected bool IsPresent { get { return ElementInformation.IsPresent; } } + + /// + /// Resets settings that were set programmatically, to their initial values. + /// + /// >To be used in unit tests. + internal protected virtual void ResetSection() + { } + } +} diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings.cs b/src/Umbraco.Core/Configuration/UmbracoSettings.cs index 1ca199f8a7..0d23928d1c 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings.cs @@ -6,9 +6,11 @@ using System.Threading; using System.Web; using System.Web.Caching; using System.Xml; +using System.Configuration; using System.Collections.Generic; using Umbraco.Core.Logging; +using Umbraco.Core.CodeAnnotations; namespace Umbraco.Core.Configuration @@ -52,7 +54,7 @@ namespace Umbraco.Core.Configuration /// /// Used in unit testing to reset all config items that were set with property setters (i.e. did not come from config) /// - internal static void ResetSetters() + private static void ResetSetters() { _addTrailingSlash = null; _forceSafeAliases = null; @@ -60,7 +62,7 @@ namespace Umbraco.Core.Configuration _useDomainPrefixes = null; _umbracoLibraryCacheDuration = null; _trySkipIisCustomErrors = null; - SettingsFilePath = null; + SettingsFilePath = null; } internal const string TempFriendlyXmlChildContainerNodename = ""; // "children"; @@ -1440,6 +1442,95 @@ namespace Umbraco.Core.Configuration } #endregion - } - } + } + + #region Extensible settings + + /// + /// Resets settings that were set programmatically, to their initial values. + /// + /// To be used in unit tests. + internal static void Reset() + { + ResetSetters(); + + using (new WriteLock(SectionsLock)) + { + foreach (var section in Sections.Values) + section.ResetSection(); + } + } + + private static readonly ReaderWriterLockSlim SectionsLock = new ReaderWriterLockSlim(); + private static readonly Dictionary Sections = new Dictionary(); + + /// + /// Gets the specified UmbracoConfigurationSection. + /// + /// The type of the UmbracoConfigurationSectiont. + /// The UmbracoConfigurationSection of the specified type. + internal static T For() + where T : UmbracoConfigurationSection, new() + { + var sectionType = typeof (T); + using (new WriteLock(SectionsLock)) + { + if (Sections.ContainsKey(sectionType)) return Sections[sectionType] as T; + + var attr = sectionType.GetCustomAttribute(false); + if (attr == null) + throw new InvalidOperationException(string.Format("Type \"{0}\" is missing attribute ConfigurationKeyAttribute.", sectionType.FullName)); + + var sectionKey = attr.ConfigurationKey; + if (string.IsNullOrWhiteSpace(sectionKey)) + throw new InvalidOperationException(string.Format("Type \"{0}\" ConfigurationKeyAttribute value is null or empty.", sectionType.FullName)); + + var keyType = attr.KeyType; + var section = GetSection(sectionType, sectionKey, keyType); + + Sections[sectionType] = section; + return section as T; + } + } + + private static UmbracoConfigurationSection GetSection(Type sectionType, string key, ConfigurationKeyType keyType) + { + if (!sectionType.Inherits()) + throw new ArgumentException(string.Format( + "Type \"{0}\" does not inherit from UmbracoConfigurationSection.", sectionType.FullName), "sectionType"); + + switch (keyType) + { + case ConfigurationKeyType.Umbraco: + key = "umbraco/" + key; + break; + case ConfigurationKeyType.Plugins: + key = "umbraco.plugins/" + key; + break; + case ConfigurationKeyType.Raw: + break; + default: + throw new ArgumentOutOfRangeException("keyType", keyType, "Invalid ConfigurationKeyType value."); + } + + var section = ConfigurationManager.GetSection(key); + + if (section != null && section.GetType() != sectionType) + throw new InvalidCastException(string.Format("Section at key \"{0}\" is of type \"{1}\" and not \"{2}\".", + key, section.GetType().FullName, sectionType.FullName)); + + if (section != null) return section as UmbracoConfigurationSection; + + section = Activator.CreateInstance(sectionType) as UmbracoConfigurationSection; + + if (section == null) + throw new NullReferenceException(string.Format( + "Activator failed to create an instance of type \"{0}\" for key\"{1}\" and returned null.", + sectionType.FullName, key)); + + return section as UmbracoConfigurationSection; + } + + #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 1b4eab601a..135632c68a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -112,10 +112,14 @@ + + + + diff --git a/src/Umbraco.Tests/ContentStores/PublishContentStoreTests.cs b/src/Umbraco.Tests/ContentStores/PublishContentStoreTests.cs index ac3bb924ef..7494c4ed30 100644 --- a/src/Umbraco.Tests/ContentStores/PublishContentStoreTests.cs +++ b/src/Umbraco.Tests/ContentStores/PublishContentStoreTests.cs @@ -105,7 +105,7 @@ namespace Umbraco.Tests.ContentStores [TearDown] public void TearDown() { - UmbracoSettings.ResetSetters(); + UmbracoSettings.Reset(); } [Test] diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index e5532a5f8c..354a69af50 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.TestHelpers { //reset settings SettingsForTests.Reset(); - UmbracoSettings.ResetSetters(); + TestHelper.CleanContentDirectories(); //reset the app context, this should reset most things that require resetting like ALL resolvers ApplicationContext.Current.DisposeIfDisposable(); diff --git a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs index 7aabe196cf..5040927000 100644 --- a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs +++ b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs @@ -99,7 +99,7 @@ namespace Umbraco.Tests.TestHelpers public static void Reset() { - UmbracoSettings.ResetSetters(); + UmbracoSettings.Reset(); GlobalSettings.ResetCache(); foreach (var kvp in SavedAppSettings) ConfigurationManager.AppSettings.Set(kvp.Key, kvp.Value); diff --git a/src/Umbraco.Tests/UriUtilityTests.cs b/src/Umbraco.Tests/UriUtilityTests.cs index a477d668d8..9be7ac8a59 100644 --- a/src/Umbraco.Tests/UriUtilityTests.cs +++ b/src/Umbraco.Tests/UriUtilityTests.cs @@ -15,7 +15,7 @@ namespace Umbraco.Tests [TearDown] public void TearDown() { - UmbracoSettings.ResetSetters(); + UmbracoSettings.Reset(); } // test normal urls