adds dynamic caseinsensitive support for the image cropper model
This commit is contained in:
93
src/Umbraco.Core/Dynamics/CaseInsensitiveDynamicObject.cs
Normal file
93
src/Umbraco.Core/Dynamics/CaseInsensitiveDynamicObject.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user