Skip to content

Commit

Permalink
adds dynamic caseinsensitive support for the image cropper model
Browse files Browse the repository at this point in the history
  • Loading branch information
Shazwazza committed Feb 10, 2016
1 parent fd984bd commit 2721c5c
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 7 deletions.
93 changes: 93 additions & 0 deletions src/Umbraco.Core/Dynamics/CaseInsensitiveDynamicObject.cs
@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;

namespace Umbraco.Core.Dynamics
{
/// <summary>
/// This will check enable dynamic access to properties and methods in a case insensitive manner
/// </summary>
/// <typeparam name="T"></typeparam>
/// <remarks>
/// This works by using reflection on the type - the reflection lookup is lazy so it will not execute unless a dynamic method needs to be accessed
/// </remarks>
public abstract class CaseInsensitiveDynamicObject<T> : DynamicObject
where T: class
{
/// <summary>
/// Used for dynamic access for case insensitive property access
/// </summary>`
private static readonly Lazy<IDictionary<string, Func<T, object>>> CaseInsensitivePropertyAccess = new Lazy<IDictionary<string, Func<T, object>>>(() =>
{
var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.DistinctBy(x => x.Name);
return props.Select(propInfo =>
{
var name = propInfo.Name.ToLowerInvariant();
Func<T, object> getVal = propInfo.GetValue;
return new KeyValuePair<string, Func<T, object>>(name, getVal);
}).ToDictionary(x => x.Key, x => x.Value);
});

/// <summary>
/// Used for dynamic access for case insensitive property access
/// </summary>
private static readonly Lazy<IDictionary<string, Tuple<ParameterInfo[], Func<T, object[], object>>>> CaseInsensitiveMethodAccess
= new Lazy<IDictionary<string, Tuple<ParameterInfo[], Func<T, object[], object>>>>(() =>
{
var props = typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public)
.Where(x => x.IsSpecialName == false && x.IsVirtual == false)
.DistinctBy(x => x.Name);
return props.Select(methodInfo =>
{
var name = methodInfo.Name.ToLowerInvariant();
Func<T, object[], object> getVal = methodInfo.Invoke;
var val = new Tuple<ParameterInfo[], Func<T, object[], object>>(methodInfo.GetParameters(), getVal);
return new KeyValuePair<string, Tuple<ParameterInfo[], Func<T, object[], object>>>(name, val);
}).ToDictionary(x => x.Key, x => x.Value);
});

public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
var name = binder.Name.ToLowerInvariant();
if (CaseInsensitiveMethodAccess.Value.ContainsKey(name) == false)
return base.TryInvokeMember(binder, args, out result);

var val = CaseInsensitiveMethodAccess.Value[name];
var parameters = val.Item1;
var callback = val.Item2;
var fullArgs = new List<object>(args);
if (args.Length <= parameters.Length)
{
//need to fill them up if they're optional
for (var i = args.Length; i < parameters.Length; i++)
{
if (parameters[i].IsOptional)
{
fullArgs.Add(parameters[i].DefaultValue);
}
}
if (fullArgs.Count == parameters.Length)
{
result = callback((T)(object)this, fullArgs.ToArray());
return true;
}
}
return base.TryInvokeMember(binder, args, out result);
}

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var name = binder.Name.ToLowerInvariant();
if (CaseInsensitivePropertyAccess.Value.ContainsKey(name) == false)
return base.TryGetMember(binder, out result);

result = CaseInsensitivePropertyAccess.Value[name]((T)(object)this);
return true;
}
}
}
1 change: 1 addition & 0 deletions src/Umbraco.Core/Umbraco.Core.csproj
Expand Up @@ -309,6 +309,7 @@
<Compile Include="DictionaryExtensions.cs" />
<Compile Include="Dictionary\CultureDictionaryFactoryResolver.cs" />
<Compile Include="Dictionary\ICultureDictionaryFactory.cs" />
<Compile Include="Dynamics\CaseInsensitiveDynamicObject.cs" />
<Compile Include="Dynamics\DynamicInstanceHelper.cs" />
<Compile Include="Dynamics\DynamicXmlConverter.cs" />
<Compile Include="Events\CancellableEventArgs.cs" />
Expand Down
86 changes: 84 additions & 2 deletions src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs
@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using System.Linq;
using Moq;
using Newtonsoft.Json;
Expand All @@ -24,7 +25,86 @@ public class ImageCropperTest
private const string cropperJson2 = "{\"focalPoint\": {\"left\": 0.98,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0672.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}";
private const string cropperJson3 = "{\"focalPoint\": {\"left\": 0.98,\"top\": 0.80827067669172936},\"src\": \"/media/1005/img_0672.jpg\",\"crops\": []}";
private const string mediaPath = "/media/1005/img_0671.jpg";


