Skip to content

Commit 3bf9a1a

Browse files
committed
Add Mediator Implementation
1 parent c17743a commit 3bf9a1a

13 files changed

+187
-0
lines changed

Mediator/EmailMessage.cs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
public record EmailMessage(string Subject);

Mediator/Handlers.cs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
public class EmailSender : INotificationHandler<EmailMessage>
2+
{
3+
public void Handle(EmailMessage notification)
4+
{
5+
Console.WriteLine($"Inside {nameof(EmailSender)}.Handle()...");
6+
Console.WriteLine($"Sending email with subject {notification.Subject}...");
7+
}
8+
}
9+
10+
public class EmailArchiver : INotificationHandler<EmailMessage>
11+
{
12+
public void Handle(EmailMessage notification)
13+
{
14+
Console.WriteLine($"Inside {nameof(EmailArchiver)}.Handle()...");
15+
Console.WriteLine($"Archiving email with subject {notification.Subject}...");
16+
}
17+
}

Mediator/IMediator.cs

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
public interface IMediator
2+
{
3+
void Send<TMessage>(TMessage message);
4+
}

Mediator/INotificationHandler.cs

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
public interface INotificationHandler<T>
2+
{
3+
void Handle(T notification);
4+
}

Mediator/Mediator.cs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
public class Mediator : IMediator
2+
{
3+
private readonly NotificationHandlerRegistry handlerRegistry;
4+
5+
public Mediator(NotificationHandlerRegistry handlerRegistry)
6+
{
7+
this.handlerRegistry = handlerRegistry;
8+
}
9+
10+
public void Send<TMessage>(TMessage message)
11+
{
12+
if (!handlerRegistry.HasHandler<TMessage>())
13+
{
14+
throw new InvalidOperationException($"No handler registered for message type {typeof(TMessage).Name}");
15+
}
16+
var handlers = handlerRegistry.GetHandlers<TMessage>();
17+
foreach (var handler in handlers)
18+
{
19+
handler.Handle(message);
20+
}
21+
}
22+
}

Mediator/Mediator.csproj

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net7.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
12+
</ItemGroup>
13+
14+
</Project>

