【问题标题】:Using signalR hub from MVC action从 MVC 操作中使用 signalR 集线器
【发布时间】:2015-06-08 13:56:32
【问题描述】:

this tutorial 之后,我正在尝试显示长时间操作中各个步骤的进度。我能够根据示例成功模拟集线器内的长时间操作,并在每个步骤中向客户端报告更新。

更进一步,我现在想显示一个实时、长时间运行的进程的状态,该进程发生在具有[HttpPost] 属性的 MVC 操作方法中。

问题是我似乎无法从集线器上下文更新客户端。我意识到我必须创建一个集线器上下文才能使用集线器进行通信。我知道的一个区别是我必须使用hubContext.Clients.All.sendMessage(); VS。 hubContext.Clients.Caller.sendMessage(); 列在示例中。基于我在ASP.NET SignalR Hubs API Guide - Server 中的发现 如示例中所述,我应该能够使用Clients.Caller,但我仅限于在集线器类中使用它。主要是想明白为什么我不能从action方法中得到信号。

提前感谢您的帮助。

我已经像这样创建了我的 OWIN Startup() 类...

using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(HL7works.Startup))]

namespace HL7works
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}

我的中心是这样写的...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;

namespace HL7works
{
    public class ProgressHub : Hub
    {
        public string msg = string.Empty;
        public int count = 0;

        public void CallLongOperation()
        {
            Clients.Caller.sendMessage(msg, count);
        }
    }
}

我的控制器...

// POST: /Task/ParseToExcel/
[HttpPost]
public ActionResult ParseToExcel(HttpPostedFileBase[] filesUpload)
{
    // Initialize Hub context
    var hubContext = GlobalHost.ConnectionManager.GetHubContext<ProgressHub>();
    hubContext.Clients.All.sendMessage("Initalizing...", 0);

    double fileProgressMax = 100.0;
    int currentFile = 1;
    int fileProgress = Convert.ToInt32(Math.Round(currentFile / fileProgressMax * 100, 0));

    try
    {
        // Map server path for temporary file placement (Generate new serialized path for each instance)
        var tempGenFolderName = SubstringExtensions.GenerateRandomString(10, false);
        var tempPath = Server.MapPath("~/" + tempGenFolderName + "/");

        // Create Temporary Serialized Sub-Directory
        System.IO.FileInfo thisFilePath = new System.IO.FileInfo(tempPath + tempGenFolderName);
        thisFilePath.Directory.Create();

        // Iterate through PostedFileBase collection
        foreach (HttpPostedFileBase file in filesUpload)
        {

            // Does this iteration of file have content?
            if (file.ContentLength > 0)
            {
                // Indicate file is being uploaded
                hubContext.Clients.All.sendMessage("Uploading " + Path.GetFileName(file.FileName), fileProgress);

                file.SaveAs(thisFilePath + file.FileName);
                currentFile++;
            }
        }

        // Initialize new ClosedXML/Excel workbook
        var hl7Workbook = new XLWorkbook();

        // Start current file count at 1
        currentFile = 1;

        // Iterate through the files saved in the Temporary File Path
        foreach (var file in Directory.EnumerateFiles(tempPath))
        {
            var fileNameTmp = Path.GetFileName(file);

            // Update status
            hubContext.Clients.All.sendMessage("Parsing " + Path.GetFileName(file), fileProgress);

            // Initialize string to capture text from file
            string fileDataString = string.Empty;

            // Use new Streamreader instance to read text
            using (StreamReader sr = new StreamReader(file))
            {
                fileDataString = sr.ReadToEnd();
            }

            // Do more work with the file, adding file contents to a spreadsheet...


            currentFile++;
        }


        // Delete temporary file 
        thisFilePath.Directory.Delete();


        // Prepare Http response for downloading the Excel workbook
        Response.Clear();
        Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
        Response.AddHeader("content-disposition", "attachment;filename=\"hl7Parse_" + DateTime.Now.ToString("MM-dd-yyyy") + ".xlsx\"");

        // Flush the workbook to the Response.OutputStream
        using (MemoryStream memoryStream = new MemoryStream())
        {
            hl7Workbook.SaveAs(memoryStream);
            memoryStream.WriteTo(Response.OutputStream);
            memoryStream.Close();
        }

        Response.End();
    }
    catch (Exception ex)
    {
        ViewBag.TaskMessage =
            "<div style=\"margin-left:15px;margin-right:15px\" class=\"alert alert-danger\">"
            + "<i class=\"fa fa-exclamation-circle\"></i> "
            + "An error occurred during the process...<br />"
            + "-" + ex.Message.ToString()
            + "</div>"
            ;
    }

    return View();
}

