/// <summary> /// Executes the operation. /// </summary> /// <param name="batchReader">The batch reader.</param> /// <param name="batchId">The batch id.</param> /// <param name="originalRequest">The original request containing all the batch requests.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The response for the operation.</returns> public virtual async Task<ODataBatchResponseItem> ExecuteOperationAsync(ODataBatchReader batchReader, Guid batchId, HttpRequestMessage originalRequest, CancellationToken cancellationToken) { if (batchReader == null) { throw Error.ArgumentNull("batchReader"); } if (originalRequest == null) { throw Error.ArgumentNull("originalRequest"); } cancellationToken.ThrowIfCancellationRequested(); OperationRequestItem operation = new OperationRequestItem(await batchReader.ReadOperationRequestAsync(batchId, bufferContentStream: false)); try { ODataBatchResponseItem response = await operation.SendRequestAsync(Invoker, cancellationToken); return response; } finally { originalRequest.RegisterForDispose(operation.GetResourcesForDisposal()); originalRequest.RegisterForDispose(operation); } }
/// <summary> /// Constructor. /// </summary> /// <param name="batchReader">The reader to wrap.</param> /// <param name="testConfiguration">The test configuration to use.</param> public ODataBatchReaderTestWrapper(ODataBatchReader batchReader, ReaderTestConfiguration testConfiguration) { ExceptionUtilities.CheckArgumentNotNull(batchReader, "batchReader"); ExceptionUtilities.CheckArgumentNotNull(testConfiguration, "testConfiguration"); this.batchReader = batchReader; this.testConfiguration = testConfiguration; }
private Exception ProcessCurrentOperationResponse(ODataBatchReader batchReader, bool isChangesetOperation) { Debug.Assert(batchReader != null, "batchReader != null"); Debug.Assert(batchReader.State == ODataBatchReaderState.Operation, "This method requires the batch reader to be on an operation."); ODataBatchOperationResponseMessage operationResponseMessage = batchReader.CreateOperationResponseMessage(); Descriptor descriptor = null; if (isChangesetOperation) { // We need to peek at the content-Id before handing the response to the user, so we can expose the Descriptor them. // We're OK with this exception to our general rule of not using them before ReceivingResponse event is fired. this.entryIndex = this.ValidateContentID(operationResponseMessage.ContentId); descriptor = this.ChangedEntries[entryIndex]; } // If we hit en error inside a batch, we will never expose a descriptor since we don't know which one to return. // The descriptor we fetched above based on the content-ID is bogus because the server returns an errounous content-id when // it hits an error inside batch. if (!WebUtil.SuccessStatusCode((HttpStatusCode)operationResponseMessage.StatusCode)) { descriptor = null; } this.RequestInfo.Context.FireReceivingResponseEvent(new ReceivingResponseEventArgs(operationResponseMessage, descriptor, true)); // We need to know if the content of the operation response is empty or not. // We also need to cache the entire content, since in case of GET response the response itself will be parsed // lazily and so it can happen that we will move the batch reader after this operation before we actually read // the content of the operation. Stream originalOperationResponseContentStream = operationResponseMessage.GetStream(); if (originalOperationResponseContentStream == null) { Error.ThrowBatchExpectedResponse(InternalError.NullResponseStream); } MemoryStream operationResponseContentStream; try { operationResponseContentStream = new MemoryStream(); WebUtil.CopyStream(originalOperationResponseContentStream, operationResponseContentStream, ref this.streamCopyBuffer); operationResponseContentStream.Position = 0; } finally { originalOperationResponseContentStream.Dispose(); } this.currentOperationResponse = new CurrentOperationResponse( (HttpStatusCode)operationResponseMessage.StatusCode, operationResponseMessage.Headers, operationResponseContentStream); Version responseVersion; string headerName = XmlConstants.HttpODataVersion; return BaseSaveResult.HandleResponse( this.RequestInfo, this.currentOperationResponse.StatusCode, this.currentOperationResponse.Headers.GetHeader(headerName), () => this.currentOperationResponse.ContentStream, false, out responseVersion); }
/// <summary> /// process the batch response /// </summary> /// <param name="batchReader">The batch reader to use for reading the batch response.</param> /// <returns>enumerable of QueryResponse or null</returns> /// <remarks> /// The batch message reader for the entire batch response is stored in this.batchMessageReader. /// Note that this method takes over the ownership of this reader and must Dispose it if it successfully returns. /// </remarks> private IEnumerable<OperationResponse> HandleBatchResponse(ODataBatchReader batchReader) { try { if (this.batchMessageReader == null) { // The enumerable returned by this method can be enumerated multiple times. // In that case it looks like if the method is called multiple times. // This didn't fail in previous versions, it simply returned no results, so we need to do the same. yield break; } Debug.Assert(batchReader != null, "batchReader != null"); bool changesetFound = false; bool insideChangeset = false; int queryCount = 0; int operationCount = 0; this.entryIndex = 0; while (batchReader.Read()) { switch (batchReader.State) { #region ChangesetStart case ODataBatchReaderState.ChangesetStart: if ((Util.IsBatchWithSingleChangeset(this.Options) && changesetFound) || (operationCount != 0)) { // Throw if we encounter multiple changesets when running in batch with single changeset mode // or if we encounter operations outside of a changeset. Error.ThrowBatchUnexpectedContent(InternalError.UnexpectedBeginChangeSet); } insideChangeset = true; break; #endregion #region ChangesetEnd case ODataBatchReaderState.ChangesetEnd: changesetFound = true; operationCount = 0; insideChangeset = false; break; #endregion #region Operation case ODataBatchReaderState.Operation: Exception exception = this.ProcessCurrentOperationResponse(batchReader, insideChangeset); if (!insideChangeset) { #region Get response Debug.Assert(operationCount == 0, "missing an EndChangeSet 2"); QueryOperationResponse qresponse = null; try { if (exception == null) { DataServiceRequest query = this.Queries[queryCount]; ResponseInfo responseInfo = this.RequestInfo.GetDeserializationInfo(null /*mergeOption*/); MaterializeAtom materializer = DataServiceRequest.Materialize( responseInfo, query.QueryComponents(this.RequestInfo.Model), null, this.currentOperationResponse.Headers.GetHeader(XmlConstants.HttpContentType), this.currentOperationResponse.CreateResponseMessage(), query.PayloadKind); qresponse = QueryOperationResponse.GetInstance(query.ElementType, this.currentOperationResponse.Headers, query, materializer); } } catch (ArgumentException e) { exception = e; } catch (FormatException e) { exception = e; } catch (InvalidOperationException e) { exception = e; } if (qresponse == null) { if (this.Queries != null) { // this is the normal ExecuteBatch response DataServiceRequest query = this.Queries[queryCount]; if (this.RequestInfo.IgnoreResourceNotFoundException && this.currentOperationResponse.StatusCode == HttpStatusCode.NotFound) { qresponse = QueryOperationResponse.GetInstance(query.ElementType, this.currentOperationResponse.Headers, query, MaterializeAtom.EmptyResults); } else { qresponse = QueryOperationResponse.GetInstance(query.ElementType, this.currentOperationResponse.Headers, query, MaterializeAtom.EmptyResults); qresponse.Error = exception; } } else { // This is top-level failure for SaveChanges(SaveChangesOptions.BatchWithSingleChangeset) or SaveChanges(SaveChangesOptions.BatchWithIndependentOperations) operations. // example: server doesn't support batching or number of batch objects exceeded an allowed limit. // ex could be null if the server responded to SaveChanges with an unexpected success with // response of batched GETS that did not correspond the original POST/PATCH/PUT/DELETE requests. // we expect non-null since server should have failed with a non-success code // and HandleResponse(status, ...) should generate the exception object throw exception; } } qresponse.StatusCode = (int)this.currentOperationResponse.StatusCode; queryCount++; yield return qresponse; #endregion } else { #region Update response try { Descriptor descriptor = this.ChangedEntries[this.entryIndex]; operationCount += this.SaveResultProcessed(descriptor); if (exception != null) { throw exception; } this.HandleOperationResponseHeaders(this.currentOperationResponse.StatusCode, this.currentOperationResponse.Headers); #if DEBUG this.HandleOperationResponse(descriptor, this.currentOperationResponse.Headers, this.currentOperationResponse.StatusCode); #else this.HandleOperationResponse(descriptor, this.currentOperationResponse.Headers); #endif } catch (Exception e) { this.ChangedEntries[this.entryIndex].SaveError = e; exception = e; if (!CommonUtil.IsCatchableExceptionType(e)) { throw; } } ChangeOperationResponse changeOperationResponse = new ChangeOperationResponse(this.currentOperationResponse.Headers, this.ChangedEntries[this.entryIndex]); changeOperationResponse.StatusCode = (int)this.currentOperationResponse.StatusCode; if (exception != null) { changeOperationResponse.Error = exception; } operationCount++; this.entryIndex++; yield return changeOperationResponse; #endregion } break; #endregion default: Error.ThrowBatchExpectedResponse(InternalError.UnexpectedBatchState); break; } } Debug.Assert(batchReader.State == ODataBatchReaderState.Completed, "unexpected batch state"); // Check for a changeset without response (first line) or GET request without response (second line). // either all saved entries must be processed or it was a batch and one of the entries has the error if ((this.Queries == null && (!changesetFound || 0 < queryCount || this.ChangedEntries.Any(o => o.ContentGeneratedForSave && o.SaveResultWasProcessed == 0) && (!this.IsBatchRequest || this.ChangedEntries.FirstOrDefault(o => o.SaveError != null) == null))) || (this.Queries != null && queryCount != this.Queries.Length)) { throw Error.InvalidOperation(Strings.Batch_IncompleteResponseCount); } } finally { // Note that this will be called only once the enumeration of all responses is finished and the Dispose // was called on the IEnumerator used for that enumeration. It is not called when the method returns, // since the compiler change this method to return the compiler-generated IEnumerable. Util.Dispose(ref this.batchMessageReader); } }
/// <summary> /// process the batch response /// </summary> /// <param name="batchReader">The batch reader to use for reading the batch response.</param> /// <returns>an instance of the DataServiceResponse, containing individual operation responses for this batch request.</returns> /// <remarks> /// The message reader for the entire batch response is stored in the this.batchMessageReader. /// The message reader is disposable, but this method should not dispose it itself. It will be either disposed by the caller (in case of exception) /// or the ownership will be passed to the returned response object (in case of success). /// In could also be diposed indirectly by this method when it enumerates through the responses. /// </remarks> private DataServiceResponse HandleBatchResponseInternal(ODataBatchReader batchReader) { Debug.Assert(this.batchMessageReader != null, "this.batchMessageReader != null"); Debug.Assert(batchReader != null, "batchReader != null"); DataServiceResponse response; HeaderCollection headers = new HeaderCollection(this.batchResponseMessage); IEnumerable<OperationResponse> responses = this.HandleBatchResponse(batchReader); if (this.Queries != null) { // ExecuteBatch, EndExecuteBatch response = new DataServiceResponse( headers, (int)this.batchResponseMessage.StatusCode, responses, true /*batchResponse*/); } else { List<OperationResponse> operationResponses = new List<OperationResponse>(); response = new DataServiceResponse(headers, (int)this.batchResponseMessage.StatusCode, operationResponses, true /*batchResponse*/); Exception exception = null; // SaveChanges, EndSaveChanges // enumerate the entire response foreach (ChangeOperationResponse changeOperationResponse in responses) { operationResponses.Add(changeOperationResponse); if (Util.IsBatchWithSingleChangeset(this.Options) && exception == null && changeOperationResponse.Error != null) { exception = changeOperationResponse.Error; } // Note that this will dispose the enumerator and this release the batch message reader which is owned // by the enumerable of responses by now. } // Note that if we encounter any error in a batch request with a single changeset, // we throw here since all change operations in the changeset are rolled back on the server. // If we encounter any error in a batch request with independent operations, we don't want to throw // since some of the operations might succeed. // Users need to inspect each OperationResponse to get the exception information from the failed operations. if (exception != null) { throw new DataServiceRequestException(Strings.DataServiceException_GeneralError, exception, response); } } return response; }
private static async Task<HttpRequestMessage> ReadOperationInternalAsync( ODataBatchReader reader, Guid batchId, Guid? changeSetId, CancellationToken cancellationToken, bool bufferContentStream = true) { ODataBatchOperationRequestMessage batchRequest = reader.CreateOperationRequestMessage(); HttpRequestMessage request = new HttpRequestMessage(); request.Method = new HttpMethod(batchRequest.Method); request.RequestUri = batchRequest.Url; if (bufferContentStream) { using (Stream stream = batchRequest.GetStream()) { MemoryStream bufferedStream = new MemoryStream(); // Passing in the default buffer size of 81920 so that we can also pass in a cancellation token await stream.CopyToAsync(bufferedStream, bufferSize: 81920, cancellationToken: cancellationToken); bufferedStream.Position = 0; request.Content = new StreamContent(bufferedStream); } } else { request.Content = new LazyStreamContent(() => batchRequest.GetStream()); } foreach (var header in batchRequest.Headers) { string headerName = header.Key; string headerValue = header.Value; if (!request.Headers.TryAddWithoutValidation(headerName, headerValue)) { request.Content.Headers.TryAddWithoutValidation(headerName, headerValue); } } request.SetODataBatchId(batchId); if (changeSetId != null && changeSetId.HasValue) { request.SetODataChangeSetId(changeSetId.Value); } return request; }
/// <summary> /// Constructor. /// </summary> /// <param name="batchReader">The batch reader to get the batch stream from.</param> public BatchReaderStreamWrapper(ODataBatchReader batchReader) { this.batchStream = ReflectionUtils.GetField(batchReader, "batchStream"); this.batchStreamBuffer = new BatchReaderStreamBufferWrapper(this.batchStream); }
/// <summary> /// Processed the operation response reported by the batch reader. /// This is a side-effecting method that is tied deeply to how it is used in the batch processing pipeline. /// </summary> /// <param name="batchReader">The batch reader to get the operation response from.</param> /// <param name="isChangesetOperation">True if the current operation is inside a changeset (implying CUD, not query)</param> /// <returns>An exception if the operation response is an error response, null for success response.</returns> private Exception ProcessCurrentOperationResponse(ODataBatchReader batchReader, bool isChangesetOperation) { Debug.Assert(batchReader != null, "batchReader != null"); Debug.Assert(batchReader.State == ODataBatchReaderState.Operation, "This method requires the batch reader to be on an operation."); ODataBatchOperationResponseMessage operationResponseMessage = batchReader.CreateOperationResponseMessage(); Descriptor descriptor = null; if (isChangesetOperation) { // We need to peek at the content-Id before handing the response to the user, so we can expose the Descriptor them. // We're OK with this exception to our general rule of not using them before ReceivingResponse event is fired. this.entryIndex = this.ValidateContentID(operationResponseMessage.ContentId); descriptor = this.ChangedEntries[entryIndex]; } // If we hit en error inside a batch, we will never expose a descriptor since we don't know which one to return. // The descriptor we fetched above based on the content-ID is bogus because the server returns an errounous content-id when // it hits an error inside batch. if (!WebUtil.SuccessStatusCode((HttpStatusCode)operationResponseMessage.StatusCode)) { descriptor = null; } this.RequestInfo.Context.FireReceivingResponseEvent(new ReceivingResponseEventArgs(operationResponseMessage, descriptor, true)); // We need to know if the content of the operation response is empty or not. // We also need to cache the entire content, since in case of GET response the response itself will be parsed // lazily and so it can happen that we will move the batch reader after this operation before we actually read // the content of the operation. Stream originalOperationResponseContentStream = operationResponseMessage.GetStream(); if (originalOperationResponseContentStream == null) { Error.ThrowBatchExpectedResponse(InternalError.NullResponseStream); } MemoryStream operationResponseContentStream; try { operationResponseContentStream = new MemoryStream(); WebUtil.CopyStream(originalOperationResponseContentStream, operationResponseContentStream, ref this.streamCopyBuffer); operationResponseContentStream.Position = 0; } finally { originalOperationResponseContentStream.Dispose(); } this.currentOperationResponse = new CurrentOperationResponse( (HttpStatusCode)operationResponseMessage.StatusCode, operationResponseMessage.Headers, operationResponseContentStream); Version responseVersion; string headerName = XmlConstants.HttpODataVersion; return(BaseSaveResult.HandleResponse( this.RequestInfo, this.currentOperationResponse.StatusCode, this.currentOperationResponse.Headers.GetHeader(headerName), () => this.currentOperationResponse.ContentStream, false, out responseVersion)); }
private DataServiceResponse HandleBatchResponseInternal(ODataBatchReader batchReader) { Dictionary<string, string> headers = WebUtil.WrapResponseHeaders(base.batchResponse); IEnumerable<OperationResponse> enumerable = this.HandleBatchResponse(batchReader); if (this.Queries != null) { return new DataServiceResponse(headers, (int) base.batchResponse.StatusCode, enumerable, true); } List<OperationResponse> list = new List<OperationResponse>(); DataServiceResponse response = new DataServiceResponse(headers, (int) base.batchResponse.StatusCode, list, true); Exception innerException = null; foreach (ChangeOperationResponse response2 in enumerable) { list.Add(response2); if ((innerException == null) && (response2.Error != null)) { innerException = response2.Error; } } if (innerException != null) { throw new DataServiceRequestException(System.Data.Services.Client.Strings.DataServiceException_GeneralError, innerException, response); } return response; }
private IEnumerable<OperationResponse> HandleBatchResponse(ODataBatchReader batchReader) { if (this.batchMessageReader == null) { goto Label_056D; } bool iteratorVariable0 = false; bool iteratorVariable1 = false; int index = 0; int iteratorVariable3 = 0; this.entryIndex = 0; Label_PostSwitchInIterator:; while (batchReader.Read()) { Exception iteratorVariable4; switch (batchReader.State) { case ODataBatchReaderState.Operation: { iteratorVariable4 = this.ProcessCurrentOperationResponse(batchReader); if (iteratorVariable1) { break; } QueryOperationResponse iteratorVariable5 = null; try { if (iteratorVariable4 == null) { DataServiceRequest query = this.Queries[index]; MaterializeAtom results = DataServiceRequest.Materialize(this.RequestInfo.GetDeserializationInfo(null), query.QueryComponents(this.RequestInfo.MaxProtocolVersion), null, this.currentOperationResponse.GetHeader("Content-Type"), this.currentOperationResponse.CreateResponseMessage(), query.PayloadKind); iteratorVariable5 = QueryOperationResponse.GetInstance(query.ElementType, this.currentOperationResponse.Headers, query, results); } } catch (ArgumentException exception) { iteratorVariable4 = exception; } catch (FormatException exception2) { iteratorVariable4 = exception2; } catch (InvalidOperationException exception3) { iteratorVariable4 = exception3; } if (iteratorVariable5 == null) { if (this.Queries == null) { throw iteratorVariable4; } DataServiceRequest request2 = this.Queries[index]; if (this.RequestInfo.IgnoreResourceNotFoundException && (this.currentOperationResponse.StatusCode == HttpStatusCode.NotFound)) { iteratorVariable5 = QueryOperationResponse.GetInstance(request2.ElementType, this.currentOperationResponse.Headers, request2, MaterializeAtom.EmptyResults); } else { iteratorVariable5 = QueryOperationResponse.GetInstance(request2.ElementType, this.currentOperationResponse.Headers, request2, MaterializeAtom.EmptyResults); iteratorVariable5.Error = iteratorVariable4; } } iteratorVariable5.StatusCode = (int) this.currentOperationResponse.StatusCode; index++; yield return iteratorVariable5; goto Label_PostSwitchInIterator; } case ODataBatchReaderState.ChangesetStart: { if ((this.IsBatch && iteratorVariable0) || (iteratorVariable3 != 0)) { System.Data.Services.Client.Error.ThrowBatchUnexpectedContent(InternalError.UnexpectedBeginChangeSet); } iteratorVariable1 = true; continue; } case ODataBatchReaderState.ChangesetEnd: { iteratorVariable0 = true; iteratorVariable3 = 0; iteratorVariable1 = false; continue; } default: goto Label_0491; } this.entryIndex = this.ValidateContentID(this.currentOperationResponse.Headers); try { Descriptor descriptor = this.ChangedEntries[this.entryIndex]; iteratorVariable3 += this.SaveResultProcessed(descriptor); if (iteratorVariable4 != null) { throw iteratorVariable4; } this.HandleOperationResponseHeaders(this.currentOperationResponse.StatusCode, this.currentOperationResponse.Headers); this.HandleOperationResponse(descriptor, this.currentOperationResponse.Headers); } catch (Exception exception4) { this.ChangedEntries[this.entryIndex].SaveError = exception4; iteratorVariable4 = exception4; if (!CommonUtil.IsCatchableExceptionType(exception4)) { throw; } } ChangeOperationResponse iteratorVariable6 = new ChangeOperationResponse(this.currentOperationResponse.Headers, this.ChangedEntries[this.entryIndex]) { StatusCode = (int) this.currentOperationResponse.StatusCode }; if (iteratorVariable4 != null) { iteratorVariable6.Error = iteratorVariable4; } iteratorVariable3++; this.entryIndex++; yield return iteratorVariable6; goto Label_PostSwitchInIterator; Label_0491: System.Data.Services.Client.Error.ThrowBatchExpectedResponse(InternalError.UnexpectedBatchState); } if (((this.Queries == null) && ((!iteratorVariable0 || (0 < index)) || (this.ChangedEntries.Any<Descriptor>(o => (o.ContentGeneratedForSave && (o.SaveResultWasProcessed == 0))) && (!this.IsBatch || (this.ChangedEntries.FirstOrDefault<Descriptor>(o => (o.SaveError != null)) == null))))) || ((this.Queries != null) && (index != this.Queries.Length))) { throw System.Data.Services.Client.Error.InvalidOperation(System.Data.Services.Client.Strings.Batch_IncompleteResponseCount); } Label_056D: yield break; }
public static async Task <IList <HttpRequestMessage> > ReadChangeSetRequestAsync(this ODataBatchReader reader, Guid batchId, CancellationToken cancellationToken) { if (reader == null) { throw Error.ArgumentNull("reader"); } if (reader.State != ODataBatchReaderState.ChangesetStart) { throw Error.InvalidOperation( SRResources.InvalidBatchReaderState, reader.State.ToString(), ODataBatchReaderState.ChangesetStart.ToString()); } Guid changeSetId = Guid.NewGuid(); List <HttpRequestMessage> requests = new List <HttpRequestMessage>(); while (reader.Read() && reader.State != ODataBatchReaderState.ChangesetEnd) { if (reader.State == ODataBatchReaderState.Operation) { requests.Add(await ReadOperationInternalAsync(reader, batchId, changeSetId, cancellationToken)); } } return(requests); }
public static Task <IList <HttpRequestMessage> > ReadChangeSetRequestAsync(this ODataBatchReader reader, Guid batchId) { return(reader.ReadChangeSetRequestAsync(batchId, CancellationToken.None)); }
/// <summary> /// Reads an Operation request in a ChangeSet. /// </summary> /// <param name="reader">The <see cref="ODataBatchReader"/>.</param> /// <param name="batchId">The Batch ID.</param> /// <param name="changeSetId">The ChangeSet ID.</param> /// <param name="bufferContentStream">if set to <c>true</c> then the request content stream will be buffered.</param> /// <returns>A <see cref="HttpRequestMessage"/> representing a ChangeSet operation</returns> public static Task <HttpRequestMessage> ReadChangeSetOperationRequestAsync(this ODataBatchReader reader, Guid batchId, Guid changeSetId, bool bufferContentStream) { return(reader.ReadChangeSetOperationRequestAsync(batchId, changeSetId, bufferContentStream, CancellationToken.None)); }
public async Task ExecuteChangeSetAsync_ReturnsSingleErrorResponse() { // Arrange RequestDelegate handler = context => { if (context.Request.Method.ToUpperInvariant() == "POST") { context.Response.StatusCode = StatusCodes.Status400BadRequest; return(Task.FromResult(context.Response)); } return(Task.FromResult(context.Response)); }; UnbufferedODataBatchHandler batchHandler = new UnbufferedODataBatchHandler(); string batchRequest = @"--86aef3eb-4af6-4750-a66d-df65e3f31ab0 Content-Type: multipart/mixed; boundary=""7a61b8c1-a80e-4e6b-bac7-2f65564e3fd6"" --7a61b8c1-a80e-4e6b-bac7-2f65564e3fd6 Content-Type: application/http Content-Transfer-Encoding: binary Content-ID: -1233709575 PUT /values HTTP/1.1 Host: example.com --7a61b8c1-a80e-4e6b-bac7-2f65564e3fd6 Content-Type: application/http Content-Transfer-Encoding: binary Content-ID: -1854117385 POST /values HTTP/1.1 Host: example.com --7a61b8c1-a80e-4e6b-bac7-2f65564e3fd6 Content-Type: application/http Content-Transfer-Encoding: binary Content-ID: -1665098746 DELETE /values HTTP/1.1 Host: example.com --7a61b8c1-a80e-4e6b-bac7-2f65564e3fd6-- --86aef3eb-4af6-4750-a66d-df65e3f31ab0--"; IEdmModel model = new EdmModel(); HttpRequest request = RequestFactory.Create("Post", "http://example.com/$batch", opt => opt.AddRouteComponents(model)); HttpContext httpContext = request.HttpContext; byte[] requestBytes = Encoding.UTF8.GetBytes(batchRequest); httpContext.Request.Body = new MemoryStream(requestBytes); httpContext.Request.ContentType = "multipart/mixed;boundary=\"86aef3eb-4af6-4750-a66d-df65e3f31ab0\""; httpContext.Request.ContentLength = 431; httpContext.ODataFeature().RoutePrefix = ""; IServiceProvider sp = request.GetRouteServices(); IODataRequestMessage oDataRequestMessage = ODataMessageWrapperHelper.Create(httpContext.Request.Body, request.Headers, sp); ODataMessageReader oDataMessageReader = new ODataMessageReader(oDataRequestMessage, new ODataMessageReaderSettings { BaseUri = new Uri("http://example.com") }); ODataBatchReader batchReader = await oDataMessageReader.CreateODataBatchReaderAsync(); // Act var response = await batchHandler.ExecuteChangeSetAsync(batchReader, Guid.NewGuid(), httpContext.Request, handler); // Assert var changesetResponse = Assert.IsType <ChangeSetResponseItem>(response); var returnContext = Assert.Single(changesetResponse.Contexts); Assert.Equal(StatusCodes.Status400BadRequest, returnContext.Response.StatusCode); }
/// <summary> /// Executes the ChangeSet. /// </summary> /// <param name="batchReader">The batch reader.</param> /// <param name="batchId">The batch id.</param> /// <param name="originalRequest">The original request containing all the batch requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <returns>The response for the ChangeSet.</returns> public virtual async Task<ODataBatchResponseItem> ExecuteChangeSetAsync(ODataBatchReader batchReader, Guid batchId, HttpRequestMessage originalRequest, CancellationToken cancellationToken) { if (batchReader == null) { throw Error.ArgumentNull("batchReader"); } if (originalRequest == null) { throw Error.ArgumentNull("originalRequest"); } Guid changeSetId = Guid.NewGuid(); List<HttpResponseMessage> changeSetResponse = new List<HttpResponseMessage>(); Dictionary<string, string> contentIdToLocationMapping = new Dictionary<string, string>(); try { while (batchReader.Read() && batchReader.State != ODataBatchReaderState.ChangesetEnd) { if (batchReader.State == ODataBatchReaderState.Operation) { HttpRequestMessage changeSetOperationRequest = await batchReader.ReadChangeSetOperationRequestAsync(batchId, changeSetId, bufferContentStream: false); changeSetOperationRequest.CopyBatchRequestProperties(originalRequest); try { HttpResponseMessage response = await ODataBatchRequestItem.SendMessageAsync(Invoker, changeSetOperationRequest, cancellationToken, contentIdToLocationMapping); if (response.IsSuccessStatusCode) { changeSetResponse.Add(response); } else { ChangeSetRequestItem.DisposeResponses(changeSetResponse); changeSetResponse.Clear(); changeSetResponse.Add(response); return new ChangeSetResponseItem(changeSetResponse); } } finally { originalRequest.RegisterForDispose(changeSetOperationRequest.GetResourcesForDisposal()); originalRequest.RegisterForDispose(changeSetOperationRequest); } } } } catch { ChangeSetRequestItem.DisposeResponses(changeSetResponse); throw; } return new ChangeSetResponseItem(changeSetResponse); }
private Exception ProcessCurrentOperationResponse(ODataBatchReader batchReader) { MemoryStream stream2; Version version; IODataResponseMessage message = batchReader.CreateOperationResponseMessage(); Stream input = message.GetStream(); if (input == null) { System.Data.Services.Client.Error.ThrowBatchExpectedResponse(InternalError.NullResponseStream); } try { stream2 = new MemoryStream(); WebUtil.CopyStream(input, stream2, ref this.streamCopyBuffer); stream2.Position = 0L; } finally { input.Dispose(); } this.currentOperationResponse = new CurrentOperationResponse((HttpStatusCode) message.StatusCode, message.Headers, stream2); return BaseSaveResult.HandleResponse(base.RequestInfo, this.currentOperationResponse.StatusCode, this.currentOperationResponse.GetHeader("DataServiceVersion"), () => this.currentOperationResponse.ContentStream, false, out version); }
private async Task<ODataResponse> ReadResponse(ODataBatchReader odataReader) { var batch = new List<ODataResponse>(); while (odataReader.Read()) { switch (odataReader.State) { case ODataBatchReaderState.ChangesetStart: break; case ODataBatchReaderState.Operation: var operationMessage = odataReader.CreateOperationResponseMessage(); if (operationMessage.StatusCode == (int)HttpStatusCode.NoContent) batch.Add(ODataResponse.FromStatusCode(operationMessage.StatusCode)); else batch.Add(await GetResponseAsync(operationMessage)); break; case ODataBatchReaderState.ChangesetEnd: break; } } return ODataResponse.FromBatch(batch); }
/// <summary> /// process the batch response /// </summary> /// <param name="batchReader">The batch reader to use for reading the batch response.</param> /// <returns>enumerable of QueryResponse or null</returns> /// <remarks> /// The batch message reader for the entire batch response is stored in this.batchMessageReader. /// Note that this method takes over the ownership of this reader and must Dispose it if it successfully returns. /// </remarks> private IEnumerable <OperationResponse> HandleBatchResponse(ODataBatchReader batchReader) { try { if (this.batchMessageReader == null) { // The enumerable returned by this method can be enumerated multiple times. // In that case it looks like if the method is called multiple times. // This didn't fail in previous versions, it simply returned no results, so we need to do the same. yield break; } Debug.Assert(batchReader != null, "batchReader != null"); bool changesetFound = false; bool insideChangeset = false; int queryCount = 0; int operationCount = 0; this.entryIndex = 0; while (batchReader.Read()) { switch (batchReader.State) { #region ChangesetStart case ODataBatchReaderState.ChangesetStart: if ((Util.IsBatchWithSingleChangeset(this.Options) && changesetFound) || (operationCount != 0)) { // Throw if we encounter multiple changesets when running in batch with single changeset mode // or if we encounter operations outside of a changeset. Error.ThrowBatchUnexpectedContent(InternalError.UnexpectedBeginChangeSet); } insideChangeset = true; break; #endregion #region ChangesetEnd case ODataBatchReaderState.ChangesetEnd: changesetFound = true; operationCount = 0; insideChangeset = false; break; #endregion #region Operation case ODataBatchReaderState.Operation: Exception exception = this.ProcessCurrentOperationResponse(batchReader, insideChangeset); if (!insideChangeset) { #region Get response Debug.Assert(operationCount == 0, "missing an EndChangeSet 2"); QueryOperationResponse qresponse = null; try { if (exception == null) { DataServiceRequest query = this.Queries[queryCount]; ResponseInfo responseInfo = this.RequestInfo.GetDeserializationInfo(null /*mergeOption*/); MaterializeAtom materializer = DataServiceRequest.Materialize( responseInfo, query.QueryComponents(this.RequestInfo.Model), null, this.currentOperationResponse.Headers.GetHeader(XmlConstants.HttpContentType), this.currentOperationResponse.CreateResponseMessage(), query.PayloadKind); qresponse = QueryOperationResponse.GetInstance(query.ElementType, this.currentOperationResponse.Headers, query, materializer); } } catch (ArgumentException e) { exception = e; } catch (FormatException e) { exception = e; } catch (InvalidOperationException e) { exception = e; } if (qresponse == null) { if (this.Queries != null) { // this is the normal ExecuteBatch response DataServiceRequest query = this.Queries[queryCount]; if (this.RequestInfo.IgnoreResourceNotFoundException && this.currentOperationResponse.StatusCode == HttpStatusCode.NotFound) { qresponse = QueryOperationResponse.GetInstance(query.ElementType, this.currentOperationResponse.Headers, query, MaterializeAtom.EmptyResults); } else { qresponse = QueryOperationResponse.GetInstance(query.ElementType, this.currentOperationResponse.Headers, query, MaterializeAtom.EmptyResults); qresponse.Error = exception; } } else { // This is top-level failure for SaveChanges(SaveChangesOptions.BatchWithSingleChangeset) or SaveChanges(SaveChangesOptions.BatchWithIndependentOperations) operations. // example: server doesn't support batching or number of batch objects exceeded an allowed limit. // ex could be null if the server responded to SaveChanges with an unexpected success with // response of batched GETS that did not correspond the original POST/PATCH/PUT/DELETE requests. // we expect non-null since server should have failed with a non-success code // and HandleResponse(status, ...) should generate the exception object throw exception; } } qresponse.StatusCode = (int)this.currentOperationResponse.StatusCode; queryCount++; yield return(qresponse); #endregion } else { #region Update response try { Descriptor descriptor = this.ChangedEntries[this.entryIndex]; operationCount += this.SaveResultProcessed(descriptor); if (exception != null) { throw exception; } this.HandleOperationResponseHeaders(this.currentOperationResponse.StatusCode, this.currentOperationResponse.Headers); #if DEBUG this.HandleOperationResponse(descriptor, this.currentOperationResponse.Headers, this.currentOperationResponse.StatusCode); #else this.HandleOperationResponse(descriptor, this.currentOperationResponse.Headers); #endif } catch (Exception e) { this.ChangedEntries[this.entryIndex].SaveError = e; exception = e; if (!CommonUtil.IsCatchableExceptionType(e)) { throw; } } ChangeOperationResponse changeOperationResponse = new ChangeOperationResponse(this.currentOperationResponse.Headers, this.ChangedEntries[this.entryIndex]); changeOperationResponse.StatusCode = (int)this.currentOperationResponse.StatusCode; if (exception != null) { changeOperationResponse.Error = exception; } operationCount++; this.entryIndex++; yield return(changeOperationResponse); #endregion } break; #endregion default: Error.ThrowBatchExpectedResponse(InternalError.UnexpectedBatchState); break; } } Debug.Assert(batchReader.State == ODataBatchReaderState.Completed, "unexpected batch state"); // Check for a changeset without response (first line) or GET request without response (second line). // either all saved entries must be processed or it was a batch and one of the entries has the error if ((this.Queries == null && (!changesetFound || 0 < queryCount || this.ChangedEntries.Any(o => o.ContentGeneratedForSave && o.SaveResultWasProcessed == 0) && (!this.IsBatchRequest || this.ChangedEntries.FirstOrDefault(o => o.SaveError != null) == null))) || (this.Queries != null && queryCount != this.Queries.Length)) { throw Error.InvalidOperation(Strings.Batch_IncompleteResponseCount); } } finally { // Note that this will be called only once the enumeration of all responses is finished and the Dispose // was called on the IEnumerator used for that enumeration. It is not called when the method returns, // since the compiler change this method to return the compiler-generated IEnumerable. Util.Dispose(ref this.batchMessageReader); } }