Archive

Monthly Archives: February 2015

Introduction

I decided to build a next generation IDE targeting the HoloLens platform.
This project is code-named: Holoware

Framework Requirements

The IDE will serve as a framework for building applications using the following workflow:

• Identify architectural layers

• Construct modules for each layer

• Construct classes for each module

The IDE will leverage both keyboard and gestures throughout the user-experience of the software development process. Gestures can be used to manipulate panes (i.e. expand, shrink) and drill into and out of an architecture (i.e. layers, modules, classes).

Holoware as a vehicle

This IDE will attempt to leverage the metaphor of driving a car. However, instead of driving a car, the targeted user-experience for this IDE is to serve as a development vehicle for driving and monitoring code.

Do the Following:

Get behind the steering wheel of a car and observe the user-interface. Specifically, observe the design of how that vehicle enables you to observe real-time events. What do you see?

Windows

A car has several windows (also known as panes) (i.e. windshield, side-views, and rear-view). The rear-view window enables a user to view their past as it relates to their journey. The side-view windows enable a user to view activity in parallel relative to their vehicles progress.

Mirrors

In addition to the windows on a car, a car also has mirrors that are tightly coupled to the position of these particular windows (i.e. rear-view, drivers-side, and passenger-side). These mirrors have dual functions. Not only do they reflect activity, but they do so through the unique perspective of their location relative to their position of a window. More specifically, while a window enables the observation of an activity, a mirror relative to that window provides further detail (i.e. history or a log).

Dashboard

A driver of a vehicle leverages a dashboard to monitor that vehicle’s health. This IDE will attempt to follow that metaphor.

Conclusion

You can find the Holoware project on Github.

Advertisements

Introduction

We are beginning a new era of user-experiences that will soon be introduced to us via augmented reality. This technology can introduce a new perspective of the tooling that can be leveraged to design and build the software systems of the future. Specifically, the software industry can now collaborate on how to design the future user-experience of building software systems through augmented reality.

This article will be the first of several articles that detail the possibilities of future software development.

Areas of Enhancement

Instead of common software development involving a keyboard and a dual-monitor setup, imagine building software with just a keyboard and HoloLens. In addition, imagine your future IDE as a development environment that enables software development and maintenance at various levels of abstraction. For example, what if developers had an architectural view of their code and were able to drill-in to a specific layer of their architecture’s abstraction by selecting a layer (i.e. UI) to view its modules or by zooming out of that particular abstraction all while using their augmented reality headset.

Here’s another example. What if a developer could place various panels (i.e. Test Explorer, Output Window, CodeMap, and CallStack) at different depths of their view or dock them to different corners of their view relative to their text editor. Again, using augmented reality, a developer can zoom in and out from their IDE’s range of views. A developer can also look to the left, right, up, and down to interact with other sets of tools and data (i.e. windows for debugging) as they pertain to the IDE’s text editor.

In short, there are several areas within the user-experience of building software that can be redesigned. The areas that this series will discuss are Notifications, UI Module Placement, and Development Views.

The following is a summary of the areas that will be discussed in the upcoming series.

  • Notifications
    • Automated Tests
    • Work Items (defects, tasks)
  • UI Module Placement (docking)
    • Test Results
    • Output Window
    • Application runtime
    • Editors
      • Designer ( Blend)
      • Code (Visual Studio)
  • Development Views
    • Classic
      • Prior versions (Visual Studio)
    • Architectural
      • UI
      • Services
      • Model
      • DAL
    • System
      • Clients
      • Servers (Build, Database, Source Control)

Conclusion

In conclusion, we are beginning a new era of user-experiences that will soon be introduced to us via augmented reality. This technology can introduce a new perspective of the tooling that can be leveraged to design and build the software systems of the future. Specifically, the software industry can now collaborate on how to design the future user-experience of building software systems through the lens of augmented reality. The coming articles that this series will discuss will be Notifications, UI Module Placement, and Development Views.

 

Introduction

Sometimes we may not always have an environment where we can leverage Visual Studio’s feature for running unit tests after compilation. I observed this situation with a client onsite and decided to implement this function for the system I was building regardless. In my case, I could not leverage this feature as a result of Visual Studio’s license agreement.

