//Copyright (C) Microsoft Corporation. All rights reserved. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Diagnostics; using Umbraco.Core.Dynamics; using Umbraco.Web.Models; namespace Umbraco.Web.Dynamics { //TODO: Much of this can move to Umbraco.Core.Dynamics - but somehow need to remove all the hard coded references to things like // dynamicnull, etc... //NOTE: The OrderBy stuff here seems to be a bit hacked with hard references to umbraco node objects so don't think it can be // re-used which is why we have the OrderBy stuff that hasn't been hacked in teh Umbraco.Core.Dynamics internal static class DynamicQueryable { public static IQueryable Where(this IQueryable source, string predicate, params object[] values) { return (IQueryable)Where((IQueryable)source, predicate, values); } public static IQueryable Where(this IQueryable source, string predicate, params object[] values) { if (source == null) throw new ArgumentNullException("source"); if (predicate == null) throw new ArgumentNullException("predicate"); LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(bool), predicate, true, values); if (lambda.Parameters.Count > 0 && lambda.Parameters[0].Type == typeof(T)) { //source list is DynamicNode and the lambda returns a Func IQueryable typedSource = source as IQueryable; var compiledFunc = lambda.Compile(); Func func = null; Func boolFunc = null; if (compiledFunc is Func) { func = (Func)compiledFunc; } if (compiledFunc is Func) { boolFunc = (Func)compiledFunc; } return typedSource.Where(delegate(T node) { object value = -1; //value = func(node); //I can't figure out why this is double func<>'d try { if (func != null) { var firstFuncResult = func(node); if (firstFuncResult is Func) { value = (firstFuncResult as Func)(node); } if (firstFuncResult is Func) { value = (firstFuncResult as Func)(node); } if (firstFuncResult is bool) { return (bool)firstFuncResult; } if (value is bool) { return (bool)value; } } if (boolFunc != null) { return boolFunc(node); } return false; } catch (Exception ex) { Trace.WriteLine(ex.Message); return false; } }).AsQueryable(); } else { return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "Where", new Type[] { source.ElementType }, source.Expression, Expression.Quote(lambda))); } } public static IQueryable Select(this IQueryable source, string selector, params object[] values) { if (source == null) throw new ArgumentNullException("source"); if (selector == null) throw new ArgumentNullException("selector"); LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(object), selector, false, values); if (lambda.Parameters.Count > 0 && lambda.Parameters[0].Type == typeof(T)) { //source list is DynamicNode and the lambda returns a Func IQueryable typedSource = source as IQueryable; var compiledFunc = lambda.Compile(); Func func = null; if (compiledFunc is Func) { func = (Func)compiledFunc; } return typedSource.Select(delegate(T node) { object value = null; value = func(node); if (value is Func) { var innerValue = (value as Func)(node); return innerValue; } return value; }).AsQueryable(); } else { return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "Select", new Type[] { source.ElementType, lambda.Body.Type }, source.Expression, Expression.Quote(lambda))); } } public static IQueryable OrderBy(this IQueryable source, string ordering, Func getDynamicListTypeCallback, params object[] values) { return (IQueryable)OrderBy((IQueryable)source, ordering, getDynamicListTypeCallback, values); } public static IQueryable OrderBy(this IQueryable source, string ordering, Func getDynamicListTypeCallback, params object[] values) { if (source == null) throw new ArgumentNullException("source"); if (ordering == null) throw new ArgumentNullException("ordering"); IQueryable typedSource = source as IQueryable; if (!ordering.Contains(",")) { bool descending = false; if (ordering.IndexOf(" descending", StringComparison.CurrentCultureIgnoreCase) >= 0) { ordering = ordering.Replace(" descending", ""); descending = true; } if (ordering.IndexOf(" desc", StringComparison.CurrentCultureIgnoreCase) >= 0) { ordering = ordering.Replace(" desc", ""); descending = true; } LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(object), ordering, false, values); if (lambda.Parameters.Count > 0 && lambda.Parameters[0].Type == typeof(T)) { //source list is DynamicNode and the lambda returns a Func Func func = (Func)lambda.Compile(); //get the values out var query = typedSource.ToList().ConvertAll(item => new { node = item, key = EvaluateDynamicNodeFunc(item, func) }); if (query.Count == 0) { return source; } var types = from i in query group i by i.key.GetType() into g where g.Key != typeof(DynamicNull) orderby g.Count() descending select new { g, Instances = g.Count() }; var dominantType = types.First().g.Key; // NH - add culture dependencies StringComparer comp = StringComparer.Create(CultureInfo.CurrentCulture, true); if (!descending) { // if the dominant type is a string we'll ensure that strings are sorted based on culture settings on node if (dominantType.FullName == "System.String") return query.OrderBy(item => item.key.ToString(), comp).Select(item => item.node).AsQueryable(); else return query.OrderBy(item => GetObjectAsTypeOrDefault(item.key, dominantType)).Select(item => item.node).AsQueryable(); } else { if (dominantType.FullName == "System.String") return query.OrderByDescending(item => item.key.ToString(), comp).Select(item => item.node).AsQueryable(); else return query.OrderByDescending(item => GetObjectAsTypeOrDefault(item.key, dominantType)).Select(item => item.node).AsQueryable(); } } } bool isDynamicNodeList = false; if (typedSource != null) { isDynamicNodeList = true; } ParameterExpression[] parameters = new ParameterExpression[] { Expression.Parameter(source.ElementType, "") }; var parser = new ExpressionParser(parameters, ordering, values, false); IEnumerable orderings = parser.ParseOrdering(); Expression queryExpr = source.Expression; string methodAsc = "OrderBy"; string methodDesc = "OrderByDescending"; foreach (DynamicOrdering o in orderings) { if (!isDynamicNodeList) { queryExpr = Expression.Call( typeof(Queryable), o.Ascending ? methodAsc : methodDesc, new Type[] { source.ElementType, o.Selector.Type }, queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters))); } else { //reroute each stacked Expression.Call into our own methods that know how to deal //with DynamicNode queryExpr = Expression.Call( getDynamicListTypeCallback(), o.Ascending ? methodAsc : methodDesc, null, queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters)) ); } methodAsc = "ThenBy"; methodDesc = "ThenByDescending"; } if (isDynamicNodeList) { return typedSource.Provider.CreateQuery(queryExpr); } return source.Provider.CreateQuery(queryExpr); } private static object GetObjectAsTypeOrDefault(object value, Type type) { if (type.IsAssignableFrom(value.GetType())) { return (object)Convert.ChangeType(value, type); } else { if (type.IsValueType) { return Activator.CreateInstance(type); } return null; } } private static object EvaluateDynamicNodeFunc(T publishedContent, Func func) { object value = -1; var firstFuncResult = func(publishedContent); if (firstFuncResult is Func) { value = (firstFuncResult as Func)(publishedContent); } if (firstFuncResult.GetType().IsValueType || firstFuncResult is string) { value = firstFuncResult; } return value; } public static IQueryable Take(this IQueryable source, int count) { if (source == null) throw new ArgumentNullException("source"); return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "Take", new Type[] { source.ElementType }, source.Expression, Expression.Constant(count))); } public static IQueryable Skip(this IQueryable source, int count) { if (source == null) throw new ArgumentNullException("source"); return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "Skip", new Type[] { source.ElementType }, source.Expression, Expression.Constant(count))); } public static IQueryable GroupBy(this IQueryable source, string keySelector, string elementSelector, params object[] values) { if (source == null) throw new ArgumentNullException("source"); if (keySelector == null) throw new ArgumentNullException("keySelector"); if (elementSelector == null) throw new ArgumentNullException("elementSelector"); LambdaExpression keyLambda = DynamicExpression.ParseLambda(source.ElementType, null, keySelector, true, values); LambdaExpression elementLambda = DynamicExpression.ParseLambda(source.ElementType, null, elementSelector, true, values); return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "GroupBy", new Type[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type }, source.Expression, Expression.Quote(keyLambda), Expression.Quote(elementLambda))); } public static bool Any(this IQueryable source) { if (source == null) throw new ArgumentNullException("source"); return (bool)source.Provider.Execute( Expression.Call( typeof(Queryable), "Any", new Type[] { source.ElementType }, source.Expression)); } public static int Count(this IQueryable source) { if (source == null) throw new ArgumentNullException("source"); return (int)source.Provider.Execute( Expression.Call( typeof(Queryable), "Count", new Type[] { source.ElementType }, source.Expression)); } } }