// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Web.Http.Internal; using System.Web.Http.Properties; namespace System.Web.Http.Query { /// /// Used to deserialize a set of string based query operations into expressions and /// compose them over a specified query. /// internal static class ODataQueryDeserializer { /// /// Deserializes the query operations in the specified Uri and applies them /// to the specified IQueryable. /// /// The root query to compose the deserialized query over. /// The request Uri containing the query operations. /// The resulting IQueryable with the deserialized query composed over it. public static IQueryable Deserialize(IQueryable query, Uri uri) { if (query == null) { throw Error.ArgumentNull("query"); } if (uri == null) { throw Error.ArgumentNull("uri"); } ServiceQuery serviceQuery = GetServiceQuery(uri); return Deserialize(query, serviceQuery.QueryParts, null); } /// /// Deserializes the query operations in the specified Uri and returns an IQueryable /// with a manufactured query root with those operations applied. /// /// The element type of the query /// The request Uri containing the query operations. /// The resulting IQueryable with the deserialized query composed over it. public static IQueryable Deserialize(Uri uri) { if (uri == null) { throw Error.ArgumentNull("uri"); } return (IQueryable)Deserialize(typeof(T), uri); } /// /// Deserializes the query operations in the specified Uri and returns an IQueryable /// with a manufactured query root with those operations applied. /// /// The element type of the query /// The request Uri containing the query operations. /// The resulting IQueryable with the deserialized query composed over it. public static IQueryable Deserialize(Type elementType, Uri uri) { if (elementType == null) { throw Error.ArgumentNull("elementType"); } if (uri == null) { throw Error.ArgumentNull("uri"); } ServiceQuery serviceQuery = GetServiceQuery(uri); Array array = Array.CreateInstance(elementType, 0); IQueryable baseQuery = ((IEnumerable)array).AsQueryable(); return Deserialize(baseQuery, serviceQuery.QueryParts, null); } internal static IQueryable Deserialize(IQueryable query, IEnumerable queryParts) { if (query == null) { throw Error.ArgumentNull("query"); } if (queryParts == null) { throw Error.ArgumentNull("queryParts"); } return Deserialize(query, queryParts, null); } internal static IQueryable Deserialize(IQueryable query, IEnumerable queryParts, QueryResolver queryResolver) { if (query == null) { throw Error.ArgumentNull("query"); } if (queryParts == null) { throw Error.ArgumentNull("queryParts"); } foreach (ServiceQueryPart part in queryParts) { switch (part.QueryOperator) { case "filter": try { query = DynamicQueryable.Where(query, part.Expression, queryResolver); } catch (ParseException e) { throw new ParseException( Error.Format(SRResources.ParseErrorInClause, "$filter", e.Message)); } break; case "orderby": try { query = DynamicQueryable.OrderBy(query, part.Expression, queryResolver); } catch (ParseException e) { throw new ParseException( Error.Format(SRResources.ParseErrorInClause, "$orderby", e.Message)); } break; case "skip": try { int skipCount = Convert.ToInt32(part.Expression, System.Globalization.CultureInfo.InvariantCulture); if (skipCount < 0) { throw new ParseException( Error.Format(SRResources.PositiveIntegerExpectedForODataQueryParameter, "$skip", part.Expression)); } query = DynamicQueryable.Skip(query, skipCount); } catch (FormatException e) { throw new ParseException( Error.Format(SRResources.ParseErrorInClause, "$skip", e.Message)); } break; case "top": try { int topCount = Convert.ToInt32(part.Expression, System.Globalization.CultureInfo.InvariantCulture); if (topCount < 0) { throw new ParseException( Error.Format(SRResources.PositiveIntegerExpectedForODataQueryParameter, "$top", part.Expression)); } query = DynamicQueryable.Take(query, topCount); } catch (FormatException e) { throw new ParseException( Error.Format(SRResources.ParseErrorInClause, "$top", e.Message)); } break; } } return query; } internal static ServiceQuery GetServiceQuery(Uri uri) { if (uri == null) { throw Error.ArgumentNull("uri"); } NameValueCollection queryPartCollection = UriQueryUtility.ParseQueryString(uri.Query); List serviceQueryParts = new List(); foreach (string queryPart in queryPartCollection) { if (queryPart == null || !queryPart.StartsWith("$", StringComparison.Ordinal)) { // not a special query string continue; } foreach (string value in queryPartCollection.GetValues(queryPart)) { string queryOperator = queryPart.Substring(1); if (!ServiceQuery.IsSupportedQueryOperator(queryOperator)) { // skip any operators we don't support continue; } ServiceQueryPart serviceQueryPart = new ServiceQueryPart(queryOperator, value); serviceQueryParts.Add(serviceQueryPart); } } // Query parts for OData need to be ordered $filter, $orderby, $skip, $top. For this // set of query operators, they are already in alphabetical order, so it suffices to // order by operator name. In the future if we support other operators, this may need // to be reexamined. serviceQueryParts = serviceQueryParts.OrderBy(p => p.QueryOperator).ToList(); ServiceQuery serviceQuery = new ServiceQuery() { QueryParts = serviceQueryParts, }; return serviceQuery; } } }