asp.net-mvc - 将控制台应用程序的标准输出流式传输到 ASP.NET MVC View

标签 asp.net-mvc ajax json stream redirectstandardoutput

我想做的是:

1) 从 MVC View 中,启动一个长时间运行的进程。就我而言,这个过程是一个正在执行的单独的控制台应用程序。控制台应用程序可能运行 30 分钟,并定期 Console.Write 执行其当前操作。

2) 返回 MVC View ,定期轮询服务器以检索我已重定向到流(或任何我可以访问它的地方)的最新标准输出。我会将新检索到的标准输出附加到日志文本框或等效内容。

听起来相对容易。我的客户端编程有点生疏,而且我在实际流媒体方面遇到了问题。我认为这并不是一个不常见的任务。有人在 ASP.NET MVC 中找到了一个不错的解决方案吗?

最大的问题似乎是在执行结束之前我无法获得标准输出,但我可以通过事件处理程序获得它。当然,使用事件处理程序似乎失去了我的输出焦点。

这就是我迄今为止正在研究的内容...

    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 来测试操作,但我也对您如何处理此文本的轮询感兴趣。如果您也可以提供实际的代码,那就太好了。我假设您在启动进程后会运行一个 js 循环,该进程将使用 ajax 调用返回 JSON 结果的服务器...但同样,这不是我的强项,所以很想看看它是如何完成的。

谢谢!

最佳答案

是的,根据我收到的一些建议以及大量的试验和错误,我想出了一个正在进行中的解决方案,并认为我应该与大家分享。目前它肯定存在潜在的问题,因为它依赖于整个网站共享的静态变量,但对于我的要求,它做得很好。开始吧!

让我们从我的观点开始吧。我们首先将按钮的单击事件与一些 jquery 绑定(bind),该 jquery 向/Upload/ProcessImport 发送信息(上传是我的 MVC Controller ,ProcessImport 是我的 MVC 操作)。流程导入启动了我的流程,我将在下面详细介绍。然后js等待一小会儿(使用setTimeout),然后再调用js函数getMessages。

因此,单击按钮后将调用 getMessages,并且它会向/Upload/Update 发布消息(我的更新操作)。 Update 操作基本上检索 Process 的状态并返回它以及自上次调用 Update 以来的标准输出。然后 getMessages 将解析 JSON 结果并将 StandardOutput 附加到我 View 中的列表中。我还尝试滚动到列表底部,但这并不完美。最后,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>

Controller 。我选择不使用 AsyncController,主要是因为我发现我不需要。我最初的问题是将控制台应用程序的标准输出通过管道传输到 View 。我发现无法 ReadToEnd 标准输出,因此改为 Hook 事件处理程序 ProcessOutputDataReceived ,当收到标准输出数据时会触发该事件处理程序,然后使用 StringBuilder 将输出附加到先前接收到的输出。这种方法的问题是 Controller 在每个帖子中都会重新实例化,为了克服这个问题,我决定使应用程序的 Process 和 StringBuilder 静态。这允许我接收对更新操作的调用,获取静态 StringBuilder 并有效地将其内容刷新回我的 View 。我还向 View 发送回一个 bool 值,指示进程是否已退出,以便 View 在知道这一点时可以停止轮询。另外,作为静态,我试图确保如果正在进行导入,则不允许其他导入开始。

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

嗯,到目前为止就是这样。有用。它仍然需要工作,所以希望当我完善我的解决方案时我会更新这个解决方案。您对静态方法有何看法(假设业务规则是任何一次只能发生一次导入)?

关于asp.net-mvc - 将控制台应用程序的标准输出流式传输到 ASP.NET MVC View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8478530/

相关文章:

c# - 在 Razor ASP.NET MVC 中调用方法

c# - ASP.NET MVC - 合并多个小应用程序

asp.net-mvc - 在 MVC 3 中,如何使默认模型绑定(bind)器将空字符串视为 "Empty"而不是 "NULL"?

php - 自动创建网上商店

javascript - 如何迭代 JSON 以使用动态数据制作 ejs 片段

asp.net-mvc - 从数据库读取图像时出现colorbox问题

Javascript:无法通过 Ajax 调用访问对象属性

php - 创建一个包含元素名称和值的 json 数组,并使用 ajax 将其提供给 php

json - bash shell 脚本出现问题,尝试使用 cURL 发布变量 JSON 数据

javascript - 如何在 jQuery 中导航 JSON 对象?