Suggestion on Messenger

Jul 24, 2009 at 11:27 PM

Would it possibly be a good idea to add object sender information to the Messenger? IE on Register and Notify the user of the messenger would have to identify itself via some sort of unique id. This way if you have a two ViewModels that are listening and changing the same data you will not end up in a circular infinite recursion.

I came across this thought because I have some ViewModels that need to update Messages, but they are also listening to the same ones. Is there anything already preventing this case?

I think at a basic level on register all you would need is the Registered objects unique name and maybe its Type. Inside of the Messenger class on the NotifyColleagues the DynamicInvoke on the action would occur for everyone but the sender.

Hope I am not totally off base.

 

Nick

 

Jul 27, 2009 at 7:10 PM

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MvvmFoundation.Wpf
{
    /// <summary>
    /// Nick McCready (NEM)
    /// Extending Josh Smith's Messenger to help try and prevent infinite recursion for VieModels that listen and Update the same data
    /// </summary>
    public class SourceMessenger
    {
        //public void NotifyColleagues(string message);
        //public void NotifyColleagues(string message, object parameter);
        //public void Register(string message, Delegate callback);

        Dictionary<string, Type> _registeredSenders = new Dictionary<string, Type>();
        Messenger _messenger;

        public SourceMessenger()
        {
            _messenger = new Messenger();
        }


        public void NotifyColleagues(MessengerSender sender, string message)
        {
            if (!_registeredSenders.ContainsKey(sender.Name))
                throw (new Exception("Sender is not registered"));
            if (_registeredSenders[sender.Name] != sender.SenderType)
                throw (new Exception("Sender Type MisMatch"));
            _messenger.NotifyColleagues(message, new MessagePayload(sender, null));
        }
        public void NotifyColleagues(MessengerSender sender, string message, object parameter)
        {
            if (!_registeredSenders.ContainsKey(sender.Name))
                throw (new Exception("Sender is not registered"));
            if (_registeredSenders[sender.Name] != sender.SenderType)
                throw (new Exception("Sender Type MisMatch"));
            _messenger.NotifyColleagues(message, new MessagePayload(sender, parameter));
        }

        public void RegisterSender(MessengerSender sender)
        {
            try
            {
                _registeredSenders.Add(sender.Name, sender.SenderType);
            }
            catch
            {
                throw (new Exception("InvalidUnique Name on Sender"));
            }
        }

        /// <summary>
        /// Registers the specified sender. At Least this way the user can check who sent it
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="message">The message.</param>
        /// <param name="callback">The callback.</param>
        public void RegisterMessage(MessengerSender sender, string message, Action<MessagePayload> callback)
        {
            if (!_registeredSenders.ContainsKey(sender.Name))
                throw (new Exception("Sender is not registered"));
            if (_registeredSenders[sender.Name] != sender.SenderType)
                throw (new Exception("Sender Type MisMatch"));
            _messenger.Register(message, callback);
        }

    }

    public class MessengerSender
    {
        public Type SenderType { get; set; }
        public string Name { get; set; }
        public MessengerSender(string name, Type type)
        {
            SenderType = type;
            Name = name;
        }

        public override bool Equals(object obj)
        {
            if (obj is MessengerSender)
            {
                MessengerSender sender = (MessengerSender)obj;
                if (sender.Name == this.Name && sender.SenderType == this.SenderType)
                    return true;
                return false;
            }
            else
                return false;
        }

    }

    public class MessagePayload
    {
        public object Parameter { get; set; }
        public MessengerSender Sender { get; set; }
        public MessagePayload(MessengerSender sender, object parameter)
        {
            Sender = sender;
            Parameter = parameter;
        }
    }
}

