ASP.NET MVC3 COMET SQLDependency 监视数据库表变化

来源:互联网 发布:淘宝店铺招牌装修 编辑:程序博客网 时间:2024/06/05 01:18

COMET

英文解释:

Comet is a web application model in which a long-held HTTP request allows a web server to push data to a browser, without the browser explicitly requesting it. (来自wiki)

中文解释:

Comet是基于 HTTP 长连接的“服务器推”技术(来自IBM developerWorks)。

有两种实现方式:

基于 XMLHttpRequest long polling(长轮询) 的 Comet 

Multipart XMLHttpRequest
英文解释:

It is a method for bundling multiple HTTP requests into a single HTTP request and unbundling on the client side through a Javascript handler. (来自TechnoMagicians Blog)

中文解释:

它允许客户端只用一个HTTP请求就可以从服务端向客户端传送多个资源

反向Ajax,第1部分:Comet介绍

作者: Mathieu Carbou 发布时间: 2011-10-14 22:03 

  英文原文:Reverse Ajax, Part 1: Introduction to Comet

在过去的几年中,web开发已经发生了很大的变化。现如今,我们期望的是能够通过web快速、动态地访问应用。在这一新的文章系列中,我们学习如何使用反向Ajax(Reverse Ajax)技术来开发事件驱动的web应用,以此来实现更好的用户体验。客户端的例子使用的是JQuery JavaScript库,在这首篇文章中,我们探索不同的反向Ajax技术,使用可下载的例子来学习使用了流(streaming)方法和长轮询(long polling)方法的Comet。

  前言

  web开发在过去的几年中有了很大的进展,我们已经远超了把静态网页链接在一起的做法,这种做法会引起浏览器的刷新,并且要等待页面的加载。现在需要的是能够通过web来访问的完全动态的应用,这些应用通常需要尽可能的快,提供近乎实时的组件。在这一新的由五部分组成的文章系列中,我们学习如何使用反向Ajax(Reverse Ajax)技术来开发事件驱动的web应用。

  在这第一篇文章中,我们要了解反向Ajax、轮询(polling)、流(streaming)、Comet和长轮询(long polling),学习如何实现不同的反向Ajax通信技术,并探讨每种方法的优点和缺点。你可以下载本文中例子的相应源代码。

  Ajax、反向Ajax和WebSocket

  异步的JavaScript和XML(Asynchronous JavaScript and XML,Ajax),一种可通过JavaScript来访问的浏览器功能特性,其允许脚本向幕后的网站发送一个HTTP请求而又无需重新加载页面。Ajax的出现已经超过了十年,尽管其名字中包含了XML,但你几乎可以在Ajax请求中传送任何的东西,最常用的数据是JSON,其与JavaScript语法很接近,且消耗更少带宽。清单1给出了这样的一个例子,Ajax请求通过某个地方的邮政编码来检索该地的名称。

  清单1. Ajax请求举例

var url ='http://www.geonames.org/postalCodeLookupJSON?postalcode='
  + $('#postalCode').val()+'&country='
  + $('#country').val()+'&callback=?';
  $.getJSON(url,
function(data) {
  $(
'#placeName').val(data.postalcodes[0].placeName);
});

  在本文可下载的源代码中,你可在listing1.html中看到这一例子的作用。

  反向Ajax(Reverse Ajax)本质上则是这样的一种概念:能够从服务器端向客户端发送数据。在一个标准的HTTP Ajax请求中,数据是发送给服务器端的,反向Ajax可以某些特定的方式来模拟发出一个Ajax请求,这些方式本文都会论及,这样的话,服务器就可以尽可能快地向客户端发送事件(低延迟通信)。

  WebSocket技术来自HTML5,是一种最近才出现的技术,许多浏览器已经支持它(Firefox、Google Chrome、Safari等等)。WebSocket启用双向的、全双工的通信信道,其通过某种被称为WebSocket握手的HTTP请求来打开连接,并用到了一些特殊的报头。连接保持在活动状态,你可以用JavaScript来写和接收数据,就像是正在用一个原始的TCP套接口一样。WebSocket会在这一文章系列的第二部分中谈及。

  反向Ajax技术

  反向Ajax的目的是允许服务器端向客户端推送信息。Ajax请求在缺省情况下是无状态的,且只能从客户端向服务器端发出请求。你可以通过使用技术模拟服务器端和客户端之间的响应式通信来绕过这一限制。

  HTTP轮询和JSONP轮询

  轮询(polling)涉及了从客户端向服务器端发出请求以获取一些数据,这显然就是一个纯粹的Ajax HTTP请求。为了尽快地获得服务器端事件,轮询的间隔(两次请求相隔的时间)必须尽可能地小。但有这样的一个缺点存在:如果间隔减小的话,客户端浏览器就会发出更多的请求,这些请求中的许多都不会返回任何有用的数据,而这将会白白地浪费掉带宽和处理资源。

  图1中的时间线说明了客户端发出了某些轮询请求,但没有信息返回这种情况,客户端必须要等到下一个轮询来获取两个服务器端接收到的事件。

  图1. 使用HTTP轮询的反向Ajax

  JSONP轮询基本上与HTTP轮询一样,不同之处则是JSONP可以发出跨域请求(不是在你的域内的请求)。清单1使用JSONP来通过邮政编码获取地名,JSONP请求通常可通过它的回调参数和返回内容识别出来,这些内容是可执行的JavaScript代码。

  要在JavaScript中实现轮询的话,你可以使用setInterval来定期地发出Ajax请求,如清单2所示:

  清单2. JavaScript轮询