在我看来(已更新以反映 Detail 的回答)...

@using (Html.BeginForm("ParseToExcel", "Task", FormMethod.Post, new { enctype = "multipart/form-data", id = "parseFrm" }))
{

    <!-- File Upload Row -->
    <div class="row">

        <!-- Select Files -->
        <div class="col-lg-6">
            <input type="file" multiple="multiple" accept=".adt" name="filesUpload" id="filesUpload" />
        </div>


        <!-- Upload/Begin Parse -->
        <div class="col-lg-6 text-right">
            <button id="beginParse" class="btn btn-success"><i class="fa fa-download"></i>&nbsp;Parse and Download Spreadsheet</button>
        </div>

    </div>

}



 <!-- Task Progress Row -->
<div class="row">

    <!-- Space Column -->
    <div class="col-lg-12">
        &nbsp;
    </div>

    <!-- Progress Indicator Column -->
    <script type="text/javascript" language="javascript">

        $(document).ready(function () {
            $('.progress').hide();

            $('#beginParse').on('click', function () {
                $('#parseFrm').submit();
            })

            $('#parseFrm').on('submit', function (e) {

                e.preventDefault();

                $.ajax({
                    url: '/Task/ParseToExcel',
                    type: "POST",
                    //success: function () {
                    //    console.log("done");
                    //}
                });

                // initialize the connection to the server
                var progressNotifier = $.connection.progressHub;

                // client-side sendMessage function that will be called from the server-side
                progressNotifier.client.sendMessage = function (message, count) {
                    // update progress
                    UpdateProgress(message, count);
                };

                // establish the connection to the server and start server-side operation
                $.connection.hub.start().done(function () {
                    // call the method CallLongOperation defined in the Hub
                    progressNotifier.server.callLongOperation();
                });
            });
        });

        function UpdateProgress(message, count) {

            // get status div
            var status = $("#status");

            // set message
            status.html(message);

            // get progress bar
            if (count > 0) {
                $('.progress').show();
            }

            $('.progress-bar').css('width', count + '%').attr('aria-valuenow', count);
            $('.progress-bar').html(count + '%');

        }


    </script>

    <div class="col-lg-12">
        <div id="status">Ready</div>
    </div>

    <div class="col-lg-12">
        <div class="progress">
            <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="min-width:20px;">
                0%
            </div>
        </div>
    </div>
</div>
<!-- Task Message Row -->
<div class="row">
    <div clss="col-lg-12">
        @Html.Raw(ViewBag.TaskMessage)
    </div>
</div>

更新:我的问题的解决方案最终成为 Detail 的答案,但 AJAX 发布方法稍作修改以将文件传递给我的操作方法..

e.preventDefault();

$.ajax({
    url: '/Task/ParseToExcel',
    type: "POST",
    data: new FormData( this ),
    processData: false,
    contentType: false,
    //success: function () {
    //    console.log("done");
    //}
});

参考..http://portfolio.planetjon.ca/2014/01/26/submit-file-input-via-ajax-jquery-easy-way/

【问题讨论】:

  • 最好不要包含unnecessary tags
  • @lloyd,谢谢。下次我会记住这一点的。
  • 没问题,这是solution relevant
  • @lloyd,我对集线器类的引用和集线器方法的调用正是他们在您引用的解决方案中所做的。然而,我担心我可能会错过另一块拼图。我只是不确定那可能是什么。
  • 我认为您不需要向所有客户发送消息......只是连接的那个人

标签: c# asp.net-mvc signalr


【解决方案1】:

