/// <summary> /// Asynchronously creates an <see cref="ODataBatchOperationResponseMessage"/> for writing an operation of a batch response - implementation of the actual functionality. /// </summary> /// <param name="contentId">The Content-ID value to write in ChangeSet head.</param> /// <returns>A task that represents the asynchronous operation. /// The value of the TResult parameter contains an <see cref="ODataBatchOperationResponseMessage"/> /// that can be used to write the response operation.</returns> protected override async Task <ODataBatchOperationResponseMessage> CreateOperationResponseMessageImplementationAsync(string contentId) { await this.WritePendingMessageDataAsync(true) .ConfigureAwait(false); // In responses we don't need to use our batch URL resolver, since there are no cross referencing URLs // so use the URL resolver from the batch message instead. this.CurrentOperationResponseMessage = BuildOperationResponseMessage( this.RawOutputContext.OutputStream, contentId, this.changeSetBoundary); this.SetState(BatchWriterState.OperationCreated); // Write the operation's start boundary string await this.WriteStartBoundaryForOperationAsync() .ConfigureAwait(false); // Write the headers and request separator line await ODataMultipartMixedBatchWriterUtils.WriteResponsePreambleAsync( this.RawOutputContext.TextWriter, changeSetBoundary != null, contentId).ConfigureAwait(false); return(this.CurrentOperationResponseMessage); }
/// <summary> /// Creates an <see cref="ODataBatchOperationRequestMessage"/> for writing an operation of a batch request /// - implementation of the actual functionality. /// </summary> /// <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> /// <param name="dependsOnIds">The prerequisite request ids of this request. By default its value should be null for Multipart/Mixed /// format and the dependsOnIds implicitly derived per the protocol will be used; Otherwise, non-null will be used as override after /// validation.</param> /// <returns>The message that can be used to write the request operation.</returns> protected override ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation( string method, Uri uri, string contentId, BatchPayloadUriOption payloadUriOption, IEnumerable <string> dependsOnIds) { // write pending message data (headers, response line) for a previously unclosed message/request this.WritePendingMessageData(true); // create the new request operation // For Multipart batch format, validate dependsOnIds if it is user explicit input, otherwise skip validation // when it is implicitly derived per protocol. ODataBatchOperationRequestMessage operationRequestMessage = BuildOperationRequestMessage( this.RawOutputContext.OutputStream, method, uri, contentId, this.changeSetBoundary, dependsOnIds); this.SetState(BatchWriterState.OperationCreated); // write the operation's start boundary string this.WriteStartBoundaryForOperation(); if (contentId != null) { this.dependsOnIdsTracker.AddDependsOnId(contentId); } // write the headers and request line ODataMultipartMixedBatchWriterUtils.WriteRequestPreamble(this.RawOutputContext.TextWriter, method, uri, this.RawOutputContext.MessageWriterSettings.BaseUri, changeSetBoundary != null, contentId, payloadUriOption); return(operationRequestMessage); }
/// <summary> /// Asynchronously starts a new changeset - implementation of the actual functionality. /// </summary> /// <param name="changesetId">The value for changeset boundary for multipart batch.</param> /// <returns>A task that represents the asynchronous write operation.</returns> protected override async Task WriteStartChangesetImplementationAsync(string changesetId) { Debug.Assert(changesetId != null, "changesetId != null"); // Write pending message data (headers, response line) for a previously unclosed message/request await this.WritePendingMessageDataAsync(true) .ConfigureAwait(false); this.SetState(BatchWriterState.ChangesetStarted); Debug.Assert(this.changeSetBoundary == null, "this.changeSetBoundary == null"); this.changeSetBoundary = ODataMultipartMixedBatchWriterUtils.CreateChangeSetBoundary(this.RawOutputContext.WritingResponse, changesetId); // Write the boundary string await ODataMultipartMixedBatchWriterUtils.WriteStartBoundaryAsync( this.RawOutputContext.TextWriter, this.batchBoundary, !this.batchStartBoundaryWritten).ConfigureAwait(false); this.batchStartBoundaryWritten = true; // Write the changeset headers await ODataMultipartMixedBatchWriterUtils.WriteChangesetPreambleAsync( this.RawOutputContext.TextWriter, this.changeSetBoundary).ConfigureAwait(false); this.changesetStartBoundaryWritten = false; // Set state to track dependsOnIds. this.dependsOnIdsTracker.ChangeSetStarted(); }
/// <summary> /// Asynchronously ends an active changeset - implementation of the actual functionality. /// </summary> /// <returns>A task that represents the asynchronous write operation.</returns> protected override async Task WriteEndChangesetImplementationAsync() { // Write pending message data (headers, response line) for a previously unclosed message/request await this.WritePendingMessageDataAsync(true) .ConfigureAwait(false); string currentChangesetBoundary = this.changeSetBoundary; // Change the state first so we validate the change set boundary before attempting to write it. this.SetState(BatchWriterState.ChangesetCompleted); this.dependsOnIdsTracker.ChangeSetEnded(); Debug.Assert(this.changeSetBoundary != null, "this.changeSetBoundary != null"); this.changeSetBoundary = null; // In the case of an empty changeset the start changeset boundary has not been written yet // we will leave it like that, since we want the empty changeset to be represented only as // the end changeset boundary. // Due to WCF DS V2 compatibility we must not write the start boundary in this case // otherwise WCF DS V2 won't be able to read it (it fails on the start-end boundary empty changeset). // Write the end boundary for the change set await ODataMultipartMixedBatchWriterUtils.WriteEndBoundaryAsync( this.RawOutputContext.TextWriter, currentChangesetBoundary, !this.changesetStartBoundaryWritten).ConfigureAwait(false); }
/// <summary> /// Constructor. /// </summary> /// <param name="format">The format for this output context.</param> /// <param name="messageInfo">The context information for the message.</param> /// <param name="messageWriterSettings">Configuration settings of the OData writer.</param> internal ODataMultipartMixedBatchOutputContext( ODataFormat format, ODataMessageInfo messageInfo, ODataMessageWriterSettings messageWriterSettings) : base(format, messageInfo, messageWriterSettings) { Debug.Assert(messageInfo.MessageStream != null, "messageInfo.MessageStream != null"); Debug.Assert(messageInfo.MediaType != null, "Media type should have been set in messageInfo prior to creating Raw Input Context for Batch"); this.batchBoundary = ODataMultipartMixedBatchWriterUtils.GetBatchBoundaryFromMediaType(messageInfo.MediaType); }
/// <summary> /// Writes the start boundary for an operation. This is either the batch or the changeset boundary. /// </summary> private void WriteStartBoundaryForOperation() { if (this.changeSetBoundary == null) { ODataMultipartMixedBatchWriterUtils.WriteStartBoundary(this.RawOutputContext.TextWriter, this.batchBoundary, !this.batchStartBoundaryWritten); this.batchStartBoundaryWritten = true; } else { ODataMultipartMixedBatchWriterUtils.WriteStartBoundary(this.RawOutputContext.TextWriter, this.changeSetBoundary, !this.changesetStartBoundaryWritten); this.changesetStartBoundaryWritten = true; } }
/// <summary> /// Ends a batch - implementation of the actual functionality. /// </summary> protected override void WriteEndBatchImplementation() { Debug.Assert( this.batchStartBoundaryWritten || this.CurrentOperationMessage == null, "If not batch boundary was written we must not have an active message."); // write pending message data (headers, response line) for a previously unclosed message/request this.WritePendingMessageData(true); this.SetState(BatchWriterState.BatchCompleted); // write the end boundary for the batch ODataMultipartMixedBatchWriterUtils.WriteEndBoundary(this.RawOutputContext.TextWriter, this.batchBoundary, !this.batchStartBoundaryWritten); // For compatibility with WCF DS we write a newline after the end batch boundary. // Technically it's not needed, but it doesn't violate anything either. this.RawOutputContext.TextWriter.WriteLine(); }
/// <summary> /// Parse the content type header value to retrieve the boundary and encoding of a changeset. /// </summary> /// <param name="contentType">The content type to parse.</param> private void DetermineChangesetBoundaryAndEncoding(string contentType) { Debug.Assert(!string.IsNullOrEmpty(contentType), "Should have validated that non-null, non-empty content type header exists."); ODataMediaType mediaType; ODataPayloadKind readerPayloadKind; MediaTypeUtils.GetFormatFromContentType( contentType, new ODataPayloadKind[] { ODataPayloadKind.Batch }, this.mediaTypeResolver, out mediaType, out this.changesetEncoding, out readerPayloadKind); Debug.Assert(readerPayloadKind == ODataPayloadKind.Batch, "Must find batch payload kind."); Debug.Assert(HttpUtils.CompareMediaTypeNames(MimeConstants.MimeMultipartMixed, mediaType.FullTypeName), "Must be multipart/mixed media type."); this.changesetBoundary = ODataMultipartMixedBatchWriterUtils.GetBatchBoundaryFromMediaType(mediaType); Debug.Assert(this.changesetBoundary != null && this.changesetBoundary.Length > 0, "Boundary string should have been validated by now."); }
/// <summary> /// Returns the cached <see cref="ODataBatchOperationRequestMessage"/> for reading the content of an operation /// in a batch request. /// </summary> /// <returns>The message that can be used to read the content of the batch request operation from.</returns> protected override ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation() { string requestLine = this.batchStream.ReadFirstNonEmptyLine(); string httpMethod; Uri requestUri; this.ParseRequestLine(requestLine, out httpMethod, out requestUri); // Read all headers and create the request message ODataBatchOperationHeaders headers = this.batchStream.ReadHeaders(); if (this.batchStream.ChangeSetBoundary != null) { if (this.currentContentId == null) { headers.TryGetValue(ODataConstants.ContentIdHeader, out this.currentContentId); if (this.currentContentId == null) { throw new ODataException(Strings.ODataBatchOperationHeaderDictionary_KeyNotFound(ODataConstants.ContentIdHeader)); } } } ODataBatchOperationRequestMessage requestMessage = BuildOperationRequestMessage( () => ODataBatchUtils.CreateBatchOperationReadStream(this.batchStream, headers, this), httpMethod, requestUri, headers, this.currentContentId, ODataMultipartMixedBatchWriterUtils.GetChangeSetIdFromBoundary(this.batchStream.ChangeSetBoundary), this.dependsOnIdsTracker.GetDependsOnIds(), /*dependsOnIdsValidationRequired*/ false); if (this.currentContentId != null) { this.dependsOnIdsTracker.AddDependsOnId(this.currentContentId); } this.currentContentId = null; return(requestMessage); }
/// <summary> /// Returns the content type for the MultipartMime Batch format /// </summary> /// <param name="mediaType">The specified media type.</param> /// <param name="encoding">The specified encoding.</param> /// <param name="writingResponse">True if the message writer is being used to write a response.</param> /// <param name="mediaTypeParameters"> The resultant parameters list of the media type. /// For multipart/mixed batch type, boundary parameter will be created as required and be added to parameters list. /// </param> /// <returns>The content-type value for the format.</returns> internal override string GetContentType(ODataMediaType mediaType, Encoding encoding, bool writingResponse, out IEnumerable <KeyValuePair <string, string> > mediaTypeParameters) { ExceptionUtils.CheckArgumentNotNull(mediaType, "mediaType"); IEnumerable <KeyValuePair <string, string> > origParameters = mediaType.Parameters != null ? mediaType.Parameters : new List <KeyValuePair <string, string> >(); IEnumerable <KeyValuePair <string, string> > boundaryParameters = origParameters.Where( p => string.Compare(p.Key, ODataConstants.HttpMultipartBoundary, StringComparison.OrdinalIgnoreCase) == 0); string batchBoundary; if (boundaryParameters.Count() > 1) { throw new ODataContentTypeException( Strings.MediaTypeUtils_NoOrMoreThanOneContentTypeSpecified(mediaType.ToText())); } else if (boundaryParameters.Count() == 1) { batchBoundary = boundaryParameters.First().Value; mediaTypeParameters = mediaType.Parameters; } else { // No boundary parameters found. // Create and add the boundary parameter required by the multipart/mixed batch format. batchBoundary = ODataMultipartMixedBatchWriterUtils.CreateBatchBoundary(writingResponse); List <KeyValuePair <string, string> > newList = new List <KeyValuePair <string, string> >(origParameters); newList.Add(new KeyValuePair <string, string>(ODataConstants.HttpMultipartBoundary, batchBoundary)); mediaTypeParameters = newList; } // Set the content type header here since all headers have to be set before getting the stream // Note that the mediaType may have additional parameters, which we ignore here (intentional as per MIME spec). // Note that we always generate a new boundary string here, even if the accept header contained one. // We need the boundary to be as unique as possible to avoid possible collision with content of the batch operation payload. // Our boundary string are generated to fulfill this requirement, client specified ones might not which might lead to wrong responses // and at least in theory security issues. return(ODataMultipartMixedBatchWriterUtils.CreateMultipartMixedContentType(batchBoundary)); }
/// <summary> /// Starts a new changeset - implementation of the actual functionality. /// </summary> /// <param name="changeSetId">The value for changeset boundary for multipart batch.</param> protected override void WriteStartChangesetImplementation(string changeSetId) { Debug.Assert(changeSetId != null, "changeSetId != null"); // write pending message data (headers, response line) for a previously unclosed message/request this.WritePendingMessageData(true); this.SetState(BatchWriterState.ChangesetStarted); Debug.Assert(this.changeSetBoundary == null, "this.changeSetBoundary == null"); this.changeSetBoundary = ODataMultipartMixedBatchWriterUtils.CreateChangeSetBoundary(this.RawOutputContext.WritingResponse, changeSetId); // write the boundary string ODataMultipartMixedBatchWriterUtils.WriteStartBoundary(this.RawOutputContext.TextWriter, this.batchBoundary, !this.batchStartBoundaryWritten); this.batchStartBoundaryWritten = true; // write the change set headers ODataMultipartMixedBatchWriterUtils.WriteChangeSetPreamble(this.RawOutputContext.TextWriter, this.changeSetBoundary); this.changesetStartBoundaryWritten = false; }
/// <summary> /// Creates an <see cref="ODataBatchOperationResponseMessage"/> for writing an operation of a batch response - implementation of the actual functionality. /// </summary> /// <param name="contentId">The Content-ID value to write in ChangeSet head.</param> /// <returns>The message that can be used to write the response operation.</returns> protected override ODataBatchOperationResponseMessage CreateOperationResponseMessageImplementation(string contentId) { this.WritePendingMessageData(true); // In responses we don't need to use our batch URL resolver, since there are no cross referencing URLs // so use the URL resolver from the batch message instead. this.CurrentOperationResponseMessage = BuildOperationResponseMessage( this.RawOutputContext.OutputStream, contentId, ODataMultipartMixedBatchWriterUtils.GetChangeSetIdFromBoundary(this.changeSetBoundary)); this.SetState(BatchWriterState.OperationCreated); // write the operation's start boundary string this.WriteStartBoundaryForOperation(); // write the headers and request separator line ODataMultipartMixedBatchWriterUtils.WriteResponsePreamble(this.RawOutputContext.TextWriter, changeSetBoundary != null, contentId); return(this.CurrentOperationResponseMessage); }
/// <summary> /// Asynchronously writes the start boundary for an operation. This is either the batch or the changeset boundary. /// </summary> /// <returns>A task that represents the asynchronous write operation.</returns> private async Task WriteStartBoundaryForOperationAsync() { if (this.changeSetBoundary == null) { await ODataMultipartMixedBatchWriterUtils.WriteStartBoundaryAsync( this.RawOutputContext.TextWriter, this.batchBoundary, !this.batchStartBoundaryWritten).ConfigureAwait(false); this.batchStartBoundaryWritten = true; } else { await ODataMultipartMixedBatchWriterUtils.WriteStartBoundaryAsync( this.RawOutputContext.TextWriter, this.changeSetBoundary, !this.changesetStartBoundaryWritten).ConfigureAwait(false); this.changesetStartBoundaryWritten = true; } }
/// <summary>Constructor.</summary> /// <param name="format">The format for this input context.</param> /// <param name="messageInfo">The context information for the message.</param> /// <param name="messageReaderSettings">Configuration settings of the OData reader.</param> public ODataMultipartMixedBatchInputContext( ODataFormat format, ODataMessageInfo messageInfo, ODataMessageReaderSettings messageReaderSettings) : base(format, messageInfo, messageReaderSettings) { Debug.Assert(messageInfo.MessageStream != null, "messageInfo.MessageStream != null"); Debug.Assert(messageInfo.MediaType != null, "Media type should have been set in messageInfo prior to creating Raw Input Context for Batch"); try { this.batchBoundary = ODataMultipartMixedBatchWriterUtils.GetBatchBoundaryFromMediaType(messageInfo.MediaType); } catch (Exception e) { // Dispose the message stream if we failed to get a batch boundary. if (ExceptionUtils.IsCatchableExceptionType(e)) { messageInfo.MessageStream.Dispose(); } throw; } }
/// <summary> /// Creates an <see cref="ODataBatchOperationRequestMessage"/> for writing an operation of a batch request /// - implementation of the actual functionality. /// </summary> /// <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> /// <param name="dependsOnIds">The prerequisite request ids of this request.</param> /// <returns>The message that can be used to write the request operation.</returns> protected override ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation( string method, Uri uri, string contentId, BatchPayloadUriOption payloadUriOption, IEnumerable<string> dependsOnIds) { // write pending message data (headers, response line) for a previously unclosed message/request this.WritePendingMessageData(true); // create the new request operation ODataBatchOperationRequestMessage operationRequestMessage = BuildOperationRequestMessage( this.RawOutputContext.OutputStream, method, uri, contentId, /*groupId*/null, dependsOnIds, ODataFormat.Batch); this.SetState(BatchWriterState.OperationCreated); // write the operation's start boundary string this.WriteStartBoundaryForOperation(); // write the headers and request line ODataMultipartMixedBatchWriterUtils.WriteRequestPreamble(this.RawOutputContext.TextWriter, method, uri, this.RawOutputContext.MessageWriterSettings.BaseUri, changeSetBoundary != null, contentId, payloadUriOption); return operationRequestMessage; }