setInterval(function() {
  $.getJSON(
'events',function(events) {
    console.log(events);
  });
},
2000);

  文章源代码中的轮询演示给出了轮询方法所消耗的带宽,间隔很小,但可以看到有些请求并未返回事件,清单3给出了这一轮询示例的输出。

  清单3. 轮询演示例子的输出

[client] checking for events...
[client] no event
[client] checking for events...
[client]2 events
[event] At Sun Jun0515:17:14 EDT2011
[event] At Sun Jun0515:17:14 EDT2011
[client] checking for events...
[client]1 events
[event] At Sun Jun0515:17:16 EDT2011

  用JavaScript实现的轮询的优点和缺点:

  1. 优点:很容易实现,不需要任何服务器端的特定功能,且在所有的浏览器上都能工作。

  2. 缺点:这种方法很少被用到,因为它是完全不具伸缩性的。试想一下,在100个客户端每个都发出2秒钟的轮询请求的情况下,所损失的带宽和资源数量,在这种情况下30%的请求没有返回数据。

  Piggyback

  捎带轮询(piggyback polling)是一种比轮询更加聪明的做法,因为它会删除掉所有非必需的请求(没有返回数据的那些)。不存在时间间隔,客户端在需要的时候向服务器端发送请求。不同之处在于响应的那部分上,响应被分成两个部分:对请求数据的响应和对服务器事件的响应,如果任何一部分有发生的话。图2给出了一个例子。

  图2. 使用了piggyback轮询的反向Ajax

  在实现piggyback技术时,通常针对服务器端的所有Ajax请求可能会返回一个混合的响应,文章的下载中有一个实现示例,如下面的清单4所示。

  清单4. piggyback代码示例

$('#submit').click(function() {
  $.post(
'ajax',function(data) {
    var valid= data.formValid;
    // 处理验证结果
    //
然后处理响应的其他部分(事件)
    processEvents(data.events);
  });
});

  清单5给出了一些piggyback输出。

  清单5. piggyback输出示例

