From 7747b8f64f9cb7d831bb9a65a6e462044226e2a9 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 19 Nov 2013 09:16:54 +0100 Subject: [PATCH 01/11] U4-3579 - support chars such as <, > or & in prevalues --- src/Umbraco.Web.UI/umbraco/developer/DataTypes/editDatatype.aspx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI/umbraco/developer/DataTypes/editDatatype.aspx b/src/Umbraco.Web.UI/umbraco/developer/DataTypes/editDatatype.aspx index 86066a5c63..12f830e9eb 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/DataTypes/editDatatype.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/DataTypes/editDatatype.aspx @@ -1,4 +1,5 @@ <%@ Page Language="c#" MasterPageFile="../../masterpages/umbracoPage.Master" Title="Edit data type" + ValidateRequest="false" CodeBehind="editDatatype.aspx.cs" AutoEventWireup="True" Inherits="umbraco.cms.presentation.developer.editDatatype" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> From 026d040a622aaf842526065ae7b63fbe1ae18da3 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 6 Nov 2013 11:50:22 +0100 Subject: [PATCH 02/11] Enable StandaloneApplication to work with Powershell --- .../Standalone/PowershellAssemblyResolver.cs | 41 +++ .../Standalone/StandaloneApplication.cs | 26 +- .../Standalone/WriteableConfigSystem.cs | 262 ++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 + 4 files changed, 327 insertions(+), 4 deletions(-) create mode 100644 src/Umbraco.Web/Standalone/PowershellAssemblyResolver.cs create mode 100644 src/Umbraco.Web/Standalone/WriteableConfigSystem.cs diff --git a/src/Umbraco.Web/Standalone/PowershellAssemblyResolver.cs b/src/Umbraco.Web/Standalone/PowershellAssemblyResolver.cs new file mode 100644 index 0000000000..8c323902c0 --- /dev/null +++ b/src/Umbraco.Web/Standalone/PowershellAssemblyResolver.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +namespace Umbraco.Web.Standalone +{ + public static class PowershellAssemblyResolver + { + private static readonly Dictionary Assemblies; + private static readonly object Locko = new object(); + + static PowershellAssemblyResolver() + { + var comparer = StringComparer.CurrentCultureIgnoreCase; + Assemblies = new Dictionary(comparer); + AppDomain.CurrentDomain.AssemblyResolve += ResolveHandler; + } + + public static void AddAssemblyLocation(string path) + { + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("Arg is null or empty.", "path"); + + lock (Locko) + { + var name = Path.GetFileNameWithoutExtension(path); + Assemblies.Add(name, path); + } + } + + private static Assembly ResolveHandler(object sender, ResolveEventArgs args) + { + var assemblyName = new AssemblyName(args.Name); + string assemblyFile; + return Assemblies.TryGetValue(assemblyName.Name, out assemblyFile) + ? Assembly.LoadFrom(assemblyFile) + : null; + } + } +} diff --git a/src/Umbraco.Web/Standalone/StandaloneApplication.cs b/src/Umbraco.Web/Standalone/StandaloneApplication.cs index 64e2975dc2..e490684dc7 100644 --- a/src/Umbraco.Web/Standalone/StandaloneApplication.cs +++ b/src/Umbraco.Web/Standalone/StandaloneApplication.cs @@ -59,7 +59,15 @@ namespace Umbraco.Web.Standalone if (noerr) return; throw new InvalidOperationException("Application has already started."); } - Application_Start(this, EventArgs.Empty); + try + { + Application_Start(this, EventArgs.Empty); + } + catch + { + TerminateInternal(); + throw; + } _started = true; } } @@ -74,14 +82,24 @@ namespace Umbraco.Web.Standalone throw new InvalidOperationException("Application has already been terminated."); } + TerminateInternal(); + } + } + + private void TerminateInternal() + { + if (ApplicationContext.Current != null) + { ApplicationContext.Current.DisposeIfDisposable(); // should reset resolution, clear caches & resolvers... ApplicationContext.Current = null; + } + if (UmbracoContext.Current != null) + { UmbracoContext.Current.DisposeIfDisposable(); // dunno UmbracoContext.Current = null; - - _started = false; - _application = null; } + _started = false; + _application = null; } #endregion diff --git a/src/Umbraco.Web/Standalone/WriteableConfigSystem.cs b/src/Umbraco.Web/Standalone/WriteableConfigSystem.cs new file mode 100644 index 0000000000..86d187c393 --- /dev/null +++ b/src/Umbraco.Web/Standalone/WriteableConfigSystem.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Specialized; +using System.Configuration; +using System.Configuration.Internal; +using System.Reflection; +using System.Threading; + +namespace Umbraco.Web.Standalone +{ + // see http://stackoverflow.com/questions/15653621/how-to-update-add-modify-delete-keys-in-appsettings-section-of-web-config-at-r + // see http://www.codeproject.com/Articles/69364/Override-Configuration-Manager + + public sealed class WriteableConfigSystem : IInternalConfigSystem + { + private static readonly ReaderWriterLockSlim RwLock = new ReaderWriterLockSlim(); + private static WriteableConfigSystem _installed; + private static IInternalConfigSystem _clientConfigSystem; + private object _appsettings; + private object _connectionStrings; + private static object _sInitStateOrig; + private static object _sConfigSystemOrig; + + public static bool Installed + { + get + { + try + { + RwLock.EnterReadLock(); + return _installed != null; + } + finally + { + RwLock.ExitReadLock(); + } + } + } + + /// + /// Re-initializes the ConfigurationManager, allowing us to merge in the settings from Core.Config + /// + public static void Install() + { + try + { + RwLock.EnterWriteLock(); + + if (_installed != null) + throw new InvalidOperationException("ConfigSystem is already installed."); + + FieldInfo[] fiStateValues = null; + var tInitState = typeof(ConfigurationManager).GetNestedType("InitState", BindingFlags.NonPublic); + + if (tInitState != null) + fiStateValues = tInitState.GetFields(); + // 0: NotStarted + // 1: Started + // 2: Usable + // 3: Completed + + var fiInit = typeof(ConfigurationManager).GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static); + var fiSystem = typeof(ConfigurationManager).GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static); + + if (fiInit != null && fiSystem != null && fiStateValues != null) + { + _sInitStateOrig = fiInit.GetValue(null); + _sConfigSystemOrig = fiSystem.GetValue(null); + fiInit.SetValue(null, fiStateValues[1].GetValue(null)); // set to Started + fiSystem.SetValue(null, null); // clear current config system + } + + _installed = new WriteableConfigSystem(); + + var configFactoryType = Type.GetType("System.Configuration.Internal.InternalConfigSettingsFactory, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true); + var configSettingsFactory = (IInternalConfigSettingsFactory)Activator.CreateInstance(configFactoryType, true); + // just does ConfigurationManager.SetConfigurationSystem(_installed, false); + // 'false' turns initState to 2 ie usable (vs 3 ie completed) + configSettingsFactory.SetConfigurationSystem(_installed, false); + + // note: prob. don't need the factory... see how we uninstall... + + var clientConfigSystemType = Type.GetType("System.Configuration.ClientConfigurationSystem, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true); + _clientConfigSystem = (IInternalConfigSystem)Activator.CreateInstance(clientConfigSystemType, true); + } + finally + { + RwLock.ExitWriteLock(); + } + } + + public static void Uninstall() + { + try + { + RwLock.EnterWriteLock(); + + if (_installed == null) + throw new InvalidOperationException("ConfigSystem is not installed."); + + FieldInfo[] fiStateValues = null; + var tInitState = typeof(ConfigurationManager).GetNestedType("InitState", BindingFlags.NonPublic); + + if (tInitState != null) + fiStateValues = tInitState.GetFields(); + + var fiInit = typeof(ConfigurationManager).GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static); + var fiSystem = typeof(ConfigurationManager).GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static); + + if (fiInit != null && fiSystem != null && fiStateValues != null) + { + // reset - the hard way + fiInit.SetValue(null, _sInitStateOrig); + fiSystem.SetValue(null, _sConfigSystemOrig); + } + + _installed = null; + _clientConfigSystem = null; + } + finally + { + RwLock.ExitWriteLock(); + } + } + + public static void Reset() + { + try + { + RwLock.EnterWriteLock(); + + if (_installed == null) + throw new InvalidOperationException("ConfigSystem is not installed."); + + _installed._appsettings = null; + _installed._connectionStrings = null; + } + finally + { + RwLock.ExitWriteLock(); + } + } + + #region IInternalConfigSystem Members + + public object GetSection(string configKey) + { + // get the section from the default location (web.config or app.config) + var section = _clientConfigSystem.GetSection(configKey); + + try + { + RwLock.EnterReadLock(); + + switch (configKey) + { + case "appSettings": + // Return cached version if exists + if (_appsettings != null) + return _appsettings; + + // create a new collection because the underlying collection is read-only + var cfg = new NameValueCollection(); + + // If an AppSettings section exists in Web.config, read and add values from it + var nvSection = section as NameValueCollection; + if (nvSection != null) + { + var localSettings = nvSection; + foreach (string key in localSettings) + cfg.Add(key, localSettings[key]); + } + + //// -------------------------------------------------------------------- + //// Here I read and decrypt keys and add them to secureConfig dictionary + //// To test assume the following line is a key stored in secure sotrage. + ////secureConfig = SecureConfig.LoadConfig(); + //secureConfig.Add("ACriticalKey", "VeryCriticalValue"); + //// -------------------------------------------------------------------- + //foreach (KeyValuePair item in secureConfig) + //{ + // if (cfg.AllKeys.Contains(item.Key)) + // { + // cfg[item.Key] = item.Value; + // } + // else + // { + // cfg.Add(item.Key, item.Value); + // } + //} + //// -------------------------------------------------------------------- + + + // Cach the settings for future use + + _appsettings = cfg; + // return the merged version of the items from secure storage and appsettings + section = _appsettings; + break; + + case "connectionStrings": + // Return cached version if exists + if (_connectionStrings != null) + return _connectionStrings; + + // create a new collection because the underlying collection is read-only + var connectionStringsSection = new ConnectionStringsSection(); + + // copy the existing connection strings into the new collection + foreach ( + ConnectionStringSettings connectionStringSetting in + ((ConnectionStringsSection)section).ConnectionStrings) + connectionStringsSection.ConnectionStrings.Add(connectionStringSetting); + + // -------------------------------------------------------------------- + // Again Load connection strings from secure storage and merge like below + // connectionStringsSection.ConnectionStrings.Add(connectionStringSetting); + // -------------------------------------------------------------------- + + // Cach the settings for future use + _connectionStrings = connectionStringsSection; + // return the merged version of the items from secure storage and appsettings + section = _connectionStrings; + break; + } + } + finally + { + RwLock.ExitReadLock(); + } + + return section; + } + + public void RefreshConfig(string sectionName) + { + try + { + RwLock.EnterWriteLock(); + + if (sectionName == "appSettings") + { + _appsettings = null; + } + + if (sectionName == "connectionStrings") + { + _connectionStrings = null; + } + } + finally + { + RwLock.ExitWriteLock(); + } + + _clientConfigSystem.RefreshConfig(sectionName); + } + + public bool SupportsUserConfig { get { return _clientConfigSystem.SupportsUserConfig; } } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 944d51536a..f1a6f79d57 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -321,6 +321,7 @@ + @@ -396,6 +397,7 @@ + From 2f953c3819e86a74848aa01767fd241b914b53f9 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 12 Feb 2014 16:40:42 +0100 Subject: [PATCH 03/11] Fix a38e0dc - use UInt64 instead of int --- src/Umbraco.Core/Models/UmbracoEntity.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index 6822582576..7abffc913d 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -54,7 +54,8 @@ namespace Umbraco.Core.Models Trashed = trashed; } - public UmbracoEntity(int trashed) + // for MySql + public UmbracoEntity(UInt64 trashed) { AdditionalData = new Dictionary(); Trashed = trashed == 1; From 83736bec0170e938bc02e17c851e4c49cdee7867 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 4 Mar 2014 17:28:24 +0100 Subject: [PATCH 04/11] U4-4190 - enable retrieval of wildcard domains --- src/umbraco.cms/businesslogic/web/Domain.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/umbraco.cms/businesslogic/web/Domain.cs b/src/umbraco.cms/businesslogic/web/Domain.cs index 4547078673..8c3e630285 100644 --- a/src/umbraco.cms/businesslogic/web/Domain.cs +++ b/src/umbraco.cms/businesslogic/web/Domain.cs @@ -143,7 +143,7 @@ namespace umbraco.cms.businesslogic.web return GetDomains(false); } - internal static IEnumerable GetDomains(bool includeWildcards) + public static IEnumerable GetDomains(bool includeWildcards) { var domains = ApplicationContext.Current.ApplicationCache.GetCacheItem( CacheKeys.DomainCacheKey, @@ -191,6 +191,11 @@ namespace umbraco.cms.businesslogic.web return GetDomains().Where(d => d._root == nodeId).ToArray(); } + public static Domain[] GetDomainsById(int nodeId, bool includeWildcards) + { + return GetDomains(includeWildcards).Where(d => d._root == nodeId).ToArray(); + } + public static bool Exists(string DomainName) { return GetDomain(DomainName) != null; From 5dbc72897a366c9d9cba2276eb38cc3e1637fef5 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 4 Mar 2014 13:01:48 +0100 Subject: [PATCH 05/11] Add method to reset PublishedContentRequest template --- src/Umbraco.Web/Routing/PublishedContentRequest.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Umbraco.Web/Routing/PublishedContentRequest.cs b/src/Umbraco.Web/Routing/PublishedContentRequest.cs index 424ffa1b52..9ab312e33e 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequest.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequest.cs @@ -312,6 +312,16 @@ namespace Umbraco.Web.Routing TemplateModel = template; } + /// + /// Resets the template. + /// + /// The RenderingEngine becomes unknown. + public void ResetTemplate() + { + EnsureWriteable(); + TemplateModel = null; + } + /// /// Gets a value indicating whether the content request has a template. /// From ff0bcffefa303ad8ef543f66533d11fdc0ea6ca3 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 12 Feb 2014 23:27:48 +0100 Subject: [PATCH 06/11] Implement DefaultShortStringHelper.PostFilter to cut url segments at 240 chars --- .../Strings/DefaultShortStringHelper.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index 07f262c272..508e356f71 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -115,6 +115,11 @@ namespace Umbraco.Core.Strings return InvalidFileNameChars.Contains(c) == false; } + public static string CutMaxLength(string text, int length) + { + return text.Length <= length ? text : text.Substring(0, length); + } + #endregion #region Configuration @@ -168,6 +173,7 @@ namespace Umbraco.Core.Strings return WithConfig(CleanStringType.UrlSegment, new Config { PreFilter = ApplyUrlReplaceCharacters, + PostFilter = x => CutMaxLength(x, 240), IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore StringType = (UrlReplacingToAscii ? CleanStringType.Ascii : CleanStringType.Utf8) | CleanStringType.LowerCase, BreakTermsOnUpper = false, @@ -202,6 +208,7 @@ namespace Umbraco.Core.Strings { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged; PreFilter = null; + PostFilter = null; IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c); BreakTermsOnUpper = false; CutAcronymOnNonUpper = false; @@ -214,6 +221,7 @@ namespace Umbraco.Core.Strings return new Config { PreFilter = PreFilter, + PostFilter = PostFilter, IsTerm = IsTerm, StringType = StringType, BreakTermsOnUpper = BreakTermsOnUpper, @@ -224,6 +232,7 @@ namespace Umbraco.Core.Strings } public Func PreFilter { get; set; } + public Func PostFilter { get; set; } public Func IsTerm { get; set; } public CleanStringType StringType { get; set; } @@ -554,6 +563,10 @@ function validateSafeAlias(id, value, immediate, callback) {{ // clean text = CleanCodeString(text, stringType, separator.Value, culture, config); + // apply post-filter + if (config.PostFilter != null) + text = config.PostFilter(text); + return text; } From cec4d41df7b20de321e22a06e2d26255ee91bca8 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 4 Mar 2014 11:58:37 +0100 Subject: [PATCH 07/11] Add comments to UmbracoViewPageOfTModel --- src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs index 373e103a56..b7220b7cc1 100644 --- a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs +++ b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs @@ -141,6 +141,7 @@ namespace Umbraco.Web.Mvc // maps model protected override void SetViewData(ViewDataDictionary viewData) { + // if view data contains no model, nothing to do var source = viewData.Model; if (source == null) { @@ -148,6 +149,8 @@ namespace Umbraco.Web.Mvc return; } + // get the type of the view data model (what we have) + // get the type of this view model (what we want) var sourceType = source.GetType(); var targetType = typeof (TModel); @@ -160,13 +163,15 @@ namespace Umbraco.Web.Mvc // try to grab the content // if no content is found, return, nothing we can do - var sourceContent = source as IPublishedContent; + var sourceContent = source as IPublishedContent; // check if what we have is an IPublishedContent if (sourceContent == null && sourceType.Implements()) { + // else check if it's an IRenderModel => get the content sourceContent = ((IRenderModel)source).Content; } if (sourceContent == null) { + // else check if we can convert it to a content var attempt = source.TryConvertTo(); if (attempt.Success) sourceContent = attempt.Result; } From 3b6dcc3421d2fe17d2d36f146fdcb108d80e5eb0 Mon Sep 17 00:00:00 2001 From: Stephan Date: Sun, 26 Jan 2014 14:59:35 +0100 Subject: [PATCH 08/11] Factor Dictionary.ReplaceKey method --- src/umbraco.cms/businesslogic/Dictionary.cs | 19 ++++++++++++++ .../dropdownlist/dropdown.cs | 25 ++----------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/umbraco.cms/businesslogic/Dictionary.cs b/src/umbraco.cms/businesslogic/Dictionary.cs index e524f90429..cd32162fa7 100644 --- a/src/umbraco.cms/businesslogic/Dictionary.cs +++ b/src/umbraco.cms/businesslogic/Dictionary.cs @@ -532,5 +532,24 @@ namespace umbraco.cms.businesslogic } #endregion } + + // zb023 - utility method + public static string ReplaceKey(string text) + { + if (text.StartsWith("#") == false) + return text; + + var lang = Language.GetByCultureCode(Thread.CurrentThread.CurrentCulture.Name); + + if (lang == null) + return "[" + text + "]"; + + if (DictionaryItem.hasKey(text.Substring(1, text.Length - 1)) == false) + return "[" + text + "]"; + + var di = new DictionaryItem(text.Substring(1, text.Length - 1)); + return di.Value(lang.id); + } + } } \ No newline at end of file diff --git a/src/umbraco.editorControls/dropdownlist/dropdown.cs b/src/umbraco.editorControls/dropdownlist/dropdown.cs index f4d887cfb4..2cc44ae6a9 100644 --- a/src/umbraco.editorControls/dropdownlist/dropdown.cs +++ b/src/umbraco.editorControls/dropdownlist/dropdown.cs @@ -59,7 +59,7 @@ namespace umbraco.editorControls { foreach (object key in _prevalues.Keys) { - this.Items.Add(new ListItem(dropdown.DictionaryReplace(_prevalues[key].ToString()), key.ToString())); + this.Items.Add(new ListItem(Dictionary.ReplaceKey(_prevalues[key].ToString()), key.ToString())); } } @@ -67,7 +67,7 @@ namespace umbraco.editorControls { foreach (KeyValuePair item in Prevalues) { - this.Items.Add(new ListItem(dropdown.DictionaryReplace(item.Value), item.Key.ToString())); + this.Items.Add(new ListItem(Dictionary.ReplaceKey(item.Value), item.Key.ToString())); } } @@ -76,26 +76,5 @@ namespace umbraco.editorControls if (_data != null && _data.Value != null) this.SelectedValue = _data.Value.ToString(); } - - static string DictionaryReplace(string text) - { - if (!text.StartsWith("#")) - return text; - else - { - Language lang = Language.GetByCultureCode(System.Threading.Thread.CurrentThread.CurrentCulture.Name); - if (lang != null) - { - if (Dictionary.DictionaryItem.hasKey(text.Substring(1, text.Length - 1))) - { - Dictionary.DictionaryItem di = new Dictionary.DictionaryItem(text.Substring(1, text.Length - 1)); - return di.Value(lang.id); - } - } - - return "[" + text + "]"; - } - } - } } \ No newline at end of file From acd377956f14e903ae277b0f4a7c3a7498668291 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 5 Mar 2014 13:37:03 +0100 Subject: [PATCH 09/11] Improve how DomainHelper determines domain --- src/Umbraco.Web/Routing/DomainHelper.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainHelper.cs index 85a1c5f66f..9f64b51e04 100644 --- a/src/Umbraco.Web/Routing/DomainHelper.cs +++ b/src/Umbraco.Web/Routing/DomainHelper.cs @@ -152,12 +152,21 @@ namespace Umbraco.Web.Routing } else { - // look for the first domain that would be the base of the hint - var hintWithSlash = current.EndPathWithSlash(); + // look for the first domain that would be the base of the current url + // ie current is www.example.com/foo/bar, look for domain www.example.com + var currentWithSlash = current.EndPathWithSlash(); domainAndUri = domainsAndUris - .FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash)); + .FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(currentWithSlash)); + if (domainAndUri != null) return domainAndUri; + + // look for the first domain that the current url would be the base of + // ie current is www.example.com, look for domain www.example.com/foo/bar + domainAndUri = domainsAndUris + .FirstOrDefault(d => currentWithSlash.IsBaseOf(d.Uri.EndPathWithSlash())); + if (domainAndUri != null) return domainAndUri; + // if none matches, then try to run the filter to pick a domain - if (domainAndUri == null && filter != null) + if (filter != null) { domainAndUri = filter(domainsAndUris); // if still nothing, pick the first one? From 3d4409e52a806793a70b48849f12c56286a631ed Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 27 Jan 2014 14:27:53 +0100 Subject: [PATCH 10/11] Refactor CacheRefreshersResolver.CacheResolvers into .CacheRefreshers --- src/Umbraco.Core/CacheRefreshersResolver.cs | 2 +- .../umbraco.presentation/umbraco/cache/LegacyClasses.cs | 2 +- .../umbraco/webservices/CacheRefresher.asmx.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/CacheRefreshersResolver.cs b/src/Umbraco.Core/CacheRefreshersResolver.cs index e01dfba01a..e8df98fc1b 100644 --- a/src/Umbraco.Core/CacheRefreshersResolver.cs +++ b/src/Umbraco.Core/CacheRefreshersResolver.cs @@ -27,7 +27,7 @@ namespace Umbraco.Core /// /// Gets the implementations. /// - public IEnumerable CacheResolvers + public IEnumerable CacheRefreshers { get { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/cache/LegacyClasses.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/cache/LegacyClasses.cs index a497c86884..ad00646305 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/cache/LegacyClasses.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/cache/LegacyClasses.cs @@ -52,7 +52,7 @@ namespace umbraco.presentation.cache /// public ICacheRefresher[] GetAll() { - return CacheRefreshersResolver.Current.CacheResolvers.ToArray(); + return CacheRefreshersResolver.Current.CacheRefreshers.ToArray(); } #endregion diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs index 7f79097615..4c2f721bd2 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs @@ -115,7 +115,7 @@ namespace umbraco.presentation.webservices { var xd = new XmlDocument(); xd.LoadXml(""); - foreach (var cr in CacheRefreshersResolver.Current.CacheResolvers) + foreach (var cr in CacheRefreshersResolver.Current.CacheRefreshers) { var n = xmlHelper.addTextNode(xd, "cacheRefresher", cr.Name); n.Attributes.Append(xmlHelper.addAttribute(xd, "uniqueIdentifier", cr.UniqueIdentifier.ToString())); From 792ab8d088a873aaaf04c9421db92fa93fa48c16 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 3 Mar 2014 13:29:33 +0100 Subject: [PATCH 11/11] Introduce support for content fragments --- .../PublishedContentTestElements.cs | 6 + .../PublishedCache/IPublishedContentCache.cs | 48 +++++ .../PublishedContentCache.cs | 10 ++ .../XmlPublishedCache/PublishedFragment.cs | 169 ++++++++++++++++++ .../PublishedFragmentProperty.cs | 43 +++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 + 6 files changed, 278 insertions(+) create mode 100644 src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragment.cs create mode 100644 src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragmentProperty.cs diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs index 3f3fa0a926..be357c4c03 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs @@ -99,6 +99,12 @@ namespace Umbraco.Tests.PublishedContent { return _content.Count > 0; } + + public IPublishedContent CreateFragment(string contentTypeAlias, IDictionary dataValues, + bool isPreviewing, bool managed) + { + throw new NotImplementedException(); + } } class SolidPublishedContent : IPublishedContent diff --git a/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs index a80aaa41b3..f020dc6097 100644 --- a/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs @@ -33,5 +33,53 @@ namespace Umbraco.Web.PublishedCache /// The route. /// The value of overrides the context. string GetRouteById(UmbracoContext umbracoContext, bool preview, int contentId); + + /// + /// Creates a content fragment. + /// + /// The content type alias. + /// The content property raw values. + /// A value indicating whether the fragment is previewing. + /// A value indicating whether the fragment is managed by the cache. + /// The newly created content fragment. + // + // notes + // + // in XmlPublishedCache, IPublishedContent instances are not meant to survive longer + // that a request or else we cannot guarantee that the converted property values will + // be properly managed - because XmlPublishedProperty just stores the result of the + // conversion locally. + // + // in DrippingPublishedCache, IPublishedContent instances are meant to survive for as + // long as the content itself has not been modified, and the property respects the + // converter's indication ie whether the converted value should be cached at + // .Content - cache until the content changes + // .ContentCache - cache until any content changes + // .Request - cache for the current request + // + // a fragment can be either "detached" or "managed". + // detached: created from code, managed by code, converted property values are + // cached within the fragment itself for as long as the fragment lives + // managed: created from a property converter as part of a content, managed by + // the cache, converted property values can be cached... + // + // XmlPublishedCache: same as content properties, store the result of the + // conversion locally, neither content nor fragments should survive longer + // than a request + // DrippingPublishedCache: depends + // .Content: cache within the fragment + // .ContentCache, .Request: cache within the cache + // + // in the latter case, use a fragment-owned guid as the cache key. because we + // don't really have any other choice. this opens potential memory leaks: if the + // fragment is re-created on each request and has a property that caches its + // converted value at .ContentCache level then we'll flood that cache with data + // that's never removed (as long as no content is edited). + // + // so a requirement should be that any converter that creates fragment, should + // be marked .Content -- and nothing else + // + IPublishedContent CreateFragment(string contentTypeAlias, IDictionary dataValues, + bool isPreviewing, bool managed); } } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index 82a5b6f85a..2fe9c13c21 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -460,5 +460,15 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } #endregion + + #region Fragments + + public IPublishedContent CreateFragment(string contentTypeAlias, IDictionary dataValues, + bool isPreviewing, bool managed) + { + return new PublishedFragment(contentTypeAlias, dataValues, isPreviewing, managed); + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragment.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragment.cs new file mode 100644 index 0000000000..860f9f043f --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragment.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Models; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + class PublishedFragment : PublishedContentBase + { + private readonly PublishedContentType _contentType; + private readonly IPublishedProperty[] _properties; + + public PublishedFragment(string contentTypeAlias, IDictionary dataValues, + bool isPreviewing, bool managed) + { + IsPreviewing = isPreviewing; + _contentType = PublishedContentType.Get(PublishedItemType.Content, contentTypeAlias); + + // we don't care about managed because in both cases, XmlPublishedCache stores + // converted property values in the IPublishedContent, which is not meant to + // survive the request + + var dataValues2 = new Dictionary(); + foreach (var kvp in dataValues) + dataValues2[kvp.Key.ToLowerInvariant()] = kvp.Value; + + _properties = _contentType.PropertyTypes + .Select(x => + { + object dataValue; + return dataValues2.TryGetValue(x.PropertyTypeAlias.ToLowerInvariant(), out dataValue) + ? new PublishedFragmentProperty(x, this, dataValue) + : new PublishedFragmentProperty(x, this); + }) + .Cast() + .ToArray(); + } + + #region IPublishedContent + + public override int Id + { + get { throw new NotImplementedException(); } + } + + public override int DocumentTypeId + { + get { return _contentType.Id; } + } + + public override string DocumentTypeAlias + { + get { return _contentType.Alias; } + } + + public override PublishedItemType ItemType + { + get { return PublishedItemType.Content; } + } + + public override string Name + { + get { throw new NotImplementedException(); } + } + + public override int Level + { + get { throw new NotImplementedException(); } + } + + public override string Path + { + get { throw new NotImplementedException(); } + } + + public override int SortOrder + { + // note - could a published fragment have a sort order? + get { throw new NotImplementedException(); } + } + + public override Guid Version + { + get { throw new NotImplementedException(); } + } + + public override int TemplateId + { + get { throw new NotImplementedException(); } + } + + public override string UrlName + { + get { return string.Empty; } + } + + public override DateTime CreateDate + { + get { throw new NotImplementedException(); } + } + + public override DateTime UpdateDate + { + get { throw new NotImplementedException(); } + } + + public override int CreatorId + { + get { throw new NotImplementedException(); } + } + + public override string CreatorName + { + get { throw new NotImplementedException(); } + } + + public override int WriterId + { + get { throw new NotImplementedException(); } + } + + public override string WriterName + { + get { throw new NotImplementedException(); } + } + + public override bool IsDraft + { + get { throw new NotImplementedException(); } + } + + public override IPublishedContent Parent + { + get { throw new NotImplementedException(); } + } + + public override IEnumerable Children + { + get { throw new NotImplementedException(); } + } + + public override ICollection Properties + { + get { return _properties; } + } + + public override IPublishedProperty GetProperty(string alias) + { + return _properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); + } + + public override PublishedContentType ContentType + { + get { return _contentType; } + } + + #endregion + + #region Internal + + // used by PublishedFragmentProperty + internal bool IsPreviewing { get; private set; } + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragmentProperty.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragmentProperty.cs new file mode 100644 index 0000000000..7ae10aebdd --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedFragmentProperty.cs @@ -0,0 +1,43 @@ +using System; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + class PublishedFragmentProperty : PublishedPropertyBase + { + private readonly object _dataValue; + private readonly PublishedFragment _content; + + private readonly Lazy _sourceValue; + private readonly Lazy _objectValue; + private readonly Lazy _xpathValue; + + public PublishedFragmentProperty(PublishedPropertyType propertyType, PublishedFragment content) + : this(propertyType, content, null) + { } + + public PublishedFragmentProperty(PublishedPropertyType propertyType, PublishedFragment content, object dataValue) + : base(propertyType) + { + _dataValue = dataValue; + _content = content; + + _sourceValue = new Lazy(() => PropertyType.ConvertDataToSource(_dataValue, _content.IsPreviewing)); + _objectValue = new Lazy(() => PropertyType.ConvertSourceToObject(_sourceValue.Value, _content.IsPreviewing)); + _xpathValue = new Lazy(() => PropertyType.ConvertSourceToXPath(_sourceValue.Value, _content.IsPreviewing)); + } + + public override bool HasValue + { + get { return _dataValue != null && ((_dataValue is string) == false || string.IsNullOrWhiteSpace((string)_dataValue) == false); } + } + + public override object DataValue + { + get { return _dataValue; } + } + + public override object Value { get { return _objectValue.Value; } } + public override object XPathValue { get { return _xpathValue.Value; } } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index f1a6f79d57..130a6d0fbf 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -298,6 +298,8 @@ + +