DataType refactoring - troubleshooting
This commit is contained in:
@@ -18,6 +18,9 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public static class ObjectExtensions
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Type, Dictionary<string, object>> ToObjectTypes
|
||||
= new ConcurrentDictionary<Type, Dictionary<string, object>>();
|
||||
|
||||
//private static readonly ConcurrentDictionary<Type, Func<object>> ObjectFactoryCache = new ConcurrentDictionary<Type, Func<object>>();
|
||||
|
||||
/// <summary>
|
||||
@@ -503,34 +506,34 @@ namespace Umbraco.Core
|
||||
return new Dictionary<string, TVal>();
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, Dictionary<string, object>> ToObjectTypes
|
||||
= new ConcurrentDictionary<Type, Dictionary<string, object>>();
|
||||
|
||||
/// <summary>
|
||||
/// Converts an object's properties into a dictionary.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to convert.</param>
|
||||
/// <param name="namer">A property namer function.</param>
|
||||
/// <returns>A dictionary containing each properties.</returns>
|
||||
public static Dictionary<string, object> ToObjectDictionary<T>(T obj)
|
||||
public static Dictionary<string, object> ToObjectDictionary<T>(T obj, Func<PropertyInfo, string> namer = null)
|
||||
{
|
||||
// fixme refactor this!
|
||||
var d = new Dictionary<string, object>();
|
||||
if (obj == null) return d;
|
||||
foreach (var p in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
|
||||
if (obj == null) return new Dictionary<string, object>();
|
||||
|
||||
string DefaultNamer(PropertyInfo property)
|
||||
{
|
||||
var jsonProperty = p.GetCustomAttribute<JsonPropertyAttribute>();
|
||||
var name = jsonProperty != null ? jsonProperty.PropertyName : p.Name;
|
||||
d[name] = p.GetValue(obj);
|
||||
var jsonProperty = property.GetCustomAttribute<JsonPropertyAttribute>();
|
||||
return jsonProperty?.PropertyName ?? property.Name;
|
||||
}
|
||||
return d;
|
||||
|
||||
var t = obj.GetType();
|
||||
|
||||
if (namer == null) namer = DefaultNamer;
|
||||
|
||||
if (!ToObjectTypes.TryGetValue(t, out var properties))
|
||||
{
|
||||
ToObjectTypes[t] = properties = t
|
||||
.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
|
||||
.ToDictionary(x => x.Name, x => (object) ReflectionUtilities.EmitPropertyGetter<T, object>(x));
|
||||
properties = new Dictionary<string, object>();
|
||||
|
||||
foreach (var p in t.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
|
||||
properties[namer(p)] = ReflectionUtilities.EmitPropertyGetter<T, object>(p);
|
||||
|
||||
ToObjectTypes[t] = properties;
|
||||
}
|
||||
|
||||
return properties.ToDictionary(x => x.Key, x => ((Func<T, object>) x.Value)(obj));
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// <remarks>Used to create the actual configuration dictionary from the database value.</remarks>
|
||||
public virtual object ParseConfiguration(string configurationJson)
|
||||
=> string.IsNullOrWhiteSpace(configurationJson)
|
||||
? null
|
||||
? new Dictionary<string, object>()
|
||||
: JsonConvert.DeserializeObject<Dictionary<string, object>>(configurationJson);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
@@ -13,6 +15,10 @@ namespace Umbraco.Core.PropertyEditors
|
||||
public abstract class ConfigurationEditor<TConfiguration> : ConfigurationEditor
|
||||
where TConfiguration : new()
|
||||
{
|
||||
// ReSharper disable once StaticMemberInGenericType
|
||||
private static Dictionary<string, (Type PropertyType, object Setter)> _fromObjectTypes
|
||||
= new Dictionary<string, (Type, object)>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigurationEditor{TConfiguration}"/> class.
|
||||
/// </summary>
|
||||
@@ -96,7 +102,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(configuration)) return default;
|
||||
if (string.IsNullOrWhiteSpace(configuration)) return new TConfiguration();
|
||||
return JsonConvert.DeserializeObject<TConfiguration>(configuration);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -118,7 +124,21 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// <param name="configuration">The current configuration object.</param>
|
||||
public virtual TConfiguration FromEditor(Dictionary<string, object> editorValue, TConfiguration configuration)
|
||||
{
|
||||
return ToObject<TConfiguration>(editorValue);
|
||||
// note - editorValue contains a mix of Clr types (string, int...) and JToken
|
||||
// turning everything back into a JToken... might not be fastest but is simplest
|
||||
// for now
|
||||
|
||||
var o = new JObject();
|
||||
|
||||
foreach (var field in Fields)
|
||||
{
|
||||
// only fields - ignore json property
|
||||
// fixme should we deal with jsonProperty anyways?
|
||||
if (editorValue.TryGetValue(field.Key, out var value) && value != null)
|
||||
o[field.PropertyName] = value is JToken jtoken ? jtoken : JToken.FromObject(value);
|
||||
}
|
||||
|
||||
return o.ToObject<TConfiguration>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -134,7 +154,18 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// <param name="configuration">The configuration.</param>
|
||||
public virtual Dictionary<string, object> ToEditor(TConfiguration configuration)
|
||||
{
|
||||
var dictionary = ObjectExtensions.ToObjectDictionary(configuration);
|
||||
string FieldNamer(PropertyInfo property)
|
||||
{
|
||||
// field first
|
||||
var field = property.GetCustomAttribute<ConfigurationFieldAttribute>();
|
||||
if (field != null) return field.Key;
|
||||
|
||||
// then, json property
|
||||
var jsonProperty = property.GetCustomAttribute<JsonPropertyAttribute>();
|
||||
return jsonProperty?.PropertyName ?? property.Name;
|
||||
}
|
||||
|
||||
var dictionary = ObjectExtensions.ToObjectDictionary(configuration, FieldNamer);
|
||||
|
||||
if (configuration is ConfigurationWithAdditionalData withAdditionalData)
|
||||
foreach (var kv in withAdditionalData.GetAdditionalValues())
|
||||
@@ -149,27 +180,49 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// <typeparam name="T">The type of the object.</typeparam>
|
||||
/// <param name="source">The source dictionary.</param>
|
||||
/// <returns>The object corresponding to the dictionary.</returns>
|
||||
protected T ToObject<T>(Dictionary<string, object> source)
|
||||
protected T FromObjectDictionary<T>(Dictionary<string, object> source) // fixme KILL - NOT USED ANYMORE
|
||||
where T : new()
|
||||
{
|
||||
// fixme cache! see also ToObject in ObjectExtensions
|
||||
// this is probably very bad, must REAFACTOR! the property setter of course cannot work like this!
|
||||
//var properties = TypeHelper.CachedDiscoverableProperties(typeof(T))
|
||||
// .ToDictionary(x => x.Name, x => (Type: x.PropertyType, Set: ReflectionUtilities.EmitPropertySetter<object, object>(x)));
|
||||
var properties = TypeHelper.CachedDiscoverableProperties(typeof(T))
|
||||
.ToDictionary(x => x.Name, x => (Type: x.PropertyType, Infos: x));
|
||||
// this needs to be here (and not in ObjectExtensions) because it is based on fields
|
||||
|
||||
var t = typeof(T);
|
||||
|
||||
if (_fromObjectTypes == null)
|
||||
{
|
||||
var p = t.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
|
||||
var fromObjectTypes = new Dictionary<string, (Type, object)>();
|
||||
|
||||
foreach (var field in Fields)
|
||||
{
|
||||
var fp = p.FirstOrDefault(x => x.Name == field.PropertyName);
|
||||
if (fp == null) continue;
|
||||
|
||||
fromObjectTypes[field.Key] = (fp.PropertyType, ReflectionUtilities.EmitPropertySetter<T, object>(fp));
|
||||
}
|
||||
|
||||
_fromObjectTypes = fromObjectTypes;
|
||||
}
|
||||
|
||||
var obj = new T();
|
||||
|
||||
foreach (var field in Fields)
|
||||
{
|
||||
if (!properties.TryGetValue(field.PropertyName, out var property)) continue;
|
||||
if (!_fromObjectTypes.TryGetValue(field.Key, out var ps)) continue;
|
||||
if (!source.TryGetValue(field.Key, out var value)) continue;
|
||||
// fixme if value is null? is this what we want?
|
||||
if (!value.GetType().IsInstanceOfType(property.Type))
|
||||
throw new Exception();
|
||||
//property.Set(obj, value);
|
||||
property.Infos.SetValue(obj, value);
|
||||
|
||||
if (ps.PropertyType.IsValueType)
|
||||
{
|
||||
if (value == null)
|
||||
throw new InvalidCastException($"Cannot cast null value to {ps.PropertyType.Name}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// ReSharper disable once UseMethodIsInstanceOfType
|
||||
if (!ps.PropertyType.IsAssignableFrom(value.GetType()))
|
||||
throw new InvalidCastException($"Cannot cast value of type {value.GetType()} to {ps.PropertyType.Name}.");
|
||||
}
|
||||
|
||||
((Action<T, object>) ps.Setter)(obj, value);
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
||||
366
src/Umbraco.Core/ReflectionUtilities-Unused.cs
Normal file
366
src/Umbraco.Core/ReflectionUtilities-Unused.cs
Normal file
@@ -0,0 +1,366 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides utilities to simplify reflection.
|
||||
/// </summary>
|
||||
public static partial class ReflectionUtilities
|
||||
{
|
||||
// the code below should NOT be used
|
||||
//
|
||||
// keeping all this around as a reference for now - building expressions into dynamic assemblies,
|
||||
// the resulting methods are fast (as fast as IL-generated methods) whereas the standard compiled
|
||||
// expressions are slowish - alas, the compiled methods do not have access to eg private members
|
||||
// and anything that would violate access control - we're not using it anymore - still used in a
|
||||
// benchmark
|
||||
|
||||
internal static Func<TArg0, TInstance> GetCtor<TInstance, TArg0>()
|
||||
{
|
||||
var type = typeof (TInstance);
|
||||
var type0 = typeof (TArg0);
|
||||
|
||||
// get the constructor infos
|
||||
var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
|
||||
null, new[] { type0 }, null);
|
||||
|
||||
if (ctor == null)
|
||||
throw new InvalidOperationException($"Could not find constructor {type}.ctor({type0}).");
|
||||
|
||||
var exprArgs = ctor.GetParameters().Select(x => Expression.Parameter(x.ParameterType, x.Name)).ToArray();
|
||||
// ReSharper disable once CoVariantArrayConversion
|
||||
var exprNew = Expression.New(ctor, exprArgs);
|
||||
var expr = Expression.Lambda<Func<TArg0, TInstance>>(exprNew, exprArgs);
|
||||
return CompileToDelegate(expr);
|
||||
}
|
||||
|
||||
internal static Func<TArg0, TArg1, TInstance> GetCtor<TInstance, TArg0, TArg1>()
|
||||
{
|
||||
var type = typeof (TInstance);
|
||||
var type0 = typeof (TArg0);
|
||||
var type1 = typeof (TArg1);
|
||||
|
||||
// get the constructor infos
|
||||
var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
|
||||
null, new[] { type0, type1 }, null);
|
||||
|
||||
if (ctor == null)
|
||||
throw new InvalidOperationException($"Could not find constructor {type}.ctor({type0}, {type1}).");
|
||||
|
||||
var exprArgs = ctor.GetParameters().Select(x => Expression.Parameter(x.ParameterType, x.Name)).ToArray();
|
||||
// ReSharper disable once CoVariantArrayConversion
|
||||
var exprNew = Expression.New(ctor, exprArgs);
|
||||
var expr = Expression.Lambda<Func<TArg0, TArg1, TInstance>>(exprNew, exprArgs);
|
||||
return CompileToDelegate(expr);
|
||||
}
|
||||
|
||||
internal static TMethod GetMethod<TMethod>(MethodInfo method)
|
||||
{
|
||||
var type = method.DeclaringType;
|
||||
|
||||
GetMethodParms<TMethod>(out var parameterTypes, out var returnType);
|
||||
return GetStaticMethod<TMethod>(method, method.Name, type, parameterTypes, returnType);
|
||||
}
|
||||
|
||||
internal static TMethod GetMethod<TInstance, TMethod>(MethodInfo method)
|
||||
{
|
||||
var type = method.DeclaringType;
|
||||
|
||||
GetMethodParms<TInstance, TMethod>(out var parameterTypes, out var returnType);
|
||||
return GetMethod<TMethod>(method, method.Name, type, parameterTypes, returnType);
|
||||
}
|
||||
|
||||
internal static TMethod GetMethod<TInstance, TMethod>(string methodName)
|
||||
{
|
||||
var type = typeof (TInstance);
|
||||
|
||||
GetMethodParms<TInstance, TMethod>(out var parameterTypes, out var returnType);
|
||||
|
||||
var method = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
|
||||
null, parameterTypes, null);
|
||||
|
||||
return GetMethod<TMethod>(method, methodName, type, parameterTypes, returnType);
|
||||
}
|
||||
|
||||
private static void GetMethodParms<TMethod>(out Type[] parameterTypes, out Type returnType)
|
||||
{
|
||||
var typeM = typeof (TMethod);
|
||||
var typeList = new List<Type>();
|
||||
returnType = typeof (void);
|
||||
|
||||
if (!typeof (MulticastDelegate).IsAssignableFrom(typeM) || typeM == typeof (MulticastDelegate))
|
||||
throw new InvalidOperationException("Invalid TMethod, must be a Func or Action.");
|
||||
|
||||
var typeName = typeM.FullName;
|
||||
if (typeName == null)
|
||||
throw new InvalidOperationException($"Could not get {typeM} type name.");
|
||||
if (typeName.StartsWith("System.Func`"))
|
||||
{
|
||||
var i = 0;
|
||||
while (i < typeM.GenericTypeArguments.Length - 1)
|
||||
typeList.Add(typeM.GenericTypeArguments[i++]);
|
||||
returnType = typeM.GenericTypeArguments[i];
|
||||
}
|
||||
else if (typeName.StartsWith("System.Action`"))
|
||||
{
|
||||
var i = 0;
|
||||
while (i < typeM.GenericTypeArguments.Length)
|
||||
typeList.Add(typeM.GenericTypeArguments[i++]);
|
||||
}
|
||||
else if (typeName == "System.Action")
|
||||
{
|
||||
// no args
|
||||
}
|
||||
else
|
||||
throw new InvalidOperationException(typeName);
|
||||
|
||||
parameterTypes = typeList.ToArray();
|
||||
}
|
||||
|
||||
private static void GetMethodParms<TInstance, TMethod>(out Type[] parameterTypes, out Type returnType)
|
||||
{
|
||||
var type = typeof (TInstance);
|
||||
|
||||
var typeM = typeof (TMethod);
|
||||
var typeList = new List<Type>();
|
||||
returnType = typeof (void);
|
||||
|
||||
if (!typeof (MulticastDelegate).IsAssignableFrom(typeM) || typeM == typeof (MulticastDelegate))
|
||||
throw new InvalidOperationException("Invalid TMethod, must be a Func or Action.");
|
||||
|
||||
var typeName = typeM.FullName;
|
||||
if (typeName == null)
|
||||
throw new InvalidOperationException($"Could not get {typeM} type name.");
|
||||
if (!typeM.IsGenericType)
|
||||
throw new InvalidOperationException($"Type {typeName} is not generic.");
|
||||
if (typeM.GenericTypeArguments[0] != type)
|
||||
throw new InvalidOperationException($"Invalid type {typeName}, the first generic argument must be {type.FullName}.");
|
||||
if (typeName.StartsWith("System.Func`"))
|
||||
{
|
||||
var i = 1;
|
||||
while (i < typeM.GenericTypeArguments.Length - 1)
|
||||
typeList.Add(typeM.GenericTypeArguments[i++]);
|
||||
returnType = typeM.GenericTypeArguments[i];
|
||||
}
|
||||
else if (typeName.StartsWith("System.Action`"))
|
||||
{
|
||||
var i = 1;
|
||||
while (i < typeM.GenericTypeArguments.Length)
|
||||
typeList.Add(typeM.GenericTypeArguments[i++]);
|
||||
}
|
||||
else
|
||||
throw new InvalidOperationException(typeName);
|
||||
|
||||
parameterTypes = typeList.ToArray();
|
||||
}
|
||||
|
||||
private static TMethod GetStaticMethod<TMethod>(MethodInfo method, string methodName, Type type, Type[] parameterTypes, Type returnType)
|
||||
{
|
||||
if (method == null || method.ReturnType != returnType)
|
||||
throw new InvalidOperationException($"Could not find static method {type}.{methodName}({string.Join(",", parameterTypes.Select(x => x.ToString()))}) : {returnType}");
|
||||
|
||||
var e = new List<ParameterExpression>();
|
||||
foreach (var p in method.GetParameters())
|
||||
e.Add(Expression.Parameter(p.ParameterType, p.Name));
|
||||
var exprCallArgs = e.ToArray();
|
||||
var exprLambdaArgs = exprCallArgs;
|
||||
|
||||
// ReSharper disable once CoVariantArrayConversion
|
||||
var exprCall = Expression.Call(method, exprCallArgs);
|
||||
var expr = Expression.Lambda<TMethod>(exprCall, exprLambdaArgs);
|
||||
return CompileToDelegate(expr);
|
||||
}
|
||||
|
||||
private static TMethod GetMethod<TMethod>(MethodInfo method, string methodName, Type type, Type[] parameterTypes, Type returnType)
|
||||
{
|
||||
if (method == null || method.ReturnType != returnType)
|
||||
throw new InvalidOperationException($"Could not find method {type}.{methodName}({string.Join(",", parameterTypes.Select(x => x.ToString()))}) : {returnType}");
|
||||
|
||||
var e = new List<ParameterExpression>();
|
||||
foreach (var p in method.GetParameters())
|
||||
e.Add(Expression.Parameter(p.ParameterType, p.Name));
|
||||
var exprCallArgs = e.ToArray();
|
||||
|
||||
var exprThis = Expression.Parameter(type, "this");
|
||||
e.Insert(0, exprThis);
|
||||
var exprLambdaArgs = e.ToArray();
|
||||
|
||||
// ReSharper disable once CoVariantArrayConversion
|
||||
var exprCall = Expression.Call(exprThis, method, exprCallArgs);
|
||||
var expr = Expression.Lambda<TMethod>(exprCall, exprLambdaArgs);
|
||||
return expr.Compile();
|
||||
}
|
||||
|
||||
internal const AssemblyBuilderAccess DefaultAssemblyBuilderAccess = AssemblyBuilderAccess.Run;
|
||||
internal const AssemblyBuilderAccess NoAssembly = 0;
|
||||
|
||||
internal static TLambda Compile<TLambda>(Expression<TLambda> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
|
||||
{
|
||||
return access == NoAssembly
|
||||
? expr.Compile()
|
||||
: CompileToDelegate(expr, access);
|
||||
}
|
||||
|
||||
internal static Action CompileToDelegate(Expression<Action> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
|
||||
{
|
||||
var typeBuilder = CreateTypeBuilder(access);
|
||||
|
||||
var builder = typeBuilder.DefineMethod("Method",
|
||||
MethodAttributes.Public | MethodAttributes.Static); // CompileToMethod requires a static method
|
||||
|
||||
expr.CompileToMethod(builder);
|
||||
|
||||
return (Action) Delegate.CreateDelegate(typeof (Action), typeBuilder.CreateType().GetMethod("Method"));
|
||||
}
|
||||
|
||||
internal static Action<T1> CompileToDelegate<T1>(Expression<Action<T1>> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
|
||||
{
|
||||
var typeBuilder = CreateTypeBuilder(access);
|
||||
|
||||
var builder = typeBuilder.DefineMethod("Method",
|
||||
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
|
||||
typeof (void), new[] { typeof (T1) });
|
||||
|
||||
expr.CompileToMethod(builder);
|
||||
|
||||
return (Action<T1>) Delegate.CreateDelegate(typeof (Action<T1>), typeBuilder.CreateType().GetMethod("Method"));
|
||||
}
|
||||
|
||||
internal static Action<T1, T2> CompileToDelegate<T1, T2>(Expression<Action<T1, T2>> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
|
||||
{
|
||||
var typeBuilder = CreateTypeBuilder(access);
|
||||
|
||||
var builder = typeBuilder.DefineMethod("Method",
|
||||
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
|
||||
typeof (void), new[] { typeof (T1), typeof (T2) });
|
||||
|
||||
expr.CompileToMethod(builder);
|
||||
|
||||
return (Action<T1, T2>) Delegate.CreateDelegate(typeof (Action<T1, T2>), typeBuilder.CreateType().GetMethod("Method"));
|
||||
}
|
||||
|
||||
internal static Action<T1, T2, T3> CompileToDelegate<T1, T2, T3>(Expression<Action<T1, T2, T3>> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
|
||||
{
|
||||
var typeBuilder = CreateTypeBuilder(access);
|
||||
|
||||
var builder = typeBuilder.DefineMethod("Method",
|
||||
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
|
||||
typeof (void), new[] { typeof (T1), typeof (T2), typeof (T3) });
|
||||
|
||||
expr.CompileToMethod(builder);
|
||||
|
||||
return (Action<T1, T2, T3>) Delegate.CreateDelegate(typeof (Action<T1, T2, T3>), typeBuilder.CreateType().GetMethod("Method"));
|
||||
}
|
||||
|
||||
internal static Func<TResult> CompileToDelegate<TResult>(Expression<Func<TResult>> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
|
||||
{
|
||||
var typeBuilder = CreateTypeBuilder(access);
|
||||
|
||||
var builder = typeBuilder.DefineMethod("Method",
|
||||
MethodAttributes.Public | MethodAttributes.Static,
|
||||
typeof (TResult), Array.Empty<Type>()); // CompileToMethod requires a static method
|
||||
|
||||
expr.CompileToMethod(builder);
|
||||
|
||||
return (Func<TResult>) Delegate.CreateDelegate(typeof (Func<TResult>), typeBuilder.CreateType().GetMethod("Method"));
|
||||
}
|
||||
|
||||
internal static Func<T1, TResult> CompileToDelegate<T1, TResult>(Expression<Func<T1, TResult>> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
|
||||
{
|
||||
var typeBuilder = CreateTypeBuilder(access);
|
||||
|
||||
var builder = typeBuilder.DefineMethod("Method",
|
||||
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
|
||||
typeof (TResult), new[] { typeof (T1) });
|
||||
|
||||
expr.CompileToMethod(builder);
|
||||
|
||||
return (Func<T1, TResult>) Delegate.CreateDelegate(typeof (Func<T1, TResult>), typeBuilder.CreateType().GetMethod("Method"));
|
||||
}
|
||||
|
||||
internal static Func<T1, T2, TResult> CompileToDelegate<T1, T2, TResult>(Expression<Func<T1, T2, TResult>> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
|
||||
{
|
||||
var typeBuilder = CreateTypeBuilder(access);
|
||||
|
||||
var builder = typeBuilder.DefineMethod("Method",
|
||||
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
|
||||
typeof (TResult), new[] { typeof (T1), typeof (T2) });
|
||||
|
||||
expr.CompileToMethod(builder);
|
||||
|
||||
return (Func<T1, T2, TResult>) Delegate.CreateDelegate(typeof (Func<T1, T2, TResult>), typeBuilder.CreateType().GetMethod("Method"));
|
||||
}
|
||||
|
||||
internal static Func<T1, T2, T3, TResult> CompileToDelegate<T1, T2, T3, TResult>(Expression<Func<T1, T2, T3, TResult>> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
|
||||
{
|
||||
var typeBuilder = CreateTypeBuilder(access);
|
||||
|
||||
var builder = typeBuilder.DefineMethod("Method",
|
||||
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
|
||||
typeof (TResult), new[] { typeof (T1), typeof (T2), typeof (T3) });
|
||||
|
||||
expr.CompileToMethod(builder);
|
||||
|
||||
return (Func<T1, T2, T3, TResult>) Delegate.CreateDelegate(typeof (Func<T1, T2, T3, TResult>), typeBuilder.CreateType().GetMethod("Method"));
|
||||
}
|
||||
|
||||
internal static TMethod CompileToDelegate<TMethod>(Expression<TMethod> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
|
||||
{
|
||||
var typeBuilder = CreateTypeBuilder(access);
|
||||
|
||||
GetMethodParms<TMethod>(out var parameterTypes, out var returnType);
|
||||
|
||||
var builder = typeBuilder.DefineMethod("Method",
|
||||
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
|
||||
returnType, parameterTypes);
|
||||
|
||||
expr.CompileToMethod(builder);
|
||||
|
||||
return (TMethod) (object) Delegate.CreateDelegate(typeof (TMethod), typeBuilder.CreateType().GetMethod("Method"));
|
||||
}
|
||||
|
||||
internal static TMethod[] CompileToDelegates<TMethod>(params Expression<TMethod>[] exprs)
|
||||
=> CompileToDelegates(AssemblyBuilderAccess.RunAndCollect, exprs);
|
||||
|
||||
internal static TMethod[] CompileToDelegates<TMethod>(AssemblyBuilderAccess access, params Expression<TMethod>[] exprs)
|
||||
{
|
||||
var typeBuilder = CreateTypeBuilder(access);
|
||||
|
||||
GetMethodParms<TMethod>(out var parameterTypes, out var returnType);
|
||||
|
||||
var i = 0;
|
||||
foreach (var expr in exprs)
|
||||
{
|
||||
var builder = typeBuilder.DefineMethod($"Method_{i++}",
|
||||
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
|
||||
returnType, parameterTypes);
|
||||
|
||||
expr.CompileToMethod(builder);
|
||||
}
|
||||
|
||||
var type = typeBuilder.CreateType();
|
||||
|
||||
var methods = new TMethod[exprs.Length];
|
||||
for (i = 0; i < exprs.Length; i++)
|
||||
methods[i] = (TMethod) (object) Delegate.CreateDelegate(typeof (TMethod), type.GetMethod($"Method_{i++}"));
|
||||
return methods;
|
||||
}
|
||||
|
||||
private static TypeBuilder CreateTypeBuilder(AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
|
||||
{
|
||||
var assemblyName = new AssemblyName("Umbraco.Core.DynamicAssemblies." + Guid.NewGuid().ToString("N"));
|
||||
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, access);
|
||||
|
||||
var module = (access & AssemblyBuilderAccess.Save) > 0
|
||||
? assembly.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll")
|
||||
: assembly.DefineDynamicModule(assemblyName.Name); // has to be transient
|
||||
|
||||
return module.DefineType("Class", TypeAttributes.Public | TypeAttributes.Abstract);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -343,6 +343,7 @@
|
||||
<Compile Include="PropertyEditors\TagConfiguration.cs" />
|
||||
<Compile Include="PropertyEditors\ValueConverters\ImageCropperValue.cs" />
|
||||
<Compile Include="PropertyEditors\ValueConverters\ImageCropperValueTypeConverter.cs" />
|
||||
<Compile Include="ReflectionUtilities-Unused.cs" />
|
||||
<Compile Include="Runtime\CoreRuntime.cs" />
|
||||
<Compile Include="Runtime\CoreRuntimeComponent.cs" />
|
||||
<Compile Include="CustomBooleanTypeConverter.cs" />
|
||||
|
||||
@@ -38,6 +38,8 @@ namespace Umbraco.Tests.Clr
|
||||
Assert.Contains(typeof(IInterface2), interfaces);
|
||||
}
|
||||
|
||||
#region Test Objects
|
||||
|
||||
interface IInterface1
|
||||
{ }
|
||||
|
||||
@@ -53,5 +55,7 @@ namespace Umbraco.Tests.Clr
|
||||
|
||||
class Class2 : Class1
|
||||
{ }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Lucene.Net.Index;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Umbraco.Tests.Clr
|
||||
{
|
||||
@@ -267,8 +268,261 @@ namespace Umbraco.Tests.Clr
|
||||
Assert.IsNull(ReflectionUtilities.EmitPropertyGetter<Class1, int>("Value2", false));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetterCanCastValue()
|
||||
{
|
||||
// test that we can emit property setters that cast from eg 'object'
|
||||
|
||||
// well - no - we don't do this
|
||||
|
||||
/*
|
||||
|
||||
var type4 = typeof(Class4);
|
||||
var propString4 = type4.GetProperty("StringValue");
|
||||
var propClassA4 = type4.GetProperty("ClassAValue");
|
||||
|
||||
var object4 = new Class4();
|
||||
var object2A = new Class2A();
|
||||
|
||||
// works with a string property
|
||||
Assert.IsNotNull(propString4);
|
||||
var setterString4 = ReflectionUtilities.EmitPropertySetter<Class4, object>(propString4);
|
||||
Assert.IsNotNull(setterString4);
|
||||
setterString4(object4, "foo");
|
||||
Assert.IsNotNull(object4.StringValue);
|
||||
Assert.AreEqual("foo", object4.StringValue);
|
||||
|
||||
setterString4(object4, new Class2());
|
||||
|
||||
// works with a reference property
|
||||
Assert.IsNotNull(propClassA4);
|
||||
var setterClassA4 = ReflectionUtilities.EmitPropertySetter<Class4, object>(propClassA4);
|
||||
Assert.IsNotNull(setterClassA4);
|
||||
setterClassA4(object4, object2A);
|
||||
Assert.IsNotNull(object4.ClassAValue);
|
||||
Assert.AreEqual(object2A, object4.ClassAValue);
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetterCanCastObject()
|
||||
{
|
||||
var type5 = typeof(Class5);
|
||||
var propClass4 = type5.GetProperty("ClassValue");
|
||||
|
||||
var object2 = new Class2();
|
||||
var object4 = new Class5();
|
||||
|
||||
// can cast the object type from Class5 to Class4
|
||||
var setterClass4 = ReflectionUtilities.EmitPropertySetter<Class5, Class2>(propClass4);
|
||||
|
||||
setterClass4(object4, object2);
|
||||
Assert.IsNotNull(object4.ClassValue);
|
||||
Assert.AreSame(object2, object4.ClassValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetterCanCastValue()
|
||||
{
|
||||
var type4 = typeof(Class4);
|
||||
var propClassA4 = type4.GetProperty("ClassAValue");
|
||||
var propInt4 = type4.GetProperty("IntValue");
|
||||
|
||||
var object2A = new Class2A();
|
||||
var object4 = new Class4 { ClassAValue = object2A, IntValue = 159 };
|
||||
|
||||
// can cast the return type from Class2A to Class2
|
||||
var getterClassA4 = ReflectionUtilities.EmitPropertyGetter<Class4, Class2>(propClassA4);
|
||||
|
||||
var valueClass4A = getterClassA4(object4);
|
||||
Assert.IsNotNull(valueClass4A);
|
||||
Assert.AreSame(object2A, valueClass4A);
|
||||
|
||||
// cannot cast the return type from Class2A to Class3!
|
||||
Assert.Throws<ArgumentException>(()
|
||||
=> ReflectionUtilities.EmitPropertyGetter<Class4, Class3>(propClassA4));
|
||||
|
||||
// can cast and box the return type from int to object
|
||||
var getterInt4 = ReflectionUtilities.EmitPropertyGetter<Class4, object>(propInt4);
|
||||
|
||||
var valueInt4 = getterInt4(object4);
|
||||
Assert.IsTrue(valueInt4 is int);
|
||||
Assert.AreEqual(159, valueInt4);
|
||||
|
||||
// cannot cast the return type from int to Class3!
|
||||
Assert.Throws<ArgumentException>(()
|
||||
=> ReflectionUtilities.EmitPropertyGetter<Class4, Class3>(propInt4));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetterCanCastObject()
|
||||
{
|
||||
var type5 = typeof(Class5);
|
||||
var propClass4 = type5.GetProperty("ClassValue");
|
||||
|
||||
var object2 = new Class2();
|
||||
var object4 = new Class5 { ClassValue = object2 };
|
||||
|
||||
// can cast the object type from Class5 to Class4
|
||||
var getterClass4 = ReflectionUtilities.EmitPropertyGetter<Class5, Class2>(propClass4);
|
||||
|
||||
var valueClass4 = getterClass4(object4);
|
||||
Assert.IsNotNull(valueClass4);
|
||||
Assert.AreSame(object2, valueClass4);
|
||||
|
||||
// cannot cast the object type from Class3 to Class4!
|
||||
Assert.Throws<ArgumentException>(()
|
||||
=> ReflectionUtilities.EmitPropertyGetter<Class3, Class2>(propClass4));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanEmitCastGetters()
|
||||
{
|
||||
// test that we can emit property getters that cast the returned value to 'object'
|
||||
|
||||
// test simple class
|
||||
|
||||
var type4 = typeof(Class4);
|
||||
|
||||
var object4 = new Class4
|
||||
{
|
||||
IntValue = 1,
|
||||
StringValue = "foo",
|
||||
ClassValue = new Class2(),
|
||||
};
|
||||
|
||||
// works with a string property
|
||||
var propString4 = type4.GetProperty("StringValue");
|
||||
Assert.IsNotNull(propString4);
|
||||
var getterString4 = ReflectionUtilities.EmitPropertyGetter<Class4, object>(propString4);
|
||||
Assert.IsNotNull(getterString4);
|
||||
var valueString4 = getterString4(object4);
|
||||
Assert.IsNotNull(valueString4);
|
||||
Assert.AreEqual("foo", valueString4);
|
||||
|
||||
// works with a reference property
|
||||
var propClass4 = type4.GetProperty("ClassValue");
|
||||
Assert.IsNotNull(propClass4);
|
||||
var getterClass4 = ReflectionUtilities.EmitPropertyGetter<Class4, object>(propClass4);
|
||||
Assert.IsNotNull(getterClass4);
|
||||
var valueClass4 = getterClass4(object4);
|
||||
Assert.IsNotNull(valueClass4);
|
||||
Assert.IsInstanceOf<Class2>(valueClass4);
|
||||
|
||||
// works with a value type property
|
||||
var propInt4 = type4.GetProperty("IntValue");
|
||||
Assert.IsNotNull(propInt4);
|
||||
|
||||
// ... if explicitely getting a value type
|
||||
var getterInt4T = ReflectionUtilities.EmitPropertyGetter<Class4, int>(propInt4);
|
||||
Assert.IsNotNull(getterInt4T);
|
||||
var valueInt4T = getterInt4T(object4);
|
||||
Assert.AreEqual(1, valueInt4T);
|
||||
|
||||
// ... if using a compiled getter
|
||||
var valueInt4D = GetIntValue(object4);
|
||||
Assert.IsNotNull(valueInt4D);
|
||||
Assert.IsTrue(valueInt4D is int);
|
||||
Assert.AreEqual(1, valueInt4D);
|
||||
|
||||
// ... if getting a non-value type (emit adds a box)
|
||||
var getterInt4 = ReflectionUtilities.EmitPropertyGetter<Class4, object>(propInt4);
|
||||
Assert.IsNotNull(getterInt4);
|
||||
var valueInt4 = getterInt4(object4);
|
||||
Assert.IsNotNull(valueInt4);
|
||||
Assert.IsTrue(valueInt4 is int);
|
||||
Assert.AreEqual(1, valueInt4);
|
||||
|
||||
var getters4 = type4
|
||||
.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
|
||||
.ToDictionary(x => x.Name, x => (object) ReflectionUtilities.EmitPropertyGetter<Class4, object>(x));
|
||||
|
||||
Console.WriteLine("Getting object4 values...");
|
||||
var values4 = getters4.ToDictionary(kvp => kvp.Key, kvp => ((Func<Class4, object>) kvp.Value)(object4));
|
||||
|
||||
Console.WriteLine("Writing object4 values...");
|
||||
foreach ((var name, var value) in values4)
|
||||
Console.WriteLine($"{name}: {value}");
|
||||
Assert.AreEqual(4, values4.Count);
|
||||
Assert.AreEqual("foo", values4["StringValue"]);
|
||||
Assert.IsInstanceOf<Class2>(values4["ClassValue"]);
|
||||
Assert.AreEqual(1, values4["IntValue"]);
|
||||
|
||||
// test hierarchy
|
||||
|
||||
var type5 = typeof(Class5);
|
||||
|
||||
var getters5 = type5
|
||||
.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
|
||||
.ToDictionary(x => x.Name, x => (object) ReflectionUtilities.EmitPropertyGetter<Class5, object>(x));
|
||||
|
||||
var object5 = new Class5
|
||||
{
|
||||
IntValue = 1,
|
||||
IntValue2 = 1,
|
||||
StringValue = "foo",
|
||||
StringValue2 = "foo",
|
||||
ClassValue = new Class2(),
|
||||
ClassValue2 = new Class2()
|
||||
};
|
||||
|
||||
Console.WriteLine("Getting object5 values...");
|
||||
var values5 = getters5.ToDictionary(kvp => kvp.Key, kvp => ((Func<Class5, object>) kvp.Value)(object5));
|
||||
|
||||
Console.WriteLine("Writing object5 values...");
|
||||
foreach ((var name, var value) in values5)
|
||||
Console.WriteLine($"{name}: {value}");
|
||||
Assert.AreEqual(7, values5.Count);
|
||||
Assert.AreEqual("foo", values5["StringValue"]);
|
||||
Assert.IsInstanceOf<Class2>(values5["ClassValue"]);
|
||||
Assert.AreEqual(1, values5["IntValue"]);
|
||||
Assert.AreEqual("foo", values5["StringValue2"]);
|
||||
Assert.IsInstanceOf<Class2>(values5["ClassValue2"]);
|
||||
Assert.AreEqual(1, values5["IntValue2"]);
|
||||
|
||||
// test object extensions
|
||||
|
||||
Console.WriteLine("Getting object5D values...");
|
||||
var values5D = ObjectExtensions.ToObjectDictionary(object5);
|
||||
|
||||
Console.WriteLine("Writing object5D values...");
|
||||
foreach ((var name, var value) in values5)
|
||||
Console.WriteLine($"{name}: {value}");
|
||||
Assert.AreEqual(7, values5.Count);
|
||||
Assert.AreEqual("foo", values5D["StringValue"]);
|
||||
Assert.IsInstanceOf<Class2>(values5D["ClassValue"]);
|
||||
Assert.AreEqual(1, values5D["IntValue"]);
|
||||
Assert.AreEqual("foo", values5D["StringValue2"]);
|
||||
Assert.IsInstanceOf<Class2>(values5D["ClassValue2"]);
|
||||
Assert.AreEqual(1, values5D["intValue2"]); // JsonProperty changes property name
|
||||
}
|
||||
|
||||
// fixme - missing tests specifying 'returned' on method, property
|
||||
|
||||
#region IL Code
|
||||
|
||||
// these functions can be examined in eg DotPeek to understand IL works
|
||||
|
||||
// box [mscorlib]System.Int32
|
||||
public object GetIntValue(Class4 object4) => object4.IntValue;
|
||||
|
||||
// unbox.any [mscorlib]System.Int32
|
||||
public void SetIntValue(Class4 object4, object i) => object4.IntValue = (int) i;
|
||||
|
||||
// castclass [mscorlib]System.String
|
||||
public void SetStringValue(Class4 object4, object s) => object4.StringValue = (string) s;
|
||||
|
||||
// conv.i4
|
||||
public void SetIntValue(Class4 object4, double d) => object4.IntValue = (int) d;
|
||||
|
||||
// conv.i4
|
||||
public void SetIntValue2(Class4 object4, object d) => object4.IntValue = (int) (double) d;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Objects
|
||||
|
||||
public static class StaticClass1
|
||||
{
|
||||
public static void Method() { }
|
||||
@@ -302,11 +556,31 @@ namespace Umbraco.Tests.Clr
|
||||
|
||||
public class Class2 { }
|
||||
|
||||
public class Class2A : Class2 { }
|
||||
|
||||
public class Class3
|
||||
{
|
||||
public Class3(int i) { }
|
||||
|
||||
private Class3(string s) { }
|
||||
}
|
||||
|
||||
public class Class4
|
||||
{
|
||||
public int IntValue { get; set; }
|
||||
public string StringValue { get; set; }
|
||||
public Class2 ClassValue { get;set; }
|
||||
public Class2A ClassAValue { get; set; }
|
||||
}
|
||||
|
||||
public class Class5 : Class4
|
||||
{
|
||||
[JsonProperty("intValue2")]
|
||||
public int IntValue2 { get; set; }
|
||||
public string StringValue2 { get; set; }
|
||||
public Class2 ClassValue2 { get;set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
73
src/Umbraco.Tests/Composing/ActionCollectionTests.cs
Normal file
73
src/Umbraco.Tests/Composing/ActionCollectionTests.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.UI.Pages;
|
||||
using Umbraco.Web._Legacy.Actions;
|
||||
|
||||
namespace Umbraco.Tests.Composing
|
||||
{
|
||||
[TestFixture]
|
||||
public class ActionCollectionTests : ComposingTestBase
|
||||
{
|
||||
[Test]
|
||||
public void ActionCollectionBuilderWorks()
|
||||
{
|
||||
var collectionBuilder = new ActionCollectionBuilder();
|
||||
collectionBuilder.SetProducer(() => TypeLoader.GetActions());
|
||||
|
||||
var actions = collectionBuilder.CreateCollection();
|
||||
Assert.AreEqual(2, actions.Count());
|
||||
|
||||
// order is unspecified, but both must be there
|
||||
var hasAction1 = actions.ElementAt(0) is SingletonAction || actions.ElementAt(1) is SingletonAction;
|
||||
var hasAction2 = actions.ElementAt(0) is NonSingletonAction || actions.ElementAt(1) is NonSingletonAction;
|
||||
Assert.IsTrue(hasAction1);
|
||||
Assert.IsTrue(hasAction2);
|
||||
|
||||
var singletonAction = (SingletonAction) (actions.ElementAt(0) is SingletonAction ? actions.ElementAt(0) : actions.ElementAt(1));
|
||||
|
||||
// ensure it is a singleton
|
||||
Assert.AreSame(SingletonAction.Instance, singletonAction);
|
||||
}
|
||||
|
||||
#region Test Objects
|
||||
|
||||
public class SingletonAction : IAction
|
||||
{
|
||||
public static SingletonAction Instance { get; } = new SingletonAction();
|
||||
|
||||
public char Letter => 'I';
|
||||
|
||||
public string JsFunctionName => $"{ClientTools.Scripts.GetAppActions}.actionAssignDomain()";
|
||||
|
||||
public string JsSource => null;
|
||||
|
||||
public string Alias => "assignDomain";
|
||||
|
||||
public string Icon => ".sprDomain";
|
||||
|
||||
public bool ShowInNotifier => false;
|
||||
|
||||
public bool CanBePermissionAssigned => true;
|
||||
}
|
||||
|
||||
public class NonSingletonAction : IAction
|
||||
{
|
||||
public char Letter => 'Q';
|
||||
|
||||
public string JsFunctionName => $"{ClientTools.Scripts.GetAppActions}.actionAssignDomain()";
|
||||
|
||||
public string JsSource => null;
|
||||
|
||||
public string Alias => "asfasdf";
|
||||
|
||||
public string Icon => ".sprDomain";
|
||||
|
||||
public bool ShowInNotifier => false;
|
||||
|
||||
public bool CanBePermissionAssigned => true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using LightInject;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Tests.DI
|
||||
namespace Umbraco.Tests.Composing
|
||||
{
|
||||
[TestFixture]
|
||||
public class CollectionBuildersTests
|
||||
@@ -30,73 +30,6 @@ namespace Umbraco.Tests.DI
|
||||
_container = null;
|
||||
}
|
||||
|
||||
#region Test objects
|
||||
|
||||
public abstract class Resolved
|
||||
{ }
|
||||
|
||||
public class Resolved1 : Resolved
|
||||
{ }
|
||||
|
||||
[Weight(5)] // default is 10
|
||||
public class Resolved2 : Resolved
|
||||
{ }
|
||||
|
||||
public class Resolved3 : Resolved
|
||||
{ }
|
||||
|
||||
public class Resolved4 // not! : Resolved
|
||||
{ }
|
||||
|
||||
private class TestCollectionBuilder : OrderedCollectionBuilderBase<TestCollectionBuilder, TestCollection, Resolved>
|
||||
{
|
||||
public TestCollectionBuilder(IServiceContainer container)
|
||||
: base(container)
|
||||
{ }
|
||||
|
||||
protected override TestCollectionBuilder This => this;
|
||||
}
|
||||
|
||||
private class TestCollectionBuilderTransient : OrderedCollectionBuilderBase<TestCollectionBuilderTransient, TestCollection, Resolved>
|
||||
{
|
||||
public TestCollectionBuilderTransient(IServiceContainer container)
|
||||
: base(container)
|
||||
{ }
|
||||
|
||||
protected override TestCollectionBuilderTransient This => this;
|
||||
|
||||
protected override ILifetime CollectionLifetime => null; // transient
|
||||
}
|
||||
|
||||
private class TestCollectionBuilderScope : OrderedCollectionBuilderBase<TestCollectionBuilderScope, TestCollection, Resolved>
|
||||
{
|
||||
public TestCollectionBuilderScope(IServiceContainer container)
|
||||
: base(container)
|
||||
{ }
|
||||
|
||||
protected override TestCollectionBuilderScope This => this;
|
||||
|
||||
protected override ILifetime CollectionLifetime => new PerScopeLifetime();
|
||||
}
|
||||
|
||||
private class TestCollectionBuilderWeighted : WeightedCollectionBuilderBase<TestCollectionBuilderWeighted, TestCollection, Resolved>
|
||||
{
|
||||
public TestCollectionBuilderWeighted(IServiceContainer container)
|
||||
: base(container)
|
||||
{ }
|
||||
|
||||
protected override TestCollectionBuilderWeighted This => this;
|
||||
}
|
||||
|
||||
private class TestCollection : BuilderCollectionBase<Resolved>
|
||||
{
|
||||
public TestCollection(IEnumerable<Resolved> items)
|
||||
: base(items)
|
||||
{ }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[Test]
|
||||
public void ContainsTypes()
|
||||
{
|
||||
@@ -114,7 +47,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Clear()
|
||||
public void CanClearBuilderBeforeCollectionIsCreated()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>()
|
||||
@@ -129,7 +62,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ClearOnceResolved()
|
||||
public void CannotClearBuilderOnceCollectionIsCreated()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>()
|
||||
@@ -141,7 +74,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Append()
|
||||
public void CanAppendToBuilder()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>();
|
||||
builder.Append<Resolved1>();
|
||||
@@ -156,7 +89,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AppendOnceResolved()
|
||||
public void CannotAppendToBuilderOnceCollectionIsCreated()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>();
|
||||
|
||||
@@ -168,7 +101,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AppendDuplicate()
|
||||
public void CanAppendDuplicateToBuilderAndDeDuplicate()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>();
|
||||
builder.Append<Resolved1>();
|
||||
@@ -179,7 +112,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AppendInvalid()
|
||||
public void CannotAppendInvalidTypeToBUilder()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>();
|
||||
//builder.Append<Resolved4>(); // does not compile
|
||||
@@ -189,7 +122,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Remove()
|
||||
public void CanRemoveFromBuilder()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>()
|
||||
@@ -205,7 +138,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveAbsent()
|
||||
public void CanRemoveMissingFromBuilder()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>()
|
||||
@@ -217,7 +150,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveOnceResolved()
|
||||
public void CannotRemoveFromBuilderOnceCollectionIsCreated()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>()
|
||||
@@ -230,7 +163,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Insert()
|
||||
public void CanInsertIntoBuilder()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>()
|
||||
@@ -246,7 +179,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InsertOnceResolved()
|
||||
public void CannotInsertIntoBuilderOnceCollectionIsCreated()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>()
|
||||
@@ -259,7 +192,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanInsertDuplicate()
|
||||
public void CanInsertDuplicateIntoBuilderAndDeDuplicate()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>()
|
||||
@@ -271,7 +204,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InsertInEmpty()
|
||||
public void CanInsertIntoEmptyBuilder()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>();
|
||||
builder.Insert<Resolved2>();
|
||||
@@ -281,7 +214,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InsertAtWrongIndex1()
|
||||
public void CannotInsertIntoBuilderAtWrongIndex()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>()
|
||||
@@ -290,14 +223,6 @@ namespace Umbraco.Tests.DI
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
builder.Insert<Resolved3>(99) // throws
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InsertAtWrongIndex2()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>()
|
||||
.Append<Resolved2>();
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
builder.Insert<Resolved3>(-1) // throws
|
||||
@@ -305,7 +230,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InsertBefore()
|
||||
public void CanInsertIntoBuilderBefore()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>()
|
||||
@@ -321,7 +246,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InsertBeforeOnceResolved()
|
||||
public void CannotInsertIntoBuilderBeforeOnceCollectionIsCreated()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>()
|
||||
@@ -334,7 +259,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InsertBeforeDuplicate()
|
||||
public void CanInsertDuplicateIntoBuilderBeforeAndDeDuplicate()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>()
|
||||
@@ -346,7 +271,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InsertBeforeAbsent()
|
||||
public void CannotInsertIntoBuilderBeforeMissing()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>();
|
||||
@@ -357,7 +282,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ScopeIsApplication()
|
||||
public void ScopeBuilderCreatesScopedCollection()
|
||||
{
|
||||
_container.RegisterCollectionBuilder<TestCollectionBuilder>()
|
||||
.Append<Resolved1>()
|
||||
@@ -377,7 +302,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ScopeIsTransient()
|
||||
public void TransientBuilderCreatesTransientCollection()
|
||||
{
|
||||
_container.RegisterCollectionBuilder<TestCollectionBuilderTransient>()
|
||||
.Append<Resolved1>()
|
||||
@@ -397,7 +322,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OrderOfTypes()
|
||||
public void BuilderRespectsTypesOrder()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilderTransient>()
|
||||
.Append<Resolved3>()
|
||||
@@ -409,7 +334,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ScopeIsScope()
|
||||
public void ScopeBuilderRespectsContainerScope()
|
||||
{
|
||||
_container.RegisterCollectionBuilder<TestCollectionBuilderScope>()
|
||||
.Append<Resolved1>()
|
||||
@@ -440,7 +365,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Weights()
|
||||
public void WeightedBuilderCreatesWeightedCollection()
|
||||
{
|
||||
var builder = _container.RegisterCollectionBuilder<TestCollectionBuilderWeighted>()
|
||||
.Add<Resolved1>()
|
||||
@@ -450,6 +375,8 @@ namespace Umbraco.Tests.DI
|
||||
AssertCollection(col, typeof(Resolved2), typeof(Resolved1));
|
||||
}
|
||||
|
||||
#region Assertions
|
||||
|
||||
private static void AssertCollection(IEnumerable<Resolved> col, params Type[] expected)
|
||||
{
|
||||
var colA = col.ToArray();
|
||||
@@ -481,5 +408,79 @@ namespace Umbraco.Tests.DI
|
||||
for (var i = 0; i < col1A.Length; i++)
|
||||
Assert.AreNotSame(col1A[i], col2A[i]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Objects
|
||||
|
||||
public abstract class Resolved
|
||||
{ }
|
||||
|
||||
public class Resolved1 : Resolved
|
||||
{ }
|
||||
|
||||
[Weight(5)] // default is 10
|
||||
public class Resolved2 : Resolved
|
||||
{ }
|
||||
|
||||
public class Resolved3 : Resolved
|
||||
{ }
|
||||
|
||||
public class Resolved4 // not! : Resolved
|
||||
{ }
|
||||
|
||||
// ReSharper disable once ClassNeverInstantiated.Local
|
||||
private class TestCollectionBuilder : OrderedCollectionBuilderBase<TestCollectionBuilder, TestCollection, Resolved>
|
||||
{
|
||||
public TestCollectionBuilder(IServiceContainer container)
|
||||
: base(container)
|
||||
{ }
|
||||
|
||||
protected override TestCollectionBuilder This => this;
|
||||
}
|
||||
|
||||
// ReSharper disable once ClassNeverInstantiated.Local
|
||||
private class TestCollectionBuilderTransient : OrderedCollectionBuilderBase<TestCollectionBuilderTransient, TestCollection, Resolved>
|
||||
{
|
||||
public TestCollectionBuilderTransient(IServiceContainer container)
|
||||
: base(container)
|
||||
{ }
|
||||
|
||||
protected override TestCollectionBuilderTransient This => this;
|
||||
|
||||
protected override ILifetime CollectionLifetime => null; // transient
|
||||
}
|
||||
|
||||
// ReSharper disable once ClassNeverInstantiated.Local
|
||||
private class TestCollectionBuilderScope : OrderedCollectionBuilderBase<TestCollectionBuilderScope, TestCollection, Resolved>
|
||||
{
|
||||
public TestCollectionBuilderScope(IServiceContainer container)
|
||||
: base(container)
|
||||
{ }
|
||||
|
||||
protected override TestCollectionBuilderScope This => this;
|
||||
|
||||
protected override ILifetime CollectionLifetime => new PerScopeLifetime();
|
||||
}
|
||||
|
||||
// ReSharper disable once ClassNeverInstantiated.Local
|
||||
private class TestCollectionBuilderWeighted : WeightedCollectionBuilderBase<TestCollectionBuilderWeighted, TestCollection, Resolved>
|
||||
{
|
||||
public TestCollectionBuilderWeighted(IServiceContainer container)
|
||||
: base(container)
|
||||
{ }
|
||||
|
||||
protected override TestCollectionBuilderWeighted This => this;
|
||||
}
|
||||
|
||||
// ReSharper disable once ClassNeverInstantiated.Local
|
||||
private class TestCollection : BuilderCollectionBase<Resolved>
|
||||
{
|
||||
public TestCollection(IEnumerable<Resolved> items)
|
||||
: base(items)
|
||||
{ }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,12 @@ using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Tests.DI
|
||||
namespace Umbraco.Tests.Composing
|
||||
{
|
||||
public abstract class ResolverBaseTest // fixme rename, do something!
|
||||
public abstract class ComposingTestBase
|
||||
{
|
||||
protected TypeLoader TypeLoader { get; private set; }
|
||||
|
||||
protected ProfilingLogger ProfilingLogger { get; private set; }
|
||||
|
||||
[SetUp]
|
||||
@@ -18,9 +19,7 @@ namespace Umbraco.Tests.DI
|
||||
{
|
||||
ProfilingLogger = new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>());
|
||||
|
||||
TypeLoader = new TypeLoader(NullCacheProvider.Instance,
|
||||
ProfilingLogger,
|
||||
false)
|
||||
TypeLoader = new TypeLoader(NullCacheProvider.Instance, ProfilingLogger, detectChanges: false)
|
||||
{
|
||||
AssembliesToScan = AssembliesToScan
|
||||
};
|
||||
@@ -6,7 +6,7 @@ using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Tests.DI
|
||||
namespace Umbraco.Tests.Composing
|
||||
{
|
||||
[TestFixture]
|
||||
public class LazyCollectionBuilderTests
|
||||
@@ -28,7 +28,7 @@ namespace Umbraco.Tests.DI
|
||||
// so we don't have a test for duplicates as we had with resolvers in v7
|
||||
|
||||
[Test]
|
||||
public void LazyCollectionBuilderTypes()
|
||||
public void LazyCollectionBuilderHandlesTypes()
|
||||
{
|
||||
var container = new ServiceContainer();
|
||||
container.ConfigureUmbracoCore();
|
||||
@@ -52,7 +52,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LazyCollectionBuilderProducers()
|
||||
public void LazyCollectionBuilderHandlesProducers()
|
||||
{
|
||||
var container = new ServiceContainer();
|
||||
container.ConfigureUmbracoCore();
|
||||
@@ -75,7 +75,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LazyCollectionBuilderBoth()
|
||||
public void LazyCollectionBuilderHandlesTypesAndProducers()
|
||||
{
|
||||
var container = new ServiceContainer();
|
||||
container.ConfigureUmbracoCore();
|
||||
@@ -99,7 +99,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LazyCollectionBuilderThrows()
|
||||
public void LazyCollectionBuilderThrowsOnIllegalTypes()
|
||||
{
|
||||
var container = new ServiceContainer();
|
||||
container.ConfigureUmbracoCore();
|
||||
@@ -121,7 +121,7 @@ namespace Umbraco.Tests.DI
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LazyCollectionBuilderExclude()
|
||||
public void LazyCollectionBuilderCanExcludeTypes()
|
||||
{
|
||||
var container = new ServiceContainer();
|
||||
container.ConfigureUmbracoCore();
|
||||
@@ -145,7 +145,7 @@ namespace Umbraco.Tests.DI
|
||||
Assert.IsFalse(values.Contains(o1)); // transient
|
||||
}
|
||||
|
||||
#region Test classes
|
||||
#region Test Objects
|
||||
|
||||
private interface ITestInterface
|
||||
{ }
|
||||
@@ -162,6 +162,7 @@ namespace Umbraco.Tests.DI
|
||||
private class TransientObject4
|
||||
{ }
|
||||
|
||||
// ReSharper disable once ClassNeverInstantiated.Local
|
||||
private class TestCollectionBuilder : LazyCollectionBuilderBase<TestCollectionBuilder, TestCollection, ITestInterface>
|
||||
{
|
||||
public TestCollectionBuilder(IServiceContainer container)
|
||||
@@ -173,6 +174,7 @@ namespace Umbraco.Tests.DI
|
||||
protected override ILifetime CollectionLifetime => null; // transient
|
||||
}
|
||||
|
||||
// ReSharper disable once ClassNeverInstantiated.Local
|
||||
private class TestCollection : BuilderCollectionBase<ITestInterface>
|
||||
{
|
||||
public TestCollection(IEnumerable<ITestInterface> items)
|
||||
@@ -6,17 +6,13 @@ using NUnit.Framework;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core._Legacy.PackageActions;
|
||||
|
||||
namespace Umbraco.Tests.DI
|
||||
namespace Umbraco.Tests.Composing
|
||||
{
|
||||
[TestFixture]
|
||||
public class PackageActionCollectionTests : ResolverBaseTest
|
||||
public class PackageActionCollectionTests : ComposingTestBase
|
||||
{
|
||||
// NOTE
|
||||
// ManyResolverTests ensure that we'll get our actions back and PackageActionResolver works,
|
||||
// so all we're testing here is that plugin manager _does_ find our package actions
|
||||
// which should be ensured by PlugingManagerTests anyway, so this is useless?
|
||||
[Test]
|
||||
public void FindAllPackageActions()
|
||||
public void PackageActionCollectionBuilderWorks()
|
||||
{
|
||||
var container = new ServiceContainer();
|
||||
container.ConfigureUmbracoCore();
|
||||
@@ -34,7 +30,7 @@ namespace Umbraco.Tests.DI
|
||||
Assert.IsTrue(hasAction2);
|
||||
}
|
||||
|
||||
#region Classes for tests
|
||||
#region Test Objects
|
||||
|
||||
public class PackageAction1 : IPackageAction
|
||||
{
|
||||
@@ -4,18 +4,13 @@ using NUnit.Framework;
|
||||
using Umbraco.Core.Macros;
|
||||
using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Tests.DI
|
||||
namespace Umbraco.Tests.Composing
|
||||
{
|
||||
[TestFixture]
|
||||
public class XsltExtensionsResolverTests : ResolverBaseTest
|
||||
public class XsltExtensionCollectionTests : ComposingTestBase
|
||||
{
|
||||
// NOTE
|
||||
// ManyResolverTests ensure that we'll get our actions back and ActionsResolver works,
|
||||
// so all we're testing here is that plugin manager _does_ find our actions
|
||||
// which should be ensured by PlugingManagerTests anyway, so this is useless?
|
||||
// maybe not as it seems to handle the "instance" thing... so we test that we respect the singleton?
|
||||
[Test]
|
||||
public void Find_All_Extensions()
|
||||
public void XsltExtensionsCollectionBuilderWorks()
|
||||
{
|
||||
var container = new ServiceContainer();
|
||||
var builder = new XsltExtensionCollectionBuilder(container);
|
||||
@@ -30,19 +25,16 @@ namespace Umbraco.Tests.DI
|
||||
Assert.AreEqual("test2", extensions.Single(x => x.ExtensionObject.GetType() == typeof(XsltEx2)).Namespace);
|
||||
}
|
||||
|
||||
#region Classes for tests
|
||||
#region Test Objects
|
||||
|
||||
[Umbraco.Core.Macros.XsltExtension("test1")]
|
||||
[XsltExtension("test1")]
|
||||
public class XsltEx1
|
||||
{
|
||||
|
||||
}
|
||||
{ }
|
||||
|
||||
//test with legacy one
|
||||
[umbraco.XsltExtension("test2")]
|
||||
public class XsltEx2
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.UI.Pages;
|
||||
using Umbraco.Web._Legacy.Actions;
|
||||
|
||||
namespace Umbraco.Tests.DI
|
||||
{
|
||||
[TestFixture]
|
||||
public class ActionCollectionTests : ResolverBaseTest
|
||||
{
|
||||
// NOTE
|
||||
// ManyResolverTests ensure that we'll get our actions back and ActionsResolver works,
|
||||
// so all we're testing here is that plugin manager _does_ find our actions
|
||||
// which should be ensured by PlugingManagerTests anyway, so this is useless?
|
||||
// maybe not as it seems to handle the "instance" thing... so we test that we respect the singleton?
|
||||
[Test]
|
||||
public void FindAllActions()
|
||||
{
|
||||
var collectionBuilder = new ActionCollectionBuilder();
|
||||
collectionBuilder.SetProducer(() => TypeLoader.GetActions());
|
||||
|
||||
var actions = collectionBuilder.CreateCollection();
|
||||
Assert.AreEqual(2, actions.Count());
|
||||
|
||||
// order is unspecified, but both must be there
|
||||
var hasAction1 = actions.ElementAt(0) is SingletonAction || actions.ElementAt(1) is SingletonAction;
|
||||
var hasAction2 = actions.ElementAt(0) is NonSingletonAction || actions.ElementAt(1) is NonSingletonAction;
|
||||
Assert.IsTrue(hasAction1);
|
||||
Assert.IsTrue(hasAction2);
|
||||
|
||||
var action = (SingletonAction)(actions.ElementAt(0) is SingletonAction ? actions.ElementAt(0) : actions.ElementAt(1));
|
||||
|
||||
// ensure we respect the singleton
|
||||
Assert.AreSame(SingletonAction.Instance, action);
|
||||
}
|
||||
|
||||
#region Classes for tests
|
||||
|
||||
public class SingletonAction : IAction
|
||||
{
|
||||
//create singleton
|
||||
private static readonly SingletonAction instance = new SingletonAction();
|
||||
|
||||
public static SingletonAction Instance
|
||||
{
|
||||
get { return instance; }
|
||||
}
|
||||
|
||||
#region IAction Members
|
||||
|
||||
public char Letter
|
||||
{
|
||||
get
|
||||
{
|
||||
return 'I';
|
||||
}
|
||||
}
|
||||
|
||||
public string JsFunctionName
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.Format("{0}.actionAssignDomain()", ClientTools.Scripts.GetAppActions);
|
||||
}
|
||||
}
|
||||
|
||||
public string JsSource
|
||||
{
|
||||
get
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string Alias
|
||||
{
|
||||
get
|
||||
{
|
||||
return "assignDomain";
|
||||
}
|
||||
}
|
||||
|
||||
public string Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
return ".sprDomain";
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowInNotifier
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool CanBePermissionAssigned
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class NonSingletonAction : IAction
|
||||
{
|
||||
#region IAction Members
|
||||
|
||||
public char Letter
|
||||
{
|
||||
get
|
||||
{
|
||||
return 'Q';
|
||||
}
|
||||
}
|
||||
|
||||
public string JsFunctionName
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.Format("{0}.actionAssignDomain()", ClientTools.Scripts.GetAppActions);
|
||||
}
|
||||
}
|
||||
|
||||
public string JsSource
|
||||
{
|
||||
get
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string Alias
|
||||
{
|
||||
get
|
||||
{
|
||||
return "asfasdf";
|
||||
}
|
||||
}
|
||||
|
||||
public string Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
return ".sprDomain";
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowInNotifier
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool CanBePermissionAssigned
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -319,7 +319,7 @@
|
||||
<Compile Include="Persistence\Repositories\PublicAccessRepositoryTest.cs" />
|
||||
<Compile Include="Persistence\Repositories\TaskRepositoryTest.cs" />
|
||||
<Compile Include="Persistence\Repositories\TaskTypeRepositoryTest.cs" />
|
||||
<Compile Include="DependencyInjection\ResolverBaseTest.cs" />
|
||||
<Compile Include="Composing\ComposingTestBase.cs" />
|
||||
<Compile Include="Routing\RoutesCacheTests.cs" />
|
||||
<Compile Include="Routing\UrlRoutingTestBase.cs" />
|
||||
<Compile Include="Security\BackOfficeCookieManagerTests.cs" />
|
||||
@@ -428,7 +428,7 @@
|
||||
<Compile Include="Services\LocalizedTextServiceTests.cs" />
|
||||
<Compile Include="Services\TagServiceTests.cs" />
|
||||
<Compile Include="Services\LocalizationServiceTests.cs" />
|
||||
<Compile Include="DependencyInjection\XsltExtensionsResolverTests.cs" />
|
||||
<Compile Include="Composing\XsltExtensionCollectionTests.cs" />
|
||||
<Compile Include="Services\MemberServiceTests.cs" />
|
||||
<Compile Include="Services\MediaServiceTests.cs" />
|
||||
<Compile Include="Services\MemberTypeServiceTests.cs" />
|
||||
@@ -508,7 +508,7 @@
|
||||
<Compile Include="Persistence\Repositories\StylesheetRepositoryTest.cs" />
|
||||
<Compile Include="PublishedContent\PublishedContentMoreTests.cs" />
|
||||
<Compile Include="Publishing\PublishingStrategyTests.cs" />
|
||||
<Compile Include="DependencyInjection\ActionCollectionTests.cs" />
|
||||
<Compile Include="Composing\ActionCollectionTests.cs" />
|
||||
<Compile Include="TestHelpers\Entities\MockedMember.cs" />
|
||||
<Compile Include="TestHelpers\Entities\MockedUser.cs" />
|
||||
<Compile Include="TestHelpers\Stubs\TestProfiler.cs" />
|
||||
@@ -521,8 +521,8 @@
|
||||
<Compile Include="TreesAndSections\ApplicationTreeTest.cs" />
|
||||
<Compile Include="CoreThings\ObjectExtensionsTests.cs" />
|
||||
<Compile Include="Cache\PublishedCache\PublishedContentCacheTests.cs" />
|
||||
<Compile Include="DependencyInjection\LazyCollectionBuilderTests.cs" />
|
||||
<Compile Include="DependencyInjection\CollectionBuildersTests.cs" />
|
||||
<Compile Include="Composing\LazyCollectionBuilderTests.cs" />
|
||||
<Compile Include="Composing\CollectionBuildersTests.cs" />
|
||||
<Compile Include="Routing\ContentFinderByAliasWithDomainsTests.cs" />
|
||||
<Compile Include="Routing\ContentFinderByNiceUrlWithDomainsTests.cs" />
|
||||
<Compile Include="Routing\DomainsAndCulturesTests.cs" />
|
||||
@@ -581,7 +581,7 @@
|
||||
<Compile Include="UmbracoExamine\ExamineDemoDataContentService.cs" />
|
||||
<Compile Include="CoreThings\UriExtensionsTests.cs" />
|
||||
<Compile Include="Misc\UriUtilityTests.cs" />
|
||||
<Compile Include="DependencyInjection\PackageActionCollectionTests.cs" />
|
||||
<Compile Include="Composing\PackageActionCollectionTests.cs" />
|
||||
<Compile Include="Plugins\PluginManagerExtensions.cs" />
|
||||
<Compile Include="Plugins\PluginManagerTests.cs" />
|
||||
<Compile Include="TestHelpers\Stubs\TestLastChanceFinder.cs" />
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace Umbraco.Web.Editors
|
||||
// get the new configuration as a dictionary (this is how we get it from model)
|
||||
// and map it to an actual configuration object
|
||||
var currentConfiguration = dataType.PersistedDataType.Configuration;
|
||||
var configurationDictionary = dataType.ConfigurationFields.ToDictionary(x => x.Key, x => x.Value);
|
||||
var configurationDictionary = dataType.ConfigurationFields.ToDictionary(x => x.Key, x => x.Value); // fixme tokens!
|
||||
var configuration = dataType.PropertyEditor.ConfigurationEditor.FromEditor(configurationDictionary, currentConfiguration);
|
||||
|
||||
dataType.PersistedDataType.Configuration = configuration;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
@@ -9,15 +10,15 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
public class DataTypeConfigurationFieldSave
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the configuration field key.
|
||||
/// Gets or sets the configuration field key.
|
||||
/// </summary>
|
||||
[DataMember(Name = "key", IsRequired = true)]
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configuration field value.
|
||||
/// Gets or sets the configuration field value.
|
||||
/// </summary>
|
||||
[DataMember(Name = "value", IsRequired = true)]
|
||||
public object Value { get; set; } // fixme - what's a value?
|
||||
public object Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Web.PropertyEditors
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Web.PropertyEditors
|
||||
{
|
||||
@@ -7,8 +8,19 @@ namespace Umbraco.Web.PropertyEditors
|
||||
/// </summary>
|
||||
public class ImageCropperConfiguration
|
||||
{
|
||||
// fixme should not be a string!
|
||||
[ConfigurationField("crops", "Crop sizes", "views/propertyeditors/imagecropper/imagecropper.prevalues.html")]
|
||||
public string Crops { get; set; }
|
||||
public ImageCropperCrop[] Crops { get; set; }
|
||||
|
||||
public class ImageCropperCrop
|
||||
{
|
||||
[JsonProperty("alias")]
|
||||
public string Alias { get; set; }
|
||||
|
||||
[JsonProperty("width")]
|
||||
public int Width { get; set; }
|
||||
|
||||
[JsonProperty("height")]
|
||||
public int Height { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,8 +168,7 @@ namespace Umbraco.Web.PropertyEditors
|
||||
|
||||
// more magic here ;-(
|
||||
var configuration = dataTypeService.GetDataType(propertyType.DataTypeId).ConfigurationAs<ImageCropperConfiguration>();
|
||||
var crops = configuration?.Crops; // fixme but Crops should not be a string and then we'd serialize them
|
||||
if (string.IsNullOrWhiteSpace(crops)) crops = "[]";
|
||||
var crops = configuration?.Crops ?? Array.Empty<ImageCropperConfiguration.ImageCropperCrop>();
|
||||
return "{src: '" + val + "', crops: " + crops + "}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
@@ -19,12 +18,10 @@ namespace Umbraco.Web.PropertyEditors
|
||||
// not simply deserializing Json because we want to validate the valueType
|
||||
|
||||
if (editorValue.TryGetValue(Constants.PropertyEditors.ConfigurationKeys.DataValueType, out var valueTypeObj)
|
||||
&& valueTypeObj is JToken jtoken
|
||||
&& jtoken.Type == JTokenType.String)
|
||||
&& valueTypeObj is string stringValue)
|
||||
{
|
||||
var valueType = jtoken.Value<string>();
|
||||
if (!string.IsNullOrWhiteSpace(valueType) && ValueTypes.IsValue(valueType)) // validate
|
||||
newConfiguration.ValueType = valueType;
|
||||
if (!string.IsNullOrWhiteSpace(stringValue) && ValueTypes.IsValue(stringValue)) // validate
|
||||
newConfiguration.ValueType = stringValue;
|
||||
}
|
||||
|
||||
return newConfiguration;
|
||||
|
||||
@@ -9,39 +9,30 @@ namespace Umbraco.Web.PropertyEditors
|
||||
public class ListViewConfiguration
|
||||
{
|
||||
[ConfigurationField("pageSize", "Page Size", "number", Description = "Number of items per page")]
|
||||
[JsonProperty("pageSize")]
|
||||
public int PageSize { get; set; }
|
||||
|
||||
[ConfigurationField("displayAtTabNumber", "Display At Tab Number", "number", Description = "Which tab position that the list of child items will be displayed")]
|
||||
[JsonProperty("displayAtNumber")]
|
||||
public int DisplayAtTabNumber { get; set; }
|
||||
|
||||
[ConfigurationField("orderBy", "Order By", "views/propertyeditors/listview/sortby.prevalues.html",
|
||||
Description = "The default sort order for the list")]
|
||||
[JsonProperty("orderBy")]
|
||||
public string OrderBy { get; set; }
|
||||
|
||||
[ConfigurationField("orderDirection", "Order Direction", "views/propertyeditors/listview/orderdirection.prevalues.html")]
|
||||
[JsonProperty("orderDirection")]
|
||||
public string OrderDirection { get; set; }
|
||||
|
||||
|
||||
[ConfigurationField("includeProperties", "Columns Displayed", "views/propertyeditors/listview/includeproperties.prevalues.html",
|
||||
Description = "The properties that will be displayed for each column")]
|
||||
[JsonProperty("includeProperties")]
|
||||
public Property[] IncludeProperties { get; set; }
|
||||
|
||||
[ConfigurationField("layouts", "Layouts", "views/propertyeditors/listview/layouts.prevalues.html")]
|
||||
[JsonProperty("layouts")]
|
||||
public Layout[] Layouts { get; set; }
|
||||
|
||||
[ConfigurationField("bulkActionPermissions", "Bulk Action Permissions", "views/propertyeditors/listview/bulkactionpermissions.prevalues.html",
|
||||
Description = "The bulk actions that are allowed from the list view")]
|
||||
[JsonProperty("bulkActionPermissions")]
|
||||
public BulkActionPermissionSettings BulkActionPermissions { get; set; } = new BulkActionPermissionSettings(); // fixme managing defaults?
|
||||
|
||||
[ConfigurationField("tabName", "Tab Name", "textstring", Description = "The name of the listview tab (default if empty: 'Child Items')")]
|
||||
[JsonProperty("tabName")]
|
||||
public int TabName { get; set; }
|
||||
|
||||
public class Property
|
||||
|
||||
@@ -38,7 +38,9 @@ namespace Umbraco.Web.PropertyEditors
|
||||
var dictionary = base.ToEditor(configuration);
|
||||
|
||||
// the front-end editor expects the string value of the storage type
|
||||
dictionary["storageType"] = dictionary["storageType"].ToString();
|
||||
if (!dictionary.TryGetValue("storageType", out var storageType))
|
||||
storageType = TagsStorageType.Csv;
|
||||
dictionary["storageType"] = storageType.ToString();
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,11 @@ namespace Umbraco.Web.PropertyEditors
|
||||
/// <inheritdoc />
|
||||
public override Dictionary<string, object> ToEditor(ValueListConfiguration configuration)
|
||||
{
|
||||
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
|
||||
if (configuration == null)
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{ "items", new object() }
|
||||
};
|
||||
|
||||
// map to what the (still v7) editor expects
|
||||
// {"item":{"169":{"value":"a","sortOrder":1},"170":{"value":"b","sortOrder":2},"171":{"value":"c","sortOrder":3}}}
|
||||
|
||||
Reference in New Issue
Block a user