实时,异步网页使用jTable, SignalR和ASP。NET MVC

时间:2022-07-15 17:12:54

实时,异步网页使用jTable, SignalR和ASP。NET MVC 图:不同客户端的实时同步表。 点击这里观看现场演示。 文章概述 介绍使用的工具演示实现 模型视图控制器 遗言和感谢参考历史 介绍 HTTP(即web)工作于请求/应答机制。客户端(浏览器)发出请求(通常一个GET或POST)到服务器,服务器将响应(这可能是一个HTML页面,一个图像,一个JSON数据,等等),发送给客户端,然后客户端和服务器之间的连接被关闭(因此,HTTP是称为无连接协议)。服务器不能向客户端异步发送任何信息或通知(没有请求)。这是web页面/站点与桌面应用程序相比的主要缺点之一,因为桌面应用程序可以打开到服务器的TCP连接并异步获取数据。 作为web开发人员,我们尝试了许多方法来克服这一限制。让我们看看一些已知的技术刷新当前页面或页面的某些部分在服务器上的变化: 定期刷新页面:这显然是最糟糕的方法,因为所有页面都要刷新。不幸的是,它仍然在一些新闻门户网站上使用。定期刷新页面中的某个部分:在此方法中,我们定期向服务器发出AJAX请求,并用传入的数据刷新页面的一部分。这通常是在ASP中使用UpdatePanel和Timer实现的。净Web表单。对该方法的改进是只有在片段数据发生更改时才刷新片段。这种方法也不好,而且不可扩展,因为即使在服务器上没有可用的新数据时,我们也会周期性地发出请求,从而使服务器繁忙。另外,它不是实时通知,因为我们是周期性地而不是连续地发出请求。长轮询:这是SignalR使用的方法。我们向服务器发出请求,但直到服务器上有可用的新数据时才接收响应。因此,如果服务器上没有可用的新信息,客户机和服务器将等待,不执行任何操作,因此服务器不繁忙。HTML5自带WebSockets API。它允许我们在客户端和服务器之间创建持久的连接,因此,服务器和客户端可以异步地互相发送数据。它是一个更高级别的TCP连接。在不久的将来,这将成为web上异步通信的标准方式。但是,到目前为止,并不是每个浏览器都完全实现了它。我读过但还没试过,SignalR也支持WebSockets。所以,你现在就可以使用SignalR,并在将来改变传输层。 因此,长轮询是目前最可接受的服务器到客户端通知方式。它快速且可扩展。 在本文中,我实现了一个在服务器上的客户机之间实时同步的HTML表。你可以使用相同的机制来实现一个聊天系统,一个实时股票监视系统,一个实时监控系统…等等。我在本文中重点介绍了使用SignalR,因为我以前已经写过一篇关于jTable的完整文章。 使用工具 下面是用于实现本文演示的工具列表: SignalR:一个开源、功能强大且易于使用的服务器(ASP.NET)和客户端(JavaScript/jQuery)库,用于服务器和客户端之间的异步通信。您可以通过https://github.com/SignalR获得详细信息。jTable:一个开源jQuery插件,用于创建由我开发的基于AJAX的CRUD表。它有分页、排序、选择、主/子表、自动创建表单等等。在http://jtable.org上获取详细信息。还可以在http://www.codeproject.com/KB/ajax/jTable.aspx上看到我关于使用jTable的文章。 我使用jQuery,因为SignalR和jTable都在使用它。我还使用了ASP。但是你可以使用asp.net MVC 3。当然是网络形式。 示范 在继续阅读本文之前,我建议您查看http://jtable.org/RealTime上的运行演示。在两个或模式不同的浏览器窗口中打开这个页面,并添加/删除/更新表中的一些行。你也可以做一个简单的聊天。 实现 这个演示允许用户添加/删除/更新表中的任何行。此外,用户可以发送聊天消息给所有其他在线用户,如下所示。每个用户都有一个随机生成的惟一用户名(如user-78636)。 首先,我们创建一个新的空ASP。NET MVC 3 web应用程序项目(我将其命名为jTableWithSignalR)。由于SignalR依赖于它,所以我们首先引用Microsoft.Web。使用包管理器(NuGet)的基础设施。SignalR也需要jQuery 1.6。如果您的jQuery版本低于1.6,您还必须更新它。最后,我们可以安装SignalR包: 我们还将在项目中添加jTable插件。您可以从http://jtable.org/Home/Downloads下载它。jTable管理AJAX调用自身的所有插入/更新/删除/列表。我们只是准备ASP。NET MVC操作(或asp.net MVC的页面方法)。净Web表单。看到我的文章)。 模型 正如您在上图中看到的,th中的一条记录e表表示一个学生,其定义如下: 隐藏,收缩,复制Code