好的,我对此进行了一些尝试,我认为您最好使用一个名为“jQuery Form Plugin”(http://jquery.malsup.com/form) 的插件,这将有助于解决 HttpPostedFiles 问题。

我已经获取了您的代码并进行了一些调整并使其正常工作。您需要在循环的每一轮(两个循环)期间重新计算您的 fileProgress,并且使用您添加到表单中的按钮,不再需要通过 jQuery 触发帖子,所以我将其注释掉。

另外,我认为 CallLongOperation() 函数现在是多余的(我想这只是源材料中的一个演示),所以我已经从你的集线器启动逻辑中删除了该调用,并将其替换为显示的行按钮 - 在 signalR 准备好之前,您可能应该阻止用户开始上传,但 signalR 几乎立即启动,所以我认为您甚至不会注意到延迟。

我不得不注释掉一些代码,因为我没有这些对象(XLWorkbook 的东西、openxml 位等),但您应该能够在没有这些位的情况下运行它并跟踪代码以遵循逻辑,然后将这些位添加回自己。

这是一个有趣的问题,希望对我有所帮助:)

控制器:

public class TaskController : Controller
{
    [HttpPost]
    public ActionResult ParseToExcel(HttpPostedFileBase[] filesUpload)
    {
        decimal currentFile = 1.0M;
        int fileProgress = 0;
        int maxCount = filesUpload.Count();

        // Initialize Hub context
        var hubContext = GlobalHost.ConnectionManager.GetHubContext<ProgressHub>();
        hubContext.Clients.All.sendMessage("Initalizing...", fileProgress);            

        try
        {
            // Map server path for temporary file placement (Generate new serialized path for each instance)
            var tempGenFolderName = DateTime.Now.ToString("yyyyMMdd_HHmmss"); //SubstringExtensions.GenerateRandomString(10, false);
            var tempPath = Server.MapPath("~/" + tempGenFolderName + "/");

            // Create Temporary Serialized Sub-Directory
            FileInfo thisFilePath = new FileInfo(tempPath);
            thisFilePath.Directory.Create();

            // Iterate through PostedFileBase collection
            foreach (HttpPostedFileBase file in filesUpload)
            {
                // Does this iteration of file have content?
                if (file.ContentLength > 0)
                {
                    fileProgress = Convert.ToInt32(Math.Round(currentFile / maxCount * 100, 0));

                    // Indicate file is being uploaded
                    hubContext.Clients.All.sendMessage("Uploading " + Path.GetFileName(file.FileName), fileProgress);

                    file.SaveAs(Path.Combine(thisFilePath.FullName, file.FileName));
                    currentFile++;
                }
            }

            // Initialize new ClosedXML/Excel workbook
            //var hl7Workbook = new XLWorkbook();

            // Restart progress
            currentFile = 1.0M;
            maxCount = Directory.GetFiles(tempPath).Count();

            // Iterate through the files saved in the Temporary File Path
            foreach (var file in Directory.EnumerateFiles(tempPath))
            {
                var fileNameTmp = Path.GetFileName(file);

                fileProgress = Convert.ToInt32(Math.Round(currentFile / maxCount * 100, 0));

                // Update status
                hubContext.Clients.All.sendMessage("Parsing " + Path.GetFileName(file), fileProgress);

                // Initialize string to capture text from file
                string fileDataString = string.Empty;

                // Use new Streamreader instance to read text
                using (StreamReader sr = new StreamReader(file))
                {
                    fileDataString = sr.ReadToEnd();
                }

                // Do more work with the file, adding file contents to a spreadsheet...
                currentFile++;
            }


            // Delete temporary file 
            thisFilePath.Directory.Delete();


            // Prepare Http response for downloading the Excel workbook
            //Response.Clear();
            //Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
            //Response.AddHeader("content-disposition", "attachment;filename=\"hl7Parse_" + DateTime.Now.ToString("MM-dd-yyyy") + ".xlsx\"");

            // Flush the workbook to the Response.OutputStream
            //using (MemoryStream memoryStream = new MemoryStream())
            //{
            //    hl7Workbook.SaveAs(memoryStream);
            //    memoryStream.WriteTo(Response.OutputStream);
            //    memoryStream.Close();
            //}

            //Response.End();
        }
        catch (Exception ex)
        {
            ViewBag.TaskMessage =
                "<div style=\"margin-left:15px;margin-right:15px\" class=\"alert alert-danger\">"
                + "<i class=\"fa fa-exclamation-circle\"></i> "
                + "An error occurred during the process...<br />"
                + "-" + ex.Message.ToString()
                + "</div>"
                ;
        }

        return View();
    }
}

