示例#1
0
        /// <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);
 }
示例#9
0
        /// <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;
        }
示例#11
0
        /// <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);
            }
        }
示例#12
0
        /// <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);
            }
        }
示例#13
0
 /// <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;
 }
示例#14
0
 internal static BatchServiceHost CreateBatchServiceHostForError(ODataBatchWriter writer)
 {
     BatchServiceHost host = new BatchServiceHost(writer) { queryParameters = new NameValueCollection() };
     return host;
 }
示例#15
0
        /// <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();
        }