IncaTechnologies.ObjectsMessenger 1.0.0-beta

Prefix Reserved
This is a prerelease version of IncaTechnologies.ObjectsMessenger.
dotnet add package IncaTechnologies.ObjectsMessenger --version 1.0.0-beta                
NuGet\Install-Package IncaTechnologies.ObjectsMessenger -Version 1.0.0-beta                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="IncaTechnologies.ObjectsMessenger" Version="1.0.0-beta" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add IncaTechnologies.ObjectsMessenger --version 1.0.0-beta                
#r "nuget: IncaTechnologies.ObjectsMessenger, 1.0.0-beta"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install IncaTechnologies.ObjectsMessenger as a Cake Addin
#addin nuget:?package=IncaTechnologies.ObjectsMessenger&version=1.0.0-beta&prerelease

// Install IncaTechnologies.ObjectsMessenger as a Cake Tool
#tool nuget:?package=IncaTechnologies.ObjectsMessenger&version=1.0.0-beta&prerelease                

ObjectsMessenger

Utility class 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.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.0-beta 22 5/2/2024

1.0.0-beta : Release