Unit-Tests: A conversation between a coder and their machine

Yes. This is my quote. I require unit tests to guide my thoughts as I place my thoughts on the computer monitor. It’s that feedback loop that comforts me when I am unsure of the side-effects that might occur. Imagine tuning a guitar without any feedback from the vibrations that resonates. This is how I feel about my code. Moreover, this is how I feel when I’m implementing or maintaining complex systems. I need that frequency of feedback and guidance.

Implementation

The program that I am providing consists of a WPF application that issues notifications via the notification area on the taskbar. This program checks for updates to an assembly based on a time-interval. In the event that an update to the targeted assembly is recognized, the program executes “mstests.exe”, parses its results, and sends a notification to the notification area.

XAML

The XAML is short and sweet.

This app will be hidden and will not show up in the taskbar.

Note the following property-value pairs in the XAML:

  • ShowInTaskbar=”False”
  • Visibility=”Hidden”
  • WindowState=”Minimized”

The following is the XAML:

<Window x:Class="TestRunner.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" ShowInTaskbar="False" Visibility="Hidden" WindowState="Minimized">
</Window>

 

Code-Behind

The following is the code to implement a bot for unit-testing code:

using AARLGS.TestRunner.Properties;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Forms;

namespace AARLGS.TestRunner
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Members
        string _title = null;
        string _testDllBin = null;
        string _testDllPath = null;
        string _targetAssemblyBin = null;
        string _resultsDirectoryPath = null;
        string _solutionPath = null;

        int _normalWaitInterval = 0;
        int _longWaitInterval = 0;

        int _passNotificationMessageDuration = 0;
        int _failNotificationMessageDuration = 0;

        int _showPassNotificationOnPassOccurance = 0;

        NotifyIcon _notificationIcon = new NotifyIcon();
        int _count = -1;
        bool _lastTestFailed = false;
        #endregion

        public MainWindow()
        {
            InitializeComponent();

            AssignConfigurationValues();
            InitializeNotification();

            var fileCompareList = BuildFileCompares();
            Copy(_targetAssemblyBin, _testDllBin);

            var isModified = true;

            while (true)
            {
                ProcessState(isModified);
                isModified = SetState(fileCompareList, isModified);
            };
        }

        #region Helpers
        private void AssignConfigurationValues()
        {
            _testDllBin = Settings.Default.Test_DLL_Bin;
            _testDllPath = Settings.Default.Test_DLL_Path;
            _targetAssemblyBin = Settings.Default.Target_Assembly_Bin;
            _resultsDirectoryPath = Settings.Default.Results_Path;
            _solutionPath = Settings.Default.Solution_Path;
            _title = Path.GetFileNameWithoutExtension(_solutionPath.Trim('"'));

            _normalWaitInterval = Settings.Default.Normal_Wait_Interval;
            _longWaitInterval = Settings.Default.Long_Wait_Interval;
            
            _showPassNotificationOnPassOccurance = Settings.Default.Show_Pass_Notification_On_Pass_Count;
            _passNotificationMessageDuration = Settings.Default.Show_Pass_Notification_Message_Duration;
            _failNotificationMessageDuration = Settings.Default.Show_Fail_Notification_Message_Duration;
        }

        void Copy(string sourceDirectory, string targetDirectory)
        {
            while (true)
            {
                try
                {
                    Directory.CreateDirectory(targetDirectory);

                    foreach (var file in Directory.GetFiles(sourceDirectory))
                        File.Copy(file, Path.Combine(targetDirectory, Path.GetFileName(file)), overwrite: true);

                    foreach (var directory in Directory.GetDirectories(sourceDirectory))
                        Copy(directory, Path.Combine(targetDirectory, Path.GetFileName(directory)));

                    break;
                }

                catch (Exception ex)
                {
                    var sleeptime = 1000;
                    Debug.WriteLine(string.Format("{0} - Copy Failed\n{1}\nTry again in {2} second...", DateTime.Now, ex.GetBaseException().Message, (sleeptime / 1000)));
                    Thread.Sleep(sleeptime);
                }
            }
        }

        private bool SetState(List<FileHistory> fileCompareList, bool isModified)
        {
            var currentAssembly = Directory.EnumerateFiles(_targetAssemblyBin).Where(f => Path.GetExtension(f).ToLower() == ".dll" || Path.GetExtension(f).ToLower() == ".exe").ToList();
            var currentCodeFiles = Directory.EnumerateFiles(_targetAssemblyBin).Where(f => Path.GetExtension(f).ToLower() == ".cs" || Path.GetExtension(f).ToLower() == ".xaml").ToList();
            var currentFiles = currentAssembly.Union(currentCodeFiles).ToList();
            currentFiles.Add(_testDllPath);

            if (currentFiles.Count() != fileCompareList.Count)
            {
                isModified = true;
            }
            else
            {
                isModified = CheckForModification(fileCompareList, isModified, currentFiles);
                Copy(_targetAssemblyBin, _testDllBin);
            }
            return isModified;
        }

        private void ProcessState(bool isModified)
        {
            if (isModified)
            {
                RunTests();
            }
            else
            {
                Thread.Sleep(_normalWaitInterval);
            }
        }

        private List<FileHistory> BuildFileCompares()
        {
            var previousTestDllInfo = new FileInfo(_testDllPath);
            var currentTestDLLInfo = new FileInfo(_testDllPath);

            var fileCompareList = new List<FileHistory>();
            fileCompareList.Add(new FileHistory() { Path = _testDllPath, PreviousFileInfo = previousTestDllInfo, CurrentFileInfo = currentTestDLLInfo });

            var currentAssembly = Directory.EnumerateFiles(_targetAssemblyBin).Where(f => Path.GetExtension(f).ToLower() == ".dll" || Path.GetExtension(f).ToLower() == ".exe");
            var currentCodeFiles = Directory.EnumerateFiles(_targetAssemblyBin).Where(f => Path.GetExtension(f).ToLower() == ".cs" || Path.GetExtension(f).ToLower() == ".xaml").ToList();
            var currentFiles = currentAssembly.Union(currentCodeFiles).ToList();

            foreach (var file in currentFiles)
            {
                fileCompareList.Add(new FileHistory() { Path = file, PreviousFileInfo = new FileInfo(file), CurrentFileInfo = new FileInfo(file) });
            }

            return fileCompareList;
        }

        private void InitializeNotification()
        {
            var path = @"../../Assets/Failing.ico";

            this._notificationIcon.Icon = new Icon(path);
            this._notificationIcon.Visible = true;
        }

        private void RunTests()
        {
            _count++;

            int passingTests = 0;
            int totalTests = 0;
            int failedTests = 0;

            RunTests(ref passingTests, ref totalTests, ref failedTests);

            if ((failedTests > 0 || _lastTestFailed) || (_count == 0 || _count % _showPassNotificationOnPassOccurance == 0))
            {
                OnNotify(passingTests, totalTests, failedTests);

                if (failedTests > 0)
                {
                    _lastTestFailed = true;
                    _count = -1;
                }
                else
                {
                    _lastTestFailed = false;
                }

                Thread.Sleep(_longWaitInterval);
            }
            else
            {
                Thread.Sleep(_normalWaitInterval);
            }
        }

        private static bool CheckForModification(List<FileHistory> fileCompareList, bool isModified, List<string> currentFiles)
        {
            var filesToAdd = new List<string>();

            foreach (var file in currentFiles)
            {
                var fileHistory = fileCompareList.Where(fh => fh.Path == file).SingleOrDefault();
                var fileExists = fileHistory != null;

                if (!fileExists)
                {
                    filesToAdd.Add(file);
                    isModified = true;
                }
                else
                {
                    fileHistory.PreviousFileInfo = fileHistory.CurrentFileInfo;
                    fileHistory.CurrentFileInfo = new FileInfo(file);

                    var isThisFileModified = fileHistory.CurrentFileInfo.LastWriteTime.CompareTo(fileHistory.PreviousFileInfo.LastWriteTime) > 0;

                    if (isThisFileModified)
                    {
                        isModified = true;
                    }
                }
            }

            foreach (var file in filesToAdd)
            {
                var fileHistory = new FileHistory() { Path = file, CurrentFileInfo = new FileInfo(file), PreviousFileInfo = new FileInfo(file) };
                fileCompareList.Add(fileHistory);
            }

            return isModified;
        }

        private void OnNotify(int passingTests, int totalTests, int failedTests)
        {
            if (failedTests > 0)
            {
                var summary = string.Format("( {0} ) Failed\n( {1} ) Passed\n______________\n( {2} ) Total", failedTests, passingTests, totalTests);
                this._notificationIcon.ShowBalloonTip(_failNotificationMessageDuration, string.Format("{0} Tests Failed", failedTests), summary, ToolTipIcon.Info);
            }
            else
            {
                var summary = string.Format("All ( {0} ) Tests Passed", passingTests);
                this._notificationIcon.ShowBalloonTip(_passNotificationMessageDuration, string.Format("{0}\n", _title, passingTests), summary, ToolTipIcon.Info);
            }
        }

        private void RunTests(ref int passingTests, ref int totalTests, ref int failedTests)
        {
            var buildProcess = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = @"C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe",
                    Arguments = string.Format("{0} {1}", _solutionPath,  @"/p:Configuration=Debug /p:Platform=""Mixed Platforms"""),
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    CreateNoWindow = true
                }
            };

            buildProcess.Start();

            var output = buildProcess.StandardOutput.ReadToEnd();

            var buildSucceeded = output.Contains("Build succeeded");

            if (!buildSucceeded)
            {
                return;
            }

            var testProcess = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe",
                    Arguments = _testDllPath.Replace(_testDllPath, '"' + _testDllPath + '"'),
                    
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    CreateNoWindow = true
                }
            };

            testProcess.Start();

            var stringBuilder = new StringBuilder();

            while (!testProcess.StandardOutput.EndOfStream)
            {
                try
                {
                    var line = testProcess.StandardOutput.ReadLine();
                    stringBuilder.Append(line);

                    if (line.Contains("Passed:"))
                    {
                        var parts = line.Split(':');
                        totalTests = int.Parse(parts[1].Split('.')[0]);
                        passingTests = int.Parse(parts[2].Split('.')[0]);
                        failedTests = int.Parse(parts[3].Split('.')[0]);
                        break;
                    }
                }

                catch (Exception ex)
                {
                    var message = string.Format("Error parsing results...\nTrying again later");
                    this._notificationIcon.ShowBalloonTip(_passNotificationMessageDuration, _title, message, ToolTipIcon.Info);
                    Debug.WriteLine(ex.GetBaseException().Message);
                }
            }

            var text = stringBuilder.ToString();
        }
        #endregion
    }
}

 