Here is the code I wrote as a wrapper around Messenger. I have a full working example I would like to upload for anyone to look at. All I did was use the existing example.

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MvvmFoundation.Wpf
{
    /// <summary>
    /// Nick McCready (NEM)
    /// Extending Josh Smith's Messenger to help try and prevent infinite recursion for VieModels that listen and Update the same data
    /// </summary>
    public class SourceMessenger
    {
        //public void NotifyColleagues(string message);
        //public void NotifyColleagues(string message, object parameter);
        //public void Register(string message, Delegate callback);
        Dictionary<string, Type> _registeredSenders = new Dictionary<string, Type>();
        Messenger _messenger;
        public SourceMessenger()
        {
            _messenger = new Messenger();
        }
        public void NotifyColleagues(MessengerSender sender, string message)
        {
            if (!_registeredSenders.ContainsKey(sender.Name))
                throw (new Exception("Sender is not registered"));
            if (_registeredSenders[sender.Name] != sender.SenderType)
                throw (new Exception("Sender Type MisMatch"));
            _messenger.NotifyColleagues(message, new MessagePayload(sender, null));
        }
        public void NotifyColleagues(MessengerSender sender, string message, object parameter)
        {
            if (!_registeredSenders.ContainsKey(sender.Name))
                throw (new Exception("Sender is not registered"));
            if (_registeredSenders[sender.Name] != sender.SenderType)
                throw (new Exception("Sender Type MisMatch"));
            _messenger.NotifyColleagues(message, new MessagePayload(sender, parameter));
        }
        public void RegisterSender(MessengerSender sender)
        {
            try
            {
                _registeredSenders.Add(sender.Name, sender.SenderType);
            }
            catch
            {
                throw (new Exception("InvalidUnique Name on Sender"));
            }
        }
        /// <summary>
        /// Registers the specified sender. At Least this way the user can check who sent it
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="message">The message.</param>
        /// <param name="callback">The callback.</param>
        public void RegisterMessage(MessengerSender sender, string message, Action<MessagePayload> callback)
        {
            if (!_registeredSenders.ContainsKey(sender.Name))
                throw (new Exception("Sender is not registered"));
            if (_registeredSenders[sender.Name] != sender.SenderType)
                throw (new Exception("Sender Type MisMatch"));
            _messenger.Register(message, callback);
        }
    }
    public class MessengerSender
    {
        public Type SenderType { get; set; }
        public string Name { get; set; }
        public MessengerSender(string name, Type type)
        {
            SenderType = type;
            Name = name;
        }
        public override bool Equals(object obj)
        {
            if (obj is MessengerSender)
            {
                MessengerSender sender = (MessengerSender)obj;
                if (sender.Name == this.Name && sender.SenderType == this.SenderType)
                    return true;
                return false;
            }
            else
                return false;
        }
    }
    public class MessagePayload
    {
        public object Parameter { get; set; }
        public MessengerSender Sender { get; set; }
        public MessagePayload(MessengerSender sender, object parameter)
        {
            Sender = sender;
            Parameter = parameter;
        }
    }
}

 

 

Jul 27, 2009 at 7:35 PM

This probably should be updated with weak references like the original, but this was a start.

Coordinator
Jul 27, 2009 at 9:58 PM

That's an interesting idea, thanks for sharing it.  I am not going to add it to MVVM Foundation because I think that it adds complexity to Messenger that is only in place to avoid an edge case, which could be avoided by the type's client code.  Thanks for sharing your ideas!

Jul 28, 2009 at 12:16 AM

Actually, I really meant for this class not to be used. I just wanted to use it as an example of the issue. This is why I would like to send you the  Demo updated with this. What I will probably do is integrate the source and payload stuff directly into Messenger so that  it skips the delegate invocation for the the person that called the NotifyColleagues method.

Jul 28, 2009 at 7:33 PM
Edited Jul 28, 2009 at 8:39 PM

Here is another way where the Messenger makes thge decision not to invoke delegates by the Notifying sender.

 

