diff --git a/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs b/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs new file mode 100644 index 0000000000..a7bb20ef5b --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Reflection; +using System.Text; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Dynamics +{ + /// + /// A helper class to try invoke members, find properties, etc... + /// + internal class DynamicInstanceHelper + { + + internal class TryInvokeMemberResult + { + public object ObjectResult { get; private set; } + public TryInvokeMemberSuccessReason Reason { get; private set; } + + public TryInvokeMemberResult(object result, TryInvokeMemberSuccessReason reason) + { + ObjectResult = result; + Reason = reason; + } + } + + internal enum TryInvokeMemberSuccessReason + { + FoundProperty, + FoundMethod, + FoundExtensionMethod + } + + /// + /// Attempts to invoke a member based on the dynamic instance + /// + /// + /// The object instance to invoke the extension method for + /// + /// + /// + /// + /// First tries to find a property with the binder name, if that fails it will try to find a static or instance method + /// on the object that matches the binder name + /// + public static Attempt TryInvokeMember(T thisObject, InvokeMemberBinder binder, object[] args) + { + return TryInvokeMember(thisObject, binder, args, null); + } + + /// + /// Attempts to invoke a member based on the dynamic instance + /// + /// + /// The object instance to invoke the extension method for + /// + /// + /// The types to scan for extension methods + /// + /// + /// First tries to find a property with the binder name, if that fails it will try to find a static or instance method + /// on the object that matches the binder name, if that fails it will then attempt to invoke an extension method + /// based on the binder name and the extension method types to scan. + /// + public static Attempt TryInvokeMember(T thisObject, + InvokeMemberBinder binder, + object[] args, + IEnumerable findExtensionMethodsOnTypes) + { + //TODO: We MUST cache the result here, it is very expensive to keep finding extension methods! + object result; + try + { + //Property? + result = typeof(T).InvokeMember(binder.Name, + System.Reflection.BindingFlags.Instance | + System.Reflection.BindingFlags.Public | + System.Reflection.BindingFlags.GetProperty, + null, + thisObject, + args); + return new Attempt(true, new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundProperty)); + } + catch (MissingMethodException) + { + try + { + //Static or Instance Method? + result = typeof(T).InvokeMember(binder.Name, + System.Reflection.BindingFlags.Instance | + System.Reflection.BindingFlags.Public | + System.Reflection.BindingFlags.Static | + System.Reflection.BindingFlags.InvokeMethod, + null, + thisObject, + args); + return new Attempt(true, new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundMethod)); + } + catch (MissingMethodException) + { + if (findExtensionMethodsOnTypes != null) + { + try + { + result = FindAndExecuteExtensionMethod(thisObject, args, binder.Name, findExtensionMethodsOnTypes); + return new Attempt(true, new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundExtensionMethod)); + } + catch (TargetInvocationException ext) + { + //don't log here, we return this exception because the caller may need to do something specific when + //this exception occurs. + return new Attempt(ext); + } + catch (Exception ex) + { + var sb = new StringBuilder("An error occurred finding an executing an extension method for type "); + sb.Append(typeof (T)); + sb.Append("Types searched for extension methods were "); + foreach(var t in findExtensionMethodsOnTypes) + { + sb.Append(t + ","); + } + LogHelper.Error(sb.ToString(), ex); + return new Attempt(ex); + } + } + return Attempt.False; + } + } + catch (Exception ex) + { + LogHelper.Error("An unhandled exception occurred in method TryInvokeMember", ex); + return new Attempt(ex); + } + } + + /// + /// Attempts to find an extension method that matches the name and arguments based on scanning the Type's passed in + /// to the findMethodsOnTypes parameter + /// + /// The instance object to execute the extension method for + /// + /// + /// + /// + internal static object FindAndExecuteExtensionMethod(T thisObject, + object[] args, + string name, + IEnumerable findMethodsOnTypes) + { + object result = null; + + //find known extension methods that match the first type in the list + MethodInfo toExecute = null; + foreach (var t in findMethodsOnTypes) + { + toExecute = ExtensionMethodFinder.FindExtensionMethod(t, args, name, false); + if (toExecute != null) + break; + } + + if (toExecute != null) + { + var genericArgs = (new[] { (object)thisObject }).Concat(args); + result = toExecute.Invoke(null, genericArgs.ToArray()); + } + else + { + throw new MissingMethodException(); + } + return result; + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/DynamicXml.cs b/src/Umbraco.Core/Dynamics/DynamicXml.cs index 80a3303a60..ef3114f111 100644 --- a/src/Umbraco.Core/Dynamics/DynamicXml.cs +++ b/src/Umbraco.Core/Dynamics/DynamicXml.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Dynamic; +using System.Reflection; using System.Xml.Linq; using System.Xml.XPath; using System.Collections; @@ -10,7 +11,7 @@ using System.Web; namespace Umbraco.Core.Dynamics { - public class DynamicXml : DynamicObject, IEnumerable + public class DynamicXml : DynamicObject, IEnumerable, IEnumerable { public XElement BaseElement { get; set; } @@ -70,7 +71,51 @@ namespace Umbraco.Core.Dynamics HandleIEnumerableXElement(elements, out result); return true; //anyway } - return base.TryInvokeMember(binder, args, out result); + + //ok, now lets try to match by member, property, extensino method + var attempt = DynamicInstanceHelper.TryInvokeMember(this, binder, args, new[] + { + typeof (IEnumerable), + typeof (DynamicXml) + }); + + if (attempt.Success) + { + result = attempt.Result.ObjectResult; + + //need to check the return type and possibly cast if result is from an extension method found + if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod) + { + if (attempt.Result.ObjectResult != null) + { + if (attempt.Result.ObjectResult is XElement) + { + result = new DynamicXml((XElement) attempt.Result.ObjectResult); + } + if (attempt.Result.ObjectResult is IEnumerable) + { + result = ((IEnumerable) attempt.Result.ObjectResult).Select(x => new DynamicXml(x)); + } + if (attempt.Result.ObjectResult is IEnumerable) + { + result = ((IEnumerable)attempt.Result.ObjectResult).Select(x => new DynamicXml(x.BaseElement)); + } + } + } + return true; + } + + //this is the result of an extension method execution gone wrong so we return dynamic null + if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod + && attempt.Error != null && attempt.Error is TargetInvocationException) + { + result = new DynamicNull(); + return true; + } + + result = null; + return false; + } public override bool TryGetMember(GetMemberBinder binder, out object result) { @@ -181,20 +226,41 @@ namespace Umbraco.Core.Dynamics return new DynamicXml(this.BaseElement.XPathSelectElements(expression).FirstOrDefault()); } - public IEnumerator GetEnumerator() - { - return this.BaseElement.Elements().Select(e => new DynamicXml(e)).GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() + { + return this.BaseElement.Elements().GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return this.BaseElement.Elements().Select(e => new DynamicXml(e)).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + public int Count() { - return this.BaseElement.Elements().Count(); + return ((IEnumerable)this).Count(); } public bool Any() { - return this.BaseElement.Elements().Any(); + return ((IEnumerable)this).Any(); } + public IEnumerable Take(int count) + { + return ((IEnumerable)this).Take(count); + } + + public IEnumerable Skip(int count) + { + return ((IEnumerable)this).Skip(count); + } + public bool IsNull() { return false; @@ -514,7 +580,7 @@ namespace Umbraco.Core.Dynamics } public IEnumerable Ancestors(Func func) { - List ancestorList = new List(); + var ancestorList = new List(); var node = this.BaseElement; while (node != null) { @@ -661,5 +727,7 @@ namespace Umbraco.Core.Dynamics } } } + + } } diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs index 6dd8a1ad12..2fe86ddeec 100644 --- a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs +++ b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs @@ -44,7 +44,7 @@ namespace Umbraco.Core.Dynamics ); //add the extension methods defined in IEnumerable - candidates = candidates.Concat(typeof(IEnumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)); + candidates = candidates.Concat(typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)); //filter by name var methodsByName = candidates.Where(m => m.Name == name); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 1415d981ae..36b279ce81 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -65,6 +65,7 @@ + diff --git a/src/Umbraco.Tests/PublishedContent/DynamicPublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/DynamicPublishedContentTests.cs index 3e0d52feb1..e8bb815802 100644 --- a/src/Umbraco.Tests/PublishedContent/DynamicPublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/DynamicPublishedContentTests.cs @@ -77,6 +77,7 @@ namespace Umbraco.Tests.PublishedContent Assert.AreEqual("Hello world!" + 123 + false, asDynamic.DynamicDocumentMultiParam("Hello world!", 123, false)); Assert.AreEqual("Hello world!" + 123 + false, asDynamic.Children.DynamicDocumentListMultiParam("Hello world!", 123, false)); Assert.AreEqual("Hello world!" + 123 + false, asDynamic.Children.DynamicDocumentEnumerableMultiParam("Hello world!", 123, false)); + } [Test] diff --git a/src/Umbraco.Tests/PublishedContent/DynamicXmlTests.cs b/src/Umbraco.Tests/PublishedContent/DynamicXmlTests.cs index 479fcc5929..7c1a3598fb 100644 --- a/src/Umbraco.Tests/PublishedContent/DynamicXmlTests.cs +++ b/src/Umbraco.Tests/PublishedContent/DynamicXmlTests.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using Microsoft.CSharp.RuntimeBinder; using NUnit.Framework; using Umbraco.Core.Dynamics; +using System.Linq; namespace Umbraco.Tests.PublishedContent { @@ -10,6 +11,65 @@ namespace Umbraco.Tests.PublishedContent public class DynamicXmlTests { + [Test] + public void Custom_Extension_Method_Legacy() + { + var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; + var typedXml = new global::umbraco.MacroEngines.DynamicXml(xml); + dynamic dynamicXml = typedXml; + + //we haven't explicitly defined ElementAt so this will dynamically invoke this method + var element = dynamicXml.ElementAt(0); + + Assert.AreEqual("1057", Enumerable.First(element.BaseElement.Elements()).Attribute("id").Value); + } + + [Test] + public void Custom_Extension_Method() + { + var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; + var typedXml = new DynamicXml(xml); + + dynamic dynamicXml = typedXml; + + //we haven't explicitly defined ElementAt so this will dynamically invoke this method + var element = dynamicXml.ElementAt(0); + + Assert.AreEqual("1057", Enumerable.First(element.BaseElement.Elements()).Attribute("id").Value); + } + + [Test] + public void Take_Legacy() + { + var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; + var typedXml = new global::umbraco.MacroEngines.DynamicXml(xml); + dynamic dynamicXml = typedXml; + var typedTaken = typedXml.Take(1); + var dynamicTaken = dynamicXml.Take(1); + + Assert.AreEqual(1, typedTaken.Count()); + Assert.AreEqual(1, Enumerable.Count(dynamicTaken)); + + Assert.AreEqual("1057", typedTaken.ElementAt(0).BaseElement.Elements().First().Attribute("id").Value); + Assert.AreEqual("1057", Enumerable.First(Enumerable.ElementAt(dynamicTaken, 0).BaseElement.Elements()).Attribute("id").Value); + } + + [Test] + public void Take() + { + var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; + var typedXml = new DynamicXml(xml); + dynamic dynamicXml = typedXml; + var typedTaken = typedXml.Take(1); + var dynamicTaken = dynamicXml.Take(1); + + Assert.AreEqual(1, typedTaken.Count()); + Assert.AreEqual(1, Enumerable.Count(dynamicTaken)); + + Assert.AreEqual("1057", typedTaken.ElementAt(0).BaseElement.Elements().First().Attribute("id").Value); + Assert.AreEqual("1057", Enumerable.First(Enumerable.ElementAt(dynamicTaken, 0).BaseElement.Elements()).Attribute("id").Value); + } + [Test] public void Ensure_Legacy_Objects_Are_Returned() { diff --git a/src/Umbraco.Web/Models/DynamicPublishedContent.cs b/src/Umbraco.Web/Models/DynamicPublishedContent.cs index 107c20b70f..c41fd4b540 100644 --- a/src/Umbraco.Web/Models/DynamicPublishedContent.cs +++ b/src/Umbraco.Web/Models/DynamicPublishedContent.cs @@ -54,111 +54,47 @@ namespace Umbraco.Web.Models /// public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { - //TODO: We MUST cache the result here, it is very expensive to keep finding extension methods! - - try - { - //Property? - result = typeof(DynamicPublishedContent).InvokeMember(binder.Name, - System.Reflection.BindingFlags.Instance | - System.Reflection.BindingFlags.Public | - System.Reflection.BindingFlags.GetProperty, - null, - this, - args); - return true; - } - catch (MissingMethodException) - { - try - { - //Static or Instance Method? - result = typeof(DynamicPublishedContent).InvokeMember(binder.Name, - System.Reflection.BindingFlags.Instance | - System.Reflection.BindingFlags.Public | - System.Reflection.BindingFlags.Static | - System.Reflection.BindingFlags.InvokeMethod, - null, - this, - args); - return true; - } - catch (MissingMethodException) - { - try - { - result = ExecuteExtensionMethod(args, binder.Name); - return true; - } - catch (TargetInvocationException) - { - result = new DynamicNull(); - return true; - } - - catch - { - //TODO: LOg this! - - result = null; - return false; - } - - } - - - } - catch - { - result = null; - return false; - } - - } - - private object ExecuteExtensionMethod(object[] args, string name) - { - object result = null; - - var methodTypesToFind = new[] + var attempt = DynamicInstanceHelper.TryInvokeMember(this, binder, args, new[] { typeof(DynamicPublishedContent) - }; + }); - //find known extension methods that match the first type in the list - MethodInfo toExecute = null; - foreach (var t in methodTypesToFind) + if (attempt.Success) { - toExecute = ExtensionMethodFinder.FindExtensionMethod(t, args, name, false); - if (toExecute != null) - break; - } + result = attempt.Result.ObjectResult; - if (toExecute != null) - { - var genericArgs = (new[] { this }).Concat(args); - result = toExecute.Invoke(null, genericArgs.ToArray()); - } - else - { - throw new MissingMethodException(); - } - if (result != null) - { - if (result is IPublishedContent) - { - result = new DynamicPublishedContent((IPublishedContent)result); - } - if (result is IEnumerable) - { - result = new DynamicPublishedContentList((IEnumerable)result); + //need to check the return type and possibly cast if result is from an extension method found + if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod) + { + if (attempt.Result.ObjectResult != null) + { + if (attempt.Result.ObjectResult is IPublishedContent) + { + result = new DynamicPublishedContent((IPublishedContent)attempt.Result.ObjectResult); + } + if (attempt.Result.ObjectResult is IEnumerable) + { + result = new DynamicPublishedContentList((IEnumerable)attempt.Result.ObjectResult); + } + if (attempt.Result.ObjectResult is IEnumerable) + { + result = new DynamicPublishedContentList((IEnumerable)attempt.Result.ObjectResult); + } + } } - if (result is IEnumerable) - { - result = new DynamicPublishedContentList((IEnumerable)result); - } + return true; } - return result; + + //this is the result of an extension method execution gone wrong so we return dynamic null + if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod + && attempt.Error != null && attempt.Error is TargetInvocationException) + { + result = new DynamicNull(); + return true; + } + + result = null; + return false; } /// diff --git a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs index 7740a485fd..94d32dade3 100644 --- a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs +++ b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs @@ -46,9 +46,6 @@ namespace Umbraco.Web.Models } public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { - - //TODO: We MUST cache the result here, it is very expensive to keep finding extension methods and processing this stuff! - //TODO: Nowhere here are we checking if args is the correct length! //NOTE: For many of these we could actually leave them out since we are executing custom extension methods and because @@ -196,11 +193,6 @@ namespace Umbraco.Web.Models { result = new DynamicPublishedContentList(this.Items.Except(firstArg as IEnumerable, new DynamicPublishedContentIdEqualityComparer())); return true; - } - if ((firstArg as DynamicPublishedContentList) != null) - { - result = new DynamicPublishedContentList(this.Items.Except((firstArg as DynamicPublishedContentList).Items, new DynamicPublishedContentIdEqualityComparer())); - return true; } } if (name == "Intersect") @@ -209,11 +201,6 @@ namespace Umbraco.Web.Models { result = new DynamicPublishedContentList(this.Items.Intersect(firstArg as IEnumerable, new DynamicPublishedContentIdEqualityComparer())); return true; - } - if ((firstArg as DynamicPublishedContentList) != null) - { - result = new DynamicPublishedContentList(this.Items.Intersect((firstArg as DynamicPublishedContentList).Items, new DynamicPublishedContentIdEqualityComparer())); - return true; } } if (name == "Distinct") @@ -226,65 +213,50 @@ namespace Umbraco.Web.Models result = Pluck(args); return true; } - try - { - //Property? - result = Items.GetType().InvokeMember(binder.Name, - System.Reflection.BindingFlags.Instance | - System.Reflection.BindingFlags.Public | - System.Reflection.BindingFlags.GetProperty, - null, - Items, - args); - return true; - } - catch (MissingMethodException) - { - try - { - //Static or Instance Method? - result = Items.GetType().InvokeMember(binder.Name, - System.Reflection.BindingFlags.Instance | - System.Reflection.BindingFlags.Public | - System.Reflection.BindingFlags.Static | - System.Reflection.BindingFlags.InvokeMethod, - null, - Items, - args); - return true; - } - catch (MissingMethodException) - { - try - { - result = ExecuteExtensionMethod(args, name); - return true; - } - catch (TargetInvocationException) - { - //We do this to enable error checking of Razor Syntax when a method e.g. ElementAt(2) is used. - //When the Script is tested, there's no Children which means ElementAt(2) is invalid (IndexOutOfRange) - //Instead, we are going to return DynamicNull; - result = new DynamicNull(); - return true; - } + //ok, now lets try to match by member, property, extensino method + var attempt = DynamicInstanceHelper.TryInvokeMember(this, binder, args, new[] + { + typeof (IEnumerable), + typeof (DynamicPublishedContentList) + }); - catch - { - result = null; - return false; - } + if (attempt.Success) + { + result = attempt.Result.ObjectResult; - } + //need to check the return type and possibly cast if result is from an extension method found + if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod) + { + if (attempt.Result.ObjectResult != null) + { + if (attempt.Result.ObjectResult is IPublishedContent) + { + result = new DynamicPublishedContent((IPublishedContent)attempt.Result.ObjectResult); + } + if (attempt.Result.ObjectResult is IEnumerable) + { + result = new DynamicPublishedContentList((IEnumerable)attempt.Result.ObjectResult); + } + if (attempt.Result.ObjectResult is IEnumerable) + { + result = new DynamicPublishedContentList((IEnumerable)attempt.Result.ObjectResult); + } + } + } + return true; + } + //this is the result of an extension method execution gone wrong so we return dynamic null + if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod + && attempt.Error != null && attempt.Error is TargetInvocationException) + { + result = new DynamicNull(); + return true; + } - } - catch - { - result = null; - return false; - } + result = null; + return false; } private T Aggregate(IEnumerable data, string name) where T : struct @@ -425,67 +397,7 @@ namespace Umbraco.Web.Models } return result; } - - private object ExecuteExtensionMethod(object[] args, string name) - { - object result = null; - - var methodTypesToFind = new[] - { - typeof(IEnumerable), - typeof(DynamicPublishedContentList) - }; - - //find known extension methods that match the first type in the list - MethodInfo toExecute = null; - foreach(var t in methodTypesToFind) - { - toExecute = ExtensionMethodFinder.FindExtensionMethod(t, args, name, false); - if (toExecute != null) - break; - } - - if (toExecute != null) - { - if (toExecute.GetParameters().First().ParameterType == typeof(DynamicPublishedContentList)) - { - var genericArgs = (new[] { this }).Concat(args); - result = toExecute.Invoke(null, genericArgs.ToArray()); - } - else if (TypeHelper.IsTypeAssignableFrom(toExecute.GetParameters().First().ParameterType)) - { - //if it is IQueryable, we'll need to cast Items AsQueryable - var genericArgs = (new[] { Items.AsQueryable() }).Concat(args); - result = toExecute.Invoke(null, genericArgs.ToArray()); - } - else - { - var genericArgs = (new[] { Items }).Concat(args); - result = toExecute.Invoke(null, genericArgs.ToArray()); - } - } - else - { - throw new MissingMethodException(); - } - if (result != null) - { - if (result is IPublishedContent) - { - result = new DynamicPublishedContent((IPublishedContent)result); - } - if (result is IEnumerable) - { - result = new DynamicPublishedContentList((IEnumerable)result); - } - if (result is IEnumerable) - { - result = new DynamicPublishedContentList((IEnumerable)result); - } - } - return result; - } - + public T Single(string predicate, params object[] values) { return predicate.IsNullOrWhiteSpace() diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicXml.cs b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicXml.cs index fa45127c52..1eff680b30 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicXml.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicXml.cs @@ -11,8 +11,8 @@ using System.Web; namespace umbraco.MacroEngines { - [Obsolete("This class has been superceded by Umbraco.Core.Dynamics.DynamicXml")] - public class DynamicXml : DynamicObject, IEnumerable + [Obsolete("This class has been superceded by Umbraco.Core.Dynamics.DynamicXml")] + public class DynamicXml : DynamicObject, IEnumerable, IEnumerable { private readonly Umbraco.Core.Dynamics.DynamicXml _inner; @@ -92,10 +92,21 @@ namespace umbraco.MacroEngines return new DynamicXml(_inner.BaseElement.XPathSelectElements(expression).FirstOrDefault()); } - public IEnumerator GetEnumerator() - { + IEnumerator IEnumerable.GetEnumerator() + { + return this.BaseElement.Elements().GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { return this.BaseElement.Elements().Select(e => new DynamicXml(e)).GetEnumerator(); - } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + public int Count() { return _inner.Count(); @@ -115,6 +126,16 @@ namespace umbraco.MacroEngines return _inner.HasValue(); } + public IEnumerable Take(int count) + { + return _inner.Take(count).Select(x => new DynamicXml(x.BaseElement)); + } + + public IEnumerable Skip(int count) + { + return _inner.Skip(count).Select(x => new DynamicXml(x.BaseElement)); + } + public bool IsFirst() { return _inner.IsFirst();