This commit is contained in:
Morten Christensen
2013-03-18 14:15:30 -01:00
31 changed files with 739 additions and 217 deletions

View File

@@ -0,0 +1,32 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Configuration;
namespace Umbraco.Core.Configuration
{
/// <summary>
/// A case-insensitive configuration converter for enumerations.
/// </summary>
/// <typeparam name="T">The type of the enumeration.</typeparam>
internal class CaseInsensitiveEnumConfigConverter<T> : 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<T>())));
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Umbraco.Core.Configuration
{
/// <summary>
/// Indicates the configuration key for a section or a group.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
internal sealed class ConfigurationKeyAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurationKeyAttribute"/> class with a configuration key.
/// </summary>
/// <param name="configurationKey">The configurationkey.</param>
/// <remarks>The default configuration key type is <c>Umbraco</c>.</remarks>
public ConfigurationKeyAttribute(string configurationKey)
: this(configurationKey, ConfigurationKeyType.Umbraco)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurationKeyAttribute"/> class with a configuration key and a key type.
/// </summary>
/// <param name="configurationKey">The configurationkey.</param>
/// <param name="keyType">The key type.</param>
public ConfigurationKeyAttribute(string configurationKey, ConfigurationKeyType keyType)
{
ConfigurationKey = configurationKey;
KeyType = keyType;
}
/// <summary>
/// Gets or sets the configuration key.
/// </summary>
public string ConfigurationKey { get; private set; }
/// <summary>
/// Gets or sets the configuration key type.
/// </summary>
public ConfigurationKeyType KeyType { get; private set; }
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Umbraco.Core.Configuration
{
/// <summary>
/// Indicates the type of configuration section keys.
/// </summary>
internal enum ConfigurationKeyType
{
/// <summary>
/// An Umbraco section ie with path "/umbraco/sectionKey".
/// </summary>
Umbraco,
/// <summary>
/// An Umbraco plugins section ie with path "/umbraco.plugins/sectionKey".
/// </summary>
Plugins,
/// <summary>
/// A raw section ie with path "/sectionKey".
/// </summary>
Raw
}
}

View File

@@ -42,15 +42,24 @@ namespace Umbraco.Core.Configuration
#endregion
/// <summary>
/// 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)
/// </summary>
internal static void ResetCache()
private static void ResetInternal()
{
_reservedUrlsCache = null;
_reservedPaths = null;
_reservedUrls = null;
}
/// <summary>
/// Resets settings that were set programmatically, to their initial values.
/// </summary>
/// <remarks>To be used in unit tests.</remarks>
internal static void Reset()
{
ResetInternal();
}
/// <summary>
/// Gets the reserved urls from web.config.
/// </summary>

View File

@@ -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)
/// <summary>
/// Represents an Umbraco section within the configuration file.
/// </summary>
/// <remarks>
/// <para>The requirement for these sections is to be read-only.</para>
/// <para>However for unit tests purposes it is internally possible to override some values, and
/// then calling <c>>ResetSection</c> should cancel these changes and bring the section back to
/// what it was originally.</para>
/// <para>The <c>UmbracoSettings.For{T}</c> method will return a section, either one that
/// is in the configuration file, or a section that was created with default values.</para>
/// </remarks>
internal abstract class UmbracoConfigurationSection : ConfigurationSection
{
/// <summary>
/// Gets a value indicating whether the section actually is in the configuration file.
/// </summary>
protected bool IsPresent { get { return ElementInformation.IsPresent; } }
/// <summary>
/// Resets settings that were set programmatically, to their initial values.
/// </summary>
/// <remarks>>To be used in unit tests.</remarks>
internal protected virtual void ResetSection()
{ }
}
}

View File