查看:

@using (Html.BeginForm("ParseToExcel", "Task", FormMethod.Post, new { enctype = "multipart/form-data", id = "parseFrm" }))
{
    <!-- File Upload Row -->
    <div class="row">

        <!-- Select Files -->
        <div class="col-lg-6">
            <input type="file" multiple="multiple" accept=".adt" name="filesUpload" id="filesUpload" />
        </div>

        <!-- Upload/Begin Parse -->
        <div class="col-lg-6 text-right">
            <button id="beginParse" class="btn btn-success"><i class="fa fa-download"></i>&nbsp;Parse and Download Spreadsheet</button>
        </div>
    </div>
}

<!-- Task Progress Row -->
<div class="row">

    <!-- Progress Indicator Column -->
    <script type="text/javascript" language="javascript">

        $(document).ready(function () {

            $('.progress').hide();
            $('#beginParse').hide();

            // initialize the connection to the server
            var progressNotifier = $.connection.progressHub;

            // client-side sendMessage function that will be called from the server-side
            progressNotifier.client.sendMessage = function (message, count) {
                // update progress
                UpdateProgress(message, count);
            };

            // establish the connection to the server
            $.connection.hub.start().done(function () {
                //once we're connected, enable the upload button
                $('#beginParse').show();
            });

            //no need for this, the button submits the form
            //$('#beginParse').on('click', function () {
            //    $('#parseFrm').submit();
            //})

            //ajaxify the form post
            $('#parseFrm').on('submit', function (e) {
                e.preventDefault();
                $('#parseFrm').ajaxSubmit();
            });
        });

        function UpdateProgress(message, count) {

            // get status div
            var status = $("#status");

            // set message
            status.html(message);

            // get progress bar
            if (count > 0) {
                $('.progress').show();
            }

            $('.progress-bar').css('width', count + '%').attr('aria-valuenow', count);
            $('.progress-bar').html(count + '%');

        }


    </script>

    <div class="col-lg-12">
        <div id="status">Ready</div>
    </div>

    <div class="col-lg-12">
        <div class="progress">
            <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="min-width:20px;">
                0%
            </div>
        </div>
    </div>
</div>
<!-- Task Message Row -->
<div class="row">
    <div clss="col-lg-12">
        @Html.Raw(ViewBag.TaskMessage)
    </div>
</div>

附言不要忘记在 _Layout.cshtml 中添加对 jQuery Form Plugin 的脚本引用:

<script src="http://malsup.github.com/jquery.form.js"></script>

【讨论】:

  • 超级。哇,我很感激走过。我以类似的方式连接所有内容,使用 jQuery 表单插件并让它识别我的文件输入/HttpPostedFileBase 数组。除了返回文件之外,我所有的控制器操作逻辑似乎都在执行。有趣的是,在指示处理完最后一个文件后,我在客户端(Firebug)上收到一个错误。“格式不正确”..然后是一些 ascii 通配符。其中,我看到“xl/workbook.xml”好像控制器正在发回文件,但不是通过 HttpHeader。我得调查一下。
  • 否则,这很好。以后我会记住这个 jQuery Form 插件。至于 signalR,我进行了一些搜索,试图找到一种巧妙的方法来指示来自服务器的实时状态。许多基于 AJAX 的解决方案当然是基于 AJAX 的,然后我阅读了用户在哪里使用 signalR。我听说过 signalR 的经典聊天应用程序,但从未尝试过。一切都是第一次。看起来还蛮有趣的。没有我们试图解决的问题,生活会很无聊。
  • @Detail: ajaxify form post如何实现,需要使用默认的post方式
  • @Rocky:抱歉,我不清楚你在问什么……你能改写一下吗?
  • 我的意思是如果我不想使用 $('#parseFrm').on('submit', function (e) { e.preventDefault(); $('#parseFrm' ).ajaxSubmit(); });
