Creates IRememberBeingDirty interface to track the properties that we're changed before the last commit for a given entity.
Adds internal property and tracks it's dirty changes to the ContentTypeBase - HasPropertyBeenRemoved. Fixes: #U4-1942 - ensures content xml cache is refreshed even across servers when a property is removed or when the alias is changed.
This commit is contained in:
@@ -31,6 +31,8 @@ namespace Umbraco.Core.Models
|
||||
private PropertyGroupCollection _propertyGroups;
|
||||
private PropertyTypeCollection _propertyTypes;
|
||||
private IEnumerable<ContentTypeSort> _allowedContentTypes;
|
||||
private bool _hasPropertyTypeBeenRemoved;
|
||||
|
||||
|
||||
protected ContentTypeBase(int parentId)
|
||||
{
|
||||
@@ -68,6 +70,8 @@ namespace Umbraco.Core.Models
|
||||
private static readonly PropertyInfo AllowedContentTypesSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, IEnumerable<ContentTypeSort>>(x => x.AllowedContentTypes);
|
||||
private static readonly PropertyInfo PropertyGroupCollectionSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, PropertyGroupCollection>(x => x.PropertyGroups);
|
||||
private static readonly PropertyInfo PropertyTypeCollectionSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, IEnumerable<PropertyType>>(x => x.PropertyTypes);
|
||||
private static readonly PropertyInfo HasPropertyTypeBeenRemovedSelector = ExpressionHelper.GetPropertyInfo<ContentTypeBase, bool>(x => x.HasPropertyTypeBeenRemoved);
|
||||
|
||||
|
||||
protected void PropertyGroupsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
@@ -153,8 +157,11 @@ namespace Umbraco.Core.Models
|
||||
get { return _alias; }
|
||||
set
|
||||
{
|
||||
_alias = value.ToSafeAlias();
|
||||
OnPropertyChanged(AliasSelector);
|
||||
SetPropertyValueAndDetectChanges(o =>
|
||||
{
|
||||
_alias = value.ToSafeAlias();
|
||||
return _alias;
|
||||
}, _alias, AliasSelector);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,6 +329,24 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A boolean flag indicating if a property type has been removed from this instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is currently (specifically) used in order to know that we need to refresh the content cache which
|
||||
/// needs to occur when a property has been removed from a content type
|
||||
/// </remarks>
|
||||
[IgnoreDataMember]
|
||||
internal bool HasPropertyTypeBeenRemoved
|
||||
{
|
||||
get { return _hasPropertyTypeBeenRemoved; }
|
||||
private set
|
||||
{
|
||||
_hasPropertyTypeBeenRemoved = value;
|
||||
OnPropertyChanged(HasPropertyTypeBeenRemovedSelector);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a PropertyType with a given alias already exists
|
||||
/// </summary>
|
||||
@@ -396,6 +421,15 @@ namespace Umbraco.Core.Models
|
||||
/// <param name="propertyTypeAlias">Alias of the <see cref="PropertyType"/> to remove</param>
|
||||
public void RemovePropertyType(string propertyTypeAlias)
|
||||
{
|
||||
|
||||
//check if the property exist in one of our collections
|
||||
if (PropertyGroups.Any(group => group.PropertyTypes.Any(pt => pt.Alias == propertyTypeAlias))
|
||||
|| _propertyTypes.Any(x => x.Alias == propertyTypeAlias))
|
||||
{
|
||||
//set the flag that a property has been removed
|
||||
HasPropertyTypeBeenRemoved = true;
|
||||
}
|
||||
|
||||
foreach (var propertyGroup in PropertyGroups)
|
||||
{
|
||||
propertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias);
|
||||
@@ -403,7 +437,7 @@ namespace Umbraco.Core.Models
|
||||
|
||||
if (_propertyTypes.Any(x => x.Alias == propertyTypeAlias))
|
||||
{
|
||||
_propertyTypes.RemoveItem(propertyTypeAlias);
|
||||
_propertyTypes.RemoveItem(propertyTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
[DebuggerDisplay("Id: {Id}")]
|
||||
public abstract class Entity : IEntity, ICanBeDirty
|
||||
public abstract class Entity : IEntity, IRememberBeingDirty, ICanBeDirty
|
||||
{
|
||||
private bool _hasIdentity;
|
||||
private int? _hash;
|
||||
@@ -115,6 +115,11 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
/// </summary>
|
||||
private readonly IDictionary<string, bool> _propertyChangedInfo = new Dictionary<string, bool>();
|
||||
|
||||
/// <summary>
|
||||
/// Tracks the properties that we're changed before the last commit (or last call to ResetDirtyProperties)
|
||||
/// </summary>
|
||||
private IDictionary<string, bool> _lastPropertyChangedInfo = null;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether a specific property on the current entity is dirty.
|
||||
/// </summary>
|
||||
@@ -134,6 +139,33 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
return _propertyChangedInfo.Any();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the entity had been changed and the changes were committed
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool WasDirty()
|
||||
{
|
||||
return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether a specific property on the current entity was changed and the changes were committed
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property to check</param>
|
||||
/// <returns>True if Property was changed, otherwise False. Returns false if the entity had not been previously changed.</returns>
|
||||
public virtual bool WasPropertyDirty(string propertyName)
|
||||
{
|
||||
return WasDirty() && _lastPropertyChangedInfo.Any(x => x.Key == propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the remembered dirty properties from before the last commit
|
||||
/// </summary>
|
||||
public void ForgetPreviouslyDirtyProperties()
|
||||
{
|
||||
_lastPropertyChangedInfo.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets dirty properties by clearing the dictionary used to track changes.
|
||||
/// </summary>
|
||||
@@ -143,9 +175,37 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
/// </remarks>
|
||||
public virtual void ResetDirtyProperties()
|
||||
{
|
||||
//copy the changed properties to the last changed properties
|
||||
_lastPropertyChangedInfo = _propertyChangedInfo.ToDictionary(v => v.Key, v => v.Value);
|
||||
|
||||
_propertyChangedInfo.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did
|
||||
/// it will ensure that the property has a dirty flag set.
|
||||
/// </summary>
|
||||
/// <param name="setValue"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="propertySelector"></param>
|
||||
/// <returns>returns true if the value changed</returns>
|
||||
/// <remarks>
|
||||
/// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we
|
||||
/// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set
|
||||
/// to the same value, so it's really not dirty.
|
||||
/// </remarks>
|
||||
internal bool SetPropertyValueAndDetectChanges<T>(Func<T, T> setValue, T value, PropertyInfo propertySelector)
|
||||
{
|
||||
var initVal = value;
|
||||
var newVal = setValue(value);
|
||||
if (!Equals(initVal, newVal))
|
||||
{
|
||||
OnPropertyChanged(propertySelector);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current entity has an identity, eg. Id.
|
||||
/// </summary>
|
||||
|
||||
13
src/Umbraco.Core/Models/EntityBase/IRememberBeingDirty.cs
Normal file
13
src/Umbraco.Core/Models/EntityBase/IRememberBeingDirty.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Umbraco.Core.Models.EntityBase
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface that defines if the object is tracking property changes and that is is also
|
||||
/// remembering what property changes had been made after the changes were committed.
|
||||
/// </summary>
|
||||
public interface IRememberBeingDirty : ICanBeDirty
|
||||
{
|
||||
bool WasDirty();
|
||||
bool WasPropertyDirty(string propertyName);
|
||||
void ForgetPreviouslyDirtyProperties();
|
||||
}
|
||||
}
|
||||
@@ -166,6 +166,7 @@
|
||||
<Compile Include="Models\Css\TrieNode.cs" />
|
||||
<Compile Include="Models\DictionaryItem.cs" />
|
||||
<Compile Include="Models\DictionaryTranslation.cs" />
|
||||
<Compile Include="Models\EntityBase\IRememberBeingDirty.cs" />
|
||||
<Compile Include="Models\EntityBase\IUmbracoEntity.cs" />
|
||||
<Compile Include="Models\File.cs" />
|
||||
<Compile Include="Models\IDataTypeDefinition.cs" />
|
||||
|
||||
@@ -404,6 +404,51 @@ namespace Umbraco.Tests.Models
|
||||
//Assert.That(contentType.IsPropertyDirty("PropertyGroups"), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void After_Committing_Changes_Was_Dirty_Is_True()
|
||||
{
|
||||
// Arrange
|
||||
var contentType = MockedContentTypes.CreateTextpageContentType();
|
||||
contentType.ResetDirtyProperties(); //reset
|
||||
|
||||
// Act
|
||||
contentType.Alias = "newAlias";
|
||||
contentType.ResetDirtyProperties(); //this would be like committing the entity
|
||||
|
||||
// Assert
|
||||
Assert.That(contentType.IsDirty(), Is.False);
|
||||
Assert.That(contentType.WasDirty(), Is.True);
|
||||
Assert.That(contentType.WasPropertyDirty("Alias"), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void If_Not_Committed_Was_Dirty_Is_False()
|
||||
{
|
||||
// Arrange
|
||||
var contentType = MockedContentTypes.CreateTextpageContentType();
|
||||
|
||||
// Act
|
||||
contentType.Alias = "newAlias";
|
||||
|
||||
// Assert
|
||||
Assert.That(contentType.IsDirty(), Is.True);
|
||||
Assert.That(contentType.WasDirty(), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Detect_That_A_Property_Is_Removed()
|
||||
{
|
||||
// Arrange
|
||||
var contentType = MockedContentTypes.CreateTextpageContentType();
|
||||
Assert.That(contentType.WasPropertyDirty("HasPropertyTypeBeenRemoved"), Is.False);
|
||||
|
||||
// Act
|
||||
contentType.RemovePropertyType("title");
|
||||
|
||||
// Assert
|
||||
Assert.That(contentType.IsPropertyDirty("HasPropertyTypeBeenRemoved"), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Adding_PropertyType_To_PropertyGroup_On_ContentType_Results_In_Dirty_Entity()
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Text;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.Caching;
|
||||
|
||||
@@ -87,12 +88,34 @@ namespace Umbraco.Web.Cache
|
||||
/// - InMemoryCacheProvider.Current.Clear();
|
||||
/// - RuntimeCacheProvider.Current.Clear();
|
||||
///
|
||||
/// TODO: Needs to update any content items that this effects for the xml cache... currently it would seem that this is not handled!
|
||||
/// 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.
|
||||
/// TODO: Needs to update any content items that this effects for the xml 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.
|
||||
/// </remarks>
|
||||
private static void ClearContentTypeCache(params IContentTypeBase[] contentTypes)
|
||||
{
|
||||
contentTypes.ForEach(ClearContentTypeCache);
|
||||
var needsContentRefresh = false;
|
||||
|
||||
contentTypes.ForEach(contentType =>
|
||||
{
|
||||
//clear the cache for each item
|
||||
ClearContentTypeCache(contentType);
|
||||
|
||||
//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;
|
||||
}
|
||||
});
|
||||
|
||||
//need to refresh the xml content cache if required
|
||||
if (needsContentRefresh)
|
||||
{
|
||||
var pageRefresher = CacheRefreshersResolver.Current.GetById(new Guid(DistributedCache.PageCacheRefresherId));
|
||||
pageRefresher.RefreshAll();
|
||||
}
|
||||
|
||||
//clear the cache providers if there were any content types to clear
|
||||
if (contentTypes.Any())
|
||||
@@ -110,6 +133,9 @@ namespace Umbraco.Web.Cache
|
||||
/// See notes for the other overloaded ClearContentTypeCache for
|
||||
/// full details on clearing cache.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// Return true if the alias of the content type changed
|
||||
/// </returns>
|
||||
private static void ClearContentTypeCache(IContentTypeBase contentType)
|
||||
{
|
||||
//clears the cache for each property type associated with the content type
|
||||
@@ -131,7 +157,7 @@ namespace Umbraco.Web.Cache
|
||||
foreach (var dto in dtos)
|
||||
{
|
||||
ClearContentTypeCache(dto.ChildId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user