Convenience using the MessageMediator

May 11, 2012 at 11:50 AM

You wrote Catel is all about convenience.
While trying out the MessageMediator system, I found it could be a lot more convenient to me.
So I decided to code the following MessageBase class.
Maybe you find it useful and want to integrate this with Catel.

Basically you can define different message types and very easy subscribe to them or send them.

Example for registering a message (via a static factory method):
SomeMessage.Register(this, msg => MyHandler(msg.Data));

Example for sending a message:
SomeMessage.SendWith(data);

Following the well documented base class code and some sample message implementations.

    /// <summary>
    /// Base class for messages distributed via the Catel MessageMediator subsystem.<br />
    /// Inherit from this class to define individual message types.<br />
    /// For most subclasses the only thing to code is an empty class body including the type parameters.<br />
    /// For the payload data you can choose betweeen the following options:<br />
    /// <list type="bullet">
    /// <item><description>The Data property provided within this base class of type TData using simple types like int or string.</description></item>
    /// <item><description>The Data property provided within this base class of type TData using userdefined data types.</description></item>
    /// <item><description>Define properties on the derived message class itself.</description></item>
    /// <item><description>A combination of the previous options.</description></item>
    /// </list>
    /// </summary>
    /// <typeparam name="TMessage">The actual type of the message.</typeparam>
    /// <typeparam name="TData">The type of payload data to be carried with the message.</typeparam>
    public abstract class MessageBase<TMessage, TData>
        where TMessage : MessageBase<TMessage, TData>, new()
    {
        // stores a reference to the Catels MessageMediator
        private static readonly IMessageMediator mediator;

        /// <summary>
        /// Static constructor stores the reference to the Catels MessageMediator.
        /// </summary>
        static MessageBase()
        {
            mediator = ServiceLocator.Instance.ResolveType<IMessageMediator>();
        }

        /// <summary>
        /// Neccesary for two reasons:<br/>
        /// <list type="number">
        /// <item><description>Create an instance of the Message class via the TMessage type parameter used by the With() method.</description></item>
        /// <item><description>Allow derived classes to be defined using an empty class body with the implicit default constructor.</description></item>
        /// </list>
        /// </summary>
        protected MessageBase()
        {
        }

        /// <summary>
        /// The constructor of a derived Message class can use this constructor to populate the built in data property.
        /// </summary>
        /// <param name="data">The payload data to be used.</param>
        protected MessageBase(TData data)
        {
            Data = data;
        }
        
        /// <summary>
        /// Provides access to the paylaod data
        /// </summary>
        public TData Data { get; protected set; }

        /// <summary>
        /// Use MessageClass.SendWith(data) to send a new message via the mediator service.
        /// </summary>
        /// <param name="data">The payload data.</param>
        /// <param name="tag">The optional Catel mediator tag to be used.</param>
        public static void SendWith(TData data, object tag = null)
        {
            var message = With(data);
            Send(message);
        }

        /// <summary>
        /// Send the message.
        /// </summary>
        /// <param name="message">The message to be sent.</param>
        /// <param name="tag">The optional Catel mediator tag to be used.</param>
        protected static void Send(TMessage message, object tag = null)
        {
            mediator.SendMessage(message, tag);
        }

        /// <summary>
        /// Convenient helper method to subscribe to this Message type.<br />
        /// Usage:
        /// <list type="bullet">
        /// <item><description>MessageClass.Register(this, msg => Handler) if the handler has the signature void Handler(MessageClass message)</description></item>
        /// <item><description>MessageClass.Register(this, msg => Handler(msg.Data)) if the handler has the signature void Handler(TData data)</description></item>
        /// </list>
        /// </summary>
        /// <param name="recipient">The instance which registers to the messages. Is most cases this will be "this"</param>
        /// <param name="handler">A delegate handling the incoming message. For example: msg => Handler(msg.Data).</param>
        /// <param name="tag">The optional Catel mediator tag to be used.</param>
        public static void Register(object recipient, Action<TMessage> handler, object tag = null)
        {
            mediator.Register<TMessage>(recipient, handler, tag);
        }

        /// <summary>
        /// Returns an instance of the MessageClass populated with payload Data.<br/>
        /// Most times used internally by the SendWith() method.
        /// </summary>
        /// <param name="data">The payload data.</param>
        /// <returns>An instance of the MessageClass populated with the given payload data.</returns>
        public static TMessage With(TData data)
        {
            return new TMessage {Data = data};
        }
    }

    /// <summary>
    /// Implements a simple message with a string payload data.
    /// </summary>
    public class SimpleMessage : MessageBase<SimpleMessage, string>
    {
    }

    /// <summary>
    /// Custom class to carry some data within a message.
    /// </summary>
    public class UserdefinedMessageData
    {
        public string Prop1 { get; set; }
        public string Prop2 { get; set; }
    }

    /// <summary>
    /// Implements a message transferring userdefined payload data.
    /// </summary>
    public class UserdefinedMessage : MessageBase<UserdefinedMessage, UserdefinedMessageData>
    {
    }

    /// <summary>
    /// Implements a message transferring a boolean value and a custom property.
    /// </summary>
    public class CombinedMessage : MessageBase<CombinedMessage, bool>
    {
        /// <summary>
        /// needed by the base class....
        /// </summary>
        public CombinedMessage()
        {
        }

        /// <summary>
        /// Used internally by the SendWith overload to create an instance.
        /// </summary>
        private CombinedMessage(bool data, Exception exception) : base(data)
        {
            Exception = exception;
        }

        /// <summary>
        /// Send a CombinedMessage with the given payload data.
        /// </summary>
        /// <param name="data">The boolean payload Data to be sent with.</param>
        /// <param name="exception">The exception payload Data to be sent with.</param>
        /// <param name="tag">The optional Catel mediator tag to be used.</param>
        public static void SendWith(bool data, Exception exception, object tag = null)
        {
            var message = new CombinedMessage(data, exception);
            Send(message, tag);
        }

        /// <summary>
        /// Provides access to the additional exception payload data of the message.
        /// </summary>
        public Exception Exception { get; private set; }
    }

Coordinator
May 13, 2012 at 12:11 PM

Great, we will consider impleemnting this in catel!

Coordinator
May 14, 2012 at 8:39 PM

I have created an issue for this:

http://catel.codeplex.com/workitem/7133

Coordinator
May 16, 2012 at 5:15 PM

I have added the class. I added some small modifications, but it is very nice to receive such a clean and well documented class! You don't have any unit tests laying around? :)

Coordinator
May 16, 2012 at 8:04 PM

Unit tests and documentation are also written, thanks for this code!