Moved more files and removed modelsbuilder from UI project

This commit is contained in:
Bjarke Berg
2019-05-27 08:57:57 +02:00
parent cd93ebe7c1
commit ff71d89415
16 changed files with 54 additions and 82 deletions

View File

@@ -0,0 +1,382 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
namespace Umbraco.Core.Composing
{
/// <summary>
/// A utility class for type checking, this provides internal caching so that calls to these methods will be faster
/// than doing a manual type check in c#
/// </summary>
public static class TypeHelper
{
private static readonly ConcurrentDictionary<Tuple<Type, bool, bool, bool>, PropertyInfo[]> GetPropertiesCache
= new ConcurrentDictionary<Tuple<Type, bool, bool, bool>, PropertyInfo[]>();
private static readonly ConcurrentDictionary<Type, FieldInfo[]> GetFieldsCache
= new ConcurrentDictionary<Type, FieldInfo[]>();
private static readonly Assembly[] EmptyAssemblies = new Assembly[0];
/// <summary>
/// Based on a type we'll check if it is IEnumerable{T} (or similar) and if so we'll return a List{T}, this will also deal with array types and return List{T} for those too.
/// If it cannot be done, null is returned.
/// </summary>
public static IList CreateGenericEnumerableFromObject(object obj)
{
var type = obj.GetType();
if (type.IsGenericType)
{
var genericTypeDef = type.GetGenericTypeDefinition();
if (genericTypeDef == typeof(IEnumerable<>)
|| genericTypeDef == typeof(ICollection<>)
|| genericTypeDef == typeof(Collection<>)
|| genericTypeDef == typeof(IList<>)
|| genericTypeDef == typeof(List<>)
//this will occur when Linq is used and we get the odd WhereIterator or DistinctIterators since those are special iterator types
|| obj is IEnumerable)
{
//if it is a IEnumerable<>, IList<T> or ICollection<> we'll use a List<>
var genericType = typeof(List<>).MakeGenericType(type.GetGenericArguments());
//pass in obj to fill the list
return (IList)Activator.CreateInstance(genericType, obj);
}
}
if (type.IsArray)
{
//if its an array, we'll use a List<>
var genericType = typeof(List<>).MakeGenericType(type.GetElementType());
//pass in obj to fill the list
return (IList)Activator.CreateInstance(genericType, obj);
}
return null;
}
/// <summary>
/// Checks if the method is actually overriding a base method
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
public static bool IsOverride(MethodInfo m)
{
return m.GetBaseDefinition().DeclaringType != m.DeclaringType;
}
/// <summary>
/// Find all assembly references that are referencing the assignTypeFrom Type's assembly found in the assemblyList
/// </summary>
/// <param name="assembly">The referenced assembly.</param>
/// <param name="assemblies">A list of assemblies.</param>
/// <returns></returns>
/// <remarks>
/// If the assembly of the assignTypeFrom Type is in the App_Code assembly, then we return nothing since things cannot
/// reference that assembly, same with the global.asax assembly.
/// </remarks>
public static Assembly[] GetReferencingAssemblies(Assembly assembly, IEnumerable<Assembly> assemblies)
{
if (assembly.IsAppCodeAssembly() || assembly.IsGlobalAsaxAssembly())
return EmptyAssemblies;
// find all assembly references that are referencing the current type's assembly since we
// should only be scanning those assemblies because any other assembly will definitely not
// contain sub type's of the one we're currently looking for
var name = assembly.GetName().Name;
return assemblies.Where(x => x == assembly || HasReference(x, name)).ToArray();
}
/// <summary>
/// Determines if an assembly references another assembly.
/// </summary>
/// <param name="assembly"></param>
/// <param name="name"></param>
/// <returns></returns>
public static bool HasReference(Assembly assembly, string name)
{
// ReSharper disable once LoopCanBeConvertedToQuery - no!
foreach (var a in assembly.GetReferencedAssemblies())
{
if (string.Equals(a.Name, name, StringComparison.Ordinal)) return true;
}
return false;
}
/// <summary>
/// Returns true if the type is a class and is not static
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public static bool IsNonStaticClass(Type t)
{
return t.IsClass && IsStaticClass(t) == false;
}
/// <summary>
/// Returns true if the type is a static class
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
/// <remarks>
/// In IL a static class is abstract and sealed
/// see: http://stackoverflow.com/questions/1175888/determine-if-a-type-is-static
/// </remarks>
public static bool IsStaticClass(Type type)
{
return type.IsAbstract && type.IsSealed;
}
/// <summary>
/// Finds a lowest base class amongst a collection of types
/// </summary>
/// <param name="types"></param>
/// <returns></returns>
/// <remarks>
/// The term 'lowest' refers to the most base class of the type collection.
/// If a base type is not found amongst the type collection then an invalid attempt is returned.
/// </remarks>
public static Attempt<Type> GetLowestBaseType(params Type[] types)
{
if (types.Length == 0)
return Attempt<Type>.Fail();
if (types.Length == 1)
return Attempt.Succeed(types[0]);
foreach (var curr in types)
{
var others = types.Except(new[] {curr});
//is the current type a common denominator for all others ?
var isBase = others.All(curr.IsAssignableFrom);
//if this type is the base for all others
if (isBase)
{
return Attempt.Succeed(curr);
}
}
return Attempt<Type>.Fail();
}
/// <summary>
/// Determines whether the type <paramref name="implementation"/> is assignable from the specified implementation,
/// and caches the result across the application using a <see cref="ConcurrentDictionary{TKey,TValue}"/>.
/// </summary>
/// <param name="contract">The type of the contract.</param>
/// <param name="implementation">The implementation.</param>
/// <returns>
/// <c>true</c> if [is type assignable from] [the specified contract]; otherwise, <c>false</c>.
/// </returns>
public static bool IsTypeAssignableFrom(Type contract, Type implementation)
{
return contract.IsAssignableFrom(implementation);
}
/// <summary>
/// Determines whether the type <paramref name="implementation"/> is assignable from the specified implementation <typeparamref name="TContract"/>,
/// and caches the result across the application using a <see cref="ConcurrentDictionary{TKey,TValue}"/>.
/// </summary>
/// <typeparam name="TContract">The type of the contract.</typeparam>
/// <param name="implementation">The implementation.</param>
public static bool IsTypeAssignableFrom<TContract>(Type implementation)
{
return IsTypeAssignableFrom(typeof(TContract), implementation);
}
/// <summary>
/// Determines whether the object instance <paramref name="implementation"/> is assignable from the specified implementation <typeparamref name="TContract"/>,
/// and caches the result across the application using a <see cref="ConcurrentDictionary{TKey,TValue}"/>.
/// </summary>
/// <typeparam name="TContract">The type of the contract.</typeparam>
/// <param name="implementation">The implementation.</param>
public static bool IsTypeAssignableFrom<TContract>(object implementation)
{
if (implementation == null) throw new ArgumentNullException(nameof(implementation));
return IsTypeAssignableFrom<TContract>(implementation.GetType());
}
/// <summary>
/// A method to determine whether <paramref name="implementation"/> represents a value type.
/// </summary>
/// <param name="implementation">The implementation.</param>
public static bool IsValueType(Type implementation)
{
return implementation.IsValueType || implementation.IsPrimitive;
}
/// <summary>
/// A method to determine whether <paramref name="implementation"/> is an implied value type (<see cref="Type.IsValueType"/>, <see cref="Type.IsEnum"/> or a string).
/// </summary>
/// <param name="implementation">The implementation.</param>
public static bool IsImplicitValueType(Type implementation)
{
return IsValueType(implementation) || implementation.IsEnum || implementation == typeof (string);
}
/// <summary>
/// Returns (and caches) a PropertyInfo from a type
/// </summary>
/// <param name="type"></param>
/// <param name="name"></param>
/// <param name="mustRead"></param>
/// <param name="mustWrite"></param>
/// <param name="includeIndexed"></param>
/// <param name="caseSensitive"> </param>
/// <returns></returns>
public static PropertyInfo GetProperty(Type type, string name,
bool mustRead = true,
bool mustWrite = true,
bool includeIndexed = false,
bool caseSensitive = true)
{
return CachedDiscoverableProperties(type, mustRead, mustWrite, includeIndexed)
.FirstOrDefault(x => caseSensitive ? (x.Name == name) : x.Name.InvariantEquals(name));
}
/// <summary>
/// Gets (and caches) <see cref="FieldInfo"/> discoverable in the current <see cref="AppDomain"/> for a given <paramref name="type"/>.
/// </summary>
/// <param name="type">The source.</param>
/// <returns></returns>
public static FieldInfo[] CachedDiscoverableFields(Type type)
{
return GetFieldsCache.GetOrAdd(
type,
x => type
.GetFields(BindingFlags.Public | BindingFlags.Instance)
.Where(y => y.IsInitOnly == false)
.ToArray());
}
/// <summary>
/// Gets (and caches) <see cref="PropertyInfo"/> discoverable in the current <see cref="AppDomain"/> for a given <paramref name="type"/>.
/// </summary>
/// <param name="type">The source.</param>
/// <param name="mustRead">true if the properties discovered are readable</param>
/// <param name="mustWrite">true if the properties discovered are writable</param>
/// <param name="includeIndexed">true if the properties discovered are indexable</param>
/// <returns></returns>
public static PropertyInfo[] CachedDiscoverableProperties(Type type, bool mustRead = true, bool mustWrite = true, bool includeIndexed = false)
{
return GetPropertiesCache.GetOrAdd(
new Tuple<Type, bool, bool, bool>(type, mustRead, mustWrite, includeIndexed),
x => type
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(y => (mustRead == false || y.CanRead)
&& (mustWrite == false || y.CanWrite)
&& (includeIndexed || y.GetIndexParameters().Any() == false))
.ToArray());
}
#region Match Type
// TODO: Need to determine if these methods should replace/combine/merge etc with IsTypeAssignableFrom, IsAssignableFromGeneric
// readings:
// http://stackoverflow.com/questions/2033912/c-sharp-variance-problem-assigning-listderived-as-listbase
// http://stackoverflow.com/questions/2208043/generic-variance-in-c-sharp-4-0
// http://stackoverflow.com/questions/8401738/c-sharp-casting-generics-covariance-and-contravariance
// http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class
// http://stackoverflow.com/questions/74616/how-to-detect-if-type-is-another-generic-type/1075059#1075059
private static bool MatchGeneric(Type implementation, Type contract, IDictionary<string, Type> bindings)
{
// trying to match eg List<int> with List<T>
// or List<List<List<int>>> with List<ListList<T>>>
// classes are NOT invariant so List<string> does not match List<object>
if (implementation.IsGenericType == false) return false;
// must have the same generic type definition
var implDef = implementation.GetGenericTypeDefinition();
var contDef = contract.GetGenericTypeDefinition();
if (implDef != contDef) return false;
// must have the same number of generic arguments
var implArgs = implementation.GetGenericArguments();
var contArgs = contract.GetGenericArguments();
if (implArgs.Length != contArgs.Length) return false;
// generic arguments must match
// in insta we should have actual types (eg int, string...)
// in typea we can have generic parameters (eg <T>)
for (var i = 0; i < implArgs.Length; i++)
{
const bool variance = false; // classes are NOT invariant
if (MatchType(implArgs[i], contArgs[i], bindings, variance) == false)
return false;
}
return true;
}
public static bool MatchType(Type implementation, Type contract)
{
return MatchType(implementation, contract, new Dictionary<string, Type>());
}
public static bool MatchType(Type implementation, Type contract, IDictionary<string, Type> bindings, bool variance = true)
{
if (contract.IsGenericType)
{
// eg type is List<int> or List<T>
// if we have variance then List<int> can match IList<T>
// if we don't have variance it can't - must have exact type
// try to match implementation against contract
if (MatchGeneric(implementation, contract, bindings)) return true;
// if no variance, fail
if (variance == false) return false;
// try to match an ancestor of implementation against contract
var t = implementation.BaseType;
while (t != null)
{
if (MatchGeneric(t, contract, bindings)) return true;
t = t.BaseType;
}
// try to match an interface of implementation against contract
return implementation.GetInterfaces().Any(i => MatchGeneric(i, contract, bindings));
}
if (contract.IsGenericParameter)
{
// eg <T>
if (bindings.ContainsKey(contract.Name))
{
// already bound: ensure it's compatible
return bindings[contract.Name] == implementation;
}
// not already bound: bind
bindings[contract.Name] = implementation;
return true;
}
// not a generic type, not a generic parameter
// so normal class or interface
// about primitive types, value types, etc:
// http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class
// if it's a primitive type... it needs to be ==
if (implementation == contract) return true;
if (contract.IsClass && implementation.IsClass && implementation.IsSubclassOf(contract)) return true;
if (contract.IsInterface && implementation.GetInterfaces().Contains(contract)) return true;
return false;
}
#endregion
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a consent.
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class Consent : EntityBase, IConsent
{
private bool _current;
private string _source;
private string _context;
private string _action;
private ConsentState _state;
private string _comment;
/// <inheritdoc />
public bool Current
{
get => _current;
set => SetPropertyValueAndDetectChanges(value, ref _current, nameof(Current));
}
/// <inheritdoc />
public string Source
{
get => _source;
set
{
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value));
SetPropertyValueAndDetectChanges(value, ref _source, nameof(Source));
}
}
/// <inheritdoc />
public string Context
{
get => _context;
set
{
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value));
SetPropertyValueAndDetectChanges(value, ref _context, nameof(Context));
}
}
/// <inheritdoc />
public string Action
{
get => _action;
set
{
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value));
SetPropertyValueAndDetectChanges(value, ref _action, nameof(Action));
}
}
/// <inheritdoc />
public ConsentState State
{
get => _state;
// note: we probably should validate the state here, but since the
// enum is [Flags] with many combinations, this could be expensive
set => SetPropertyValueAndDetectChanges(value, ref _state, nameof(State));
}
/// <inheritdoc />
public string Comment
{
get => _comment;
set => SetPropertyValueAndDetectChanges(value, ref _comment, nameof(Comment));
}
/// <inheritdoc />
public IEnumerable<IConsent> History => HistoryInternal;
/// <summary>
/// Gets the previous states of this consent.
/// </summary>
public List<IConsent> HistoryInternal { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
namespace Umbraco.Core.Models
{
/// <summary>
/// Provides extension methods for the <see cref="IConsent"/> interface.
/// </summary>
public static class ConsentExtensions
{
/// <summary>
/// Determines whether the consent is granted.
/// </summary>
public static bool IsGranted(this IConsent consent) => (consent.State & ConsentState.Granted) > 0;
/// <summary>
/// Determines whether the consent is revoked.
/// </summary>
public static bool IsRevoked(this IConsent consent) => (consent.State & ConsentState.Revoked) > 0;
}
}

View File

@@ -0,0 +1,38 @@
using System;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents the state of a consent.
/// </summary>
[Flags]
public enum ConsentState // : int
{
// note - this is a [Flags] enumeration
// on can create detailed flags such as:
//GrantedOptIn = Granted | 0x0001
//GrandedByForce = Granted | 0x0002
//
// 16 situations for each Pending/Granted/Revoked should be ok
/// <summary>
/// There is no consent.
/// </summary>
None = 0,
/// <summary>
/// Consent is pending and has not been granted yet.
/// </summary>
Pending = 0x10000,
/// <summary>
/// Consent has been granted.
/// </summary>
Granted = 0x20000,
/// <summary>
/// Consent has been revoked.
/// </summary>
Revoked = 0x40000
}
}