[Test]
public void ImageCropData_Properties_As_Dynamic()
{
var sourceObj = cropperJson1.SerializeToCropDataSet();
dynamic d = sourceObj;

var index = 0;
foreach (var crop in d.crops)
{
var realObjCrop = sourceObj.Crops.ElementAt(index);
Assert.AreEqual(realObjCrop.Alias, crop.Alias);
Assert.AreEqual(realObjCrop.Alias, crop.alias);

Assert.AreEqual(realObjCrop.Height, crop.Height);
Assert.AreEqual(realObjCrop.Height, crop.height);

Assert.AreEqual(realObjCrop.Coordinates.X1, crop.Coordinates.X1);
Assert.AreEqual(realObjCrop.Coordinates.X1, crop.coordinates.x1);

Assert.AreEqual(realObjCrop.Coordinates.X2, crop.Coordinates.X2);
Assert.AreEqual(realObjCrop.Coordinates.X2, crop.coordinates.x2);

Assert.AreEqual(realObjCrop.Coordinates.Y1, crop.Coordinates.Y1);
Assert.AreEqual(realObjCrop.Coordinates.Y1, crop.coordinates.y1);

Assert.AreEqual(realObjCrop.Coordinates.Y2, crop.Coordinates.Y2);
Assert.AreEqual(realObjCrop.Coordinates.Y2, crop.coordinates.y2);
index++;
}

Assert.AreEqual(index, 1);
}

[Test]
public void ImageCropFocalPoint_Properties_As_Dynamic()
{
var sourceObj = cropperJson1.SerializeToCropDataSet();
dynamic d = sourceObj;

Assert.AreEqual(sourceObj.FocalPoint.Left, d.FocalPoint.Left);
Assert.AreEqual(sourceObj.FocalPoint.Left, d.focalPoint.left);

Assert.AreEqual(sourceObj.FocalPoint.Top, d.FocalPoint.Top);
Assert.AreEqual(sourceObj.FocalPoint.Top, d.focalPoint.top);
}

[Test]
public void ImageCropDataSet_Properties_As_Dynamic()
{
var sourceObj = cropperJson1.SerializeToCropDataSet();
dynamic d = sourceObj;

Assert.AreEqual(sourceObj.Src, d.Src);
Assert.AreEqual(sourceObj.Src, d.src);

Assert.AreEqual(sourceObj.FocalPoint, d.FocalPoint);
Assert.AreEqual(sourceObj.FocalPoint, d.focalPoint);

Assert.AreEqual(sourceObj.Crops, d.Crops);
Assert.AreEqual(sourceObj.Crops, d.crops);
}

[Test]
public void ImageCropDataSet_Methods_As_Dynamic()
{
var sourceObj = cropperJson1.SerializeToCropDataSet();
dynamic d = sourceObj;

Assert.AreEqual(sourceObj.HasCrop("thumb"), d.HasCrop("thumb"));
Assert.AreEqual(sourceObj.HasCrop("thumb"), d.hasCrop("thumb"));

Assert.AreEqual(sourceObj.GetCropUrl("thumb"), d.GetCropUrl("thumb"));
Assert.AreEqual(sourceObj.GetCropUrl("thumb"), d.getCropUrl("thumb"));

Assert.AreEqual(sourceObj.HasFocalPoint(), d.HasFocalPoint());
Assert.AreEqual(sourceObj.HasFocalPoint(), d.hasFocalPoint());
}

[Test]
public void CanConvertImageCropperDataSetSrcToString()
{
//cropperJson3 - has not crops
Expand All @@ -34,6 +114,7 @@ public void CanConvertImageCropperDataSetSrcToString()
Assert.AreEqual(destObj.Result, "/media/1005/img_0672.jpg");
}

