Skip to content

Commit

Permalink
Issue OData#271 Allow relative URIs in OData batch operations
Browse files Browse the repository at this point in the history
  • Loading branch information
TomDu committed May 10, 2016
1 parent 855fe94 commit f7026f2
Show file tree
Hide file tree
Showing 10 changed files with 409 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1572,6 +1572,9 @@
<Compile Include="..\Json\IJsonWriterFactory.cs">
<Link>Microsoft\OData\Core\Json\IJsonWriterFactory.cs</Link>
</Compile>
<Compile Include="..\ODataBatchPayloadUriOptions.cs">
<Link>Microsoft\OData\Core\ODataBatchPayloadUriOptions.cs</Link>
</Compile>
<Compile Include="..\ODataEdmPropertyAnnotation.cs">
<Link>Microsoft\OData\Core\ODataEdmPropertyAnnotation.cs</Link>
</Compile>
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.OData.Core/Microsoft.OData.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<Compile Include="IContainerBuilder.cs" />
<Compile Include="Metadata\ODataAtomErrorDeserializer.cs" />
<Compile Include="Metadata\ODataMetadataConstants.cs" />
<Compile Include="ODataBatchPayloadUriOptions.cs" />
<Compile Include="ODataEdmPropertyAnnotation.cs" />
<Compile Include="ODataNullValueBehaviorKind.cs" />
<Compile Include="ODataRawValueUtils.cs" />
Expand Down
36 changes: 36 additions & 0 deletions src/Microsoft.OData.Core/ODataBatchPayloadUriOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//---------------------------------------------------------------------
// <copyright file="ODataBatchPayloadUriOptions.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

namespace Microsoft.OData
{
/// <summary>
/// Indicates the format of Request-URI in each sub request in the batch operation.
/// </summary>
public enum BatchPayloadUriOption
{
/// <summary>
/// Absolute URI with schema, host, port, and absolute resource path.
/// </summary>
/// Example:
/// GET https://host:1234/path/service/People(1) HTTP/1.1
AbsoluteUri,

/// <summary>
/// Absolute resource path and separate Host header.
/// </summary>
/// Example:
/// GET /path/service/People(1) HTTP/1.1
/// Host: myserver.mydomain.org:1234
AbsoluteResourcePathAndHost,

/// <summary>
/// Resource path relative to the batch request URI.
/// </summary>
/// Example:
/// GET People(1) HTTP/1.1
RelativeResourcePath
}
}
33 changes: 29 additions & 4 deletions src/Microsoft.OData.Core/ODataBatchWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,20 @@ public Task WriteEndChangesetAsync()
/// <param name="uri">The Uri to be used for this request operation.</param>
/// <param name="contentId">The Content-ID value to write in ChangeSet head, would be ignored if <paramref name="method"/> is "GET".</param>
public ODataBatchOperationRequestMessage CreateOperationRequestMessage(string method, Uri uri, string contentId)
{
return CreateOperationRequestMessage(method, uri, contentId, BatchPayloadUriOption.AbsoluteUri);
}

/// <summary>Creates an <see cref="T:Microsoft.OData.ODataBatchOperationRequestMessage" /> for writing an operation of a batch request.</summary>
/// <returns>The message that can be used to write the request operation.</returns>
/// <param name="method">The Http method to be used for this request operation.</param>
/// <param name="uri">The Uri to be used for this request operation.</param>
/// <param name="contentId">The Content-ID value to write in ChangeSet head, would be ignored if <paramref name="method"/> is "GET".</param>
/// <param name="payloadUriOption">The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath.</param>
public ODataBatchOperationRequestMessage CreateOperationRequestMessage(string method, Uri uri, string contentId, BatchPayloadUriOption payloadUriOption)
{
this.VerifyCanCreateOperationRequestMessage(true, method, uri, contentId);
return this.CreateOperationRequestMessageImplementation(method, uri, contentId);
return this.CreateOperationRequestMessageImplementation(method, uri, contentId, payloadUriOption);
}