View File

@@ -0,0 +1,200 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Umbraco.Core.Composing;
namespace Umbraco.Core.Models
{
public static class DeepCloneHelper
{
/// <summary>
/// Stores the metadata for the properties for a given type so we know how to create them
/// </summary>
private struct ClonePropertyInfo
{
public ClonePropertyInfo(PropertyInfo propertyInfo) : this()
{
if (propertyInfo == null) throw new ArgumentNullException("propertyInfo");
PropertyInfo = propertyInfo;
}
public PropertyInfo PropertyInfo { get; private set; }
public bool IsDeepCloneable { get; set; }
public Type GenericListType { get; set; }
public bool IsList
{
get { return GenericListType != null; }
}
}
/// <summary>
/// Used to avoid constant reflection (perf)
/// </summary>
private static readonly ConcurrentDictionary<Type, ClonePropertyInfo[]> PropCache = new ConcurrentDictionary<Type, ClonePropertyInfo[]>();
/// <summary>
/// Used to deep clone any reference properties on the object (should be done after a MemberwiseClone for which the outcome is 'output')
/// </summary>
/// <param name="input"></param>
/// <param name="output"></param>
/// <returns></returns>
public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable output)
{
var inputType = input.GetType();
var outputType = output.GetType();
if (inputType != outputType)
{
throw new InvalidOperationException("Both the input and output types must be the same");
}
//get the property metadata from cache so we only have to figure this out once per type
var refProperties = PropCache.GetOrAdd(inputType, type =>
inputType.GetProperties()
.Select<PropertyInfo, ClonePropertyInfo?>(propertyInfo =>
{
if (
//is not attributed with the ignore clone attribute
propertyInfo.GetCustomAttribute<DoNotCloneAttribute>() != null
//reference type but not string
|| propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof (string)
//settable
|| propertyInfo.CanWrite == false
//non-indexed
|| propertyInfo.GetIndexParameters().Any())
{
return null;
}
if (TypeHelper.IsTypeAssignableFrom<IDeepCloneable>(propertyInfo.PropertyType))
{
return new ClonePropertyInfo(propertyInfo) { IsDeepCloneable = true };
}
if (TypeHelper.IsTypeAssignableFrom<IEnumerable>(propertyInfo.PropertyType)
&& TypeHelper.IsTypeAssignableFrom<string>(propertyInfo.PropertyType) == false)
{
if (propertyInfo.PropertyType.IsGenericType
&& (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
|| propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>)
|| propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>)))
{
//if it is a IEnumerable<>, IList<T> or ICollection<> we'll use a List<>
var genericType = typeof(List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments());
return new ClonePropertyInfo(propertyInfo) { GenericListType = genericType };
}
if (propertyInfo.PropertyType.IsArray
|| (propertyInfo.PropertyType.IsInterface && propertyInfo.PropertyType.IsGenericType == false))
{
//if its an array, we'll create a list to work with first and then convert to array later
//otherwise if its just a regular derivative of IEnumerable, we can use a list too
return new ClonePropertyInfo(propertyInfo) { GenericListType = typeof(List<object>) };
}
//skip instead of trying to create instance of abstract or interface
if (propertyInfo.PropertyType.IsAbstract || propertyInfo.PropertyType.IsInterface)
{
return null;
}
//its a custom IEnumerable, we'll try to create it
try
{
var custom = Activator.CreateInstance(propertyInfo.PropertyType);
//if it's an IList we can work with it, otherwise we cannot
var newList = custom as IList;
if (newList == null)
{
return null;
}
return new ClonePropertyInfo(propertyInfo) {GenericListType = propertyInfo.PropertyType};
}
catch (Exception)
{
//could not create this type so we'll skip it
return null;
}
}
return new ClonePropertyInfo(propertyInfo);
})
.Where(x => x.HasValue)
.Select(x => x.Value)
.ToArray());
foreach (var clonePropertyInfo in refProperties)
{
if (clonePropertyInfo.IsDeepCloneable)
{
//this ref property is also deep cloneable so clone it
var result = (IDeepCloneable)clonePropertyInfo.PropertyInfo.GetValue(input, null);
if (result != null)
{
//set the cloned value to the property
clonePropertyInfo.PropertyInfo.SetValue(output, result.DeepClone(), null);
}
}
else if (clonePropertyInfo.IsList)
{
var enumerable = (IEnumerable)clonePropertyInfo.PropertyInfo.GetValue(input, null);
if (enumerable == null) continue;
var newList = (IList)Activator.CreateInstance(clonePropertyInfo.GenericListType);
var isUsableType = true;
//now clone each item
foreach (var o in enumerable)
{
//first check if the item is deep cloneable and copy that way
var dc = o as IDeepCloneable;
if (dc != null)
{
newList.Add(dc.DeepClone());
}
else if (o is string || o.GetType().IsValueType)
{
//check if the item is a value type or a string, then we can just use it
newList.Add(o);
}
else
{
//this will occur if the item is not a string or value type or IDeepCloneable, in this case we cannot
// clone each element, we'll need to skip this property, people will have to manually clone this list
isUsableType = false;
break;
}
}
//if this was not usable, skip this property
if (isUsableType == false)
{
continue;
}
if (clonePropertyInfo.PropertyInfo.PropertyType.IsArray)
{
//need to convert to array
var arr = (object[])Activator.CreateInstance(clonePropertyInfo.PropertyInfo.PropertyType, newList.Count);
for (int i = 0; i < newList.Count; i++)
{
arr[i] = newList[i];
}
//set the cloned collection
clonePropertyInfo.PropertyInfo.SetValue(output, arr, null);
}
else
{
//set the cloned collection
clonePropertyInfo.PropertyInfo.SetValue(output, newList, null);
}
}
}
}
}
}

