public void ParseQueryOptionsShouldHandleOperationCustomShortCodes() { var validOptions = new DynamicQueryOptions { Filters = new List <Filter> { new Filter { Value = "123", PropertyName = "name", Operator = FilterOperation.Equals }, new Filter { Value = "21", PropertyName = "age", Operator = FilterOperation.GreaterThanOrEqual } } }; var customOpShortCodes = new CustomOpCodes { { "fizz", FilterOperation.Equals }, { "buzz", FilterOperation.GreaterThanOrEqual } }; DynamicQueryOptions result = ExpressionBuilder.ParseQueryOptions("o=fizz&p=name&v=123&o=buzz&p=age&v=21", opShortCodes: customOpShortCodes); Assert.True(AreObjectPropertiesMatching(validOptions, result)); }
/// <summary> /// Populates an Instance of DynamicQueryOptions from parsed query string values. /// </summary> /// <param name="dynamicQueryOptions">DynamicQueryOptions ref to populate to.</param> /// <param name="operations">Operations array.</param> /// <param name="parameterNames">ParameterNames array.</param> /// <param name="parameterValues">ParameterValues array.</param> /// <param name="sortOptions">SortOptions array.</param> /// <param name="offsetOptions">Offset array.</param> /// <param name="countOptions">Count array.</param> /// <param name="opShortCodes">CustomOpCodes instance.</param> internal static void PopulateDynamicQueryOptions( DynamicQueryOptions dynamicQueryOptions, string[] operations, string[] parameterNames, string[] parameterValues, string[] sortOptions, string[] offsetOptions, string[] countOptions, CustomOpCodes opShortCodes = null, DynamicQueryOptions memberQueryOptions = null) { if (dynamicQueryOptions == null) { throw new DynamicQueryException("DynamicQueryOptions should not be null"); } // Check the counts for every operation, since they work in tuples they should be the same. if (AreCountsMatching(operations, parameterNames, parameterValues)) { for (int i = 0; i < operations.Length; i++) { FilterOperation foundOperation = default(FilterOperation); // Check if we support this operation. if (Enum.TryParse(operations[i], true, out FilterOperation parsedOperation)) { foundOperation = parsedOperation; } else if (opShortCodes != null && opShortCodes.Count > 0 && opShortCodes.TryGetValue(operations[i], out FilterOperation shortCodeOperation)) // Whoop maybe its a short code ? { foundOperation = shortCodeOperation; } else { throw new OperationNotSupportedException($"Invalid operation {operations[i]}"); } string[] splittedParameterName = parameterNames[i].Split(PARAMETER_OPTION_DELIMITER); bool isCaseSensitive = false; if (splittedParameterName.Length > 1) { if (splittedParameterName[1].ToLower() == CASE_SENSITIVITY_PARAMETER_OPTION) { isCaseSensitive = true; } else { throw new InvalidDynamicQueryException($"Invalid extra option provided for filter property {splittedParameterName[0]}. Received value was {splittedParameterName[1]}"); } } var composedFilter = new Filter { Operator = foundOperation, PropertyName = splittedParameterName[0], CaseSensitive = isCaseSensitive }; if (foundOperation >= FilterOperation.Any) { composedFilter.Value = memberQueryOptions; } else { composedFilter.Value = parameterValues[i]; } dynamicQueryOptions.Filters.Add(composedFilter); } } else { throw new QueryTripletsMismatchException("Invalid query structure. Operation, parameter name and value triplets are not matching."); } if (sortOptions != null && sortOptions.Length >= 1) { foreach (string sortOption in sortOptions) { if (!string.IsNullOrEmpty(sortOption)) { // Split the property name to sort and the direction. string[] splittedParam = sortOption.Split(PARAMETER_OPTION_DELIMITER); bool isCaseSensitive = false; SortingDirection direction = SortingDirection.Asc; if (splittedParam.Length == 2) { // If we get an array of 2 we have a sorting direction, try to apply it. if (!Enum.TryParse(splittedParam[1], true, out direction)) { throw new InvalidDynamicQueryException("Invalid sorting direction"); } } else if (splittedParam.Length == 3) { if (splittedParam[2].ToLower() == CASE_SENSITIVITY_PARAMETER_OPTION) { isCaseSensitive = true; } else { throw new InvalidDynamicQueryException($"Invalid extra option provided for sort property {splittedParam[0]}. Received value was {splittedParam[2]}"); } } else if (splittedParam.Length > 3) // If we get more than 3 results in the array, url must be wrong. { throw new InvalidDynamicQueryException("Invalid query structure. SortOption is misformed"); } // Create the sorting option. dynamicQueryOptions.SortOptions.Add(new SortOption { SortingDirection = direction, PropertyName = splittedParam[0], CaseSensitive = isCaseSensitive }); } } } if (offsetOptions != null && countOptions != null && countOptions.Length > 0 && offsetOptions.Length > 0 && offsetOptions.Length == countOptions.Length) { if (int.TryParse(countOptions[0], out int countValue) && // We only care about the first values. int.TryParse(offsetOptions[0], out int offsetValue)) { dynamicQueryOptions.PaginationOption = new PaginationOption { Count = countValue, Offset = offsetValue }; } else { throw new DynamicQueryException("Invalid pagination options"); } } }
/// <summary> /// Parses a Querystring into DynamicQueryOptions instance. /// </summary> /// <param name="query">QueryString to parse.</param> /// <param name="opShortCodes">Custom operation shortcodes.</param> /// <returns>Parsed DynamicQueryOptions instance.</returns> public static DynamicQueryOptions ParseQueryOptions(string query, CustomOpCodes opShortCodes = null) { try { var dynamicQueryOptions = new DynamicQueryOptions(); if (string.IsNullOrEmpty(query)) { return(dynamicQueryOptions); } string decodedQuery = HttpUtility.UrlDecode(query); DynamicQueryOptions innerQueryOptions = null; const string innerMemberKey = "v=("; int indexOfInnerMemberKey = decodedQuery.IndexOf(innerMemberKey); if (indexOfInnerMemberKey != -1) { indexOfInnerMemberKey += innerMemberKey.Length; string innerQuery = decodedQuery.Substring(indexOfInnerMemberKey, decodedQuery.LastIndexOf(')') - indexOfInnerMemberKey); innerQueryOptions = ParseQueryOptions(innerQuery, opShortCodes); decodedQuery = decodedQuery.Replace(innerQuery, string.Empty); } string[] defaultArrayValue = new string[0]; NameValueCollection queryCollection = HttpUtility.ParseQueryString(decodedQuery); string[] operations = queryCollection .GetValues(OPERATION_PARAMETER_KEY) ?.Select(x => x.ClearSpaces()) .ToArray() ?? defaultArrayValue; string[] parameterNames = queryCollection .GetValues(PARAMETER_NAME_KEY) ?.Select(x => x.ClearSpaces()) .ToArray() ?? defaultArrayValue; string[] parameterValues = queryCollection .GetValues(PARAMETER_VALUE_KEY) .ToArray() ?? defaultArrayValue; string[] sortOptions = queryCollection .GetValues(SORT_OPTIONS_PARAMETER_KEY) ?.Select(x => x.ClearSpaces()) .ToArray() ?? defaultArrayValue; string[] offsetOptions = queryCollection .GetValues(OFFSET_PARAMETER_KEY) ?.Select(x => x.ClearSpaces()) .ToArray() ?? defaultArrayValue; string[] countOptions = queryCollection .GetValues(COUNT_PARAMETER_KEY) ?.Select(x => x.ClearSpaces()) .ToArray() ?? defaultArrayValue; PopulateDynamicQueryOptions( dynamicQueryOptions, operations, parameterNames, parameterValues, sortOptions, offsetOptions, countOptions, opShortCodes ?? DefaultOpShortCodes, innerQueryOptions); return(dynamicQueryOptions); } catch (Exception ex) { throw new DynamicQueryException("DynamicQueryBuilder has encountered an unhandled exception", query, ex); } }
/// <summary> /// Parses a Querystring into DynamicQueryOptions instance. /// </summary> /// <param name="query">QueryString to parse.</param> /// <param name="opShortCodes">Custom operation shortcodes.</param> /// <returns>Parsed DynamicQueryOptions instance.</returns> public static DynamicQueryOptions ParseQueryOptions(string query, CustomOpCodes opShortCodes = null) { try { var dynamicQueryOptions = new DynamicQueryOptions(); if (string.IsNullOrEmpty(query)) { return(dynamicQueryOptions); } ////+ character issue ////https://docs.microsoft.com/en-us/dotnet/api/system.web.httputility.urlencode?redirectedfrom=MSDN&view=net-5.0#System_Web_HttpUtility_UrlEncode_System_String_ if (QueryStringParser.IsQueryStringEncoded(query)) { query = HttpUtility.UrlDecode(query); } DynamicQueryOptions innerQueryOptions = null; const string innerMemberKey = "v=("; int indexOfInnerMemberKey = query.IndexOf(innerMemberKey, StringComparison.Ordinal); if (indexOfInnerMemberKey != -1) { indexOfInnerMemberKey += innerMemberKey.Length; string innerQuery = query.Substring(indexOfInnerMemberKey, query.LastIndexOf(')') - indexOfInnerMemberKey); innerQueryOptions = ParseQueryOptions(innerQuery, opShortCodes); query = query.Replace(innerQuery, string.Empty); } string[] defaultArrayValue = new string[0]; NameValueCollection queryCollection = HttpUtility.ParseQueryString(query); IEnumerable <QueryStringParserResult> queryStringParserResults = QueryStringParser .GetAllParameterWithValue(query) .Where(e => !string.IsNullOrEmpty(e.Value) && e.Value.Contains(InternalConstants.PLUS_CHARACTER)).ToList(); if (queryStringParserResults.Any()) { QueryStringParser.ReplaceNameValueCollection(queryStringParserResults, queryCollection, InternalConstants.PARAMETER_VALUE_KEY); } string[] operations = queryCollection .GetValues(InternalConstants.OPERATION_PARAMETER_KEY) ?.Select(x => x.ClearSpaces()) .ToArray() ?? defaultArrayValue; string[] parameterNames = queryCollection .GetValues(InternalConstants.PARAMETER_NAME_KEY) ?.Select(x => x.ClearSpaces()) .ToArray() ?? defaultArrayValue; string[] parameterValues = queryCollection .GetValues(InternalConstants.PARAMETER_VALUE_KEY) ?.ToArray() ?? defaultArrayValue; string[] sortOptions = queryCollection .GetValues(InternalConstants.SORT_OPTIONS_PARAMETER_KEY) ?.Select(x => x.ClearSpaces()) .ToArray() ?? defaultArrayValue; string[] offsetOptions = queryCollection .GetValues(InternalConstants.OFFSET_PARAMETER_KEY) ?.Select(x => x.ClearSpaces()) .ToArray() ?? defaultArrayValue; string[] countOptions = queryCollection .GetValues(InternalConstants.COUNT_PARAMETER_KEY) ?.Select(x => x.ClearSpaces()) .ToArray() ?? defaultArrayValue; PopulateDynamicQueryOptions( dynamicQueryOptions, operations, parameterNames, parameterValues, sortOptions, offsetOptions, countOptions, opShortCodes: opShortCodes ?? Defaults.DefaultOpShortCodes, memberQueryOptions: innerQueryOptions); return(dynamicQueryOptions); } catch (Exception ex) { throw new DynamicQueryException("DynamicQueryBuilder has encountered an unhandled exception", query, ex); } }