需要一个带有用户反馈的ASP.NET MVC长时间运行过程

51

我一直在尝试为我的项目创建一个控制器,以便传送可能非常复杂的报告。因此,这可能需要相对较长的时间,进度条可以帮助用户知道事情正在进展。该报告将通过AJAX请求启动,周期性的JSON请求将获取状态并更新进度条。

我一直在尝试使用AsyncController,因为它似乎是一种不占用资源的运行长时间进程的好方法,但它似乎没有任何方式可以检查进度(而且似乎会阻止进一步的JSON请求,我还没弄清楚为什么)。之后,我尝试在控制器上的静态变量中存储进度,并从中读取状态 - 但老实说那似乎有点hacky!

欢迎所有建议!


报告的输出是什么(屏幕,PDF)? - Mark Redman
屏幕 - 目前对我来说,它是一块HTML或JSON对象并不重要(只要我得到一个可以解析和显示的响应即可)。 - Jason
1
这不是ASP.Net SignalR的绝佳候选吗?我知道这是一个旧帖子,但我现在也处于类似的情况。 - Vivek Bernard
那么,我很好奇你最终用什么方法解决了你面临的会话锁定问题。 - Tetsujin no Oni
3个回答

55

这是一个示例,你可以尝试一下:

控制器:

public class HomeController : AsyncController
{
    public ActionResult Index()
    {
        return View();
    }

    public void SomeTaskAsync(int id)
    {
        AsyncManager.OutstandingOperations.Increment();
        Task.Factory.StartNew(taskId =>
        {
            for (int i = 0; i < 100; i++)
            {
                Thread.Sleep(200);
                HttpContext.Application["task" + taskId] = i;
            }
            var result = "result";
            AsyncManager.OutstandingOperations.Decrement();
            AsyncManager.Parameters["result"] = result;
            return result;
        }, id);
    }

    public ActionResult SomeTaskCompleted(string result)
    {
        return Content(result, "text/plain");
    }

    public ActionResult SomeTaskProgress(int id)
    {
        return Json(new
        {
            Progress = HttpContext.Application["task" + id]
        }, JsonRequestBehavior.AllowGet);
    }
}

Index() 视图:

<script type="text/javascript">
$(function () {
    var taskId = 543;
    $.get('/home/sometask', { id: taskId }, function (result) {
        window.clearInterval(intervalId);
        $('#result').html(result);
    });

    var intervalId = window.setInterval(function () {
        $.getJSON('/home/sometaskprogress', { id: taskId }, function (json) {
            $('#progress').html(json.Progress + '%');
        });
    }, 5000);
});
</script>

<div id="progress"></div>
<div id="result"></div>

这个想法是启动一个异步操作,并使用HttpContext.Application报告进度,意味着每个任务必须有一个唯一的id。然后在客户端上启动任务,然后发送多个AJAX请求(每5秒)以更新进度。您可以调整参数以适应您的情况。进一步改进是添加异常处理。


1
另外,我刚刚让你的代码运行起来了,方法是删除控制器中涉及Session的任何代码,所以这显然是我的问题。不幸的是,我的所有控制器都需要我保存在Session中的各种数据(并在它们都扩展的BaseController中检索),因此我看不到任何简单的解决方法。 - Jason
@DarinDimitrov 如果Session不总是可用,那么如何验证正在进行异步ajax请求的用户。我有自定义的成员身份验证和角色提供程序,它在会话中存储一些属性。我似乎被禁用了SessionState这个东西。这是我发布的原始问题及我一直面临的问题(http://stackoverflow.com/questions/20396154/asynccontrollers-async-action-blocks-requests-instead-of-returning-immediately) - Jatin
@Nirvan,您可以使用ASP.NET表单身份验证来验证发出请求的用户。您拥有一些自定义成员资格和角色提供程序,这些提供程序将内容存储在ASP.NET会话中,这完全是您的问题。就个人而言,我永远不会依赖这样的东西。 - Darin Dimitrov
我喜欢这个解决方案。请注意,对于IE9(我不知道其他IE版本),您应该使用'/home/sometaskprogress' + new Date().getTime(),否则它不会再次命中您的控制器。它将使用它的缓存。 - Jon Koeter
什么是 AsynManager - Kiquenet
显示剩余9条评论

5
4.5年前,这个问题已经得到解答,现在我们有一个可以更轻松完成此任务的库: SignalR。不需要使用共享状态(这是不好的,因为它可能导致意外的结果),只需使用HubContext类连接到发送消息给客户端的Hub即可。
首先,像往常一样设置SignalR连接(例如,请参见此处),除了我们不需要任何服务器端方法在我们的Hub上。然后,我们向我们的Endpoint/Controller/whatever发起一个AJAX调用,并传递我们通常获取的连接ID:var connectionId = $.connection.hub.id;。在服务器端,您可以在不同的线程上启动进程并向客户端返回200 OK。该进程将知道连接ID,以便可以向客户端发送消息,如下所示:
GlobalHost.ConnectionManager.GetHubContext<LogHub>()
                              .Clients.Client(connectionId)
                              .log(message);

这里,log是一个你想要从服务器端调用的客户端方法,因此应该像使用SignalR一样定义它:

$.connection.logHub.client.log = function(message){...};

关于此处更多详细信息,请查看我的博客文章此处


7
当然,关于SignalR的评论在将近两年前就已经发布在上述问题中了。你真的不应该只是发布一个指向你博客的链接,因为这很接近垃圾信息,并且仅包含链接的答案也会受到反感。 - Erik Funkenbusch
这似乎是一个相当不错的答案。有人在使用SignalR而不是像Darrin的答案中那样进行轮询吗? - Jess
博客链接失效了 - 这就是为什么链接不好的原因。 - ransems

0

如果您可以访问您的 Web 服务器机器,您可以创建 Windows 服务或简单的控制台应用程序来执行长时间运行的任务。您的 Web 应用程序应该向数据库添加一条记录,以指示操作应该开始,而定期检查数据库中的新记录的 Windows 服务则应开始执行任务并将进度报告给数据库。

您的 Web 应用程序可以使用 Ajax 请求来检查进度并向用户显示。

我使用了这种方法来实现 Asp.net MVC 应用程序的 Excel 报表。这个报表是由运行在机器上并不断检查报表表中的新记录的 Windows 服务创建的。找到新记录时,它开始创建报表并通过更新记录字段来指示进度。Asp.net MVC 应用程序只需添加新的报表记录并在数据库中跟踪进度,直到完成,然后提供一个链接以下载准备好的文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接