[Test]
public void CanConvertImageCropperDataSetJObject()
{
//cropperJson3 - has not crops
Expand All @@ -43,13 +124,14 @@ public void CanConvertImageCropperDataSetJObject()
Assert.AreEqual(sourceObj, destObj.Result.ToObject<ImageCropDataSet>());
}

[Test]
public void CanConvertImageCropperDataSetJsonToString()
{
var sourceObj = cropperJson1.SerializeToCropDataSet();
var destObj = sourceObj.TryConvertTo<string>();
Assert.IsTrue(destObj.Success);
Assert.IsTrue(destObj.Result.DetectIsJson());
var obj = JsonConvert.DeserializeObject<ImageCropDataSet>(cropperJson1);
var obj = JsonConvert.DeserializeObject<ImageCropDataSet>(cropperJson1, new JsonSerializerSettings {Culture = CultureInfo.InvariantCulture, FloatParseHandling = FloatParseHandling.Decimal});
Assert.AreEqual(sourceObj, obj);
}

Expand Down
3 changes: 2 additions & 1 deletion src/Umbraco.Web/Models/ImageCropCoordinates.cs
@@ -1,10 +1,11 @@
using System;
using System.Runtime.Serialization;
using Umbraco.Core.Dynamics;

namespace Umbraco.Web.Models
{
[DataContract(Name = "imageCropCoordinates")]
public class ImageCropCoordinates : IEquatable<ImageCropCoordinates>
public class ImageCropCoordinates : CaseInsensitiveDynamicObject<ImageCropCoordinates>, IEquatable<ImageCropCoordinates>
{
[DataMember(Name = "x1")]
public decimal X1 { get; set; }
Expand Down
3 changes: 2 additions & 1 deletion src/Umbraco.Web/Models/ImageCropData.cs
@@ -1,11 +1,12 @@
using System;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using Umbraco.Core.Dynamics;

namespace Umbraco.Web.Models
{
[DataContract(Name = "imageCropData")]
public class ImageCropData : IEquatable<ImageCropData>
public class ImageCropData : CaseInsensitiveDynamicObject<ImageCropData>, IEquatable<ImageCropData>
{
[DataMember(Name = "alias")]
public string Alias { get; set; }
Expand Down
11 changes: 9 additions & 2 deletions src/Umbraco.Web/Models/ImageCropDataSet.cs
@@ -1,11 +1,16 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using System.Web;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Dynamics;
using Umbraco.Core.Serialization;
using Umbraco.Web.PropertyEditors.ValueConverters;

Expand All @@ -14,8 +19,9 @@ namespace Umbraco.Web.Models
[JsonConverter(typeof(NoTypeConverterJsonConverter<ImageCropDataSet>))]
[TypeConverter(typeof(ImageCropDataSetConverter))]
[DataContract(Name="imageCropDataSet")]
public class ImageCropDataSet : IHtmlString, IEquatable<ImageCropDataSet>
{
public class ImageCropDataSet : CaseInsensitiveDynamicObject<ImageCropDataSet>, IHtmlString, IEquatable<ImageCropDataSet>
{

[DataMember(Name="src")]
public string Src { get; set;}

Expand Down Expand Up @@ -88,6 +94,7 @@ public override string ToString()
{
return Crops.Any() ? JsonConvert.SerializeObject(this) : Src;
}


/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
Expand Down
3 changes: 2 additions & 1 deletion src/Umbraco.Web/Models/ImageCropFocalPoint.cs
@@ -1,10 +1,11 @@
using System;
using System.Runtime.Serialization;
using Umbraco.Core.Dynamics;

namespace Umbraco.Web.Models
{
[DataContract(Name = "imageCropFocalPoint")]
public class ImageCropFocalPoint : IEquatable<ImageCropFocalPoint>
public class ImageCropFocalPoint : CaseInsensitiveDynamicObject<ImageCropFocalPoint>, IEquatable<ImageCropFocalPoint>
{
[DataMember(Name = "left")]
public decimal Left { get; set; }
Expand Down

0 comments on commit 2721c5c

Please sign in to comment.