From 6c4ee40b963c6f00cf83ea6fea8c1be7c4f97293 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 26 Feb 2013 16:52:44 -0100 Subject: [PATCH 1/8] Core.Configuration - import infrastructure --- .../CaseInsensitiveEnumConfigConverter.cs | 32 ++++++ .../ConfigurationKeyAttribute.cs | 44 +++++++++ .../Configuration/ConfigurationKeyType.cs | 28 ++++++ .../UmbracoConfigurationSection.cs | 33 +++++++ .../Configuration/UmbracoSettings.cs | 99 ++++++++++++++++++- src/Umbraco.Core/Umbraco.Core.csproj | 4 + .../ContentStores/PublishContentStoreTests.cs | 2 +- .../TestHelpers/BaseUmbracoApplicationTest.cs | 2 +- .../TestHelpers/SettingsForTests.cs | 2 +- src/Umbraco.Tests/UriUtilityTests.cs | 2 +- 10 files changed, 240 insertions(+), 8 deletions(-) create mode 100644 src/Umbraco.Core/Configuration/CaseInsensitiveEnumConfigConverter.cs create mode 100644 src/Umbraco.Core/Configuration/ConfigurationKeyAttribute.cs create mode 100644 src/Umbraco.Core/Configuration/ConfigurationKeyType.cs create mode 100644 src/Umbraco.Core/Configuration/UmbracoConfigurationSection.cs 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 From 2c6daa2918b660f977e32bf64b42b30c993cff0a Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 1 Mar 2013 13:56:11 -0100 Subject: [PATCH 2/8] Core.Configuration - cleanup --- src/Umbraco.Core/Configuration/UmbracoSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings.cs b/src/Umbraco.Core/Configuration/UmbracoSettings.cs index 0d23928d1c..c010e24347 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings.cs @@ -505,7 +505,7 @@ namespace Umbraco.Core.Configuration private static IEnumerable _razorDataTypeModelStaticMapping; private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(); - public static IEnumerable RazorDataTypeModelStaticMapping + internal static IEnumerable RazorDataTypeModelStaticMapping { get { From 750370d1489be6a186ef948d6c2db06696916fe6 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 12 Mar 2013 12:02:04 -0100 Subject: [PATCH 3/8] Core.Configuration, Tests - refactor settings management --- src/Umbraco.Core/Configuration/GlobalSettings.cs | 13 +++++++++++-- src/Umbraco.Core/Configuration/UmbracoSettings.cs | 4 ++-- src/Umbraco.Tests/TestHelpers/SettingsForTests.cs | 3 ++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index 10446c7f5c..dc00fc41f0 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -42,15 +42,24 @@ namespace Umbraco.Core.Configuration #endregion /// - /// used for unit tests + /// 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 ResetCache() + private static void ResetInternal() { _reservedUrlsCache = null; _reservedPaths = null; _reservedUrls = null; } + /// + /// Resets settings that were set programmatically, to their initial values. + /// + /// To be used in unit tests. + internal static void Reset() + { + ResetInternal(); + } + /// /// Gets the reserved urls from web.config. /// diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings.cs b/src/Umbraco.Core/Configuration/UmbracoSettings.cs index c010e24347..4e4b0b5801 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings.cs @@ -54,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) /// - private static void ResetSetters() + private static void ResetInternal() { _addTrailingSlash = null; _forceSafeAliases = null; @@ -1452,7 +1452,7 @@ namespace Umbraco.Core.Configuration /// To be used in unit tests. internal static void Reset() { - ResetSetters(); + ResetInternal(); using (new WriteLock(SectionsLock)) { diff --git a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs index 5040927000..7dd9e64e52 100644 --- a/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs +++ b/src/Umbraco.Tests/TestHelpers/SettingsForTests.cs @@ -100,7 +100,8 @@ namespace Umbraco.Tests.TestHelpers public static void Reset() { UmbracoSettings.Reset(); - GlobalSettings.ResetCache(); + GlobalSettings.Reset(); + foreach (var kvp in SavedAppSettings) ConfigurationManager.AppSettings.Set(kvp.Key, kvp.Value); From c635ec67d02e7787737a37147629caf0030850b9 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 1 Mar 2013 13:56:11 -0100 Subject: [PATCH 4/8] Web.Configuration - import WebRouting infrastructure --- src/Umbraco.Web/Configuration/WebRouting.cs | 14 ++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 2 files changed, 15 insertions(+) create mode 100644 src/Umbraco.Web/Configuration/WebRouting.cs diff --git a/src/Umbraco.Web/Configuration/WebRouting.cs b/src/Umbraco.Web/Configuration/WebRouting.cs new file mode 100644 index 0000000000..0471211f31 --- /dev/null +++ b/src/Umbraco.Web/Configuration/WebRouting.cs @@ -0,0 +1,14 @@ +using System.ComponentModel; +using System.Configuration; +using Umbraco.Core.Configuration; + +namespace Umbraco.Web.Configuration +{ + /// + /// The Web.Routing settings section. + /// + [ConfigurationKey("web.routing", ConfigurationKeyType.Umbraco)] + internal class WebRouting : UmbracoConfigurationSection + { + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 3ca5d86f87..4ad274d4b9 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -272,6 +272,7 @@ + From e8e8053063ba59cc3cccabd50fc7cc14eb25ef87 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 1 Mar 2013 13:56:11 -0100 Subject: [PATCH 5/8] Web.Configuration - migrate TryIisCustomErrors --- .../Configuration/UmbracoSettings.cs | 19 +++----------- src/Umbraco.Web/Configuration/WebRouting.cs | 25 +++++++++++++++++++ src/Umbraco.Web/UmbracoModule.cs | 3 ++- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings.cs b/src/Umbraco.Core/Configuration/UmbracoSettings.cs index 4e4b0b5801..a86992e19c 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings.cs @@ -61,7 +61,6 @@ namespace Umbraco.Core.Configuration _useLegacySchema = null; _useDomainPrefixes = null; _umbracoLibraryCacheDuration = null; - _trySkipIisCustomErrors = null; SettingsFilePath = null; } @@ -654,23 +653,13 @@ namespace Umbraco.Core.Configuration } } - private static bool? _trySkipIisCustomErrors; - /// - /// Gets or sets a value indicating where to try to skip IIS custom errors. + /// Gets a value indicating whether to try to skip IIS custom errors. /// - public static bool TrySkipIisCustomErrors + [UmbracoWillObsolete("Use UmbracoSettings.For.TrySkipIisCustomErrors instead.")] + internal static bool TrySkipIisCustomErrors { - get - { - // default: false - return _trySkipIisCustomErrors ?? GetKeyValue("/settings/web.routing/@trySkipIisCustomErrors", false); - } - internal set - { - // used for unit testing - _trySkipIisCustomErrors = value; - } + get { return GetKeyValue("/settings/web.routing/@trySkipIisCustomErrors", false); } } /// diff --git a/src/Umbraco.Web/Configuration/WebRouting.cs b/src/Umbraco.Web/Configuration/WebRouting.cs index 0471211f31..d128c029de 100644 --- a/src/Umbraco.Web/Configuration/WebRouting.cs +++ b/src/Umbraco.Web/Configuration/WebRouting.cs @@ -10,5 +10,30 @@ namespace Umbraco.Web.Configuration [ConfigurationKey("web.routing", ConfigurationKeyType.Umbraco)] internal class WebRouting : UmbracoConfigurationSection { + private const string KeyTrySkipIisCustomErrors = "trySkipIisCustomErrors"; + + private bool? _trySkipIisCustomErrors; + + internal protected override void ResetSection() + { + base.ResetSection(); + + _trySkipIisCustomErrors = null; + } + + /// + /// Gets or sets a value indicating whether to try to skip IIS custom errors. + /// + [ConfigurationProperty(KeyTrySkipIisCustomErrors, DefaultValue = false, IsRequired = false)] + public bool TrySkipIisCustomErrors + { + get + { + return _trySkipIisCustomErrors ?? (IsPresent + ? (bool)this[KeyTrySkipIisCustomErrors] + : UmbracoSettings.TrySkipIisCustomErrors); + } + internal set { _trySkipIisCustomErrors = value; } + } } } diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index dca6a898ce..ddc1c3fad2 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -10,6 +10,7 @@ using umbraco; using umbraco.IO; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; using UmbracoSettings = Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Web.Configuration; namespace Umbraco.Web { @@ -130,7 +131,7 @@ namespace Umbraco.Web else if (pcr.Is404) { response.StatusCode = 404; - response.TrySkipIisCustomErrors = UmbracoSettings.TrySkipIisCustomErrors; + response.TrySkipIisCustomErrors = UmbracoSettings.For().TrySkipIisCustomErrors; } if (pcr.ResponseStatusCode > 0) From 9fc5ee897f8d89306af2c405b29a9cf6b0d04faa Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 1 Mar 2013 13:56:11 -0100 Subject: [PATCH 6/8] Web.Routing - implement UrlProviderMode --- .../Routing/NiceUrlProviderTests.cs | 4 +- .../NiceUrlsProviderWithDomainsTests.cs | 2 +- src/Umbraco.Web/Configuration/WebRouting.cs | 19 +++++ src/Umbraco.Web/Routing/AliasUrlProvider.cs | 4 +- src/Umbraco.Web/Routing/DefaultUrlProvider.cs | 79 ++++++++++++++----- src/Umbraco.Web/Routing/IUrlProvider.cs | 7 +- src/Umbraco.Web/Routing/UrlProvider.cs | 57 ++++++++++--- src/Umbraco.Web/Routing/UrlProviderMode.cs | 36 +++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 9 files changed, 166 insertions(+), 43 deletions(-) create mode 100644 src/Umbraco.Web/Routing/UrlProviderMode.cs diff --git a/src/Umbraco.Tests/Routing/NiceUrlProviderTests.cs b/src/Umbraco.Tests/Routing/NiceUrlProviderTests.cs index 9d61f315ce..79cab7e194 100644 --- a/src/Umbraco.Tests/Routing/NiceUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/NiceUrlProviderTests.cs @@ -155,7 +155,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual("http://example.com/home/sub1/custom-sub-1/", routingContext.UrlProvider.GetUrl(1177)); SettingsForTests.UseDomainPrefixes = false; - routingContext.UrlProvider.EnforceAbsoluteUrls = true; + routingContext.UrlProvider.Mode = UrlProviderMode.Absolute; Assert.AreEqual("http://example.com/home/sub1/custom-sub-1/", routingContext.UrlProvider.GetUrl(1177)); } @@ -172,7 +172,7 @@ namespace Umbraco.Tests.Routing SettingsForTests.UseDomainPrefixes = true; Assert.AreEqual("#", routingContext.UrlProvider.GetUrl(999999)); SettingsForTests.UseDomainPrefixes = false; - routingContext.UrlProvider.EnforceAbsoluteUrls = true; + routingContext.UrlProvider.Mode = UrlProviderMode.Absolute; Assert.AreEqual("#", routingContext.UrlProvider.GetUrl(999999)); } } diff --git a/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs index 6eb981fb22..88f17deb46 100644 --- a/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/NiceUrlsProviderWithDomainsTests.cs @@ -368,7 +368,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual("http://domain3.com/en/1003-1-1/", routingContext.UrlProvider.GetUrl(100311)); SettingsForTests.UseDomainPrefixes = false; - routingContext.UrlProvider.EnforceAbsoluteUrls = true; + routingContext.UrlProvider.Mode = UrlProviderMode.Absolute; Assert.AreEqual("http://domain1.com/en/1001-1-1/", routingContext.UrlProvider.GetUrl(100111)); Assert.AreEqual("http://domain3.com/en/1003-1-1/", routingContext.UrlProvider.GetUrl(100311)); } diff --git a/src/Umbraco.Web/Configuration/WebRouting.cs b/src/Umbraco.Web/Configuration/WebRouting.cs index d128c029de..e05a8e3ccd 100644 --- a/src/Umbraco.Web/Configuration/WebRouting.cs +++ b/src/Umbraco.Web/Configuration/WebRouting.cs @@ -11,14 +11,17 @@ namespace Umbraco.Web.Configuration internal class WebRouting : UmbracoConfigurationSection { private const string KeyTrySkipIisCustomErrors = "trySkipIisCustomErrors"; + private const string KeyUrlProviderMode = "urlProviderMode"; private bool? _trySkipIisCustomErrors; + private Routing.UrlProviderMode? _urlProviderMode; internal protected override void ResetSection() { base.ResetSection(); _trySkipIisCustomErrors = null; + _urlProviderMode = null; } /// @@ -35,5 +38,21 @@ namespace Umbraco.Web.Configuration } internal set { _trySkipIisCustomErrors = value; } } + + /// + /// Gets or sets the url provider mode. + /// + [ConfigurationProperty(KeyUrlProviderMode, DefaultValue = Routing.UrlProviderMode.AutoLegacy, IsRequired = false)] + [TypeConverter(typeof(CaseInsensitiveEnumConfigConverter))] + public Routing.UrlProviderMode UrlProviderMode + { + get + { + return _urlProviderMode ?? (IsPresent + ? (Routing.UrlProviderMode)this[KeyUrlProviderMode] + : Routing.UrlProviderMode.Auto); + } + internal set { _urlProviderMode = value; } + } } } diff --git a/src/Umbraco.Web/Routing/AliasUrlProvider.cs b/src/Umbraco.Web/Routing/AliasUrlProvider.cs index 6821e3c1fc..8903ddb22d 100644 --- a/src/Umbraco.Web/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Web/Routing/AliasUrlProvider.cs @@ -23,14 +23,14 @@ namespace Umbraco.Web.Routing /// The content cache. /// The published content id. /// The current absolute url. - /// A value indicating whether the url should be absolute in any case. + /// The url mode. /// The url for the published content. /// /// The url is absolute or relative depending on url indicated by current and settings, unless /// absolute is true, in which case the url is always absolute. /// If the provider is unable to provide a url, it should return null. /// - public string GetUrl(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current, bool absolute) + public string GetUrl(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current, UrlProviderMode mode) { return null; // we have nothing to say } diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index dd0f8c73e2..9b762e328c 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -23,14 +23,13 @@ namespace Umbraco.Web.Routing /// The content cache. /// The published content id. /// The current absolute url. - /// A value indicating whether the url should be absolute in any case. + /// The url mode. /// The url for the published content. /// - /// The url is absolute or relative depending on url indicated by current and settings, unless - /// absolute is true, in which case the url is always absolute. + /// The url is absolute or relative depending on mode and on current. /// If the provider is unable to provide a url, it should return null. /// - public virtual string GetUrl(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current, bool absolute) + public virtual string GetUrl(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current, UrlProviderMode mode) { DomainAndUri domainUri; string path; @@ -49,7 +48,7 @@ namespace Umbraco.Web.Routing { // there was a route in the cache - extract domainUri and path // route is / or / - int pos = route.IndexOf('/'); + var pos = route.IndexOf('/'); path = pos == 0 ? route : route.Substring(pos); domainUri = pos == 0 ? null : DomainHelper.DomainForNode(int.Parse(route.Substring(0, pos)), current); } @@ -97,7 +96,7 @@ namespace Umbraco.Web.Routing } // assemble the url from domainUri (maybe null) and path - return AssembleUrl(domainUri, path, current, absolute).ToString(); + return AssembleUrl(domainUri, path, current, mode).ToString(); } #endregion @@ -185,27 +184,65 @@ namespace Umbraco.Web.Routing #region Utilities - Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, bool absolute) + Uri AssembleUrl(DomainAndUri domainUri, string path, Uri current, UrlProviderMode mode) { Uri uri; - if (domainUri == null) + // ignore vdir at that point, UriFromUmbraco will do it + + if (mode == UrlProviderMode.AutoLegacy) { - // no domain was found : return an absolute or relative url - // ignore vdir at that point - if (!absolute || current == null) - uri = new Uri(path, UriKind.Relative); - else - uri = new Uri(current.GetLeftPart(UriPartial.Authority) + path); + mode = Core.Configuration.UmbracoSettings.UseDomainPrefixes + ? UrlProviderMode.Absolute + : UrlProviderMode.Auto; } - else + + if (mode == UrlProviderMode.AutoLegacy) { - // a domain was found : return an absolute or relative url - // ignore vdir at that point - if (!absolute && current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority)) - uri = new Uri(CombinePaths(domainUri.Uri.AbsolutePath, path), UriKind.Relative); // relative - else - uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); // absolute + mode = Core.Configuration.UmbracoSettings.UseDomainPrefixes + ? UrlProviderMode.Absolute + : UrlProviderMode.Auto; + } + + if (domainUri == null) // no domain was found + { + if (current == null) + mode = UrlProviderMode.Relative; // best we can do + + switch (mode) + { + case UrlProviderMode.Absolute: + uri = new Uri(current.GetLeftPart(UriPartial.Authority) + path); + break; + case UrlProviderMode.Relative: + case UrlProviderMode.Auto: + uri = new Uri(path, UriKind.Relative); + break; + default: + throw new ArgumentOutOfRangeException("mode"); + } + } + else // a domain was found + { + if (mode == UrlProviderMode.Auto) + { + if (current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority)) + mode = UrlProviderMode.Relative; + else + mode = UrlProviderMode.Absolute; + } + + switch (mode) + { + case UrlProviderMode.Absolute: + uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); + break; + case UrlProviderMode.Relative: + uri = new Uri(CombinePaths(domainUri.Uri.AbsolutePath, path), UriKind.Relative); + break; + default: + throw new ArgumentOutOfRangeException("mode"); + } } // UriFromUmbraco will handle vdir diff --git a/src/Umbraco.Web/Routing/IUrlProvider.cs b/src/Umbraco.Web/Routing/IUrlProvider.cs index 2a14b56cbb..7cd0924654 100644 --- a/src/Umbraco.Web/Routing/IUrlProvider.cs +++ b/src/Umbraco.Web/Routing/IUrlProvider.cs @@ -15,14 +15,13 @@ namespace Umbraco.Web.Routing /// The content cache. /// The published content id. /// The current absolute url. - /// A value indicating whether the url should be absolute in any case. + /// The url mode. /// The url for the published content. /// - /// The url is absolute or relative depending on url indicated by current and settings, unless - /// absolute is true, in which case the url is always absolute. + /// The url is absolute or relative depending on mode and on current. /// If the provider is unable to provide a url, it should return null. /// - string GetUrl(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current, bool absolute); + string GetUrl(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current, UrlProviderMode mode); /// /// Gets the other urls of a published content. diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index 6b9b5e9778..ba71e4f31c 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Routing _umbracoContext = umbracoContext; _contentCache = contentCache; _urlProviders = urlProviders; - EnforceAbsoluteUrls = false; + Mode = UmbracoSettings.For().UrlProviderMode; } private readonly UmbracoContext _umbracoContext; @@ -32,9 +32,9 @@ namespace Umbraco.Web.Routing private readonly IEnumerable _urlProviders; /// - /// Gets or sets a value indicating whether the provider should enforce absolute urls. + /// Gets or sets the provider url mode. /// - public bool EnforceAbsoluteUrls { get; set; } + public UrlProviderMode Mode { get; set; } #endregion @@ -46,13 +46,12 @@ namespace Umbraco.Web.Routing /// The published content identifier. /// The url for the published content. /// - /// The url is absolute or relative depending on the current url, settings, and options. + /// The url is absolute or relative depending on Mode and on the current url. /// If the provider is unable to provide a url, it returns "#". /// public string GetUrl(int id) { - var absolute = UmbracoSettings.UseDomainPrefixes | EnforceAbsoluteUrls; - return GetUrl(id, _umbracoContext.CleanedUmbracoUrl, absolute); + return GetUrl(id, _umbracoContext.CleanedUmbracoUrl, Mode); } /// @@ -62,14 +61,14 @@ namespace Umbraco.Web.Routing /// A value indicating whether the url should be absolute in any case. /// The url for the published content. /// - /// The url is absolute or relative depending on the current url and settings, unless absolute is true, - /// in which case the url is always absolute. + /// The url is absolute or relative depending on Mode and on current, unless + /// absolute is true, in which case the url is always absolute. /// If the provider is unable to provide a url, it returns "#". /// public string GetUrl(int id, bool absolute) { - absolute = absolute | EnforceAbsoluteUrls; - return GetUrl(id, _umbracoContext.CleanedUmbracoUrl, absolute); + var mode = absolute ? UrlProviderMode.Absolute : Mode; + return GetUrl(id, _umbracoContext.CleanedUmbracoUrl, mode); } /// @@ -80,14 +79,46 @@ namespace Umbraco.Web.Routing /// A value indicating whether the url should be absolute in any case. /// The url for the published content. /// - /// The url is absolute or relative depending on url indicated by current and settings, unless + /// The url is absolute or relative depending on Mode and on current, unless /// absolute is true, in which case the url is always absolute. /// If the provider is unable to provide a url, it returns "#". /// public string GetUrl(int id, Uri current, bool absolute) { - absolute = absolute | EnforceAbsoluteUrls; - var url = _urlProviders.Select(provider => provider.GetUrl(_umbracoContext, _contentCache, id, current, absolute)).FirstOrDefault(u => u != null); + var mode = absolute ? UrlProviderMode.Absolute : Mode; + return GetUrl(id, current, mode); + } + + /// + /// Gets the nice url of a published content. + /// + /// The published content identifier. + /// The url mode. + /// The url for the published content. + /// + /// The url is absolute or relative depending on mode and on the current url. + /// If the provider is unable to provide a url, it returns "#". + /// + public string GetUrl(int id, UrlProviderMode mode) + { + return GetUrl(id, _umbracoContext.CleanedUmbracoUrl, mode); + } + + /// + /// Gets the nice url of a published content. + /// + /// The published content id. + /// The current absolute url. + /// The url mode. + /// The url for the published content. + /// + /// The url is absolute or relative depending on mode and on current. + /// If the provider is unable to provide a url, it returns "#". + /// + public string GetUrl(int id, Uri current, UrlProviderMode mode) + { + var url = _urlProviders.Select(provider => provider.GetUrl(_umbracoContext, _contentCache, id, current, mode)) + .FirstOrDefault(u => u != null); return url ?? "#"; // legacy wants this } diff --git a/src/Umbraco.Web/Routing/UrlProviderMode.cs b/src/Umbraco.Web/Routing/UrlProviderMode.cs new file mode 100644 index 0000000000..8a26eac0c2 --- /dev/null +++ b/src/Umbraco.Web/Routing/UrlProviderMode.cs @@ -0,0 +1,36 @@ +namespace Umbraco.Web.Routing +{ + /// + /// Specifies the type of urls that the url provider should produce. + /// + /// + /// The AutoLegacy option is equivalent to Auto but it also respects the legacy useDomainPrefixes setting. + /// When that setting is true, then all urls are absolute. Otherwise, urls will be relative or absolute, depending on hostnames. + /// The Relative option can lead to invalid results when combined with hostnames, but it is the only way to reproduce + /// the true, pre-4.10, always-relative behavior of Umbraco. + /// For the time being, the default option is AutoLegacy although in the future it will be Auto. + /// + internal enum UrlProviderMode + { + /// + /// Indicates that the url provider should determine automatically whether to return relative or absolute urls, + /// and also respect the legacy useDomainPrefixes setting. + /// + AutoLegacy, + + /// + /// Indicates that the url provider should produce relative urls exclusively. + /// + Relative, + + /// + /// Indicates that the url provider should produce absolute urls exclusively. + /// + Absolute, + + /// + /// Indicates that the url provider should determine automatically whether to return relative or absolute urls. + /// + Auto + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 4ad274d4b9..a3b998c8e1 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -325,6 +325,7 @@ + From b2cedf6380c527b778862384a4cbc593afd3f867 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 1 Mar 2013 13:56:12 -0100 Subject: [PATCH 7/8] Web.BaseRest - use new settings & config interface --- src/Umbraco.Web/BaseRest/BaseRestHandler.cs | 40 ++-- .../BaseRest/Configuration/BaseRestSection.cs | 44 +++- .../Configuration/ExtensionElement.cs | 18 +- .../ExtensionElementCollection.cs | 6 +- .../BaseRest/Configuration/MethodElement.cs | 42 ++-- .../BaseRest/RestExtensionMethodInfo.cs | 189 +++++++++++------- 6 files changed, 197 insertions(+), 142 deletions(-) diff --git a/src/Umbraco.Web/BaseRest/BaseRestHandler.cs b/src/Umbraco.Web/BaseRest/BaseRestHandler.cs index d3e6dc6a89..1d3cfebb91 100644 --- a/src/Umbraco.Web/BaseRest/BaseRestHandler.cs +++ b/src/Umbraco.Web/BaseRest/BaseRestHandler.cs @@ -1,22 +1,19 @@ using System; using System.Web; using System.Web.SessionState; -using System.Reflection; -using System.Xml; -using System.IO; using System.Linq; namespace Umbraco.Web.BaseRest { internal class BaseRestHandler : IHttpHandler, IRequiresSessionState { - static string _baseUrl; + static readonly string BaseUrl; static BaseRestHandler() { - _baseUrl = UriUtility.ToAbsolute(Umbraco.Core.IO.SystemDirectories.Base).ToLower(); - if (!_baseUrl.EndsWith("/")) - _baseUrl += "/"; + BaseUrl = UriUtility.ToAbsolute(Core.IO.SystemDirectories.Base).ToLower(); + if (!BaseUrl.EndsWith("/")) + BaseUrl += "/"; } public bool IsReusable @@ -31,8 +28,8 @@ namespace Umbraco.Web.BaseRest /// A value indicating whether the specified Uri should be routed to the BaseRestHandler. public static bool IsBaseRestRequest(Uri uri) { - return Umbraco.Core.Configuration.UmbracoSettings.EnableBaseRestHandler - && uri.AbsolutePath.ToLowerInvariant().StartsWith(_baseUrl); + return Core.Configuration.UmbracoSettings.For().Enabled + && uri.AbsolutePath.ToLowerInvariant().StartsWith(BaseUrl); } public void ProcessRequest(HttpContext context) @@ -40,17 +37,17 @@ namespace Umbraco.Web.BaseRest string url = context.Request.RawUrl; // sanitize and split the url - url = url.Substring(_baseUrl.Length); + url = url.Substring(BaseUrl.Length); if (url.ToLower().Contains(".aspx")) - url = url.Substring(0, url.IndexOf(".aspx")); + url = url.Substring(0, url.IndexOf(".aspx", StringComparison.OrdinalIgnoreCase)); if (url.ToLower().Contains("?")) - url = url.Substring(0, url.IndexOf("?")); - var urlParts = url.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + url = url.Substring(0, url.IndexOf("?", StringComparison.OrdinalIgnoreCase)); + var urlParts = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); // by default, return xml content context.Response.ContentType = "text/xml"; - // ensure that we have a valid request ie /base/library/method/[parameter].aspx + // ensure that we have a valid request ie /base/library/method/[parameters].aspx if (urlParts.Length < 2) { context.Response.Write("Invalid request, missing parts."); @@ -60,10 +57,11 @@ namespace Umbraco.Web.BaseRest return; } - string extensionAlias = urlParts[0]; - string methodName = urlParts[1]; + var extensionAlias = urlParts[0]; + var methodName = urlParts[1]; + var paramsCount = urlParts.Length - 2; - var method = RestExtensionMethodInfo.GetMethod(extensionAlias, methodName); + var method = RestExtensionMethodInfo.GetMethod(extensionAlias, methodName, paramsCount); if (!method.Exists) { @@ -84,7 +82,7 @@ namespace Umbraco.Web.BaseRest TrySetCulture(); - string result = method.Invoke(urlParts.Skip(2).ToArray()); + var result = method.Invoke(urlParts.Skip(2).ToArray()); if (result.Length >= 7 && result.Substring(0, 7) == "") { context.Response.StatusCode = 500; @@ -98,15 +96,15 @@ namespace Umbraco.Web.BaseRest #region from baseHttpModule.cs - // fixme - is this ok? + // note - is this ok? private static void TrySetCulture() { - string domain = HttpContext.Current.Request.Url.Host; // host only + var domain = HttpContext.Current.Request.Url.Host; // host only if (TrySetCulture(domain)) return; domain = HttpContext.Current.Request.Url.Authority; // host with port - if (TrySetCulture(domain)) return; + TrySetCulture(domain); } private static bool TrySetCulture(string domain) diff --git a/src/Umbraco.Web/BaseRest/Configuration/BaseRestSection.cs b/src/Umbraco.Web/BaseRest/Configuration/BaseRestSection.cs index 56ee3c80c6..6fd437c5f1 100644 --- a/src/Umbraco.Web/BaseRest/Configuration/BaseRestSection.cs +++ b/src/Umbraco.Web/BaseRest/Configuration/BaseRestSection.cs @@ -1,17 +1,43 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Configuration; +using System.Configuration; +using Umbraco.Core.Configuration; namespace Umbraco.Web.BaseRest.Configuration { - public class BaseRestSection : ConfigurationSection - { - [ConfigurationProperty("", IsKey = false, IsRequired = false, IsDefaultCollection = true)] + // note: the name should be "BaseRest" but we keep it "BaseRestSection" for compat. reasons. + + [ConfigurationKey("BaseRestExtensions", ConfigurationKeyType.Raw)] + internal class BaseRestSection : UmbracoConfigurationSection + { + private const string KeyEnabled = "enabled"; + + private bool? _enabled; + + internal protected override void ResetSection() + { + base.ResetSection(); + + _enabled = null; + } + + [ConfigurationProperty("", IsKey = false, IsRequired = false, IsDefaultCollection = true)] public ExtensionElementCollection Items { get { return (ExtensionElementCollection)base[""]; } } - } + + /// + /// Gets or sets a value indicating whether base rest extensions are enabled. + /// + [ConfigurationProperty(KeyEnabled, DefaultValue = true, IsRequired = false)] + public bool Enabled + { + get + { + return _enabled ?? (IsPresent + ? (bool)this[KeyEnabled] + : true); + } + internal set { _enabled = value; } + } + } } diff --git a/src/Umbraco.Web/BaseRest/Configuration/ExtensionElement.cs b/src/Umbraco.Web/BaseRest/Configuration/ExtensionElement.cs index 7b4f7713c8..17edf92edc 100644 --- a/src/Umbraco.Web/BaseRest/Configuration/ExtensionElement.cs +++ b/src/Umbraco.Web/BaseRest/Configuration/ExtensionElement.cs @@ -9,20 +9,20 @@ namespace Umbraco.Web.BaseRest.Configuration [ConfigurationCollection(typeof(ExtensionElement), CollectionType = ConfigurationElementCollectionType.BasicMapAlternate)] public class ExtensionElement : ConfigurationElementCollection { - const string Key_Alias = "alias"; - const string Key_Type = "type"; - const string Key_Method = "method"; + const string KeyAlias = "alias"; + const string KeyType = "type"; + const string KeyMethod = "method"; - [ConfigurationProperty(Key_Alias, IsKey = true, IsRequired = true)] + [ConfigurationProperty(KeyAlias, IsKey = true, IsRequired = true)] public string Alias { - get { return (string)base[Key_Alias]; } + get { return (string)base[KeyAlias]; } } - [ConfigurationProperty(Key_Type, IsKey = false, IsRequired = true)] + [ConfigurationProperty(KeyType, IsKey = false, IsRequired = true)] public string Type { - get { return (string)base[Key_Type]; } + get { return (string)base[KeyType]; } } public override ConfigurationElementCollectionType CollectionType @@ -32,12 +32,12 @@ namespace Umbraco.Web.BaseRest.Configuration protected override string ElementName { - get { return Key_Method; } + get { return KeyMethod; } } protected override bool IsElementName(string elementName) { - return elementName.Equals(Key_Method, StringComparison.InvariantCultureIgnoreCase); + return elementName.Equals(KeyMethod, StringComparison.InvariantCultureIgnoreCase); } protected override ConfigurationElement CreateNewElement() diff --git a/src/Umbraco.Web/BaseRest/Configuration/ExtensionElementCollection.cs b/src/Umbraco.Web/BaseRest/Configuration/ExtensionElementCollection.cs index aac7315395..5413d57bff 100644 --- a/src/Umbraco.Web/BaseRest/Configuration/ExtensionElementCollection.cs +++ b/src/Umbraco.Web/BaseRest/Configuration/ExtensionElementCollection.cs @@ -9,7 +9,7 @@ namespace Umbraco.Web.BaseRest.Configuration [ConfigurationCollection(typeof(ExtensionElement), CollectionType = ConfigurationElementCollectionType.BasicMapAlternate)] public class ExtensionElementCollection : ConfigurationElementCollection { - const string Key_Extension = "extension"; + const string KeyExtension = "extension"; public override ConfigurationElementCollectionType CollectionType { @@ -18,12 +18,12 @@ namespace Umbraco.Web.BaseRest.Configuration protected override string ElementName { - get { return Key_Extension; } + get { return KeyExtension; } } protected override bool IsElementName(string elementName) { - return elementName.Equals(Key_Extension, StringComparison.InvariantCultureIgnoreCase); + return elementName.Equals(KeyExtension, StringComparison.InvariantCultureIgnoreCase); } protected override ConfigurationElement CreateNewElement() diff --git a/src/Umbraco.Web/BaseRest/Configuration/MethodElement.cs b/src/Umbraco.Web/BaseRest/Configuration/MethodElement.cs index 44e79b8e78..79ad52e182 100644 --- a/src/Umbraco.Web/BaseRest/Configuration/MethodElement.cs +++ b/src/Umbraco.Web/BaseRest/Configuration/MethodElement.cs @@ -1,54 +1,50 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Configuration; +using System.Configuration; namespace Umbraco.Web.BaseRest.Configuration { public class MethodElement : ConfigurationElement { - const string Key_Name = "name"; - const string Key_AllowAll = "allowAll"; - const string Key_AllowGroup = "allowGroup"; - const string Key_AllowType = "allowType"; - const string Key_AllowMember = "allowMember"; - const string Key_ReturnXml = "returnXml"; + const string KeyName = "name"; + const string KeyAllowAll = "allowAll"; + const string KeyAllowGroup = "allowGroup"; + const string KeyAllowType = "allowType"; + const string KeyAllowMember = "allowMember"; + const string KeyReturnXml = "returnXml"; - [ConfigurationProperty(Key_Name, IsKey = true, IsRequired = true)] + [ConfigurationProperty(KeyName, IsKey = true, IsRequired = true)] public string Name { - get { return (string)base[Key_Name]; } + get { return (string)base[KeyName]; } } - [ConfigurationProperty(Key_AllowAll, IsKey = false, IsRequired = false, DefaultValue = false)] + [ConfigurationProperty(KeyAllowAll, IsKey = false, IsRequired = false, DefaultValue = false)] public bool AllowAll { - get { return (bool)base[Key_AllowAll]; } + get { return (bool)base[KeyAllowAll]; } } - [ConfigurationProperty(Key_AllowGroup, IsKey = false, IsRequired = false, DefaultValue = null)] + [ConfigurationProperty(KeyAllowGroup, IsKey = false, IsRequired = false, DefaultValue = null)] public string AllowGroup { - get { return (string)base[Key_AllowGroup]; } + get { return (string)base[KeyAllowGroup]; } } - [ConfigurationProperty(Key_AllowType, IsKey = false, IsRequired = false, DefaultValue = null)] + [ConfigurationProperty(KeyAllowType, IsKey = false, IsRequired = false, DefaultValue = null)] public string AllowType { - get { return (string)base[Key_AllowType]; } + get { return (string)base[KeyAllowType]; } } - [ConfigurationProperty(Key_AllowMember, IsKey = false, IsRequired = false, DefaultValue = null)] + [ConfigurationProperty(KeyAllowMember, IsKey = false, IsRequired = false, DefaultValue = null)] public string AllowMember { - get { return (string)base[Key_AllowMember]; } + get { return (string)base[KeyAllowMember]; } } - [ConfigurationProperty(Key_ReturnXml, IsKey = false, IsRequired = false, DefaultValue = true)] + [ConfigurationProperty(KeyReturnXml, IsKey = false, IsRequired = false, DefaultValue = true)] public bool ReturnXml { - get { return (bool)base[Key_ReturnXml]; } + get { return (bool)base[KeyReturnXml]; } } } } diff --git a/src/Umbraco.Web/BaseRest/RestExtensionMethodInfo.cs b/src/Umbraco.Web/BaseRest/RestExtensionMethodInfo.cs index c20ece0c38..762a7ae867 100644 --- a/src/Umbraco.Web/BaseRest/RestExtensionMethodInfo.cs +++ b/src/Umbraco.Web/BaseRest/RestExtensionMethodInfo.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; -using System.Text; using System.Reflection; using System.Xml; using System.IO; @@ -16,19 +16,23 @@ namespace Umbraco.Web.BaseRest { #region Utilities - static char[] Split = new char[] { ',' }; + static readonly char[] Split = new[] { ',' }; static string[] SplitString(string s) { - if (string.IsNullOrWhiteSpace(s)) - return new string[] { }; - else - return s.ToLower().Split(Split, StringSplitOptions.RemoveEmptyEntries); + return string.IsNullOrWhiteSpace(s) + ? new string[] { } + : s.ToLower().Split(Split, StringSplitOptions.RemoveEmptyEntries); } - static string GetAttribute(XmlNode node, string name) + static string GetAttribute(XmlNode node, string name) { - var attribute = node.Attributes[name]; + if (node == null) + throw new ArgumentNullException("node"); + var attributes = node.Attributes; + if (attributes == null) + throw new ArgumentException(@"Node has no Attributes collection.", "node"); + var attribute = attributes[name]; return attribute == null ? null : attribute.Value; } @@ -36,28 +40,28 @@ namespace Umbraco.Web.BaseRest private RestExtensionMethodInfo() { - this.Exists = false; + Exists = false; } private RestExtensionMethodInfo(bool allowAll, string allowGroup, string allowType, string allowMember, bool returnXml, MethodInfo method) { - this.Exists = true; + Exists = true; _allowAll = allowAll; _allowGroups = SplitString(allowGroup); _allowTypes = SplitString(allowType); _allowMembers = SplitString(allowMember); - this.ReturnXml = returnXml; + ReturnXml = returnXml; _method = method; } - static RestExtensionMethodInfo MissingMethod = new RestExtensionMethodInfo(); - static Dictionary _cache = new Dictionary(); + static readonly RestExtensionMethodInfo MissingMethod = new RestExtensionMethodInfo(); + static readonly Dictionary Cache = new Dictionary(); - bool _allowAll; - string[] _allowGroups; - string[] _allowTypes; - string[] _allowMembers; - MethodInfo _method; + readonly bool _allowAll; + readonly string[] _allowGroups; + readonly string[] _allowTypes; + readonly string[] _allowMembers; + readonly MethodInfo _method; public bool Exists { get; private set; } public bool ReturnXml { get; private set; } @@ -68,10 +72,12 @@ namespace Umbraco.Web.BaseRest // by looking everywhere (configuration, attributes, legacy attributes) // returns MissingMethod (ie .Exists == false) if not found // - public static RestExtensionMethodInfo GetMethod(string extensionAlias, string methodName) + public static RestExtensionMethodInfo GetMethod(string extensionAlias, string methodName, int paramsCount) { - return GetFromConfiguration(extensionAlias, methodName) - ?? GetFromAttribute(extensionAlias, methodName) + // note - legacy does not support paramsCount + + return GetFromConfiguration(extensionAlias, methodName, paramsCount) + ?? GetFromAttribute(extensionAlias, methodName, paramsCount) ?? GetFromLegacyConfiguration(extensionAlias, methodName) // that one should be obsoleted at some point ?? GetFromLegacyAttribute(extensionAlias, methodName) // that one should be obsoleted at some point ?? MissingMethod; @@ -83,36 +89,40 @@ namespace Umbraco.Web.BaseRest // static RestExtensionMethodInfo GetFromLegacyConfiguration(string extensionAlias, string methodName) { - const string ExtensionXPath = "/RestExtensions/ext [@alias='{0}']"; - const string MethodXPath = "./permission [@method='{0}']"; + const string extensionXPath = "/RestExtensions/ext [@alias='{0}']"; + const string methodXPath = "./permission [@method='{0}']"; var config = (Configuration.BaseRestSection)System.Configuration.ConfigurationManager.GetSection("BaseRestExtensions"); if (config == null) return null; // does not exist - // fixme - at the moment we reload the config file each time + // note - at the moment we reload the config file each time // we have to support live edits of the config file for backward compatibility reason // so if we want to cache, we'd also need to implement a watcher on the config file... var doc = new XmlDocument(); doc.Load(IOHelper.MapPath(SystemFiles.RestextensionsConfig)); - var eNode = doc.SelectSingleNode(string.Format(ExtensionXPath, extensionAlias)); + var eNode = doc.SelectSingleNode(string.Format(extensionXPath, extensionAlias)); if (eNode == null) return null; // does not exist - var mNode = eNode.SelectSingleNode(string.Format(MethodXPath, methodName)); + var mNode = eNode.SelectSingleNode(string.Format(methodXPath, methodName)); if (mNode == null) return null; // does not exist - string assemblyName = eNode.Attributes["assembly"].Value; + var attributes = eNode.Attributes; + if (attributes == null) + return null; // has no attributes + + var assemblyName = attributes["assembly"].Value; var assembly = Assembly.Load(assemblyName); - string typeName = eNode.Attributes["type"].Value; - Type type = assembly.GetType(typeName); + var typeName = attributes["type"].Value; + var type = assembly.GetType(typeName); if (type == null) return null; // does not exist @@ -137,31 +147,45 @@ namespace Umbraco.Web.BaseRest // by looking at the configuration file // returns null if not found // - static RestExtensionMethodInfo GetFromConfiguration(string extensionAlias, string methodName) + static RestExtensionMethodInfo GetFromConfiguration(string extensionAlias, string methodName, int paramsCount) { - var config = (Configuration.BaseRestSection)System.Configuration.ConfigurationManager.GetSection("BaseRestExtensions"); + var config = Core.Configuration.UmbracoSettings.For(); - if (config == null) - return null; // does not exist - - Configuration.ExtensionElement configExtension = config.Items[extensionAlias]; + var configExtension = config.Items[extensionAlias]; if (configExtension == null) return null; // does not exist - Configuration.MethodElement configMethod = configExtension[methodName]; + var configMethod = configExtension[methodName]; if (configMethod == null) return null; // does not exist - MethodInfo method; + MethodInfo method = null; try { var parts = configExtension.Type.Split(','); if (parts.Length > 2) - throw new Exception(string.Format("Failed to load extension '{0}', invalid type.")); + throw new Exception(string.Format("Failed to load extension '{0}', invalid type.", configExtension.Type)); var assembly = parts.Length == 1 ? Assembly.GetExecutingAssembly() : Assembly.Load(parts[1]); var type = assembly.GetType(parts[0]); - method = type.GetMethod(methodName); + + if (type == null) + throw new Exception(string.Format("Could not get type \"{0}\".", parts[0])); + + var methods = type.GetMethods() + .Where(m => m.Name == methodName) + .Where(m => m.GetParameters().Count() == paramsCount) + .ToArray(); + + if (methods.Length > 1) + throw new Exception(string.Format("Method \"{0}\" has many overloads with same number of parameters.", methodName)); + + if (methods.Length > 0) + { + method = methods[0]; + if (!method.IsPublic || !method.IsStatic) + throw new Exception(string.Format("Method \"{0}\" has to be public and static.", methodName)); + } } catch (Exception e) { @@ -187,20 +211,24 @@ namespace Umbraco.Web.BaseRest { // here we can cache because any change would trigger an app restart anyway - string cacheKey = extensionAlias + "." + methodName; - lock (_cache) + var cacheKey = extensionAlias + "." + methodName; + lock (Cache) { // if it's in the cache, return - if (_cache.ContainsKey(cacheKey)) - return _cache[cacheKey]; + if (Cache.ContainsKey(cacheKey)) + return Cache[cacheKey]; } // find an extension with that alias, then find a method with that name, // which has been properly marked with the attribute, and use the attribute // properties to setup a RestExtensionMethodInfo + // note: add #pragma - yes it's obsolete but we still want to support it for the time being + var extensions = PluginManager.Current.ResolveLegacyRestExtensions() +#pragma warning disable 612,618 .Where(type => type.GetCustomAttribute(false).GetAlias() == extensionAlias); +#pragma warning restore 612,618 RestExtensionMethodInfo info = null; @@ -209,7 +237,9 @@ namespace Umbraco.Web.BaseRest var method = extension.GetMethod(methodName); if (method == null) continue; // not implementing the method = ignore +#pragma warning disable 612,618 var attribute = method.GetCustomAttributes(typeof(global::umbraco.presentation.umbracobase.RestExtensionMethod), false).Cast().SingleOrDefault(); +#pragma warning restore 612,618 if (attribute == null) continue; // method has not attribute = ignore // got it! @@ -219,9 +249,9 @@ namespace Umbraco.Web.BaseRest method); // cache - lock (_cache) + lock (Cache) { - _cache[cacheKey] = info; + Cache[cacheKey] = info; } // got it, no need to look any further @@ -235,16 +265,16 @@ namespace Umbraco.Web.BaseRest // by looking for the attributes // returns null if not found // - static RestExtensionMethodInfo GetFromAttribute(string extensionAlias, string methodName) + static RestExtensionMethodInfo GetFromAttribute(string extensionAlias, string methodName, int paramsCount) { // here we can cache because any change would trigger an app restart - string cacheKey = extensionAlias + "." + methodName; - lock (_cache) + var cacheKey = string.Format("{0}.{1}[{2}]", extensionAlias, methodName, paramsCount); + lock (Cache) { // if it's in the cache, return - if (_cache.ContainsKey(cacheKey)) - return _cache[cacheKey]; + if (Cache.ContainsKey(cacheKey)) + return Cache[cacheKey]; } // find an extension with that alias, then find a method with that name, @@ -260,8 +290,19 @@ namespace Umbraco.Web.BaseRest foreach (var extension in extensions) // foreach classes with extension alias { - var method = extension.GetMethod(methodName); - if (method == null) continue; // not implementing the method = ignore + var methods = extension.GetMethods() + .Where(m => m.Name == methodName) + .Where(m => m.GetParameters().Count() == paramsCount) + .ToArray(); + + if (methods.Length == 0) continue; // not implementing the method = ignore + + if (methods.Length > 1) + throw new Exception(string.Format("Method \"{0}\" has many overloads with same number of parameters.", methodName)); + + var method = methods[0]; + if (!method.IsPublic || !method.IsStatic) + throw new Exception(string.Format("Method \"{0}\" has to be public and static.", methodName)); var attribute = method.GetCustomAttributes(typeof(RestExtensionMethodAttribute), false).Cast().SingleOrDefault(); if (attribute == null) continue; // method has not attribute = ignore @@ -273,9 +314,9 @@ namespace Umbraco.Web.BaseRest method); // cache - lock (_cache) + lock (Cache) { - _cache[cacheKey] = info; + Cache[cacheKey] = info; } // got it, no need to look any further @@ -301,11 +342,11 @@ namespace Umbraco.Web.BaseRest if (member == null) return false; - bool allowed = false; + var allowed = false; if (_allowGroups.Length > 0) { - // fixme - are these equivalent? + // note - assuming these are equivalent //var groups = member.Groups.Values.Cast().Select(group => group.Text); var groups = System.Web.Security.Roles.GetRolesForUser(member.LoginName); allowed = groups.Select(s => s.ToLower()).Intersect(_allowGroups).Any(); @@ -318,7 +359,7 @@ namespace Umbraco.Web.BaseRest if (!allowed && _allowMembers.Length > 0) { - allowed = _allowMembers.Contains(member.Id.ToString()); + allowed = _allowMembers.Contains(member.Id.ToString(CultureInfo.InvariantCulture)); } return allowed; @@ -351,13 +392,14 @@ namespace Umbraco.Web.BaseRest } else { - object[] methodParams = new object[parameters.Length]; + var methodParams = new object[parameters.Length]; - int i = 0; + var i = 0; - foreach (ParameterInfo pInfo in _method.GetParameters()) + foreach (var pInfo in _method.GetParameters()) { - Type myType = Type.GetType(pInfo.ParameterType.ToString()); + var myType = Type.GetType(pInfo.ParameterType.ToString()); + if (myType == null) throw new Exception("Failed to get type."); methodParams[(i)] = Convert.ChangeType(parameters[i], myType); i++; } @@ -375,34 +417,27 @@ namespace Umbraco.Web.BaseRest case "System.Xml.Linq.XDocument": return response.ToString(); case "System.Xml.XmlDocument": - XmlDocument xmlDoc = (XmlDocument)response; - StringWriter sw = new StringWriter(); - XmlTextWriter xw = new XmlTextWriter(sw); + var xmlDoc = (XmlDocument)response; + var sw = new StringWriter(); + var xw = new XmlTextWriter(sw); xmlDoc.WriteTo(xw); return sw.ToString(); default: - string strResponse = (string)response.ToString(); + var strResponse = response.ToString(); - if (this.ReturnXml) + if (ReturnXml) { // do a quick "is this html?" check... if it is add CDATA... if (strResponse.Contains("<") || strResponse.Contains(">")) strResponse = ""; return "" + strResponse + ""; } - else - { - return strResponse; - } + + return strResponse; } } - else - { - if (this.ReturnXml) - return "Null value returned"; - else - return string.Empty; - } + + return ReturnXml ? "Null value returned" : string.Empty; } catch (Exception ex) { From 32e6604cf7eb25bc749a3fc13d44350426ed0b55 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 1 Mar 2013 13:56:12 -0100 Subject: [PATCH 8/8] Web.Routing - add InternalRedirectPreservesTemplate setting (U4-1222...) --- .../Configuration/UmbracoSettings.cs | 9 ++++++ .../config/umbracoSettings.Release.config | 8 +++++- .../config/umbracoSettings.config | 8 +++++- src/Umbraco.Web/Configuration/WebRouting.cs | 18 ++++++++++++ .../Routing/PublishedContentRequest.cs | 28 +++++++++++++++++++ .../Routing/PublishedContentRequestEngine.cs | 9 ++++-- 6 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings.cs b/src/Umbraco.Core/Configuration/UmbracoSettings.cs index a86992e19c..738c46efec 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings.cs @@ -662,6 +662,15 @@ namespace Umbraco.Core.Configuration get { return GetKeyValue("/settings/web.routing/@trySkipIisCustomErrors", false); } } + /// + /// Gets a value indicating whether internal redirect preserves the template. + /// + [UmbracoWillObsolete("Use UmbracoSettings.For.InternalRedirectPerservesTemplate instead.")] + internal static bool InternalRedirectPreservesTemplate + { + get { return GetKeyValue("/settings/web.routing/@internalRedirectPreservesTemplate", false); } + } + /// /// Gets the allowed image file types. /// diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index bdb473c0b0..cb1e10a0ab 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -257,9 +257,15 @@ over and render its build-in error page. See MS doc for HttpResponseBase.TrySkipIisCustomErrors. The default value is false, for backward compatibility reasons, which means that IIS _will_ take over, and _prevent_ Umbraco 404 pages to show. + @internalRedirectPreservesTemplate + By default as soon as we're not displaying the initial document, we reset the template set by the + finder or by the alt. template. Set this option to true to preserve the template set by the finder + or by the alt. template, in case of an internal redirect. + (false by default, and in fact should remain false unless you know what you're doing) --> + trySkipIisCustomErrors="false" + internalRedirectPreservesTemplate="false"> diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index ec5d8aed9d..e636594221 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -209,9 +209,15 @@ over and render its build-in error page. See MS doc for HttpResponseBase.TrySkipIisCustomErrors. The default value is false, for backward compatibility reasons, which means that IIS _will_ take over, and _prevent_ Umbraco 404 pages to show. + @internalRedirectPreservesTemplate + By default as soon as we're not displaying the initial document, we reset the template set by the + finder or by the alt. template. Set this option to true to preserve the template set by the finder + or by the alt. template, in case of an internal redirect. + (false by default, and in fact should remain false unless you know what you're doing) --> + trySkipIisCustomErrors="false" + internalRedirectPreservesTemplate="false"> \ No newline at end of file diff --git a/src/Umbraco.Web/Configuration/WebRouting.cs b/src/Umbraco.Web/Configuration/WebRouting.cs index e05a8e3ccd..b6f468e133 100644 --- a/src/Umbraco.Web/Configuration/WebRouting.cs +++ b/src/Umbraco.Web/Configuration/WebRouting.cs @@ -12,9 +12,11 @@ namespace Umbraco.Web.Configuration { private const string KeyTrySkipIisCustomErrors = "trySkipIisCustomErrors"; private const string KeyUrlProviderMode = "urlProviderMode"; + private const string KeyInternalRedirectPreservesTemplate = "internalRedirectPreservesTemplate"; private bool? _trySkipIisCustomErrors; private Routing.UrlProviderMode? _urlProviderMode; + private bool? _internalRedirectPreservesTemplate; internal protected override void ResetSection() { @@ -22,6 +24,7 @@ namespace Umbraco.Web.Configuration _trySkipIisCustomErrors = null; _urlProviderMode = null; + _internalRedirectPreservesTemplate = null; } /// @@ -54,5 +57,20 @@ namespace Umbraco.Web.Configuration } internal set { _urlProviderMode = value; } } + + /// + /// Gets or sets a value indicating whether internal redirect preserves the template. + /// + [ConfigurationProperty(KeyInternalRedirectPreservesTemplate, DefaultValue = false, IsRequired = false)] + public bool InternalRedirectPreservesTemplate + { + get + { + return _internalRedirectPreservesTemplate ?? (IsPresent + ? (bool)this[KeyInternalRedirectPreservesTemplate] + : UmbracoSettings.InternalRedirectPreservesTemplate); + } + internal set { _internalRedirectPreservesTemplate = value; } + } } } diff --git a/src/Umbraco.Web/Routing/PublishedContentRequest.cs b/src/Umbraco.Web/Routing/PublishedContentRequest.cs index f526dd715f..947fbf37aa 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequest.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequest.cs @@ -2,6 +2,8 @@ using System; using System.Globalization; using Umbraco.Core; using Umbraco.Core.Models; +using UmbracoSettings = Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Web.Configuration; using umbraco; using umbraco.cms.businesslogic.web; @@ -104,11 +106,30 @@ namespace Umbraco.Web.Routing set { _publishedContent = value; + IsInternalRedirectPublishedContent = false; TemplateModel = null; } } /// + /// Sets the requested content, following an internal redirect. + /// + /// The requested content. + /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will + /// preserve or reset the template, if any. + public void SetInternalRedirectPublishedContent(IPublishedContent content) + { + // unless a template has been set already by the finder, + // template should be null at that point. + var initial = IsInitialPublishedContent; + var template = _template; + PublishedContent = content; + IsInternalRedirectPublishedContent = (initial && !IsInitialPublishedContent); + if (IsInternalRedirectPublishedContent && UmbracoSettings.For().InternalRedirectPreservesTemplate) + _template = template; + } + + /// /// Gets the initial requested content. /// /// The initial requested content is the content that was found by the finders, @@ -133,8 +154,15 @@ namespace Umbraco.Web.Routing { // note: it can very well be null if the initial content was not found _initialPublishedContent = _publishedContent; + IsInternalRedirectPublishedContent = false; } + /// + /// Gets or sets a value indicating whether the current published has been obtained from the + /// initial published content following internal redirections exclusively. + /// + public bool IsInternalRedirectPublishedContent { get; private set; } + /// /// Gets a value indicating whether the content request has a content. /// diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs index 4e16f57b40..1e6c46321b 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs @@ -7,6 +7,8 @@ using System.IO; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using UmbracoSettings = Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Web.Configuration; using umbraco; using umbraco.cms.businesslogic.web; @@ -444,7 +446,7 @@ namespace Umbraco.Web.Routing // redirect to another page var node = _routingContext.PublishedContentStore.GetDocumentById(_routingContext.UmbracoContext, internalRedirectId); - _pcr.PublishedContent = node; + _pcr.SetInternalRedirectPublishedContent(node); // don't use .PublishedContent here if (node != null) { redirect = true; @@ -532,7 +534,10 @@ namespace Umbraco.Web.Routing // read the alternate template alias, from querystring, form, cookie or server vars, // only if the published content is the initial once, else the alternate template // does not apply - string altTemplate = _pcr.IsInitialPublishedContent + // + optionnally, apply the alternate template on internal redirects + var useAltTemplate = _pcr.IsInitialPublishedContent + || (UmbracoSettings.For().InternalRedirectPreservesTemplate && _pcr.IsInternalRedirectPublishedContent); + string altTemplate = useAltTemplate ? _routingContext.UmbracoContext.HttpContext.Request["altTemplate"] : null;