Configuration

This implementation requires that only the configuration file be updated for unit test configuration. This means that the code does not have to be edited and recompiled. Instead, just modify the app config file and launch the app again.

The following is the configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name=" TestRunner.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
        </sectionGroup>
    </configSections>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <userSettings>
        <TestRunner.Properties.Settings>
            <setting name="Test_DLL_Bin" serializeAs="String">
                <value>C:\Users\myusername\Documents\Visual Studio 2012\Projects\mySolutionFolder\myTestProjectDirectory\bin\Debug</value>
            </setting>
            <setting name="Test_DLL_Path" serializeAs="String">
                <value>C:\Users\myusername\Documents\Visual Studio 2012\Projects\mySolutionFolder\myTestProjectDirectory\bin\Debug\myTest.dll</value>
            </setting>
            <setting name="Results_Path" serializeAs="String">
                <value>C:\Temp</value>
            </setting>
            <setting name="Normal_Wait_Interval" serializeAs="String">
                <value>15000</value>
            </setting>
            <setting name="Long_Wait_Interval" serializeAs="String">
                <value>60000</value>
            </setting>
            <setting name="Target_Assembly_Bin" serializeAs="String">
                <value>C:\Users\myusername\Documents\Visual Studio 2012\Projects\mySolutionFolder\mySolution\bin\Debug</value>
            </setting>
            <setting name="Solution_Path" serializeAs="String">
                <value>"C:\Users\myusername\Documents\Visual Studio 2012\Projects\mySolutionFolder\mySolution.sln"</value>
            </setting>
            <setting name="Pass_Notification_Wait_Duration" serializeAs="String">
                <value>600000</value>
            </setting>
            <setting name="Show_Pass_Notification_On_Pass_Count" serializeAs="String">
                <value>5</value>
            </setting>
            <setting name="Show_Pass_Notification_Message_Duration" serializeAs="String">
                <value>250</value>
            </setting>
            <setting name="Show_Fail_Notification_Message_Duration" serializeAs="String">
                <value>3000</value>
            </setting>
        </TestRunner.Properties.Settings>
    </userSettings>
