namespace System.Web.UI.WebControls { using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Web; using System.Web.Resources; using System.Web.UI; using System.Web.UI.WebControls; internal static class QueryableDataSourceHelper { // This regular expression verifies that parameter names are set to valid identifiers. This validation // needs to match the parser's identifier validation as done in the default block of NextToken(). private static readonly string IdentifierPattern = @"^\s*[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}_]" + // first character @"[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\p{Nd}\p{Pc}\p{Mn}\p{Mc}\p{Cf}_]*"; // remaining characters private static readonly Regex IdentifierRegex = new Regex(IdentifierPattern + @"\s*$"); private static readonly Regex AutoGenerateOrderByRegex = new Regex(IdentifierPattern + @"(\s+(asc|ascending|desc|descending))?\s*$", RegexOptions.IgnoreCase); // order operators internal static IQueryable AsQueryable(object o) { IQueryable oQueryable = o as IQueryable; if (oQueryable != null) { return oQueryable; } // Wrap strings in IEnumerable instead of treating as IEnumerable. string oString = o as string; if (oString != null) { return Queryable.AsQueryable(new string[] { oString }); } IEnumerable oEnumerable = o as IEnumerable; if (oEnumerable != null) { // IEnumerable can be directly converted to an IQueryable. Type genericType = FindGenericEnumerableType(o.GetType()); if (genericType != null) { // The non-generic Queryable.AsQueryable gets called for array types, executing // the FindGenericType logic again. Might want to investigate way to avoid this. return Queryable.AsQueryable(oEnumerable); } // Wrap non-generic IEnumerables in IEnumerable. List genericList = new List(); foreach (object item in oEnumerable) { genericList.Add(item); } return Queryable.AsQueryable(genericList); } // Wrap non-IEnumerable types in IEnumerable. Type listType = typeof(List<>).MakeGenericType(o.GetType()); IList list = (IList)DataSourceHelper.CreateObjectInstance(listType); list.Add(o); return Queryable.AsQueryable(list); } public static IList ToList(this IQueryable query, Type dataObjectType) { MethodInfo toListMethod = typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(dataObjectType); return (IList)toListMethod.Invoke(null, new object[] { query }); } public static bool EnumerableContentEquals(IEnumerable enumerableA, IEnumerable enumerableB) { IEnumerator enumeratorA = enumerableA.GetEnumerator(); IEnumerator enumeratorB = enumerableB.GetEnumerator(); while (enumeratorA.MoveNext()) { if (!enumeratorB.MoveNext()) return false; object itemA = enumeratorA.Current; object itemB = enumeratorB.Current; if (itemA == null) { if (itemB != null) return false; } else if (!itemA.Equals(itemB)) return false; } if (enumeratorB.MoveNext()) return false; return true; } public static Type FindGenericEnumerableType(Type type) { // Logic taken from Queryable.AsQueryable which accounts for Array types which are not // generic but implement the generic IEnumerable interface. while ((type != null) && (type != typeof(object)) && (type != typeof(string))) { if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IEnumerable<>))) { return type; } foreach (Type interfaceType in type.GetInterfaces()) { Type genericInterface = FindGenericEnumerableType(interfaceType); if (genericInterface != null) { return genericInterface; } } type = type.BaseType; } return null; } internal static IDictionary ToEscapedParameterKeys(this ParameterCollection parameters, HttpContext context, Control control) { if (parameters != null) { return parameters.GetValues(context, control).ToEscapedParameterKeys(control); } return null; } internal static IDictionary ToEscapedParameterKeys(this IDictionary parameters, Control owner) { Dictionary escapedParameters = new Dictionary(parameters.Count, StringComparer.OrdinalIgnoreCase); foreach (DictionaryEntry de in parameters) { string key = (string)de.Key; if (String.IsNullOrEmpty(key)) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.LinqDataSourceView_ParametersMustBeNamed, owner.ID)); } ValidateParameterName(key, owner); escapedParameters.Add('@' + key, de.Value); } return escapedParameters; } internal static IDictionary ToEscapedParameterKeys(this IDictionary parameters, Control owner) { Dictionary escapedParameters = new Dictionary(parameters.Count, StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair parameter in parameters) { string key = parameter.Key; if (String.IsNullOrEmpty(key)) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.LinqDataSourceView_ParametersMustBeNamed, owner.ID)); } ValidateParameterName(key, owner); escapedParameters.Add('@' + key, parameter.Value); } return escapedParameters; } internal static IQueryable CreateOrderByExpression(IOrderedDictionary parameters, IQueryable source, IDynamicQueryable queryable) { if (parameters != null && parameters.Count > 0) { //extract parameter values //extract the order by expression and apply it to the queryable string orderByExpression = GetOrderByClause(parameters.ToDictionary()); if (!String.IsNullOrEmpty(orderByExpression)) { return queryable.OrderBy(source, orderByExpression); } } return source; } internal static IQueryable CreateWhereExpression(IDictionary parameters, IQueryable source, IDynamicQueryable queryable) { if (parameters != null && parameters.Count > 0) { //extract the where clause WhereClause clause = GetWhereClause(parameters); if (!String.IsNullOrEmpty(clause.Expression)) { //transform the current query with the where clause return queryable.Where(source, clause.Expression, clause.Parameters); } } return source; } private static WhereClause GetWhereClause(IDictionary whereParameters) { Debug.Assert((whereParameters != null) && (whereParameters.Count > 0)); WhereClause whereClause = new WhereClause(); whereClause.Parameters = new Dictionary(whereParameters.Count); StringBuilder where = new StringBuilder(); int index = 0; foreach (KeyValuePair parameter in whereParameters) { string key = parameter.Key; string value = (parameter.Value == null) ? null : parameter.Value.ToString(); // exclude null and empty values. if (!(String.IsNullOrEmpty(key) || String.IsNullOrEmpty(value))) { string newKey = "@p" + index++; if (where.Length > 0) { where.Append(" AND "); } where.Append(key); where.Append(" == "); where.Append(newKey); whereClause.Parameters.Add(newKey, parameter.Value); } } whereClause.Expression = where.ToString(); return whereClause; } private static string GetOrderByClause(IDictionary orderByParameters) { Debug.Assert((orderByParameters != null) && (orderByParameters.Count > 0)); StringBuilder orderBy = new StringBuilder(); foreach (KeyValuePair parameter in orderByParameters) { string value = (string)parameter.Value; // exclude null and empty values. if (!String.IsNullOrEmpty(value)) { string name = parameter.Key; //validate parameter name ValidateOrderByParameter(name, value); if (orderBy.Length > 0) { orderBy.Append(", "); } orderBy.Append(value); } } return orderBy.ToString(); } internal static void ValidateOrderByParameter(string name, string value) { if (!AutoGenerateOrderByRegex.IsMatch(value)) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.LinqDataSourceView_InvalidOrderByFieldName, value, name)); } } internal static void ValidateParameterName(string name, Control owner) { if (!IdentifierRegex.IsMatch(name)) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, AtlasWeb.LinqDataSourceView_InvalidParameterName, name, owner.ID)); } } private class WhereClause { public string Expression { get; set; } public IDictionary Parameters { get; set; } } } }