/// <summary>Initializes a new <see cref="RequestQueryProcessor"/> instance.</summary> /// <param name="service">Service with data and configuration.</param> /// <param name="description">Description for request processed so far.</param> private RequestQueryProcessor(IDataService service, RequestDescription description) { this.service = service; this.description = description; #if DEBUG this.orderApplied = false; #endif this.skipCount = null; this.topCount = null; this.queryExpression = description.RequestExpression; this.setQueryApplicable = (description.TargetKind == RequestTargetKind.Resource && !description.IsSingleResult) || description.CountOption == RequestQueryCountOption.CountSegment; // Server Driven Paging is not considered for the following cases: // 1. Top level result is not or resource type or it is a single valued result. // 2. $count segment provided. // 3. Non-GET requests do not honor SDP. // 4. Only exception for Non-GET requests is if the request is coming from a Service // operation that returns a set of result values of entity type. this.pagingApplicable = (description.TargetKind == RequestTargetKind.Resource && !description.IsSingleResult) && (description.CountOption != RequestQueryCountOption.CountSegment) && !description.IsRequestForEnumServiceOperation && (service.OperationContext.RequestMessage.HttpVerb.IsQuery() || description.SegmentInfos[0].TargetSource == RequestTargetSource.ServiceOperation); this.appliedCustomPaging = false; this.rootProjectionNode = null; this.topLevelOrderingInfo = null; this.skipTokenExpressionBuilder = new SkipTokenExpressionBuilder(NodeToExpressionTranslator.Create(this.service, description, Expression.Parameter(typeof(object)))); }
/// <summary> /// Initializes a new instance of the <see cref="ClientPreference"/> class. /// </summary> /// <param name="requestDescription">The request description.</param> /// <param name="verb">The request verb.</param> /// <param name="requestMessage">The request message.</param> /// <param name="effectiveMaxResponseVersion">The effective max response version for the request, which is the min of MDSV and MPV.</param> public ClientPreference(RequestDescription requestDescription, HttpVerbs verb, IODataRequestMessage requestMessage, Version effectiveMaxResponseVersion) : this(InterpretClientPreference(requestDescription, verb, requestMessage)) { if (effectiveMaxResponseVersion >= VersionUtil.Version4Dot0) { this.annotationFilter = requestMessage.PreferHeader().AnnotationFilter; } }
public void AnnotationFilterShouldNotBeSetAndRequiredResponseVersionShouldBe10WhenTheODataAnnotationsPreferenceIsMissing() { RequestDescription descrption = new RequestDescription(RequestTargetKind.Link, RequestTargetSource.ServiceOperation, new Uri("http://service/set")); IODataRequestMessage requestMessage = new ODataRequestMessageSimulator(); ClientPreference preference = new ClientPreference(descrption, HttpVerbs.None, requestMessage, effectiveMaxResponseVersion: VersionUtil.Version4Dot0); preference.AnnotationFilter.Should().BeNull(); preference.RequiredResponseVersion.Should().Be(VersionUtil.Version4Dot0); }
public void AnnotationFilterShouldBeSetWithODataAnnotationsPreferenceAndRequiredResponseVersionShouldBe30WhenEffectiveMaxResponseVersionIs30() { RequestDescription descrption = new RequestDescription(RequestTargetKind.Link, RequestTargetSource.ServiceOperation, new Uri("http://service/set")); IODataRequestMessage requestMessage = new ODataRequestMessageSimulator(); requestMessage.PreferHeader().AnnotationFilter = "*"; ClientPreference preference = new ClientPreference(descrption, HttpVerbs.None, requestMessage, effectiveMaxResponseVersion: VersionUtil.Version4Dot0); preference.AnnotationFilter.Should().Be("*"); preference.RequiredResponseVersion.Should().Be(VersionUtil.Version4Dot0); }
/// <summary> /// Initializes a new instance of the <see cref="Microsoft.OData.Service.ExpandAndSelectParseResult"/> class. /// </summary> /// <param name="requestDescription">The request description.</param> /// <param name="dataService">The data service.</param> internal ExpandAndSelectParseResult(RequestDescription requestDescription, IDataService dataService) { Debug.Assert(dataService != null, "dataService != null"); Debug.Assert(dataService.OperationContext != null, "dataService.OperationContext != null"); Debug.Assert(dataService.OperationContext.RequestMessage != null, "dataService.OperationContext.RequestMessage != null"); string expand = dataService.OperationContext.RequestMessage.GetQueryStringItem(XmlConstants.HttpQueryStringExpand); this.RawSelectQueryOptionValue = dataService.OperationContext.RequestMessage.GetQueryStringItem(XmlConstants.HttpQueryStringSelect); ResourceType targetResourceType = requestDescription.TargetResourceType; ResourceSetWrapper targetResourceSet = requestDescription.TargetResourceSet; if (!string.IsNullOrEmpty(expand)) { if (targetResourceType == null || targetResourceType.ResourceTypeKind != ResourceTypeKind.EntityType || targetResourceSet == null) { throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryExpandOptionNotApplicable); } } if (!string.IsNullOrEmpty(this.RawSelectQueryOptionValue)) { ValidateSelectIsAllowedForRequest(requestDescription); // Throw if $select requests have been disabled by the user Debug.Assert(dataService.Configuration != null, "dataService.Configuration != null"); if (!dataService.Configuration.DataServiceBehavior.AcceptProjectionRequests) { throw DataServiceException.CreateBadRequestError(Strings.DataServiceConfiguration_ProjectionsNotAccepted); } } MetadataProviderEdmModel model = dataService.Provider.GetMetadataProviderEdmModel(); var uriParser = RequestUriProcessor.CreateUriParserWithBatchReferenceCallback(dataService, dataService.OperationContext.AbsoluteRequestUri); try { Debug.Assert(model.Mode == MetadataProviderEdmModelMode.Serialization, "Model expected to be in serialization mode by default"); model.Mode = MetadataProviderEdmModelMode.SelectAndExpandParsing; this.Clause = uriParser.ParseSelectAndExpand(); } catch (ODataException ex) { throw new DataServiceException(400, null, ex.Message, null, ex); } finally { model.Mode = MetadataProviderEdmModelMode.Serialization; } if (this.Clause != null) { this.HasExpand = this.Clause.SelectedItems.OfType<ExpandedNavigationSelectItem>().Any(); this.HasSelect = HasSelectedItemAtAnyLevel(this.Clause); } }
/// <summary> /// Create a new instance of ODataMessageWriterSettings for normal requests. /// </summary> /// <param name="dataService">Data service instance.</param> /// <param name="requestDescription">The current request description.</param> /// <param name="responseMessage">IODataResponseMessage implementation.</param> /// <param name="model">The model to provide to the message writer.</param> /// <returns>An instance of a message writer with the appropriate settings.</returns> internal static MessageWriterBuilder ForNormalRequest(IDataService dataService, RequestDescription requestDescription, IODataResponseMessage responseMessage, IEdmModel model) { Debug.Assert(dataService != null, "dataService != null"); Debug.Assert(dataService.OperationContext != null, "dataService.OperationContext != null"); Debug.Assert(requestDescription != null, "requestDescription != null"); Debug.Assert(dataService.OperationContext.RequestMessage != null, "dataService.OperationContext.RequestMessage != null"); Debug.Assert(responseMessage != null, "responseMessage != null"); Uri serviceUri = dataService.OperationContext.AbsoluteServiceUri; Version responseVersion = requestDescription.ActualResponseVersion; MessageWriterBuilder messageWriterBuilder = new MessageWriterBuilder(serviceUri, responseVersion, dataService, responseMessage, model); // ODataLib doesn't allow custom MIME types on raw values (must be text/plain for non-binary, and application/octet for binary values). // To maintain existing V1/V2 behavior, work around this by setting the format as RawValue (we handle conneg ourself for this, so don't make ODL do its own), // and then later manually override the content type header. Conneg is done by Astoria in DataService.CreateResponseBodyWriter. if (requestDescription.ResponsePayloadKind == ODataPayloadKind.Value && !string.IsNullOrEmpty(requestDescription.MimeType)) { messageWriterBuilder.WriterSettings.SetContentType(ODataFormat.RawValue); } else { string acceptHeaderValue = dataService.OperationContext.RequestMessage.GetAcceptableContentTypes(); // In V1/V2 we defaulted to charset=utf-8 for the response when there was no specific Accept-Charset. // ODataMessageWriter uses a different default in some cases depending on the media type, so we need to override that here. string requestAcceptCharSet = dataService.OperationContext.RequestMessage.GetRequestAcceptCharsetHeader(); if (string.IsNullOrEmpty(requestAcceptCharSet) || requestAcceptCharSet == "*") { requestAcceptCharSet = XmlConstants.Utf8Encoding; } messageWriterBuilder.WriterSettings.SetContentType(acceptHeaderValue, requestAcceptCharSet); } // always set the metadata document URI. ODataLib will decide whether or not to write it. messageWriterBuilder.WriterSettings.ODataUri = new ODataUri() { ServiceRoot = serviceUri, SelectAndExpand = requestDescription.ExpandAndSelect.Clause, Path = requestDescription.Path }; messageWriterBuilder.WriterSettings.JsonPCallback = requestDescription.JsonPaddingFunctionName; return messageWriterBuilder; }
/// <summary> /// Binds the expand paths from the requests $expand query option to the sets/types/properties from the metadata provider of the service. /// </summary> /// <param name="requestDescription">The request description.</param> /// <param name="dataService">The data service.</param> /// <param name="expandQueryOption">The value of the $expand query option.</param> /// <returns>The bound expand segments.</returns> internal static IList<IList<ExpandItem>> BindExpandSegments(RequestDescription requestDescription, IDataService dataService, string expandQueryOption) { Debug.Assert(requestDescription != null, "requestDescription != null"); Debug.Assert(dataService != null, "dataService != null"); if (string.IsNullOrWhiteSpace(expandQueryOption)) { return new List<IList<ExpandItem>>(); } ResourceType targetResourceType = requestDescription.TargetResourceType; ResourceSetWrapper targetResourceSet = requestDescription.TargetResourceSet; if (targetResourceType == null || targetResourceType.ResourceTypeKind != ResourceTypeKind.EntityType || targetResourceSet == null) { throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryExpandOptionNotApplicable); } MetadataProviderEdmModel model = dataService.Provider.GetMetadataProviderEdmModel(); IEdmEntityType targetType = (IEdmEntityType)model.EnsureSchemaType(targetResourceType); IEdmEntitySet targetSet = model.EnsureEntitySet(targetResourceSet); SelectExpandClause clause; try { model.Mode = MetadataProviderEdmModelMode.SelectAndExpandParsing; clause = ODataUriParser.ParseSelectAndExpand(/*select*/ null, expandQueryOption, model, targetType, targetSet); } catch (ODataException ex) { throw new DataServiceException(400, null, ex.Message, null, ex); } finally { model.Mode = MetadataProviderEdmModelMode.Serialization; } return new ExpandAndSelectPathExtractor(clause).ExpandPaths; }
private static void RunPayloadKindTest(RequestTargetKind requestTargetKind, RequestTargetSource requestTargetSource, ResourceType targetResourceType, bool singleResult, bool isLinkUri, ODataPayloadKind expectedKind) { var segment = new SegmentInfo { TargetKind = requestTargetKind, TargetSource = requestTargetSource, TargetResourceType = targetResourceType, SingleResult = singleResult, Identifier = "Fake", }; var operation = new ServiceOperation("Fake", ServiceOperationResultKind.Void, null, null, "GET", null); operation.SetReadOnly(); segment.Operation = new OperationWrapper(operation); segment.ProjectedProperty = new ResourceProperty("Fake", ResourcePropertyKind.Primitive, ResourceType.GetPrimitiveResourceType(typeof(int))); SegmentInfo[] segmentInfos; if (isLinkUri) { segmentInfos = new[] { new SegmentInfo(), new SegmentInfo { TargetKind = RequestTargetKind.Link, }, segment }; } else { segmentInfos = new[] { new SegmentInfo { Identifier = "Fake", }, new SegmentInfo(), segment }; } var requestDescription = new RequestDescription(segmentInfos, new Uri("http://temp.org/")); requestDescription.ResponsePayloadKind.Should().Be(expectedKind); }
private static void RunNegotiatedFormatTest(string requestAccept, string requestMaxVersion, Microsoft.OData.Client.ODataProtocolVersion maxProtocolVersion, ODataFormat expectedFormat) { DataServiceHostSimulator host = new DataServiceHostSimulator { RequestHttpMethod = "GET", RequestAccept = requestAccept, RequestMaxVersion = requestMaxVersion, RequestVersion = "4.0", }; DataServiceSimulator service = new DataServiceSimulator { OperationContext = new DataServiceOperationContext(host), Configuration = new DataServiceConfiguration(new DataServiceProviderSimulator()), }; service.Configuration.DataServiceBehavior.MaxProtocolVersion = maxProtocolVersion; service.OperationContext.InitializeAndCacheHeaders(service); service.OperationContext.RequestMessage.InitializeRequestVersionHeaders(VersionUtil.ToVersion(maxProtocolVersion)); var d = new RequestDescription(RequestTargetKind.Primitive, RequestTargetSource.Property, new Uri("http://temp.org/")); d.DetermineWhetherResponseBodyOrETagShouldBeWritten(service.OperationContext.RequestMessage.HttpVerb); d.DetermineWhetherResponseBodyShouldBeWritten(service.OperationContext.RequestMessage.HttpVerb); d.DetermineResponseFormat(service); d.ResponseFormat.Should().NotBeNull(); d.ResponseFormat.Format.Should().BeSameAs(expectedFormat); }
private static void RunSimpleFormatTest(RequestTargetKind requestTargetKind, ODataFormat expectedFormat) { var requestDescription = new RequestDescription(requestTargetKind, RequestTargetSource.None, new Uri("http://temp.org/")); requestDescription.DetermineResponseFormat(new DataServiceSimulator()); if (expectedFormat == null) { requestDescription.ResponseFormat.Should().BeNull(); } else { requestDescription.ResponseFormat.Should().NotBeNull(); requestDescription.ResponseFormat.Format.Should().BeSameAs(expectedFormat); } }
public void RawValueWithMimeTypeShouldUseFormat() { this.host.AbsoluteServiceUri = new Uri("http://myservice.org/"); this.host.AbsoluteRequestUri = new Uri("http://myservice.org/FakeSet"); SegmentInfo segment = new SegmentInfo { TargetKind = RequestTargetKind.PrimitiveValue, TargetSource = RequestTargetSource.Property, ProjectedProperty = new ResourceProperty("Fake", ResourcePropertyKind.Primitive, ResourceType.GetPrimitiveResourceType(typeof(string))) { MimeType = "fake/things" } }; var requestDescription = new RequestDescription(new[] { segment }, new Uri("http://temp.org")); var testSubject = this.ForNormalRequest(requestDescription); testSubject.WriterSettings.UseFormat.Should().BeTrue(); testSubject.WriterSettings.Format.Should().BeSameAs(ODataFormat.RawValue); }
/// <summary> /// Calls the Execution provider to invoke the request expressions for the current request /// </summary> /// <param name="description">Request description.</param> /// <param name="service">Service instance.</param> private static void InvokeRequestExpression(RequestDescription description, IDataService service) { Debug.Assert(description != null, "description != null"); Debug.Assert(service != null, "service != null"); HttpVerbs httpVerb = service.OperationContext.RequestMessage.HttpVerb; bool isPostOperationRequest = httpVerb == HttpVerbs.POST && description.TargetSource == RequestTargetSource.ServiceOperation; if (httpVerb.IsQuery() || isPostOperationRequest) { SegmentInfo segmentToInvoke = description.LastSegmentInfo; if (httpVerb.IsQuery() && description.TargetSource == RequestTargetSource.Property) { // GET ~/Customer(1)/Address/State for example we need to execute the expression at the resource level, i.e. Customer(1). // Then we call IDSQP.GetValue() to get the property values for Address and State. segmentToInvoke = description.SegmentInfos[description.GetIndexOfTargetEntityResource()]; } if (segmentToInvoke.RequestExpression != null && segmentToInvoke.RequestEnumerable == null) { segmentToInvoke.RequestEnumerable = service.ExecutionProvider.GetResultEnumerableFromRequest(segmentToInvoke); } } }
/// <summary>Gets a string with methods allowed on the target for the <paramref name="description"/>.</summary> /// <param name="configuration">configuration object which has the data</param> /// <param name="description">Description with target.</param> /// <returns>A string with methods allowed on the description; possibly null.</returns> internal static string GetAllowedMethods(DataServiceConfiguration configuration, RequestDescription description) { Debug.Assert(description != null, "description != null"); Debug.Assert( description.TargetKind != RequestTargetKind.Nothing, "description.TargetKind != RequestTargetKind.Void - otherwise it hasn't been determined yet"); Debug.Assert( description.TargetKind != RequestTargetKind.VoidOperation, "description.TargetKind != RequestTargetKind.VoidOperation - this method is only for containers"); if (description.TargetKind == RequestTargetKind.Metadata || description.TargetKind == RequestTargetKind.ServiceDirectory) { return XmlConstants.HttpMethodGet; } if (description.TargetKind == RequestTargetKind.Batch) { return XmlConstants.HttpMethodPost; } int index = description.GetIndexOfTargetEntityResource(); Debug.Assert(index >= 0 && index < description.SegmentInfos.Count, "index >=0 && index <description.SegmentInfos.Count"); ResourceSetWrapper container = description.SegmentInfos[index].TargetResourceSet; return GetAllowedMethods(configuration, container, description); }
/// <summary> /// Create a new request description from the given request description and new entity as the result. /// </summary> /// <param name="description">Existing request description.</param> /// <param name="entity">entity that needs to be the result of the new request.</param> /// <returns>a new instance of request description containing information about the given entity.</returns> internal static RequestDescription CreateSingleResultRequestDescription(RequestDescription description, object entity) { return RequestDescription.CreateSingleResultRequestDescription(description, entity, description.TargetResourceType); }
public object GetResource(RequestDescription description, int segmentIndex, string typeFullName) { throw new NotImplementedException(); }
private static void RunSpatialDistanceFilterTest(RequestDescription d, Action<GeographyPoint> verifyPoint1, Action<GeographyPoint> verifyPoint2, double distanceToReturn, int expectedCount) { bool called = false; Func<Geography, Geography, double> callback = (Geography geo1, Geography geo2) => { called = true; var point1 = geo1 as GeographyPointImplementation; Assert.IsNotNull(point1); var point2 = geo2 as GeographyPointImplementation; Assert.IsNotNull(point2); if (verifyPoint1 != null) { verifyPoint1(point1); } if (verifyPoint2 != null) { verifyPoint2(point2); } return distanceToReturn; }; AssertConstantExpressionsAreOfPublicType(d.RequestExpression); var e = Invoke(d.RequestExpression).GetEnumerator(); using (SpatialTestUtils.RegisterOperations(new DistanceOperationImplementation(callback))) { int count = 0; while (e.MoveNext()) { count++; } Assert.IsTrue(called); Assert.AreEqual(expectedCount, count); } }
/// <summary>Sets the response status code and the default caching and versioning headers.</summary> /// <param name="description">The request description for the current request.</param> /// <param name="statusCode">The status code for the response.</param> internal void SetResponseHeaders(RequestDescription description, int statusCode) { // Set the caching policy appropriately - for the time being, we disable caching. this.SetHeader(XmlConstants.HttpResponseCacheControl, XmlConstants.HttpCacheControlNoCache); // If a preference was applied, add corresponding response header. ClientPreference preference = description.Preference; this.PreferenceAppliedHeader().ReturnContent = preference.ShouldIncludeResponseBody ? true : (preference.ShouldNotIncludeResponseBody ? (bool?)false : null); // Only set the annotation filter to the Preference-Applied if we are writing a response body // since instance annotations only appear in the response body. if (description.ShouldWriteResponseBody && !string.IsNullOrEmpty(preference.AnnotationFilter)) { this.PreferenceAppliedHeader().AnnotationFilter = preference.AnnotationFilter; } this.SetHeader(XmlConstants.HttpODataVersion, description.ResponseVersion.ToString() + ";"); this.StatusCode = statusCode; }
/// <summary> /// Interprets the client preference for having a response body. /// </summary> /// <param name="requestDescription">The request description.</param> /// <param name="verb">The request verb.</param> /// <param name="requestMessage">The request message.</param> /// <returns>An enum representation of the client's preference.</returns> private static ResponseBodyPreference InterpretClientPreference(RequestDescription requestDescription, HttpVerbs verb, IODataRequestMessage requestMessage) { Debug.Assert(requestDescription != null, "requestDescription != null"); // If no responseBodyPreference given, we have default behavior of producing content for POST and not producing it for PUT/PATCH. // If responseBodyPreference is given we honor the responseBodyPreference only if the request was for an entity and following conditions are true: // This is not a service operation invoke // DSV was set to 3.0 and above // Server is configured to be >= 3.0 if (requestDescription.LinkUri || requestDescription.SegmentInfos[0].TargetSource == RequestTargetSource.ServiceOperation || requestDescription.RequestVersion < VersionUtil.Version4Dot0) { return ResponseBodyPreference.None; } if ((verb.IsInsert()) || ((verb.IsUpdate()) && (requestDescription.TargetKind == RequestTargetKind.Resource || requestDescription.IsRequestForNonEntityProperty))) { if (requestMessage.PreferHeader().ReturnContent.HasValue) { return requestMessage.PreferHeader().ReturnContent.Value ? ResponseBodyPreference.Content : ResponseBodyPreference.NoContent; } } // TODO: move logic for when/when-not-to write a response body here and remove all checks for 'none' elsewhere return ResponseBodyPreference.None; }
private MessageWriterBuilder ForNormalRequest(RequestDescription requestDescription) { this.dataServiceSimulator.OperationContext.InitializeAndCacheHeaders(this.dataServiceSimulator); this.dataServiceSimulator.OperationContext.RequestMessage.CacheHeaders(); this.dataServiceSimulator.OperationContext.RequestMessage.InitializeRequestVersionHeaders(VersionUtil.Version4Dot0); requestDescription.ParseExpandAndSelect(this.dataServiceSimulator); requestDescription.DetermineWhetherResponseBodyOrETagShouldBeWritten(HttpVerbs.GET); requestDescription.DetermineWhetherResponseBodyShouldBeWritten(HttpVerbs.GET); requestDescription.DetermineResponseFormat(this.dataServiceSimulator); var testSubject = MessageWriterBuilder.ForNormalRequest(this.dataServiceSimulator, requestDescription, this.responseMessageSimulator, new EdmModel()); return testSubject; }
private MessageWriterBuilder ForNormalRequest() { var requestDescription = new RequestDescription(RequestTargetKind.Resource, RequestTargetSource.EntitySet, new Uri("http://temp.org/")); var resourceType = new ResourceType(typeof(object), ResourceTypeKind.EntityType, null, "Fake", "Type", false) { CanReflectOnInstanceType = false, IsOpenType = true }; resourceType.AddProperty(new ResourceProperty("Id", ResourcePropertyKind.Key | ResourcePropertyKind.Primitive, ResourceType.GetPrimitiveResourceType(typeof(int))) { CanReflectOnInstanceTypeProperty = false }); var resourceSet = new ResourceSet("FakeSet", resourceType); resourceSet.SetReadOnly(); requestDescription.LastSegmentInfo.TargetResourceType = resourceType; requestDescription.LastSegmentInfo.TargetResourceSet = ResourceSetWrapper.CreateForTests(resourceSet); return this.ForNormalRequest(requestDescription); }
/// <summary> /// Copies settings from another request-description instance. /// </summary> /// <param name="other">The description to copy from.</param> private void CopyFrom(RequestDescription other) { this.SkipTokenExpressionCount = other.SkipTokenExpressionCount; this.SkipTokenProperties = other.SkipTokenProperties; this.CountOption = other.CountOption; this.CountValue = other.CountValue; this.Preference = other.Preference; this.ResponseVersion = other.ResponseVersion; this.ActualResponseVersion = other.ActualResponseVersion; this.serviceMaxProtocolVersion = other.serviceMaxProtocolVersion; this.requestMaxVersion = other.requestMaxVersion; #if DEBUG this.maxFeatureVersion = other.maxFeatureVersion; #endif this.ResponseFormat = other.ResponseFormat; this.PayloadMetadataParameterInterpreter = other.PayloadMetadataParameterInterpreter; this.ExpandAndSelect = other.ExpandAndSelect; this.responseBodyOrETagShouldBeWritten = other.responseBodyOrETagShouldBeWritten; this.responseBodyShouldBeWritten = other.responseBodyShouldBeWritten; this.JsonPaddingFunctionName = other.JsonPaddingFunctionName; this.Path = other.Path; }
/// <summary>Initializes a new RequestDescription based on an existing one.</summary> /// <param name="other">Other description to base new description on.</param> /// <param name="resultExpression">Query results for new request description.</param> /// <param name="rootProjectionNode">Projection segment describing the projections on the top level of the query.</param> internal RequestDescription( RequestDescription other, Expression resultExpression, RootProjectionNode rootProjectionNode) { Debug.Assert( resultExpression == null || other.SegmentInfos != null, "queryResults == null || other.SegmentInfos != null -- otherwise there isn't a segment in which to replace the query."); Debug.Assert( rootProjectionNode == null || resultExpression != null, "rootProjectionNode == null || queryResults != null -- otherwise there isn't a query to execute and expand"); this.rootProjectionNode = rootProjectionNode; this.containerName = other.ContainerName; this.mimeType = other.MimeType; this.resultUri = other.ResultUri; this.segmentInfos = other.SegmentInfos; this.CopyFrom(other); if (resultExpression != null) { this.LastSegmentInfo.RequestExpression = resultExpression; } }
/// <summary> /// Binds the paths from the request's $select query option to the sets/types/properties from the metadata provider of the service. /// </summary> /// <param name="requestDescription">The request description.</param> /// <param name="dataService">The data service.</param> /// <param name="selectQueryOption">The raw value of the $select query option.</param> /// <returns>The bound select segments.</returns> internal static IList<IList<SelectItem>> BindSelectSegments(RequestDescription requestDescription, IDataService dataService, string selectQueryOption) { Debug.Assert(requestDescription != null, "requestDescription != null"); Debug.Assert(dataService != null, "dataService != null"); if (string.IsNullOrEmpty(selectQueryOption)) { return new List<IList<SelectItem>>(); } // Throw if $select requests have been disabled by the user Debug.Assert(dataService.Configuration != null, "dataService.Configuration != null"); if (!dataService.Configuration.DataServiceBehavior.AcceptProjectionRequests) { throw DataServiceException.CreateBadRequestError(Strings.DataServiceConfiguration_ProjectionsNotAccepted); } IList<IList<string>> selectPathsAsText = SplitSelect(selectQueryOption, dataService.Provider); Debug.Assert(selectPathsAsText != null, "selectPathsAsText != null"); List<IList<SelectItem>> boundSegments = new List<IList<SelectItem>>(selectPathsAsText.Count); if (selectPathsAsText.Count == 0) { return boundSegments; } ValidateSelectIsAllowedForRequest(requestDescription); MetadataProviderEdmModel metadataProviderEdmModel = dataService.Provider.GetMetadataProviderEdmModel(); for (int i = selectPathsAsText.Count - 1; i >= 0; i--) { IList<string> path = selectPathsAsText[i]; List<SelectItem> boundSegmentPath = new List<SelectItem>(path.Count); boundSegments.Add(boundSegmentPath); ResourceType targetResourceType = requestDescription.TargetResourceType; ResourceSetWrapper targetResourceSet = requestDescription.TargetResourceSet; // if we get to here, we're building a partial selection List<TypeSegment> typeSegments = new List<TypeSegment>(); bool previousSegmentIsTypeSegment = false; for (int j = 0; j < path.Count; j++) { string pathSegment = path[j]; bool lastPathSegment = (j == path.Count - 1); // '*' is special, it means "Project all immediate properties on this level." if (pathSegment == "*") { Debug.Assert(lastPathSegment, "A wildcard select segment must be the last one. This should have been checked already when splitting appart the paths."); boundSegmentPath.Add(CreateWildcardSelection()); continue; } bool nameIsContainerQualified; string nameFromContainerQualifiedName = dataService.Provider.GetNameFromContainerQualifiedName(pathSegment, out nameIsContainerQualified); if (nameFromContainerQualifiedName == "*") { Debug.Assert(lastPathSegment, "A wildcard select segment must be the last one. This should have been checked already when splitting appart the paths."); Debug.Assert(nameIsContainerQualified, "nameIsContainerQualified == true"); boundSegmentPath.Add(CreateContainerQualifiedWildcardSelection(metadataProviderEdmModel)); continue; } ResourceProperty property = targetResourceType.TryResolvePropertyName(pathSegment); if (property == null) { ResourceType resolvedResourceType = WebUtil.ResolveTypeIdentifier(dataService.Provider, pathSegment, targetResourceType, previousSegmentIsTypeSegment); if (resolvedResourceType != null) { previousSegmentIsTypeSegment = true; if (lastPathSegment) { throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryParametersPathCannotEndInTypeIdentifier(XmlConstants.HttpQueryStringSelect, resolvedResourceType.FullName)); } targetResourceType = resolvedResourceType; // Whenever we encounter the type segment, we need to only verify that the MPV is set to 3.0 or higher. // There is no need to check for request DSV, request MinDSV or request MaxDSV since there are no protocol changes in // the payload for uri's with type identifier. requestDescription.VerifyProtocolVersion(VersionUtil.Version3Dot0, dataService); IEdmSchemaType edmType = metadataProviderEdmModel.EnsureSchemaType(targetResourceType); TypeSegment typeSegment = new TypeSegment(edmType); typeSegments.Add(typeSegment); continue; } previousSegmentIsTypeSegment = false; // If the currentResourceType is an open type, we require the service action name to be fully qualified or else we treat it as an open property name. if (!targetResourceType.IsOpenType || nameIsContainerQualified) { // Note that if the service does not implement IDataServiceActionProvider and the MaxProtocolVersion < V3, // GetActionsBoundToAnyTypeInResourceSet() would simply return an empty ServiceOperationWrapper collection. Debug.Assert(dataService.ActionProvider != null, "dataService.ActionProvider != null"); IEnumerable<OperationWrapper> allOperationsInSet = dataService.ActionProvider.GetActionsBoundToAnyTypeInResourceSet(targetResourceSet); List<OperationWrapper> selectedServiceActions = allOperationsInSet.Where(o => o.Name == nameFromContainerQualifiedName).ToList(); if (selectedServiceActions.Count > 0) { if (!lastPathSegment) { throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_ServiceActionMustBeLastSegmentInSelect(pathSegment)); } boundSegmentPath.Add(CreateOperationSelection(metadataProviderEdmModel, selectedServiceActions, typeSegments)); continue; } } if (!targetResourceType.IsOpenType) { throw DataServiceException.CreateSyntaxError(Strings.RequestUriProcessor_PropertyNotFound(targetResourceType.FullName, pathSegment)); } if (!lastPathSegment) { // Open navigation properties are not supported on OpenTypes. throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(pathSegment)); } boundSegmentPath.Add(CreateOpenPropertySelection(pathSegment, typeSegments)); } else { previousSegmentIsTypeSegment = false; ValidateSelectedProperty(targetResourceType, property, lastPathSegment); boundSegmentPath.Add(CreatePropertySelection(metadataProviderEdmModel, targetResourceType, property, typeSegments)); if (property.TypeKind == ResourceTypeKind.EntityType) { targetResourceSet = dataService.Provider.GetResourceSet(targetResourceSet, targetResourceType, property); targetResourceType = property.ResourceType; } } } // Note that this check is also covering cases where a type segment is followed by a wildcard. if (previousSegmentIsTypeSegment) { throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryParametersPathCannotEndInTypeIdentifier(XmlConstants.HttpQueryStringSelect, targetResourceType.FullName)); } } return boundSegments; }
/// <summary> /// Create a new request description from the given request description and new entity as the result. /// </summary> /// <param name="description">Existing request description.</param> /// <param name="entity">entity that needs to be the result of the new request.</param> /// <param name="targetResourceType">The new target resource type for the new request description.</param> /// <returns>a new instance of request description containing information about the given entity.</returns> internal static RequestDescription CreateSingleResultRequestDescription(RequestDescription description, object entity, ResourceType targetResourceType) { // Create a new request description for the results that will be returned. SegmentInfo segmentInfo = new SegmentInfo { RequestExpression = Expression.Constant(entity), RequestEnumerable = new[] { entity }, TargetKind = description.TargetKind, TargetSource = description.TargetSource, SingleResult = true, ProjectedProperty = description.Property, TargetResourceType = targetResourceType, TargetResourceSet = description.LastSegmentInfo.TargetResourceSet, Identifier = description.LastSegmentInfo.Identifier }; #if DEBUG segmentInfo.AssertValid(); #endif IList<SegmentInfo> segmentInfos = description.SegmentInfos; segmentInfos[segmentInfos.Count - 1] = segmentInfo; RequestDescription resultDescription = new RequestDescription(segmentInfos, description.ResultUri); resultDescription.CopyFrom(description); return resultDescription; }
internal static RequestDescription ProcessRequestUri(Uri absoluteRequestUri, IDataService service, bool internalQuery) { Debug.Assert(service != null, "service != null"); Debug.Assert(absoluteRequestUri != null, "absoluteRequestUri != null"); Debug.Assert(absoluteRequestUri.IsAbsoluteUri, "absoluteRequestUri.IsAbsoluteUri(" + absoluteRequestUri + ")"); MetadataProviderEdmModel metadataProviderEdmModel = service.Provider.GetMetadataProviderEdmModel(); ODataPath path = ParsePath(metadataProviderEdmModel, absoluteRequestUri, service); IList<SegmentInfo> segmentInfos = ODataPathToSegmentInfoConverter.Create(service).ConvertPath(path); SegmentInfo lastSegment = null; if (segmentInfos.Count > 0) { lastSegment = segmentInfos[segmentInfos.Count - 1]; // we need to check syntax validity for Service Ops here, if it's the last segment in the URI // Reason for checking here is we need to fail before it's invoked // Reason for checking the last segment instead of inside the CreateSegmentForSOP is further composing on a SOP segment // could change its result kind (i.e., using Navigation Properties on SingleResult SOP) if (lastSegment.Operation != null) { ServiceOperationResultKind operationResultKind = lastSegment.Operation.ResultKind; if (operationResultKind == ServiceOperationResultKind.QueryWithSingleResult) { WebUtil.CheckEmptySetQueryArguments(service); } if (operationResultKind == ServiceOperationResultKind.DirectValue || operationResultKind == ServiceOperationResultKind.Enumeration) { // no further composition should be allowed WebUtil.CheckEmptyQueryArguments(service, false /*checkForOnlyV2QueryParameters*/); } if (lastSegment.IsServiceActionSegment) { if (service.OperationContext.RequestMethod != lastSegment.Operation.Method) { throw DataServiceException.CreateMethodNotAllowed(Strings.RequestUriProcessor_MethodNotAllowed, lastSegment.Operation.Method); } WebUtil.CheckEmptyQueryArguments(service, false /*checkForOnlyV2QueryParameters*/); } } if (lastSegment.Identifier == XmlConstants.UriCountSegment) { if (service.OperationContext.RequestMessage.HttpVerb.IsChange()) { throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_RequestVerbCannotCountError); } } var entityReferenceSegmentInfo = segmentInfos.FirstOrDefault(segmentInfo => segmentInfo.Identifier == XmlConstants.UriLinkSegment); // Check for entity reference segment $ref // DEVNOTE: Ideally $ref should be the last segment, but in order to keep minimum changes to server code, this order is not changed // If the order is changed, lot of server code needs to be changed since LastSegment of RequestDescription is used in many places if (entityReferenceSegmentInfo != null && service.OperationContext.RequestMessage.HttpVerb == HttpVerbs.DELETE) { WebUtil.CheckEmptyQueryArguments(service, /*checkForOnlyV2QueryParameters*/false); NameValueCollection queryCollection = HttpUtility.ParseQueryString(absoluteRequestUri.Query); // For single-valued navigation properites, there should not be any query parameters if (lastSegment.SingleResult && queryCollection.Count > 0) { // For single-valued navigation properties, the $id or any other query option MUST NOT be specified. throw DataServiceException.CreateBadRequestError(Strings.BadRequest_QueryOptionsShouldNotBeSpecifiedForDeletingSingleEntityReference); } // For collection-valued navigation properties, the entity reference of the entity to be removed MUST be specified using the $id query string option. if (!lastSegment.SingleResult) { // only one query ($id) parameter needs to be specified for DELETE operation on a collection of entity reference if (queryCollection.Count > 1) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidUriForDeleteOperation(absoluteRequestUri.Query)); } // For DELETE operations on a collection, $id query parameter is required var entityId = service.OperationContext.RequestMessage.GetQueryStringItem(XmlConstants.HttpQueryStringId); // $id must be specified for DELETE operation on a collection of entity references if (string.IsNullOrEmpty(entityId)) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_IdMustBeSpecifiedForDeletingCollectionOfEntityReference); } // Get the EntityId from $id query parameter Uri uri = RequestUriProcessor.GetAbsoluteUriFromReference(entityId, service.OperationContext); var parser = CreateUriParserWithBatchReferenceCallback(service, uri); lastSegment.SingleResult = true; lastSegment.Key = (KeySegment)parser.ParsePath().LastSegment; } } if (lastSegment.TargetKind == RequestTargetKind.Batch) { CheckNoDollarFormat(service); } if (lastSegment.TargetKind == RequestTargetKind.MediaResource) { // There is no valid query option for Media Resource. WebUtil.CheckEmptyQueryArguments(service, false /*checkForOnlyV2QueryParameters*/); } SegmentInfo openPropertySegment = segmentInfos.FirstOrDefault(p => p.TargetKind == RequestTargetKind.OpenProperty); if (openPropertySegment != null) { if (service.OperationContext.RequestMessage.HttpVerb == HttpVerbs.POST) { throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(openPropertySegment.Identifier)); } } } bool isCrossReferencingUri = segmentInfos.Count > 0 && path.FirstSegment is BatchReferenceSegment; ComposeExpressionForSegments(segmentInfos, service, isCrossReferencingUri); RequestTargetKind targetKind = (lastSegment == null) ? RequestTargetKind.ServiceDirectory : lastSegment.TargetKind; // Create a ResultDescription from the processed segments. RequestDescription resultDescription; Uri resultUri = GetResultUri(service.OperationContext); if (targetKind == RequestTargetKind.Metadata || targetKind == RequestTargetKind.Batch || targetKind == RequestTargetKind.ServiceDirectory) { resultDescription = new RequestDescription(targetKind, RequestTargetSource.None, resultUri); } else { Debug.Assert(lastSegment != null, "lastSegment != null"); Debug.Assert( targetKind == RequestTargetKind.ComplexObject || targetKind == RequestTargetKind.Collection || targetKind == RequestTargetKind.OpenProperty || targetKind == RequestTargetKind.OpenPropertyValue || targetKind == RequestTargetKind.Primitive || targetKind == RequestTargetKind.PrimitiveValue || targetKind == RequestTargetKind.Resource || targetKind == RequestTargetKind.MediaResource || targetKind == RequestTargetKind.VoidOperation || targetKind == RequestTargetKind.Link, "Unknown targetKind " + targetKind); resultDescription = new RequestDescription(segmentInfos, resultUri); // Only GET and PUT operations are allowed for a request uri refering to a named stream. // Note that we defer the same test for the default atom stream till later when we know if the instance type is an MLE. if (resultDescription.TargetKind == RequestTargetKind.MediaResource && resultDescription.IsNamedStream && service.OperationContext.RequestMessage.HttpVerb.IsChange() && service.OperationContext.RequestMessage.HttpVerb != HttpVerbs.PUT) { throw DataServiceException.CreateMethodNotAllowed( Strings.RequestUriProcessor_InvalidHttpMethodForNamedStream(UriUtil.UriToString(service.OperationContext.AbsoluteRequestUri), service.OperationContext.RequestMethod), DataServiceConfiguration.GetAllowedMethods(service.Configuration, resultDescription)); } } // apply the $count segment if it is present resultDescription.ApplyCountOption(service); resultDescription.DetermineWhetherResponseBodyOrETagShouldBeWritten(service.OperationContext.RequestMessage.HttpVerb); if (!internalQuery) { // Analyze client preferences resultDescription.AnalyzeClientPreference(service); // Determine whether the response will have a body based on verb and client preference resultDescription.DetermineWhetherResponseBodyShouldBeWritten(service.OperationContext.RequestMessage.HttpVerb); // Determine the response format resultDescription.DetermineResponseFormat(service); // Update the response version according to the features used by the request. resultDescription.UpdateVersion(service); // Handle $callback resultDescription.HandleCallbackQueryOption(service); } // In some cases, like CUD operations, we do not want to allow any query parameters to be specified. // But in V1, we didn't have this check hence we cannot fix this now. But we need to check only for // V2 query parameters and stop them if ((service.OperationContext.RequestMessage.HttpVerb.IsChange()) && resultDescription.SegmentInfos[0].TargetSource != RequestTargetSource.ServiceOperation) { WebUtil.CheckV2EmptyQueryArguments(service); } resultDescription.ParseExpandAndSelect(service); // Process query options ($filter, $orderby, $expand, etc.) resultDescription = RequestQueryProcessor.ProcessQuery(service, resultDescription); RequestUriProcessor.InvokeRequestExpression(resultDescription, service); resultDescription.Path = path; return resultDescription; }
/// <summary> /// Gets a string representation of allowed methods on the container (with the specified target cardinality), /// suitable for an 'Allow' header. /// </summary> /// <param name="configuration">configuration object which has the data</param> /// <param name="container">Targetted container, possibly null.</param> /// <param name="description">Description with target.</param> /// <returns>A value for an 'Allow' header; null if <paramref name="container"/> is null.</returns> internal static string GetAllowedMethods(DataServiceConfiguration configuration, ResourceSetWrapper container, RequestDescription description) { if (container == null) { return null; } System.Text.StringBuilder result = new System.Text.StringBuilder(); EntitySetRights rights = configuration.GetResourceSetRights(container.ResourceSet); if (description.IsSingleResult) { AppendRight(rights, EntitySetRights.ReadSingle, XmlConstants.HttpMethodGet, result); AppendRight(rights, EntitySetRights.WriteReplace, XmlConstants.HttpMethodPut, result); if (description.TargetKind != RequestTargetKind.MediaResource) { AppendRight(rights, EntitySetRights.WriteMerge, XmlConstants.HttpMethodPatch, result); AppendRight(rights, EntitySetRights.WriteDelete, XmlConstants.HttpMethodDelete, result); } } else { AppendRight(rights, EntitySetRights.ReadMultiple, XmlConstants.HttpMethodGet, result); AppendRight(rights, EntitySetRights.WriteAppend, XmlConstants.HttpMethodPost, result); } return result.ToString(); }
/// <summary>Initializes a new <see cref="ResponseBodyWriter"/> that can write the body of a response.</summary> /// <param name="service">Service for the request being processed.</param> /// <param name="queryResults">Enumerator for results.</param> /// <param name="requestDescription">Description of request made to the system.</param> /// <param name="actualResponseMessageWhoseHeadersMayBeOverridden">IODataResponseMessage instance for the response.</param> internal ResponseBodyWriter( IDataService service, QueryResultInfo queryResults, RequestDescription requestDescription, IODataResponseMessage actualResponseMessageWhoseHeadersMayBeOverridden) { Debug.Assert(service != null, "service != null"); Debug.Assert(requestDescription != null, "requestDescription != null"); Debug.Assert(actualResponseMessageWhoseHeadersMayBeOverridden != null, "actualResponseMessageWhoseHeadersMayBeOverridden != null"); this.service = service; this.queryResults = queryResults; this.requestDescription = requestDescription; this.actualResponseMessageWhoseHeadersMayBeOverridden = actualResponseMessageWhoseHeadersMayBeOverridden; Debug.Assert(this.PayloadKind != ODataPayloadKind.Unsupported, "payloadKind != ODataPayloadKind.Unsupported"); this.encoding = ContentTypeUtil.EncodingFromAcceptCharset(this.service.OperationContext.RequestMessage.GetRequestAcceptCharsetHeader()); if (this.PayloadKind == ODataPayloadKind.Entry || this.PayloadKind == ODataPayloadKind.Feed || this.PayloadKind == ODataPayloadKind.Property || this.PayloadKind == ODataPayloadKind.Collection || this.PayloadKind == ODataPayloadKind.EntityReferenceLink || this.PayloadKind == ODataPayloadKind.EntityReferenceLinks || this.PayloadKind == ODataPayloadKind.Error || this.PayloadKind == ODataPayloadKind.ServiceDocument || this.PayloadKind == ODataPayloadKind.Parameter) { AstoriaRequestMessage requestMessage = service.OperationContext.RequestMessage; IODataResponseMessage responseMessageOnOperationContext = service.OperationContext.ResponseMessage; Version effectiveMaxResponseVersion = VersionUtil.GetEffectiveMaxResponseVersion(service.Configuration.DataServiceBehavior.MaxProtocolVersion.ToVersion(), requestMessage.RequestMaxVersion); bool isEntityOrFeed = this.PayloadKind == ODataPayloadKind.Entry || this.PayloadKind == ODataPayloadKind.Feed; if (ContentTypeUtil.IsResponseMediaTypeJsonLight(requestMessage.GetAcceptableContentTypes(), isEntityOrFeed, effectiveMaxResponseVersion)) { // If JSON light 'wins', then bump the version to V3. requestDescription.VerifyAndRaiseResponseVersion(VersionUtil.Version4Dot0, service); responseMessageOnOperationContext.SetHeader(XmlConstants.HttpODataVersion, XmlConstants.ODataVersion4Dot0 + ";"); } } if (this.requestDescription.TargetKind == RequestTargetKind.MediaResource) { Debug.Assert(this.PayloadKind == ODataPayloadKind.BinaryValue, "payloadKind == ODataPayloadKind.BinaryValue"); // Note that GetReadStream will set the ResponseETag before it returns this.mediaResourceStream = service.StreamProvider.GetReadStream( this.queryResults.Current, this.requestDescription.StreamProperty, this.service.OperationContext); } else if (this.PayloadKind != ODataPayloadKind.BinaryValue) { IEdmModel model; if (this.PayloadKind == ODataPayloadKind.MetadataDocument) { model = MetadataSerializer.PrepareModelForSerialization(this.service.Provider, this.service.Configuration); } else { model = this.GetModelFromService(); } // Create the message writer using which the response needs to be written. this.messageWriterBuilder = MessageWriterBuilder.ForNormalRequest( this.service, this.requestDescription, this.actualResponseMessageWhoseHeadersMayBeOverridden, model); this.messageWriter = this.messageWriterBuilder.CreateWriter(); try { // Make sure all the headers are written before the method returns. this.contentFormat = ODataUtils.SetHeadersForPayload(this.messageWriter, this.PayloadKind); } catch (ODataContentTypeException contentTypeException) { throw new DataServiceException(415, null, Strings.DataServiceException_UnsupportedMediaType, null, contentTypeException); } Debug.Assert(requestDescription.ResponseFormat != null, "Response format should already have been determined."); Debug.Assert(ReferenceEquals(this.contentFormat, requestDescription.ResponseFormat.Format), "Response format in request description did not match format when writing."); if (this.PayloadKind == ODataPayloadKind.Value && !String.IsNullOrEmpty(this.requestDescription.MimeType)) { this.actualResponseMessageWhoseHeadersMayBeOverridden.SetHeader(XmlConstants.HttpContentType, this.requestDescription.MimeType); } // EPM is currently removed, but this doesn't seem to be used for EPM only. The old comment was saying: // In astoria, there is a bug in V1/V2 that while computing response version, we did not take // epm into account. Hence while creating the writer, we need to pass the RequestDescription.ActualResponseVersion // so that ODataLib can do the correct payload validation. But we need to write the response version without // the epm into the response headers because of backward-compat issue. Hence over-writing the response version // header with the wrong version value. string responseVersion = this.requestDescription.ResponseVersion.ToString() + ";"; this.actualResponseMessageWhoseHeadersMayBeOverridden.SetHeader(XmlConstants.HttpODataVersion, responseVersion); } }
/// <summary> /// Throws exceptions if the $select query option cannot be specified on this request. /// </summary> /// <param name="requestDescription">The request description.</param> private static void ValidateSelectIsAllowedForRequest(RequestDescription requestDescription) { // We only allow $select on entity/entityset queries. Queries which return a primitive/complex value don't support $select. if (requestDescription.TargetResourceType == null || (requestDescription.TargetResourceType.ResourceTypeKind != ResourceTypeKind.EntityType)) { throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QuerySelectOptionNotApplicable); } // $select can't be used on $ref URIs as it doesn't make sense if (requestDescription.SegmentInfos.Any(si => si.TargetKind == RequestTargetKind.Link)) { throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QuerySelectOptionNotApplicable); } }
/// <summary> /// Processes query arguments and returns a request description for /// the resulting query. /// </summary> /// <param name="service">Service with data and configuration information.</param> /// <param name="description">Description for request processed so far.</param> /// <returns>A new <see cref="RequestDescription"/>.</returns> internal static RequestDescription ProcessQuery(IDataService service, RequestDescription description) { Debug.Assert(service != null, "service != null"); // When the request doesn't produce an IQueryable result or it is a service action // we can short-circuit all further processing. if (description.RequestExpression == null || description.IsServiceActionRequest || !(typeof(IQueryable).IsAssignableFrom(description.RequestExpression.Type))) { WebUtil.CheckEmptyQueryArguments(service, false /*checkForOnlyV2QueryParameters*/); return description; } return new RequestQueryProcessor(service, description).ProcessQuery(); }