In this article I will revisit Observer pattern and try to show how it can be modified and used to make web services to dispatch and receive notifications.
Motivation
Web services are synchronous. In the same way as our web browser communicates with web server, web services communicate with their clients or other web services. It is how internet and its HTTP protocol works. A pair of request-response is used for every method invocation. Some of us know that .NET has something called asynchronous web method invocation. When we need to communicate with web service, we are using .NET provided plumbing. Usually we create and call proxy from our application. That proxy has synchronous and asynchronous method invocation. When we want to invoke asynchronously some web method, proxy will wait for the answer and notify us about the arrival of the response using Win32 and .NET asynchronous features. Standards which currently cover web services (WSDL, SOAP, XSD and XML) are better suited for some SQL query than for real programming. We can't pass reference to object, describe methods and so on. We can only describe and serialize public fields and properties. All in all, to do any kind of decent programming via web service will require lot of workarounds. So lets go on one such workaround.
Merging Observer and Observable
I was writing about Observer (http://www.c-sharpcorner.com/3/ObserverNETEventFB.asp, http://www.codenotes.com/cnp/baseAction.aspx?cnp=CS050012) and also about using it in web service environment http://www.codenotes.com/cnp?cnp=WS030001. So I'm advising the readers to take a look at those three articles. There are many reasons why Observer, the way it was originally written, can't be used in web services scenario. The first one is that Attach and Detach are taking object reference which is out of reach in the world of web services. That problem will be attended to later in the article. The second one is that Update should access State of Observable which is in the scenario where web services are used; a waste of bandwidth and time. The best way to tackle this is to start in multi-threaded environment, which should make a good simulation of working model of one distributed application. We will use the following simple interface :
public interface IObservableObserver
{
void Attach(IObservableObserver o);
void Detach(IObservableObserver o);
void UpdateState(string v);
}
Since we want to use it with Windows.Forms.Form we need to change Update to UpdateState. Also, the parameter accepted by UpdateState could be anything else. That interface doesn't mention other crucial methods and properties for proper functioning of such an object. Why I'm doing that, we will see when the same logic is applied to web service. Here is a complete example :
using System;
using System.Collections;
using System.Windows.Forms;
public interface IObservableObserver
{
void Attach(IObservableObserver o);
void Detach(IObservableObserver o);
void UpdateState(string v);
}
public class ObservableObserver:Form,IObservableObserver
{
private TextBox t;
protected ArrayList observers = new ArrayList();
public void Attach(IObservableObserver o)
{
observers.Add(o);
}
public void Detach(IObservableObserver o)
{
observers.Remove(o);
}
void Notify()
{
foreach (IObservableObserver o in observers)
{
o.UpdateState(t.Text);
}
}
string State
{
get
{
return t.Text;
}
set
{
t.Text = value;
Notify();
}
}
public void UpdateState(string v)
{
t.Text = v;
}
public ObservableObserver()
{
Width = 170;
Height = 160;
Text="Observable-Observer";
Button b = new Button();
b.Text = "Set State";
b.Width = 100;
b.Top = 20;
b.Left = 30;
t = new TextBox();
t.Top = 60;
t.Left = 20;
t.Width = 120;
t.Text = "Here comes State.";
Label l=new Label();
l.Text = "HWND = " + Handle.ToString();
l.Top = 100;
l.Left = 20;
l.Width = 100;
b.Click += new EventHandler(clickHandler);
Controls.Add(b);
Controls.Add(t);
Controls.Add(l);
}
private void clickHandler(object sender, EventArgs e)
{
this.State = String.Format("Click Time: {0:T}", DateTime.Now);
}
}
class Client
{
public static void Main()
{
ObservableObserver OO0 = new ObservableObserver();
ObservableObserver OO1=new ObservableObserver();
ObservableObserver OO2=new ObservableObserver();
OO0.Attach(OO1);
OO0.Attach(OO2);
OO1.Attach(OO2);
OO2.Attach(OO0);
OO1.Show();
OO2.Show();
Application.Run(OO0);
}
}
It could be compiled from command line or pasted in VS.NET or SharpDevelop, if somebody prefers IDE. When we run it, it shows three forms and to set state we are using only button. From the code in Main we can see who will notify whom when State changes. Here we are using that interface so anything that complies to that interface may be part of the system. It is the responsibility of the user to do the rest of the coding, like implementation of Notify. Also, I completely omitted the possibility of synchronization problems during Attach, Detach or Notify operations. If you want to use it for anything except demonstration, that problem must be attended to. Using lock(observers) or lock(this) where needed should do the trick.
ObservableObserver as web service
To switch to web services we must abandon object reference and interface parameters as well. The thing which can do the job done by interface is WSDL.EXE generated proxy. Further, instead of object reference we can use URL of ObservedObserver. That allows creating object reference to proxy object and using the same mechanics which is used by Observer. Since proxy and ObservableObserver are sharing the name we will need to write proxy manually and rename proxy into something else. Finally we will need persistent collection for observers. For a start lets write proxy :
using System.Diagnostics;
using System.Xml.Serialization;
using System;
using System.Web.Services.Protocols;
using System.ComponentModel;
using System.Web.Services;
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Web.Services.WebServiceBindingAttribute
Name="ObservableObserverSoap", Namespace=http://tempuri.org/)]
public class ObservableObserverProxy : System.Web.Services.Protocols.SoapHttpClientProtocol
{
public ObservableObserverProxy(string subscribersURL)
{
this.Url = subscribersURL;
}
[System.Web.Services.Protocols.SoapDocumentMethodAttribute(
"http://tempuri.org/Attach", RequestNamespace=http://tempuri.org/,
ResponseNamespace=http://tempuri.org/,
Use=System.Web.Services.Description.SoapBindingUse.Literal,
ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public void Attach(string subscribersURL)
{
this.Invoke("Attach", new object[] {
subscribersURL});}
[System.Web.Services.Protocols.SoapDocumentMethodAttribute(http://tempuri.org/Detach,
RequestNamespace=http://tempuri.org/,
ResponseNamespace=http://tempuri.org/,
Use=System.Web.Services.Description.SoapBindingUse.Literal,
ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public void Detach(string subscribersURL)
{
this.Invoke("Detach", new object[] {
subscribersURL});
}
[System.Web.Services.Protocols.SoapDocumentMethodAttribute(http://tempuri.org/UpdateState,
RequestNamespace=http://tempuri.org/,
ResponseNamespace=http://tempuri.org/,
Use=System.Web.Services.Description.SoapBindingUse.Literal,
ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public void UpdateState(object newState)
{
this.Invoke("UpdateState", new object[] {
newState});}
}
That is all we need to start writing web service. Save it as proxy.cs in bin directory where your web service will be deployed and compile from command line using csc /t:library proxy.cs. I removed asynchronous invocation because there is not too much sense in sitting and waiting for void to arrive. The code for OservableObserver web service looks like this :
using System;
using System.Text;
using System.Collections;
using System.Web.Services;
public class ObservableObserver : WebService
{
[WebMethod]
public void UpdateState(object newState, string subscriptionURL)
{
lock(this)
{
StaticContainer.SetState(newState);
}
}
[WebMethod]
public void Attach(string subscribersURL)
{
lock(this)
{
StaticContainer.AddObserver(subscribersURL);
}
}
[WebMethod]
public void InitApp(string local)
{
StaticContainer.LoadContainers(local);
}
[WebMethod]
public void CleenUp()
{
StaticContainer.EmptyContainers();
}
[WebMethod]
public void Detach(string subscribersURL)
{
lock(this)
{
StaticContainer.RemoveObserver(subscribersURL);
}
}
[WebMethod]
public void Subscribe(string subscriptionURL)
{
lock(this)
{
StaticContainer.AddObservable(subscriptionURL);
}
}
[WebMethod]
public void Unsubscribe(string subscriptionURL)
{
lock(this)
{
StaticContainer.RemoveObservable(subscriptionURL);
}
}
[WebMethod]
public void UnsubscribeAll()
{
lock(this)
{
StaticContainer.DropAll();
}
}
[WebMethod]
public void SetState(object newState)
{
lock(this)
{
StaticContainer.SetState(newState);
StaticContainer.Notify();
}
}
[WebMethod]
public string GetResults()
{
lock(this)
{
return StaticContainer.DumpNotifications();
}
}
}
class StaticContainer
{
internal static string MyURL;
internal static object currentState;
internal static Hashtable observers;
internal static ArrayList observables;
internal static Hashtable results;
public static void LoadContainers(string local)
{
MyURL = local;
observers = new Hashtable();
results = new Hashtable();
observables = new ArrayList();
}
public static void EmptyContainers()
{
MyURL = null;
observers = null;
results = null;
observables = null;
}
public static void SetState(object o)
{
currentState = o;
AddResult(o);
}
public static void Notify()
{
foreach(object o in observers.Keys)
{
((ObservableObserverProxy)observers[o]).UpdateState(currentState);
}
}
public static void AddObserver(string subscribersURL)
{
if(!observers.Contains(subscribersURL))
{
ObservableObserverProxy temp = new ObservableObserverProxy(subscribersURL);
temp.UpdateState("Welcome "+DateTime.Now.ToString("hh:mm:ss.fff"));
observers.Add(subscribersURL, temp);
}
}
public static void RemoveObserver(string subscribersURL)
{
if(observers.Contains(subscribersURL))
observers.Remove(subscribersURL);
}
public static void AddObservable(string subscriptionURL)
{
if(!observables.Contains(subscriptionURL))
{
(new ObservableObserverProxy(subscriptionURL)).Attach(MyURL);
observables.Add(subscriptionURL);
}
}
public static void RemoveObservable(string subscriptionURL)
{
if(observables.Contains(subscriptionURL))
{
(new ObservableObserverProxy(subscriptionURL)).Detach(MyURL);
observables.Remove(subscriptionURL);
}
}
public static void AddResult(object newState)
{
//possibly do something else like take some action because state has been changed
string s = newState.ToString() + " " + DateTime.Now.ToString("hh:mm:ss.fff");
results.Add(DateTime.Now.Ticks,s);
}
public static void DropAll()
{
foreach(object o in observables)
(new ObservableObserverProxy(o.ToString())).Detach(MyURL);
observables.Clear();
}
public static string DumpNotifications()
{
StringBuilder result = new StringBuilder();
foreach(object o in results.Keys)
result.Append(results[o].ToString() + " ");
return result.ToString();
}
}
Save it as ObservableObserver.cs in bin directory where you want it deployed. Again, it needs to be compiled from command line using csc /t:library /r:proxy.dll ObservableObserver.cs. In application directory we will place asmx file with the following content :

<%@ WebService
Class=ObservableObserver,ObservableObserver %>Now we need to spread our web service across a couple of more virtual directories. The simplest way is to copy asmx and two dll files to new locations. We need to connect web service manually. So we will init all web services using InitApp. After that we will subscribe two or three to the remaining one. For that remaining web service we need a client which sets new state. Using WSDL.EXE we will create proxy and client's code could look like this :
using System;
class A
{
static void Main()
{
ObservableObserver o=new ObservableObserver();
o.SetState("test 1 "+DateTime.Now.Ticks.ToString("hh:mm:ss:fff"));
o.SetState("test 2 "+DateTime.Now.Ticks.ToString("hh:mm:ss:fff"));
o.SetState("test 3 "+DateTime.Now.Ticks.ToString("hh:mm:ss:fff"));
}
}
Don't forget to reference proxy. It is up to you who is going to be subscribed to whom, just be careful if you want to run a couple of events simultaneously, DateTime.Now may not be unique enough as Key, and Hash table may throw an exception. The first notification may take longer but after that they take something like 0.01 to 0.02 seconds to travel from one node to another.
Please note that this is only an example and that many things are still missing, like more civilized resource management.
Explanation, possibilities and conclusion
During the writing of this article I learnt that Francis Geysermans and Jeff Miller published Build asynchronous applications with the Distributed Event-Based Architecture for Web Services on March 10 (ftp://www6.software.ibm.com/software/developer/library/ws-dbarch.pdf). I warmly recommend to the readers to check their excellent article. Also, I must state here that every similarity between their and my work is unintentional. I've been working on asynchronous web services since January as a part of my presentation for The International Web Services / XML Conference & Expo in Toronto, the readers may take a look at the following page http://www.wowgao.com/web_services_conference/other/speakers.php and find out that on their own.
The inspiration for this actually comes from one old article .NET P2P: Writing Peer-to-Peer Networked Apps with the Microsoft .NET Framework by Lance Olson, which was published in MSDN magazine. Along the lines of his analysis of possible organization of nodes of one distributed application made of ObservableObservers, I see there three main types of internal organization. The first is a small decentralized one where instances are cross-subscribed to one another. Certainly with increase of number of nodes their internal communication will finally take bigger part of available bandwidth, so that is why I'm calling it small. The next one is small centralized which relies on the existence of a specialized central node to maintain synchronization of the group. Such controller node needs a slightly different implementation of UpdateState. It must distinguish who is the caller and propagate update to the rest of the group, in other words its UpdateState behaves as a selective SetState-Notify. All members of the group are subscribed to the controller node and the controller is subscribed to all the others. Since it takes time for the controller to notify other members of the group, with the increase in the size of the group the controller will become too slow to accept and process notifications. That is why such a group may only be small. Finally there is a relay large group where we can simply cross-subscribe one controller node to two or more controllers and in that way connect their respective groups in one bigger structure.
My explorations of the possible usage of such hybrid between Observer and Observable are also related to synchronization of web farm where servers are not on the same network. There it can be used for synchronization of in memory data store and/or session variables.
Join the conversation! Your thoughts help the community grow.