diff --git a/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataContext.cs b/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataContext.cs
index a009a610f8..4aab88050e 100644
--- a/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataContext.cs
+++ b/src/Microsoft.OData.Core/Evaluation/ODataResourceMetadataContext.cs
@@ -166,41 +166,46 @@ internal static ODataResourceMetadataContext Create(
/// The resource instance.
/// The serialization info of the resource for writing without model.
/// The edm entity type of the resource
+ /// Whether key properties are required to be returned
/// Key value pair array
internal static KeyValuePair[] GetKeyProperties(
ODataResourceBase resource,
ODataResourceSerializationInfo serializationInfo,
- IEdmEntityType actualEntityType)
+ IEdmEntityType actualEntityType,
+ bool requiresId)
{
+ Debug.Assert(resource != null, "GetKeyProperties called for a null resource.");
+
KeyValuePair[] keyProperties = null;
- string actualEntityTypeName = null;
+ string actualEntityTypeName = string.IsNullOrEmpty(resource.TypeName) ? actualEntityType?.FullName() : resource.TypeName;
+ // if we have serializationInfo, try that first
if (serializationInfo != null)
{
- if (String.IsNullOrEmpty(resource.TypeName))
- {
- throw new ODataException(Strings.ODataResourceTypeContext_ODataResourceTypeNameMissing);
- }
-
- actualEntityTypeName = resource.TypeName;
keyProperties = ODataResourceMetadataContextWithoutModel.GetPropertiesBySerializationInfoPropertyKind(resource, ODataPropertyKind.Key, actualEntityTypeName);
}
- else
+
+ // if we didn't get any keys from serializationInfo, try using entity type
+ if ((keyProperties == null || keyProperties.Length == 0) && actualEntityType != null)
{
- keyProperties = GetPropertyValues(actualEntityType.Key(), resource, actualEntityType, /*isKeyProperty*/ true, /*isRequired*/ true).ToArray();
+ keyProperties = GetPropertyValues(actualEntityType.Key(), resource, actualEntityType, requiresId).ToArray();
+ }
+
+ if (!ValidateEntityTypeHasKeyProperties(keyProperties, actualEntityTypeName, requiresId))
+ {
+ return Enumerable.Empty>().ToArray();
}
- ValidateEntityTypeHasKeyProperties(keyProperties, actualEntityTypeName);
return keyProperties;
}
- private static IEnumerable> GetPropertyValues(IEnumerable properties, ODataResourceBase resource, IEdmEntityType actualEntityType, bool isKeyProperty, bool isRequired)
+ private static IEnumerable> GetPropertyValues(IEnumerable properties, ODataResourceBase resource, IEdmEntityType actualEntityType, bool isRequired)
{
string actualEntityTypeName = actualEntityType.FullName();
object primitiveValue;
foreach (IEdmStructuralProperty property in properties)
{
- if (TryGetPrimitiveOrEnumPropertyValue(resource, property.Name, actualEntityTypeName, isKeyProperty, isRequired, out primitiveValue))
+ if (TryGetPrimitiveOrEnumPropertyValue(resource, property.Name, actualEntityTypeName, isRequired, out primitiveValue))
{
yield return new KeyValuePair(property.Name, primitiveValue);
}
@@ -213,11 +218,10 @@ private static IEnumerable> GetPropertyValues(IEnum
/// The resource to get the property value.
/// Name of the property.
/// The name of the entity type to get the property value.
- /// true if the property is a key property, false otherwise.
+ /// true, if the property value is required to be non-null.
/// returned value, or null if no value is found.
- /// true, if the property value is required.
/// true, if the primitive value is found, otherwise false.
- private static bool TryGetPrimitiveOrEnumPropertyValue(ODataResourceBase resource, string propertyName, string entityTypeName, bool isKeyProperty, bool isRequired, out object value)
+ private static bool TryGetPrimitiveOrEnumPropertyValue(ODataResourceBase resource, string propertyName, string entityTypeName, bool isRequired, out object value)
{
Debug.Assert(resource != null, "resource != null");
@@ -235,7 +239,7 @@ private static bool TryGetPrimitiveOrEnumPropertyValue(ODataResourceBase resourc
}
}
- value = GetPrimitiveOrEnumPropertyValue(entityTypeName, property, isKeyProperty);
+ value = GetPrimitiveOrEnumPropertyValue(entityTypeName, property, isRequired);
return true;
}
@@ -244,12 +248,12 @@ private static bool TryGetPrimitiveOrEnumPropertyValue(ODataResourceBase resourc
///
/// The name of the entity type to get the property value.
/// The ODataProperty to get the value from.
- /// true if the property is a key property, false otherwise.
+ /// true if property must not be null, false otherwise.
/// The value of the property.
- private static object GetPrimitiveOrEnumPropertyValue(string entityTypeName, ODataProperty property, bool isKeyProperty)
+ private static object GetPrimitiveOrEnumPropertyValue(string entityTypeName, ODataProperty property, bool validateNotNull)
{
object propertyValue = property.Value;
- if (propertyValue == null && isKeyProperty)
+ if (propertyValue == null && validateNotNull)
{
throw new ODataException(Strings.ODataResourceMetadataContext_NullKeyValue(property.Name, entityTypeName));
}
@@ -267,13 +271,39 @@ private static object GetPrimitiveOrEnumPropertyValue(string entityTypeName, ODa
///
/// Key properties of the resource.
/// The entity type name of the resource.
- private static void ValidateEntityTypeHasKeyProperties(KeyValuePair[] keyProperties, string actualEntityTypeName)
+ /// Whether to throw if validation fails.
+ /// True, if validation succeeds, or false if validation fails.
+ private static bool ValidateEntityTypeHasKeyProperties(KeyValuePair[] keyProperties, string actualEntityTypeName, bool throwOnError)
{
- Debug.Assert(keyProperties != null, "keyProperties != null");
if (keyProperties == null || keyProperties.Length == 0)
{
- throw new ODataException(Strings.ODataResourceMetadataContext_EntityTypeWithNoKeyProperties(actualEntityTypeName));
+ if (throwOnError)
+ {
+ throw new ODataException(Strings.ODataResourceMetadataContext_EntityTypeWithNoKeyProperties(actualEntityTypeName));
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ for (int keyProperty = 0; keyProperty < keyProperties.Length; keyProperty++)
+ {
+ object keyValue = keyProperties[keyProperty].Value;
+ if (keyValue == null || (keyValue is ODataValue && !(keyValue is ODataEnumValue)))
+ {
+ if (throwOnError)
+ {
+ throw new ODataException(Strings.ODataResourceMetadataContext_NullKeyValue(keyProperties[keyProperty].Key, actualEntityTypeName));
+ }
+ else
+ {
+ return false;
+ }
+ }
}
+
+ return true;
}
///
@@ -288,14 +318,14 @@ private static KeyValuePair[] GetPropertiesBySerializationInfoPr
Debug.Assert(resource != null, "resource != null");
Debug.Assert(propertyKind == ODataPropertyKind.Key || propertyKind == ODataPropertyKind.ETag, "propertyKind == ODataPropertyKind.Key || propertyKind == ODataPropertyKind.ETag");
- List> properties = new List>(); ;
+ List> properties = new List>();
if (resource.NonComputedProperties != null)
{
foreach(ODataProperty property in resource.NonComputedProperties)
{
if(property.SerializationInfo != null && property.SerializationInfo.PropertyKind == propertyKind)
{
- properties.Add(new KeyValuePair(property.Name, GetPrimitiveOrEnumPropertyValue(actualEntityTypeName, property, propertyKind == ODataPropertyKind.Key)));
+ properties.Add(new KeyValuePair(property.Name, GetPrimitiveOrEnumPropertyValue(actualEntityTypeName, property, false)));
}
}
}
@@ -357,11 +387,8 @@ public override ICollection> KeyProperties
{
if (this.keyProperties == null)
{
- this.keyProperties = GetPropertiesBySerializationInfoPropertyKind(this.resource, ODataPropertyKind.Key, this.ActualResourceTypeName);
- if (this.requiresId)
- {
- ValidateEntityTypeHasKeyProperties(this.keyProperties, this.ActualResourceTypeName);
- }
+ IEdmEntityType entityType = this.ActualResourceType as IEdmEntityType;
+ this.keyProperties = GetKeyProperties(this.resource, this.serializationInfo, entityType, this.requiresId);
}
return this.keyProperties;
@@ -483,16 +510,12 @@ public override ICollection> KeyProperties
IEdmEntityType entityType = this.actualResourceType as IEdmEntityType;
if (entityType != null)
{
- this.keyProperties = keyProperties = GetPropertyValues(entityType.Key(), resource, entityType, /*isKeyProperty*/ true, this.requiresId).ToArray();
-
- if (this.requiresId)
- {
- ValidateEntityTypeHasKeyProperties(this.keyProperties, this.ActualResourceTypeName);
- }
+ this.keyProperties = GetPropertyValues(entityType.Key(), resource, entityType, this.requiresId).ToArray();
}
- else
+
+ if (!ValidateEntityTypeHasKeyProperties(this.keyProperties, this.ActualResourceTypeName, this.requiresId))
{
- this.keyProperties = Enumerable.Empty>().ToArray();
+ return Enumerable.Empty>().ToArray();
}
}
@@ -512,7 +535,7 @@ public override IEnumerable> ETagProperties
IEdmEntityType actualEntityType = this.actualResourceType as IEdmEntityType;
IEnumerable properties = this.ComputeETagPropertiesFromAnnotation();
this.etagProperties = properties.Any()
- ? GetPropertyValues(properties, resource, actualEntityType, /*isKeyProperty*/false, /*isRequired*/ true)
+ ? GetPropertyValues(properties, resource, actualEntityType, /*isRequired*/ false)
: EmptyProperties;
}
diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs
index a6235ace62..156a7b70d6 100644
--- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs
+++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs
@@ -2426,20 +2426,18 @@ private void StartNestedStreamInfo(ODataJsonLightReaderStreamInfo readerStreamIn
/// True if successfully append key segment.
private bool TryAppendEntitySetKeySegment(ref ODataPath odataPath)
{
- try
+ if (EdmExtensionMethods.HasKey(this.CurrentScope.NavigationSource, this.CurrentScope.ResourceType as IEdmStructuredType))
{
- if (EdmExtensionMethods.HasKey(this.CurrentScope.NavigationSource, this.CurrentScope.ResourceType as IEdmStructuredType))
+ IEdmEntityType currentEntityType = this.CurrentScope.ResourceType as IEdmEntityType;
+ ODataResourceBase resource = this.CurrentScope.Item as ODataResourceBase;
+ KeyValuePair[] keys = ODataResourceMetadataContext.GetKeyProperties(resource, null, currentEntityType, false);
+ if (keys.Length == 0)
{
- IEdmEntityType currentEntityType = this.CurrentScope.ResourceType as IEdmEntityType;
- ODataResourceBase resource = this.CurrentScope.Item as ODataResourceBase;
- KeyValuePair[] keys = ODataResourceMetadataContext.GetKeyProperties(resource, null, currentEntityType);
- odataPath = odataPath.AddKeySegment(keys, currentEntityType, this.CurrentScope.NavigationSource);
+ odataPath = null;
+ return false;
}
- }
- catch (ODataException)
- {
- odataPath = null;
- return false;
+
+ odataPath = odataPath.AddKeySegment(keys, currentEntityType, this.CurrentScope.NavigationSource);
}
return true;
diff --git a/src/Microsoft.OData.Core/ODataWriterCore.cs b/src/Microsoft.OData.Core/ODataWriterCore.cs
index 676af6a8c7..e690217dc0 100644
--- a/src/Microsoft.OData.Core/ODataWriterCore.cs
+++ b/src/Microsoft.OData.Core/ODataWriterCore.cs
@@ -1,4453 +1,4392 @@
-//---------------------------------------------------------------------
-//
-// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
-//
-//---------------------------------------------------------------------
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.OData.Edm;
-using Microsoft.OData.Evaluation;
-using Microsoft.OData.Metadata;
-using Microsoft.OData.UriParser;
-
-namespace Microsoft.OData
-{
- ///
- /// Base class for OData writers that verifies a proper sequence of write calls on the writer.
- ///
- internal abstract class ODataWriterCore : ODataWriter, IODataOutputInStreamErrorListener, IODataStreamListener
- {
- /// The writer validator to use.
- protected readonly IWriterValidator WriterValidator;
-
- /// The output context to write to.
- private readonly ODataOutputContext outputContext;
-
- /// True if the writer was created for writing a resourceSet; false when it was created for writing a resource.
- private readonly bool writingResourceSet;
-
- /// If not null, the writer will notify the implementer of the interface of relevant state changes in the writer.
- private readonly IODataReaderWriterListener listener;
-
- /// Stack of writer scopes to keep track of the current context of the writer.
- private readonly ScopeStack scopeStack = new ScopeStack();
-
- /// The number of entries which have been started but not yet ended.
- private int currentResourceDepth;
-
- ///
- /// Constructor.
- ///
- /// The output context to write to.
- /// The navigation source we are going to write resource set for.
- /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used).
- /// True if the writer is created for writing a resourceSet; false when it is created for writing a resource.
- /// True if the writer is created for writing a delta response; false otherwise. This parameter is ignored and will be removed in the next major release.
- /// If not null, the writer will notify the implementer of the interface of relevant state changes in the writer.
- protected ODataWriterCore(
- ODataOutputContext outputContext,
- IEdmNavigationSource navigationSource,
- IEdmStructuredType resourceType,
- bool writingResourceSet,
- bool writingDelta = false,
- IODataReaderWriterListener listener = null)
- {
- Debug.Assert(outputContext != null, "outputContext != null");
-
- this.outputContext = outputContext;
- this.writingResourceSet = writingResourceSet;
- this.WriterValidator = outputContext.WriterValidator;
- this.Version = outputContext.MessageWriterSettings.Version;
-
- if (navigationSource != null && resourceType == null)
- {
- resourceType = this.outputContext.EdmTypeResolver.GetElementType(navigationSource);
- }
-
- ODataUri odataUri = outputContext.MessageWriterSettings.ODataUri.Clone();
-
- // Remove key for top level resource
- if (!writingResourceSet && odataUri != null && odataUri.Path != null)
- {
- odataUri.Path = odataUri.Path.TrimEndingKeySegment();
- }
-
- this.listener = listener;
-
- this.scopeStack.Push(new Scope(WriterState.Start, /*item*/null, navigationSource, resourceType, /*skipWriting*/false, outputContext.MessageWriterSettings.SelectedProperties, odataUri, /*enableDelta*/ true));
- this.CurrentScope.DerivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationSource)?.ToList();
- }
-
- ///
- /// An enumeration representing the current state of the writer.
- ///
- internal enum WriterState
- {
- /// The writer is at the start; nothing has been written yet.
- Start,
-
- /// The writer is currently writing a resource.
- Resource,
-
- /// The writer is currently writing a resourceSet.
- ResourceSet,
-
- /// The writer is currently writing a delta resource set.
- DeltaResourceSet,
-
- /// The writer is currently writing a deleted resource.
- DeletedResource,
-
- /// The writer is currently writing a delta link.
- DeltaLink,
-
- /// The writer is currently writing a delta deleted link.
- DeltaDeletedLink,
-
- /// The writer is currently writing a nested resource info (possibly an expanded link but we don't know yet).
- ///
- /// This state is used when a nested resource info was started but we didn't see any children for it yet.
- ///
- NestedResourceInfo,
-
- /// The writer is currently writing a nested resource info with content.
- ///
- /// This state is used when a nested resource info with either an entity reference link or expanded resourceSet/resource was written.
- ///
- NestedResourceInfoWithContent,
-
- /// The writer is currently writing a primitive value.
- Primitive,
-
- /// The writer is currently writing a single property.
- Property,
-
- /// The writer is currently writing a stream value.
- Stream,
-
- /// The writer is currently writing a string value.
- String,
-
- /// The writer has completed; nothing can be written anymore.
- Completed,
-
- /// The writer is in error state; nothing can be written anymore.
- Error
- }
-
- ///
- /// OData Version being written.
- ///
- internal ODataVersion? Version { get; }
-
- ///
- /// The current scope for the writer.
- ///
- protected Scope CurrentScope
- {
- get
- {
- Debug.Assert(this.scopeStack.Count > 0, "We should have at least one active scope all the time.");
- return this.scopeStack.Peek();
- }
- }
-
- ///
- /// The current state of the writer.
- ///
- protected WriterState State
- {
- get
- {
- return this.CurrentScope.State;
- }
- }
-
- ///
- /// true if the writer should not write any input specified and should just skip it.
- ///
- protected bool SkipWriting
- {
- get
- {
- return this.CurrentScope.SkipWriting;
- }
- }
-
- ///
- /// A flag indicating whether the writer is at the top level.
- ///
- protected bool IsTopLevel
- {
- get
- {
- Debug.Assert(this.State != WriterState.Start && this.State != WriterState.Completed, "IsTopLevel should only be called while writing the payload.");
-
- // there is the root scope at the top (when the writer has not started or has completed)
- // and then the top-level scope (the top-level resource/resourceSet item) as the second scope on the stack
- return this.scopeStack.Count == 2;
- }
- }
-
- ///
- /// The scope level the writer is writing.
- ///
- protected int ScopeLevel
- {
- get { return this.scopeStack.Count; }
- }
-
- ///
- /// Returns the immediate parent link which is being expanded, or null if no such link exists
- ///
- protected ODataNestedResourceInfo ParentNestedResourceInfo
- {
- get
- {
- Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.ResourceSet || this.State == WriterState.DeltaResourceSet, "ParentNestedResourceInfo should only be called while writing a resource or a resourceSet.");
-
- Scope linkScope = this.scopeStack.ParentOrNull;
- return linkScope == null ? null : (linkScope.Item as ODataNestedResourceInfo);
- }
- }
-
- ///
- /// Returns the nested info that current resource belongs to.
- ///
- protected ODataNestedResourceInfo BelongingNestedResourceInfo
- {
- get
- {
- Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.ResourceSet || this.State == WriterState.DeletedResource || this.State == WriterState.DeltaResourceSet, "BelongingNestedResourceInfo should only be called while writing a (deleted) resource or a (delta) resourceSet.");
-
- Scope linkScope = this.scopeStack.ParentOrNull;
-
- // For single navigation
- if (linkScope is NestedResourceInfoScope)
- {
- return linkScope.Item as ODataNestedResourceInfo;
- }
- else if (linkScope is ResourceSetBaseScope)
- {
- // For resource under collection of navigation/complex, parent is ResourceSetScope, so we need find parent of parent.
- linkScope = this.scopeStack.ParentOfParent;
- return linkScope == null ? null : (linkScope.Item as ODataNestedResourceInfo);
- }
- else
- {
- return null;
- }
- }
- }
-
- ///
- /// Returns the resource type of the immediate parent resource for which a nested resource info is being written.
- ///
- protected IEdmStructuredType ParentResourceType
- {
- get
- {
- Debug.Assert(
- this.State == WriterState.NestedResourceInfo || this.State == WriterState.NestedResourceInfoWithContent,
- "ParentResourceType should only be called while writing a nested resource info (with or without content), or within an untyped ResourceSet.");
- Scope resourceScope = this.scopeStack.Parent;
- return resourceScope.ResourceType;
- }
- }
-
- ///
- /// Returns the navigation source of the immediate parent resource for which a nested resource info is being written.
- ///
- protected IEdmNavigationSource ParentResourceNavigationSource
- {
- get
- {
- Scope resourceScope = this.scopeStack.Parent;
- return resourceScope == null ? null : resourceScope.NavigationSource;
- }
- }
-
- ///
- /// Returns the parent scope of current scope.
- ///
- protected Scope ParentScope
- {
- get
- {
- Debug.Assert(this.scopeStack.Count > 1);
- return this.scopeStack.Parent;
- }
- }
-
- ///
- /// Returns the number of items seen so far on the current resource set scope.
- ///
- /// Can only be accessed on a resource set scope.
- protected int ResourceSetScopeResourceCount
- {
- get
- {
- Debug.Assert(this.State == WriterState.ResourceSet, "ResourceSetScopeResourceCount should only be called while writing a resource set.");
- return ((ResourceSetBaseScope)this.CurrentScope).ResourceCount;
- }
- }
-
- ///
- /// Checker to detect duplicate property names.
- ///
- protected IDuplicatePropertyNameChecker DuplicatePropertyNameChecker
- {
- get
- {
- Debug.Assert(
- this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.NestedResourceInfo || this.State == WriterState.NestedResourceInfoWithContent || this.State == WriterState.Property,
- "PropertyAndAnnotationCollector should only be called while writing a resource or an (expanded or deferred) nested resource info.");
-
- ResourceBaseScope resourceScope;
- switch (this.State)
- {
- case WriterState.DeletedResource:
- case WriterState.Resource:
- resourceScope = (ResourceBaseScope)this.CurrentScope;
- break;
- case WriterState.Property:
- case WriterState.NestedResourceInfo:
- case WriterState.NestedResourceInfoWithContent:
- resourceScope = (ResourceBaseScope)this.scopeStack.Parent;
- break;
- default:
- throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_PropertyAndAnnotationCollector));
- }
-
- return resourceScope.DuplicatePropertyNameChecker;
- }
- }
-
- ///
- /// The structured type of the current resource.
- ///
- protected IEdmStructuredType ResourceType
- {
- get
- {
- return this.CurrentScope.ResourceType;
- }
- }
-
- ///
- /// Returns the parent nested resource info scope of a resource in an expanded link (if it exists).
- /// The resource can either be the content of the expanded link directly or nested inside a resourceSet.
- ///
- /// The parent navigation scope of a resource in an expanded link (if it exists).
- protected NestedResourceInfoScope ParentNestedResourceInfoScope
- {
- get
- {
- Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.ResourceSet || this.State == WriterState.DeltaResourceSet, "ParentNestedResourceInfoScope should only be called while writing a resource or a resourceSet.");
- Debug.Assert(this.scopeStack.Count >= 2, "We should have at least the resource scope and the start scope on the stack.");
-
- Scope parentScope = this.scopeStack.Parent;
- if (parentScope.State == WriterState.Start)
- {
- // Top-level resource.
- return null;
- }
-
- if (parentScope.State == WriterState.ResourceSet || parentScope.State == WriterState.DeltaResourceSet)
- {
- Debug.Assert(this.scopeStack.Count >= 3, "We should have at least the resource scope, the resourceSet scope and the start scope on the stack.");
-
- // Get the resourceSet's parent
- parentScope = this.scopeStack.ParentOfParent;
- if (parentScope.State == WriterState.Start ||
- (parentScope.State == WriterState.ResourceSet &&
- parentScope.ResourceType != null &&
- parentScope.ResourceType.TypeKind == EdmTypeKind.Untyped))
- {
- // Top-level resourceSet, or resourceSet within an untyped resourceSet.
- return null;
- }
- }
-
- if (parentScope.State == WriterState.NestedResourceInfoWithContent)
- {
- // Get the scope of the nested resource info
- return (NestedResourceInfoScope)parentScope;
- }
-
- // The parent scope of a resource can only be a resourceSet or an expanded nav link
- throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_ParentNestedResourceInfoScope));
- }
- }
-
- ///
- /// Validator to validate consistency of collection items (or null if no such validator applies to the current scope).
- ///
- private ResourceSetWithoutExpectedTypeValidator CurrentResourceSetValidator
- {
- get
- {
- Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.Primitive, "CurrentCollectionValidator should only be called while writing a resource.");
-
- ResourceSetBaseScope resourceSetScope = this.ParentScope as ResourceSetBaseScope;
- return resourceSetScope == null ? null : resourceSetScope.ResourceTypeValidator;
- }
- }
-
- ///
- /// Flushes the write buffer to the underlying stream.
- ///
- public sealed override void Flush()
- {
- this.VerifyCanFlush(true);
-
- // Make sure we switch to writer state Error if an exception is thrown during flushing.
- try
- {
- this.FlushSynchronously();
- }
- catch
- {
- this.EnterScope(WriterState.Error, null);
- throw;
- }
- }
-
- ///
- /// Asynchronously flushes the write buffer to the underlying stream.
- ///
- /// A task instance that represents the asynchronous operation.
- public sealed override Task FlushAsync()
- {
- this.VerifyCanFlush(false);
-
- // Make sure we switch to writer state Error if an exception is thrown during flushing.
- return this.FlushAsynchronously().FollowOnFaultWith(t => this.EnterScope(WriterState.Error, null));
- }
-
- ///
- /// Start writing a resourceSet.
- ///
- /// Resource Set/collection to write.
- public sealed override void WriteStart(ODataResourceSet resourceSet)
- {
- this.VerifyCanWriteStartResourceSet(true, resourceSet);
- this.WriteStartResourceSetImplementation(resourceSet);
- }
-
- ///
- /// Asynchronously start writing a resourceSet.
- ///
- /// Resource Set/collection to write.
- /// A task instance that represents the asynchronous write operation.
- public sealed override async Task WriteStartAsync(ODataResourceSet resourceSet)
- {
- await this.VerifyCanWriteStartResourceSetAsync(false, resourceSet)
- .ConfigureAwait(false);
- await this.WriteStartResourceSetImplementationAsync(resourceSet)
- .ConfigureAwait(false);
- }
-
- ///
- /// Start writing a delta resource Set.
- ///
- /// Resource Set/collection to write.
- public sealed override void WriteStart(ODataDeltaResourceSet deltaResourceSet)
- {
- this.VerifyCanWriteStartDeltaResourceSet(true, deltaResourceSet);
- this.WriteStartDeltaResourceSetImplementation(deltaResourceSet);
- }
-
- ///
- /// Asynchronously start writing a delta resourceSet.
- ///
- /// Resource Set/collection to write.
- /// A task instance that represents the asynchronous write operation.
- public sealed override async Task WriteStartAsync(ODataDeltaResourceSet deltaResourceSet)
- {
- await this.VerifyCanWriteStartDeltaResourceSetAsync(false, deltaResourceSet)
- .ConfigureAwait(false);
- await this.WriteStartDeltaResourceSetImplementationAsync(deltaResourceSet)
- .ConfigureAwait(false);
- }
-
- ///
- /// Start writing a resource.
- ///
- /// Resource/item to write.
- public sealed override void WriteStart(ODataResource resource)
- {
- this.VerifyCanWriteStartResource(true, resource);
- this.WriteStartResourceImplementation(resource);
- }
-
- ///
- /// Asynchronously start writing a resource.
- ///
- /// Resource/item to write.
- /// A task instance that represents the asynchronous write operation.
- public sealed override async Task WriteStartAsync(ODataResource resource)
- {
- this.VerifyCanWriteStartResource(false, resource);
- await this.WriteStartResourceImplementationAsync(resource)
- .ConfigureAwait(false);
- }
-
- ///
- /// Start writing a delta deleted resource.
- ///
- /// The delta deleted resource to write.
- public sealed override void WriteStart(ODataDeletedResource deletedResource)
- {
- this.VerifyCanWriteStartDeletedResource(true, deletedResource);
- this.WriteStartDeletedResourceImplementation(deletedResource);
- }
-
- ///
- /// Asynchronously write a delta deleted resource.
- ///
- /// The delta deleted resource to write.
- /// A task instance that represents the asynchronous write operation.
- public sealed override async Task WriteStartAsync(ODataDeletedResource deletedResource)
- {
- this.VerifyCanWriteStartDeletedResource(false, deletedResource);
- await this.WriteStartDeletedResourceImplementationAsync(deletedResource)
- .ConfigureAwait(false);
- }
-
- ///
- /// Writing a delta link.
- ///
- /// The delta link to write.
- public override void WriteDeltaLink(ODataDeltaLink deltaLink)
- {
- this.VerifyCanWriteLink(true, deltaLink);
- this.WriteDeltaLinkImplementation(deltaLink);
- }
-
- ///
- /// Asynchronously writing a delta link.
- ///
- /// The delta link to write.
- /// A task instance that represents the asynchronous write operation.
- public override async Task WriteDeltaLinkAsync(ODataDeltaLink deltaLink)
- {
- this.VerifyCanWriteLink(false, deltaLink);
- await this.WriteDeltaLinkImplementationAsync(deltaLink)
- .ConfigureAwait(false);
- }
-
- ///
- /// Writing a delta deleted link.
- ///
- /// The delta link to write.
- public override void WriteDeltaDeletedLink(ODataDeltaDeletedLink deltaLink)
- {
- this.VerifyCanWriteLink(true, deltaLink);
- this.WriteDeltaLinkImplementation(deltaLink);
- }
-
- ///
- /// Asynchronously writing a delta link.
- ///
- /// The delta link to write.
- /// A task instance that represents the asynchronous write operation.
- public override async Task WriteDeltaDeletedLinkAsync(ODataDeltaDeletedLink deltaLink)
- {
- this.VerifyCanWriteLink(false, deltaLink);
- await this.WriteDeltaLinkImplementationAsync(deltaLink)
- .ConfigureAwait(false);
- }
-
- ///
- /// Write a primitive value within an untyped collection.
- ///
- /// Primitive value to write.
- public sealed override void WritePrimitive(ODataPrimitiveValue primitiveValue)
- {
- this.VerifyCanWritePrimitive(true, primitiveValue);
- this.WritePrimitiveValueImplementation(primitiveValue);
- }
-
- ///
- /// Asynchronously write a primitive value.
- ///
- /// Primitive value to write.
- /// A task instance that represents the asynchronous write operation.
- public sealed override async Task WritePrimitiveAsync(ODataPrimitiveValue primitiveValue)
- {
- this.VerifyCanWritePrimitive(false, primitiveValue);
- await this.WritePrimitiveValueImplementationAsync(primitiveValue)
- .ConfigureAwait(false);
- }
-
- /// Writes a primitive property within a resource.
- /// The primitive property to write.
- public sealed override void WriteStart(ODataPropertyInfo primitiveProperty)
- {
- this.VerifyCanWriteProperty(true, primitiveProperty);
- this.WriteStartPropertyImplementation(primitiveProperty);
- }
-
- /// Asynchronously write a primitive property within a resource.
- /// A task instance that represents the asynchronous write operation.
- /// The primitive property to write.
- public sealed override async Task WriteStartAsync(ODataPropertyInfo primitiveProperty)
- {
- this.VerifyCanWriteProperty(false, primitiveProperty);
- await this.WriteStartPropertyImplementationAsync(primitiveProperty)
- .ConfigureAwait(false);
- }
-
- /// Creates a stream for writing a binary value.
- /// A stream to write a binary value to.
- public sealed override Stream CreateBinaryWriteStream()
- {
- this.VerifyCanCreateWriteStream(true);
- return this.CreateWriteStreamImplementation();
- }
-
- /// Asynchronously creates a stream for writing a binary value.
- /// A task that represents the asynchronous operation.
- /// The value of the TResult parameter contains a to write a binary value to.
- public sealed override async Task CreateBinaryWriteStreamAsync()
- {
- this.VerifyCanCreateWriteStream(false);
- return await this.CreateWriteStreamImplementationAsync()
- .ConfigureAwait(false);
- }
-
- /// Creates a TextWriter for writing a string value.
- /// A TextWriter to write a string value to.
- public sealed override TextWriter CreateTextWriter()
- {
- this.VerifyCanCreateTextWriter(true);
- return this.CreateTextWriterImplementation();
- }
-
- /// Asynchronously creates a for writing a string value.
- /// A task that represents the asynchronous operation.
- /// The value of the TResult parameter contains a to write a string value to.
- public sealed override async Task CreateTextWriterAsync()
- {
- this.VerifyCanCreateWriteStream(false);
- return await this.CreateTextWriterImplementationAsync()
- .ConfigureAwait(false);
- }
-
- ///
- /// Start writing a nested resource info.
- ///
- /// Navigation link to write.
- public sealed override void WriteStart(ODataNestedResourceInfo nestedResourceInfo)
- {
- this.VerifyCanWriteStartNestedResourceInfo(true, nestedResourceInfo);
- this.WriteStartNestedResourceInfoImplementation(nestedResourceInfo);
- }
-
-
- ///
- /// Asynchronously start writing a nested resource info.
- ///
- /// Navigation link to writer.
- /// A task instance that represents the asynchronous write operation.
- public sealed override async Task WriteStartAsync(ODataNestedResourceInfo nestedResourceInfo)
- {
- this.VerifyCanWriteStartNestedResourceInfo(false, nestedResourceInfo);
- // Currently, no asynchronous operation is involved when commencing with writing a nested resource info
- await TaskUtils.GetTaskForSynchronousOperation(
- () => this.WriteStartNestedResourceInfoImplementation(nestedResourceInfo)).ConfigureAwait(false);
- }
-
- ///
- /// Finish writing a resourceSet/resource/nested resource info.
- ///
- public sealed override void WriteEnd()
- {
- this.VerifyCanWriteEnd(true);
- this.WriteEndImplementation();
- if (this.CurrentScope.State == WriterState.Completed)
- {
- // Note that we intentionally go through the public API so that if the Flush fails the writer moves to the Error state.
- this.Flush();
- }
- }
-
-
- ///
- /// Asynchronously finish writing a resourceSet/resource/nested resource info.
- ///
- /// A task instance that represents the asynchronous write operation.
- public sealed override async Task WriteEndAsync()
- {
- this.VerifyCanWriteEnd(false);
- await this.WriteEndImplementationAsync()
- .ConfigureAwait(false);
-
- if (this.CurrentScope.State == WriterState.Completed)
- {
- await this.FlushAsync()
- .ConfigureAwait(false);
- }
- }
-
- ///
- /// Writes an entity reference link, which is used to represent binding to an existing resource in a request payload.
- ///
- /// The entity reference link to write.
- ///
- /// This method can only be called for writing request messages. The entity reference link must be surrounded
- /// by a navigation link written through WriteStart/WriteEnd.
- /// The will be ignored in that case and the Uri from the will be used
- /// as the binding URL to be written.
- ///
- public sealed override void WriteEntityReferenceLink(ODataEntityReferenceLink entityReferenceLink)
- {
- this.VerifyCanWriteEntityReferenceLink(entityReferenceLink, true);
- this.WriteEntityReferenceLinkImplementation(entityReferenceLink);
- }
-
-
- ///
- /// Asynchronously writes an entity reference link, which is used to represent binding to an existing resource in a request payload.
- ///
- /// The entity reference link to write.
- /// A task instance that represents the asynchronous write operation.
- ///
- /// This method can only be called for writing request messages. The entity reference link must be surrounded
- /// by a navigation link written through WriteStart/WriteEnd.
- /// The will be ignored in that case and the Uri from the will be used
- /// as the binding URL to be written.
- ///
- public sealed override async Task WriteEntityReferenceLinkAsync(ODataEntityReferenceLink entityReferenceLink)
- {
- this.VerifyCanWriteEntityReferenceLink(entityReferenceLink, false);
- await this.WriteEntityReferenceLinkImplementationAsync(entityReferenceLink)
- .ConfigureAwait(false);
- }
-
- ///
- /// This method notifies the listener, that an in-stream error is to be written.
- ///
- ///
- /// This listener can choose to fail, if the currently written payload doesn't support in-stream error at this position.
- /// If the listener returns, the writer should not allow any more writing, since the in-stream error is the last thing in the payload.
- ///
- void IODataOutputInStreamErrorListener.OnInStreamError()
- {
- this.VerifyNotDisposed();
-
- // We're in a completed state trying to write an error (we can't write error after the payload was finished as it might
- // introduce another top-level element in XML)
- if (this.State == WriterState.Completed)
- {
- throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), WriterState.Error.ToString()));
- }
-
- this.StartPayloadInStartState();
- this.EnterScope(WriterState.Error, this.CurrentScope.Item);
- }
-
- ///
- async Task IODataOutputInStreamErrorListener.OnInStreamErrorAsync()
- {
- this.VerifyNotDisposed();
-
- // We're in a completed state trying to write an error (we can't write error after the payload was finished as it might
- // introduce another top-level element in XML)
- if (this.State == WriterState.Completed)
- {
- throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), WriterState.Error.ToString()));
- }
-
- await this.StartPayloadInStartStateAsync()
- .ConfigureAwait(false);
- this.EnterScope(WriterState.Error, this.CurrentScope.Item);
- }
-
- ///
- /// This method is called when a stream is requested. It is a no-op.
- ///
- void IODataStreamListener.StreamRequested()
- {
- }
-
- ///
- /// This method is called when an async stream is requested. It is a no-op.
- ///
- /// A task for method called when a stream is requested.
- Task IODataStreamListener.StreamRequestedAsync()
- {
- return TaskUtils.CompletedTask;
- }
-
- ///
- /// This method is called when a stream is disposed.
- ///
- void IODataStreamListener.StreamDisposed()
- {
- Debug.Assert(this.State == WriterState.Stream || this.State == WriterState.String, "Stream was disposed when not in WriterState.Stream state.");
-
- // Complete writing the stream
- if (this.State == WriterState.Stream)
- {
- this.EndBinaryStream();
- }
- else if (this.State == WriterState.String)
- {
- this.EndTextWriter();
- }
-
- this.LeaveScope();
- }
-
- ///
- /// This method is called asynchronously when a stream is disposed.
- ///
- /// A task that represents the asynchronous operation.
- async Task IODataStreamListener.StreamDisposedAsync()
- {
- Debug.Assert(this.State == WriterState.Stream || this.State == WriterState.String,
- "Stream was disposed when not in WriterState.Stream state.");
-
- // Complete writing the stream
- if (this.State == WriterState.Stream)
- {
- await this.EndBinaryStreamAsync()
- .ConfigureAwait(false);
- }
- else if (this.State == WriterState.String)
- {
- await this.EndTextWriterAsync()
- .ConfigureAwait(false);
- }
-
- await this.LeaveScopeAsync()
- .ConfigureAwait(false);
- }
-
- ///
- /// Get instance of the parent resource scope
- ///
- ///
- /// The parent resource scope
- /// Or null if there is no parent resource scope
- ///
- protected ResourceScope GetParentResourceScope()
- {
- ScopeStack scopeStack = new ScopeStack();
- Scope parentResourceScope = null;
-
- if (this.scopeStack.Count > 0)
- {
- // pop current scope and push into scope stack
- scopeStack.Push(this.scopeStack.Pop());
- }
-
- while (this.scopeStack.Count > 0)
- {
- Scope scope = this.scopeStack.Pop();
- scopeStack.Push(scope);
-
- if (scope is ResourceScope)
- {
- parentResourceScope = scope;
- break;
- }
- }
-
- while (scopeStack.Count > 0)
- {
- Scope scope = scopeStack.Pop();
- this.scopeStack.Push(scope);
- }
-
- return parentResourceScope as ResourceScope;
- }
-
- ///
- /// Determines whether a given writer state is considered an error state.
- ///
- /// The writer state to check.
- /// True if the writer state is an error state; otherwise false.
- protected static bool IsErrorState(WriterState state)
- {
- return state == WriterState.Error;
- }
-
- ///
- /// Check if the object has been disposed; called from all public API methods. Throws an ObjectDisposedException if the object
- /// has already been disposed.
- ///
- protected abstract void VerifyNotDisposed();
-
- ///
- /// Flush the output.
- ///
- protected abstract void FlushSynchronously();
-
-
- ///
- /// Flush the output.
- ///
- /// Task representing the pending flush operation.
- protected abstract Task FlushAsynchronously();
-
- ///
- /// Start writing an OData payload.
- ///
- protected abstract void StartPayload();
-
- ///
- /// Start writing a resource.
- ///
- /// The resource to write.
- protected abstract void StartResource(ODataResource resource);
-
- ///
- /// Finish writing a resource.
- ///
- /// The resource to write.
- protected abstract void EndResource(ODataResource resource);
-
- ///
- /// Start writing a single property.
- ///
- /// The property to write.
- protected virtual void StartProperty(ODataPropertyInfo property)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Finish writing a property.
- ///
- /// The property to write.
- protected virtual void EndProperty(ODataPropertyInfo property)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Start writing a resourceSet.
- ///
- /// The resourceSet to write.
- protected abstract void StartResourceSet(ODataResourceSet resourceSet);
-
- ///
- /// Start writing a delta resource set.
- ///
- /// The delta resource set to write.
- protected virtual void StartDeltaResourceSet(ODataDeltaResourceSet deltaResourceSet)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Start writing a deleted resource.
- ///
- /// The deleted entry to write.
- protected virtual void StartDeletedResource(ODataDeletedResource deletedEntry)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Write a delta link or delta deleted link.
- ///
- /// The deleted entry to write.
- protected virtual void StartDeltaLink(ODataDeltaLinkBase deltaLink)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Create a stream to write a binary value.
- ///
- /// A stream for writing the binary value.
- protected virtual Stream StartBinaryStream()
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Finish writing a stream.
- ///
- protected virtual void EndBinaryStream()
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Create a TextWriter to write a string value.
- ///
- /// A TextWriter for writing the string value.
- protected virtual TextWriter StartTextWriter()
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Finish writing a string value.
- ///
- protected virtual void EndTextWriter()
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Finish writing an OData payload.
- ///
- protected abstract void EndPayload();
-
- ///
- /// Finish writing a resourceSet.
- ///
- /// The resourceSet to write.
- protected abstract void EndResourceSet(ODataResourceSet resourceSet);
-
- ///
- /// Finish writing a delta resource set.
- ///
- /// The delta resource set to write.
- protected virtual void EndDeltaResourceSet(ODataDeltaResourceSet deltaResourceSet)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Finish writing a deleted resource.
- ///
- /// The delta resource set to write.
- protected virtual void EndDeletedResource(ODataDeletedResource deletedResource)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Write a primitive value within an untyped collection.
- ///
- /// The primitive value to write.
- protected virtual void WritePrimitiveValue(ODataPrimitiveValue primitiveValue)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Write a deferred (non-expanded) nested resource info.
- ///
- /// The nested resource info to write.
- protected abstract void WriteDeferredNestedResourceInfo(ODataNestedResourceInfo nestedResourceInfo);
-
- ///
- /// Start writing a nested resource info with content.
- ///
- /// The nested resource info to write.
- protected abstract void StartNestedResourceInfoWithContent(ODataNestedResourceInfo nestedResourceInfo);
-
- ///
- /// Finish writing a nested resource info with content.
- ///
- /// The nested resource info to write.
- protected abstract void EndNestedResourceInfoWithContent(ODataNestedResourceInfo nestedResourceInfo);
-
- ///
- /// Write an entity reference link into a navigation link content.
- ///
- /// The parent navigation link which is being written around the entity reference link.
- /// The entity reference link to write.
- protected abstract void WriteEntityReferenceInNavigationLinkContent(ODataNestedResourceInfo parentNestedResourceInfo, ODataEntityReferenceLink entityReferenceLink);
-
- ///
- /// Create a new resource set scope.
- ///
- /// The resource set for the new scope.
- /// The navigation source we are going to write resource set for.
- /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used).
- /// true if the content of the scope to create should not be written.
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- /// true if the resource set is for an undeclared property
- /// The newly create scope.
- protected abstract ResourceSetScope CreateResourceSetScope(ODataResourceSet resourceSet, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared);
-
- ///
- /// Create a new delta resource set scope.
- ///
- /// The delta resource set for the new scope.
- /// The navigation source we are going to write resource set for.
- /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used).
- /// true if the content of the scope to create should not be written.
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- /// true if the resource set is for an undeclared property
- /// The newly create scope.
- protected virtual DeltaResourceSetScope CreateDeltaResourceSetScope(ODataDeltaResourceSet deltaResourceSet, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Create a new resource scope.
- ///
- /// The resource for the new scope.
- /// The navigation source we are going to write resource set for.
- /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used).
- /// true if the content of the scope to create should not be written.
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- /// true if the resource is for an undeclared property
- /// The newly create scope.
- protected abstract ResourceScope CreateResourceScope(ODataResource resource, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared);
-
- ///
- /// Create a new resource scope.
- ///
- /// The (deleted) resource for the new scope.
- /// The navigation source we are going to write resource set for.
- /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used).
- /// true if the content of the scope to create should not be written.
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- /// true if the resource is for an undeclared property
- /// The newly create scope.
- protected virtual DeletedResourceScope CreateDeletedResourceScope(ODataDeletedResource resource, IEdmNavigationSource navigationSource, IEdmEntityType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Create a new property scope.
- ///
- /// The property for the new scope.
- /// The navigation source.
- /// The structured type for the resource containing the property to be written.
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- /// The newly created property scope.
- protected virtual PropertyInfoScope CreatePropertyInfoScope(ODataPropertyInfo property, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Create a new delta link scope.
- ///
- /// The link for the new scope.
- /// The navigation source we are going to write entities for.
- /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used).
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- /// The newly create scope.
- protected virtual DeltaLinkScope CreateDeltaLinkScope(ODataDeltaLinkBase link, IEdmNavigationSource navigationSource, IEdmEntityType entityType, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Gets the serialization info for the given resource.
- ///
- /// The resource to get the serialization info for.
- /// The serialization info for the given resource.
- protected ODataResourceSerializationInfo GetResourceSerializationInfo(ODataResourceBase resource)
- {
- // Need to check for null for the resource since we can be writing a null reference to a navigation property.
- ODataResourceSerializationInfo serializationInfo = resource == null ? null : resource.SerializationInfo;
-
- // Always try to use the serialization info from the resource first. If it is not found on the resource, use the one inherited from the parent resourceSet.
- // Note that we don't try to guard against inconsistent serialization info between entries and their parent resourceSet.
- if (serializationInfo != null)
- {
- return serializationInfo;
- }
-
- ODataResourceSetBase resourceSet = this.CurrentScope.Item as ODataResourceSetBase;
- if (resourceSet != null)
- {
- return resourceSet.SerializationInfo;
- }
-
- return null;
- }
-
- ///
- /// Gets the serialization info for the given delta link.
- ///
- /// The resource to get the serialization info for.
- /// The serialization info for the given resource.
- protected ODataResourceSerializationInfo GetLinkSerializationInfo(ODataItem item)
- {
- Debug.Assert(item != null, "item != null");
-
- ODataDeltaSerializationInfo deltaSerializationInfo = null;
- ODataResourceSerializationInfo resourceSerializationInfo = null;
-
- ODataDeltaLink deltaLink = item as ODataDeltaLink;
- if (deltaLink != null)
- {
- deltaSerializationInfo = deltaLink.SerializationInfo;
- }
-
- ODataDeltaDeletedLink deltaDeletedLink = item as ODataDeltaDeletedLink;
- if (deltaDeletedLink != null)
- {
- deltaSerializationInfo = deltaDeletedLink.SerializationInfo;
- }
-
- if (deltaSerializationInfo == null)
- {
- DeltaResourceSetScope parentDeltaResourceSetScope = this.CurrentScope as DeltaResourceSetScope;
- if (parentDeltaResourceSetScope != null)
- {
- ODataDeltaResourceSet resourceSet = (ODataDeltaResourceSet)parentDeltaResourceSetScope.Item;
- Debug.Assert(resourceSet != null, "resourceSet != null");
-
- ODataResourceSerializationInfo deltaSetSerializationInfo = resourceSet.SerializationInfo;
- if (deltaSetSerializationInfo != null)
- {
- resourceSerializationInfo = deltaSetSerializationInfo;
- }
- }
- }
- else
- {
- resourceSerializationInfo = new ODataResourceSerializationInfo()
- {
- NavigationSourceName = deltaSerializationInfo.NavigationSourceName
- };
- }
-
- return resourceSerializationInfo;
- }
-
- ///
- /// Creates a new nested resource info scope.
- ///
- /// The writer state for the new scope.
- /// The nested resource info for the new scope.
- /// The navigation source we are going to write entities for.
- /// The type for the items in the resourceSet to be written (or null if the resource set base type should be used).
- /// true if the content of the scope to create should not be written.
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- /// The newly created nested resource info scope.
- protected virtual NestedResourceInfoScope CreateNestedResourceInfoScope(
- WriterState writerState,
- ODataNestedResourceInfo navLink,
- IEdmNavigationSource navigationSource,
- IEdmType itemType,
- bool skipWriting,
- SelectedPropertiesNode selectedProperties,
- ODataUri odataUri)
- {
- Debug.Assert(this.CurrentScope != null, "Creating a nested resource info scope with a null parent scope.");
- return new NestedResourceInfoScope(writerState, navLink, navigationSource, itemType, skipWriting, selectedProperties, odataUri, this.CurrentScope);
- }
-
- ///
- /// Place where derived writers can perform custom steps before the resource is written, at the beginning of WriteStartEntryImplementation.
- ///
- /// The ResourceScope.
- /// Resource to write.
- /// True if writing response.
- /// The selected properties of this scope.
- protected virtual void PrepareResourceForWriteStart(ResourceScope resourceScope, ODataResource resource, bool writingResponse, SelectedPropertiesNode selectedProperties)
- {
- // No-op Atom and Verbose JSON. The JSON Light writer will override this method and inject the appropriate metadata builder
- // into the resource before writing.
- // Actually we can inject the metadata builder in here and
- // remove virtual from this method.
- }
-
- ///
- /// Place where derived writers can perform custom steps before the deleted resource is written, at the beginning of WriteStartEntryImplementation.
- ///
- /// The ResourceScope.
- /// Resource to write.
- /// True if writing response.
- /// The selected properties of this scope.
- protected virtual void PrepareDeletedResourceForWriteStart(DeletedResourceScope resourceScope, ODataDeletedResource deletedResource, bool writingResponse, SelectedPropertiesNode selectedProperties)
- {
- // No-op Atom and Verbose JSON. The JSON Light writer will override this method and inject the appropriate metadata builder
- // into the resource before writing.
- // Actually we can inject the metadata builder in here and
- // remove virtual from this method.
- }
-
- ///
- /// Gets the type of the resource and validates it against the model.
- ///
- /// The resource to get the type for.
- /// The validated structured type.
- protected IEdmStructuredType GetResourceType(ODataResourceBase resource)
- {
- return TypeNameOracle.ResolveAndValidateTypeFromTypeName(
- this.outputContext.Model,
- this.CurrentScope.ResourceType,
- resource.TypeName,
- this.WriterValidator);
- }
-
- ///
- /// Gets the element type of the resource set and validates it against the model.
- ///
- /// The resource set to get the element type for.
- /// The validated structured element type.
- protected IEdmStructuredType GetResourceSetType(ODataResourceSetBase resourceSet)
- {
- return TypeNameOracle.ResolveAndValidateTypeFromTypeName(
- this.outputContext.Model,
- this.CurrentScope.ResourceType,
- EdmLibraryExtensions.GetCollectionItemTypeName(resourceSet.TypeName),
- this.WriterValidator);
- }
-
- ///
- /// Validates that the ODataResourceSet.DeltaLink is null for the given expanded resourceSet.
- ///
- /// The expanded resourceSet in question.
- [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "An instance field is used in a debug assert.")]
- protected void ValidateNoDeltaLinkForExpandedResourceSet(ODataResourceSet resourceSet)
- {
- Debug.Assert(resourceSet != null, "resourceSet != null");
- Debug.Assert(
- this.ParentNestedResourceInfo != null && (!this.ParentNestedResourceInfo.IsCollection.HasValue || this.ParentNestedResourceInfo.IsCollection.Value == true),
- "This should only be called when writing an expanded resourceSet.");
-
- if (resourceSet.DeltaLink != null)
- {
- throw new ODataException(Strings.ODataWriterCore_DeltaLinkNotSupportedOnExpandedResourceSet);
- }
- }
-
- ///
- /// Asynchronously start writing an OData payload.
- ///
- /// A task that represents the asynchronous write operation.
- protected abstract Task StartPayloadAsync();
-
- ///
- /// Asynchronously finish writing an OData payload.
- ///
- /// A task that represents the asynchronous write operation.
- protected abstract Task EndPayloadAsync();
-
- ///
- /// Asynchronously start writing a resource.
- ///
- /// The resource to write.
- /// A task that represents the asynchronous write operation.
- protected abstract Task StartResourceAsync(ODataResource resource);
-
- ///
- /// Asynchronously finish writing a resource.
- ///
- /// The resource to write.
- /// A task that represents the asynchronous write operation.
- protected abstract Task EndResourceAsync(ODataResource resource);
-
- ///
- /// Asynchronously start writing a single property.
- ///
- /// The property to write.
- /// A task that represents the asynchronous write operation.
- protected virtual Task StartPropertyAsync(ODataPropertyInfo property)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Asynchronously finish writing a property.
- ///
- /// The property to write.
- /// A task that represents the asynchronous write operation.
- protected virtual Task EndPropertyAsync(ODataPropertyInfo property)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Asynchronously start writing a resourceSet.
- ///
- /// The resourceSet to write.
- /// A task that represents the asynchronous write operation.
- protected abstract Task StartResourceSetAsync(ODataResourceSet resourceSet);
-
- ///
- /// Asynchronously finish writing a resourceSet.
- ///
- /// The resourceSet to write.
- /// A task that represents the asynchronous write operation.
- protected abstract Task EndResourceSetAsync(ODataResourceSet resourceSet);
-
- ///
- /// Asynchronously start writing a delta resource set.
- ///
- /// The delta resource set to write.
- /// A task that represents the asynchronous write operation.
- protected virtual Task StartDeltaResourceSetAsync(ODataDeltaResourceSet deltaResourceSet)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Asynchronously finish writing a delta resource set.
- ///
- /// The delta resource set to write.
- /// A task that represents the asynchronous write operation.
- protected virtual Task EndDeltaResourceSetAsync(ODataDeltaResourceSet deltaResourceSet)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Asynchronously start writing a deleted resource.
- ///
- /// The deleted entry to write.
- /// A task that represents the asynchronous write operation.
- protected virtual Task StartDeletedResourceAsync(ODataDeletedResource deletedEntry)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Asynchronously finish writing a deleted resource.
- ///
- /// The delta resource set to write.
- /// A task that represents the asynchronous write operation.
- protected virtual Task EndDeletedResourceAsync(ODataDeletedResource deletedResource)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Asynchronously write a delta link or delta deleted link.
- ///
- /// The deleted entry to write.
- /// A task that represents the asynchronous write operation.
- protected virtual Task StartDeltaLinkAsync(ODataDeltaLinkBase deltaLink)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Asynchronously create a to write a binary value.
- ///
- /// A task that represents the asynchronous write operation.
- /// The value of the TResult parameter contains the for writing the binary value.
- protected virtual Task StartBinaryStreamAsync()
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Asynchronously finish writing a stream.
- ///
- /// A task that represents the asynchronous write operation.
- protected virtual Task EndBinaryStreamAsync()
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Asynchronously create a to write a string value.
- ///
- /// A task that represents the asynchronous operation.
- /// The value of the TResult parameter contains the for writing a string value.
- protected virtual Task StartTextWriterAsync()
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Asynchronously finish writing a string value.
- ///
- /// A task that represents the asynchronous write operation.
- protected virtual Task EndTextWriterAsync()
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Asynchronously write a primitive value within an untyped collection.
- ///
- /// The primitive value to write.
- /// A task that represents the asynchronous write operation.
- protected virtual Task WritePrimitiveValueAsync(ODataPrimitiveValue primitiveValue)
- {
- throw new NotImplementedException();
- }
-
- ///
- /// Asynchronously write a deferred (non-expanded) nested resource info.
- ///
- /// The nested resource info to write.
- /// A task that represents the asynchronous write operation.
- protected abstract Task WriteDeferredNestedResourceInfoAsync(ODataNestedResourceInfo nestedResourceInfo);
-
- ///
- /// Asynchronously start writing a nested resource info with content.
- ///
- /// The nested resource info to write.
- /// A task that represents the asynchronous write operation.
- protected abstract Task StartNestedResourceInfoWithContentAsync(ODataNestedResourceInfo nestedResourceInfo);
-
- ///
- /// Asynchronously finish writing a nested resource info with content.
- ///
- /// The nested resource info to write.
- /// A task that represents the asynchronous write operation.
- protected abstract Task EndNestedResourceInfoWithContentAsync(ODataNestedResourceInfo nestedResourceInfo);
-
- ///
- /// Asynchronously write an entity reference link into a navigation link content.
- ///
- /// The parent navigation link which is being written around the entity reference link.
- /// The entity reference link to write.
- /// A task that represents the asynchronous write operation.
- protected abstract Task WriteEntityReferenceInNavigationLinkContentAsync(
- ODataNestedResourceInfo parentNestedResourceInfo,
- ODataEntityReferenceLink entityReferenceLink);
-
- ///
- /// Place where derived writers can perform custom steps before the resource is written,
- /// at the beginning of WriteStartAsync(ODataResource).
- ///
- /// The ResourceScope.
- /// Resource to write.
- /// True if writing response.
- /// The selected properties of this scope.
- protected virtual Task PrepareResourceForWriteStartAsync(
- ResourceScope resourceScope,
- ODataResource resource,
- bool writingResponse,
- SelectedPropertiesNode selectedProperties)
- {
- // No-op Atom and Verbose JSON.
- // ODataJsonLightWriter will override this method and inject the appropriate metadata builder
- // into the resource before writing.
- return TaskUtils.CompletedTask;
- }
-
- ///
- /// Place where derived writers can perform custom steps before the deleted resource is written,
- /// at the beginning of WriteStartAsync(ODataDeletedResource).
- ///
- /// The ResourceScope.
- /// Resource to write.
- /// True if writing response.
- /// The selected properties of this scope.
- protected virtual Task PrepareDeletedResourceForWriteStartAsync(
- DeletedResourceScope resourceScope,
- ODataDeletedResource deletedResource,
- bool writingResponse,
- SelectedPropertiesNode selectedProperties)
- {
- // No-op Atom and Verbose JSON.
- // ODataJsonLightWriter will override this method and inject the appropriate metadata builder
- // into the resource before writing.
- return TaskUtils.CompletedTask;
- }
-
- ///
- /// Verifies that calling WriteStart resourceSet is valid.
- ///
- /// true if the call is to be synchronous; false otherwise.
- /// Resource Set/collection to write.
- private void VerifyCanWriteStartResourceSet(bool synchronousCall, ODataResourceSet resourceSet)
- {
- ExceptionUtils.CheckArgumentNotNull(resourceSet, "resourceSet");
-
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
- this.StartPayloadInStartState();
- }
-
- ///
- /// Start writing a resourceSet - implementation of the actual functionality.
- ///
- /// The resource set to write.
- private void WriteStartResourceSetImplementation(ODataResourceSet resourceSet)
- {
- this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.ResourceSet, resourceSet);
- this.EnterScope(WriterState.ResourceSet, resourceSet);
-
- if (!this.SkipWriting)
- {
- this.InterceptException(
- (thisParam, resourceSetParam) =>
- {
- // Verify query count
- if (resourceSetParam.Count.HasValue)
- {
- // Check that Count is not set for requests
- if (!thisParam.outputContext.WritingResponse)
- {
- thisParam.ThrowODataException(Strings.ODataWriterCore_QueryCountInRequest, resourceSetParam);
- }
-
- // Verify version requirements
- }
-
- thisParam.StartResourceSet(resourceSetParam);
- }, resourceSet);
- }
- }
-
- ///
- /// Verifies that calling WriteStart deltaResourceSet is valid.
- ///
- /// true if the call is to be synchronous; false otherwise.
- /// Resource Set/collection to write.
- private void VerifyCanWriteStartDeltaResourceSet(bool synchronousCall, ODataDeltaResourceSet deltaResourceSet)
- {
- ExceptionUtils.CheckArgumentNotNull(deltaResourceSet, "deltaResourceSet");
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
- this.StartPayloadInStartState();
- }
-
- ///
- /// Start writing a delta resource set - implementation of the actual functionality.
- ///
- /// The delta resource Set to write.
- private void WriteStartDeltaResourceSetImplementation(ODataDeltaResourceSet deltaResourceSet)
- {
- this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.ResourceSet, deltaResourceSet);
- this.EnterScope(WriterState.DeltaResourceSet, deltaResourceSet);
-
- this.InterceptException(
- (thisParam, deltaResourceSetParam) =>
- {
- // Check that links are not set for requests
- if (!thisParam.outputContext.WritingResponse)
- {
- if (deltaResourceSetParam.NextPageLink != null)
- {
- thisParam.ThrowODataException(Strings.ODataWriterCore_QueryNextLinkInRequest, deltaResourceSetParam);
- }
-
- if (deltaResourceSetParam.DeltaLink != null)
- {
- thisParam.ThrowODataException(Strings.ODataWriterCore_QueryDeltaLinkInRequest, deltaResourceSetParam);
- }
- }
-
- thisParam.StartDeltaResourceSet(deltaResourceSetParam);
- }, deltaResourceSet);
- }
-
- ///
- /// Verifies that calling WriteStart resource is valid.
- ///
- /// true if the call is to be synchronous; false otherwise.
- /// Resource/item to write.
- private void VerifyCanWriteStartResource(bool synchronousCall, ODataResource resource)
- {
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
- }
-
- ///
- /// Verifies that calling WriteDeletedResource is valid.
- ///
- /// true if the call is to be synchronous; false otherwise.
- /// Resource/item to write.
- private void VerifyCanWriteStartDeletedResource(bool synchronousCall, ODataDeletedResource resource)
- {
- ExceptionUtils.CheckArgumentNotNull(resource, "resource");
-
- this.VerifyWritingDelta();
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
- }
-
- ///
- /// Start writing a resource - implementation of the actual functionality.
- ///
- /// Resource/item to write.
- private void WriteStartResourceImplementation(ODataResource resource)
- {
- this.StartPayloadInStartState();
- this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.Resource, resource);
- this.EnterScope(WriterState.Resource, resource);
- if (!this.SkipWriting)
- {
- this.IncreaseResourceDepth();
- this.InterceptException(
- (thisParam, resourceParam) =>
- {
- if (resourceParam != null)
- {
- ResourceScope resourceScope = (ResourceScope)thisParam.CurrentScope;
- thisParam.ValidateResourceForResourceSet(resourceParam, resourceScope);
- thisParam.PrepareResourceForWriteStart(
- resourceScope,
- resourceParam,
- thisParam.outputContext.WritingResponse,
- resourceScope.SelectedProperties);
- }
-
- thisParam.StartResource(resourceParam);
- }, resource);
- }
- }
-
- ///
- /// Start writing a delta deleted resource - implementation of the actual functionality.
- ///
- /// Resource/item to write.
- private void WriteStartDeletedResourceImplementation(ODataDeletedResource resource)
- {
- Debug.Assert(resource != null, "resource != null");
-
- this.StartPayloadInStartState();
- this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.Resource, resource);
- this.EnterScope(WriterState.DeletedResource, resource);
- this.IncreaseResourceDepth();
-
- this.InterceptException(
- (thisParam, resourceParam) =>
- {
- DeletedResourceScope resourceScope = thisParam.CurrentScope as DeletedResourceScope;
- thisParam.ValidateResourceForResourceSet(resourceParam, resourceScope);
- thisParam.PrepareDeletedResourceForWriteStart(
- resourceScope,
- resourceParam,
- thisParam.outputContext.WritingResponse,
- resourceScope.SelectedProperties);
- thisParam.StartDeletedResource(resourceParam);
- }, resource);
- }
-
- ///
- /// Verifies that calling WriteStart for a property is valid.
- ///
- /// true if the call is to be synchronous; false otherwise.
- /// Primitive property to write.
- private void VerifyCanWriteProperty(bool synchronousCall, ODataPropertyInfo property)
- {
- ExceptionUtils.CheckArgumentNotNull(property, "property");
-
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
- }
-
- ///
- /// Start writing a property - implementation of the actual functionality.
- ///
- /// Property to write.
- private void WriteStartPropertyImplementation(ODataPropertyInfo property)
- {
- this.EnterScope(WriterState.Property, property);
- if (!this.SkipWriting)
- {
- this.InterceptException(
- (thisParam, propertyParam) =>
- {
- thisParam.StartProperty(propertyParam);
- if (propertyParam is ODataProperty)
- {
- PropertyInfoScope scope = thisParam.CurrentScope as PropertyInfoScope;
- Debug.Assert(scope != null, "Scope for ODataPropertyInfo is not ODataPropertyInfoScope");
- scope.ValueWritten = true;
- }
- }, property);
- }
- }
-
- ///
- /// Start writing a delta link or delta deleted link - implementation of the actual functionality.
- ///
- /// Delta (deleted) link to write.
- private void WriteDeltaLinkImplementation(ODataDeltaLinkBase deltaLink)
- {
- this.EnterScope(deltaLink is ODataDeltaLink ? WriterState.DeltaLink : WriterState.DeltaDeletedLink, deltaLink);
- this.StartDeltaLink(deltaLink);
- this.WriteEnd();
- }
-
- ///
- /// Start writing a delta link or delta deleted link - implementation of the actual functionality.
- ///
- /// Delta (deleted) link to write.
- /// The task.
- private async Task WriteDeltaLinkImplementationAsync(ODataDeltaLinkBase deltaLink)
- {
- EnterScope(deltaLink is ODataDeltaLink ? WriterState.DeltaLink : WriterState.DeltaDeletedLink, deltaLink);
- await this.StartDeltaLinkAsync(deltaLink)
- .ConfigureAwait(false);
- await this.WriteEndAsync()
- .ConfigureAwait(false);
- }
-
- ///
- /// Verifies that calling WriteStart nested resource info is valid.
- ///
- /// true if the call is to be synchronous; false otherwise.
- /// Navigation link to write.
- private void VerifyCanWriteStartNestedResourceInfo(bool synchronousCall, ODataNestedResourceInfo nestedResourceInfo)
- {
- ExceptionUtils.CheckArgumentNotNull(nestedResourceInfo, "nestedResourceInfo");
-
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
- }
-
- ///
- /// Start writing a nested resource info - implementation of the actual functionality.
- ///
- /// Navigation link to write.
- private void WriteStartNestedResourceInfoImplementation(ODataNestedResourceInfo nestedResourceInfo)
- {
- this.EnterScope(WriterState.NestedResourceInfo, nestedResourceInfo);
-
- // If the parent resource has a metadata builder, use that metadatabuilder on the nested resource info as well.
- Debug.Assert(this.scopeStack.Parent != null, "Navigation link scopes must have a parent scope.");
- Debug.Assert(this.scopeStack.Parent.Item is ODataResourceBase, "The parent of a nested resource info scope should always be a resource");
- ODataResourceBase parentResource = (ODataResourceBase)this.scopeStack.Parent.Item;
- if (parentResource.MetadataBuilder != null)
- {
- nestedResourceInfo.MetadataBuilder = parentResource.MetadataBuilder;
- }
- }
-
- ///
- /// Verifies that calling WritePrimitive is valid.
- ///
- /// true if the call is to be synchronous; false otherwise.
- /// Primitive value to write.
- private void VerifyCanWritePrimitive(bool synchronousCall, ODataPrimitiveValue primitiveValue)
- {
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
- }
-
- ///
- /// Write primitive value - implementation of the actual functionality.
- ///
- /// Primitive value to write.
- private void WritePrimitiveValueImplementation(ODataPrimitiveValue primitiveValue)
- {
- this.InterceptException(
- (thisParam, primitiveValueParam) =>
- {
- thisParam.EnterScope(WriterState.Primitive, primitiveValueParam);
- if (!(thisParam.CurrentResourceSetValidator == null) && primitiveValueParam != null)
- {
- Debug.Assert(primitiveValueParam.Value != null, "PrimitiveValue.Value should never be null!");
- IEdmType itemType = EdmLibraryExtensions.GetPrimitiveTypeReference(primitiveValueParam.Value.GetType()).Definition;
- thisParam.CurrentResourceSetValidator.ValidateResource(itemType);
- }
-
- thisParam.WritePrimitiveValue(primitiveValueParam);
- thisParam.WriteEnd();
- }, primitiveValue);
- }
-
- ///
- /// Write primitive value asynchronously - implementation of the actual functionality.
- ///
- /// Primitive value to write.
- /// The task.
- private async Task WritePrimitiveValueImplementationAsync(ODataPrimitiveValue primitiveValue)
- {
- EnterScope(WriterState.Primitive, primitiveValue);
-
- await InterceptExceptionAsync(
- async (thisParam, primiteValueParam) =>
- {
- if (!(CurrentResourceSetValidator == null) && primiteValueParam != null)
- {
- Debug.Assert(primiteValueParam.Value != null, "PrimitiveValue.Value should never be null!");
- IEdmType itemType = EdmLibraryExtensions.GetPrimitiveTypeReference(primiteValueParam.Value.GetType()).Definition;
- CurrentResourceSetValidator.ValidateResource(itemType);
- }
-
- await thisParam.WritePrimitiveValueAsync(primiteValueParam)
- .ConfigureAwait(false);
- await thisParam.WriteEndAsync()
- .ConfigureAwait(false);
- }, primitiveValue).ConfigureAwait(false);
- }
-
- ///
- /// Verifies that calling CreateWriteStream is valid.
- ///
- /// true if the call is to be synchronous; false otherwise.
- private void VerifyCanCreateWriteStream(bool synchronousCall)
- {
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
- }
-
- ///
- /// Create a write stream - implementation of the actual functionality.
- ///
- /// A stream for writing the binary value.
- private Stream CreateWriteStreamImplementation()
- {
- this.EnterScope(WriterState.Stream, null);
- return new ODataNotificationStream(this.StartBinaryStream(), this);
- }
-
- ///
- /// Verifies that calling CreateTextWriter is valid.
- ///
- /// true if the call is to be synchronous; false otherwise.
- private void VerifyCanCreateTextWriter(bool synchronousCall)
- {
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
- }
-
- ///
- /// Create a text writer - implementation of the actual functionality.
- ///
- /// A TextWriter for writing the string value.
- private TextWriter CreateTextWriterImplementation()
- {
- this.EnterScope(WriterState.String, null);
- return new ODataNotificationWriter(this.StartTextWriter(), this);
- }
-
- ///
- /// Verify that calling WriteEnd is valid.
- ///
- /// true if the call is to be synchronous; false otherwise.
- private void VerifyCanWriteEnd(bool synchronousCall)
- {
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
- }
-
- ///
- /// Finish writing a resourceSet/resource/nested resource info.
- ///
- private void WriteEndImplementation()
- {
- this.InterceptException(
- (thisParam) =>
- {
- bool wasenableDelta;
- Scope currentScope = thisParam.CurrentScope;
-
- switch (currentScope.State)
- {
- case WriterState.Resource:
- if (!thisParam.SkipWriting)
- {
- ODataResource resource = (ODataResource)currentScope.Item;
-
- thisParam.EndResource(resource);
- thisParam.DecreaseResourceDepth();
- }
-
- break;
- case WriterState.DeletedResource:
- if (!thisParam.SkipWriting)
- {
- ODataDeletedResource resource = (ODataDeletedResource)currentScope.Item;
-
- thisParam.EndDeletedResource(resource);
- thisParam.DecreaseResourceDepth();
- }
-
- break;
- case WriterState.ResourceSet:
- if (!thisParam.SkipWriting)
- {
- ODataResourceSet resourceSet = (ODataResourceSet)currentScope.Item;
- WriterValidationUtils.ValidateResourceSetAtEnd(resourceSet, !thisParam.outputContext.WritingResponse);
- thisParam.EndResourceSet(resourceSet);
- }
-
- break;
- case WriterState.DeltaLink:
- case WriterState.DeltaDeletedLink:
- break;
- case WriterState.DeltaResourceSet:
- if (!thisParam.SkipWriting)
- {
- ODataDeltaResourceSet deltaResourceSet = (ODataDeltaResourceSet)currentScope.Item;
- WriterValidationUtils.ValidateDeltaResourceSetAtEnd(deltaResourceSet, !thisParam.outputContext.WritingResponse);
- thisParam.EndDeltaResourceSet(deltaResourceSet);
- }
-
- break;
- case WriterState.NestedResourceInfo:
- if (!thisParam.outputContext.WritingResponse)
- {
- throw new ODataException(Strings.ODataWriterCore_DeferredLinkInRequest);
- }
-
- if (!thisParam.SkipWriting)
- {
- ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item;
- thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(link);
- thisParam.WriteDeferredNestedResourceInfo(link);
-
- thisParam.MarkNestedResourceInfoAsProcessed(link);
- }
-
- break;
- case WriterState.NestedResourceInfoWithContent:
- if (!thisParam.SkipWriting)
- {
- ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item;
- thisParam.EndNestedResourceInfoWithContent(link);
-
- thisParam.MarkNestedResourceInfoAsProcessed(link);
- }
-
- break;
- case WriterState.Property:
- {
- ODataPropertyInfo property = (ODataPropertyInfo)currentScope.Item;
- thisParam.EndProperty(property);
- }
-
- break;
- case WriterState.Primitive:
- // WriteEnd for WriterState.Primitive is a no-op; just leave scope
- break;
- case WriterState.Stream:
- case WriterState.String:
- throw new ODataException(Strings.ODataWriterCore_StreamNotDisposed);
- case WriterState.Start: // fall through
- case WriterState.Completed: // fall through
- case WriterState.Error: // fall through
- throw new ODataException(Strings.ODataWriterCore_WriteEndCalledInInvalidState(currentScope.State.ToString()));
- default:
- throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_WriteEnd_UnreachableCodePath));
- }
-
- thisParam.LeaveScope();
- });
- }
-
- ///
- /// Marks the navigation currently being written as processed in the parent entity's metadata builder.
- /// This is needed so that at the end of writing the resource we can query for all the unwritten navigation properties
- /// defined on the entity type and write out their metadata in fullmetadata mode.
- ///
- /// The nested resource info being written.
- private void MarkNestedResourceInfoAsProcessed(ODataNestedResourceInfo link)
- {
- Debug.Assert(
- this.CurrentScope.State == WriterState.NestedResourceInfo || this.CurrentScope.State == WriterState.NestedResourceInfoWithContent,
- "This method should only be called when we're writing a nested resource info.");
-
- ODataResourceBase parent = (ODataResourceBase)this.scopeStack.Parent.Item;
- Debug.Assert(parent.MetadataBuilder != null, "parent.MetadataBuilder != null");
- parent.MetadataBuilder.MarkNestedResourceInfoProcessed(link.Name);
- }
-
- ///
- /// Verifies that calling WriteEntityReferenceLink is valid.
- ///
- /// The entity reference link to write.
- /// true if the call is to be synchronous; false otherwise.
- private void VerifyCanWriteEntityReferenceLink(ODataEntityReferenceLink entityReferenceLink, bool synchronousCall)
- {
- ExceptionUtils.CheckArgumentNotNull(entityReferenceLink, "entityReferenceLink");
-
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
- }
-
- ///
- /// Verifies that calling Write(Deleted)DeltaLink is valid.
- ///
- /// true if the call is to be synchronous; false otherwise.
- /// Delta link to write.
- private void VerifyCanWriteLink(bool synchronousCall, ODataDeltaLinkBase deltaLink)
- {
- this.VerifyWritingDelta();
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
-
- ExceptionUtils.CheckArgumentNotNull(deltaLink, "deltaLink");
- }
-
- ///
- /// Write an entity reference link.
- ///
- /// The entity reference link to write.
- private void WriteEntityReferenceLinkImplementation(ODataEntityReferenceLink entityReferenceLink)
- {
- Debug.Assert(entityReferenceLink != null, "entityReferenceLink != null");
-
- this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.EntityReferenceLink, null);
- Debug.Assert(
- this.CurrentScope.Item is ODataNestedResourceInfo || this.ParentNestedResourceInfoScope.Item is ODataNestedResourceInfo,
- "The CheckForNestedResourceInfoWithContent should have verified that entity reference link can only be written inside a nested resource info.");
-
- if (!this.SkipWriting)
- {
- this.InterceptException(
- (thisParam, entityReferenceLinkParam) =>
- {
- WriterValidationUtils.ValidateEntityReferenceLink(entityReferenceLinkParam);
-
- ODataNestedResourceInfo nestedInfo = thisParam.CurrentScope.Item as ODataNestedResourceInfo;
- if (nestedInfo == null)
- {
- NestedResourceInfoScope nestedResourceInfoScope = thisParam.ParentNestedResourceInfoScope;
- Debug.Assert(nestedResourceInfoScope != null);
- nestedInfo = (ODataNestedResourceInfo)nestedResourceInfoScope.Item;
- }
-
- thisParam.WriteEntityReferenceInNavigationLinkContent(nestedInfo, entityReferenceLinkParam);
- }, entityReferenceLink);
- }
- }
-
- ///
- /// Verifies that calling Flush is valid.
- ///
- /// true if the call is to be synchronous; false otherwise.
- private void VerifyCanFlush(bool synchronousCall)
- {
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
- }
-
- ///
- /// Verifies that a call is allowed to the writer.
- ///
- /// true if the call is to be synchronous; false otherwise.
- private void VerifyCallAllowed(bool synchronousCall)
- {
- if (synchronousCall)
- {
- if (!this.outputContext.Synchronous)
- {
- throw new ODataException(Strings.ODataWriterCore_SyncCallOnAsyncWriter);
- }
- }
- else
- {
- if (this.outputContext.Synchronous)
- {
- throw new ODataException(Strings.ODataWriterCore_AsyncCallOnSyncWriter);
- }
- }
- }
-
- ///
- /// Verifies that the writer is writing within a delta resource set.
- ///
- private void VerifyWritingDelta()
- {
- if (!this.CurrentScope.EnableDelta)
- {
- throw new ODataException(Strings.ODataWriterCore_CannotWriteDeltaWithResourceSetWriter);
- }
- }
-
- ///
- /// Enters the 'Error' state and then throws an ODataException with the specified error message.
- ///
- /// The error message for the exception.
- /// The OData item to associate with the 'Error' state.
- private void ThrowODataException(string errorMessage, ODataItem item)
- {
- this.EnterScope(WriterState.Error, item);
- throw new ODataException(errorMessage);
- }
-
- ///
- /// Checks whether we are currently writing the first top-level element; if so call StartPayload
- ///
- private void StartPayloadInStartState()
- {
- if (this.State == WriterState.Start)
- {
- this.InterceptException((thisParam) => thisParam.StartPayload());
- }
- }
-
- ///
- /// Checks whether we are currently writing a nested resource info and switches to NestedResourceInfoWithContent state if we do.
- ///
- ///
- /// What kind of payload kind is being written as the content of a nested resource info.
- /// Only Resource Set, Resource or EntityReferenceLink are allowed.
- ///
- /// The ODataResource or ODataResourceSet to write, or null for ODataEntityReferenceLink.
- private void CheckForNestedResourceInfoWithContent(ODataPayloadKind contentPayloadKind, ODataItem contentPayload)
- {
- Debug.Assert(
- contentPayloadKind == ODataPayloadKind.ResourceSet || contentPayloadKind == ODataPayloadKind.Resource || contentPayloadKind == ODataPayloadKind.EntityReferenceLink,
- "Only ResourceSet, Resource or EntityReferenceLink can be specified as a payload kind for a nested resource info content.");
-
- Scope currentScope = this.CurrentScope;
- if (currentScope.State == WriterState.NestedResourceInfo || currentScope.State == WriterState.NestedResourceInfoWithContent)
- {
- ODataNestedResourceInfo currentNestedResourceInfo = (ODataNestedResourceInfo)currentScope.Item;
- this.InterceptException(
- (thisParam, currentNestedResourceInfoParam, contentPayloadKindParam) =>
- {
- if (thisParam.ParentResourceType != null)
- {
- IEdmStructuralProperty structuralProperty = thisParam.ParentResourceType.FindProperty(currentNestedResourceInfoParam.Name) as IEdmStructuralProperty;
- if (structuralProperty != null)
- {
- thisParam.CurrentScope.ItemType = structuralProperty.Type.Definition.AsElementType();
- IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource;
-
- thisParam.CurrentScope.NavigationSource = parentNavigationSource;
- }
- else
- {
- IEdmNavigationProperty navigationProperty =
- thisParam.WriterValidator.ValidateNestedResourceInfo(currentNestedResourceInfoParam, thisParam.ParentResourceType, contentPayloadKindParam);
- if (navigationProperty != null)
- {
- thisParam.CurrentScope.ResourceType = navigationProperty.ToEntityType();
- IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource;
-
- if (thisParam.CurrentScope.NavigationSource == null)
- {
- IEdmPathExpression bindingPath;
- thisParam.CurrentScope.NavigationSource = parentNavigationSource == null ?
- null :
- parentNavigationSource.FindNavigationTarget(navigationProperty, BindingPathHelper.MatchBindingPath, thisParam.CurrentScope.ODataUri.Path.Segments, out bindingPath);
- }
- }
- }
- }
- }, currentNestedResourceInfo, contentPayloadKind);
-
- if (currentScope.State == WriterState.NestedResourceInfoWithContent)
- {
- // If we are already in the NestedResourceInfoWithContent state, it means the caller is trying to write two items
- // into the nested resource info content. This is only allowed for collection navigation property in request/response.
- if (currentNestedResourceInfo.IsCollection != true)
- {
- this.ThrowODataException(Strings.ODataWriterCore_MultipleItemsInNestedResourceInfoWithContent, currentNestedResourceInfo);
- }
-
- // Note that we don't invoke duplicate property checker in this case as it's not necessary.
- // What happens inside the nested resource info was already validated by the condition above.
- // For collection in request we allow any combination anyway.
- // For everything else we only allow a single item in the content and thus we will fail above.
- }
- else
- {
- // we are writing a nested resource info with content; change the state
- this.PromoteNestedResourceInfoScope(contentPayload);
-
- if (!this.SkipWriting)
- {
- this.InterceptException(
- (thisParam, currentNestedResourceInfoParam) =>
- {
- if (!(currentNestedResourceInfoParam.SerializationInfo != null && currentNestedResourceInfoParam.SerializationInfo.IsComplex)
- && (thisParam.CurrentScope.ItemType == null || thisParam.CurrentScope.ItemType.IsEntityOrEntityCollectionType()))
- {
- thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(currentNestedResourceInfoParam);
- thisParam.StartNestedResourceInfoWithContent(currentNestedResourceInfoParam);
- }
- }, currentNestedResourceInfo);
- }
- }
- }
- else
- {
- if (contentPayloadKind == ODataPayloadKind.EntityReferenceLink)
- {
- Scope parenScope = this.ParentNestedResourceInfoScope;
- Debug.Assert(parenScope != null);
- if (parenScope.State != WriterState.NestedResourceInfo && parenScope.State != WriterState.NestedResourceInfoWithContent)
- {
- this.ThrowODataException(Strings.ODataWriterCore_EntityReferenceLinkWithoutNavigationLink, null);
- }
- }
- }
- }
-
- ///
- /// Verifies that the (deleted) resource has the correct type for the (delta) resource set.
- ///
- /// The resource to be validated.
- /// The scope for the resource to be validated.
- private void ValidateResourceForResourceSet(ODataResourceBase resource, ResourceBaseScope resourceScope)
- {
- IEdmStructuredType resourceType = GetResourceType(resource);
- NestedResourceInfoScope parentNestedResourceInfoScope = this.ParentNestedResourceInfoScope;
- if (parentNestedResourceInfoScope != null)
- {
- // Validate the consistency of resource types in the nested resourceSet/resource
- this.WriterValidator.ValidateResourceInNestedResourceInfo(resourceType, parentNestedResourceInfoScope.ResourceType);
- resourceScope.ResourceTypeFromMetadata = parentNestedResourceInfoScope.ResourceType;
-
- this.WriterValidator.ValidateDerivedTypeConstraint(resourceType, resourceScope.ResourceTypeFromMetadata,
- parentNestedResourceInfoScope.DerivedTypeConstraints, "property", ((ODataNestedResourceInfo)parentNestedResourceInfoScope.Item).Name);
- }
- else
- {
- resourceScope.ResourceTypeFromMetadata = this.ParentScope.ResourceType;
- if (this.CurrentResourceSetValidator != null)
- {
- if (this.ParentScope.State == WriterState.DeltaResourceSet
- && this.currentResourceDepth <= 1
- && resourceScope.NavigationSource != null)
- {
- // if the (deleted) resource is in the top level of a delta resource set, it doesn't
- // need to match the delta resource set, but must match the navigation source resolved for
- // the current scope
- if (!resourceScope.NavigationSource.EntityType().IsAssignableFrom(resourceType))
- {
- throw new ODataException(Strings.ResourceSetWithoutExpectedTypeValidator_IncompatibleTypes(resourceType.FullTypeName(), resourceScope.NavigationSource.EntityType()));
- }
-
- resourceScope.ResourceTypeFromMetadata = resourceScope.NavigationSource.EntityType();
- }
- else
- {
- // Validate the consistency of resource types
- this.CurrentResourceSetValidator.ValidateResource(resourceType);
- }
- }
-
- if (this.ParentScope.NavigationSource != null)
- {
- this.WriterValidator.ValidateDerivedTypeConstraint(resourceType, resourceScope.ResourceTypeFromMetadata,
- this.ParentScope.DerivedTypeConstraints, "navigation source", this.ParentScope.NavigationSource.Name);
- }
- }
-
- resourceScope.ResourceType = resourceType;
-
- // If writing in a delta resource set, the entity must have all key properties or the id set
- if (this.ParentScope.State == WriterState.DeltaResourceSet)
- {
- IEdmEntityType entityType = resourceType as IEdmEntityType;
- if (resource.Id == null &&
- entityType != null &&
- (this.outputContext.WritingResponse || resource is ODataDeletedResource) &&
- !HasKeyProperties(entityType, resource.Properties))
- {
- throw new ODataException(Strings.ODataWriterCore_DeltaResourceWithoutIdOrKeyProperties);
- }
- }
- }
-
- ///
- /// Determines whether a collection contains all key properties for a particular entity type.
- ///
- /// The entity type.
- /// The set of properties.
- /// True if the set of properties include all key properties for the entity type; otherwise false.
- private static bool HasKeyProperties(IEdmEntityType entityType, IEnumerable properties)
- {
- Debug.Assert(entityType != null, "entityType null");
- if (properties == null)
- {
- return false;
- }
-
- return entityType.Key().All(keyProp => properties.Select(p => p.Name).Contains(keyProp.Name));
- }
-
- ///
- /// Catch any exception thrown by the action passed in; in the exception case move the writer into
- /// state Error and then rethrow the exception.
- ///
- /// The action to execute.
- ///
- /// Make sure to only use anonymous functions that don't capture state from the enclosing context,
- /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method.
- ///
- private void InterceptException(Action action)
- {
- try
- {
- action(this);
- }
- catch
- {
- if (!IsErrorState(this.State))
- {
- this.EnterScope(WriterState.Error, this.CurrentScope.Item);
- }
-
- throw;
- }
- }
-
- ///
- /// Catch any exception thrown by the action passed in; in the exception case move the writer into
- /// state Error and then rethrow the exception.
- ///
- /// The action argument type.
- /// The action to execute.
- /// The argument value provided to the action.
- ///
- /// Make sure to only use anonymous functions that don't capture state from the enclosing context,
- /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method.
- ///
- private void InterceptException(Action action, TArg0 arg0)
- {
- try
- {
- action(this, arg0);
- }
- catch
- {
- if (!IsErrorState(this.State))
- {
- this.EnterScope(WriterState.Error, this.CurrentScope.Item);
- }
-
- throw;
- }
- }
-
- ///
- /// Catch any exception thrown by the action passed in; in the exception case move the writer into
- /// state Error and then rethrow the exception.
- ///
- /// The delegate first argument type.
- /// The delegate second argument type.
- /// The action to execute.
- /// The argument value provided to the action.
- /// The argument value provided to the action.
- ///
- /// Make sure to only use anonymous functions that don't capture state from the enclosing context,
- /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method.
- ///
- private void InterceptException(Action action, TArg0 arg0, TArg1 arg1)
- {
- try
- {
- action(this, arg0, arg1);
- }
- catch
- {
- if (!IsErrorState(this.State))
- {
- this.EnterScope(WriterState.Error, this.CurrentScope.Item);
- }
-
- throw;
- }
- }
-
- ///
- /// Catch any exception thrown by the action passed in; in the exception case move the writer into
- /// state Error and then rethrow the exception.
- ///
- /// The action to execute.
- /// The task.
- ///
- /// Make sure to only use anonymous functions that don't capture state from the enclosing context,
- /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method.
- ///
- private async Task InterceptExceptionAsync(Func action)
- {
- try
- {
- await action(this).ConfigureAwait(false);
- }
- catch
- {
- if (!IsErrorState(this.State))
- {
- this.EnterScope(WriterState.Error, this.CurrentScope.Item);
- }
-
- throw;
- }
- }
-
- ///
- /// Catch any exception thrown by the action passed in; in the exception case move the writer into
- /// state Error and then rethrow the exception.
- ///
- /// The action argument type.
- /// The action to execute.
- /// The argument value provided to the action.
- /// The task.
- ///
- /// Make sure to only use anonymous functions that don't capture state from the enclosing context,
- /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method.
- ///
- private async Task InterceptExceptionAsync(Func action, TArg0 arg0)
- {
- try
- {
- await action(this, arg0).ConfigureAwait(false);
- }
- catch
- {
- if (!IsErrorState(this.State))
- {
- this.EnterScope(WriterState.Error, this.CurrentScope.Item);
- }
-
- throw;
- }
- }
-
- ///
- /// Increments the nested resource count by one and fails if the new value exceeds the maximum nested resource depth limit.
- ///
- private void IncreaseResourceDepth()
- {
- this.currentResourceDepth++;
-
- if (this.currentResourceDepth > this.outputContext.MessageWriterSettings.MessageQuotas.MaxNestingDepth)
- {
- this.ThrowODataException(Strings.ValidationUtils_MaxDepthOfNestedEntriesExceeded(this.outputContext.MessageWriterSettings.MessageQuotas.MaxNestingDepth), null);
- }
- }
-
- ///
- /// Decrements the nested resource count by one.
- ///
- private void DecreaseResourceDepth()
- {
- Debug.Assert(this.currentResourceDepth > 0, "Resource depth should never become negative.");
-
- this.currentResourceDepth--;
- }
-
-
- ///
- /// Notifies the implementer of the interface of relevant state changes in the writer.
- ///
- /// The new writer state.
- private void NotifyListener(WriterState newState)
- {
- if (this.listener != null)
- {
- if (IsErrorState(newState))
- {
- this.listener.OnException();
- }
- else if (newState == WriterState.Completed)
- {
- this.listener.OnCompleted();
- }
- }
- }
-
- ///
- /// Enter a new writer scope; verifies that the transition from the current state into new state is valid
- /// and attaches the item to the new scope.
- ///
- /// The writer state to transition into.
- /// The item to associate with the new scope.
- [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug only cast.")]
- private void EnterScope(WriterState newState, ODataItem item)
- {
- this.InterceptException((thisParam, newStateParam) => thisParam.ValidateTransition(newStateParam), newState);
-
- // If the parent scope was marked for skipping content, the new child scope should be as well.
- bool skipWriting = this.SkipWriting;
-
- Scope currentScope = this.CurrentScope;
-
- IEdmNavigationSource navigationSource = null;
- IEdmType itemType = null;
- SelectedPropertiesNode selectedProperties = currentScope.SelectedProperties;
- ODataUri odataUri = currentScope.ODataUri.Clone();
- if (odataUri.Path == null)
- {
- odataUri.Path = new ODataPath();
- }
-
- IEnumerable derivedTypeConstraints = null;
-
- WriterState currentState = currentScope.State;
-
- if (newState == WriterState.Resource || newState == WriterState.ResourceSet || newState == WriterState.Primitive || newState == WriterState.DeltaResourceSet || newState == WriterState.DeletedResource)
- {
- // if we're in a DeltaResourceSet and writing a resource or deleted resource then the parent may not be the navigation source
- ODataResourceBase resource = item as ODataResourceBase;
- if (resource != null)
- {
- IEdmModel model = this.outputContext.Model;
- if (model != null && model.IsUserModel())
- {
- try
- {
- string typeNameFromResource = resource.TypeName;
- if (!String.IsNullOrEmpty(typeNameFromResource))
- {
- // try resolving type from resource TypeName
- itemType = TypeNameOracle.ResolveAndValidateTypeName(
- model,
- typeNameFromResource,
- EdmTypeKind.None,
- /* expectStructuredType */ true,
- this.outputContext.WriterValidator);
- }
-
- // Try resolving navigation source from serialization info.
- ODataResourceSerializationInfo serializationInfo = resource.SerializationInfo;
- if (serializationInfo != null)
- {
- if (serializationInfo.NavigationSourceName != null)
- {
- ODataUriParser uriParser = new ODataUriParser(model, new Uri(serializationInfo.NavigationSourceName, UriKind.Relative), this.outputContext.Container);
- odataUri = uriParser.ParseUri();
- navigationSource = odataUri.Path.NavigationSource();
- itemType = itemType ?? navigationSource.EntityType();
- }
-
- if (typeNameFromResource == null)
- {
- // Try resolving entity type from SerializationInfo
- if (!string.IsNullOrEmpty(serializationInfo.ExpectedTypeName))
- {
- itemType = TypeNameOracle.ResolveAndValidateTypeName(
- model,
- serializationInfo.ExpectedTypeName,
- EdmTypeKind.None,
- /* expectStructuredType */ true,
- this.outputContext.WriterValidator);
- }
- else if (!string.IsNullOrEmpty(serializationInfo.NavigationSourceEntityTypeName))
- {
- itemType = TypeNameOracle.ResolveAndValidateTypeName(
- model,
- serializationInfo.NavigationSourceEntityTypeName,
- EdmTypeKind.Entity,
- /* expectStructuredType */ true,
- this.outputContext.WriterValidator);
- }
- }
- }
- }
- catch (ODataException)
- {
- // SerializationInfo doesn't match model.
- // This should be an error but, for legacy reasons, we ignore this.
- }
- }
- }
-
- if (navigationSource == null)
- {
- derivedTypeConstraints = currentScope.DerivedTypeConstraints;
- }
- else
- {
- derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationSource);
- }
-
- navigationSource = navigationSource ?? currentScope.NavigationSource;
- itemType = itemType ?? currentScope.ItemType;
-
- // This is to resolve the item type for a resource set for an undeclared nested resource info.
- if (itemType == null
- && (currentState == WriterState.Start || currentState == WriterState.NestedResourceInfo || currentState == WriterState.NestedResourceInfoWithContent)
- && (newState == WriterState.ResourceSet || newState == WriterState.DeltaResourceSet))
- {
- ODataResourceSetBase resourceSet = item as ODataResourceSetBase;
- if (resourceSet != null && resourceSet.TypeName != null && this.outputContext.Model.IsUserModel())
- {
- IEdmCollectionType collectionType = TypeNameOracle.ResolveAndValidateTypeName(
- this.outputContext.Model,
- resourceSet.TypeName,
- EdmTypeKind.Collection,
- false,
- this.outputContext.WriterValidator) as IEdmCollectionType;
-
- if (collectionType != null)
- {
- itemType = collectionType.ElementType.Definition;
- }
- }
- }
- }
-
- // When writing a nested resource info, check if the link is being projected.
- // If we are projecting properties, but the nav. link is not projected mark it to skip its content.
- if ((currentState == WriterState.Resource || currentState == WriterState.DeletedResource) && newState == WriterState.NestedResourceInfo)
- {
- Debug.Assert(currentScope.Item is ODataResourceBase, "If the current state is Resource the current Item must be resource as well (and not null either).");
- Debug.Assert(item is ODataNestedResourceInfo, "If the new state is NestedResourceInfo the new item must be a nested resource info as well (and not null either).");
- ODataNestedResourceInfo nestedResourceInfo = (ODataNestedResourceInfo)item;
-
- if (!skipWriting)
- {
- selectedProperties = currentScope.SelectedProperties.GetSelectedPropertiesForNavigationProperty(currentScope.ResourceType, nestedResourceInfo.Name);
-
- ODataPath odataPath = odataUri.Path;
- IEdmStructuredType currentResourceType = currentScope.ResourceType;
-
- ResourceBaseScope resourceScope = currentScope as ResourceBaseScope;
- TypeSegment resourceTypeCast = null;
- if (resourceScope.ResourceTypeFromMetadata != currentResourceType)
- {
- resourceTypeCast = new TypeSegment(currentResourceType, null);
- }
-
- IEdmStructuralProperty structuredProperty = this.WriterValidator.ValidatePropertyDefined(
- nestedResourceInfo.Name, currentResourceType)
- as IEdmStructuralProperty;
-
- // Handle primitive or complex type property.
- if (structuredProperty != null)
- {
- odataPath = AppendEntitySetKeySegment(odataPath, false);
- itemType = structuredProperty.Type == null ? null : structuredProperty.Type.Definition.AsElementType();
- navigationSource = null;
-
- if (resourceTypeCast != null)
- {
- odataPath = odataPath.AddSegment(resourceTypeCast);
- }
-
- odataPath = odataPath.AddPropertySegment(structuredProperty);
-
- derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(structuredProperty);
- }
- else
- {
- IEdmNavigationProperty navigationProperty = this.WriterValidator.ValidateNestedResourceInfo(nestedResourceInfo, currentResourceType, /*payloadKind*/null);
- if (navigationProperty != null)
- {
- derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationProperty);
-
- itemType = navigationProperty.ToEntityType();
- if (!nestedResourceInfo.IsCollection.HasValue)
- {
- nestedResourceInfo.IsCollection = navigationProperty.Type.IsEntityCollectionType();
- }
-
- IEdmNavigationSource currentNavigationSource = currentScope.NavigationSource;
- IEdmPathExpression bindingPath;
-
- if (resourceTypeCast != null)
- {
- odataPath = odataPath.AddSegment(resourceTypeCast);
- }
-
- navigationSource = currentNavigationSource == null
- ? null
- : currentNavigationSource.FindNavigationTarget(navigationProperty, BindingPathHelper.MatchBindingPath, odataPath.Segments, out bindingPath);
-
- SelectExpandClause clause = odataUri.SelectAndExpand;
- TypeSegment typeCastFromExpand = null;
- if (clause != null)
- {
- SelectExpandClause subClause;
- clause.GetSubSelectExpandClause(nestedResourceInfo.Name, out subClause, out typeCastFromExpand);
- odataUri.SelectAndExpand = subClause;
- }
-
- switch (navigationSource.NavigationSourceKind())
- {
- case EdmNavigationSourceKind.ContainedEntitySet:
- // Containment cannot be written alone without odata uri.
- if (!odataPath.Any())
- {
- throw new ODataException(Strings.ODataWriterCore_PathInODataUriMustBeSetWhenWritingContainedElement);
- }
-
- odataPath = AppendEntitySetKeySegment(odataPath, true);
-
- if (odataPath != null && typeCastFromExpand != null)
- {
- odataPath = odataPath.AddSegment(typeCastFromExpand);
- }
-
- Debug.Assert(navigationSource is IEdmContainedEntitySet, "If the NavigationSourceKind is ContainedEntitySet, the navigationSource must be IEdmContainedEntitySet.");
- IEdmContainedEntitySet containedEntitySet = (IEdmContainedEntitySet)navigationSource;
- odataPath = odataPath.AddNavigationPropertySegment(containedEntitySet.NavigationProperty, containedEntitySet);
- break;
- case EdmNavigationSourceKind.EntitySet:
- odataPath = new ODataPath(new EntitySetSegment(navigationSource as IEdmEntitySet));
- break;
- case EdmNavigationSourceKind.Singleton:
- odataPath = new ODataPath(new SingletonSegment(navigationSource as IEdmSingleton));
- break;
- default:
- odataPath = null;
- break;
- }
- }
- }
-
- odataUri.Path = odataPath;
- }
- }
- else if ((currentState == WriterState.ResourceSet || currentState == WriterState.DeltaResourceSet) && (newState == WriterState.Resource || newState == WriterState.Primitive || newState == WriterState.ResourceSet || newState == WriterState.DeletedResource))
- {
- // When writing a new resource to a resourceSet, increment the count of entries on that resourceSet.
- if (currentState == WriterState.ResourceSet || currentState == WriterState.DeltaResourceSet)
- {
- ((ResourceSetBaseScope)currentScope).ResourceCount++;
- }
- }
-
- if (navigationSource == null)
- {
- navigationSource = this.CurrentScope.NavigationSource ?? odataUri.Path.TargetNavigationSource();
- }
-
- this.PushScope(newState, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, derivedTypeConstraints);
-
- this.NotifyListener(newState);
- }
-
- ///
- /// Attempt to append key segment to ODataPath.
- ///
- /// The ODataPath to be evaluated.
- /// Whether throw if fails to append key segment.
- /// The new odata path.
- private ODataPath AppendEntitySetKeySegment(ODataPath odataPath, bool throwIfFail)
- {
- ODataPath path = odataPath;
-
- if (EdmExtensionMethods.HasKey(this.CurrentScope.NavigationSource, this.CurrentScope.ResourceType))
- {
- IEdmEntityType currentEntityType = this.CurrentScope.ResourceType as IEdmEntityType;
- ODataResourceBase resource = this.CurrentScope.Item as ODataResourceBase;
- Debug.Assert(resource != null,
- "If the current state is Resource the current item must be an ODataResource as well (and not null either).");
-
- ODataResourceSerializationInfo serializationInfo = this.GetResourceSerializationInfo(resource);
-
- if (!throwIfFail)
- {
- if (resource.NonComputedProperties != null)
- {
- List> keys = new List>();
-
- if (serializationInfo != null)
- {
- foreach (ODataProperty property in resource.NonComputedProperties)
- {
- if (property.SerializationInfo != null && property.SerializationInfo.PropertyKind == ODataPropertyKind.Key)
- {
- if(!CreateKeyValuePair(keys, property, property.Value))
- {
- return path;
- }
- }
- }
- }
- else
- {
- HashSet keySet = new HashSet();
-
- foreach (IEdmStructuralProperty property in currentEntityType.Key())
- {
- keySet.Add(property.Name);
- }
-
- foreach (ODataProperty property in resource.NonComputedProperties)
- {
- if (keySet.Contains(property.Name))
- {
- if (!CreateKeyValuePair(keys, property, property.Value))
- {
- return path;
- }
- }
- }
- }
-
- path = path.AddKeySegment(keys.ToArray(), currentEntityType, this.CurrentScope.NavigationSource);
- }
- }
- else
- {
- KeyValuePair[] keys = ODataResourceMetadataContext.GetKeyProperties(resource,
- this.GetResourceSerializationInfo(resource), currentEntityType);
-
- path = path.AddKeySegment(keys, currentEntityType, this.CurrentScope.NavigationSource);
- }
- }
-
-
- return path;
- }
-
- private static bool CreateKeyValuePair(List> keys, ODataProperty property, object propertyValue)
- {
- if (propertyValue == null || (propertyValue is ODataValue && !(propertyValue is ODataEnumValue)))
- {
- return false;
- }
-
- keys.Add(new KeyValuePair(property.Name, propertyValue));
-
- return true;
- }
-
- ///
- /// Leave the current writer scope and return to the previous scope.
- /// When reaching the top-level replace the 'Started' scope with a 'Completed' scope.
- ///
- /// Note that this method is never called once an error has been written or a fatal exception has been thrown.
- private void LeaveScope()
- {
- Debug.Assert(this.State != WriterState.Error, "this.State != WriterState.Error");
-
- this.scopeStack.Pop();
-
- // if we are back at the root replace the 'Start' state with the 'Completed' state
- if (this.scopeStack.Count == 1)
- {
- Scope startScope = this.scopeStack.Pop();
- Debug.Assert(startScope.State == WriterState.Start, "startScope.State == WriterState.Start");
- this.PushScope(WriterState.Completed, /*item*/null, startScope.NavigationSource, startScope.ResourceType, /*skipWriting*/false, startScope.SelectedProperties, startScope.ODataUri, null);
- this.InterceptException((thisParam) => thisParam.EndPayload());
- this.NotifyListener(WriterState.Completed);
- }
- }
-
- ///
- /// Promotes the current nested resource info scope to a nested resource info scope with content.
- ///
- /// The nested content to write. May be of either ODataResource or ODataResourceSet type.
- [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Second cast only in debug.")]
- private void PromoteNestedResourceInfoScope(ODataItem content)
- {
- Debug.Assert(
- this.State == WriterState.NestedResourceInfo,
- "Only a NestedResourceInfo state can be promoted right now. If this changes please review the scope replacement code below.");
- Debug.Assert(
- this.CurrentScope.Item != null && this.CurrentScope.Item is ODataNestedResourceInfo,
- "Item must be a non-null nested resource info.");
- Debug.Assert(content == null || content is ODataResourceBase || content is ODataResourceSetBase);
-
- this.ValidateTransition(WriterState.NestedResourceInfoWithContent);
- NestedResourceInfoScope previousScope = (NestedResourceInfoScope)this.scopeStack.Pop();
- NestedResourceInfoScope newScope = previousScope.Clone(WriterState.NestedResourceInfoWithContent);
-
- this.scopeStack.Push(newScope);
- if (newScope.ItemType == null && content != null && !SkipWriting && !(content is ODataPrimitiveValue))
- {
- ODataPrimitiveValue primitiveValue = content as ODataPrimitiveValue;
- if (primitiveValue != null)
- {
- newScope.ItemType = EdmLibraryExtensions.GetPrimitiveTypeReference(primitiveValue.GetType()).Definition;
- }
- else
- {
- ODataResourceBase resource = content as ODataResourceBase;
- newScope.ResourceType = resource != null
- ? GetResourceType(resource)
- : GetResourceSetType(content as ODataResourceSetBase);
- }
- }
- }
-
- ///
- /// Verify that the transition from the current state into new state is valid .
- ///
- /// The new writer state to transition into.
- [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "All the transition checks are encapsulated in this method.")]
- private void ValidateTransition(WriterState newState)
- {
- if (!IsErrorState(this.State) && IsErrorState(newState))
- {
- // we can always transition into an error state if we are not already in an error state
- return;
- }
-
- switch (this.State)
- {
- case WriterState.Start:
- if (newState != WriterState.ResourceSet && newState != WriterState.Resource && newState != WriterState.DeltaResourceSet && newState != WriterState.DeletedResource)
- {
- throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromStart(this.State.ToString(), newState.ToString()));
- }
-
- if ((newState == WriterState.ResourceSet || newState == WriterState.DeltaResourceSet) && !this.writingResourceSet)
- {
- throw new ODataException(Strings.ODataWriterCore_CannotWriteTopLevelResourceSetWithResourceWriter);
- }
-
- if (newState == WriterState.Resource && this.writingResourceSet)
- {
- throw new ODataException(Strings.ODataWriterCore_CannotWriteTopLevelResourceWithResourceSetWriter);
- }
-
- break;
- case WriterState.DeletedResource:
- case WriterState.Resource:
- {
- if (this.CurrentScope.Item == null)
- {
- throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromNullResource(this.State.ToString(), newState.ToString()));
- }
-
- if (newState != WriterState.NestedResourceInfo && newState != WriterState.Property)
- {
- throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResource(this.State.ToString(), newState.ToString()));
- }
-
- // TODO: The conditional expressions in the 2 `if` blocks below are adequately covered by the `if` block above?
- if (newState == WriterState.DeletedResource && this.ParentScope.State != WriterState.DeltaResourceSet)
- {
- throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString()));
- }
-
- if (this.State == WriterState.DeletedResource && this.Version < ODataVersion.V401 && newState == WriterState.NestedResourceInfo)
- {
- throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFrom40DeletedResource(this.State.ToString(), newState.ToString()));
- }
- }
-
- break;
- case WriterState.ResourceSet:
- // Within a typed resource set we can only write a resource.
- // Within an untyped resource set we can also write a primitive value or nested resource set.
- if (newState != WriterState.Resource &&
- (this.CurrentScope.ResourceType != null &&
- (this.CurrentScope.ResourceType.TypeKind != EdmTypeKind.Untyped ||
- (newState != WriterState.Primitive && newState != WriterState.Stream && newState != WriterState.String && newState != WriterState.ResourceSet))))
- {
- throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString()));
- }
-
- break;
- case WriterState.DeltaResourceSet:
- if (newState != WriterState.Resource &&
- newState != WriterState.DeletedResource &&
- !(this.ScopeLevel < 3 && (newState == WriterState.DeltaDeletedLink || newState == WriterState.DeltaLink)))
- {
- throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString()));
- }
-
- break;
- case WriterState.NestedResourceInfo:
- if (newState != WriterState.NestedResourceInfoWithContent)
- {
- throw new ODataException(Strings.ODataWriterCore_InvalidStateTransition(this.State.ToString(), newState.ToString()));
- }
-
- break;
- case WriterState.NestedResourceInfoWithContent:
- if (newState != WriterState.ResourceSet && newState != WriterState.Resource && newState != WriterState.Primitive && (this.Version < ODataVersion.V401 || (newState != WriterState.DeltaResourceSet && newState != WriterState.DeletedResource)))
- {
- throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromExpandedLink(this.State.ToString(), newState.ToString()));
- }
-
- break;
- case WriterState.Property:
- PropertyInfoScope propertyScope = this.CurrentScope as PropertyInfoScope;
- Debug.Assert(propertyScope != null, "Scope in WriterState.Property is not PropertyInfoScope");
- if (propertyScope.ValueWritten)
- {
- // we've already written the value for this property
- ODataPropertyInfo propertyInfo = propertyScope.Item as ODataPropertyInfo;
- Debug.Assert(propertyInfo != null, "Item in PropertyInfoScope is not ODataPropertyInfo");
- throw new ODataException(Strings.ODataWriterCore_PropertyValueAlreadyWritten(propertyInfo.Name));
- }
-
- if (newState == WriterState.Stream || newState == WriterState.String || newState == WriterState.Primitive)
- {
- propertyScope.ValueWritten = true;
- }
- else
- {
- throw new ODataException(Strings.ODataWriterCore_InvalidStateTransition(this.State.ToString(), newState.ToString()));
- }
-
- break;
- case WriterState.Stream:
- case WriterState.String:
- throw new ODataException(Strings.ODataWriterCore_StreamNotDisposed);
- case WriterState.Completed:
- // we should never see a state transition when in state 'Completed'
- throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), newState.ToString()));
- case WriterState.Error:
- if (newState != WriterState.Error)
- {
- // No more state transitions once we are in error state except for the fatal error
- throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromError(this.State.ToString(), newState.ToString()));
- }
-
- break;
- default:
- throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_ValidateTransition_UnreachableCodePath));
- }
- }
-
- ///
- /// Create a new writer scope.
- ///
- /// The writer state of the scope to create.
- /// The item attached to the scope to create.
- /// The navigation source we are going to write resource set for.
- /// The structured type for the items in the resource set to be written (or null if the navigationSource base type should be used).
- /// true if the content of the scope to create should not be written.
- /// The selected properties of this scope.
- /// The OdataUri info of this scope.
- /// The derived type constraints.
- [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug.Assert check only.")]
- private void PushScope(WriterState state, ODataItem item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri,
- IEnumerable derivedTypeConstraints)
- {
- IEdmStructuredType resourceType = itemType as IEdmStructuredType;
-
- Debug.Assert(
- state == WriterState.Error ||
- state == WriterState.Resource && (item == null || item is ODataResource) ||
- state == WriterState.DeletedResource && (item == null || item is ODataDeletedResource) ||
- state == WriterState.DeltaLink && (item == null || item is ODataDeltaLink) ||
- state == WriterState.DeltaDeletedLink && (item == null || item is ODataDeltaDeletedLink) ||
- state == WriterState.ResourceSet && item is ODataResourceSet ||
- state == WriterState.DeltaResourceSet && item is ODataDeltaResourceSet ||
- state == WriterState.Primitive && (item == null || item is ODataPrimitiveValue) ||
- state == WriterState.Property && (item is ODataPropertyInfo) ||
- state == WriterState.NestedResourceInfo && item is ODataNestedResourceInfo ||
- state == WriterState.NestedResourceInfoWithContent && item is ODataNestedResourceInfo ||
- state == WriterState.Stream && item == null ||
- state == WriterState.String && item == null ||
- state == WriterState.Start && item == null ||
- state == WriterState.Completed && item == null,
- "Writer state and associated item do not match.");
-
- bool isUndeclaredResourceOrResourceSet = false;
- if ((state == WriterState.Resource || state == WriterState.ResourceSet)
- && (this.CurrentScope.State == WriterState.NestedResourceInfo || this.CurrentScope.State == WriterState.NestedResourceInfoWithContent))
- {
- isUndeclaredResourceOrResourceSet = this.IsUndeclared(this.CurrentScope.Item as ODataNestedResourceInfo);
- }
-
- Scope scope;
- switch (state)
- {
- case WriterState.Resource:
- scope = this.CreateResourceScope((ODataResource)item, navigationSource, resourceType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet);
- break;
- case WriterState.DeletedResource:
- scope = this.CreateDeletedResourceScope((ODataDeletedResource)item, navigationSource, (IEdmEntityType)itemType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet);
- break;
- case WriterState.DeltaLink:
- case WriterState.DeltaDeletedLink:
- scope = this.CreateDeltaLinkScope((ODataDeltaLinkBase)item, navigationSource, (IEdmEntityType)itemType, selectedProperties, odataUri);
- break;
- case WriterState.ResourceSet:
- scope = this.CreateResourceSetScope((ODataResourceSet)item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet);
- if (this.outputContext.Model.IsUserModel())
- {
- Debug.Assert(scope is ResourceSetBaseScope, "Create a scope for a resource set that is not a ResourceSetBaseScope");
- ((ResourceSetBaseScope)scope).ResourceTypeValidator = new ResourceSetWithoutExpectedTypeValidator(itemType);
- }
-
- break;
- case WriterState.DeltaResourceSet:
- scope = this.CreateDeltaResourceSetScope((ODataDeltaResourceSet)item, navigationSource, resourceType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet);
- if (this.outputContext.Model.IsUserModel())
- {
- Debug.Assert(scope is ResourceSetBaseScope, "Create a scope for a delta resource set that is not a ResourceSetBaseScope");
- ((ResourceSetBaseScope)scope).ResourceTypeValidator = new ResourceSetWithoutExpectedTypeValidator(resourceType);
- }
-
- break;
- case WriterState.Property:
- scope = this.CreatePropertyInfoScope((ODataPropertyInfo)item, navigationSource, resourceType, selectedProperties, odataUri);
- break;
- case WriterState.NestedResourceInfo: // fall through
- case WriterState.NestedResourceInfoWithContent:
- scope = this.CreateNestedResourceInfoScope(state, (ODataNestedResourceInfo)item, navigationSource, itemType, skipWriting, selectedProperties, odataUri);
- break;
- case WriterState.Start:
- scope = new Scope(state, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, /*enableDelta*/ true);
- break;
- case WriterState.Primitive: // fall through
- case WriterState.Stream: // fall through
- case WriterState.String: // fall through
- case WriterState.Completed: // fall through
- case WriterState.Error:
- scope = new Scope(state, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, /*enableDelta*/ false);
- break;
- default:
- string errorMessage = Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_Scope_Create_UnreachableCodePath);
- Debug.Assert(false, errorMessage);
- throw new ODataException(errorMessage);
- }
-
- scope.DerivedTypeConstraints = derivedTypeConstraints?.ToList();
- this.scopeStack.Push(scope);
- }
-
- ///
- /// Test to see if for a complex property or a collection of complex property, or a navigation property is declared or not.
- ///
- /// The nested info in question
- /// true if the nested info is undeclared; false if it is not, or if it cannot be determined
- private bool IsUndeclared(ODataNestedResourceInfo nestedResourceInfo)
- {
- Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null");
-
- if (nestedResourceInfo.SerializationInfo != null)
- {
- return nestedResourceInfo.SerializationInfo.IsUndeclared;
- }
- else
- {
- return this.ParentResourceType != null && (this.ParentResourceType.FindProperty((this.CurrentScope.Item as ODataNestedResourceInfo).Name) == null);
- }
- }
-
- ///
- /// Asynchronously verifies that calling is valid.
- ///
- /// true if the call is to be synchronous; false otherwise.
- /// The resource set/collection to write.
- /// A task that represents the asynchronous operation.
- private async Task VerifyCanWriteStartResourceSetAsync(bool synchronousCall, ODataResourceSet resourceSet)
- {
- ExceptionUtils.CheckArgumentNotNull(resourceSet, "resourceSet");
-
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
-
- await this.StartPayloadInStartStateAsync()
- .ConfigureAwait(false);
- }
-
- ///
- /// Asynchronously start writing a resource set - implementation of the actual functionality.
- ///
- /// The resource set to write.
- /// A task that represents the asynchronous write operation.
- private async Task WriteStartResourceSetImplementationAsync(ODataResourceSet resourceSet)
- {
- await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.ResourceSet, resourceSet)
- .ConfigureAwait(false);
- this.EnterScope(WriterState.ResourceSet, resourceSet);
-
- if (!this.SkipWriting)
- {
- await this.InterceptExceptionAsync(
- async (thisParam, resourceSetParam) =>
- {
- // Verify query count
- if (resourceSetParam.Count.HasValue)
- {
- // Check that Count is not set for requests
- if (!thisParam.outputContext.WritingResponse)
- {
- thisParam.ThrowODataException(Strings.ODataWriterCore_QueryCountInRequest, resourceSetParam);
- }
- }
-
- await thisParam.StartResourceSetAsync(resourceSetParam)
- .ConfigureAwait(false);
- }, resourceSet).ConfigureAwait(false);
- }
- }
-
- ///
- /// Asynchronously verifies that calling is valid.
- ///
- /// true if the call is to be synchronous; false otherwise.
- /// The delta resource set/collection to write.
- /// A task that represents the asynchronous operation.
- private async Task VerifyCanWriteStartDeltaResourceSetAsync(bool synchronousCall, ODataDeltaResourceSet deltaResourceSet)
- {
- ExceptionUtils.CheckArgumentNotNull(deltaResourceSet, "deltaResourceSet");
- this.VerifyNotDisposed();
- this.VerifyCallAllowed(synchronousCall);
-
- await this.StartPayloadInStartStateAsync()
- .ConfigureAwait(false);
- }
-
- ///
- /// Asynchronously start writing a delta resource set - implementation of the actual functionality.
- ///
- /// The delta resource set to write.
- /// A task that represents the asynchronous write operation.
- private async Task WriteStartDeltaResourceSetImplementationAsync(ODataDeltaResourceSet deltaResourceSet)
- {
- await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.ResourceSet, deltaResourceSet)
- .ConfigureAwait(false);
- this.EnterScope(WriterState.DeltaResourceSet, deltaResourceSet);
-
- await this.InterceptExceptionAsync(
- async (thisParam, deltaResourceSetParam) =>
- {
- if (!thisParam.outputContext.WritingResponse)
- {
- // Check that links are not set for requests
- if (deltaResourceSetParam.NextPageLink != null)
- {
- thisParam.ThrowODataException(Strings.ODataWriterCore_QueryNextLinkInRequest, deltaResourceSetParam);
- }
-
- if (deltaResourceSetParam.DeltaLink != null)
- {
- thisParam.ThrowODataException(Strings.ODataWriterCore_QueryDeltaLinkInRequest, deltaResourceSetParam);
- }
- }
-
- await thisParam.StartDeltaResourceSetAsync(deltaResourceSetParam)
- .ConfigureAwait(false);
- }, deltaResourceSet).ConfigureAwait(false);
- }
-
- ///
- /// Asynchronously start writing a resource - implementation of the actual functionality.
- ///
- /// Resource/item to write.
- /// A task that represents the asynchronous write operation.
- private async Task WriteStartResourceImplementationAsync(ODataResource resource)
- {
- await this.StartPayloadInStartStateAsync()
- .ConfigureAwait(false);
- await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.Resource, resource)
- .ConfigureAwait(false);
- this.EnterScope(WriterState.Resource, resource);
-
- if (!this.SkipWriting)
- {
- this.IncreaseResourceDepth();
- await this.InterceptExceptionAsync(
- async (thisParam, resourceParam) =>
- {
- if (resourceParam != null)
- {
- ResourceScope resourceScope = (ResourceScope)thisParam.CurrentScope;
- thisParam.ValidateResourceForResourceSet(resourceParam, resourceScope);
- await thisParam.PrepareResourceForWriteStartAsync(
- resourceScope,
- resourceParam,
- thisParam.outputContext.WritingResponse,
- resourceScope.SelectedProperties).ConfigureAwait(false);
- }
-
- await thisParam.StartResourceAsync(resourceParam)
- .ConfigureAwait(false);
- }, resource).ConfigureAwait(false);
- }
- }
-
- ///
- /// Asynchronously start writing a deleted resource - implementation of the actual functionality.
- ///
- /// Resource/item to write.
- /// A task that represents the asynchronous write operation.
- private async Task WriteStartDeletedResourceImplementationAsync(ODataDeletedResource resource)
- {
- Debug.Assert(resource != null, "resource != null");
-
- await this.StartPayloadInStartStateAsync()
- .ConfigureAwait(false);
- await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.Resource, resource)
- .ConfigureAwait(false);
- this.EnterScope(WriterState.DeletedResource, resource);
- this.IncreaseResourceDepth();
-
- await this.InterceptExceptionAsync(
- async (thisParam, resourceParam) =>
- {
- DeletedResourceScope resourceScope = thisParam.CurrentScope as DeletedResourceScope;
- this.ValidateResourceForResourceSet(resourceParam, resourceScope);
-
- await this.PrepareDeletedResourceForWriteStartAsync(
- resourceScope,
- resourceParam,
- thisParam.outputContext.WritingResponse,
- resourceScope.SelectedProperties).ConfigureAwait(false);
- await thisParam.StartDeletedResourceAsync(resource)
- .ConfigureAwait(false);
- }, resource).ConfigureAwait(false);
- }
-
- ///
- /// Asynchronously start writing a property - implementation of the actual functionality.
- ///
- /// Property to write.
- /// A task that represents the asynchronous write operation.
- private async Task WriteStartPropertyImplementationAsync(ODataPropertyInfo property)
- {
- this.EnterScope(WriterState.Property, property);
-
- if (!this.SkipWriting)
- {
- await this.InterceptExceptionAsync(
- async (thisParam, propertyParam) =>
- {
- await thisParam.StartPropertyAsync(propertyParam)
- .ConfigureAwait(false);
-
- if (propertyParam is ODataProperty)
- {
- PropertyInfoScope scope = thisParam.CurrentScope as PropertyInfoScope;
- Debug.Assert(scope != null, "Scope for ODataPropertyInfo is not ODataPropertyInfoScope");
- scope.ValueWritten = true;
- }
- }, property).ConfigureAwait(false);
- }
- }
-
- ///
- /// Asynchronously create a write stream - implementation of the actual functionality.
- ///
- /// A task that represents the asynchronous operation.
- /// The value of the TResult parameter contains the stream for writing the binary.
- private async Task CreateWriteStreamImplementationAsync()
- {
- this.EnterScope(WriterState.Stream, null);
- Stream underlyingStream = await this.StartBinaryStreamAsync()
- .ConfigureAwait(false);
-
- return new ODataNotificationStream(underlyingStream, /*listener*/ this, /*synchronous*/ false);
- }
-
- ///
- /// Asynchronously create a text writer - implementation of the actual functionality.
- ///
- /// A task that represents the asynchronous operation.
- /// The value of the TResult parameter contains the textwriter for writing a string value.
- private async Task CreateTextWriterImplementationAsync()
- {
- this.EnterScope(WriterState.String, null);
- TextWriter textWriter = await this.StartTextWriterAsync()
- .ConfigureAwait(false);
-
- return new ODataNotificationWriter(textWriter, /*listener*/ this, /*synchronous*/ false);
- }
-
- ///
- /// Asynchronously finish writing a resource set/resource/nested resource info.
- ///
- /// A task that represents the asynchronous write operation.
- private Task WriteEndImplementationAsync()
- {
- return this.InterceptExceptionAsync(
- async (thisParam) =>
- {
- bool wasenableDelta;
- Scope currentScope = thisParam.CurrentScope;
-
- switch (currentScope.State)
- {
- case WriterState.Resource:
- if (!thisParam.SkipWriting)
- {
- ODataResource resource = (ODataResource)currentScope.Item;
-
- await thisParam.EndResourceAsync(resource)
- .ConfigureAwait(false);
- thisParam.DecreaseResourceDepth();
- }
-
- break;
- case WriterState.DeletedResource:
- if (!thisParam.SkipWriting)
- {
- ODataDeletedResource resource = (ODataDeletedResource)currentScope.Item;
-
- await thisParam.EndDeletedResourceAsync(resource)
- .ConfigureAwait(false);
- thisParam.DecreaseResourceDepth();
- }
-
- break;
- case WriterState.ResourceSet:
- if (!thisParam.SkipWriting)
- {
- ODataResourceSet resourceSet = (ODataResourceSet)currentScope.Item;
- WriterValidationUtils.ValidateResourceSetAtEnd(resourceSet, !thisParam.outputContext.WritingResponse);
- await thisParam.EndResourceSetAsync(resourceSet)
- .ConfigureAwait(false);
- }
-
- break;
- case WriterState.DeltaLink:
- case WriterState.DeltaDeletedLink:
- break;
- case WriterState.DeltaResourceSet:
- if (!thisParam.SkipWriting)
- {
- ODataDeltaResourceSet deltaResourceSet = (ODataDeltaResourceSet)currentScope.Item;
- WriterValidationUtils.ValidateDeltaResourceSetAtEnd(deltaResourceSet, !thisParam.outputContext.WritingResponse);
- await thisParam.EndDeltaResourceSetAsync(deltaResourceSet)
- .ConfigureAwait(false);
- }
-
- break;
- case WriterState.NestedResourceInfo:
- if (!thisParam.outputContext.WritingResponse)
- {
- throw new ODataException(Strings.ODataWriterCore_DeferredLinkInRequest);
- }
-
- if (!thisParam.SkipWriting)
- {
- ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item;
- thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(link);
- await thisParam.WriteDeferredNestedResourceInfoAsync(link)
- .ConfigureAwait(false);
-
- thisParam.MarkNestedResourceInfoAsProcessed(link);
- }
-
- break;
- case WriterState.NestedResourceInfoWithContent:
- if (!thisParam.SkipWriting)
- {
- ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item;
- await thisParam.EndNestedResourceInfoWithContentAsync(link)
- .ConfigureAwait(false);
-
- thisParam.MarkNestedResourceInfoAsProcessed(link);
- }
-
- break;
- case WriterState.Property:
- {
- ODataPropertyInfo property = (ODataPropertyInfo)currentScope.Item;
- await thisParam.EndPropertyAsync(property)
- .ConfigureAwait(false);
- }
-
- break;
- case WriterState.Primitive:
- // WriteEnd for WriterState.Primitive is a no-op; just leave scope
- break;
- case WriterState.Stream:
- case WriterState.String:
- throw new ODataException(Strings.ODataWriterCore_StreamNotDisposed);
- case WriterState.Start: // fall through
- case WriterState.Completed: // fall through
- case WriterState.Error: // fall through
- throw new ODataException(Strings.ODataWriterCore_WriteEndCalledInInvalidState(currentScope.State.ToString()));
- default:
- throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_WriteEnd_UnreachableCodePath));
- }
-
- await thisParam.LeaveScopeAsync()
- .ConfigureAwait(false);
- });
- }
-
- ///
- /// Asynchronously write an entity reference link.
- ///
- /// The entity reference link to write.
- /// A task that represents the asynchronous write operation.
- private async Task WriteEntityReferenceLinkImplementationAsync(ODataEntityReferenceLink entityReferenceLink)
- {
- Debug.Assert(entityReferenceLink != null, "entityReferenceLink != null");
-
- await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.EntityReferenceLink, null)
- .ConfigureAwait(false);
- Debug.Assert(
- this.CurrentScope.Item is ODataNestedResourceInfo || this.ParentNestedResourceInfoScope.Item is ODataNestedResourceInfo,
- "The CheckForNestedResourceInfoWithContent should have verified that entity reference link can only be written inside a nested resource info.");
-
- if (!this.SkipWriting)
- {
- await this.InterceptExceptionAsync(
- async (thisParam, entityReferenceLinkParam) =>
- {
- WriterValidationUtils.ValidateEntityReferenceLink(entityReferenceLinkParam);
-
- ODataNestedResourceInfo nestedInfo = thisParam.CurrentScope.Item as ODataNestedResourceInfo;
- if (nestedInfo == null)
- {
- NestedResourceInfoScope nestedResourceInfoScope = thisParam.ParentNestedResourceInfoScope;
- Debug.Assert(nestedResourceInfoScope != null);
- nestedInfo = (ODataNestedResourceInfo)nestedResourceInfoScope.Item;
- }
-
- await thisParam.WriteEntityReferenceInNavigationLinkContentAsync(nestedInfo, entityReferenceLinkParam)
- .ConfigureAwait(false);
- }, entityReferenceLink).ConfigureAwait(false);
- }
- }
-
- ///
- /// Asynchronously checks whether we are currently writing the first top-level element; if so call StartPayloadAsync
- ///
- /// A task that represents the asynchronous write operation.
- private Task StartPayloadInStartStateAsync()
- {
- if (this.State == WriterState.Start)
- {
- return this.InterceptExceptionAsync((thisParam) => thisParam.StartPayloadAsync());
- }
-
- return TaskUtils.CompletedTask;
- }
-
- ///
- /// Asynchronously checks whether we are currently writing a nested resource info and switches to NestedResourceInfoWithContent state if we do.
- ///
- ///
- /// What kind of payload kind is being written as the content of a nested resource info.
- /// Only Resource Set, Resource or EntityReferenceLink are allowed.
- ///
- /// The ODataResource or ODataResourceSet to write, or null for ODataEntityReferenceLink.
- /// A task that represents the asynchronous operation.
- private async Task CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind contentPayloadKind, ODataItem contentPayload)
- {
- Debug.Assert(
- contentPayloadKind == ODataPayloadKind.ResourceSet || contentPayloadKind == ODataPayloadKind.Resource || contentPayloadKind == ODataPayloadKind.EntityReferenceLink,
- "Only ResourceSet, Resource or EntityReferenceLink can be specified as a payload kind for a nested resource info content.");
-
- Scope currentScope = this.CurrentScope;
- if (currentScope.State == WriterState.NestedResourceInfo || currentScope.State == WriterState.NestedResourceInfoWithContent)
- {
- ODataNestedResourceInfo currentNestedResourceInfo = (ODataNestedResourceInfo)currentScope.Item;
-
- this.InterceptException(
- (thisParam, currentNestedResourceInfoParam, contentPayloadKindParam) =>
- {
- if (thisParam.ParentResourceType != null)
- {
- IEdmStructuralProperty structuralProperty = thisParam.ParentResourceType.FindProperty(
- currentNestedResourceInfoParam.Name) as IEdmStructuralProperty;
- if (structuralProperty != null)
- {
- thisParam.CurrentScope.ItemType = structuralProperty.Type.Definition.AsElementType();
- IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource;
-
- thisParam.CurrentScope.NavigationSource = parentNavigationSource;
- }
- else
- {
- IEdmNavigationProperty navigationProperty = thisParam.WriterValidator.ValidateNestedResourceInfo(
- currentNestedResourceInfoParam,
- thisParam.ParentResourceType,
- contentPayloadKindParam);
- if (navigationProperty != null)
- {
- thisParam.CurrentScope.ResourceType = navigationProperty.ToEntityType();
- IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource;
-
- if (thisParam.CurrentScope.NavigationSource == null)
- {
- IEdmPathExpression bindingPath;
- thisParam.CurrentScope.NavigationSource = parentNavigationSource?.FindNavigationTarget(
- navigationProperty,
- BindingPathHelper.MatchBindingPath,
- thisParam.CurrentScope.ODataUri.Path.Segments,
- out bindingPath);
- }
- }
- }
- }
- }, currentNestedResourceInfo, contentPayloadKind);
-
- if (currentScope.State == WriterState.NestedResourceInfoWithContent)
- {
- // If we are already in the NestedResourceInfoWithContent state, it means the caller is trying to write two items
- // into the nested resource info content. This is only allowed for collection navigation property in request/response.
- if (currentNestedResourceInfo.IsCollection != true)
- {
- this.ThrowODataException(Strings.ODataWriterCore_MultipleItemsInNestedResourceInfoWithContent, currentNestedResourceInfo);
- }
-
- // Note that we don't invoke duplicate property checker in this case as it's not necessary.
- // What happens inside the nested resource info was already validated by the condition above.
- // For collection in request we allow any combination anyway.
- // For everything else we only allow a single item in the content and thus we will fail above.
- }
- else
- {
- // We are writing a nested resource info with content; change the state
- this.PromoteNestedResourceInfoScope(contentPayload);
-
- if (!this.SkipWriting)
- {
- await this.InterceptExceptionAsync(
- async (thisParam, currentNestedResourceInfoParam) =>
- {
- if (!(currentNestedResourceInfoParam.SerializationInfo != null && currentNestedResourceInfoParam.SerializationInfo.IsComplex)
- && (thisParam.CurrentScope.ItemType == null || thisParam.CurrentScope.ItemType.IsEntityOrEntityCollectionType()))
- {
- thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(currentNestedResourceInfoParam);
- await thisParam.StartNestedResourceInfoWithContentAsync(currentNestedResourceInfoParam)
- .ConfigureAwait(false);
- }
- }, currentNestedResourceInfo).ConfigureAwait(false);
- }
- }
- }
- else
- {
- if (contentPayloadKind == ODataPayloadKind.EntityReferenceLink)
- {
- Scope parentScope = this.ParentNestedResourceInfoScope;
- Debug.Assert(parentScope != null);
- if (parentScope.State != WriterState.NestedResourceInfo && parentScope.State != WriterState.NestedResourceInfoWithContent)
- {
- this.ThrowODataException(Strings.ODataWriterCore_EntityReferenceLinkWithoutNavigationLink, null);
- }
- }
- }
- }
-
- ///
- /// Asynchronously leave the current writer scope and return to the previous scope.
- /// When reaching the top-level replace the 'Started' scope with a 'Completed' scope.
- ///
- /// Note that this method is never called once an error has been written or a fatal exception has been thrown.
- private async Task LeaveScopeAsync()
- {
- Debug.Assert(this.State != WriterState.Error, "this.State != WriterState.Error");
-
- this.scopeStack.Pop();
-
- // if we are back at the root replace the 'Start' state with the 'Completed' state
- if (this.scopeStack.Count == 1)
- {
- Scope startScope = this.scopeStack.Pop();
- Debug.Assert(startScope.State == WriterState.Start, "startScope.State == WriterState.Start");
- this.PushScope(
- WriterState.Completed,
- /*item*/ null,
- startScope.NavigationSource,
- startScope.ResourceType,
- /*skipWriting*/ false,
- startScope.SelectedProperties,
- startScope.ODataUri,
- /*derivedTypeConstraints*/ null);
- await this.InterceptExceptionAsync((thisParam) => thisParam.EndPayloadAsync())
- .ConfigureAwait(false);
- this.NotifyListener(WriterState.Completed);
- }
- }
-
- ///
- /// Lightweight wrapper for the stack of scopes which exposes a few helper properties for getting parent scopes.
- ///
- internal sealed class ScopeStack
- {
- ///
- /// Use a list to store the scopes instead of a true stack so that parent/grandparent lookups will be fast.
- ///
- private readonly List scopes = new List();
-
- ///
- /// Initializes a new instance of the class.
- ///
- internal ScopeStack()
- {
- }
-
- ///
- /// Gets the count of items in the stack.
- ///
- internal int Count
- {
- get
- {
- return this.scopes.Count;
- }
- }
-
- ///
- /// Gets the scope below the current scope on top of the stack.
- ///
- internal Scope Parent
- {
- get
- {
- Debug.Assert(this.scopes.Count > 1, "this.scopes.Count > 1");
- return this.scopes[this.scopes.Count - 2];
- }
- }
-
- ///
- /// Gets the scope below the parent of the current scope on top of the stack.
- ///
- internal Scope ParentOfParent
- {
- get
- {
- Debug.Assert(this.scopes.Count > 2, "this.scopes.Count > 2");
- return this.scopes[this.scopes.Count - 3];
- }
- }
-
- ///
- /// Gets the scope below the current scope on top of the stack or null if there is only one item on the stack or the stack is empty.
- ///
- internal Scope ParentOrNull
- {
- get
- {
- return this.Count == 0 ? null : this.Parent;
- }
- }
-
- ///
- /// Pushes the specified scope onto the stack.
- ///
- /// The scope.
- internal void Push(Scope scope)
- {
- Debug.Assert(scope != null, "scope != null");
- this.scopes.Add(scope);
- }
-
- ///
- /// Pops the current scope off the stack.
- ///
- /// The popped scope.
- internal Scope Pop()
- {
- Debug.Assert(this.scopes.Count > 0, "this.scopes.Count > 0");
- int last = this.scopes.Count - 1;
- Scope current = this.scopes[last];
- this.scopes.RemoveAt(last);
- return current;
- }
-
- ///
- /// Peeks at the current scope on the top of the stack.
- ///
- /// The current scope at the top of the stack.
- internal Scope Peek()
- {
- Debug.Assert(this.scopes.Count > 0, "this.scopes.Count > 0");
- return this.scopes[this.scopes.Count - 1];
- }
- }
-
- ///
- /// A writer scope; keeping track of the current writer state and an item associated with this state.
- ///
- internal class Scope
- {
- /// The writer state of this scope.
- private readonly WriterState state;
-
- /// The item attached to this scope.
- private readonly ODataItem item;
-
- /// Set to true if the content of the scope should not be written.
- /// This is used when writing navigation links which were not projected on the owning resource.
- private readonly bool skipWriting;
-
- /// The selected properties for the current scope.
- private readonly SelectedPropertiesNode selectedProperties;
-
- /// The navigation source we are going to write entities for.
- private IEdmNavigationSource navigationSource;
-
- /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used).
- private IEdmStructuredType resourceType;
-
- /// The IEdmType of the item (may not be structured for primitive types).
- private IEdmType itemType;
-
- /// The odata uri info for current scope.
- private ODataUri odataUri;
-
- /// Whether we are in the context of writing a delta collection.
- private bool enableDelta;
-
- ///
- /// Constructor creating a new writer scope.
- ///
- /// The writer state of this scope.
- /// The item attached to this scope.
- /// The navigation source we are going to write resource set for.
- /// The type for the items in the resource set to be written (or null if the entity set base type should be used).
- /// true if the content of this scope should not be written.
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- /// Whether we are in the context of writing a delta collection.
- internal Scope(WriterState state, ODataItem item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool enableDelta)
- {
- this.state = state;
- this.item = item;
- this.itemType = itemType;
- this.resourceType = itemType as IEdmStructuredType;
- this.navigationSource = navigationSource;
- this.skipWriting = skipWriting;
- this.selectedProperties = selectedProperties;
- this.odataUri = odataUri;
- this.enableDelta = enableDelta;
- }
-
- ///
- /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used).
- ///
- public IEdmStructuredType ResourceType
- {
- get
- {
- return this.resourceType;
- }
-
- set
- {
- this.resourceType = value;
- this.itemType = value;
- }
- }
-
- ///
- /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used).
- ///
- public IEdmType ItemType
- {
- get
- {
- return this.itemType;
- }
-
- set
- {
- this.itemType = value;
- this.resourceType = value as IEdmStructuredType;
- }
- }
-
- ///
- /// The writer state of this scope.
- ///
- internal WriterState State
- {
- get
- {
- return this.state;
- }
- }
-
- ///
- /// The item attached to this scope.
- ///
- internal ODataItem Item
- {
- get
- {
- return this.item;
- }
- }
-
- /// The navigation source we are going to write entities for.
- internal IEdmNavigationSource NavigationSource
- {
- get
- {
- return this.navigationSource;
- }
-
- set
- {
- this.navigationSource = value;
- }
- }
-
- /// The selected properties for the current scope.
- internal SelectedPropertiesNode SelectedProperties
- {
- get
- {
- return this.selectedProperties;
- }
- }
-
- /// The odata Uri for the current scope.
- internal ODataUri ODataUri
- {
- get
- {
- Debug.Assert(this.odataUri != null, "this.odataUri != null");
- return this.odataUri;
- }
- }
-
- ///
- /// Set to true if the content of this scope should not be written.
- ///
- internal bool SkipWriting
- {
- get
- {
- return this.skipWriting;
- }
- }
-
- ///
- /// True if we are in the process of writing a delta collection.
- ///
- public bool EnableDelta
- {
- get
- {
- return this.enableDelta;
- }
-
- protected set
- {
- this.enableDelta = value;
- }
- }
-
- /// Gets or sets the derived type constraints for the current scope.
- internal List DerivedTypeConstraints { get; set; }
- }
-
- ///
- /// A base scope for a resourceSet.
- ///
- internal abstract class ResourceSetBaseScope : Scope
- {
- /// The serialization info for the current resourceSet.
- private readonly ODataResourceSerializationInfo serializationInfo;
-
- ///
- /// The to use for entries in this resourceSet.
- ///
- private ResourceSetWithoutExpectedTypeValidator resourceTypeValidator;
-
- /// The number of entries in this resourceSet seen so far.
- private int resourceCount;
-
- /// Maintains the write status for each annotation using its key.
- private InstanceAnnotationWriteTracker instanceAnnotationWriteTracker;
-
- /// The type context to answer basic questions regarding the type info of the resource.
- private ODataResourceTypeContext typeContext;
-
- ///
- /// Constructor to create a new resource set scope.
- ///
- /// The writer state for the scope.
- /// The resourceSet for the new scope.
- /// The navigation source we are going to write resource set for.
- /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used).
- /// true if the content of the scope to create should not be written.
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- internal ResourceSetBaseScope(WriterState writerState, ODataResourceSetBase resourceSet, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
- : base(writerState, resourceSet, navigationSource, itemType, skipWriting, selectedProperties, odataUri, writerState == WriterState.DeltaResourceSet)
- {
- this.serializationInfo = resourceSet.SerializationInfo;
- }
-
- ///
- /// The number of entries in this resource Set seen so far.
- ///
- internal int ResourceCount
- {
- get
- {
- return this.resourceCount;
- }
-
- set
- {
- this.resourceCount = value;
- }
- }
-
- ///
- /// Tracks the write status of the annotations.
- ///
- internal InstanceAnnotationWriteTracker InstanceAnnotationWriteTracker
- {
- get
- {
- if (this.instanceAnnotationWriteTracker == null)
- {
- this.instanceAnnotationWriteTracker = new InstanceAnnotationWriteTracker();
- }
-
- return this.instanceAnnotationWriteTracker;
- }
- }
-
- ///
- /// Validator for resource type.
- ///
- internal ResourceSetWithoutExpectedTypeValidator ResourceTypeValidator
- {
- get
- {
- return this.resourceTypeValidator;
- }
-
- set
- {
- this.resourceTypeValidator = value;
- }
- }
-
- ///
- /// Gets or creates the type context to answer basic questions regarding the type info of the resource.
- ///
- /// True if writing a response payload, false otherwise.
- /// The type context to answer basic questions regarding the type info of the resource.
- internal ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse)
- {
- if (this.typeContext == null)
- {
- // For Entity, currently we check the navigation source.
- // For Complex, we don't have navigation source, So we shouldn't check it.
- // If ResourceType is not provided, serialization info or navigation source info should be provided.
- bool throwIfMissingTypeInfo = writingResponse && (this.ResourceType == null || this.ResourceType.TypeKind == EdmTypeKind.Entity);
-
- this.typeContext = ODataResourceTypeContext.Create(
- this.serializationInfo,
- this.NavigationSource,
- EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource),
- this.ResourceType,
- throwIfMissingTypeInfo);
- }
-
- return this.typeContext;
- }
- }
-
- ///
- /// A scope for a resource set.
- ///
- internal abstract class ResourceSetScope : ResourceSetBaseScope
- {
- ///
- /// Constructor to create a new resource set scope.
- ///
- /// The resource set for the new scope.
- /// The navigation source we are going to write resource set for.
- /// The type of the items in the resource set to be written (or null if the entity set base type should be used).
- /// true if the content of the scope to create should not be written.
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- protected ResourceSetScope(ODataResourceSet item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
- : base(WriterState.ResourceSet, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri)
- {
- }
- }
-
- ///
- /// A scope for a delta resource set.
- ///
- internal abstract class DeltaResourceSetScope : ResourceSetBaseScope
- {
- ///
- /// Constructor to create a new resource set scope.
- ///
- /// The resource set for the new scope.
- /// The navigation source we are going to write resource set for.
- /// The structured type of the items in the resource set to be written (or null if the entity set base type should be used).
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- protected DeltaResourceSetScope(ODataDeltaResourceSet item, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
- : base(WriterState.DeltaResourceSet, item, navigationSource, resourceType, false /*skip writing*/, selectedProperties, odataUri)
- {
- }
-
- ///
- /// The context uri info created for this scope.
- ///
- public ODataContextUrlInfo ContextUriInfo { get; set; }
- }
-
- ///
- /// A base scope for a resource.
- ///
- internal class ResourceBaseScope : Scope
- {
- /// Checker to detect duplicate property names.
- private readonly IDuplicatePropertyNameChecker duplicatePropertyNameChecker;
-
- /// The serialization info for the current resource.
- private readonly ODataResourceSerializationInfo serializationInfo;
-
- /// The resource type which was derived from the model (may be either the same as structured type or its base type.
- private IEdmStructuredType resourceTypeFromMetadata;
-
- /// The type context to answer basic questions regarding the type info of the resource.
- private ODataResourceTypeContext typeContext;
-
- /// Maintains the write status for each annotation using its key.
- private InstanceAnnotationWriteTracker instanceAnnotationWriteTracker;
-
- ///
- /// Constructor to create a new resource scope.
- ///
- /// The writer state of this scope.
- /// The resource for the new scope.
- /// The serialization info for the current resource.
- /// The navigation source we are going to write resource set for.
- /// The type for the items in the resource set to be written (or null if the entity set base type should be used).
- /// true if the content of the scope to create should not be written.
- /// The The settings of the writer.
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- internal ResourceBaseScope(WriterState state, ODataResourceBase resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
- : base(state, resource, navigationSource, itemType, skipWriting, selectedProperties, odataUri, /*enableDelta*/ true)
- {
- Debug.Assert(writerSettings != null, "writerBehavior != null");
-
- if (resource != null)
- {
- duplicatePropertyNameChecker = writerSettings.Validator.CreateDuplicatePropertyNameChecker();
- }
-
- this.serializationInfo = serializationInfo;
- }
-
- ///
- /// The structured type which was derived from the model, i.e. the expected structured type, which may be either the same as structured type or its base type.
- /// For example, if we are writing a resource set of Customers and the current resource is of DerivedCustomer, this.ResourceTypeFromMetadata would be Customer and this.ResourceType would be DerivedCustomer.
- ///
- public IEdmStructuredType ResourceTypeFromMetadata
- {
- get
- {
- return this.resourceTypeFromMetadata;
- }
-
- internal set
- {
- this.resourceTypeFromMetadata = value;
- }
- }
-
- ///
- /// The serialization info for the current resource.
- ///
- public ODataResourceSerializationInfo SerializationInfo
- {
- get { return this.serializationInfo; }
- }
-
- ///
- /// Checker to detect duplicate property names.
- ///
- internal IDuplicatePropertyNameChecker DuplicatePropertyNameChecker
- {
- get
- {
- return duplicatePropertyNameChecker;
- }
- }
-
- ///
- /// Tracks the write status of the annotations.
- ///
- internal InstanceAnnotationWriteTracker InstanceAnnotationWriteTracker
- {
- get
- {
- if (this.instanceAnnotationWriteTracker == null)
- {
- this.instanceAnnotationWriteTracker = new InstanceAnnotationWriteTracker();
- }
-
- return this.instanceAnnotationWriteTracker;
- }
- }
-
- ///
- /// Gets or creates the type context to answer basic questions regarding the type info of the resource.
- ///
- /// True if writing a response payload, false otherwise.
- /// The type context to answer basic questions regarding the type info of the resource.
- public ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse)
- {
- if (this.typeContext == null)
- {
- IEdmStructuredType expectedResourceType = this.ResourceTypeFromMetadata ?? this.ResourceType;
-
- // For entity, we will check the navigation source info
- bool throwIfMissingTypeInfo = writingResponse && (expectedResourceType == null || expectedResourceType.TypeKind == EdmTypeKind.Entity);
-
- this.typeContext = ODataResourceTypeContext.Create(
- this.serializationInfo,
- this.NavigationSource,
- EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource),
- expectedResourceType,
- throwIfMissingTypeInfo);
- }
-
- return this.typeContext;
- }
- }
-
- ///
- /// A base scope for a resource.
- ///
- internal class ResourceScope : ResourceBaseScope
- {
- ///
- /// Constructor to create a new resource scope.
- ///
- /// The resource for the new scope.
- /// The serialization info for the current resource.
- /// The navigation source we are going to write resource set for.
- /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used).
- /// true if the content of the scope to create should not be written.
- /// The The settings of the writer.
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- protected ResourceScope(ODataResource resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
- : base(WriterState.Resource, resource, serializationInfo, navigationSource, resourceType, skipWriting, writerSettings, selectedProperties, odataUri)
- {
- }
- }
-
- ///
- /// Base class for DeletedResourceScope.
- ///
- internal class DeletedResourceScope : ResourceBaseScope
- {
- ///
- /// Constructor to create a new resource scope.
- ///
- /// The resource for the new scope.
- /// The serialization info for the current resource.
- /// The navigation source we are going to write entities for.
- /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used).
- /// The The settings of the writer.
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- protected DeletedResourceScope(ODataDeletedResource resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmEntityType entityType, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
- : base(WriterState.DeletedResource, resource, serializationInfo, navigationSource, entityType, false /*skipWriting*/, writerSettings, selectedProperties, odataUri)
- {
- }
- }
-
- ///
- /// A scope for a delta link.
- ///
- internal abstract class DeltaLinkScope : Scope
- {
- /// The serialization info for the current link.
- private readonly ODataResourceSerializationInfo serializationInfo;
-
- ///
- /// Fake entity type to be passed to context.
- ///
- private readonly EdmEntityType fakeEntityType = new EdmEntityType("MyNS", "Fake");
-
- /// The type context to answer basic questions regarding the type info of the link.
- private ODataResourceTypeContext typeContext;
-
- ///
- /// Constructor to create a new delta link scope.
- ///
- /// The writer state of this scope.
- /// The link for the new scope.
- /// The serialization info for the current resource.
- /// The navigation source we are going to write entities for.
- /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used).
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- protected DeltaLinkScope(WriterState state, ODataItem link, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmEntityType entityType, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
- : base(state, link, navigationSource, entityType, /*skipWriting*/false, selectedProperties, odataUri, /*enableDelta*/ false)
- {
- Debug.Assert(link != null, "link != null");
- Debug.Assert(
- state == WriterState.DeltaLink && link is ODataDeltaLink ||
- state == WriterState.DeltaDeletedLink && link is ODataDeltaDeletedLink,
- "link must be either DeltaLink or DeltaDeletedLink.");
-
- this.serializationInfo = serializationInfo;
- }
-
- ///
- /// Gets or creates the type context to answer basic questions regarding the type info of the resource.
- ///
- /// Whether writing Json payload. Should always be true.
- /// The type context to answer basic questions regarding the type info of the resource.
- public ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse = true)
- {
- if (this.typeContext == null)
- {
- this.typeContext = ODataResourceTypeContext.Create(
- this.serializationInfo,
- this.NavigationSource,
- EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource),
- this.fakeEntityType,
- writingResponse);
- }
-
- return this.typeContext;
- }
- }
-
- ///
- /// A scope for writing a single property within a resource.
- ///
- internal class PropertyInfoScope : Scope
- {
- ///
- /// Constructor to create a new property scope.
- ///
- /// The property for the new scope.
- /// The navigation source.
- /// The structured type for the resource containing the property to be written.
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- internal PropertyInfoScope(ODataPropertyInfo property, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
- : base(WriterState.Property, property, navigationSource, resourceType, /*skipWriting*/ false, selectedProperties, odataUri, /*enableDelta*/ true)
- {
- ValueWritten = false;
- }
-
- public ODataPropertyInfo Property
- {
- get
- {
- Debug.Assert(this.Item is ODataProperty, "The item of a property scope is not an item.");
- return this.Item as ODataProperty;
- }
- }
-
- internal bool ValueWritten { get; set; }
- }
-
- ///
- /// A scope for a nested resource info.
- ///
- internal class NestedResourceInfoScope : Scope
- {
- ///
- /// Constructor to create a new nested resource info scope.
- ///
- /// The writer state for the new scope.
- /// The nested resource info for the new scope.
- /// The navigation source we are going to write resource set for.
- /// The type for the items in the resource set to be written (or null if the entity set base type should be used).
- /// true if the content of the scope to create should not be written.
- /// The selected properties of this scope.
- /// The ODataUri info of this scope.
- /// The scope of the parent.
- internal NestedResourceInfoScope(WriterState writerState, ODataNestedResourceInfo navLink, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, Scope parentScope)
- : base(writerState, navLink, navigationSource, itemType, skipWriting, selectedProperties, odataUri, parentScope.EnableDelta)
- {
- this.parentScope = parentScope;
- }
-
- /// Scope of the parent
- protected Scope parentScope;
-
- ///
- /// Clones this nested resource info scope and sets a new writer state.
- ///
- /// The to set.
- /// The cloned nested resource info scope with the specified writer state.
- internal virtual NestedResourceInfoScope Clone(WriterState newWriterState)
- {
- return new NestedResourceInfoScope(newWriterState, (ODataNestedResourceInfo)this.Item, this.NavigationSource, this.ItemType, this.SkipWriting, this.SelectedProperties, this.ODataUri, parentScope)
- {
- DerivedTypeConstraints = this.DerivedTypeConstraints,
- };
- }
- }
- }
-}
+//---------------------------------------------------------------------
+//
+// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
+//
+//---------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.OData.Edm;
+using Microsoft.OData.Evaluation;
+using Microsoft.OData.Metadata;
+using Microsoft.OData.UriParser;
+
+namespace Microsoft.OData
+{
+ ///
+ /// Base class for OData writers that verifies a proper sequence of write calls on the writer.
+ ///
+ internal abstract class ODataWriterCore : ODataWriter, IODataOutputInStreamErrorListener, IODataStreamListener
+ {
+ /// The writer validator to use.
+ protected readonly IWriterValidator WriterValidator;
+
+ /// The output context to write to.
+ private readonly ODataOutputContext outputContext;
+
+ /// True if the writer was created for writing a resourceSet; false when it was created for writing a resource.
+ private readonly bool writingResourceSet;
+
+ /// If not null, the writer will notify the implementer of the interface of relevant state changes in the writer.
+ private readonly IODataReaderWriterListener listener;
+
+ /// Stack of writer scopes to keep track of the current context of the writer.
+ private readonly ScopeStack scopeStack = new ScopeStack();
+
+ /// The number of entries which have been started but not yet ended.
+ private int currentResourceDepth;
+
+ ///
+ /// Constructor.
+ ///
+ /// The output context to write to.
+ /// The navigation source we are going to write resource set for.
+ /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used).
+ /// True if the writer is created for writing a resourceSet; false when it is created for writing a resource.
+ /// True if the writer is created for writing a delta response; false otherwise. This parameter is ignored and will be removed in the next major release.
+ /// If not null, the writer will notify the implementer of the interface of relevant state changes in the writer.
+ protected ODataWriterCore(
+ ODataOutputContext outputContext,
+ IEdmNavigationSource navigationSource,
+ IEdmStructuredType resourceType,
+ bool writingResourceSet,
+ bool writingDelta = false,
+ IODataReaderWriterListener listener = null)
+ {
+ Debug.Assert(outputContext != null, "outputContext != null");
+
+ this.outputContext = outputContext;
+ this.writingResourceSet = writingResourceSet;
+ this.WriterValidator = outputContext.WriterValidator;
+ this.Version = outputContext.MessageWriterSettings.Version;
+
+ if (navigationSource != null && resourceType == null)
+ {
+ resourceType = this.outputContext.EdmTypeResolver.GetElementType(navigationSource);
+ }
+
+ ODataUri odataUri = outputContext.MessageWriterSettings.ODataUri.Clone();
+
+ // Remove key for top level resource
+ if (!writingResourceSet && odataUri != null && odataUri.Path != null)
+ {
+ odataUri.Path = odataUri.Path.TrimEndingKeySegment();
+ }
+
+ this.listener = listener;
+
+ this.scopeStack.Push(new Scope(WriterState.Start, /*item*/null, navigationSource, resourceType, /*skipWriting*/false, outputContext.MessageWriterSettings.SelectedProperties, odataUri, /*enableDelta*/ true));
+ this.CurrentScope.DerivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationSource)?.ToList();
+ }
+
+ ///
+ /// An enumeration representing the current state of the writer.
+ ///
+ internal enum WriterState
+ {
+ /// The writer is at the start; nothing has been written yet.
+ Start,
+
+ /// The writer is currently writing a resource.
+ Resource,
+
+ /// The writer is currently writing a resourceSet.
+ ResourceSet,
+
+ /// The writer is currently writing a delta resource set.
+ DeltaResourceSet,
+
+ /// The writer is currently writing a deleted resource.
+ DeletedResource,
+
+ /// The writer is currently writing a delta link.
+ DeltaLink,
+
+ /// The writer is currently writing a delta deleted link.
+ DeltaDeletedLink,
+
+ /// The writer is currently writing a nested resource info (possibly an expanded link but we don't know yet).
+ ///
+ /// This state is used when a nested resource info was started but we didn't see any children for it yet.
+ ///
+ NestedResourceInfo,
+
+ /// The writer is currently writing a nested resource info with content.
+ ///
+ /// This state is used when a nested resource info with either an entity reference link or expanded resourceSet/resource was written.
+ ///
+ NestedResourceInfoWithContent,
+
+ /// The writer is currently writing a primitive value.
+ Primitive,
+
+ /// The writer is currently writing a single property.
+ Property,
+
+ /// The writer is currently writing a stream value.
+ Stream,
+
+ /// The writer is currently writing a string value.
+ String,
+
+ /// The writer has completed; nothing can be written anymore.
+ Completed,
+
+ /// The writer is in error state; nothing can be written anymore.
+ Error
+ }
+
+ ///
+ /// OData Version being written.
+ ///
+ internal ODataVersion? Version { get; }
+
+ ///
+ /// The current scope for the writer.
+ ///
+ protected Scope CurrentScope
+ {
+ get
+ {
+ Debug.Assert(this.scopeStack.Count > 0, "We should have at least one active scope all the time.");
+ return this.scopeStack.Peek();
+ }
+ }
+
+ ///
+ /// The current state of the writer.
+ ///
+ protected WriterState State
+ {
+ get
+ {
+ return this.CurrentScope.State;
+ }
+ }
+
+ ///
+ /// true if the writer should not write any input specified and should just skip it.
+ ///
+ protected bool SkipWriting
+ {
+ get
+ {
+ return this.CurrentScope.SkipWriting;
+ }
+ }
+
+ ///
+ /// A flag indicating whether the writer is at the top level.
+ ///
+ protected bool IsTopLevel
+ {
+ get
+ {
+ Debug.Assert(this.State != WriterState.Start && this.State != WriterState.Completed, "IsTopLevel should only be called while writing the payload.");
+
+ // there is the root scope at the top (when the writer has not started or has completed)
+ // and then the top-level scope (the top-level resource/resourceSet item) as the second scope on the stack
+ return this.scopeStack.Count == 2;
+ }
+ }
+
+ ///
+ /// The scope level the writer is writing.
+ ///
+ protected int ScopeLevel
+ {
+ get { return this.scopeStack.Count; }
+ }
+
+ ///
+ /// Returns the immediate parent link which is being expanded, or null if no such link exists
+ ///
+ protected ODataNestedResourceInfo ParentNestedResourceInfo
+ {
+ get
+ {
+ Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.ResourceSet || this.State == WriterState.DeltaResourceSet, "ParentNestedResourceInfo should only be called while writing a resource or a resourceSet.");
+
+ Scope linkScope = this.scopeStack.ParentOrNull;
+ return linkScope == null ? null : (linkScope.Item as ODataNestedResourceInfo);
+ }
+ }
+
+ ///
+ /// Returns the nested info that current resource belongs to.
+ ///
+ protected ODataNestedResourceInfo BelongingNestedResourceInfo
+ {
+ get
+ {
+ Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.ResourceSet || this.State == WriterState.DeletedResource || this.State == WriterState.DeltaResourceSet, "BelongingNestedResourceInfo should only be called while writing a (deleted) resource or a (delta) resourceSet.");
+
+ Scope linkScope = this.scopeStack.ParentOrNull;
+
+ // For single navigation
+ if (linkScope is NestedResourceInfoScope)
+ {
+ return linkScope.Item as ODataNestedResourceInfo;
+ }
+ else if (linkScope is ResourceSetBaseScope)
+ {
+ // For resource under collection of navigation/complex, parent is ResourceSetScope, so we need find parent of parent.
+ linkScope = this.scopeStack.ParentOfParent;
+ return linkScope == null ? null : (linkScope.Item as ODataNestedResourceInfo);
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ ///
+ /// Returns the resource type of the immediate parent resource for which a nested resource info is being written.
+ ///
+ protected IEdmStructuredType ParentResourceType
+ {
+ get
+ {
+ Debug.Assert(
+ this.State == WriterState.NestedResourceInfo || this.State == WriterState.NestedResourceInfoWithContent,
+ "ParentResourceType should only be called while writing a nested resource info (with or without content), or within an untyped ResourceSet.");
+ Scope resourceScope = this.scopeStack.Parent;
+ return resourceScope.ResourceType;
+ }
+ }
+
+ ///
+ /// Returns the navigation source of the immediate parent resource for which a nested resource info is being written.
+ ///
+ protected IEdmNavigationSource ParentResourceNavigationSource
+ {
+ get
+ {
+ Scope resourceScope = this.scopeStack.Parent;
+ return resourceScope == null ? null : resourceScope.NavigationSource;
+ }
+ }
+
+ ///
+ /// Returns the parent scope of current scope.
+ ///
+ protected Scope ParentScope
+ {
+ get
+ {
+ Debug.Assert(this.scopeStack.Count > 1);
+ return this.scopeStack.Parent;
+ }
+ }
+
+ ///
+ /// Returns the number of items seen so far on the current resource set scope.
+ ///
+ /// Can only be accessed on a resource set scope.
+ protected int ResourceSetScopeResourceCount
+ {
+ get
+ {
+ Debug.Assert(this.State == WriterState.ResourceSet, "ResourceSetScopeResourceCount should only be called while writing a resource set.");
+ return ((ResourceSetBaseScope)this.CurrentScope).ResourceCount;
+ }
+ }
+
+ ///
+ /// Checker to detect duplicate property names.
+ ///
+ protected IDuplicatePropertyNameChecker DuplicatePropertyNameChecker
+ {
+ get
+ {
+ Debug.Assert(
+ this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.NestedResourceInfo || this.State == WriterState.NestedResourceInfoWithContent || this.State == WriterState.Property,
+ "PropertyAndAnnotationCollector should only be called while writing a resource or an (expanded or deferred) nested resource info.");
+
+ ResourceBaseScope resourceScope;
+ switch (this.State)
+ {
+ case WriterState.DeletedResource:
+ case WriterState.Resource:
+ resourceScope = (ResourceBaseScope)this.CurrentScope;
+ break;
+ case WriterState.Property:
+ case WriterState.NestedResourceInfo:
+ case WriterState.NestedResourceInfoWithContent:
+ resourceScope = (ResourceBaseScope)this.scopeStack.Parent;
+ break;
+ default:
+ throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_PropertyAndAnnotationCollector));
+ }
+
+ return resourceScope.DuplicatePropertyNameChecker;
+ }
+ }
+
+ ///
+ /// The structured type of the current resource.
+ ///
+ protected IEdmStructuredType ResourceType
+ {
+ get
+ {
+ return this.CurrentScope.ResourceType;
+ }
+ }
+
+ ///
+ /// Returns the parent nested resource info scope of a resource in an expanded link (if it exists).
+ /// The resource can either be the content of the expanded link directly or nested inside a resourceSet.
+ ///
+ /// The parent navigation scope of a resource in an expanded link (if it exists).
+ protected NestedResourceInfoScope ParentNestedResourceInfoScope
+ {
+ get
+ {
+ Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.ResourceSet || this.State == WriterState.DeltaResourceSet, "ParentNestedResourceInfoScope should only be called while writing a resource or a resourceSet.");
+ Debug.Assert(this.scopeStack.Count >= 2, "We should have at least the resource scope and the start scope on the stack.");
+
+ Scope parentScope = this.scopeStack.Parent;
+ if (parentScope.State == WriterState.Start)
+ {
+ // Top-level resource.
+ return null;
+ }
+
+ if (parentScope.State == WriterState.ResourceSet || parentScope.State == WriterState.DeltaResourceSet)
+ {
+ Debug.Assert(this.scopeStack.Count >= 3, "We should have at least the resource scope, the resourceSet scope and the start scope on the stack.");
+
+ // Get the resourceSet's parent
+ parentScope = this.scopeStack.ParentOfParent;
+ if (parentScope.State == WriterState.Start ||
+ (parentScope.State == WriterState.ResourceSet &&
+ parentScope.ResourceType != null &&
+ parentScope.ResourceType.TypeKind == EdmTypeKind.Untyped))
+ {
+ // Top-level resourceSet, or resourceSet within an untyped resourceSet.
+ return null;
+ }
+ }
+
+ if (parentScope.State == WriterState.NestedResourceInfoWithContent)
+ {
+ // Get the scope of the nested resource info
+ return (NestedResourceInfoScope)parentScope;
+ }
+
+ // The parent scope of a resource can only be a resourceSet or an expanded nav link
+ throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_ParentNestedResourceInfoScope));
+ }
+ }
+
+ ///
+ /// Validator to validate consistency of collection items (or null if no such validator applies to the current scope).
+ ///
+ private ResourceSetWithoutExpectedTypeValidator CurrentResourceSetValidator
+ {
+ get
+ {
+ Debug.Assert(this.State == WriterState.Resource || this.State == WriterState.DeletedResource || this.State == WriterState.Primitive, "CurrentCollectionValidator should only be called while writing a resource.");
+
+ ResourceSetBaseScope resourceSetScope = this.ParentScope as ResourceSetBaseScope;
+ return resourceSetScope == null ? null : resourceSetScope.ResourceTypeValidator;
+ }
+ }
+
+ ///
+ /// Flushes the write buffer to the underlying stream.
+ ///
+ public sealed override void Flush()
+ {
+ this.VerifyCanFlush(true);
+
+ // Make sure we switch to writer state Error if an exception is thrown during flushing.
+ try
+ {
+ this.FlushSynchronously();
+ }
+ catch
+ {
+ this.EnterScope(WriterState.Error, null);
+ throw;
+ }
+ }
+
+ ///
+ /// Asynchronously flushes the write buffer to the underlying stream.
+ ///
+ /// A task instance that represents the asynchronous operation.
+ public sealed override Task FlushAsync()
+ {
+ this.VerifyCanFlush(false);
+
+ // Make sure we switch to writer state Error if an exception is thrown during flushing.
+ return this.FlushAsynchronously().FollowOnFaultWith(t => this.EnterScope(WriterState.Error, null));
+ }
+
+ ///
+ /// Start writing a resourceSet.
+ ///
+ /// Resource Set/collection to write.
+ public sealed override void WriteStart(ODataResourceSet resourceSet)
+ {
+ this.VerifyCanWriteStartResourceSet(true, resourceSet);
+ this.WriteStartResourceSetImplementation(resourceSet);
+ }
+
+ ///
+ /// Asynchronously start writing a resourceSet.
+ ///
+ /// Resource Set/collection to write.
+ /// A task instance that represents the asynchronous write operation.
+ public sealed override async Task WriteStartAsync(ODataResourceSet resourceSet)
+ {
+ await this.VerifyCanWriteStartResourceSetAsync(false, resourceSet)
+ .ConfigureAwait(false);
+ await this.WriteStartResourceSetImplementationAsync(resourceSet)
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Start writing a delta resource Set.
+ ///
+ /// Resource Set/collection to write.
+ public sealed override void WriteStart(ODataDeltaResourceSet deltaResourceSet)
+ {
+ this.VerifyCanWriteStartDeltaResourceSet(true, deltaResourceSet);
+ this.WriteStartDeltaResourceSetImplementation(deltaResourceSet);
+ }
+
+ ///
+ /// Asynchronously start writing a delta resourceSet.
+ ///
+ /// Resource Set/collection to write.
+ /// A task instance that represents the asynchronous write operation.
+ public sealed override async Task WriteStartAsync(ODataDeltaResourceSet deltaResourceSet)
+ {
+ await this.VerifyCanWriteStartDeltaResourceSetAsync(false, deltaResourceSet)
+ .ConfigureAwait(false);
+ await this.WriteStartDeltaResourceSetImplementationAsync(deltaResourceSet)
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Start writing a resource.
+ ///
+ /// Resource/item to write.
+ public sealed override void WriteStart(ODataResource resource)
+ {
+ this.VerifyCanWriteStartResource(true, resource);
+ this.WriteStartResourceImplementation(resource);
+ }
+
+ ///
+ /// Asynchronously start writing a resource.
+ ///
+ /// Resource/item to write.
+ /// A task instance that represents the asynchronous write operation.
+ public sealed override async Task WriteStartAsync(ODataResource resource)
+ {
+ this.VerifyCanWriteStartResource(false, resource);
+ await this.WriteStartResourceImplementationAsync(resource)
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Start writing a delta deleted resource.
+ ///
+ /// The delta deleted resource to write.
+ public sealed override void WriteStart(ODataDeletedResource deletedResource)
+ {
+ this.VerifyCanWriteStartDeletedResource(true, deletedResource);
+ this.WriteStartDeletedResourceImplementation(deletedResource);
+ }
+
+ ///
+ /// Asynchronously write a delta deleted resource.
+ ///
+ /// The delta deleted resource to write.
+ /// A task instance that represents the asynchronous write operation.
+ public sealed override async Task WriteStartAsync(ODataDeletedResource deletedResource)
+ {
+ this.VerifyCanWriteStartDeletedResource(false, deletedResource);
+ await this.WriteStartDeletedResourceImplementationAsync(deletedResource)
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Writing a delta link.
+ ///
+ /// The delta link to write.
+ public override void WriteDeltaLink(ODataDeltaLink deltaLink)
+ {
+ this.VerifyCanWriteLink(true, deltaLink);
+ this.WriteDeltaLinkImplementation(deltaLink);
+ }
+
+ ///
+ /// Asynchronously writing a delta link.
+ ///
+ /// The delta link to write.
+ /// A task instance that represents the asynchronous write operation.
+ public override async Task WriteDeltaLinkAsync(ODataDeltaLink deltaLink)
+ {
+ this.VerifyCanWriteLink(false, deltaLink);
+ await this.WriteDeltaLinkImplementationAsync(deltaLink)
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Writing a delta deleted link.
+ ///
+ /// The delta link to write.
+ public override void WriteDeltaDeletedLink(ODataDeltaDeletedLink deltaLink)
+ {
+ this.VerifyCanWriteLink(true, deltaLink);
+ this.WriteDeltaLinkImplementation(deltaLink);
+ }
+
+ ///
+ /// Asynchronously writing a delta link.
+ ///
+ /// The delta link to write.
+ /// A task instance that represents the asynchronous write operation.
+ public override async Task WriteDeltaDeletedLinkAsync(ODataDeltaDeletedLink deltaLink)
+ {
+ this.VerifyCanWriteLink(false, deltaLink);
+ await this.WriteDeltaLinkImplementationAsync(deltaLink)
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Write a primitive value within an untyped collection.
+ ///
+ /// Primitive value to write.
+ public sealed override void WritePrimitive(ODataPrimitiveValue primitiveValue)
+ {
+ this.VerifyCanWritePrimitive(true, primitiveValue);
+ this.WritePrimitiveValueImplementation(primitiveValue);
+ }
+
+ ///
+ /// Asynchronously write a primitive value.
+ ///
+ /// Primitive value to write.
+ /// A task instance that represents the asynchronous write operation.
+ public sealed override async Task WritePrimitiveAsync(ODataPrimitiveValue primitiveValue)
+ {
+ this.VerifyCanWritePrimitive(false, primitiveValue);
+ await this.WritePrimitiveValueImplementationAsync(primitiveValue)
+ .ConfigureAwait(false);
+ }
+
+ /// Writes a primitive property within a resource.
+ /// The primitive property to write.
+ public sealed override void WriteStart(ODataPropertyInfo primitiveProperty)
+ {
+ this.VerifyCanWriteProperty(true, primitiveProperty);
+ this.WriteStartPropertyImplementation(primitiveProperty);
+ }
+
+ /// Asynchronously write a primitive property within a resource.
+ /// A task instance that represents the asynchronous write operation.
+ /// The primitive property to write.
+ public sealed override async Task WriteStartAsync(ODataPropertyInfo primitiveProperty)
+ {
+ this.VerifyCanWriteProperty(false, primitiveProperty);
+ await this.WriteStartPropertyImplementationAsync(primitiveProperty)
+ .ConfigureAwait(false);
+ }
+
+ /// Creates a stream for writing a binary value.
+ /// A stream to write a binary value to.
+ public sealed override Stream CreateBinaryWriteStream()
+ {
+ this.VerifyCanCreateWriteStream(true);
+ return this.CreateWriteStreamImplementation();
+ }
+
+ /// Asynchronously creates a stream for writing a binary value.
+ /// A task that represents the asynchronous operation.
+ /// The value of the TResult parameter contains a to write a binary value to.
+ public sealed override async Task CreateBinaryWriteStreamAsync()
+ {
+ this.VerifyCanCreateWriteStream(false);
+ return await this.CreateWriteStreamImplementationAsync()
+ .ConfigureAwait(false);
+ }
+
+ /// Creates a TextWriter for writing a string value.
+ /// A TextWriter to write a string value to.
+ public sealed override TextWriter CreateTextWriter()
+ {
+ this.VerifyCanCreateTextWriter(true);
+ return this.CreateTextWriterImplementation();
+ }
+
+ /// Asynchronously creates a for writing a string value.
+ /// A task that represents the asynchronous operation.
+ /// The value of the TResult parameter contains a to write a string value to.
+ public sealed override async Task CreateTextWriterAsync()
+ {
+ this.VerifyCanCreateWriteStream(false);
+ return await this.CreateTextWriterImplementationAsync()
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Start writing a nested resource info.
+ ///
+ /// Navigation link to write.
+ public sealed override void WriteStart(ODataNestedResourceInfo nestedResourceInfo)
+ {
+ this.VerifyCanWriteStartNestedResourceInfo(true, nestedResourceInfo);
+ this.WriteStartNestedResourceInfoImplementation(nestedResourceInfo);
+ }
+
+
+ ///
+ /// Asynchronously start writing a nested resource info.
+ ///
+ /// Navigation link to writer.
+ /// A task instance that represents the asynchronous write operation.
+ public sealed override async Task WriteStartAsync(ODataNestedResourceInfo nestedResourceInfo)
+ {
+ this.VerifyCanWriteStartNestedResourceInfo(false, nestedResourceInfo);
+ // Currently, no asynchronous operation is involved when commencing with writing a nested resource info
+ await TaskUtils.GetTaskForSynchronousOperation(
+ () => this.WriteStartNestedResourceInfoImplementation(nestedResourceInfo)).ConfigureAwait(false);
+ }
+
+ ///
+ /// Finish writing a resourceSet/resource/nested resource info.
+ ///
+ public sealed override void WriteEnd()
+ {
+ this.VerifyCanWriteEnd(true);
+ this.WriteEndImplementation();
+ if (this.CurrentScope.State == WriterState.Completed)
+ {
+ // Note that we intentionally go through the public API so that if the Flush fails the writer moves to the Error state.
+ this.Flush();
+ }
+ }
+
+
+ ///
+ /// Asynchronously finish writing a resourceSet/resource/nested resource info.
+ ///
+ /// A task instance that represents the asynchronous write operation.
+ public sealed override async Task WriteEndAsync()
+ {
+ this.VerifyCanWriteEnd(false);
+ await this.WriteEndImplementationAsync()
+ .ConfigureAwait(false);
+
+ if (this.CurrentScope.State == WriterState.Completed)
+ {
+ await this.FlushAsync()
+ .ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Writes an entity reference link, which is used to represent binding to an existing resource in a request payload.
+ ///
+ /// The entity reference link to write.
+ ///
+ /// This method can only be called for writing request messages. The entity reference link must be surrounded
+ /// by a navigation link written through WriteStart/WriteEnd.
+ /// The will be ignored in that case and the Uri from the will be used
+ /// as the binding URL to be written.
+ ///
+ public sealed override void WriteEntityReferenceLink(ODataEntityReferenceLink entityReferenceLink)
+ {
+ this.VerifyCanWriteEntityReferenceLink(entityReferenceLink, true);
+ this.WriteEntityReferenceLinkImplementation(entityReferenceLink);
+ }
+
+
+ ///
+ /// Asynchronously writes an entity reference link, which is used to represent binding to an existing resource in a request payload.
+ ///
+ /// The entity reference link to write.
+ /// A task instance that represents the asynchronous write operation.
+ ///
+ /// This method can only be called for writing request messages. The entity reference link must be surrounded
+ /// by a navigation link written through WriteStart/WriteEnd.
+ /// The will be ignored in that case and the Uri from the will be used
+ /// as the binding URL to be written.
+ ///
+ public sealed override async Task WriteEntityReferenceLinkAsync(ODataEntityReferenceLink entityReferenceLink)
+ {
+ this.VerifyCanWriteEntityReferenceLink(entityReferenceLink, false);
+ await this.WriteEntityReferenceLinkImplementationAsync(entityReferenceLink)
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// This method notifies the listener, that an in-stream error is to be written.
+ ///
+ ///
+ /// This listener can choose to fail, if the currently written payload doesn't support in-stream error at this position.
+ /// If the listener returns, the writer should not allow any more writing, since the in-stream error is the last thing in the payload.
+ ///
+ void IODataOutputInStreamErrorListener.OnInStreamError()
+ {
+ this.VerifyNotDisposed();
+
+ // We're in a completed state trying to write an error (we can't write error after the payload was finished as it might
+ // introduce another top-level element in XML)
+ if (this.State == WriterState.Completed)
+ {
+ throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), WriterState.Error.ToString()));
+ }
+
+ this.StartPayloadInStartState();
+ this.EnterScope(WriterState.Error, this.CurrentScope.Item);
+ }
+
+ ///
+ async Task IODataOutputInStreamErrorListener.OnInStreamErrorAsync()
+ {
+ this.VerifyNotDisposed();
+
+ // We're in a completed state trying to write an error (we can't write error after the payload was finished as it might
+ // introduce another top-level element in XML)
+ if (this.State == WriterState.Completed)
+ {
+ throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), WriterState.Error.ToString()));
+ }
+
+ await this.StartPayloadInStartStateAsync()
+ .ConfigureAwait(false);
+ this.EnterScope(WriterState.Error, this.CurrentScope.Item);
+ }
+
+ ///
+ /// This method is called when a stream is requested. It is a no-op.
+ ///
+ void IODataStreamListener.StreamRequested()
+ {
+ }
+
+ ///
+ /// This method is called when an async stream is requested. It is a no-op.
+ ///
+ /// A task for method called when a stream is requested.
+ Task IODataStreamListener.StreamRequestedAsync()
+ {
+ return TaskUtils.CompletedTask;
+ }
+
+ ///
+ /// This method is called when a stream is disposed.
+ ///
+ void IODataStreamListener.StreamDisposed()
+ {
+ Debug.Assert(this.State == WriterState.Stream || this.State == WriterState.String, "Stream was disposed when not in WriterState.Stream state.");
+
+ // Complete writing the stream
+ if (this.State == WriterState.Stream)
+ {
+ this.EndBinaryStream();
+ }
+ else if (this.State == WriterState.String)
+ {
+ this.EndTextWriter();
+ }
+
+ this.LeaveScope();
+ }
+
+ ///
+ /// This method is called asynchronously when a stream is disposed.
+ ///
+ /// A task that represents the asynchronous operation.
+ async Task IODataStreamListener.StreamDisposedAsync()
+ {
+ Debug.Assert(this.State == WriterState.Stream || this.State == WriterState.String,
+ "Stream was disposed when not in WriterState.Stream state.");
+
+ // Complete writing the stream
+ if (this.State == WriterState.Stream)
+ {
+ await this.EndBinaryStreamAsync()
+ .ConfigureAwait(false);
+ }
+ else if (this.State == WriterState.String)
+ {
+ await this.EndTextWriterAsync()
+ .ConfigureAwait(false);
+ }
+
+ await this.LeaveScopeAsync()
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Get instance of the parent resource scope
+ ///
+ ///
+ /// The parent resource scope
+ /// Or null if there is no parent resource scope
+ ///
+ protected ResourceScope GetParentResourceScope()
+ {
+ ScopeStack scopeStack = new ScopeStack();
+ Scope parentResourceScope = null;
+
+ if (this.scopeStack.Count > 0)
+ {
+ // pop current scope and push into scope stack
+ scopeStack.Push(this.scopeStack.Pop());
+ }
+
+ while (this.scopeStack.Count > 0)
+ {
+ Scope scope = this.scopeStack.Pop();
+ scopeStack.Push(scope);
+
+ if (scope is ResourceScope)
+ {
+ parentResourceScope = scope;
+ break;
+ }
+ }
+
+ while (scopeStack.Count > 0)
+ {
+ Scope scope = scopeStack.Pop();
+ this.scopeStack.Push(scope);
+ }
+
+ return parentResourceScope as ResourceScope;
+ }
+
+ ///
+ /// Determines whether a given writer state is considered an error state.
+ ///
+ /// The writer state to check.
+ /// True if the writer state is an error state; otherwise false.
+ protected static bool IsErrorState(WriterState state)
+ {
+ return state == WriterState.Error;
+ }
+
+ ///
+ /// Check if the object has been disposed; called from all public API methods. Throws an ObjectDisposedException if the object
+ /// has already been disposed.
+ ///
+ protected abstract void VerifyNotDisposed();
+
+ ///
+ /// Flush the output.
+ ///
+ protected abstract void FlushSynchronously();
+
+
+ ///
+ /// Flush the output.
+ ///
+ /// Task representing the pending flush operation.
+ protected abstract Task FlushAsynchronously();
+
+ ///
+ /// Start writing an OData payload.
+ ///
+ protected abstract void StartPayload();
+
+ ///
+ /// Start writing a resource.
+ ///
+ /// The resource to write.
+ protected abstract void StartResource(ODataResource resource);
+
+ ///
+ /// Finish writing a resource.
+ ///
+ /// The resource to write.
+ protected abstract void EndResource(ODataResource resource);
+
+ ///
+ /// Start writing a single property.
+ ///
+ /// The property to write.
+ protected virtual void StartProperty(ODataPropertyInfo property)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Finish writing a property.
+ ///
+ /// The property to write.
+ protected virtual void EndProperty(ODataPropertyInfo property)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Start writing a resourceSet.
+ ///
+ /// The resourceSet to write.
+ protected abstract void StartResourceSet(ODataResourceSet resourceSet);
+
+ ///
+ /// Start writing a delta resource set.
+ ///
+ /// The delta resource set to write.
+ protected virtual void StartDeltaResourceSet(ODataDeltaResourceSet deltaResourceSet)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Start writing a deleted resource.
+ ///
+ /// The deleted entry to write.
+ protected virtual void StartDeletedResource(ODataDeletedResource deletedEntry)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Write a delta link or delta deleted link.
+ ///
+ /// The deleted entry to write.
+ protected virtual void StartDeltaLink(ODataDeltaLinkBase deltaLink)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Create a stream to write a binary value.
+ ///
+ /// A stream for writing the binary value.
+ protected virtual Stream StartBinaryStream()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Finish writing a stream.
+ ///
+ protected virtual void EndBinaryStream()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Create a TextWriter to write a string value.
+ ///
+ /// A TextWriter for writing the string value.
+ protected virtual TextWriter StartTextWriter()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Finish writing a string value.
+ ///
+ protected virtual void EndTextWriter()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Finish writing an OData payload.
+ ///
+ protected abstract void EndPayload();
+
+ ///
+ /// Finish writing a resourceSet.
+ ///
+ /// The resourceSet to write.
+ protected abstract void EndResourceSet(ODataResourceSet resourceSet);
+
+ ///
+ /// Finish writing a delta resource set.
+ ///
+ /// The delta resource set to write.
+ protected virtual void EndDeltaResourceSet(ODataDeltaResourceSet deltaResourceSet)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Finish writing a deleted resource.
+ ///
+ /// The delta resource set to write.
+ protected virtual void EndDeletedResource(ODataDeletedResource deletedResource)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Write a primitive value within an untyped collection.
+ ///
+ /// The primitive value to write.
+ protected virtual void WritePrimitiveValue(ODataPrimitiveValue primitiveValue)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Write a deferred (non-expanded) nested resource info.
+ ///
+ /// The nested resource info to write.
+ protected abstract void WriteDeferredNestedResourceInfo(ODataNestedResourceInfo nestedResourceInfo);
+
+ ///
+ /// Start writing a nested resource info with content.
+ ///
+ /// The nested resource info to write.
+ protected abstract void StartNestedResourceInfoWithContent(ODataNestedResourceInfo nestedResourceInfo);
+
+ ///
+ /// Finish writing a nested resource info with content.
+ ///
+ /// The nested resource info to write.
+ protected abstract void EndNestedResourceInfoWithContent(ODataNestedResourceInfo nestedResourceInfo);
+
+ ///
+ /// Write an entity reference link into a navigation link content.
+ ///
+ /// The parent navigation link which is being written around the entity reference link.
+ /// The entity reference link to write.
+ protected abstract void WriteEntityReferenceInNavigationLinkContent(ODataNestedResourceInfo parentNestedResourceInfo, ODataEntityReferenceLink entityReferenceLink);
+
+ ///
+ /// Create a new resource set scope.
+ ///
+ /// The resource set for the new scope.
+ /// The navigation source we are going to write resource set for.
+ /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used).
+ /// true if the content of the scope to create should not be written.
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ /// true if the resource set is for an undeclared property
+ /// The newly create scope.
+ protected abstract ResourceSetScope CreateResourceSetScope(ODataResourceSet resourceSet, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared);
+
+ ///
+ /// Create a new delta resource set scope.
+ ///
+ /// The delta resource set for the new scope.
+ /// The navigation source we are going to write resource set for.
+ /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used).
+ /// true if the content of the scope to create should not be written.
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ /// true if the resource set is for an undeclared property
+ /// The newly create scope.
+ protected virtual DeltaResourceSetScope CreateDeltaResourceSetScope(ODataDeltaResourceSet deltaResourceSet, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Create a new resource scope.
+ ///
+ /// The resource for the new scope.
+ /// The navigation source we are going to write resource set for.
+ /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used).
+ /// true if the content of the scope to create should not be written.
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ /// true if the resource is for an undeclared property
+ /// The newly create scope.
+ protected abstract ResourceScope CreateResourceScope(ODataResource resource, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared);
+
+ ///
+ /// Create a new resource scope.
+ ///
+ /// The (deleted) resource for the new scope.
+ /// The navigation source we are going to write resource set for.
+ /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used).
+ /// true if the content of the scope to create should not be written.
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ /// true if the resource is for an undeclared property
+ /// The newly create scope.
+ protected virtual DeletedResourceScope CreateDeletedResourceScope(ODataDeletedResource resource, IEdmNavigationSource navigationSource, IEdmEntityType resourceType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool isUndeclared)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Create a new property scope.
+ ///
+ /// The property for the new scope.
+ /// The navigation source.
+ /// The structured type for the resource containing the property to be written.
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ /// The newly created property scope.
+ protected virtual PropertyInfoScope CreatePropertyInfoScope(ODataPropertyInfo property, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Create a new delta link scope.
+ ///
+ /// The link for the new scope.
+ /// The navigation source we are going to write entities for.
+ /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used).
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ /// The newly create scope.
+ protected virtual DeltaLinkScope CreateDeltaLinkScope(ODataDeltaLinkBase link, IEdmNavigationSource navigationSource, IEdmEntityType entityType, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Gets the serialization info for the given resource.
+ ///
+ /// The resource to get the serialization info for.
+ /// The serialization info for the given resource.
+ protected ODataResourceSerializationInfo GetResourceSerializationInfo(ODataResourceBase resource)
+ {
+ // Need to check for null for the resource since we can be writing a null reference to a navigation property.
+ ODataResourceSerializationInfo serializationInfo = resource == null ? null : resource.SerializationInfo;
+
+ // Always try to use the serialization info from the resource first. If it is not found on the resource, use the one inherited from the parent resourceSet.
+ // Note that we don't try to guard against inconsistent serialization info between entries and their parent resourceSet.
+ if (serializationInfo != null)
+ {
+ return serializationInfo;
+ }
+
+ ODataResourceSetBase resourceSet = this.CurrentScope.Item as ODataResourceSetBase;
+ if (resourceSet != null)
+ {
+ return resourceSet.SerializationInfo;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets the serialization info for the given delta link.
+ ///
+ /// The resource to get the serialization info for.
+ /// The serialization info for the given resource.
+ protected ODataResourceSerializationInfo GetLinkSerializationInfo(ODataItem item)
+ {
+ Debug.Assert(item != null, "item != null");
+
+ ODataDeltaSerializationInfo deltaSerializationInfo = null;
+ ODataResourceSerializationInfo resourceSerializationInfo = null;
+
+ ODataDeltaLink deltaLink = item as ODataDeltaLink;
+ if (deltaLink != null)
+ {
+ deltaSerializationInfo = deltaLink.SerializationInfo;
+ }
+
+ ODataDeltaDeletedLink deltaDeletedLink = item as ODataDeltaDeletedLink;
+ if (deltaDeletedLink != null)
+ {
+ deltaSerializationInfo = deltaDeletedLink.SerializationInfo;
+ }
+
+ if (deltaSerializationInfo == null)
+ {
+ DeltaResourceSetScope parentDeltaResourceSetScope = this.CurrentScope as DeltaResourceSetScope;
+ if (parentDeltaResourceSetScope != null)
+ {
+ ODataDeltaResourceSet resourceSet = (ODataDeltaResourceSet)parentDeltaResourceSetScope.Item;
+ Debug.Assert(resourceSet != null, "resourceSet != null");
+
+ ODataResourceSerializationInfo deltaSetSerializationInfo = resourceSet.SerializationInfo;
+ if (deltaSetSerializationInfo != null)
+ {
+ resourceSerializationInfo = deltaSetSerializationInfo;
+ }
+ }
+ }
+ else
+ {
+ resourceSerializationInfo = new ODataResourceSerializationInfo()
+ {
+ NavigationSourceName = deltaSerializationInfo.NavigationSourceName
+ };
+ }
+
+ return resourceSerializationInfo;
+ }
+
+ ///
+ /// Creates a new nested resource info scope.
+ ///
+ /// The writer state for the new scope.
+ /// The nested resource info for the new scope.
+ /// The navigation source we are going to write entities for.
+ /// The type for the items in the resourceSet to be written (or null if the resource set base type should be used).
+ /// true if the content of the scope to create should not be written.
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ /// The newly created nested resource info scope.
+ protected virtual NestedResourceInfoScope CreateNestedResourceInfoScope(
+ WriterState writerState,
+ ODataNestedResourceInfo navLink,
+ IEdmNavigationSource navigationSource,
+ IEdmType itemType,
+ bool skipWriting,
+ SelectedPropertiesNode selectedProperties,
+ ODataUri odataUri)
+ {
+ Debug.Assert(this.CurrentScope != null, "Creating a nested resource info scope with a null parent scope.");
+ return new NestedResourceInfoScope(writerState, navLink, navigationSource, itemType, skipWriting, selectedProperties, odataUri, this.CurrentScope);
+ }
+
+ ///
+ /// Place where derived writers can perform custom steps before the resource is written, at the beginning of WriteStartEntryImplementation.
+ ///
+ /// The ResourceScope.
+ /// Resource to write.
+ /// True if writing response.
+ /// The selected properties of this scope.
+ protected virtual void PrepareResourceForWriteStart(ResourceScope resourceScope, ODataResource resource, bool writingResponse, SelectedPropertiesNode selectedProperties)
+ {
+ // No-op Atom and Verbose JSON. The JSON Light writer will override this method and inject the appropriate metadata builder
+ // into the resource before writing.
+ // Actually we can inject the metadata builder in here and
+ // remove virtual from this method.
+ }
+
+ ///
+ /// Place where derived writers can perform custom steps before the deleted resource is written, at the beginning of WriteStartEntryImplementation.
+ ///
+ /// The ResourceScope.
+ /// Resource to write.
+ /// True if writing response.
+ /// The selected properties of this scope.
+ protected virtual void PrepareDeletedResourceForWriteStart(DeletedResourceScope resourceScope, ODataDeletedResource deletedResource, bool writingResponse, SelectedPropertiesNode selectedProperties)
+ {
+ // No-op Atom and Verbose JSON. The JSON Light writer will override this method and inject the appropriate metadata builder
+ // into the resource before writing.
+ // Actually we can inject the metadata builder in here and
+ // remove virtual from this method.
+ }
+
+ ///
+ /// Gets the type of the resource and validates it against the model.
+ ///
+ /// The resource to get the type for.
+ /// The validated structured type.
+ protected IEdmStructuredType GetResourceType(ODataResourceBase resource)
+ {
+ return TypeNameOracle.ResolveAndValidateTypeFromTypeName(
+ this.outputContext.Model,
+ this.CurrentScope.ResourceType,
+ resource.TypeName,
+ this.WriterValidator);
+ }
+
+ ///
+ /// Gets the element type of the resource set and validates it against the model.
+ ///
+ /// The resource set to get the element type for.
+ /// The validated structured element type.
+ protected IEdmStructuredType GetResourceSetType(ODataResourceSetBase resourceSet)
+ {
+ return TypeNameOracle.ResolveAndValidateTypeFromTypeName(
+ this.outputContext.Model,
+ this.CurrentScope.ResourceType,
+ EdmLibraryExtensions.GetCollectionItemTypeName(resourceSet.TypeName),
+ this.WriterValidator);
+ }
+
+ ///
+ /// Validates that the ODataResourceSet.DeltaLink is null for the given expanded resourceSet.
+ ///
+ /// The expanded resourceSet in question.
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "An instance field is used in a debug assert.")]
+ protected void ValidateNoDeltaLinkForExpandedResourceSet(ODataResourceSet resourceSet)
+ {
+ Debug.Assert(resourceSet != null, "resourceSet != null");
+ Debug.Assert(
+ this.ParentNestedResourceInfo != null && (!this.ParentNestedResourceInfo.IsCollection.HasValue || this.ParentNestedResourceInfo.IsCollection.Value == true),
+ "This should only be called when writing an expanded resourceSet.");
+
+ if (resourceSet.DeltaLink != null)
+ {
+ throw new ODataException(Strings.ODataWriterCore_DeltaLinkNotSupportedOnExpandedResourceSet);
+ }
+ }
+
+ ///
+ /// Asynchronously start writing an OData payload.
+ ///
+ /// A task that represents the asynchronous write operation.
+ protected abstract Task StartPayloadAsync();
+
+ ///
+ /// Asynchronously finish writing an OData payload.
+ ///
+ /// A task that represents the asynchronous write operation.
+ protected abstract Task EndPayloadAsync();
+
+ ///
+ /// Asynchronously start writing a resource.
+ ///
+ /// The resource to write.
+ /// A task that represents the asynchronous write operation.
+ protected abstract Task StartResourceAsync(ODataResource resource);
+
+ ///
+ /// Asynchronously finish writing a resource.
+ ///
+ /// The resource to write.
+ /// A task that represents the asynchronous write operation.
+ protected abstract Task EndResourceAsync(ODataResource resource);
+
+ ///
+ /// Asynchronously start writing a single property.
+ ///
+ /// The property to write.
+ /// A task that represents the asynchronous write operation.
+ protected virtual Task StartPropertyAsync(ODataPropertyInfo property)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Asynchronously finish writing a property.
+ ///
+ /// The property to write.
+ /// A task that represents the asynchronous write operation.
+ protected virtual Task EndPropertyAsync(ODataPropertyInfo property)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Asynchronously start writing a resourceSet.
+ ///
+ /// The resourceSet to write.
+ /// A task that represents the asynchronous write operation.
+ protected abstract Task StartResourceSetAsync(ODataResourceSet resourceSet);
+
+ ///
+ /// Asynchronously finish writing a resourceSet.
+ ///
+ /// The resourceSet to write.
+ /// A task that represents the asynchronous write operation.
+ protected abstract Task EndResourceSetAsync(ODataResourceSet resourceSet);
+
+ ///
+ /// Asynchronously start writing a delta resource set.
+ ///
+ /// The delta resource set to write.
+ /// A task that represents the asynchronous write operation.
+ protected virtual Task StartDeltaResourceSetAsync(ODataDeltaResourceSet deltaResourceSet)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Asynchronously finish writing a delta resource set.
+ ///
+ /// The delta resource set to write.
+ /// A task that represents the asynchronous write operation.
+ protected virtual Task EndDeltaResourceSetAsync(ODataDeltaResourceSet deltaResourceSet)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Asynchronously start writing a deleted resource.
+ ///
+ /// The deleted entry to write.
+ /// A task that represents the asynchronous write operation.
+ protected virtual Task StartDeletedResourceAsync(ODataDeletedResource deletedEntry)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Asynchronously finish writing a deleted resource.
+ ///
+ /// The delta resource set to write.
+ /// A task that represents the asynchronous write operation.
+ protected virtual Task EndDeletedResourceAsync(ODataDeletedResource deletedResource)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Asynchronously write a delta link or delta deleted link.
+ ///
+ /// The deleted entry to write.
+ /// A task that represents the asynchronous write operation.
+ protected virtual Task StartDeltaLinkAsync(ODataDeltaLinkBase deltaLink)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Asynchronously create a to write a binary value.
+ ///
+ /// A task that represents the asynchronous write operation.
+ /// The value of the TResult parameter contains the for writing the binary value.
+ protected virtual Task StartBinaryStreamAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Asynchronously finish writing a stream.
+ ///
+ /// A task that represents the asynchronous write operation.
+ protected virtual Task EndBinaryStreamAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Asynchronously create a to write a string value.
+ ///
+ /// A task that represents the asynchronous operation.
+ /// The value of the TResult parameter contains the for writing a string value.
+ protected virtual Task StartTextWriterAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Asynchronously finish writing a string value.
+ ///
+ /// A task that represents the asynchronous write operation.
+ protected virtual Task EndTextWriterAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Asynchronously write a primitive value within an untyped collection.
+ ///
+ /// The primitive value to write.
+ /// A task that represents the asynchronous write operation.
+ protected virtual Task WritePrimitiveValueAsync(ODataPrimitiveValue primitiveValue)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Asynchronously write a deferred (non-expanded) nested resource info.
+ ///
+ /// The nested resource info to write.
+ /// A task that represents the asynchronous write operation.
+ protected abstract Task WriteDeferredNestedResourceInfoAsync(ODataNestedResourceInfo nestedResourceInfo);
+
+ ///
+ /// Asynchronously start writing a nested resource info with content.
+ ///
+ /// The nested resource info to write.
+ /// A task that represents the asynchronous write operation.
+ protected abstract Task StartNestedResourceInfoWithContentAsync(ODataNestedResourceInfo nestedResourceInfo);
+
+ ///
+ /// Asynchronously finish writing a nested resource info with content.
+ ///
+ /// The nested resource info to write.
+ /// A task that represents the asynchronous write operation.
+ protected abstract Task EndNestedResourceInfoWithContentAsync(ODataNestedResourceInfo nestedResourceInfo);
+
+ ///
+ /// Asynchronously write an entity reference link into a navigation link content.
+ ///
+ /// The parent navigation link which is being written around the entity reference link.
+ /// The entity reference link to write.
+ /// A task that represents the asynchronous write operation.
+ protected abstract Task WriteEntityReferenceInNavigationLinkContentAsync(
+ ODataNestedResourceInfo parentNestedResourceInfo,
+ ODataEntityReferenceLink entityReferenceLink);
+
+ ///
+ /// Place where derived writers can perform custom steps before the resource is written,
+ /// at the beginning of WriteStartAsync(ODataResource).
+ ///
+ /// The ResourceScope.
+ /// Resource to write.
+ /// True if writing response.
+ /// The selected properties of this scope.
+ protected virtual Task PrepareResourceForWriteStartAsync(
+ ResourceScope resourceScope,
+ ODataResource resource,
+ bool writingResponse,
+ SelectedPropertiesNode selectedProperties)
+ {
+ // No-op Atom and Verbose JSON.
+ // ODataJsonLightWriter will override this method and inject the appropriate metadata builder
+ // into the resource before writing.
+ return TaskUtils.CompletedTask;
+ }
+
+ ///
+ /// Place where derived writers can perform custom steps before the deleted resource is written,
+ /// at the beginning of WriteStartAsync(ODataDeletedResource).
+ ///
+ /// The ResourceScope.
+ /// Resource to write.
+ /// True if writing response.
+ /// The selected properties of this scope.
+ protected virtual Task PrepareDeletedResourceForWriteStartAsync(
+ DeletedResourceScope resourceScope,
+ ODataDeletedResource deletedResource,
+ bool writingResponse,
+ SelectedPropertiesNode selectedProperties)
+ {
+ // No-op Atom and Verbose JSON.
+ // ODataJsonLightWriter will override this method and inject the appropriate metadata builder
+ // into the resource before writing.
+ return TaskUtils.CompletedTask;
+ }
+
+ ///
+ /// Verifies that calling WriteStart resourceSet is valid.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ /// Resource Set/collection to write.
+ private void VerifyCanWriteStartResourceSet(bool synchronousCall, ODataResourceSet resourceSet)
+ {
+ ExceptionUtils.CheckArgumentNotNull(resourceSet, "resourceSet");
+
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+ this.StartPayloadInStartState();
+ }
+
+ ///
+ /// Start writing a resourceSet - implementation of the actual functionality.
+ ///
+ /// The resource set to write.
+ private void WriteStartResourceSetImplementation(ODataResourceSet resourceSet)
+ {
+ this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.ResourceSet, resourceSet);
+ this.EnterScope(WriterState.ResourceSet, resourceSet);
+
+ if (!this.SkipWriting)
+ {
+ this.InterceptException(
+ (thisParam, resourceSetParam) =>
+ {
+ // Verify query count
+ if (resourceSetParam.Count.HasValue)
+ {
+ // Check that Count is not set for requests
+ if (!thisParam.outputContext.WritingResponse)
+ {
+ thisParam.ThrowODataException(Strings.ODataWriterCore_QueryCountInRequest, resourceSetParam);
+ }
+
+ // Verify version requirements
+ }
+
+ thisParam.StartResourceSet(resourceSetParam);
+ }, resourceSet);
+ }
+ }
+
+ ///
+ /// Verifies that calling WriteStart deltaResourceSet is valid.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ /// Resource Set/collection to write.
+ private void VerifyCanWriteStartDeltaResourceSet(bool synchronousCall, ODataDeltaResourceSet deltaResourceSet)
+ {
+ ExceptionUtils.CheckArgumentNotNull(deltaResourceSet, "deltaResourceSet");
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+ this.StartPayloadInStartState();
+ }
+
+ ///
+ /// Start writing a delta resource set - implementation of the actual functionality.
+ ///
+ /// The delta resource Set to write.
+ private void WriteStartDeltaResourceSetImplementation(ODataDeltaResourceSet deltaResourceSet)
+ {
+ this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.ResourceSet, deltaResourceSet);
+ this.EnterScope(WriterState.DeltaResourceSet, deltaResourceSet);
+
+ this.InterceptException(
+ (thisParam, deltaResourceSetParam) =>
+ {
+ // Check that links are not set for requests
+ if (!thisParam.outputContext.WritingResponse)
+ {
+ if (deltaResourceSetParam.NextPageLink != null)
+ {
+ thisParam.ThrowODataException(Strings.ODataWriterCore_QueryNextLinkInRequest, deltaResourceSetParam);
+ }
+
+ if (deltaResourceSetParam.DeltaLink != null)
+ {
+ thisParam.ThrowODataException(Strings.ODataWriterCore_QueryDeltaLinkInRequest, deltaResourceSetParam);
+ }
+ }
+
+ thisParam.StartDeltaResourceSet(deltaResourceSetParam);
+ }, deltaResourceSet);
+ }
+
+ ///
+ /// Verifies that calling WriteStart resource is valid.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ /// Resource/item to write.
+ private void VerifyCanWriteStartResource(bool synchronousCall, ODataResource resource)
+ {
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+ }
+
+ ///
+ /// Verifies that calling WriteDeletedResource is valid.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ /// Resource/item to write.
+ private void VerifyCanWriteStartDeletedResource(bool synchronousCall, ODataDeletedResource resource)
+ {
+ ExceptionUtils.CheckArgumentNotNull(resource, "resource");
+
+ this.VerifyWritingDelta();
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+ }
+
+ ///
+ /// Start writing a resource - implementation of the actual functionality.
+ ///
+ /// Resource/item to write.
+ private void WriteStartResourceImplementation(ODataResource resource)
+ {
+ this.StartPayloadInStartState();
+ this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.Resource, resource);
+ this.EnterScope(WriterState.Resource, resource);
+ if (!this.SkipWriting)
+ {
+ this.IncreaseResourceDepth();
+ this.InterceptException(
+ (thisParam, resourceParam) =>
+ {
+ if (resourceParam != null)
+ {
+ ResourceScope resourceScope = (ResourceScope)thisParam.CurrentScope;
+ thisParam.ValidateResourceForResourceSet(resourceParam, resourceScope);
+ thisParam.PrepareResourceForWriteStart(
+ resourceScope,
+ resourceParam,
+ thisParam.outputContext.WritingResponse,
+ resourceScope.SelectedProperties);
+ }
+
+ thisParam.StartResource(resourceParam);
+ }, resource);
+ }
+ }
+
+ ///
+ /// Start writing a delta deleted resource - implementation of the actual functionality.
+ ///
+ /// Resource/item to write.
+ private void WriteStartDeletedResourceImplementation(ODataDeletedResource resource)
+ {
+ Debug.Assert(resource != null, "resource != null");
+
+ this.StartPayloadInStartState();
+ this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.Resource, resource);
+ this.EnterScope(WriterState.DeletedResource, resource);
+ this.IncreaseResourceDepth();
+
+ this.InterceptException(
+ (thisParam, resourceParam) =>
+ {
+ DeletedResourceScope resourceScope = thisParam.CurrentScope as DeletedResourceScope;
+ thisParam.ValidateResourceForResourceSet(resourceParam, resourceScope);
+ thisParam.PrepareDeletedResourceForWriteStart(
+ resourceScope,
+ resourceParam,
+ thisParam.outputContext.WritingResponse,
+ resourceScope.SelectedProperties);
+ thisParam.StartDeletedResource(resourceParam);
+ }, resource);
+ }
+
+ ///
+ /// Verifies that calling WriteStart for a property is valid.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ /// Primitive property to write.
+ private void VerifyCanWriteProperty(bool synchronousCall, ODataPropertyInfo property)
+ {
+ ExceptionUtils.CheckArgumentNotNull(property, "property");
+
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+ }
+
+ ///
+ /// Start writing a property - implementation of the actual functionality.
+ ///
+ /// Property to write.
+ private void WriteStartPropertyImplementation(ODataPropertyInfo property)
+ {
+ this.EnterScope(WriterState.Property, property);
+ if (!this.SkipWriting)
+ {
+ this.InterceptException(
+ (thisParam, propertyParam) =>
+ {
+ thisParam.StartProperty(propertyParam);
+ if (propertyParam is ODataProperty)
+ {
+ PropertyInfoScope scope = thisParam.CurrentScope as PropertyInfoScope;
+ Debug.Assert(scope != null, "Scope for ODataPropertyInfo is not ODataPropertyInfoScope");
+ scope.ValueWritten = true;
+ }
+ }, property);
+ }
+ }
+
+ ///
+ /// Start writing a delta link or delta deleted link - implementation of the actual functionality.
+ ///
+ /// Delta (deleted) link to write.
+ private void WriteDeltaLinkImplementation(ODataDeltaLinkBase deltaLink)
+ {
+ this.EnterScope(deltaLink is ODataDeltaLink ? WriterState.DeltaLink : WriterState.DeltaDeletedLink, deltaLink);
+ this.StartDeltaLink(deltaLink);
+ this.WriteEnd();
+ }
+
+ ///
+ /// Start writing a delta link or delta deleted link - implementation of the actual functionality.
+ ///
+ /// Delta (deleted) link to write.
+ /// The task.
+ private async Task WriteDeltaLinkImplementationAsync(ODataDeltaLinkBase deltaLink)
+ {
+ EnterScope(deltaLink is ODataDeltaLink ? WriterState.DeltaLink : WriterState.DeltaDeletedLink, deltaLink);
+ await this.StartDeltaLinkAsync(deltaLink)
+ .ConfigureAwait(false);
+ await this.WriteEndAsync()
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Verifies that calling WriteStart nested resource info is valid.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ /// Navigation link to write.
+ private void VerifyCanWriteStartNestedResourceInfo(bool synchronousCall, ODataNestedResourceInfo nestedResourceInfo)
+ {
+ ExceptionUtils.CheckArgumentNotNull(nestedResourceInfo, "nestedResourceInfo");
+
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+ }
+
+ ///
+ /// Start writing a nested resource info - implementation of the actual functionality.
+ ///
+ /// Navigation link to write.
+ private void WriteStartNestedResourceInfoImplementation(ODataNestedResourceInfo nestedResourceInfo)
+ {
+ this.EnterScope(WriterState.NestedResourceInfo, nestedResourceInfo);
+
+ // If the parent resource has a metadata builder, use that metadatabuilder on the nested resource info as well.
+ Debug.Assert(this.scopeStack.Parent != null, "Navigation link scopes must have a parent scope.");
+ Debug.Assert(this.scopeStack.Parent.Item is ODataResourceBase, "The parent of a nested resource info scope should always be a resource");
+ ODataResourceBase parentResource = (ODataResourceBase)this.scopeStack.Parent.Item;
+ if (parentResource.MetadataBuilder != null)
+ {
+ nestedResourceInfo.MetadataBuilder = parentResource.MetadataBuilder;
+ }
+ }
+
+ ///
+ /// Verifies that calling WritePrimitive is valid.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ /// Primitive value to write.
+ private void VerifyCanWritePrimitive(bool synchronousCall, ODataPrimitiveValue primitiveValue)
+ {
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+ }
+
+ ///
+ /// Write primitive value - implementation of the actual functionality.
+ ///
+ /// Primitive value to write.
+ private void WritePrimitiveValueImplementation(ODataPrimitiveValue primitiveValue)
+ {
+ this.InterceptException(
+ (thisParam, primitiveValueParam) =>
+ {
+ thisParam.EnterScope(WriterState.Primitive, primitiveValueParam);
+ if (!(thisParam.CurrentResourceSetValidator == null) && primitiveValueParam != null)
+ {
+ Debug.Assert(primitiveValueParam.Value != null, "PrimitiveValue.Value should never be null!");
+ IEdmType itemType = EdmLibraryExtensions.GetPrimitiveTypeReference(primitiveValueParam.Value.GetType()).Definition;
+ thisParam.CurrentResourceSetValidator.ValidateResource(itemType);
+ }
+
+ thisParam.WritePrimitiveValue(primitiveValueParam);
+ thisParam.WriteEnd();
+ }, primitiveValue);
+ }
+
+ ///
+ /// Write primitive value asynchronously - implementation of the actual functionality.
+ ///
+ /// Primitive value to write.
+ /// The task.
+ private async Task WritePrimitiveValueImplementationAsync(ODataPrimitiveValue primitiveValue)
+ {
+ EnterScope(WriterState.Primitive, primitiveValue);
+
+ await InterceptExceptionAsync(
+ async (thisParam, primiteValueParam) =>
+ {
+ if (!(CurrentResourceSetValidator == null) && primiteValueParam != null)
+ {
+ Debug.Assert(primiteValueParam.Value != null, "PrimitiveValue.Value should never be null!");
+ IEdmType itemType = EdmLibraryExtensions.GetPrimitiveTypeReference(primiteValueParam.Value.GetType()).Definition;
+ CurrentResourceSetValidator.ValidateResource(itemType);
+ }
+
+ await thisParam.WritePrimitiveValueAsync(primiteValueParam)
+ .ConfigureAwait(false);
+ await thisParam.WriteEndAsync()
+ .ConfigureAwait(false);
+ }, primitiveValue).ConfigureAwait(false);
+ }
+
+ ///
+ /// Verifies that calling CreateWriteStream is valid.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ private void VerifyCanCreateWriteStream(bool synchronousCall)
+ {
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+ }
+
+ ///
+ /// Create a write stream - implementation of the actual functionality.
+ ///
+ /// A stream for writing the binary value.
+ private Stream CreateWriteStreamImplementation()
+ {
+ this.EnterScope(WriterState.Stream, null);
+ return new ODataNotificationStream(this.StartBinaryStream(), this);
+ }
+
+ ///
+ /// Verifies that calling CreateTextWriter is valid.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ private void VerifyCanCreateTextWriter(bool synchronousCall)
+ {
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+ }
+
+ ///
+ /// Create a text writer - implementation of the actual functionality.
+ ///
+ /// A TextWriter for writing the string value.
+ private TextWriter CreateTextWriterImplementation()
+ {
+ this.EnterScope(WriterState.String, null);
+ return new ODataNotificationWriter(this.StartTextWriter(), this);
+ }
+
+ ///
+ /// Verify that calling WriteEnd is valid.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ private void VerifyCanWriteEnd(bool synchronousCall)
+ {
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+ }
+
+ ///
+ /// Finish writing a resourceSet/resource/nested resource info.
+ ///
+ private void WriteEndImplementation()
+ {
+ this.InterceptException(
+ (thisParam) =>
+ {
+ Scope currentScope = thisParam.CurrentScope;
+
+ switch (currentScope.State)
+ {
+ case WriterState.Resource:
+ if (!thisParam.SkipWriting)
+ {
+ ODataResource resource = (ODataResource)currentScope.Item;
+
+ thisParam.EndResource(resource);
+ thisParam.DecreaseResourceDepth();
+ }
+
+ break;
+ case WriterState.DeletedResource:
+ if (!thisParam.SkipWriting)
+ {
+ ODataDeletedResource resource = (ODataDeletedResource)currentScope.Item;
+
+ thisParam.EndDeletedResource(resource);
+ thisParam.DecreaseResourceDepth();
+ }
+
+ break;
+ case WriterState.ResourceSet:
+ if (!thisParam.SkipWriting)
+ {
+ ODataResourceSet resourceSet = (ODataResourceSet)currentScope.Item;
+ WriterValidationUtils.ValidateResourceSetAtEnd(resourceSet, !thisParam.outputContext.WritingResponse);
+ thisParam.EndResourceSet(resourceSet);
+ }
+
+ break;
+ case WriterState.DeltaLink:
+ case WriterState.DeltaDeletedLink:
+ break;
+ case WriterState.DeltaResourceSet:
+ if (!thisParam.SkipWriting)
+ {
+ ODataDeltaResourceSet deltaResourceSet = (ODataDeltaResourceSet)currentScope.Item;
+ WriterValidationUtils.ValidateDeltaResourceSetAtEnd(deltaResourceSet, !thisParam.outputContext.WritingResponse);
+ thisParam.EndDeltaResourceSet(deltaResourceSet);
+ }
+
+ break;
+ case WriterState.NestedResourceInfo:
+ if (!thisParam.outputContext.WritingResponse)
+ {
+ throw new ODataException(Strings.ODataWriterCore_DeferredLinkInRequest);
+ }
+
+ if (!thisParam.SkipWriting)
+ {
+ ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item;
+ thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(link);
+ thisParam.WriteDeferredNestedResourceInfo(link);
+
+ thisParam.MarkNestedResourceInfoAsProcessed(link);
+ }
+
+ break;
+ case WriterState.NestedResourceInfoWithContent:
+ if (!thisParam.SkipWriting)
+ {
+ ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item;
+ thisParam.EndNestedResourceInfoWithContent(link);
+
+ thisParam.MarkNestedResourceInfoAsProcessed(link);
+ }
+
+ break;
+ case WriterState.Property:
+ {
+ ODataPropertyInfo property = (ODataPropertyInfo)currentScope.Item;
+ thisParam.EndProperty(property);
+ }
+
+ break;
+ case WriterState.Primitive:
+ // WriteEnd for WriterState.Primitive is a no-op; just leave scope
+ break;
+ case WriterState.Stream:
+ case WriterState.String:
+ throw new ODataException(Strings.ODataWriterCore_StreamNotDisposed);
+ case WriterState.Start: // fall through
+ case WriterState.Completed: // fall through
+ case WriterState.Error: // fall through
+ throw new ODataException(Strings.ODataWriterCore_WriteEndCalledInInvalidState(currentScope.State.ToString()));
+ default:
+ throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_WriteEnd_UnreachableCodePath));
+ }
+
+ thisParam.LeaveScope();
+ });
+ }
+
+ ///
+ /// Marks the navigation currently being written as processed in the parent entity's metadata builder.
+ /// This is needed so that at the end of writing the resource we can query for all the unwritten navigation properties
+ /// defined on the entity type and write out their metadata in fullmetadata mode.
+ ///
+ /// The nested resource info being written.
+ private void MarkNestedResourceInfoAsProcessed(ODataNestedResourceInfo link)
+ {
+ Debug.Assert(
+ this.CurrentScope.State == WriterState.NestedResourceInfo || this.CurrentScope.State == WriterState.NestedResourceInfoWithContent,
+ "This method should only be called when we're writing a nested resource info.");
+
+ ODataResourceBase parent = (ODataResourceBase)this.scopeStack.Parent.Item;
+ Debug.Assert(parent.MetadataBuilder != null, "parent.MetadataBuilder != null");
+ parent.MetadataBuilder.MarkNestedResourceInfoProcessed(link.Name);
+ }
+
+ ///
+ /// Verifies that calling WriteEntityReferenceLink is valid.
+ ///
+ /// The entity reference link to write.
+ /// true if the call is to be synchronous; false otherwise.
+ private void VerifyCanWriteEntityReferenceLink(ODataEntityReferenceLink entityReferenceLink, bool synchronousCall)
+ {
+ ExceptionUtils.CheckArgumentNotNull(entityReferenceLink, "entityReferenceLink");
+
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+ }
+
+ ///
+ /// Verifies that calling Write(Deleted)DeltaLink is valid.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ /// Delta link to write.
+ private void VerifyCanWriteLink(bool synchronousCall, ODataDeltaLinkBase deltaLink)
+ {
+ this.VerifyWritingDelta();
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+
+ ExceptionUtils.CheckArgumentNotNull(deltaLink, "deltaLink");
+ }
+
+ ///
+ /// Write an entity reference link.
+ ///
+ /// The entity reference link to write.
+ private void WriteEntityReferenceLinkImplementation(ODataEntityReferenceLink entityReferenceLink)
+ {
+ Debug.Assert(entityReferenceLink != null, "entityReferenceLink != null");
+
+ this.CheckForNestedResourceInfoWithContent(ODataPayloadKind.EntityReferenceLink, null);
+ Debug.Assert(
+ this.CurrentScope.Item is ODataNestedResourceInfo || this.ParentNestedResourceInfoScope.Item is ODataNestedResourceInfo,
+ "The CheckForNestedResourceInfoWithContent should have verified that entity reference link can only be written inside a nested resource info.");
+
+ if (!this.SkipWriting)
+ {
+ this.InterceptException(
+ (thisParam, entityReferenceLinkParam) =>
+ {
+ WriterValidationUtils.ValidateEntityReferenceLink(entityReferenceLinkParam);
+
+ ODataNestedResourceInfo nestedInfo = thisParam.CurrentScope.Item as ODataNestedResourceInfo;
+ if (nestedInfo == null)
+ {
+ NestedResourceInfoScope nestedResourceInfoScope = thisParam.ParentNestedResourceInfoScope;
+ Debug.Assert(nestedResourceInfoScope != null);
+ nestedInfo = (ODataNestedResourceInfo)nestedResourceInfoScope.Item;
+ }
+
+ thisParam.WriteEntityReferenceInNavigationLinkContent(nestedInfo, entityReferenceLinkParam);
+ }, entityReferenceLink);
+ }
+ }
+
+ ///
+ /// Verifies that calling Flush is valid.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ private void VerifyCanFlush(bool synchronousCall)
+ {
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+ }
+
+ ///
+ /// Verifies that a call is allowed to the writer.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ private void VerifyCallAllowed(bool synchronousCall)
+ {
+ if (synchronousCall)
+ {
+ if (!this.outputContext.Synchronous)
+ {
+ throw new ODataException(Strings.ODataWriterCore_SyncCallOnAsyncWriter);
+ }
+ }
+ else
+ {
+ if (this.outputContext.Synchronous)
+ {
+ throw new ODataException(Strings.ODataWriterCore_AsyncCallOnSyncWriter);
+ }
+ }
+ }
+
+ ///
+ /// Verifies that the writer is writing within a delta resource set.
+ ///
+ private void VerifyWritingDelta()
+ {
+ if (!this.CurrentScope.EnableDelta)
+ {
+ throw new ODataException(Strings.ODataWriterCore_CannotWriteDeltaWithResourceSetWriter);
+ }
+ }
+
+ ///
+ /// Enters the 'Error' state and then throws an ODataException with the specified error message.
+ ///
+ /// The error message for the exception.
+ /// The OData item to associate with the 'Error' state.
+ private void ThrowODataException(string errorMessage, ODataItem item)
+ {
+ this.EnterScope(WriterState.Error, item);
+ throw new ODataException(errorMessage);
+ }
+
+ ///
+ /// Checks whether we are currently writing the first top-level element; if so call StartPayload
+ ///
+ private void StartPayloadInStartState()
+ {
+ if (this.State == WriterState.Start)
+ {
+ this.InterceptException((thisParam) => thisParam.StartPayload());
+ }
+ }
+
+ ///
+ /// Checks whether we are currently writing a nested resource info and switches to NestedResourceInfoWithContent state if we do.
+ ///
+ ///
+ /// What kind of payload kind is being written as the content of a nested resource info.
+ /// Only Resource Set, Resource or EntityReferenceLink are allowed.
+ ///
+ /// The ODataResource or ODataResourceSet to write, or null for ODataEntityReferenceLink.
+ private void CheckForNestedResourceInfoWithContent(ODataPayloadKind contentPayloadKind, ODataItem contentPayload)
+ {
+ Debug.Assert(
+ contentPayloadKind == ODataPayloadKind.ResourceSet || contentPayloadKind == ODataPayloadKind.Resource || contentPayloadKind == ODataPayloadKind.EntityReferenceLink,
+ "Only ResourceSet, Resource or EntityReferenceLink can be specified as a payload kind for a nested resource info content.");
+
+ Scope currentScope = this.CurrentScope;
+ if (currentScope.State == WriterState.NestedResourceInfo || currentScope.State == WriterState.NestedResourceInfoWithContent)
+ {
+ ODataNestedResourceInfo currentNestedResourceInfo = (ODataNestedResourceInfo)currentScope.Item;
+ this.InterceptException(
+ (thisParam, currentNestedResourceInfoParam, contentPayloadKindParam) =>
+ {
+ if (thisParam.ParentResourceType != null)
+ {
+ IEdmStructuralProperty structuralProperty = thisParam.ParentResourceType.FindProperty(currentNestedResourceInfoParam.Name) as IEdmStructuralProperty;
+ if (structuralProperty != null)
+ {
+ thisParam.CurrentScope.ItemType = structuralProperty.Type.Definition.AsElementType();
+ IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource;
+
+ thisParam.CurrentScope.NavigationSource = parentNavigationSource;
+ }
+ else
+ {
+ IEdmNavigationProperty navigationProperty =
+ thisParam.WriterValidator.ValidateNestedResourceInfo(currentNestedResourceInfoParam, thisParam.ParentResourceType, contentPayloadKindParam);
+ if (navigationProperty != null)
+ {
+ thisParam.CurrentScope.ResourceType = navigationProperty.ToEntityType();
+ IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource;
+
+ if (thisParam.CurrentScope.NavigationSource == null)
+ {
+ IEdmPathExpression bindingPath;
+ thisParam.CurrentScope.NavigationSource = parentNavigationSource == null ?
+ null :
+ parentNavigationSource.FindNavigationTarget(navigationProperty, BindingPathHelper.MatchBindingPath, thisParam.CurrentScope.ODataUri.Path.Segments, out bindingPath);
+ }
+ }
+ }
+ }
+ }, currentNestedResourceInfo, contentPayloadKind);
+
+ if (currentScope.State == WriterState.NestedResourceInfoWithContent)
+ {
+ // If we are already in the NestedResourceInfoWithContent state, it means the caller is trying to write two items
+ // into the nested resource info content. This is only allowed for collection navigation property in request/response.
+ if (currentNestedResourceInfo.IsCollection != true)
+ {
+ this.ThrowODataException(Strings.ODataWriterCore_MultipleItemsInNestedResourceInfoWithContent, currentNestedResourceInfo);
+ }
+
+ // Note that we don't invoke duplicate property checker in this case as it's not necessary.
+ // What happens inside the nested resource info was already validated by the condition above.
+ // For collection in request we allow any combination anyway.
+ // For everything else we only allow a single item in the content and thus we will fail above.
+ }
+ else
+ {
+ // we are writing a nested resource info with content; change the state
+ this.PromoteNestedResourceInfoScope(contentPayload);
+
+ if (!this.SkipWriting)
+ {
+ this.InterceptException(
+ (thisParam, currentNestedResourceInfoParam) =>
+ {
+ if (!(currentNestedResourceInfoParam.SerializationInfo != null && currentNestedResourceInfoParam.SerializationInfo.IsComplex)
+ && (thisParam.CurrentScope.ItemType == null || thisParam.CurrentScope.ItemType.IsEntityOrEntityCollectionType()))
+ {
+ thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(currentNestedResourceInfoParam);
+ thisParam.StartNestedResourceInfoWithContent(currentNestedResourceInfoParam);
+ }
+ }, currentNestedResourceInfo);
+ }
+ }
+ }
+ else
+ {
+ if (contentPayloadKind == ODataPayloadKind.EntityReferenceLink)
+ {
+ Scope parenScope = this.ParentNestedResourceInfoScope;
+ Debug.Assert(parenScope != null);
+ if (parenScope.State != WriterState.NestedResourceInfo && parenScope.State != WriterState.NestedResourceInfoWithContent)
+ {
+ this.ThrowODataException(Strings.ODataWriterCore_EntityReferenceLinkWithoutNavigationLink, null);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Verifies that the (deleted) resource has the correct type for the (delta) resource set.
+ ///
+ /// The resource to be validated.
+ /// The scope for the resource to be validated.
+ private void ValidateResourceForResourceSet(ODataResourceBase resource, ResourceBaseScope resourceScope)
+ {
+ IEdmStructuredType resourceType = GetResourceType(resource);
+ NestedResourceInfoScope parentNestedResourceInfoScope = this.ParentNestedResourceInfoScope;
+ if (parentNestedResourceInfoScope != null)
+ {
+ // Validate the consistency of resource types in the nested resourceSet/resource
+ this.WriterValidator.ValidateResourceInNestedResourceInfo(resourceType, parentNestedResourceInfoScope.ResourceType);
+ resourceScope.ResourceTypeFromMetadata = parentNestedResourceInfoScope.ResourceType;
+
+ this.WriterValidator.ValidateDerivedTypeConstraint(resourceType, resourceScope.ResourceTypeFromMetadata,
+ parentNestedResourceInfoScope.DerivedTypeConstraints, "property", ((ODataNestedResourceInfo)parentNestedResourceInfoScope.Item).Name);
+ }
+ else
+ {
+ resourceScope.ResourceTypeFromMetadata = this.ParentScope.ResourceType;
+ if (this.CurrentResourceSetValidator != null)
+ {
+ if (this.ParentScope.State == WriterState.DeltaResourceSet
+ && this.currentResourceDepth <= 1
+ && resourceScope.NavigationSource != null)
+ {
+ // if the (deleted) resource is in the top level of a delta resource set, it doesn't
+ // need to match the delta resource set, but must match the navigation source resolved for
+ // the current scope
+ if (!resourceScope.NavigationSource.EntityType().IsAssignableFrom(resourceType))
+ {
+ throw new ODataException(Strings.ResourceSetWithoutExpectedTypeValidator_IncompatibleTypes(resourceType.FullTypeName(), resourceScope.NavigationSource.EntityType()));
+ }
+
+ resourceScope.ResourceTypeFromMetadata = resourceScope.NavigationSource.EntityType();
+ }
+ else
+ {
+ // Validate the consistency of resource types
+ this.CurrentResourceSetValidator.ValidateResource(resourceType);
+ }
+ }
+
+ if (this.ParentScope.NavigationSource != null)
+ {
+ this.WriterValidator.ValidateDerivedTypeConstraint(resourceType, resourceScope.ResourceTypeFromMetadata,
+ this.ParentScope.DerivedTypeConstraints, "navigation source", this.ParentScope.NavigationSource.Name);
+ }
+ }
+
+ resourceScope.ResourceType = resourceType;
+
+ // If writing in a delta resource set, the entity must have all key properties or the id set
+ if (this.ParentScope.State == WriterState.DeltaResourceSet)
+ {
+ IEdmEntityType entityType = resourceType as IEdmEntityType;
+ if (resource.Id == null &&
+ entityType != null &&
+ (this.outputContext.WritingResponse || resource is ODataDeletedResource) &&
+ !HasKeyProperties(entityType, resource.Properties))
+ {
+ throw new ODataException(Strings.ODataWriterCore_DeltaResourceWithoutIdOrKeyProperties);
+ }
+ }
+ }
+
+ ///
+ /// Determines whether a collection contains all key properties for a particular entity type.
+ ///
+ /// The entity type.
+ /// The set of properties.
+ /// True if the set of properties include all key properties for the entity type; otherwise false.
+ private static bool HasKeyProperties(IEdmEntityType entityType, IEnumerable properties)
+ {
+ Debug.Assert(entityType != null, "entityType null");
+ if (properties == null)
+ {
+ return false;
+ }
+
+ return entityType.Key().All(keyProp => properties.Select(p => p.Name).Contains(keyProp.Name));
+ }
+
+ ///
+ /// Catch any exception thrown by the action passed in; in the exception case move the writer into
+ /// state Error and then rethrow the exception.
+ ///
+ /// The action to execute.
+ ///
+ /// Make sure to only use anonymous functions that don't capture state from the enclosing context,
+ /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method.
+ ///
+ private void InterceptException(Action action)
+ {
+ try
+ {
+ action(this);
+ }
+ catch
+ {
+ if (!IsErrorState(this.State))
+ {
+ this.EnterScope(WriterState.Error, this.CurrentScope.Item);
+ }
+
+ throw;
+ }
+ }
+
+ ///
+ /// Catch any exception thrown by the action passed in; in the exception case move the writer into
+ /// state Error and then rethrow the exception.
+ ///
+ /// The action argument type.
+ /// The action to execute.
+ /// The argument value provided to the action.
+ ///
+ /// Make sure to only use anonymous functions that don't capture state from the enclosing context,
+ /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method.
+ ///
+ private void InterceptException(Action action, TArg0 arg0)
+ {
+ try
+ {
+ action(this, arg0);
+ }
+ catch
+ {
+ if (!IsErrorState(this.State))
+ {
+ this.EnterScope(WriterState.Error, this.CurrentScope.Item);
+ }
+
+ throw;
+ }
+ }
+
+ ///
+ /// Catch any exception thrown by the action passed in; in the exception case move the writer into
+ /// state Error and then rethrow the exception.
+ ///
+ /// The delegate first argument type.
+ /// The delegate second argument type.
+ /// The action to execute.
+ /// The argument value provided to the action.
+ /// The argument value provided to the action.
+ ///
+ /// Make sure to only use anonymous functions that don't capture state from the enclosing context,
+ /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method.
+ ///
+ private void InterceptException(Action action, TArg0 arg0, TArg1 arg1)
+ {
+ try
+ {
+ action(this, arg0, arg1);
+ }
+ catch
+ {
+ if (!IsErrorState(this.State))
+ {
+ this.EnterScope(WriterState.Error, this.CurrentScope.Item);
+ }
+
+ throw;
+ }
+ }
+
+ ///
+ /// Catch any exception thrown by the action passed in; in the exception case move the writer into
+ /// state Error and then rethrow the exception.
+ ///
+ /// The action to execute.
+ /// The task.
+ ///
+ /// Make sure to only use anonymous functions that don't capture state from the enclosing context,
+ /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method.
+ ///
+ private async Task InterceptExceptionAsync(Func action)
+ {
+ try
+ {
+ await action(this).ConfigureAwait(false);
+ }
+ catch
+ {
+ if (!IsErrorState(this.State))
+ {
+ this.EnterScope(WriterState.Error, this.CurrentScope.Item);
+ }
+
+ throw;
+ }
+ }
+
+ ///
+ /// Catch any exception thrown by the action passed in; in the exception case move the writer into
+ /// state Error and then rethrow the exception.
+ ///
+ /// The action argument type.
+ /// The action to execute.
+ /// The argument value provided to the action.
+ /// The task.
+ ///
+ /// Make sure to only use anonymous functions that don't capture state from the enclosing context,
+ /// so the compiler optimizes the code to avoid delegate and closure allocations on every call to this method.
+ ///
+ private async Task InterceptExceptionAsync(Func action, TArg0 arg0)
+ {
+ try
+ {
+ await action(this, arg0).ConfigureAwait(false);
+ }
+ catch
+ {
+ if (!IsErrorState(this.State))
+ {
+ this.EnterScope(WriterState.Error, this.CurrentScope.Item);
+ }
+
+ throw;
+ }
+ }
+
+ ///
+ /// Increments the nested resource count by one and fails if the new value exceeds the maximum nested resource depth limit.
+ ///
+ private void IncreaseResourceDepth()
+ {
+ this.currentResourceDepth++;
+
+ if (this.currentResourceDepth > this.outputContext.MessageWriterSettings.MessageQuotas.MaxNestingDepth)
+ {
+ this.ThrowODataException(Strings.ValidationUtils_MaxDepthOfNestedEntriesExceeded(this.outputContext.MessageWriterSettings.MessageQuotas.MaxNestingDepth), null);
+ }
+ }
+
+ ///
+ /// Decrements the nested resource count by one.
+ ///
+ private void DecreaseResourceDepth()
+ {
+ Debug.Assert(this.currentResourceDepth > 0, "Resource depth should never become negative.");
+
+ this.currentResourceDepth--;
+ }
+
+
+ ///
+ /// Notifies the implementer of the interface of relevant state changes in the writer.
+ ///
+ /// The new writer state.
+ private void NotifyListener(WriterState newState)
+ {
+ if (this.listener != null)
+ {
+ if (IsErrorState(newState))
+ {
+ this.listener.OnException();
+ }
+ else if (newState == WriterState.Completed)
+ {
+ this.listener.OnCompleted();
+ }
+ }
+ }
+
+ ///
+ /// Enter a new writer scope; verifies that the transition from the current state into new state is valid
+ /// and attaches the item to the new scope.
+ ///
+ /// The writer state to transition into.
+ /// The item to associate with the new scope.
+ [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug only cast.")]
+ private void EnterScope(WriterState newState, ODataItem item)
+ {
+ this.InterceptException((thisParam, newStateParam) => thisParam.ValidateTransition(newStateParam), newState);
+
+ // If the parent scope was marked for skipping content, the new child scope should be as well.
+ bool skipWriting = this.SkipWriting;
+
+ Scope currentScope = this.CurrentScope;
+
+ IEdmNavigationSource navigationSource = null;
+ IEdmType itemType = null;
+ SelectedPropertiesNode selectedProperties = currentScope.SelectedProperties;
+ ODataUri odataUri = currentScope.ODataUri.Clone();
+ if (odataUri.Path == null)
+ {
+ odataUri.Path = new ODataPath();
+ }
+
+ IEnumerable derivedTypeConstraints = null;
+
+ WriterState currentState = currentScope.State;
+
+ if (newState == WriterState.Resource || newState == WriterState.ResourceSet || newState == WriterState.Primitive || newState == WriterState.DeltaResourceSet || newState == WriterState.DeletedResource)
+ {
+ // if we're in a DeltaResourceSet and writing a resource or deleted resource then the parent may not be the navigation source
+ ODataResourceBase resource = item as ODataResourceBase;
+ if (resource != null)
+ {
+ IEdmModel model = this.outputContext.Model;
+ if (model != null && model.IsUserModel())
+ {
+ try
+ {
+ string typeNameFromResource = resource.TypeName;
+ if (!String.IsNullOrEmpty(typeNameFromResource))
+ {
+ // try resolving type from resource TypeName
+ itemType = TypeNameOracle.ResolveAndValidateTypeName(
+ model,
+ typeNameFromResource,
+ EdmTypeKind.None,
+ /* expectStructuredType */ true,
+ this.outputContext.WriterValidator);
+ }
+
+ // Try resolving navigation source from serialization info.
+ ODataResourceSerializationInfo serializationInfo = resource.SerializationInfo;
+ if (serializationInfo != null)
+ {
+ if (serializationInfo.NavigationSourceName != null)
+ {
+ ODataUriParser uriParser = new ODataUriParser(model, new Uri(serializationInfo.NavigationSourceName, UriKind.Relative), this.outputContext.Container);
+ odataUri = uriParser.ParseUri();
+ navigationSource = odataUri.Path.NavigationSource();
+ itemType = itemType ?? navigationSource.EntityType();
+ }
+
+ if (typeNameFromResource == null)
+ {
+ // Try resolving entity type from SerializationInfo
+ if (!string.IsNullOrEmpty(serializationInfo.ExpectedTypeName))
+ {
+ itemType = TypeNameOracle.ResolveAndValidateTypeName(
+ model,
+ serializationInfo.ExpectedTypeName,
+ EdmTypeKind.None,
+ /* expectStructuredType */ true,
+ this.outputContext.WriterValidator);
+ }
+ else if (!string.IsNullOrEmpty(serializationInfo.NavigationSourceEntityTypeName))
+ {
+ itemType = TypeNameOracle.ResolveAndValidateTypeName(
+ model,
+ serializationInfo.NavigationSourceEntityTypeName,
+ EdmTypeKind.Entity,
+ /* expectStructuredType */ true,
+ this.outputContext.WriterValidator);
+ }
+ }
+ }
+ }
+ catch (ODataException)
+ {
+ // SerializationInfo doesn't match model.
+ // This should be an error but, for legacy reasons, we ignore this.
+ }
+ }
+ }
+
+ if (navigationSource == null)
+ {
+ derivedTypeConstraints = currentScope.DerivedTypeConstraints;
+ }
+ else
+ {
+ derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationSource);
+ }
+
+ navigationSource = navigationSource ?? currentScope.NavigationSource;
+ itemType = itemType ?? currentScope.ItemType;
+
+ // This is to resolve the item type for a resource set for an undeclared nested resource info.
+ if (itemType == null
+ && (currentState == WriterState.Start || currentState == WriterState.NestedResourceInfo || currentState == WriterState.NestedResourceInfoWithContent)
+ && (newState == WriterState.ResourceSet || newState == WriterState.DeltaResourceSet))
+ {
+ ODataResourceSetBase resourceSet = item as ODataResourceSetBase;
+ if (resourceSet != null && resourceSet.TypeName != null && this.outputContext.Model.IsUserModel())
+ {
+ IEdmCollectionType collectionType = TypeNameOracle.ResolveAndValidateTypeName(
+ this.outputContext.Model,
+ resourceSet.TypeName,
+ EdmTypeKind.Collection,
+ false,
+ this.outputContext.WriterValidator) as IEdmCollectionType;
+
+ if (collectionType != null)
+ {
+ itemType = collectionType.ElementType.Definition;
+ }
+ }
+ }
+ }
+
+ // When writing a nested resource info, check if the link is being projected.
+ // If we are projecting properties, but the nav. link is not projected mark it to skip its content.
+ if ((currentState == WriterState.Resource || currentState == WriterState.DeletedResource) && newState == WriterState.NestedResourceInfo)
+ {
+ Debug.Assert(currentScope.Item is ODataResourceBase, "If the current state is Resource the current Item must be resource as well (and not null either).");
+ Debug.Assert(item is ODataNestedResourceInfo, "If the new state is NestedResourceInfo the new item must be a nested resource info as well (and not null either).");
+ ODataNestedResourceInfo nestedResourceInfo = (ODataNestedResourceInfo)item;
+
+ if (!skipWriting)
+ {
+ selectedProperties = currentScope.SelectedProperties.GetSelectedPropertiesForNavigationProperty(currentScope.ResourceType, nestedResourceInfo.Name);
+
+ ODataPath odataPath = odataUri.Path;
+ IEdmStructuredType currentResourceType = currentScope.ResourceType;
+
+ ResourceBaseScope resourceScope = currentScope as ResourceBaseScope;
+ TypeSegment resourceTypeCast = null;
+ if (resourceScope.ResourceTypeFromMetadata != currentResourceType)
+ {
+ resourceTypeCast = new TypeSegment(currentResourceType, null);
+ }
+
+ IEdmStructuralProperty structuredProperty = this.WriterValidator.ValidatePropertyDefined(
+ nestedResourceInfo.Name, currentResourceType)
+ as IEdmStructuralProperty;
+
+ // Handle primitive or complex type property.
+ if (structuredProperty != null)
+ {
+ odataPath = AppendEntitySetKeySegment(odataPath, false);
+ itemType = structuredProperty.Type == null ? null : structuredProperty.Type.Definition.AsElementType();
+ navigationSource = null;
+
+ if (resourceTypeCast != null)
+ {
+ odataPath = odataPath.AddSegment(resourceTypeCast);
+ }
+
+ odataPath = odataPath.AddPropertySegment(structuredProperty);
+
+ derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(structuredProperty);
+ }
+ else
+ {
+ IEdmNavigationProperty navigationProperty = this.WriterValidator.ValidateNestedResourceInfo(nestedResourceInfo, currentResourceType, /*payloadKind*/null);
+ if (navigationProperty != null)
+ {
+ derivedTypeConstraints = this.outputContext.Model.GetDerivedTypeConstraints(navigationProperty);
+
+ itemType = navigationProperty.ToEntityType();
+ if (!nestedResourceInfo.IsCollection.HasValue)
+ {
+ nestedResourceInfo.IsCollection = navigationProperty.Type.IsEntityCollectionType();
+ }
+
+ IEdmNavigationSource currentNavigationSource = currentScope.NavigationSource;
+ IEdmPathExpression bindingPath;
+
+ if (resourceTypeCast != null)
+ {
+ odataPath = odataPath.AddSegment(resourceTypeCast);
+ }
+
+ navigationSource = currentNavigationSource == null
+ ? null
+ : currentNavigationSource.FindNavigationTarget(navigationProperty, BindingPathHelper.MatchBindingPath, odataPath.Segments, out bindingPath);
+
+ SelectExpandClause clause = odataUri.SelectAndExpand;
+ TypeSegment typeCastFromExpand = null;
+ if (clause != null)
+ {
+ SelectExpandClause subClause;
+ clause.GetSubSelectExpandClause(nestedResourceInfo.Name, out subClause, out typeCastFromExpand);
+ odataUri.SelectAndExpand = subClause;
+ }
+
+ switch (navigationSource.NavigationSourceKind())
+ {
+ case EdmNavigationSourceKind.ContainedEntitySet:
+ // Containment cannot be written alone without odata uri.
+ if (!odataPath.Any())
+ {
+ throw new ODataException(Strings.ODataWriterCore_PathInODataUriMustBeSetWhenWritingContainedElement);
+ }
+
+ odataPath = AppendEntitySetKeySegment(odataPath, true);
+
+ if (odataPath != null && typeCastFromExpand != null)
+ {
+ odataPath = odataPath.AddSegment(typeCastFromExpand);
+ }
+
+ Debug.Assert(navigationSource is IEdmContainedEntitySet, "If the NavigationSourceKind is ContainedEntitySet, the navigationSource must be IEdmContainedEntitySet.");
+ IEdmContainedEntitySet containedEntitySet = (IEdmContainedEntitySet)navigationSource;
+ odataPath = odataPath.AddNavigationPropertySegment(containedEntitySet.NavigationProperty, containedEntitySet);
+ break;
+ case EdmNavigationSourceKind.EntitySet:
+ odataPath = new ODataPath(new EntitySetSegment(navigationSource as IEdmEntitySet));
+ break;
+ case EdmNavigationSourceKind.Singleton:
+ odataPath = new ODataPath(new SingletonSegment(navigationSource as IEdmSingleton));
+ break;
+ default:
+ odataPath = null;
+ break;
+ }
+ }
+ }
+
+ odataUri.Path = odataPath;
+ }
+ }
+ else if ((currentState == WriterState.ResourceSet || currentState == WriterState.DeltaResourceSet) && (newState == WriterState.Resource || newState == WriterState.Primitive || newState == WriterState.ResourceSet || newState == WriterState.DeletedResource))
+ {
+ // When writing a new resource to a resourceSet, increment the count of entries on that resourceSet.
+ if (currentState == WriterState.ResourceSet || currentState == WriterState.DeltaResourceSet)
+ {
+ ((ResourceSetBaseScope)currentScope).ResourceCount++;
+ }
+ }
+
+ if (navigationSource == null)
+ {
+ navigationSource = this.CurrentScope.NavigationSource ?? odataUri.Path.TargetNavigationSource();
+ }
+
+ this.PushScope(newState, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, derivedTypeConstraints);
+
+ this.NotifyListener(newState);
+ }
+
+ ///
+ /// Attempt to append key segment to ODataPath.
+ ///
+ /// The ODataPath to be evaluated.
+ /// Whether throw if fails to append key segment.
+ /// The new odata path.
+ private ODataPath AppendEntitySetKeySegment(ODataPath odataPath, bool throwIfFail)
+ {
+ ODataPath path = odataPath;
+
+ if (EdmExtensionMethods.HasKey(this.CurrentScope.NavigationSource, this.CurrentScope.ResourceType))
+ {
+ IEdmEntityType currentEntityType = this.CurrentScope.ResourceType as IEdmEntityType;
+ ODataResourceBase resource = this.CurrentScope.Item as ODataResourceBase;
+ Debug.Assert(resource != null,
+ "If the current state is Resource the current item must be an ODataResource as well (and not null either).");
+
+ ODataResourceSerializationInfo serializationInfo = this.GetResourceSerializationInfo(resource);
+
+ KeyValuePair[] keys = ODataResourceMetadataContext.GetKeyProperties(resource,
+ serializationInfo, currentEntityType, throwIfFail);
+
+ path = path.AddKeySegment(keys, currentEntityType, this.CurrentScope.NavigationSource);
+ }
+
+ return path;
+ }
+
+ ///
+ /// Leave the current writer scope and return to the previous scope.
+ /// When reaching the top-level replace the 'Started' scope with a 'Completed' scope.
+ ///
+ /// Note that this method is never called once an error has been written or a fatal exception has been thrown.
+ private void LeaveScope()
+ {
+ Debug.Assert(this.State != WriterState.Error, "this.State != WriterState.Error");
+
+ this.scopeStack.Pop();
+
+ // if we are back at the root replace the 'Start' state with the 'Completed' state
+ if (this.scopeStack.Count == 1)
+ {
+ Scope startScope = this.scopeStack.Pop();
+ Debug.Assert(startScope.State == WriterState.Start, "startScope.State == WriterState.Start");
+ this.PushScope(WriterState.Completed, /*item*/null, startScope.NavigationSource, startScope.ResourceType, /*skipWriting*/false, startScope.SelectedProperties, startScope.ODataUri, null);
+ this.InterceptException((thisParam) => thisParam.EndPayload());
+ this.NotifyListener(WriterState.Completed);
+ }
+ }
+
+ ///
+ /// Promotes the current nested resource info scope to a nested resource info scope with content.
+ ///
+ /// The nested content to write. May be of either ODataResource or ODataResourceSet type.
+ [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Second cast only in debug.")]
+ private void PromoteNestedResourceInfoScope(ODataItem content)
+ {
+ Debug.Assert(
+ this.State == WriterState.NestedResourceInfo,
+ "Only a NestedResourceInfo state can be promoted right now. If this changes please review the scope replacement code below.");
+ Debug.Assert(
+ this.CurrentScope.Item != null && this.CurrentScope.Item is ODataNestedResourceInfo,
+ "Item must be a non-null nested resource info.");
+ Debug.Assert(content == null || content is ODataResourceBase || content is ODataResourceSetBase);
+
+ this.ValidateTransition(WriterState.NestedResourceInfoWithContent);
+ NestedResourceInfoScope previousScope = (NestedResourceInfoScope)this.scopeStack.Pop();
+ NestedResourceInfoScope newScope = previousScope.Clone(WriterState.NestedResourceInfoWithContent);
+
+ this.scopeStack.Push(newScope);
+ if (newScope.ItemType == null && content != null && !SkipWriting && !(content is ODataPrimitiveValue))
+ {
+ ODataPrimitiveValue primitiveValue = content as ODataPrimitiveValue;
+ if (primitiveValue != null)
+ {
+ newScope.ItemType = EdmLibraryExtensions.GetPrimitiveTypeReference(primitiveValue.GetType()).Definition;
+ }
+ else
+ {
+ ODataResourceBase resource = content as ODataResourceBase;
+ newScope.ResourceType = resource != null
+ ? GetResourceType(resource)
+ : GetResourceSetType(content as ODataResourceSetBase);
+ }
+ }
+ }
+
+ ///
+ /// Verify that the transition from the current state into new state is valid .
+ ///
+ /// The new writer state to transition into.
+ [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "All the transition checks are encapsulated in this method.")]
+ private void ValidateTransition(WriterState newState)
+ {
+ if (!IsErrorState(this.State) && IsErrorState(newState))
+ {
+ // we can always transition into an error state if we are not already in an error state
+ return;
+ }
+
+ switch (this.State)
+ {
+ case WriterState.Start:
+ if (newState != WriterState.ResourceSet && newState != WriterState.Resource && newState != WriterState.DeltaResourceSet && newState != WriterState.DeletedResource)
+ {
+ throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromStart(this.State.ToString(), newState.ToString()));
+ }
+
+ if ((newState == WriterState.ResourceSet || newState == WriterState.DeltaResourceSet) && !this.writingResourceSet)
+ {
+ throw new ODataException(Strings.ODataWriterCore_CannotWriteTopLevelResourceSetWithResourceWriter);
+ }
+
+ if (newState == WriterState.Resource && this.writingResourceSet)
+ {
+ throw new ODataException(Strings.ODataWriterCore_CannotWriteTopLevelResourceWithResourceSetWriter);
+ }
+
+ break;
+ case WriterState.DeletedResource:
+ case WriterState.Resource:
+ {
+ if (this.CurrentScope.Item == null)
+ {
+ throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromNullResource(this.State.ToString(), newState.ToString()));
+ }
+
+ if (newState != WriterState.NestedResourceInfo && newState != WriterState.Property)
+ {
+ throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResource(this.State.ToString(), newState.ToString()));
+ }
+
+ // TODO: The conditional expressions in the 2 `if` blocks below are adequately covered by the `if` block above?
+ if (newState == WriterState.DeletedResource && this.ParentScope.State != WriterState.DeltaResourceSet)
+ {
+ throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString()));
+ }
+
+ if (this.State == WriterState.DeletedResource && this.Version < ODataVersion.V401 && newState == WriterState.NestedResourceInfo)
+ {
+ throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFrom40DeletedResource(this.State.ToString(), newState.ToString()));
+ }
+ }
+
+ break;
+ case WriterState.ResourceSet:
+ // Within a typed resource set we can only write a resource.
+ // Within an untyped resource set we can also write a primitive value or nested resource set.
+ if (newState != WriterState.Resource &&
+ (this.CurrentScope.ResourceType != null &&
+ (this.CurrentScope.ResourceType.TypeKind != EdmTypeKind.Untyped ||
+ (newState != WriterState.Primitive && newState != WriterState.Stream && newState != WriterState.String && newState != WriterState.ResourceSet))))
+ {
+ throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString()));
+ }
+
+ break;
+ case WriterState.DeltaResourceSet:
+ if (newState != WriterState.Resource &&
+ newState != WriterState.DeletedResource &&
+ !(this.ScopeLevel < 3 && (newState == WriterState.DeltaDeletedLink || newState == WriterState.DeltaLink)))
+ {
+ throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromResourceSet(this.State.ToString(), newState.ToString()));
+ }
+
+ break;
+ case WriterState.NestedResourceInfo:
+ if (newState != WriterState.NestedResourceInfoWithContent)
+ {
+ throw new ODataException(Strings.ODataWriterCore_InvalidStateTransition(this.State.ToString(), newState.ToString()));
+ }
+
+ break;
+ case WriterState.NestedResourceInfoWithContent:
+ if (newState != WriterState.ResourceSet && newState != WriterState.Resource && newState != WriterState.Primitive && (this.Version < ODataVersion.V401 || (newState != WriterState.DeltaResourceSet && newState != WriterState.DeletedResource)))
+ {
+ throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromExpandedLink(this.State.ToString(), newState.ToString()));
+ }
+
+ break;
+ case WriterState.Property:
+ PropertyInfoScope propertyScope = this.CurrentScope as PropertyInfoScope;
+ Debug.Assert(propertyScope != null, "Scope in WriterState.Property is not PropertyInfoScope");
+ if (propertyScope.ValueWritten)
+ {
+ // we've already written the value for this property
+ ODataPropertyInfo propertyInfo = propertyScope.Item as ODataPropertyInfo;
+ Debug.Assert(propertyInfo != null, "Item in PropertyInfoScope is not ODataPropertyInfo");
+ throw new ODataException(Strings.ODataWriterCore_PropertyValueAlreadyWritten(propertyInfo.Name));
+ }
+
+ if (newState == WriterState.Stream || newState == WriterState.String || newState == WriterState.Primitive)
+ {
+ propertyScope.ValueWritten = true;
+ }
+ else
+ {
+ throw new ODataException(Strings.ODataWriterCore_InvalidStateTransition(this.State.ToString(), newState.ToString()));
+ }
+
+ break;
+ case WriterState.Stream:
+ case WriterState.String:
+ throw new ODataException(Strings.ODataWriterCore_StreamNotDisposed);
+ case WriterState.Completed:
+ // we should never see a state transition when in state 'Completed'
+ throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromCompleted(this.State.ToString(), newState.ToString()));
+ case WriterState.Error:
+ if (newState != WriterState.Error)
+ {
+ // No more state transitions once we are in error state except for the fatal error
+ throw new ODataException(Strings.ODataWriterCore_InvalidTransitionFromError(this.State.ToString(), newState.ToString()));
+ }
+
+ break;
+ default:
+ throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_ValidateTransition_UnreachableCodePath));
+ }
+ }
+
+ ///
+ /// Create a new writer scope.
+ ///
+ /// The writer state of the scope to create.
+ /// The item attached to the scope to create.
+ /// The navigation source we are going to write resource set for.
+ /// The structured type for the items in the resource set to be written (or null if the navigationSource base type should be used).
+ /// true if the content of the scope to create should not be written.
+ /// The selected properties of this scope.
+ /// The OdataUri info of this scope.
+ /// The derived type constraints.
+ [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Debug.Assert check only.")]
+ private void PushScope(WriterState state, ODataItem item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri,
+ IEnumerable derivedTypeConstraints)
+ {
+ IEdmStructuredType resourceType = itemType as IEdmStructuredType;
+
+ Debug.Assert(
+ state == WriterState.Error ||
+ state == WriterState.Resource && (item == null || item is ODataResource) ||
+ state == WriterState.DeletedResource && (item == null || item is ODataDeletedResource) ||
+ state == WriterState.DeltaLink && (item == null || item is ODataDeltaLink) ||
+ state == WriterState.DeltaDeletedLink && (item == null || item is ODataDeltaDeletedLink) ||
+ state == WriterState.ResourceSet && item is ODataResourceSet ||
+ state == WriterState.DeltaResourceSet && item is ODataDeltaResourceSet ||
+ state == WriterState.Primitive && (item == null || item is ODataPrimitiveValue) ||
+ state == WriterState.Property && (item is ODataPropertyInfo) ||
+ state == WriterState.NestedResourceInfo && item is ODataNestedResourceInfo ||
+ state == WriterState.NestedResourceInfoWithContent && item is ODataNestedResourceInfo ||
+ state == WriterState.Stream && item == null ||
+ state == WriterState.String && item == null ||
+ state == WriterState.Start && item == null ||
+ state == WriterState.Completed && item == null,
+ "Writer state and associated item do not match.");
+
+ bool isUndeclaredResourceOrResourceSet = false;
+ if ((state == WriterState.Resource || state == WriterState.ResourceSet)
+ && (this.CurrentScope.State == WriterState.NestedResourceInfo || this.CurrentScope.State == WriterState.NestedResourceInfoWithContent))
+ {
+ isUndeclaredResourceOrResourceSet = this.IsUndeclared(this.CurrentScope.Item as ODataNestedResourceInfo);
+ }
+
+ Scope scope;
+ switch (state)
+ {
+ case WriterState.Resource:
+ scope = this.CreateResourceScope((ODataResource)item, navigationSource, resourceType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet);
+ break;
+ case WriterState.DeletedResource:
+ scope = this.CreateDeletedResourceScope((ODataDeletedResource)item, navigationSource, (IEdmEntityType)itemType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet);
+ break;
+ case WriterState.DeltaLink:
+ case WriterState.DeltaDeletedLink:
+ scope = this.CreateDeltaLinkScope((ODataDeltaLinkBase)item, navigationSource, (IEdmEntityType)itemType, selectedProperties, odataUri);
+ break;
+ case WriterState.ResourceSet:
+ scope = this.CreateResourceSetScope((ODataResourceSet)item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet);
+ if (this.outputContext.Model.IsUserModel())
+ {
+ Debug.Assert(scope is ResourceSetBaseScope, "Create a scope for a resource set that is not a ResourceSetBaseScope");
+ ((ResourceSetBaseScope)scope).ResourceTypeValidator = new ResourceSetWithoutExpectedTypeValidator(itemType);
+ }
+
+ break;
+ case WriterState.DeltaResourceSet:
+ scope = this.CreateDeltaResourceSetScope((ODataDeltaResourceSet)item, navigationSource, resourceType, skipWriting, selectedProperties, odataUri, isUndeclaredResourceOrResourceSet);
+ if (this.outputContext.Model.IsUserModel())
+ {
+ Debug.Assert(scope is ResourceSetBaseScope, "Create a scope for a delta resource set that is not a ResourceSetBaseScope");
+ ((ResourceSetBaseScope)scope).ResourceTypeValidator = new ResourceSetWithoutExpectedTypeValidator(resourceType);
+ }
+
+ break;
+ case WriterState.Property:
+ scope = this.CreatePropertyInfoScope((ODataPropertyInfo)item, navigationSource, resourceType, selectedProperties, odataUri);
+ break;
+ case WriterState.NestedResourceInfo: // fall through
+ case WriterState.NestedResourceInfoWithContent:
+ scope = this.CreateNestedResourceInfoScope(state, (ODataNestedResourceInfo)item, navigationSource, itemType, skipWriting, selectedProperties, odataUri);
+ break;
+ case WriterState.Start:
+ scope = new Scope(state, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, /*enableDelta*/ true);
+ break;
+ case WriterState.Primitive: // fall through
+ case WriterState.Stream: // fall through
+ case WriterState.String: // fall through
+ case WriterState.Completed: // fall through
+ case WriterState.Error:
+ scope = new Scope(state, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri, /*enableDelta*/ false);
+ break;
+ default:
+ string errorMessage = Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_Scope_Create_UnreachableCodePath);
+ Debug.Assert(false, errorMessage);
+ throw new ODataException(errorMessage);
+ }
+
+ scope.DerivedTypeConstraints = derivedTypeConstraints?.ToList();
+ this.scopeStack.Push(scope);
+ }
+
+ ///
+ /// Test to see if for a complex property or a collection of complex property, or a navigation property is declared or not.
+ ///
+ /// The nested info in question
+ /// true if the nested info is undeclared; false if it is not, or if it cannot be determined
+ private bool IsUndeclared(ODataNestedResourceInfo nestedResourceInfo)
+ {
+ Debug.Assert(nestedResourceInfo != null, "nestedResourceInfo != null");
+
+ if (nestedResourceInfo.SerializationInfo != null)
+ {
+ return nestedResourceInfo.SerializationInfo.IsUndeclared;
+ }
+ else
+ {
+ return this.ParentResourceType != null && (this.ParentResourceType.FindProperty((this.CurrentScope.Item as ODataNestedResourceInfo).Name) == null);
+ }
+ }
+
+ ///
+ /// Asynchronously verifies that calling is valid.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ /// The resource set/collection to write.
+ /// A task that represents the asynchronous operation.
+ private async Task VerifyCanWriteStartResourceSetAsync(bool synchronousCall, ODataResourceSet resourceSet)
+ {
+ ExceptionUtils.CheckArgumentNotNull(resourceSet, "resourceSet");
+
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+
+ await this.StartPayloadInStartStateAsync()
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Asynchronously start writing a resource set - implementation of the actual functionality.
+ ///
+ /// The resource set to write.
+ /// A task that represents the asynchronous write operation.
+ private async Task WriteStartResourceSetImplementationAsync(ODataResourceSet resourceSet)
+ {
+ await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.ResourceSet, resourceSet)
+ .ConfigureAwait(false);
+ this.EnterScope(WriterState.ResourceSet, resourceSet);
+
+ if (!this.SkipWriting)
+ {
+ await this.InterceptExceptionAsync(
+ async (thisParam, resourceSetParam) =>
+ {
+ // Verify query count
+ if (resourceSetParam.Count.HasValue)
+ {
+ // Check that Count is not set for requests
+ if (!thisParam.outputContext.WritingResponse)
+ {
+ thisParam.ThrowODataException(Strings.ODataWriterCore_QueryCountInRequest, resourceSetParam);
+ }
+ }
+
+ await thisParam.StartResourceSetAsync(resourceSetParam)
+ .ConfigureAwait(false);
+ }, resourceSet).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Asynchronously verifies that calling is valid.
+ ///
+ /// true if the call is to be synchronous; false otherwise.
+ /// The delta resource set/collection to write.
+ /// A task that represents the asynchronous operation.
+ private async Task VerifyCanWriteStartDeltaResourceSetAsync(bool synchronousCall, ODataDeltaResourceSet deltaResourceSet)
+ {
+ ExceptionUtils.CheckArgumentNotNull(deltaResourceSet, "deltaResourceSet");
+ this.VerifyNotDisposed();
+ this.VerifyCallAllowed(synchronousCall);
+
+ await this.StartPayloadInStartStateAsync()
+ .ConfigureAwait(false);
+ }
+
+ ///
+ /// Asynchronously start writing a delta resource set - implementation of the actual functionality.
+ ///
+ /// The delta resource set to write.
+ /// A task that represents the asynchronous write operation.
+ private async Task WriteStartDeltaResourceSetImplementationAsync(ODataDeltaResourceSet deltaResourceSet)
+ {
+ await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.ResourceSet, deltaResourceSet)
+ .ConfigureAwait(false);
+ this.EnterScope(WriterState.DeltaResourceSet, deltaResourceSet);
+
+ await this.InterceptExceptionAsync(
+ async (thisParam, deltaResourceSetParam) =>
+ {
+ if (!thisParam.outputContext.WritingResponse)
+ {
+ // Check that links are not set for requests
+ if (deltaResourceSetParam.NextPageLink != null)
+ {
+ thisParam.ThrowODataException(Strings.ODataWriterCore_QueryNextLinkInRequest, deltaResourceSetParam);
+ }
+
+ if (deltaResourceSetParam.DeltaLink != null)
+ {
+ thisParam.ThrowODataException(Strings.ODataWriterCore_QueryDeltaLinkInRequest, deltaResourceSetParam);
+ }
+ }
+
+ await thisParam.StartDeltaResourceSetAsync(deltaResourceSetParam)
+ .ConfigureAwait(false);
+ }, deltaResourceSet).ConfigureAwait(false);
+ }
+
+ ///
+ /// Asynchronously start writing a resource - implementation of the actual functionality.
+ ///
+ /// Resource/item to write.
+ /// A task that represents the asynchronous write operation.
+ private async Task WriteStartResourceImplementationAsync(ODataResource resource)
+ {
+ await this.StartPayloadInStartStateAsync()
+ .ConfigureAwait(false);
+ await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.Resource, resource)
+ .ConfigureAwait(false);
+ this.EnterScope(WriterState.Resource, resource);
+
+ if (!this.SkipWriting)
+ {
+ this.IncreaseResourceDepth();
+ await this.InterceptExceptionAsync(
+ async (thisParam, resourceParam) =>
+ {
+ if (resourceParam != null)
+ {
+ ResourceScope resourceScope = (ResourceScope)thisParam.CurrentScope;
+ thisParam.ValidateResourceForResourceSet(resourceParam, resourceScope);
+ await thisParam.PrepareResourceForWriteStartAsync(
+ resourceScope,
+ resourceParam,
+ thisParam.outputContext.WritingResponse,
+ resourceScope.SelectedProperties).ConfigureAwait(false);
+ }
+
+ await thisParam.StartResourceAsync(resourceParam)
+ .ConfigureAwait(false);
+ }, resource).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Asynchronously start writing a deleted resource - implementation of the actual functionality.
+ ///
+ /// Resource/item to write.
+ /// A task that represents the asynchronous write operation.
+ private async Task WriteStartDeletedResourceImplementationAsync(ODataDeletedResource resource)
+ {
+ Debug.Assert(resource != null, "resource != null");
+
+ await this.StartPayloadInStartStateAsync()
+ .ConfigureAwait(false);
+ await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.Resource, resource)
+ .ConfigureAwait(false);
+ this.EnterScope(WriterState.DeletedResource, resource);
+ this.IncreaseResourceDepth();
+
+ await this.InterceptExceptionAsync(
+ async (thisParam, resourceParam) =>
+ {
+ DeletedResourceScope resourceScope = thisParam.CurrentScope as DeletedResourceScope;
+ this.ValidateResourceForResourceSet(resourceParam, resourceScope);
+
+ await this.PrepareDeletedResourceForWriteStartAsync(
+ resourceScope,
+ resourceParam,
+ thisParam.outputContext.WritingResponse,
+ resourceScope.SelectedProperties).ConfigureAwait(false);
+ await thisParam.StartDeletedResourceAsync(resource)
+ .ConfigureAwait(false);
+ }, resource).ConfigureAwait(false);
+ }
+
+ ///
+ /// Asynchronously start writing a property - implementation of the actual functionality.
+ ///
+ /// Property to write.
+ /// A task that represents the asynchronous write operation.
+ private async Task WriteStartPropertyImplementationAsync(ODataPropertyInfo property)
+ {
+ this.EnterScope(WriterState.Property, property);
+
+ if (!this.SkipWriting)
+ {
+ await this.InterceptExceptionAsync(
+ async (thisParam, propertyParam) =>
+ {
+ await thisParam.StartPropertyAsync(propertyParam)
+ .ConfigureAwait(false);
+
+ if (propertyParam is ODataProperty)
+ {
+ PropertyInfoScope scope = thisParam.CurrentScope as PropertyInfoScope;
+ Debug.Assert(scope != null, "Scope for ODataPropertyInfo is not ODataPropertyInfoScope");
+ scope.ValueWritten = true;
+ }
+ }, property).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Asynchronously create a write stream - implementation of the actual functionality.
+ ///
+ /// A task that represents the asynchronous operation.
+ /// The value of the TResult parameter contains the stream for writing the binary.
+ private async Task CreateWriteStreamImplementationAsync()
+ {
+ this.EnterScope(WriterState.Stream, null);
+ Stream underlyingStream = await this.StartBinaryStreamAsync()
+ .ConfigureAwait(false);
+
+ return new ODataNotificationStream(underlyingStream, /*listener*/ this, /*synchronous*/ false);
+ }
+
+ ///
+ /// Asynchronously create a text writer - implementation of the actual functionality.
+ ///
+ /// A task that represents the asynchronous operation.
+ /// The value of the TResult parameter contains the textwriter for writing a string value.
+ private async Task CreateTextWriterImplementationAsync()
+ {
+ this.EnterScope(WriterState.String, null);
+ TextWriter textWriter = await this.StartTextWriterAsync()
+ .ConfigureAwait(false);
+
+ return new ODataNotificationWriter(textWriter, /*listener*/ this, /*synchronous*/ false);
+ }
+
+ ///
+ /// Asynchronously finish writing a resource set/resource/nested resource info.
+ ///
+ /// A task that represents the asynchronous write operation.
+ private Task WriteEndImplementationAsync()
+ {
+ return this.InterceptExceptionAsync(
+ async (thisParam) =>
+ {
+ Scope currentScope = thisParam.CurrentScope;
+
+ switch (currentScope.State)
+ {
+ case WriterState.Resource:
+ if (!thisParam.SkipWriting)
+ {
+ ODataResource resource = (ODataResource)currentScope.Item;
+
+ await thisParam.EndResourceAsync(resource)
+ .ConfigureAwait(false);
+ thisParam.DecreaseResourceDepth();
+ }
+
+ break;
+ case WriterState.DeletedResource:
+ if (!thisParam.SkipWriting)
+ {
+ ODataDeletedResource resource = (ODataDeletedResource)currentScope.Item;
+
+ await thisParam.EndDeletedResourceAsync(resource)
+ .ConfigureAwait(false);
+ thisParam.DecreaseResourceDepth();
+ }
+
+ break;
+ case WriterState.ResourceSet:
+ if (!thisParam.SkipWriting)
+ {
+ ODataResourceSet resourceSet = (ODataResourceSet)currentScope.Item;
+ WriterValidationUtils.ValidateResourceSetAtEnd(resourceSet, !thisParam.outputContext.WritingResponse);
+ await thisParam.EndResourceSetAsync(resourceSet)
+ .ConfigureAwait(false);
+ }
+
+ break;
+ case WriterState.DeltaLink:
+ case WriterState.DeltaDeletedLink:
+ break;
+ case WriterState.DeltaResourceSet:
+ if (!thisParam.SkipWriting)
+ {
+ ODataDeltaResourceSet deltaResourceSet = (ODataDeltaResourceSet)currentScope.Item;
+ WriterValidationUtils.ValidateDeltaResourceSetAtEnd(deltaResourceSet, !thisParam.outputContext.WritingResponse);
+ await thisParam.EndDeltaResourceSetAsync(deltaResourceSet)
+ .ConfigureAwait(false);
+ }
+
+ break;
+ case WriterState.NestedResourceInfo:
+ if (!thisParam.outputContext.WritingResponse)
+ {
+ throw new ODataException(Strings.ODataWriterCore_DeferredLinkInRequest);
+ }
+
+ if (!thisParam.SkipWriting)
+ {
+ ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item;
+ thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(link);
+ await thisParam.WriteDeferredNestedResourceInfoAsync(link)
+ .ConfigureAwait(false);
+
+ thisParam.MarkNestedResourceInfoAsProcessed(link);
+ }
+
+ break;
+ case WriterState.NestedResourceInfoWithContent:
+ if (!thisParam.SkipWriting)
+ {
+ ODataNestedResourceInfo link = (ODataNestedResourceInfo)currentScope.Item;
+ await thisParam.EndNestedResourceInfoWithContentAsync(link)
+ .ConfigureAwait(false);
+
+ thisParam.MarkNestedResourceInfoAsProcessed(link);
+ }
+
+ break;
+ case WriterState.Property:
+ {
+ ODataPropertyInfo property = (ODataPropertyInfo)currentScope.Item;
+ await thisParam.EndPropertyAsync(property)
+ .ConfigureAwait(false);
+ }
+
+ break;
+ case WriterState.Primitive:
+ // WriteEnd for WriterState.Primitive is a no-op; just leave scope
+ break;
+ case WriterState.Stream:
+ case WriterState.String:
+ throw new ODataException(Strings.ODataWriterCore_StreamNotDisposed);
+ case WriterState.Start: // fall through
+ case WriterState.Completed: // fall through
+ case WriterState.Error: // fall through
+ throw new ODataException(Strings.ODataWriterCore_WriteEndCalledInInvalidState(currentScope.State.ToString()));
+ default:
+ throw new ODataException(Strings.General_InternalError(InternalErrorCodes.ODataWriterCore_WriteEnd_UnreachableCodePath));
+ }
+
+ await thisParam.LeaveScopeAsync()
+ .ConfigureAwait(false);
+ });
+ }
+
+ ///
+ /// Asynchronously write an entity reference link.
+ ///
+ /// The entity reference link to write.
+ /// A task that represents the asynchronous write operation.
+ private async Task WriteEntityReferenceLinkImplementationAsync(ODataEntityReferenceLink entityReferenceLink)
+ {
+ Debug.Assert(entityReferenceLink != null, "entityReferenceLink != null");
+
+ await this.CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind.EntityReferenceLink, null)
+ .ConfigureAwait(false);
+ Debug.Assert(
+ this.CurrentScope.Item is ODataNestedResourceInfo || this.ParentNestedResourceInfoScope.Item is ODataNestedResourceInfo,
+ "The CheckForNestedResourceInfoWithContent should have verified that entity reference link can only be written inside a nested resource info.");
+
+ if (!this.SkipWriting)
+ {
+ await this.InterceptExceptionAsync(
+ async (thisParam, entityReferenceLinkParam) =>
+ {
+ WriterValidationUtils.ValidateEntityReferenceLink(entityReferenceLinkParam);
+
+ ODataNestedResourceInfo nestedInfo = thisParam.CurrentScope.Item as ODataNestedResourceInfo;
+ if (nestedInfo == null)
+ {
+ NestedResourceInfoScope nestedResourceInfoScope = thisParam.ParentNestedResourceInfoScope;
+ Debug.Assert(nestedResourceInfoScope != null);
+ nestedInfo = (ODataNestedResourceInfo)nestedResourceInfoScope.Item;
+ }
+
+ await thisParam.WriteEntityReferenceInNavigationLinkContentAsync(nestedInfo, entityReferenceLinkParam)
+ .ConfigureAwait(false);
+ }, entityReferenceLink).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Asynchronously checks whether we are currently writing the first top-level element; if so call StartPayloadAsync
+ ///
+ /// A task that represents the asynchronous write operation.
+ private Task StartPayloadInStartStateAsync()
+ {
+ if (this.State == WriterState.Start)
+ {
+ return this.InterceptExceptionAsync((thisParam) => thisParam.StartPayloadAsync());
+ }
+
+ return TaskUtils.CompletedTask;
+ }
+
+ ///
+ /// Asynchronously checks whether we are currently writing a nested resource info and switches to NestedResourceInfoWithContent state if we do.
+ ///
+ ///
+ /// What kind of payload kind is being written as the content of a nested resource info.
+ /// Only Resource Set, Resource or EntityReferenceLink are allowed.
+ ///
+ /// The ODataResource or ODataResourceSet to write, or null for ODataEntityReferenceLink.
+ /// A task that represents the asynchronous operation.
+ private async Task CheckForNestedResourceInfoWithContentAsync(ODataPayloadKind contentPayloadKind, ODataItem contentPayload)
+ {
+ Debug.Assert(
+ contentPayloadKind == ODataPayloadKind.ResourceSet || contentPayloadKind == ODataPayloadKind.Resource || contentPayloadKind == ODataPayloadKind.EntityReferenceLink,
+ "Only ResourceSet, Resource or EntityReferenceLink can be specified as a payload kind for a nested resource info content.");
+
+ Scope currentScope = this.CurrentScope;
+ if (currentScope.State == WriterState.NestedResourceInfo || currentScope.State == WriterState.NestedResourceInfoWithContent)
+ {
+ ODataNestedResourceInfo currentNestedResourceInfo = (ODataNestedResourceInfo)currentScope.Item;
+
+ this.InterceptException(
+ (thisParam, currentNestedResourceInfoParam, contentPayloadKindParam) =>
+ {
+ if (thisParam.ParentResourceType != null)
+ {
+ IEdmStructuralProperty structuralProperty = thisParam.ParentResourceType.FindProperty(
+ currentNestedResourceInfoParam.Name) as IEdmStructuralProperty;
+ if (structuralProperty != null)
+ {
+ thisParam.CurrentScope.ItemType = structuralProperty.Type.Definition.AsElementType();
+ IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource;
+
+ thisParam.CurrentScope.NavigationSource = parentNavigationSource;
+ }
+ else
+ {
+ IEdmNavigationProperty navigationProperty = thisParam.WriterValidator.ValidateNestedResourceInfo(
+ currentNestedResourceInfoParam,
+ thisParam.ParentResourceType,
+ contentPayloadKindParam);
+ if (navigationProperty != null)
+ {
+ thisParam.CurrentScope.ResourceType = navigationProperty.ToEntityType();
+ IEdmNavigationSource parentNavigationSource = thisParam.ParentResourceNavigationSource;
+
+ if (thisParam.CurrentScope.NavigationSource == null)
+ {
+ IEdmPathExpression bindingPath;
+ thisParam.CurrentScope.NavigationSource = parentNavigationSource?.FindNavigationTarget(
+ navigationProperty,
+ BindingPathHelper.MatchBindingPath,
+ thisParam.CurrentScope.ODataUri.Path.Segments,
+ out bindingPath);
+ }
+ }
+ }
+ }
+ }, currentNestedResourceInfo, contentPayloadKind);
+
+ if (currentScope.State == WriterState.NestedResourceInfoWithContent)
+ {
+ // If we are already in the NestedResourceInfoWithContent state, it means the caller is trying to write two items
+ // into the nested resource info content. This is only allowed for collection navigation property in request/response.
+ if (currentNestedResourceInfo.IsCollection != true)
+ {
+ this.ThrowODataException(Strings.ODataWriterCore_MultipleItemsInNestedResourceInfoWithContent, currentNestedResourceInfo);
+ }
+
+ // Note that we don't invoke duplicate property checker in this case as it's not necessary.
+ // What happens inside the nested resource info was already validated by the condition above.
+ // For collection in request we allow any combination anyway.
+ // For everything else we only allow a single item in the content and thus we will fail above.
+ }
+ else
+ {
+ // We are writing a nested resource info with content; change the state
+ this.PromoteNestedResourceInfoScope(contentPayload);
+
+ if (!this.SkipWriting)
+ {
+ await this.InterceptExceptionAsync(
+ async (thisParam, currentNestedResourceInfoParam) =>
+ {
+ if (!(currentNestedResourceInfoParam.SerializationInfo != null && currentNestedResourceInfoParam.SerializationInfo.IsComplex)
+ && (thisParam.CurrentScope.ItemType == null || thisParam.CurrentScope.ItemType.IsEntityOrEntityCollectionType()))
+ {
+ thisParam.DuplicatePropertyNameChecker.ValidatePropertyUniqueness(currentNestedResourceInfoParam);
+ await thisParam.StartNestedResourceInfoWithContentAsync(currentNestedResourceInfoParam)
+ .ConfigureAwait(false);
+ }
+ }, currentNestedResourceInfo).ConfigureAwait(false);
+ }
+ }
+ }
+ else
+ {
+ if (contentPayloadKind == ODataPayloadKind.EntityReferenceLink)
+ {
+ Scope parentScope = this.ParentNestedResourceInfoScope;
+ Debug.Assert(parentScope != null);
+ if (parentScope.State != WriterState.NestedResourceInfo && parentScope.State != WriterState.NestedResourceInfoWithContent)
+ {
+ this.ThrowODataException(Strings.ODataWriterCore_EntityReferenceLinkWithoutNavigationLink, null);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Asynchronously leave the current writer scope and return to the previous scope.
+ /// When reaching the top-level replace the 'Started' scope with a 'Completed' scope.
+ ///
+ /// Note that this method is never called once an error has been written or a fatal exception has been thrown.
+ private async Task LeaveScopeAsync()
+ {
+ Debug.Assert(this.State != WriterState.Error, "this.State != WriterState.Error");
+
+ this.scopeStack.Pop();
+
+ // if we are back at the root replace the 'Start' state with the 'Completed' state
+ if (this.scopeStack.Count == 1)
+ {
+ Scope startScope = this.scopeStack.Pop();
+ Debug.Assert(startScope.State == WriterState.Start, "startScope.State == WriterState.Start");
+ this.PushScope(
+ WriterState.Completed,
+ /*item*/ null,
+ startScope.NavigationSource,
+ startScope.ResourceType,
+ /*skipWriting*/ false,
+ startScope.SelectedProperties,
+ startScope.ODataUri,
+ /*derivedTypeConstraints*/ null);
+ await this.InterceptExceptionAsync((thisParam) => thisParam.EndPayloadAsync())
+ .ConfigureAwait(false);
+ this.NotifyListener(WriterState.Completed);
+ }
+ }
+
+ ///
+ /// Lightweight wrapper for the stack of scopes which exposes a few helper properties for getting parent scopes.
+ ///
+ internal sealed class ScopeStack
+ {
+ ///
+ /// Use a list to store the scopes instead of a true stack so that parent/grandparent lookups will be fast.
+ ///
+ private readonly List scopes = new List();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal ScopeStack()
+ {
+ }
+
+ ///
+ /// Gets the count of items in the stack.
+ ///
+ internal int Count
+ {
+ get
+ {
+ return this.scopes.Count;
+ }
+ }
+
+ ///
+ /// Gets the scope below the current scope on top of the stack.
+ ///
+ internal Scope Parent
+ {
+ get
+ {
+ Debug.Assert(this.scopes.Count > 1, "this.scopes.Count > 1");
+ return this.scopes[this.scopes.Count - 2];
+ }
+ }
+
+ ///
+ /// Gets the scope below the parent of the current scope on top of the stack.
+ ///
+ internal Scope ParentOfParent
+ {
+ get
+ {
+ Debug.Assert(this.scopes.Count > 2, "this.scopes.Count > 2");
+ return this.scopes[this.scopes.Count - 3];
+ }
+ }
+
+ ///
+ /// Gets the scope below the current scope on top of the stack or null if there is only one item on the stack or the stack is empty.
+ ///
+ internal Scope ParentOrNull
+ {
+ get
+ {
+ return this.Count == 0 ? null : this.Parent;
+ }
+ }
+
+ ///
+ /// Pushes the specified scope onto the stack.
+ ///
+ /// The scope.
+ internal void Push(Scope scope)
+ {
+ Debug.Assert(scope != null, "scope != null");
+ this.scopes.Add(scope);
+ }
+
+ ///
+ /// Pops the current scope off the stack.
+ ///
+ /// The popped scope.
+ internal Scope Pop()
+ {
+ Debug.Assert(this.scopes.Count > 0, "this.scopes.Count > 0");
+ int last = this.scopes.Count - 1;
+ Scope current = this.scopes[last];
+ this.scopes.RemoveAt(last);
+ return current;
+ }
+
+ ///
+ /// Peeks at the current scope on the top of the stack.
+ ///
+ /// The current scope at the top of the stack.
+ internal Scope Peek()
+ {
+ Debug.Assert(this.scopes.Count > 0, "this.scopes.Count > 0");
+ return this.scopes[this.scopes.Count - 1];
+ }
+ }
+
+ ///
+ /// A writer scope; keeping track of the current writer state and an item associated with this state.
+ ///
+ internal class Scope
+ {
+ /// The writer state of this scope.
+ private readonly WriterState state;
+
+ /// The item attached to this scope.
+ private readonly ODataItem item;
+
+ /// Set to true if the content of the scope should not be written.
+ /// This is used when writing navigation links which were not projected on the owning resource.
+ private readonly bool skipWriting;
+
+ /// The selected properties for the current scope.
+ private readonly SelectedPropertiesNode selectedProperties;
+
+ /// The navigation source we are going to write entities for.
+ private IEdmNavigationSource navigationSource;
+
+ /// The structured type for the resources in the resourceSet to be written (or null if the entity set base type should be used).
+ private IEdmStructuredType resourceType;
+
+ /// The IEdmType of the item (may not be structured for primitive types).
+ private IEdmType itemType;
+
+ /// The odata uri info for current scope.
+ private ODataUri odataUri;
+
+ /// Whether we are in the context of writing a delta collection.
+ private bool enableDelta;
+
+ ///
+ /// Constructor creating a new writer scope.
+ ///
+ /// The writer state of this scope.
+ /// The item attached to this scope.
+ /// The navigation source we are going to write resource set for.
+ /// The type for the items in the resource set to be written (or null if the entity set base type should be used).
+ /// true if the content of this scope should not be written.
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ /// Whether we are in the context of writing a delta collection.
+ internal Scope(WriterState state, ODataItem item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, bool enableDelta)
+ {
+ this.state = state;
+ this.item = item;
+ this.itemType = itemType;
+ this.resourceType = itemType as IEdmStructuredType;
+ this.navigationSource = navigationSource;
+ this.skipWriting = skipWriting;
+ this.selectedProperties = selectedProperties;
+ this.odataUri = odataUri;
+ this.enableDelta = enableDelta;
+ }
+
+ ///
+ /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used).
+ ///
+ public IEdmStructuredType ResourceType
+ {
+ get
+ {
+ return this.resourceType;
+ }
+
+ set
+ {
+ this.resourceType = value;
+ this.itemType = value;
+ }
+ }
+
+ ///
+ /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used).
+ ///
+ public IEdmType ItemType
+ {
+ get
+ {
+ return this.itemType;
+ }
+
+ set
+ {
+ this.itemType = value;
+ this.resourceType = value as IEdmStructuredType;
+ }
+ }
+
+ ///
+ /// The writer state of this scope.
+ ///
+ internal WriterState State
+ {
+ get
+ {
+ return this.state;
+ }
+ }
+
+ ///
+ /// The item attached to this scope.
+ ///
+ internal ODataItem Item
+ {
+ get
+ {
+ return this.item;
+ }
+ }
+
+ /// The navigation source we are going to write entities for.
+ internal IEdmNavigationSource NavigationSource
+ {
+ get
+ {
+ return this.navigationSource;
+ }
+
+ set
+ {
+ this.navigationSource = value;
+ }
+ }
+
+ /// The selected properties for the current scope.
+ internal SelectedPropertiesNode SelectedProperties
+ {
+ get
+ {
+ return this.selectedProperties;
+ }
+ }
+
+ /// The odata Uri for the current scope.
+ internal ODataUri ODataUri
+ {
+ get
+ {
+ Debug.Assert(this.odataUri != null, "this.odataUri != null");
+ return this.odataUri;
+ }
+ }
+
+ ///
+ /// Set to true if the content of this scope should not be written.
+ ///
+ internal bool SkipWriting
+ {
+ get
+ {
+ return this.skipWriting;
+ }
+ }
+
+ ///
+ /// True if we are in the process of writing a delta collection.
+ ///
+ public bool EnableDelta
+ {
+ get
+ {
+ return this.enableDelta;
+ }
+
+ protected set
+ {
+ this.enableDelta = value;
+ }
+ }
+
+ /// Gets or sets the derived type constraints for the current scope.
+ internal List DerivedTypeConstraints { get; set; }
+ }
+
+ ///
+ /// A base scope for a resourceSet.
+ ///
+ internal abstract class ResourceSetBaseScope : Scope
+ {
+ /// The serialization info for the current resourceSet.
+ private readonly ODataResourceSerializationInfo serializationInfo;
+
+ ///
+ /// The to use for entries in this resourceSet.
+ ///
+ private ResourceSetWithoutExpectedTypeValidator resourceTypeValidator;
+
+ /// The number of entries in this resourceSet seen so far.
+ private int resourceCount;
+
+ /// Maintains the write status for each annotation using its key.
+ private InstanceAnnotationWriteTracker instanceAnnotationWriteTracker;
+
+ /// The type context to answer basic questions regarding the type info of the resource.
+ private ODataResourceTypeContext typeContext;
+
+ ///
+ /// Constructor to create a new resource set scope.
+ ///
+ /// The writer state for the scope.
+ /// The resourceSet for the new scope.
+ /// The navigation source we are going to write resource set for.
+ /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used).
+ /// true if the content of the scope to create should not be written.
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ internal ResourceSetBaseScope(WriterState writerState, ODataResourceSetBase resourceSet, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
+ : base(writerState, resourceSet, navigationSource, itemType, skipWriting, selectedProperties, odataUri, writerState == WriterState.DeltaResourceSet)
+ {
+ this.serializationInfo = resourceSet.SerializationInfo;
+ }
+
+ ///
+ /// The number of entries in this resource Set seen so far.
+ ///
+ internal int ResourceCount
+ {
+ get
+ {
+ return this.resourceCount;
+ }
+
+ set
+ {
+ this.resourceCount = value;
+ }
+ }
+
+ ///
+ /// Tracks the write status of the annotations.
+ ///
+ internal InstanceAnnotationWriteTracker InstanceAnnotationWriteTracker
+ {
+ get
+ {
+ if (this.instanceAnnotationWriteTracker == null)
+ {
+ this.instanceAnnotationWriteTracker = new InstanceAnnotationWriteTracker();
+ }
+
+ return this.instanceAnnotationWriteTracker;
+ }
+ }
+
+ ///
+ /// Validator for resource type.
+ ///
+ internal ResourceSetWithoutExpectedTypeValidator ResourceTypeValidator
+ {
+ get
+ {
+ return this.resourceTypeValidator;
+ }
+
+ set
+ {
+ this.resourceTypeValidator = value;
+ }
+ }
+
+ ///
+ /// Gets or creates the type context to answer basic questions regarding the type info of the resource.
+ ///
+ /// True if writing a response payload, false otherwise.
+ /// The type context to answer basic questions regarding the type info of the resource.
+ internal ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse)
+ {
+ if (this.typeContext == null)
+ {
+ // For Entity, currently we check the navigation source.
+ // For Complex, we don't have navigation source, So we shouldn't check it.
+ // If ResourceType is not provided, serialization info or navigation source info should be provided.
+ bool throwIfMissingTypeInfo = writingResponse && (this.ResourceType == null || this.ResourceType.TypeKind == EdmTypeKind.Entity);
+
+ this.typeContext = ODataResourceTypeContext.Create(
+ this.serializationInfo,
+ this.NavigationSource,
+ EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource),
+ this.ResourceType,
+ throwIfMissingTypeInfo);
+ }
+
+ return this.typeContext;
+ }
+ }
+
+ ///
+ /// A scope for a resource set.
+ ///
+ internal abstract class ResourceSetScope : ResourceSetBaseScope
+ {
+ ///
+ /// Constructor to create a new resource set scope.
+ ///
+ /// The resource set for the new scope.
+ /// The navigation source we are going to write resource set for.
+ /// The type of the items in the resource set to be written (or null if the entity set base type should be used).
+ /// true if the content of the scope to create should not be written.
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ protected ResourceSetScope(ODataResourceSet item, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
+ : base(WriterState.ResourceSet, item, navigationSource, itemType, skipWriting, selectedProperties, odataUri)
+ {
+ }
+ }
+
+ ///
+ /// A scope for a delta resource set.
+ ///
+ internal abstract class DeltaResourceSetScope : ResourceSetBaseScope
+ {
+ ///
+ /// Constructor to create a new resource set scope.
+ ///
+ /// The resource set for the new scope.
+ /// The navigation source we are going to write resource set for.
+ /// The structured type of the items in the resource set to be written (or null if the entity set base type should be used).
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ protected DeltaResourceSetScope(ODataDeltaResourceSet item, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
+ : base(WriterState.DeltaResourceSet, item, navigationSource, resourceType, false /*skip writing*/, selectedProperties, odataUri)
+ {
+ }
+
+ ///
+ /// The context uri info created for this scope.
+ ///
+ public ODataContextUrlInfo ContextUriInfo { get; set; }
+ }
+
+ ///
+ /// A base scope for a resource.
+ ///
+ internal class ResourceBaseScope : Scope
+ {
+ /// Checker to detect duplicate property names.
+ private readonly IDuplicatePropertyNameChecker duplicatePropertyNameChecker;
+
+ /// The serialization info for the current resource.
+ private readonly ODataResourceSerializationInfo serializationInfo;
+
+ /// The resource type which was derived from the model (may be either the same as structured type or its base type.
+ private IEdmStructuredType resourceTypeFromMetadata;
+
+ /// The type context to answer basic questions regarding the type info of the resource.
+ private ODataResourceTypeContext typeContext;
+
+ /// Maintains the write status for each annotation using its key.
+ private InstanceAnnotationWriteTracker instanceAnnotationWriteTracker;
+
+ ///
+ /// Constructor to create a new resource scope.
+ ///
+ /// The writer state of this scope.
+ /// The resource for the new scope.
+ /// The serialization info for the current resource.
+ /// The navigation source we are going to write resource set for.
+ /// The type for the items in the resource set to be written (or null if the entity set base type should be used).
+ /// true if the content of the scope to create should not be written.
+ /// The The settings of the writer.
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ internal ResourceBaseScope(WriterState state, ODataResourceBase resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
+ : base(state, resource, navigationSource, itemType, skipWriting, selectedProperties, odataUri, /*enableDelta*/ true)
+ {
+ Debug.Assert(writerSettings != null, "writerBehavior != null");
+
+ if (resource != null)
+ {
+ duplicatePropertyNameChecker = writerSettings.Validator.CreateDuplicatePropertyNameChecker();
+ }
+
+ this.serializationInfo = serializationInfo;
+ }
+
+ ///
+ /// The structured type which was derived from the model, i.e. the expected structured type, which may be either the same as structured type or its base type.
+ /// For example, if we are writing a resource set of Customers and the current resource is of DerivedCustomer, this.ResourceTypeFromMetadata would be Customer and this.ResourceType would be DerivedCustomer.
+ ///
+ public IEdmStructuredType ResourceTypeFromMetadata
+ {
+ get
+ {
+ return this.resourceTypeFromMetadata;
+ }
+
+ internal set
+ {
+ this.resourceTypeFromMetadata = value;
+ }
+ }
+
+ ///
+ /// The serialization info for the current resource.
+ ///
+ public ODataResourceSerializationInfo SerializationInfo
+ {
+ get { return this.serializationInfo; }
+ }
+
+ ///
+ /// Checker to detect duplicate property names.
+ ///
+ internal IDuplicatePropertyNameChecker DuplicatePropertyNameChecker
+ {
+ get
+ {
+ return duplicatePropertyNameChecker;
+ }
+ }
+
+ ///
+ /// Tracks the write status of the annotations.
+ ///
+ internal InstanceAnnotationWriteTracker InstanceAnnotationWriteTracker
+ {
+ get
+ {
+ if (this.instanceAnnotationWriteTracker == null)
+ {
+ this.instanceAnnotationWriteTracker = new InstanceAnnotationWriteTracker();
+ }
+
+ return this.instanceAnnotationWriteTracker;
+ }
+ }
+
+ ///
+ /// Gets or creates the type context to answer basic questions regarding the type info of the resource.
+ ///
+ /// True if writing a response payload, false otherwise.
+ /// The type context to answer basic questions regarding the type info of the resource.
+ public ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse)
+ {
+ if (this.typeContext == null)
+ {
+ IEdmStructuredType expectedResourceType = this.ResourceTypeFromMetadata ?? this.ResourceType;
+
+ // For entity, we will check the navigation source info
+ bool throwIfMissingTypeInfo = writingResponse && (expectedResourceType == null || expectedResourceType.TypeKind == EdmTypeKind.Entity);
+
+ this.typeContext = ODataResourceTypeContext.Create(
+ this.serializationInfo,
+ this.NavigationSource,
+ EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource),
+ expectedResourceType,
+ throwIfMissingTypeInfo);
+ }
+
+ return this.typeContext;
+ }
+ }
+
+ ///
+ /// A base scope for a resource.
+ ///
+ internal class ResourceScope : ResourceBaseScope
+ {
+ ///
+ /// Constructor to create a new resource scope.
+ ///
+ /// The resource for the new scope.
+ /// The serialization info for the current resource.
+ /// The navigation source we are going to write resource set for.
+ /// The structured type for the items in the resource set to be written (or null if the entity set base type should be used).
+ /// true if the content of the scope to create should not be written.
+ /// The The settings of the writer.
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ protected ResourceScope(ODataResource resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, bool skipWriting, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
+ : base(WriterState.Resource, resource, serializationInfo, navigationSource, resourceType, skipWriting, writerSettings, selectedProperties, odataUri)
+ {
+ }
+ }
+
+ ///
+ /// Base class for DeletedResourceScope.
+ ///
+ internal class DeletedResourceScope : ResourceBaseScope
+ {
+ ///
+ /// Constructor to create a new resource scope.
+ ///
+ /// The resource for the new scope.
+ /// The serialization info for the current resource.
+ /// The navigation source we are going to write entities for.
+ /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used).
+ /// The The settings of the writer.
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ protected DeletedResourceScope(ODataDeletedResource resource, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmEntityType entityType, ODataMessageWriterSettings writerSettings, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
+ : base(WriterState.DeletedResource, resource, serializationInfo, navigationSource, entityType, false /*skipWriting*/, writerSettings, selectedProperties, odataUri)
+ {
+ }
+ }
+
+ ///
+ /// A scope for a delta link.
+ ///
+ internal abstract class DeltaLinkScope : Scope
+ {
+ /// The serialization info for the current link.
+ private readonly ODataResourceSerializationInfo serializationInfo;
+
+ ///
+ /// Fake entity type to be passed to context.
+ ///
+ private readonly EdmEntityType fakeEntityType = new EdmEntityType("MyNS", "Fake");
+
+ /// The type context to answer basic questions regarding the type info of the link.
+ private ODataResourceTypeContext typeContext;
+
+ ///
+ /// Constructor to create a new delta link scope.
+ ///
+ /// The writer state of this scope.
+ /// The link for the new scope.
+ /// The serialization info for the current resource.
+ /// The navigation source we are going to write entities for.
+ /// The entity type for the entries in the resource set to be written (or null if the entity set base type should be used).
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ protected DeltaLinkScope(WriterState state, ODataItem link, ODataResourceSerializationInfo serializationInfo, IEdmNavigationSource navigationSource, IEdmEntityType entityType, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
+ : base(state, link, navigationSource, entityType, /*skipWriting*/false, selectedProperties, odataUri, /*enableDelta*/ false)
+ {
+ Debug.Assert(link != null, "link != null");
+ Debug.Assert(
+ state == WriterState.DeltaLink && link is ODataDeltaLink ||
+ state == WriterState.DeltaDeletedLink && link is ODataDeltaDeletedLink,
+ "link must be either DeltaLink or DeltaDeletedLink.");
+
+ this.serializationInfo = serializationInfo;
+ }
+
+ ///
+ /// Gets or creates the type context to answer basic questions regarding the type info of the resource.
+ ///
+ /// Whether writing Json payload. Should always be true.
+ /// The type context to answer basic questions regarding the type info of the resource.
+ public ODataResourceTypeContext GetOrCreateTypeContext(bool writingResponse = true)
+ {
+ if (this.typeContext == null)
+ {
+ this.typeContext = ODataResourceTypeContext.Create(
+ this.serializationInfo,
+ this.NavigationSource,
+ EdmTypeWriterResolver.Instance.GetElementType(this.NavigationSource),
+ this.fakeEntityType,
+ writingResponse);
+ }
+
+ return this.typeContext;
+ }
+ }
+
+ ///
+ /// A scope for writing a single property within a resource.
+ ///
+ internal class PropertyInfoScope : Scope
+ {
+ ///
+ /// Constructor to create a new property scope.
+ ///
+ /// The property for the new scope.
+ /// The navigation source.
+ /// The structured type for the resource containing the property to be written.
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ internal PropertyInfoScope(ODataPropertyInfo property, IEdmNavigationSource navigationSource, IEdmStructuredType resourceType, SelectedPropertiesNode selectedProperties, ODataUri odataUri)
+ : base(WriterState.Property, property, navigationSource, resourceType, /*skipWriting*/ false, selectedProperties, odataUri, /*enableDelta*/ true)
+ {
+ ValueWritten = false;
+ }
+
+ public ODataPropertyInfo Property
+ {
+ get
+ {
+ Debug.Assert(this.Item is ODataProperty, "The item of a property scope is not an item.");
+ return this.Item as ODataProperty;
+ }
+ }
+
+ internal bool ValueWritten { get; set; }
+ }
+
+ ///
+ /// A scope for a nested resource info.
+ ///
+ internal class NestedResourceInfoScope : Scope
+ {
+ ///
+ /// Constructor to create a new nested resource info scope.
+ ///
+ /// The writer state for the new scope.
+ /// The nested resource info for the new scope.
+ /// The navigation source we are going to write resource set for.
+ /// The type for the items in the resource set to be written (or null if the entity set base type should be used).
+ /// true if the content of the scope to create should not be written.
+ /// The selected properties of this scope.
+ /// The ODataUri info of this scope.
+ /// The scope of the parent.
+ internal NestedResourceInfoScope(WriterState writerState, ODataNestedResourceInfo navLink, IEdmNavigationSource navigationSource, IEdmType itemType, bool skipWriting, SelectedPropertiesNode selectedProperties, ODataUri odataUri, Scope parentScope)
+ : base(writerState, navLink, navigationSource, itemType, skipWriting, selectedProperties, odataUri, parentScope.EnableDelta)
+ {
+ this.parentScope = parentScope;
+ }
+
+ /// Scope of the parent
+ protected Scope parentScope;
+
+ ///
+ /// Clones this nested resource info scope and sets a new writer state.
+ ///
+ /// The to set.
+ /// The cloned nested resource info scope with the specified writer state.
+ internal virtual NestedResourceInfoScope Clone(WriterState newWriterState)
+ {
+ return new NestedResourceInfoScope(newWriterState, (ODataNestedResourceInfo)this.Item, this.NavigationSource, this.ItemType, this.SkipWriting, this.SelectedProperties, this.ODataUri, parentScope)
+ {
+ DerivedTypeConstraints = this.DerivedTypeConstraints,
+ };
+ }
+ }
+ }
+}
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataEntryMetadataContextTest.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataEntryMetadataContextTest.cs
index 95a3e803bf..90a0fc9669 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataEntryMetadataContextTest.cs
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataEntryMetadataContextTest.cs
@@ -196,8 +196,8 @@ public void ShouldGetKeyPropertiesBasedOnMetadata()
{
this.entry.Properties = new[]
{
- new ODataProperty { Name = "ID1", Value = 1, SerializationInfo = new ODataPropertySerializationInfo { PropertyKind = ODataPropertyKind.Key } },
- new ODataProperty { Name = "ID2", Value = 2, SerializationInfo = new ODataPropertySerializationInfo { PropertyKind = ODataPropertyKind.Key } },
+ new ODataProperty { Name = "ID1", Value = 1 },
+ new ODataProperty { Name = "ID2", Value = 2 },
new ODataProperty { Name = "ID3", Value = 3 },
};
@@ -206,6 +206,7 @@ public void ShouldGetKeyPropertiesBasedOnMetadata()
Assert.Contains(keys, p => p.Key == "ID2");
Assert.Contains(keys, p => p.Key == "ID3");
}
+
#endregion KeyProperties
#region ETagProperties
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Evaluation/AutoComputePayloadMetadataInJsonIntegrationTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Evaluation/AutoComputePayloadMetadataInJsonIntegrationTests.cs
index 0ed8d16a3e..68032a126b 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Evaluation/AutoComputePayloadMetadataInJsonIntegrationTests.cs
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Evaluation/AutoComputePayloadMetadataInJsonIntegrationTests.cs
@@ -512,6 +512,150 @@ static AutoComputePayloadMetadataInJsonIntegrationTests()
Model.AddElement(function4);
}
+ public enum SerializationType
+ {
+ NoSerializationInfo,
+ ResourceSerializationInfo,
+ ResourceAndPropertySerializationInfo
+ }
+
+ [Theory]
+ [InlineData(SerializationType.NoSerializationInfo, /*fullMetadata*/ false)]
+ [InlineData(SerializationType.ResourceAndPropertySerializationInfo, /*fullMetadata*/ false)]
+ [InlineData(SerializationType.ResourceSerializationInfo, /*fullMetadata*/ false)]
+ [InlineData(SerializationType.NoSerializationInfo, /*fullMetadata*/ true)]
+ [InlineData(SerializationType.ResourceAndPropertySerializationInfo, /*fullMetadata*/ true)]
+ [InlineData(SerializationType.ResourceSerializationInfo, /*fullMetadata*/ true)]
+ public void WritingNestedResourceInfoWithVariousSerializationInfoSucceeds(SerializationType serializationType, bool fullMetadata)
+ {
+ // setup model
+ var model = new EdmModel();
+ var entityType = new EdmEntityType("NS", "entityType");
+ entityType.AddKeys(
+ entityType.AddStructuralProperty("keyProperty", EdmPrimitiveTypeKind.Int64));
+
+ var nestedEntityType = new EdmEntityType("NS", "nestedEntityType");
+ nestedEntityType.AddKeys(
+ nestedEntityType.AddStructuralProperty("keyProperty", EdmPrimitiveTypeKind.Int64));
+
+ entityType.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo
+ {
+ Name = "nestedEntities",
+ Target = nestedEntityType,
+ TargetMultiplicity = EdmMultiplicity.Many,
+ ContainsTarget = true
+ });
+
+ var container = new EdmEntityContainer("NS", "Container");
+ var entitySet = container.AddEntitySet("EntitySet", entityType);
+
+ model.AddElements(new IEdmSchemaElement[] { entityType, nestedEntityType, container });
+
+ // setup writer
+ string str;
+ using (var stream = new MemoryStream())
+ {
+ var message = new InMemoryMessage { Stream = stream };
+ message.SetHeader("Content-Type", fullMetadata ? "application/json;odata.metadata=full" : "application/json");
+ var settings = new ODataMessageWriterSettings
+ {
+ ODataUri = new ODataUri
+ {
+ ServiceRoot = new Uri("http://svc/"),
+ Path = new ODataUriParser(model, new Uri("EntitySet", UriKind.Relative)).ParsePath()
+ },
+ };
+ var writer = new ODataMessageWriter((IODataResponseMessage)message, settings, model);
+
+ // write payload
+ var entitySetWriter = writer.CreateODataResourceSetWriter(entitySet, entitySet.EntityType());
+ entitySetWriter.WriteStart(new ODataResourceSet());
+ var resource = new ODataResource
+ {
+ Properties = new[]
+ {
+ new ODataProperty {
+ Name = "keyProperty",
+ Value = 1L,
+ SerializationInfo = serializationType == SerializationType.ResourceAndPropertySerializationInfo ?
+ new ODataPropertySerializationInfo {
+ PropertyKind = ODataPropertyKind.Key
+ } : null
+ }
+ },
+ TypeName = serializationType == SerializationType.NoSerializationInfo ? null : entityType.FullName
+ };
+
+ if (serializationType != SerializationType.NoSerializationInfo)
+ {
+ resource.SerializationInfo = new ODataResourceSerializationInfo
+ {
+ NavigationSourceName = "EntitySet",
+ NavigationSourceKind = EdmNavigationSourceKind.EntitySet,
+ NavigationSourceEntityTypeName = entityType.FullName,
+ ExpectedTypeName = entityType.FullName,
+ IsFromCollection = true
+ };
+ }
+
+ entitySetWriter.WriteStart(resource);
+ entitySetWriter.WriteStart(
+ new ODataNestedResourceInfo
+ {
+ Name = "nestedEntities",
+ }
+ );
+ entitySetWriter.WriteStart(new ODataResourceSet());
+ var entityValue = new ODataResource
+ {
+ TypeName = "NS.nestedEntityType",
+ Properties = new[]
+ {
+ new ODataProperty { Name = "keyProperty", Value = 1L }
+ }
+ };
+ entitySetWriter.WriteStart(entityValue);
+ entitySetWriter.WriteEnd(); // nestedEntity
+ entitySetWriter.WriteEnd(); // nested resourceSet
+ entitySetWriter.WriteEnd(); // nestedInfo
+ entitySetWriter.WriteEnd(); // entity
+ entitySetWriter.WriteEnd(); // resourceSet
+ str = Encoding.UTF8.GetString(stream.ToArray());
+ }
+
+ var typeAnnotation =
+ serializationType == SerializationType.NoSerializationInfo ? "" :
+ "\"@odata.type\":\"#NS.entityType\",";
+
+ var expected = fullMetadata ?
+ "{\"@odata.context\":\"http://svc/$metadata#EntitySet\"," +
+ "\"value\":[{" +
+ typeAnnotation +
+ "\"@odata.id\":\"EntitySet(1)\"," +
+ "\"@odata.editLink\":\"EntitySet(1)\"," +
+ "\"keyProperty@odata.type\":\"#Int64\"," +
+ "\"keyProperty\":1," +
+ "\"nestedEntities@odata.associationLink\":\"http://svc/EntitySet(1)/nestedEntities/$ref\"," +
+ "\"nestedEntities@odata.navigationLink\":\"http://svc/EntitySet(1)/nestedEntities\"," +
+ "\"nestedEntities\":[{" +
+ "\"@odata.type\":\"#NS.nestedEntityType\"," +
+ "\"@odata.id\":\"EntitySet(1)/nestedEntities(1)\"," +
+ "\"@odata.editLink\":\"EntitySet(1)/nestedEntities(1)\"," +
+ "\"keyProperty@odata.type\":\"#Int64\"," +
+ "\"keyProperty\":1" +
+ "}]" +
+ "}]}" :
+ "{\"@odata.context\":\"http://svc/$metadata#EntitySet\"," +
+ "\"value\":[{" +
+ "\"keyProperty\":1," +
+ "\"nestedEntities\":[{" +
+ "\"keyProperty\":1" +
+ "}]" +
+ "}]}";
+
+ Assert.Equal(expected, str);
+ }
+
[Fact]
public void WritingDynamicComplexPropertyWithModelSpecifiedInFullMetadataMode()
{
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataReaderDerivedTypeConstraintTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataReaderDerivedTypeConstraintTests.cs
index 121a95f8ff..7ca0ab6709 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataReaderDerivedTypeConstraintTests.cs
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataReaderDerivedTypeConstraintTests.cs
@@ -516,7 +516,7 @@ public void ReadingingSingleComplexProppertyWithNotAllowedTypeWorksWithoutDerive
// Negative test case --Structural property has the derived type constraint.
SetDerivedTypeAnnotation(this.edmModel, locationProperty, "NS.CnAddress");
- Action test = () => ReadEntityPayload(payload, this.edmModel, this.edmMe, this.edmCustomerType); ;
+ Action test = () => ReadEntityPayload(payload, this.edmModel, this.edmMe, this.edmCustomerType);
var exception = Assert.Throws(test);
Assert.Equal(Strings.ReaderValidationUtils_ValueTypeNotAllowedInDerivedTypeConstraint("NS.UsAddress", "nested resource", "Location"), exception.Message);
}
diff --git a/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/Serialization/EdmModelCsdlSerializationVisitorTests.cs b/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/Serialization/EdmModelCsdlSerializationVisitorTests.cs
index 057c7b7025..b5954e35ba 100644
--- a/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/Serialization/EdmModelCsdlSerializationVisitorTests.cs
+++ b/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/Serialization/EdmModelCsdlSerializationVisitorTests.cs
@@ -2026,7 +2026,7 @@ internal void VisitAndVerifyXml(Action testAct
{
Indent = indent,
ConformanceLevel = ConformanceLevel.Auto
- }); ;
+ });
var schemaWriter = new EdmModelCsdlSchemaXmlWriter(model, xmlWriter, edmxVersion);
var visitor = new EdmModelCsdlSerializationVisitor(model, schemaWriter);