#if PORTABLELIB
Expand All @@ -284,10 +295,21 @@ public ODataBatchOperationRequestMessage CreateOperationRequestMessage(string me
/// <param name="uri">The URI to be used for this request operation.</param>
/// <param name="contentId">The Content-ID value to write in ChangeSet head, would be ignored if <paramref name="method"/> is "GET".</param>
public Task<ODataBatchOperationRequestMessage> CreateOperationRequestMessageAsync(string method, Uri uri, string contentId)
{
return CreateOperationRequestMessageAsync(method, uri, contentId, BatchPayloadUriOption.AbsoluteUri);
}

/// <summary>Creates a message for asynchronously writing an operation of a batch request.</summary>
/// <returns>The message that can be used to asynchronously write the request operation.</returns>
/// <param name="method">The HTTP method to be used for this request operation.</param>
/// <param name="uri">The URI to be used for this request operation.</param>
/// <param name="contentId">The Content-ID value to write in ChangeSet head, would be ignored if <paramref name="method"/> is "GET".</param>
/// <param name="payloadUriOption">The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath.</param>
public Task<ODataBatchOperationRequestMessage> CreateOperationRequestMessageAsync(string method, Uri uri, string contentId, BatchPayloadUriOption payloadUriOption)
{
this.VerifyCanCreateOperationRequestMessage(false, method, uri, contentId);
return TaskUtils.GetTaskForSynchronousOperation<ODataBatchOperationRequestMessage>(
() => this.CreateOperationRequestMessageImplementation(method, uri, contentId));
() => this.CreateOperationRequestMessageImplementation(method, uri, contentId, payloadUriOption));
}
#endif

