/// <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);
        }
Example #5
0
 /// <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();
        }
Example #8
0
        /// <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.");
        }
Example #9
0
        /// <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;
        }
Example #12
0
        /// <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;
            }
        }
Example #14
0
        /// <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;
        }