From 52f05612ad4a542523c62f3fff92eb5b39304c20 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Thu, 21 Mar 2013 08:38:18 +0600 Subject: [PATCH] Adds another method of distributed cache syncing using a custom json payload. this is necessary for things like ContentType cache updates especially when deleting one since we require all sorts of Ids from the object but the object will be deleted by the time the request reaches other servers so instead we create a json payload to send to other servers which contains all information necessary to refresh/clear the cache on the other servers. This will probably be the preferred way going forward to handle cache refreshing. With this in place, when removing a conent type cache is removed based on events. This also adds a 'class' generic argument constraint to the repository base classes to guarantee we can do null checks and then we also fix a null check on RepositoryBase. Updates the Entity class to properly support tracking properties - this now allows us to determine if an entity was new, which is now used to ensure we don't re-update all of the content cache when a new content type is created. Have also changed the deletion and creation of document types to use the new API, this allows for a lot less processing and streamlining how all cache is invalidated. Fixes the construction of a new Template and Content Type in the v6 api and ensures that default values are set - #U4-1972, #U4-1971 --- src/Umbraco.Core/Cache/IJsonCacheRefresher.cs | 13 ++ src/Umbraco.Core/Models/ContentTypeBase.cs | 4 +- .../Models/ContentTypeExtensions.cs | 21 +- src/Umbraco.Core/Models/EntityBase/Entity.cs | 59 +++++- src/Umbraco.Core/Models/File.cs | 2 +- .../Repositories/ContentTypeBaseRepository.cs | 2 +- .../Repositories/PetaPocoRepositoryBase.cs | 2 +- .../Repositories/RepositoryBase.cs | 16 +- .../Repositories/VersionableRepositoryBase.cs | 2 +- .../Services/ContentTypeService.cs | 33 +++- .../Sync/DefaultServerMessenger.cs | 138 +++++++++---- src/Umbraco.Core/Sync/IServerMessenger.cs | 21 ++ src/Umbraco.Core/Sync/MessageType.cs | 4 +- .../Sync/ServerSyncWebServiceClient.cs | 54 ++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Sync/DistributedCacheTests.cs | 14 +- .../Cache/CacheRefresherEventHandler.cs | 6 +- .../Cache/ContentTypeCacheRefresher.cs | 181 +++++++++++++----- .../Cache/DistributedCacheExtensions.cs | 90 +++++++-- .../umbraco/create/nodetypeTasks.cs | 39 ++-- .../webservices/CacheRefresher.asmx.cs | 49 +++++ 21 files changed, 584 insertions(+), 167 deletions(-) create mode 100644 src/Umbraco.Core/Cache/IJsonCacheRefresher.cs diff --git a/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs b/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs new file mode 100644 index 0000000000..c72c2569a1 --- /dev/null +++ b/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs @@ -0,0 +1,13 @@ +using umbraco.interfaces; + +namespace Umbraco.Core.Cache +{ + /// + /// A cache refresher that supports refreshing or removing cache based on a custom Json payload + /// + interface IJsonCacheRefresher : ICacheRefresher + { + void Refresh(string jsonPayload); + void Remove(string jsonPayload); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 419e265e7f..0159621a9e 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -22,8 +22,8 @@ namespace Umbraco.Core.Models private string _alias; private string _description; private int _sortOrder; - private string _icon; - private string _thumbnail; + private string _icon = "folder.png"; + private string _thumbnail = "folder.png"; private int _creatorId; private bool _allowedAsRoot; private bool _isContainer; diff --git a/src/Umbraco.Core/Models/ContentTypeExtensions.cs b/src/Umbraco.Core/Models/ContentTypeExtensions.cs index b328cb5e0b..df8c09b3d1 100644 --- a/src/Umbraco.Core/Models/ContentTypeExtensions.cs +++ b/src/Umbraco.Core/Models/ContentTypeExtensions.cs @@ -1,16 +1,17 @@ using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Services; namespace Umbraco.Core.Models { - public static class ContentTypeExtensions + internal static class ContentTypeExtensions { /// /// Get all descendant content types /// /// /// - public static IEnumerable Descendants(this IContentType contentType) + public static IEnumerable Descendants(this IContentTypeBase contentType) { var contentTypeService = ApplicationContext.Current.Services.ContentTypeService; var descendants = contentTypeService.GetContentTypeChildren(contentType.Id) @@ -23,13 +24,21 @@ namespace Umbraco.Core.Models /// /// /// - public static IEnumerable DescendantsAndSelf(this IContentType contentType) + public static IEnumerable DescendantsAndSelf(this IContentTypeBase contentType) { - var contentTypeService = ApplicationContext.Current.Services.ContentTypeService; - var descendants = contentTypeService.GetContentTypeChildren(contentType.Id) - .FlattenList(type => contentTypeService.GetContentTypeChildren(type.Id)); var descendantsAndSelf = new[] { contentType }.Concat(contentType.Descendants()); return descendantsAndSelf; } + + ///// + ///// Returns the descendant content type Ids for the given content type + ///// + ///// + ///// + //public static IEnumerable DescendantIds(this IContentTypeBase contentType) + //{ + // return ((ContentTypeService) ApplicationContext.Current.Services.ContentTypeService) + // .GetDescendantContentTypeIds(contentType.Id); + //} } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs index 3c58b61695..f5b228aad6 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Reflection; using System.Runtime.Serialization; namespace Umbraco.Core.Models.EntityBase @@ -16,6 +17,15 @@ namespace Umbraco.Core.Models.EntityBase private int? _hash; private int _id; private Guid _key; + private DateTime _createDate; + private DateTime _updateDate; + + private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); + private static readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo(x => x.Key); + private static readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.CreateDate); + private static readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo(x => x.UpdateDate); + private static readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.HasIdentity); + /// /// Integer Id @@ -29,8 +39,12 @@ namespace Umbraco.Core.Models.EntityBase } set { - _id = value; - HasIdentity = true; + SetPropertyValueAndDetectChanges(o => + { + _id = value; + HasIdentity = true; //set the has Identity + return _id; + }, _id, IdSelector); } } @@ -49,20 +63,49 @@ namespace Umbraco.Core.Models.EntityBase return _key; } - set { _key = value; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _key = value; + return _key; + }, _key, KeySelector); + } } /// /// Gets or sets the Created Date /// [DataMember] - public DateTime CreateDate { get; set; } + public DateTime CreateDate + { + get { return _createDate; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _createDate = value; + return _createDate; + }, _createDate, CreateDateSelector); + } + } /// /// Gets or sets the Modified Date /// [DataMember] - public DateTime UpdateDate { get; set; } + public DateTime UpdateDate + { + get { return _updateDate; } + set + { + SetPropertyValueAndDetectChanges(o => + { + _updateDate = value; + return _updateDate; + }, _updateDate, UpdateDateSelector); + } + } internal virtual void ResetIdentity() { @@ -98,7 +141,11 @@ namespace Umbraco.Core.Models.EntityBase } protected set { - _hasIdentity = value; + SetPropertyValueAndDetectChanges(o => + { + _hasIdentity = value; + return _hasIdentity; + }, _hasIdentity, HasIdentitySelector); } } diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 7fefddfaed..978e54390f 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Models public abstract class File : Entity, IFile { private string _path; - private string _content; + private string _content = string.Empty; //initialize to empty string, not null protected File(string path) { diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index f577e0f9db..179db898df 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -21,7 +21,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// internal abstract class ContentTypeBaseRepository : PetaPocoRepositoryBase - where TEntity : IContentTypeComposition + where TEntity : class, IContentTypeComposition { protected ContentTypeBaseRepository(IDatabaseUnitOfWork work) : base(work) diff --git a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs index f286392e87..a08dec6c76 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// internal abstract class PetaPocoRepositoryBase : RepositoryBase - where TEntity : IAggregateRoot + where TEntity : class, IAggregateRoot { protected PetaPocoRepositoryBase(IDatabaseUnitOfWork work) : base(work) diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 33b4ad392b..c7c5b236e6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Persistence.Repositories /// Type of entity for which the repository is used /// Type of the Id used for this entity internal abstract class RepositoryBase : DisposableObject, IRepositoryQueryable, IUnitOfWorkRepository - where TEntity : IAggregateRoot + where TEntity : class, IAggregateRoot { private readonly IUnitOfWork _work; private readonly IRepositoryCacheProvider _cache; @@ -98,13 +98,17 @@ namespace Umbraco.Core.Persistence.Repositories _cache.Save(typeof(TEntity), entity); } - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - var asEntity = entity as Entity; - if (asEntity != null) + if (entity != null) { - asEntity.ResetDirtyProperties(false); + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + Entity asEntity = entity as Entity; + if (asEntity != null) + { + asEntity.ResetDirtyProperties(false); + } } + return entity; } diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 15266aae16..6364a106fb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -11,7 +11,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { internal abstract class VersionableRepositoryBase : PetaPocoRepositoryBase - where TEntity : IAggregateRoot + where TEntity : class, IAggregateRoot { protected VersionableRepositoryBase(IDatabaseUnitOfWork work) : base(work) { diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 3c81b7c80a..6afc43d9d8 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; @@ -101,6 +102,26 @@ namespace Umbraco.Core.Services } } + ///// + ///// Returns the content type descendant Ids for the content type specified + ///// + ///// + ///// + //internal IEnumerable GetDescendantContentTypeIds(int contentTypeId) + //{ + // using (var uow = _uowProvider.GetUnitOfWork()) + // { + // //method to return the child content type ids for the id specified + // Func getChildIds = + // parentId => + // uow.Database.Fetch("WHERE parentContentTypeId = @Id", new {Id = parentId}) + // .Select(x => x.ChildId).ToArray(); + + // //recursively get all descendant ids + // return getChildIds(contentTypeId).FlattenList(getChildIds); + // } + //} + /// /// Checks whether an item has any children /// @@ -120,20 +141,23 @@ namespace Umbraco.Core.Services /// This is called after an IContentType is saved and is used to update the content xml structures in the database /// if they are required to be updated. /// - /// + /// A tuple of a content type and a boolean indicating if it is new (HasIdentity was false before committing) private void UpdateContentXmlStructure(params IContentType[] contentTypes) { - var toUpdate = new List(); + var toUpdate = new List(); foreach (var contentType in contentTypes) { //we need to determine if we need to refresh the xml content in the database. This is to be done when: + // - the item is not new (already existed in the db) // - a content type changes it's alias // - if a content type has it's property removed //here we need to check if the alias of the content type changed or if one of the properties was removed. var dirty = contentType as IRememberBeingDirty; - if (dirty != null && (dirty.WasPropertyDirty("Alias") || dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved"))) + if (dirty != null + && !dirty.WasPropertyDirty("HasIdentity") //ensure it's now 'new' + && (dirty.WasPropertyDirty("Alias") || dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved"))) { //if the alias was changed then we only need to update the xml structures for content of the current content type. //if a property was deleted then we need to update the xml structures for any content of the current content type @@ -145,7 +169,7 @@ namespace Umbraco.Core.Services } else { - //if a property was deleted (and maybe the alias changed too), the update all content of the current content type + //if a property was deleted (and maybe the alias changed too), then update all content of the current content type // and all of it's desscendant doc types. toUpdate.AddRange(contentType.DescendantsAndSelf()); } @@ -207,7 +231,6 @@ namespace Umbraco.Core.Services using (new WriteLock(Locker)) { - var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentTypeRepository(uow)) { diff --git a/src/Umbraco.Core/Sync/DefaultServerMessenger.cs b/src/Umbraco.Core/Sync/DefaultServerMessenger.cs index f637fd995d..973bbb055d 100644 --- a/src/Umbraco.Core/Sync/DefaultServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DefaultServerMessenger.cs @@ -56,6 +56,15 @@ namespace Umbraco.Core.Sync _getUserNamePasswordDelegate = getUserNamePasswordDelegate; } + public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, string jsonPayload) + { + if (servers == null) throw new ArgumentNullException("servers"); + if (refresher == null) throw new ArgumentNullException("refresher"); + if (jsonPayload == null) throw new ArgumentNullException("jsonPayload"); + + MessageSeversForIdsOrJson(servers, refresher, MessageType.RefreshByJson, jsonPayload: jsonPayload); + } + public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher,Func getNumericId, params T[] instances) { if (servers == null) throw new ArgumentNullException("servers"); @@ -82,6 +91,15 @@ namespace Umbraco.Core.Sync instances); } + public void PerformRemove(IEnumerable servers, ICacheRefresher refresher, string jsonPayload) + { + if (servers == null) throw new ArgumentNullException("servers"); + if (refresher == null) throw new ArgumentNullException("refresher"); + if (jsonPayload == null) throw new ArgumentNullException("jsonPayload"); + + MessageSeversForIdsOrJson(servers, refresher, MessageType.RemoveByJson, jsonPayload: jsonPayload); + } + public void PerformRemove(IEnumerable servers, ICacheRefresher refresher, Func getNumericId, params T[] instances) { if (servers == null) throw new ArgumentNullException("servers"); @@ -100,7 +118,7 @@ namespace Umbraco.Core.Sync if (servers == null) throw new ArgumentNullException("servers"); if (refresher == null) throw new ArgumentNullException("refresher"); - MessageSeversForManyIds(servers, refresher, MessageType.RemoveById, numericIds.Cast()); + MessageSeversForIdsOrJson(servers, refresher, MessageType.RemoveById, numericIds.Cast()); } public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, params int[] numericIds) @@ -108,7 +126,7 @@ namespace Umbraco.Core.Sync if (servers == null) throw new ArgumentNullException("servers"); if (refresher == null) throw new ArgumentNullException("refresher"); - MessageSeversForManyIds(servers, refresher, MessageType.RefreshById, numericIds.Cast()); + MessageSeversForIdsOrJson(servers, refresher, MessageType.RefreshById, numericIds.Cast()); } public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, params Guid[] guidIds) @@ -116,12 +134,12 @@ namespace Umbraco.Core.Sync if (servers == null) throw new ArgumentNullException("servers"); if (refresher == null) throw new ArgumentNullException("refresher"); - MessageSeversForManyIds(servers, refresher, MessageType.RefreshById, guidIds.Cast()); + MessageSeversForIdsOrJson(servers, refresher, MessageType.RefreshById, guidIds.Cast()); } public void PerformRefreshAll(IEnumerable servers, ICacheRefresher refresher) { - MessageSeversForManyIds(servers, refresher, MessageType.RefreshAll, Enumerable.Empty().ToArray()); + MessageSeversForIdsOrJson(servers, refresher, MessageType.RefreshAll, Enumerable.Empty().ToArray()); } private void InvokeMethodOnRefresherInstance(ICacheRefresher refresher, MessageType dispatchType, Func getId, IEnumerable instances) @@ -216,7 +234,7 @@ namespace Umbraco.Core.Sync } } - private void InvokeMethodOnRefresherInstance(ICacheRefresher refresher, MessageType dispatchType, IEnumerable ids) + private void InvokeMethodOnRefresherInstance(ICacheRefresher refresher, MessageType dispatchType, IEnumerable ids = null, string jsonPayload = null) { if (refresher == null) throw new ArgumentNullException("refresher"); @@ -227,35 +245,55 @@ namespace Umbraco.Core.Sync } else { - foreach (var id in ids) + if (ids != null) { + foreach (var id in ids) + { + //if we are not, then just invoke the call on the cache refresher + switch (dispatchType) + { + case MessageType.RefreshById: + if (id is int) + { + refresher.Refresh((int) id); + } + else if (id is Guid) + { + refresher.Refresh((Guid) id); + } + else + { + throw new InvalidOperationException("The id must be either an int or a Guid"); + } + + break; + case MessageType.RemoveById: + refresher.Remove((int) id); + break; + } + } + } + else + { + //we can only proceed if the cache refresher is IJsonCacheRefresher! + var jsonRefresher = refresher as IJsonCacheRefresher; + if (jsonRefresher == null) + { + throw new InvalidOperationException("The cache refresher " + refresher.GetType() + " is not of type " + typeof(IJsonCacheRefresher)); + } + //if we are not, then just invoke the call on the cache refresher switch (dispatchType) - { - case MessageType.RefreshById: - if (id is int) - { - refresher.Refresh((int)id); - } - else if (id is Guid) - { - refresher.Refresh((Guid)id); - } - else - { - throw new InvalidOperationException("The id must be either an int or a Guid"); - } - + { + case MessageType.RefreshByJson: + jsonRefresher.Refresh(jsonPayload); break; - case MessageType.RemoveById: - refresher.Remove((int)id); + case MessageType.RemoveByJson: + jsonRefresher.Remove(jsonPayload); break; } } } - - - } private void MessageSeversForManyObjects( @@ -268,8 +306,6 @@ namespace Umbraco.Core.Sync if (servers == null) throw new ArgumentNullException("servers"); if (refresher == null) throw new ArgumentNullException("refresher"); - EnsureLazyUsernamePasswordDelegateResolved(); - //Now, check if we are using Distrubuted calls. If there are no servers in the list then we // can definitely not distribute. if (!_useDistributedCalls || !servers.Any()) @@ -280,36 +316,42 @@ namespace Umbraco.Core.Sync } //if we are distributing calls then we'll need to do it by id - MessageSeversForManyIds(servers, refresher, dispatchType, instances.Select(getId)); + MessageSeversForIdsOrJson(servers, refresher, dispatchType, instances.Select(getId)); } - private void MessageSeversForManyIds( + private void MessageSeversForIdsOrJson( IEnumerable servers, ICacheRefresher refresher, MessageType dispatchType, - IEnumerable ids) + IEnumerable ids = null, + string jsonPayload = null) { if (servers == null) throw new ArgumentNullException("servers"); if (refresher == null) throw new ArgumentNullException("refresher"); Type arrayType = null; - foreach (var id in ids) + if (ids != null) { - if (!(id is int) && (!(id is Guid))) - throw new ArgumentException("The id must be either an int or a Guid"); - if (arrayType == null) - arrayType = id.GetType(); - if (arrayType != id.GetType()) - throw new ArgumentException("The array must contain the same type of " + arrayType); + foreach (var id in ids) + { + if (!(id is int) && (!(id is Guid))) + throw new ArgumentException("The id must be either an int or a Guid"); + if (arrayType == null) + arrayType = id.GetType(); + if (arrayType != id.GetType()) + throw new ArgumentException("The array must contain the same type of " + arrayType); + } } - + //Now, check if we are using Distrubuted calls. If there are no servers in the list then we // can definitely not distribute. if (!_useDistributedCalls || !servers.Any()) { //if we are not, then just invoke the call on the cache refresher - InvokeMethodOnRefresherInstance(refresher, dispatchType, ids); + InvokeMethodOnRefresherInstance(refresher, dispatchType, ids, jsonPayload); return; } + + EnsureLazyUsernamePasswordDelegateResolved(); //We are using distributed calls, so lets make them... try @@ -334,6 +376,16 @@ namespace Umbraco.Core.Sync // Add the returned WaitHandle to the list for later checking switch (dispatchType) { + case MessageType.RefreshByJson: + asyncResultsList.Add( + cacheRefresher.BeginRefreshByJson( + refresher.UniqueIdentifier, jsonPayload, _login, _password, null, null)); + break; + case MessageType.RemoveByJson: + asyncResultsList.Add( + cacheRefresher.BeginRemoveByJson( + refresher.UniqueIdentifier, jsonPayload, _login, _password, null, null)); + break; case MessageType.RefreshAll: asyncResultsList.Add( cacheRefresher.BeginRefreshAll( @@ -383,6 +435,12 @@ namespace Umbraco.Core.Sync // Find out if the call succeeded switch (dispatchType) { + case MessageType.RefreshByJson: + cacheRefresher.EndRefreshByJson(t); + break; + case MessageType.RemoveByJson: + cacheRefresher.EndRemoveByJson(t); + break; case MessageType.RefreshAll: cacheRefresher.EndRefreshAll(t); break; diff --git a/src/Umbraco.Core/Sync/IServerMessenger.cs b/src/Umbraco.Core/Sync/IServerMessenger.cs index b7321e5996..b39a6367db 100644 --- a/src/Umbraco.Core/Sync/IServerMessenger.cs +++ b/src/Umbraco.Core/Sync/IServerMessenger.cs @@ -9,6 +9,17 @@ namespace Umbraco.Core.Sync /// internal interface IServerMessenger { + + /// + /// Performs a refresh and sends along the JSON payload to each server + /// + /// + /// + /// + /// A pre-formatted custom json payload to be sent to the servers, the cache refresher will deserialize and use to refresh cache + /// + void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, string jsonPayload); + /// /// Performs a sync against all instance objects /// @@ -29,6 +40,16 @@ namespace Umbraco.Core.Sync /// void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, Func getGuidId, params T[] instances); + /// + /// Performs a remove and sends along the JSON payload to each server + /// + /// + /// + /// + /// A pre-formatted custom json payload to be sent to the servers, the cache refresher will deserialize and use to remove cache + /// + void PerformRemove(IEnumerable servers, ICacheRefresher refresher, string jsonPayload); + /// /// Removes the cache for the specified items /// diff --git a/src/Umbraco.Core/Sync/MessageType.cs b/src/Umbraco.Core/Sync/MessageType.cs index c4c66e99e2..8b0b69a026 100644 --- a/src/Umbraco.Core/Sync/MessageType.cs +++ b/src/Umbraco.Core/Sync/MessageType.cs @@ -7,6 +7,8 @@ { RefreshAll, RefreshById, - RemoveById + RefreshByJson, + RemoveById, + RemoveByJson } } \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs b/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs index ca2add64d4..c45ceace23 100644 --- a/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs +++ b/src/Umbraco.Core/Sync/ServerSyncWebServiceClient.cs @@ -46,6 +46,60 @@ namespace Umbraco.Core.Sync this.EndInvoke(asyncResult); } + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RemoveByJson", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public void RemoveByJson(System.Guid uniqueIdentifier, string jsonPayload, string Login, string Password) + { + this.Invoke("RemoveByJson", new object[] { + uniqueIdentifier, + jsonPayload, + Login, + Password}); + } + + /// + public System.IAsyncResult BeginRemoveByJson(System.Guid uniqueIdentifier, string jsonPayload, string Login, string Password, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("RemoveByJson", new object[] { + uniqueIdentifier, + jsonPayload, + Login, + Password}, callback, asyncState); + } + + /// + public void EndRemoveByJson(System.IAsyncResult asyncResult) + { + this.EndInvoke(asyncResult); + } + + /// + [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshByJson", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] + public void RefreshByJson(System.Guid uniqueIdentifier, string jsonPayload, string Login, string Password) + { + this.Invoke("RefreshByJson", new object[] { + uniqueIdentifier, + jsonPayload, + Login, + Password}); + } + + /// + public System.IAsyncResult BeginRefreshByJson(System.Guid uniqueIdentifier, string jsonPayload, string Login, string Password, System.AsyncCallback callback, object asyncState) + { + return this.BeginInvoke("RefreshByJson", new object[] { + uniqueIdentifier, + jsonPayload, + Login, + Password}, callback, asyncState); + } + + /// + public void EndRefreshByJson(System.IAsyncResult asyncResult) + { + this.EndInvoke(asyncResult); + } + /// [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshByGuid", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] public void RefreshByGuid(System.Guid uniqueIdentifier, System.Guid Id, string Login, string Password) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 930c94dc72..06999311dc 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -109,6 +109,7 @@ + diff --git a/src/Umbraco.Tests/Sync/DistributedCacheTests.cs b/src/Umbraco.Tests/Sync/DistributedCacheTests.cs index cc8fc0fc76..5e667645b9 100644 --- a/src/Umbraco.Tests/Sync/DistributedCacheTests.cs +++ b/src/Umbraco.Tests/Sync/DistributedCacheTests.cs @@ -132,8 +132,15 @@ namespace Umbraco.Tests.Sync public List IntIdsRefreshed = new List(); public List GuidIdsRefreshed = new List(); public List IntIdsRemoved = new List(); + public List PayloadsRemoved = new List(); + public List PayloadsRefreshed = new List(); public int CountOfFullRefreshes = 0; - + + + public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, string jsonPayload) + { + PayloadsRefreshed.Add(jsonPayload); + } public void PerformRefresh(IEnumerable servers, ICacheRefresher refresher, Func getNumericId, params T[] instances) { @@ -145,6 +152,11 @@ namespace Umbraco.Tests.Sync GuidIdsRefreshed.AddRange(instances.Select(getGuidId)); } + public void PerformRemove(IEnumerable servers, ICacheRefresher refresher, string jsonPayload) + { + PayloadsRemoved.Add(jsonPayload); + } + public void PerformRemove(IEnumerable servers, ICacheRefresher refresher, Func getNumericId, params T[] instances) { IntIdsRemoved.AddRange(instances.Select(getNumericId)); diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 73fe852abe..1b4ff703d6 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -21,8 +21,6 @@ namespace Umbraco.Web.Cache { protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { - if (UmbracoSettings.UmbracoLibraryCacheDuration <= 0) return; - //Bind to content events - currently used for: // - macro clearing // - clearing the xslt cache (MS.Internal.Xml.XPath.XPathSelectionIterator) @@ -97,7 +95,7 @@ namespace Umbraco.Web.Cache /// static void ContentTypeServiceSavedMediaType(IContentTypeService sender, Core.Events.SaveEventArgs e) { - e.SavedEntities.ForEach(x => DistributedCache.Instance.RemoveMediaTypeCache(x)); + e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshMediaTypeCache(x)); } /// @@ -107,7 +105,7 @@ namespace Umbraco.Web.Cache /// static void ContentTypeServiceSavedContentType(IContentTypeService sender, Core.Events.SaveEventArgs e) { - e.SavedEntities.ForEach(contentType => DistributedCache.Instance.RemoveContentTypeCache(contentType)); + e.SavedEntities.ForEach(contentType => DistributedCache.Instance.RefreshContentTypeCache(contentType)); } /// diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 4f8aed80d8..ecf7a8d03f 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Web.Script.Serialization; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; @@ -20,8 +21,90 @@ namespace Umbraco.Web.Cache /// /// This is not intended to be used directly in your code /// - public sealed class ContentTypeCacheRefresher : ICacheRefresher, ICacheRefresher + public sealed class ContentTypeCacheRefresher : IJsonCacheRefresher { + + #region Static helpers + + /// + /// Converts the json to a JsonPayload object + /// + /// + /// + private static JsonPayload[] DeserializeFromJsonPayload(string json) + { + var serializer = new JavaScriptSerializer(); + var jsonObject = serializer.Deserialize(json); + return jsonObject; + } + + /// + /// Converts a content type to a jsonPayload object + /// + /// + /// if the item was deleted + /// + private static JsonPayload FromContentType(IContentTypeBase contentType, bool isDeleted = false) + { + var payload = new JsonPayload + { + Alias = contentType.Alias, + Id = contentType.Id, + PropertyTypeIds = contentType.PropertyTypes.Select(x => x.Id).ToArray(), + //either IContentType or IMediaType + Type = (contentType is IContentType) ? typeof(IContentType).Name : typeof(IMediaType).Name, + DescendantPayloads = contentType.Descendants().Select(x => FromContentType(x)).ToArray(), + WasDeleted = isDeleted + }; + //here we need to check if the alias of the content type changed or if one of the properties was removed. + var dirty = contentType as IRememberBeingDirty; + if (dirty != null) + { + payload.PropertyRemoved = dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved"); + payload.AliasChanged = dirty.WasPropertyDirty("Alias"); + payload.IsNew = dirty.WasPropertyDirty("HasIdentity"); + } + return payload; + } + + /// + /// Creates the custom Json payload used to refresh cache amongst the servers + /// + /// specify false if this is an update, otherwise true if it is a deletion + /// + /// + internal static string SerializeToJsonPayload(bool isDeleted, params IContentTypeBase[] contentTypes) + { + var serializer = new JavaScriptSerializer(); + var items = contentTypes.Select(x => FromContentType(x, isDeleted)).ToArray(); + var json = serializer.Serialize(items); + return json; + } + + #endregion + + #region Sub classes + + private class JsonPayload + { + public JsonPayload() + { + WasDeleted = false; + IsNew = false; + } + public string Alias { get; set; } + public int Id { get; set; } + public int[] PropertyTypeIds { get; set; } + public string Type { get; set; } + public bool AliasChanged { get; set; } + public bool PropertyRemoved { get; set; } + public JsonPayload[] DescendantPayloads { get; set; } + public bool WasDeleted { get; set; } + public bool IsNew { get; set; } + } + + #endregion + public Guid UniqueIdentifier { get { return new Guid(DistributedCache.ContentTypeCacheRefresherId); } @@ -45,42 +128,42 @@ namespace Umbraco.Web.Cache public void Refresh(int id) { - ClearContentTypeCache(id); + ClearContentTypeCache(false, id); } public void Remove(int id) { - ClearContentTypeCache(id); + ClearContentTypeCache(true, id); } public void Refresh(Guid id) { + } + + /// + /// Refreshes the cache using the custom jsonPayload provided + /// + /// + public void Refresh(string jsonPayload) + { + var payload = DeserializeFromJsonPayload(jsonPayload); + ClearContentTypeCache(payload); } - public void Refresh(IContentType instance) + /// + /// Removes the cache using the custom jsonPayload provided + /// + /// + public void Remove(string jsonPayload) { - ClearContentTypeCache(instance); - } - - public void Remove(IContentType instance) - { - ClearContentTypeCache(instance); - } - - public void Refresh(IMediaType instance) - { - ClearContentTypeCache(instance); - } - - public void Remove(IMediaType instance) - { - ClearContentTypeCache(instance); + var payload = DeserializeFromJsonPayload(jsonPayload); + ClearContentTypeCache(payload); } /// /// This clears out all cache associated with a content type /// - /// + /// /// /// The cache that is required to be cleared when a content type is updated is as follows: /// - ApplicationCache (keys to clear): @@ -95,26 +178,23 @@ namespace Umbraco.Web.Cache /// it is only handled in the ContentTypeControlNew.ascx, not by business logic/events. - The xml cache needs to be updated /// when the doc type alias changes or when a property type is removed, the ContentService.RePublishAll should be executed anytime either of these happens. /// - private static void ClearContentTypeCache(params IContentTypeBase[] contentTypes) + private static void ClearContentTypeCache(IEnumerable payloads) { var needsContentRefresh = false; - contentTypes.ForEach(contentType => + payloads.ForEach(payload => { //clear the cache for each item - ClearContentTypeCache(contentType); - - //we only need to do this for IContentType NOT for IMediaType, we don't want to refresh the whole cache - //if a media type has changed. - if (contentType is IContentType) + ClearContentTypeCache(payload); + + //we only need to do this for IContentType NOT for IMediaType, we don't want to refresh the whole cache. + //if the item was deleted or the alias changed or property removed then we need to refresh the content. + //and, don't refresh the cache if it is new. + if (payload.Type == typeof(IContentType).Name + && !payload.IsNew + && (payload.WasDeleted || payload.AliasChanged || payload.PropertyRemoved)) { - //here we need to check if the alias of the content type changed or if one of the properties was removed. - var dirty = contentType as IRememberBeingDirty; - if (dirty == null) return; - if (dirty.WasPropertyDirty("Alias") || dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved")) - { - needsContentRefresh = true; - } + needsContentRefresh = true; } }); @@ -126,14 +206,16 @@ namespace Umbraco.Web.Cache } //clear the cache providers if there were any content types to clear - if (contentTypes.Any()) + if (payloads.Any()) { InMemoryCacheProvider.Current.Clear(); RuntimeCacheProvider.Current.Clear(); //we only need to do this for IContentType NOT for IMediaType, we don't want to refresh the whole routes //cache if only a media type has changed. - if (contentTypes.Any(x => x is IContentType)) + //we don't want to update the routes cache if all of the content types here are new. + if (payloads.Any(x => x.Type == typeof(IContentType).Name) + && !payloads.All(x => x.IsNew)) //if they are all new then don't proceed { //we need to clear the routes cache here! //TODO: Is there a better way to handle this without casting ? @@ -149,7 +231,7 @@ namespace Umbraco.Web.Cache /// /// Clears cache for an individual IContentTypeBase object /// - /// + /// /// /// See notes for the other overloaded ClearContentTypeCache for /// full details on clearing cache. @@ -157,44 +239,43 @@ namespace Umbraco.Web.Cache /// /// Return true if the alias of the content type changed /// - private static void ClearContentTypeCache(IContentTypeBase contentType) + private static void ClearContentTypeCache(JsonPayload payload) { //clears the cache for each property type associated with the content type - foreach (var p in contentType.PropertyTypes) + foreach (var pid in payload.PropertyTypeIds) { - ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.PropertyTypeCacheKey + p.Id); + ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.PropertyTypeCacheKey + pid); } //clears the cache associated with the Content type itself - ApplicationContext.Current.ApplicationCache.ClearCacheItem(string.Format("{0}{1}", CacheKeys.ContentTypeCacheKey, contentType.Id)); + ApplicationContext.Current.ApplicationCache.ClearCacheItem(string.Format("{0}{1}", CacheKeys.ContentTypeCacheKey, payload.Id)); //clears the cache associated with the content type properties collection - ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.ContentTypePropertiesCacheKey + contentType.Id); + ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.ContentTypePropertiesCacheKey + payload.Id); //clears the dictionary object cache of the legacy ContentType - global::umbraco.cms.businesslogic.ContentType.RemoveFromDataTypeCache(contentType.Alias); + global::umbraco.cms.businesslogic.ContentType.RemoveFromDataTypeCache(payload.Alias); //need to recursively clear the cache for each child content type - // performance related to http://issues.umbraco.org/issue/U4-1714 - var dtos = ApplicationContext.Current.DatabaseContext.Database.Fetch("WHERE parentContentTypeId = @Id", new { Id = contentType.Id }); - foreach (var dto in dtos) + foreach (var descendant in payload.DescendantPayloads) { - ClearContentTypeCache(dto.ChildId); + ClearContentTypeCache(descendant); } } /// /// Clears the cache for any content type with the specified Ids /// + /// true if the entity was deleted, false if it is just an update /// - private static void ClearContentTypeCache(params int[] ids) + private static void ClearContentTypeCache(bool isDeleted, params int[] ids) { ClearContentTypeCache( ids.Select( x => ApplicationContext.Current.Services.ContentTypeService.GetContentType(x) as IContentTypeBase) .WhereNotNull() + .Select(x => FromContentType(x, isDeleted)) .ToArray()); } - } } diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index e108c3d5cd..9d1cd2efeb 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -12,6 +12,7 @@ namespace Umbraco.Web.Cache /// public static class DistributedCacheExtensions { + #region User cache public static void RemoveUserCache(this DistributedCache dc, int userId) { dc.Remove(new Guid(DistributedCache.UserCacheRefresherId), userId); @@ -20,8 +21,10 @@ namespace Umbraco.Web.Cache public static void RefreshUserCache(this DistributedCache dc, int userId) { dc.Refresh(new Guid(DistributedCache.UserCacheRefresherId), userId); - } + } + #endregion + #region Template cache /// /// Refreshes the cache amongst servers for a template /// @@ -40,8 +43,10 @@ namespace Umbraco.Web.Cache public static void RemoveTemplateCache(this DistributedCache dc, int templateId) { dc.Remove(new Guid(DistributedCache.TemplateRefresherId), templateId); - } + } + #endregion + #region Page cache /// /// Refreshes the cache amongst servers for all pages /// @@ -59,7 +64,7 @@ namespace Umbraco.Web.Cache public static void RefreshPageCache(this DistributedCache dc, int documentId) { dc.Refresh(new Guid(DistributedCache.PageCacheRefresherId), documentId); - } + } /// /// Refreshes page cache for all instances passed in @@ -89,8 +94,10 @@ namespace Umbraco.Web.Cache public static void RemovePageCache(this DistributedCache dc, int documentId) { dc.Remove(new Guid(DistributedCache.PageCacheRefresherId), documentId); - } + } + #endregion + #region Member cache /// /// Refreshes the cache amongst servers for a member /// @@ -109,8 +116,10 @@ namespace Umbraco.Web.Cache public static void RemoveMemberCache(this DistributedCache dc, int memberId) { dc.Remove(new Guid(DistributedCache.MemberCacheRefresherId), memberId); - } + } + #endregion + #region Media Cache /// /// Refreshes the cache amongst servers for a media item /// @@ -149,8 +158,21 @@ namespace Umbraco.Web.Cache public static void RemoveMediaCache(this DistributedCache dc, params IMedia[] media) { dc.Remove(new Guid(DistributedCache.MediaCacheRefresherId), x => x.Id, media); - } + } + #endregion + #region Macro Cache + + /// + /// Clears the cache for all macros on the current server + /// + /// + public static void ClearAllMacroCacheOnCurrentServer(this DistributedCache dc) + { + //NOTE: The 'false' ensure that it will only refresh on the current server, not post to all servers + dc.RefreshAll(new Guid(DistributedCache.MacroCacheRefresherId), false); + } + /// /// Refreshes the cache amongst servers for a macro item /// @@ -182,7 +204,7 @@ namespace Umbraco.Web.Cache public static void RemoveMacroCache(this DistributedCache dc, int macroId) { dc.Remove(new Guid(DistributedCache.MacroCacheRefresherId), macroId); - } + } /// /// Removes the cache amongst servers for a macro item @@ -208,6 +230,39 @@ namespace Umbraco.Web.Cache { dc.Remove(new Guid(DistributedCache.MacroCacheRefresherId), macro1 => macro1.Model.Id, macro); } + } + #endregion + + #region Content type cache + + /// + /// Remove all cache for a given content type + /// + /// + /// + public static void RefreshContentTypeCache(this DistributedCache dc, IContentType contentType) + { + if (contentType != null) + { + //dc.Refresh(new Guid(DistributedCache.ContentTypeCacheRefresherId), x => x.Id, contentType); + dc.RefreshByJson(new Guid(DistributedCache.ContentTypeCacheRefresherId), + ContentTypeCacheRefresher.SerializeToJsonPayload(false, contentType)); + } + } + + /// + /// Remove all cache for a given media type + /// + /// + /// + public static void RefreshMediaTypeCache(this DistributedCache dc, IMediaType mediaType) + { + if (mediaType != null) + { + //dc.Refresh(new Guid(DistributedCache.ContentTypeCacheRefresherId), x => x.Id, mediaType); + dc.RefreshByJson(new Guid(DistributedCache.ContentTypeCacheRefresherId), + ContentTypeCacheRefresher.SerializeToJsonPayload(false, mediaType)); + } } /// @@ -219,7 +274,9 @@ namespace Umbraco.Web.Cache { if (contentType != null) { - dc.Remove(new Guid(DistributedCache.ContentTypeCacheRefresherId), x => x.Id, contentType); + //dc.Remove(new Guid(DistributedCache.ContentTypeCacheRefresherId), x => x.Id, contentType); + dc.RemoveByJson(new Guid(DistributedCache.ContentTypeCacheRefresherId), + ContentTypeCacheRefresher.SerializeToJsonPayload(true, contentType)); } } @@ -232,19 +289,12 @@ namespace Umbraco.Web.Cache { if (mediaType != null) { - dc.Remove(new Guid(DistributedCache.ContentTypeCacheRefresherId), x => x.Id, mediaType); + //dc.Remove(new Guid(DistributedCache.ContentTypeCacheRefresherId), x => x.Id, mediaType); + dc.RemoveByJson(new Guid(DistributedCache.ContentTypeCacheRefresherId), + ContentTypeCacheRefresher.SerializeToJsonPayload(true, mediaType)); } - } - - /// - /// Clears the cache for all macros on the current server - /// - /// - public static void ClearAllMacroCacheOnCurrentServer(this DistributedCache dc) - { - //NOTE: The 'false' ensure that it will only refresh on the current server, not post to all servers - dc.RefreshAll(new Guid(DistributedCache.MacroCacheRefresherId), false); - } + } + #endregion public static void ClearXsltCacheOnCurrentServer(this DistributedCache dc) { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/nodetypeTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/nodetypeTasks.cs index 16658ff59a..8bf4cfa489 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/nodetypeTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/nodetypeTasks.cs @@ -2,6 +2,7 @@ using System; using System.Data; using System.Web.Security; using Umbraco.Core; +using Umbraco.Core.Models; using umbraco.BusinessLogic; using umbraco.DataLayer; using umbraco.BasePages; @@ -43,26 +44,27 @@ namespace umbraco public bool Save() { - var dt = cms.businesslogic.web.DocumentType.MakeNew(BusinessLogic.User.GetUser(_userID), Alias.Replace("'", "''")); - dt.IconUrl = "folder.gif"; - + //NOTE: TypeID is the parent id! + //NOTE: ParentID is aparently a flag to determine if we are to create a template! Hack much ?! :P + var contentType = new ContentType(TypeID != 0 ? TypeID : -1) + { + CreatorId = _userID, + Alias = Alias.Replace("'", "''"), + Icon = "folder.gif", + Name = Alias.Replace("'", "''") + }; // Create template? if (ParentID == 1) { - cms.businesslogic.template.Template[] t = { cms.businesslogic.template.Template.MakeNew(_alias, BusinessLogic.User.GetUser(_userID)) }; - dt.allowedTemplates = t; - dt.DefaultTemplate = t[0].Id; + var template = new Template(string.Empty, _alias, _alias); + ApplicationContext.Current.Services.FileService.SaveTemplate(template, _userID); + + contentType.AllowedTemplates = new[] {template}; + contentType.DefaultTemplateId = template.Id; } + ApplicationContext.Current.Services.ContentTypeService.Save(contentType); - // Master Content Type? - if (TypeID != 0) - { - dt.MasterContentType = TypeID; - } - - dt.Save(); - - m_returnUrl = "settings/editNodeTypeNew.aspx?id=" + dt.Id.ToString(); + m_returnUrl = "settings/editNodeTypeNew.aspx?id=" + contentType.Id.ToString(); return true; } @@ -77,13 +79,6 @@ namespace umbraco return false; } - public nodetypeTasks() - { - // - // TODO: Add constructor logic here - // - } - #region ITaskReturnUrl Members private string m_returnUrl = ""; public string ReturnUrl diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs index 23f43d7685..2be399f850 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs @@ -9,6 +9,7 @@ using System.Web.Script.Serialization; using System.Web.Services; using System.Xml; using Umbraco.Core; +using Umbraco.Core.Cache; namespace umbraco.presentation.webservices { @@ -107,6 +108,54 @@ namespace umbraco.presentation.webservices } } + /// + /// Refreshes objects using the passed in Json payload, it will be up to the cache refreshers to deserialize + /// + /// + /// A custom JSON payload used by the cache refresher + /// + /// + /// + /// NOTE: the cache refresher defined by the ID MUST be of type IJsonCacheRefresher or an exception will be thrown + /// + [WebMethod] + public void RefreshByJson(Guid uniqueIdentifier, string jsonPayload, string Login, string Password) + { + if (BusinessLogic.User.validateCredentials(Login, Password)) + { + var cr = CacheRefreshersResolver.Current.GetById(uniqueIdentifier) as IJsonCacheRefresher; + if (cr == null) + { + throw new InvalidOperationException("The cache refresher: " + uniqueIdentifier + " is not of type " + typeof (IJsonCacheRefresher)); + } + cr.Refresh(jsonPayload); + } + } + + /// + /// Removes objects using the passed in Json payload, it will be up to the cache refreshers to deserialize + /// + /// + /// A custom JSON payload used by the cache refresher + /// + /// + /// + /// NOTE: the cache refresher defined by the ID MUST be of type IJsonCacheRefresher or an exception will be thrown + /// + [WebMethod] + public void RemoveByJson(Guid uniqueIdentifier, string jsonPayload, string Login, string Password) + { + if (BusinessLogic.User.validateCredentials(Login, Password)) + { + var cr = CacheRefreshersResolver.Current.GetById(uniqueIdentifier) as IJsonCacheRefresher; + if (cr == null) + { + throw new InvalidOperationException("The cache refresher: " + uniqueIdentifier + " is not of type " + typeof(IJsonCacheRefresher)); + } + cr.Remove(jsonPayload); + } + } + [WebMethod] public void RemoveById(Guid uniqueIdentifier, int Id, string Login, string Password) {