Using MemBus for messaging between application components
Sometimes we need publisher/subscriber messaging in our applications to broadcast messages to different parts of system in real time. We can always build our own solution for this but we can also use something that is already there. In this posting I will show you how to use MemBus to send messages from MDI parent form to MDI child forms.
NB! You can download and browse source code of this post from my GitHub samples repository.
What is MemBus?
As we can read from MemBus introduction:
“it is messaging framework … that utilizes the semantics of sending and receiving messages. Those messages are not meant to leave the AppDomain where the Bus lives in. There is no durability – when the AppDomain is gone, the Bus is gone.”
So it seems like we have interface to send and receive messages between our system components. Now let’s build something to demonstrate how to use MemBus for something useful.
Design draft
Before going to details let’s take a look at my design draft. This is just drawing that gives you idea about logical parts of system we are building. How we implement our application is another topic. But this is the concept we will try to follow.
We need something that receives events and lets MDI parent window know that there is new data. MDI parent window constructs message and sends it to Bus. Bus has subscribers that are implemented as MDI child windows. These windows implementIObserver interface. When new child window is opened then it is also registered as subscriber to bus. When child window is closed then it is unregistered. So, there is nothing complex.
Sample application
Before sending and receiving messages we need some type. Well, string is the simplest one but let’s write some more interesting application that we can extend to cool demo. So, instead of simple types I will use my own class called GeoLocationItem. The class is here.
public class GeoLocationItem
{
public string Title { get; set; }
public DateTime Time { get; set; }
public decimal Latitude { get; set; }
public decimal Longitude { get; set; }
}
Now we need event receiver. It is possible to build some more complex receiver but let’s try to make something more primitive that doesn’t need much code and maintenance. I will use Timer object on my MDI parent form. After every three seconds it sends new message to bus.
private void LocationTimerTick(object sender, EventArgs e)
{
var item = new GeoLocationItem();
item.Time = DateTime.Now;
var secondString = item.Time.Second.ToString();
item.Title = "Car " + secondString[secondString.Length - 1];
item.Longitude = item.Time.Second;
item.Latitude = item.Time.Millisecond;
_bus.Publish(item);
}
This code creates GeoLocationItem with some random data and it simulates how to track cars on their tracks.
We have also IBus instance defined in form scope.
public partial class MainForm : Form
{
private readonly IBus _bus;
public MainForm()
{
InitializeComponent();
_bus = BusSetup.StartWith<Fast>().Construct();
}
// more code here
}
When new MDI child window is opened then it is registered also with bus. When child window is closed it is also removed from bus so it does not receive events anymore.
private void NewWindowToolStripMenuItemClick(object sender, EventArgs e)
{
var child = new ChildForm {MdiParent = this};
var observable = _bus.Observe<GeoLocationItem>();
observable.Subscribe(child);
child.Tag = observable;
child.FormClosed += ChildFormClosed;
child.Show();
}
static void ChildFormClosed(object sender, FormClosedEventArgs e)
{
var form = (Form)sender;
var observable = form.Tag as IObservable<GeoLocationItem>;
if (observable != null)
{
observable.Subscribe(null);
}
}
The tricky part here is how form is introduced to bus. I ask new observable object from bus and then register my child window as subscriber to it. When child window is closed then I subscribe null as observer and bus stops sending messages to this instance. If you don’t unsubscribe then form is not destroyed and receives messages background. You may not want it.
Here is the child form. It implements IObserver interface and when new messages is received then it sends out event that is invoked in form’s own thread. Otherwise we get errors and there is no feedback shown on child forms.
public partial class ChildForm : Form, IObserver<GeoLocationItem>
{
private delegate void AddDataItemDelegate(GeoLocationItem item);
private readonly AddDataItemDelegate _addDataItem;
public ChildForm()
{
InitializeComponent();
_addDataItem = new AddDataItemDelegate(SetValue);
}
public void OnNext(GeoLocationItem value)
{
Invoke(_addDataItem, new object[] { value });
}
private void SetValue(GeoLocationItem value)
{
var item = new ListViewItem();
item.Text = value.Time.ToString();
item.SubItems.Add(value.Title);
item.SubItems.Add(value.Latitude.ToString());
item.SubItems.Add(value.Longitude.ToString());
BusDataList.Items.Insert(0, item);
}
public void OnError(Exception error)
{
throw new NotImplementedException();
}
public void OnCompleted()
{
}
}
Now let’s try to run our program.
And here is the result
I opened some windows and tiled them in MDI parent. Here is the example of program.
Okay, that’s it for now. You can see that all my eight child forms are receiving messages from parent through MemBus and this was our goal.
Conclusion
Although we can create our own messaging solutions where some clients are publishers and other are subscribers of messages we can avoid this task and use existing solutions. In this posting I demonstrated how to use MemBus to organize communication between MDI parent and child forms. We used nice standardized interface without any bad hacks and we got clean and working solution.
Nice article, thanks for sharing…
Thanks for feedback, Frank! :)
This example is my first solution that started to work with minimal set of knowledge about MemBus. I plan to go on and optimize the code so it is shorter and also easier to read. I will try out Rx Components as soon I get some time and I will make some modifications to source code of this sample project.
Thanks for feedback, Sean! I will try your messenger out for sure. :)