</configuration>

 

Conclusion

In conclusion, I have described the implementation for writing a bot to unit test code. The program is implemented to compile once. Thus, settings modifications can be set within the app’s config file.

Introduction

Inspired by Microsoft’s Prism framework, I decided to implement their EventAggregator class as a learning exercise. The EventAggregator is a class within Prism that serves as a mediator between publishers and subscribers of an event. Imagine a platform where arbitrary subscribers can place the messages that they’re interested in onto it. Next imagine publishers placing messages on this same platform for subscribers to respond to. What you have imagined is what I call a message bus. Instead of using the class name “EventAggregator”, I decided to rename the class to MessageBus. This implementation requires the following classes:

  • MessageBus
    • The message platform
  • Observer
    • An object that specifies the messages it wants to be notified about as well as message-handler
  • Messages
    • Any literal value (typically a string)

In addition, I could have also implemented a class called Subscription. This class could be used by an observer to manage their subscription to the MessageBus. I will show this in another post. Demo The following code demonstrates how to use the MessageBus for subscribing and publishing. Note, even a publisher can be a subscriber to requests.


using Bizmonger.Patterns;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using System.Diagnostics;

[TestClass]
public class Test1
{
    #region Testware
    const string SOME_SUBSCRIPTION = "SOME_EVENT";
    #endregion

