Skip to content

Matt90hz/ObjectsMessenger

Repository files navigation

ObjectsMessenger

Utility library to provide objects communication.

What it should do?

  • Allow communication between objects.
  • Enforce the existence of a message channel.
  • Ensure that only senders can send messages, and only receivers can receive them.
  • Support dependency injection.
  • Support asyncronous communication.
  • Define communication in a self-contained object.
  • Expose events related to the communication process.
  • Optionally deliver messages to anyone.
  • Not bind the messaging process to properties.
  • Provide an option to persist or delete messages after delivery.
  • Not cause memory leaks due to sender storage.
  • Only interfaces should be used to interact with the system.
  • Include a utility for dealing with property accessors.
  • Not rely on exceptions.
  • Rely on observables instead of events.

How it is done?

Implement Messenger<TSender, TReceiver, TMessage> to create a single-channel communication between two objects. Otherwise, implement Messenger<TSender, TReceiver> to establish multichannel communication, where one object can dispatch messages to anyone.

Optionally, register the messengers in the MessengerHub to handle all messenger events and errors.

Implement a single channel messenger

Given a sender class like:

public sealed class UsersViewModel
{
    private User? _currentUser;

    public User? CurrentUser
    {
        get => _currentUser;
        set => _currentUser = value;
    }

    public IEnumerable<User> Users { get; }

    public UsersViewModel(...) {...}
}

And a receiver class like:

public sealed class EditUserViewModel
{
    public User? User { get; set; }
}

Let's assume that you want to deliver the current user from UsersViewModel to EditUserViewModel. Then, implement a single-channel messenger like:

public sealed class CurrentUserMessenger : Messenger<UsersViewModel, EditUserViewModel, User?>
{
    public override bool IsMessagePreserved => false;

    protected override void ReceiveMessage(EditUserViewModel receiver, User? message) => receiver.User = message;

    protected override User? SendMessage(UsersViewModel sender) => sender.CurrentUser; 
}

Refactor UsersViewModel and EditUserViewModel to send and receive the message as appropriate. What follows is just an example of how the communication process might be arranged.

public sealed class UsersViewModel
{
    private readonly CurrentUserMessenger _currentUserMessenger;

    private User? _currentUser;

    public User? CurrentUser
    {
        get => _currentUser;
        set
        {
            _currentUser = value;
            _currentUserMessenger.Send(this);
        }
    }

    public IEnumerable<User> Users { get; }

    public UsersViewModel(...) {...}
}

public sealed class EditUserViewModel
{
    public User? User { get; set; }

    public EditUserViewModel(CurrentUserMessenger currentUserMessenger)
    {
        currentUserMessenger.Receive(this);
    }
}

Implement a multichannel messenger

Implementing a multi-channel Messenger does not differ much from the single channel. First, implement Messenger<TSender, TMessage>.

public sealed class CurrentUSerPublisher : Messenger<UsersViewModel, User?>
{
    public override User? Default => default;

    protected override User? SendMessage(UsersViewModel sender) => sender.CurrentUser;
}

The sending process remains the same, whereas the receiving process lets you get the message directly. Like so:

public sealed class EditUserViewModel
{
    public User? User { get; set; }

    public EditUserViewModel(CurrentUserMessenger currentUserMessenger)
    {
        User = currentUserMessenger.Receive();
    }
}

Register the messenger

It is possible to register any Messenger into the MessengerHub. This will allow handling the messenger's events in a single place.

MessengerHub.Default.RegisterMessenger(currentUserMessenger);

Doing this will allow the creation of behaviors. For example, the EditUserViewModel from before can be refactored to better separate concerns.

public sealed class ReceiveCurrentUserBehavior
{
    public ReceiveCurrentUserBehavior(CurrentUserMessenger currentUserMessenger, EditUserViewModel editUserViewModel)
    {
        currentUserMessenger.Events
            .Where(@event => @event is MessengerEvent.Sended)           
            .Subscribe(_ => currentUserMessenger.Receive(editUserViewModel)); 
    }
}

Notes

The use of IObservable instead of CLR events makes the system very flexible. For more information, check System.Reactive.

Utilities

Dependency injection

services.RegisterMessengers(Assembly.GetExecutingAssembly());

This call not only registers all Messenger instances in the given assembly, but also registers all the Messenger instances in the MessengerHub.

Property setter

The SetAndSend(...) function can be used to simplify the sending process for a property.

public sealed class UsersViewModel
{
    private readonly CurrentUserMessenger _currentUserMessenger;

    private User? _currentUser;

    public User? CurrentUser
    {
        get => _currentUser;
        set => _currentUserMessenger.SetAndSend(this, ref _currentUser, value);
    }

    public IEnumerable<User> Users { get; }

    public UsersViewModel(...) {...}
}

Contribute

Do you like this library, and do you want to make it better? Just open an issue on GitHub.

Releases

No releases published

Packages

No packages published

Languages