Expand Down Expand Up @@ -580,8 +602,9 @@ private void VerifyCanCreateOperationRequestMessage(bool synchronousCall, string
/// <param name="method">The Http method to be used for this request operation.</param>
/// <param name="uri">The Uri to be used for this request operation.</param>
/// <param name="contentId">The Content-ID value to write in ChangeSet head.</param>
/// <param name="payloadUriOption">The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath.</param>
/// <returns>The message that can be used to write the request operation.</returns>
private ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation(string method, Uri uri, string contentId)
private ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation(string method, Uri uri, string contentId, BatchPayloadUriOption payloadUriOption)
{
if (this.changeSetBoundary == null)
{
Expand Down Expand Up @@ -626,7 +649,9 @@ private ODataBatchOperationRequestMessage CreateOperationRequestMessageImplement
this.WriteStartBoundaryForOperation();

// write the headers and request line
ODataBatchWriterUtils.WriteRequestPreamble(this.rawOutputContext.TextWriter, method, uri, changeSetBoundary != null, contentId);
ODataBatchWriterUtils.WriteRequestPreamble(this.rawOutputContext.TextWriter, method, uri,
this.rawOutputContext.MessageWriterSettings.PayloadBaseUri, changeSetBoundary != null, contentId,
payloadUriOption);

return this.CurrentOperationRequestMessage;
}
Expand Down
87 changes: 72 additions & 15 deletions src/Microsoft.OData.Core/ODataBatchWriterUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,31 +101,36 @@ internal static void WriteEndBoundary(TextWriter writer, string boundary, bool m
}

/// <summary>
/// Writes the headers, (optional) Content-ID and the request line
/// Writes the headers, (optional) Content-ID and the request line.
/// </summary>
/// <param name="writer">Writer to write to.</param>
/// <param name="httpMethod">The Http method to be used for this request operation.</param>
/// <param name="uri">The Uri to be used for this request operation.</param>
/// <param name="baseUri">The service root Uri to be used for this request operation.</param>
/// <param name="inChangeSetBound">Whether we are in ChangeSetBound.</param>
/// <param name="contentId">The Content-ID value to write in ChangeSet head.</param>
internal static void WriteRequestPreamble(TextWriter writer, string httpMethod, Uri uri, bool inChangeSetBound, string contentId)
/// <param name="payloadUriOption">The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath.</param>
internal static void WriteRequestPreamble(
TextWriter writer,
string httpMethod,
Uri uri,
Uri baseUri,
bool inChangeSetBound,
string contentId,
BatchPayloadUriOption payloadUriOption)
{
Debug.Assert(writer != null, "writer != null");
Debug.Assert(uri != null, "uri != null");
Debug.Assert(uri.IsAbsoluteUri || UriUtils.UriToString(uri).StartsWith("$", StringComparison.Ordinal), "uri.IsAbsoluteUri || uri.OriginalString.StartsWith(\"$\")");

// write the headers
writer.WriteLine("{0}: {1}", ODataConstants.ContentTypeHeader, MimeConstants.MimeApplicationHttp);
writer.WriteLine("{0}: {1}", ODataConstants.ContentTransferEncoding, ODataConstants.BatchContentTransferEncoding);
if (inChangeSetBound && contentId != null)
{
writer.WriteLine("{0}: {1}", ODataConstants.ContentIdHeader, contentId);
}
WriteHeaders(writer, inChangeSetBound, contentId);

// write separator line between headers and the request line
writer.WriteLine();

writer.WriteLine("{0} {1} {2}", httpMethod, UriUtils.UriToString(uri), ODataConstants.HttpVersionInBatching);
// write request line
WriteRequestUri(writer, httpMethod, uri, baseUri, payloadUriOption);
}

/// <summary>
Expand All @@ -139,12 +144,7 @@ internal static void WriteResponsePreamble(TextWriter writer, bool inChangeSetBo
Debug.Assert(writer != null, "writer != null");

// write the headers
writer.WriteLine("{0}: {1}", ODataConstants.ContentTypeHeader, MimeConstants.MimeApplicationHttp);
writer.WriteLine("{0}: {1}", ODataConstants.ContentTransferEncoding, ODataConstants.BatchContentTransferEncoding);
if (inChangeSetBound && contentId != null)
{
writer.WriteLine("{0}: {1}", ODataConstants.ContentIdHeader, contentId);
}
WriteHeaders(writer, inChangeSetBound, contentId);

// write separator line between headers and the response line
writer.WriteLine();
Expand All @@ -165,5 +165,62 @@ internal static void WriteChangeSetPreamble(TextWriter writer, string changeSetB
// write separator line between headers and first change set operation
writer.WriteLine();
}

/// <summary>
/// Writes the headers.
/// </summary>
/// <param name="writer">Writer to write headers.</param>
/// <param name="inChangeSetBound">Whether we are in ChangeSetBound.</param>
/// <param name="contentId">The Content-ID value to write in ChangeSet head.</param>
private static void WriteHeaders(TextWriter writer, bool inChangeSetBound, string contentId)
{
writer.WriteLine("{0}: {1}", ODataConstants.ContentTypeHeader, MimeConstants.MimeApplicationHttp);
writer.WriteLine("{0}: {1}", ODataConstants.ContentTransferEncoding, ODataConstants.BatchContentTransferEncoding);
if (inChangeSetBound && contentId != null)
{
writer.WriteLine("{0}: {1}", ODataConstants.ContentIdHeader, contentId);
}
}

/// <summary>
/// Writes the request line.
/// </summary>
/// <param name="writer">Writer to write request uri.</param>
/// <param name="httpMethod">The Http method to be used for this request operation.</param>
/// <param name="uri">The Uri to be used for this request operation.</param>
/// <param name="baseUri">The service root Uri to be used for this request operation.</param>
/// <param name="payloadUriOption">The format of operation Request-URI, which could be AbsoluteUri, AbsoluteResourcePathAndHost, or RelativeResourcePath.</param>
private static void WriteRequestUri(TextWriter writer, string httpMethod, Uri uri, Uri baseUri, BatchPayloadUriOption payloadUriOption)
{
if (uri.IsAbsoluteUri)
{
string absoluteUriString = uri.AbsoluteUri;

switch (payloadUriOption)
{
case BatchPayloadUriOption.AbsoluteUri:
writer.WriteLine("{0} {1} {2}", httpMethod, UriUtils.UriToString(uri), ODataConstants.HttpVersionInBatching);
break;

case BatchPayloadUriOption.AbsoluteResourcePathAndHost:
string absoluteResourcePath = absoluteUriString.Substring(absoluteUriString.IndexOf('/', absoluteUriString.IndexOf("//", StringComparison.Ordinal) + 2));
writer.WriteLine("{0} {1} {2}", httpMethod, absoluteResourcePath, ODataConstants.HttpVersionInBatching);
writer.WriteLine("Host: {0}:{1}", uri.Host, uri.Port);
break;

case BatchPayloadUriOption.RelativeResourcePath:
Debug.Assert(baseUri != null, "baseUri != null");
string baseUriString = UriUtils.UriToString(baseUri);
Debug.Assert(absoluteUriString.StartsWith(baseUriString), "absoluteUriString.StartsWith(baseUriString)");
string relativeResourcePath = absoluteUriString.Substring(baseUriString.Length);
writer.WriteLine("{0} {1} {2}", httpMethod, relativeResourcePath, ODataConstants.HttpVersionInBatching);
break;
}
}
else
{
writer.WriteLine("{0} {1} {2}", httpMethod, UriUtils.UriToString(uri), ODataConstants.HttpVersionInBatching);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public override void Process(IODataRequestMessage requestMessage, IODataResponse
var batchWriter = batchRequestMessageWriter.CreateODataBatchWriter();
batchWriter.WriteStartBatch();

using (var batchRequestMessageReader = this.CreateMessageReader(requestMessage))
using (var batchRequestMessageReader = this.CreateBatchMessageReader(requestMessage))
{
var batchReader = batchRequestMessageReader.CreateODataBatchReader();

Expand Down Expand Up @@ -79,5 +79,17 @@ public override void Process(IODataRequestMessage requestMessage, IODataResponse
}

}

private ODataMessageReader CreateBatchMessageReader(IODataRequestMessage message)
{
return new ODataMessageReader(
message,
new ODataMessageReaderSettings
{
BaseUri = ServiceConstants.ServiceBaseUri
},
this.DataSource.Model);
}

}
}
Loading

0 comments on commit f7026f2

Please sign in to comment.