Потоковый вывод текста для длительных действий?

У меня есть несколько действий утилиты, которые возвращают вывод текста через return Content("my text","text/plain").

Иногда эти методы занимают несколько минут для запуска (т.е. синтаксический анализ журналов, обслуживание базы данных).

Я хотел бы изменить мой метод действий, чтобы вместо того, чтобы сразу возвращать весь вывод, текст будет передаваться клиенту по его готовности.

Вот надуманный пример:

public ActionResult SlowText()
{
    var sb = new System.Text.StringBuilder();
    sb.AppendLine("This happens quickly...");
    sb.AppendLine("Starting a slow 10 second process...");
    System.Threading.Thread.Sleep(10000);
    sb.AppendLine("All done with 10 second process!");
    return Content(sb.ToString(), "text/plain");
}

Как написано, это действие вернет три строки текста через 10 секунд. То, что я хочу, это способ сохранить поток ответа открытым и сразу же вернуть первые две строки, а затем третью строку через 10 секунд.

Я помню, как это делал 10 лет назад в Classic ASP 3.0 с помощью объекта Response. Есть ли официальный способ MVC для достижения этого?

-

Обновление: использование Razor.cshtml в приложении; но не используя какие-либо представления (просто ContentResult) для этих действий.

Ответы

Ответ 1

Запись непосредственно в объект Response должна работать, но только в некоторых простых случаях. Многие функции MVC зависят от замены замещения вывода (например, частичных представлений, механизма просмотра Razor и других), и если вы пишете непосредственно в ответ, ваш результат будет неработоспособным.

Однако, если вы не используете представление и вместо этого пишете прямо в контроллере, тогда вы должны быть в порядке (если ваше действие не вызывается как дочернее действие).

Ответ 2

Я бы пропустил контроллер MVC полностью, так как вы все равно сломаете инкапсуляцию. В этом месте я бы использовал реализацию Barenaked IHttpHandler, которая напрямую передавалась в вышеупомянутый выходной поток.

Ответ 3

Вы подвергаете себя тайм-ауту браузера, если процесс занимает больше времени, чем первоначально предполагалось. Тогда у вас нет способа восстановить то, что произошло/если вы не реализуете отдельный метод, который дает информацию о длительном процессе.

Учитывая, что вы хотите использовать другой метод, вы можете начать длительный процесс и немедленно вернуться. Попросите браузер проверить другой метод, который дает самую последнюю информацию о продолжительном процессе. В последний раз я должен был сделать это, я оставил его простым и просто установил заголовок обновления из контроллера, прежде чем возвращать представление.

Что касается начала долгого процесса, вы можете сделать что-то вроде этого:

// in the controller class
delegate void MyLongProcess(); 
//...
// in the method that starts the action
MyLongProcess processTask = new MyLongProcess(_someInstance.TheLongRunningImplementation);
processTask.BeginInvoke(new AsyncCallback(EndMyLongProcess), processTask);
//...
public void EndMyLongProcess(IAsyncResult result)
{
   try{
      MyLongProcess processTask = (MyLongProcess)result.AsyncState;
      processTask.EndInvoke(result);
      // anything you needed at the end of the process
   } catch(Exception ex) {
      // an error happened, make sure to log this 
      // as it won't hit the global.asax error handler                    
   }
}

Что касается того, где вы помещаете журнал действий, которые произошли, то вам до того, как долго вы хотите, чтобы это было. Это может быть так же просто, как статическое поле/класс, в котором вы добавляете информацию о текущем процессе или вместо этого сохраняете его в хранилище данных, где он может пережить повторное использование приложения.

Вышеизложенное предполагает, что все это связано с длительным процессом, который сообщает о действиях, которые были сделаны. Потоковая передача - это другой вопрос, но приведенное выше может по-прежнему играть роль в поддержании операций в вашем контроллере и только в части, которая отвечает за потоковое вещание, доступное клиенту в результате действия.

Ответ 4

Вы можете реализовать свой собственный ActionResult, например ContentStreamingResult, и использовать HttpContext, HttpRequest и HttpResponse в методе ExecuteResult.

public class ContentStreamingResult : ActionResult
    {
        private readonly TextReader _reader;

        public ContentStreamingResult(TextReader reader)
        {
            _reader = reader;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            var httpContext = context.HttpContext;
            //Read text from the reader and write to the response
        }
    }

public class YourController : Controller
    {
        public ContentStreamingResult DownloadText()
        {
            string text = "text text text";
            return new ContentStreamingResult(new System.IO.StringReader(text));
        }
    }

Ответ 5

Попробуйте Response.Flush и BufferOutput до false. Обратите внимание, что это будет работать с различными результатами действий, вы должны напрямую писать в объект response. Вероятно, вы можете использовать его вместе с AsyncController.