/// <summary> /// Deserializes the message into requests parameters. Also parses query parameters /// from the request and stores the results in the original message. /// </summary> /// <param name="message">The incoming message to deserialize.</param> /// <param name="parameters">The parameters that are passed to the query operation. /// </param> void IDispatchMessageFormatter.DeserializeRequest(Message message, object[] parameters) { Message originalMessage = message; var isPost = MessageUtility.IsHttpPOSTMethod(message.Properties); // If user tried to GET a query with side-effects then fail // Note: This should never ever happen since it would fail earlier in the WCF pipeline since the method should be "POST"-only if (_queryHasSideEffects && !isPost) { throw new FaultException("Must use POST to for queries with side effects"); } if (isPost) { // If the HTTP Method is POST, get the query from the message body instead from the URL ServiceQuery serviceQuery = MessageUtility.GetServiceQuery(ref message); if (serviceQuery != null) { // Since a new message is returned by the GetServiceQueryFromMessageBody, the OperationContext does not find the property // if set on the new message. So we set the property directly on the current OperationContext IncomingMessageProperties. OperationContext.Current.IncomingMessageProperties[ServiceQuery.QueryPropertyName] = serviceQuery; } } else if (!String.IsNullOrEmpty(message.Properties.Via.Query)) { string query = HttpUtility.UrlDecode(message.Properties.Via.Query); string fullRequestUrl = HttpUtility.UrlDecode(HttpContext.Current.Request.RawUrl); message.Properties[ServiceQuery.QueryPropertyName] = DomainServiceWebHttpBehavior.GetServiceQuery(query, fullRequestUrl); } try { this._innerDispatchMessageFormatter.DeserializeRequest(message, parameters); } finally { // The original message belongs to the service model pipeline. We cannot // dispose it. On the other hand we could have just created a new message. If // that is the case we are responsible for disposing the new message. if (message != originalMessage) { message.Properties.Clear(); message.Headers.Clear(); message.Close(); } } }
protected override object InvokeCore(object instance, object[] inputs, out object[] outputs) { outputs = ServiceUtility.EmptyObjectArray; ServiceQuery serviceQuery = null; QueryAttribute queryAttribute = (QueryAttribute)this.operation.OperationAttribute; if (queryAttribute.IsComposable) { object value; if (OperationContext.Current.IncomingMessageProperties.TryGetValue(ServiceQuery.QueryPropertyName, out value)) { serviceQuery = (ServiceQuery)value; } } IEnumerable <ValidationResult> validationErrors; int totalCount; QueryResult <TEntity> result; try { QueryOperationInvoker.SetOutputCachingPolicy(this.operation); result = QueryProcessor.Process <TEntity>((DomainService)instance, this.operation, inputs, serviceQuery, out validationErrors, out totalCount); } catch (Exception ex) { if (ex.IsFatal()) { throw; } QueryOperationInvoker.ClearOutputCachingPolicy(); throw ServiceUtility.CreateFaultException(ex); } if (validationErrors != null && validationErrors.Any()) { throw ServiceUtility.CreateFaultException(validationErrors); } return(result); }
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { int headerPosition = request.Headers.FindHeader(QueryHeaderName, QueryHeaderNamespace); if (headerPosition > -1) { XmlDictionaryReader reader = request.Headers.GetReaderAtHeader(headerPosition); // Advance to contents, ReadServiceQuery expect to be on the QueryOption if (reader.IsStartElement(QueryPropertyName)) { reader.Read(); } ServiceQuery serviceQuery = MessageUtility.ReadServiceQuery(reader); request.Properties[QueryPropertyName] = serviceQuery; } return(null); }
protected override async ValueTask <object> InvokeCoreAsync(DomainService instance, object[] inputs, bool disableStackTraces) { ServiceQuery serviceQuery = null; QueryAttribute queryAttribute = (QueryAttribute)this.operation.OperationAttribute; // httpContext is lost on await so need to save it for later ise HttpContext httpContext = HttpContext.Current; if (queryAttribute.IsComposable) { object value; if (OperationContext.Current.IncomingMessageProperties.TryGetValue(ServiceQuery.QueryPropertyName, out value)) { serviceQuery = (ServiceQuery)value; } } QueryResult <TEntity> result; try { QueryOperationInvoker.SetOutputCachingPolicy(httpContext, this.operation); result = await QueryProcessor.ProcessAsync <TEntity>(instance, this.operation, inputs, serviceQuery); } catch (Exception ex) { if (ex.IsFatal()) { throw; } QueryOperationInvoker.ClearOutputCachingPolicy(httpContext); throw ServiceUtility.CreateFaultException(ex, disableStackTraces); } if (result.ValidationErrors != null && result.ValidationErrors.Any()) { throw ServiceUtility.CreateFaultException(result.ValidationErrors, disableStackTraces); } return(result); }
public void DomainService_DirectQuery() { DomainServiceDescription description = DomainServiceDescription.GetDescription(typeof(TestDomainServices.EF.Catalog)); TestDomainServices.EF.Catalog service = new TestDomainServices.EF.Catalog(); DomainServiceContext dsc = new DomainServiceContext(new MockDataService(new MockUser("mathew") { IsAuthenticated = true }), DomainOperationType.Query); service.Initialize(dsc); DomainOperationEntry queryOperation = description.GetQueryMethod("GetPurchaseOrders"); ServiceQuery serviceQuery = new ServiceQuery(); serviceQuery.QueryParts = new ServiceQueryPart[] { new ServiceQueryPart("where", "(it.Freight!=0)"), new ServiceQueryPart("take", "1") }; IEnumerable<ValidationResult> validationErrors; int totalCount; QueryResult<AdventureWorksModel.PurchaseOrder> result = QueryProcessor.Process<AdventureWorksModel.PurchaseOrder>(service, queryOperation, new object[0], serviceQuery, out validationErrors, out totalCount); Assert.AreEqual(1, result.RootResults.Count()); }
/// <summary> /// This method returns a ServiceQuery for the specified URL and query string. /// <remarks> /// This method must ensure that the original ordering of the query parts is maintained /// in the results. We want to do this without doing any custom URL parsing. The approach /// taken is to use HttpUtility to parse the query string, and from those results we search /// in the full URL for the relative positioning of those elements. /// </remarks> /// </summary> /// <param name="queryString">The query string portion of the URL</param> /// <param name="fullRequestUrl">The full request URL</param> /// <returns>The corresponding ServiceQuery</returns> internal static ServiceQuery GetServiceQuery(string queryString, string fullRequestUrl) { NameValueCollection queryPartCollection = HttpUtility.ParseQueryString(queryString); bool includeTotalCount = false; // Reconstruct a list of all key/value pairs List<string> queryParts = new List<string>(); foreach (string queryPart in queryPartCollection) { if (queryPart == null || !queryPart.StartsWith("$", StringComparison.Ordinal)) { // not a special query string continue; } if (queryPart.Equals("$includeTotalCount", StringComparison.OrdinalIgnoreCase)) { string value = queryPartCollection.GetValues(queryPart).First(); Boolean.TryParse(value, out includeTotalCount); continue; } foreach (string value in queryPartCollection.GetValues(queryPart)) { queryParts.Add(queryPart + "=" + value); } } string decodedQueryString = HttpUtility.UrlDecode(fullRequestUrl); // For each query part, find all occurrences of it in the Url (could be duplicates) List<KeyValuePair<string, int>> keyPairIndicies = new List<KeyValuePair<string, int>>(); foreach (string queryPart in queryParts.Distinct()) { int idx = -1; int endIdx = 0; while (((idx = decodedQueryString.IndexOf(queryPart, endIdx, StringComparison.Ordinal)) != -1) && (endIdx < decodedQueryString.Length - 1)) { // We found a match, however, we must ensure that the match is exact. For example, // The string "$take=1" will be found twice in query string "?$take=10&$orderby=Name&$take=1", // but the first match should be discarded. Therefore, before adding the match, we ensure // the next character is EOS or the param seperator '&'. endIdx = idx + queryPart.Length - 1; if ((endIdx == decodedQueryString.Length - 1) || (endIdx < decodedQueryString.Length - 1 && (decodedQueryString[endIdx + 1] == '&'))) { keyPairIndicies.Add(new KeyValuePair<string, int>(queryPart, idx)); } } } // create the list of ServiceQueryParts in order, ordered by // their location in the query string IEnumerable<string> orderedParts = keyPairIndicies.OrderBy(p => p.Value).Select(p => p.Key); IEnumerable<ServiceQueryPart> serviceQueryParts = from p in orderedParts let idx = p.IndexOf('=') select new ServiceQueryPart(p.Substring(1, idx - 1), p.Substring(idx + 1)); ServiceQuery serviceQuery = new ServiceQuery() { QueryParts = serviceQueryParts.ToList(), IncludeTotalCount = includeTotalCount }; return serviceQuery; }
/// <summary> /// This method returns a ServiceQuery for the specified URL and query string. /// <remarks> /// This method must ensure that the original ordering of the query parts is maintained /// in the results. We want to do this without doing any custom URL parsing. The approach /// taken is to use HttpUtility to parse the query string, and from those results we search /// in the full URL for the relative positioning of those elements. /// </remarks> /// </summary> /// <param name="queryString">The query string portion of the URL</param> /// <param name="fullRequestUrl">The full request URL</param> /// <returns>The corresponding ServiceQuery</returns> internal static ServiceQuery GetServiceQuery(string queryString, string fullRequestUrl) { NameValueCollection queryPartCollection = HttpUtility.ParseQueryString(queryString); bool includeTotalCount = false; // Reconstruct a list of all key/value pairs List <string> queryParts = new List <string>(); foreach (string queryPart in queryPartCollection) { if (queryPart == null || !queryPart.StartsWith("$", StringComparison.Ordinal)) { // not a special query string continue; } if (queryPart.Equals("$includeTotalCount", StringComparison.OrdinalIgnoreCase)) { string value = queryPartCollection.GetValues(queryPart).First(); Boolean.TryParse(value, out includeTotalCount); continue; } foreach (string value in queryPartCollection.GetValues(queryPart)) { queryParts.Add(queryPart + "=" + value); } } string decodedQueryString = HttpUtility.UrlDecode(fullRequestUrl); // For each query part, find all occurrences of it in the Url (could be duplicates) List <KeyValuePair <string, int> > keyPairIndicies = new List <KeyValuePair <string, int> >(); foreach (string queryPart in queryParts.Distinct()) { int idx = -1; int endIdx = 0; while (((idx = decodedQueryString.IndexOf(queryPart, endIdx, StringComparison.Ordinal)) != -1) && (endIdx < decodedQueryString.Length - 1)) { // We found a match, however, we must ensure that the match is exact. For example, // The string "$take=1" will be found twice in query string "?$take=10&$orderby=Name&$take=1", // but the first match should be discarded. Therefore, before adding the match, we ensure // the next character is EOS or the param seperator '&'. endIdx = idx + queryPart.Length - 1; if ((endIdx == decodedQueryString.Length - 1) || (endIdx < decodedQueryString.Length - 1 && (decodedQueryString[endIdx + 1] == '&'))) { keyPairIndicies.Add(new KeyValuePair <string, int>(queryPart, idx)); } } } // create the list of ServiceQueryParts in order, ordered by // their location in the query string IEnumerable <string> orderedParts = keyPairIndicies.OrderBy(p => p.Value).Select(p => p.Key); IEnumerable <ServiceQueryPart> serviceQueryParts = from p in orderedParts let idx = p.IndexOf('=') select new ServiceQueryPart(p.Substring(1, idx - 1), p.Substring(idx + 1)); ServiceQuery serviceQuery = new ServiceQuery() { QueryParts = serviceQueryParts.ToList(), IncludeTotalCount = includeTotalCount }; return(serviceQuery); }
public static async ValueTask <QueryResult <TEntity> > ProcessAsync <TEntity>(DomainService domainService, DomainOperationEntry queryOperation, object[] parameters, ServiceQuery serviceQuery) { DomainServiceDescription domainServiceDescription = DomainServiceDescription.GetDescription(domainService.GetType()); // deserialize the query if specified IQueryable query = null; bool includeTotalCount = false; if (serviceQuery != null) { query = GetQueryable <TEntity>(domainServiceDescription, serviceQuery); includeTotalCount = serviceQuery.IncludeTotalCount; } // invoke the query operation QueryDescription queryDescription = new QueryDescription(queryOperation, parameters, includeTotalCount, query); var res = await domainService.QueryAsync <TEntity>(queryDescription, CancellationToken.None); if (res.HasValidationErrors) { return(new QueryResult <TEntity>(res.ValidationErrors)); } IEnumerable <TEntity> results = (IEnumerable <TEntity>)res.Result; int totalCount = res.TotalCount; // Performance optimization: if there are no included associations, we can assume we don't need to flatten. if (!QueryProcessor.RequiresFlattening(domainServiceDescription, typeof(TEntity))) { // if the root entity type doesn't have any included associations // return the results immediately, bypassing the flattening operation return(new QueryResult <TEntity>(results, totalCount)); } List <TEntity> rootResults = null; if (!(results is ICollection <TEntity>)) { // Not an ICollection<TEntity>... Copy over the items into a list of root results. rootResults = new List <TEntity>(); } // flatten the results List <object> includedResults = new List <object>(); FlattenGraph(results, rootResults, includedResults, new HashSet <object>(), domainServiceDescription); return(new QueryResult <TEntity>(rootResults ?? results, totalCount) { IncludedResults = includedResults }); }
private static IQueryable GetQueryable <TEntity>(DomainServiceDescription domainServiceDescription, ServiceQuery query) { if (query != null && query.QueryParts != null && query.QueryParts.Any()) { IQueryable queryable = Enumerable.Empty <TEntity>().AsQueryable(); return(QueryDeserializer.Deserialize(domainServiceDescription, queryable, query.QueryParts)); } return(null); }