/// <summary> /// Initializes a new dummy host for the batch request. /// This host represents a single operation in the batch. /// </summary> /// <param name="absoluteServiceUri">Absolute Uri to the service.</param> /// <param name="operationMessage">The request message representing a batch operation to wrap.</param> /// <param name="contentId">Content id for the given operation host.</param> /// <param name="writer">ODataBatchWriter instance.</param> /// <param name="odataMaxVersion">OData-MaxVersion header on the batch request. If the OData-MaxVersion header is not specified in the current operation, we default to the MaxDSV from the batch level.</param> internal BatchServiceHost(Uri absoluteServiceUri, IODataRequestMessage operationMessage, string contentId, ODataBatchWriter writer, Version odataMaxVersion) : this(writer) { Debug.Assert(absoluteServiceUri != null && absoluteServiceUri.IsAbsoluteUri, "absoluteServiceUri != null && absoluteServiceUri.IsAbsoluteUri"); Debug.Assert(operationMessage != null, "operationMessage != null"); this.absoluteServiceUri = absoluteServiceUri; this.absoluteRequestUri = RequestUriProcessor.GetAbsoluteUriFromReference(operationMessage.Url, absoluteServiceUri); this.requestHttpMethod = operationMessage.Method; this.contentId = contentId; foreach (KeyValuePair<string, string> header in operationMessage.Headers) { this.requestHeaders.Add(header.Key, header.Value); } // If the MaxDSV header is not specified in the current operation, we default to the MaxDSV from the batch level. if (string.IsNullOrEmpty(this.requestHeaders[XmlConstants.HttpODataMaxVersion])) { Debug.Assert(odataMaxVersion != null, "odataMaxVersion != null"); this.requestHeaders[XmlConstants.HttpODataMaxVersion] = odataMaxVersion.ToString(); } this.requestStream = operationMessage.GetStream(); }
/// <summary> /// Writes a single OData batch response. /// </summary> /// <param name="writer">The <see cref="ODataBatchWriter"/>.</param> /// <param name="response">The response message.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <returns>A task object representing writing the given batch response using the given writer.</returns> public static async Task WriteMessageAsync(ODataBatchWriter writer, HttpResponseMessage response, CancellationToken cancellationToken) { if (writer == null) { throw Error.ArgumentNull("writer"); } if (response == null) { throw Error.ArgumentNull("response"); } ODataBatchOperationResponseMessage batchResponse = writer.CreateOperationResponseMessage(); batchResponse.StatusCode = (int)response.StatusCode; foreach (KeyValuePair<string, IEnumerable<string>> header in response.Headers) { batchResponse.SetHeader(header.Key, String.Join(",", header.Value)); } if (response.Content != null) { foreach (KeyValuePair<string, IEnumerable<string>> header in response.Content.Headers) { batchResponse.SetHeader(header.Key, String.Join(",", header.Value)); } using (Stream stream = batchResponse.GetStream()) { cancellationToken.ThrowIfCancellationRequested(); await response.Content.CopyToAsync(stream); } } }
public override async Task StartBatchAsync() { _requestMessage = new ODataRequestMessage() { Url = _session.Settings.BaseUri }; _messageWriter = new ODataMessageWriter(_requestMessage); _batchWriter = await _messageWriter.CreateODataBatchWriterAsync(); await _batchWriter.WriteStartBatchAsync(); this.HasOperations = true; }
/// <summary> /// Constructor. /// </summary> /// <param name="batchWriter">The writer to wrap.</param> /// <param name="testConfiguration">The test configuration to use.</param> public ODataBatchWriterTestWrapper(ODataBatchWriter batchWriter, WriterTestConfiguration testConfiguration) { ExceptionUtilities.CheckArgumentNotNull(batchWriter, "batchWriter"); ExceptionUtilities.CheckArgumentNotNull(testConfiguration, "testConfiguration"); this.batchWriter = batchWriter; this.testConfiguration = testConfiguration; this.contentId = 100; }
/// <summary> /// Writes the response as an Operation. /// </summary> /// <param name="writer">The <see cref="ODataBatchWriter"/>.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> public override Task WriteResponseAsync(ODataBatchWriter writer, CancellationToken cancellationToken) { if (writer == null) { throw Error.ArgumentNull("writer"); } return WriteMessageAsync(writer, Response, cancellationToken); }
/// <summary> /// Writes the responses as a ChangeSet. /// </summary> /// <param name="writer">The <see cref="ODataBatchWriter"/>.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> public override async Task WriteResponseAsync(ODataBatchWriter writer, CancellationToken cancellationToken) { if (writer == null) { throw Error.ArgumentNull("writer"); } writer.WriteStartChangeset(); foreach (HttpResponseMessage responseMessage in Responses) { await WriteMessageAsync(writer, responseMessage, cancellationToken); } writer.WriteEndChangeset(); }
/// <summary> /// Writes the response. /// </summary> /// <param name="writer">The <see cref="ODataBatchWriter"/>.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> public abstract Task WriteResponseAsync(ODataBatchWriter writer, CancellationToken cancellationToken);
/// <summary> /// Writes a single OData batch response. /// </summary> /// <param name="writer">The <see cref="ODataBatchWriter"/>.</param> /// <param name="response">The response message.</param> /// <returns>A task object representing writing the given batch response using the given writer.</returns> public static Task WriteMessageAsync(ODataBatchWriter writer, HttpResponseMessage response) { return WriteMessageAsync(writer, response, CancellationToken.None); }
/// <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> /// Create a request message for a batch part from the batch writer. This method copies request headers /// from <paramref name="requestMessageArgs"/> in addition to the method and Uri. /// </summary> /// <param name="batchWriter">ODataBatchWriter instance to build operation message from.</param> /// <param name="requestMessageArgs">RequestMessageArgs for the request.</param> /// <param name="requestInfo">RequestInfo instance.</param> /// <param name="contentId">The Content-ID value to write in ChangeSet head.</param> /// <returns>an instance of ODataRequestMessageWrapper.</returns> internal static ODataRequestMessageWrapper CreateBatchPartRequestMessage( ODataBatchWriter batchWriter, BuildingRequestEventArgs requestMessageArgs, RequestInfo requestInfo, string contentId) { IODataRequestMessage requestMessage = batchWriter.CreateOperationRequestMessage(requestMessageArgs.Method, requestMessageArgs.RequestUri, contentId); foreach (var h in requestMessageArgs.Headers) { requestMessage.SetHeader(h.Key, h.Value); } var clientRequestMessage = new InternalODataRequestMessage(requestMessage, false /*allowGetStream*/); ODataRequestMessageWrapper messageWrapper = new InnerBatchRequestMessageWrapper(clientRequestMessage, requestMessage, requestInfo, requestMessageArgs.Descriptor); return messageWrapper; }
/// <summary>Handles an exception when processing a batch response.</summary> /// <param name='service'>Data service doing the processing.</param> /// <param name="requestMessage">requestMessage holding information about the request that caused an error</param> /// <param name="responseMessage">responseMessage to which we need to write the exception message</param> /// <param name='exception'>Exception thrown.</param> /// <param name='batchWriter'>Output writer for the batch.</param> /// <param name="responseStream">Underlying response stream.</param> /// <param name="defaultResponseVersion">The data service version to use for response, if it cannot be computed from the requestMessage.</param> internal static void HandleBatchOperationError(IDataService service, AstoriaRequestMessage requestMessage, IODataResponseMessage responseMessage, Exception exception, ODataBatchWriter batchWriter, Stream responseStream, Version defaultResponseVersion) { Debug.Assert(service != null, "service != null"); Debug.Assert(exception != null, "exception != null"); Debug.Assert(batchWriter != null, "batchWriter != null"); Debug.Assert(service.Configuration != null, "service.Configuration != null"); Debug.Assert(CommonUtil.IsCatchableExceptionType(exception), "CommonUtil.IsCatchableExceptionType(exception)"); ErrorHandler handler = CreateHandler(service, requestMessage, exception, defaultResponseVersion); service.InternalHandleException(handler.exceptionArgs); if (requestMessage != null && responseMessage != null) { responseMessage.SetHeader(XmlConstants.HttpODataVersion, handler.responseVersion.ToString(2) + ";"); requestMessage.ProcessException(handler.exceptionArgs); // if ProcessBenignException returns anything, we can safely not write to the stream. if (ProcessBenignException(exception, service) != null) { return; } } if (requestMessage != null) { responseMessage = requestMessage.BatchServiceHost.GetOperationResponseMessage(); WebUtil.SetResponseHeadersForBatchRequests(responseMessage, requestMessage.BatchServiceHost); } else { responseMessage = batchWriter.CreateOperationResponseMessage(null); responseMessage.StatusCode = handler.exceptionArgs.ResponseStatusCode; } MessageWriterBuilder messageWriterBuilder = MessageWriterBuilder.ForError( null, service, handler.responseVersion, responseMessage, handler.contentType, null /*acceptCharsetHeaderValue*/); using (ODataMessageWriter messageWriter = messageWriterBuilder.CreateWriter()) { ODataError error = handler.exceptionArgs.CreateODataError(); WriteErrorWithFallbackForXml(messageWriter, handler.encoding, responseStream, handler.exceptionArgs, error, messageWriterBuilder); } }
/// <summary>Handles an exception when processing a batch request.</summary> /// <param name='service'>Data service doing the processing.</param> /// <param name='exception'>Exception thrown.</param> /// <param name='batchWriter'>Output writer for the batch.</param> /// <param name="responseStream">Underlying response stream.</param> internal static void HandleBatchInStreamError(IDataService service, Exception exception, ODataBatchWriter batchWriter, Stream responseStream) { Debug.Assert(service != null, "service != null"); Debug.Assert(exception != null, "exception != null"); Debug.Assert(responseStream != null, "responseStream != null"); Debug.Assert(service.Configuration != null, "service.Configuration != null - it should have been initialized by now"); Debug.Assert(CommonUtil.IsCatchableExceptionType(exception), "CommonUtil.IsCatchableExceptionType(exception) - "); AstoriaRequestMessage requestMessage = service.OperationContext == null ? null : service.OperationContext.RequestMessage; ErrorHandler handler = CreateHandler(service, requestMessage, exception, VersionUtil.DataServiceDefaultResponseVersion); service.InternalHandleException(handler.exceptionArgs); // Make sure to flush the batch writer before we write anything to the underlying stream batchWriter.Flush(); // Note the OData protocol spec did not defined the behavior when an exception is encountered outside of a batch operation. // The batch writer in ODataLib doesn't allow WriteError in this case. // Unfortunately the shipped behavior on the server is we serialize out an error payload in XML format. We need to keep the // existing behavior. The batch client doesn't know how to deserialize an error payload outside of a batch operation however. using (XmlWriter xmlWriter = XmlUtil.CreateXmlWriterAndWriteProcessingInstruction(responseStream, handler.encoding)) { ODataError error = handler.exceptionArgs.CreateODataError(); ErrorUtils.WriteXmlError(xmlWriter, error, handler.exceptionArgs.UseVerboseErrors, MaxInnerErrorDepth); } }
/// <summary> /// Creates a batch writer. /// </summary> /// <param name="batchBoundary">The boundary string for the batch structure itself.</param> /// <returns>The newly created batch writer.</returns> private ODataBatchWriter CreateODataBatchWriterImplementation(string batchBoundary) { // Batch writer needs the default encoding to not use the preamble. this.encoding = this.encoding ?? MediaTypeUtils.EncodingUtf8NoPreamble; ODataBatchWriter batchWriter = new ODataBatchWriter(this, batchBoundary); this.outputInStreamErrorListener = batchWriter; return batchWriter; }
internal static BatchServiceHost CreateBatchServiceHostForError(ODataBatchWriter writer) { BatchServiceHost host = new BatchServiceHost(writer) { queryParameters = new NameValueCollection() }; return host; }
/// <summary> /// Private constructor code that is common between normal and error construction code. /// </summary> /// <param name='writer'>ODataBatchWriter instance.</param> private BatchServiceHost(ODataBatchWriter writer) { Debug.Assert(writer != null, "writer != null"); this.writer = writer; this.requestHeaders = new WebHeaderCollection(); this.responseHeaders = new WebHeaderCollection(); }