PushStreamContent in asp.net 5 / mvc 6 is not working

rasmus-s picture rasmus-s · Nov 24, 2015 · Viewed 9.1k times · Source

Im trying to migrate a web api project (classic web.config project) there use PushStreamContent to the latest asp.net 5 web app (project.json).

My problem is that i can not get PushStreamContent to work.

When I use this api controller – a result will end up in a json format and not as a stream:

[Route("api/[controller]")]
public class EventsController : Controller
{
    private static readonly ConcurrentQueue<StreamWriter> s_streamWriter = new ConcurrentQueue<StreamWriter>();

   [HttpGet]
    public HttpResponseMessage Get(HttpRequestMessage request)
    {
         HttpResponseMessage response = request.CreateResponse();
        response.Content = new PushStreamContent(new Action<Stream, HttpContent, TransportContext>(WriteToStream), "text/event-stream");
        return response;
    }

    private void WriteToStream(Stream outputStream, HttpContent headers, TransportContext context)
    {
        var streamWriter = new StreamWriter(outputStream) {AutoFlush = true};
        s_streamWriter.Enqueue(streamWriter);
    }
}

If I change the controller action to return a task and wrap PushStreamContent in a class MyPushStreamResult - Like this:

[HttpGet]
public async Task<IActionResult> Get(HttpRequestMessage request)
{
    var stream = new PushStreamContent(new Action<Stream, HttpContent, TransportContext>(WriteToStream), "text/event-stream");
    return new MyPushStreamResult(stream, "text/event-stream");
}

public class MyPushStreamResult : ActionResult
{
    public string ContentType { get; private set; }
    public PushStreamContent Stream { get; private set; }
    public MyPushStreamResult(PushStreamContent stream, string contentType)
    {
        Stream = stream;
        ContentType = contentType;
    }
    public override async Task ExecuteResultAsync(ActionContext context)
    {
        var response = context.HttpContext.Response;
        response.ContentType = ContentType;
        await Stream.CopyToAsync(response.Body);
    }
}

A request to my controller action is now returning a stream, BUT the stream is not flushing before it close on serverside or contains a lot of data. When I push data to the PushStreamContent outputstream I flush after each text write, but I guess the flush is not on the response.Body stream.

What do i miss? Cannot find any samples with asp.net 5 structure.

Answer

Kiran picture Kiran · Nov 24, 2015

HttpResponseMessage is not treated specially in ASP.NET 5 unless you are using Microsoft.AspNet.Mvc.WebApiCompatShim package. This package is not recommended if you can use the ASP.NET 5 features to do similar stuff and was created to support backward compatibility.

So since HttpResponseMessage is not being considered special, its being rendered as json by the JsonOutuptFormatter just like any other .NET object

Instead of PushStreamContent, you currently have access to response stream directly via HttpContext.Response.Body property, so you can just directly write to the stream.

Updated:
PushStreamContent in Web API allowed you to directly write to the response stream. This type was created(by Web API team and is not present as part of System.Net.Http library where all other content types are) so that one could write directly to the stream, say for example, from a controller or filter etc. The alternative to PushStreamContent was StreamContent which only allowed you to provide a Stream object and then the host layers 'copy' the data from the source stream(like 'pulling' data). Also PushStreamContent is nothing special by itself. One could write their own type which derives from HttpContent.

To summarize, PushStreamContent allowed writing to the response stream directly where as in ASP.NET 5 we have direct access to the stream and so you can write to it.

Updated:
At the very basic form (right, you could convert to actionresult for testability), the following should work.

[HttpGet]
public Task Get()
{
    HttpContext.Response.ContentType = "text/event-stream";
    var sourceStream = // get the source stream
    return sourceStream.CopyToAsync(HttpContext.Response.Body);
}