【解决方案2】:

目前尚不清楚您的问题到底是什么,但使用您的代码,我已经完成了一些更改。

首先,表单发布正在重新加载页面,如果您要为此使用 POST,那么您需要通过捕获发布事件并阻止默认操作来异步执行此操作(然后使用 jQuery 接管)。我不确定您打算如何触发该帖子(也许我只是在您的代码中错过了它),所以我添加了一个按钮并与之挂钩,但根据需要进行更改:

<!-- Progress Indicator Column -->
<script type="text/javascript" language="javascript">

    $(document).ready(function () {
        $('.progress').hide();
        $('#button1').on('click', function () {
            $('#form1').submit();
        })

        $('#form1').on('submit', function (e) {

            e.preventDefault();

            $.ajax({
                url: '/Progress/DoTest',
                type: "POST",
                success: function () {
                    console.log("done");
                }
            });

            // initialize the connection to the server
            var progressNotifier = $.connection.progressHub;

            // client-side sendMessage function that will be called from the server-side
            progressNotifier.client.sendMessage = function (message, count) {
                // update progress
                UpdateProgress(message, count);
            };

            // establish the connection to the server and start server-side operation
            $.connection.hub.start().done(function () {
                // call the method CallLongOperation defined in the Hub
                progressNotifier.server.callLongOperation();
            });
        });
    });

    function UpdateProgress(message, count) {

        // get status div
        var status = $("#status");

        // set message
        status.html(message);

        // get progress bar
        if (count > 0)
        {
            $('.progress').show();
        }

        $('.progress-bar').css('width', count + '%').attr('aria-valuenow', count);
        $('.progress-bar').html(count + '%');

    }

</script>

<div class="col-lg-12">
    <div id="status">Ready</div>
</div>


<form id="form1">
    <button type="button" id="button1">Submit Form</button>
</form>

<div class="col-lg-12">
    <div class="progress">
        <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="min-width:20px;">
            0%
        </div>
    </div>
</div>

我还稍微简化了控制器,只是为了专注于手头的问题。分解问题并首先让机制发挥作用,尤其是在遇到问题时,然后再添加额外的逻辑:

public class ProgressController : Controller
{
    // GET: Progress
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public ActionResult DoTest()
    {
        // Initialize Hub context
        var hubContext = GlobalHost.ConnectionManager.GetHubContext<ProgressHub>();
        hubContext.Clients.All.sendMessage("Initalizing...", 0);

        int i = 0;
        do
        {
            hubContext.Clients.All.sendMessage("Uploading ", i * 10);
            Thread.Sleep(1000);
            i++;
        }
        while (i < 10);             

        return View("Index");
    }
}

另外,请确保您的 javascript 引用排序正确,jquery 必须首先加载,然后是信号器,然后是集线器脚本。

如果您仍然有问题,请发布您的确切错误消息,但我怀疑这是您的问题是同步表单/重新加载的事情。

希望对你有帮助

【讨论】:

  • 我更新了我的视图代码,以显示我目前如何设置我的表单以及表单中submit 按钮的位置。正如您所指出的,不存在任何错误。我确定这确实是一个同步问题。我稍后会尝试您的解决方案并进行更新。
  • 您的示例在创建上面的测试控制器操作时有效。在我的原始控制器操作中实现相同的操作时,我无法让它工作,我认为这是因为我的 HttpPostedFileBase 不再达到控制器操作。文件没有到达那里,所以没有什么要处理的。有任何想法吗?我会发布更新。
  • 我会继续将您的解决方案标记为答案。我认为这让我更接近我需要的东西。我会自己弄清楚发布的文件基础。但是,您帮助我弄清楚了导致我原来问题的主要原因是什么。捕获 post 事件并异步发布。
猜你喜欢
  • 2018-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-09
  • 2020-10-26
相关资源
最近更新 更多