View File

@@ -0,0 +1,185 @@
using System;
using System.Diagnostics;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Provides a base class for entities.
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
[DebuggerDisplay("Id: {" + nameof(Id) + "}")]
public abstract class EntityBase : BeingDirtyBase, IEntity
{
#if DEBUG_MODEL
public Guid InstanceId = Guid.NewGuid();
#endif
private bool _hasIdentity;
private int _id;
private Guid _key;
private DateTime _createDate;
private DateTime _updateDate;
/// <inheritdoc />
[DataMember]
public int Id
{
get => _id;
set
{
SetPropertyValueAndDetectChanges(value, ref _id, nameof(Id));
_hasIdentity = value != 0;
}
}
/// <inheritdoc />
[DataMember]
public Guid Key
{
get
{
// if an entity does NOT have a key yet, assign one now
if (_key == Guid.Empty)
_key = Guid.NewGuid();
return _key;
}
set => SetPropertyValueAndDetectChanges(value, ref _key, nameof(Key));
}
/// <inheritdoc />
[DataMember]
public DateTime CreateDate
{
get => _createDate;
set => SetPropertyValueAndDetectChanges(value, ref _createDate, nameof(CreateDate));
}
/// <inheritdoc />
[DataMember]
public DateTime UpdateDate
{
get => _updateDate;
set => SetPropertyValueAndDetectChanges(value, ref _updateDate, nameof(UpdateDate));
}
/// <inheritdoc />
[DataMember]
public DateTime? DeleteDate { get; set; } // no change tracking - not persisted
/// <inheritdoc />
[DataMember]
public virtual bool HasIdentity => _hasIdentity;
/// <summary>
/// Resets the entity identity.
/// </summary>
public virtual void ResetIdentity()
{
_id = default;
_key = Guid.Empty;
_hasIdentity = false;
}
/// <summary>
/// Updates the entity when it is being saved for the first time.
/// </summary>
public virtual void AddingEntity()
{
var now = DateTime.Now;
// set the create and update dates, if not already set
if (IsPropertyDirty("CreateDate") == false || _createDate == default)
CreateDate = now;
if (IsPropertyDirty("UpdateDate") == false || _updateDate == default)
UpdateDate = now;
}
/// <summary>
/// Updates the entity when it is being saved.
/// </summary>
public virtual void UpdatingEntity()
{
var now = DateTime.Now;
// just in case
if (_createDate == default)
CreateDate = now;
// set the update date if not already set
if (IsPropertyDirty("UpdateDate") == false || _updateDate == default)
UpdateDate = now;
}
public virtual bool Equals(EntityBase other)
{
return other != null && (ReferenceEquals(this, other) || SameIdentityAs(other));
}
public override bool Equals(object obj)
{
return obj != null && (ReferenceEquals(this, obj) || SameIdentityAs(obj as EntityBase));
}
private bool SameIdentityAs(EntityBase other)
{
if (other == null) return false;
// same identity if
// - same object (reference equals)
// - or same CLR type, both have identities, and they are identical
if (ReferenceEquals(this, other))
return true;
return GetType() == other.GetType() && HasIdentity && other.HasIdentity && Id == other.Id;
}
public override int GetHashCode()
{
unchecked
{
var hashCode = HasIdentity.GetHashCode();
hashCode = (hashCode * 397) ^ Id;
hashCode = (hashCode * 397) ^ GetType().GetHashCode();
return hashCode;
}
}
public object DeepClone()
{
// memberwise-clone (ie shallow clone) the entity
var unused = Key; // ensure that 'this' has a key, before cloning
var clone = (EntityBase) MemberwiseClone();
#if DEBUG_MODEL
clone.InstanceId = Guid.NewGuid();
#endif
//disable change tracking while we deep clone IDeepCloneable properties
clone.DisableChangeTracking();
// deep clone ref properties that are IDeepCloneable
DeepCloneHelper.DeepCloneRefProperties(this, clone);
PerformDeepClone(clone);
// clear changes (ensures the clone has its own dictionaries)
clone.ResetDirtyProperties(false);
//re-enable change tracking
clone.EnableChangeTracking();
return clone;
}
/// <summary>
/// Used by inheritors to modify the DeepCloning logic
/// </summary>
/// <param name="clone"></param>
protected virtual void PerformDeepClone(object clone)
{
}
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Provides a base class for tree entities.
/// </summary>
public abstract class TreeEntityBase : EntityBase, ITreeEntity
{
private string _name;
private int _creatorId;
private int _parentId;
private bool _hasParentId;
private ITreeEntity _parent;
private int _level;
private string _path;
private int _sortOrder;
private bool _trashed;
/// <inheritdoc />
[DataMember]
public string Name
{
get => _name;
set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
}
/// <inheritdoc />
[DataMember]
public int CreatorId
{
get => _creatorId;
set => SetPropertyValueAndDetectChanges(value, ref _creatorId, nameof(CreatorId));
}
/// <inheritdoc />
[DataMember]
public int ParentId
{
get
{
if (_hasParentId) return _parentId;
if (_parent == null) throw new InvalidOperationException("Content does not have a parent.");
if (!_parent.HasIdentity) throw new InvalidOperationException("Content's parent does not have an identity.");
_parentId = _parent.Id;
if (_parentId == 0)
throw new Exception("Panic: parent has an identity but id is zero.");
_hasParentId = true;
_parent = null;
return _parentId;
}
set
{
if (value == 0)
throw new ArgumentException("Value cannot be zero.", nameof(value));
SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId));
_hasParentId = true;
_parent = null;
}
}
/// <inheritdoc />
public void SetParent(ITreeEntity parent)
{
_hasParentId = false;
_parent = parent;
OnPropertyChanged(nameof(ParentId));
}
/// <inheritdoc />
[DataMember]
public int Level
{
get => _level;
set => SetPropertyValueAndDetectChanges(value, ref _level, nameof(Level));
}
/// <inheritdoc />
[DataMember]
public string Path
{
get => _path;
set => SetPropertyValueAndDetectChanges(value, ref _path, nameof(Path));
}
/// <inheritdoc />
[DataMember]
public int SortOrder
{
get => _sortOrder;
set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder));
}
/// <inheritdoc />
[DataMember]
public bool Trashed
{
get => _trashed;
set => SetPropertyValueAndDetectChanges(value, ref _trashed, nameof(Trashed));
}
}
}

