Moved more files and removed modelsbuilder from UI project
This commit is contained in:
382
src/Umbraco.Abstractions/Composing/TypeHelper.cs
Normal file
382
src/Umbraco.Abstractions/Composing/TypeHelper.cs
Normal 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
|
||||
}
|
||||
}
|
||||
86
src/Umbraco.Abstractions/Models/Consent.cs
Normal file
86
src/Umbraco.Abstractions/Models/Consent.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
18
src/Umbraco.Abstractions/Models/ConsentExtensions.cs
Normal file
18
src/Umbraco.Abstractions/Models/ConsentExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
38
src/Umbraco.Abstractions/Models/ConsentState.cs
Normal file
38
src/Umbraco.Abstractions/Models/ConsentState.cs
Normal 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
|
||||
}
|
||||
}
|
||||
200
src/Umbraco.Abstractions/Models/DeepCloneHelper.cs
Normal file
200
src/Umbraco.Abstractions/Models/DeepCloneHelper.cs
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
185
src/Umbraco.Abstractions/Models/Entities/EntityBase.cs
Normal file
185
src/Umbraco.Abstractions/Models/Entities/EntityBase.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
106
src/Umbraco.Abstractions/Models/Entities/TreeEntityBase.cs
Normal file
106
src/Umbraco.Abstractions/Models/Entities/TreeEntityBase.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/Umbraco.Abstractions/Models/IConsent.cs
Normal file
55
src/Umbraco.Abstractions/Models/IConsent.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
45
src/Umbraco.Abstractions/Services/IConsentService.cs
Normal file
45
src/Umbraco.Abstractions/Services/IConsentService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
12
src/Umbraco.Abstractions/Services/IService.cs
Normal file
12
src/Umbraco.Abstractions/Services/IService.cs
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user