@@ -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,15 +54,14 @@ namespace Umbraco.Core.Configuration
/// <summary>
/// Used in unit testing to reset all config items that were set with property setters (i.e. did not come from config)
/// </summary>
internal static void ResetSetters()
private static void ResetInternal()
{
_addTrailingSlash = null;
_forceSafeAliases = null;
_useLegacySchema = null;
_useDomainPrefixes = null;
_umbracoLibraryCacheDuration = null;
_trySkipIisCustomErrors = null;
SettingsFilePath = null;
SettingsFilePath = null;
}
internal const string TempFriendlyXmlChildContainerNodename = ""; // "children";
@@ -503,7 +504,7 @@ namespace Umbraco.Core.Configuration
private static IEnumerable<RazorDataTypeModelStaticMappingItem> _razorDataTypeModelStaticMapping;
private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
public static IEnumerable<RazorDataTypeModelStaticMappingItem> RazorDataTypeModelStaticMapping
internal static IEnumerable<RazorDataTypeModelStaticMappingItem> RazorDataTypeModelStaticMapping
{
get
{
@@ -652,23 +653,22 @@ namespace Umbraco.Core.Configuration
}
}
private static bool? _trySkipIisCustomErrors;
/// <summary>
/// Gets a value indicating whether to try to skip IIS custom errors.
/// </summary>
[UmbracoWillObsolete("Use UmbracoSettings.For<WebRouting>.TrySkipIisCustomErrors instead.")]
internal static bool TrySkipIisCustomErrors
{
get { return GetKeyValue("/settings/web.routing/@trySkipIisCustomErrors", false); }
}
/// <summary>
/// Gets or sets a value indicating where to try to skip IIS custom errors.
/// Gets a value indicating whether internal redirect preserves the template.
/// </summary>
public static bool TrySkipIisCustomErrors
{
get
{
// default: false
return _trySkipIisCustomErrors ?? GetKeyValue("/settings/web.routing/@trySkipIisCustomErrors", false);
}
internal set
{
// used for unit testing
_trySkipIisCustomErrors = value;
}
[UmbracoWillObsolete("Use UmbracoSettings.For<WebRouting>.InternalRedirectPerservesTemplate instead.")]
internal static bool InternalRedirectPreservesTemplate
{
get { return GetKeyValue("/settings/web.routing/@internalRedirectPreservesTemplate", false); }
}
/// <summary>
@@ -1440,6 +1440,95 @@ namespace Umbraco.Core.Configuration
}
#endregion
}
}
}
#region Extensible settings
/// <summary>
/// Resets settings that were set programmatically, to their initial values.
/// </summary>
/// <remarks>To be used in unit tests.</remarks>
internal static void Reset()
{
ResetInternal();
using (new WriteLock(SectionsLock))
{
foreach (var section in Sections.Values)
section.ResetSection();
}
}
private static readonly ReaderWriterLockSlim SectionsLock = new ReaderWriterLockSlim();
private static readonly Dictionary<Type, UmbracoConfigurationSection> Sections = new Dictionary<Type, UmbracoConfigurationSection>();
/// <summary>
/// Gets the specified UmbracoConfigurationSection.
/// </summary>
/// <typeparam name="T">The type of the UmbracoConfigurationSectiont.</typeparam>
/// <returns>The UmbracoConfigurationSection of the specified type.</returns>
internal static T For<T>()
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<ConfigurationKeyAttribute>(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<UmbracoConfigurationSection>())
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
}
}

View File

@@ -114,10 +114,14 @@
<Compile Include="CodeAnnotations\UmbracoWillObsoleteAttribute.cs" />
<Compile Include="CodeAnnotations\UmbracoExperimentalFeatureAttribute.cs" />
<Compile Include="CodeAnnotations\UmbracoProposedPublicAttribute.cs" />
<Compile Include="Configuration\CaseInsensitiveEnumConfigConverter.cs" />
<Compile Include="Configuration\ClientDependencyConfiguration.cs" />
<Compile Include="Configuration\ConfigurationKeyAttribute.cs" />
<Compile Include="Configuration\ConfigurationKeyType.cs" />
<Compile Include="Configuration\FileSystemProviderElement.cs" />
<Compile Include="Configuration\FileSystemProviderElementCollection.cs" />
<Compile Include="Configuration\FileSystemProvidersSection.cs" />
<Compile Include="Configuration\UmbracoConfigurationSection.cs" />
<Compile Include="Configuration\UmbracoVersion.cs" />
<Compile Include="CoreBootManager.cs" />
<Compile Include="DatabaseContext.cs" />

