/// <summary> /// Creates a new instance of ODataRequestMessage. This constructor is used for top level requests. /// </summary> /// <param name="requestMessage">RequestMessage that needs to be wrapped.</param> /// <param name="requestInfo">Request Info.</param> /// <param name="descriptor">Descriptor for this request.</param> protected ODataRequestMessageWrapper(DataServiceClientRequestMessage requestMessage, RequestInfo requestInfo, Descriptor descriptor) { Debug.Assert(requestMessage != null, "requestMessage != null"); Debug.Assert(requestInfo != null, "requestInfo != null"); this.requestMessage = requestMessage; this.requestInfo = requestInfo; this.Descriptor = descriptor; }
/// <summary> /// Initializes a new instance of the <see cref="BuildingRequestEventArgs"/> class. /// </summary> /// <param name="method">The method.</param> /// <param name="requestUri">The request URI.</param> /// <param name="headers">The request headers.</param> /// <param name="descriptor">Descriptor for this request; or null if there isn't one.</param> /// <param name="httpStack">The http stack.</param> internal BuildingRequestEventArgs(string method, Uri requestUri, HeaderCollection headers, Descriptor descriptor, HttpStack httpStack) { this.Method = method; this.RequestUri = requestUri; this.HeaderCollection = headers ?? new HeaderCollection(); this.ClientHttpStack = httpStack; this.Descriptor = descriptor; }
/// <summary> /// Creates a new instance of SendingRequest2EventsArgs /// </summary> /// <param name="requestMessage">request message.</param> /// <param name="descriptor">Descriptor that represents this change.</param> /// <param name="isBatchPart">True if this args represents a request within a batch, otherwise false.</param> internal SendingRequest2EventArgs(IODataRequestMessage requestMessage, Descriptor descriptor, bool isBatchPart) { this.RequestMessage = requestMessage; this.Descriptor = descriptor; this.IsBatchPart = isBatchPart; }
/// <summary> /// Asks the context to Fire the BuildingRequest event and get RequestMessageArgs. /// </summary> /// <param name="method">Http method for the request.</param> /// <param name="requestUri">Base Uri for the request.</param> /// <param name="headers">Request headers.</param> /// <param name="httpStack">HttpStack to use.</param> /// <param name="descriptor">Descriptor for the request, if there is one.</param> /// <returns>A new RequestMessageArgs object for building the request message.</returns> internal BuildingRequestEventArgs CreateRequestArgsAndFireBuildingRequest(string method, Uri requestUri, HeaderCollection headers, HttpStack httpStack, Descriptor descriptor) { return this.Context.CreateRequestArgsAndFireBuildingRequest(method, requestUri, headers, httpStack, descriptor); }
/// <summary>flag results as being processed</summary> /// <param name="descriptor">result descriptor being processed</param> /// <returns>count of related links that were also processed</returns> protected int SaveResultProcessed(Descriptor descriptor) { // media links will be processed twice descriptor.SaveResultWasProcessed = descriptor.State; int count = 0; if (descriptor.DescriptorKind == DescriptorKind.Entity && (EntityStates.Added == descriptor.State)) { foreach (LinkDescriptor end in this.RelatedLinks((EntityDescriptor)descriptor)) { if (end.ContentGeneratedForSave) { Debug.Assert(0 == end.SaveResultWasProcessed, "this link already had a result"); end.SaveResultWasProcessed = end.State; count++; } } } return count; }
/// <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> /// <param name="statusCode">status code.</param> protected void HandleOperationResponse(Descriptor descriptor, HeaderCollection contentHeaders, HttpStatusCode statusCode)
/// <summary>Handle response to deleted entity.</summary> /// <param name="descriptor">deleted entity</param> private void HandleResponseDelete(Descriptor descriptor) { if (EntityStates.Deleted != descriptor.State) { Error.ThrowBatchUnexpectedContent(InternalError.EntityNotDeleted); } if (descriptor.DescriptorKind == DescriptorKind.Entity) { EntityDescriptor resource = (EntityDescriptor)descriptor; this.RequestInfo.EntityTracker.DetachResource(resource); } else { this.RequestInfo.EntityTracker.DetachExistingLink((LinkDescriptor)descriptor, false); } }
/// <summary>Initializes a new instance of the <see cref="T:Microsoft.OData.Client.ChangeOperationResponse" /> class. </summary> /// <param name="headers">HTTP headers</param> /// <param name="descriptor">response object containing information about resources that got changed.</param> internal ChangeOperationResponse(HeaderCollection headers, Descriptor descriptor) : base(headers) { Debug.Assert(descriptor != null, "descriptor != null"); this.descriptor = descriptor; }
/// <summary> /// Loads the metadata and converts it into an EdmModel that is then used by a dataservice context /// This allows the user to use the DataServiceContext directly without having to manually pass an IEdmModel in the Format /// </summary> /// <returns>A service model to be used in format tracking</returns> internal IEdmModel LoadServiceModelFromNetwork() { HttpWebRequestMessage httpRequest; BuildingRequestEventArgs requestEventArgs = null; // test hook for injecting a network request to use instead of the default if (InjectMetadataHttpNetworkRequest != null) { httpRequest = InjectMetadataHttpNetworkRequest(); } else { requestEventArgs = new BuildingRequestEventArgs( "GET", context.GetMetadataUri(), null, null, context.HttpStack); // fire the right events if they exist to allow user to modify the request if (context.HasBuildingRequestEventHandlers) { requestEventArgs = context.CreateRequestArgsAndFireBuildingRequest( requestEventArgs.Method, requestEventArgs.RequestUri, requestEventArgs.HeaderCollection, requestEventArgs.ClientHttpStack, requestEventArgs.Descriptor); } DataServiceClientRequestMessageArgs args = new DataServiceClientRequestMessageArgs( requestEventArgs.Method, requestEventArgs.RequestUri, context.UseDefaultCredentials, context.UsePostTunneling, requestEventArgs.Headers); httpRequest = new HttpWebRequestMessage(args); } Descriptor descriptor = requestEventArgs != null ? requestEventArgs.Descriptor : null; // fire the right events if they exist if (context.HasSendingRequest2EventHandlers) { SendingRequest2EventArgs eventArgs = new SendingRequest2EventArgs( httpRequest, descriptor, false); context.FireSendingRequest2(eventArgs); } Task <IODataResponseMessage> asyncResponse = Task <IODataResponseMessage> .Factory.FromAsync(httpRequest.BeginGetResponse, httpRequest.EndGetResponse, httpRequest); IODataResponseMessage response = asyncResponse.GetAwaiter().GetResult(); ReceivingResponseEventArgs responseEvent = new ReceivingResponseEventArgs(response, descriptor); context.FireReceivingResponseEvent(responseEvent); using (StreamReader streamReader = new StreamReader(response.GetStream())) using (XmlReader xmlReader = XmlReader.Create(streamReader)) { return(CsdlReader.Parse(xmlReader)); } }
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> /// Generate the batch request for all changes to save. /// </summary> /// <returns>Returns the instance of ODataRequestMessage containing all the headers and payload for the batch request.</returns> private ODataRequestMessageWrapper GenerateBatchRequest() { if (this.ChangedEntries.Count == 0 && this.Queries == null) { this.SetCompleted(); return(null); } ODataRequestMessageWrapper batchRequestMessage = this.CreateBatchRequest(); // we need to fire request after the headers have been written, but before we write the payload batchRequestMessage.FireSendingRequest2(null); using (ODataMessageWriter messageWriter = Serializer.CreateMessageWriter(batchRequestMessage, this.RequestInfo, false /*isParameterPayload*/)) { this.batchWriter = messageWriter.CreateODataBatchWriter(); this.batchWriter.WriteStartBatch(); if (this.Queries != null) { foreach (DataServiceRequest query in this.Queries) { QueryComponents queryComponents = query.QueryComponents(this.RequestInfo.Model); Uri requestUri = this.RequestInfo.BaseUriResolver.GetOrCreateAbsoluteUri(queryComponents.Uri); Debug.Assert(requestUri != null, "request uri is null"); Debug.Assert(requestUri.IsAbsoluteUri, "request uri is not absolute uri"); HeaderCollection headers = new HeaderCollection(); headers.SetRequestVersion(queryComponents.Version, this.RequestInfo.MaxProtocolVersionAsVersion); this.RequestInfo.Format.SetRequestAcceptHeaderForQuery(headers, queryComponents); ODataRequestMessageWrapper batchOperationRequestMessage = this.CreateRequestMessage(XmlConstants.HttpMethodGet, requestUri, headers, this.RequestInfo.HttpStack, null /*descriptor*/, null /*contentId*/); batchOperationRequestMessage.FireSendingEventHandlers(null /*descriptor*/); } } else if (0 < this.ChangedEntries.Count) { if (Util.IsBatchWithSingleChangeset(this.Options)) { this.batchWriter.WriteStartChangeset(); } var model = this.RequestInfo.Model; for (int i = 0; i < this.ChangedEntries.Count; ++i) { if (Util.IsBatchWithIndependentOperations(this.Options)) { this.batchWriter.WriteStartChangeset(); } Descriptor descriptor = this.ChangedEntries[i]; if (descriptor.ContentGeneratedForSave) { continue; } EntityDescriptor entityDescriptor = descriptor as EntityDescriptor; if (descriptor.DescriptorKind == DescriptorKind.Entity) { if (entityDescriptor.State == EntityStates.Added) { // We don't support adding MLE/MR in batch mode ClientTypeAnnotation type = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityDescriptor.Entity.GetType())); if (type.IsMediaLinkEntry || entityDescriptor.IsMediaLinkEntry) { throw Error.NotSupported(Strings.Context_BatchNotSupportedForMediaLink); } } else if (entityDescriptor.State == EntityStates.Unchanged || entityDescriptor.State == EntityStates.Modified) { // We don't support PUT for the MR in batch mode // It's OK to PUT the MLE alone inside a batch mode though if (entityDescriptor.SaveStream != null) { throw Error.NotSupported(Strings.Context_BatchNotSupportedForMediaLink); } } } else if (descriptor.DescriptorKind == DescriptorKind.NamedStream) { // Similar to MR, we do not support adding named streams in batch mode. throw Error.NotSupported(Strings.Context_BatchNotSupportedForNamedStreams); } ODataRequestMessageWrapper operationRequestMessage; if (descriptor.DescriptorKind == DescriptorKind.Entity) { operationRequestMessage = this.CreateRequest(entityDescriptor); } else { operationRequestMessage = this.CreateRequest((LinkDescriptor)descriptor); } // we need to fire request after the headers have been written, but before we write the payload operationRequestMessage.FireSendingRequest2(descriptor); this.CreateChangeData(i, operationRequestMessage); if (Util.IsBatchWithIndependentOperations(this.Options)) { this.batchWriter.WriteEndChangeset(); } } if (Util.IsBatchWithSingleChangeset(this.Options)) { this.batchWriter.WriteEndChangeset(); } } this.batchWriter.WriteEndBatch(); this.batchWriter.Flush(); } Debug.Assert(this.ChangedEntries.All(o => o.ContentGeneratedForSave), "didn't generated content for all entities/links"); return(batchRequestMessage); }
/// <summary> /// Returns the request message to write the headers and payload into. /// </summary> /// <param name="method">Http method for the request.</param> /// <param name="requestUri">Base Uri for the request.</param> /// <param name="headers">Request headers.</param> /// <param name="httpStack">HttpStack to use.</param> /// <param name="descriptor">Descriptor for the request, if there is one.</param> /// <param name="contentId">Content-ID header that could be used in batch request.</param> /// <returns>an instance of IODataRequestMessage.</returns> protected override ODataRequestMessageWrapper CreateRequestMessage(string method, Uri requestUri, HeaderCollection headers, HttpStack httpStack, Descriptor descriptor, string contentId) { BuildingRequestEventArgs args = this.RequestInfo.CreateRequestArgsAndFireBuildingRequest(method, requestUri, headers, this.RequestInfo.HttpStack, descriptor); return(ODataRequestMessageWrapper.CreateBatchPartRequestMessage(this.batchWriter, args, this.RequestInfo, contentId)); }
/// <summary> /// FireSendingRequest2 event. /// </summary> /// <param name="descriptor">Descriptor for which this request is getting generated.</param> internal void FireSendingRequest2(Descriptor descriptor) { #if DEBUG Debug.Assert(!this.fireSendingRequest2MethodCalled, "!this.fireSendingRequest2MethodCalled"); Debug.Assert(this.cachedRequestHeaders == null, "this.cachedRequestHeaders == null"); // Currently for actions there are no descriptors and hence this assert needs to be disabled. // Once the actions have descriptors, we can enable this assert. // Debug.Assert( // descriptor != null || this.requestMessage.Method == XmlConstants.HttpMethodGet || this.requestMessage.Url.AbsoluteUri.Contains("$batch"), // "For CUD operations, decriptor must be specified in every SendingRequest2 event except top level batch request"); #endif // Do we need to fire these events if someone has replaced the transport layer? Maybe no. if (this.requestInfo.HasSendingRequest2EventHandlers) { // For now, we don't think this adds a lot of value exposing on the public DataServiceClientRequestMessage class // In future, we can always add it if customers ask for this. Erring on the side of keeping the public // class simple. var httpWebRequestMessage = this.requestMessage as HttpWebRequestMessage; if (httpWebRequestMessage != null) { // For now we are saying that anyone who implements the transport layer do not get a chance to fire // SendingRequest yet at all. That does not seem that bad. httpWebRequestMessage.BeforeSendingRequest2Event(); } try { this.requestInfo.FireSendingRequest2(new SendingRequest2EventArgs(this.requestMessage, descriptor, this.IsBatchPartRequest)); } finally { if (httpWebRequestMessage != null) { httpWebRequestMessage.AfterSendingRequest2Event(); } } } #if DEBUG else { // Cache the headers if there is no sending request 2 event subscribers. At the time of GetRequestStream // or GetSyncronousResponse, we will validate that the headers are the same. this.cachedRequestHeaders = new HeaderCollection(); foreach (var header in this.requestMessage.Headers) { this.cachedRequestHeaders.SetHeader(header.Key, header.Value); } } this.fireSendingRequest2MethodCalled = true; #endif }
/// <summary> /// Creates a new instance of ODataOuterRequestMessage. /// </summary> /// <param name="requestMessage">DataServiceClientRequestMessage instance.</param> /// <param name="requestInfo">RequestInfo instance.</param> /// <param name="descriptor">Descriptor for this request.</param> internal TopLevelRequestMessageWrapper(DataServiceClientRequestMessage requestMessage, RequestInfo requestInfo, Descriptor descriptor) : base(requestMessage, requestInfo, descriptor) { // Wrapper for the top-level request messages which caches the request stream as it is written. In order to keep the sync and non-async // code the same, we write all requests into an cached stream and then copy it to the underlying network stream in sync or async manner. this.messageWithCachedStream = new RequestMessageWithCachedStream(this.requestMessage); }
/// <summary> /// Initializes a new instance of the <see cref="ReceivingResponseEventArgs"/> class for a /// non-batch or top level $batch response. /// </summary> /// <param name="responseMessage">The response message the client is receiving.</param> /// <param name="descriptor">Descriptor for the request that the client is receiving the response for.</param> public ReceivingResponseEventArgs(IODataResponseMessage responseMessage, Descriptor descriptor) : this(responseMessage, descriptor, false) { }
/// <summary> /// Returns the request message to write the headers and payload into. /// </summary> /// <param name="method">Http method for the request.</param> /// <param name="requestUri">Base Uri for the request.</param> /// <param name="headers">Request headers.</param> /// <param name="httpStack">HttpStack to use.</param> /// <param name="descriptor">Descriptor for the request, if there is one.</param> /// <param name="contentId">Content-ID header that could be used in batch request.</param> /// <returns>an instance of IODataRequestMessage.</returns> protected override ODataRequestMessageWrapper CreateRequestMessage(string method, Uri requestUri, HeaderCollection headers, HttpStack httpStack, Descriptor descriptor, string contentId) { BuildingRequestEventArgs args = this.RequestInfo.CreateRequestArgsAndFireBuildingRequest(method, requestUri, headers, this.RequestInfo.HttpStack, descriptor); return ODataRequestMessageWrapper.CreateBatchPartRequestMessage(this.batchWriter, args, this.RequestInfo, contentId); }
/// <summary> /// Initializes a new instance of the <see cref="ReceivingResponseEventArgs"/> class. /// </summary> /// <param name="responseMessage">The response message the client is receiving.</param> /// <param name="descriptor">Descriptor for the request that the client is receiving the response for.</param> /// <param name="isBatchPart">Indicates if this response is to an inner batch query or operation.</param> public ReceivingResponseEventArgs(IODataResponseMessage responseMessage, Descriptor descriptor, bool isBatchPart) { this.ResponseMessage = responseMessage; this.Descriptor = descriptor; this.IsBatchPart = isBatchPart; }
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> /// increment the resource change for sorting during submit changes /// </summary> /// <param name="descriptor">the resource to update the change order</param> internal void IncrementChange(Descriptor descriptor) { descriptor.ChangeOrder = ++this.nextChange; }
/// <summary> /// Returns the request message to write the headers and payload into. /// </summary> /// <param name="method">Http method for the request.</param> /// <param name="requestUri">Base Uri for the request.</param> /// <param name="headers">Request headers.</param> /// <param name="httpStack">HttpStack to use.</param> /// <param name="descriptor">Descriptor for the request, if there is one.</param> /// <param name="contentId">Content-ID header that could be used in batch request.</param> /// <returns>an instance of IODataRequestMessage.</returns> protected abstract ODataRequestMessageWrapper CreateRequestMessage(string method, Uri requestUri, HeaderCollection headers, HttpStack httpStack, Descriptor descriptor, string contentId);
/// <summary> /// Asks the context to Fire the BuildingRequest event and get RequestMessageArgs. /// </summary> /// <param name="method">Http method for the request.</param> /// <param name="requestUri">Base Uri for the request.</param> /// <param name="headers">Request headers.</param> /// <param name="httpStack">HttpStack to use.</param> /// <param name="descriptor">Descriptor for the request, if there is one.</param> /// <returns>A new RequestMessageArgs object for building the request message.</returns> internal BuildingRequestEventArgs CreateRequestArgsAndFireBuildingRequest(string method, Uri requestUri, HeaderCollection headers, HttpStack httpStack, Descriptor descriptor) { return(this.Context.CreateRequestArgsAndFireBuildingRequest(method, requestUri, headers, httpStack, descriptor)); }
/// <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. }
/// <summary> /// Fires the following events, in order /// 1. WritingRequest /// 2. SendingRequest2 /// </summary> /// <param name="descriptor">Descriptor for which this request is getting generated.</param> internal void FireSendingEventHandlers(Descriptor descriptor) { this.FireSendingRequest2(descriptor); }
/// <summary> /// Returns the request message to write the headers and payload into. /// </summary> /// <param name="method">Http method for the request.</param> /// <param name="requestUri">Base Uri for the request.</param> /// <param name="headers">Request headers.</param> /// <param name="httpStack">HttpStack to use.</param> /// <param name="descriptor">Descriptor for the request, if there is one.</param> /// <returns>an instance of IODataRequestMessage.</returns> protected ODataRequestMessageWrapper CreateTopLevelRequest(string method, Uri requestUri, HeaderCollection headers, HttpStack httpStack, Descriptor descriptor) { BuildingRequestEventArgs args = this.RequestInfo.CreateRequestArgsAndFireBuildingRequest(method, requestUri, headers, httpStack, descriptor); return this.RequestInfo.WriteHelper.CreateRequestMessage(args); }
private static BuildingRequestEventArgs SetupTest(string method, Uri uri, HeaderCollection headers, Descriptor descriptor) { return new BuildingRequestEventArgs(method, uri, headers, descriptor, HttpStack.Auto); }
/// <summary> /// Creates a new instance of InnerBatchRequestMessageWrapper; /// </summary> /// <param name="clientRequestMessage">Instance of DataServiceClientRequestMessage that represents this request.</param> /// <param name="odataRequestMessage">Instance of IODataRequestMessage created by ODataLib.</param> /// <param name="requestInfo">RequestInfo instance.</param> /// <param name="descriptor">Descriptor for this request.</param> internal InnerBatchRequestMessageWrapper(DataServiceClientRequestMessage clientRequestMessage, IODataRequestMessage odataRequestMessage, RequestInfo requestInfo, Descriptor descriptor) : base(clientRequestMessage, requestInfo, descriptor) { this.innerBatchRequestMessage = odataRequestMessage; }
/// <summary> /// Returns the request message to write the headers and payload into. /// </summary> /// <param name="method">Http method for the request.</param> /// <param name="requestUri">Base Uri for the request.</param> /// <param name="headers">Request headers.</param> /// <param name="httpStack">HttpStack to use.</param> /// <param name="descriptor">Descriptor for the request, if there is one.</param> /// <param name="contentId">Content-ID header that could be used in batch request.</param> /// <returns>an instance of IODataRequestMessage.</returns> protected override ODataRequestMessageWrapper CreateRequestMessage(string method, Uri requestUri, HeaderCollection headers, HttpStack httpStack, Descriptor descriptor, string contentId) { return this.CreateTopLevelRequest(method, requestUri, headers, httpStack, descriptor); }
/// <summary> /// Creates HTTP request for the media resource (MR) /// </summary> /// <param name="requestUri">The URI to request</param> /// <param name="method">The HTTP method to use (POST or PUT)</param> /// <param name="version">version to be sent in the DSV request header.</param> /// <param name="sendChunked">Send the request using chunked encoding to avoid buffering.</param> /// <param name="applyResponsePreference">If the response preference setting should be applied to the request /// (basically means if the response is expected to contain an entity or not).</param> /// <param name="headers">Collection of request headers</param> /// <param name="descriptor">Descriptor for this media resource request.</param> /// <returns>An instance of ODataRequestMessage.</returns> private ODataRequestMessageWrapper CreateMediaResourceRequest(Uri requestUri, string method, Version version, bool sendChunked, bool applyResponsePreference, HeaderCollection headers, Descriptor descriptor) { headers.SetHeaderIfUnset(XmlConstants.HttpContentType, XmlConstants.MimeAny); if (applyResponsePreference) { ApplyPreferences(headers, method, this.RequestInfo.AddAndUpdateResponsePreference, ref version); } // Set the request DSV and request MDSV headers headers.SetRequestVersion(version, this.RequestInfo.MaxProtocolVersionAsVersion); this.RequestInfo.Format.SetRequestAcceptHeader(headers); ODataRequestMessageWrapper requestMessage = this.CreateRequestMessage(method, requestUri, headers, this.RequestInfo.HttpStack, descriptor, null /*contentId*/); // TODO: since under the hood this is a header, we should put it in our dictionary of headers that the user gets in BuildingRequest // and later on handle the setting of the strongly named property on the underlying request requestMessage.SendChunked = sendChunked; return requestMessage; }
/// <summary> /// Constructor /// </summary> /// <param name="descriptor">descriptor whose response is getting processed.</param> /// <param name="headers">headers</param> /// <param name="statusCode">status code</param> /// <param name="responseVersion">Parsed response OData-Version header.</param> /// <param name="entry">atom entry, if there is a non-error response payload.</param> /// <param name="exception">exception, if the request threw an exception.</param> internal CachedResponse(Descriptor descriptor, HeaderCollection headers, HttpStatusCode statusCode, Version responseVersion, MaterializerEntry entry, Exception exception) { Debug.Assert(descriptor != null, "descriptor != null"); Debug.Assert(headers != null, "headers != null"); Debug.Assert(entry == null || (exception == null && descriptor.DescriptorKind == DescriptorKind.Entity), "if entry is specified, exception cannot be specified and entry must be a resource, since we expect responses only for entities"); this.Descriptor = descriptor; this.MaterializerEntry = entry; this.Exception = exception; this.Headers = headers; this.StatusCode = statusCode; this.Version = responseVersion; }
private void VerifyDescriptor(DescriptorData expected, Descriptor actual, int responseOrder) { EntityDescriptorData entityDescriptorData = expected as EntityDescriptorData; LinkDescriptorData linkDescriptorData = expected as LinkDescriptorData; StreamDescriptorData streamDescriptorData = expected as StreamDescriptorData; if (entityDescriptorData != null) { EntityDescriptor entityDescriptor = actual as EntityDescriptor; this.Assert.IsNotNull(entityDescriptor, GetVerificationFailureMessage(responseOrder, "Unexpected descriptor type:\r\nExpected: {0}\r\nActual: {1}\r\nExpected descriptor data: {2}.", typeof(EntityDescriptor).Name, actual.GetType().Name, entityDescriptorData)); this.Assert.AreSame( entityDescriptorData.Entity, entityDescriptor.Entity, GetVerificationFailureMessage(responseOrder, "Entity verification failed for the entity descriptor data: {0}.", expected)); } else if (linkDescriptorData != null) { LinkDescriptor linkDescriptor = actual as LinkDescriptor; this.Assert.IsNotNull(linkDescriptor, GetVerificationFailureMessage(responseOrder, "Unexpected descriptor type:\r\nExpected: {0}\r\nActual: {1}\r\nExpected descriptor data: {2}.", typeof(LinkDescriptor).Name, actual.GetType().Name, linkDescriptorData)); bool notMatch = linkDescriptorData.SourceDescriptor.Entity != linkDescriptor.Source || (linkDescriptorData.TargetDescriptor == null && linkDescriptor.Target != null) || (linkDescriptorData.TargetDescriptor != null && linkDescriptorData.TargetDescriptor.Entity != linkDescriptor.Target) || linkDescriptorData.SourcePropertyName != linkDescriptor.SourceProperty; this.Assert.IsFalse(notMatch, GetVerificationFailureMessage(responseOrder, "Link verification failed.\r\nExpected: {0}\r\nActual: {1}", linkDescriptorData, linkDescriptor.ToTraceString())); } else { #if WINDOWS_PHONE throw new TaupoNotSupportedException("StreamDescriptors are not supported on Windows Phone"); #else ExceptionUtilities.CheckObjectNotNull(streamDescriptorData, "Expected was not an entity, link, or stream descriptor: {0}", expected); StreamDescriptor streamDescriptor = actual as StreamDescriptor; this.Assert.IsNotNull(streamDescriptor, GetVerificationFailureMessage(responseOrder, "Unexpected descriptor type:\r\nExpected: {0}\r\nActual: {1}\r\nExpected descriptor data: {2}.", typeof(StreamDescriptor).Name, actual.GetType().Name, streamDescriptorData)); this.Assert.AreEqual(streamDescriptorData.State.ToProductEnum(), streamDescriptor.State, GetVerificationFailureMessage(responseOrder, "Stream descriptor state verification failed.")); this.Assert.AreEqual(streamDescriptorData.Name, streamDescriptor.StreamLink.Name, GetVerificationFailureMessage(responseOrder, "Stream descriptor name verification failed.")); this.Assert.AreEqual(streamDescriptorData.ETag, streamDescriptor.StreamLink.ETag, GetVerificationFailureMessage(responseOrder, "Stream descriptor etag verification failed.")); this.Assert.AreEqual(streamDescriptorData.ContentType, streamDescriptor.StreamLink.ContentType, GetVerificationFailureMessage(responseOrder, "Stream descriptor content type verification failed.")); this.Assert.AreEqual(streamDescriptorData.EditLink, streamDescriptor.StreamLink.EditLink, GetVerificationFailureMessage(responseOrder, "Stream descriptor edit link verification failed.")); this.Assert.AreEqual(streamDescriptorData.SelfLink, streamDescriptor.StreamLink.SelfLink, GetVerificationFailureMessage(responseOrder, "Stream descriptor self link verification failed.")); #endif } }
/// <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); } }