View File

@@ -0,0 +1,55 @@
using System.Collections.Generic;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a consent state.
/// </summary>
/// <remarks>
/// <para>A consent is fully identified by a source (whoever is consenting), a context (for
/// example, an application), and an action (whatever is consented).</para>
/// <para>A consent state registers the state of the consent (granted, revoked...).</para>
/// </remarks>
public interface IConsent : IEntity, IRememberBeingDirty
{
/// <summary>
/// Determines whether the consent entity represents the current state.
/// </summary>
bool Current { get; }
/// <summary>
/// Gets the unique identifier of whoever is consenting.
/// </summary>
string Source { get; }
/// <summary>
/// Gets the unique identifier of the context of the consent.
/// </summary>
/// <remarks>
/// <para>Represents the domain, application, scope... of the action.</para>
/// <para>When the action is a Udi, this should be the Udi type.</para>
/// </remarks>
string Context { get; }
/// <summary>
/// Gets the unique identifier of the consented action.
/// </summary>
string Action { get; }
/// <summary>
/// Gets the state of the consent.
/// </summary>
ConsentState State { get; }
/// <summary>
/// Gets some additional free text.
/// </summary>
string Comment { get; }
/// <summary>
/// Gets the previous states of this consent.
/// </summary>
IEnumerable<IConsent> History { get; }
}
}

