使用SignalR和XSLT进行实时注释
Introduction 众所周知,web请求(HTTP请求)是根据请求/响应机制工作的。通过这种方式,作为客户机的浏览器使用GET或POST向服务器发送请求,服务器根据客户机对它的请求准备适当的响应,然后关闭它们之间的连接。因此,这个过程是一种单向通信,客户端是初学者,因此服务器只是一个响应,它不能发送一个请求给客户端,并通知他们自己的状态。对于开发人员来说,这种请求/响应机制似乎是一个麻烦的限制。为什么我们被强制发送一个请求来告知当前的服务器状态?我们如何摆脱这个响应/请求问题?这个问题促使开发人员尝试许多方法、技巧和技术来克服这个限制。其中的一些方法是: 刷新页面在一个周期时间:这个方法是最糟糕的,因为它刷新所有元素的指定页面没有必要性refreshmentRefreshing页面在一个周期的一部分时间使用Iframe 彗星Programming TechniquesWeb Socket : HTML5和IIS7 在本文中,我们主要讨论Web套接字、Comet技术和SignalR,它是一个很好的工具,可以帮助在ASP中使用Comet技术。网络开发者。 彗星Programming Techniques 我引用以下维基百科的描述: Comet是一种web应用程序模型,在该模型中,一个长期存在的HTTP 请求允许web服务器将数据推送到浏览器,而不需要浏览器明确地请求它。所有这些方法都依赖于浏览器默认包含的特性,比如JavaScript,而不是非默认插件。Comet方法不同于web的原始模型,在原始模型中,浏览器一次请求一个完整的web页面 Comet技术在web开发中的使用早于Comet一词作为集体技术的新词的使用。Comet还有其他几个名字,包括Ajax Push,[4][5] Reverse Ajax,[6]双向web,[7] HTTP流,[7]和HTTP服务器Push[8]等等。 Comet有一些用于实现的技术,它们是两大类的子集:流和长轮询 流媒体包括以下: 依次向服务器(传统的AJAX)隐藏框架发送AJAX请求 长轮询的AJAX:“以上所有流传输都不能在所有现代浏览器上正常工作而不产生负面的副作用。这迫使Comet开发人员实现几个复杂的流传输,并根据浏览器在它们之间切换。因此,许多Comet应用程序使用长轮询,这更容易在浏览器端实现,并且至少可以在支持XHR的每个浏览器中工作。顾名思义,长轮询要求客户机向服务器轮询一个事件(或一组事件)。浏览器向服务器发出ajax风格的请求,该请求一直保持打开状态,直到服务器有新数据要发送给浏览器,该数据将以完整的响应发送给浏览器。浏览器启动一个新的长轮询请求,以获取后续事件。完成长轮询的具体技术包括: XMLHttpRequest长轮询脚本标记长轮询 SignalR 使用comet技术进行编程是很复杂的,如果您想这样做的话,它将是一项困难而耗时的工作。SignalR是一个用于ASP的库。NET在web应用程序中添加实时功能。使用SignalR并不困难,但更重要的是,它根据浏览器的功能使用一组comet技术。如果你的浏览器不支持HTML5和web套接字技术,SignalR将回到以前的框架或长池作为comet优先级(例如:IE8)。使用SignalR需要有丰富的背景对客户端和服务器之间的通信,但是这个库是一个简单的方法使用彗星,这可能就像一把双刃剑肤浅的程序员(程序员所使用客户端框架的豪言壮语jQuery和JavaScript编程没有经验,因为他们大多没有深度了解客户端编程),这可能会导致缺乏很多不适。此外,你应该了解。net 4中的动态类型。除此之外,SignalR是一个很棒的库,它提供了许多用于创建实时web应用程序的高级功能。 如何安装信号 进入Nuget: PM>安装包Microsoft.AspNet.SignalR 和 PM>安装包SignalR版本0.6.1 为了得到它的例子,在Nuget上键入这一行: PM>安装包Microsoft.AspNet.SignalR.Sample安装后,一些dll将添加到bin目录中,一些JavaScript文件将添加到Scripts文件夹中。 对于IE8,你应该安装JSON2.js如下: PM>安装包json2 如果这个文件没有安装到你的脚本文件夹,你会遇到这个错误: 隐藏,复制Code
No JSON parser found. Please ensure json2.js is referenced before the SignalR.js file
如何使用信号 有两种使用SignalR的框架,包括Hub和persistence connection。Hub提供了一个比持久连接更高级的框架。PersistentConnection给了你很多控制权。如果您想让SignalR为您做所有繁重的工作,并将困难的工作分配给SignalR来自动完成,您可以使用Hub框架。在本文中,我们使用Hub。 首先将以下代码写入全局变量。在ApplicationStart()方法中的asax: 隐藏,复制Code
RouteTable.Routes.MapHubs();
与低级的持久连接不同,不需要为hub指定路由,因为它们可以通过一个特殊的URL自动访问。 创建一个从Hub类继承的类。 隐藏,复制Code
public class CommentHub : Hub {......}
客户机调用服务器 要展示一个可从所有客户端调用的方法,只需声明一个公共方法: 隐藏,复制Code
public class MyHub : Hub { public string Send(string data) { return data; } }
此方法可从客户端访问。客户端使用指定的数据作为参数调用此方法,然后此方法可以对输入数据执行任何操作,然后将其作为输出返回。 隐藏,复制Code
public string get(string data) { return data; }
服务器调用客户端 从服务器调用客户端方法使用客户端属性: 隐藏,复制Code
public string get(string data) { return Clients.All.broadcastMessage(data); }
现在我们扩展我们的描述,关注细节并仔细阅读项目代码。 在这个项目中,主要文件有: CommentHub。cs:根据注释成功或失败的过程,将添加的注释的XML文档(已在客户机中生成)转换为适当的html。发表评论。注释的实体及其属性注释。xslt Comments.aspx CommentHub类 隐藏,收缩,复制Code
public class CommentHub : Hub { public void Send(string name, string message, int status, Guid hubId, string thisCommentId,string contentId,string currentUserID) { XmlDocument XmlMessage = new XmlDocument(); XmlMessage.LoadXml(message); string CommentId=XmlMessage.SelectSingleNode("comment").Attributes["id"].Value; string messageBody = XmlMessage.SelectSingleNode("comment/message").InnerText; XSLTransformer XSL = new XSLTransformer(); XsltArgumentList argsList = new XsltArgumentList(); try { if (messageBody != string.Empty && messageBody != null) { if (message.Contains("fail")) { argsList.AddParam("ResultSet", "", 0); Clients.All.broadcastMessage(name, XSL.TransformToHtml(XmlMessage, "Comments.xslt", argsList), 0, hubId, CommentId); } //Adding parameters to XSLT //If ResultSet set to 1,it means success else { argsList.AddParam("ResultSet", "", 1); argsList.AddParam("CommentingDate", "", DateTime.Now); Clients.All.broadcastMessage(name, XSL.TransformToHtml(XmlMessage, "Comments.xslt", argsList).Replace( "<?xml version=\"1.0\" encoding=\"utf-16\"?>", ""), 1, hubId, CommentId); } } } catch (Exception exp) { //Adding parameters to XSLT //If ResultSet set to 0,it means fail argsList.AddParam("ResultSet", "", 0); switch (exp.GetType().ToString()) { case "SqlException": Clients.All.broadcastMessage(name, XSL.TransformToHtml(XmlMessage, "Comments.xslt", argsList), -2, hubId,CommentId); break; default: Clients.All.broadcastMessage(name, XSL.TransformToHtml(XmlMessage, "Comments.xslt", argsList), 0, hubId, CommentId); break; } } } }
从客户端调用Send()方法,客户端将所需的参数传递给该方法。其中一个参数是message。实际上message是由客户机生成的XML,它包含当前评论的一些属性和属性,如:用户名、CommentID和message。这个参数被加载到一个XmlDocument中,然后XSLTransformer将这个XML文档转换为将广播给用户的适当的HTML文档。在这个项目中,没有数据库中的存储注释或其他功能的代码,但您可以做任何您想做的事情。例如,假设我们的代码将注释存储到数据库中,通过插入过程,代码遇到了异常,比如连接失败等等。在这种情况下,我们想通知用户他的评论没有被发送,并给他再一次机会重新发送评论。为了实现此操作,我们将ResultSet参数添加到XSLT Transformer的参数列表中,以便对html输出进行决策。如果结果集被设置为“0”,则表示失败,而“1”表示成功。正如我写的,客户,所有人。broadcastMessage意味着我们在客户端有一个名为“broadcastMessage”的函数,服务器可以调用它。 下面的代码模拟了失败状态。如果你写了一个包含“fail”的句子,你会看到失败状态的结果。 隐藏,复制Code
//Lines 38 to 42 simulate failed status if (message.Contains("fail")) { argsList.AddParam("ResultSet", "", 0); Clients.All.broadcastMessage(name, XSL.TransformToHtml(XmlMessage, "Comments.xslt", argsList), 0, hubId, CommentId); }
SignalR JS客户端hub: 在你的页面上包括以下脚本: 隐藏,复制Code
<script src="http://www.codeproject.com/Scripts/json2.js"></script> <script src="http://www.codeproject.com/Scripts/jquery-1.7.1.min.js"></script> <script src="http://www.codeproject.com/Scripts/jquery.signalR-1.0.0-rc2.js"></script> <script src="http://www.codeproject.com/signalr/hubs"></script>
编程模型 .connection.hub美元 所有集线器的连接(URL指向/signalr)。 返回a 连接 .connection.hub.id美元 hub连接的客户端id。 .connection.hub.logging美元 设置为true则启用日志记录。默认的是假的 $ .connection.hub.start () 启动所有集线器的连接。 查看其他重载,开始()返回jQuery延迟 .connection美元。{hubname}, 从生成的代理访问客户端中心。 返回一个中心。服务器上集线器的名称。注意:集线器的名称是驼峰式大小写(例如,如果服务器集线器是MyHub,连接上的属性将是MyHub) stateChanged是一个函数,每次连接状态改变时执行。 隐藏,复制Code
$.connection.hub.error(function (error) { $.connection.hub.stop(); }); $.connection.hub.stateChanged(function (change) { if (change.newState === $.signalR.connectionState.reconnecting) { //console.log('Re-connecting'); } else if (change.newState === $.signalR.connectionState.connected) { //console.log('The server is online'); } });
首先,我们声明一个变量如下: 隐藏,复制Code
var commentList = $.connection.commentHub;
根据以上描述,其功能如下: 隐藏,收缩,复制Code
//-------functions------ $(function () { commentList.client.broadcastMessage = function (name, message, status, hubId, thisCommentId) { var messageResonse = message; var oo = thisCommentId.toString(); var GUID = null; if (writerHubId == hubId) { GUID = comments.generateGuid(); messageResonse = message + "<tr><td align='left' id='delete_" + GUID + "' width='200px' align='left' style='color:red;'>" + "<a href='javascript:void(0)' " + "onclick='javascript:comments.deleteComment(\"" + oo + "\");return false;'><img title='delete' " + "style='width:20px;height:20px;border:0px;cursor:pointer' " + "src='Images/trash_green.png' /></a></td></tr>; } messageResonse = "<table width='100%' id='PT_" + thisCommentId + "' style='background-color:#F1F1F1;" + "border:1px;border-style:solid;border-color:#C4C4C4'>" + comments.setRealBreakLine(messageResonse) + "</table><table id='HDT_" + thisCommentId + "' width='100%'><tr><td " + "height='15px'></td></tr></table>"; if (status == 1) $('#Comments').append(messageResonse); else if ((writerHubId == hubId) && (status == 0 || status == -2)) $('#Comments').append(comments.setRealBreakLineForInnerTextOnTextArea(message)); }; $('#message').focus(); $.connection.hub.start({ waitForPageLoad: false }, function () { }).done(function () { $('#sendmessage').click(function () { sendOverHub(); }); }); $.connection.hub.error(function (error) { $.connection.hub.stop(); }); $.connection.hub.stateChanged(function (change) { if (change.newState === $.signalR.connectionState.reconnecting) { //console.log('Re-connecting'); } else if (change.newState === $.signalR.connectionState.connected) { //console.log('The server is online'); } }); });
broadcastMessage函数由服务器调用,并为客户端显示最后的注释。为了检查用户对自己的评论进行删除评论等行为的可访问性,我们需要检查当前用户的hubId是否等于当前评论已发送的hubId。 隐藏,复制Code
var GUID = null; if (writerHubId == hubId) { GUID = comments.generateGuid(); messageResonse = message + "<tr><td align='left' id='delete_" + GUID + "' width='200px' align='left' style='color:red;'><a " + "href='javascript:void(0)' onclick='javascript:comments.deleteComment(\"" + oo + "\");return false;'><img title='delete' " + "style='width:20px;height:20px;border:0px;cursor:pointer' " + "src='Images/trash_green.png' /></a></td></tr>"; }
函数通过CommentHub类的Send方法发送注释。在本文中,我没有为用户使用不同的名称,而是设置了相同的nam因为代码简单,所以被称为“测试用户”。 隐藏,复制Code
function send() { writerHubId = $.connection.hub.id; commentList.server.send( $('test').val() ,comments.ToXml(comments.setFakeBreakline("message"), "Test User",comments.generateGuid()) , -1 , $.connection.hub.id , null , "" , $('').val()); $('#message').val('').focus(); }
如果评论出现错误,你之前的评论会再次出现,并且会显示错误信息,在再次尝试之前还会给你一次编辑评论的机会。当你点击Try again链接时,tryToSendMessageAgain 被调用: 隐藏,复制Code
tryToSendMessageAgain: function (Id) { var messageBody = comments.getLastFailedCommentText(Id); $("#div_" + comments.getPureId(Id)).animate({ height: 'hide', opacity: 'hide' }, 500); window.setTimeout(function () { $("#div_" + comments.getPureId(Id)).remove() },600); writerHubId = $.connection.hub.id; commentList.server.send( "Test User" , comments.ToXml ( comments._setFakeBreaklineForInnerText(messageBody) , "Test User" , comments.generateGuid() ) , -1 , writerHubId , "" , "" , ""); $('#message').val('').focus(); }
thetrytosendmessageagain和send 函数可以在一个集成的函数,但我没有花更多的时间来实现这个。 ToXML()函数将注释转换为XML格式,以便通过XSLT进行转换,而generateGuid函数为每个注释生成一个惟一的ID。 隐藏,复制Code
ToXml: function (message, user, previousID) { return (previousID == null) ? "<comment id='" + this.generateGuid() + "'><message>" + message + "</message><username>" + user + "</username></comment>" : "<comment id='" + previousID + "'><message>" + message + "</message><username>" + user + "</username></comment>"; }
隐藏,复制Code
generateGuid: function () { var result, i, j; result = ''; for (j = 0; j < 32; j++) { if (j == 8 || j == 12 || j == 16 || j == 20) result = result + '-'; i = Math.floor(Math.random() * 16).toString(16).toUpperCase(); result = result + i; } return result; },
XSLTransformer类 这个类根据传递给它的参数将每个注释的xmldocument转换为HTML文档。它包含两个重载方法。如果您想要将一些参数发送到指定的XSLT,那么应该使用第二个参数,它接受参数并将它们传递给XSLT。 隐藏,复制Code
public string TransformToHtml(XmlDocument xmlDocument, string XmlTransformer,XsltArgumentList Argument) { try { XslCompiledTransform transform = new XslCompiledTransform(); transform.Load(HttpContext.Current.Server.MapPath( "~/XSLT/" + XmlTransformer + "")); StringBuilder htmlStrb = new StringBuilder(); StringReader stringReader = new StringReader(xmlDocument.InnerXml); StringWriter stringWriter = new StringWriter(htmlStrb); transform.Transform(new XPathDocument(stringReader), Argument, stringWriter); return htmlStrb.ToString(); } catch (Exception exp) { throw new Exception(exp.InnerException.Message); } }
XSLT XSLT (可扩展样式表语言转换)是一个语言转换XML 文档转换为其他XML文档,[1],或其他对象(as HTML for 网页,,纯text 或into XSL格式化Objects 然后可以用转换PDF, PostScript 以及PNG。[2] 通常,输入文档是XML文件,但是可以使用处理器可以从中构建XQuery和XPath数据模型的任何文件,例如关系数据库表或地理信息系统 原始文件未发生变化;相反,新文档是基于现有文档的内容创建的 XSLT是一种图尔-完整的语言,这意味着它可以执行现代计算机程序所能执行的任何计算。 我发现XSLT是一种非常有用的语言,可以处理各种情况,例如:错误处理、基于服务器或客户端生成的XML导出适当的HTML以及基于用户请求处理各种外观。它有一些巨大的好处,如: 干净,简洁的模板一种简单的方法处理XML数据到HTML合理的快速一种伟大的方法有干净的代码在UI层 在本文中,我使用XSLT处理评论的用户界面和处理诸如连接错误之类的错误。XSLT中有两个模板。第一个调用在服务器端作业完成且没有任何错误时调用,第二个调用在发生错误时调用。 showNewComment:当服务器端作业完成且没有任何错误时调用第一个。 隐藏,复制Code
<xsl:templatematch="/"> <xsl:iftest="$ResultSet='1'"> <xsl:call-templatename="showNewComment"></xsl:call-template> </xsl:if> <xsl:iftest="$ResultSet='0'"> <xsl:call-templatename="showFailedComment"></xsl:call-template> </xsl:if> </xsl:template>
对于基于成功/失败调用正确的调用,我们使用xsl:call-template。 如果在服务器端,ResultSet参数被初始化为1,那么showNewComment 模板被调用;如果它被初始化为0,那么showFailedComment 被调用。 在这个XSLT中,我不关心HTML设计,它看起来可能设计得很糟糕,但这并不重要。这只是一个样本。 根据以上描述,XSLT结构如下: 隐藏,收缩,复制Code
<xsl:stylesheetversion="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:msxsl="urn:schemas-microsoft-com:xslt"exclude-result-prefixes="msxsl"> <xsl:outputmethod="xml"indent="yes"/> <xsl:paramname="ResultSet"></xsl:param> <xsl:paramname="CurrentUser"></xsl:param> <xsl:paramname="CommentingDate"></xsl:param> <xsl:templatematch="/"> <xsl:iftest="$ResultSet='1'"> <xsl:call-templatename="showNewComment"></xsl:call-template> </xsl:if> <xsl:iftest="$ResultSet='0'"> <xsl:call-templatename="showFailedComment"></xsl:call-template> </xsl:if> </xsl:template> <xsl:templatename="showNewComment"> <xsl:for-eachselect="comment"> <xsl:variablename="elementID"select="@id"/> <xsl:variablename="userName"select="username"></xsl:variable> ....... </xsl:for-each> </xsl:template> <xsl:templatename="showFailedComment"> .......... </xsl:for-each> </xsl:template> </xsl:stylesheet>
的兴趣点 这一点,对我来说是很有趣的和有趣的是这一现实,一个杰出的程序员没有丰富的背景是客户机/服务器的概念和缺乏当他们想要程序出现在comet技术之一,显然有些JavaScript 框架类似jQuery,加强这种短缺的程序员没有优秀的背景与JavaScript编程和使用它来处理AJAX请求。这组程序员在理解SignalR是如何工作的时候会混淆。所以我建议他们更深入地学习JavaScript和客户端/服务器的概念。 References Comet编程: http://en.wikipedia.org/wiki/Comet_(编程)SignalR wiki: https://github.com/SignalR/SignalR/wiki History 2013年3月1日(星期五):1.0版本。 本文转载于:http://www.diyabc.com/frontweb/news18852.html
浙公网安备 33010602011771号