13

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.

rasmus-s
  • 131
  • 1
  • 1
  • 3
  • 1
    the `IHttpBufferingFeature` feature can help I think, did you try to disable buffering ? – agua from mars Nov 24 '15 at 13:13
  • it look like the `IHttpBufferingFeature` is not supported in Kestrel [docs](http://docs.asp.net/en/latest/fundamentals/servers.html#supported-features-by-server) – rasmus-s Nov 25 '15 at 08:43
  • In the first example you are never writing to the stream. – usr Nov 29 '15 at 16:45
  • Its just a snippet. I have a method there loop over the queue of streamWriters and write to them. Sorry to not make that clear. – rasmus-s Nov 30 '15 at 16:42

2 Answers2

9

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);
}
Kiran
  • 56,921
  • 15
  • 176
  • 161
  • `PushStreamContent` is used to write multiple times to the stream - like a chat where the server can push new data to client. I can not see how i can write multiple times to the `HttpContext.Response.Body`. Can you give a example ? – rasmus-s Nov 25 '15 at 08:41
  • Updated my post with more details. – Kiran Nov 25 '15 at 17:53
  • 1
    I have read your update and tried different things. But I cannot get it to work, and not able to find any code example written in asp.net 5 there return a stream to the client, the server can write individual chunks of data to. Can you make a sample project / code snippet? – rasmus-s Nov 29 '15 at 12:15
  • I pasted the sample code which at the most basic level should just work...let me know otherwise – Kiran Nov 29 '15 at 15:59
  • 3
    I had tried that, but the server just close the stream right after it have finished the method, and the client will not get data due the request has ended. I have created a [github repo with a sample project](https://github.com/rasmus-s/stackoverflow-PushStreamContent) - I appreciate your help. – rasmus-s Nov 30 '15 at 09:49
  • 1
    Maybe this will help: http://stackoverflow.com/questions/36227565/aspnet-core-server-sent-events-response-flush I think you should flush the response.Body instead of the streamWriter in your github code. – Thieme Jan 17 '17 at 20:33
  • Did you ever get this working? I'm working with an DotNet Framework 4.5.2 Web API, and I'm seeing the same thing - JSON being returned by my method, not a stream, etc. The callback to PushStreamContent's Func etc doesn't seem to be called. – Curtis Herrick May 25 '21 at 15:15
  • The solution posted here doesn't seem to actually "stream" the results incrementally, but collects them in their entirety before returning? – eulerfx Nov 15 '21 at 17:17
0

As @Thieme commented on @Kiran post, the key as noted here is using await HttpContext.Response.Body.FlushAsync();

for example:

[Route("/api/sse")]
public class ServerSentEventController : Controller
{
    [HttpGet]
    public async Task Get()
    {
        var response = Response;
        response.Headers.Add("Content-Type", "text/event-stream");

        for(var i = 0; true; ++i)
        {
            await response
                .WriteAsync($"data: Controller {i} at {DateTime.Now}\r\r");

            response.Body.Flush();
            await Task.Delay(5 * 1000);
        }
    }
}

You should flush Body stream while the Action Task is continuing.

afruzan
  • 1,454
  • 19
  • 20