View File

@@ -105,7 +105,7 @@ namespace Umbraco.Tests.ContentStores
[TearDown]
public void TearDown()
{
UmbracoSettings.ResetSetters();
UmbracoSettings.Reset();
}
[Test]

View File

@@ -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));
}
}

View File

@@ -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));
}

View File

@@ -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();

View File

@@ -99,8 +99,9 @@ namespace Umbraco.Tests.TestHelpers
public static void Reset()
{
UmbracoSettings.ResetSetters();
GlobalSettings.ResetCache();
UmbracoSettings.Reset();
GlobalSettings.Reset();
foreach (var kvp in SavedAppSettings)
ConfigurationManager.AppSettings.Set(kvp.Key, kvp.Value);

View File

@@ -15,7 +15,7 @@ namespace Umbraco.Tests
[TearDown]
public void TearDown()
{
UmbracoSettings.ResetSetters();
UmbracoSettings.Reset();
}
// test normal urls

View File

@@ -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)
-->
<web.routing
trySkipIisCustomErrors="false">
trySkipIisCustomErrors="false"
internalRedirectPreservesTemplate="false">
</web.routing>
</settings>

View File

@@ -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)
-->
<web.routing
trySkipIisCustomErrors="false">
trySkipIisCustomErrors="false"
internalRedirectPreservesTemplate="false">
</web.routing>
</settings>

View File

@@ -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
/// <returns>A value indicating whether the specified Uri should be routed to the BaseRestHandler.</returns>
public static bool IsBaseRestRequest(Uri uri)
{
return Umbraco.Core.Configuration.UmbracoSettings.EnableBaseRestHandler
&& uri.AbsolutePath.ToLowerInvariant().StartsWith(_baseUrl);
return Core.Configuration.UmbracoSettings.For<Configuration.BaseRestSection>().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("<error>Invalid request, missing parts.</error>");
@@ -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) == "<error>")
{
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)

View File

@@ -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[""]; }
}
}
/// <summary>
/// Gets or sets a value indicating whether base rest extensions are enabled.
/// </summary>
[ConfigurationProperty(KeyEnabled, DefaultValue = true, IsRequired = false)]
public bool Enabled
{
get
{
return _enabled ?? (IsPresent
? (bool)this[KeyEnabled]
: true);
}
internal set { _enabled = value; }
}
}
}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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]; }
}
}
}

View File