public class Student
{
public int StudentId { get; set; } [Required]
public int CityId { get; set; } [Required]
public string Name { get; set; } [Required]
public string EmailAddress { get; set; } [Required]
public string Password { get; set; } // "M" for mail, "F" for female.
[Required]
public string Gender { get; set; } [Required]
public DateTime BirthDate { get; set; } public string About { get; set; } // 0: Unselected, 1: Primary school, 2: High school 3: University
[Required]
public int Education { get; set; } //true: Active, false: Passive
[Required]
public bool IsActive { get; set; } [Required]
public DateTime RecordDate { get; set; } public Student()
{
RecordDate = DateTime.Now;
Password = "123";
About = "";
}
}

它是一个常规的c#类,用作我们在客户端和服务器之间传输的模型。 控制器 SignalR是一个功能强大的框架。这里,我使用了它的Hub类,它允许我们轻松地在服务器和在线客户端之间构建双向通信。SignalR是一个非常容易使用的库。我创建了一个Hub类,服务于客户端: 隐藏,复制Code

public class RealTimeJTableDe*b : Hub
{
public void SendMessage(string clientName, string message)
{
Clients.GetMessage(clientName, message);
}
}

如您所见,它只定义了一个客户端可以调用的方法:SendMessage。它被用来和其他客户聊天。如你所见,它调用所有客户端的GetMessage方法。其他事件(例如通知客户机将新行插入到表中)是服务器到客户机的调用。让我们看看当客户端删除表中的一行时,服务器上发生了什么: 隐藏,复制Code

[HttpPost]
public JsonResult DeleteStudent(int studentId)
{
try
{
//Delete from database
_repository.StudentRepository.DeleteStudent(studentId); //Inform all connected clients
var clientName = Request["clientName"];
Task.Factory.StartNew(
() =>
{
var clients = Hub.GetClients<RealTimeJTableDe*b>();
clients.RecordDeleted(clientName, studentId);
}); //Return result to current (caller) client
return Json(new { Result = "OK" });
}
catch (Exception ex)
{
return Json(new { Result = "ERROR", Message = ex.Message });
}
}

当用户删除一行时,jTable会自动调用这个操作(DeleteStudent)(我们将在view部分看到它的配置)。在DeleteStudent操作中,我执行了以下操作: 我根据它的ID (StudentId)从数据库中删除了记录。然后我得到了调用该操作的客户机的名称(我们将在视图中看到,它被作为查询字符串参数发送到URL)。然后我开始了一个新的任务(线程)来发送通知给客户端(当然这不是必需的,我可以发送在同一个线程,但我不想等待调用者客户端)。在该任务中,我获得了对使用Hub的所有客户端的引用。GetClients通用方法。在从Hub派生的类中,您可以直接访问客户机(当客户机调用它时,如RealTimeJTableHub类的SendMessage方法中所示)。但是,为了在任何时候(特别是在这个类之外)获得对客户机的引用,我们使用Hub。GetClients方法。然后我调用所有客户端的RecordDeleted方法来通知记录删除。重要!我们从服务器异步调用客户端的JavaScript方法。那太神奇了!最后,我向jTable返回了一切正常的响应。 让我们看看服务器代码更新行/记录: 隐藏,收缩,复制Code