    [TestMethod]
    public void MyTest()
    {
        MessageBus.Instance.Register(ParticipationType.PUBLISHER, SOME_SUBSCRIPTION, OnPublish);

        var payload = "subscriber_parameter";
        MessageBus.Instance.Register(ParticipationType.SUBSCRIBER, SOME_SUBSCRIPTION, payload, (obj) =>
            {
                Debug.WriteLine("Response to an event with" + obj);
                MessageBus.Instance.Register(ParticipationType.PUBLISHER, SOME_SUBSCRIPTION, OnPublish);
            });
    }

    private void OnPublish(object obj)
    {
        MessageBus.Instance.Publish(SOME_SUBSCRIPTION, string.Format("published_{0}", obj));
    }
}

MessageBus

A MessageBus is a platform to for publishers and subscribers to manage messages while being decoupled from one another. Here’s a simple implementation of MessageBus:

</pre>
<pre>using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
 
namespace Bizmonger.Patterns
{
    public class MessageBus
    {
        #region Singleton
        static MessageBus _messageBus = null;
        private MessageBus() { }
 
        public static MessageBus Instance
        {
            get
            {
                if (_messageBus == null)
                {
                    _messageBus = new MessageBus();
                }
 
                return _messageBus;
            }
        }
        #endregion
 
        #region Members
        Dictionary<string, List<Observer>> _observers = new Dictionary<string, List<Observer>>();
        Dictionary<string, List<Observer>> _oneTimeObservers = new Dictionary<string, List<Observer>>();
        Dictionary<string, List<Observer>> _waitingSubscribers = new Dictionary<string, List<Observer>>();
        Dictionary<string, List<Observer>> _waitingUnsubscribers = new Dictionary<string, List<Observer>>();
 
        int _publishingCount = 0;
        #endregion
 
        public void Subscribe(string subscription, Action<object> response)
        {
            Subscribe(subscription, response, _observers);
        }
 
        public void SubscribeFirstPublication(string subscription, Action<object> response)
        {
            Subscribe(subscription, response, _oneTimeObservers);
        }
 
        public int Unsubscribe(string subscription, Action<object> response)
        {
            List<Observer> observersToUnsubscribe = null;
            var found = _observers.TryGetValue(subscription, out observersToUnsubscribe);
 
            var waitingSubscribers = new List<Observer>();
            _waitingSubscribers.Values.ToList().ForEach(o => waitingSubscribers.AddRange(o));
            observersToUnsubscribe.AddRange(waitingSubscribers.Where(o => o.Respond == response));
 
            var oneTimeObservers = new List<Observer>();
            _oneTimeObservers.Values.ToList().ForEach(o => oneTimeObservers.AddRange(o));
            observersToUnsubscribe.AddRange(oneTimeObservers.Where(o => o.Respond == response));
 
            if (_publishingCount == 0)
            {
                observersToUnsubscribe.ForEach(o => _observers.Remove(o.Subscription));
            }
 
            else
            {
                waitingSubscribers.AddRange(observersToUnsubscribe);
            }
 
            return observersToUnsubscribe.Count;
        }
 
