Потоковый вывод текста для длительных действий?
У меня есть несколько действий утилиты, которые возвращают вывод текста через 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
.