Files
Umbraco-CMS/src/Umbraco.Core/Models/PublicAccessEntry.cs
Shannon 4a3525ece3 Fixes a memory leak caused by deep cloning
There was a memory leak with PublicAccessEntry during even unassignment which was not clearing the correct handler.
This goes a step further and adds a new ClearCollectionChangedEvents method for all observable collections used in umbraco
which allows fully clearing ALL event handlers instead of having to track specific ones. This will ensure there are no
unintended memory leaks in case end-users have assigned event handlers to the collection changed event which would
not be unassigned during deep cloning.
2021-01-12 13:41:50 +11:00

158 lines
5.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Runtime.Serialization;
using Umbraco.Core.Collections;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
[Serializable]
[DataContract(IsReference = true)]
public class PublicAccessEntry : EntityBase
{
private readonly EventClearingObservableCollection<PublicAccessRule> _ruleCollection;
private int _protectedNodeId;
private int _noAccessNodeId;
private int _loginNodeId;
private readonly List<Guid> _removedRules = new List<Guid>();
public PublicAccessEntry(IContent protectedNode, IContent loginNode, IContent noAccessNode, IEnumerable<PublicAccessRule> ruleCollection)
{
if (protectedNode == null) throw new ArgumentNullException(nameof(protectedNode));
if (loginNode == null) throw new ArgumentNullException(nameof(loginNode));
if (noAccessNode == null) throw new ArgumentNullException(nameof(noAccessNode));
LoginNodeId = loginNode.Id;
NoAccessNodeId = noAccessNode.Id;
_protectedNodeId = protectedNode.Id;
_ruleCollection = new EventClearingObservableCollection<PublicAccessRule>(ruleCollection);
_ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged;
foreach (var rule in _ruleCollection)
rule.AccessEntryId = Key;
}
public PublicAccessEntry(Guid id, int protectedNodeId, int loginNodeId, int noAccessNodeId, IEnumerable<PublicAccessRule> ruleCollection)
{
Key = id;
Id = Key.GetHashCode();
LoginNodeId = loginNodeId;
NoAccessNodeId = noAccessNodeId;
_protectedNodeId = protectedNodeId;
_ruleCollection = new EventClearingObservableCollection<PublicAccessRule>(ruleCollection);
_ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged;
foreach (var rule in _ruleCollection)
rule.AccessEntryId = Key;
}
void _ruleCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(nameof(Rules));
//if (e.Action == NotifyCollectionChangedAction.Add)
//{
// var item = e.NewItems.Cast<PublicAccessRule>().First();
// if (_addedSections.Contains(item) == false)
// {
// _addedSections.Add(item);
// }
//}
if (e.Action == NotifyCollectionChangedAction.Remove)
{
var item = e.OldItems.Cast<PublicAccessRule>().First();
if (_removedRules.Contains(item.Key) == false)
{
_removedRules.Add(item.Key);
}
}
}
internal IEnumerable<Guid> RemovedRules => _removedRules;
public IEnumerable<PublicAccessRule> Rules => _ruleCollection;
public PublicAccessRule AddRule(string ruleValue, string ruleType)
{
var rule = new PublicAccessRule
{
AccessEntryId = Key,
RuleValue = ruleValue,
RuleType = ruleType
};
_ruleCollection.Add(rule);
return rule;
}
public void RemoveRule(PublicAccessRule rule)
{
_ruleCollection.Remove(rule);
}
public void ClearRules()
{
_ruleCollection.Clear();
}
internal void ClearRemovedRules()
{
_removedRules.Clear();
}
[DataMember]
public int LoginNodeId
{
get => _loginNodeId;
set => SetPropertyValueAndDetectChanges(value, ref _loginNodeId, nameof(LoginNodeId));
}
[DataMember]
public int NoAccessNodeId
{
get => _noAccessNodeId;
set => SetPropertyValueAndDetectChanges(value, ref _noAccessNodeId, nameof(NoAccessNodeId));
}
[DataMember]
public int ProtectedNodeId
{
get => _protectedNodeId;
set => SetPropertyValueAndDetectChanges(value, ref _protectedNodeId, nameof(ProtectedNodeId));
}
public override void ResetDirtyProperties(bool rememberDirty)
{
_removedRules.Clear();
base.ResetDirtyProperties(rememberDirty);
foreach (var publicAccessRule in _ruleCollection)
{
publicAccessRule.ResetDirtyProperties(rememberDirty);
}
}
protected override void PerformDeepClone(object clone)
{
base.PerformDeepClone(clone);
var cloneEntity = (PublicAccessEntry)clone;
if (cloneEntity._ruleCollection != null)
{
cloneEntity._ruleCollection.ClearCollectionChangedEvents(); //clear this event handler if any
cloneEntity._ruleCollection.CollectionChanged += cloneEntity._ruleCollection_CollectionChanged; //re-assign correct event handler
}
}
}
}