PushStreamContent в asp.net 5 / mvc 6 не работает

Я пытаюсь перенести проект веб-API (классический проект web.config), там использовать PushStreamContent в последнее веб-приложение asp.net 5 (project.json).

Моя проблема в том, что я не могу заставить PushStreamContent работать.

Когда я использую этот контроллер API - результат будет в формате JSON, а не в виде потока:

[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);
    }
}

Если я изменю действие контроллера, чтобы вернуть задачу и обернуть PushStreamContent в класс MyPushStreamResult - как это:

[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);
    }
}

Запрос к действию моего контроллера теперь возвращает поток, НО поток не сбрасывается до его закрытия на стороне сервера или содержит много данных. Когда я отправляю данные в выходной поток PushStreamContent, я сбрасываю их после каждой записи текста, но я предполагаю, что сброс не выполняется в потоке response.Body.

Что я скучаю? Не удается найти образцы со структурой asp.net 5.

2 ответа

HttpResponseMessage не обрабатывается специально в ASP.NET 5, если вы не используете Microsoft.AspNet.Mvc.WebApiCompatShim пакет. Этот пакет не рекомендуется, если вы можете использовать функции ASP.NET 5 для выполнения подобных задач, и был создан для поддержки обратной совместимости.

Так как HttpResponseMessage не считается особенным, его переводится как JSON JsonOutuptFormatter как и любой другой объект.NET

Вместо PushStreamContentВы в настоящее время имеете доступ к потоку ответов напрямую через HttpContext.Response.Body свойство, так что вы можете просто написать прямо в поток.

Обновлено:
PushStreamContent в веб-API позволяет напрямую писать в поток ответов. Этот тип был создан (командой веб-API и отсутствует как часть System.Net.Http библиотека, в которой находятся все остальные типы контента), чтобы можно было писать напрямую в поток, например, из контроллера, фильтра и т. д. Альтернатива PushStreamContent была StreamContent Это позволило вам предоставить только объект Stream, а затем слои хоста "скопировать" данные из исходного потока (например, "извлечение" данных). Также PushStreamContent сам по себе ничего особенного. Можно написать свой собственный тип, который происходит от HttpContent,

Подвести итоги, PushStreamContent разрешена запись в поток ответов напрямую, где, как и в ASP.NET 5, у нас есть прямой доступ к потоку, и поэтому вы можете писать в него.

Обновлено:
В самой базовой форме (верно, вы можете преобразовать в actionresult для тестируемости) должно работать следующее.

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

Как @Thieme прокомментировал сообщение @Kiran, ключевым моментом, как указано здесь, является использованиеawait HttpContext.Response.Body.FlushAsync();

например:

      [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);
        }
    }
}

Вам следует очистить поток Body во время выполнения задачи действия.

Другие вопросы по тегам