diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightContextUriParser.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightContextUriParser.cs
index 5d953c8929..7228e57556 100644
--- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightContextUriParser.cs
+++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightContextUriParser.cs
@@ -61,9 +61,12 @@ private ODataJsonLightContextUriParser(IEdmModel model, Uri contextUriFromPayloa
/// The model to use when resolving the target of the URI.
/// The string value of the odata.metadata annotation read from the payload.
/// The payload kind we expect the context URI to conform to.
- /// The function of client cuetom type resolver.
+ /// The function of client custom type resolver.
/// Whether the fragment after $metadata should be parsed, if set to false, only MetadataDocumentUri is parsed.
/// Whether to throw if a type specified in the ContextUri is not found in metadata.
+ /// Optional value (with default value of null) of base Uri used for resolving the context url.
+ /// It should be a non-null value when resolving a top-level relative context Uri.
+ ///
/// The result from parsing the context URI.
internal static ODataJsonLightContextUriParseResult Parse(
IEdmModel model,
@@ -71,7 +74,8 @@ internal static ODataJsonLightContextUriParseResult Parse(
ODataPayloadKind payloadKind,
Func clientCustomTypeResolver,
bool needParseFragment,
- bool throwIfMetadataConflict = true)
+ bool throwIfMetadataConflict = true,
+ Uri baseUri = null)
{
if (contextUriFromPayload == null)
{
@@ -79,9 +83,8 @@ internal static ODataJsonLightContextUriParseResult Parse(
}
// Create an absolute URI from the payload string
- // TODO: Support relative context uri and resolving other relative uris
Uri contextUri;
- if (!Uri.TryCreate(contextUriFromPayload, UriKind.Absolute, out contextUri))
+ if (!Uri.TryCreate(baseUri, contextUriFromPayload, out contextUri))
{
throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_TopLevelContextUrlShouldBeAbsolute(contextUriFromPayload));
}
diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeserializer.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeserializer.cs
index 603d6f92d0..ffff9ac774 100644
--- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeserializer.cs
+++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeserializer.cs
@@ -255,7 +255,8 @@ internal void ReadPayloadStart(
payloadKind,
this.MessageReaderSettings.ClientCustomTypeResolver,
this.JsonLightInputContext.ReadingResponse,
- this.JsonLightInputContext.MessageReaderSettings.ThrowIfTypeConflictsWithMetadata);
+ this.JsonLightInputContext.MessageReaderSettings.ThrowIfTypeConflictsWithMetadata,
+ this.MessageReaderSettings.RequestUri);
}
this.contextUriParseResult = parseResult;
@@ -306,7 +307,9 @@ internal Task ReadPayloadStartAsync(
contextUriAnnotationValue,
payloadKind,
this.MessageReaderSettings.ClientCustomTypeResolver,
- this.JsonLightInputContext.ReadingResponse);
+ this.JsonLightInputContext.ReadingResponse,
+ this.JsonLightInputContext.MessageReaderSettings.ThrowIfTypeConflictsWithMetadata,
+ this.MessageReaderSettings.RequestUri);
}
#if DEBUG
diff --git a/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs b/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs
index b168c674ad..9bee7c0829 100644
--- a/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs
+++ b/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs
@@ -197,6 +197,11 @@ public ODataMessageQuotas MessageQuotas
///
internal bool ThrowIfTypeConflictsWithMetadata { get; private set; }
+ ///
+ /// Gets or sets current request Uri.
+ ///
+ public Uri RequestUri { get; set; }
+
///
/// Returns whether ThrowOnUndeclaredPropertyForNonOpenType validation setting is enabled.
///
@@ -262,6 +267,7 @@ private void CopyFrom(ODataMessageReaderSettings other)
this.validations = other.validations;
this.ThrowOnDuplicatePropertyNames = other.ThrowOnDuplicatePropertyNames;
this.ThrowIfTypeConflictsWithMetadata = other.ThrowIfTypeConflictsWithMetadata;
+ this.RequestUri = other.RequestUri;
this.ThrowOnUndeclaredPropertyForNonOpenType = other.ThrowOnUndeclaredPropertyForNonOpenType;
this.LibraryCompatibility = other.LibraryCompatibility;
this.Version = other.Version;
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Build.NetFramework/Microsoft.OData.Core.Tests.csproj b/test/FunctionalTests/Microsoft.OData.Core.Tests/Build.NetFramework/Microsoft.OData.Core.Tests.csproj
index 4407bd8f10..875ea43e47 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/Build.NetFramework/Microsoft.OData.Core.Tests.csproj
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Build.NetFramework/Microsoft.OData.Core.Tests.csproj
@@ -225,6 +225,7 @@
+
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightContextUriParserTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightContextUriParserTests.cs
index ecf77e5c1b..e146acd332 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightContextUriParserTests.cs
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightContextUriParserTests.cs
@@ -30,9 +30,28 @@ private EdmModel GetModel()
return model;
}
- // TODO: Support relative context uri and resolving other relative uris
[Fact]
- public void ParseRelativeContextUrlShouldThrowException()
+ public void ParseRelativeContextUrlWithBaseUrl()
+ {
+ const bool needParseSegment = true;
+ const bool throwIfMetadataConflict = true;
+
+ var model = new EdmModel();
+ var entityType = new EdmEntityType("Sample", "R");
+ model.AddElement(entityType);
+ string relativeUrl1 = "$metadata#Sample.R";
+ string relativeUrl2 = "/SampleService/$metadata#Sample.R";
+ var parseResult = ODataJsonLightContextUriParser.Parse(model, relativeUrl1, ODataPayloadKind.Unsupported, null, needParseSegment,
+ throwIfMetadataConflict, new Uri("http://service/SampleService/EntitySet"));
+ parseResult.ContextUri.OriginalString.Should().Be("http://service/SampleService/$metadata#Sample.R");
+
+ parseResult = ODataJsonLightContextUriParser.Parse(model, relativeUrl2, ODataPayloadKind.Unsupported, null, needParseSegment,
+ throwIfMetadataConflict, new Uri("http://service/SampleService/EntitySet"));
+ parseResult.ContextUri.OriginalString.Should().Be("http://service/SampleService/$metadata#Sample.R");
+ }
+
+ [Fact]
+ public void ParseRelativeContextUrlWithoutBaseUriShouldThrowException()
{
string relativeUrl = "$metadata#R";
Action parseContextUri = () => ODataJsonLightContextUriParser.Parse(new EdmModel(), relativeUrl, ODataPayloadKind.Unsupported, null, true);
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Reader/JsonLight/RelativeUriReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Reader/JsonLight/RelativeUriReaderTests.cs
new file mode 100644
index 0000000000..e5a5568602
--- /dev/null
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Reader/JsonLight/RelativeUriReaderTests.cs
@@ -0,0 +1,588 @@
+//---------------------------------------------------------------------
+//
+// 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.IO;
+using System.Linq;
+using System.Text;
+using Microsoft.OData.UriParser;
+using Microsoft.OData.Edm;
+using Microsoft.OData.Tests;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.OData.Core.Tests.ScenarioTests.Reader.JsonLight
+{
+ public class RelativeUriReaderTests
+ {
+ private readonly ITestOutputHelper output;
+
+ private const string NS = "SampleService";
+ private const string ServiceRoot = "http://www.example.com/SampleService/";
+ private static readonly Uri ServiceDocumentUri = new Uri(ServiceRoot);
+
+ #region variable
+
+ private EdmModel model;
+ private EdmEntityType et1;
+ private EdmEntityType derivedET1;
+ private EdmComplexType ct1;
+ private EdmComplexType derivedCT1;
+ private EdmEntityType et2;
+ private EdmEntityType et3;
+
+ private EdmEntitySet et1Set;
+ private EdmEntitySet et2Set;
+ private EdmSingleton et2Singleton;
+
+ private EdmNavigationProperty et1CNav1;
+ private EdmNavigationProperty et1CNav2;
+
+ #endregion variable
+
+ public RelativeUriReaderTests(ITestOutputHelper output)
+ {
+ this.output = output;
+
+ #region GenerateModel
+
+ model = new EdmModel();
+
+ ct1 = new EdmComplexType(NS, "CT1");
+ ct1.AddStructuralProperty("CT1P1", EdmCoreModel.Instance.GetString(false));
+ model.AddElement(ct1);
+
+ derivedCT1 = new EdmComplexType(NS, "DerivedCT1", ct1);
+ derivedCT1.AddStructuralProperty("DerivedCT1P1", EdmCoreModel.Instance.GetInt32(true));
+ model.AddElement(derivedCT1);
+
+ et1 = new EdmEntityType(NS, "ET1");
+ var et1key = new EdmStructuralProperty(et1, "ET1Key", EdmCoreModel.Instance.GetInt32(false));
+ et1.AddProperty(et1key);
+ et1.AddKeys(et1key);
+ et1.AddStructuralProperty("ET1P1", EdmCoreModel.Instance.GetString(false));
+ model.AddElement(et1);
+
+ derivedET1 = new EdmEntityType(NS, "DerivedET1", et1);
+ derivedET1.AddStructuralProperty("DerivedET1P1", EdmCoreModel.Instance.GetInt32(true));
+ model.AddElement(derivedET1);
+
+ et2 = new EdmEntityType(NS, "ET2");
+ var et2key = new EdmStructuralProperty(et2, "ET2Key", EdmCoreModel.Instance.GetInt32(false));
+ et2.AddProperty(et2key);
+ et2.AddKeys(et2key);
+ et2.AddStructuralProperty("ET2P1", EdmCoreModel.Instance.GetString(false));
+ model.AddElement(et2);
+
+ et3 = new EdmEntityType(NS, "ET3");
+ var et3key = new EdmStructuralProperty(et3, "ET3Key", EdmCoreModel.Instance.GetInt32(false));
+ et3.AddProperty(et3key);
+ et3.AddKeys(et3key);
+ et3.AddStructuralProperty("ET3P1", EdmCoreModel.Instance.GetString(false));
+ model.AddElement(et3);
+
+ var container = new EdmEntityContainer(NS, "SampleService");
+ this.model.AddElement(container);
+
+ et1Set = new EdmEntitySet(container, "ET1Set", et1);
+ container.AddElement(et1Set);
+
+ et2Set = new EdmEntitySet(container, "ET2Set", et2);
+ container.AddElement(et2Set);
+
+ et2Singleton = new EdmSingleton(container, "ET2Singleton", et2);
+ container.AddElement(et2Singleton);
+
+ var et1Nav1Info = new EdmNavigationPropertyInfo()
+ {
+ Name = "ET1Nav1",
+ ContainsTarget = false,
+ Target = et2,
+ TargetMultiplicity = Edm.EdmMultiplicity.Many
+ };
+
+ var et1Nav1 = et1.AddUnidirectionalNavigation(et1Nav1Info);
+ et1Set.AddNavigationTarget(et1Nav1, et2Set);
+
+ var et1Nav2Info = new EdmNavigationPropertyInfo()
+ {
+ Name = "ET1Nav2",
+ ContainsTarget = false,
+ Target = et2,
+ TargetMultiplicity = Edm.EdmMultiplicity.One
+ };
+
+ var et1Nav2 = et1.AddUnidirectionalNavigation(et1Nav2Info);
+ et1Set.AddNavigationTarget(et1Nav2, et2Singleton);
+
+ var et1CNav1Info = new EdmNavigationPropertyInfo()
+ {
+ Name = "ET1CNav1",
+ ContainsTarget = true,
+ Target = et3,
+ TargetMultiplicity = Edm.EdmMultiplicity.Many
+ };
+
+ et1CNav1 = et1.AddUnidirectionalNavigation(et1CNav1Info);
+
+ var et1CNav2Info = new EdmNavigationPropertyInfo()
+ {
+ Name = "ET1CNav2",
+ ContainsTarget = true,
+ Target = et3,
+ TargetMultiplicity = Edm.EdmMultiplicity.One
+ };
+
+ et1CNav2 = et1.AddUnidirectionalNavigation(et1CNav2Info);
+
+ #endregion
+ }
+
+ [Theory]
+ [InlineData(true, false)]
+ [InlineData(false, false)]
+ [InlineData(true, true)]
+ [InlineData(false, true)]
+ public void RelativeContextUri_ServiceDocument_RelativeUrlForEntitySet(bool startWithSlash, bool relativeUriForEntitySet)
+ {
+ string contentType;
+
+ var payload = this.WritePayload(this.model, omWriter =>
+ {
+ ODataServiceDocument serviceDocument = new ODataServiceDocument()
+ {
+ EntitySets = new[] { new ODataEntitySetInfo { Name = "ET1Set", Url = new Uri(ServiceDocumentUri, "ET1Set") } }
+ };
+ omWriter.WriteServiceDocument(serviceDocument);
+ }, null, out contentType);
+
+ // relativeUriForEntitySet == true means that the url for entity set in payload will also be changed to relative uri
+ string FixReplacementFormat = relativeUriForEntitySet ? "{0}" : "@odata.context\":\"{0}";
+ payload = startWithSlash
+ ? payload.Replace(string.Format(FixReplacementFormat, ServiceRoot), string.Format(FixReplacementFormat, "/SampleService/"))
+ : payload.Replace(string.Format(FixReplacementFormat, ServiceRoot), string.Format(FixReplacementFormat, ""));
+
+ output.WriteLine(payload);
+
+ ODataServiceDocument serviceDocumentResult = null;
+ this.ReadPayload(payload, contentType, model, omReader => serviceDocumentResult = omReader.ReadServiceDocument(), ServiceDocumentUri);
+
+ var entitySets = serviceDocumentResult.EntitySets.ToList();
+ Assert.Equal(1, entitySets.Count);
+ Assert.Equal(new Uri(ServiceDocumentUri, "ET1Set"), entitySets[0].Url);
+ }
+
+ // Test ReadPayloadStart with request Uri and relative Uri
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void RelativeContextUri_Feed(bool startWithSlash)
+ {
+ string contentType;
+
+ var payload = this.WritePayload(this.model, omWriter =>
+ {
+ var odataWriter = omWriter.CreateODataResourceSetWriter(et1Set, et1);
+
+ ODataResourceSet feed = new ODataResourceSet();
+ ODataResource resource = CreateResource1();
+
+ odataWriter.WriteStart(feed);
+ odataWriter.WriteStart(resource);
+ odataWriter.WriteEnd();
+ odataWriter.WriteEnd();
+
+ }, null, out contentType);
+
+ string FixReplacementFormat = "@odata.context\":\"{0}";
+ payload = startWithSlash
+ ? payload.Replace(string.Format(FixReplacementFormat, ServiceRoot), string.Format(FixReplacementFormat, "/SampleService/"))
+ : payload.Replace(string.Format(FixReplacementFormat, ServiceRoot), string.Format(FixReplacementFormat, ""));
+
+ output.WriteLine(payload);
+
+ this.ReadPayload(payload, contentType, model, omReader =>
+ {
+ var odataReader = omReader.CreateODataResourceSetReader();
+ while (odataReader.Read())
+ {
+ if (odataReader.State == ODataReaderState.ResourceSetEnd)
+ {
+ Assert.NotNull(odataReader.Item as ODataResourceSet);
+ }
+ }
+ }, new Uri(ServiceDocumentUri, et1Set.Name));
+ }
+
+ // Test ReadPayloadStart with request Uri and relative Uri
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void RelativeContextUri_Entry_Expand(bool startWithSlash)
+ {
+ var payload = @"
+{
+ ""@odata.context"":""http://www.example.com/SampleService/$metadata#ET1Set/$entity"",
+ ""ET1Key"":1,
+ ""ET1P1"":""P1"",
+ ""ET1Nav1"":
+ [
+ {
+ ""@odata.context"":""http://www.example.com/SampleService/$metadata#ET1Set(1)/ET1Nav1/$entity"",
+ ""ET2Key"":1,
+ ""ET2P1"":""P1""
+ }
+ ]
+}";
+
+ string FixReplacementFormat = "@odata.context\":\"{0}";
+ payload = startWithSlash
+ ? payload.Replace(string.Format(FixReplacementFormat, ServiceRoot), string.Format(FixReplacementFormat, "/SampleService/"))
+ : payload.Replace(string.Format(FixReplacementFormat, ServiceRoot), string.Format(FixReplacementFormat, ""));
+
+ output.WriteLine(payload);
+
+ this.ReadPayload(payload, "application/json", model, omReader =>
+ {
+ var odataReader = omReader.CreateODataResourceReader();
+ while (odataReader.Read())
+ {
+ if (odataReader.State == ODataReaderState.ResourceEnd)
+ {
+ Assert.NotNull(odataReader.Item as ODataResource);
+ }
+ }
+ }, new Uri(ServiceDocumentUri, "ET1Set(1)?$expand=ET1Nav1"));
+ }
+
+ // Test StartNavigationLink with request Uri
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void RelativeUri_ContainedNavigation(bool startWithSlash)
+ {
+ string contentType = "application/json";
+
+ Uri requestUri = new Uri(ServiceDocumentUri, et1Set.Name + "(1)");
+
+ var payload = this.WritePayload(this.model, omWriter =>
+ {
+ var odataWriter = omWriter.CreateODataResourceWriter(et1Set, et1);
+
+ ODataResource et1Entry = CreateResource1();
+ ODataNestedResourceInfo navigationLink = new ODataNestedResourceInfo()
+ {
+ IsCollection = true,
+ Name = "ET1CNav1",
+ };
+
+ ODataResourceSet feed = new ODataResourceSet();
+ ODataResource et3Entry = CreateResource3();
+
+ odataWriter.WriteStart(et1Entry);
+ odataWriter.WriteStart(navigationLink);
+ odataWriter.WriteStart(feed);
+ odataWriter.WriteStart(et3Entry);
+ odataWriter.WriteEnd(); // end et3Entry
+ odataWriter.WriteEnd(); // end feed
+ odataWriter.WriteEnd(); // end navigationLink
+ odataWriter.WriteEnd(); // end et1Entry
+
+ }, requestUri, out contentType);
+
+ string FixReplacementFormat = "@odata.context\":\"{0}";
+ payload = startWithSlash
+ ? payload.Replace(string.Format(FixReplacementFormat, ServiceRoot), string.Format(FixReplacementFormat, "/SampleService/"))
+ : payload.Replace(string.Format(FixReplacementFormat, ServiceRoot), string.Format(FixReplacementFormat, ""));
+
+ output.WriteLine(payload);
+
+ this.ReadPayload(payload, contentType, model, omReader =>
+ {
+ var odataReader = omReader.CreateODataResourceReader();
+ while (odataReader.Read())
+ {
+ if (odataReader.State == ODataReaderState.NestedResourceInfoEnd)
+ {
+ var navLink = (odataReader.Item as ODataNestedResourceInfo);
+ if (navLink != null && navLink.Name.Equals("ET1CNav1"))
+ {
+ Assert.Equal(new Uri(requestUri, "ET1Set(1)/ET1CNav1").OriginalString, navLink.Url.OriginalString);
+ }
+ }
+ }
+ }, requestUri);
+ }
+
+ // Test StartNavigationLink with request Uri and single navigation link.
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void RelativeUri_ContainedNavigation_SingleNav(bool startWithSlash)
+ {
+ string contentType;
+
+ Uri requestUri = new Uri(ServiceDocumentUri, et1Set.Name + "(1)");
+
+ var payload = this.WritePayload(this.model, omWriter =>
+ {
+ var odataWriter = omWriter.CreateODataResourceWriter(et1Set, et1);
+
+ ODataResource entry = CreateResource1();
+
+ ODataNestedResourceInfo navigationLink = new ODataNestedResourceInfo()
+ {
+ IsCollection = false,
+ Name = "ET1CNav2",
+ };
+
+ ODataResource et3Entry = CreateResource3();
+
+ odataWriter.WriteStart(entry);
+ odataWriter.WriteStart(navigationLink);
+ odataWriter.WriteStart(et3Entry);
+ odataWriter.WriteEnd(); // end et3Entry
+ odataWriter.WriteEnd(); // end navigationLink
+ odataWriter.WriteEnd(); // end entry
+
+ }, requestUri, out contentType);
+
+ string FixReplacementFormat = "@odata.context\":\"{0}";
+ payload = startWithSlash
+ ? payload.Replace(string.Format(FixReplacementFormat, ServiceRoot), string.Format(FixReplacementFormat, "/SampleService/"))
+ : payload.Replace(string.Format(FixReplacementFormat, ServiceRoot), string.Format(FixReplacementFormat, ""));
+
+ output.WriteLine(payload);
+
+ this.ReadPayload(payload, contentType, model, omReader =>
+ {
+ var odataReader = omReader.CreateODataResourceReader();
+ while (odataReader.Read())
+ {
+ if (odataReader.State == ODataReaderState.NestedResourceInfoEnd)
+ {
+ var navLink = (odataReader.Item as ODataNestedResourceInfo);
+ if (navLink != null && navLink.Name.Equals("ET1CNav2"))
+ {
+ Assert.Equal(new Uri(requestUri, "ET1Set(1)/ET1CNav2").OriginalString,
+ navLink.Url.OriginalString);
+ }
+ }
+ }
+ }, requestUri);
+ }
+
+ // Test ReadDeltaStart with request Uri and relative Context Uri
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void RelativeContextUri_Delta_ContainedNavigation(bool startWithSlash)
+ {
+ string contentType;
+
+ Uri requestUri = new Uri(ServiceDocumentUri, et1Set.Name + "(1)");
+
+ var payload = this.WritePayload(this.model, omWriter =>
+ {
+ var deltaWriter = omWriter.CreateODataDeltaWriter(et1Set, et1);
+
+ var deltaFeed = new ODataDeltaResourceSet();
+ ODataResource deltaEntry = CreateResource1(new Uri(ServiceDocumentUri, "ET1Set(1)"));
+
+ var deletedEntry = new ODataDeltaDeletedEntry(
+ new Uri(ServiceDocumentUri, "ET1Set(2)/ET1CNav1(1)").AbsoluteUri,
+ DeltaDeletedEntryReason.Deleted);
+ deletedEntry.SetSerializationInfo(new ODataDeltaSerializationInfo()
+ {
+ NavigationSourceName = "ET1Set(2)/ET1CNav1"
+ });
+
+ var deletedLink = new ODataDeltaDeletedLink(
+ new Uri(ServiceDocumentUri, "ET1Set(2))"),
+ new Uri(ServiceDocumentUri, "ET1Set(2)/ET1CNav1(1)"),
+ "ET1CNav1");
+
+ ODataNestedResourceInfo navigationLink = new ODataNestedResourceInfo()
+ {
+ IsCollection = false,
+ Name = "ET1CNav1",
+ };
+
+ ODataResource et3Entry = CreateResource3(new Uri(ServiceDocumentUri, "ET1Set(2)/ET1CNav1(1)"));
+
+ var addedLink = new ODataDeltaLink(
+ new Uri(ServiceDocumentUri, "ET1Set(2)"),
+ new Uri(ServiceDocumentUri, "ET1Set(2)/ET1CNav1(1)"),
+ "ET1CNav1");
+
+ et3Entry.SetSerializationInfo(new ODataResourceSerializationInfo
+ {
+ NavigationSourceEntityTypeName = NS + "." + et3.Name,
+ NavigationSourceKind = EdmNavigationSourceKind.ContainedEntitySet,
+ NavigationSourceName = "ET1Set(2)/ET1CNav1"
+ });
+
+ ODataNestedResourceInfo nav2 = new ODataNestedResourceInfo()
+ {
+ IsCollection = true,
+ Name = "ET1Nav1",
+ };
+
+ ODataResourceSet nav2Feed = new ODataResourceSet();
+ ODataResource et2Entry = CreateResource2(new Uri(ServiceDocumentUri, "ET1Set(2)/ET1Nav1(1)"));
+
+ deltaWriter.WriteStart(deltaFeed);
+ deltaWriter.WriteStart(deltaEntry);
+
+ deltaWriter.WriteStart(nav2);
+ deltaWriter.WriteStart(nav2Feed);
+ deltaWriter.WriteStart(et2Entry);
+ deltaWriter.WriteEnd();
+ deltaWriter.WriteEnd();
+ deltaWriter.WriteEnd();
+
+ deltaWriter.WriteEnd();
+ deltaWriter.WriteDeltaDeletedEntry(deletedEntry);
+ deltaWriter.WriteDeltaDeletedLink(deletedLink);
+ deltaWriter.WriteStart(et3Entry);
+ deltaWriter.WriteEnd();
+ deltaWriter.WriteDeltaLink(addedLink);
+
+ deltaWriter.WriteEnd();
+
+ }, requestUri, out contentType);
+
+ string FixReplacementFormat = "@odata.context\":\"{0}";
+ payload = startWithSlash
+ ? payload.Replace(string.Format(FixReplacementFormat, ServiceRoot), string.Format(FixReplacementFormat, "/SampleService/"))
+ : payload.Replace(string.Format(FixReplacementFormat, ServiceRoot), string.Format(FixReplacementFormat, ""));
+
+ output.WriteLine(payload);
+
+ this.ReadPayload(payload, "application/json", model, omReader =>
+ {
+ var deltaReader = omReader.CreateODataDeltaReader(et1Set, et1);
+ while (deltaReader.Read()) ;
+ }, requestUri);
+ }
+
+ private string WritePayload(EdmModel edmModel, Action write, Uri requestUri, out string contentType)
+ {
+ var message = new InMemoryMessage() { Stream = new MemoryStream() };
+
+ var writerSettings = new ODataMessageWriterSettings
+ {
+ EnableMessageStreamDisposal = false
+ };
+
+ writerSettings.SetServiceDocumentUri(ServiceDocumentUri);
+ writerSettings.SetContentType(ODataFormat.Json);
+
+ if (requestUri != null)
+ {
+ ODataUriParser odataUriParser = new ODataUriParser(edmModel, ServiceDocumentUri, requestUri);
+ writerSettings.ODataUri = new ODataUri()
+ {
+ ServiceRoot = ServiceDocumentUri,
+ Path = odataUriParser.ParsePath()
+ };
+ }
+
+ using (var msgWriter = new ODataMessageWriter((IODataResponseMessage)message, writerSettings, edmModel))
+ {
+ write(msgWriter);
+ }
+
+ message.Stream.Seek(0, SeekOrigin.Begin);
+ using (StreamReader reader = new StreamReader(message.Stream))
+ {
+ contentType = message.GetHeader("Content-Type");
+ return reader.ReadToEnd();
+ }
+ }
+
+ private void ReadPayload(string payload, string contentType, EdmModel edmModel, Action test, Uri requestUri)
+ {
+ var message = new InMemoryMessage() { Stream = new MemoryStream(Encoding.UTF8.GetBytes(payload)) };
+ message.SetHeader("Content-Type", contentType);
+
+ ODataMessageReaderSettings readerSettings = new ODataMessageReaderSettings()
+ {
+ BaseUri = new Uri(ServiceDocumentUri, "$metadata"),
+ RequestUri = requestUri
+ };
+
+ using (var msgReader = new ODataMessageReader((IODataResponseMessage)message, readerSettings, edmModel))
+ {
+ test(msgReader);
+ }
+ }
+
+ private ODataResource CreateResource1(Uri id = null)
+ {
+ return new ODataResource()
+ {
+ Id = id,
+ Properties = new List
+ {
+ new ODataProperty()
+ {
+ Name = "ET1Key",
+ Value = 1,
+ },
+ new ODataProperty()
+ {
+ Name = "ET1P1",
+ Value = "P1",
+ }
+ }
+ };
+ }
+
+ private ODataResource CreateResource2(Uri id = null)
+ {
+ return new ODataResource()
+ {
+ Id = id,
+ Properties = new List
+ {
+ new ODataProperty()
+ {
+ Name = "ET2Key",
+ Value = 1,
+ },
+ new ODataProperty()
+ {
+ Name = "ET2P1",
+ Value = "P1",
+ }
+ }
+ };
+ }
+
+ private ODataResource CreateResource3(Uri id = null)
+ {
+ return new ODataResource()
+ {
+ Id = id,
+ Properties = new List
+ {
+ new ODataProperty()
+ {
+ Name = "ET3Key",
+ Value = 1,
+ },
+ new ODataProperty()
+ {
+ Name = "ET3P1",
+ Value = "P1",
+ }
+ }
+ };
+ }
+ }
+}
\ No newline at end of file