/// <summary> /// This starts the next change /// </summary> internal void CreateNextChange() { ODataRequestMessageWrapper requestMessage = null; do { IODataResponseMessage responseMsg = null; try { requestMessage = this.CreateNextRequest(); if ((requestMessage != null) || (this.entryIndex < this.ChangedEntries.Count)) { if (this.ChangedEntries[this.entryIndex].ContentGeneratedForSave) { Debug.Assert(this.ChangedEntries[this.entryIndex] is LinkDescriptor, "only expected RelatedEnd to presave"); Debug.Assert( this.ChangedEntries[this.entryIndex].State == EntityStates.Added || this.ChangedEntries[this.entryIndex].State == EntityStates.Modified, "only expected added to presave"); continue; } ContentStream contentStream = this.CreateNonBatchChangeData(this.entryIndex, requestMessage); if (contentStream != null && contentStream.Stream != null) { requestMessage.SetRequestStream(contentStream); } responseMsg = this.RequestInfo.GetSynchronousResponse(requestMessage, false); this.HandleOperationResponse(responseMsg); this.HandleOperationResponseHeaders((HttpStatusCode)responseMsg.StatusCode, new HeaderCollection(responseMsg)); this.HandleOperationResponseData(responseMsg); this.perRequest = null; } } catch (InvalidOperationException e) { e = WebUtil.GetHttpWebResponse(e, ref responseMsg); this.HandleOperationException(e, responseMsg); } finally { WebUtil.DisposeMessage(responseMsg); } // we have no more pending requests or there has been an error in the previous request and we decided not to continue // (When an error occurs and we are not going to continue on error, we call SetCompleted }while (this.entryIndex < this.ChangedEntries.Count && !this.IsCompletedInternally); Debug.Assert(this.entryIndex < this.ChangedEntries.Count || this.ChangedEntries.All(o => o.ContentGeneratedForSave), "didn't generate content for all entities/links"); }
/// <summary>cleanup work to do once the request has completed</summary> protected override void CompletedRequest() { byte[] buffer = this.asyncStreamCopyBuffer; this.asyncStreamCopyBuffer = null; if ((null != buffer) && !this.usingBuffer) { this.PutAsyncResponseStreamCopyBuffer(buffer); } if (this.responseStreamOwner) { if (null != this.outputResponseStream) { this.outputResponseStream.Position = 0; } } Debug.Assert(null != this.responseMessage || null != this.Failure || this.IsAborted, "should have response or exception"); if (null != this.responseMessage) { // we've cached off what we need, headers still accessible after close WebUtil.DisposeMessage(this.responseMessage); Version responseVersion; Exception ex = SaveResult.HandleResponse( this.RequestInfo, this.StatusCode, this.responseMessage.GetHeader(XmlConstants.HttpODataVersion), this.GetResponseStream, false, out responseVersion); if (null != ex) { this.HandleFailure(ex); } else { this.responseInfo = this.CreateResponseInfo(); } } }
/// <summary>invoked for derived classes to cleanup before callback is invoked</summary> protected override void CompletedRequest() { Debug.Assert(null != this.responseMessage || null != this.Failure, "should have response or exception"); if (null != this.responseMessage) { // Can't use DataServiceContext.HandleResponse as this request didn't necessarily go to our server // the MR could have been served by arbitrary server. InvalidOperationException failure = null; if (!WebUtil.SuccessStatusCode((HttpStatusCode)this.responseMessage.StatusCode)) { failure = SaveResult.GetResponseText( this.responseMessage.GetStream, (HttpStatusCode)this.responseMessage.StatusCode); } if (failure != null) { // we've cached off what we need, headers still accessible after close WebUtil.DisposeMessage(this.responseMessage); this.HandleFailure(failure); } } }
/// <summary>Releases all resources used by the current instance of the <see cref="T:Microsoft.OData.Client.DataServiceStreamResponse" /> class.</summary> public void Dispose() { WebUtil.DisposeMessage(this.responseMessage); }
internal void BeginCreateNextChange() { Debug.Assert(!this.IsCompletedInternally, "why being called if already completed?"); // create the memory stream required to cache the responses as we read async from the underlying http web response this.inMemoryResponseStream = new MemoryStream(); // SaveCallback can't chain synchronously completed responses, caller will loop the to next change PerRequest pereq = null; IAsyncResult asyncResult = null; do { IODataResponseMessage responseMsg = null; ODataRequestMessageWrapper requestMessage = null; try { if (this.perRequest != null) { this.SetCompleted(); Error.ThrowInternalError(InternalError.InvalidBeginNextChange); } requestMessage = this.CreateNextRequest(); // Keeping the old behavior (V1/V2) where the abortable was set to null, // if CreateNextRequest returned null. if (requestMessage == null) { this.Abortable = null; } if ((requestMessage != null) || (this.entryIndex < this.ChangedEntries.Count)) { if (this.ChangedEntries[this.entryIndex].ContentGeneratedForSave) { Debug.Assert(this.ChangedEntries[this.entryIndex] is LinkDescriptor, "only expected RelatedEnd to presave"); Debug.Assert( this.ChangedEntries[this.entryIndex].State == EntityStates.Added || this.ChangedEntries[this.entryIndex].State == EntityStates.Modified, "only expected added to presave"); continue; } this.Abortable = requestMessage; ContentStream contentStream = this.CreateNonBatchChangeData(this.entryIndex, requestMessage); this.perRequest = pereq = new PerRequest(); pereq.Request = requestMessage; AsyncStateBag asyncStateBag = new AsyncStateBag(pereq); if (contentStream == null || contentStream.Stream == null) { asyncResult = BaseAsyncResult.InvokeAsync(requestMessage.BeginGetResponse, this.AsyncEndGetResponse, asyncStateBag); } else { if (contentStream.IsKnownMemoryStream) { requestMessage.SetContentLengthHeader(); } pereq.RequestContentStream = contentStream; asyncResult = BaseAsyncResult.InvokeAsync(requestMessage.BeginGetRequestStream, this.AsyncEndGetRequestStream, asyncStateBag); } pereq.SetRequestCompletedSynchronously(asyncResult.CompletedSynchronously); this.SetCompletedSynchronously(pereq.RequestCompletedSynchronously); } else { this.SetCompleted(); if (this.CompletedSynchronously) { this.HandleCompleted(pereq); } } } catch (InvalidOperationException e) { e = WebUtil.GetHttpWebResponse(e, ref responseMsg); this.HandleOperationException(e, responseMsg); this.HandleCompleted(pereq); } finally { WebUtil.DisposeMessage(responseMsg); } // If the current request is completed synchronously, we need to call FinishCurrentChange() to process the response payload. // FinishCurrentChange() will not call BeginCreateNextChange() when the request is synchronous. // If the current request is completed asynchronously, an async thread will call FinishCurrentChange() to process the response payload. // FinishCurrentchange() will then call BeginCreateNextChange() from the async thread and we need to exit this loop. // If requestMessage = this.CreateNextRequest() returns null, we would have called this.SetCompleted() above and this.IsCompletedInternally // would be true. This means we are done processing all changed entries and we should not call this.FinishCurrentChange(). if (pereq != null && pereq.RequestCompleted && pereq.RequestCompletedSynchronously && !this.IsCompletedInternally) { Debug.Assert(requestMessage != null, "httpWebRequest != null"); this.FinishCurrentChange(pereq); } // In the condition for the do-while loop we must test for pereq.RequestCompletedSynchronously and not asyncResult.CompletedSynchronously. // pereq.RequestCompletedSynchronously is true only if all async calls completed synchronously. If we don't exit this loop when // pereq.RequestCompletedSynchronously is false, the current thread and an async thread will both re-enter BeginCreateNextChange() // and we will fail. We can only process one request at a given time. }while (((pereq == null) || (pereq.RequestCompleted && pereq.RequestCompletedSynchronously)) && !this.IsCompletedInternally); Debug.Assert((this.CompletedSynchronously && this.IsCompleted) || !this.CompletedSynchronously, "sync without complete"); Debug.Assert(this.entryIndex < this.ChangedEntries.Count || this.ChangedEntries.All(o => o.ContentGeneratedForSave), "didn't generate content for all entities/links"); }