[client] checking for events...
[server] form valid ? true
[client]4 events
[event] At Sun Jun0516:08:32 EDT2011
[event] At Sun Jun0516:08:34 EDT2011
[event] At Sun Jun0516:08:34 EDT2011
[event] At Sun Jun0516:08:37 EDT2011

  你可以看到表单验证的结果和附加到响应上的事件,同样,这种方法也有着一些优点和缺点:

  1. 优点:没有不返回数据的请求,因为客户端对何时发送请求做了控制,对资源的消耗较少。该方法也是可用在所有的浏览器上,不需要服务器端的特殊功能。

  2. 缺点:当累积在服务器端的事件需要传送给客户端时,你却一点都不知道,因为这需要一个客户端行为来请求它们。

  Comet

  使用了轮询或是捎带的反向Ajax非常受限:其不具伸缩性,不提供低延迟通信(只要事件一到达服务器端,它们就以尽可能快的速度到达浏览器端)。Comet是一个web应用模型,在该模型中,请求被发送到服务器端并保持一个很长的存活期,直到超时或是有服务器端事件发生。在该请求完成后,另一个长生存期的Ajax请求就被送去等待另一个服务器端事件。使用Comet的话,web服务器就可以在无需显式请求的情况下向客户端发送数据。

  Comet的一大优点是,每个客户端始终都有一个向服务器端打开的通信链路。服务器端可以通过在事件到来时立即提交(完成)响应来把事件推给客户端,或者它甚至可以累积再连续发送。因为请求长时间保持打开的状态,故服务器端需要特别的功能来处理所有的这些长生存期请求。图3给出了一个例子。(这一文章系列的第2部分会更加详细地解释服务器端的约束条件)。

  图3. 使用Comet的反向Ajax

  Comet的实现可以分成两类:使用流(streaming)的那些和使用长轮询(long polling)的那些。

  使用HTTP流的Comet

  在流(streaming)模式中,有一个持久连接会被打开。只会存在一个长生存期请求(图3中的#1),因为每个到达服务器端的事件都会通过这同一连接来发送。因此,客户端需要有一种方法来把通过这同一连接发送过来的不同响应分隔开来。从技术上来讲,两种常见的流技术包括Forever Iframe(隐藏的IFrame),或是被用来在JavaScript中创建Ajax请求的XMLHttpRequest对象的多部分(multi-part)特性。

  Forever Iframe

  Forever Iframe(永存的Iframe)技术涉及了一个置于页面中的隐藏Iframe标签,该标签的src属性指向返回服务器端事件的servlet路径。每次在事件到达时,servlet写入并刷新一个新的script标签,该标签内部带有JavaScript代码,iframe的内容被附加上这一script标签,标签中的内容就会得到执行。

  1. 优点:实现简单,在所有支持iframe的浏览器上都可用。

  2. 缺点: 没有方法可用来实现可靠的错误处理或是跟踪连接的状态,因为所有的连接和数据都是由浏览器通过HTML标签来处理的,因此你没有办法知道连接何时在哪一端已被断开了。

  Multi-part XMLHttpRequest

  第二种技术,更可靠一些,是XMLHttpRequest对象上使用某些浏览器(比如说Firefox)支持的multi-part标志。Ajax请求被发送给服务器端并保持打开状态,每次有事件到来时,一个多部分的响应就会通过这同一连接来写入,清单6给出了一个例子。

  清单6. 设置Multi-part XMLHttpRequest的JavaScript代码示例

var xhr = $.ajaxSettings.xhr();
xhr.multipart
=true;
xhr.open(
'GET','ajax',true);
xhr.onreadystatechange
= function() {
  if (xhr.readyState== 4) {
    processEvents($.parseJSON(xhr.responseText));
  }
};
xhr.send(
null);

  在服务器端,事情要稍加复杂一些。首先你必须要设置多部分请求,然后挂起连接。清单7展示了如何挂起一个HTTP流请求。(这一系列的第3部分会更加详细地谈及这些API。)

  清单7. 使用Servlet 3 API来在servlet中挂起一个HTTP流请求

protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
  // 开始请求的挂起
  AsyncContext asyncContext = req.startAsync();
  asyncContext.setTimeout(
0);

  // 给客户端发回多部分的分隔符
  resp.setContentType("multipart/x-mixed-replace;boundary=\""
  + boundary+"\"");
  resp.setHeader(
"Connection","keep-alive");
  resp.getOutputStream().print(
"--"+ boundary);
  resp.flushBuffer();

  // 把异步上下文放在列表中以被将来只用
  asyncContexts.offer(asyncContext);
}

  现在,每次有事件发生时你都可以遍历所有的挂起连接并向它们写入数据,如清单8所示:

  清单8. 使用Servlet 3 API来向挂起的多部分请求发送事件