I had a bug.. fixed it. was in GetActions()

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace MvvmFoundation.Wpf
{
    /// <summary>
    /// Nick McCready (NEM)
    /// Extending Josh Smith's Messenger to help try and prevent infinite recursion for VieModels that listen and Update the same data
    /// </summary>
    public class SourceMessenger
    {

        Dictionary<string, Type> _registeredSenders = new Dictionary<string, Type>();

        public SourceMessenger()
        {
        }


        public void NotifyColleagues(MessengerSender sender, string message)
        {
            lock (_registeredSenders)
            {
                if (!_registeredSenders.ContainsKey(sender.Name))
                    throw (new Exception("Sender is not registered"));
                if (_registeredSenders[sender.Name] != sender.SenderType)
                    throw (new Exception("Sender Type MisMatch"));
            }
            this.OldNotifyColleagues(sender, message);

        }
        public void NotifyColleagues(MessengerSender sender, string message, object parameter)
        {
            lock (_registeredSenders)
            {
                if (!_registeredSenders.ContainsKey(sender.Name))
                    throw (new Exception("Sender is not registered"));
                if (_registeredSenders[sender.Name] != sender.SenderType)
                    throw (new Exception("Sender Type MisMatch"));
            }
            this.OldNotifyColleagues(sender, message, parameter);
        }
        #region Register
        public void RegisterSender(MessengerSender sender)
        {
            try
            {
                lock (_registeredSenders)
                    _registeredSenders.Add(sender.Name, sender.SenderType);
            }
            catch
            {
                throw (new Exception("InvalidUnique Name on Sender"));
            }
        }

        public void DeRegisterSender(MessengerSender sender)
        {
            lock (_registeredSenders)
            {
                if (_registeredSenders.ContainsKey(sender.Name))
                {
                    //verify the type
                    if (_registeredSenders[sender.Name] == sender.SenderType)
                        _registeredSenders.Remove(sender.Name);
                }
            }
        }

        /// <summary>
        /// Registers the specified sender. At Least this way the user can check who sent it
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="message">The message.</param>
        /// <param name="callback">The callback.</param>
        public void RegisterMessage(MessengerSender sender, string message, Delegate callback)
        {
            lock (_registeredSenders)
            {
                if (!_registeredSenders.ContainsKey(sender.Name))
                    throw (new Exception("Sender is not registered"));
                if (_registeredSenders[sender.Name] != sender.SenderType)
                    throw (new Exception("Sender Type MisMatch"));
            }
            Register(sender, message, callback);

        }

        /// <summary>
        /// Registers a callback method to be invoked when a specific message is broadcasted.
        /// </summary>
        /// <param name="message">The message to register for.</param>
        /// <param name="callback">The callback to be called when this message is broadcasted.</param>
        protected void Register(MessengerSender sender, string message, Delegate callback)
        {
            if (String.IsNullOrEmpty(message))
                throw new ArgumentException("'message' cannot be null or empty.");

            if (callback == null)
                throw new ArgumentNullException("callback");

            ParameterInfo[] parameters = callback.Method.GetParameters();
            if (parameters != null && parameters.Length > 1)
                throw new InvalidOperationException("The registered delegate can have no more than one parameter.");

            Type parameterType = (parameters == null || parameters.Length == 0) ? null : parameters[0].ParameterType;

            _messageToActionsMap.AddAction(sender, message, callback.Target, callback.Method, parameterType);
        }

        #endregion // Register

        #region NotifyColleagues

        /// <summary>
        /// Notifies all registered parties that a message is being broadcasted.
        /// </summary>
        /// <param name="message">The message to broadcast</param>
        /// <param name="parameter">The parameter to pass together with the message</param>
        protected void OldNotifyColleagues(MessengerSender sender, string message, object parameter)
        {
            if (String.IsNullOrEmpty(message))
                throw new ArgumentException("'message' cannot be null or empty.");

            var actions = _messageToActionsMap.GetActions(sender, message);
            if (actions != null)
                actions.ForEach(action => action.DynamicInvoke(parameter));
        }

        /// <summary>
        /// Notifies all registered parties that a message is being broadcasted.
        /// </summary>
        /// <param name="message">The message to broadcast.</param>
        protected void OldNotifyColleagues(MessengerSender sender, string message)
        {
            if (String.IsNullOrEmpty(message))
                throw new ArgumentException("'message' cannot be null or empty.");

            var actions = _messageToActionsMap.GetActions(sender, message);
            if (actions != null)
                actions.ForEach(action => action.DynamicInvoke());
        }

        #endregion // NotifyColleauges

        #region MessageToActionsMap [nested class]

        /// <summary>
        /// This class is an implementation detail of the Messenger class.
        /// </summary>
        private class MessageToActionsMap
        {
            #region Constructor

            internal MessageToActionsMap()
            {
            }

            #endregion // Constructor

            #region AddAction

            /// <summary>
            /// Adds an action to the list.
            /// </summary>
            /// <param name="message">The message to register.</param>
            /// <param name="target">The target object to invoke, or null.</param>
            /// <param name="method">The method to invoke.</param>
            /// <param name="actionType">The type of the Action delegate.</param>
            internal void AddAction(MessengerSender sender, string message, object target, MethodInfo method, Type actionType)
            {
                if (message == null)
                    throw new ArgumentNullException("message");

                if (method == null)
                    throw new ArgumentNullException("method");

                lock (_map)
                {
                    if (!_map.ContainsKey(message))
                        _map[message] = new List<WeakAction>();

                    _map[message].Add(new WeakAction(sender, target, method, actionType));
                }
            }

            #endregion // AddAction

            #region GetActions

            /// <summary>
            /// Gets the list of actions to be invoked for the specified message
            /// </summary>
            /// <param name="message">The message to get the actions for</param>
            /// <returns>Returns a list of actions that are registered to the specified message</returns>
            internal List<Delegate> GetActions(MessengerSender sender, string message)
            {
                if (message == null)
                    throw new ArgumentNullException("message");

                List<Delegate> actions;
                lock (_map)
                {
                    if (!_map.ContainsKey(message))
                        return null;

                    List<WeakAction> weakActions = _map[message];
                    actions = new List<Delegate>(weakActions.Count);
                    for (int i = weakActions.Count - 1; i > -1; --i)
                    {
                        WeakAction weakAction = weakActions[i];
                        if (weakAction == null)
                            continue;

                        Delegate action = weakAction.CreateAction();
                        if (action != null && !sender.Equals(weakAction.Sender.Target))
                        {
                            actions.Add(action);
                        }
                        else
                        {
                            if (action == null)
                            {
                                // The target object is dead, so get rid of the weak action.
                                weakActions.Remove(weakAction);
                            }
                        }
                    }

                    // Delete the list from the map if it is now empty.
                    if (weakActions.Count == 0)
                        _map.Remove(message);
                }

                return actions;
            }

            #endregion // GetActions

            #region Fields

            // Stores a hash where the key is the message and the value is the list of callbacks to invoke.
            readonly Dictionary<string, List<WeakAction>> _map = new Dictionary<string, List<WeakAction>>();

            #endregion // Fields
        }

        #endregion // MessageToActionsMap [nested class]

        #region WeakAction [nested class]

        /// <summary>
        /// This class is an implementation detail of the MessageToActionsMap class.
        /// </summary>
        private class WeakAction
        {
            #region Constructor

            /// <summary>
            /// Constructs a WeakAction.
            /// </summary>
            /// <param name="target">The object on which the target method is invoked, or null if the method is static.</param>
            /// <param name="method">The MethodInfo used to create the Action.</param>
            /// <param name="parameterType">The type of parameter to be passed to the action. Pass null if there is no parameter.</param>
            internal WeakAction(object sender, object target, MethodInfo method, Type parameterType)
            {
                if (sender == null)
                    _sender = null;
                else
                    _sender = new WeakReference(sender);

                if (target == null)
                {
                    _targetRef = null;
                }
                else
                {
                    _targetRef = new WeakReference(target);
                }

                _method = method;

                if (parameterType == null)
                {
                    _delegateType = typeof(Action);
                }
                else
                {
                    _delegateType = typeof(Action<>).MakeGenericType(parameterType);
                }
            }

            #endregion // Constructor

            #region CreateAction

            /// <summary>
            /// Creates a "throw away" delegate to invoke the method on the target, or null if the target object is dead.
            /// </summary>
            internal Delegate CreateAction()
            {
                // Rehydrate into a real Action object, so that the method can be invoked.
                if (_targetRef == null)
                {
                    return Delegate.CreateDelegate(_delegateType, _method);
                }
                else
                {
                    try
                    {
                        object target = _targetRef.Target;
                        if (target != null)
                            return Delegate.CreateDelegate(_delegateType, target, _method);
                    }
                    catch
                    {
                    }
                }

                return null;
            }

            #endregion // CreateAction

            #region Fields

            readonly Type _delegateType;
            readonly MethodInfo _method;
            readonly WeakReference _targetRef;
            readonly WeakReference _sender;

            public WeakReference Sender
            {
                get { return _sender; }
            }

            #endregion // Fields
        }

        #endregion // WeakAction [nested class]

        #region Fields

        readonly MessageToActionsMap _messageToActionsMap = new MessageToActionsMap();

        #endregion // Fields

    }

    /// <summary>
    /// Simple Class used to identify the source of a Message
    /// </summary>
    public class MessengerSender
    {
        public Type SenderType { get; set; }
        public string Name { get; set; }
        public MessengerSender(string name, Type type)
        {
            SenderType = type;
            Name = name;
        }

        public override bool Equals(object obj)
        {
            // If parameter is null return false.
            if (obj == null)
                return false;

            MessengerSender sender = obj as MessengerSender;
            if ((System.Object)sender == null)
                return false;
            else
            {
                return (sender.Name == this.Name) && (sender.SenderType == this.SenderType);
            }
        }

        public bool Equals(MessengerSender sender)
        {
            if ((object)sender == null)
                return false;

            return (sender.Name == this.Name) && (sender.SenderType == this.SenderType);
        }
    }
}