public void CreateODataErrorFromExceptionArgsShouldCreateODataErrorWithCustomAnnotationsAndInstanceAnnotations() { DataServiceException dse = new DataServiceException(500, "500", "Test message", "en-US", null); HandleExceptionArgs args = new HandleExceptionArgs(dse, responseWritten:false, contentType:"application/json",verboseResponse:true); ODataError error = args.CreateODataError(); error.InstanceAnnotations.As<object>().Should().BeSameAs(args.InstanceAnnotations); }
private static DataServiceException SerializeAndDeserializeDataServiceException(DataServiceException sut) { var bf = new BinaryFormatter(); var stream = new MemoryStream(); bf.Serialize(stream, sut); stream.Seek(0, SeekOrigin.Begin); return (DataServiceException)bf.Deserialize(stream); }
/// <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); }
public void When_DataServiceException_is_serialized_all_public_instance_data_is_saved() { // Arrange const int expectedStatusCode = 705; const string expectedErrorCode = "A47"; const string expectedMessage = "Server error."; const string expectedMessageLang = "en"; var sut = new DataServiceException( expectedStatusCode, expectedErrorCode, expectedMessage, expectedMessageLang, innerException: null); // Act var ds = SerializeAndDeserializeDataServiceException(sut); // Assert Assert.AreNotSame(sut, ds); Assert.AreEqual(sut.GetType(), ds.GetType()); Assert.AreEqual(expectedMessage, ds.Message); Assert.AreEqual(expectedStatusCode, ds.StatusCode); Assert.AreEqual(expectedErrorCode, ds.ErrorCode); Assert.AreEqual(expectedMessageLang, ds.MessageLanguage); }
/// <summary> /// update the request version header, if it is not specified. /// </summary> /// <param name="maxProtocolVersion">protocol version as specified in the config.</param> internal void InitializeRequestVersionHeaders(Version maxProtocolVersion) { if (this.requestVersionHeadersInitialized) { return; } this.requestVersionHeadersInitialized = true; Debug.Assert(this.requestVersion == null, "this.requestVersion == null"); Debug.Assert(this.RequestMaxVersion == null, "this.RequestMaxVersion == null"); Version maxRequestVersionAllowed = GetMaxRequestVersionAllowed(maxProtocolVersion); // read the request version headers from the underlying host this.requestVersionString = this.host.RequestVersion; this.RequestVersion = ValidateVersionHeader(XmlConstants.HttpODataVersion, this.requestVersionString); this.RequestMaxVersion = ValidateVersionHeader(XmlConstants.HttpODataMaxVersion, this.host.RequestMaxVersion); // If the request version is not specified. if (this.requestVersion == null) { // In request headers, if the OData-Version header is not specified and OData-MaxVersion is specified, // we should ideally set the OData-Version header to whatever the value was specified in OData-MaxVersion // header has. if (this.RequestMaxVersion != null) { // set the OData-Version to minimum of OData-MaxVersion and ProtocolVersion. this.RequestVersion = (this.RequestMaxVersion < maxProtocolVersion) ? this.RequestMaxVersion : maxProtocolVersion; } else { // If both request DSV and request MaxDSV is not specified, then set the request DSV to the MPV this.RequestVersion = maxProtocolVersion; } this.requestVersionString = this.RequestVersion.ToString(2); } else { if (this.RequestVersion > maxRequestVersionAllowed) { throw DataServiceException.CreateBadRequestError(Strings.DataService_RequestVersionMustBeLessThanMPV(this.RequestVersion, maxProtocolVersion)); } // Verify that the request DSV is a known version number. if (!VersionUtil.IsKnownRequestVersion(this.RequestVersion)) { string message = Strings.DataService_InvalidRequestVersion( this.RequestVersion.ToString(2), KnownODataVersionsToString(maxRequestVersionAllowed)); throw DataServiceException.CreateBadRequestError(message); } } // Initialize the request OData-MaxVersion if not specified. if (this.RequestMaxVersion == null) { this.RequestMaxVersion = maxProtocolVersion; } else if (this.RequestMaxVersion < VersionUtil.DataServiceDefaultResponseVersion) { // We need to make sure the MaxDSV is at least 1.0. This was the V1 behavior. // Verified that this was checked both in batch and non-batch cases. string message = Strings.DataService_MaxDSVTooLow( this.RequestMaxVersion.ToString(2), VersionUtil.DataServiceDefaultResponseVersion.Major, VersionUtil.DataServiceDefaultResponseVersion.Minor); throw DataServiceException.CreateBadRequestError(message); } }
/// <summary> /// Parse the given etag value in the If-Match request header. /// </summary> /// <param name="etagProperties">List of etag properties for the type whose etag values we are parsing.</param> /// <param name="ifMatchHeaderValue">value of the If-Match header as specified in the request.</param> /// <returns>returns the etag value as a list containing the property name and its corresponding value. If the If-Match header value is '*', then returns an empty collection.</returns> private static IEnumerable <KeyValuePair <string, object> > ParseETagValue(IList <ResourceProperty> etagProperties, string ifMatchHeaderValue) { Debug.Assert(etagProperties != null && etagProperties.Count != 0, "There must be atleast one etag property specified"); Debug.Assert(!String.IsNullOrEmpty(ifMatchHeaderValue), "IfMatch header cannot be null"); if (ifMatchHeaderValue == XmlConstants.HttpAnyETag) { // if the value is '*', then we return an empty IEnumerable. return(WebUtil.EmptyKeyValuePairStringObject); } Debug.Assert(ifMatchHeaderValue.StartsWith(XmlConstants.HttpWeakETagPrefix, StringComparison.Ordinal), "If-Match header must be properly formatted - this check is done in DataService.CheckETagValues method"); Debug.Assert(ifMatchHeaderValue.Length >= XmlConstants.HttpWeakETagPrefix.Length + 1, "If-Match header must be properly formatted - this check is done in DataService.CheckETagValues method"); // Just get the etag value - we need to ignore the 'W/"' and the last '"' character from the etag string strippedETag = ifMatchHeaderValue.Substring(XmlConstants.HttpWeakETagPrefix.Length, ifMatchHeaderValue.Length - XmlConstants.HttpWeakETagPrefix.Length - 1); IList <object> etagValues = null; bool success; Exception innerException = null; // In V1, when we didn't have IConcurrencyProvider interface, we always used to compute the // latest etag from the entity instance we got from IUpdatable.GetResource and then comparing // it with the If-Match request header. Hence all invalid cases always used to throw // DataServiceException with 412, since the etags didn't match. // In V1.5, we have added the support for IConcurrencyProvider, which means we need to parse // the etag values and parse it to the provider, if it has implement this interface. To avoid // breaking changes, we need to catch all parsing errors and report them as 412 instead of 400 // to avoid it from becoming a breaking change. try { success = SkipTokenAndETagParser.TryParseNullableTokens(Uri.UnescapeDataString(strippedETag), out etagValues); } catch (DataServiceException e) { success = false; innerException = e; } if (!success) { // We could have throwed BadRequest here since the etag value is not properly formattted. But since // we used to do throw 412 in V1, keeping it that way to avoid breaking change. throw DataServiceException.CreatePreConditionFailedError(Strings.Serializer_ETagValueDoesNotMatch, innerException); } if (etagValues.Count != etagProperties.Count) { // We could have throwed BadRequest here since the etag value is not properly formattted. But since // we used to do throw 412 in V1, keeping it that way to avoid breaking change. throw DataServiceException.CreatePreConditionFailedError(Strings.Serializer_ETagValueDoesNotMatch); } KeyValuePair <string, object>[] etagPropertyInfo = new KeyValuePair <string, object> [etagProperties.Count]; for (int i = 0; i < etagPropertyInfo.Length; i++) { ResourceProperty etagProperty = etagProperties[i]; object propertyValue = null; string value = (string)etagValues[i]; if (value != XmlConstants.NullLiteralInETag) { // The reason we need to catch the Overflow Exception here is: // In V1, when we didn't have IConcurrencyProvider interface, we always used to compute the // latest etag from the entity instance we got from IUpdatable.GetResource and then comparing // it with the If-Match request header. Hence all invalid cases always used to throw // DataServiceException with 412, since the etags didn't match. // In V1.5, we have added the support for IConcurrencyProvider, which means we need to parse // the etag values and parse it to the provider, if it has implement this interface. To avoid // breaking changes, we need to catch all parsing errors and report them as 412 instead of 400 // to avoid it from becoming a breaking change. try { success = LiteralParser.ForETags.TryParseLiteral(etagProperty.Type, value, out propertyValue); } catch (OverflowException e) { success = false; innerException = e; } if (!success) { // We could have throwed BadRequest here since the etag value is not properly formattted. But since // we used to do throw 412 in V1, keeping it that way to avoid breaking change. throw DataServiceException.CreatePreConditionFailedError(Strings.Serializer_ETagValueDoesNotMatch, innerException); } } etagPropertyInfo[i] = new KeyValuePair <string, object>(etagProperties[i].Name, propertyValue); } return(etagPropertyInfo); }
/// <summary> /// If the provider implements IConcurrencyProvider, then this method passes the etag values /// to the provider, otherwise compares the etag itself. /// </summary> /// <param name="resourceCookie">etag values for the given resource.</param> /// <param name="container">container for the given resource.</param> internal void SetETagValues(object resourceCookie, ResourceSetWrapper container) { Debug.Assert(resourceCookie != null, "resourceCookie != null"); Debug.Assert(container != null, "container != null"); AstoriaRequestMessage host = this.service.OperationContext.RequestMessage; Debug.Assert(String.IsNullOrEmpty(host.GetRequestIfNoneMatchHeader()), "IfNoneMatch header cannot be specified for Update/Delete operations"); // Resolve the cookie first to the actual resource type object actualEntity = this.ResolveResource(resourceCookie); Debug.Assert(actualEntity != null, "actualEntity != null"); ResourceType resourceType = WebUtil.GetNonPrimitiveResourceType(this.service.Provider, actualEntity); Debug.Assert(resourceType != null, "resourceType != null"); IList <ResourceProperty> etagProperties = this.service.Provider.GetETagProperties(container.Name, resourceType); if (etagProperties.Count == 0) { if (!String.IsNullOrEmpty(host.GetRequestIfMatchHeader())) { throw DataServiceException.CreateBadRequestError(Strings.Serializer_NoETagPropertiesForType); } // If the type has no etag properties, then we do not need to do any etag checks return; } // If the provider implements IConcurrencyProvider, then we need to call the provider // and pass the etag values. Else, we need to compare the etag values ourselves. IDataServiceUpdateProvider concurrencyProvider = this.updateProvider as IDataServiceUpdateProvider; if (concurrencyProvider != null) { bool?checkForEquality = null; IEnumerable <KeyValuePair <string, object> > etagValues; if (!String.IsNullOrEmpty(host.GetRequestIfMatchHeader())) { checkForEquality = true; etagValues = ParseETagValue(etagProperties, host.GetRequestIfMatchHeader()); } else { etagValues = WebUtil.EmptyKeyValuePairStringObject; } concurrencyProvider.SetConcurrencyValues(resourceCookie, checkForEquality, etagValues); } else if (String.IsNullOrEmpty(host.GetRequestIfMatchHeader())) { throw DataServiceException.CreateBadRequestError(Strings.DataService_CannotPerformOperationWithoutETag(resourceType.FullName)); } else if (host.GetRequestIfMatchHeader() != XmlConstants.HttpAnyETag) { // Compare If-Match header value with the current etag value, if the If-Match header value is not equal to '*' string etagValue = WebUtil.GetETagValue(resourceCookie, resourceType, etagProperties, this.service, false /*getMethod*/); Debug.Assert(!String.IsNullOrEmpty(etagValue), "etag value can never be null"); if (etagValue != host.GetRequestIfMatchHeader()) { throw DataServiceException.CreatePreConditionFailedError(Strings.Serializer_ETagValueDoesNotMatch); } } }
/// <summary> /// Parses a single error element from the reader /// </summary> /// <param name="reader">The reader to read from.</param> /// <returns>The exception parsed.</returns> private static Exception ParseInStreamSingleError(XmlReader reader) { string type = null; string stackTrace = null; string message = null; Exception innerException = null; reader.Read(); reader.Read(); while (!reader.EOF) { if (reader.NodeType != XmlNodeType.Element || reader.NamespaceURI != UnitTestsUtil.MetadataNamespace.NamespaceName) { reader.Read(); continue; } switch (reader.LocalName) { case "message": message = reader.ReadElementContentAsString(); break; case "type": type = reader.ReadElementContentAsString(); break; case "stacktrace": stackTrace = reader.ReadElementContentAsString(); break; case "innererror": innerException = ParseInStreamSingleError(reader.ReadSubtree()); break; default: reader.Skip(); break; } } Exception exception; switch (type) { case "System.InvalidOperationException": exception = new InvalidOperationException(message, innerException); break; default: exception = new DataServiceException(message, innerException); break; } exception.Data["StackTrace"] = stackTrace; return exception; }
/// <summary>Creates a new exception to indicate a syntax error.</summary> /// <param name="message">Plain text error message for this exception.</param> /// <returns>A new exception to indicate a syntax error.</returns> internal static DataServiceException CreateSyntaxError(string message) { return(DataServiceException.CreateBadRequestError(message)); }
/// <summary>Creates a new "Method Not Allowed" exception.</summary> /// <param name="message">Error message.</param> /// <param name="allow">String value for 'Allow' header in response.</param> /// <returns>A new exception to indicate the requested method is not allowed on the response.</returns> internal static DataServiceException CreateMethodNotAllowed(string message, string allow) { // 405 - Method Not Allowed DataServiceException result = new DataServiceException(405, message); result.state.ResponseAllowHeader = allow; return result; }
/// <summary>Creates a new "Bad Request" exception for recursion limit exceeded.</summary> /// <returns>A new exception to indicate that the request is rejected.</returns> internal static DataServiceException CreateDeepRecursion_General() { return(DataServiceException.CreateBadRequestError(Strings.BadRequest_DeepRecursion_General)); }
/// <summary>Creates a new "Bad Request" exception for recursion limit exceeded.</summary> /// <param name="recursionLimit">Recursion limit that was reaced.</param> /// <returns>A new exception to indicate that the request is rejected.</returns> internal static DataServiceException CreateDeepRecursion(int recursionLimit) { return(DataServiceException.CreateBadRequestError(Strings.BadRequest_DeepRecursion(recursionLimit))); }
/// <summary>Creates an <see cref="SegmentInfo"/> list for the given <paramref name="path"/>.</summary> /// <param name="path">Segments to process.</param> /// <returns>Segment information describing the given <paramref name="path"/>.</returns> internal IList <SegmentInfo> ConvertPath(ODataPath path) { Debug.Assert(path != null, "path != null"); SegmentInfo previous = null; List <SegmentInfo> segmentInfos = new List <SegmentInfo>(); bool crossReferencingUri = false; foreach (ODataPathSegment segment in path) { crossReferencingUri |= segment is BatchReferenceSegment; SegmentInfo segmentInfo; if (previous == null) { segmentInfo = this.CreateFirstSegment(segment); } else { ThrowIfMustBeLeafSegment(previous); var keySegment = segment as KeySegment; if (keySegment != null) { // All service operations other than those returning IQueryable MUST NOT have a key expression. if (!IsSegmentComposable(previous)) { throw DataServiceException.CreateBadRequestError(Strings.RequestUriProcessor_SegmentDoesNotSupportKeyPredicates(previous.Identifier)); } CheckSegmentIsComposable(previous); if (crossReferencingUri) { throw DataServiceException.CreateBadRequestError(Strings.BadRequest_ResourceCanBeCrossReferencedOnlyForBindOperation); } previous.SingleResult = true; previous.Key = keySegment; continue; } if (segment is NavigationPropertyLinkSegment) { segmentInfo = CreateEntityReferenceLinkSegment(previous); #if DEBUG segmentInfo.AssertValid(); #endif segmentInfos.Add(segmentInfo); previous = segmentInfo; } segmentInfo = this.CreateNextSegment(previous, segment); } Debug.Assert(segmentInfo != null, "segment != null"); #if DEBUG segmentInfo.AssertValid(); #endif segmentInfos.Add(segmentInfo); // we need to copy the segment over (even if it was an escape marker) as decisions will be made about it the next time through the loop. previous = segmentInfo; } return(segmentInfos); }
/// <summary>Creates the first <see cref="SegmentInfo"/> for a request.</summary> /// <param name="segment">The text of the segment.</param> /// <returns>A description of the information on the segment.</returns> private SegmentInfo CreateFirstSegment(ODataPathSegment segment) { Debug.Assert(segment != null, "identifier != null"); // Look for well-known system entry points. if (segment is MetadataSegment) { return(new SegmentInfo { Identifier = XmlConstants.UriMetadataSegment, TargetKind = RequestTargetKind.Metadata }); } if (segment is BatchSegment) { return(new SegmentInfo { Identifier = XmlConstants.UriBatchSegment, TargetKind = RequestTargetKind.Batch }); } if (segment is CountSegment) { // $count on root: throw throw DataServiceException.CreateResourceNotFound(Strings.RequestUriProcessor_CountOnRoot); } // Look for a service operation. OperationImportSegment serviceOperation = segment as OperationImportSegment; if (serviceOperation != null) { Debug.Assert(serviceOperation.OperationImports.Count() == 1, "Operation import segment should only ever have exactly one operation. Was a change made to how MetadataProviderEdmModel finds actions/service operations"); var operationImport = serviceOperation.OperationImports.Single(); var operation = ((MetadataProviderEdmOperationImport)operationImport).ServiceOperation; Debug.Assert(operation != null, "operation != null"); if (operation.Kind == OperationKind.ServiceOperation) { return(CreateSegmentForServiceOperation(operation)); } Debug.Assert(operation.Kind == OperationKind.Action, "serviceAction.Kind == OperationKind.Action"); return(this.CreateSegmentForServiceAction(null /*previousSegment*/, operation)); } var batchReferenceSegment = segment as BatchReferenceSegment; if (batchReferenceSegment != null) { SegmentInfo referencedSegmentInfo = this.crossReferenceCallback(batchReferenceSegment.ContentId); Debug.Assert(referencedSegmentInfo != null, "Could not find SegmentInfo for content-id: " + batchReferenceSegment.ContentId); referencedSegmentInfo.Identifier = batchReferenceSegment.ContentId; return(referencedSegmentInfo); } // Look for an entity set. EntitySetSegment entitySetSegment = segment as EntitySetSegment; if (entitySetSegment != null) { var container = ((IResourceSetBasedEdmEntitySet)entitySetSegment.EntitySet).ResourceSet; Debug.Assert(container != null, "container != null"); SegmentInfo segmentInfo = new SegmentInfo { Identifier = container.Name, TargetResourceSet = container, TargetResourceType = container.ResourceType, TargetSource = RequestTargetSource.EntitySet, TargetKind = RequestTargetKind.Resource, SingleResult = false }; return(segmentInfo); } WebUtil.CheckResourceExists(false, segment.ToString()); return(null); }
/// <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); }