for (AsyncContext asyncContext : asyncContexts) {
  HttpServletResponse peer
= (HttpServletResponse)
  asyncContext.getResponse();
  peer.getOutputStream().println(
"Content-Type: application/json");
  peer.getOutputStream().println();
  peer.getOutputStream().println(
new JSONArray()
  .put(
"At "+new Date()).toString());
  peer.getOutputStream().println(
"--"+ boundary);
  peer.flushBuffer();
}

  本文可下载文件的Comet-straming文件夹中的部分说明了HTTP流,在运行例子并打开主页时,你会看到只要事件一到达服务器端,虽然不同步但它们几乎立刻会出现在页面上。而且,如果打开Firebug控制台的话,你就能看到只有一个Ajax请求是打开的。如果再往下看一些,你会看到JSON响应被附在Response选项卡中,如图4所示:

  图4. HTTP流请求的FireBug视图

  照例,做法存在着一些优点和缺点:

  1. 优点:只打开了一个持久连接,这就是节省了大部分带宽使用率的Comet技术。

  2. 缺点:并非所有的浏览器都支持multi-part标志。某些被广泛使用的库,比如说用Java实现的CometD,被报告在缓冲方面有问题。例如,一些数据块(多个部分)可能被缓冲,然后只有在连接完成或是缓冲区已满时才被发送,而这有可能会带来比预期要高的延迟。

  使用HTTP长轮询的Comet

  长轮询(long polling)模式涉及了打开连接的技术。连接由服务器端保持着打开的状态,只要一有事件发生,响应就会被提交,然后连接关闭。接下来。一个新的长轮询连接就会被正在等待新事件到达的客户端重新打开。

  你可以使用script标签或是单纯的XMLHttpRequest对象来实现HTTP长轮询。

  script标签

  正如iframe一样,其目标是把script标签附加到页面上以让脚本执行。服务器端则会:挂起连接直到有事件发生,接着把脚本内容发送回浏览器,然后重新打开另一个script标签来获取下一个事件。

  1. 优点:因为是基于HTML标签的,所有这一技术非常容易实现,且可跨域工作(缺省情况下,XMLHttpRequest不允许向其他域或是子域发送请求)。

  2. 缺点:类似于iframe技术,错误处理缺失,你不能获得连接的状态或是有干涉连接的能力。

  XMLHttpRequest长轮询

  第二种,也是一种推荐的实现Comet的做法是打开一个到服务器端的Ajax请求然后等待响应。服务器端需要一些特定的功能来允许请求被挂起,只要一有事件发生,服务器端就会在挂起的请求中送回响应并关闭该请求,完全就像是你关闭了servlet响应的输出流。然后客户端就会使用这一响应并打开一个新的到服务器端的长生存期的Ajax请求,如清单9所示:

  清单9. 设置长轮询请求的JavaScript代码示例

function long_polling() {
  $.getJSON(
'ajax',function(events) {
    processEvents(events);
    long_polling();
  });
}
long_polling();

  在后端,代码也是使用Servlet 3 API来挂起请求,正如HTTP流的做法一样,但你不需要所有的多部分处理代码,清单10给出了一个例子。

  清单10. 挂起一个长轮询Ajax请求

protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
  AsyncContext asyncContext
= req.startAsync();
  asyncContext.setTimeout(
0);
  asyncContexts.offer(asyncContext);
}

  在接收到事件时,只是取出所有的挂起请求并完成它们,如清单11所示:

  清单11. 在有事件发生时完成长轮询Ajax请求