View File

@@ -0,0 +1,45 @@
using System.Collections.Generic;
using Umbraco.Core.Models;
namespace Umbraco.Core.Services
{
/// <summary>
/// A service for handling lawful data processing requirements
/// </summary>
/// <remarks>
/// <para>Consent can be given or revoked or changed via the <see cref="RegisterConsent"/> method, which
/// creates a new <see cref="IConsent"/> entity to track the consent. Revoking a consent is performed by
/// registering a revoked consent.</para>
/// <para>A consent can be revoked, by registering a revoked consent, but cannot be deleted.</para>
/// <para>Getter methods return the current state of a consent, i.e. the latest <see cref="IConsent"/>
/// entity that was created.</para>
/// </remarks>
public interface IConsentService : IService
{
/// <summary>
/// Registers consent.
/// </summary>
/// <param name="source">The source, i.e. whoever is consenting.</param>
/// <param name="context"></param>
/// <param name="action"></param>
/// <param name="state">The state of the consent.</param>
/// <param name="comment">Additional free text.</param>
/// <returns>The corresponding consent entity.</returns>
IConsent RegisterConsent(string source, string context, string action, ConsentState state, string comment = null);
/// <summary>
/// Retrieves consents.
/// </summary>
/// <param name="source">The optional source.</param>
/// <param name="context">The optional context.</param>
/// <param name="action">The optional action.</param>
/// <param name="sourceStartsWith">Determines whether <paramref name="source"/> is a start pattern.</param>
/// <param name="contextStartsWith">Determines whether <paramref name="context"/> is a start pattern.</param>
/// <param name="actionStartsWith">Determines whether <paramref name="action"/> is a start pattern.</param>
/// <param name="includeHistory">Determines whether to include the history of consents.</param>
/// <returns>Consents matching the parameters.</returns>
IEnumerable<IConsent> LookupConsent(string source = null, string context = null, string action = null,
bool sourceStartsWith = false, bool contextStartsWith = false, bool actionStartsWith = false,
bool includeHistory = false);
}
}

View File

@@ -0,0 +1,12 @@
using Umbraco.Core.Logging;
namespace Umbraco.Core.Services
{
/// <summary>
/// Marker interface for services, which is used to store difference services in a list or dictionary
/// </summary>
public interface IService
{
}
}