DataType refactoring - troubleshooting

This commit is contained in:
Stephan
2018-02-02 19:43:03 +01:00
parent c748a2bc41
commit 947d08c947
25 changed files with 1358 additions and 974 deletions

View File

@@ -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));

View File

@@ -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>

View File

@@ -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;

View 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

View File

@@ -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" />

View File

@@ -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
}
}

View File

@@ -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
}
}

View 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
}
}

View File

@@ -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
}
}

View File

@@ -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
};

View File

@@ -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)

View File

@@ -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
{

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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" />

View File

@@ -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;

View File

@@ -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; }
}
}

View File

@@ -1,4 +1,6 @@
using Umbraco.Core.PropertyEditors;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{

View File

@@ -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; }
}
}
}

View File

@@ -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 + "}";
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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}}}