将控制台应用程序的标准输出流式传输到 ASP.NET MVC 视图
Posted
技术标签:
【中文标题】将控制台应用程序的标准输出流式传输到 ASP.NET MVC 视图【英文标题】:Streaming Standard Output of a console application to an ASP.NET MVC View 【发布时间】:2012-01-18 16:41:51 【问题描述】:我想做的是:
1) 从 MVC 视图中,启动一个长时间运行的进程。就我而言,这个过程是一个单独的控制台应用程序正在执行。控制台应用程序可能会运行 30 分钟,并定期控制台。写入其当前操作。
2) 返回 MVC 视图,定期轮询服务器以检索我已重定向到 Stream(或任何我可以访问它的地方)的最新标准输出。我会将新检索的标准输出附加到日志文本框或类似的东西。
听起来相对容易。虽然我的客户端编程有点生疏,但我在实际流式传输时遇到了问题。我认为这不是一个不寻常的任务。有人在 ASP.NET MVC 中找到了一个不错的解决方案吗?
最大的问题似乎是在执行结束之前我无法获得 StandardOutput,但我能够通过事件处理程序获得它。当然,使用事件处理程序似乎失去了我的输出焦点。
这就是我目前为止的工作......
public ActionResult ProcessImport()
// 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 += new DataReceivedEventHandler(_process_OutputDataReceived);
_process.WaitForExit(1);
Session["pid"] = _process.Id;
return Json(new success = true , JsonRequestBehavior.AllowGet);
void _process_OutputDataReceived(object sender, DataReceivedEventArgs e)
_importStandardOutputBuilder.Insert(0, e.Data);
public ActionResult Update()
//var pid = (int)Session["pid"];
//_process = Process.GetProcessById(pid);
var newOutput = _importStandardOutputBuilder.ToString();
_importStandardOutputBuilder.Clear();
//return View("Index", new Text = _process.StandardOutput.ReadToEnd() );
return Json(new output = newOutput , "text/html");
我还没有编写客户端代码,因为我只是点击 URL 来测试操作,但我也很感兴趣您将如何处理此文本的轮询。如果您也可以为此提供实际代码,那就太好了。我假设您在启动将使用 ajax 调用返回 JSON 结果的服务器的进程后运行一个 js 循环......但同样,它不是我的强项,所以很想看看它是如何完成的。
谢谢!
【问题讨论】:
【参考方案1】:查看长民意调查。基本上你可以打开一个 ajax 请求,然后在控制器中保持它。
Sample of long poll
这是您想要执行异步的事情,否则您可能会遇到线程不足的问题。
【讨论】:
酷。我会检查一下。我还没有任何使用 AsyncControllers 的经验。他们的示例看起来就像我想要的一样......但我仍然需要看看我是否可以在控制台应用程序处理时获得 StandardOut。 我会在网站上托管一个端点,控制台应用程序将其输出传送到该端点。然后可以将其推送到一个可观察的集合中,该集合可以将输出推送到长轮询客户端。【参考方案2】:考虑编写一个在某处服务器上运行的服务,并将其输出通过管道传输到您的 Web 服务器可访问的文件/数据库。然后,您可以将生成的数据加载到您的网站中并将它们返回给您的调用者。
了解长时间占用您的网络服务器的线程可能会导致线程匮乏,并使您的网站看起来像是崩溃了(即使它实际上只是忙于等待您的控制台应用运行)。
【讨论】:
【参考方案3】:好的,所以根据我收到的一些建议和大量的反复试验,我想出了一个正在进行中的解决方案,并认为我应该与大家分享。目前它肯定存在潜在问题,因为它依赖于整个网站共享的静态变量,但根据我的要求,它做得很好。来了!
让我们从我的观点开始。我们首先将我的按钮的单击事件与一些 jquery 绑定,该 jquery 向 /Upload/ProcessImport 发布信息(Upload 是我的 MVC 控制器,ProcessImport 是我的 MVC 操作)。流程导入启动我的流程,我将在下面详细说明。然后 js 在调用 js 函数 getMessages 之前等待一小段时间(使用 setTimeout)。
所以 getMessages 在单击按钮后被调用,它会发布到 /Upload/Update (我的更新操作)。 Update 操作基本上检索 Process 的状态并返回它以及自上次调用 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("01", e.Data, "</br>"));
public ActionResult Update()
var newOutput = _importStandardOutputBuilder.ToString();
_importStandardOutputBuilder.Clear();
return Json(new message = newOutput, processExited = _process.HasExited , "text/html");
嗯,到此为止。有用。它仍然需要工作,所以希望我能在完善我的解决方案时更新这个解决方案。您对静态方法有什么看法(假设业务规则是任何时候只能进行一次导入)?
【讨论】:
这是一个很好的基础解决方案。我在自己的代码中将它用作跳板,在那里我必须解决你所说的“静态方法”,因为我不得不假设我的网站被多个用户同时使用,并且每个用户都可以运行一个进程时间。所以控制器中的静态进程对象不会为我做。因此,我围绕 Process 类编写了一个包装器,我以松散耦合的方式将其放入用户会话状态(参见josephwoodward.co.uk/2014/06/…)。以上是关于将控制台应用程序的标准输出流式传输到 ASP.NET MVC 视图的主要内容,如果未能解决你的问题,请参考以下文章