Sie sind auf Seite 1von 6

Sign up for our free weekly Web Developer Newsletter.

Web Development Web Services General Licence First Posted Views Downloads Bookmarked CPOL 1 Sep 2008 43,673 587 130 times

By Victuar | 17 Sep 2008


.NET2.0 VS2005 C#2.0 C# ASP.NET Windows .NET VisualStudio Dev Intermediate

This article describes how to use asynchronous Web Service method calls to implement callback events from a Web Service to its clients.
4.87 (36 votes)

Download solution - 66.5 KB

Introduction
Web Service is a very popular tool which makes it possible to share useful Internet resources among Internet communities. Using the .NET Framework and Visual Studio 2005 or later as a development tool, anybody can develop a Web Service in minutes. There are some tricks which a novice usually struggles with, such as publishing, web referencing, etc., but finally, it's an easy way to create a Web Service. The only thing I found inconvenient is that there is no way to send events from a Web Service to a client if you are using as a template, say an ASP.NET Web Service Application template which is available in Visual Studio 2005. This article describes a way to fix this inconvenience.

Background
When you describe a Web Service interface, you have to use attributes to make Web Service methods visible from clients. For example, you have to put a [WebMethod] attribute before each method which you want to call from remote web clients. I would like to have something similar for events. Say, hypothetically, it would be nice if the following is possible:
[WebEvent] public event ActiveClientsChangedDelegate OnActiveClientsChanged = null;

where the delegate is defined in a Web Service as:


public delegate void ActiveClientsChangedDelegate(int[] clients);

And, in case your service determined that the active client list has changed, it can call:
if (OnActiveClientsChanged != null) OnActiveClientsChanged(clients);

where clients is array of IDs of all the currently active clients of this service. Unfortunately, this is not possible if you are using the current .NET Framework templates for Web Service creation. One way to get around this is to poll the service from the client. This is not good because it's not real time and it overloads the client PC. Here is another way to do this using asynchronous calls.

Implementation of Callbacks from a Web Service Using Asynchronous Calls


Let's consider an example. We have a Web Service and we want it to report its clients the number of

currently active clients. If any client logs in, then all other active clients should receive notification about this. Here is the Web Server part of the 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; } } } }

As you can find out from the proxy part of the WebService (you can find it in the auto-generated module, Reverence.cs), for each declared [WebMethod], there is an additional asynchronous call and an additional completion event. In our case, for the declared method GetActiveUsers, there is additional auto-generated method GetActiveUsersAsync and an additional auto-generated event GetActiveUsersCompleted. On the client-side, when we want to create a listener for the GetActiveUsersCompleted event, we do two things. First, in the constructor, we setup a handler to process the GetActiveUsersCompleted event:
// Create proxy for WebService m_service = new WebServiceWindowsClient.localhost.WebService(); // Subscribe for event m_service.GetActiveClientsCompleted += new

WebServiceWindowsClient.localhost.GetActiveClientsCompletedEventHandler( m_service_GetActiveClientsCompleted);

And second, we make an asynchronous call to GetActiveUsers to start listening:


// This call activates GetActiveClients event listener m_service.GetActiveClientsAsync(m_sessionID);

This asynchronous operation is indefinitely long, and it ends when an "active clients list changed" event occurs in the Web Service part (say, if a new client is added to or removed from the s_services list) and the GetActiveClientsCompleted event is signaled in this case for each active client:
// Signal GetActiveClientsCompleted event for each client foreach (Guid sID in s_services.Keys) { s_services[sID].GetActiveClientsCompleted.Set(); }

When this occurs, this part of the GetActiveClients method of the Web Service works:
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

As soon as this event fires, each client gets a GetActiveClientsCompleted event, and the corresponding method will be processed:
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); }

You can notice that at the end of this handler, we call the GetActiveUsersAsync method to activate the listener again. This is a trick.

Instructions to Setup the Demo Project

You can download the sources of this demo solution to check that this approach works and that there is no need to have any polling methods on the client side. In the sources, the Web Service is published on the localhost. You can try to publish it on any web host you can access, and check how it works. In this case, you will have to change all the lines of the client's source code containing localhost. When you are done with the setup, just run some number of WebServiceWindowsClient.exes and push the Start Session buttons on each.

Conclusion
This article described how to use asynchronous Web Service method calls to implement callback events from a Web Service to its clients. Every asynchronous call from a client to a Web Service invokes the corresponding synchronous call which just waits for some AutoResetEvent to fire. As soon as this event occurs, the corresponding <method name>Completed event on the client side gets the control.

First Revision
After this article was published I found and fixed two bugs: if you stop the session for one of the clients it does not stop event listener if you restart the session it will crash The problem with stopping event listener is this: to stop event listener I use AutoResetEvent:
s_services[sessionID].GetActiveClientsCompleted.Set();

and there is no simple way to notify the method

WebServiceClientForm.m_service_GetActiveClientsCompleted that it does not have to reactivate event listener. To do that I introduced a new member, GetActiveClientsDone, of ClientState. Now, when I need to close listener I can set this member to true:
public void StopGetActiveClients(Guid sessionID) { s_services[sessionID].GetActiveClientsDone = true; s_services[sessionID].GetActiveClientsCompleted.Set(); }

This value will be passed along with the result of the GetActiveClients method in the object of type GetActiveClientsResult:
public class GetActiveClientsResult { public bool _Done = false; public int[] _Clients = new int[] { }; public GetActiveClientsResult() { } public GetActiveClientsResult(int[] clients, bool done) { _Clients = clients; _Done = done; } }

Then I can use member _Done of this class GetActiveClientsResult in WebServiceClientForm.m_service_GetActiveClientsCompleted to prevent event listener reactivation:
// This call reactivates GetActiveClients event listener only if we are not closing it if (!e.Result._Done) m_service.GetActiveClientsAsync(m_sessionID, new object());

Also pay attention on the second parameter of call to the GetActiveClientsAsync method: new

object() added there! This fixes the second critical crash bag listed above because it creates new
context for every new invocation of the asynchronous method.

History
First published on September 1, 2008. First revision & bug fixes on September 15, 2008. Solution for download also was updated.

License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author


Victuar

Software Developer (Senior) Intel Corp. United States Member

Comments and Discussions


22 messages have been posted for this article Visit http://www.codeproject.com/Articles /28998/How-to-Get-Notifications-from-a-NET-Web-Service to post and view comments on this article, or click here to get a print view with messages.
Permalink | Advertise | Privacy | Mobile Article Copyright 2008 by Victuar Web01 | 2.5.120315.1 | Last Updated 17 Sep 2008 Everything else Copyright CodeProject, 1999-2012 Terms of Use

Das könnte Ihnen auch gefallen