Mediator/Mediator.sln

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mediator", "Mediator.csproj", "{03CCC487-C218-4D95-A874-0CA42AE2494D}"
4+
EndProject
5+
Global
6+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7+
Debug|Any CPU = Debug|Any CPU
8+
Release|Any CPU = Release|Any CPU
9+
EndGlobalSection
10+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
11+
{03CCC487-C218-4D95-A874-0CA42AE2494D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
12+
{03CCC487-C218-4D95-A874-0CA42AE2494D}.Debug|Any CPU.Build.0 = Debug|Any CPU
13+
{03CCC487-C218-4D95-A874-0CA42AE2494D}.Release|Any CPU.ActiveCfg = Release|Any CPU
14+
{03CCC487-C218-4D95-A874-0CA42AE2494D}.Release|Any CPU.Build.0 = Release|Any CPU
15+
EndGlobalSection
16+
EndGlobal

Mediator/MediatorExtensions.cs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.Reflection;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
public static class MediatorExtensions
5+
{
6+
public static IServiceCollection AddMediator(this IServiceCollection services, params Assembly[] assemblies)
7+
{
8+
// We allow multiple assemblies to scan - if the user doesn't add anything, we assume the current one
9+
// where we are looking for INotificationHandler<T>
10+
if (assemblies.Length == 0)
11+
{
12+
assemblies = new[] { Assembly.GetExecutingAssembly() };
13+
}
14+
15+
services.AddSingleton<IMediator, Mediator>();
16+
17+
// Register all handler types found in the specified assemblies.
18+
foreach (var assembly in assemblies)
19+
{
20+
var handlerTypes = assembly.ExportedTypes
21+
.Where(x => x.GetInterfaces().Any(y => y.IsGenericType && y.GetGenericTypeDefinition() == typeof(INotificationHandler<>)))
22+
.ToList();
23+
24+
foreach (var handlerType in handlerTypes)
25+
{
26+
services.AddTransient(handlerType);
27+
}
28+
}
29+
30+
// Build the handler registry using the registered handlers.
31+
services.AddSingleton<NotificationHandlerRegistry>(provider =>
32+
{
33+
var registry = new NotificationHandlerRegistry();
34+
35+
foreach (var service in services)
36+
{
37+
if (service.ServiceType.GetInterfaces().Any(y => y.IsGenericType && y.GetGenericTypeDefinition() == typeof(INotificationHandler<>)))
38+
{
39+
// Get the INotificationHandler<T> instances from the container
40+
var handler = provider.GetServices(service.ServiceType);
41+
foreach (var h in handler.Where(s => s is not null))
42+
{
43+
var handlerInterface = h!.GetType().GetInterfaces().First();
44+
var messageType = handlerInterface.GetGenericArguments().First();
45+
typeof(NotificationHandlerRegistry)
46+
.GetMethod("AddHandler")!
47+
.MakeGenericMethod(messageType)
48+
.Invoke(registry, new[] { h });
49+
}
50+
}
51+
}
52+
53+
return registry;
54+
});
55+
56+
return services;
57+
}
58+
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
public class NotificationHandlerRegistry
2+
{
3+
// The handler has to hold objects as key, as we don't know the type of the message yet
4+
private readonly Dictionary<Type, object> handlers = new();
5+
6+
public void AddHandler<T>(INotificationHandler<T> handler)
7+
{
8+
var messageType = typeof(T);
9+
10+
if (!handlers.ContainsKey(messageType))
11+
{
12+
handlers[messageType] = new List<INotificationHandler<T>>();
13+
}
14+
15+
((List<INotificationHandler<T>>)handlers[messageType]).Add(handler);
16+
}
17+
18+
public bool HasHandler<T>() => handlers.ContainsKey(typeof(T));
19+
20+
public IReadOnlyCollection<INotificationHandler<T>> GetHandlers<T>()
21+
=> handlers.TryGetValue(typeof(T), out var list) ? (List<INotificationHandler<T>>)list : Array.Empty<INotificationHandler<T>>();
22+
}

Mediator/ProducerService.cs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
public class ProducerService
2+
{
3+
private readonly IMediator _mediator;
4+
5+
public ProducerService(IMediator mediator)
6+
{
7+
_mediator = mediator;
8+
}
9+
10+
public void ProduceEmail(string subject)
11+
{
12+
_mediator.Send(new EmailMessage(subject));
13+
}
14+
}

Mediator/Program.cs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
3+
var services = new ServiceCollection()
4+
.AddMediator()
5+
.AddScoped<ProducerService>()
6+
.BuildServiceProvider();
7+
8+
var producer = services.GetRequiredService<ProducerService>();
9+
producer.ProduceEmail("Hello World");

Mediator/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Create your own Mediator (like Mediatr)
2+
3+
In this blog post, I'll show you the fundamentals of the Mediator pattern and how to implement it in your application from scratch. And yes, we basically implement the famous MediatR library.
4+
5+
Found [here](https://steven-giesel.com/blogPost/dc27bbf9-55c7-4f11-bec5-cf1704df3a21)

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Contains all of my examples from various blog posts. You can find a comprehensiv
44

55
| BlogPost | Publish Date |
66
| -------------------------------------------------------------------------------------------------------------- | ------------ |
7+
| [Create your own Mediator (like Mediatr)](Mediator/) | 21.06.2023 |
78
| [Verifying your DI Container](ServiceCollectionVerify/) | 30.04.2023 |
89
| [.NET 8 Performance Edition](BenchmarkDotNet8/) | 12.04.2023 |
910
| [Creating a ToolTip Component in Blazor](BlazorToolTip/) | 31.03.2023 |

0 commit comments

Comments
 (0)