/// <summary> /// Handle operation response /// </summary> /// <param name="descriptor">descriptor whose response is getting processed.</param> /// <param name="contentHeaders">content headers as returned in the response.</param> protected void HandleOperationResponse(Descriptor descriptor, HeaderCollection contentHeaders) #endif { EntityStates streamState = EntityStates.Unchanged; if (descriptor.DescriptorKind == DescriptorKind.Entity) { EntityDescriptor entityDescriptor = (EntityDescriptor)descriptor; streamState = entityDescriptor.StreamState; #if DEBUG if (entityDescriptor.StreamState == EntityStates.Added) { // We do not depend anywhere for the status code to be Created (201). Hence changing the assert from checking for a specific status code // to just checking for success status code. Debug.Assert( WebUtil.SuccessStatusCode(statusCode) && entityDescriptor.State == EntityStates.Modified && entityDescriptor.IsMediaLinkEntry, "WebUtil.SuccessStatusCode(statusCode) && descriptor.State == EntityStates.Modified && descriptor.IsMediaLinkEntry -- Processing Post MR"); } else if (entityDescriptor.StreamState == EntityStates.Modified) { // We do not depend anywhere for the status code to be Created (201). Hence changing the assert from checking for a specific status code // to just checking for success status code. Debug.Assert( WebUtil.SuccessStatusCode(statusCode) && entityDescriptor.IsMediaLinkEntry, "WebUtil.SuccessStatusCode(statusCode) && descriptor.IsMediaLinkEntry -- Processing Put MR"); } // if the entity is added state or modified state with patch requests if (streamState == EntityStates.Added || descriptor.State == EntityStates.Added || (descriptor.State == EntityStates.Modified && !Util.IsFlagSet(this.Options, SaveChangesOptions.ReplaceOnUpdate))) { string location; string odataEntityId; contentHeaders.TryGetHeader(XmlConstants.HttpResponseLocation, out location); contentHeaders.TryGetHeader(XmlConstants.HttpODataEntityId, out odataEntityId); Debug.Assert(location == null || location == entityDescriptor.GetLatestEditLink().AbsoluteUri, "edit link must already be set to location header"); Debug.Assert((location == null && odataEntityId == null) || (odataEntityId ?? location) == UriUtil.UriToString(entityDescriptor.GetLatestIdentity()), "Identity must already be set"); } #endif } if (streamState == EntityStates.Added || descriptor.State == EntityStates.Added) { this.HandleResponsePost(descriptor, contentHeaders); } else if (streamState == EntityStates.Modified || descriptor.State == EntityStates.Modified) { this.HandleResponsePut(descriptor, contentHeaders); } else if (descriptor.State == EntityStates.Deleted) { this.HandleResponseDelete(descriptor); } // else condition is not interesting here and we intentionally do nothing. }
private void HandleResponsePut(Descriptor descriptor, HeaderCollection responseHeaders) { Debug.Assert(descriptor != null, "descriptor != null"); if (descriptor.DescriptorKind == DescriptorKind.Entity) { string etag; responseHeaders.TryGetHeader(XmlConstants.HttpResponseETag, out etag); EntityDescriptor entityDescriptor = (EntityDescriptor)descriptor; // Only process the response if the resource is an entity resource and process update response is set to true if (this.ProcessResponsePayload) { this.MaterializeResponse(entityDescriptor, this.CreateResponseInfo(entityDescriptor), etag); } else { if (EntityStates.Modified != entityDescriptor.State && EntityStates.Modified != entityDescriptor.StreamState) { Error.ThrowBatchUnexpectedContent(InternalError.EntryNotModified); } // We MUST process the MR before the MLE since we always issue the requests in that order. if (entityDescriptor.StreamState == EntityStates.Modified) { entityDescriptor.StreamETag = etag; entityDescriptor.StreamState = EntityStates.Unchanged; } else { Debug.Assert(entityDescriptor.State == EntityStates.Modified, "descriptor.State == EntityStates.Modified"); entityDescriptor.ETag = etag; entityDescriptor.State = EntityStates.Unchanged; entityDescriptor.PropertiesToSerialize.Clear(); } } } else if (descriptor.DescriptorKind == DescriptorKind.Link) { if ((EntityStates.Added == descriptor.State) || (EntityStates.Modified == descriptor.State)) { descriptor.State = EntityStates.Unchanged; } else if (EntityStates.Detached != descriptor.State) { // this link may have been previously detached by a detaching entity Error.ThrowBatchUnexpectedContent(InternalError.LinkBadState); } } else { Debug.Assert(descriptor.DescriptorKind == DescriptorKind.NamedStream, "it must be named stream"); Debug.Assert(descriptor.State == EntityStates.Modified, "named stream must only be in modified state"); descriptor.State = EntityStates.Unchanged; StreamDescriptor streamDescriptor = (StreamDescriptor)descriptor; // The named stream has been updated, so the old ETag value is stale. Replace // it with the new value or clear it if no value was specified. string etag; responseHeaders.TryGetHeader(XmlConstants.HttpResponseETag, out etag); streamDescriptor.ETag = etag; } }
/// <summary>operation with HttpWebResponse</summary> /// <param name="statusCode">status code of the response.</param> /// <param name="headers">response headers.</param> protected void HandleOperationResponseHeaders(HttpStatusCode statusCode, HeaderCollection headers) { Descriptor descriptor = this.ChangedEntries[this.entryIndex]; // in the first pass, the http response is packaged into a batch response (which is then processed in second pass). // in this first pass, (all added entities and first call of modified media link entities) update their edit location // added entities - so entities that have not sent content yet w/ reference links can inline those reference links in their payload // media entities - because they can change edit location which is then necessary for second call that includes property content if (descriptor.DescriptorKind == DescriptorKind.Entity) { EntityDescriptor entityDescriptor = (EntityDescriptor)descriptor; Debug.Assert(this.streamRequestKind != StreamRequestKind.PostMediaResource || descriptor.State == EntityStates.Modified, "For the POST MR, the entity state must be modified"); // For POST and PATCH scenarios if (descriptor.State == EntityStates.Added || this.streamRequestKind == StreamRequestKind.PostMediaResource || !Util.IsFlagSet(this.Options, SaveChangesOptions.ReplaceOnUpdate)) { if (WebUtil.SuccessStatusCode(statusCode)) { string location; string odataEntityId; Uri editLink = null; headers.TryGetHeader(XmlConstants.HttpResponseLocation, out location); headers.TryGetHeader(XmlConstants.HttpODataEntityId, out odataEntityId); if (location != null) { // Verify the location header is an absolute uri editLink = WebUtil.ValidateLocationHeader(location); } else if (descriptor.State == EntityStates.Added || this.streamRequestKind == StreamRequestKind.PostMediaResource) { // For POST scenarios, location header must be specified. throw Error.NotSupported(Strings.Deserialize_NoLocationHeader); } // Verify the id value if present. Otherwise we should use the location header // as identity. This was done to avoid breaking change, since in V1/V2, we used // to do this. Uri odataId = null; if (odataEntityId != null) { odataId = WebUtil.ValidateIdentityValue(odataEntityId); if (location == null) { throw Error.NotSupported(Strings.Context_BothLocationAndIdMustBeSpecified); } } else { // we already verified that the location must be an absolute uri odataId = UriUtil.CreateUri(location, UriKind.Absolute); } if (null != editLink) { this.RequestInfo.EntityTracker.AttachLocation(entityDescriptor.Entity, odataId, editLink); } } } if (this.streamRequestKind != StreamRequestKind.None) { if (!WebUtil.SuccessStatusCode(statusCode)) { // If the request failed and it was the MR request we should not try to send the PUT MLE after it // for one we don't have the location to send it to (if it was POST MR) if (this.streamRequestKind == StreamRequestKind.PostMediaResource) { // If this was the POST MR it means we tried to add the entity. Now its state is Modified but we need // to revert back to Added so that user can retry by calling SaveChanges again. Debug.Assert(descriptor.State == EntityStates.Modified, "Entity state should be set to Modified once we've sent the POST MR"); descriptor.State = EntityStates.Added; } // Just reset the streamRequestKind flag - that means that we will not try to PUT the MLE and instead skip over // to the next change (if we are to ignore errors that is) this.streamRequestKind = StreamRequestKind.None; // And we also need to mark it such that we generated the save content (which we did before the POST request in fact) // to workaround the fact that we use the same descriptor object to track two requests. descriptor.ContentGeneratedForSave = true; } else if (this.streamRequestKind == StreamRequestKind.PostMediaResource) { // We just finished a POST MR request and the PUT MLE coming immediately after it will // need the new etag value from the server to succeed. string etag; if (headers.TryGetHeader(XmlConstants.HttpResponseETag, out etag)) { entityDescriptor.ETag = etag; } // else is not interesting and we intentionally do nothing. } } } }
/// <summary>Handle changeset response.</summary> /// <param name="descriptor">descriptor whose response is getting handled.</param> /// <param name="contentHeaders">response headers.</param> private void HandleResponsePost(Descriptor descriptor, HeaderCollection contentHeaders) { if (descriptor.DescriptorKind == DescriptorKind.Entity) { string etag; contentHeaders.TryGetHeader(XmlConstants.HttpResponseETag, out etag); this.HandleResponsePost((EntityDescriptor)descriptor, etag); } else { HandleResponsePost((LinkDescriptor)descriptor); } }