while (!asyncContexts.isEmpty()) {
  AsyncContext asyncContext
= asyncContexts.poll();
  HttpServletResponse peer
= (HttpServletResponse)
  asyncContext.getResponse();
  peer.getWriter().write(
    new JSONArray().put("At" + new Date()).toString());
  peer.setStatus(HttpServletResponse.SC_OK);
  peer.setContentType(
"application/json");
  asyncContext.complete();
}

  在附带的下载源文件中,comet-long-polling文件夹包含了一个长轮询示例web应用,你可以使用 mvn jetty:run 命令来运行它。

  1. 优点:客户端很容易实现良好的错误处理系统和超时管理。这一可靠的技术还允许在与服务器端的连接之间有一个往返,即使连接是非持久的(当你的应用有许多的客户端时,这是一件好事)。它可用在所有的浏览器上;你只需要确保所用的XMLHttpRequest对象发送到的简单的Ajax请求就可以了。

  2. 缺点:相比于其他技术来说,不存在什么重要的缺点,像所有我们已经讨论过的技术一样,该方法依然依赖于无状态的HTTP连接,其要求服务器端有特殊的功能来临时挂起连接。

  建议

  因为所有现代的浏览器都支持跨域资源共享(Cross-Origin Resource Share,CORS)规范,该规范允许XHR执行跨域请求,因此基于脚本的和基于iframe的技术已成为了一种过时的需要。

  把Comet做为反向Ajax的实现和使用的最好方式是通过XMLHttpRequest对象,该做法提供了一个真正的连接句柄和错误处理。考虑到不是所有的浏览器都支持multi-part标志,且多部分流可能会遇到缓冲问题,因此建议你选择经由HTTP长轮询使用XMLHttpRequest对象(在服务器端挂起的一个简单的Ajax请求)的Comet模式,所有支持Ajax的浏览器也都支持该种做法。

  结论

  本文提供的是反向Ajax技术的一个入门级介绍,文章探索了实现反向Ajax通信的不同方法,并说明了每种实现的优势和弊端。你的具体情况和应用需求将会影响到你对最合适方法的选择。不过一般来说,如果你想要在低延迟通信、超时和错误检测、简易性,以及所有浏览器和平台的良好支持这几方面有一个最好的折中的话,那就选择使用了Ajax长轮询请求的Comet。

  请继续阅读这一系列的第2部分:该部分将会探讨第三种反向Ajax技术:WebSocket。尽管还不是所有的浏览器都支持该技术,但WebSocket肯定是一种非常好的反向Ajax通信媒介,WebSocket消除了所有与HTTP连接的无状态特性相关的限制。第2部分还会谈及由Comet和WebSocket技术带来的服务器端约束。

  代码下载

  reverse_ajaxpt1_source.zip

用 ASP.NET MVC 实现基于 Multipart XMLHttpRequest 的 Comet

Comet是什么?

英文解释:Comet is a web application model in which a long-held HTTP request allows a web server to push data to a browser, without the browser explicitly requesting it. (来自wiki)

中文解释:Comet是基于 HTTP 长连接的“服务器推”技术(来自IBM developerWorks)。

Multipart XMLHttpRequest是什么?

英文解释:It is a method for bundling multiple HTTP requests into a single HTTP request and unbundling on the client side through a Javascript handler. (来自TechnoMagicians Blog)

中文解决:它允许客户端只用一个HTTP请求就可以从服务端向客户端传送多个资源(来自鼓浪鱼-前端博客)。

为什么要用 ASP.NET MVC 实现?

昨天学习了“反向Ajax,第1部分:Comet介绍”(英文版),但文中的代码服务端代码用的是Java。为了更好的理解这部分知识,实际体验一下,于是用ASP.NET MVC实现了其中一个示例 —— 基于 Multi-part XMLHttpRequest 的 Comet。

这东西有实用价值吗?

说实话,没有实用价值,只为学习之用。因为支持Multipart XMLHttpRequest的浏览器太少,目前只知道FireFox支持,Chrome与IE9都不支持。

代码示例

1. 前端JavaScript代码

复制代码
var xhr = $.ajaxSettings.xhr();xhr.multipart = true;xhr.open('GET', '/comet/multipart', true);xhr.onreadystatechange = function () {    if (xhr.readyState == 4) {        $('#logs').append(xhr.responseText+"<br/>");    }};xhr.send(null);
复制代码

关键代码就是xhr.multipart = true;

2. 服务端ASP.NET MVC 控制器代码

