Правильно, поэтому из пары полученных мной предложений и большого количества проб и ошибок я нашел решение в процессе разработки и решил поделиться со всеми вами.В настоящий момент, безусловно, существуют потенциальные проблемы, так как он опирается на статические переменные, общие для всего сайта, но, по моему требованию, хорошо справляется со своей задачей.Вот так!
Давайте начнем с моей точки зрения.Мы начинаем с привязки события click моей кнопки к некоторому jquery, который отправляет сообщение в / Upload / ProcessImport (Upload - мой MVC-контроллер, а ProcessImport - мое MVC-действие).Процесс Импорт запускает мой процесс, который я подробно опишу ниже.Затем js ждет короткое время (используя setTimeout) перед вызовом функции js getMessages.
Таким образом, getMessages вызывается после нажатия кнопки и делает сообщение в / Upload / Update (мое действие Update).Действие Обновить в основном извлекает состояние Процесса и возвращает его, а также StandardOutput с момента последнего вызова Обновления.Затем getMessages проанализирует результат JSON и добавит StandardOutput в список, на мой взгляд.Я также пытаюсь прокрутить до конца списка, но это не работает идеально.Наконец, getMessages проверяет, завершился ли процесс, и если он этого не сделал, он будет рекурсивно вызывать себя каждую секунду, пока не получит.
<script type="text/javascript">
function getMessages() {
$.post("/Upload/Update", null, function (data, s) {
if (data) {
var obj = jQuery.parseJSON(data);
$("#processOutputList").append('<li>' + obj.message + '</li>');
$('#processOutputList').animate({
scrollTop: $('#processOutputList').get(0).scrollHeight
}, 500);
}
// Recurivly call itself until process finishes
if (!obj.processExited) {
setTimeout(function () {
getMessages();
}, 1000)
}
});
}
$(document).ready(function () {
// bind importButton click to run import and then poll for messages
$('#importButton').bind('click', function () {
// Call ProcessImport
$.post("/Upload/ProcessImport", {}, function () { });
// TODO: disable inputs
// Run's getMessages after waiting the specified time
setTimeout(function () {
getMessages();
}, 500)
});
});
</script>
<h2>Upload</h2>
<p style="padding: 20px;">
Description of the upload process and any warnings or important information here.
</p>
<div style="padding: 20px;">
<div id="importButton" class="qq-upload-button">Process files</div>
<div id="processOutput">
<ul id="processOutputList"
style="list-style-type: none; margin: 20px 0px 10px 0px; max-height: 500px; min-height: 500px; overflow: auto;">
</ul>
</div>
</div>
Контроллер.Я решил не использовать AsyncController, главным образом потому, что обнаружил, что мне это не нужно.Моя первоначальная проблема заключалась в том, чтобы передать представление StdOut моего консольного приложения в представление.Я обнаружил, что ReadToEnd стандартного выхода не может, поэтому вместо этого подключил обработчик событий ProcessOutputDataReceived, который запускается при получении стандартных выходных данных, а затем с помощью StringBuilder добавляет вывод к ранее полученному выводу.Проблема с этим подходом заключалась в том, что Контроллер обновляется каждый пост, и чтобы преодолеть это, я решил сделать Process и StringBuilder статическими для приложения.Это позволяет мне затем получать вызов к действию обновления, захватывать статический StringBuilder и эффективно сбрасывать его содержимое обратно в мое представление.Я также отправляю обратно в представление логическое значение, указывающее, завершился ли процесс или нет, чтобы представление могло прекратить опрос, когда оно это знает.Кроме того, будучи статичным, я старался обеспечить, чтобы в процессе импорта не разрешалось запускать другие.
public class UploadController : Controller
{
private static Process _process;
private static StringBuilder _importStandardOutputBuilder;
public UploadController()
{
if(_importStandardOutputBuilder == null)
_importStandardOutputBuilder = new StringBuilder();
}
public ActionResult Index()
{
ViewData["Title"] = "Upload";
return View("UploadView");
}
//[HttpPost]
public ActionResult ProcessImport()
{
// Validate that process is not running
if (_process != null && !_process.HasExited)
return Json(new { success = false, message = "An Import Process is already in progress. Only one Import can occur at any one time." }, "text/html");
// Get the file path of your Application (exe)
var importApplicationFilePath = ConfigurationManager.AppSettings["ImportApplicationFilePath"];
var info = new ProcessStartInfo
{
FileName = importApplicationFilePath,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false
};
_process = Process.Start(info);
_process.BeginOutputReadLine();
_process.OutputDataReceived += ProcessOutputDataReceived;
_process.WaitForExit(1);
return Json(new { success = true }, JsonRequestBehavior.AllowGet);
}
static void ProcessOutputDataReceived(object sender, DataReceivedEventArgs e)
{
_importStandardOutputBuilder.Append(String.Format("{0}{1}", e.Data, "</br>"));
}
public ActionResult Update()
{
var newOutput = _importStandardOutputBuilder.ToString();
_importStandardOutputBuilder.Clear();
return Json(new { message = newOutput, processExited = _process.HasExited }, "text/html");
}
}
Ну, вот и все.Оно работает.Это все еще нуждается в работе, так что, надеюсь, я обновлю это решение, когда буду совершенствовать свое.Что вы думаете о статическом подходе (если предположить, что бизнес-правило заключается в том, что одновременно может выполняться только один импорт)?