diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs
index f646358201..2e777d143d 100644
--- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs
+++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs
@@ -90,6 +90,7 @@ internal void WriteTopLevelProperty(ODataProperty property)
null /*owningType*/,
true /* isTopLevel */,
false /* allowStreamProperty */,
+ false /*omitNullValues */,
this.CreateDuplicatePropertyNameChecker());
this.JsonLightValueSerializer.AssertRecursionDepthIsZero();
@@ -97,6 +98,26 @@ internal void WriteTopLevelProperty(ODataProperty property)
});
}
+ ///
+ /// This is the backward-compatible method to write property names and value pairs
+ /// without omitting null property values.
+ ///
+ /// The of the resource (or null if not metadata is available).
+ /// The enumeration of properties to write out.
+ ///
+ /// Whether the properties are being written for complex value. Also used for detecting whether stream properties
+ /// are allowed as named stream properties should only be defined on ODataResource instances
+ ///
+ /// The DuplicatePropertyNameChecker to use.
+ internal void WriteProperties(
+ IEdmStructuredType owningType,
+ IEnumerable properties,
+ bool isComplexValue,
+ IDuplicatePropertyNameChecker duplicatePropertyNameChecker)
+ {
+ this.WriteProperties(owningType, properties, isComplexValue, /*omitNullValues*/ false, duplicatePropertyNameChecker);
+ }
+
///
/// Writes property names and value pairs.
///
@@ -106,11 +127,13 @@ internal void WriteTopLevelProperty(ODataProperty property)
/// Whether the properties are being written for complex value. Also used for detecting whether stream properties
/// are allowed as named stream properties should only be defined on ODataResource instances
///
+ /// Whether to omit null property values.
/// The DuplicatePropertyNameChecker to use.
internal void WriteProperties(
IEdmStructuredType owningType,
IEnumerable properties,
bool isComplexValue,
+ bool omitNullValues,
IDuplicatePropertyNameChecker duplicatePropertyNameChecker)
{
if (properties == null)
@@ -125,6 +148,7 @@ internal void WriteProperties(
owningType,
false /* isTopLevel */,
!isComplexValue,
+ omitNullValues,
duplicatePropertyNameChecker);
}
}
@@ -177,6 +201,7 @@ private void WriteProperty(
IEdmStructuredType owningType,
bool isTopLevel,
bool allowStreamProperty,
+ bool omitNullValues,
IDuplicatePropertyNameChecker duplicatePropertyNameChecker)
{
WriterValidationUtils.ValidatePropertyNotNull(property);
@@ -235,7 +260,7 @@ private void WriteProperty(
if (value is ODataNullValue || value == null)
{
- this.WriteNullProperty(property);
+ this.WriteNullProperty(property, omitNullValues);
return;
}
@@ -363,7 +388,8 @@ private void WriteStreamReferenceProperty(string propertyName, ODataStreamRefere
///
/// The property to write out.
private void WriteNullProperty(
- ODataProperty property)
+ ODataProperty property,
+ bool omitNullValues)
{
this.WriterValidator.ValidateNullPropertyValue(
this.currentPropertyInfo.MetadataType.TypeReference, property.Name, this.Model);
@@ -373,7 +399,7 @@ private void WriteNullProperty(
// TODO: Enable updating top-level properties to null #645
throw new ODataException("A null top-level property is not allowed to be serialized.");
}
- else if (!this.MessageWriterSettings.IgnoreNullValues)
+ else if (!omitNullValues)
{
this.JsonWriter.WriteName(property.Name);
this.JsonLightValueSerializer.WriteNullValue();
diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs
index bdccce1323..7ff95fa04f 100644
--- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs
+++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs
@@ -2232,6 +2232,9 @@ private void EndEntry()
if (this.State == ODataReaderState.ResourceStart)
{
+ // For non-delta response, need to restore omitted null values as required.
+ RestoreOmittedNullValues();
+
this.EndEntry(
new JsonLightResourceScope(
ODataReaderState.ResourceEnd,
@@ -2256,6 +2259,91 @@ private void EndEntry()
}
}
+ ///
+ /// For response de-serialization, restore the null values for properties that are omitted
+ /// by the omit-values=nulls preference header.
+ ///
+ private void RestoreOmittedNullValues()
+ {
+ // Restore omitted null value only when processing response and when
+ // Preference header omit-values=nulls is specified.
+ if (this.jsonLightInputContext.ReadingResponse
+ && this.jsonLightInputContext.MessageReaderSettings.NullValuesOmitted)
+ {
+ IODataJsonLightReaderResourceState resourceState = this.CurrentResourceState;
+ IEdmStructuredType edmStructuredType = resourceState.ResourceType;
+
+ if (resourceState.SelectedProperties == SelectedPropertiesNode.Empty)
+ {
+ return;
+ }
+ else
+ {
+ if (resourceState.Resource != null && edmStructuredType != null)
+ {
+ ODataResourceBase resource = resourceState.Resource;
+
+ IEnumerable selectedProperties;
+ if (resourceState.SelectedProperties == SelectedPropertiesNode.EntireSubtree)
+ {
+ selectedProperties = edmStructuredType.DeclaredProperties;
+ }
+ else
+ { // Partial subtree. Combine navigation properties and selected properties at the node with distinct.
+ selectedProperties =
+ resourceState.SelectedProperties.GetSelectedNavigationProperties(edmStructuredType)
+ as IEnumerable;
+ selectedProperties = selectedProperties.Concat(
+ resourceState.SelectedProperties.GetSelectedProperties(edmStructuredType).Values)
+ .Distinct();
+ }
+
+ foreach (IEdmProperty currentProperty in selectedProperties)
+ {
+ Debug.Assert(currentProperty.Type != null, "currentProperty.Type != null");
+ if (!currentProperty.Type.IsNullable)
+ {
+ // Skip declared properties that are not null-able types.
+ continue;
+ }
+
+ // Response should be generated using case sensitive, matching EDM defined by metadata.
+ // In order to find potential omitted properties, need to check
+ // resource properties read and navigation properties read.
+ if (!resource.Properties.Any(
+ p => p.Name.Equals(currentProperty.Name, StringComparison.Ordinal))
+ && !resourceState.NavigationPropertiesRead.Contains(currentProperty.Name))
+ {
+ // Add null value to omitted property declared in the type
+ ODataProperty property = new ODataProperty
+ {
+ Name = currentProperty.Name,
+ Value = new ODataNullValue()
+ };
+
+ // Mark as processed, will throw if duplicate is detected.
+ resourceState.PropertyAndAnnotationCollector.MarkPropertyAsProcessed(property.Name);
+
+ ReadOnlyEnumerable readonlyEnumerable =
+ resource.Properties as ReadOnlyEnumerable;
+ if (readonlyEnumerable != null)
+ {
+ // For non-entity resource, add the property underneath the read-only enumerable.
+ resource.Properties.ConcatToReadOnlyEnumerable("Properties", property);
+ }
+ else
+ {
+ // For entity resource, concatenate the property to original enumerable.
+ resource.Properties =
+ resource.Properties.Concat(new List() {property});
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
///
/// Add info resolved from context url to current scope.
///
diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightWriter.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightWriter.cs
index ce8fb90a89..fcd654a61c 100644
--- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightWriter.cs
+++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightWriter.cs
@@ -331,6 +331,7 @@ protected override void StartResource(ODataResource resource)
this.ResourceType,
resource.Properties,
false /* isComplexValue */,
+ this.ShouldOmitNullValues(),
this.DuplicatePropertyNameChecker);
this.jsonLightResourceSerializer.JsonLightValueSerializer.AssertRecursionDepthIsZero();
@@ -1226,6 +1227,7 @@ private void WriteDeltaResourceProperties(IEnumerable properties)
this.ResourceType,
properties,
false /* isComplexValue */,
+ false /* omitNullValues */,
this.DuplicatePropertyNameChecker);
this.jsonLightResourceSerializer.JsonLightValueSerializer.AssertRecursionDepthIsZero();
}
diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs
index 588c149fe9..a0d042ae54 100644
--- a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs
+++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs
@@ -271,6 +271,9 @@ internal sealed class TextRes {
internal const string HttpHeaderValueLexer_FailedToReadTokenOrQuotedString = "HttpHeaderValueLexer_FailedToReadTokenOrQuotedString";
internal const string HttpHeaderValueLexer_InvalidSeparatorAfterQuotedString = "HttpHeaderValueLexer_InvalidSeparatorAfterQuotedString";
internal const string HttpHeaderValueLexer_EndOfFileAfterSeparator = "HttpHeaderValueLexer_EndOfFileAfterSeparator";
+
+ internal const string HttpPreferenceHeader_InvalidValueForToken = "HttpPreferenceHeader_InvalidValueForToken";
+
internal const string MediaType_EncodingNotSupported = "MediaType_EncodingNotSupported";
internal const string MediaTypeUtils_DidNotFindMatchingMediaType = "MediaTypeUtils_DidNotFindMatchingMediaType";
internal const string MediaTypeUtils_CannotDetermineFormatFromContentType = "MediaTypeUtils_CannotDetermineFormatFromContentType";
diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.txt b/src/Microsoft.OData.Core/Microsoft.OData.Core.txt
index 0aad37d499..42efa743e3 100644
--- a/src/Microsoft.OData.Core/Microsoft.OData.Core.txt
+++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.txt
@@ -241,6 +241,8 @@ HttpHeaderValueLexer_FailedToReadTokenOrQuotedString=An error occurred when pars
HttpHeaderValueLexer_InvalidSeparatorAfterQuotedString=An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because '{3}' is not a valid separator after a quoted-string.
HttpHeaderValueLexer_EndOfFileAfterSeparator=An error occurred when parsing the HTTP header '{0}'. The header value '{1}' is incorrect at position '{2}' because the header value should not end with the separator '{3}'.
+HttpPreferenceHeader_InvalidValueForToken=The value '{0}' is not a valid value for OData token '{1}' in HTTP Prefer or Preference-Applied headers.
+
MediaType_EncodingNotSupported=The character set '{0}' is not supported.
MediaTypeUtils_DidNotFindMatchingMediaType=A supported MIME type could not be found that matches the acceptable MIME types for the request. The supported type(s) '{0}' do not match any of the acceptable MIME types '{1}'.
diff --git a/src/Microsoft.OData.Core/ODataConstants.cs b/src/Microsoft.OData.Core/ODataConstants.cs
index 8027f37f23..f7927b18df 100644
--- a/src/Microsoft.OData.Core/ODataConstants.cs
+++ b/src/Microsoft.OData.Core/ODataConstants.cs
@@ -199,6 +199,19 @@ internal static class ODataInternalConstants
/// The $deletedLink token indicates delta deleted link.
internal const string ContextUriDeletedLink = UriSegmentSeparator + DeletedLink;
+
#endregion Context URL
+
+ #region Preference Headers
+ ///
+ /// Valid value for OmitValuesPreferenceToken
indicating nulls are omitted.
+ ///
+ internal const string OmitValuesNulls = "nulls";
+
+ ///
+ /// Valid value for OmitValuesPreferenceToken
indicating defaults are omitted.
+ ///
+ internal const string OmitValuesDefaults = "defaults";
+ #endregion Preference Headers
}
}
diff --git a/src/Microsoft.OData.Core/ODataMessageReader.cs b/src/Microsoft.OData.Core/ODataMessageReader.cs
index ede858649b..d28afb6f32 100644
--- a/src/Microsoft.OData.Core/ODataMessageReader.cs
+++ b/src/Microsoft.OData.Core/ODataMessageReader.cs
@@ -169,6 +169,17 @@ public ODataMessageReader(IODataResponseMessage responseMessage, ODataMessageRea
this.model = model ?? GetModel(this.container);
this.edmTypeResolver = new EdmTypeReaderResolver(this.model, this.settings.ClientCustomTypeResolver);
+ // Whether null values were omitted in the response.
+ this.settings.NullValuesOmitted = false;
+ string omitValuePreferenceApplied = responseMessage.PreferenceAppliedHeader().OmitValues;
+ if (omitValuePreferenceApplied != null)
+ {
+ // If the Preference-Applied header's omit-values parameter is present in the response, its value should
+ // be applied to the reader's setting.
+ this.settings.NullValuesOmitted =
+ omitValuePreferenceApplied.Equals(ODataConstants.OmitValuesNulls, StringComparison.OrdinalIgnoreCase);
+ }
+
// If the Preference-Applied header on the response message contains an annotation filter, we set the filter
// to the reader settings if it's not already set, so that we would only read annotations that satisfy the filter.
string annotationFilter = responseMessage.PreferenceAppliedHeader().AnnotationFilter;
diff --git a/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs b/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs
index 0e59409302..a9e7593945 100644
--- a/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs
+++ b/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs
@@ -196,6 +196,11 @@ public ODataMessageQuotas MessageQuotas
///
internal bool ThrowOnUndeclaredPropertyForNonOpenType { get; private set; }
+ ///
+ /// True if null values are omitted, per the omit-values parameter in Preference header of the message.
+ ///
+ internal bool NullValuesOmitted { get; set; }
+
///
/// Creates a shallow copy of this .
///
@@ -253,6 +258,7 @@ private void CopyFrom(ODataMessageReaderSettings other)
this.MaxProtocolVersion = other.MaxProtocolVersion;
this.ReadUntypedAsString = other.ReadUntypedAsString;
this.ShouldIncludeAnnotation = other.ShouldIncludeAnnotation;
+ this.NullValuesOmitted = other.NullValuesOmitted;
this.validations = other.validations;
this.ThrowOnDuplicatePropertyNames = other.ThrowOnDuplicatePropertyNames;
this.ThrowIfTypeConflictsWithMetadata = other.ThrowIfTypeConflictsWithMetadata;
diff --git a/src/Microsoft.OData.Core/ODataMessageWriter.cs b/src/Microsoft.OData.Core/ODataMessageWriter.cs
index d0a5788699..b13010ba53 100644
--- a/src/Microsoft.OData.Core/ODataMessageWriter.cs
+++ b/src/Microsoft.OData.Core/ODataMessageWriter.cs
@@ -151,6 +151,14 @@ public ODataMessageWriter(IODataResponseMessage responseMessage, ODataMessageWri
WriterValidationUtils.ValidateMessageWriterSettings(this.settings, this.writingResponse);
this.message = new ODataResponseMessage(responseMessage, /*writing*/ true, this.settings.EnableMessageStreamDisposal, /*maxMessageSize*/ -1);
+ // Set the Preference-Applied header's parameter omit-values=nulls per writer settings.
+ // For response writing, source of specifying omit-null-values preference is the settings object
+ // from caller(the OData service).
+ if (this.settings.OmitNullValues)
+ {
+ responseMessage.PreferenceAppliedHeader().OmitValues = ODataConstants.OmitValuesNulls;
+ }
+
// If the Preference-Applied header on the response message contains an annotation filter, we set the filter
// to the writer settings so that we would only write annotations that satisfy the filter.
string annotationFilter = responseMessage.PreferenceAppliedHeader().AnnotationFilter;
diff --git a/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs b/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs
index 51332a093b..8cd056fdf0 100644
--- a/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs
+++ b/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs
@@ -178,12 +178,12 @@ public ODataUri ODataUri
internal bool ThrowOnDuplicatePropertyNames { get; private set; }
///
- /// Don't serialize null values
+ /// Whether to omit null values when writing response.
///
///
- /// Default valus is false, that means serialize null values.
+ /// Default value is false, that means serialize null values.
///
- public bool IgnoreNullValues { get; set; }
+ public bool OmitNullValues { get; set; }
///
/// Returns whether ThrowOnUndeclaredPropertyForNonOpenType validation setting is enabled.
@@ -406,7 +406,7 @@ private void CopyFrom(ODataMessageWriterSettings other)
this.shouldIncludeAnnotation = other.shouldIncludeAnnotation;
this.useFormat = other.useFormat;
this.Version = other.Version;
- this.IgnoreNullValues = other.IgnoreNullValues;
+ this.OmitNullValues = other.OmitNullValues;
this.validations = other.validations;
this.ThrowIfTypeConflictsWithMetadata = other.ThrowIfTypeConflictsWithMetadata;
diff --git a/src/Microsoft.OData.Core/ODataPreferenceHeader.cs b/src/Microsoft.OData.Core/ODataPreferenceHeader.cs
index 82a905504b..4c2aa87948 100644
--- a/src/Microsoft.OData.Core/ODataPreferenceHeader.cs
+++ b/src/Microsoft.OData.Core/ODataPreferenceHeader.cs
@@ -6,8 +6,10 @@
namespace Microsoft.OData
{
+ using System;
using System.Collections.Generic;
using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
using System.Globalization;
///
@@ -61,6 +63,11 @@ public class ODataPreferenceHeader
///
private const string ODataTrackChangesPreferenceToken = "odata.track-changes";
+ ///
+ /// The omit-values preference token.
+ ///
+ private const string OmitValuesPreferenceToken = "omit-values";
+
///
/// The Prefer header name.
///
@@ -243,6 +250,40 @@ public string AnnotationFilter
}
}
+ ///
+ /// Property to get and set the "omit-values" preference to the "Prefer" header on the underlying IODataRequestMessage or
+ /// the "Preference-Applied" header on the underlying IODataResponseMessage.
+ /// For the preference or applied preference of omitting null values, use string "nulls".
+ ///
+ public string OmitValues
+ {
+ get
+ {
+ HttpHeaderValueElement omitValues = this.Get(OmitValuesPreferenceToken);
+ return omitValues?.Value;
+ }
+
+ [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Need lower case string here.")]
+ set
+ {
+ ExceptionUtils.CheckArgumentStringNotEmpty(value, "OmitValues");
+
+ string normalizedValue = value.Trim().ToLowerInvariant();
+ if (string.Equals(normalizedValue, ODataConstants.OmitValuesNulls, StringComparison.Ordinal))
+ {
+ this.Set(new HttpHeaderValueElement(OmitValuesPreferenceToken, ODataConstants.OmitValuesNulls, EmptyParameters));
+ }
+ else if (string.Equals(normalizedValue, ODataConstants.OmitValuesDefaults, StringComparison.Ordinal))
+ {
+ throw new NotSupportedException("omit-values=defaults is not supported yet.");
+ }
+ else
+ {
+ throw new ODataException(Strings.HttpPreferenceHeader_InvalidValueForToken(value, OmitValuesPreferenceToken));
+ }
+ }
+ }
+
///
/// Property to get and set the "respond-async" preference to the "Prefer" header on the underlying IODataRequestMessage or
/// the "Preference-Applied" header on the underlying IODataResponseMessage.
diff --git a/src/Microsoft.OData.Core/ODataWriterCore.cs b/src/Microsoft.OData.Core/ODataWriterCore.cs
index a5523c3df7..59fa9d85ab 100644
--- a/src/Microsoft.OData.Core/ODataWriterCore.cs
+++ b/src/Microsoft.OData.Core/ODataWriterCore.cs
@@ -1144,6 +1144,19 @@ protected void ValidateNoDeltaLinkForExpandedResourceSet(ODataResourceSet resour
}
}
+ ///
+ /// Returns whether properties of null values should be omitted when serializing the payload.
+ ///
+ /// Return true to omit null-value properties; false otherwise.
+ protected bool ShouldOmitNullValues()
+ {
+ // Omit null values preference should only affect response payload, and should not
+ // be applied to writing delta payload.
+ return this.outputContext.WritingResponse
+ && !this.writingDelta
+ && this.outputContext.MessageWriterSettings.OmitNullValues;
+ }
+
///
/// Verifies that calling WriteStart resourceSet is valid.
///
diff --git a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs
index 84df52e28c..f09599549f 100644
--- a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs
+++ b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs
@@ -270,7 +270,7 @@ internal static string ODataWriterCore_CannotWriteTopLevelResourceSetWithResourc
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.ODataWriterCore_CannotWriteTopLevelResourceSetWithResourceWriter);
}
}
-
+
///
/// A string like "Cannot write a top-level resource with a writer that was created to write a top-level resource set."
///
@@ -1639,6 +1639,14 @@ internal static string HttpHeaderValueLexer_EndOfFileAfterSeparator(object p0, o
return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpHeaderValueLexer_EndOfFileAfterSeparator, p0, p1, p2, p3);
}
+ ///
+ /// A string like "The value '{0}' is not a valid value for OData token '{1}' in HTTP Prefer or Preference-Applied headers."
+ ///
+ internal static string HttpPreferenceHeader_InvalidValueForToken(object p0, object p1)
+ {
+ return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.HttpPreferenceHeader_InvalidValueForToken, p0, p1);
+ }
+
///
/// A string like "The character set '{0}' is not supported."
///
diff --git a/src/Microsoft.OData.Core/SelectedPropertiesNode.cs b/src/Microsoft.OData.Core/SelectedPropertiesNode.cs
index 70c82b29c9..dbc37b072c 100644
--- a/src/Microsoft.OData.Core/SelectedPropertiesNode.cs
+++ b/src/Microsoft.OData.Core/SelectedPropertiesNode.cs
@@ -38,6 +38,9 @@ internal sealed class SelectedPropertiesNode
/// An empty set of navigation properties to return when nothing is selected.
private static readonly IEnumerable EmptyNavigationProperties = Enumerable.Empty();
+ /// An empty dictionary of EDM properties to return when nothing is selected.
+ private static readonly Dictionary EmptyEdmProperties = new Dictionary(StringComparer.Ordinal);
+
/// The type of the current node.
private readonly SelectionType selectionType;
@@ -253,7 +256,7 @@ internal SelectedPropertiesNode GetSelectedPropertiesForNavigationProperty(IEdmS
}
// We cannot determine the selected navigation properties without the user model. This means we won't be computing the missing navigation properties.
- // For reading we will report what's on the wire and for writing we just write what the user explicitely told us to write.
+ // For reading we will report what's on the wire and for writing we just write what the user explicitly told us to write.
if (structuredType == null)
{
return EntireSubtree;
@@ -299,7 +302,7 @@ internal IEnumerable GetSelectedNavigationProperties(IEd
}
// We cannot determine the selected navigation properties without the user model. This means we won't be computing the missing navigation properties.
- // For reading we will report what's on the wire and for writing we just write what the user explicitely told us to write.
+ // For reading we will report what's on the wire and for writing we just write what the user explicitly told us to write.
if (structuredType == null)
{
return EmptyNavigationProperties;
@@ -335,11 +338,43 @@ internal IEnumerable GetSelectedNavigationProperties(IEd
return selectedNavigationProperties.Distinct();
}
+ ///
+ /// Gets all selected properties at the current node.
+ ///
+ /// The current structured type.
+ /// The selected properties at this node level.
+ internal IDictionary GetSelectedProperties(IEdmStructuredType structuredType)
+ {
+ if (this.selectionType == SelectionType.Empty)
+ {
+ return EmptyEdmProperties;
+ }
+
+ // We cannot determine the selected properties without the user model. This means we won't be computing the missing properties.
+ if (structuredType == null)
+ {
+ return EmptyEdmProperties;
+ }
+
+ if (this.selectionType == SelectionType.EntireSubtree || this.hasWildcard)
+ {
+ return structuredType.DeclaredProperties.ToDictionary(sp => sp.Name, StringComparer.Ordinal);
+ }
+
+ Debug.Assert(this.selectedProperties != null, "selectedProperties != null");
+
+ IDictionary selectedEdmProperties = this.selectedProperties
+ .Select(structuredType.FindProperty)
+ .ToDictionary(p => p.Name, StringComparer.Ordinal);
+
+ return selectedEdmProperties;
+ }
+
///
/// Gets the selected stream properties for the current node.
///
/// The current entity type.
- /// The selected stream properties.
+ /// The selected stream properties at this node level.
internal IDictionary GetSelectedStreamProperties(IEdmEntityType entityType)
{
if (this.selectionType == SelectionType.Empty)
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Reader/JsonLight/PropertyAndValueJsonLightReaderIntegrationTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Reader/JsonLight/PropertyAndValueJsonLightReaderIntegrationTests.cs
index 3a8714590e..d6f934ff14 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Reader/JsonLight/PropertyAndValueJsonLightReaderIntegrationTests.cs
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/IntegrationTests/Reader/JsonLight/PropertyAndValueJsonLightReaderIntegrationTests.cs
@@ -20,6 +20,8 @@ namespace Microsoft.OData.Tests.IntegrationTests.Reader.JsonLight
{
public class PropertyAndValueJsonLightReaderIntegrationTests
{
+ private static string omitValuesNulls = "omit-values=" + ODataConstants.OmitValuesNulls;
+
[Fact]
public void ReadingPayloadInt64SingleDoubleDecimalWithoutSuffix()
{
@@ -106,7 +108,7 @@ public void ReadingPayloadInt64SingleDoubleDecimalWithoutSuffix()
}
[Fact]
- public void ReadNullableCollectionValue()
+ public void ReadNullableCollectionValueExpanded()
{
EdmModel model = new EdmModel();
EdmEntityType entityType = new EdmEntityType("NS", "MyTestEntity");
@@ -128,25 +130,30 @@ public void ReadNullableCollectionValue()
"}";
IEdmModel mainModel = TestUtils.WrapReferencedModelsToMainModel("EntityNs", "MyContainer", model);
- ODataResource entry = null;
- this.ReadEntryPayload(mainModel, payload, entitySet, entityType, reader => { entry = entry ?? reader.Item as ODataResource; });
- Assert.NotNull(entry);
- var intCollection = entry.Properties.FirstOrDefault(
- s => string.Equals(
- s.Name,
- "NullableIntNumbers",
- StringComparison.OrdinalIgnoreCase)).Value.As();
- var list = new List();
- foreach (var val in intCollection.Items)
+ foreach (bool isNullValuesOmitted in new bool[] {false, true})
{
- list.Add(val as int?);
- }
+ ODataResource entry = null;
+ this.ReadEntryPayload(mainModel, payload, entitySet, entityType,
+ reader => { entry = entry ?? reader.Item as ODataResource; }, isNullValuesOmitted);
+ Assert.NotNull(entry);
+
+ var intCollection = entry.Properties.FirstOrDefault(
+ s => string.Equals(
+ s.Name,
+ "NullableIntNumbers",
+ StringComparison.OrdinalIgnoreCase)).Value.As();
+ var list = new List();
+ foreach (var val in intCollection.Items)
+ {
+ list.Add(val as int?);
+ }
- Assert.Equal(0, list[0]);
- Assert.Null(list[1]);
- Assert.Equal(1, (int)list[2]);
- Assert.Equal(2, (int)list[3]);
+ Assert.Equal(0, list[0]);
+ Assert.Null(list[1]);
+ Assert.Equal(1, (int) list[2]);
+ Assert.Equal(2, (int) list[3]);
+ }
}
[Fact]
@@ -176,53 +183,60 @@ public void ReadOpenCollectionPropertyValue()
"}";
IEdmModel mainModel = TestUtils.WrapReferencedModelsToMainModel("EntityNs", "MyContainer", model);
- ODataResource entry = null;
- List complexCollection = new List();
- ODataNestedResourceInfo nestedOpenCollection = null;
- bool startComplexCollection = false;
- this.ReadEntryPayload(mainModel, payload, entitySet, entityType,
- reader =>
- {
- switch (reader.State)
- {
- case ODataReaderState.NestedResourceInfoStart:
- startComplexCollection = true;
- break;
- case ODataReaderState.ResourceEnd:
- if(startComplexCollection)
- {
- complexCollection.Add(reader.Item as ODataResource);
- }
- entry = reader.Item as ODataResource;
- break;
- case ODataReaderState.NestedResourceInfoEnd:
- startComplexCollection = false;
- nestedOpenCollection = reader.Item as ODataNestedResourceInfo;
- break;
- }
- });
- Assert.NotNull(entry);
- var intCollection = entry.Properties.FirstOrDefault(
- s => string.Equals(
- s.Name,
- "OpenPrimitiveCollection",
- StringComparison.OrdinalIgnoreCase)).Value.As();
- var list = new List();
- foreach (var val in intCollection.Items)
+ foreach (bool isNullValuesOmitted in new bool[] {false, true})
{
- list.Add(val as int?);
- }
+ ODataResource entry = null;
+ List complexCollection = new List();
+ ODataNestedResourceInfo nestedOpenCollection = null;
+ bool startComplexCollection = false;
+ this.ReadEntryPayload(mainModel, payload, entitySet, entityType,
+ reader =>
+ {
+ switch (reader.State)
+ {
+ case ODataReaderState.NestedResourceInfoStart:
+ startComplexCollection = true;
+ break;
+ case ODataReaderState.ResourceEnd:
+ if (startComplexCollection)
+ {
+ complexCollection.Add(reader.Item as ODataResource);
+ }
+ entry = reader.Item as ODataResource;
+ break;
+ case ODataReaderState.NestedResourceInfoEnd:
+ startComplexCollection = false;
+ nestedOpenCollection = reader.Item as ODataNestedResourceInfo;
+ break;
+ }
+ },
+ nullValuesOmitted: isNullValuesOmitted);
+ Assert.NotNull(entry);
+
+ var intCollection = entry.Properties.FirstOrDefault(
+ s => string.Equals(
+ s.Name,
+ "OpenPrimitiveCollection",
+ StringComparison.OrdinalIgnoreCase)).Value.As();
+ var list = new List();
+ foreach (var val in intCollection.Items)
+ {
+ list.Add(val as int?);
+ }
- Assert.Equal(0, list[0]);
- Assert.Equal(1, (int)list[1]);
- Assert.Equal(2, (int)list[2]);
+ Assert.Equal(0, list[0]);
+ Assert.Equal(1, (int) list[1]);
+ Assert.Equal(2, (int) list[2]);
- Assert.Equal("OpenComplexTypeCollection", nestedOpenCollection.Name);
+ Assert.Equal("OpenComplexTypeCollection", nestedOpenCollection.Name);
- foreach (var val in complexCollection)
- {
- val.Properties.FirstOrDefault(s => string.Equals(s.Name, "CLongId", StringComparison.OrdinalIgnoreCase)).Value.ShouldBeEquivalentTo(1L, "value should be in correct type.");
+ foreach (var val in complexCollection)
+ {
+ val.Properties.FirstOrDefault(
+ s => string.Equals(s.Name, "CLongId", StringComparison.OrdinalIgnoreCase))
+ .Value.ShouldBeEquivalentTo(1L, "value should be in correct type.");
+ }
}
}
@@ -353,6 +367,64 @@ public void ReadingTypeDefinitionPayloadJsonLight()
address.Properties.FirstOrDefault(s => string.Equals(s.Name, "CountryRegion", StringComparison.OrdinalIgnoreCase)).Value.Should().Be("China");
}
+ [Fact]
+ public void ReadingTypeDefinitionPayloadJsonLightWithOmittedNullValues()
+ {
+ EdmEntityType entityType;
+ EdmEntitySet entitySet;
+ EdmModel model = BuildEdmModelForOmittedNullValuesTestCases(out entityType, out entitySet);
+
+ const string payload = @"
+{
+ ""@odata.context"":""http://www.example.com/$metadata#EntityNs.MyContainer.People/$entity"",
+ ""@odata.id"":""http://mytest"",
+ ""Id"":0,
+ ""Weight"":60.5,
+ ""Address"":{""CountryRegion"":""US"", ""City"":""Redmond""}
+}";
+
+ List entries = new List();
+ ODataNestedResourceInfo navigationLink = null;
+ this.ReadEntryPayload(model, payload, entitySet, entityType,
+ reader =>
+ {
+ switch (reader.State)
+ {
+ case ODataReaderState.ResourceStart:
+ entries.Add(reader.Item as ODataResource);
+ break;
+ case ODataReaderState.NestedResourceInfoStart:
+ navigationLink = (ODataNestedResourceInfo)reader.Item;
+ break;
+ default:
+ break;
+ }
+ },
+ nullValuesOmitted: true);
+
+ Assert.Equal(2, entries.Count);
+
+ ODataResource person = entries[0];
+ person.Properties.FirstOrDefault(s => string.Equals(s.Name, "Id", StringComparison.Ordinal))
+ .Value.Should().Be(0);
+ person.Properties.FirstOrDefault(s => string.Equals(s.Name, "Weight", StringComparison.Ordinal))
+ .Value.Should().Be(60.5);
+ // omitted value should be restored to null.
+ person.Properties.FirstOrDefault(s => string.Equals(s.Name, "Education", StringComparison.Ordinal))
+ .Value.Should().BeNull();
+
+ ODataResource address = entries[1];
+ address.Properties.FirstOrDefault(s => string.Equals(s.Name, "CountryRegion", StringComparison.Ordinal))
+ .Value.Should().Be("US");
+ address.Properties.FirstOrDefault(s => string.Equals(s.Name, "City", StringComparison.Ordinal))
+ .Value.Should().Be("Redmond");
+ // Omitted value should be restored to null.
+ address.Properties.FirstOrDefault(s => string.Equals(s.Name, "ZipCode", StringComparison.Ordinal))
+ .Value.Should().BeNull();
+
+ navigationLink.Name.Should().Be("Address");
+ }
+
[Fact]
public void ReadingTypeDefinitionPayloadWithTypeAnnotationJsonLight()
{
@@ -773,6 +845,246 @@ public void ReadingNullValueForDeclaredCollectionPropertyInComplexTypeShouldFail
read.ShouldThrow().WithMessage("A null value was found for the property named 'CountriesOrRegions', which has the expected type 'Collection(Edm.String)[Nullable=False]'. The expected type 'Collection(Edm.String)[Nullable=False]' does not allow null values.");
}
+ [Fact]
+ public void ReadingPropertyInTopLevelOrInComplexTypeShouldRestoreOmittedNullValues()
+ {
+ EdmEntityType entityType;
+ EdmEntitySet entitySet;
+ EdmModel model = BuildEdmModelForOmittedNullValuesTestCases(out entityType, out entitySet);
+
+ const string payload =@"{
+ ""@odata.context"":""http://www.example.com/$metadata#EntityNs.MyContainer.People/$entity"",
+ ""@odata.id"":""http://mytest"",
+ ""Id"":0,
+ ""Education"":{""Id"":1}
+ }";
+
+ List entries = new List();
+ this.ReadEntryPayload(model, payload, entitySet, entityType,
+ reader =>
+ {
+ switch (reader.State)
+ {
+ case ODataReaderState.ResourceStart:
+ entries.Add(reader.Item as ODataResource);
+ break;
+ default:
+ break;
+ }
+ },
+ nullValuesOmitted: true);
+
+ Assert.Equal(2, entries.Count);
+
+ ODataResource edu = entries.FirstOrDefault(s => string.Equals(s.TypeName, "NS.Edu", StringComparison.Ordinal));
+ edu.Should().NotBeNull();
+ edu.Properties.FirstOrDefault(s => string.Equals(s.Name, "Id", StringComparison.Ordinal)).Value.Should().Be(1);
+ // null-able collection should be restored.
+ edu.Properties.FirstOrDefault(s => string.Equals(s.Name, "Campuses", StringComparison.Ordinal)).Value.Should().BeNull();
+ // null-able property value should be restored.
+ edu.Properties.FirstOrDefault(s => string.Equals(s.Name, "SchoolName", StringComparison.Ordinal)).Value.Should().BeNull();
+
+ ODataResource person = entries.FirstOrDefault(s => string.Equals(s.TypeName, "NS.Person", StringComparison.Ordinal));
+ person.Should().NotBeNull();
+ person.Properties.FirstOrDefault(s => string.Equals(s.Name, "Id", StringComparison.Ordinal)).Value.Should().Be(0);
+ // omitted null-able property should be restored as null.
+ person.Properties.FirstOrDefault(s => string.Equals(s.Name, "Height", StringComparison.Ordinal)).Value.Should().BeNull();
+ // missing non-null-able property should not be restored.
+ person.Properties.Any(s => string.Equals(s.Name, "Weight", StringComparison.Ordinal)).Should().BeFalse();
+ }
+
+ [Fact]
+ public void ReadingPropertyInTopLevelOrInComplexTypeShouldRestoreOmittedNullValuesWithSelectedPropertiesEntireSubTree()
+ {
+ EdmEntityType entityType;
+ EdmEntitySet entitySet;
+ EdmModel model = BuildEdmModelForOmittedNullValuesTestCases(out entityType, out entitySet);
+
+ // null-able property Height is not selected, thus should not be restored.
+ // null-able property Address is selected, thus should be restored.
+ // Property Education is null-able.
+ const string payloadWithQueryOption = @"{
+ ""@odata.context"":""http://www.example.com/$metadata#EntityNs.MyContainer.People(Id,Education,Address)/$entity"",
+ ""@odata.id"":""http://mytest"",
+ ""Id"":0,
+ ""Education"":{""Id"":1}
+ }";
+ const string payloadWithWildcardInQueryOption = @"{
+ ""@odata.context"":""http://www.example.com/$metadata#EntityNs.MyContainer.People(Id,Education/*,Address)/$entity"",
+ ""@odata.id"":""http://mytest"",
+ ""Id"":0,
+ ""Education"":{""Id"":1}
+ }";
+
+ foreach (string payload in new string[] {payloadWithQueryOption, payloadWithWildcardInQueryOption})
+ {
+ List entries = new List();
+ List navigationLinks = new List();
+ this.ReadEntryPayload(model, payload, entitySet, entityType,
+ reader =>
+ {
+ switch (reader.State)
+ {
+ case ODataReaderState.ResourceStart:
+ entries.Add(reader.Item as ODataResource);
+ break;
+ case ODataReaderState.NestedResourceInfoStart:
+ navigationLinks.Add(reader.Item as ODataNestedResourceInfo);
+ break;
+ default:
+ break;
+ }
+ },
+ nullValuesOmitted: true);
+
+ entries.Count.Should().Be(2);
+ navigationLinks.Count.Should().Be(1);
+ navigationLinks.FirstOrDefault(s => string.Equals(s.Name, "Education", StringComparison.Ordinal))
+ .Should().NotBeNull();
+
+ // Education
+ ODataResource edu =
+ entries.FirstOrDefault(s => string.Equals(s.TypeName, "NS.Edu", StringComparison.Ordinal));
+ edu.Should().NotBeNull();
+ edu.Properties.FirstOrDefault(s => string.Equals(s.Name, "Id", StringComparison.Ordinal))
+ .Value.Should().Be(1);
+ // null-able collection should be restored.
+ edu.Properties.FirstOrDefault(s => string.Equals(s.Name, "Campuses", StringComparison.Ordinal))
+ .Value.Should().BeNull();
+ // null-able property value should be restored.
+ edu.Properties.FirstOrDefault(s => string.Equals(s.Name, "SchoolName", StringComparison.Ordinal))
+ .Value.Should().BeNull();
+
+ // Person
+ ODataResource person =
+ entries.FirstOrDefault(s => string.Equals(s.TypeName, "NS.Person", StringComparison.Ordinal));
+ person.Should().NotBeNull();
+ person.Properties.FirstOrDefault(s => string.Equals(s.Name, "Id", StringComparison.Ordinal))
+ .Value.Should().Be(0);
+ // selected null-able property/navigationLink should be restored as null.
+ person.Properties.FirstOrDefault(s => string.Equals(s.Name, "Address", StringComparison.Ordinal))
+ .Value.Should().BeNull();
+ // null-able but not selected properties and not-null-able properties should not be restored.
+ person.Properties.Any(s => string.Equals(s.Name, "Height", StringComparison.Ordinal)).Should().BeFalse();
+ person.Properties.Any(s => string.Equals(s.Name, "Weight", StringComparison.Ordinal)).Should().BeFalse();
+ }
+ }
+
+ [Fact]
+ public void ReadingPropertyInTopLevelOrInComplexTypeShouldRestoreOmittedNullValuesWithSelectedPropertiesPartialSubTree()
+ {
+ EdmEntityType entityType;
+ EdmEntitySet entitySet;
+ EdmModel model = BuildEdmModelForOmittedNullValuesTestCases(out entityType, out entitySet);
+
+ // null-able property Height is not selected, thus should not be restored.
+ // null-able property Address is selected, thus should be restored.
+ // Property Education is null-able.
+ const string payloadWithWildcardInQueryOption = @"{
+ ""@odata.context"":""http://www.example.com/$metadata#EntityNs.MyContainer.People(Id,%20Education/Id,%20Education/SchoolName,%20Address)/$entity"",
+ ""@odata.id"":""http://mytest"",
+ ""Id"":0,
+ ""Education"":{""Id"":1}
+ }";
+
+ foreach (string payload in new string[] { payloadWithWildcardInQueryOption })
+ {
+ List entries = new List();
+ List navigationLinks = new List();
+ this.ReadEntryPayload(model, payload, entitySet, entityType,
+ reader =>
+ {
+ switch (reader.State)
+ {
+ case ODataReaderState.ResourceStart:
+ entries.Add(reader.Item as ODataResource);
+ break;
+ case ODataReaderState.NestedResourceInfoStart:
+ navigationLinks.Add(reader.Item as ODataNestedResourceInfo);
+ break;
+ default:
+ break;
+ }
+ },
+ nullValuesOmitted: true);
+
+ entries.Count.Should().Be(2);
+ navigationLinks.Count.Should().Be(1);
+ navigationLinks.FirstOrDefault(s => string.Equals(s.Name, "Education", StringComparison.Ordinal))
+ .Should().NotBeNull();
+
+ // Education
+ ODataResource edu =
+ entries.FirstOrDefault(s => string.Equals(s.TypeName, "NS.Edu", StringComparison.Ordinal));
+ edu.Should().NotBeNull();
+ edu.Properties.FirstOrDefault(s => string.Equals(s.Name, "Id", StringComparison.Ordinal))
+ .Value.Should().Be(1);
+ // null-able property value should be restored.
+ edu.Properties.FirstOrDefault(s => string.Equals(s.Name, "SchoolName", StringComparison.Ordinal))
+ .Value.Should().BeNull();
+ // not selected property should not be restored.
+ edu.Properties.Any(s => string.Equals(s.Name, "Campuses", StringComparison.Ordinal)).Should().BeFalse();
+ }
+ }
+
+ [Fact]
+ public void ReadingPropertyInTopLevelOrInComplexTypeShouldRestoreOmittedNullValuesWithSelectedPropertiesPartialSubTreeAndBaseTypeProperty()
+ {
+ EdmEntityType entityType;
+ EdmEntitySet entitySet;
+ EdmModel model = BuildEdmModelForOmittedNullValuesTestCases(out entityType, out entitySet);
+
+ // null-able property Height is not selected, thus should not be restored.
+ // null-able property Address is selected, thus should be restored.
+ // Property Education is null-able.
+ const string payloadWithWildcardInQueryOption = @"{
+ ""@odata.context"":""http://www.example.com/$metadata#EntityNs.MyContainer.People(Id,%20Education/Id,%20Education/OrgId,%20Address)/$entity"",
+ ""@odata.id"":""http://mytest"",
+ ""Id"":0,
+ ""Education"":{""Id"":1}
+ }";
+
+ foreach (string payload in new string[] { payloadWithWildcardInQueryOption })
+ {
+ List entries = new List();
+ List navigationLinks = new List();
+ this.ReadEntryPayload(model, payload, entitySet, entityType,
+ reader =>
+ {
+ switch (reader.State)
+ {
+ case ODataReaderState.ResourceStart:
+ entries.Add(reader.Item as ODataResource);
+ break;
+ case ODataReaderState.NestedResourceInfoStart:
+ navigationLinks.Add(reader.Item as ODataNestedResourceInfo);
+ break;
+ default:
+ break;
+ }
+ },
+ nullValuesOmitted: true);
+
+ entries.Count.Should().Be(2);
+ navigationLinks.Count.Should().Be(1);
+ navigationLinks.FirstOrDefault(s => string.Equals(s.Name, "Education", StringComparison.Ordinal))
+ .Should().NotBeNull();
+
+ // Education
+ ODataResource edu =
+ entries.FirstOrDefault(s => string.Equals(s.TypeName, "NS.Edu", StringComparison.Ordinal));
+ edu.Should().NotBeNull();
+ edu.Properties.FirstOrDefault(s => string.Equals(s.Name, "Id", StringComparison.Ordinal))
+ .Value.Should().Be(1);
+ // selected null-able property from base type should be restored to null if omitted.
+ edu.Properties.FirstOrDefault(s => string.Equals(s.Name, "OrgId", StringComparison.Ordinal))
+ .Value.Should().BeNull();
+ // not selected property should not be restored.
+ edu.Properties.Any(s => string.Equals(s.Name, "SchoolName", StringComparison.Ordinal)).Should().BeFalse();
+ edu.Properties.Any(s => string.Equals(s.Name, "Campuses", StringComparison.Ordinal)).Should().BeFalse();
+ }
+ }
+
[Fact]
public void ReadingNullValueForDynamicCollectionPropertyInOpenStructuralTypeShouldWork()
{
@@ -840,13 +1152,23 @@ public void ReadDateTimeOffsetWithCustomFormat()
birthday.Value.Should().Be(new DateTimeOffset(2012, 4, 12, 18, 43, 10, TimeSpan.Zero));
}
- private void ReadEntryPayload(IEdmModel userModel, string payload, EdmEntitySet entitySet, IEdmEntityType entityType, Action action, bool isIeee754Compatible = true, IServiceProvider container = null)
+ private void ReadEntryPayload(IEdmModel userModel, string payload, EdmEntitySet entitySet, IEdmEntityType entityType,
+ Action action,
+ bool isIeee754Compatible = true,
+ IServiceProvider container = null,
+ bool nullValuesOmitted = false)
{
var message = new InMemoryMessage() { Stream = new MemoryStream(Encoding.UTF8.GetBytes(payload)), Container = container};
string contentType = isIeee754Compatible
? "application/json;odata.metadata=minimal;IEEE754Compatible=true"
: "application/json;odata.metadata=minimal;IEEE754Compatible=false";
message.SetHeader("Content-Type", contentType);
+
+ if (nullValuesOmitted)
+ {
+ message.SetHeader("Preference-Applied", omitValuesNulls);
+ }
+
var readerSettings = new ODataMessageReaderSettings { EnableMessageStreamDisposal = true };
readerSettings.Validations &= ~ValidationKinds.ThrowOnUndeclaredPropertyForNonOpenType;
using (var msgReader = new ODataMessageReader((IODataResponseMessage)message, readerSettings, userModel))
@@ -858,5 +1180,66 @@ private void ReadEntryPayload(IEdmModel userModel, string payload, EdmEntitySet
}
}
}
+
+ private EdmModel BuildEdmModelForOmittedNullValuesTestCases(out EdmEntityType entityType, out EdmEntitySet entitySet)
+ {
+ EdmModel model = new EdmModel();
+
+ entityType = new EdmEntityType("NS", "Person");
+ entityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32);
+
+ EdmTypeDefinition weightType = new EdmTypeDefinition("NS", "Wgt", EdmPrimitiveTypeKind.Double);
+ EdmTypeDefinitionReference weightTypeRef = new EdmTypeDefinitionReference(weightType, false);
+ entityType.AddStructuralProperty("Weight", weightTypeRef);
+
+ EdmTypeDefinition heightType = new EdmTypeDefinition("NS", "Ht", EdmPrimitiveTypeKind.Double);
+ EdmTypeDefinitionReference heightTypeRef = new EdmTypeDefinitionReference(heightType, true);
+ entityType.AddStructuralProperty("Height", heightTypeRef);
+
+ EdmComplexType addressType = new EdmComplexType("NS", "OpnAddr");
+ EdmComplexTypeReference addressTypeRef = new EdmComplexTypeReference(addressType, true);
+
+ EdmTypeDefinition countryRegionType = new EdmTypeDefinition("NS", "CR", EdmPrimitiveTypeKind.String);
+ EdmTypeDefinitionReference countryRegionTypeRef = new EdmTypeDefinitionReference(countryRegionType, false);
+ addressType.AddStructuralProperty("CountryRegion", countryRegionTypeRef);
+
+ // Address/City
+ EdmTypeDefinition cityType = new EdmTypeDefinition("NS", "Cty", EdmPrimitiveTypeKind.String);
+ EdmTypeDefinitionReference cityTypeRef = new EdmTypeDefinitionReference(cityType, false);
+ addressType.AddStructuralProperty("City", cityTypeRef);
+
+ EdmTypeDefinition zipCodeType = new EdmTypeDefinition("NS", "ZC", EdmPrimitiveTypeKind.String);
+ EdmTypeDefinitionReference zipCodeTypeRef = new EdmTypeDefinitionReference(zipCodeType, true);
+ addressType.AddStructuralProperty("ZipCode", zipCodeTypeRef);
+
+ entityType.AddStructuralProperty("Address", addressTypeRef);
+
+ // Education
+ EdmComplexType organizationType = new EdmComplexType("NS", "Org");
+ organizationType.AddStructuralProperty("OrgId", EdmPrimitiveTypeKind.Int32);
+ organizationType.AddStructuralProperty("OrgCategory", EdmPrimitiveTypeKind.String);
+
+ EdmComplexType educationType = new EdmComplexType("NS", "Edu", organizationType);
+ EdmComplexTypeReference educationTypeRef = new EdmComplexTypeReference(educationType, true);
+
+ // top level null-able complex type properties
+ educationType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32); // same property name but inside different type.
+ educationType.AddStructuralProperty("Campuses",
+ new EdmCollectionTypeReference(new EdmCollectionType(EdmCoreModel.Instance.GetString(true))));
+ educationType.AddStructuralProperty("SchoolName", EdmPrimitiveTypeKind.String);
+
+ entityType.AddStructuralProperty("Education", educationTypeRef);
+
+ model.AddElement(weightType);
+ model.AddElement(heightType);
+ model.AddElement(addressType);
+ model.AddElement(entityType);
+
+ EdmEntityContainer container = new EdmEntityContainer("EntityNs", "MyContainer");
+ entitySet = container.AddEntitySet("People", entityType);
+ model.AddElement(container);
+
+ return model;
+ }
}
}
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightDeltaWriterTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightDeltaWriterTests.cs
index cc07bbf4fb..2ec094e928 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightDeltaWriterTests.cs
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightDeltaWriterTests.cs
@@ -60,6 +60,22 @@ public class ODataJsonLightDeltaWriterTests
}
};
+ private readonly ODataResource customerUpdatedWithNullValues = new ODataResource
+ {
+ Id = new Uri("Customers('BOTTM')", UriKind.Relative),
+ Properties = new List
+ {
+ new ODataProperty { Name = "ContactName", Value = null },
+ },
+ TypeName = "MyNS.Customer",
+ SerializationInfo = new ODataResourceSerializationInfo
+ {
+ NavigationSourceEntityTypeName = "MyNS.Customer",
+ NavigationSourceKind = EdmNavigationSourceKind.EntitySet,
+ NavigationSourceName = "Customers"
+ }
+ };
+
private readonly ODataDeltaDeletedLink linkToOrder10643 = new ODataDeltaDeletedLink(new Uri("Customers('ALFKI')", UriKind.Relative), new Uri("Orders('10643')", UriKind.Relative), "Orders");
private readonly ODataDeltaLink linkToOrder10645 = new ODataDeltaLink(new Uri("Customers('BOTTM')", UriKind.Relative), new Uri("Orders('10645')", UriKind.Relative), "Orders");
@@ -1358,7 +1374,7 @@ public void WriteNestedDeletedEntryInDeletedEntry()
new ODataDeletedResource()
{
Reason = DeltaDeletedEntryReason.Deleted,
- Properties = new ODataProperty[]
+ Properties = new ODataProperty[]
{
new ODataProperty() { Name = "Name", Value = "Scissors" },
new ODataProperty() { Name = "Id", Value = 1 }
@@ -2033,7 +2049,7 @@ public void WriteTopLevelDeletedEntityFromDifferentSet()
this.TestPayload().Should().Be("{\"@context\":\"http://host/service/$metadata#Customers/$delta\",\"@count\":5,\"@deltaLink\":\"Customers?$expand=Orders&$deltatoken=8015\",\"value\":[{\"@id\":\"Customers('BOTTM')\",\"ContactName\":\"Susan Halvenstern\"},{\"@context\":\"http://host/service/$metadata#Orders/$deletedEntity\",\"@removed\":{\"reason\":\"deleted\"},\"@id\":\"Orders(10643)\"}]}");
}
-
+
[Fact]
public void WriteTopLevelDeletedEntityFromDifferentSetWithoutInfo()
{
@@ -2051,6 +2067,155 @@ public void WriteTopLevelDeletedEntityFromDifferentSetWithoutInfo()
this.TestPayload().Should().Be("{\"@context\":\"http://host/service/$metadata#Customers/$delta\",\"@count\":5,\"@deltaLink\":\"Customers?$expand=Orders&$deltatoken=8015\",\"value\":[{\"@id\":\"Customers('BOTTM')\",\"ContactName\":\"Susan Halvenstern\"},{\"@context\":\"http://host/service/$metadata#Orders/$deletedEntity\",\"@removed\":{\"reason\":\"deleted\"},\"@id\":\"Orders(10643)\"}]}");
}
+ ///
+ /// When using regular OData writer with required writingDelta = true
to write
+ /// containing , property value of null
+ /// should always be written in response (regardless of omitNullValues preference setting).
+ ///
+ [Fact]
+ public void WriteTopLevelEntityWithNullPropertyValues()
+ {
+ this.TestInit(this.GetModel());
+
+
+ foreach (bool omitNullValues in new bool[] {true, false})
+ {
+ using (this.stream = new MemoryStream())
+ {
+ // Create resource set writer with specified omitNullValues setting
+ ODataJsonLightWriter writer = new ODataJsonLightWriter(
+ CreateJsonLightOutputContext(this.stream, this.GetModel(), omitNullValues, false, null, ODataVersion.V401),
+ this.GetCustomers(),
+ this.GetCustomerType(),
+ true,
+ false,
+ /*writingDelta*/ true);
+ writer.WriteStart(feedWithoutInfo);
+ writer.WriteStart(customerUpdatedWithNullValues);
+ writer.WriteEnd(); // customer
+ writer.WriteEnd(); // delta resource set
+ writer.Flush();
+
+ this.TestPayload().Should().Be(
+ "{\"@context\":\"http://host/service/$metadata#Customers/$delta\",\"@count\":5,\"@deltaLink\":\"Customers?$expand=Orders&$deltatoken=8015\",\"value\":[{\"@id\":\"Customers('BOTTM')\",\"ContactName\":null}]}");
+ }
+ }
+ }
+
+ ///
+ /// When using with required writingDelta = true
to write
+ /// containing , property value of null
+ /// should always be written in response (regardless of omitNullValues preference setting).
+ ///
+ [Fact]
+ public void WriteEntityWithNullPropertyValueInDeltaFeed()
+ {
+ this.TestInit(this.GetModel());
+
+ ODataDeltaResourceSet feed = new ODataDeltaResourceSet();
+
+ ODataResource orderEntry = new ODataResource()
+ {
+ SerializationInfo = new ODataResourceSerializationInfo
+ {
+ NavigationSourceEntityTypeName = "MyNS.Order",
+ NavigationSourceKind = EdmNavigationSourceKind.EntitySet,
+ NavigationSourceName = "Orders"
+ },
+ Properties = new ODataProperty[]
+ {
+ new ODataProperty() { Name = "Id", Value = 1 }
+ },
+ };
+
+ ODataNestedResourceInfo shippingAddressInfo = new ODataNestedResourceInfo
+ {
+ Name = "ShippingAddress",
+ IsCollection = false
+ };
+
+ ODataResource shippingAddress = new ODataResource
+ {
+ Properties = new List
+ {
+ new ODataProperty { Name = "City", Value = "Shanghai" },
+ new ODataProperty { Name = "PostalCode", Value = null }
+ }
+ };
+
+ var result = new ODataQueryOptionParser(this.GetModel(), this.GetCustomerType(), this.GetCustomers(), new Dictionary { { "$expand", "Orders($select=ShippingAddress)" }}).ParseSelectAndExpand();
+
+ ODataUri odataUri = new ODataUri()
+ {
+ ServiceRoot = new Uri("http://host/service"),
+ SelectAndExpand = result
+ };
+
+ foreach (bool omitNullValues in new bool[] {true, false})
+ {
+ using (this.stream = new MemoryStream())
+ {
+ var outputContext = CreateJsonLightOutputContext(this.stream, this.GetModel(), omitNullValues,
+ false, odataUri, ODataVersion.V401);
+ ODataJsonLightDeltaWriter writer = new ODataJsonLightDeltaWriter(outputContext, this.GetProducts(),
+ this.GetProductType());
+ writer.WriteStart(feed);
+ writer.WriteStart(orderEntry);
+ writer.WriteStart(shippingAddressInfo);
+ writer.WriteStart(shippingAddress);
+ writer.WriteEnd(); // shippingAddress
+ writer.WriteEnd(); // shippingAddressInfo
+ writer.WriteEnd(); // Order
+ writer.WriteEnd(); // Feed
+ writer.Flush();
+
+ this.TestPayload().Should().Be(
+ "{\"@context\":\"http://host/service/$metadata#Products(Orders(ShippingAddress))/$delta\",\"value\":[{\"@context\":\"http://host/service/$metadata#Orders/$entity\",\"Id\":1,\"ShippingAddress\":{\"City\":\"Shanghai\",\"PostalCode\":null}}]}");
+ }
+ }
+ }
+
+ ///
+ /// When using regular OData writer with required writingDelta = true
to write ,
+ /// property value of null should always be written in response (regardless of omitNullValues preference setting).
+ ///
+ [Fact]
+ public void WriteDeletedResourceWithPropertyValueNull()
+ {
+ this.TestInit(this.GetModel());
+ ODataDeletedResource deletedEntity = new ODataDeletedResource()
+ {
+ Reason = DeltaDeletedEntryReason.Changed,
+ TypeName = "MyNS.PhysicalProduct",
+ Properties = new[]
+ {
+ new ODataProperty {Name = "Id", Value = new ODataPrimitiveValue(1)},
+ new ODataProperty {Name = "Name", Value = new ODataPrimitiveValue("car")},
+ new ODataProperty {Name = "Material", Value = null}
+ }
+ };
+
+ foreach (bool omitNullValues in new bool[] {true, false})
+ {
+ using (this.stream = new MemoryStream())
+ {
+ ODataDeltaResourceSet feed = new ODataDeltaResourceSet();
+ ODataJsonLightWriter writer = new ODataJsonLightWriter(
+ CreateJsonLightOutputContext(this.stream, this.GetModel(), omitNullValues, false, null,
+ ODataVersion.V401),
+ this.GetProducts(), this.GetProductType(), true, false, /*writingDelta*/true);
+ writer.WriteStart(feed);
+ writer.WriteStart(deletedEntity);
+ writer.WriteEnd();
+ writer.WriteEnd();
+ writer.Flush();
+
+ this.TestPayload().Should().Be(
+ "{\"@context\":\"http://host/service/$metadata#Products/$delta\",\"value\":[{\"@removed\":{\"reason\":\"changed\"},\"@type\":\"#MyNS.PhysicalProduct\",\"Id\":1,\"Name\":\"car\",\"Material\":null}]}");
+ }
+ }
+ }
+
[Fact]
public void WriteEntityFromDifferentSetToEntitySetShouldFail()
{
@@ -2425,9 +2590,20 @@ private string TestPayload()
return (new StreamReader(stream)).ReadToEnd();
}
- private static ODataJsonLightOutputContext CreateJsonLightOutputContext(MemoryStream stream, IEdmModel userModel, bool fullMetadata = false, ODataUri uri = null, ODataVersion version = ODataVersion.V4)
+ private static ODataJsonLightOutputContext CreateJsonLightOutputContext(MemoryStream stream, IEdmModel userModel,
+ bool fullMetadata = false, ODataUri uri = null, ODataVersion version = ODataVersion.V4)
{
- var settings = new ODataMessageWriterSettings { Version = version, ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter("*") };
+ return CreateJsonLightOutputContext(stream, userModel, /*omitNullValues*/ false, fullMetadata, uri, version);
+ }
+
+ private static ODataJsonLightOutputContext CreateJsonLightOutputContext(MemoryStream stream, IEdmModel userModel, bool omitNullValues, bool fullMetadata = false, ODataUri uri = null, ODataVersion version = ODataVersion.V4)
+ {
+ var settings = new ODataMessageWriterSettings
+ {
+ Version = version,
+ ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter("*"),
+ OmitNullValues = omitNullValues
+ };
settings.SetServiceDocumentUri(new Uri("http://host/service"));
if (uri != null)
{
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/SelectedPropertiesNodeTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/SelectedPropertiesNodeTests.cs
index edb60729a0..74cd149ff1 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/SelectedPropertiesNodeTests.cs
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/SelectedPropertiesNodeTests.cs
@@ -113,6 +113,15 @@ public void WildcardShouldSelectAllPropertiesParsingTest()
SelectedPropertiesNode.Create("*").Should().HaveStreams(this.cityType, "Photo").And.HaveNavigations(this.cityType, "Districts").And.HaveChild(this.cityType, "Districts", c => c.Should().BeSameAsEmpty());
}
+ [Fact]
+ public void WildcardShouldSelectAllProperties()
+ {
+ SelectedPropertiesNode.Create("*").Should()
+ .HaveProperties(this.cityType, "Photo", "Districts")
+ .And.HaveNavigations(this.cityType, "Districts")
+ .And.HaveChild(this.cityType, "Districts", c => c.Should().BeSameAsEmpty());
+ }
+
[Fact]
public void SingleStreamPropertyWithNormalProperty()
{
@@ -141,6 +150,12 @@ public void SpecifyingTheSameNavigationTwiceShouldNotCauseDuplicates()
public void SpecifyingAWildCardShouldNotCauseDuplicates()
{
SelectedPropertiesNode.Create("Districts,*,Photo").Should().HaveStreams(this.cityType, "Photo").And.HaveNavigations(this.cityType, "Districts").And.HaveChild(this.cityType, "Districts", c => c.Should().BeSameAsEntireSubtree());
+
+ SelectedPropertiesNode.Create("Districts,*,Photo").Should()
+ .HaveStreams(this.cityType, "Photo")
+ .And.HaveProperties(this.cityType, "Photo", "Districts")
+ .And.HaveNavigations(this.cityType, "Districts")
+ .And.HaveChild(this.cityType, "Districts", c => c.Should().BeSameAsEntireSubtree());
}
[Fact]
@@ -166,6 +181,7 @@ public void DeepAndWideSelection1()
// 2) 'Districts/*' should not override 'Districts'
SelectedPropertiesNode.Create("*,Districts,Districts/*").Should()
.HaveStreams(this.cityType, "Photo")
+ .And.HaveProperties(this.cityType, "Photo", "Districts")
.And.HaveNavigations(this.cityType, "Districts")
.And.HaveChild(this.cityType, "Districts", c => c.Should().BeSameAsEntireSubtree());
}
@@ -180,6 +196,7 @@ public void DeepAndWideSelection2()
"Districts",
c => c.Should()
.HaveStreams(this.districtType, "Thumbnail")
+ .And.HaveProperties(this.districtType, "Id", "Zip", "Thumbnail", "City")
.And.HaveChild(this.districtType, "City", c2 => c2.Should().BeSameAsEntireSubtree()));
}
@@ -193,6 +210,7 @@ public void WildcardAfterNavigationShouldNotSelectTheEntireTree()
"Districts",
c => c.Should()
.HaveStreams(this.districtType, "Thumbnail")
+ .And.HaveProperties(this.districtType, "Id", "Zip", "Thumbnail", "City")
.And.HaveNavigations(this.districtType, "City"));
}
@@ -332,7 +350,10 @@ public void CombiningPropertyWithWildcardShouldBeWildcard()
{
SelectedPropertiesNode left = SelectedPropertiesNode.Create("*");
SelectedPropertiesNode right = SelectedPropertiesNode.Create("Fake");
- this.VerifyCombination(left, right, n => n.Should().HaveStreams(this.cityType, "Photo").And.HaveNavigations(this.cityType, "Districts"));
+ this.VerifyCombination(left, right,
+ n => n.Should().HaveStreams(this.cityType, "Photo")
+ .And.HaveProperties(this.cityType, "Districts", "Photo")
+ .And.HaveNavigations(this.cityType, "Districts"));
}
[Fact]
@@ -343,6 +364,7 @@ public void CombiningPartialNodesShouldCombineProperties()
Action verify = n => n.Should()
.HaveStreams(this.cityType, "Photo")
+ .And.HaveProperties(this.cityType, "Photo")
.And.HaveNavigations(this.cityType, "Districts")
.And.HaveChild(this.cityType, "Districts", c => c.Should().HaveOnlyStreams(this.districtType, "Thumbnail"));
@@ -362,6 +384,7 @@ public void CombiningDeepPartialNodesShouldCombineRecursively()
"Districts",
c => c.Should()
.HaveStreams(this.districtType, "Thumbnail")
+ .And.HaveProperties(this.districtType, "Thumbnail")
.And.HaveNavigations(this.districtType, "City")
.And.HaveChild(this.districtType, "City", c2 => c2.Should().HaveOnlyNavigations(this.cityType, "Districts")));
@@ -536,6 +559,12 @@ internal SelectedPropertiesNodeAssertions(SelectedPropertiesNode node) : base(no
{
}
+ internal AndConstraint HaveProperties(IEdmEntityType entityType, params string[] propertyNames)
+ {
+ this.Subject.As().GetSelectedProperties(entityType).Keys.Should().BeEquivalentTo(propertyNames);
+ return new AndConstraint(this);
+ }
+
internal AndConstraint HaveStreams(IEdmEntityType entityType, params string[] streamPropertyNames)
{
this.Subject.As().GetSelectedStreamProperties(entityType).Keys.Should().BeEquivalentTo(streamPropertyNames);
diff --git a/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/PublicApi/PublicApi.bsl b/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/PublicApi/PublicApi.bsl
index 206c6fe62e..db78a5239d 100644
--- a/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/PublicApi/PublicApi.bsl
+++ b/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/PublicApi/PublicApi.bsl
@@ -4670,6 +4670,7 @@ public class Microsoft.OData.ODataPreferenceHeader {
string AnnotationFilter { public get; public set; }
bool ContinueOnError { public get; public set; }
System.Nullable`1[[System.Int32]] MaxPageSize { public get; public set; }
+ string OmitValues { public get; public set; }
bool RespondAsync { public get; public set; }
System.Nullable`1[[System.Boolean]] ReturnContent { public get; public set; }
bool TrackChanges { public get; public set; }
@@ -5139,10 +5140,10 @@ public sealed class Microsoft.OData.ODataMessageWriterSettings {
System.Uri BaseUri { public get; public set; }
bool EnableCharactersCheck { public get; public set; }
bool EnableMessageStreamDisposal { public get; public set; }
- bool IgnoreNullValues { public get; public set; }
string JsonPCallback { public get; public set; }
Microsoft.OData.ODataMessageQuotas MessageQuotas { public get; public set; }
Microsoft.OData.ODataUri ODataUri { public get; public set; }
+ bool OmitNullValues { public get; public set; }
Microsoft.OData.ValidationKinds Validations { public get; public set; }
System.Nullable`1[[Microsoft.OData.ODataVersion]] Version { public get; public set; }
diff --git a/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.IgnoreNullPropertiesInEntryTest.approved.txt b/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.IgnoreNullPropertiesInEntryTest.approved.txt
index 3578c7bcee..a1f0970148 100644
--- a/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.IgnoreNullPropertiesInEntryTest.approved.txt
+++ b/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.IgnoreNullPropertiesInEntryTest.approved.txt
@@ -16,7 +16,7 @@ Model Present: true
Combination: 5; TestConfiguration = Format: JsonLight, Request: True, Synchronous: True
Model Present: true
-{"@odata.context":"http://odata.org/test/$metadata#CustomerSet/$entity","ID":44}
+{"@odata.context":"http://odata.org/test/$metadata#CustomerSet/$entity","ID":44,"Hobby":null}
Combination: 6; TestConfiguration = Format: JsonLight, Request: False, Synchronous: True
Model Present: true
@@ -24,7 +24,7 @@ Model Present: true
Combination: 7; TestConfiguration = Format: JsonLight, Request: True, Synchronous: False
Model Present: true
-{"@odata.context":"http://odata.org/test/$metadata#CustomerSet/$entity","ID":44}
+{"@odata.context":"http://odata.org/test/$metadata#CustomerSet/$entity","ID":44,"Hobby":null}
Combination: 8; TestConfiguration = Format: JsonLight, Request: False, Synchronous: False
Model Present: true
diff --git a/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.cs b/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.cs
index 3056e96869..11aae8bd24 100644
--- a/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.cs
+++ b/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.cs
@@ -127,7 +127,7 @@ public void PayloadOrderTest()
{
DebugDescription = "TypeName at the beginning, ID and ETag at the end, ID and ETag are not written and are ignored at the end",
Items = new[] { new ODataResource() { TypeName = "TestModel.NonMLEType" }
- .WithAnnotation(new WriteEntryCallbacksAnnotation
+ .WithAnnotation(new WriteEntryCallbacksAnnotation
{
BeforeWriteStartCallback = (entry) => { entry.Id = null; entry.ETag = null; },
BeforeWriteEndCallback = (entry) => { entry.Id = new Uri("urn:id"); entry.ETag = "etag"; }
@@ -143,7 +143,7 @@ public void PayloadOrderTest()
new EntryPayloadTestCase
{
DebugDescription = "Everything at the beginning",
- Items = new[] { new ODataResource() {
+ Items = new[] { new ODataResource() {
TypeName = "TestModel.MLEType",
Id = new Uri("urn:id"),
ETag = "etag",
@@ -183,7 +183,7 @@ public void PayloadOrderTest()
new EntryPayloadTestCase
{
DebugDescription = "TypeName, Id, ETag and ReadLinks at the beginning, the rest at the end",
- Items = new[] { new ODataResource() {
+ Items = new[] { new ODataResource() {
TypeName = "TestModel.MLEType",
Id = new Uri("urn:id"),
ETag = "etag",
@@ -432,8 +432,8 @@ public void ActionAndFunctionPayloadOrderTest()
});
}
- [TestMethod, Variation(Description = "Test skipping null values when writing JSON Lite entries with IgnoreNullValues = true.")]
- public void IgnoreNullPropertiesInEntryTest()
+ [TestMethod, Variation(Description = "Test skipping null values when writing JSON Lite entries with OmitNullValues = true.")]
+ public void OmitNullPropertiesInEntryTest()
{
EdmModel model = new EdmModel();
var container = new EdmEntityContainer("TestModel", "TestContainer");
@@ -517,7 +517,7 @@ public void IgnoreNullPropertiesInEntryTest()
this.WriterTestConfigurationProvider.JsonLightFormatConfigurationsWithIndent,
(testDescriptor, testConfiguration) =>
{
- testConfiguration.MessageWriterSettings.IgnoreNullValues = true;
+ testConfiguration.MessageWriterSettings.OmitNullValues = true;
TestWriterUtils.WriteAndVerifyODataEdmPayload(testDescriptor, testConfiguration, this.Assert, this.Logger);
});
}
@@ -545,9 +545,9 @@ public void OpenPropertiesInEntryTest()
new EntryPayloadTestCase
{
DebugDescription = "Customer instance with open primitive property.",
- Items = new[] { new ODataResource()
- {
- TypeName = "TestModel.OpenCustomerType",
+ Items = new[] { new ODataResource()
+ {
+ TypeName = "TestModel.OpenCustomerType",
Properties = new ODataProperty[]
{
new ODataProperty { Name = "Age", Value = (long)42 }
@@ -563,9 +563,9 @@ public void OpenPropertiesInEntryTest()
new EntryPayloadTestCase
{
DebugDescription = "Customer instance with open spatial property.",
- Items = new[] { new ODataResource()
- {
- TypeName = "TestModel.OpenCustomerType",
+ Items = new[] { new ODataResource()
+ {
+ TypeName = "TestModel.OpenCustomerType",
Properties = new ODataProperty[]
{
new ODataProperty { Name = "Location", Value = pointValue }
@@ -659,9 +659,9 @@ public void SpatialPropertiesInEntryTest()
new EntryPayloadTestCase
{
DebugDescription = "Customer instance with spatial property (expected and payload type don't match).",
- Items = new[] { new ODataResource()
- {
- TypeName = "TestModel.CustomerType",
+ Items = new[] { new ODataResource()
+ {
+ TypeName = "TestModel.CustomerType",
Properties = new ODataProperty[]
{
new ODataProperty { Name = "Location1", Value = pointValue }
@@ -687,9 +687,9 @@ public void SpatialPropertiesInEntryTest()
new EntryPayloadTestCase
{
DebugDescription = "Customer instance with spatial property (expected and payload type match).",
- Items = new[] { new ODataResource()
- {
- TypeName = "TestModel.CustomerType",
+ Items = new[] { new ODataResource()
+ {
+ TypeName = "TestModel.CustomerType",
Properties = new ODataProperty[]
{
new ODataProperty { Name = "Location2", Value = pointValue }
@@ -776,7 +776,7 @@ public void TopLevelOpenComplexProperties()
tc => new JsonWriterTestExpectedResults(this.Settings.ExpectedResultSettings)
{
Json = string.Format(
- CultureInfo.InvariantCulture,
+ CultureInfo.InvariantCulture,
testCase.Json,
JsonLightWriterUtils.GetMetadataUrlPropertyForProperty(testCase.EntityType.FullTypeName()) + ","),
FragmentExtractor = (result) => result.RemoveAllAnnotations(true)
@@ -796,7 +796,7 @@ public void TopLevelOpenComplexProperties()
{
TestWriterUtils.WriteAndVerifyODataEdmPayload(testDescriptor, testConfiguration, this.Assert, this.Logger);
});
-
+
}
private sealed class EntryPayloadTestCase
{
diff --git a/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/TestWriterUtils.cs b/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/TestWriterUtils.cs
index 4c1a4f7a4d..4bbe0a1ff0 100644
--- a/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/TestWriterUtils.cs
+++ b/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/TestWriterUtils.cs
@@ -166,6 +166,29 @@ internal static void WriteActionAndVerifyODataPayload(Action(
this PayloadWriterTestDescriptor testDescriptor,
WriterTestConfiguration testConfiguration,
bool alwaysSpecifyOwningContainer = false,
- BaselineLogger baselineLogger = null,
+ BaselineLogger baselineLogger = null,
Action writeAction = null)
{
T property = testDescriptor.PayloadItems.Single();
@@ -679,9 +702,9 @@ private static void WritePayload(ODataMessageWriterTestWrapper messageWriter, OD
/// A boolean flag indicating whether to flush the writer after writing.
/// The payload items to write.
///
- /// The list of is interpreted in the following way:
+ /// The list of is interpreted in the following way:
/// for every non-null item we call WriteStart; for every null item we call WriteEnd.
- /// null items can be omitted at the end of the list, e.g., a list of a feed and an entry
+ /// null items can be omitted at the end of the list, e.g., a list of a feed and an entry
/// item would be 'auto-completed' with two null entries.
///
internal static void WritePayload(ODataMessageWriterTestWrapper messageWriter, ODataWriter writer, bool flush, IList items, int throwUserExceptionAt = -1)