diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 65529d27ea..4127ea702e 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -17,7 +17,7 @@ - + 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.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/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); + } + } } diff --git a/src/Umbraco.Core/IO/ViewHelper.cs b/src/Umbraco.Core/IO/ViewHelper.cs index a247f5170e..933a9d2956 100644 --- a/src/Umbraco.Core/IO/ViewHelper.cs +++ b/src/Umbraco.Core/IO/ViewHelper.cs @@ -66,21 +66,58 @@ namespace Umbraco.Core.IO return viewContent; } - public static string GetDefaultFileContent(string layoutPageAlias = null, string modelClassName = null) + public static string GetDefaultFileContent(string layoutPageAlias = null, string modelClassName = null, string modelNamespace = null, string modelNamespaceAlias = null) { - var design = @"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage -@{ - Layout = null; -}"; + var content = new StringBuilder(); - if (layoutPageAlias.IsNullOrWhiteSpace() == false) - design = design.Replace("null", string.Format("\"{0}.cshtml\"", layoutPageAlias)); + if (string.IsNullOrWhiteSpace(modelNamespaceAlias)) + modelNamespaceAlias = "ContentModels"; + // either + // @inherits Umbraco.Web.Mvc.UmbracoTemplatePage + // @inherits Umbraco.Web.Mvc.UmbracoTemplatePage + // @inherits Umbraco.Web.Mvc.UmbracoTemplatePage + content.Append("@inherits Umbraco.Web.Mvc.UmbracoTemplatePage"); if (modelClassName.IsNullOrWhiteSpace() == false) - design = design.Replace(".UmbracoTemplatePage", string.Format(".UmbracoTemplatePage<{0}>", modelClassName)); + { + content.Append("<"); + if (modelNamespace.IsNullOrWhiteSpace() == false) + { + content.Append(modelNamespaceAlias); + content.Append("."); + } + content.Append(modelClassName); + content.Append(">"); + } + content.Append("\r\n"); + // if required, add + // @using ContentModels = ModelNamespace; + if (modelClassName.IsNullOrWhiteSpace() == false && modelNamespace.IsNullOrWhiteSpace() == false) + { + content.Append("@using "); + content.Append(modelNamespaceAlias); + content.Append(" = "); + content.Append(modelNamespace); + content.Append(";\r\n"); + } - return design; + // either + // Layout = null; + // Layout = "layoutPage.cshtml"; + content.Append("@{\r\n\tLayout = "); + if (layoutPageAlias.IsNullOrWhiteSpace()) + { + content.Append("null"); + } + else + { + content.Append("\""); + content.Append(layoutPageAlias); + content.Append(".cshtml\""); + } + content.Append(";\r\n}"); + return content.ToString(); } private string SaveTemplateToFile(ITemplate template) diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 6beead06e6..b3d0f693d9 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -367,8 +367,27 @@ namespace Umbraco.Core.Models /// Value to set for the Property public virtual void SetPropertyValue(string propertyTypeAlias, long value) { - string val = value.ToString(); - SetValueOnProperty(propertyTypeAlias, val); + SetValueOnProperty(propertyTypeAlias, value); + } + + /// + /// Sets the value of a Property + /// + /// Alias of the PropertyType + /// Value to set for the Property + public virtual void SetPropertyValue(string propertyTypeAlias, decimal value) + { + SetValueOnProperty(propertyTypeAlias, value); + } + + /// + /// Sets the value of a Property + /// + /// Alias of the PropertyType + /// Value to set for the Property + public virtual void SetPropertyValue(string propertyTypeAlias, double value) + { + SetValueOnProperty(propertyTypeAlias, value); } /// diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index a85010f8aa..fd061dbda9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -32,11 +32,8 @@ 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; public IEnumerable> Move(TEntity toMove, EntityContainer container) { @@ -1227,71 +1224,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 @@ -1300,7 +1246,7 @@ AND umbracoNode.id <> @id", /// public TEntity Get(Guid id) { - return _guidRepo.Get(id); + return PerformGet(id); } /// @@ -1308,9 +1254,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); } /// @@ -1320,7 +1269,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 fca0f02811..af2c1778c4 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.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; @@ -28,15 +29,22 @@ namespace Umbraco.Core.Persistence.Repositories : base(work, cache, logger, sqlSyntax) { _templateRepository = templateRepository; - } + } + + 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) @@ -255,25 +263,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 df2af380d1..49520387cd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs @@ -6,7 +6,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 6d9331a22a..1cec8005c9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs @@ -1,12 +1,11 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Umbraco.Core.Events; 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 3616a145ce..225aa759d1 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.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; @@ -26,7 +27,17 @@ namespace Umbraco.Core.Persistence.Repositories : base(work, cache, logger, sqlSyntax) { } - + + 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) { var contentTypes = ContentTypeQueryMapper.GetMediaTypes( @@ -149,28 +160,39 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); } - + 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 91b6e67cf9..97c744c43e 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) @@ -256,38 +267,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/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 4ab856cc8d..e2b2c2cc22 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.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/Services/ApplicationTreeService.cs b/src/Umbraco.Core/Services/ApplicationTreeService.cs index c9ce839450..a0a5d8cb82 100644 --- a/src/Umbraco.Core/Services/ApplicationTreeService.cs +++ b/src/Umbraco.Core/Services/ApplicationTreeService.cs @@ -8,7 +8,6 @@ using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Cache; using File = System.IO.File; namespace Umbraco.Core.Services @@ -84,7 +83,7 @@ namespace Umbraco.Core.Services // based on the ApplicationTreeRegistrar - and as noted there this is not an ideal way to do things but were stuck like this // currently because of the legacy assemblies and types not in the Core. - //Get all the trees not registered in the config + //Get all the trees not registered in the config (those not matching by alias casing will be detected as "unregistered") var unregistered = _allAvailableTrees .Where(x => list.Any(l => l.Alias == x.Alias) == false) .ToArray(); @@ -93,19 +92,46 @@ namespace Umbraco.Core.Services if (hasChanges == false) return false; - //add the unregistered ones to the list and re-save the file if any changes were found + //add or edit the unregistered ones and re-save the file if any changes were found var count = 0; foreach (var tree in unregistered) { - doc.Root.Add(new XElement("add", - new XAttribute("initialize", tree.Initialize), - new XAttribute("sortOrder", tree.SortOrder), - new XAttribute("alias", tree.Alias), - new XAttribute("application", tree.ApplicationAlias), - new XAttribute("title", tree.Title), - new XAttribute("iconClosed", tree.IconClosed), - new XAttribute("iconOpen", tree.IconOpened), - new XAttribute("type", tree.Type))); + var existingElement = doc.Root.Elements("add").SingleOrDefault(x => + string.Equals(x.Attribute("alias").Value, tree.Alias, + StringComparison.InvariantCultureIgnoreCase) && + string.Equals(x.Attribute("application").Value, tree.ApplicationAlias, + StringComparison.InvariantCultureIgnoreCase)); + if (existingElement != null) + { + existingElement.SetAttributeValue("alias", tree.Alias); + } + else + { + if (tree.Title.IsNullOrWhiteSpace()) + { + doc.Root.Add(new XElement("add", + new XAttribute("initialize", tree.Initialize), + new XAttribute("sortOrder", tree.SortOrder), + new XAttribute("alias", tree.Alias), + new XAttribute("application", tree.ApplicationAlias), + new XAttribute("iconClosed", tree.IconClosed), + new XAttribute("iconOpen", tree.IconOpened), + new XAttribute("type", tree.Type))); + } + else + { + doc.Root.Add(new XElement("add", + new XAttribute("initialize", tree.Initialize), + new XAttribute("sortOrder", tree.SortOrder), + new XAttribute("alias", tree.Alias), + new XAttribute("application", tree.ApplicationAlias), + new XAttribute("title", tree.Title), + new XAttribute("iconClosed", tree.IconClosed), + new XAttribute("iconOpen", tree.IconOpened), + new XAttribute("type", tree.Type))); + } + + } count++; } @@ -124,11 +150,7 @@ namespace Umbraco.Core.Services } } } - - return list; - - }, new TimeSpan(0, 10, 0)); } @@ -351,13 +373,15 @@ namespace Umbraco.Core.Services { list.Add(new ApplicationTree( addElement.Attribute("initialize") == null || Convert.ToBoolean(addElement.Attribute("initialize").Value), - addElement.Attribute("sortOrder") != null ? Convert.ToByte(addElement.Attribute("sortOrder").Value) : (byte)0, - addElement.Attribute("application").Value, - addElement.Attribute("alias").Value, - addElement.Attribute("title").Value, - addElement.Attribute("iconClosed").Value, - addElement.Attribute("iconOpen").Value, - addElement.Attribute("type").Value)); + addElement.Attribute("sortOrder") != null + ? Convert.ToByte(addElement.Attribute("sortOrder").Value) + : (byte)0, + (string)addElement.Attribute("application"), + (string)addElement.Attribute("alias"), + (string)addElement.Attribute("title"), + (string)addElement.Attribute("iconClosed"), + (string)addElement.Attribute("iconOpen"), + (string)addElement.Attribute("type"))); } } diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index be57b9159f..493768b368 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -387,10 +387,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); } } @@ -787,10 +784,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/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.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 00ca6863ed..3476d1fcc3 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -471,6 +471,7 @@ + @@ -483,6 +484,7 @@ + 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) diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index 76d1220ea3..f0d423cda5 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -69,7 +69,123 @@ namespace Umbraco.Tests.Models.Mapping entityMapper.ConfigureMappings(configuration, appContext); }); } - + + [Test] + public void MemberTypeSave_To_IMemberType() + { + //Arrange + + // setup the mocks to return the data we want to test against... + + _dataTypeService.Setup(x => x.GetDataTypeDefinitionById(It.IsAny())) + .Returns(Mock.Of( + definition => + definition.Id == 555 + && definition.PropertyEditorAlias == "myPropertyType" + && definition.DatabaseType == DataTypeDatabaseType.Nvarchar)); + + var display = CreateMemberTypeSave(); + + //Act + + var result = Mapper.Map(display); + + //Assert + + Assert.AreEqual(display.Alias, result.Alias); + Assert.AreEqual(display.Description, result.Description); + Assert.AreEqual(display.Icon, result.Icon); + Assert.AreEqual(display.Id, result.Id); + Assert.AreEqual(display.Name, result.Name); + Assert.AreEqual(display.ParentId, result.ParentId); + Assert.AreEqual(display.Path, result.Path); + Assert.AreEqual(display.Thumbnail, result.Thumbnail); + Assert.AreEqual(display.IsContainer, result.IsContainer); + Assert.AreEqual(display.AllowAsRoot, result.AllowedAsRoot); + Assert.AreEqual(display.CreateDate, result.CreateDate); + Assert.AreEqual(display.UpdateDate, result.UpdateDate); + + //TODO: Now we need to assert all of the more complicated parts + Assert.AreEqual(display.Groups.Count(), result.PropertyGroups.Count); + for (var i = 0; i < display.Groups.Count(); i++) + { + Assert.AreEqual(display.Groups.ElementAt(i).Id, result.PropertyGroups.ElementAt(i).Id); + Assert.AreEqual(display.Groups.ElementAt(i).Name, result.PropertyGroups.ElementAt(i).Name); + var propTypes = display.Groups.ElementAt(i).Properties; + Assert.AreEqual(propTypes.Count(), result.PropertyTypes.Count()); + for (var j = 0; j < propTypes.Count(); j++) + { + Assert.AreEqual(propTypes.ElementAt(j).Id, result.PropertyTypes.ElementAt(j).Id); + Assert.AreEqual(propTypes.ElementAt(j).DataTypeId, result.PropertyTypes.ElementAt(j).DataTypeDefinitionId); + Assert.AreEqual(propTypes.ElementAt(j).MemberCanViewProperty, result.MemberCanViewProperty(result.PropertyTypes.ElementAt(j).Alias)); + Assert.AreEqual(propTypes.ElementAt(j).MemberCanEditProperty, result.MemberCanEditProperty(result.PropertyTypes.ElementAt(j).Alias)); + } + } + + Assert.AreEqual(display.AllowedContentTypes.Count(), result.AllowedContentTypes.Count()); + for (var i = 0; i < display.AllowedContentTypes.Count(); i++) + { + Assert.AreEqual(display.AllowedContentTypes.ElementAt(i), result.AllowedContentTypes.ElementAt(i).Id.Value); + } + } + + [Test] + public void MediaTypeSave_To_IMediaType() + { + //Arrange + + // setup the mocks to return the data we want to test against... + + _dataTypeService.Setup(x => x.GetDataTypeDefinitionById(It.IsAny())) + .Returns(Mock.Of( + definition => + definition.Id == 555 + && definition.PropertyEditorAlias == "myPropertyType" + && definition.DatabaseType == DataTypeDatabaseType.Nvarchar)); + + var display = CreateMediaTypeSave(); + + //Act + + var result = Mapper.Map(display); + + //Assert + + Assert.AreEqual(display.Alias, result.Alias); + Assert.AreEqual(display.Description, result.Description); + Assert.AreEqual(display.Icon, result.Icon); + Assert.AreEqual(display.Id, result.Id); + Assert.AreEqual(display.Name, result.Name); + Assert.AreEqual(display.ParentId, result.ParentId); + Assert.AreEqual(display.Path, result.Path); + Assert.AreEqual(display.Thumbnail, result.Thumbnail); + Assert.AreEqual(display.IsContainer, result.IsContainer); + Assert.AreEqual(display.AllowAsRoot, result.AllowedAsRoot); + Assert.AreEqual(display.CreateDate, result.CreateDate); + Assert.AreEqual(display.UpdateDate, result.UpdateDate); + + //TODO: Now we need to assert all of the more complicated parts + Assert.AreEqual(display.Groups.Count(), result.PropertyGroups.Count); + for (var i = 0; i < display.Groups.Count(); i++) + { + Assert.AreEqual(display.Groups.ElementAt(i).Id, result.PropertyGroups.ElementAt(i).Id); + Assert.AreEqual(display.Groups.ElementAt(i).Name, result.PropertyGroups.ElementAt(i).Name); + var propTypes = display.Groups.ElementAt(i).Properties; + Assert.AreEqual(propTypes.Count(), result.PropertyTypes.Count()); + for (var j = 0; j < propTypes.Count(); j++) + { + Assert.AreEqual(propTypes.ElementAt(j).Id, result.PropertyTypes.ElementAt(j).Id); + Assert.AreEqual(propTypes.ElementAt(j).DataTypeId, result.PropertyTypes.ElementAt(j).DataTypeDefinitionId); + } + } + + Assert.AreEqual(display.AllowedContentTypes.Count(), result.AllowedContentTypes.Count()); + for (var i = 0; i < display.AllowedContentTypes.Count(); i++) + { + Assert.AreEqual(display.AllowedContentTypes.ElementAt(i), result.AllowedContentTypes.ElementAt(i).Id.Value); + } + } + [Test] public void ContentTypeSave_To_IContentType() { @@ -144,6 +260,33 @@ namespace Umbraco.Tests.Models.Mapping } } + [Test] + public void MediaTypeSave_With_Composition_To_IMediaType() + { + //Arrange + + // setup the mocks to return the data we want to test against... + + _dataTypeService.Setup(x => x.GetDataTypeDefinitionById(It.IsAny())) + .Returns(Mock.Of( + definition => + definition.Id == 555 + && definition.PropertyEditorAlias == "myPropertyType" + && definition.DatabaseType == DataTypeDatabaseType.Nvarchar)); + + + var display = CreateCompositionMediaTypeSave(); + + //Act + + var result = Mapper.Map(display); + + //Assert + + //TODO: Now we need to assert all of the more complicated parts + Assert.AreEqual(display.Groups.Count(x => x.Inherited == false), result.PropertyGroups.Count); + } + [Test] public void ContentTypeSave_With_Composition_To_IContentType() { @@ -171,6 +314,141 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(display.Groups.Count(x => x.Inherited == false), result.PropertyGroups.Count); } + [Test] + public void IMemberType_To_MemberTypeDisplay() + { + //Arrange + + // setup the mocks to return the data we want to test against... + + // for any call to GetPreValuesCollectionByDataTypeId just return an empty dictionary for now + // TODO: but we'll need to change this to return some pre-values to test the mappings + _dataTypeService.Setup(x => x.GetPreValuesCollectionByDataTypeId(It.IsAny())) + .Returns(new PreValueCollection(new Dictionary())); + + //return a textbox property editor for any requested editor by alias + _propertyEditorResolver.Setup(resolver => resolver.GetByAlias(It.IsAny())) + .Returns(new TextboxPropertyEditor()); + //for testing, just return a list of whatever property editors we want + _propertyEditorResolver.Setup(resolver => resolver.PropertyEditors) + .Returns(new[] { new TextboxPropertyEditor() }); + + var memberType = MockedContentTypes.CreateSimpleMemberType(); + memberType.MemberTypePropertyTypes[memberType.PropertyTypes.Last().Alias] = new MemberTypePropertyProfileAccess(true, true); + + MockedContentTypes.EnsureAllIds(memberType, 8888); + + //Act + + var result = Mapper.Map(memberType); + + //Assert + + Assert.AreEqual(memberType.Alias, result.Alias); + Assert.AreEqual(memberType.Description, result.Description); + Assert.AreEqual(memberType.Icon, result.Icon); + Assert.AreEqual(memberType.Id, result.Id); + Assert.AreEqual(memberType.Name, result.Name); + Assert.AreEqual(memberType.ParentId, result.ParentId); + Assert.AreEqual(memberType.Path, result.Path); + Assert.AreEqual(memberType.Thumbnail, result.Thumbnail); + Assert.AreEqual(memberType.IsContainer, result.IsContainer); + Assert.AreEqual(memberType.CreateDate, result.CreateDate); + Assert.AreEqual(memberType.UpdateDate, result.UpdateDate); + + //TODO: Now we need to assert all of the more complicated parts + + Assert.AreEqual(memberType.PropertyGroups.Count(), result.Groups.Count()); + for (var i = 0; i < memberType.PropertyGroups.Count(); i++) + { + Assert.AreEqual(memberType.PropertyGroups.ElementAt(i).Id, result.Groups.ElementAt(i).Id); + Assert.AreEqual(memberType.PropertyGroups.ElementAt(i).Name, result.Groups.ElementAt(i).Name); + var propTypes = memberType.PropertyGroups.ElementAt(i).PropertyTypes; + + Assert.AreEqual(propTypes.Count(), result.Groups.ElementAt(i).Properties.Count()); + for (var j = 0; j < propTypes.Count(); j++) + { + Assert.AreEqual(propTypes.ElementAt(j).Id, result.Groups.ElementAt(i).Properties.ElementAt(j).Id); + Assert.AreEqual(propTypes.ElementAt(j).DataTypeDefinitionId, result.Groups.ElementAt(i).Properties.ElementAt(j).DataTypeId); + + Assert.AreEqual(memberType.MemberCanViewProperty(propTypes.ElementAt(j).Alias), result.Groups.ElementAt(i).Properties.ElementAt(j).MemberCanViewProperty); + Assert.AreEqual(memberType.MemberCanEditProperty(propTypes.ElementAt(j).Alias), result.Groups.ElementAt(i).Properties.ElementAt(j).MemberCanEditProperty); + } + } + + Assert.AreEqual(memberType.AllowedContentTypes.Count(), result.AllowedContentTypes.Count()); + for (var i = 0; i < memberType.AllowedContentTypes.Count(); i++) + { + Assert.AreEqual(memberType.AllowedContentTypes.ElementAt(i).Id.Value, result.AllowedContentTypes.ElementAt(i)); + } + + } + + [Test] + public void IMediaType_To_MediaTypeDisplay() + { + //Arrange + + // setup the mocks to return the data we want to test against... + + // for any call to GetPreValuesCollectionByDataTypeId just return an empty dictionary for now + // TODO: but we'll need to change this to return some pre-values to test the mappings + _dataTypeService.Setup(x => x.GetPreValuesCollectionByDataTypeId(It.IsAny())) + .Returns(new PreValueCollection(new Dictionary())); + + //return a textbox property editor for any requested editor by alias + _propertyEditorResolver.Setup(resolver => resolver.GetByAlias(It.IsAny())) + .Returns(new TextboxPropertyEditor()); + //for testing, just return a list of whatever property editors we want + _propertyEditorResolver.Setup(resolver => resolver.PropertyEditors) + .Returns(new[] { new TextboxPropertyEditor() }); + + var mediaType = MockedContentTypes.CreateImageMediaType(); + MockedContentTypes.EnsureAllIds(mediaType, 8888); + + //Act + + var result = Mapper.Map(mediaType); + + //Assert + + Assert.AreEqual(mediaType.Alias, result.Alias); + Assert.AreEqual(mediaType.Description, result.Description); + Assert.AreEqual(mediaType.Icon, result.Icon); + Assert.AreEqual(mediaType.Id, result.Id); + Assert.AreEqual(mediaType.Name, result.Name); + Assert.AreEqual(mediaType.ParentId, result.ParentId); + Assert.AreEqual(mediaType.Path, result.Path); + Assert.AreEqual(mediaType.Thumbnail, result.Thumbnail); + Assert.AreEqual(mediaType.IsContainer, result.IsContainer); + Assert.AreEqual(mediaType.CreateDate, result.CreateDate); + Assert.AreEqual(mediaType.UpdateDate, result.UpdateDate); + + //TODO: Now we need to assert all of the more complicated parts + + Assert.AreEqual(mediaType.PropertyGroups.Count(), result.Groups.Count()); + for (var i = 0; i < mediaType.PropertyGroups.Count(); i++) + { + Assert.AreEqual(mediaType.PropertyGroups.ElementAt(i).Id, result.Groups.ElementAt(i).Id); + Assert.AreEqual(mediaType.PropertyGroups.ElementAt(i).Name, result.Groups.ElementAt(i).Name); + var propTypes = mediaType.PropertyGroups.ElementAt(i).PropertyTypes; + + Assert.AreEqual(propTypes.Count(), result.Groups.ElementAt(i).Properties.Count()); + for (var j = 0; j < propTypes.Count(); j++) + { + Assert.AreEqual(propTypes.ElementAt(j).Id, result.Groups.ElementAt(i).Properties.ElementAt(j).Id); + Assert.AreEqual(propTypes.ElementAt(j).DataTypeDefinitionId, result.Groups.ElementAt(i).Properties.ElementAt(j).DataTypeId); + } + } + + Assert.AreEqual(mediaType.AllowedContentTypes.Count(), result.AllowedContentTypes.Count()); + for (var i = 0; i < mediaType.AllowedContentTypes.Count(); i++) + { + Assert.AreEqual(mediaType.AllowedContentTypes.ElementAt(i).Id.Value, result.AllowedContentTypes.ElementAt(i)); + } + + } + [Test] public void IContentType_To_ContentTypeDisplay() { @@ -195,7 +473,7 @@ namespace Umbraco.Tests.Models.Mapping //Act - var result = Mapper.Map(contentType); + var result = Mapper.Map(contentType); //Assert @@ -243,6 +521,75 @@ namespace Umbraco.Tests.Models.Mapping } + [Test] + public void MemberPropertyGroupBasic_To_MemberPropertyGroup() + { + _dataTypeService.Setup(x => x.GetDataTypeDefinitionById(It.IsAny())) + .Returns(new DataTypeDefinition("test")); + + var basic = new PropertyGroupBasic + { + Id = 222, + Name = "Group 1", + SortOrder = 1, + Properties = new[] + { + new MemberPropertyTypeBasic() + { + MemberCanEditProperty = true, + MemberCanViewProperty = true, + Id = 33, + SortOrder = 1, + Alias = "prop1", + Description = "property 1", + DataTypeId = 99, + GroupId = 222, + Label = "Prop 1", + Validation = new PropertyTypeValidation() + { + Mandatory = true, + Pattern = null + } + }, + new MemberPropertyTypeBasic() + { + MemberCanViewProperty = false, + MemberCanEditProperty = false, + Id = 34, + SortOrder = 2, + Alias = "prop2", + Description = "property 2", + DataTypeId = 99, + GroupId = 222, + Label = "Prop 2", + Validation = new PropertyTypeValidation() + { + Mandatory = false, + Pattern = null + } + }, + } + }; + + var contentType = new MemberTypeSave + { + Id = 0, + ParentId = -1, + Alias = "alias", + Groups = new[] { basic } + }; + + // proper group properties mapping takes place when mapping the content type, + // not when mapping the group - because of inherited properties and such + //var result = Mapper.Map(basic); + var result = Mapper.Map(contentType).PropertyGroups[0]; + + Assert.AreEqual(basic.Name, result.Name); + Assert.AreEqual(basic.Id, result.Id); + Assert.AreEqual(basic.SortOrder, result.SortOrder); + Assert.AreEqual(basic.Properties.Count(), result.PropertyTypes.Count()); + } + [Test] public void PropertyGroupBasic_To_PropertyGroup() { @@ -289,7 +636,7 @@ namespace Umbraco.Tests.Models.Mapping } }; - var contentType = new ContentTypeSave + var contentType = new DocumentTypeSave { Id = 0, ParentId = -1, @@ -309,6 +656,40 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(basic.Properties.Count(), result.PropertyTypes.Count()); } + [Test] + public void MemberPropertyTypeBasic_To_PropertyType() + { + _dataTypeService.Setup(x => x.GetDataTypeDefinitionById(It.IsAny())) + .Returns(new DataTypeDefinition("test")); + + var basic = new MemberPropertyTypeBasic() + { + Id = 33, + SortOrder = 1, + Alias = "prop1", + Description = "property 1", + DataTypeId = 99, + GroupId = 222, + Label = "Prop 1", + Validation = new PropertyTypeValidation() + { + Mandatory = true, + Pattern = "xyz" + } + }; + + var result = Mapper.Map(basic); + + Assert.AreEqual(basic.Id, result.Id); + Assert.AreEqual(basic.SortOrder, result.SortOrder); + Assert.AreEqual(basic.Alias, result.Alias); + Assert.AreEqual(basic.Description, result.Description); + Assert.AreEqual(basic.DataTypeId, result.DataTypeDefinitionId); + Assert.AreEqual(basic.Label, result.Name); + Assert.AreEqual(basic.Validation.Mandatory, result.Mandatory); + Assert.AreEqual(basic.Validation.Pattern, result.ValidationRegExp); + } + [Test] public void PropertyTypeBasic_To_PropertyType() { @@ -343,6 +724,109 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(basic.Validation.Pattern, result.ValidationRegExp); } + [Test] + public void IMediaTypeComposition_To_MediaTypeDisplay() + { + //Arrange + + // setup the mocks to return the data we want to test against... + + // for any call to GetPreValuesCollectionByDataTypeId just return an empty dictionary for now + // TODO: but we'll need to change this to return some pre-values to test the mappings + _dataTypeService.Setup(x => x.GetPreValuesCollectionByDataTypeId(It.IsAny())) + .Returns(new PreValueCollection(new Dictionary())); + + _entityService.Setup(x => x.GetObjectType(It.IsAny())) + .Returns(UmbracoObjectTypes.DocumentType); + + //return a textbox property editor for any requested editor by alias + _propertyEditorResolver.Setup(resolver => resolver.GetByAlias(It.IsAny())) + .Returns(new TextboxPropertyEditor()); + //for testing, just return a list of whatever property editors we want + _propertyEditorResolver.Setup(resolver => resolver.PropertyEditors) + .Returns(new[] { new TextboxPropertyEditor() }); + + var ctMain = MockedContentTypes.CreateSimpleMediaType("parent", "Parent"); + //not assigned to tab + ctMain.AddPropertyType(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "umbracoUrlName", + Name = "Slug", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeDefinitionId = -88 + }); + MockedContentTypes.EnsureAllIds(ctMain, 8888); + var ctChild1 = MockedContentTypes.CreateSimpleMediaType("child1", "Child 1", ctMain, true); + ctChild1.AddPropertyType(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "someProperty", + Name = "Some Property", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeDefinitionId = -88 + }, "Another tab"); + MockedContentTypes.EnsureAllIds(ctChild1, 7777); + var contentType = MockedContentTypes.CreateSimpleMediaType("child2", "Child 2", ctChild1, true, "CustomGroup"); + //not assigned to tab + contentType.AddPropertyType(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext) + { + Alias = "umbracoUrlAlias", + Name = "AltUrl", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeDefinitionId = -88 + }); + MockedContentTypes.EnsureAllIds(contentType, 6666); + + + //Act + + var result = Mapper.Map(contentType); + + //Assert + + Assert.AreEqual(contentType.Alias, result.Alias); + Assert.AreEqual(contentType.Description, result.Description); + Assert.AreEqual(contentType.Icon, result.Icon); + Assert.AreEqual(contentType.Id, result.Id); + Assert.AreEqual(contentType.Name, result.Name); + Assert.AreEqual(contentType.ParentId, result.ParentId); + Assert.AreEqual(contentType.Path, result.Path); + Assert.AreEqual(contentType.Thumbnail, result.Thumbnail); + Assert.AreEqual(contentType.IsContainer, result.IsContainer); + Assert.AreEqual(contentType.CreateDate, result.CreateDate); + Assert.AreEqual(contentType.UpdateDate, result.UpdateDate); + + //TODO: Now we need to assert all of the more complicated parts + + Assert.AreEqual(contentType.CompositionPropertyGroups.Select(x => x.Name).Distinct().Count(), result.Groups.Count(x => x.IsGenericProperties == false)); + Assert.AreEqual(1, result.Groups.Count(x => x.IsGenericProperties)); + Assert.AreEqual(contentType.PropertyGroups.Count(), result.Groups.Count(x => x.Inherited == false && x.IsGenericProperties == false)); + + var allPropertiesMapped = result.Groups.SelectMany(x => x.Properties).ToArray(); + var allPropertyIdsMapped = allPropertiesMapped.Select(x => x.Id).ToArray(); + var allSourcePropertyIds = contentType.CompositionPropertyTypes.Select(x => x.Id).ToArray(); + + Assert.AreEqual(contentType.PropertyTypes.Count(), allPropertiesMapped.Count(x => x.Inherited == false)); + Assert.AreEqual(allPropertyIdsMapped.Count(), allSourcePropertyIds.Count()); + Assert.IsTrue(allPropertyIdsMapped.ContainsAll(allSourcePropertyIds)); + + Assert.AreEqual(2, result.Groups.Count(x => x.ParentTabContentTypes.Any())); + Assert.IsTrue(result.Groups.SelectMany(x => x.ParentTabContentTypes).ContainsAll(new[] { ctMain.Id, ctChild1.Id })); + + + Assert.AreEqual(contentType.AllowedContentTypes.Count(), result.AllowedContentTypes.Count()); + for (var i = 0; i < contentType.AllowedContentTypes.Count(); i++) + { + Assert.AreEqual(contentType.AllowedContentTypes.ElementAt(i).Id.Value, result.AllowedContentTypes.ElementAt(i)); + } + + } + [Test] public void IContentTypeComposition_To_ContentTypeDisplay() { @@ -394,7 +878,7 @@ namespace Umbraco.Tests.Models.Mapping //Act - var result = Mapper.Map(contentType); + var result = Mapper.Map(contentType); //Assert @@ -442,10 +926,175 @@ namespace Umbraco.Tests.Models.Mapping } + [Test] + public void MemberPropertyTypeBasic_To_MemberPropertyTypeDisplay() + { + _dataTypeService.Setup(x => x.GetDataTypeDefinitionById(It.IsAny())) + .Returns(new DataTypeDefinition("test")); - private ContentTypeSave CreateContentTypeSave() + var basic = new MemberPropertyTypeBasic() + { + Id = 33, + SortOrder = 1, + Alias = "prop1", + Description = "property 1", + DataTypeId = 99, + GroupId = 222, + Label = "Prop 1", + MemberCanViewProperty = true, + MemberCanEditProperty = true, + Validation = new PropertyTypeValidation() + { + Mandatory = true, + Pattern = "xyz" + } + }; + + var result = Mapper.Map(basic); + + Assert.AreEqual(basic.Id, result.Id); + Assert.AreEqual(basic.SortOrder, result.SortOrder); + Assert.AreEqual(basic.Alias, result.Alias); + Assert.AreEqual(basic.Description, result.Description); + Assert.AreEqual(basic.GroupId, result.GroupId); + Assert.AreEqual(basic.Inherited, result.Inherited); + Assert.AreEqual(basic.Label, result.Label); + Assert.AreEqual(basic.Validation, result.Validation); + Assert.AreEqual(basic.MemberCanViewProperty, result.MemberCanViewProperty); + Assert.AreEqual(basic.MemberCanEditProperty, result.MemberCanEditProperty); + } + + [Test] + public void PropertyTypeBasic_To_PropertyTypeDisplay() + { + _dataTypeService.Setup(x => x.GetDataTypeDefinitionById(It.IsAny())) + .Returns(new DataTypeDefinition("test")); + + var basic = new PropertyTypeBasic() + { + Id = 33, + SortOrder = 1, + Alias = "prop1", + Description = "property 1", + DataTypeId = 99, + GroupId = 222, + Label = "Prop 1", + Validation = new PropertyTypeValidation() + { + Mandatory = true, + Pattern = "xyz" + } + }; + + var result = Mapper.Map(basic); + + Assert.AreEqual(basic.Id, result.Id); + Assert.AreEqual(basic.SortOrder, result.SortOrder); + Assert.AreEqual(basic.Alias, result.Alias); + Assert.AreEqual(basic.Description, result.Description); + Assert.AreEqual(basic.GroupId, result.GroupId); + Assert.AreEqual(basic.Inherited, result.Inherited); + Assert.AreEqual(basic.Label, result.Label); + Assert.AreEqual(basic.Validation, result.Validation); + } + + private MemberTypeSave CreateMemberTypeSave() + { + return new MemberTypeSave + { + Alias = "test", + AllowAsRoot = true, + AllowedContentTypes = new[] { 666, 667 }, + Description = "hello world", + Icon = "tree-icon", + Id = 1234, + Key = new Guid("8A60656B-3866-46AB-824A-48AE85083070"), + Name = "My content type", + Path = "-1,1234", + ParentId = -1, + Thumbnail = "tree-thumb", + IsContainer = true, + Groups = new[] + { + new PropertyGroupBasic() + { + Id = 987, + Name = "Tab 1", + SortOrder = 0, + Inherited = false, + Properties = new [] + { + new MemberPropertyTypeBasic + { + MemberCanEditProperty = true, + MemberCanViewProperty = true, + Alias = "property1", + Description = "this is property 1", + Inherited = false, + Label = "Property 1", + Validation = new PropertyTypeValidation + { + Mandatory = false, + Pattern = "" + }, + SortOrder = 0, + DataTypeId = 555 + } + } + } + } + }; + } + + private MediaTypeSave CreateMediaTypeSave() + { + return new MediaTypeSave + { + Alias = "test", + AllowAsRoot = true, + AllowedContentTypes = new[] { 666, 667 }, + Description = "hello world", + Icon = "tree-icon", + Id = 1234, + Key = new Guid("8A60656B-3866-46AB-824A-48AE85083070"), + Name = "My content type", + Path = "-1,1234", + ParentId = -1, + Thumbnail = "tree-thumb", + IsContainer = true, + Groups = new[] + { + new PropertyGroupBasic() + { + Id = 987, + Name = "Tab 1", + SortOrder = 0, + Inherited = false, + Properties = new [] + { + new PropertyTypeBasic + { + Alias = "property1", + Description = "this is property 1", + Inherited = false, + Label = "Property 1", + Validation = new PropertyTypeValidation + { + Mandatory = false, + Pattern = "" + }, + SortOrder = 0, + DataTypeId = 555 + } + } + } + } + }; + } + + private DocumentTypeSave CreateContentTypeSave() { - return new ContentTypeSave + return new DocumentTypeSave { Alias = "test", AllowAsRoot = true, @@ -495,9 +1144,81 @@ namespace Umbraco.Tests.Models.Mapping }; } - private ContentTypeSave CreateCompositionContentTypeSave() + private MediaTypeSave CreateCompositionMediaTypeSave() { - return new ContentTypeSave + return new MediaTypeSave + { + Alias = "test", + AllowAsRoot = true, + AllowedContentTypes = new[] { 666, 667 }, + Description = "hello world", + Icon = "tree-icon", + Id = 1234, + Key = new Guid("8A60656B-3866-46AB-824A-48AE85083070"), + Name = "My content type", + Path = "-1,1234", + ParentId = -1, + Thumbnail = "tree-thumb", + IsContainer = true, + Groups = new[] + { + new PropertyGroupBasic() + { + Id = 987, + Name = "Tab 1", + SortOrder = 0, + Inherited = false, + Properties = new[] + { + new PropertyTypeBasic + { + Alias = "property1", + Description = "this is property 1", + Inherited = false, + Label = "Property 1", + Validation = new PropertyTypeValidation + { + Mandatory = false, + Pattern = "" + }, + SortOrder = 0, + DataTypeId = 555 + } + } + }, + new PropertyGroupBasic() + { + Id = 894, + Name = "Tab 2", + SortOrder = 0, + Inherited = true, + Properties = new[] + { + new PropertyTypeBasic + { + Alias = "parentProperty", + Description = "this is a property from the parent", + Inherited = true, + Label = "Parent property", + Validation = new PropertyTypeValidation + { + Mandatory = false, + Pattern = "" + }, + SortOrder = 0, + DataTypeId = 555 + } + } + + } + } + + }; + } + + private DocumentTypeSave CreateCompositionContentTypeSave() + { + return new DocumentTypeSave { Alias = "test", AllowAsRoot = true, diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 7be732fecb..257c7c9612 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 // there is NO mapping from display to contentType, but only from save // to contentType, so if we want to test, let's to it properly! - var display = Mapper.Map(contentType); + var display = Mapper.Map(contentType); var save = MapToContentTypeSave(display); var mapped = Mapper.Map(save); @@ -358,9 +358,9 @@ namespace Umbraco.Tests.Persistence.Repositories // this is for tests only because it makes no sense at all to have such a // mapping defined, we only need it for the weird tests that use it - private ContentTypeSave MapToContentTypeSave(ContentTypeDisplay display) + private DocumentTypeSave MapToContentTypeSave(DocumentTypeDisplay display) { - return new ContentTypeSave + return new DocumentTypeSave { // EntityBasic Name = display.Name, @@ -410,7 +410,7 @@ namespace Umbraco.Tests.Persistence.Repositories // there is NO mapping from display to contentType, but only from save // to contentType, so if we want to test, let's to it properly! - var display = Mapper.Map(contentType); + var display = Mapper.Map(contentType); var save = MapToContentTypeSave(display); // modify... @@ -603,7 +603,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 07a2ad4a93..42d95bc487 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -340,7 +340,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.Tests/Templates/ViewHelperTests.cs b/src/Umbraco.Tests/Templates/ViewHelperTests.cs new file mode 100644 index 0000000000..5bd7733920 --- /dev/null +++ b/src/Umbraco.Tests/Templates/ViewHelperTests.cs @@ -0,0 +1,91 @@ +using NUnit.Framework; +using Umbraco.Core.IO; + +namespace Umbraco.Tests.Templates +{ + [TestFixture] + public class ViewHelperTests + { + [Test] + public void NoOptions() + { + var view = ViewHelper.GetDefaultFileContent(); + Assert.AreEqual(FixView(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage +@{ + Layout = null; +}"), FixView(view)); + } + + [Test] + public void Layout() + { + var view = ViewHelper.GetDefaultFileContent(layoutPageAlias: "Dharznoik"); + Assert.AreEqual(FixView(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage +@{ + Layout = ""Dharznoik.cshtml""; +}"), FixView(view)); + } + + [Test] + public void ClassName() + { + var view = ViewHelper.GetDefaultFileContent(modelClassName: "ClassName"); + Assert.AreEqual(FixView(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage +@{ + Layout = null; +}"), FixView(view)); + } + + [Test] + public void Namespace() + { + var view = ViewHelper.GetDefaultFileContent(modelNamespace: "Models"); + Assert.AreEqual(FixView(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage +@{ + Layout = null; +}"), FixView(view)); + } + + [Test] + public void ClassNameAndNamespace() + { + var view = ViewHelper.GetDefaultFileContent(modelClassName: "ClassName", modelNamespace: "My.Models"); + Assert.AreEqual(FixView(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage +@using ContentModels = My.Models; +@{ + Layout = null; +}"), FixView(view)); + } + + [Test] + public void ClassNameAndNamespaceAndAlias() + { + var view = ViewHelper.GetDefaultFileContent(modelClassName: "ClassName", modelNamespace: "My.Models", modelNamespaceAlias: "MyModels"); + Assert.AreEqual(FixView(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage +@using MyModels = My.Models; +@{ + Layout = null; +}"), FixView(view)); + } + + [Test] + public void Combined() + { + var view = ViewHelper.GetDefaultFileContent(layoutPageAlias: "Dharznoik", modelClassName: "ClassName", modelNamespace: "My.Models", modelNamespaceAlias: "MyModels"); + Assert.AreEqual(FixView(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage +@using MyModels = My.Models; +@{ + Layout = ""Dharznoik.cshtml""; +}"), FixView(view)); + } + + private static string FixView(string view) + { + view = view.Replace("\r\n", "\n"); + view = view.Replace("\r", "\n"); + view = view.Replace("\n", "\r\n"); + view = view.Replace("\t", " "); + return view; + } + } +} diff --git a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs index 3eb0df882a..786053e680 100644 --- a/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs +++ b/src/Umbraco.Tests/TestHelpers/Entities/MockedContentTypes.cs @@ -431,7 +431,7 @@ namespace Umbraco.Tests.TestHelpers.Entities return contentType; } - public static void EnsureAllIds(ContentType contentType, int seedId) + public static void EnsureAllIds(ContentTypeCompositionBase contentType, int seedId) { //ensure everything has ids contentType.Id = seedId; @@ -446,6 +446,7 @@ namespace Umbraco.Tests.TestHelpers.Entities } } + private static string RandomAlias(string alias, bool randomizeAliases) { if (randomizeAliases) diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 5bb59eaa04..2f048f12b5 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -369,6 +369,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js index 0d8ddc2170..d993880e8c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbcontentgrid.directive.js @@ -5,16 +5,15 @@ function link(scope, el, attr, ctrl) { - scope.clickItem = function(item) { + scope.clickItem = function(item, $event, $index) { if(scope.onClick) { - scope.onClick(item); + scope.onClick(item, $event, $index); } }; - scope.selectItem = function(item, $event, $index) { - if(scope.onSelect) { - scope.onSelect(item, $event, $index); - $event.stopPropagation(); + scope.clickItemName = function(item, $event, $index) { + if(scope.onClickName) { + scope.onClickName(item, $event, $index); } }; @@ -27,8 +26,8 @@ scope: { content: '=', contentProperties: "=", - onSelect: '=', - onClick: "=" + onClick: "=", + onClickName: "=" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index 336956ff4c..81c6a6612e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -173,6 +173,11 @@ scope.compositionsDialogModel.availableCompositeContentTypes = result; //iterate each one and set it up _.each(scope.compositionsDialogModel.availableCompositeContentTypes, function (c) { + //enable it if it's part of the selected model + if (scope.compositionsDialogModel.compositeContentTypes.indexOf(c.contentType.alias) !== -1) { + c.allowed = true; + } + //set the inherited flags c.inherited = false; if (scope.model.lockedCompositeContentTypes.indexOf(c.contentType.alias) > -1) { @@ -380,6 +385,7 @@ scope.removeGroup = function(groupIndex) { scope.model.groups.splice(groupIndex, 1); + addInitGroup(scope.model.groups); }; scope.updateGroupTitle = function(group) { @@ -445,6 +451,7 @@ scope.propertySettingsDialogModel = {}; scope.propertySettingsDialogModel.title = "Property settings"; scope.propertySettingsDialogModel.property = property; + scope.propertySettingsDialogModel.contentType = scope.contentType; scope.propertySettingsDialogModel.contentTypeName = scope.model.name; scope.propertySettingsDialogModel.view = "views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html"; scope.propertySettingsDialogModel.show = true; @@ -493,6 +500,10 @@ property.dataTypeId = oldModel.property.dataTypeId; property.dataTypeIcon = oldModel.property.dataTypeIcon; property.dataTypeName = oldModel.property.dataTypeName; + property.validation.mandatory = oldModel.property.validation.mandatory; + property.validation.pattern = oldModel.property.validation.pattern; + property.showOnMemberProfile = oldModel.property.showOnMemberProfile; + property.memberCanEdit = oldModel.property.memberCanEdit; // because we set state to active, to show a preview, we have to check if has been filled out // label is required so if it is not filled we know it is a placeholder diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 7bc069ef44..9687e5c40d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -108,7 +108,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo * @param {String} source The URL to load into the iframe */ loadLegacyIFrame: function (source) { - $location.path("/" + appState.getSectionState("currentSection").toLowerCase() + "/framed/" + encodeURIComponent(source)); + $location.path("/" + appState.getSectionState("currentSection") + "/framed/" + encodeURIComponent(source)); }, /** @@ -132,7 +132,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setSectionState("currentSection", sectionAlias); this.showTree(sectionAlias); - $location.path(sectionAlias.toLowerCase()); + $location.path(sectionAlias); }, /** @@ -214,7 +214,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo //this reacts to tree items themselves being clicked //the tree directive should not contain any handling, simply just bubble events - mainTreeEventHandler.bind("treeNodeSelect", function(ev, args) { + mainTreeEventHandler.bind("treeNodeSelect", function (ev, args) { var n = args.node; ev.stopPropagation(); ev.preventDefault(); @@ -250,10 +250,10 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo appState.setMenuState("currentNode", args.node); //not legacy, lets just set the route value and clear the query string if there is one. - $location.path(n.routePath.toLowerCase()).search(""); + $location.path(n.routePath).search(""); } else if (args.element.section) { - $location.path(args.element.section.toLowerCase()).search(""); + $location.path(args.element.section).search(""); } service.hideNavigation(); @@ -446,7 +446,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo if (action.metaData && action.metaData["actionRoute"] && angular.isString(action.metaData["actionRoute"])) { //first check if the menu item simply navigates to a route var parts = action.metaData["actionRoute"].split("?"); - $location.path(parts[0].toLowerCase()).search(parts.length > 1 ? parts[1] : ""); + $location.path(parts[0]).search(parts.length > 1 ? parts[1] : ""); this.hideNavigation(); return; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 4371c0d7a7..554790b6d0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -512,7 +512,7 @@ function umbDataFormatter() { }); var saveProperties = _.map(realProperties, function (p) { - var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId'); + var saveProperty = _.pick(p, 'id', 'alias', 'description', 'validation', 'label', 'sortOrder', 'dataTypeId', 'groupId', 'memberCanEdit', 'showOnMemberProfile'); return saveProperty; }); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less index a4e18abb20..b34face8ba 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less @@ -11,16 +11,11 @@ cursor: pointer; position: relative; margin: 10px; - border: 1px solid #ffffff; user-select: none; } -.umb-content-grid__item:hover { - border: 1px solid @blue; -} - -.umb-content-grid__item:hover .umb-content-grid__action { - opacity: 1; +.umb-content-grid__item.-selected { + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.35); } .umb-content-grid__icon-container { @@ -48,12 +43,16 @@ .umb-content-grid__item-name { font-weight: bold; - color: @grayDark; margin-bottom: 10px; color: black; padding-bottom: 10px; line-height: 1.4em; border-bottom: 1px solid @grayLight; + display: inline-block; +} + +.umb-content-grid__item-name:hover { + text-decoration: underline; } .umb-content-grid__item-name.-light { @@ -80,15 +79,14 @@ display: inline-block; } -.umb-content-grid__action { +.umb-content-grid__checkmark { position: absolute; - opacity: 0; top: 10px; right: 10px; border: 1px solid #ffffff; width: 25px; height: 25px; - background: rgba(0, 0, 0, 0.4); + background: @blue; border-radius: 50px; box-sizing: border-box; display: flex; @@ -98,16 +96,6 @@ cursor: pointer; } -.umb-content-grid__action:hover { - background: @blue; - transition: background 0.1s; -} - -.umb-content-grid__action.-selected { - opacity: 1; - background: @blue; -} - .umb-content-grid__item:hover .umb-content-grid__action:not(.-selected) { opacity: 1; animation: fadeIn; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index ba817db398..d61f5dece2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -274,7 +274,7 @@ input.umb-group-builder__group-sort-value { height: 30px; overflow: hidden; position: relative; - padding: 25px 10px; + padding: 35px 10px 25px 10px; } .umb-group-builder__property-preview:hover { @@ -336,26 +336,38 @@ input.umb-group-builder__group-sort-value { color: @blueDark; } -.umb-group-builder__property-inherited-label { - font-size: 11px; - background-color: #E9E9E9; - margin-left: 5px; - position: absolute; - right: 0; - z-index: 100; - padding: 0 10px 0 5px; - top: 0; +.umb-group-builder__property-tags { + position: absolute; + z-index: 20; + top: 0; + left: 0; + display: flex; + flex-direction: row; } -.umb-group-builder__property-locked-label { - font-size: 11px; - background-color: #E9E9E9; - margin-left: 5px; - position: absolute; - right: 0; - z-index: 100; - padding: 0 10px 0 5px; - top: 0; +.umb-group-builder__property-tags.-right { + right: 0; + left: auto; +} + +.umb-group-builder__property-tag { + font-size: 11px; + background-color: @grayLight; + margin-left: 10px; + padding: 0 4px; + display: flex; +} + +.umb-group-builder__property-tag:first-child { + margin-left: 0; +} + +.umb-group-builder__property-tag.-white { + background-color: @white; +} + +.umb-group-builder__property-tag-icon { + margin-right: 3px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less index 14d19181b9..185b5a6e3b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/tree.less @@ -368,6 +368,10 @@ div.locked:before{ border-top: 1px solid #efefef; } +.umb-actions li.sep:first-child { + border-top: none; +} + .umb-actions a { white-space: nowrap; display: block; diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index bd122a6c7c..5d641fcb6c 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -130,7 +130,7 @@ app.config(function ($routeProvider) { // angular dashboards working. Perhaps a normal section dashboard would list out the registered // dashboards (as tabs if we wanted) and each tab could actually be a route link to one of these views? - return ('views/' + rp.tree + '/' + rp.method + '.html').toLowerCase(); + return ('views/' + rp.tree + '/' + rp.method + '.html'); }, resolve: canRoute(true) }) @@ -156,10 +156,10 @@ app.config(function ($routeProvider) { if (packageTreeFolder) { $scope.templateUrl = (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + "/" + packageTreeFolder + - "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html").toLowerCase(); + "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html"); } else { - $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html').toLowerCase(); + $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html'); } }, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html index 4d2037d88e..7c457ac4c9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/propertysettings/propertysettings.html @@ -61,6 +61,8 @@
+
+
+
+ +
+ + + + + +
+ + ng-click="clickItem(item, $event, $index)"> - +
@@ -14,7 +14,7 @@
-
{{ item.name }}
+
{{ item.name }}
  • @@ -28,7 +28,8 @@
+ ng-if="!content" + position="center"> There are no items to show diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html index c27b463316..06ed26a155 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html @@ -128,14 +128,6 @@
-
- {{property.contentTypeName}} -
- -
- Locked -
-
@@ -189,10 +181,44 @@ - - {{property.dataTypeName}} - - +
+ + + {{property.dataTypeName}} + + + +
+ * + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ + + {{property.contentTypeName}} +
+ +
+ + +
+ +
- + diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/views/design/design.html b/src/Umbraco.Web.UI.Client/src/views/membertypes/views/design/design.html index 86702e0917..7cd40dd25e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/views/design/design.html +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/views/design/design.html @@ -1 +1,5 @@ - + + 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 a3e5f07817..f90f607e9d 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 @@ -7,8 +7,8 @@ ng-if="items || !items && !options.filter" content="items" content-properties="options.includeProperties" - on-click="vm.clickItem" - on-select="vm.selectItem"> + on-click="vm.selectContentItem" + on-click-name="vm.goToItem"> @@ -60,6 +61,12 @@ + + + + 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 0d326a1d22..a8a1ac0766 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,6 +19,7 @@ vm.activeDrag = false; vm.mediaDetailsTooltip = {}; vm.itemsWithoutFolders = []; + vm.isRecycleBin = $scope.contentId === '-21'; vm.dragEnter = dragEnter; vm.dragLeave = dragLeave; @@ -26,6 +27,7 @@ vm.onUploadComplete = onUploadComplete; vm.hoverMediaItemDetails = hoverMediaItemDetails; + vm.selectContentItem = selectContentItem; vm.selectItem = selectItem; vm.selectFolder = selectFolder; vm.goToItem = goToItem; @@ -86,6 +88,10 @@ } + function selectContentItem(item, $event, $index) { + listViewHelper.selectHandler(item, $index, $scope.items, $scope.selection, $event); + } + function selectItem(item, $event, $index) { listViewHelper.selectHandler(item, $index, vm.itemsWithoutFolders, $scope.selection, $event); } 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 5a234ea69d..24f1dabf33 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 @@ -9,6 +9,7 @@ on-drag-enter="vm.dragEnter()"> + + + +
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 3716f9010a..59327524d6 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,6 +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.selectItem = selectItem; vm.clickItem = clickItem; diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index b8ef4a53a7..6993b7cad9 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -350,12 +350,12 @@ umbraco.providers - - ..\packages\Umbraco.ModelsBuilder.3.0.0-beta004\lib\Umbraco.ModelsBuilder.dll + + ..\packages\Umbraco.ModelsBuilder.3.0.0-beta006\lib\Umbraco.ModelsBuilder.dll True - - ..\packages\Umbraco.ModelsBuilder.AspNet.3.0.0-beta004\lib\Umbraco.ModelsBuilder.AspNet.dll + + ..\packages\Umbraco.ModelsBuilder.AspNet.3.0.0-beta006\lib\Umbraco.ModelsBuilder.AspNet.dll True diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index 66f6d587b6..5d0ad917f4 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -37,7 +37,7 @@ - - + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 354e212654..ed408ab7b1 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -382,6 +382,7 @@ Egenskaber Email der skal modtage indhold af formular Papirkurv + Din papirkurv er tom Mangler Omdøb Forny diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 80358d626c..22e14d83fa 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -420,6 +420,7 @@ Log off Logout Macro + Mandatory Move More Name @@ -1064,6 +1065,10 @@ To manage your website, simply open the Umbraco back office and start adding con and all members using this type using this editor will get updated with the new settings + + Member can edit + Show on member profile + @@ -1225,7 +1230,7 @@ To manage your website, simply open the Umbraco back office and start adding con Your profile Your recent history Session expires in - + Validation Validate as email 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 cd0a3d3efd..caa84e8461 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -421,6 +421,7 @@ Log off Logout Macro + Mandatory Move More Name @@ -439,6 +440,7 @@ Properties Email to receive form data Recycle Bin + Your recycle bin is empty Remaining Rename Renew @@ -1058,6 +1060,9 @@ To manage your website, simply open the Umbraco back office and start adding con and all members using this type using this editor will get updated with the new settings + + Member can edit + Show on member profile diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml index 1ba5f40c79..3dc9e04a4e 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml @@ -52,10 +52,10 @@ ドメイン '%0%' は既に割り当てられています ドメイン '%0%' は更新されました ドメインの編集 - - Inherit + + 継承 カルチャの割り当て - + ドメイン割り当て @@ -67,7 +67,7 @@ その他のアクション 太字 インデント解除 - フィールドから挿入 + フォーム フィールドの挿入 グラフィックヘッドラインの挿入 HTMLの編集 インデント @@ -92,6 +92,7 @@ スタイルの選択 スタイルの表示 表の挿入 + モデルを生成 ドキュメントタイプを変更するには、まず有効なドキュメントタイプのリストから選択します @@ -105,10 +106,10 @@ 割り当てるプロパティ 新しいテンプレート 新しいドキュメントタイプ - None + なし コンテント ドキュメントタイプを変更する - プロパティが以下のように割り当てられました + 選択コンテンツのドキュメント タイプは、[新しいタイプ] に変更され、以下のプロパティがマップされました。 から 1つ以上のプロパティを割り当てられませんでした。プロパティが定義が重複しています 有効なドキュメントタイプのみが表示されます @@ -126,7 +127,7 @@ 作成日時 このドキュメントが作成された日時 ドキュメントタイプ - 変種中 + 編集中 公開終了日時 このページは公開後変更されています このページは公開されていません @@ -231,8 +232,8 @@ サイトは再インデックスされました ウェブサイトのキャッシュがリフレッシュされました。 公開されているコンテンツはリフレッシュされましたが、非公開のコンテンツは非公開のままです。 ウェブサイトのキャッシュがリフレッシュされます。 公開されているコンテンツはリフレッシュされますが、非公開のコンテンツは非公開のままです。 - 行数 - 列数 + 列数 + 行数 プレースホルダにidを設定して、子テンプレートからもこのテンプレートをコンテントに入れられるようにできます。 IDは<asp:content />エレメントとして用います。]]> プレースホルダのidを選択してください。 @@ -290,18 +291,18 @@ 説明を入力してください... 検索する... 条件で絞り込む... - タグを追加します... + 入力してタグを追加 (各タグの後に Enter を押してください)... ルートノードとして許可する これを有効にするとコンテンツとメディアツリーのルートレベルに作成することができます 子ノードとして許可するタイプ - Document Type Compositions + ドキュメント タイプ構成 新規作成 - 削除 + タブの削除 説明 - 新規見出し - 見出し + 新しいタブ + タブ サムネイル リストビューを有効にする 子ノードをツリーに表示せずにリストビューに表示します @@ -313,8 +314,8 @@ 値の前に追加 データベースのデータ型 - データ型のGUID - 対応させるコントロール + プロパティ エディター GUID + プロパティ エディター ボタン 高度な設定を有効にする コンテキストメニューを有効にする @@ -342,6 +343,12 @@ 注意! CodeMirrorが設定で有効化されていますが、 Internet Explorerでは不安定なので無効化してください。 新しいプロパティ型のエイリアスと名前の両方を設定してください! 特定のファイルまたはフォルタの読み込み/書き込みアクセスに問題があります + Partial View スクリプトの読み込みエラー (ファイル: %0%) + userControl の読み込みエラー '%0%' + customControl の読み込みエラー (アセンブリ: %0%, タイプ: '%1%') + MacroEngine スクリプトの読み込みエラー (ファイル: %0%) + XSLT ファイル解析エラー: %0% + XSLT ファイル読み込みエラー: %0% タイトルを入力してください 型を選択してください 元画像より大きくしようとしていますが、本当によろしいのですか? @@ -440,7 +447,7 @@ 送信後にページを表示 サイズ 並べ替え - Submit + 送信 検索... @@ -457,8 +464,8 @@ はい フォルダー 検索結果 - Reorder - I am done reordering + 順序変更 + 順序変更終了 プレビュー パスワードの変更 ->  @@ -635,17 +642,16 @@ Runwayをインストールして作られた新しいウェブサイトがど 作業を保存して今すぐ更新 - Happy super sunday - Happy manic monday - Happy tubular tuesday - Happy wonderful wednesday - Happy thunder thursday - Happy funky friday - Happy caturday + ハッピー スーパー日曜日 + ハッピー マニアック月曜日 + ハッピー最高の火曜日 + ハッピー ワンダフル水曜日 + ハッピー サンダー木曜日 + ハッピー ファンキー金曜日 + ハッピー土曜日 ウェブサイトにログインします。 セッションタイムアウトしました。 © 2001 - %0%
umbraco.org

]]>
- Umbraco にようこそ。ユーザー名とパスワードを入力してください: ダッシュボード @@ -662,6 +668,7 @@ Runwayをインストールして作られた新しいウェブサイトがど まだノードが選択されていません。'ok'をクリックする前に上のリストでノードを選択してください。 現在のノードは、ドキュメントタイプの設定により選択されたノードの子になることはできません。 ノードは、自分のサブページには移動できません + 現在のノードはルートにできません。 子ドキュメントで権限がないので、その操作はできません。 コピーしたものを元と関係づける @@ -713,8 +720,8 @@ Runwayをインストールして作られた新しいウェブサイトがど パッケージを選択できます。Umbracoのパッケージは概ね".zip"ないしは".umb"といった拡張子です。 ]]>
作成者 - 文書 - 文書 + デモ + ヘルプ パッケージのメタデータ パッケージ名 パッケージには何も含まれません @@ -796,21 +803,13 @@ Runwayをインストールして作られた新しいウェブサイトがど 設定済みの色はありません。 - 外部リンクを追加 - 内部リンクを追加 - 追加 - タイトル - キャプション表示を入力 - 内部リンクを選択 外部リンクを入力 - リンクを入力 - 内部ページ - リンク - URL - 下に移動 - 上に移動 - 新規ウィンドウで開く - リンクを削除 + 内部リンクを選択 + キャプション + リンク + 新規ウィンドウで開く + キャプションを入力 + リンクを入力 リセット @@ -877,6 +876,8 @@ Runwayをインストールして作られた新しいウェブサイトがど
並び替え中はウィンドウを閉じないでください。]]>
+ 検証 + アイテムを保存する前に検証エラーを修正してください。 失敗しました 不十分なユーザー権限により操作を完了できませんでした キャンセルされました @@ -934,6 +935,11 @@ Runwayをインストールして作られた新しいウェブサイトがど 部分ビューをエラーなしで保存しました! 部分ビューは保存されていません ファイルを保存するときにエラーが発生しました。 + スクリプト ビューが保存されました + スクリプト ビューが正しく保存されました + スクリプト ビューが保存されていません + ファイルの保存でエラーが発生しました。 + ファイルの保存でエラーが発生しました。 CSSシンタックスを使用 例: h1, .redHeader, .blueTex @@ -947,7 +953,7 @@ Runwayをインストールして作られた新しいウェブサイトがど テンプレートの編集 コンテンツ領域の挿入 コンテンツ領域プレースホルダーの挿入 - dictionary item の挿入 + ディクショナリ アイテムを挿入 マクロの挿入 umbraco ページフィールドの挿入 マスターテンプレート @@ -957,14 +963,14 @@ Runwayをインストールして作られた新しいウェブサイトがど 挿入するアイテムを選択する - Choose a layout - ここからレイアウトを選択します - 最初の要素を追加します]]> - Drop content - Settings applied + レイアウトを選択 + 行の追加 + コンテンツの追加 + コンテンツのドロップ + 適用される設定 - This content is not allowed here - This content is allowed here + このコンテンツはここでは許可されていません + このコンテンツはここに使用できます クリックして埋め込む クリックして画像を挿入する @@ -1073,7 +1079,7 @@ Runwayをインストールして作られた新しいウェブサイトがど フィールドの値の後ろに追加される フィールドの値の前に追加される 小文字変換 - None + なし フィールド値の後ろ追加 フィールド値の前に追加 再帰的 @@ -1151,6 +1157,7 @@ Runwayをインストールして作られた新しいウェブサイトがど 役割 メンバーの種類 ドキュメントタイプ + 関連タイプ パッケージ パッケージ Python ファイル @@ -1209,6 +1216,7 @@ Runwayをインストールして作られた新しいウェブサイトがど ユーザーの種類 投稿者 翻訳者 + 変更 あなたのプロフィール あなたの最新の履歴 セッションの期限 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 diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index f9efb4e366..a325cdd005 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -56,7 +56,7 @@ namespace Umbraco.Web.Editors return Services.ContentTypeService.CountContentTypes(); } - public ContentTypeDisplay GetById(int id) + public DocumentTypeDisplay GetById(int id) { var ct = Services.ContentTypeService.GetContentType(id); if (ct == null) @@ -64,7 +64,7 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - var dto = Mapper.Map(ct); + var dto = Mapper.Map(ct); return dto; } @@ -175,9 +175,9 @@ namespace Umbraco.Web.Editors : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); } - public ContentTypeDisplay PostSave(ContentTypeSave contentTypeSave) + public DocumentTypeDisplay PostSave(DocumentTypeSave contentTypeSave) { - var savedCt = PerformPostSave( + var savedCt = PerformPostSave( contentTypeSave: contentTypeSave, getContentType: i => Services.ContentTypeService.GetContentType(i), saveContentType: type => Services.ContentTypeService.Save(type), @@ -206,7 +206,7 @@ namespace Umbraco.Web.Editors } }); - var display = Mapper.Map(savedCt); + var display = Mapper.Map(savedCt); display.AddSuccessNotification( Services.TextService.Localize("speechBubbles/contentTypeSavedHeader"), @@ -220,7 +220,7 @@ namespace Umbraco.Web.Editors /// /// /// - public ContentTypeDisplay GetEmpty(int parentId) + public DocumentTypeDisplay GetEmpty(int parentId) { IContentType ct; if (parentId != Constants.System.Root) @@ -233,7 +233,7 @@ namespace Umbraco.Web.Editors ct.Icon = "icon-document"; - var dto = Mapper.Map(ct); + var dto = Mapper.Map(ct); return dto; } diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index ae2e7f1afb..5a861589a2 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -148,13 +148,15 @@ namespace Umbraco.Web.Editors return CultureDictionary[text].IfNullOrWhiteSpace(text); } - protected TContentType PerformPostSave( - ContentTypeSave contentTypeSave, + protected TContentType PerformPostSave( + TContentTypeSave contentTypeSave, Func getContentType, Action saveContentType, - Action beforeCreateNew = null) + Action beforeCreateNew = null) where TContentType : class, IContentTypeComposition where TContentTypeDisplay : ContentTypeCompositionDisplay + where TContentTypeSave : ContentTypeSave + where TPropertyType : PropertyTypeBasic { var ctId = Convert.ToInt32(contentTypeSave.Id); var ct = ctId > 0 ? getContentType(ctId) : null; diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index 611c92e5a3..926cfcb4cc 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -56,7 +56,7 @@ namespace Umbraco.Web.Editors return Services.ContentTypeService.CountContentTypes(); } - public ContentTypeCompositionDisplay GetById(int id) + public MediaTypeDisplay GetById(int id) { var ct = Services.ContentTypeService.GetMediaType(id); if (ct == null) @@ -64,7 +64,7 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - var dto = Mapper.Map(ct); + var dto = Mapper.Map(ct); return dto; } @@ -114,12 +114,12 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(result); } - public ContentTypeCompositionDisplay GetEmpty(int parentId) + public MediaTypeDisplay GetEmpty(int parentId) { var ct = new MediaType(parentId); ct.Icon = "icon-picture"; - var dto = Mapper.Map(ct); + var dto = Mapper.Map(ct); return dto; } @@ -157,14 +157,14 @@ namespace Umbraco.Web.Editors : Request.CreateNotificationValidationErrorResponse(result.Exception.Message); } - public ContentTypeCompositionDisplay PostSave(ContentTypeSave contentTypeSave) + public MediaTypeDisplay PostSave(MediaTypeSave contentTypeSave) { - var savedCt = PerformPostSave( + var savedCt = PerformPostSave( contentTypeSave: contentTypeSave, getContentType: i => Services.ContentTypeService.GetMediaType(i), saveContentType: type => Services.ContentTypeService.Save(type)); - var display = Mapper.Map(savedCt); + var display = Mapper.Map(savedCt); display.AddSuccessNotification( Services.TextService.Localize("speechBubbles/contentTypeSavedHeader"), diff --git a/src/Umbraco.Web/Editors/MemberTypeController.cs b/src/Umbraco.Web/Editors/MemberTypeController.cs index ccd321d53a..8ed61fd52c 100644 --- a/src/Umbraco.Web/Editors/MemberTypeController.cs +++ b/src/Umbraco.Web/Editors/MemberTypeController.cs @@ -48,7 +48,7 @@ namespace Umbraco.Web.Editors private readonly MembershipProvider _provider; - public ContentTypeCompositionDisplay GetById(int id) + public MemberTypeDisplay GetById(int id) { var ct = Services.MemberTypeService.Get(id); if (ct == null) @@ -56,7 +56,7 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - var dto = Mapper.Map(ct); + var dto = Mapper.Map(ct); return dto; } @@ -106,12 +106,12 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(result); } - public ContentTypeCompositionDisplay GetEmpty() + public MemberTypeDisplay GetEmpty() { var ct = new MemberType(-1); ct.Icon = "icon-user"; - var dto = Mapper.Map(ct); + var dto = Mapper.Map(ct); return dto; } @@ -129,14 +129,14 @@ namespace Umbraco.Web.Editors return Enumerable.Empty(); } - public ContentTypeCompositionDisplay PostSave(ContentTypeSave contentTypeSave) + public MemberTypeDisplay PostSave(MemberTypeSave contentTypeSave) { - var savedCt = PerformPostSave( + var savedCt = PerformPostSave( contentTypeSave: contentTypeSave, getContentType: i => Services.MemberTypeService.Get(i), saveContentType: type => Services.MemberTypeService.Save(type)); - var display = Mapper.Map(savedCt); + var display = Mapper.Map(savedCt); display.AddSuccessNotification( Services.TextService.Localize("speechBubbles/contentTypeSavedHeader"), diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs index 5f6e2c5ce5..fb50fdec5c 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs @@ -10,32 +10,26 @@ using Umbraco.Core.Models.Validation; namespace Umbraco.Web.Models.ContentEditing { - [DataContract(Name = "contentType", Namespace = "")] - public class ContentTypeCompositionDisplay : ContentTypeBasic, INotificationModel - { - public ContentTypeCompositionDisplay() + public abstract class ContentTypeCompositionDisplay : ContentTypeBasic, INotificationModel + { + protected ContentTypeCompositionDisplay() { //initialize collections so at least their never null - Groups = new List(); AllowedContentTypes = new List(); CompositeContentTypes = new List(); Notifications = new List(); - } - - //name, alias, icon, thumb, desc, inherited from basic - + } + + //name, alias, icon, thumb, desc, inherited from basic + //List view [DataMember(Name = "isContainer")] public bool IsContainer { get; set; } [DataMember(Name = "listViewEditorName")] [ReadOnly(true)] - public string ListViewEditorName { get; set; } + public string ListViewEditorName { get; set; } - //Tabs - [DataMember(Name = "groups")] - public IEnumerable Groups { get; set; } - //Allowed child types [DataMember(Name = "allowedContentTypes")] public IEnumerable AllowedContentTypes { get; set; } @@ -43,7 +37,7 @@ namespace Umbraco.Web.Models.ContentEditing //Compositions [DataMember(Name = "compositeContentTypes")] public IEnumerable CompositeContentTypes { get; set; } - + //Locked compositions [DataMember(Name = "lockedCompositeContentTypes")] public IEnumerable LockedCompositeContentTypes { get; set; } @@ -72,4 +66,20 @@ namespace Umbraco.Web.Models.ContentEditing [ReadOnly(true)] public IDictionary Errors { get; set; } } + + [DataContract(Name = "contentType", Namespace = "")] + public abstract class ContentTypeCompositionDisplay : ContentTypeCompositionDisplay + where TPropertyTypeDisplay : PropertyTypeDisplay + { + protected ContentTypeCompositionDisplay() + { + //initialize collections so at least their never null + Groups = new List>(); + } + + //Tabs + [DataMember(Name = "groups")] + public IEnumerable> Groups { get; set; } + + } } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs index 99d094af7b..5d961487ad 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs @@ -6,13 +6,14 @@ using Umbraco.Core; namespace Umbraco.Web.Models.ContentEditing { + /// + /// Abstract model used to save content types + /// [DataContract(Name = "contentType", Namespace = "")] - public class ContentTypeSave : ContentTypeBasic, IValidatableObject + public abstract class ContentTypeSave : ContentTypeBasic, IValidatableObject { - public ContentTypeSave() + protected ContentTypeSave() { - //initialize collections so at least their never null - Groups = new List>(); AllowedContentTypes = new List(); CompositeContentTypes = new List(); } @@ -27,38 +28,50 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "allowAsRoot")] public bool AllowAsRoot { get; set; } - /// - /// The list of allowed templates to assign (template alias) - /// - [DataMember(Name = "allowedTemplates")] - public IEnumerable AllowedTemplates { get; set; } - //Allowed child types [DataMember(Name = "allowedContentTypes")] public IEnumerable AllowedContentTypes { get; set; } - /// - /// The default template to assign (template alias) - /// - [DataMember(Name = "defaultTemplate")] - public string DefaultTemplate { get; set; } - - //Tabs - [DataMember(Name = "groups")] - public IEnumerable> Groups { get; set; } - /// /// Custom validation /// /// /// - public IEnumerable Validate(ValidationContext validationContext) + public virtual IEnumerable Validate(ValidationContext validationContext) { - if (AllowedTemplates.Any(x => x.IsNullOrWhiteSpace())) - yield return new ValidationResult("Template value cannot be null", new[] {"AllowedTemplates"}); - if (CompositeContentTypes.Any(x => x.IsNullOrWhiteSpace())) - yield return new ValidationResult("Composite Content Type value cannot be null", new[] { "CompositeContentTypes" }); + yield return new ValidationResult("Composite Content Type value cannot be null", new[] {"CompositeContentTypes"}); + } + } + + /// + /// Abstract model used to save content types + /// + /// + [DataContract(Name = "contentType", Namespace = "")] + public abstract class ContentTypeSave : ContentTypeSave + where TPropertyType : PropertyTypeBasic + { + protected ContentTypeSave() + { + Groups = new List>(); + } + + //Tabs + [DataMember(Name = "groups")] + public IEnumerable> Groups { get; set; } + + /// + /// Custom validation + /// + /// + /// + public override IEnumerable Validate(ValidationContext validationContext) + { + foreach (var validationResult in base.Validate(validationContext)) + { + yield return validationResult; + } var duplicateGroups = Groups.GroupBy(x => x.Name).Where(x => x.Count() > 1).ToArray(); if (duplicateGroups.Any()) @@ -70,7 +83,7 @@ namespace Umbraco.Web.Models.ContentEditing string.Format("Groups[{0}].Name", lastIndex) }); } - + var duplicateProperties = Groups.SelectMany(x => x.Properties).Where(x => x.Inherited == false).GroupBy(x => x.Alias).Where(x => x.Count() > 1).ToArray(); if (duplicateProperties.Any()) { @@ -83,7 +96,7 @@ namespace Umbraco.Web.Models.ContentEditing string.Format("Groups[{0}].Properties[{1}].Alias", propertyGroup.SortOrder, lastProperty.SortOrder) }); } - + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs similarity index 83% rename from src/Umbraco.Web/Models/ContentEditing/ContentTypeDisplay.cs rename to src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs index 12d08ac683..65641e7f63 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs @@ -1,26 +1,25 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace Umbraco.Web.Models.ContentEditing -{ - - [DataContract(Name = "contentType", Namespace = "")] - public class ContentTypeDisplay : ContentTypeCompositionDisplay - { - public ContentTypeDisplay() - { - //initialize collections so at least their never null - AllowedTemplates = new List(); - } - - //name, alias, icon, thumb, desc, inherited from the content type - - // Templates - [DataMember(Name = "allowedTemplates")] - public IEnumerable AllowedTemplates { get; set; } - - [DataMember(Name = "defaultTemplate")] - public EntityBasic DefaultTemplate { get; set; } - - } -} +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "contentType", Namespace = "")] + public class DocumentTypeDisplay : ContentTypeCompositionDisplay + { + public DocumentTypeDisplay() + { + //initialize collections so at least their never null + AllowedTemplates = new List(); + } + + //name, alias, icon, thumb, desc, inherited from the content type + + // Templates + [DataMember(Name = "allowedTemplates")] + public IEnumerable AllowedTemplates { get; set; } + + [DataMember(Name = "defaultTemplate")] + public EntityBasic DefaultTemplate { get; set; } + + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/DocumentTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/DocumentTypeSave.cs new file mode 100644 index 0000000000..cdf20dba8d --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/DocumentTypeSave.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Runtime.Serialization; +using Umbraco.Core; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Model used to save a document type + /// + [DataContract(Name = "contentType", Namespace = "")] + public class DocumentTypeSave : ContentTypeSave + { + /// + /// The list of allowed templates to assign (template alias) + /// + [DataMember(Name = "allowedTemplates")] + public IEnumerable AllowedTemplates { get; set; } + + /// + /// The default template to assign (template alias) + /// + [DataMember(Name = "defaultTemplate")] + public string DefaultTemplate { get; set; } + + /// + /// Custom validation + /// + /// + /// + public override IEnumerable Validate(ValidationContext validationContext) + { + if (AllowedTemplates.Any(x => StringExtensions.IsNullOrWhiteSpace(x))) + yield return new ValidationResult("Template value cannot be null", new[] { "AllowedTemplates" }); + + foreach (var v in base.Validate(validationContext)) + { + yield return v; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/MediaTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MediaTypeDisplay.cs new file mode 100644 index 0000000000..7063613d66 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/MediaTypeDisplay.cs @@ -0,0 +1,10 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "contentType", Namespace = "")] + public class MediaTypeDisplay : ContentTypeCompositionDisplay + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/MediaTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/MediaTypeSave.cs new file mode 100644 index 0000000000..e20eb23125 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/MediaTypeSave.cs @@ -0,0 +1,12 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Model used to save a media type + /// + [DataContract(Name = "contentType", Namespace = "")] + public class MediaTypeSave : ContentTypeSave + { + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeBasic.cs b/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeBasic.cs new file mode 100644 index 0000000000..dfe98461dd --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeBasic.cs @@ -0,0 +1,17 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Basic member property type + /// + [DataContract(Name = "contentType", Namespace = "")] + public class MemberPropertyTypeBasic : PropertyTypeBasic + { + [DataMember(Name = "showOnMemberProfile")] + public bool MemberCanViewProperty { get; set; } + + [DataMember(Name = "memberCanEdit")] + public bool MemberCanEditProperty { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeDisplay.cs new file mode 100644 index 0000000000..8f7d4be310 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/MemberPropertyTypeDisplay.cs @@ -0,0 +1,14 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "propertyType")] + public class MemberPropertyTypeDisplay : PropertyTypeDisplay + { + [DataMember(Name = "showOnMemberProfile")] + public bool MemberCanViewProperty { get; set; } + + [DataMember(Name = "memberCanEdit")] + public bool MemberCanEditProperty { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/MemberTypeDisplay.cs new file mode 100644 index 0000000000..e6092a10f4 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/MemberTypeDisplay.cs @@ -0,0 +1,10 @@ +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "contentType", Namespace = "")] + public class MemberTypeDisplay : ContentTypeCompositionDisplay + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/MemberTypeSave.cs new file mode 100644 index 0000000000..98bfbf0fce --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/MemberTypeSave.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Model used to save a member type + /// + public class MemberTypeSave : ContentTypeSave + { + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs index b8560ba342..6bfe1dc76e 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupBasic.cs @@ -5,14 +5,8 @@ using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { [DataContract(Name = "propertyGroup", Namespace = "")] - public class PropertyGroupBasic - where TPropertyType: PropertyTypeBasic + public abstract class PropertyGroupBasic { - public PropertyGroupBasic() - { - Properties = new List(); - } - /// /// Gets the special generic properties tab identifier. /// @@ -38,9 +32,6 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "id")] public int Id { get; set; } - [DataMember(Name = "properties")] - public IEnumerable Properties { get; set; } - [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } @@ -48,4 +39,21 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "name")] public string Name { get; set; } } + + [DataContract(Name = "propertyGroup", Namespace = "")] + public class PropertyGroupBasic : PropertyGroupBasic + where TPropertyType: PropertyTypeBasic + { + public PropertyGroupBasic() + { + Properties = new List(); + } + + + + [DataMember(Name = "properties")] + public IEnumerable Properties { get; set; } + + + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs index 1bae9c96d5..c31c93b206 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyGroupDisplay.cs @@ -5,11 +5,12 @@ using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { [DataContract(Name = "propertyGroup", Namespace = "")] - public class PropertyGroupDisplay : PropertyGroupBasic + public class PropertyGroupDisplay : PropertyGroupBasic + where TPropertyTypeDisplay : PropertyTypeDisplay { public PropertyGroupDisplay() { - Properties = new List(); + Properties = new List(); ParentTabContentTypeNames = new List(); ParentTabContentTypes = new List(); } diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeDisplay.cs index a7ef06b52c..ce9e2b7586 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeDisplay.cs @@ -27,15 +27,5 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "locked")] [ReadOnly(true)] public bool Locked { get; set; } - - //SD: Seems strange that this is needed - [DataMember(Name = "contentTypeId")] - [ReadOnly(true)] - public int ContentTypeId { get; set; } - - //SD: Seems strange that this is needed - [DataMember(Name = "contentTypeName")] - [ReadOnly(true)] - public string ContentTypeName { get; set; } } } diff --git a/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs b/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs index fd9d1f388f..624c641d3b 100644 --- a/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/AvailablePropertyEditorsResolver.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using AutoMapper; using Umbraco.Core.Models; diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs index 6ade23b6d5..d49c40bdaf 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs @@ -50,9 +50,9 @@ namespace Umbraco.Web.Models.Mapping .ForMember(type => type.UpdateDate, expression => expression.Ignore()) .ForMember(type => type.HasIdentity, expression => expression.Ignore()); - config.CreateMap() + config.CreateMap() //do the base mapping - .MapBaseContentTypeSaveToEntity(applicationContext) + .MapBaseContentTypeSaveToEntity(applicationContext) .ConstructUsing((source) => new ContentType(source.ParentId)) .ForMember(source => source.AllowedTemplates, expression => expression.Ignore()) .ForMember(dto => dto.DefaultTemplate, expression => expression.Ignore()) @@ -69,33 +69,59 @@ namespace Umbraco.Web.Models.Mapping ContentTypeModelMapperExtensions.AfterMapContentTypeSaveToEntity(source, dest, applicationContext); }); - config.CreateMap() + config.CreateMap() //do the base mapping - .MapBaseContentTypeSaveToEntity(applicationContext) + .MapBaseContentTypeSaveToEntity(applicationContext) .ConstructUsing((source) => new MediaType(source.ParentId)) .AfterMap((source, dest) => { ContentTypeModelMapperExtensions.AfterMapContentTypeSaveToEntity(source, dest, applicationContext); }); - config.CreateMap() + config.CreateMap() //do the base mapping - .MapBaseContentTypeSaveToEntity(applicationContext) + .MapBaseContentTypeSaveToEntity(applicationContext) .ConstructUsing((source) => new MemberType(source.ParentId)) .AfterMap((source, dest) => { ContentTypeModelMapperExtensions.AfterMapContentTypeSaveToEntity(source, dest, applicationContext); + + //map the MemberCanEditProperty,MemberCanViewProperty + foreach (var propertyType in source.Groups.SelectMany(x => x.Properties)) + { + var localCopy = propertyType; + var destProp = dest.PropertyTypes.SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias)); + if (destProp != null) + { + dest.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty); + dest.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty); + } + } }); config.CreateMap().ConvertUsing(x => x.Alias); - config.CreateMap() + config.CreateMap() //map base logic - .MapBaseContentTypeEntityToDisplay(applicationContext, _propertyEditorResolver); + .MapBaseContentTypeEntityToDisplay(applicationContext, _propertyEditorResolver) + .AfterMap((memberType, display) => + { + //map the MemberCanEditProperty,MemberCanViewProperty + foreach (var propertyType in memberType.PropertyTypes) + { + var localCopy = propertyType; + var displayProp = display.Groups.SelectMany(x => x.Properties).SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias)); + if (displayProp != null) + { + displayProp.MemberCanEditProperty = memberType.MemberCanEditProperty(localCopy.Alias); + displayProp.MemberCanViewProperty = memberType.MemberCanViewProperty(localCopy.Alias); + } + } + }); - config.CreateMap() + config.CreateMap() //map base logic - .MapBaseContentTypeEntityToDisplay(applicationContext, _propertyEditorResolver) + .MapBaseContentTypeEntityToDisplay(applicationContext, _propertyEditorResolver) .AfterMap((source, dest) => { //default listview @@ -109,9 +135,9 @@ namespace Umbraco.Web.Models.Mapping } }); - config.CreateMap() + config.CreateMap() //map base logic - .MapBaseContentTypeEntityToDisplay(applicationContext, _propertyEditorResolver) + .MapBaseContentTypeEntityToDisplay(applicationContext, _propertyEditorResolver) .ForMember(dto => dto.AllowedTemplates, expression => expression.Ignore()) .ForMember(dto => dto.DefaultTemplate, expression => expression.Ignore()) .ForMember(display => display.Notifications, expression => expression.Ignore()) @@ -141,7 +167,7 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() - .ConstructUsing((PropertyTypeBasic propertyTypeBasic) => + .ConstructUsing(propertyTypeBasic => { var dataType = applicationContext.Services.DataTypeService.GetDataTypeDefinitionById(propertyTypeBasic.DataTypeId); if (dataType == null) throw new NullReferenceException("No data type found with id " + propertyTypeBasic.DataTypeId); @@ -170,11 +196,14 @@ namespace Umbraco.Web.Models.Mapping #region *** Used for mapping on top of an existing display object from a save object *** - config.CreateMap() - .MapBaseContentTypeSaveToDisplay(); + config.CreateMap() + .MapBaseContentTypeSaveToDisplay(); + + config.CreateMap() + .MapBaseContentTypeSaveToDisplay(); - config.CreateMap() - .MapBaseContentTypeSaveToDisplay() + config.CreateMap() + .MapBaseContentTypeSaveToDisplay() .ForMember(dto => dto.AllowedTemplates, expression => expression.Ignore()) .ForMember(dto => dto.DefaultTemplate, expression => expression.Ignore()) .AfterMap((source, dest) => @@ -206,41 +235,39 @@ namespace Umbraco.Web.Models.Mapping } }); + //for doc types, media types config.CreateMap, PropertyGroup>() - .ForMember(dest => dest.Id, map => map.Condition(source => source.Id > 0)) - .ForMember(dest => dest.Key, map => map.Ignore()) - .ForMember(dest => dest.HasIdentity, map => map.Ignore()) - .ForMember(dest => dest.CreateDate, map => map.Ignore()) - .ForMember(dest => dest.UpdateDate, map => map.Ignore()) - // fixme - // this is basically *replacing* dest properties by a mapped version of - // *every* source properties (including, I guess, inherited properties?) - // also, ContentTypeModelMapperExtensions will map properties *again* so - // this makes little sense - ignore for now - .ForMember(dest => dest.PropertyTypes, map => map.Ignore()); - //.ForMember(dest => dest.PropertyTypes, map => map.MapFrom(source => - // source.Properties.Select(Mapper.Map))); + .MapPropertyGroupBasicToPropertyGroupPersistence, PropertyTypeBasic>(); - config.CreateMap, PropertyGroupDisplay>() - .ForMember(dest => dest.Id, expression => expression.Condition(source => source.Id > 0)) - .ForMember(g => g.ContentTypeId, expression => expression.Ignore()) - .ForMember(g => g.ParentTabContentTypes, expression => expression.Ignore()) - .ForMember(g => g.ParentTabContentTypeNames, expression => expression.Ignore()) - .ForMember(g => g.Properties, expression => expression.MapFrom(display => display.Properties.Select(Mapper.Map))); + //for members + config.CreateMap, PropertyGroup>() + .MapPropertyGroupBasicToPropertyGroupPersistence, MemberPropertyTypeBasic>(); + + //for doc types, media types + config.CreateMap, PropertyGroupDisplay>() + .MapPropertyGroupBasicToPropertyGroupDisplay, PropertyTypeBasic, PropertyTypeDisplay>(); + + //for members + config.CreateMap, PropertyGroupDisplay>() + .MapPropertyGroupBasicToPropertyGroupDisplay, MemberPropertyTypeBasic, MemberPropertyTypeDisplay>(); config.CreateMap() .ForMember(g => g.Editor, expression => expression.Ignore()) .ForMember(g => g.View, expression => expression.Ignore()) .ForMember(g => g.Config, expression => expression.Ignore()) - .ForMember(g => g.ContentTypeId, expression => expression.Ignore()) - .ForMember(g => g.ContentTypeName, expression => expression.Ignore()) + .ForMember(g => g.Locked, exp => exp.Ignore()); + + config.CreateMap() + .ForMember(g => g.Editor, expression => expression.Ignore()) + .ForMember(g => g.View, expression => expression.Ignore()) + .ForMember(g => g.Config, expression => expression.Ignore()) .ForMember(g => g.Locked, exp => exp.Ignore()); #endregion - - + + } diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs index 5ad728ccab..431357d5f2 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapperExtensions.cs @@ -20,6 +20,34 @@ namespace Umbraco.Web.Models.Mapping internal static class ContentTypeModelMapperExtensions { + public static IMappingExpression MapPropertyGroupBasicToPropertyGroupPersistence( + this IMappingExpression mapping) + where TSource : PropertyGroupBasic + where TPropertyTypeBasic : PropertyTypeBasic + { + return mapping + .ForMember(dest => dest.Id, map => map.Condition(source => source.Id > 0)) + .ForMember(dest => dest.Key, map => map.Ignore()) + .ForMember(dest => dest.HasIdentity, map => map.Ignore()) + .ForMember(dest => dest.CreateDate, map => map.Ignore()) + .ForMember(dest => dest.UpdateDate, map => map.Ignore()) + .ForMember(dest => dest.PropertyTypes, map => map.Ignore()); + } + + public static IMappingExpression> MapPropertyGroupBasicToPropertyGroupDisplay( + this IMappingExpression> mapping) + where TSource : PropertyGroupBasic + where TPropertyTypeBasic : PropertyTypeBasic + where TPropertyTypeDisplay : PropertyTypeDisplay + { + return mapping + .ForMember(dest => dest.Id, expression => expression.Condition(source => source.Id > 0)) + .ForMember(g => g.ContentTypeId, expression => expression.Ignore()) + .ForMember(g => g.ParentTabContentTypes, expression => expression.Ignore()) + .ForMember(g => g.ParentTabContentTypeNames, expression => expression.Ignore()) + .ForMember(g => g.Properties, expression => expression.MapFrom(display => display.Properties.Select(Mapper.Map))); + } + public static void AfterMapContentTypeSaveToEntity( TSource source, TDestination dest, ApplicationContext applicationContext) @@ -47,10 +75,12 @@ namespace Umbraco.Web.Models.Mapping } } - public static IMappingExpression MapBaseContentTypeSaveToDisplay( + public static IMappingExpression MapBaseContentTypeSaveToDisplay( this IMappingExpression mapping) - where TSource : ContentTypeSave - where TDestination : ContentTypeCompositionDisplay + where TSource : ContentTypeSave + where TDestination : ContentTypeCompositionDisplay + where TPropertyTypeDestination : PropertyTypeDisplay + where TPropertyTypeSource : PropertyTypeBasic { return mapping .ForMember(dto => dto.CreateDate, expression => expression.Ignore()) @@ -58,13 +88,15 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dto => dto.ListViewEditorName, expression => expression.Ignore()) .ForMember(dto => dto.Notifications, expression => expression.Ignore()) .ForMember(dto => dto.Errors, expression => expression.Ignore()) - .ForMember(dto => dto.LockedCompositeContentTypes, exp => exp.Ignore()); + .ForMember(dto => dto.LockedCompositeContentTypes, exp => exp.Ignore()) + .ForMember(dto => dto.Groups, expression => expression.ResolveUsing(new PropertyGroupDisplayResolver())); } - public static IMappingExpression MapBaseContentTypeEntityToDisplay( + public static IMappingExpression MapBaseContentTypeEntityToDisplay( this IMappingExpression mapping, ApplicationContext applicationContext, Lazy propertyEditorResolver) where TSource : IContentTypeComposition - where TDestination : ContentTypeCompositionDisplay + where TDestination : ContentTypeCompositionDisplay + where TPropertyTypeDisplay : PropertyTypeDisplay, new() { return mapping .ForMember(display => display.Notifications, expression => expression.Ignore()) @@ -88,7 +120,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember( dto => dto.Groups, - expression => expression.ResolveUsing(new PropertyTypeGroupResolver(applicationContext, propertyEditorResolver))); + expression => expression.ResolveUsing(new PropertyTypeGroupResolver(applicationContext, propertyEditorResolver))); } /// @@ -96,14 +128,16 @@ namespace Umbraco.Web.Models.Mapping /// /// /// + /// /// /// /// - public static IMappingExpression MapBaseContentTypeSaveToEntity( + public static IMappingExpression MapBaseContentTypeSaveToEntity( this IMappingExpression mapping, ApplicationContext applicationContext) //where TSource : ContentTypeCompositionDisplay - where TSource : ContentTypeSave - where TDestination : IContentTypeComposition + where TSource : ContentTypeSave + where TDestination : IContentTypeComposition + where TSourcePropertyType : PropertyTypeBasic { return mapping //only map id if set to something higher then zero @@ -200,7 +234,8 @@ namespace Umbraco.Web.Models.Mapping }); } - private static PropertyGroup MapSaveGroup(PropertyGroupBasic sourceGroup, IEnumerable destOrigGroups) + private static PropertyGroup MapSaveGroup(PropertyGroupBasic sourceGroup, IEnumerable destOrigGroups) + where TPropertyType: PropertyTypeBasic { PropertyGroup destGroup; if (sourceGroup.Id > 0) diff --git a/src/Umbraco.Web/Models/Mapping/PropertyGroupDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/PropertyGroupDisplayResolver.cs new file mode 100644 index 0000000000..2f11eb04c4 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/PropertyGroupDisplayResolver.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class PropertyGroupDisplayResolver : ValueResolver>> + where TSource : ContentTypeSave + where TPropertyTypeDestination : PropertyTypeDisplay + where TPropertyTypeSource : PropertyTypeBasic + { + protected override IEnumerable> ResolveCore(TSource source) + { + return source.Groups.Select(Mapper.Map>); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs index bfd55cfbaa..f8d0af4bdf 100644 --- a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs @@ -9,7 +9,8 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping { - internal class PropertyTypeGroupResolver : ValueResolver> + internal class PropertyTypeGroupResolver : ValueResolver>> + where TPropertyType : PropertyTypeDisplay, new() { private readonly ApplicationContext _applicationContext; private readonly Lazy _propertyEditorResolver; @@ -40,15 +41,15 @@ namespace Umbraco.Web.Models.Mapping .FirstOrDefault(x => x != null); } - protected override IEnumerable ResolveCore(IContentTypeComposition source) + protected override IEnumerable> ResolveCore(IContentTypeComposition source) { // deal with groups - var groups = new List(); + var groups = new List>(); // add groups local to this content type foreach (var tab in source.PropertyGroups) { - var group = new PropertyGroupDisplay + var group = new PropertyGroupDisplay { Id = tab.Id, Inherited = false, @@ -57,7 +58,7 @@ namespace Umbraco.Web.Models.Mapping ContentTypeId = source.Id }; - group.Properties = MapProperties(tab.PropertyTypes, source, tab.Id, false); + group.Properties = MapProperties(tab.PropertyTypes, tab.Id, false); groups.Add(group); } @@ -73,7 +74,7 @@ namespace Umbraco.Web.Models.Mapping if (definingContentType == null) throw new Exception("PropertyGroup with id=" + tab.Id + " was not found on any of the content type's compositions."); - var group = new PropertyGroupDisplay + var group = new PropertyGroupDisplay { Id = tab.Id, Inherited = true, @@ -84,30 +85,30 @@ namespace Umbraco.Web.Models.Mapping ParentTabContentTypeNames = new[] { definingContentType.Name } }; - group.Properties = MapProperties(tab.PropertyTypes, definingContentType, tab.Id, true); + group.Properties = MapProperties(tab.PropertyTypes, tab.Id, true); groups.Add(group); } // deal with generic properties - var genericProperties = new List(); + var genericProperties = new List(); // add generic properties local to this content type var entityGenericProperties = source.PropertyTypes.Where(x => x.PropertyGroupId == null); - genericProperties.AddRange(MapProperties(entityGenericProperties, source, PropertyGroupDisplay.GenericPropertiesGroupId, false)); + genericProperties.AddRange(MapProperties(entityGenericProperties, PropertyGroupBasic.GenericPropertiesGroupId, false)); // add generic properties inherited through compositions var localGenericPropertyIds = genericProperties.Select(x => x.Id).ToArray(); var compositionGenericProperties = source.CompositionPropertyTypes .Where(x => x.PropertyGroupId == null // generic && localGenericPropertyIds.Contains(x.Id) == false); // skip those that are local - genericProperties.AddRange(MapProperties(compositionGenericProperties, source, PropertyGroupDisplay.GenericPropertiesGroupId, true)); + genericProperties.AddRange(MapProperties(compositionGenericProperties, PropertyGroupBasic.GenericPropertiesGroupId, true)); // if there are any generic properties, add the corresponding tab if (genericProperties.Any()) { - var genericTab = new PropertyGroupDisplay + var genericTab = new PropertyGroupDisplay { - Id = PropertyGroupDisplay.GenericPropertiesGroupId, + Id = PropertyGroupBasic.GenericPropertiesGroupId, Name = "Generic properties", ContentTypeId = source.Id, SortOrder = 999, @@ -133,7 +134,7 @@ namespace Umbraco.Web.Models.Mapping // now merge tabs based on names // as for one name, we might have one local tab, plus some inherited tabs var groupsGroupsByName = groups.GroupBy(x => x.Name).ToArray(); - groups = new List(); // start with a fresh list + groups = new List>(); // start with a fresh list foreach (var groupsByName in groupsGroupsByName) { // single group, just use it @@ -164,17 +165,20 @@ namespace Umbraco.Web.Models.Mapping return groups.OrderBy(x => x.SortOrder); } - private IEnumerable MapProperties(IEnumerable properties, IContentTypeBase contentType, int groupId, bool inherited) + private IEnumerable MapProperties(IEnumerable properties, int groupId, bool inherited) { - var mappedProperties = new List(); + var mappedProperties = new List(); foreach (var p in properties.Where(x => x.DataTypeDefinitionId != 0).OrderBy(x => x.SortOrder)) { var propertyEditor = _propertyEditorResolver.Value.GetByAlias(p.PropertyEditorAlias); var preValues = _applicationContext.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(p.DataTypeDefinitionId); - mappedProperties.Add(new PropertyTypeDisplay - { + if (propertyEditor == null) + throw new InvalidOperationException("No property editor could be resolved with the alias: " + p.PropertyEditorAlias + ", ensure all packages are installed correctly."); + + mappedProperties.Add(new TPropertyType + { Id = p.Id, Alias = p.Alias, Description = p.Description, @@ -184,8 +188,6 @@ namespace Umbraco.Web.Models.Mapping View = propertyEditor.ValueEditor.View, Config = propertyEditor.PreValueEditor.ConvertDbToEditor(propertyEditor.DefaultPreValues, preValues) , //Value = "", - ContentTypeId = contentType.Id, - ContentTypeName = contentType.Name, GroupId = groupId, Inherited = inherited, DataTypeId = p.DataTypeDefinitionId, 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(); } } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 67af0ee515..2ae1436d58 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -99,14 +99,17 @@ ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.dll + False True ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll + False True ..\packages\ClientDependency.1.8.4\lib\net45\ClientDependency.Core.dll + False True @@ -118,6 +121,7 @@ ..\packages\Examine.0.1.68.0\lib\Examine.dll + False True @@ -134,6 +138,7 @@ ..\packages\Markdown.1.14.4\lib\net45\MarkdownSharp.dll + False True @@ -166,8 +171,9 @@ ..\packages\Microsoft.Owin.Security.OAuth.3.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll - True ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + False + True False @@ -175,6 +181,7 @@ ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll + False True @@ -218,6 +225,7 @@ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll + False True @@ -234,6 +242,7 @@ ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll + False True @@ -241,14 +250,17 @@ ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll + False True ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll + False True ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll + False True @@ -292,7 +304,8 @@ ..\packages\UrlRewritingNet.UrlRewriter.2.0.7\lib\UrlRewritingNet.UrlRewriter.dll - True + False + False @@ -308,15 +321,23 @@ - + + + + + + + + + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 1ab96a6a1b..d2f7910e5c 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -40,6 +40,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; @@ -184,7 +185,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(); } /// @@ -242,7 +245,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; } ///