/// <summary> /// Reads any next link annotation immediately after the end of a feed. /// </summary> /// <param name="feed">The feed being read.</param> /// <param name="expandedNavigationLinkInfo">The information about the expanded link. This must be non-null if we're reading an expanded feed, and must be null if we're reading a top-level feed.</param> /// <param name="duplicatePropertyNamesChecker">The top-level duplicate property names checker, if we're reading a top-level feed.</param> internal void ReadNextLinkAnnotationAtFeedEnd( ODataFeedBase feed, ODataJsonLightReaderNavigationLinkInfo expandedNavigationLinkInfo, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker) { Debug.Assert(feed != null, "feed != null"); // Check for annotations on the feed that occur after the feed itself. (Note: the only allowed one is odata.nextLink, and we fail for anything else.) // We do this slightly differently depending on whether the feed was an expanded navigation or a top-level feed. if (expandedNavigationLinkInfo != null) { this.ReadExpandedFeedAnnotationsAtFeedEnd(feed, expandedNavigationLinkInfo); } else { Debug.Assert(duplicatePropertyNamesChecker != null, "duplicatePropertyNamesChecker != null"); // Check for feed instance annotations that appear after the feed. bool isReordering = this.JsonReader is ReorderingJsonReader; this.ReadTopLevelFeedAnnotations(feed, duplicatePropertyNamesChecker, /*forFeedStart*/false, /*readAllFeedProperties*/isReordering); } }
/// <summary> /// Reads the value of a feed annotation (count or next link). /// </summary> /// <param name="annotationName">The name of the annotation found.</param> /// <param name="feed">The feed to read the annotation for; if non-null, the annotation value will be assigned to the feed.</param> /// <param name="duplicatePropertyNamesChecker">The duplicate property names checker instance.</param> /// <remarks> /// Pre-Condition: JsonNodeType.PrimitiveValue The value of the annotation /// Post-Condition: JsonNodeType.EndObject The end of the feed object /// JsonNodeType.Property The next annotation after the current annotation /// </remarks> internal void ReadAndApplyFeedInstanceAnnotationValue(string annotationName, ODataFeedBase feed, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker) { Debug.Assert(!string.IsNullOrEmpty(annotationName), "!string.IsNullOrEmpty(annotationName)"); Debug.Assert(feed != null, "feed != null"); switch (annotationName) { case ODataAnnotationNames.ODataCount: feed.Count = this.ReadAndValidateAnnotationAsLongForIeee754Compatible(ODataAnnotationNames.ODataCount); break; case ODataAnnotationNames.ODataNextLink: feed.NextPageLink = this.ReadAndValidateAnnotationStringValueAsUri(ODataAnnotationNames.ODataNextLink); break; case ODataAnnotationNames.ODataDeltaLink: feed.DeltaLink = this.ReadAndValidateAnnotationStringValueAsUri(ODataAnnotationNames.ODataDeltaLink); break; default: ODataAnnotationNames.ValidateIsCustomAnnotationName(annotationName); Debug.Assert( !this.MessageReaderSettings.ShouldSkipAnnotation(annotationName), "!this.MessageReaderSettings.ShouldReadAndValidateAnnotation(annotationName) -- otherwise we should have already skipped the custom annotation and won't see it here."); object instanceAnnotationValue = this.ReadCustomInstanceAnnotationValue(duplicatePropertyNamesChecker, annotationName); feed.InstanceAnnotations.Add(new ODataInstanceAnnotation(annotationName, instanceAnnotationValue.ToODataValue())); break; } }
/// <summary> /// Checks if there is a next link annotation immediately after an expanded feed, and reads and stores it if there is one. /// We fail here if we encounter any other property annotation for the expanded navigation (since these should come before the property itself). /// </summary> /// <param name="feed">The feed that was just read.</param> /// <param name="expandedNavigationLinkInfo">The information for the current expanded navigation link being read.</param> private void ReadExpandedFeedAnnotationsAtFeedEnd(ODataFeedBase feed, ODataJsonLightReaderNavigationLinkInfo expandedNavigationLinkInfo) { Debug.Assert(expandedNavigationLinkInfo != null, "expandedNavigationLinkInfo != null"); Debug.Assert(expandedNavigationLinkInfo.NavigationLink.IsCollection == true, "Only collection navigation properties can have feed content."); // Look at the next property in the owning entry, if it's a property annotation for the expanded navigation link info property, read it. string propertyName, annotationName; while (this.JsonReader.NodeType == JsonNodeType.Property && TryParsePropertyAnnotation(this.JsonReader.GetPropertyName(), out propertyName, out annotationName) && string.CompareOrdinal(propertyName, expandedNavigationLinkInfo.NavigationLink.Name) == 0) { if (!this.ReadingResponse) { throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedPropertyAnnotation(propertyName, annotationName)); } // Read over the property name. this.JsonReader.Read(); switch (annotationName) { case ODataAnnotationNames.ODataNextLink: if (feed.NextPageLink != null) { throw new ODataException(ODataErrorStrings.ODataJsonLightEntryAndFeedDeserializer_DuplicateExpandedFeedAnnotation(ODataAnnotationNames.ODataNextLink, expandedNavigationLinkInfo.NavigationLink.Name)); } // Read the property value. feed.NextPageLink = this.ReadAndValidateAnnotationStringValueAsUri(ODataAnnotationNames.ODataNextLink); break; case ODataAnnotationNames.ODataCount: if (feed.Count != null) { throw new ODataException(ODataErrorStrings.ODataJsonLightEntryAndFeedDeserializer_DuplicateExpandedFeedAnnotation(ODataAnnotationNames.ODataCount, expandedNavigationLinkInfo.NavigationLink.Name)); } // Read the property value. feed.Count = this.ReadAndValidateAnnotationAsLongForIeee754Compatible(ODataAnnotationNames.ODataCount); break; case ODataAnnotationNames.ODataDeltaLink: // Delta links are not supported on expanded feeds. default: throw new ODataException(ODataErrorStrings.ODataJsonLightEntryAndFeedDeserializer_UnexpectedPropertyAnnotationAfterExpandedFeed(annotationName, expandedNavigationLinkInfo.NavigationLink.Name)); } } }
/// <summary> /// Reads the feed instance annotations for a top-level feed. /// </summary> /// <param name="feed">The <see cref="ODataFeed"/> to read the instance annotations for.</param> /// <param name="duplicatePropertyNamesChecker">The duplicate property names checker for the top-level scope.</param> /// <param name="forFeedStart">true when parsing the instance annotations before the feed property; /// false when parsing the instance annotations after the feed property.</param> /// <param name="readAllFeedProperties">true if we should scan ahead for the annotations and ignore the actual data properties (used with /// the reordering reader); otherwise false.</param> internal void ReadTopLevelFeedAnnotations(ODataFeedBase feed, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, bool forFeedStart, bool readAllFeedProperties) { Debug.Assert(feed != null, "feed != null"); Debug.Assert(duplicatePropertyNamesChecker != null, "duplicatePropertyNamesChecker != null"); this.JsonReader.AssertNotBuffering(); bool buffering = false; try { while (this.JsonReader.NodeType == JsonNodeType.Property) { bool foundValueProperty = false; if (!forFeedStart && readAllFeedProperties) { // If this is not called for reading FeedStart and we already scanned ahead and processed all feed properties, we already checked for duplicate property names. // Use an empty duplicate property name checker since this.ParseProperty() read through the same property annotation of instance annotations again. duplicatePropertyNamesChecker = new DuplicatePropertyNamesChecker(/*allowDuplicateProperties*/ true, this.JsonLightInputContext.ReadingResponse, !this.JsonLightInputContext.MessageReaderSettings.EnableFullValidation); } this.ProcessProperty( duplicatePropertyNamesChecker, this.ReadTypePropertyAnnotationValue, (propertyParseResult, propertyName) => { switch (propertyParseResult) { case PropertyParsingResult.ODataInstanceAnnotation: case PropertyParsingResult.CustomInstanceAnnotation: // When we are reading the start of a feed (in scan-ahead mode or not) or when // we read the end of a feed and not in scan-ahead mode, read the value; // otherwise skip it. if (forFeedStart || !readAllFeedProperties) { this.ReadAndApplyFeedInstanceAnnotationValue(propertyName, feed, duplicatePropertyNamesChecker); } else { this.JsonReader.SkipValue(); } break; case PropertyParsingResult.PropertyWithValue: if (string.CompareOrdinal(JsonLightConstants.ODataValuePropertyName, propertyName) == 0) { // We found the feed property and are done parsing property annotations; // When we are in the mode where we scan ahead and read all feed properties // (for the reordering scenario), we have to start buffering and continue // reading. Otherwise we found the feed's data property and are done. if (readAllFeedProperties) { this.JsonReader.StartBuffering(); buffering = true; this.JsonReader.SkipValue(); } else { foundValueProperty = true; } } else { throw new ODataException(ODataErrorStrings.ODataJsonLightEntryAndFeedDeserializer_InvalidPropertyInTopLevelFeed(propertyName, JsonLightConstants.ODataValuePropertyName)); } break; case PropertyParsingResult.PropertyWithoutValue: // If we find a property without a value it means that we did not find the feed property (yet) // but an invalid property annotation throw new ODataException(ODataErrorStrings.ODataJsonLightEntryAndFeedDeserializer_InvalidPropertyAnnotationInTopLevelFeed(propertyName)); case PropertyParsingResult.EndOfObject: break; case PropertyParsingResult.MetadataReferenceProperty: if (!(feed is ODataFeed)) { throw new ODataException(ODataErrorStrings.ODataJsonLightPropertyAndValueDeserializer_UnexpectedMetadataReferenceProperty(propertyName)); } this.ReadMetadataReferencePropertyValue((ODataFeed)feed, propertyName); break; default: throw new ODataException(ODataErrorStrings.General_InternalError(InternalErrorCodes.ODataJsonLightEntryAndFeedDeserializer_ReadTopLevelFeedAnnotations)); } }); if (foundValueProperty) { return; } } } finally { if (buffering) { Debug.Assert(readAllFeedProperties, "Expect the reader to be in buffering mode only when scanning to the end."); this.JsonReader.StopBuffering(); } } if (forFeedStart && !readAllFeedProperties) { // We did not find any properties or only instance annotations. throw new ODataException(ODataErrorStrings.ODataJsonLightEntryAndFeedDeserializer_ExpectedFeedPropertyNotFound(JsonLightConstants.ODataValuePropertyName)); } }
/// <summary> /// Create an ODataFeed cloning from an ODataFeedBase. /// </summary> /// <param name="feedBase">The feed to be cloned.</param> /// <returns>The created feed.</returns> private static ODataFeed Clone(ODataFeedBase feedBase) { Debug.Assert(feedBase != null, "feedBase != null"); return new ODataFeed { Count = feedBase.Count, DeltaLink = feedBase.DeltaLink, Id = feedBase.Id, InstanceAnnotations = feedBase.InstanceAnnotations, NextPageLink = feedBase.NextPageLink }; }