/// <summary>operation with exception</summary> /// <param name="e">exception object</param> /// <param name="response">response object</param> private void HandleOperationException(InvalidOperationException e, IODataResponseMessage response) { Debug.Assert(this.entryIndex >= 0 && this.entryIndex < this.ChangedEntries.Count, string.Format(System.Globalization.CultureInfo.InvariantCulture, "this.entryIndex = '{0}', this.ChangedEntries.Count = '{1}'", this.entryIndex, this.ChangedEntries.Count)); Descriptor current = this.ChangedEntries[this.entryIndex]; HeaderCollection headers = null; HttpStatusCode statusCode = HttpStatusCode.InternalServerError; Version responseVersion = null; if (response != null) { headers = new HeaderCollection(response); statusCode = (HttpStatusCode)response.StatusCode; this.HandleOperationResponseHeaders(statusCode, headers); e = BaseSaveResult.HandleResponse( this.RequestInfo, statusCode, response.GetHeader(XmlConstants.HttpODataVersion), response.GetStream, false /*throwOnFailure*/, out responseVersion); } else { headers = new HeaderCollection(); headers.SetHeader(XmlConstants.HttpContentType, XmlConstants.MimeTextPlain); // In V2 we used to merge individual responses from a call to SaveChanges() into a single batch response payload and then process that. // When we encounter an exception at this point in V2, we used to write the exception to the batch response payload and later on when we // process through the batch response, we create a DataServiceClientException for each failed operation. // For backcompat reason, we will always convert the exception type to DataServiceClientException here. Debug.Assert(e != null, "e != null"); if (e.GetType() != typeof(DataServiceClientException)) { e = new DataServiceClientException(e.Message, e); } } // For error scenarios, we never invoke the ReadingEntity event. this.cachedResponses.Add(new CachedResponse(current, headers, statusCode, responseVersion, null, e)); this.perRequest = null; this.CheckContinueOnError(); }
/// <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)); }
/// <summary> /// process the batch response /// </summary> /// <returns>an instance of the DataServiceResponse, containing individual operation responses for this batch request.</returns> private DataServiceResponse HandleBatchResponse() { bool batchMessageReaderOwned = true; try { if ((this.batchResponseMessage == null) || (this.batchResponseMessage.StatusCode == (int)HttpStatusCode.NoContent)) { // we always expect a response to our batch POST request throw Error.InvalidOperation(Strings.Batch_ExpectedResponse(1)); } Func <Stream> getResponseStream = () => this.ResponseStream; // We are not going to use the responseVersion returned from this call, as the $batch request itself doesn't apply versioning // of the responses on the root level. The responses are versioned on the part level. (Note that the version on the $batch level // is actually used to version the batch itself, but we for now we only recognize a single version so to keep it backward compatible // we don't check this here. Also note that the HandleResponse method will verify that we can support the version, that is it's // lower than the highest version we understand). Version responseVersion; BaseSaveResult.HandleResponse( this.RequestInfo, (HttpStatusCode)this.batchResponseMessage.StatusCode, // statusCode this.batchResponseMessage.GetHeader(XmlConstants.HttpODataVersion), // responseVersion getResponseStream, // getResponseStream true, // throwOnFailure out responseVersion); if (this.ResponseStream == null) { Error.ThrowBatchExpectedResponse(InternalError.NullResponseStream); } // Create the message and the message reader. this.batchResponseMessage = new HttpWebResponseMessage(new HeaderCollection(this.batchResponseMessage), this.batchResponseMessage.StatusCode, getResponseStream); ODataMessageReaderSettings messageReaderSettings = this.RequestInfo.GetDeserializationInfo(/*mergeOption*/ null).ReadHelper.CreateSettings(); // No need to pass in any model to the batch reader. this.batchMessageReader = new ODataMessageReader(this.batchResponseMessage, messageReaderSettings); ODataBatchReader batchReader; try { batchReader = this.batchMessageReader.CreateODataBatchReader(); } catch (ODataContentTypeException contentTypeException) { string mime; Encoding encoding; Exception inner = contentTypeException; ContentTypeUtil.ReadContentType(this.batchResponseMessage.GetHeader(XmlConstants.HttpContentType), out mime, out encoding); if (String.Equals(XmlConstants.MimeTextPlain, mime)) { inner = GetResponseText( this.batchResponseMessage.GetStream, (HttpStatusCode)this.batchResponseMessage.StatusCode); } throw Error.InvalidOperation(Strings.Batch_ExpectedContentType(this.batchResponseMessage.GetHeader(XmlConstants.HttpContentType)), inner); } DataServiceResponse response = this.HandleBatchResponseInternal(batchReader); // In case of successful processing of at least the beginning of the batch, the message reader is owned by the returned response // (or rather by the IEnumerable of operation responses inside it). // It will be disposed once the operation responses are enumerated (since the IEnumerator should be disposed once used). // In that case we must NOT dispose it here, since that enumeration can exist long after we return from this method. batchMessageReaderOwned = false; return(response); } catch (DataServiceRequestException) { throw; } catch (InvalidOperationException ex) { HeaderCollection headers = new HeaderCollection(this.batchResponseMessage); int statusCode = this.batchResponseMessage == null ? (int)HttpStatusCode.InternalServerError : (int)this.batchResponseMessage.StatusCode; DataServiceResponse response = new DataServiceResponse(headers, statusCode, new OperationResponse[0], this.IsBatchRequest); throw new DataServiceRequestException(Strings.DataServiceException_GeneralError, ex, response); } finally { if (batchMessageReaderOwned) { Util.Dispose(ref this.batchMessageReader); } } }
/// <summary> /// Handle the response payload. /// </summary> /// <param name="responseMsg">httpwebresponse instance.</param> /// <param name="responseStream">stream containing the response payload.</param> private void HandleOperationResponseData(IODataResponseMessage responseMsg, Stream responseStream) { Debug.Assert(this.entryIndex >= 0 && this.entryIndex < this.ChangedEntries.Count, string.Format(System.Globalization.CultureInfo.InvariantCulture, "this.entryIndex = '{0}', this.ChangedEntries.Count() = '{1}'", this.entryIndex, this.ChangedEntries.Count)); // Parse the response Descriptor current = this.ChangedEntries[this.entryIndex]; MaterializerEntry entry = default(MaterializerEntry); Version responseVersion; Exception exception = BaseSaveResult.HandleResponse(this.RequestInfo, (HttpStatusCode)responseMsg.StatusCode, responseMsg.GetHeader(XmlConstants.HttpODataVersion), () => { return(responseStream); }, false /*throwOnFailure*/, out responseVersion); var headers = new HeaderCollection(responseMsg); if (responseStream != null && current.DescriptorKind == DescriptorKind.Entity && exception == null) { // Only process the response if the current resource is an entity and it's an insert or update scenario EntityDescriptor entityDescriptor = (EntityDescriptor)current; // We were ignoring the payload for non-insert and non-update scenarios. We need to keep doing that. if (entityDescriptor.State == EntityStates.Added || entityDescriptor.StreamState == EntityStates.Added || entityDescriptor.State == EntityStates.Modified || entityDescriptor.StreamState == EntityStates.Modified) { try { ResponseInfo responseInfo = this.CreateResponseInfo(entityDescriptor); var responseMessageWrapper = new HttpWebResponseMessage( headers, responseMsg.StatusCode, () => responseStream); entry = ODataReaderEntityMaterializer.ParseSingleEntityPayload(responseMessageWrapper, responseInfo, entityDescriptor.Entity.GetType()); entityDescriptor.TransientEntityDescriptor = entry.EntityDescriptor; } catch (Exception ex) { exception = ex; if (!CommonUtil.IsCatchableExceptionType(ex)) { throw; } } } } this.cachedResponses.Add(new CachedResponse( current, headers, (HttpStatusCode)responseMsg.StatusCode, responseVersion, entry, exception)); if (exception != null) { current.SaveError = exception; // DEVNOTE(pqian): // There are two possible scenario here: // 1. We are in the sync code path, and there's an in stream error on the server side, or there are bad xml thrown // 2. We are in the async code path, there's a error thrown on the server side (any error) // Ideally, we need to check whether we want to continue to the next changeset. (Call this.CheckContinueOnError) // However, in V1/V2, we did not do this. Thus we will always continue on error on these scenarios } }