@@ -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<string, RestExtensionMethodInfo> _cache = new Dictionary<string, RestExtensionMethodInfo>();
static readonly RestExtensionMethodInfo MissingMethod = new RestExtensionMethodInfo();
static readonly Dictionary<string, RestExtensionMethodInfo> Cache = new Dictionary<string, RestExtensionMethodInfo>();
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<Configuration.BaseRestSection>();
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<global::umbraco.presentation.umbracobase.RestExtension>(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<global::umbraco.presentation.umbracobase.RestExtensionMethod>().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<RestExtensionMethodAttribute>().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<MemberGroup>().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 = "<![CDATA[" + strResponse + "]]>";
return "<value>" + strResponse + "</value>";
}
else
{
return strResponse;
}
return strResponse;
}
}
else
{
if (this.ReturnXml)
return "<error>Null value returned</error>";
else
return string.Empty;
}
return ReturnXml ? "<error>Null value returned</error>" : string.Empty;
}
catch (Exception ex)
{

View File

@@ -0,0 +1,76 @@
using System.ComponentModel;
using System.Configuration;
using Umbraco.Core.Configuration;
namespace Umbraco.Web.Configuration
{
/// <summary>
/// The Web.Routing settings section.
/// </summary>
[ConfigurationKey("web.routing", ConfigurationKeyType.Umbraco)]
internal class WebRouting : UmbracoConfigurationSection
{
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()
{
base.ResetSection();
_trySkipIisCustomErrors = null;
_urlProviderMode = null;
_internalRedirectPreservesTemplate = null;
}
/// <summary>
/// Gets or sets a value indicating whether to try to skip IIS custom errors.
/// </summary>
[ConfigurationProperty(KeyTrySkipIisCustomErrors, DefaultValue = false, IsRequired = false)]
public bool TrySkipIisCustomErrors
{
get
{
return _trySkipIisCustomErrors ?? (IsPresent
? (bool)this[KeyTrySkipIisCustomErrors]
: UmbracoSettings.TrySkipIisCustomErrors);
}
internal set { _trySkipIisCustomErrors = value; }
}
/// <summary>
/// Gets or sets the url provider mode.
/// </summary>
[ConfigurationProperty(KeyUrlProviderMode, DefaultValue = Routing.UrlProviderMode.AutoLegacy, IsRequired = false)]
[TypeConverter(typeof(CaseInsensitiveEnumConfigConverter<Routing.UrlProviderMode>))]
public Routing.UrlProviderMode UrlProviderMode
{
get
{
return _urlProviderMode ?? (IsPresent
? (Routing.UrlProviderMode)this[KeyUrlProviderMode]
: Routing.UrlProviderMode.Auto);
}
internal set { _urlProviderMode = value; }
}
/// <summary>
/// Gets or sets a value indicating whether internal redirect preserves the template.
/// </summary>
[ConfigurationProperty(KeyInternalRedirectPreservesTemplate, DefaultValue = false, IsRequired = false)]
public bool InternalRedirectPreservesTemplate
{
get
{
return _internalRedirectPreservesTemplate ?? (IsPresent
? (bool)this[KeyInternalRedirectPreservesTemplate]
: UmbracoSettings.InternalRedirectPreservesTemplate);
}
internal set { _internalRedirectPreservesTemplate = value; }
}
}
}

View File

@@ -23,14 +23,14 @@ namespace Umbraco.Web.Routing
/// <param name="contentCache">The content cache.</param>
/// <param name="id">The published content id.</param>
/// <param name="current">The current absolute url.</param>
/// <param name="absolute">A value indicating whether the url should be absolute in any case.</param>
/// <param name="mode">The url mode.</param>
/// <returns>The url for the published content.</returns>
/// <remarks>
/// <para>The url is absolute or relative depending on url indicated by <c>current</c> and settings, unless
/// <c>absolute</c> is true, in which case the url is always absolute.</para>
/// <para>If the provider is unable to provide a url, it should return <c>null</c>.</para>
/// </remarks>
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
}

View File

@@ -23,14 +23,13 @@ namespace Umbraco.Web.Routing
/// <param name="contentCache">The content cache.</param>
/// <param name="id">The published content id.</param>
/// <param name="current">The current absolute url.</param>
/// <param name="absolute">A value indicating whether the url should be absolute in any case.</param>
/// <param name="mode">The url mode.</param>
/// <returns>The url for the published content.</returns>
/// <remarks>
/// <para>The url is absolute or relative depending on url indicated by <c>current</c> and settings, unless
/// <c>absolute</c> is true, in which case the url is always absolute.</para>
/// <para>The url is absolute or relative depending on <c>mode</c> and on <c>current</c>.</para>
/// <para>If the provider is unable to provide a url, it should return <c>null</c>.</para>
/// </remarks>
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 /<path> or <domainRootId>/<path>
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

View File

