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