Mike Nichols - Son of Nun Technology

Observer for Notification Revisited

In my previous post I proposed a way of adding Notification messages to a global object that would be observed by Presenters. Upon flushing the current Unit of Work, the Notification messages would be delivered to some implementation of INotificationHandler; this could be a custom web control or some logging or whatever. The point is to eliminate throwing exceptions as the way of messaging from the domain up to the UI.

My first iteration works ok, but I had pushed the registration of the INotificationObserver into my PresenterBase class. Now, I want to be able to have multiple Observers for the notifications gathered during a Unit of Work, so I changed my NotificationManager class (renamed Notifier)  and tried to implement a static class similar to how Ayende does so with his Unit of Work class:

 

   public static class Notifier

    {

 

        public const string CurrentObserverKey = "CurrentObserver.Key";

        public const string CurrentNotificationsKey = "CurrentNotification.Key";

        private static string LocalDataObserverKey(IUnitOfWork unitOfWork)

        {

            return CurrentObserverKey + unitOfWork.GetHashCode().ToString();

        }

        private static string LocalDataNotificationsKey(IUnitOfWork unitOfWork)

        {

            return CurrentNotificationsKey + unitOfWork.GetHashCode().ToString();

        }

        public static void Register(IUnitOfWork unitOfWork, INotificationObserver observer)

        {

            string key = LocalDataObserverKey(unitOfWork);

 

            if (!Local.Data.ContainsKey(key))

            {

                Initialize(unitOfWork);

            }

 

            Set<INotificationObserver> observers =

                (Set<INotificationObserver>) Local.Data[key];

            if (!observers.Contains(observer))

            {

                observers.Add(observer);

 

            }

        }

 

        private static void Initialize(IUnitOfWork unitOfWork)

        {

 

            Local.Data[LocalDataObserverKey(unitOfWork)] = new  Set<INotificationObserver>();

            Local.Data[LocalDataNotificationsKey(unitOfWork)] = new Set<INotification>();

            unitOfWork.UnitOfWorkFlushed += new EventHandler(Notify);

 

        }

 

        private static void Notify(object sender, EventArgs e)

        {

            IUnitOfWork unitOfWork = (IUnitOfWork)sender;

            Set<INotificationObserver> observers =

                (Set<INotificationObserver>) Local.Data[LocalDataObserverKey(unitOfWork)];

            Set<INotification> notifications =

                (Set<INotification>) Local.Data[LocalDataNotificationsKey(unitOfWork)];

            foreach(INotificationObserver observer in observers)

            {

                observer.Update(notifications.ToArray());

            }

            Clear(unitOfWork);

 

        }

 

        public static void AddNotification(INotification notification)

        {

            Set<INotification> notifications =

                (Set<INotification>) Local.Data[LocalDataNotificationsKey(UnitOfWork.Current)];

 

            notifications.Add(notification);

        }

 

        public static void AddItem(INotificationItem notificationItem)

        {

            Set<INotification> notifications =

                (Set<INotification>) Local.Data[LocalDataNotificationsKey(UnitOfWork.Current)];

 

            INotification notification = null;

            if (notifications.Count == 0)

            {

                notification = new Notification();

                AddNotification(notification);

            }

            else

            {

                notification = notifications.ToArray()[0];

            }

 

            notification.AddItem(notificationItem);

        }

 

        public static void AddItem(INotification notification,INotificationItem item)

        {

            Set<INotification> notifications =

                (Set<INotification>) Local.Data[LocalDataNotificationsKey(UnitOfWork.Current)];

 

            INotification target = null;

            if (!notifications.TryGetItem(notification,out target))

            {

                target = notification;

                notifications.Add(notification);

            }

 

            target.AddItem(item);

        }

        public static void Flush(INotificationObserver observer)

        {

            IUnitOfWork current = UnitOfWork.Current;

 

        }

        public static void Clear(IUnitOfWork unitOfWork)

        {

            Local.Data.Remove(LocalDataObserverKey(unitOfWork));

            Local.Data.Remove(LocalDataNotificationsKey(unitOfWork));

        }

 

    }

INotificationObservers and the INotification objects are mapped to their respective UnitOfWork implementation.

Now the problem arise when there are multiple presenters on a page...very common if you use User Controls as your Views in an MVP setup. I like to have small components to work with so this is very common on my pages. PLacing the registration with the Notifier in teh PresenterBase class will obviously result in the same notifications being delivered multiple times and if yo have a single control for Notification messages on your web page each message will be duplicated. One solution might be to simply prevent duplicate equal messages in the control. But I'd rather have more granular control of who is being observed for notifications so I added another With to apply to my Unit of Work.:

    public static partial class With

    {

        public static void UnitOfWork(IUnitOfWork unitOfWork, INotificationObserver observer, Proc method)

        {

            if (observer != null)

                Common.Notifier.Register(unitOfWork, observer);

            try

            {

                method();

                unitOfWork.Flush();

            }

            catch

            {

                //Do something

            }

        }

 

        public static void UnitOfWork(INotificationObserver observer,Proc method)

        {

            UnitOfWork(Common.UnitOfWork.Current,observer, method);

        }

        public static void UnitOfWork(Proc method)

        {

            UnitOfWork(Common.UnitOfWork.Current, null, method);

        }

    }

No big deal here, just running a delegate within a unit of work (typically the Current) while accepting registrations for the INotificationObserver and then flushing out the Unit of Work.

To implement this in my Presenter method (remember that the Presenter implements INotificationObserver) now, it might look like this:

       public void AddPerson2()

        {

            With.UnitOfWork(this,

                          delegate

                          {

                            //Do some stuff in the service layer or whatever

 

                              _addCommand.Execute(Person);

                          });

        }

The unit of work details and notification registration is handled for me and if I need to change the way I deal with it it shoul dbe a snap later on.

Comments?