@@ -15,14 +15,13 @@ namespace Umbraco.Web.Routing
/// <param name="contentCache">The content cache.</param>
/// <param name="id">The published content id.</param>
/// <param name="current">The current absolute url.</param>
/// <param name="absolute">A value indicating whether the url should be absolute in any case.</param>
/// <param name="mode">The url mode.</param>
/// <returns>The url for the published content.</returns>
/// <remarks>
/// <para>The url is absolute or relative depending on url indicated by <c>current</c> and settings, unless
/// <c>absolute</c> is true, in which case the url is always absolute.</para>
/// <para>The url is absolute or relative depending on <c>mode</c> and on <c>current</c>.</para>
/// <para>If the provider is unable to provide a url, it should return <c>null</c>.</para>
/// </remarks>
string GetUrl(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current, bool absolute);
string GetUrl(UmbracoContext umbracoContext, IPublishedContentStore contentCache, int id, Uri current, UrlProviderMode mode);
/// <summary>
/// Gets the other urls of a published content.

View File

@@ -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;
}
}
/// <summary>
/// Sets the requested content, following an internal redirect.
/// </summary>
/// <param name="content">The requested content.</param>
/// <remarks>Depending on <c>UmbracoSettings.InternalRedirectPreservesTemplate</c>, will
/// preserve or reset the template, if any.</remarks>
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<WebRouting>().InternalRedirectPreservesTemplate)
_template = template;
}
/// <summary>
/// Gets the initial requested content.
/// </summary>
/// <remarks>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;
}
/// <summary>
/// Gets or sets a value indicating whether the current published has been obtained from the
/// initial published content following internal redirections exclusively.
/// </summary>
public bool IsInternalRedirectPublishedContent { get; private set; }
/// <summary>
/// Gets a value indicating whether the content request has a content.
/// </summary>

View File

@@ -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<WebRouting>().InternalRedirectPreservesTemplate && _pcr.IsInternalRedirectPublishedContent);
string altTemplate = useAltTemplate
? _routingContext.UmbracoContext.HttpContext.Request["altTemplate"]
: null;

View File

@@ -24,7 +24,7 @@ namespace Umbraco.Web.Routing
_umbracoContext = umbracoContext;
_contentCache = contentCache;
_urlProviders = urlProviders;
EnforceAbsoluteUrls = false;
Mode = UmbracoSettings.For<Configuration.WebRouting>().UrlProviderMode;
}
private readonly UmbracoContext _umbracoContext;
@@ -32,9 +32,9 @@ namespace Umbraco.Web.Routing
private readonly IEnumerable<IUrlProvider> _urlProviders;
/// <summary>
/// Gets or sets a value indicating whether the provider should enforce absolute urls.
/// Gets or sets the provider url mode.
/// </summary>
public bool EnforceAbsoluteUrls { get; set; }
public UrlProviderMode Mode { get; set; }
#endregion
@@ -46,13 +46,12 @@ namespace Umbraco.Web.Routing
/// <param name="id">The published content identifier.</param>
/// <returns>The url for the published content.</returns>
/// <remarks>
/// <para>The url is absolute or relative depending on the current url, settings, and options.</para>
/// <para>The url is absolute or relative depending on <c>Mode</c> and on the current url.</para>
/// <para>If the provider is unable to provide a url, it returns "#".</para>
/// </remarks>
public string GetUrl(int id)
{
var absolute = UmbracoSettings.UseDomainPrefixes | EnforceAbsoluteUrls;
return GetUrl(id, _umbracoContext.CleanedUmbracoUrl, absolute);
return GetUrl(id, _umbracoContext.CleanedUmbracoUrl, Mode);
}
/// <summary>
@@ -62,14 +61,14 @@ namespace Umbraco.Web.Routing
/// <param name="absolute">A value indicating whether the url should be absolute in any case.</param>
/// <returns>The url for the published content.</returns>
/// <remarks>
/// <para>The url is absolute or relative depending on the current url and settings, unless <c>absolute</c> is true,
/// in which case the url is always absolute.</para>
/// <para>The url is absolute or relative depending on <c>Mode</c> and on <c>current</c>, unless
/// <c>absolute</c> is true, in which case the url is always absolute.</para>
/// <para>If the provider is unable to provide a url, it returns "#".</para>
/// </remarks>
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);
}
/// <summary>
@@ -80,14 +79,46 @@ namespace Umbraco.Web.Routing
/// <param name="absolute">A value indicating whether the url should be absolute in any case.</param>
/// <returns>The url for the published content.</returns>
/// <remarks>
/// <para>The url is absolute or relative depending on url indicated by <c>current</c> and settings, unless
/// <para>The url is absolute or relative depending on <c>Mode</c> and on <c>current</c>, unless
/// <c>absolute</c> is true, in which case the url is always absolute.</para>
/// <para>If the provider is unable to provide a url, it returns "#".</para>
/// </remarks>
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);
}
/// <summary>
/// Gets the nice url of a published content.
/// </summary>
/// <param name="id">The published content identifier.</param>
/// <param name="mode">The url mode.</param>
/// <returns>The url for the published content.</returns>
/// <remarks>
/// <para>The url is absolute or relative depending on <c>mode</c> and on the current url.</para>
/// <para>If the provider is unable to provide a url, it returns "#".</para>
/// </remarks>
public string GetUrl(int id, UrlProviderMode mode)
{
return GetUrl(id, _umbracoContext.CleanedUmbracoUrl, mode);
}
/// <summary>
/// Gets the nice url of a published content.
/// </summary>
/// <param name="id">The published content id.</param>
/// <param name="current">The current absolute url.</param>
/// <param name="mode">The url mode.</param>
/// <returns>The url for the published content.</returns>
/// <remarks>
/// <para>The url is absolute or relative depending on <c>mode</c> and on <c>current</c>.</para>
/// <para>If the provider is unable to provide a url, it returns "#".</para>
/// </remarks>
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
}

