// 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
}
}
}