diff --git a/src/Umbraco.Core/Dynamics/CaseInsensitiveDynamicObject.cs b/src/Umbraco.Core/Dynamics/CaseInsensitiveDynamicObject.cs
new file mode 100644
index 0000000000..43d7adc647
--- /dev/null
+++ b/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
+{
+ ///
+ /// This will check enable dynamic access to properties and methods in a case insensitive manner
+ ///
+ ///
+ ///
+ /// 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
+ ///
+ public abstract class CaseInsensitiveDynamicObject : DynamicObject
+ where T: class
+ {
+ ///
+ /// Used for dynamic access for case insensitive property access
+ /// `
+ private static readonly Lazy>> CaseInsensitivePropertyAccess = new Lazy>>(() =>
+ {
+ var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
+ .DistinctBy(x => x.Name);
+ return props.Select(propInfo =>
+ {
+ var name = propInfo.Name.ToLowerInvariant();
+ Func getVal = propInfo.GetValue;
+ return new KeyValuePair>(name, getVal);
+
+ }).ToDictionary(x => x.Key, x => x.Value);
+ });
+
+ ///
+ /// Used for dynamic access for case insensitive property access
+ ///
+ private static readonly Lazy>>> CaseInsensitiveMethodAccess
+ = new Lazy>>>(() =>
+ {
+ 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 getVal = methodInfo.Invoke;
+ var val = new Tuple>(methodInfo.GetParameters(), getVal);
+ return new KeyValuePair>>(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(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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index ba14fa044a..ef45bb08ab 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -309,6 +309,7 @@
+
diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs
index 41ba665b4a..8a12b8a651 100644
--- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs
+++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs
@@ -1,4 +1,5 @@
using System;
+using System.Globalization;
using System.Linq;
using Moq;
using Newtonsoft.Json;
@@ -24,7 +25,86 @@ namespace Umbraco.Tests.PropertyEditors
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
@@ -34,6 +114,7 @@ namespace Umbraco.Tests.PropertyEditors
Assert.AreEqual(destObj.Result, "/media/1005/img_0672.jpg");
}
+ [Test]
public void CanConvertImageCropperDataSetJObject()
{
//cropperJson3 - has not crops
@@ -43,13 +124,14 @@ namespace Umbraco.Tests.PropertyEditors
Assert.AreEqual(sourceObj, destObj.Result.ToObject());
}
+ [Test]
public void CanConvertImageCropperDataSetJsonToString()
{
var sourceObj = cropperJson1.SerializeToCropDataSet();
var destObj = sourceObj.TryConvertTo();
Assert.IsTrue(destObj.Success);
Assert.IsTrue(destObj.Result.DetectIsJson());
- var obj = JsonConvert.DeserializeObject(cropperJson1);
+ var obj = JsonConvert.DeserializeObject(cropperJson1, new JsonSerializerSettings {Culture = CultureInfo.InvariantCulture, FloatParseHandling = FloatParseHandling.Decimal});
Assert.AreEqual(sourceObj, obj);
}
diff --git a/src/Umbraco.Web/Models/ImageCropCoordinates.cs b/src/Umbraco.Web/Models/ImageCropCoordinates.cs
index 69a2daee95..4a480b4beb 100644
--- a/src/Umbraco.Web/Models/ImageCropCoordinates.cs
+++ b/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
+ public class ImageCropCoordinates : CaseInsensitiveDynamicObject, IEquatable
{
[DataMember(Name = "x1")]
public decimal X1 { get; set; }
diff --git a/src/Umbraco.Web/Models/ImageCropData.cs b/src/Umbraco.Web/Models/ImageCropData.cs
index 5992910c5b..fb1b3c7424 100644
--- a/src/Umbraco.Web/Models/ImageCropData.cs
+++ b/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
+ public class ImageCropData : CaseInsensitiveDynamicObject, IEquatable
{
[DataMember(Name = "alias")]
public string Alias { get; set; }
diff --git a/src/Umbraco.Web/Models/ImageCropDataSet.cs b/src/Umbraco.Web/Models/ImageCropDataSet.cs
index 174d52d06f..4c5a68a6b9 100644
--- a/src/Umbraco.Web/Models/ImageCropDataSet.cs
+++ b/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;
@@ -14,8 +19,9 @@ namespace Umbraco.Web.Models
[JsonConverter(typeof(NoTypeConverterJsonConverter))]
[TypeConverter(typeof(ImageCropDataSetConverter))]
[DataContract(Name="imageCropDataSet")]
- public class ImageCropDataSet : IHtmlString, IEquatable
- {
+ public class ImageCropDataSet : CaseInsensitiveDynamicObject, IHtmlString, IEquatable
+ {
+
[DataMember(Name="src")]
public string Src { get; set;}
@@ -88,6 +94,7 @@ namespace Umbraco.Web.Models
{
return Crops.Any() ? JsonConvert.SerializeObject(this) : Src;
}
+
///
/// Indicates whether the current object is equal to another object of the same type.
diff --git a/src/Umbraco.Web/Models/ImageCropFocalPoint.cs b/src/Umbraco.Web/Models/ImageCropFocalPoint.cs
index d9a0984167..e47bab5b7a 100644
--- a/src/Umbraco.Web/Models/ImageCropFocalPoint.cs
+++ b/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
+ public class ImageCropFocalPoint : CaseInsensitiveDynamicObject, IEquatable
{
[DataMember(Name = "left")]
public decimal Left { get; set; }