public void InitialHeadersComesFromConstructor() { var headers = new HeaderCollection(); headers.SetHeader("Header1", "Value1"); headers.SetHeader("Header #2", "A Second *value*"); SetupTest("ABCD", new Uri("https://www.example.com/odata.svc/"), headers).Headers.Should().Contain("Header1", "Value1").And.Contain("Header #2", "A Second *value*"); }
/// <summary> /// Instantiates a new Serializer class and calls WriteEntry method on it. /// </summary> /// <param name="dataServiceContext"></param> /// <returns></returns> private static Person SetupSerializerAndCallWriteEntry(DataServiceContext dataServiceContext) { Person person = new Person(); Address address = new Address(); Car car1 = new Car(); person.Cars.Add(car1); person.HomeAddress = address; dataServiceContext.AttachTo("Cars", car1); dataServiceContext.AttachTo("Addresses", address); var requestInfo = new RequestInfo(dataServiceContext); var serializer = new Serializer(requestInfo); var headers = new HeaderCollection(); var clientModel = new ClientEdmModel(ODataProtocolVersion.V4); var entityDescriptor = new EntityDescriptor(clientModel); entityDescriptor.State = EntityStates.Added; entityDescriptor.Entity = person; var requestMessageArgs = new BuildingRequestEventArgs("POST", new Uri("http://www.foo.com/Northwind"), headers, entityDescriptor, HttpStack.Auto); var linkDescriptors = new LinkDescriptor[] { new LinkDescriptor(person, "Cars", car1, clientModel), new LinkDescriptor(person, "HomeAddress", address, clientModel) }; var odataRequestMessageWrapper = ODataRequestMessageWrapper.CreateRequestMessageWrapper(requestMessageArgs, requestInfo); serializer.WriteEntry(entityDescriptor, linkDescriptors, odataRequestMessageWrapper); return person; }
public HttpWebResponseMessage(HttpWebResponse httpResponse) { Util.CheckArgumentNull(httpResponse, "httpResponse"); this.headers = new HeaderCollection(httpResponse.Headers); this.statusCode = (int)httpResponse.StatusCode; this.getResponseStream = httpResponse.GetResponseStream; this.httpWebResponse = httpResponse; }
/// <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> /// Constructor. /// </summary> /// <param name="headers">The headers.</param> /// <param name="statusCode">The status code.</param> /// <param name="getResponseStream">A function returning the response stream.</param> internal HttpWebResponseMessage(HeaderCollection headers, int statusCode, Func<Stream> getResponseStream) { Debug.Assert(headers != null, "headers != null"); Debug.Assert(getResponseStream != null, "getResponseStream != null"); this.headers = headers; this.statusCode = statusCode; this.getResponseStream = getResponseStream; }
/// <summary> /// Constructor. /// </summary> /// <param name="headers">The headers.</param> /// <param name="statusCode">The status code.</param> /// <param name="getResponseStream">A function returning the response stream.</param> public HttpWebResponseMessage(IDictionary<string, string> headers, int statusCode, Func<Stream> getResponseStream) { Debug.Assert(headers != null, "headers != null"); Debug.Assert(getResponseStream != null, "getResponseStream != null"); this.headers = new HeaderCollection(headers); this.statusCode = statusCode; this.getResponseStream = getResponseStream; }
public void HeaderDictionaryKeysShouldBeUnchanged() { var expectedKeys = GetInterestingHeaderNames().Select(InvertCase).ToList(); var headers = new WebHeaderCollection(); foreach (var header in expectedKeys) { headers[header] = "some value"; } var dictionary = new HeaderCollection(headers); dictionary.HeaderNames.Should().BeEquivalentTo(expectedKeys); }
/// <summary> /// The reason for adding this method is that we have seen a couple of asserts that we were not able to figure out why they are getting fired. /// So added this method which returns the current headers as well as cached headers as string and we display that in the assert message. /// </summary> /// <param name="currentHeaders">current header values.</param> /// <param name="cachedHeaders">cached header values.</param> /// <returns>returns a string which contains both current and cached header names.</returns> private static string GetHeaderValues(IEnumerable <KeyValuePair <string, string> > currentHeaders, HeaderCollection cachedHeaders) { StringBuilder sb = new StringBuilder(); string separator = String.Empty; sb.Append("Current Headers: "); foreach (var header in currentHeaders) { sb.Append(separator); sb.Append(header.Key); separator = ", "; } sb.Append(". Headers fired in SendingRequest: "); separator = String.Empty; foreach (string name in cachedHeaders.HeaderNames) { sb.Append(separator); sb.Append(name); separator = ", "; } return(sb.ToString()); }
/// <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 (this.ChangedEntries.Count > 0) { 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); }
public void DefaultConventionShouldNotAddAnyHeaders() { var headers = new HeaderCollection(); this.defaultConvention.AddRequiredHeaders(headers); headers.UnderlyingDictionary.Should().BeEmpty(); }
/// <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; }
/// <summary> /// This method validates that headers values are identical to what they originally were when the request was configured. /// DataServiceContext.CachedRequestHeaders is populated in the DataServiceContext.CreateGetRequest method. /// </summary> private void ValidateHeaders() { Debug.Assert(this.fireSendingRequest2MethodCalled, "In ValidateHeaders - FireSendingRequest2 method must have been called"); if (this.cachedRequestHeaders != null) { Debug.Assert(this.Headers.Count() == this.cachedRequestHeaders.Count, "The request headers count must match" + GetHeaderValues(this.Headers, this.cachedRequestHeaders)); foreach (KeyValuePair<string, string> header in this.Headers) { if (!this.cachedRequestHeaders.HasHeader(header.Key)) { Debug.Assert(false, "Missing header: " + GetHeaderValues(this.Headers, this.cachedRequestHeaders)); } Debug.Assert( header.Value == this.cachedRequestHeaders.GetHeader(header.Key), String.Format(CultureInfo.InvariantCulture, "The header '{0}' has a different value. Old Value: '{1}', Current Value: '{2}' Please make sure to set the header before SendingRequest event is fired", header.Key, header.Value, this.cachedRequestHeaders.GetHeader(header.Key))); } this.cachedRequestHeaders = null; } }
/// <summary> /// Synchronizely get the query set count from the server by executing the $count=value query /// </summary> /// <param name="context">The context</param> /// <returns>The server side count of the query set</returns> internal long GetQuerySetCount(DataServiceContext context) { Debug.Assert(null != context, "context is null"); Version requestVersion = this.QueryComponents(context.Model).Version; if (requestVersion == null) { requestVersion = Util.ODataVersion4; } QueryResult response = null; QueryComponents qc = this.QueryComponents(context.Model); Uri requestUri = qc.Uri; DataServiceRequest <long> serviceRequest = new DataServiceRequest <long>(requestUri, qc, null); HeaderCollection headers = new HeaderCollection(); // Validate and set the request DSV header headers.SetRequestVersion(requestVersion, context.MaxProtocolVersionAsVersion); context.Format.SetRequestAcceptHeaderForCount(headers); string httpMethod = XmlConstants.HttpMethodGet; ODataRequestMessageWrapper request = context.CreateODataRequestMessage( context.CreateRequestArgsAndFireBuildingRequest(httpMethod, requestUri, headers, context.HttpStack, null /*descriptor*/), null /*descriptor*/); response = new QueryResult(this, Util.ExecuteMethodName, serviceRequest, request, new RequestInfo(context), null, null); try { response.ExecuteQuery(); if (HttpStatusCode.NoContent != response.StatusCode) { StreamReader sr = new StreamReader(response.GetResponseStream()); long r = -1; try { r = XmlConvert.ToInt64(sr.ReadToEnd()); } finally { sr.Close(); } return(r); } else { throw new DataServiceQueryException(Strings.DataServiceRequest_FailGetCount, response.Failure); } } catch (InvalidOperationException ex) { QueryOperationResponse operationResponse = null; operationResponse = response.GetResponse <long>(MaterializeAtom.EmptyResults); if (null != operationResponse) { operationResponse.Error = ex; throw new DataServiceQueryException(Strings.DataServiceException_GeneralError, ex, operationResponse); } throw; } }
/// <summary> /// Sets the value of the Accept header for a count request (will set it to 'multipart/mixed'). /// </summary> /// <param name="headers">The headers to modify.</param> internal void SetRequestAcceptHeaderForBatch(HeaderCollection headers) { this.SetAcceptHeaderAndCharset(headers, MimeMultiPartMixed); }
/// <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> /// Sets the value of the ContentType header on the specified links request to the appropriate value for the current format. /// </summary> /// <param name="headers">Dictionary of request headers.</param> internal void SetRequestContentTypeForLinks(HeaderCollection headers) { this.SetRequestContentTypeHeader(headers, this.ChooseMediaType(/*valueIfUsingAtom*/ MimeApplicationXml, false)); }
/// <summary> /// Sets the value of the Accept header to the appropriate value for the current format. /// </summary> /// <param name="headers">The headers to modify.</param> internal void SetRequestAcceptHeader(HeaderCollection headers) { this.SetAcceptHeaderAndCharset(headers, ChooseMediaType(false)); }
/// <summary> /// Sets the value of the Accept header for a stream request (will set it to '*/*'). /// </summary> /// <param name="headers">The headers to modify.</param> internal void SetRequestAcceptHeaderForStream(HeaderCollection headers) { this.SetAcceptHeaderAndCharset(headers, XmlConstants.MimeAny); }
/// <summary> /// Check to see if the resource to be inserted is a media descriptor, and if so /// setup a POST request for the media content first and turn the rest of /// the operation into a PUT to update the rest of the properties. /// </summary> /// <param name="entityDescriptor">The resource to check/process</param> /// <returns>An instance of ODataRequestMessage to do POST to the media resource</returns> private ODataRequestMessageWrapper CheckAndProcessMediaEntryPost(EntityDescriptor entityDescriptor) { // TODO: Revisit the design of how media link entries are handled during update ClientEdmModel model = this.RequestInfo.Model; ClientTypeAnnotation type = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityDescriptor.Entity.GetType())); if (!type.IsMediaLinkEntry && !entityDescriptor.IsMediaLinkEntry) { // this is not a media link descriptor, process normally return(null); } if (type.MediaDataMember == null && entityDescriptor.SaveStream == null) { // The entity is marked as MLE but we don't have the content property // and the user didn't set the save stream. throw Error.InvalidOperation(Strings.Context_MLEWithoutSaveStream(type.ElementTypeName)); } Debug.Assert( (type.MediaDataMember != null && entityDescriptor.SaveStream == null) || (type.MediaDataMember == null && entityDescriptor.SaveStream != null), "Only one way of specifying the MR content is allowed."); ODataRequestMessageWrapper mediaRequest = null; if (type.MediaDataMember != null) { string contentType = null; int contentLength = 0; if (type.MediaDataMember.MimeTypeProperty == null) { contentType = XmlConstants.MimeApplicationOctetStream; } else { object mimeTypeValue = type.MediaDataMember.MimeTypeProperty.GetValue(entityDescriptor.Entity); String mimeType = mimeTypeValue != null?mimeTypeValue.ToString() : null; if (String.IsNullOrEmpty(mimeType)) { throw Error.InvalidOperation( Strings.Context_NoContentTypeForMediaLink( type.ElementTypeName, type.MediaDataMember.MimeTypeProperty.PropertyName)); } contentType = mimeType; } object value = type.MediaDataMember.GetValue(entityDescriptor.Entity); if (value == null) { this.mediaResourceRequestStream = null; } else { byte[] buffer = value as byte[]; if (buffer == null) { string mime; Encoding encoding; ContentTypeUtil.ReadContentType(contentType, out mime, out encoding); if (encoding == null) { encoding = Encoding.UTF8; contentType += XmlConstants.MimeTypeUtf8Encoding; } buffer = encoding.GetBytes(ClientConvert.ToString(value)); } contentLength = buffer.Length; // Need to specify that the buffer is publicly visible as we need to access it later on this.mediaResourceRequestStream = new MemoryStream(buffer, 0, buffer.Length, false, true); } HeaderCollection headers = new HeaderCollection(); headers.SetHeader(XmlConstants.HttpContentLength, contentLength.ToString(CultureInfo.InvariantCulture)); headers.SetHeader(XmlConstants.HttpContentType, contentType); mediaRequest = this.CreateMediaResourceRequest( entityDescriptor.GetResourceUri(this.RequestInfo.BaseUriResolver, false /*queryLink*/), XmlConstants.HttpMethodPost, Util.ODataVersion4, type.MediaDataMember == null, // sendChunked true, // applyResponsePreference headers, entityDescriptor); } else { HeaderCollection headers = new HeaderCollection(); this.SetupMediaResourceRequest(headers, entityDescriptor.SaveStream, null /*etag*/); mediaRequest = this.CreateMediaResourceRequest( entityDescriptor.GetResourceUri(this.RequestInfo.BaseUriResolver, false /*queryLink*/), XmlConstants.HttpMethodPost, Util.ODataVersion4, type.MediaDataMember == null, // sendChunked true, // applyResponsePreference headers, entityDescriptor); } // Convert the insert into an update for the media link descriptor we just created // (note that the identity still needs to be fixed up on the resbox once // the response comes with the 'location' header; that happens during processing // of the response in SavedResource()) entityDescriptor.State = EntityStates.Modified; return(mediaRequest); }
/// <summary> /// Sets the value of the ContentType header on the specified links request to the appropriate value for the current format. /// </summary> /// <param name="headers">Dictionary of request headers.</param> internal void SetRequestContentTypeForLinks(HeaderCollection headers) { this.SetRequestContentTypeHeader(headers, ChooseMediaType(false)); }
/// <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> /// 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> /// 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 } }
/// <summary> /// Sets the value of the Accept header for a query. /// </summary> /// <param name="headers">The headers to modify.</param> /// <param name="components">The query components for the request.</param> internal void SetRequestAcceptHeaderForQuery(HeaderCollection headers, QueryComponents components) { this.SetAcceptHeaderAndCharset(headers, this.ChooseMediaType(/*valueIfUsingAtom*/ MimeApplicationAtomOrXml, components.HasSelectQueryOption)); }
/// <summary> /// constructor /// </summary> /// <param name="headers">HTTP headers</param> internal OperationResponse(HeaderCollection headers) { Debug.Assert(null != headers, "null headers"); this.headers = headers; }
public void RequestInfoShouldCreateTunneledPatchRequestMessagePostMethodAndPatchInHttpXMethodHeader() { bool previousPostTunnelingValue = ctx.UsePostTunneling; ctx.UsePostTunneling = true; HeaderCollection headersCollection = new HeaderCollection(); var descriptor = new EntityDescriptor(this.clientEdmModel) { ServerTypeName = this.serverTypeName, Entity = new Customer() }; var buildingRequestArgs = new BuildingRequestEventArgs("PATCH", new Uri("http://localhost/fakeService.svc/"), headersCollection, descriptor, HttpStack.Auto); var requestMessage = (HttpWebRequestMessage)testSubject.CreateRequestMessage(buildingRequestArgs); requestMessage.GetHeader(XmlConstants.HttpXMethod).Should().Be("PATCH"); requestMessage.Method.Should().Be("PATCH"); requestMessage.HttpWebRequest.Method.Should().Be("POST"); // undoing change so this is applicable only for this test. ctx.UsePostTunneling = previousPostTunnelingValue; }
/// <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> /// Sets the value of the Accept header for a batch request /// Will set it to 'multipart/mixed' for a multipart batch request /// Will set it to 'application/json' for a json batch request /// </summary> /// <param name="headers">The headers to modify.</param> internal void SetRequestAcceptHeaderForBatch(HeaderCollection headers) { bool useJsonBatch = headers.GetHeader(XmlConstants.HttpContentType).Equals(MimeApplicationJson); this.SetAcceptHeaderAndCharset(headers, useJsonBatch? MimeApplicationJson : MimeMultiPartMixed); }
/// <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> /// 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, StringComparison.Ordinal)) { 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 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> /// constructor /// </summary> /// <param name="headers">HTTP headers</param> /// <param name="query">original query</param> /// <param name="results">retrieved objects</param> internal QueryOperationResponse(HeaderCollection headers, DataServiceRequest query, MaterializeAtom results) : base(headers) { this.query = query; this.results = results; }
/// <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> /// Sets the value of the Accept header for a query. /// </summary> /// <param name="headers">The headers to modify.</param> /// <param name="components">The query components for the request.</param> internal void SetRequestAcceptHeaderForQuery(HeaderCollection headers, QueryComponents components) { this.SetAcceptHeaderAndCharset(headers, ChooseMediaType(components.HasSelectQueryOption)); }
/// <summary> /// Generate the link payload. /// </summary> /// <param name="binding">binding</param> /// <returns>An instance of ODataRequestMessage for the link request.</returns> protected ODataRequestMessageWrapper CreateRequest(LinkDescriptor binding) { Debug.Assert(null != binding, "null binding"); if (binding.ContentGeneratedForSave) { return null; } EntityDescriptor sourceEntityDescriptor = this.RequestInfo.EntityTracker.GetEntityDescriptor(binding.Source); EntityDescriptor targetEntityDescriptor = (null != binding.Target) ? this.RequestInfo.EntityTracker.GetEntityDescriptor(binding.Target) : null; // We allow the source and target to be in Added state, i.e. without identities, for batch with single changeset. if (!Util.IsBatchWithSingleChangeset(this.Options)) { ValidateLinkDescriptorSourceAndTargetHaveIdentities(binding, sourceEntityDescriptor, targetEntityDescriptor); } Debug.Assert(this.IsBatchRequest || null != sourceEntityDescriptor.GetLatestIdentity(), "missing sourceResource.Identity in non-batch"); Uri requestUri = null; LinkInfo linkInfo = null; if (sourceEntityDescriptor.TryGetLinkInfo(binding.SourceProperty, out linkInfo) && linkInfo.AssociationLink != null) { Debug.Assert(null != sourceEntityDescriptor.GetLatestIdentity(), "Source must have an identity in order to have link info"); // If there is already an Association link from the payload, use that requestUri = linkInfo.AssociationLink; } else { Uri sourceEntityUri; if (null == sourceEntityDescriptor.GetLatestIdentity()) { Debug.Assert(this.IsBatchRequest && Util.IsBatchWithSingleChangeset(this.Options), "Source must have an identity outside of batch with single changeset"); // if the source hasn't yet been inserted (because its in batch), then create a uri based on its content-ID sourceEntityUri = UriUtil.CreateUri("$" + sourceEntityDescriptor.ChangeOrder.ToString(CultureInfo.InvariantCulture), UriKind.Relative); } else { // otherwise use the edit link of the source sourceEntityUri = sourceEntityDescriptor.GetResourceUri(this.RequestInfo.BaseUriResolver, false /*queryLink*/); } // get the source property Uri string sourcePropertyUri = GetSourcePropertyUri(binding, sourceEntityDescriptor); // get the convention-based relative uri for the association Uri conventionalRelativeUri = UriUtil.CreateUri(sourcePropertyUri, UriKind.Relative); // add $ref at the end conventionalRelativeUri = UriUtil.CreateUri(UriUtil.UriToString(conventionalRelativeUri) + UriHelper.FORWARDSLASH + XmlConstants.UriLinkSegment, UriKind.Relative); // combine the association uri with the source entity uri requestUri = UriUtil.CreateUri(sourceEntityUri, conventionalRelativeUri); } // in the case of deleting a link from a collection, the key of the target must be appended requestUri = AppendTargetEntityKeyIfNeeded(requestUri, binding, targetEntityDescriptor); string method = GetLinkHttpMethod(binding); HeaderCollection headers = new HeaderCollection(); headers.SetRequestVersion(Util.ODataVersion4, this.RequestInfo.MaxProtocolVersionAsVersion); this.RequestInfo.Format.SetRequestAcceptHeader(headers); // if (EntityStates.Deleted || (EntityState.Modifed && null == TargetResource)) // then the server will fail the batch section if content type exists if ((EntityStates.Added == binding.State) || (EntityStates.Modified == binding.State && (null != binding.Target))) { this.RequestInfo.Format.SetRequestContentTypeForLinks(headers); } return this.CreateRequestMessage(method, requestUri, headers, this.RequestInfo.HttpStack, binding, this.IsBatchRequest ? binding.ChangeOrder.ToString(CultureInfo.InvariantCulture) : null); }
/// <summary> /// Sets the value of the Accept header for a count request (will set it to 'text/plain'). /// </summary> /// <param name="headers">The headers to modify.</param> internal void SetRequestAcceptHeaderForCount(HeaderCollection headers) { this.SetAcceptHeaderAndCharset(headers, XmlConstants.MimeTextPlain); }
/// <summary> /// Create ODataRequestMessage for the given entity. /// </summary> /// <param name="entityDescriptor">resource</param> /// <returns>An instance of ODataRequestMessage for the given entity.</returns> protected ODataRequestMessageWrapper CreateRequest(EntityDescriptor entityDescriptor) { Debug.Assert(null != entityDescriptor, "null entityDescriptor"); Debug.Assert(entityDescriptor.State == EntityStates.Added || entityDescriptor.State == EntityStates.Deleted || entityDescriptor.State == EntityStates.Modified, "the entity must be in one of the 3 possible states"); EntityStates state = entityDescriptor.State; Uri requestUri = entityDescriptor.GetResourceUri(this.RequestInfo.BaseUriResolver, false /*queryLink*/); Debug.Assert(null != requestUri, "request uri is null"); Debug.Assert(requestUri.IsAbsoluteUri, "request uri is not absolute uri"); ClientEdmModel model = this.RequestInfo.Model; ClientTypeAnnotation clientType = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityDescriptor.Entity.GetType())); Version requestVersion = DetermineRequestVersion(clientType); string httpMethod = this.GetHttpMethod(state, ref requestVersion); HeaderCollection headers = new HeaderCollection(); // Set the content type if (EntityStates.Deleted != entityDescriptor.State) { this.RequestInfo.Context.Format.SetRequestContentTypeForEntry(headers); } // Set IfMatch (etag) header for update and delete requests if ((EntityStates.Deleted == state) || (EntityStates.Modified == state)) { string etag = entityDescriptor.GetLatestETag(); if (etag != null) { headers.SetHeader(XmlConstants.HttpRequestIfMatch, etag); } } // Set the prefer header if required ApplyPreferences(headers, httpMethod, this.RequestInfo.AddAndUpdateResponsePreference, ref requestVersion); // Set the request DSV and request MDSV headers headers.SetRequestVersion(requestVersion, this.RequestInfo.MaxProtocolVersionAsVersion); this.RequestInfo.Format.SetRequestAcceptHeader(headers); return this.CreateRequestMessage(httpMethod, requestUri, headers, this.RequestInfo.HttpStack, entityDescriptor, this.IsBatchRequest ? entityDescriptor.ChangeOrder.ToString(CultureInfo.InvariantCulture) : null); }
/// <summary> /// Sets the value of the Content-Type header a request with operation parameters to the appropriate value for the current format. /// </summary> /// <param name="headers">Dictionary of request headers.</param> internal void SetRequestContentTypeForOperationParameters(HeaderCollection headers) { // Note: There has never been an atom or xml format for parameters. this.SetRequestContentTypeHeader(headers, MimeApplicationJsonODataLight); }
/// <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); }
/// <summary> /// constructor /// </summary> /// <param name="headers">HTTP headers</param> /// <param name="query">original query</param> /// <param name="results">retrieved objects</param> internal QueryOperationResponse(HeaderCollection headers, DataServiceRequest query, MaterializeAtom results) : base(headers, query, results) { }
/// <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> /// Creates the result object for the specified query parameters. /// </summary> /// <param name="source">The source object for the request.</param> /// <param name="context">The data service context.</param> /// <param name="callback">The AsyncCallback delegate.</param> /// <param name="state">The state object for the callback.</param> /// <param name="method">async method name at the source.</param> /// <returns>Result representing the create request. The request has not been initiated yet.</returns> private QueryResult CreateExecuteResult(object source, DataServiceContext context, AsyncCallback callback, object state, string method) { Debug.Assert(null != context, "context is null"); QueryComponents qc = this.QueryComponents(context.Model); RequestInfo requestInfo = new RequestInfo(context); Debug.Assert( string.CompareOrdinal(XmlConstants.HttpMethodPost, qc.HttpMethod) == 0 || string.CompareOrdinal(XmlConstants.HttpMethodGet, qc.HttpMethod) == 0, "Only get and post are supported in the execute pipeline, which should have been caught earlier"); if (qc.UriOperationParameters != null) { Debug.Assert(qc.UriOperationParameters.Any(), "qc.UriOperationParameters.Any()"); Serializer serializer = new Serializer(requestInfo); this.RequestUri = serializer.WriteUriOperationParametersToUri(this.RequestUri, qc.UriOperationParameters); } HeaderCollection headers = new HeaderCollection(); if (string.CompareOrdinal(XmlConstants.HttpMethodPost, qc.HttpMethod) == 0) { if (qc.BodyOperationParameters == null) { // set the content length to be 0 if there are no operation parameters. headers.SetHeader(XmlConstants.HttpContentLength, "0"); } else { context.Format.SetRequestContentTypeForOperationParameters(headers); } } // Validate and set the request DSV and MDSV header headers.SetRequestVersion(qc.Version, requestInfo.MaxProtocolVersionAsVersion); requestInfo.Format.SetRequestAcceptHeaderForQuery(headers, qc); // We currently do not have a descriptor to expose to the user for invoking something through Execute. Ideally we could expose an OperationDescriptor. ODataRequestMessageWrapper requestMessage = new RequestInfo(context).WriteHelper.CreateRequestMessage(context.CreateRequestArgsAndFireBuildingRequest(qc.HttpMethod, this.RequestUri, headers, context.HttpStack, null /*descriptor*/)); requestMessage.FireSendingRequest2(null /*descriptor*/); if (qc.BodyOperationParameters != null) { Debug.Assert(string.CompareOrdinal(XmlConstants.HttpMethodPost, qc.HttpMethod) == 0, "qc.HttpMethod == XmlConstants.HttpMethodPost"); Debug.Assert(qc.BodyOperationParameters.Any(), "unexpected body operation parameter count of zero."); Serializer serializer = new Serializer(requestInfo); serializer.WriteBodyOperationParameters(qc.BodyOperationParameters, requestMessage); // pass in the request stream so that request payload can be written to the http webrequest. return(new QueryResult(source, method, this, requestMessage, requestInfo, callback, state, requestMessage.CachedRequestStream)); } #if PORTABLELIB // Empty Memorystream will be set when posting null operation parameters return(string.CompareOrdinal(XmlConstants.HttpMethodPost, qc.HttpMethod) == 0 ? new QueryResult(source, method, this, requestMessage, requestInfo, callback, state, new ContentStream(new MemoryStream(), false /*isKnownMemoryStream*/)) : new QueryResult(source, method, this, requestMessage, requestInfo, callback, state)); #else return(new QueryResult(source, method, this, requestMessage, requestInfo, callback, state)); #endif }
/// <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> /// The reason for adding this method is that we have seen a couple of asserts that we were not able to figure out why they are getting fired. /// So added this method which returns the current headers as well as cached headers as string and we display that in the assert message. /// </summary> /// <param name="currentHeaders">current header values.</param> /// <param name="cachedHeaders">cached header values.</param> /// <returns>returns a string which contains both current and cached header names.</returns> private static string GetHeaderValues(IEnumerable<KeyValuePair<string, string>> currentHeaders, HeaderCollection cachedHeaders) { StringBuilder sb = new StringBuilder(); string separator = String.Empty; sb.Append("Current Headers: "); foreach (var header in currentHeaders) { sb.Append(separator); sb.Append(header.Key); separator = ", "; } sb.Append(". Headers fired in SendingRequest: "); separator = String.Empty; foreach (string name in cachedHeaders.HeaderNames) { sb.Append(separator); sb.Append(name); separator = ", "; } return sb.ToString(); }
/// <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> /// 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> /// Sets the value of the Accept header to the appropriate value for the current format. /// </summary> /// <param name="headers">The headers to modify.</param> internal void SetRequestAcceptHeader(HeaderCollection headers) { this.SetAcceptHeaderAndCharset(headers, this.ChooseMediaType(/*valueIfUsingAtom*/ MimeApplicationAtomOrXml, false)); }
/// <summary> /// Creates a ODataRequestMessage for batch request. /// </summary> /// <returns>Returns an instance of ODataRequestMessage for the batch request.</returns> private ODataRequestMessageWrapper CreateBatchRequest() { Uri requestUri = UriUtil.CreateUri(this.RequestInfo.BaseUriResolver.GetBaseUriWithSlash(), UriUtil.CreateUri("$batch", UriKind.Relative)); HeaderCollection headers = new HeaderCollection(); headers.SetRequestVersion(Util.ODataVersion4, this.RequestInfo.MaxProtocolVersionAsVersion); headers.SetHeader(XmlConstants.HttpContentType, CreateMultiPartMimeContentType()); this.RequestInfo.Format.SetRequestAcceptHeaderForBatch(headers); return this.CreateTopLevelRequest(XmlConstants.HttpMethodPost, requestUri, headers, this.RequestInfo.HttpStack, null /*descriptor*/); }
private static BuildingRequestEventArgs SetupTest(string method, Uri uri, HeaderCollection headers) { return new BuildingRequestEventArgs(method, uri, headers, null, HttpStack.Auto); }
/// <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); } } }
internal List<Stream> GetChangeStream() { List<Stream> stream = new List<Stream>(); HeaderCollection headers = new HeaderCollection(); headers.SetHeader("Content-Type", "application/atom+xml;odata.metadata=minimal"); for (int i = 0; i < this.ChangedEntries.Count; ++i) { ODataRequestMessageWrapper requestMessage = ODataRequestMessageWrapper.CreateRequestMessageWrapper( new BuildingRequestEventArgs("GET", new Uri("http://service.svc/randomuri"), headers, null, HttpStack.Auto), this.RequestInfo); this.CreateChangeData(i, requestMessage); stream.Add(requestMessage.CachedRequestStream.Stream); } return stream; }
/// <summary> /// Constructor. /// </summary> /// <param name="statusCode">The status code of the response.</param> /// <param name="headers">The response headers.</param> /// <param name="contentStream">An in-memory copy of the response stream.</param> public CurrentOperationResponse(HttpStatusCode statusCode, IEnumerable<KeyValuePair<string, string>> headers, MemoryStream contentStream) { Debug.Assert(headers != null, "headers != null"); Debug.Assert(contentStream != null, "contentStream != null"); Debug.Assert(contentStream.Position == 0, "The stream should have been reset to the begining."); this.statusCode = statusCode; this.contentStream = contentStream; this.headers = new HeaderCollection(); foreach (KeyValuePair<string, string> operationResponseHeader in headers) { this.headers.SetHeader(operationResponseHeader.Key, operationResponseHeader.Value); } }
/// <summary> /// Adds the required headers for the url convention. /// </summary> /// <param name="requestHeaders">The request headers to add to.</param> internal void AddRequiredHeaders(HeaderCollection requestHeaders) { this.urlConvention.AddRequiredHeaders(requestHeaders); }
public void PostTunnelingDeleteRequestShouldNotHaveContentTypeHeader() { bool previousPostTunnelingValue = ctx.UsePostTunneling; ctx.UsePostTunneling = true; HeaderCollection headersCollection = new HeaderCollection(); var descriptor = new EntityDescriptor(this.clientEdmModel) { ServerTypeName = this.serverTypeName, Entity = new Customer() }; var buildingRequestArgs = new BuildingRequestEventArgs("DELETE", new Uri("http://localhost/fakeService.svc/"), headersCollection, descriptor, HttpStack.Auto); ctx.Configurations.RequestPipeline.OnMessageCreating = (args) => { buildingRequestArgs.Headers.Keys.Should().NotContain(XmlConstants.HttpContentType); return new HttpWebRequestMessage(args); }; testSubject.CreateRequestMessage(buildingRequestArgs); // undoing change so this is applicable only for this test. ctx.UsePostTunneling = previousPostTunnelingValue; ctx.Configurations.RequestPipeline.OnMessageCreating = null; }
/// <summary>Initializes a new instance of the <see cref="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; }
public void KeyAsSegmentConventionShouldAddHeader() { var headers = new HeaderCollection(); this.keyAsSegmentConvention.AddRequiredHeaders(headers); headers.UnderlyingDictionary.Should().ContainKey(UrlConventionsConstants.UrlConventionHeaderName, "KeyAsSegment"); }
/// <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, this.useRelativeUri)); }