adds dynamic caseinsensitive support for the image cropper model

This commit is contained in:
Shannon
2016-02-05 13:51:43 +01:00
parent fd984bd613
commit 2721c5cb12
7 changed files with 193 additions and 7 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<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;}
@@ -88,6 +94,7 @@ namespace Umbraco.Web.Models
{
return Crops.Any() ? JsonConvert.SerializeObject(this) : Src;
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.

View File

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