/// <summary> /// Initialize filters /// </summary> private static void InitializeFilters() { // Try to init extended filters foreach (var ext in AppDomain.CurrentDomain.GetAssemblies() .Where(a => !a.IsDynamic) .SelectMany(a => { try { return(a.ExportedTypes); } catch { return(Type.EmptyTypes); } }) .Where(t => typeof(IQueryFilterExtension).IsAssignableFrom(t) && !t.IsAbstract) .Select(t => Activator.CreateInstance(t) as IQueryFilterExtension)) { QueryFilterExtensions.AddExtendedFilter(ext); } }
/// <summary> /// Extract the path /// </summary> /// <returns>The path.</returns> /// <param name="access">Access.</param> /// <param name="fromUnary">Extract the path from a unuary or binary expression</param> /// <param name="fromOperand">Indicates the extraction should occur from an operand and not the operator</param> protected String ExtractPath(Expression access, bool fromUnary, bool fromOperand = false) { access = this.StripConvert(access); if (access.NodeType == ExpressionType.MemberAccess) { MemberExpression memberExpr = access as MemberExpression; String path = this.ExtractPath(memberExpr.Expression, fromUnary, fromOperand); // get the chain if required if (memberExpr.Expression.Type.IsGenericType && memberExpr.Expression.Type.GetGenericTypeDefinition() == typeof(Nullable <>)) { return(path); } // XML property? var memberInfo = memberExpr.Expression.Type.GetRuntimeProperty(memberExpr.Member.Name + "Xml") ?? memberExpr.Member; // Member information is declread on interface Type mapType = null; if (memberInfo.DeclaringType.IsInterface && this.m_interfaceHints.TryGetValue(memberInfo.DeclaringType, out mapType)) { memberInfo = mapType.GetRuntimeProperty(memberInfo.Name) ?? memberInfo; } // Is this a delay load? var serializationReferenceAttribute = memberExpr.Member.GetCustomAttribute <SerializationReferenceAttribute>(); var queryParameterAttribute = memberExpr.Member.GetCustomAttribute <QueryParameterAttribute>(); var xmlIgnoreAttribute = memberExpr.Member.GetCustomAttribute <XmlIgnoreAttribute>(); if (xmlIgnoreAttribute != null && serializationReferenceAttribute != null && !String.IsNullOrEmpty(serializationReferenceAttribute.RedirectProperty)) { memberInfo = memberExpr.Expression.Type.GetRuntimeProperty(serializationReferenceAttribute.RedirectProperty); } // TODO: Delay and bound properties!! var memberXattribute = memberInfo.GetCustomAttributes <XmlElementAttribute>().FirstOrDefault(); if (memberXattribute == null && queryParameterAttribute != null) { memberXattribute = new XmlElementAttribute(queryParameterAttribute.ParameterName); // We don't serialize but it does exist } else if (memberExpr.Expression is ConstantExpression) { return((memberExpr.Expression as ConstantExpression).Value.ToString()); } else if (memberXattribute == null) { if (memberExpr.Expression.Type.StripNullable() == typeof(DateTimeOffset) && memberExpr.Member.Name == "DateTime") { return(path); } throw new InvalidOperationException($"The path {access} cannot be translated, ensure the property is XML navigable or has a QueryParameter attribute"); // TODO: When this occurs? } // Return path if (String.IsNullOrEmpty(path)) { return(memberXattribute.ElementName); } else if (memberXattribute.ElementName == "id") // ID can be ignored { return(path); } else { return(String.Format("{0}.{1}", path, memberXattribute.ElementName)); } } else if (access.NodeType == ExpressionType.Call) { //CallExpression callExpr = access as MemberExpression; MethodCallExpression callExpr = access as MethodCallExpression; if (callExpr.Method.Name == "Where" || fromUnary && (callExpr.Method.Name == "Any")) { String path = this.ExtractPath(callExpr.Arguments[0], false, fromOperand); // get the chain if required var guardExpression = callExpr.Arguments[1] as LambdaExpression; // Where should be a guard so we just grab the unary equals only! var binaryExpression = guardExpression.Body as BinaryExpression; if (binaryExpression == null) { throw new InvalidOperationException("Cannot translate non-binary expression guards"); } // Is the expression the guard? String guardString = this.BuildGuardExpression(binaryExpression); return(String.Format("{0}[{1}]", path, guardString)); } else if (callExpr.Method.Name == "First" || callExpr.Method.Name == "FirstOrDefault") { String path = this.ExtractPath(callExpr.Arguments[0], false, fromOperand); // get the chain if required return(path); } else { var extendedFilter = QueryFilterExtensions.GetExtendedFilterByMethod(callExpr.Method); if (extendedFilter != null) { return(this.ExtractPath(callExpr.Arguments[0], false, fromOperand)); // get the chain if required } else if (!s_reservedNames.Contains(callExpr.Method.Name)) { throw new InvalidOperationException($"Can't find extended method handler for {callExpr.Method.Name}"); } } } else if (access.NodeType == ExpressionType.TypeAs) { UnaryExpression ua = (UnaryExpression)access; return(String.Format("{0}@{1}", this.ExtractPath(ua.Operand, false, fromOperand), ua.Type.GetCustomAttribute <XmlTypeAttribute>().TypeName)); } else if (access.NodeType == ExpressionType.Parameter && fromOperand) { return("$_"); } else if (access.NodeType == ExpressionType.Coalesce) { BinaryExpression ba = (BinaryExpression)access; return(this.ExtractPath(ba.Left, fromUnary, fromOperand) ?? this.ExtractPath(ba.Right, fromUnary, fromOperand)); } return(null); }
/// <summary> /// Visit a binary expression which is in the form of A(operator)B /// </summary> /// <returns>The binary.</returns> /// <param name="node">Node.</param> protected override Expression VisitBinary(BinaryExpression node) { var left = this.Visit(node.Left); var right = this.Visit(node.Right); String parmName = this.ExtractPath(node.Left, false); Object parmValue = this.ExtractValue(node.Right); // Not able to map if ((node.Left as MethodCallExpression)?.Method.Name == "Any" && (node.Left as MethodCallExpression)?.Arguments.Count == 1) { // Special exists method call - i.e. HAS X var mci = (node.Left as MethodCallExpression); parmName = this.ExtractPath(mci.Arguments[0], false); if (node.Right is ConstantExpression cci) { if (node.NodeType == ExpressionType.NotEqual) { this.AddCondition(parmName, true.Equals(cci.Value) ? "null" : "!null"); } else if (node.NodeType == ExpressionType.Equal) { this.AddCondition(parmName, true.Equals(cci.Value) ? "!null" : "null"); } else { throw new InvalidOperationException($"Cannot determine how to convert ANY() function '{node}'"); } } else { this.AddCondition(parmName, "null"); } } else if (!String.IsNullOrEmpty(parmName)) { Object fParmValue = this.PrepareValue(parmValue, false); if (parmValue is DateTime) { fParmValue = ((DateTime)parmValue).ToString("o"); } else if (parmValue is DateTimeOffset) { fParmValue = ((DateTimeOffset)parmValue).ToString("o"); } else if (parmValue == null) { fParmValue = "null"; } // Node type switch (node.NodeType) { case ExpressionType.GreaterThan: fParmValue = ">" + fParmValue; break; case ExpressionType.GreaterThanOrEqual: fParmValue = ">=" + fParmValue; break; case ExpressionType.LessThan: fParmValue = "<" + fParmValue; break; case ExpressionType.LessThanOrEqual: fParmValue = "<=" + fParmValue; break; case ExpressionType.NotEqual: fParmValue = "!" + fParmValue; break; } // Is this an extended method? if (node.Left is MethodCallExpression mce) { if (left != null) { return(node); } else // we have to add it as it hasn't alread been added { var extendedFn = QueryFilterExtensions.GetExtendedFilterByMethod(mce.Method); if (extendedFn != null) { var callValue = $":({extendedFn.Name}"; if (mce.Arguments.Count > 1) { callValue += $"|{String.Join(",", mce.Arguments.Skip(1).Select(o => this.PrepareValue(this.ExtractValue(o), true)))}"; } callValue += ")"; fParmValue = callValue + fParmValue; } } } this.AddCondition(parmName, fParmValue); } return(node); }
/// <summary> /// Visit method call optionally negating any values parsed /// </summary> private Expression VisitMethodCall(MethodCallExpression node, bool negate) { switch (node.Method.Name) { case "Contains": { if (node.Object == null && node.Method.DeclaringType == typeof(Enumerable)) { return(this.ParseArrayContains(node, negate)); } else { var parmName = this.ExtractPath(node.Object, false); object parmValue = this.ExtractValue(node.Arguments[0]); this.AddCondition(parmName, (negate ? "!" : "~") + parmValue.ToString()); return(null); } } case "StartsWith": { var parmName = this.ExtractPath(node.Object, false); object parmValue = this.ExtractValue(node.Arguments[0]); this.AddCondition(parmName, (negate ? "!" : "^") + parmValue.ToString()); return(null); } case "Any": { var parmName = this.ExtractPath(node.Arguments[0], false); // Process lambda var result = new List <KeyValuePair <string, object> >(); var subQueryExpressionVisitor = new HttpQueryExpressionVisitor(result, node.Arguments[0].Type); if (node.Arguments.Count == 2) { subQueryExpressionVisitor.Visit(node.Arguments[1]); // Result foreach (var itm in result) { this.AddCondition(String.Format("{0}.{1}", parmName, itm.Key), negate ? $"!{itm.Value}" : itm.Value); } return(null); } else { return(null); } } default: // extended fn? { var extendedFn = QueryFilterExtensions.GetExtendedFilterByMethod(node.Method); if (extendedFn == null) { throw new MissingMemberException($"Cannot find extension method {node.Method}"); } if (extendedFn.ExtensionMethod.ReturnType == typeof(bool)) { var parmName = this.ExtractPath(node.Arguments[0], false); if (parmName == null && typeof(IdentifiedData).IsAssignableFrom(node.Arguments[0].Type)) { parmName = "id"; } else if (parmName == null) { throw new InvalidOperationException($"Cannot determine how to map {extendedFn.Name}"); } var callValue = $"{(negate ? "!" : "")}:({extendedFn.Name}"; if (node.Arguments.Count > 1) { callValue += $"|{String.Join(",", node.Arguments.Skip(1).Select(o => this.PrepareValue(this.ExtractValue(o), true)))}"; } callValue += ")"; this.AddCondition(parmName, callValue); return(node); } return(null); } } }