介绍
Web service是一个非常流行的工具,它使得在internet社区共享互联网资源成为可能。使用.NET框架和Visual studio 2005或者是后续版本为开发工具,谁都可以在几分钟之内开发出web service。这里有一些技巧,初学者经常遇到,例如部署、web引用等。它是构建web service的简易方法。使用模板创建服务时,唯一不方便使用的地方是还没有合适的办法从web service向客户端发送事件,asp.net web service应用程序模板只是在visual studio 2005中提供。这篇文章讨论了如何在web service中支持事件。
背景
当你对一个web service接口进行描述时,你可以使用属性让web方法对客户端可见。例如,你可以在web客户端想要调用的方法的前面加上[webmethod]属性,我也希望事件也可以作类似的处理,如果以下成为可能的话那将非常使人激动人心。
Collapse Copy Code
[WebEvent]
public event ActiveClientsChangedDelegate OnActiveClientsChanged = null;
在web service定义如下的委托:
Collapse Copy Code
public delegate void ActiveClientsChangedDelegate(int[] clients);
当你编写的服务识别到激活客户列表有变化时(注册客户、注销客户),可以进行如下的调用:
Collapse Copy Code
if (OnActiveClientsChanged != null) OnActiveClientsChanged(clients);
Clients是当前服务的活动客户的ID数组。遗憾的是,你使用当前的.NET web服务框架模板是不可行的。而其实现的方法之一是从客户端来调用服务。由于它不具有实时性并且依赖于客户端电脑,因而这并不是一个好办法。这里阐述了另外一种方法,那就是通过异步调用。
通过异步调用实现web服务回调函数
先让我们看一个例子。我们有一个web服务,并且希望它能够及时通知他的活动连接客户的数目。有任何客户连接登录后,其他所有的客户能够收到这个通知。一下是该web服务的部分代码:
Collapse Copy Code
namespace WebService
{
/// <summary />
/// Summary description for WebService
/// </summary />
[WebService(Namespace = "http://localhost/webservices/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class WebService : System.Web.Services.WebService
{
#region private members
// This static Dictionary keeps track of all currently open sessions
private static Dictionary<Guid, ClientState> s_services =
new Dictionary<Guid, ClientState>();
private int m_clientID;
#endregion private members
#region WebService interface
[WebMethod]
public void StartSession(Guid sessionID, int clientID)
{
lock (s_services)
{
if (s_services.ContainsKey(sessionID))
{
// Session found in the list
m_clientID = s_services[sessionID].ClientID;
}
else
{
// Add session to the list
m_clientID = clientID;
s_services.Add(sessionID, new ClientState(m_clientID));
}
}
lock (s_services)
{
// Signal GetActiveClientsCompleted event for each client
foreach (Guid sID in s_services.Keys)
{
s_services[sID].GetActiveClientsCompleted.Set();
}
}
}
[WebMethod]
public void StopSession(Guid sessionID)
{
lock (s_services)
{
if (s_services.ContainsKey(sessionID))
{
// Remove session from the list
s_services.Remove(sessionID);
}
}
lock (s_services)
{
// Signal GetActiveClientsCompleted event for each client
foreach (Guid sID in s_services.Keys)
{
s_services[sID].GetActiveClientsCompleted.Set();
}
}
}
[WebMethod]
public int[] GetActiveClients(Guid sessionID)
{
if (!s_services.ContainsKey(sessionID))
{
// Return empty client list
return new int[] { };
}
bool signalled = s_services[sessionID].GetActiveClientsCompleted.WaitOne();
// wait for GetActiveClientsCompleted event
if (signalled)
{
lock (s_services)
{
// Create client list and return it
List<int> clients = new List<int>();
foreach (Guid sID in s_services.Keys)
{
if (sID == sessionID) continue;
clients.Add(s_services[sID].ClientID);
}
return clients.ToArray();
}
}
else
{
// Return empty client list
return new int[] { };
}
}
#endregion
private class ClientState
{
public int ClientID;
public AutoResetEvent GetActiveClientsCompleted = new AutoResetEvent(false);
public ClientState(int clientID)
{
ClientID = clientID;
}
}
}
}
你可以从webservice的客户端代理部分发现(你可以从自动生成模块reference.cs中找到),针对每个有[WebMethod]申明的,都有一个相应的异步调用方法和相应的完成事件。在本案例中,我们申明了GetActiveUsers方法,因此自动生成了GetActiveUsersAsynch方法和GetAcitveUserscomplated事件。在客户端,当你想为GetActiveUserscomplated事件创建一个监听器时,我们需要做两件事情。第一,在构造器中创建一个句柄处理GetActiveUserscomplated事件:
Collapse Copy Code
// Create proxy for WebService
m_service = new WebServiceWindowsClient.localhost.WebService();
// Subscribe for event
m_service.GetActiveClientsCompleted += new
WebServiceWindowsClient.localhost.GetActiveClientsCompletedEventHandler(
m_service_GetActiveClientsCompleted);
第二,我们通过对GetActiveUsers的异步调用启动监听:
Collapse Copy Code
// This call activates GetActiveClients event listener
m_service.GetActiveClientsAsync(m_sessionID);
这个异步调用操作并不是无限制的等待,当web服务段的“活动客户列表改变”,并且GetActiveClientsComplated事件有信号标识时,异步调用将会结束:
Collapse Copy Code
// Signal GetActiveClientsCompleted event for each client
foreach (Guid sID in s_services.Keys)
{
s_services[sID].GetActiveClientsCompleted.Set();
}
上面的情况发生时,web服务的GetActiveClients方法将会执行:
Collapse Copy Code
bool signalled = GetActiveClientsCompleted.WaitOne();
// wait for GetActiveClientsCompleted event
if (signalled)
{
lock (s_services)
{
// Create client list and return it
List<int> clients = new List<int>();
foreach (Guid sID in s_services.Keys)
{
if (sID == sessionID) continue;
clients.Add(s_services[sID].m_clientID);
}
return clients.ToArray();
}
}
else return new int[] { };
// Return empty client list
一旦事件发生,每一个客户端获得一个GetActiveClientsComplated事件,在客户端将交给相应的方法处理:
Collapse Copy Code
void m_service_GetActiveClientsCompleted(object sender,
WebServiceWindowsClient.localhost.GetActiveClientsCompletedEventArgs e)
{
// Add current list of active clients to list box
int[] clients = e.Result;
string client_list = "";
foreach (int client in clients) client_list += client + " ";
listBoxEvents.Items.Add(string.Format("GetActiveClients " +
"completed with result: {0}", client_list));
// This call reactivates GetActiveClients event listener
m_service.GetActiveClientsAsync(m_sessionID);
}
你可能注意到了在处理函数的最后,我们调用了GetActiveUserAsync方法来激活监听器。这是一个很好的技巧。
如何调试示例项目
你可以下载示例解决方案的源代码来验证这种做法的可行性,验证根本不需要在客户端进行轮询试调用。在示例源代码中,web服务发布在localhost上。你可以尝试着将其部署到你可以访问的其他任何主机上,来验证它的工作过程。在此案例中,你必须得修改客户端源码上包含localhost的所有语句。安装完成后,运行若干个webservicewindowsclient.exe,并且点击start session按钮。
结论
这篇文章阐释了如何通过异步web服务方法调用来实现web服务到客户端的事件回调功能。来自客户端到web服务的每个异步调用都会调用相应的异步方法,这些方法仅是在等待AutoResetEvent被触发。一旦这个事件触发,客户端的相应<方法名>Complated事件被激活。