        public int Unsubscribe(string subscription)
        {
            List<Observer> foundObservers = null;
            var found = _observers.TryGetValue(subscription, out foundObservers);
 
            if (!found)
            {
                foundObservers = new List<Observer>();
                _observers.Add(subscription, foundObservers);
            }
 
            foundObservers.AddRange(_waitingSubscribers[subscription]);
            foundObservers.AddRange(_oneTimeObservers[subscription]);
 
            if (_publishingCount == 0)
            {
                _observers.Remove(subscription);
            }
 
            else if (_waitingUnsubscribers[subscription] != null)
            {
                _waitingUnsubscribers[subscription].AddRange(foundObservers);
            }
 
            return foundObservers.Count;
        }
 
        public void Publish(string subscription, object payload = null)
        {
            _publishingCount++;
 
            Publish(_observers, subscription, payload);
            Publish(_oneTimeObservers, subscription, payload);
            Publish(_waitingSubscribers, subscription, payload);
 
            _oneTimeObservers.Remove(subscription);
            _waitingUnsubscribers.Clear();
 
            _publishingCount--;
        }
 
        private void Publish(Dictionary<string, List<Observer>> observers, string subscription, object payload)
        {
            Debug.Assert(_publishingCount >= 0);
 
            List<Observer> foundObservers = null;
            observers.TryGetValue(subscription, out foundObservers);
 
            if (foundObservers != null)
            {
                foundObservers.ToList().ForEach(o => o.Respond(payload));
            }
        }
 
        public IEnumerable<Observer> GetObservers(string subscription)
        {
            List<Observer> foundObservers = null;
            _observers.TryGetValue(subscription, out foundObservers);
 
            return foundObservers;
        }
 
        public void Clear()
        {
            _observers.Clear();
            _oneTimeObservers.Clear();
        }
 
        #region Helpers
        private void Subscribe(string subscription, Action<object> response, Dictionary<string, List<Observer>> observers)
        {
            Debug.Assert(_publishingCount >= 0);
 
            var observer = new Observer() { Subscription = subscription, Respond = response };
 
            if (_publishingCount == 0)
            {
                Add(subscription, observers, observer);
            }
            else
            {
                Add(subscription, _waitingSubscribers, observer);
            }
        }
 
        private static void Add(string subscription, Dictionary<string, List<Observer>> observers, Observer observer)
        {
            List<Observer> foundObservers = null;
            var observersExist = observers.TryGetValue(subscription, out foundObservers);
 
            if (observersExist)
            {
                foundObservers.Add(observer);
            }
            else
            {
                foundObservers = new List<Observer>() { observer };
                observers.Add(subscription, foundObservers);
            }
        }
        #endregion
    }
}

Observer

An observer is a subscriber to a message.


using System;

namespace Bizmonger.Patterns
{
    public class Observer
    {
        public string Subscription { get; set; }
        public Action<object> Respond {get; set; }
    }
}

ParticipationType

Participation specifies an object as a subscriber or publisher.


Participation
namespace Bizmonger.Patterns
{
    public enum ParticipationType
    {
        SUBSCRIBER = 0,
        PUBLISHER
    }
}

Conclusion

In conclusion, I have described an implementation that can be leveraged to decouple a subscriber from a publisher of an event. This example did not include the Subscription class. Also, this example did not include the support for subscribing to a message only once which could be useful when performing a subscription within a method that could be invoked numerous times within the life-cycle of an application. With that said, I have learned that subscriptions are best executed within a constructor of an object so that duplicate registrations do not occur, even though the MessageBus can incorporate logic to prevent duplicate observers (i.e. HashSet). This example also does not support memory-leak prevention. In the next article, I will show a version of MessageBus that accounts for some of these concerns.