复制代码
public class CometController : Controller{    //结束标志,这是随机生成的    static string Boundary = "ABCDEFGHIJKLMNOPQRST";    public ActionResult Multipart()    {        Response.ContentType = "multipart/x-mixed-replace;boundary=\"" + Boundary + "\"";        Response.Headers.Add("Connection", "keep-alive");        Response.Output.Write("--" + Boundary);        Response.Flush();        //每隔5秒种向客户端发送一次数据        while (true)        {            //发送给客户端的数据的MIME类型,如果是JSON,就用application/json            //注意这里一定要用WriteLine()            Response.Output.WriteLine("Content-Type: plain/text");            //这句生成空行的代码不能少            Response.Output.WriteLine();            Response.Output.WriteLine(DateTime.Now.ToString("HH:mm:ss.FFF"));            //发送结束标志,客户端就知道完成了一次发送            Response.Output.WriteLine("--" + Boundary);            Response.Flush();            System.Threading.Thread.Sleep(5000);        }    }}
复制代码

上面这段代码虽然看起来简单,但当时调试时还是费了一些周折。

代码下载

CometMvcDemo.rar

需要在IIS中以集成模式运行该程序(以VS2010内置Web服务器运行会报错)。访问MultipartXhr.htm,会5秒钟显示一次服务器时间。

基于 Multipart XMLHttpRequest 的 Comet 

用 ASP.NET MVC 实现基于 XMLHttpRequest long polling(长轮询) 的 Comet

之前在“反向Ajax,第1部分:Comet介绍”(英文版)文章中学习了“基于 Multipart XMLHttpRequest 的 Comet”的知识,然后用 ASP.NET MVC 实现了一个,详见用 ASP.NET MVC 实现基于 Multipart XMLHttpRequest 的 Comet。

今天继续学习了基于 XMLHttpRequest long polling 的 Comet,又用 ASP.NET MVC 实现了一个,在这篇文章中分享一下。

先了解一下什么是XMLHttpRequest long polling?

这是一种推荐的实现Comet的做法,打开一个到服务器端的Ajax请求然后等待响应。服务器端需要一些特定的功能来允许请求被挂起,只要一有事件发生,服务器端就会在挂起的请求中送回响应并关闭该请求。然后客户端就会使用这一响应并打开一个新的到服务器端的长生存期的Ajax请求。

This is a recommended method to implement Comet is to open an Ajax request to the server and wait for the response. The server requires specific features on the server side to allow the request to be suspended. As soon as an event occurs, the server sends back the response in the suspended request and closes it. The client then consumes the response and opens a new long-lived Ajax request to the server.

我个人的理解是,看起来就像在Web环境中客户端能订阅服务端的事件,服务器端通过事件去通知客户端。如果服务器端用 ASP.NET 来实现,可以利用 .NET 的事件驱动机制,很有意思,下面的示例代码将展示这一点。

先看Web前端js代码:

复制代码
jQuery(function ($) {    function long_polling() {
$.ajaxSetup({ cache: false });//一定要加这句,不然IE会不停的输出。        $.getJSON('/comet/LongPolling', function (data) {            if (data.d) {                $('#logs').append(data.d + "<br/>");            }             long_polling();        });    }    long_polling();});
复制代码

js代码很简单,就是一个递归调用(调用在callback时进行的),通过jQuery的$.getJSON发起Ajax请求,'/comet/LongPolling' 表示请求的服务端 CometController 的 LongPolling Action 的网址。这里我们可以看出实现 Comet 的难点不在 Web 前端,而是在服务器端。

接下来重点看 Web 服务器 ASP.NET MVC Controller 的代码。

首先要注意的是为了响应 XMLHttpRequest long polling 请求,我们需要实现一个异步控制器(AsyncController),如果您对 AsyncController 不熟悉,建议阅读MSDN上的文章Using an Asynchronous Controller in ASP.NET MVC 。

先上 Controller 的实现代码:

(注:该控制器实现的功能是每隔5秒钟向客户端发送服务器当时间)

复制代码
public class CometController : AsyncController{    //LongPolling Action 1 - 处理客户端发起的请求    public void LongPollingAsync()    {        //计时器,5秒种触发一次Elapsed事件        System.Timers.Timer timer = new System.Timers.Timer(5000);        //告诉ASP.NET接下来将进行一个异步操作        AsyncManager.OutstandingOperations.Increment();        //订阅计时器的Elapsed事件        timer.Elapsed += (sender, e) =>            {                //保存将要传递给LongPollingCompleted的参数                AsyncManager.Parameters["now"] = e.SignalTime;                //告诉ASP.NET异步操作已完成,进行LongPollingCompleted方法的调用                AsyncManager.OutstandingOperations.Decrement();            };        //启动计时器        timer.Start();    }    //LongPolling Action 2 - 异步处理完成,向客户端发送响应    public ActionResult LongPollingCompleted(DateTime now)    {        return Json(new { d = now.ToString("MM-dd HH:mm:ss ") +             "-- Welcome to cnblogs.com!" },             JsonRequestBehavior.AllowGet);    }}   
复制代码

实现异步控制器需要继承 System.Web.Mvc.AsyncController,并将 Action 分解为两个,比如 Action 叫 LongPolling,则分解为 LongPollingAsync 与 LongPollingCompleted 。LongPollingAsync 接受客户端请求,并发起异步操作;异步操作完成,调用LongPollingCompleted。

AsyncManager.OutstandingOperations.Increment(); 告诉ASP.NET接下来将进行一个异步操作。

AsyncManager.OutstandingOperations.Decrement(); 告诉ASP.NET异步操作完成,请调用LongPollingCompleted()方法。

示例代码中的异步操作就是将服务器当前时间作为参数传递给 LongPollingCompleted() 方法,LongPollingCompleted() 获取服务器当前时间并传递给客户端,客户端收到后将之显示出来,将继续发起 Ajax 请求 ... 这样周而复始,实现了基于 XMLHttpRequest long polling 的 Comet。

示例代码运行结果如下:

代码下载

CometMvcDemo_LongPolling.rar


以上是从网上转载的资料。

我的代码

Index.cshtml

@model IEnumerable<CRM.Models.tb_Channel>
@{
    ViewBag.Title = "语音通道";
}
<script type="text/javascript">
    $(function ($) {
        $.ajaxSetup({ cache: false });
        function longPolling() {
            $.get('/ChannelPolling/LongPolling', function (data) {
                $("#logs")
                    .empty()
                    .html(data);
                longPolling();
            });
        }
        longPolling();
    });
</script>
<div id="logs" style="font-family: monospace;">
    <table class="tab">
        <tr>
            <th>
                通道
            </th>
            <th>
                类型
            </th>
            <th>
                状态
            </th>
            <th>
                来电
            </th>
            <th>
                坐席
            </th>
        </tr>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.FChID)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.FChType)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.FChStatus)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.FCaller)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.FUserNo)
                </td>
            </tr>
        }
    </table>