View File

@@ -0,0 +1,36 @@
namespace Umbraco.Web.Routing
{
/// <summary>
/// Specifies the type of urls that the url provider should produce.
/// </summary>
/// <remarks>
/// <para>The <c>AutoLegacy</c> option is equivalent to <c>Auto</c> but it also respects the legacy <c>useDomainPrefixes</c> setting.
/// When that setting is true, then all urls are absolute. Otherwise, urls will be relative or absolute, depending on hostnames.</para>
/// <para>The <c>Relative</c> 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.</para>
/// <para>For the time being, the default option is <c>AutoLegacy</c> although in the future it will be <c>Auto</c>.</para>
/// </remarks>
internal enum UrlProviderMode
{
/// <summary>
/// Indicates that the url provider should determine automatically whether to return relative or absolute urls,
/// and also respect the legacy <c>useDomainPrefixes</c> setting.
/// </summary>
AutoLegacy,
/// <summary>
/// Indicates that the url provider should produce relative urls exclusively.
/// </summary>
Relative,
/// <summary>
/// Indicates that the url provider should produce absolute urls exclusively.
/// </summary>
Absolute,
/// <summary>
/// Indicates that the url provider should determine automatically whether to return relative or absolute urls.
/// </summary>
Auto
}
}

View File

@@ -272,6 +272,7 @@
<Compile Include="Cache\PageCacheRefresher.cs" />
<Compile Include="Cache\TemplateCacheRefresher.cs" />
<Compile Include="Cache\UserCacheRefresher.cs" />
<Compile Include="Configuration\WebRouting.cs" />
<Compile Include="Dynamics\DynamicExpression.cs" />
<Compile Include="Dynamics\DynamicGrouping.cs" />
<Compile Include="Dynamics\DynamicPublishedContentIdEqualityComparer.cs" />
@@ -324,6 +325,7 @@
<Compile Include="Mvc\MemberAuthorizeAttribute.cs" />
<Compile Include="Mvc\ControllerFactoryExtensions.cs" />
<Compile Include="Mvc\SurfaceRouteHandler.cs" />
<Compile Include="Routing\UrlProviderMode.cs" />
<Compile Include="Search\ExamineIndexerModel.cs" />
<Compile Include="Search\LuceneIndexerExtensions.cs" />
<Compile Include="umbraco.presentation\umbraco\dialogs\AssignDomain2.aspx.cs">

View File

@@ -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<WebRouting>().TrySkipIisCustomErrors;
}
if (pcr.ResponseStatusCode > 0)