From 9f365727e0e0d1c51e8f0c90cef8452986b87505 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 20 Jan 2016 14:10:31 +0100 Subject: [PATCH 01/29] Bumps version --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index e847b7606c..4f9245d744 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,2 +1,2 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) -7.3.5 \ No newline at end of file +7.3.6 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index f2c18b921b..e8cdf14c5b 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.3.5")] -[assembly: AssemblyInformationalVersion("7.3.5")] \ No newline at end of file +[assembly: AssemblyFileVersion("7.3.6")] +[assembly: AssemblyInformationalVersion("7.3.6")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 8b9fbf05fd..4582e57e82 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.3.5"); + private static readonly Version Version = new Version("7.3.6"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 46ca224bb2..88b157f4f5 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2414,9 +2414,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" True True - 7350 + 7360 / - http://localhost:7350 + http://localhost:7360 False False From f78a42598552f7ee0c7dc6d545acf53bb7dacdb0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 16:37:47 +0100 Subject: [PATCH 02/29] U4-7816 The IsolatedCache instance in the RepositoryFactory differs from the ApplicationContext singleton instance --- .../Cache/IsolatedRuntimeCache.cs | 8 +- src/Umbraco.Core/CacheHelper.cs | 74 ++++++++----------- .../Persistence/RepositoryFactory.cs | 30 ++++---- src/Umbraco.Web/WebBootManager.cs | 15 +++- 4 files changed, 61 insertions(+), 66 deletions(-) diff --git a/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs index 103f90345d..da20f7eb73 100644 --- a/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs +++ b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Cache /// public class IsolatedRuntimeCache { - private readonly Func _cacheFactory; + internal Func CacheFactory { get; set; } /// /// Constructor that allows specifying a factory for the type of runtime isolated cache to create @@ -20,7 +20,7 @@ namespace Umbraco.Core.Cache /// public IsolatedRuntimeCache(Func cacheFactory) { - _cacheFactory = cacheFactory; + CacheFactory = cacheFactory; } private readonly ConcurrentDictionary _isolatedCache = new ConcurrentDictionary(); @@ -32,7 +32,7 @@ namespace Umbraco.Core.Cache /// public IRuntimeCacheProvider GetOrCreateCache() { - return _isolatedCache.GetOrAdd(typeof(T), type => _cacheFactory(type)); + return _isolatedCache.GetOrAdd(typeof(T), type => CacheFactory(type)); } /// @@ -41,7 +41,7 @@ namespace Umbraco.Core.Cache /// public IRuntimeCacheProvider GetOrCreateCache(Type type) { - return _isolatedCache.GetOrAdd(type, t => _cacheFactory(t)); + return _isolatedCache.GetOrAdd(type, t => CacheFactory(t)); } /// diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs index 303cf234fd..4b009e5f86 100644 --- a/src/Umbraco.Core/CacheHelper.cs +++ b/src/Umbraco.Core/CacheHelper.cs @@ -18,12 +18,8 @@ namespace Umbraco.Core /// public class CacheHelper { - private readonly IsolatedRuntimeCache _isolatedCacheManager; - private readonly ICacheProvider _requestCache; private static readonly ICacheProvider NullRequestCache = new NullCacheProvider(); - private readonly ICacheProvider _staticCache; private static readonly ICacheProvider NullStaticCache = new NullCacheProvider(); - private readonly IRuntimeCacheProvider _runtimeCache; private static readonly IRuntimeCacheProvider NullRuntimeCache = new NullCacheProvider(); /// @@ -90,45 +86,33 @@ namespace Umbraco.Core if (staticCacheProvider == null) throw new ArgumentNullException("staticCacheProvider"); if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider"); if (isolatedCacheManager == null) throw new ArgumentNullException("isolatedCacheManager"); - _runtimeCache = httpCacheProvider; - _staticCache = staticCacheProvider; - _requestCache = requestCacheProvider; - _isolatedCacheManager = isolatedCacheManager; + RuntimeCache = httpCacheProvider; + StaticCache = staticCacheProvider; + RequestCache = requestCacheProvider; + IsolatedRuntimeCache = isolatedCacheManager; } /// /// Returns the current Request cache /// - public ICacheProvider RequestCache - { - get { return _requestCache; } - } + public ICacheProvider RequestCache { get; internal set; } /// /// Returns the current Runtime cache /// - public ICacheProvider StaticCache - { - get { return _staticCache; } - } + public ICacheProvider StaticCache { get; internal set; } /// /// Returns the current Runtime cache /// - public IRuntimeCacheProvider RuntimeCache - { - get { return _runtimeCache; } - } + public IRuntimeCacheProvider RuntimeCache { get; internal set; } /// /// Returns the current Isolated Runtime cache manager /// - public IsolatedRuntimeCache IsolatedRuntimeCache - { - get { return _isolatedCacheManager; } - } - - #region Legacy Runtime/Http Cache accessors + public IsolatedRuntimeCache IsolatedRuntimeCache { get; internal set; } + + #region Legacy Runtime/Http Cache accessors /// /// Clears the item in umbraco's runtime cache @@ -137,8 +121,8 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearAllCache() { - _runtimeCache.ClearAllCache(); - _isolatedCacheManager.ClearAllCaches(); + RuntimeCache.ClearAllCache(); + IsolatedRuntimeCache.ClearAllCaches(); } /// @@ -149,7 +133,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheItem(string key) { - _runtimeCache.ClearCacheItem(key); + RuntimeCache.ClearCacheItem(key); } @@ -161,7 +145,7 @@ namespace Umbraco.Core [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public void ClearCacheObjectTypes(string typeName) { - _runtimeCache.ClearCacheObjectTypes(typeName); + RuntimeCache.ClearCacheObjectTypes(typeName); } /// @@ -171,7 +155,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheObjectTypes() { - _runtimeCache.ClearCacheObjectTypes(); + RuntimeCache.ClearCacheObjectTypes(); } /// @@ -182,7 +166,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheByKeySearch(string keyStartsWith) { - _runtimeCache.ClearCacheByKeySearch(keyStartsWith); + RuntimeCache.ClearCacheByKeySearch(keyStartsWith); } /// @@ -193,14 +177,14 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheByKeyExpression(string regexString) { - _runtimeCache.ClearCacheByKeyExpression(regexString); + RuntimeCache.ClearCacheByKeyExpression(regexString); } [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { - return _runtimeCache.GetCacheItemsByKeySearch(keyStartsWith); + return RuntimeCache.GetCacheItemsByKeySearch(keyStartsWith); } /// @@ -213,7 +197,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public TT GetCacheItem(string cacheKey) { - return _runtimeCache.GetCacheItem(cacheKey); + return RuntimeCache.GetCacheItem(cacheKey); } /// @@ -227,7 +211,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public TT GetCacheItem(string cacheKey, Func getCacheItem) { - return _runtimeCache.GetCacheItem(cacheKey, getCacheItem); + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem); } @@ -244,7 +228,7 @@ namespace Umbraco.Core public TT GetCacheItem(string cacheKey, TimeSpan timeout, Func getCacheItem) { - return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout); + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout); } @@ -263,7 +247,7 @@ namespace Umbraco.Core CacheItemRemovedCallback refreshAction, TimeSpan timeout, Func getCacheItem) { - return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); } @@ -283,7 +267,7 @@ namespace Umbraco.Core CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan timeout, Func getCacheItem) { - return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); } @@ -306,7 +290,7 @@ namespace Umbraco.Core TimeSpan timeout, Func getCacheItem) { - var cache = _runtimeCache as HttpRuntimeCacheProvider; + var cache = RuntimeCache as HttpRuntimeCacheProvider; if (cache != null) { var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); @@ -330,7 +314,7 @@ namespace Umbraco.Core CacheDependency cacheDependency, Func getCacheItem) { - var cache = _runtimeCache as HttpRuntimeCacheProvider; + var cache = RuntimeCache as HttpRuntimeCacheProvider; if (cache != null) { var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), null, false, priority, null, cacheDependency); @@ -352,7 +336,7 @@ namespace Umbraco.Core CacheItemPriority priority, Func getCacheItem) { - _runtimeCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); + RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); } @@ -371,7 +355,7 @@ namespace Umbraco.Core TimeSpan timeout, Func getCacheItem) { - _runtimeCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); + RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); } /// @@ -390,7 +374,7 @@ namespace Umbraco.Core TimeSpan timeout, Func getCacheItem) { - var cache = _runtimeCache as HttpRuntimeCacheProvider; + var cache = RuntimeCache as HttpRuntimeCacheProvider; if (cache != null) { cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, null, cacheDependency); @@ -416,7 +400,7 @@ namespace Umbraco.Core TimeSpan? timeout, Func getCacheItem) { - var cache = _runtimeCache as HttpRuntimeCacheProvider; + var cache = RuntimeCache as HttpRuntimeCacheProvider; if (cache != null) { cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 9bb1d8d11c..1c1e551d34 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -31,29 +31,27 @@ namespace Umbraco.Core.Persistence //if (sqlSyntax == null) throw new ArgumentNullException("sqlSyntax"); if (settings == null) throw new ArgumentNullException("settings"); - _cacheHelper = cacheHelper; + _cacheHelper = cacheHelper; //IMPORTANT: We will force the DeepCloneRuntimeCacheProvider to be used here which is a wrapper for the underlying // runtime cache to ensure that anything that can be deep cloned in/out is done so, this also ensures that our tracks // changes entities are reset. if ((_cacheHelper.RuntimeCache is DeepCloneRuntimeCacheProvider) == false) { - var originalHelper = cacheHelper; - - _cacheHelper = new CacheHelper( - new DeepCloneRuntimeCacheProvider(originalHelper.RuntimeCache), - originalHelper.StaticCache, - originalHelper.RequestCache, - new IsolatedRuntimeCache(type => - { - var cache = originalHelper.IsolatedRuntimeCache.GetOrCreateCache(type); - return (cache is DeepCloneRuntimeCacheProvider) == false - //wrap the original if it's not DeepCloneRuntimeCacheProvider - ? new DeepCloneRuntimeCacheProvider(cache) - : cache; - })); + var origRuntimeCache = cacheHelper.RuntimeCache; + _cacheHelper.RuntimeCache = new DeepCloneRuntimeCacheProvider(origRuntimeCache); } - + //If the factory for isolated cache doesn't return DeepCloneRuntimeCacheProvider, then ensure it does + if (_cacheHelper.IsolatedRuntimeCache.CacheFactory.Method.ReturnType != typeof (DeepCloneRuntimeCacheProvider)) + { + var origFactory = cacheHelper.IsolatedRuntimeCache.CacheFactory; + _cacheHelper.IsolatedRuntimeCache.CacheFactory = type => + { + var cache = origFactory(type); + return new DeepCloneRuntimeCacheProvider(cache); + }; + } + _noCache = CacheHelper.CreateDisabledCacheHelper(); _logger = logger; _sqlSyntax = sqlSyntax; diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index f84d204bb1..dcf1e98be4 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -39,6 +39,7 @@ using Umbraco.Web.Scheduling; using Umbraco.Web.UI.JavaScript; using Umbraco.Web.WebApi; using umbraco.BusinessLogic; +using Umbraco.Core.Cache; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Publishing; @@ -240,7 +241,19 @@ namespace Umbraco.Web protected override CacheHelper CreateApplicationCache() { //create a web-based cache helper - return new CacheHelper(); + var cacheHelper = new CacheHelper( + //we need to have the dep clone runtime cache provider to ensure + //all entities are cached properly (cloned in and cloned out) + new DeepCloneRuntimeCacheProvider(new HttpRuntimeCacheProvider(HttpRuntime.Cache)), + new StaticCacheProvider(), + //we have no request based cache when not running in web-based context + new NullCacheProvider(), + new IsolatedRuntimeCache(type => + //we need to have the dep clone runtime cache provider to ensure + //all entities are cached properly (cloned in and cloned out) + new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); + + return cacheHelper; } /// From 3ecb9d0f862347d515dd5e70890e14d77c34b952 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 17:05:27 +0100 Subject: [PATCH 03/29] U4-7807 Domain, language, public access cache (GetAll) caches not working when there are no items --- .../Cache/DefaultRepositoryCachePolicy.cs | 13 +++++- .../Cache/FullDataSetRepositoryCachePolicy.cs | 29 +++++++++++- ...FullDataSetRepositoryCachePolicyFactory.cs | 2 +- .../Cache/FullDataSetCachePolicyTests.cs | 45 ++++++++++++++++++- 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs index 58b3a3956e..66d9b2ac25 100644 --- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs @@ -158,8 +158,7 @@ namespace Umbraco.Core.Cache else if (_options.GetAllCacheAllowZeroCount) { //if the repository allows caching a zero count, then check the zero count cache - var zeroCount = Cache.GetCacheItem(GetCacheTypeKey()); - if (zeroCount != null && zeroCount.Any() == false) + if (HasZeroCountCache()) { //there is a zero count cache so return an empty list return new TEntity[] {}; @@ -179,6 +178,16 @@ namespace Umbraco.Core.Cache return entityCollection; } + /// + /// Looks up the zero count cache, must return null if it doesn't exist + /// + /// + protected virtual bool HasZeroCountCache() + { + var zeroCount = Cache.GetCacheItem(GetCacheTypeKey()); + return (zeroCount != null && zeroCount.Any() == false); + } + /// /// Performs the lookup for all entities of this type from the cache /// diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index c4c86b2ec7..40b100ef67 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.Linq; using Umbraco.Core.Collections; using Umbraco.Core.Models.EntityBase; @@ -12,10 +14,18 @@ namespace Umbraco.Core.Cache internal class FullDataSetRepositoryCachePolicy : DefaultRepositoryCachePolicy where TEntity : class, IAggregateRoot { - public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache) : base(cache, new RepositoryCachePolicyOptions()) + public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache) : base(cache, + new RepositoryCachePolicyOptions + { + //Definitely allow zero'd cache entires since this is a full set, in many cases there will be none, + // and we must cache this! + GetAllCacheAllowZeroCount = true + }) { } + private bool? _hasZeroCountCache; + /// /// For this type of caching policy, we don't cache individual items /// @@ -44,6 +54,19 @@ namespace Umbraco.Core.Cache }); } + /// + /// Looks up the zero count cache, must return null if it doesn't exist + /// + /// + protected override bool HasZeroCountCache() + { + if (_hasZeroCountCache.HasValue) + return _hasZeroCountCache.Value; + + _hasZeroCountCache = Cache.GetCacheItem>(GetCacheTypeKey()) != null; + return _hasZeroCountCache.Value; + } + /// /// This policy will cache the full data set as a single collection /// @@ -51,6 +74,10 @@ namespace Umbraco.Core.Cache protected override TEntity[] GetAllFromCache() { var found = Cache.GetCacheItem>(GetCacheTypeKey()); + + //This method will get called before checking for zero count cache, so we'll just set the flag here + _hasZeroCountCache = found != null; + return found == null ? new TEntity[] { } : found.WhereNotNull().ToArray(); } } diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs index 470db33b6a..75bdae7e83 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Cache public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache) { - _runtimeCache = runtimeCache; + _runtimeCache = runtimeCache; } public virtual IRepositoryCachePolicy CreatePolicy() diff --git a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs index 7fd894e2a9..3d3884c686 100644 --- a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Web.Caching; using Moq; using NUnit.Framework; @@ -13,6 +14,48 @@ namespace Umbraco.Tests.Cache [TestFixture] public class FullDataSetCachePolicyTests { + [Test] + public void Get_All_Caches_Empty_List() + { + var cached = new List(); + + IList list = null; + + var cache = new Mock(); + cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string cacheKey, Func o, TimeSpan? t, bool b, CacheItemPriority cip, CacheItemRemovedCallback circ, string[] s) => + { + cached.Add(cacheKey); + + list = o() as IList; + }); + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(() => + { + //return null if this is the first pass + return cached.Any() ? new DeepCloneableList() : null; + }); + + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] {}, o => new AuditItem[] {}); + } + + Assert.AreEqual(1, cached.Count); + Assert.IsNotNull(list); + + //Do it again, ensure that its coming from the cache! + defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] { }, o => new AuditItem[] { }); + } + + Assert.AreEqual(1, cached.Count); + Assert.IsNotNull(list); + } + [Test] public void Get_All_Caches_As_Single_List() { @@ -28,7 +71,7 @@ namespace Umbraco.Tests.Cache list = o() as IList; }); - cache.Setup(x => x.GetCacheItemsByKeySearch(It.IsAny())).Returns(new AuditItem[] { }); + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem[] { }); var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); using (defaultPolicy) From fe774f557c279b59404b11a83975c92dc58de76d Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 17:06:14 +0100 Subject: [PATCH 04/29] Fixes ms value: U4-7810 DatabaseServerRegistrar should not execute as soon as a request is made, this slows down app start --- .../Sync/DatabaseServerRegistrarOptions.cs | 12 +- .../ServerRegistrationEventHandler.cs | 128 +++++++++++++----- 2 files changed, 106 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs b/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs index 33ab1c8f57..5b5f6fc457 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; namespace Umbraco.Core.Sync { @@ -14,13 +15,18 @@ namespace Umbraco.Core.Sync { StaleServerTimeout = TimeSpan.FromMinutes(2); // 2 minutes ThrottleSeconds = 30; // 30 seconds + RecurringSeconds = 60; // do it every minute } - /// - /// The number of seconds to wait between each updates to the database. - /// + [Obsolete("This is no longer used")] + [EditorBrowsable(EditorBrowsableState.Never)] public int ThrottleSeconds { get; set; } + /// + /// The amount of seconds to wait between calls to the database on the background thread + /// + public int RecurringSeconds { get; set; } + /// /// The time span to wait before considering a server stale, after it has last been accessed. /// diff --git a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs index 2a61d4177d..6c15814b92 100644 --- a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs +++ b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; using System.Web; using Newtonsoft.Json; using Umbraco.Core; @@ -6,6 +8,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Web.Routing; +using Umbraco.Web.Scheduling; namespace Umbraco.Web.Strategies { @@ -22,74 +25,137 @@ namespace Umbraco.Web.Strategies /// public sealed class ServerRegistrationEventHandler : ApplicationEventHandler { - private readonly object _locko = new object(); private DatabaseServerRegistrar _registrar; - private DateTime _lastUpdated = DateTime.MinValue; + private BackgroundTaskRunner _backgroundTaskRunner; + private bool _started = false; // bind to events protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { _registrar = ServerRegistrarResolver.Current.Registrar as DatabaseServerRegistrar; + _backgroundTaskRunner = new BackgroundTaskRunner( + new BackgroundTaskRunnerOptions { AutoStart = true }, + applicationContext.ProfilingLogger.Logger); + // only for the DatabaseServerRegistrar if (_registrar == null) return; + //We will start the whole process when a successful request is made UmbracoModule.RouteAttempt += UmbracoModuleRouteAttempt; } - // handles route attempts. + /// + /// Handle when a request is made + /// + /// + /// + /// + /// We require this because: + /// - ApplicationContext.UmbracoApplicationUrl is initialized by UmbracoModule in BeginRequest + /// - RegisterServer is called on UmbracoModule.RouteAttempt which is triggered in ProcessRequest + /// we are safe, UmbracoApplicationUrl has been initialized + /// private void UmbracoModuleRouteAttempt(object sender, RoutableAttemptEventArgs e) { - if (e.HttpContext.Request == null || e.HttpContext.Request.Url == null) return; - switch (e.Outcome) { case EnsureRoutableOutcome.IsRoutable: // front-end request RegisterServer(e); + //remove handler, we're done + UmbracoModule.RouteAttempt -= UmbracoModuleRouteAttempt; break; case EnsureRoutableOutcome.NotDocumentRequest: // anything else (back-end request, service...) //so it's not a document request, we'll check if it's a back office request if (e.HttpContext.Request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath)) + { RegisterServer(e); - break; - /* - case EnsureRoutableOutcome.NotReady: - case EnsureRoutableOutcome.NotConfigured: - case EnsureRoutableOutcome.NoContent: - default: - // otherwise, do nothing - break; - */ + //remove handler, we're done + UmbracoModule.RouteAttempt -= UmbracoModuleRouteAttempt; + } + break; } } - - // register current server (throttled). + private void RegisterServer(UmbracoRequestEventArgs e) { - lock (_locko) // ensure we trigger only once - { - var secondsSinceLastUpdate = DateTime.Now.Subtract(_lastUpdated).TotalSeconds; - if (secondsSinceLastUpdate < _registrar.Options.ThrottleSeconds) return; - _lastUpdated = DateTime.Now; - } + //only process once + if (_started) return; + _started = true; + + var serverAddress = e.UmbracoContext.Application.UmbracoApplicationUrl; var svc = e.UmbracoContext.Application.Services.ServerRegistrationService; - // because - // - ApplicationContext.UmbracoApplicationUrl is initialized by UmbracoModule in BeginRequest - // - RegisterServer is called on UmbracoModule.RouteAttempt which is triggered in ProcessRequest - // we are safe, UmbracoApplicationUrl has been initialized - var serverAddress = e.UmbracoContext.Application.UmbracoApplicationUrl; + //Perform the rest async, we don't want to block the startup sequence + // this will just reoccur on a background thread + _backgroundTaskRunner.Add(new TouchServerTask(_backgroundTaskRunner, + 15000, //delay before first execution + _registrar.Options.RecurringSeconds * 1000, //amount of ms between executions + svc, _registrar, serverAddress)); + } - try + private class TouchServerTask : RecurringTaskBase + { + private readonly IServerRegistrationService _svc; + private readonly DatabaseServerRegistrar _registrar; + private readonly string _serverAddress; + + /// + /// Initializes a new instance of the class. + /// + /// The task runner. + /// The delay. + /// The period. + /// + /// + /// + /// The task will repeat itself periodically. Use this constructor to create a new task. + public TouchServerTask(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + IServerRegistrationService svc, DatabaseServerRegistrar registrar, string serverAddress) + : base(runner, delayMilliseconds, periodMilliseconds) { - svc.TouchServer(serverAddress, svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); + if (svc == null) throw new ArgumentNullException("svc"); + _svc = svc; + _registrar = registrar; + _serverAddress = serverAddress; } - catch (Exception ex) + + public override bool IsAsync { - LogHelper.Error("Failed to update server record in database.", ex); + get { return false; } + } + + public override bool RunsOnShutdown + { + get { return false; } + } + + /// + /// Runs the background task. + /// + /// A value indicating whether to repeat the task. + public override bool PerformRun() + { + try + { + _svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); + + return true; // repeat + } + catch (Exception ex) + { + LogHelper.Error("Failed to update server record in database.", ex); + + return false; // probably stop if we have an error + } + } + + public override Task PerformRunAsync(CancellationToken token) + { + throw new NotImplementedException(); } } } From 4dc4c36581379b3ff5f2d18e6609e32fecdd2183 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 17:08:21 +0100 Subject: [PATCH 05/29] U4-7811 Content type repositories should use FullDataSetRepositoryCachePolicyFactory for performance Conflicts: src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs --- .../Repositories/ContentTypeBaseRepository.cs | 93 +++++-------------- .../Repositories/ContentTypeRepository.cs | 47 +++++++--- .../IContentTypeCompositionRepository.cs | 11 +++ .../Interfaces/IContentTypeRepository.cs | 2 +- .../Interfaces/IMediaTypeRepository.cs | 5 +- .../Interfaces/IMemberTypeRepository.cs | 2 +- .../Repositories/MediaTypeRepository.cs | 40 ++++++-- .../Repositories/MemberTypeRepository.cs | 55 ++++++----- .../Services/ContentTypeService.cs | 10 +- .../Services/MemberTypeService.cs | 5 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Repositories/ContentTypeRepositoryTest.cs | 2 +- .../Repositories/MediaTypeRepositoryTest.cs | 2 +- .../Repositories/MemberTypeRepositoryTest.cs | 2 +- .../Cache/ContentTypeCacheRefresher.cs | 6 +- 15 files changed, 142 insertions(+), 141 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index fbc3069e5c..2f42919ce3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -30,12 +30,9 @@ namespace Umbraco.Core.Persistence.Repositories protected ContentTypeBaseRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) - { - _guidRepo = new GuidReadOnlyContentTypeBaseRepository(this, work, cache, logger, sqlSyntax); + { } - - private readonly GuidReadOnlyContentTypeBaseRepository _guidRepo; - + /// /// Returns the content type ids that match the query /// @@ -1197,71 +1194,20 @@ AND umbracoNode.id <> @id", } - /// - /// Inner repository to support the GUID lookups and keep the caching consistent - /// - internal class GuidReadOnlyContentTypeBaseRepository : PetaPocoRepositoryBase - { - private readonly ContentTypeBaseRepository _parentRepo; - - public GuidReadOnlyContentTypeBaseRepository( - ContentTypeBaseRepository parentRepo, - IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { - _parentRepo = parentRepo; - } - - protected override TEntity PerformGet(Guid id) - { - return _parentRepo.PerformGet(id); - } - - protected override IEnumerable PerformGetAll(params Guid[] ids) - { - return _parentRepo.PerformGetAll(ids); - } - - protected override Sql GetBaseQuery(bool isCount) - { - return _parentRepo.GetBaseQuery(isCount); - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.uniqueID = @Id"; - } - - #region No implementation required - protected override IEnumerable PerformGetByQuery(IQuery query) - { - throw new NotImplementedException(); - } - - protected override IEnumerable GetDeleteClauses() - { - throw new NotImplementedException(); - } - - protected override Guid NodeObjectTypeId - { - get { throw new NotImplementedException(); } - } - - protected override void PersistNewItem(TEntity entity) - { - throw new NotImplementedException(); - } - - protected override void PersistUpdatedItem(TEntity entity) - { - throw new NotImplementedException(); - } - #endregion - } - protected abstract TEntity PerformGet(Guid id); + protected abstract TEntity PerformGet(string alias); protected abstract IEnumerable PerformGetAll(params Guid[] ids); + protected abstract bool PerformExists(Guid id); + + /// + /// Gets an Entity by alias + /// + /// + /// + public TEntity Get(string alias) + { + return PerformGet(alias); + } /// /// Gets an Entity by Id @@ -1270,7 +1216,7 @@ AND umbracoNode.id <> @id", /// public TEntity Get(Guid id) { - return _guidRepo.Get(id); + return PerformGet(id); } /// @@ -1278,9 +1224,12 @@ AND umbracoNode.id <> @id", /// /// /// - public IEnumerable GetAll(params Guid[] ids) + /// + /// Ensure explicit implementation, we don't want to have any accidental calls to this since it is essentially the same signature as the main GetAll when there are no parameters + /// + IEnumerable IReadRepository.GetAll(params Guid[] ids) { - return _guidRepo.GetAll(ids); + return PerformGetAll(ids); } /// @@ -1290,7 +1239,7 @@ AND umbracoNode.id <> @id", /// public bool Exists(Guid id) { - return _guidRepo.Exists(id); + return PerformExists(id); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 95601011fa..9b4f2cc44d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -27,15 +28,20 @@ namespace Umbraco.Core.Persistence.Repositories _templateRepository = templateRepository; } - #region Overrides of RepositoryBase - + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory + { + get + { + //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + } + } + protected override IContentType PerformGet(int id) { - var contentTypes = ContentTypeQueryMapper.GetContentTypes( - new[] {id}, Database, SqlSyntax, this, _templateRepository); - - var contentType = contentTypes.SingleOrDefault(); - return contentType; + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) @@ -244,25 +250,36 @@ namespace Umbraco.Core.Persistence.Repositories protected override IContentType PerformGet(Guid id) { - var contentTypes = ContentTypeQueryMapper.GetContentTypes( - new[] { id }, Database, SqlSyntax, this, _templateRepository); + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Key == id); + } - var contentType = contentTypes.SingleOrDefault(); - return contentType; + protected override IContentType PerformGet(string alias) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); } protected override IEnumerable PerformGetAll(params Guid[] ids) { + //use the underlying GetAll which will force cache all content types + if (ids.Any()) { - return ContentTypeQueryMapper.GetContentTypes(ids, Database, SqlSyntax, this, _templateRepository); + return GetAll().Where(x => ids.Contains(x.Key)); } else { - var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); - var allIds = Database.Fetch(sql).ToArray(); - return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); + return GetAll(); + //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); + //var allIds = Database.Fetch(sql).ToArray(); + //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); } } + + protected override bool PerformExists(Guid id) + { + return GetAll().FirstOrDefault(x => x.Key == id) != null; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs new file mode 100644 index 0000000000..649726b100 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs @@ -0,0 +1,11 @@ +using System; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IContentTypeCompositionRepository : IRepositoryQueryable, IReadRepository + where TEntity : IContentTypeComposition + { + TEntity Get(string alias); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs index ca7e0e3c32..8aa3217ce1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IContentTypeRepository : IRepositoryQueryable, IReadRepository + public interface IContentTypeRepository : IContentTypeCompositionRepository { /// /// Gets all entities of the specified query diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs index d28c59ac5b..4a82de7610 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs @@ -1,11 +1,10 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IMediaTypeRepository : IRepositoryQueryable, IReadRepository + public interface IMediaTypeRepository : IContentTypeCompositionRepository { /// /// Gets all entities of the specified query diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs index ae7739b28b..fc877d5227 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - public interface IMemberTypeRepository : IRepositoryQueryable, IReadRepository + public interface IMemberTypeRepository : IContentTypeCompositionRepository { } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index bf963492d2..613ca623c7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -24,7 +25,15 @@ namespace Umbraco.Core.Persistence.Repositories { } - #region Overrides of RepositoryBase + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory + { + get + { + //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + } + } protected override IMediaType PerformGet(int id) { @@ -168,25 +177,36 @@ namespace Umbraco.Core.Persistence.Repositories protected override IMediaType PerformGet(Guid id) { - var contentTypes = ContentTypeQueryMapper.GetMediaTypes( - new[] { id }, Database, SqlSyntax, this); - - var contentType = contentTypes.SingleOrDefault(); - return contentType; + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Key == id); } protected override IEnumerable PerformGetAll(params Guid[] ids) { + //use the underlying GetAll which will force cache all content types + if (ids.Any()) { - return ContentTypeQueryMapper.GetMediaTypes(ids, Database, SqlSyntax, this); + return GetAll().Where(x => ids.Contains(x.Key)); } else { - var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); - var allIds = Database.Fetch(sql).ToArray(); - return ContentTypeQueryMapper.GetMediaTypes(allIds, Database, SqlSyntax, this); + return GetAll(); + //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); + //var allIds = Database.Fetch(sql).ToArray(); + //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); } } + + protected override bool PerformExists(Guid id) + { + return GetAll().FirstOrDefault(x => x.Key == id) != null; + } + + protected override IMediaType PerformGet(string alias) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 39f2a93082..b864c05a83 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using log4net; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models; @@ -26,6 +27,16 @@ namespace Umbraco.Core.Persistence.Repositories { } + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory + { + get + { + //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + } + } + #region Overrides of RepositoryBase protected override IMemberType PerformGet(int id) @@ -262,38 +273,36 @@ namespace Umbraco.Core.Persistence.Repositories protected override IMemberType PerformGet(Guid id) { - var sql = GetBaseQuery(false); - sql.Where("umbracoNode.uniqueID = @Id", new { Id = id }); - sql.OrderByDescending(x => x.NodeId); - - var dtos = - Database.Fetch( - new PropertyTypePropertyGroupRelator().Map, sql); - - if (dtos == null || dtos.Any() == false) - return null; - - var factory = new MemberTypeReadOnlyFactory(); - var member = factory.BuildEntity(dtos.First()); - - return member; + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Key == id); } protected override IEnumerable PerformGetAll(params Guid[] ids) { - var sql = GetBaseQuery(false); + //use the underlying GetAll which will force cache all content types + if (ids.Any()) { - var statement = string.Join(" OR ", ids.Select(x => string.Format("umbracoNode.uniqueID='{0}'", x))); - sql.Where(statement); + return GetAll().Where(x => ids.Contains(x.Key)); } - sql.OrderByDescending(x => x.NodeId, SqlSyntax); + else + { + return GetAll(); + //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); + //var allIds = Database.Fetch(sql).ToArray(); + //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); + } + } - var dtos = - Database.Fetch( - new PropertyTypePropertyGroupRelator().Map, sql); + protected override bool PerformExists(Guid id) + { + return GetAll().FirstOrDefault(x => x.Key == id) != null; + } - return BuildFromDtos(dtos); + protected override IMemberType PerformGet(string alias) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); } /// diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index f144fa51e4..e6ead68810 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -158,10 +158,7 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Alias == alias); - var contentTypes = repository.GetByQuery(query); - - return contentTypes.FirstOrDefault(); + return repository.Get(alias); } } @@ -531,10 +528,7 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Alias == alias); - var contentTypes = repository.GetByQuery(query); - - return contentTypes.FirstOrDefault(); + return repository.Get(alias); } } diff --git a/src/Umbraco.Core/Services/MemberTypeService.cs b/src/Umbraco.Core/Services/MemberTypeService.cs index 73061be4d0..c6bc9dd24a 100644 --- a/src/Umbraco.Core/Services/MemberTypeService.cs +++ b/src/Umbraco.Core/Services/MemberTypeService.cs @@ -69,10 +69,7 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Alias == alias); - var contentTypes = repository.GetByQuery(query); - - return contentTypes.FirstOrDefault(); + return repository.Get(alias); } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2bf00fcc9d..2ef0501f8e 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -456,6 +456,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 1669b1af5e..f4471e6def 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -297,7 +297,7 @@ namespace Umbraco.Tests.Persistence.Repositories var allGuidIds = repository.GetAll().Select(x => x.Key).ToArray(); // Act - var contentTypes = repository.GetAll(allGuidIds); + var contentTypes = ((IReadRepository)repository).GetAll(allGuidIds); int count = DatabaseContext.Database.ExecuteScalar( "SELECT COUNT(*) FROM umbracoNode WHERE nodeObjectType = @NodeObjectType", diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index d0d66c5479..39b9475641 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -188,7 +188,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act - var mediaTypes = repository.GetAll(allGuidIds); + var mediaTypes = ((IReadRepository)repository).GetAll(allGuidIds); int count = DatabaseContext.Database.ExecuteScalar( diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index dca6b4f9f1..aa4027d8a8 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -115,7 +115,7 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(memberType2); unitOfWork.Commit(); - var result = repository.GetAll(memberType1.Key, memberType2.Key); + var result = ((IReadRepository)repository).GetAll(memberType1.Key, memberType2.Key); //there are 3 because of the Member type created for init data Assert.AreEqual(2, result.Count()); diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 9664681bb7..44a6efe9ff 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -128,7 +128,11 @@ namespace Umbraco.Web.Cache { ClearAllIsolatedCacheByEntityType(); ClearAllIsolatedCacheByEntityType(); - + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + //all property type cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.PropertyTypeCacheKey); //all content type property cache From 2c399860ca071a78040aecac44fc55cff9873c94 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 17:27:46 +0100 Subject: [PATCH 06/29] Fixes DisposableTimer so that the profiled step is disposed - and thus tracked/timed properly! --- src/Umbraco.Core/DisposableTimer.cs | 65 +++++++++++++++++++---------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/DisposableTimer.cs b/src/Umbraco.Core/DisposableTimer.cs index 816256360a..c7e8874449 100644 --- a/src/Umbraco.Core/DisposableTimer.cs +++ b/src/Umbraco.Core/DisposableTimer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Web; @@ -12,6 +13,12 @@ namespace Umbraco.Core /// public class DisposableTimer : DisposableObject { + private readonly ILogger _logger; + private readonly LogType? _logType; + private readonly IProfiler _profiler; + private readonly Type _loggerType; + private readonly string _endMessage; + private readonly IDisposable _profilerStep; private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private readonly Action _callback; @@ -25,25 +32,12 @@ namespace Umbraco.Core if (logger == null) throw new ArgumentNullException("logger"); if (loggerType == null) throw new ArgumentNullException("loggerType"); - _callback = x => - { - if (profiler != null) - { - profiler.DisposeIfDisposable(); - } - switch (logType) - { - case LogType.Debug: - logger.Debug(loggerType, () => endMessage + " (took " + x + "ms)"); - break; - case LogType.Info: - logger.Info(loggerType, () => endMessage + " (took " + x + "ms)"); - break; - default: - throw new ArgumentOutOfRangeException("logType"); - } - - }; + _logger = logger; + _logType = logType; + _profiler = profiler; + _loggerType = loggerType; + _endMessage = endMessage; + switch (logType) { case LogType.Debug: @@ -58,7 +52,7 @@ namespace Umbraco.Core if (profiler != null) { - profiler.Step(loggerType, startMessage); + _profilerStep = profiler.Step(loggerType, startMessage); } } @@ -223,7 +217,36 @@ namespace Umbraco.Core /// protected override void DisposeResources() { - _callback.Invoke(Stopwatch.ElapsedMilliseconds); + if (_profiler != null) + { + _profiler.DisposeIfDisposable(); + } + + if (_profilerStep != null) + { + _profilerStep.Dispose(); + } + + if (_logType.HasValue && _endMessage.IsNullOrWhiteSpace() == false && _loggerType != null && _logger != null) + { + switch (_logType) + { + case LogType.Debug: + _logger.Debug(_loggerType, () => _endMessage + " (took " + Stopwatch.ElapsedMilliseconds + "ms)"); + break; + case LogType.Info: + _logger.Info(_loggerType, () => _endMessage + " (took " + Stopwatch.ElapsedMilliseconds + "ms)"); + break; + default: + throw new ArgumentOutOfRangeException("logType"); + } + } + + if (_callback != null) + { + _callback.Invoke(Stopwatch.ElapsedMilliseconds); + } + } } From 20dc4f5bc4c4185eb1b33267dccd7f760a19ecab Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 23 Jan 2016 12:27:40 +0100 Subject: [PATCH 07/29] fixes merge issue/build --- .../Persistence/Repositories/ContentTypeRepository.cs | 4 +--- .../Persistence/Repositories/MediaTypeRepository.cs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 9b4f2cc44d..8ab98e86e4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -70,9 +70,7 @@ namespace Umbraco.Core.Persistence.Repositories ? GetAll(dtos.DistinctBy(x => x.ContentTypeDto.NodeId).Select(x => x.ContentTypeDto.NodeId).ToArray()) : Enumerable.Empty(); } - - #endregion - + /// /// Gets all entities of the specified query /// diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 613ca623c7..68f7a3c0d9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -70,9 +70,7 @@ namespace Umbraco.Core.Persistence.Repositories ? GetAll(dtos.DistinctBy(x => x.NodeId).Select(x => x.NodeId).ToArray()) : Enumerable.Empty(); } - - #endregion - + /// /// Gets all entities of the specified query From b5e8cb29dd785ed979ec058d54d5b41b382e2b2e Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 23 Jan 2016 12:38:08 +0100 Subject: [PATCH 08/29] ensure appctx and security isn't nulled on disposal (it's an application singleton it doesn't need to be nulled), creates new CreateContext method for creating standalone UmbracoContext instances. --- src/Umbraco.Web/UmbracoContext.cs | 57 ++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 1c688080ce..fca5ff2371 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -125,6 +125,7 @@ namespace Umbraco.Web if (umbracoSettings == null) throw new ArgumentNullException("umbracoSettings"); if (urlProviders == null) throw new ArgumentNullException("urlProviders"); + //if there's already a singleton, and we're not replacing then there's no need to ensure anything if (UmbracoContext.Current != null) { if (replaceContext == false) @@ -132,6 +133,39 @@ namespace Umbraco.Web UmbracoContext.Current._replacing = true; } + var umbracoContext = CreateContext(httpContext, applicationContext, webSecurity, umbracoSettings, urlProviders, preview ?? false); + + //assign the singleton + UmbracoContext.Current = umbracoContext; + return UmbracoContext.Current; + } + + /// + /// Creates a standalone UmbracoContext instance + /// + /// + /// + /// + /// + /// + /// + /// + /// A new instance of UmbracoContext + /// + public static UmbracoContext CreateContext( + HttpContextBase httpContext, + ApplicationContext applicationContext, + WebSecurity webSecurity, + IUmbracoSettingsSection umbracoSettings, + IEnumerable urlProviders, + bool preview) + { + if (httpContext == null) throw new ArgumentNullException("httpContext"); + if (applicationContext == null) throw new ArgumentNullException("applicationContext"); + if (webSecurity == null) throw new ArgumentNullException("webSecurity"); + if (umbracoSettings == null) throw new ArgumentNullException("umbracoSettings"); + if (urlProviders == null) throw new ArgumentNullException("urlProviders"); + var umbracoContext = new UmbracoContext( httpContext, applicationContext, @@ -142,15 +176,15 @@ namespace Umbraco.Web // create the RoutingContext, and assign var routingContext = new RoutingContext( umbracoContext, - + //TODO: Until the new cache is done we can't really expose these to override/mock new Lazy>(() => ContentFinderResolver.Current.Finders), new Lazy(() => ContentLastChanceFinderResolver.Current.Finder), - + // create the nice urls provider // there's one per request because there are some behavior parameters that can be changed new Lazy( - () => new UrlProvider( + () => new UrlProvider( umbracoContext, umbracoSettings.WebRouting, urlProviders), @@ -159,9 +193,7 @@ namespace Umbraco.Web //assign the routing context back umbracoContext.RoutingContext = routingContext; - //assign the singleton - UmbracoContext.Current = umbracoContext; - return UmbracoContext.Current; + return umbracoContext; } /// @@ -460,18 +492,9 @@ namespace Umbraco.Web protected override void DisposeResources() { Security.DisposeIfDisposable(); - Security = null; - _umbracoContext = null; - //ensure not to dispose this! - Application = null; - //Before we set these to null but in fact these are application lifespan singletons so - //there's no reason we need to set them to null and this also caused a problem with packages - //trying to access the cache properties on RequestEnd. - //http://issues.umbraco.org/issue/U4-2734 - //http://our.umbraco.org/projects/developer-tools/301-url-tracker/version-2/44327-Issues-with-URL-Tracker-in-614 - //ContentCache = null; - //MediaCache = null; + //If not running in a web ctx, ensure the thread based instance is nulled + _umbracoContext = null; } } } \ No newline at end of file From e91b53e66d3a8c8ecdee73d8f40a6a1276372646 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 25 Jan 2016 11:09:20 +0100 Subject: [PATCH 09/29] back ported: https://github.com/Umbraco/Umbraco-CMS/commit/2d0f198f58071f5113d5c3437f10f2b85220c348 --- .../Persistence/Repositories/ContentRepository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 85491b9f36..1f5cc1ecd4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -155,8 +155,8 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM cmsContentVersion WHERE ContentId = @Id", "DELETE FROM cmsContentXml WHERE nodeId = @Id", "DELETE FROM cmsContent WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id", - "DELETE FROM umbracoAccess WHERE nodeId = @Id" + "DELETE FROM umbracoAccess WHERE nodeId = @Id", + "DELETE FROM umbracoNode WHERE id = @Id" }; return list; } From e461cb92da18c0f9cb99087de98e76251bac5242 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 26 Jan 2016 16:07:44 +0100 Subject: [PATCH 10/29] U4-7787 Shorthand use of ?altTemplate= doesn't work #U4-7787 Fixed --- src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 955e316bc3..acc17b370a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -475,7 +475,7 @@ namespace Umbraco.Core.Persistence.Repositories if (aliases.Any() == false) return base.GetAll(); //return from base.GetAll, this is all cached - return base.GetAll().Where(x => aliases.Contains(x.Alias)); + return base.GetAll().Where(x => aliases.InvariantContains(x.Alias)); } public IEnumerable GetChildren(int masterTemplateId) From 74ddd09c5d325ed788e4a5f6116d92862e65e7d2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 17:05:46 +0100 Subject: [PATCH 11/29] U4-7836 YSOD when upgrading Umbraco with error during AddUserDataClaims - this also ensures that no empty/null sections can be added to the user object since this is where the original exception was coming from. --- .../Persistence/Relators/UserSectionRelator.cs | 9 ++++++--- .../Install/InstallSteps/SetUmbracoVersionStep.cs | 11 ++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs b/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs index 859fa7cf5a..923348e729 100644 --- a/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs +++ b/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs @@ -18,8 +18,11 @@ namespace Umbraco.Core.Persistence.Relators // Is this the same DictionaryItem as the current one we're processing if (Current != null && Current.Id == a.Id) { - // Yes, just add this User2AppDto to the current item's collection - Current.User2AppDtos.Add(p); + if (p.AppAlias.IsNullOrWhiteSpace() == false) + { + // Yes, just add this User2AppDto to the current item's collection + Current.User2AppDtos.Add(p); + } // Return null to indicate we're not done with this User yet return null; @@ -35,7 +38,7 @@ namespace Umbraco.Core.Persistence.Relators Current = a; Current.User2AppDtos = new List(); //this can be null since we are doing a left join - if (p.AppAlias != null) + if (p.AppAlias.IsNullOrWhiteSpace() == false) { Current.User2AppDtos.Add(p); } diff --git a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs index 6523925505..f7c9b3d6a5 100644 --- a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs @@ -40,9 +40,14 @@ namespace Umbraco.Web.Install.InstallSteps var clientDependencyConfig = new ClientDependencyConfiguration(_applicationContext.ProfilingLogger.Logger); var clientDependencyUpdated = clientDependencyConfig.IncreaseVersionNumber(); - var security = new WebSecurity(_httpContext, _applicationContext); - security.PerformLogin(0); - + //During a new install we'll log the default user in (which is id = 0). + // During an upgrade, the user will already need to be logged in in order to run the installer. + if (InstallTypeTarget == InstallationType.NewInstall) + { + var security = new WebSecurity(_httpContext, _applicationContext); + security.PerformLogin(0); + } + //reports the ended install var ih = new InstallHelper(UmbracoContext.Current); ih.InstallStatus(true, ""); From 72900ef3b3d996cc32e7681afd9241b847dbe06e Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 17:56:25 +0100 Subject: [PATCH 12/29] U4-7813 Enable MiniProfiler to profiler during startup Conflicts: src/Umbraco.Core/Umbraco.Core.csproj --- .../Profiling/StartupWebProfilerProvider.cs | 126 ++++++++++++++++++ src/Umbraco.Core/Profiling/WebProfiler.cs | 33 +++-- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Web/WebBootManager.cs | 4 +- 4 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs diff --git a/src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs b/src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs new file mode 100644 index 0000000000..16ce638c0e --- /dev/null +++ b/src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs @@ -0,0 +1,126 @@ +using System.Threading; +using System.Web; +using StackExchange.Profiling; + +namespace Umbraco.Core.Profiling +{ + /// + /// Allows us to profile items during app startup - before an HttpRequest is created + /// + internal class StartupWebProfilerProvider : WebRequestProfilerProvider + { + public StartupWebProfilerProvider() + { + _startupPhase = StartupPhase.Boot; + //create the startup profiler + _startupProfiler = new MiniProfiler("http://localhost/umbraco-startup", ProfileLevel.Verbose) + { + Name = "StartupProfiler" + }; + } + + private MiniProfiler _startupProfiler; + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); + + private enum StartupPhase + { + None = 0, + Boot = 1, + Request = 2 + } + + private volatile StartupPhase _startupPhase; + + public void BootComplete() + { + using (new ReadLock(_locker)) + { + if (_startupPhase != StartupPhase.Boot) return; + } + + using (var l = new UpgradeableReadLock(_locker)) + { + if (_startupPhase == StartupPhase.Boot) + { + l.UpgradeToWriteLock(); + + ////Now we need to transfer some information from our startup phase to the normal + ////web request phase to output the startup profiled information. + ////Stop our internal startup profiler, this will write out it's results to storage. + //StopProfiler(_startupProfiler); + //SaveProfiler(_startupProfiler); + + _startupPhase = StartupPhase.Request; + } + } + } + + public override void Stop(bool discardResults) + { + using (new ReadLock(_locker)) + { + if (_startupPhase == StartupPhase.None) + { + base.Stop(discardResults); + return; + } + } + + using (var l = new UpgradeableReadLock(_locker)) + { + if (_startupPhase > 0 && base.GetCurrentProfiler() == null) + { + l.UpgradeToWriteLock(); + + _startupPhase = StartupPhase.None; + + if (HttpContext.Current != null) + { + HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler; + base.Stop(discardResults); + _startupProfiler = null; + } + } + else + { + base.Stop(discardResults); + } + } + } + + public override MiniProfiler Start(ProfileLevel level) + { + using (new ReadLock(_locker)) + { + if (_startupPhase > 0 && base.GetCurrentProfiler() == null) + { + SetProfilerActive(_startupProfiler); + return _startupProfiler; + } + + return base.Start(level); + } + } + + public override MiniProfiler GetCurrentProfiler() + { + using (new ReadLock(_locker)) + { + if (_startupPhase > 0) + { + try + { + var current = base.GetCurrentProfiler(); + if (current == null) return _startupProfiler; + } + catch + { + return _startupProfiler; + } + } + + return base.GetCurrentProfiler(); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Profiling/WebProfiler.cs b/src/Umbraco.Core/Profiling/WebProfiler.cs index 00d088bca7..7e2cf49313 100644 --- a/src/Umbraco.Core/Profiling/WebProfiler.cs +++ b/src/Umbraco.Core/Profiling/WebProfiler.cs @@ -12,15 +12,24 @@ namespace Umbraco.Core.Profiling /// internal class WebProfiler : IProfiler { + private StartupWebProfilerProvider _startupWebProfilerProvider; /// /// Constructor - /// - /// - /// Binds to application events to enable the MiniProfiler - /// + /// internal WebProfiler() { + //setup some defaults + MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); + MiniProfiler.Settings.StackMaxLength = 5000; + + //At this point we know that we've been constructed during app startup, there won't be an HttpRequest in the HttpContext + // since it hasn't started yet. So we need to do some hacking to enable profiling during startup. + _startupWebProfilerProvider = new StartupWebProfilerProvider(); + //this should always be the case during startup, we'll need to set a custom profiler provider + MiniProfiler.Settings.ProfilerProvider = _startupWebProfilerProvider; + + //Binds to application events to enable the MiniProfiler with a real HttpRequest UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; } @@ -53,7 +62,12 @@ namespace Umbraco.Core.Profiling /// void UmbracoApplicationEndRequest(object sender, EventArgs e) { - if (CanPerformProfilingAction(sender)) + if (_startupWebProfilerProvider != null) + { + Stop(); + _startupWebProfilerProvider = null; + } + else if (CanPerformProfilingAction(sender)) { Stop(); } @@ -66,6 +80,11 @@ namespace Umbraco.Core.Profiling /// void UmbracoApplicationBeginRequest(object sender, EventArgs e) { + if (_startupWebProfilerProvider != null) + { + _startupWebProfilerProvider.BootComplete(); + } + if (CanPerformProfilingAction(sender)) { Start(); @@ -124,9 +143,7 @@ namespace Umbraco.Core.Profiling /// Start the profiler /// public void Start() - { - MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); - MiniProfiler.Settings.StackMaxLength = 5000; + { MiniProfiler.Start(); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2ef0501f8e..d11270592a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -469,6 +469,7 @@ + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index dcf1e98be4..a9495afa7c 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -183,7 +183,9 @@ namespace Umbraco.Web base.InitializeProfilerResolver(); //Set the profiler to be the web profiler - ProfilerResolver.Current.SetProfiler(new WebProfiler()); + var profiler = new WebProfiler(); + ProfilerResolver.Current.SetProfiler(profiler); + profiler.Start(); } /// From 19bc97a7fd2bc864dd9419e63420ba94076d795e Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 18:56:29 +0100 Subject: [PATCH 13/29] Removes the static (hack) cache for content types for creating published content --- .../PublishedContent/PublishedContentType.cs | 58 ++----------------- .../Cache/ContentTypeCacheRefresher.cs | 8 +-- .../Cache/DataTypeCacheRefresher.cs | 1 - 3 files changed, 7 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 5f30c08ce7..3cff4f0298 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -98,45 +98,10 @@ namespace Umbraco.Core.Models.PublishedContent #endregion - #region Cache - - // these methods are called by ContentTypeCacheRefresher and DataTypeCacheRefresher - - internal static void ClearAll() - { - Logging.LogHelper.Debug("Clear all."); - // ok and faster to do it by types, assuming noone else caches PublishedContentType instances - //ApplicationContext.Current.ApplicationCache.ClearStaticCacheByKeySearch("PublishedContentType_"); - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes(); - } - - internal static void ClearContentType(int id) - { - Logging.LogHelper.Debug("Clear content type w/id {0}.", () => id); - // requires a predicate because the key does not contain the ID - // faster than key strings comparisons anyway - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( - (key, value) => value.Id == id); - } - - internal static void ClearDataType(int id) - { - Logging.LogHelper.Debug("Clear data type w/id {0}.", () => id); - // there is no recursion to handle here because a PublishedContentType contains *all* its - // properties ie both its own properties and those that were inherited (it's based upon an - // IContentTypeComposition) and so every PublishedContentType having a property based upon - // the cleared data type, be it local or inherited, will be cleared. - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( - (key, value) => value.PropertyTypes.Any(x => x.DataTypeId == id)); - } - + public static PublishedContentType Get(PublishedItemType itemType, string alias) { - var key = string.Format("PublishedContentType_{0}_{1}", - itemType.ToString().ToLowerInvariant(), alias.ToLowerInvariant()); - - var type = ApplicationContext.Current.ApplicationCache.StaticCache.GetCacheItem(key, - () => CreatePublishedContentType(itemType, alias)); + var type = CreatePublishedContentType(itemType, alias); return type; } @@ -169,21 +134,8 @@ namespace Umbraco.Core.Models.PublishedContent return new PublishedContentType(contentType); } - // for unit tests - changing the callback must reset the cache obviously - private static Func _getPublishedContentTypeCallBack; - internal static Func GetPublishedContentTypeCallback - { - get { return _getPublishedContentTypeCallBack; } - set - { - // see note above - //ClearAll(); - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheByKeySearch("PublishedContentType_"); - - _getPublishedContentTypeCallBack = value; - } - } - - #endregion + // for unit tests + internal static Func GetPublishedContentTypeCallback { get; set; } + } } diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 44a6efe9ff..c44b3f2b51 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -141,9 +141,7 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.ContentTypeCacheKey); //clear static object cache global::umbraco.cms.businesslogic.ContentType.RemoveAllDataTypeCache(); - - PublishedContentType.ClearAll(); - + base.RefreshAll(); } @@ -280,9 +278,7 @@ namespace Umbraco.Web.Cache //clears the dictionary object cache of the legacy ContentType global::umbraco.cms.businesslogic.ContentType.RemoveFromDataTypeCache(payload.Alias); - - PublishedContentType.ClearContentType(payload.Id); - + //need to recursively clear the cache for each child content type foreach (var descendant in payload.DescendantPayloads) { diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs index 11b3ab6294..173f4dcb86 100644 --- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs @@ -108,7 +108,6 @@ namespace Umbraco.Web.Cache if (dataTypeCache) dataTypeCache.Result.ClearCacheByKeySearch(string.Format("{0}{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id)); - PublishedContentType.ClearDataType(payload.Id); }); base.Refresh(jsonPayload); From 7a25cb52b218510c1c2230e64df56168601125a1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 19:13:42 +0100 Subject: [PATCH 14/29] updates the repository cache policies so that we aren't keeping everything in cache unnecessarily --- .../Cache/DefaultRepositoryCachePolicy.cs | 17 ++++++++++++++--- .../Cache/FullDataSetRepositoryCachePolicy.cs | 4 ++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs index 66d9b2ac25..45e79a1b67 100644 --- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs @@ -11,6 +11,10 @@ namespace Umbraco.Core.Cache /// /// /// + /// + /// This cache policy uses sliding expiration and caches instances for 5 minutes. However if allow zero count is true, then we use the + /// default policy with no expiry. + /// internal class DefaultRepositoryCachePolicy : DisposableObject, IRepositoryCachePolicy where TEntity : class, IAggregateRoot { @@ -54,7 +58,9 @@ namespace Umbraco.Core.Cache //just to be safe, we cannot cache an item without an identity if (entity.HasIdentity) { - Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity); + Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity, + timeout: TimeSpan.FromMinutes(5), + isSliding: true); } //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared @@ -225,7 +231,9 @@ namespace Umbraco.Core.Cache //just to be safe, we cannot cache an item without an identity if (entity.HasIdentity) { - Cache.InsertCacheItem(cacheKey, () => entity); + Cache.InsertCacheItem(cacheKey, () => entity, + timeout: TimeSpan.FromMinutes(5), + isSliding: true); } }); } @@ -244,6 +252,7 @@ namespace Umbraco.Core.Cache { //there was nothing returned but we want to cache a zero count result so add an TEntity[] to the cache // to signify that there is a zero count cache + //NOTE: Don't set expiry/sliding for a zero count Cache.InsertCacheItem(GetCacheTypeKey(), () => new TEntity[] {}); } else @@ -256,7 +265,9 @@ namespace Umbraco.Core.Cache //just to be safe, we cannot cache an item without an identity if (localCopy.HasIdentity) { - Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy); + Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy, + timeout: TimeSpan.FromMinutes(5), + isSliding: true); } } } diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index 40b100ef67..3b3c98fc80 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -11,6 +11,10 @@ namespace Umbraco.Core.Cache /// /// /// + /// + /// This caching policy has no sliding expiration but uses the default ObjectCache.InfiniteAbsoluteExpiration as it's timeout, so it + /// should not leave the cache unless the cache memory is exceeded and it gets thrown out. + /// internal class FullDataSetRepositoryCachePolicy : DefaultRepositoryCachePolicy where TEntity : class, IAggregateRoot { From c9f71ce067df177c8d5cf85d4ebc3963e726bed3 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 26 Jan 2016 21:07:15 +0100 Subject: [PATCH 15/29] Fixes: U4-7772 Cursor jumps when editing alias in new ContentType editor + U4-7832 Editing property alias chomps 1st character --- .../forms/umbselectwhen.directive.js | 28 +++++++++++++++++++ .../components/umblockedfield.directive.js | 28 ++----------------- .../views/components/umb-locked-field.html | 5 +++- 3 files changed, 34 insertions(+), 27 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbselectwhen.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbselectwhen.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbselectwhen.directive.js new file mode 100644 index 0000000000..b8986c02d3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbselectwhen.directive.js @@ -0,0 +1,28 @@ +(function() { + 'use strict'; + + function SelectWhen($timeout) { + + function link(scope, el, attr, ctrl) { + + attr.$observe("umbSelectWhen", function(newValue) { + if (newValue === "true") { + $timeout(function() { + el.select(); + }); + } + }); + + } + + var directive = { + restrict: 'A', + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbSelectWhen', SelectWhen); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js index 870a7d45d6..b382fa1c32 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js @@ -24,12 +24,10 @@ // scope object, but that would mean we'd have to watch that value too in order to set the outer // ngModelCtrl.$modelValue. It's seems like less overhead to just do this and not have 2x watches. scope.lockedFieldForm.lockedField.$modelValue = undefined; - scope.lockedFieldForm.lockedField.$setViewValue(newValue); - scope.lockedFieldForm.lockedField.$render(); + scope.lockedFieldForm.lockedField.$render(); } + scope.lockedFieldForm.lockedField.$setViewValue(scope.lockedFieldForm.lockedField.$modelValue); }); - - var input = el.find('.umb-locked-field__input'); function activate() { @@ -57,36 +55,14 @@ scope.lock = function() { scope.locked = true; - input.unbind("blur"); }; scope.unlock = function() { scope.locked = false; - autoFocusField(); }; - function autoFocusField() { - - var onBlurHandler = function() { - scope.$apply(function(){ - scope.lock(); - }); - }; - - $timeout(function() { - input.focus(); - input.select(); - input.on("blur", onBlurHandler); - }); - - } - activate(); - scope.$on('$destroy', function() { - input.unbind('blur'); - }); - } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html index 744e635f21..217d420a3a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html @@ -21,7 +21,10 @@ umb-auto-resize required val-server-field="{{serverValidationField}}" - title="{{ngModel}}" /> + title="{{ngModel}}" + focus-when="{{!locked}}" + umb-select-when="{{!locked}}" + on-blur="lock()" /> From 5e88fbfad0309c8ef05b49e4dac2d86ba048c11e Mon Sep 17 00:00:00 2001 From: bjarnef Date: Tue, 26 Jan 2016 23:52:16 +0100 Subject: [PATCH 16/29] Indent of toolbar options in TinyMCE config --- src/Umbraco.Web.UI.Client/src/less/main.less | 35 +++++++++++++++---- .../propertyeditors/rte/rte.prevalues.html | 34 ++++++++++-------- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 4502527ce8..09f916c59f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -140,7 +140,10 @@ h5.-black { /* LABELS*/ .umb-control-group label.control-label { - text-align: left + text-align: left; +} +.umb-control-group label.control-label > div > label { + padding-left: 0; } .umb-control-group label .help-block, .umb-control-group label small { @@ -150,7 +153,7 @@ h5.-black { padding-top: 5px; } .umb-nolabel .controls { - margin-left: 0px; + margin-left: 0; } .controls-row { @@ -159,11 +162,15 @@ h5.-black { } .umb-user-panel .controls-row { - margin-left: 0px; + margin-left: 0; } .controls-row label { - display: inline-block + display: inline-block; +} + +.controls-row > div > label { + padding-left: 0; } .block-form .controls-row { @@ -171,10 +178,24 @@ h5.-black { padding-top: 0; } -.hidelabel > div > .controls-row, .hidelabel > .controls-row, .hidelabel .controls { - padding: 0px; +.hidelabel > div > .controls-row, .hidelabel > .controls-row, .hidelabel > div > .controls { + padding: 0; border: none; - margin: 0px !important; + margin: 0 !important; +} + +.controls-row > .vertical-align-items { + display: flex; + align-items: center; +} + +.controls-row > .vertical-align-items > input.umb-editor-tiny { + margin-left: 5px; + margin-right: 5px; +} + +.controls-row > .vertical-align-items > input.umb-editor-tiny:first-child { + margin-left: 0; } .thumbnails .selected { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html index f00dfa06c1..fbc97224ab 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html @@ -2,35 +2,41 @@
- +
- - {{css.name}} +
- × - Pixels +
+ × + Pixels +
- Pixels +
+ Pixels +
\ No newline at end of file From 43e49b75de00080238509b2df7706bca7255705a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Jan 2016 09:21:58 +0100 Subject: [PATCH 17/29] Add extra div to media grid to fix firefox issue in chrome --- .../src/views/components/umb-media-grid.html | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html index d62435ac28..5cd6d5c82f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html @@ -1,20 +1,20 @@
+
+
-
+ - +
+ +
{{item.name}}
+
-
- -
{{item.name}}
-
+
+ {{item.name}} + {{item.name}} -
- {{item.name}} - {{item.name}} - - - -
+ +
+
From c33b488006b49fb12f6f1c80795eb8695d5b1536 Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 27 Jan 2016 09:27:56 +0100 Subject: [PATCH 18/29] Ensuring we only do the base SetupNode stuff if its actually a media type and not a folder requested. --- src/umbraco.cms/businesslogic/media/MediaType.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/umbraco.cms/businesslogic/media/MediaType.cs b/src/umbraco.cms/businesslogic/media/MediaType.cs index 2dec2cd56f..308b01c853 100644 --- a/src/umbraco.cms/businesslogic/media/MediaType.cs +++ b/src/umbraco.cms/businesslogic/media/MediaType.cs @@ -161,7 +161,9 @@ namespace umbraco.cms.businesslogic.media protected override void setupNode() { var mediaType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(Id); - SetupNode(mediaType); + // If it's null, it's probably a folder + if (mediaType != null) + SetupNode(mediaType); } #endregion From 02e505698751c6bd3e2f064fed4202aa05be98e3 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Tue, 26 Jan 2016 19:20:19 +0100 Subject: [PATCH 19/29] Change value of vertical-align property. --- src/Umbraco.Web.UI.Client/src/less/components/card.less | 2 +- src/Umbraco.Web.UI.Client/src/less/forms.less | 2 +- src/Umbraco.Web.UI.Client/src/less/installer.less | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/card.less b/src/Umbraco.Web.UI.Client/src/less/components/card.less index 6e1603b3d8..de21406b6a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -37,7 +37,7 @@ .umb-card-icons{ text-align: center; - vertical-align: center; + vertical-align: middle; display: block; list-style: none; margin: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 960ec8a7e2..eb569c7cdf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -27,7 +27,7 @@ label.control-label { } -.controls-row label{padding: 0 10px 0 10px; vertical-align: center} +.controls-row label{padding: 0 10px 0 10px; vertical-align: middle;} diff --git a/src/Umbraco.Web.UI.Client/src/less/installer.less b/src/Umbraco.Web.UI.Client/src/less/installer.less index 574fde27a9..aa8bbccfef 100644 --- a/src/Umbraco.Web.UI.Client/src/less/installer.less +++ b/src/Umbraco.Web.UI.Client/src/less/installer.less @@ -38,7 +38,7 @@ body { line-height: @baseLineHeight; color: @textColor; - vertical-align: center; + vertical-align: middle; text-align: center; } From 81754b37a4c36d51b17b1e7bef8f376952ee190d Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 18 Jan 2016 17:33:49 +0100 Subject: [PATCH 20/29] Add support for models in controllers --- src/Umbraco.Web/Mvc/RenderModelBinder.cs | 24 ++++++++++++++++++------ src/Umbraco.Web/WebBootManager.cs | 2 +- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/Mvc/RenderModelBinder.cs b/src/Umbraco.Web/Mvc/RenderModelBinder.cs index 20045604d0..172befb791 100644 --- a/src/Umbraco.Web/Mvc/RenderModelBinder.cs +++ b/src/Umbraco.Web/Mvc/RenderModelBinder.cs @@ -7,8 +7,8 @@ using Umbraco.Web.Models; namespace Umbraco.Web.Mvc { - public class RenderModelBinder : IModelBinder - { + public class RenderModelBinder : IModelBinder, IModelBinderProvider + { /// /// Binds the model to a value by using the specified controller context and binding context. /// @@ -18,14 +18,13 @@ namespace Umbraco.Web.Mvc /// The controller context.The binding context. public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { - if (bindingContext.ModelType != typeof (RenderModel)) return null; - object model; if (controllerContext.RouteData.DataTokens.TryGetValue("umbraco", out model) == false) return null; - return model as RenderModel; - } + var culture = UmbracoContext.Current.PublishedContentRequest.Culture; + return BindModel(model, bindingContext.ModelType, culture); + } // source is the model that we have // modelType is the type of the model that we need to bind to @@ -103,5 +102,18 @@ namespace Umbraco.Web.Mvc throw new ModelBindingException(string.Format("Cannot bind source type {0} to model type {1}.", sourceType, modelType)); } + + public IModelBinder GetBinder(Type modelType) + { + // can bind to RenderModel + if (modelType == typeof(RenderModel)) return this; + + // can bind to RenderModel + if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(RenderModel<>)) return this; + + // can bind to TContent where TContent : IPublishedContent + if (typeof(IPublishedContent).IsAssignableFrom(modelType)) return this; + return null; + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index d2f7910e5c..1034d33297 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -135,7 +135,7 @@ namespace Umbraco.Web ViewEngines.Engines.Add(new PluginViewEngine()); //set model binder - ModelBinders.Binders.Add(new KeyValuePair(typeof(RenderModel), new RenderModelBinder())); + ModelBinderProviders.BinderProviders.Add(new RenderModelBinder()); // is a provider ////add the profiling action filter //GlobalFilters.Filters.Add(new ProfilingActionFilter()); From e8dfa056b0cae2a338bb5961c2ec3658e4245288 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Jan 2016 13:20:13 +0100 Subject: [PATCH 21/29] U4-2670 Add event to modify the model before being sent to render --- src/Umbraco.Web/Editors/ContentController.cs | 4 +- .../Editors/EditorModelEventManager.cs | 85 +++++++++++++++++++ src/Umbraco.Web/Editors/MediaController.cs | 4 +- src/Umbraco.Web/Editors/MemberController.cs | 2 + .../Models/PublishedContentModels.cs | 12 --- src/Umbraco.Web/Umbraco.Web.csproj | 2 + .../OutgoingEditorModelEventAttribute.cs | 38 +++++++++ 7 files changed, 133 insertions(+), 14 deletions(-) create mode 100644 src/Umbraco.Web/Editors/EditorModelEventManager.cs delete mode 100644 src/Umbraco.Web/Models/PublishedContentModels.cs create mode 100644 src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index cb6e56cb13..6358656d00 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -81,7 +81,8 @@ namespace Umbraco.Web.Editors ///
/// /// - [EnsureUserPermissionForContent("id")] + [OutgoingEditorModelEvent] + [EnsureUserPermissionForContent("id")] public ContentItemDisplay GetById(int id) { var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); @@ -116,6 +117,7 @@ namespace Umbraco.Web.Editors /// If this is a container type, we'll remove the umbContainerView tab for a new item since /// it cannot actually list children if it doesn't exist yet. /// + [OutgoingEditorModelEvent] public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) { var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias); diff --git a/src/Umbraco.Web/Editors/EditorModelEventManager.cs b/src/Umbraco.Web/Editors/EditorModelEventManager.cs new file mode 100644 index 0000000000..0414bc0cdd --- /dev/null +++ b/src/Umbraco.Web/Editors/EditorModelEventManager.cs @@ -0,0 +1,85 @@ +using System; +using System.Web.Http.Filters; +using Umbraco.Core.Events; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Editors +{ + public abstract class EditorModelEventArgs : EventArgs + { + protected EditorModelEventArgs(object model, UmbracoContext umbracoContext) + { + Model = model; + UmbracoContext = umbracoContext; + } + + public object Model { get; private set; } + public UmbracoContext UmbracoContext { get; private set; } + } + + public sealed class EditorModelEventArgs : EditorModelEventArgs + { + public EditorModelEventArgs(T model, UmbracoContext umbracoContext) + : base(model, umbracoContext) + { + Model = model; + } + + public new T Model { get; private set; } + } + + /// + /// Used to emit events for editor models in the back office + /// + public sealed class EditorModelEventManager + { + public static event TypedEventHandler> SendingContentModel; + public static event TypedEventHandler> SendingMediaModel; + public static event TypedEventHandler> SendingMemberModel; + + private static void OnSendingContentModel(HttpActionExecutedContext sender, EditorModelEventArgs e) + { + var handler = SendingContentModel; + if (handler != null) handler(sender, e); + } + + private static void OnSendingMediaModel(HttpActionExecutedContext sender, EditorModelEventArgs e) + { + var handler = SendingMediaModel; + if (handler != null) handler(sender, e); + } + + private static void OnSendingMemberModel(HttpActionExecutedContext sender, EditorModelEventArgs e) + { + var handler = SendingMemberModel; + if (handler != null) handler(sender, e); + } + + /// + /// Based on the type, emit's a specific event + /// + /// + /// + internal static void EmitEvent(HttpActionExecutedContext sender, EditorModelEventArgs e) + { + var contentItemDisplay = e.Model as ContentItemDisplay; + if (contentItemDisplay != null) + { + OnSendingContentModel(sender, (EditorModelEventArgs) e); + } + + var mediaItemDisplay = e.Model as MediaItemDisplay; + if (mediaItemDisplay != null) + { + OnSendingMediaModel(sender, (EditorModelEventArgs)e); + } + + var memberItemDisplay = e.Model as MemberDisplay; + if (memberItemDisplay != null) + { + OnSendingMemberModel(sender, (EditorModelEventArgs)e); + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index a2637e68e8..e84aaadce5 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -63,13 +63,14 @@ namespace Umbraco.Web.Editors : base(umbracoContext) { } - + /// /// Gets an empty content item for the /// /// /// /// + [OutgoingEditorModelEvent] public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) { var contentType = Services.ContentTypeService.GetMediaType(contentTypeAlias); @@ -92,6 +93,7 @@ namespace Umbraco.Web.Editors /// /// /// + [OutgoingEditorModelEvent] [EnsureUserPermissionForMedia("id")] public MediaItemDisplay GetById(int id) { diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 0788ba43eb..f03cda1bba 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -145,6 +145,7 @@ namespace Umbraco.Web.Editors /// /// /// + [OutgoingEditorModelEvent] public MemberDisplay GetByKey(Guid key) { MembershipUser foundMembershipMember; @@ -196,6 +197,7 @@ namespace Umbraco.Web.Editors /// /// /// + [OutgoingEditorModelEvent] public MemberDisplay GetEmpty(string contentTypeAlias = null) { IMember emptyContent; diff --git a/src/Umbraco.Web/Models/PublishedContentModels.cs b/src/Umbraco.Web/Models/PublishedContentModels.cs deleted file mode 100644 index d6d3cd9973..0000000000 --- a/src/Umbraco.Web/Models/PublishedContentModels.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Web.PublishedContentModels -{ - class Empty - { - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 2ae1436d58..ac3fb00af2 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -319,6 +319,7 @@ + @@ -686,6 +687,7 @@ + diff --git a/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs new file mode 100644 index 0000000000..3c74f15b8d --- /dev/null +++ b/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs @@ -0,0 +1,38 @@ +using System; +using System.Net.Http; +using System.Web.Http.Filters; +using Umbraco.Core; +using Umbraco.Web.Editors; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// Used to emit outgoing editor model events + /// + internal sealed class OutgoingEditorModelEventAttribute : ActionFilterAttribute + { + public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) + { + if (actionExecutedContext.Response == null) return; + + var user = UmbracoContext.Current.Security.CurrentUser; + if (user == null) return; + + var objectContent = actionExecutedContext.Response.Content as ObjectContent; + if (objectContent != null) + { + var model = objectContent.Value; + + if (model != null) + { + EditorModelEventManager.EmitEvent(actionExecutedContext, new EditorModelEventArgs( + (dynamic)model, + UmbracoContext.Current)); + } + } + + base.OnActionExecuted(actionExecutedContext); + } + } +} \ No newline at end of file From 9c5d5f431816bb9f96b6868835bdddd7a200a196 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Jan 2016 13:25:54 +0100 Subject: [PATCH 22/29] Fixes: U4-7845 Show drop zone in media section + fix recycle bin for content section + localise texts --- .../listview/layouts/grid/grid.html | 18 +++++++++++------ .../grid/grid.listviewlayout.controller.js | 2 +- .../listview/layouts/list/list.html | 20 +++++++++---------- .../list/list.listviewlayout.controller.js | 2 +- .../umbraco/config/lang/en_us.xml | 1 + 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html index f90f607e9d..a2c0ff534e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html @@ -4,7 +4,7 @@ ng-if="entityType !== 'media'"> - Sorry, we can not find what you are looking for + + + + + @@ -31,7 +37,7 @@ files-uploaded="vm.onUploadComplete" accept="{{vm.acceptedFileTypes}}" max-file-size="{{vm.maxFileSize}}" - hide-dropzone="{{!vm.activeDrag && items.length > 0 || !items && options.filter }}" + hide-dropzone="{{ options.filter.length > 0 }}" compact="{{ items.length > 0 }}" files-queued="vm.onFilesQueue"> @@ -68,9 +74,9 @@ - Sorry, we can not find what you are looking for + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js index a8a1ac0766..d4ca07aede 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js @@ -19,7 +19,7 @@ vm.activeDrag = false; vm.mediaDetailsTooltip = {}; vm.itemsWithoutFolders = []; - vm.isRecycleBin = $scope.contentId === '-21'; + vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; vm.dragEnter = dragEnter; vm.dragLeave = dragLeave; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html index 24f1dabf33..276274dce3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html @@ -14,7 +14,7 @@ files-uploaded="vm.onUploadComplete" accept="{{vm.acceptedFileTypes}}" max-file-size="{{vm.maxFileSize}}" - hide-dropzone="{{!vm.activeDrag && items.length > 0 || !items && options.filter }}" + hide-dropzone="{{options.filter.length > 0}}" compact="{{ items.length > 0 }}" files-queued="vm.onFilesQueue"> @@ -31,18 +31,12 @@ on-sort="vm.sort"> - - - -
- Sorry, we can not find what you are looking for + + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js index 59327524d6..8a2de97f0e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js @@ -10,7 +10,7 @@ vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; vm.activeDrag = false; - vm.isRecycleBin = $scope.contentId === '-21'; + vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; vm.selectItem = selectItem; vm.clickItem = clickItem; diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 258173ce0f..cd0067bf78 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -448,6 +448,7 @@ Retry Permissions Search + Sorry, we can not find what you are looking for Server Show Show page on Send From 38e919357308d39dfe11274ad668fd4737b09972 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Jan 2016 14:18:53 +0100 Subject: [PATCH 23/29] Fixes: U4-7846 Media grid missing min-height on item + make media item sizes as attr so they can be set outside the directive --- .../components/umbmediagrid.directive.js | 47 ++++++++++++++----- .../overlays/mediaPicker/mediapicker.html | 6 ++- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 5175dd3b4d..112e2d9026 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -9,18 +9,36 @@ var itemDefaultWidth = 200; var itemMaxWidth = 200; var itemMaxHeight = 200; + var itemMinWidth = 125; + var itemMinHeight = 125; function activate() { - for (var i = 0; scope.items.length > i; i++) { - var item = scope.items[i]; - setItemData(item); - setOriginalSize(item, itemMaxHeight); - } + if (scope.itemMaxWidth) { + itemMaxWidth = scope.itemMaxWidth; + } - if(scope.items.length > 0) { - setFlexValues(scope.items); - } + if (scope.itemMaxHeight) { + itemMaxHeight = scope.itemMaxHeight; + } + + if (scope.itemMinWidth) { + itemMinWidth = scope.itemMinWidth; + } + + if (scope.itemMinWidth) { + itemMinHeight = scope.itemMinHeight; + } + + for (var i = 0; scope.items.length > i; i++) { + var item = scope.items[i]; + setItemData(item); + setOriginalSize(item, itemMaxHeight); + } + + if (scope.items.length > 0) { + setFlexValues(scope.items); + } } @@ -100,12 +118,13 @@ flex = 1; } - var imageMinWidth = smallestImageWidth * flex; + var imageMinFlexWidth = smallestImageWidth * flex; var flexStyle = { - "flex": flex + " 1 " + imageMinWidth + "px", + "flex": flex + " 1 " + imageMinFlexWidth + "px", "max-width": mediaItem.width + "px", - "min-width": "125px" + "min-width": itemMinWidth + "px", + "min-height": itemMinHeight + "px" }; mediaItem.flexStyle = flexStyle; @@ -154,7 +173,11 @@ onDetailsHover: "=", onClick: '=', onClickName: "=", - filterBy: "=" + filterBy: "=", + itemMaxWidth: "@", + itemMaxHeight: "@", + itemMinWidth: "@", + itemMinHeight: "@" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html index 0ab9d6dba4..5bcbfbb3d2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html @@ -76,7 +76,11 @@ items="images" filter-by="searchTerm" on-click="clickHandler" - on-click-name="clickItemName"> + on-click-name="clickItemName" + item-max-width="150" + item-max-height="150" + item-min-width="100" + item-min-height="100"> From 1c04aa377c417a6a4f9be9d45e387c026acc3c2c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Jan 2016 14:21:27 +0100 Subject: [PATCH 24/29] fix tab indention --- .../components/umbmediagrid.directive.js | 330 +++++++++--------- 1 file changed, 167 insertions(+), 163 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 112e2d9026..eed0cffadb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -1,190 +1,194 @@ (function() { - 'use strict'; + 'use strict'; - function MediaGridDirective($filter, mediaHelper) { + function MediaGridDirective($filter, mediaHelper) { - function link(scope, el, attr, ctrl) { + function link(scope, el, attr, ctrl) { - var itemDefaultHeight = 200; - var itemDefaultWidth = 200; - var itemMaxWidth = 200; - var itemMaxHeight = 200; - var itemMinWidth = 125; - var itemMinHeight = 125; + var itemDefaultHeight = 200; + var itemDefaultWidth = 200; + var itemMaxWidth = 200; + var itemMaxHeight = 200; + var itemMinWidth = 125; + var itemMinHeight = 125; - function activate() { + function activate() { - if (scope.itemMaxWidth) { - itemMaxWidth = scope.itemMaxWidth; - } + if (scope.itemMaxWidth) { + itemMaxWidth = scope.itemMaxWidth; + } - if (scope.itemMaxHeight) { - itemMaxHeight = scope.itemMaxHeight; - } + if (scope.itemMaxHeight) { + itemMaxHeight = scope.itemMaxHeight; + } - if (scope.itemMinWidth) { - itemMinWidth = scope.itemMinWidth; - } + if (scope.itemMinWidth) { + itemMinWidth = scope.itemMinWidth; + } - if (scope.itemMinWidth) { - itemMinHeight = scope.itemMinHeight; - } + if (scope.itemMinWidth) { + itemMinHeight = scope.itemMinHeight; + } - for (var i = 0; scope.items.length > i; i++) { - var item = scope.items[i]; - setItemData(item); - setOriginalSize(item, itemMaxHeight); - } + for (var i = 0; scope.items.length > i; i++) { + var item = scope.items[i]; + setItemData(item); + setOriginalSize(item, itemMaxHeight); + } - if (scope.items.length > 0) { - setFlexValues(scope.items); - } - - } - - function setItemData(item) { - item.isFolder = !mediaHelper.hasFilePropertyType(item); - if(!item.isFolder){ - item.thumbnail = mediaHelper.resolveFile(item, true); - item.image = mediaHelper.resolveFile(item, false); - } - } - - function setOriginalSize(item, maxHeight) { - - //set to a square by default - item.width = itemDefaultWidth; - item.height = itemDefaultHeight; - item.aspectRatio = 1; - - var widthProp = _.find(item.properties, function(v) { return (v.alias === "umbracoWidth"); }); - - if (widthProp && widthProp.value) { - item.width = parseInt(widthProp.value, 10); - if (isNaN(item.width)) { - item.width = itemDefaultWidth; - } - } - - var heightProp = _.find(item.properties, function(v) { return (v.alias === "umbracoHeight"); }); - - if (heightProp && heightProp.value) { - item.height = parseInt(heightProp.value, 10); - if (isNaN(item.height)) { - item.height = itemDefaultWidth; - } - } - - item.aspectRatio = item.width / item.height; - - // set max width and height - // landscape - if(item.aspectRatio >= 1) { - if(item.width > itemMaxWidth) { - item.width = itemMaxWidth; - item.height = itemMaxWidth / item.aspectRatio; - } - // portrait - } else { - if(item.height > itemMaxHeight) { - item.height = itemMaxHeight; - item.width = itemMaxHeight * item.aspectRatio; - } - } - - } - - function setFlexValues(mediaItems) { - - var flexSortArray = mediaItems; - var smallestImageWidth = null; - var widestImageAspectRatio = null; - - // sort array after image width with the widest image first - flexSortArray = $filter('orderBy')(flexSortArray, 'width', true); - - // find widest image aspect ratio - widestImageAspectRatio = flexSortArray[0].aspectRatio; - - // find smallest image width - smallestImageWidth = flexSortArray[flexSortArray.length - 1].width; - - for (var i = 0; flexSortArray.length > i; i++) { - - var mediaItem = flexSortArray[i]; - var flex = 1 / (widestImageAspectRatio / mediaItem.aspectRatio); - - if (flex === 0) { - flex = 1; - } - - var imageMinFlexWidth = smallestImageWidth * flex; - - var flexStyle = { - "flex": flex + " 1 " + imageMinFlexWidth + "px", - "max-width": mediaItem.width + "px", - "min-width": itemMinWidth + "px", - "min-height": itemMinHeight + "px" - }; - - mediaItem.flexStyle = flexStyle; + if (scope.items.length > 0) { + setFlexValues(scope.items); + } } - } - - scope.clickItem = function(item, $event, $index) { - if(scope.onClick) { - scope.onClick(item, $event, $index); + function setItemData(item) { + item.isFolder = !mediaHelper.hasFilePropertyType(item); + if (!item.isFolder) { + item.thumbnail = mediaHelper.resolveFile(item, true); + item.image = mediaHelper.resolveFile(item, false); + } } - }; - scope.clickItemName = function(item, $event, $index) { - if(scope.onClickName) { - scope.onClickName(item, $event, $index); - $event.stopPropagation(); + function setOriginalSize(item, maxHeight) { + + //set to a square by default + item.width = itemDefaultWidth; + item.height = itemDefaultHeight; + item.aspectRatio = 1; + + var widthProp = _.find(item.properties, function(v) { + return (v.alias === "umbracoWidth"); + }); + + if (widthProp && widthProp.value) { + item.width = parseInt(widthProp.value, 10); + if (isNaN(item.width)) { + item.width = itemDefaultWidth; + } + } + + var heightProp = _.find(item.properties, function(v) { + return (v.alias === "umbracoHeight"); + }); + + if (heightProp && heightProp.value) { + item.height = parseInt(heightProp.value, 10); + if (isNaN(item.height)) { + item.height = itemDefaultWidth; + } + } + + item.aspectRatio = item.width / item.height; + + // set max width and height + // landscape + if (item.aspectRatio >= 1) { + if (item.width > itemMaxWidth) { + item.width = itemMaxWidth; + item.height = itemMaxWidth / item.aspectRatio; + } + // portrait + } else { + if (item.height > itemMaxHeight) { + item.height = itemMaxHeight; + item.width = itemMaxHeight * item.aspectRatio; + } + } + } - }; - scope.hoverItemDetails = function(item, $event, hover) { - if(scope.onDetailsHover) { - scope.onDetailsHover(item, $event, hover); + function setFlexValues(mediaItems) { + + var flexSortArray = mediaItems; + var smallestImageWidth = null; + var widestImageAspectRatio = null; + + // sort array after image width with the widest image first + flexSortArray = $filter('orderBy')(flexSortArray, 'width', true); + + // find widest image aspect ratio + widestImageAspectRatio = flexSortArray[0].aspectRatio; + + // find smallest image width + smallestImageWidth = flexSortArray[flexSortArray.length - 1].width; + + for (var i = 0; flexSortArray.length > i; i++) { + + var mediaItem = flexSortArray[i]; + var flex = 1 / (widestImageAspectRatio / mediaItem.aspectRatio); + + if (flex === 0) { + flex = 1; + } + + var imageMinFlexWidth = smallestImageWidth * flex; + + var flexStyle = { + "flex": flex + " 1 " + imageMinFlexWidth + "px", + "max-width": mediaItem.width + "px", + "min-width": itemMinWidth + "px", + "min-height": itemMinHeight + "px" + }; + + mediaItem.flexStyle = flexStyle; + + } + } - }; - var unbindItemsWatcher = scope.$watch('items', function(newValue, oldValue){ - if(angular.isArray(newValue)) { - activate(); - } - }); + scope.clickItem = function(item, $event, $index) { + if (scope.onClick) { + scope.onClick(item, $event, $index); + } + }; - scope.$on('$destroy', function(){ - unbindItemsWatcher(); - }); + scope.clickItemName = function(item, $event, $index) { + if (scope.onClickName) { + scope.onClickName(item, $event, $index); + $event.stopPropagation(); + } + }; - } + scope.hoverItemDetails = function(item, $event, hover) { + if (scope.onDetailsHover) { + scope.onDetailsHover(item, $event, hover); + } + }; - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-media-grid.html', - scope: { - items: '=', - onDetailsHover: "=", - onClick: '=', - onClickName: "=", - filterBy: "=", - itemMaxWidth: "@", - itemMaxHeight: "@", - itemMinWidth: "@", - itemMinHeight: "@" - }, - link: link - }; + var unbindItemsWatcher = scope.$watch('items', function(newValue, oldValue) { + if (angular.isArray(newValue)) { + activate(); + } + }); - return directive; - } + scope.$on('$destroy', function() { + unbindItemsWatcher(); + }); - angular.module('umbraco.directives').directive('umbMediaGrid', MediaGridDirective); + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-media-grid.html', + scope: { + items: '=', + onDetailsHover: "=", + onClick: '=', + onClickName: "=", + filterBy: "=", + itemMaxWidth: "@", + itemMaxHeight: "@", + itemMinWidth: "@", + itemMinHeight: "@" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbMediaGrid', MediaGridDirective); })(); From cc4c7b0d88c667d7592208297918474c9f5f152d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Jan 2016 14:30:57 +0100 Subject: [PATCH 25/29] Adds events for creating/deleting containers --- .../Services/ContentTypeService.cs | 98 +++++++++++++++---- .../Services/IContentTypeService.cs | 9 +- src/Umbraco.Core/Services/OperationStatus.cs | 6 ++ .../Services/OperationStatusType.cs | 7 +- src/Umbraco.Core/Services/PackagingService.cs | 4 +- 5 files changed, 97 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 493768b368..4207149a52 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -44,8 +44,9 @@ namespace Umbraco.Core.Services #region Containers - public Attempt CreateContentTypeContainer(int parentId, string name, int userId = 0) + public Attempt> CreateContentTypeContainer(int parentId, string name, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid)) { @@ -57,20 +58,29 @@ namespace Umbraco.Core.Services ParentId = parentId, CreatorId = userId }; + + if (SavingContentTypeContainer.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.AddOrUpdate(container); uow.Commit(); - return Attempt.Succeed(container.Id); + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); } catch (Exception ex) { - return Attempt.Fail(ex); + return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); } //TODO: Audit trail ? } } - public Attempt CreateMediaTypeContainer(int parentId, string name, int userId = 0) + public Attempt> CreateMediaTypeContainer(int parentId, string name, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid)) { @@ -82,42 +92,80 @@ namespace Umbraco.Core.Services ParentId = parentId, CreatorId = userId }; + + if (SavingContentTypeContainer.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.AddOrUpdate(container); uow.Commit(); - return Attempt.Succeed(container.Id); + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); } catch (Exception ex) { - return Attempt.Fail(ex); + return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); } //TODO: Audit trail ? } } - public void SaveContentTypeContainer(EntityContainer container, int userId = 0) + public Attempt SaveContentTypeContainer(EntityContainer container, int userId = 0) { - SaveContainer(container, Constants.ObjectTypes.DocumentTypeContainerGuid, "document type", userId); + return SaveContainer( + SavingContentTypeContainer, SavedContentTypeContainer, + container, Constants.ObjectTypes.DocumentTypeContainerGuid, "document type", userId); } - public void SaveMediaTypeContainer(EntityContainer container, int userId = 0) + public Attempt SaveMediaTypeContainer(EntityContainer container, int userId = 0) { - SaveContainer(container, Constants.ObjectTypes.MediaTypeContainerGuid, "media type", userId); + return SaveContainer( + SavingMediaTypeContainer, SavedMediaTypeContainer, + container, Constants.ObjectTypes.MediaTypeContainerGuid, "media type", userId); } - private void SaveContainer(EntityContainer container, Guid containerObjectType, string objectTypeName, int userId) + private Attempt SaveContainer( + TypedEventHandler> savingEvent, + TypedEventHandler> savedEvent, + EntityContainer container, + Guid containerObjectType, + string objectTypeName, int userId) { + var evtMsgs = EventMessagesFactory.Get(); + if (container.ContainedObjectType != containerObjectType) - throw new InvalidOperationException("Not a " + objectTypeName + " container."); + { + var ex = new InvalidOperationException("Not a " + objectTypeName + " container."); + return Attempt.Fail(OperationStatus.Exception(evtMsgs, ex), ex); + } + if (container.HasIdentity && container.IsPropertyDirty("ParentId")) - throw new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + { + var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + return Attempt.Fail(OperationStatus.Exception(evtMsgs, ex), ex); + } + + if (savingEvent.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + } var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, containerObjectType)) { repo.AddOrUpdate(container); - uow.Commit(); - //TODO: Audit trail ? + uow.Commit(); } + + savedEvent.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + + //TODO: Audit trail ? + + return Attempt.Succeed(OperationStatus.Success(evtMsgs)); } public EntityContainer GetContentTypeContainer(int containerId) @@ -1172,13 +1220,23 @@ namespace Umbraco.Core.Services uow.Commit(); } } - + #region Event Handlers - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> DeletingContentType; + public static event TypedEventHandler> SavingContentTypeContainer; + public static event TypedEventHandler> SavedContentTypeContainer; + public static event TypedEventHandler> DeletingContentTypeContainer; + public static event TypedEventHandler> DeletedContentTypeContainer; + public static event TypedEventHandler> SavingMediaTypeContainer; + public static event TypedEventHandler> SavedMediaTypeContainer; + public static event TypedEventHandler> DeletingMediaTypeContainer; + public static event TypedEventHandler> DeletedMediaTypeContainer; + + + /// + /// Occurs before Delete + /// + public static event TypedEventHandler> DeletingContentType; /// /// Occurs after Delete diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index 3c6b8404b5..6f1f4b53b3 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -21,10 +21,11 @@ namespace Umbraco.Core.Services /// Attempt ValidateComposition(IContentTypeComposition compo); - Attempt CreateContentTypeContainer(int parentId, string name, int userId = 0); - Attempt CreateMediaTypeContainer(int parentId, string name, int userId = 0); - void SaveContentTypeContainer(EntityContainer container, int userId = 0); - void SaveMediaTypeContainer(EntityContainer container, int userId = 0); + Attempt> CreateContentTypeContainer(int parentId, string name, int userId = 0); + Attempt> CreateMediaTypeContainer(int parentId, string name, int userId = 0); + Attempt SaveContentTypeContainer(EntityContainer container, int userId = 0); + Attempt SaveMediaTypeContainer(EntityContainer container, int userId = 0); + EntityContainer GetContentTypeContainer(int containerId); EntityContainer GetContentTypeContainer(Guid containerId); IEnumerable GetContentTypeContainers(int[] containerIds); diff --git a/src/Umbraco.Core/Services/OperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus.cs index 31b684c4f1..99ca71d95c 100644 --- a/src/Umbraco.Core/Services/OperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus.cs @@ -45,6 +45,12 @@ namespace Umbraco.Core.Services #region Static Helper methods + internal static OperationStatus Exception(EventMessages eventMessages, Exception ex) + { + eventMessages.Add(new EventMessage("", ex.Message, EventMessageType.Error)); + return new OperationStatus(OperationStatusType.FailedExceptionThrown, eventMessages); + } + internal static OperationStatus Cancelled(EventMessages eventMessages) { return new OperationStatus(OperationStatusType.FailedCancelledByEvent, eventMessages); diff --git a/src/Umbraco.Core/Services/OperationStatusType.cs b/src/Umbraco.Core/Services/OperationStatusType.cs index 14f24c5c4e..ea993e6ff6 100644 --- a/src/Umbraco.Core/Services/OperationStatusType.cs +++ b/src/Umbraco.Core/Services/OperationStatusType.cs @@ -16,7 +16,12 @@ namespace Umbraco.Core.Services /// /// The saving has been cancelled by a 3rd party add-in /// - FailedCancelledByEvent = 14 + FailedCancelledByEvent = 14, + + /// + /// Failed, an exception was thrown/handled + /// + FailedExceptionThrown = 15, //TODO: In the future, we might need to add more operations statuses, potentially like 'FailedByPermissions', etc... } diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 43651dd18b..335da4b600 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -480,7 +480,7 @@ namespace Umbraco.Core.Services _logger.Error("Could not create folder: " + rootFolder, tryCreateFolder.Exception); throw tryCreateFolder.Exception; } - var rootFolderId = tryCreateFolder.Result; + var rootFolderId = tryCreateFolder.Result.Entity.Id; current = _contentTypeService.GetContentTypeContainer(rootFolderId); } @@ -514,7 +514,7 @@ namespace Umbraco.Core.Services _logger.Error("Could not create folder: " + folderName, tryCreateFolder.Exception); throw tryCreateFolder.Exception; } - return _contentTypeService.GetContentTypeContainer(tryCreateFolder.Result); + return _contentTypeService.GetContentTypeContainer(tryCreateFolder.Result.Entity.Id); } private IContentType CreateContentTypeFromXml(XElement documentType) From dbb7e3825da5ee1f9a83a9d608096c6f033618ff Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Jan 2016 15:22:58 +0100 Subject: [PATCH 26/29] Fixes events for containers --- src/Umbraco.Core/Services/ContentService.cs | 16 ++-- .../Services/ContentTypeService.cs | 54 +++++++++++--- src/Umbraco.Core/Services/DataTypeService.cs | 74 ++++++++++++++++--- src/Umbraco.Core/Services/DomainService.cs | 8 +- .../Services/IContentTypeService.cs | 4 +- src/Umbraco.Core/Services/IDataTypeService.cs | 6 +- src/Umbraco.Core/Services/MediaService.cs | 16 ++-- src/Umbraco.Core/Services/OperationStatus.cs | 17 +++-- .../Services/OperationStatusType.cs | 7 +- src/Umbraco.Core/Services/PackagingService.cs | 4 +- .../Services/PublicAccessService.cs | 12 +-- 11 files changed, 156 insertions(+), 62 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 680692ae4f..439b18d9c6 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -910,7 +910,7 @@ namespace Umbraco.Core.Services new MoveEventArgs(evtMsgs, new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent)), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var moveInfo = new List> @@ -957,7 +957,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } @@ -1080,7 +1080,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(asArray, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } } using (new WriteLock(Locker)) @@ -1124,7 +1124,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } @@ -1147,7 +1147,7 @@ namespace Umbraco.Core.Services new DeleteEventArgs(content, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } //Make sure that published content is unpublished before being deleted @@ -1178,7 +1178,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } @@ -2043,7 +2043,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(content, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } } @@ -2075,7 +2075,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Save, "Save Content performed by user", userId, content.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 4207149a52..b30a66b376 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -68,13 +68,16 @@ namespace Umbraco.Core.Services repo.AddOrUpdate(container); uow.Commit(); + + SavedContentTypeContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + //TODO: Audit trail ? + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); } catch (Exception ex) { return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); } - //TODO: Audit trail ? } } @@ -93,7 +96,7 @@ namespace Umbraco.Core.Services CreatorId = userId }; - if (SavingContentTypeContainer.IsRaisedEventCancelled( + if (SavingMediaTypeContainer.IsRaisedEventCancelled( new SaveEventArgs(container, evtMsgs), this)) { @@ -102,13 +105,16 @@ namespace Umbraco.Core.Services repo.AddOrUpdate(container); uow.Commit(); + + SavedMediaTypeContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + //TODO: Audit trail ? + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); } catch (Exception ex) { return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); } - //TODO: Audit trail ? } } @@ -138,20 +144,20 @@ namespace Umbraco.Core.Services if (container.ContainedObjectType != containerObjectType) { var ex = new InvalidOperationException("Not a " + objectTypeName + " container."); - return Attempt.Fail(OperationStatus.Exception(evtMsgs, ex), ex); + return OperationStatus.Exception(evtMsgs, ex); } if (container.HasIdentity && container.IsPropertyDirty("ParentId")) { var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); - return Attempt.Fail(OperationStatus.Exception(evtMsgs, ex), ex); + return OperationStatus.Exception(evtMsgs, ex); } if (savingEvent.IsRaisedEventCancelled( new SaveEventArgs(container, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var uow = UowProvider.GetUnitOfWork(); @@ -165,7 +171,7 @@ namespace Umbraco.Core.Services //TODO: Audit trail ? - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } public EntityContainer GetContentTypeContainer(int containerId) @@ -274,28 +280,54 @@ namespace Umbraco.Core.Services } } - public void DeleteContentTypeContainer(int containerId, int userId = 0) + public Attempt DeleteContentTypeContainer(int containerId, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid)) { var container = repo.Get(containerId); - if (container == null) return; + if (container == null) return OperationStatus.NoOperation(evtMsgs); + + if (DeletingContentTypeContainer.IsRaisedEventCancelled( + new DeleteEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.Delete(container); uow.Commit(); + + DeletedContentTypeContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); + + return OperationStatus.Success(evtMsgs); //TODO: Audit trail ? } } - public void DeleteMediaTypeContainer(int containerId, int userId = 0) + public Attempt DeleteMediaTypeContainer(int containerId, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid)) { var container = repo.Get(containerId); - if (container == null) return; + if (container == null) return OperationStatus.NoOperation(evtMsgs); + + if (DeletingMediaTypeContainer.IsRaisedEventCancelled( + new DeleteEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.Delete(container); uow.Commit(); + + DeletedMediaTypeContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); + + return OperationStatus.Success(evtMsgs); //TODO: Audit trail ? } } diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index a397975f04..035cfd0ab6 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -29,8 +29,9 @@ namespace Umbraco.Core.Services #region Containers - public Attempt CreateContainer(int parentId, string name, int userId = 0) + public Attempt> CreateContainer(int parentId, string name, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) { @@ -42,15 +43,26 @@ namespace Umbraco.Core.Services ParentId = parentId, CreatorId = userId }; + + if (SavingContainer.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.AddOrUpdate(container); uow.Commit(); - return Attempt.Succeed(container.Id); + + SavedContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + //TODO: Audit trail ? + + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); } catch (Exception ex) { - return Attempt.Fail(ex); + return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); } - //TODO: Audit trail ? } } @@ -107,31 +119,65 @@ namespace Umbraco.Core.Services } } - public void SaveContainer(EntityContainer container, int userId = 0) + public Attempt SaveContainer(EntityContainer container, int userId = 0) { - if (container.ContainedObjectType != Constants.ObjectTypes.DataTypeGuid) - throw new InvalidOperationException("Not a data type container."); + var evtMsgs = EventMessagesFactory.Get(); + + if (container.ContainedObjectType != Constants.ObjectTypes.DataTypeGuid) + { + var ex = new InvalidOperationException("Not a " + Constants.ObjectTypes.DataTypeGuid + " container."); + return OperationStatus.Exception(evtMsgs, ex); + } + if (container.HasIdentity && container.IsPropertyDirty("ParentId")) - throw new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + { + var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + return OperationStatus.Exception(evtMsgs, ex); + } + + if (SavingContainer.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return OperationStatus.Cancelled(evtMsgs); + } var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) { repo.AddOrUpdate(container); uow.Commit(); - //TODO: Audit trail ? } + + SavedContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + + //TODO: Audit trail ? + + return OperationStatus.Success(evtMsgs); } - public void DeleteContainer(int containerId, int userId = 0) + public Attempt DeleteContainer(int containerId, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) { var container = repo.Get(containerId); - if (container == null) return; + if (container == null) return OperationStatus.NoOperation(evtMsgs); + + if (DeletingContainer.IsRaisedEventCancelled( + new DeleteEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.Delete(container); uow.Commit(); + + DeletedContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); + + return OperationStatus.Success(evtMsgs); //TODO: Audit trail ? } } @@ -537,6 +583,12 @@ namespace Umbraco.Core.Services } #region Event Handlers + + public static event TypedEventHandler> SavingContainer; + public static event TypedEventHandler> SavedContainer; + public static event TypedEventHandler> DeletingContainer; + public static event TypedEventHandler> DeletedContainer; + /// /// Occurs before Delete /// diff --git a/src/Umbraco.Core/Services/DomainService.cs b/src/Umbraco.Core/Services/DomainService.cs index ca9fe03dcb..3ffcb92778 100644 --- a/src/Umbraco.Core/Services/DomainService.cs +++ b/src/Umbraco.Core/Services/DomainService.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Services new DeleteEventArgs(domain, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var uow = UowProvider.GetUnitOfWork(); @@ -45,7 +45,7 @@ namespace Umbraco.Core.Services var args = new DeleteEventArgs(domain, false, evtMsgs); Deleted.RaiseEvent(args, this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } public IDomain GetByName(string name) @@ -91,7 +91,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(domainEntity, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var uow = UowProvider.GetUnitOfWork(); @@ -102,7 +102,7 @@ namespace Umbraco.Core.Services } Saved.RaiseEvent(new SaveEventArgs(domainEntity, false, evtMsgs), this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } #region Event Handlers diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index 6f1f4b53b3..cd905a5ccc 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -36,8 +36,8 @@ namespace Umbraco.Core.Services IEnumerable GetMediaTypeContainers(int[] containerIds); IEnumerable GetMediaTypeContainers(string folderName, int level); IEnumerable GetMediaTypeContainers(IMediaType mediaType); - void DeleteMediaTypeContainer(int folderId, int userId = 0); - void DeleteContentTypeContainer(int containerId, int userId = 0); + Attempt DeleteMediaTypeContainer(int folderId, int userId = 0); + Attempt DeleteContentTypeContainer(int containerId, int userId = 0); /// /// Gets all property type aliases. diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index a94628aa87..9e119cd28a 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -10,14 +10,14 @@ namespace Umbraco.Core.Services /// public interface IDataTypeService : IService { - Attempt CreateContainer(int parentId, string name, int userId = 0); - void SaveContainer(EntityContainer container, int userId = 0); + Attempt> CreateContainer(int parentId, string name, int userId = 0); + Attempt SaveContainer(EntityContainer container, int userId = 0); EntityContainer GetContainer(int containerId); EntityContainer GetContainer(Guid containerId); IEnumerable GetContainers(string folderName, int level); IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition); IEnumerable GetContainers(int[] containerIds); - void DeleteContainer(int containerId, int userId = 0); + Attempt DeleteContainer(int containerId, int userId = 0); /// /// Gets a by its Name diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 0416101728..accbebfe0b 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -747,7 +747,7 @@ namespace Umbraco.Core.Services if (Deleting.IsRaisedEventCancelled( new DeleteEventArgs(media, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } //Delete children before deleting the 'possible parent' @@ -772,7 +772,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// @@ -791,7 +791,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(media, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } } @@ -816,7 +816,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Save, "Save Media performed by user", userId, media.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// @@ -836,7 +836,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(asArray, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } } @@ -864,7 +864,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Save, "Save Media items performed by user", userId, -1); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// @@ -966,7 +966,7 @@ namespace Umbraco.Core.Services if (Trashing.IsRaisedEventCancelled( new MoveEventArgs(new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia)), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var moveInfo = new List> @@ -1008,7 +1008,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// diff --git a/src/Umbraco.Core/Services/OperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus.cs index 99ca71d95c..1561eacbda 100644 --- a/src/Umbraco.Core/Services/OperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus.cs @@ -45,20 +45,25 @@ namespace Umbraco.Core.Services #region Static Helper methods - internal static OperationStatus Exception(EventMessages eventMessages, Exception ex) + internal static Attempt Exception(EventMessages eventMessages, Exception ex) { eventMessages.Add(new EventMessage("", ex.Message, EventMessageType.Error)); - return new OperationStatus(OperationStatusType.FailedExceptionThrown, eventMessages); + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedExceptionThrown, eventMessages), ex); } - internal static OperationStatus Cancelled(EventMessages eventMessages) + internal static Attempt Cancelled(EventMessages eventMessages) { - return new OperationStatus(OperationStatusType.FailedCancelledByEvent, eventMessages); + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, eventMessages)); } - internal static OperationStatus Success(EventMessages eventMessages) + internal static Attempt Success(EventMessages eventMessages) { - return new OperationStatus(OperationStatusType.Success, eventMessages); + return Attempt.Succeed(new OperationStatus(OperationStatusType.Success, eventMessages)); + } + + internal static Attempt NoOperation(EventMessages eventMessages) + { + return Attempt.Succeed(new OperationStatus(OperationStatusType.NoOperation, eventMessages)); } #endregion diff --git a/src/Umbraco.Core/Services/OperationStatusType.cs b/src/Umbraco.Core/Services/OperationStatusType.cs index ea993e6ff6..85ec4a4746 100644 --- a/src/Umbraco.Core/Services/OperationStatusType.cs +++ b/src/Umbraco.Core/Services/OperationStatusType.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Services /// The saving was successful. /// Success = 0, - + /// /// The saving has been cancelled by a 3rd party add-in /// @@ -22,6 +22,11 @@ namespace Umbraco.Core.Services /// Failed, an exception was thrown/handled /// FailedExceptionThrown = 15, + + /// + /// When no operation is executed because it was not needed (i.e. deleting an item that doesn't exist) + /// + NoOperation = 100, //TODO: In the future, we might need to add more operations statuses, potentially like 'FailedByPermissions', etc... } diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 335da4b600..9223cad3da 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -982,7 +982,7 @@ namespace Umbraco.Core.Services _logger.Error("Could not create folder: " + rootFolder, tryCreateFolder.Exception); throw tryCreateFolder.Exception; } - current = _dataTypeService.GetContainer(tryCreateFolder.Result); + current = _dataTypeService.GetContainer(tryCreateFolder.Result.Entity.Id); } importedFolders.Add(name, current.Id); @@ -1015,7 +1015,7 @@ namespace Umbraco.Core.Services _logger.Error("Could not create folder: " + folderName, tryCreateFolder.Exception); throw tryCreateFolder.Exception; } - return _dataTypeService.GetContainer(tryCreateFolder.Result); + return _dataTypeService.GetContainer(tryCreateFolder.Result.Entity.Id); } private void SavePrevaluesFromXml(List dataTypes, IEnumerable dataTypeElements) diff --git a/src/Umbraco.Core/Services/PublicAccessService.cs b/src/Umbraco.Core/Services/PublicAccessService.cs index bb966309dc..c2eb536bca 100644 --- a/src/Umbraco.Core/Services/PublicAccessService.cs +++ b/src/Umbraco.Core/Services/PublicAccessService.cs @@ -183,7 +183,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(entry, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } repo.AddOrUpdate(entry); @@ -191,7 +191,7 @@ namespace Umbraco.Core.Services uow.Commit(); Saved.RaiseEvent(new SaveEventArgs(entry, false, evtMsgs), this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } @@ -207,7 +207,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(entry, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var uow = UowProvider.GetUnitOfWork(); @@ -218,7 +218,7 @@ namespace Umbraco.Core.Services } Saved.RaiseEvent(new SaveEventArgs(entry, false, evtMsgs), this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// @@ -232,7 +232,7 @@ namespace Umbraco.Core.Services new DeleteEventArgs(entry, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var uow = UowProvider.GetUnitOfWork(); @@ -243,7 +243,7 @@ namespace Umbraco.Core.Services } Deleted.RaiseEvent(new DeleteEventArgs(entry, false, evtMsgs), this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// From bd2b6e0f93a2b73a6837c07eb470e42d81abf874 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 27 Jan 2016 15:30:34 +0100 Subject: [PATCH 27/29] Should hopefully fix unit tests --- .../Persistence/Repositories/TemplateRepositoryTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index 864571d5df..b1b822639c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -179,7 +179,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ " + "\t" + @"Layout = null; -}", template.Content); +}".CrLf(), template.Content); } } @@ -209,7 +209,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ " + "\t" + @"Layout = ""test.cshtml""; -}", template2.Content); +}".CrLf(), template2.Content); } } From 1c58360891fd592a2727ec54bf6aed0baafb17ca Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 27 Jan 2016 15:47:19 +0100 Subject: [PATCH 28/29] Another attempt at fixing unit tests --- .../Persistence/Repositories/TemplateRepositoryTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index b1b822639c..cb8e4680b7 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -179,7 +179,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ " + "\t" + @"Layout = null; -}".CrLf(), template.Content); +}".CrLf(), template.Content.CrLf()); } } @@ -209,7 +209,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ " + "\t" + @"Layout = ""test.cshtml""; -}".CrLf(), template2.Content); +}".CrLf(), template2.Content.CrLf()); } } From 2913a0f624a2f7585c1a776e7d85b44df78df7f1 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 27 Jan 2016 17:15:30 +0100 Subject: [PATCH 29/29] With only LineFeeds then...? --- .../Persistence/Repositories/TemplateRepositoryTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index cb8e4680b7..3f168d4741 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -179,7 +179,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ " + "\t" + @"Layout = null; -}".CrLf(), template.Content.CrLf()); +}".Lf(), template.Content.Lf()); } } @@ -209,7 +209,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ " + "\t" + @"Layout = ""test.cshtml""; -}".CrLf(), template2.Content.CrLf()); +}".Lf(), template2.Content.Lf()); } }