Skip to content

Mediator v1.0

What is it?

Shiny Mediator is something new I’ve been working on. I love Jimmy Bogard’s MediatR library on the server, but I just couldn’t get it to fit the way I wanted for Apps… especially Blazor & .NET MAUI apps.

What is a mediator? It’s a small in-process version of a message bus. MAUI has MessagingCenter, the Community toolkit has the weak message center, and Prism offers an event aggregator. They’re all great, but they lack in areas that I want “more”.

  • If an event errors, the whole chain dies in the publish
  • Events are fired in a foreach (mostly)
  • You have to tie into them and unsubscribe from them or you can leak memory
  • They don’t provide the concept of command or request/response models (a command can only be responded to by a single handler)
  • They don’t provide any sort of middleware (pre & post handling)

Sure - they aren’t geared for these things, but the question is “what is”?

Some might say “this is overengineering” or “too complex”. I would counter that comment by saying that this can actually simplify your architecture overall by removing a lot of complex plumbing around services, references, and navigation.

Let’s go over some of the problems that we use mediator to solve within our apps

What Does It Solve

We believe that Shiny Mediator is the answer to these problems. It’s a simple, yet powerful library that allows you to create a mediator in your app

Problem #1 - Service & Reference Hell

Does this look familiar to you?

public class MyViewModel(
IConnectivity conn,
IDataService data,
IAuthService auth,
IDialogsService dialogs,
ILogger<MyViewModel> logger
) {
// ...
try {
if (conn.IsConnected)
{
var myData = await data.GetDataRequest();
}
else {
dialogs.Show("No Connection");
// cache?
}
}
catch (Exception ex) {
dialogs.Show(ex.Message);
logger.LogError(ex);
}
}

With a bit of our middleware and some events, you can get here:

public class MyViewModel(IMediator mediator) : IEventHandler<ConnectivityChangedEvent>, IEventHandler<AuthChangedEvent> {
// ...
var myData = await mediator.Request(new GetDataRequest());
// logging, exception handling, offline caching can all be bundle into one nice clean call without the need for coupling
}

Problem #2 - Messages EVERYWHERE (+ Leaks)

Do you use the MessagingCenter in Xamarin.Forms? It’s a great tool, but it can lead to some memory leaks if you’re not careful. It also doesn’t have a pipeline, so any errors in any of the responders will crash the entire chain. It doesn’t have a request/response style setup (not that it was meant for it), but this means you still require other services.

public class MyViewModel
{
public MyViewModel()
{
MessagingCenter.Subscribe<SomeEvent1>(this, @event => {
// do something
});
MessagingCenter.Subscribe<SomeEvent2>(this, @event => {
// do something
});
MessagingCenter.Send(new SomeEvent1());
MessagingCenter.Send(new SomeEvent2());
// and don't forget to unsubscribe
MessagingCenter.Unsubscribe<SomeEvent1>(this);
MessagingCenter.Unsubscribe<SomeEvent2>(this);
}
}

Let’s take a look at our mediator in action for this scenarios

public class MyViewModel : IEventHandler<SomeEvent1>, IEventHandler<SomeEvent2>
{
public MyViewModel(IMediator mediator)
{
// no need to unsubscribe
mediator.Publish(new SomeEvent1());
mediator.Publish(new SomeEvent2());
}
}

Problem #3 - Strongly Typed Navigation with Strongly Typed Arguments

Our amazing friends over in Prism offer the “best in class” MVVM framework. We’ll them upsell you beyond that, but one of their amazing features is ‘Modules’. Modules help break up your navigation registration, services, etc.

What they don’t solve is providing a strongly typed nature for this stuff (not their job though). We think we can help addon to their beautiful solution.

A normal call to a navigation service might look like this:

_navigationService.NavigateAsync("MyPage", new NavigationParameters { { "MyArg", "MyValue" } });

This is great. It works, but I don’t know the type OR argument requirements of “MyPage” without going to look it up. In a small project with a small dev team, this is fine. In a large project with a large dev team, this can be difficult.

Through our Shiny.Framework library we offer a GlobalNavigationService that can be used to navigate to any page in your app from anywhere, however, for the nature of this example, we’ll pass our navigation service FROM our viewmodel through the mediator request to ensure proper scope.

public record MyPageNavigatonRequest(INavigationService navigator, string MyArg) : IRequest;
public class MyPageNavigationHandler : IRequestHandler<MyPageNavigatonRequest>
{
public async Task Handle(MyPageNavigatonRequest request, CancellationToken cancellationToken)
{
await request.navigator.NavigateAsync("MyPage", new NavigationParameters { { "MyArg", request.MyArg } });
}
}

Now, in your viewmodel, you can do this:

public class MyViewModel
{
public MyViewModel(IMediator mediator)
{
mediator.Request(new MyPageNavigatonRequest(_navigationService, "MyValue"));
}
}

Strongly typed. No page required page knowledge from the module upfront. The other dev team of the module can define HOW things work.

Closing

Lastly, we offer some absolutely epic middleware that you can use to do things like logging, caching, error handling, etc.
Check out our middleware documentation for more information.

Check out our overall documentation for more information on how to use the mediator in your app. We think you’ll love it!