[HttpPost]
public JsonResult UpdateStudent(Student student)
{
try
{
//Validation
if (!ModelState.IsValid)
{
return Json(new { Result = "ERROR",
Message = "Form is not valid! Please correct it and try again." });
} //Update in the database
_repository.StudentRepository.UpdateStudent(student); //Inform all connected clients
var clientName = Request["clientName"];
Task.Factory.StartNew(
() =>
{
var clients = Hub.GetClients<RealTimeJTableDe*b>();
clients.RecordUpdated(clientName, student);
}); //Return result to current (caller) client
return Json(new { Result = "OK" });
}
catch (Exception ex)
{
return Json(new { Result = "ERROR", Message = ex.Message });
}
}

它非常类似于删除操作(DeleteStudent)。它更新数据库中的学生记录,通知所有客户机一条记录被更新。最后,将结果返回给jTable。注意,在客户端(JavaScript)和服务器(c#)之间传输Student对象非常简单。 创建动作和从服务器获取第一个学生列表是类似的,可以在源代码中探索。 视图 在视图侧(HTML代码),我们首先包括需要的CSS和JavaScript文件: 隐藏,复制Code

<!-- Include style files -->
<linkhref="@Url.Content("~/Content/themes/redmond/jquery-ui-1.8.16.custom.css")"rel="stylesheet"type="text/css"/>
<linkhref="@Url.Content("~/Scripts/jtable/themes/standard/blue/jtable_blue.css")"rel="stylesheet"type="text/css"/> <!-- Include jQuery -->
<scriptsrc="@Url.Content("~/Scripts/jquery-1.6.4.min.js")"type="text/javascript">
</script>
<scriptsrc="@Url.Content("~/Scripts/jquery-ui-1.8.17.min.js")"type="text/javascript">
</script> <!-- Include jTable -->
<scriptsrc="@Url.Content("~/Scripts/jtable/jquery.jtable.min.js")"type="text/javascript"></script> <!-- Include SignalR -->
<scriptsrc="@Url.Content("~/Scripts/jquery.signalR.min.js")"type="text/javascript"></script>
<scriptsrc="@Url.Content("~/signalr/hubs")"type="text/javascript"></script>

最后一行很重要,因为没有这样的JavaScript文件(Visual Studio可能会为此显示一个警告)。它由SignalR在运行时动态生成。 下面是客户端完整的JavaScript代码: 隐藏,收缩,复制Code

$(document).ready(function () {

    //ViewBag.ClientName is set to a random name in the Index action.
var myClientName = '@ViewBag.ClientName'; //Initialize jTable
$('#StudentTableContainer').jtable({
title: 'Student List',
actions: {
listAction: '@Url.Action("StudentList")?clientName=' + myClientName,
deleteAction: '@Url.Action("DeleteStudent")?clientName=' + myClientName,
updateAction: '@Url.Action("UpdateStudent")?clientName=' + myClientName,
createAction: '@Url.Action("CreateStudent")?clientName=' + myClientName
},
fields: {
StudentId: {
title: 'Id',
width: '8%',
key: true,
create: false,
edit: false
},
Name: {
title: 'Name',
width: '21%'
},
EmailAddress: {
title: 'Email address',
list: false
},
Password: {
title: 'User Password',
type: 'password',
list: false
},
Gender: {
title: 'Gender',
width: '12%',
options: { 'M': 'Male', 'F': 'Female' }
},
CityId: {
title: 'City',
width: '11%',
options: '@Url.Action("GetCityOptions")'
},
BirthDate: {
title: 'Birth date',
width: '13%',
type: 'date',
displayFormat: 'yy-mm-dd'
},
Education: {
title: 'Education',
list: false,
type: 'radiobutton',
options: { '1': 'Primary school', '2': 'High school', '3': 'University' }
},
About: {
title: 'About this person',
type: 'textarea',
list: false
},
IsActive: {
title: 'Status',
width: '10%',
type: 'checkbox',
values: { 'false': 'Passive', 'true': 'Active' },
defaultValue: 'true'
},
RecordDate: {
title: 'Record date',
width: '15%',
type: 'date',
displayFormat: 'yy-mm-dd',
create: false,
edit: false,
sorting: false
}
}
}); //Load student list from server
$('#StudentTableContainer').jtable('load'); //Create SignalR object to communicate with server
var realTimeHub = $.connection.realTimeJTableDe*b; //Define a function to get 'record created' events
realTimeHub.RecordCreated = function (clientName, record) {
if (clientName != myClientName) {
$('#StudentTableContainer').jtable('addRecord', {
record: record,
clientOnly: true
});
} writeEvent(clientName + ' has <b>created</b>
a new record with id = ' + record.StudentId, 'event-created');
}; //Define a function to get 'record updated' events
realTimeHub.RecordUpdated = function (clientName, record) {
if (clientName != myClientName) {
$('#StudentTableContainer').jtable('updateRecord', {
record: record,
clientOnly: true
});
} writeEvent(clientName + ' has <b>updated</b>
a new record with id = ' + record.StudentId, 'event-updated');
}; //Define a function to get 'record deleted' events
realTimeHub.RecordDeleted = function (clientName, recordId) {
if (clientName != myClientName) {
$('#StudentTableContainer').jtable('deleteRecord', {
key: recordId,
clientOnly: true
});
} writeEvent(clientName + ' has <b>removed</b>
a record with id = ' + recordId, 'event-deleted');
}; //Define a function to get 'chat messages'
realTimeHub.GetMessage = function (clientName, message) {
writeEvent('<b>' + clientName + '</b> has sent a message:
' + message, 'event-message');
}; //Send message to server when user press enter on the message textbox
$('#Message').keydown(function (e) {
if (e.which == 13) { //Enter
e.preventDefault();
realTimeHub.sendMessage(myClientName, $('#Message').val());
$('#Message').val('');
}
}); // Start the connection to get events
$.connection.hub.start(); //A function to write events to the page
function writeEvent(eventLog, logClass) {
var now = new Date();
var nowStr = now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds();
$('#EventsList').prepend('<li class="' + logClass + '"><b>' +
nowStr + '</b>: ' + eventLog + '.</li>');
}
});

让我们来解释代码的一些部分。 首先,我为所有客户端分配了一个随机名称,用于查看哪个用户进行了更改。然后初始化了jTable。如果您不了解jTable,请参阅我的文章。 要获得对信号通信对象的引用,我们使用$.connection. realtimejtablede*b。这是由SignalR根据我们的服务器到客户机和客户机到服务器方法动态生成的。请注意,代理类名(is realTimeJTableDe*b)以小写开始,而c#类名是以大写开始的。 当服务器调用client的方法时(正如我们在controller部分中看到的),SignalR将调用一个JavaScript回调方法。因此,我们可以像上面的代码那样定义这些回调方法。例如,要从服务器获取聊天消息,我们可以定义这样一个方法: 隐藏,复制Code

//Define a function to get 'chat messages'
realTimeHub.GetMessage = function (clientName, message) {
//...
};

要从客户端调用服务器方法,我们可以直接使用相同的代理对象(注意,sendMessage是驼峰式的,不是帕斯卡式的): 隐藏,复制Code

realTimeHub.sendMessage('halil', 'a test message');

最后,我们必须调用start方法来启动与服务器的通信: 隐藏,复制Code

// Start the connection with server
$.connection.hub.start();

这是所有!SignalR和jTable是非常容易使用且功能强大的库。 临终遗言与致谢 在本文中,我介绍了SignalR并将其与jTable一起使用来创建动态web表。SignalR是一个令人惊奇的框架,当我第一次看到它时,它给我留下了深刻的印象。它使得web上的异步服务器-客户端通信变得非常容易。多亏了它的开发者。 参考文献 SignalR主页:https://github.com/SignalR/SignalR jTable主页:http://jtable.org使用jTable与ASP。asp.net MVC: http://www.codeproject.com/KB/ajax/jTable.aspx使用jTable与ASPNET WebForms: http://jtable.org/Tutorials/UsingWithAspNetWebFormsPageMethods Using jTable with PHP: http://jtable.org/GettingStarted 历史 2012年1月17日:首次上映 本文转载于:http://www.diyabc.com/frontweb/news19767.html