Silverlight 4(带 HTTP 双工和 SQL 通知)






4.92/5 (7投票s)
当数据库发生更改时,立即将更新推送到你的浏览器。
引言
如果你曾经尝试过创建一种从数据库到服务再到浏览器的推送机制,这本身就已经够复杂的了,而拥有一个嵌入式 Silverlight 浏览器应用程序则会增加一些难度。
如果你在网上搜索这类应用程序,你会发现大量关于 Silverlight 和 Duplex 或 Silverlight 和 SQL 通知 (SQL Notifications) 的文章,但关于 Silverlight 结合 Duplex 和 SQL 通知 (SQL Notations) 的内容却少得可怜。
背景
最初,这个问题源于无法在网上找到针对此场景的完整文章的沮丧。有些文章接近解决方案,但并非完全一致。Silverlight 还会存在一段时间(或者直到 HTML5 被完全接受和成熟),而这种推送机制将有助于其他开发人员使用此类技术。
使用代码
项目结构布局
从图 1 可以看到应用程序的结构。有一个 Silverlight 项目 "SLDuplexDependency"(它将包含在 "SLDuplexDependency.Web" 项目的测试页面中)。这个 Silverlight 项目有一个服务引用,指向名为 "BrowserMaintenanceService" 的服务。此服务用于跟踪连接到服务器的浏览器。Silverlight 项目中第二个名为 "SqlServiceReference" 的服务将维护来自 SQL Server 的回调(通知),并处理将发送回客户端的任何新结果。当数据库上执行更新、插入或删除操作时,此服务将收到通知(会发送一个小 XML 通知到回调方法),然后它将使用静态类/方法以 JSON 格式(因为 Silverlight 没有 XML 解析器)通知浏览器。浏览器(Silverlight C#)中的回调方法将处理 JSON 并重新绑定数据网格。
主项目 "SLDuplexDependency.Web" 中的一个服务 "DBNotificationService" 用于处理浏览器连接到服务器并对来自服务器的 SQL 通知表示兴趣时的事件。另一个服务 "BrowserDuplex" 最初由 Silverlight 应用程序使用,用于建立初始连接并将通知绑定到服务器——在首次绑定使用后(`SqlDependency` 必须在每次通知后重新创建),主服务 "SqlEventsService" 将负责所有通知和 `SqlDependency` 对象的重新绑定。
有一个名为 `SilverlightClientsList` 的静态类,它将维护浏览器会话 ID 的记录,并且服务可以调用其静态方法将数据推送到客户端。
Silverlight
此方法调用 "DBNotificationService" 服务以注册此浏览器对 SQL Server 表通知的兴趣。
/// Subscribes this instance of the browser,
void Subscribe()
{
client.SubscribeToNotificationsCompleted += (sender, e) => { };
client.SubscribeToNotificationsAsync();
}
此方法用于注册浏览器并将它们的会话 ID 存储在静态列表中。每个会话将有自己的关闭事件与之关联。
public void SubscribeToNotifications()
{
IDBNotificationCallbackContract ch =
OperationContext.Current.GetCallbackChannel<IDBNotificationCallbackContract>();
string sessionId = OperationContext.Current.Channel.SessionId;
//Any message from a client we haven't seen before
//causes the new client to be added to our list
lock (syncRoot)
{
if (!SilverlightClientsList.IsClientConnected(sessionId))
{
SilverlightClientsList.AddCallbackChannel(sessionId, ch);
OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing);
OperationContext.Current.Channel.Faulted += new EventHandler(Channel_Faulted);
}
}
}
双工
此方法是在数据库发生更改时用于回调的方法。
private void dependency_OnDataChangedDelegate(object sender, SqlNotificationEventArgs e)
{
if (e.Type != SqlNotificationType.Change) return;
ObservableCollection<Employee> myAuthors =
this.GetEmployeeList(DateTime.Now.AddYears(-20), DateTime.Now.AddYears(1));
string c = SerializeObject(myAuthors);
this.SendTriggerAuditData(c);
//the notification is now used up - so now we remove
//the event handler from the dependency object
//(another one will have been bound to it again)
SqlDependency dependency = sender as SqlDependency;
dependency.OnChange -=
new OnChangeEventHandler(dependency_OnDataChangedDelegate);
}
此方法将使用新数据启动回调到浏览器的过程。
public void SendTriggerAuditData(string data)
{
// loop through channels (browsers) and make a call to their callback method
if (SilverlightClientsList.GetCallbackChannels().Count() > 0)
{
lock (syncRoot)
{
IEnumerable<IDBNotificationCallbackContract> channels =
SilverlightClientsList.GetCallbackChannels();
channels.ToList().ForEach(c => c.SendNotificationToClients(data));
}
}
}
SQL 通知 / SqlDependency
以下方法是用于查询数据库并创建 SQL 依赖关系的通用方法。
public ObservableCollection<WcfSqlNotifications.DuplexServiceReference.Employee>
GetEmployeeList(DateTime startDate, DateTime endDate)
{
employees =
new ObservableCollection<WcfSqlNotifications.DuplexServiceReference.Employee>();
//the connection string to your database
string connString = @"Data Source=YouDatabaseServer;Initial" +
@" Catalog=dependencyDB;Persist Security Info=True;" +
@"User ID=NotificationsUser;Password=password";
string proc = "SelectCredentialsWithinDOBRange";
//the name of our stored procedure
if (!CheckUserPermissions()) return null;
//first we need to check that the current user
//has the proper permissions, otherwise display the error
this.employees = new ObservableCollection<
WcfSqlNotifications.DuplexServiceReference.Employee>();
bool success = SqlDependency.Start(connString);
// create the dependency reference on the database
using (SqlConnection sqlConn = new SqlConnection(connString))
{
using (SqlCommand sqlCmd = new SqlCommand())
{
SqlParameter myParm1 = sqlCmd.Parameters.Add(
"@StartDate", SqlDbType.DateTime, 20);
myParm1.Value = startDate;
SqlParameter myParm2 = sqlCmd.Parameters.Add(
"@EndDate", SqlDbType.DateTime, 20);
myParm2.Value = endDate;
sqlCmd.Connection = sqlConn;
sqlCmd.Connection.Open();
//tell our command object what to execute
sqlCmd.CommandType = CommandType.StoredProcedure;
sqlCmd.CommandText = proc;
sqlCmd.Notification = null;
SqlDependency dependency = new SqlDependency(sqlCmd);
dependency.OnChange +=
new OnChangeEventHandler(dependency_OnDataChangedDelegate);
if (sqlConn.State != ConnectionState.Open) sqlConn.Open();
using (SqlDataReader reader = sqlCmd.ExecuteReader())
{
while (reader.Read())
{
WcfSqlNotifications.DuplexServiceReference.Employee author =
new WcfSqlNotifications.DuplexServiceReference.Employee();
author.ID = reader.GetInt32(0);
author.FirstName = reader.GetString(1);
author.SecondName = reader.GetString(2);
author.Address = reader.GetString(3);
author.DOB = reader.GetDateTime(4).ToString();
this.employees.Add(author);
}
}
}
return this.employees;
}
}
需要一个方法来确保用户能够在服务器上发起查询。
private bool CheckUserPermissions()
{
try
{
SqlClientPermission permissions =
new SqlClientPermission(PermissionState.Unrestricted);
//if we cannot Demand() it will throw an exception
//if the current user doesnt have the proper permissions
permissions.Demand();
return true;
}
catch { return false; }
}
SQL Server
将以下数据库文件附加到你的 SQL Server (2008) 数据库。
在你的新附加的数据库上运行以下脚本。
USE dependencyDB;
GO
CREATE QUEUE dependencyDBQueue;
CREATE SERVICE dependencyDBService ON QUEUE dependencyDBQueue (
[http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification]);
GRANT SUBSCRIBE QUERY NOTIFICATIONS TO GeneralUser;
ALTER DATABASE dependencyDB SET SINGLE_USER WITH ROLLBACK IMMEDIATE
ALTER DATABASE dependencyDB SET ENABLE_BROKER
ALTER DATABASE dependencyDB SET MULTI_USER
GO
注意:如果上面的代码挂起,请停止 SQL 服务然后重试。
- 创建存储过程(使用已启用 Service Broker 的数据库)时需要注意的事项 - http://msdn.microsoft.com/en-us/library/ms181122(SQL.100).aspx
- 如何启用你的数据库作为 Service Broker - http://www.dreamincode.net/forums/topic/156991-using-sqldependency-to-monitor-sql-database-changes/
- 什么是 "Service Broker" - http://msdn.microsoft.com/en-us/library/bb522889(SQL.100).aspx
测试应用程序
- 确保 (dependencyDB) 数据库已启用 Service Broker。
- 确保你在连接字符串中的用户已在数据库中创建(并具有足够的权限)- 如果只是将数据库附加到服务器,通常已自动创建。
- 在 VS2010 中运行 Silverlight 应用程序(使用修改后的连接字符串)- 应该会出现一个显示详细信息的网格。
- 转到数据库并打开 credentials 表进行编辑。
- 编辑其中一行数据。
- 结果:编辑的行的详细信息应反映在浏览器网格中。
- 进一步测试:打开多个浏览器并执行相同操作 - 更改将推送到所有浏览器。
优点和缺点
优点
- 它是跨数据库平台的 - 有一个 `OracleDependency` 对象(http://download.oracle.com/docs/ cd/B19306_01/win.102/b14307/OracleDependencyClass.htm)。
- 高可伸缩性 - 使用后台线程调用 WCF,并使用 Win2008 服务器和 IIS7 - 连接数可达数千。
- 代码可以定制以提高效率,这样只有更新的记录会被传回客户端,而不是所有记录都被重新绑定。
- 可以极大地帮助应用程序的缓存机制 - WCF 可以被通知何时发生更新并刷新其缓存。
缺点
- Mono WCF 在 .NET 2 中 - `SqlDependency` 在 .NET 3.5+ 中,所以无法将此解决方案移植到 Linux 环境。
- 在存储过程中可以使用的 SQL 有限制(http://msdn.microsoft.com/en-us/library/ms181122(SQL.100).aspx)。