</div>

ChannelController.cs

using System.Web.Mvc;
using CRM.Models.DAL;


namespace CRM.Controllers
{
    public class ChannelController : Controller
    {
        private readonly Channel _dal = new Channel();


        public ActionResult Index()
        {
            var list = _dal.GetList();
            return View(list);
        }


        protected override void Dispose(bool disposing)
        {
            _dal.Dispose();
            base.Dispose(disposing);
        }
    }
}

using System.Data.SqlClient;
using System.Web.Mvc;
using CRM.Models.DAL;


namespace CRM.Controllers
{
    public class ChannelPollingController : AsyncController
    {
        private readonly Channel _dal = new Channel();
        private readonly string _connString =
            System.Configuration.ConfigurationManager.ConnectionStrings["ConnString"].ConnectionString;
        private const string Sql = "SELECT [FChID],[FChType],[FChStatus],[FUserNo],[FCaller] FROM [dbo].[tb_Channel]";


        public ChannelPollingController()
        {
            //启动数据库主动通知
            var perm = new SqlClientPermission(System.Security.Permissions.PermissionState.Unrestricted);
            perm.Demand();
            //启动侦听器
            SqlDependency.Start(_connString);
        }


        public void LongPollingAsync()
        {
            AsyncManager.OutstandingOperations.Increment();
            using (var conn = new SqlConnection(_connString))
            {
                var cmd = new SqlCommand(Sql, conn);
                //设置通知   
                var dep = new SqlDependency(cmd);
                //通知事件   
                dep.OnChange += SqlDependency_OnChange;
                //打开连接
                conn.Open();
                //查询
                cmd.ExecuteNonQuery();
            }
        }


        /// <summary>   
        /// 通知事件   
        /// </summary>   
        void SqlDependency_OnChange(object sender, SqlNotificationEventArgs args)
        {
            AsyncManager.OutstandingOperations.Decrement();
            var dep = (SqlDependency)sender;
            dep.OnChange -= SqlDependency_OnChange;
            LongPollingAsync();
        }


        public PartialViewResult LongPollingCompleted()
        {
            var list = _dal.GetList();
            return PartialView("_Channel", list);
        }


        protected override void Dispose(bool disposing)
        {
            _dal.Dispose();
            SqlDependency.Stop(_connString);
            base.Dispose(disposing);
        }
    }
}

@model IEnumerable<CRM.Models.tb_Channel>
<table class="tab">
    <tr>
        <th>
            通道
        </th>
        <th>
            类型
        </th>
        <th>
            状态
        </th>
        <th>
            来电
        </th>
        <th>
            坐席
        </th>
    </tr>
    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.FChID)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FChType)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FChStatus)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FCaller)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FUserNo)
            </td>
        </tr>
    }
</table>

原创粉丝点击