// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.IO; using System.Net.Http.Formatting; using System.Net.Http.Headers; using System.Net.Http.Internal; using System.Threading.Tasks; using System.Web.Http; namespace System.Net.Http { /// /// Provides an implementation that exposes an output /// which can be written to directly. The ability to push data to the output stream differs from the /// where data is pulled and not pushed. /// public class PushStreamContent : HttpContent { private readonly Func _onStreamAvailable; /// /// Initializes a new instance of the class. The /// action is called when an output stream /// has become available allowing the action to write to it directly. When the /// stream is closed, it will signal to the content that is has completed and the /// HTTP request or response will be completed. /// /// The action to call when an output stream is available. public PushStreamContent(Action onStreamAvailable) : this(Taskify(onStreamAvailable), (MediaTypeHeaderValue)null) { } /// /// Initializes a new instance of the class. /// /// The action to call when an output stream is available. The stream is automatically /// closed when the return task is completed. public PushStreamContent(Func onStreamAvailable) : this(onStreamAvailable, (MediaTypeHeaderValue)null) { } /// /// Initializes a new instance of the class with the given media type. /// public PushStreamContent(Action onStreamAvailable, string mediaType) : this(Taskify(onStreamAvailable), new MediaTypeHeaderValue(mediaType)) { } /// /// Initializes a new instance of the class with the given media type. /// public PushStreamContent(Func onStreamAvailable, string mediaType) : this(onStreamAvailable, new MediaTypeHeaderValue(mediaType)) { } /// /// Initializes a new instance of the class with the given . /// public PushStreamContent(Action onStreamAvailable, MediaTypeHeaderValue mediaType) : this(Taskify(onStreamAvailable), mediaType) { } /// /// Initializes a new instance of the class with the given . /// public PushStreamContent(Func onStreamAvailable, MediaTypeHeaderValue mediaType) { if (onStreamAvailable == null) { throw Error.ArgumentNull("onStreamAvailable"); } _onStreamAvailable = onStreamAvailable; Headers.ContentType = mediaType ?? MediaTypeConstants.ApplicationOctetStreamMediaType; } private static Func Taskify( Action onStreamAvailable) { if (onStreamAvailable == null) { throw Error.ArgumentNull("onStreamAvailable"); } return (Stream stream, HttpContent content, TransportContext transportContext) => { onStreamAvailable(stream, content, transportContext); return TaskHelpers.Completed(); }; } /// /// When this method is called, it calls the action provided in the constructor with the output /// stream to write to. Once the action has completed its work it closes the stream which will /// close this content instance and complete the HTTP request or response. /// /// The to which to write. /// The associated . /// A instance that is asynchronously serializing the object's content. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception is passed as task result.")] protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) { TaskCompletionSource serializeToStreamTask = new TaskCompletionSource(); Stream wrappedStream = new CompleteTaskOnCloseStream(stream, serializeToStreamTask); await _onStreamAvailable(wrappedStream, this, context); // wait for wrappedStream.Close/Dispose to get called. await serializeToStreamTask.Task; } /// /// Computes the length of the stream if possible. /// /// The computed length of the stream. /// true if the length has been computed; otherwise false. protected override bool TryComputeLength(out long length) { // We can't know the length of the content being pushed to the output stream. length = -1; return false; } internal class CompleteTaskOnCloseStream : DelegatingStream { private TaskCompletionSource _serializeToStreamTask; public CompleteTaskOnCloseStream(Stream innerStream, TaskCompletionSource serializeToStreamTask) : base(innerStream) { Contract.Assert(serializeToStreamTask != null); _serializeToStreamTask = serializeToStreamTask; } #if NETFX_CORE [SuppressMessage( "Microsoft.Usage", "CA2215:Dispose methods should call base class dispose", Justification = "See comments, this is intentional.")] protected override void Dispose(bool disposing) { // We don't dispose the underlying stream because we don't own it. Dispose in this case just signifies // that the user's action is finished. _serializeToStreamTask.TrySetResult(true); } #else public override void Close() { // We don't Close the underlying stream because we don't own it. Dispose in this case just signifies // that the user's action is finished. _serializeToStreamTask.TrySetResult(true); } #endif } } }