Browse Source

MediatR Guide + sample (#2218)

* Add guide for MediatR

* Add sample for MediatR

* Fix exposed token in program.cs

* Fix review points

* Remove newline in MediatrDiscordEventListener.cs
tags/3.5.0
Duke GitHub 3 years ago
parent
commit
53ab9f3b16
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 354 additions and 0 deletions
  1. BIN
      docs/guides/other_libs/images/mediatr_output.png
  2. +70
    -0
      docs/guides/other_libs/mediatr.md
  3. +1
    -0
      docs/guides/other_libs/samples/MediatrConfiguringDI.cs
  4. +16
    -0
      docs/guides/other_libs/samples/MediatrCreatingMessageNotification.cs
  5. +46
    -0
      docs/guides/other_libs/samples/MediatrDiscordEventListener.cs
  6. +17
    -0
      docs/guides/other_libs/samples/MediatrMessageReceivedHandler.cs
  7. +4
    -0
      docs/guides/other_libs/samples/MediatrStartListener.cs
  8. +2
    -0
      docs/guides/toc.yml
  9. +16
    -0
      samples/MediatRSample/MediatRSample.sln
  10. +48
    -0
      samples/MediatRSample/MediatRSample/DiscordEventListener.cs
  11. +14
    -0
      samples/MediatRSample/MediatRSample/Handlers/MessageReceivedHandler.cs
  12. +20
    -0
      samples/MediatRSample/MediatRSample/MediatRSample.csproj
  13. +14
    -0
      samples/MediatRSample/MediatRSample/Notifications/MessageReceivedNotification.cs
  14. +13
    -0
      samples/MediatRSample/MediatRSample/Notifications/ReadyNotification.cs
  15. +73
    -0
      samples/MediatRSample/MediatRSample/Program.cs

BIN
docs/guides/other_libs/images/mediatr_output.png View File

Before After
Width: 428  |  Height: 166  |  Size: 24 KiB

+ 70
- 0
docs/guides/other_libs/mediatr.md View File

@@ -0,0 +1,70 @@
---
uid: Guides.OtherLibs.MediatR
title: MediatR
---

# Configuring MediatR

## Prerequisites

- A simple bot with dependency injection configured

## Downloading the required packages

You can install the following packages through your IDE or go to the NuGet link to grab the dotnet cli command.

|Name|Link|
|--|--|
| `MediatR` | [link](https://www.nuget.org/packages/MediatR) |
| `MediatR.Extensions.Microsoft.DependencyInjection` | [link](https://www.nuget.org/packages/MediatR.Extensions.Microsoft.DependencyInjection)|

## Adding MediatR to your dependency injection container

Adding MediatR to your dependency injection is made easy by the `MediatR.Extensions.Microsoft.DependencyInjection` package. You can use the following piece of code to configure it. The parameter of `.AddMediatR()` can be any type that is inside of the assembly you will have your event handlers in.

[!code-csharp[Configuring MediatR](samples/MediatrConfiguringDI.cs)]

## Creating notifications

The way MediatR publishes events throughout your applications is through notifications and notification handlers. For this guide we will create a notification to handle the `MessageReceived` event on the `DiscordSocketClient`.

[!code-csharp[Creating a notification](samples/MediatrCreatingMessageNotification.cs)]

## Creating the notification publisher / event listener

For MediatR to actually publish the events we need a way to listen for them. We will create a class to listen for discord events like so:

[!code-csharp[Creating an event listener](samples/MediatrDiscordEventListener.cs)]

The code above does a couple of things. First it receives the DiscordSocketClient from the dependency injection container. It can then use this client to register events. In this guide we will be focusing on the MessageReceived event. You register the event like any ordinary event, but inside of the handler method we will use MediatR to publish our event to all of our notification handlers.

## Adding the event listener to your dependency injection container

To start the listener we have to call the `StartAsync()` method on our `DiscordEventListener` class from inside of our main function. To do this, first register the `DiscordEventListener` class in your dependency injection container and get a reference to it in your main method.

[!code-csharp[Starting the event listener](samples/MediatrStartListener.cs)]

## Creating your notification handler

MediatR publishes notifications to all of your notification handlers that are listening for a specific notification. We will create a handler for our newly created `MessageReceivedNotification` like this:

[!code-csharp[Creating an event listener](samples/MediatrMessageReceivedHandler.cs)]

The code above implements the `INotificationHandler<>` interface provided by MediatR, this tells MediatR to dispatch `MessageReceivedNotification` notifications to this handler class.

> [!NOTE]
> You can create as many notification handlers for the same notification as you desire. That's the beauty of MediatR!

## Testing

To test if we have successfully implemented MediatR, we can start up the bot and send a message to a server the bot is in. It should print out the message we defined earlier in our `MessageReceivedHandler`.

![MediatR output](images/mediatr_output.png)

## Adding more event types

To add more event types you can follow these steps:

1. Create a new notification class for the event. it should contain all of the parameters that the event would send. (Ex: the `MessageReceived` event takes one `SocketMessage` as an argument. The notification class should also map this argument)
2. Register the event in your `DiscordEventListener` class.
3. Create a notification handler for your new notification.

+ 1
- 0
docs/guides/other_libs/samples/MediatrConfiguringDI.cs View File

@@ -0,0 +1 @@
.AddMediatR(typeof(Bot))

+ 16
- 0
docs/guides/other_libs/samples/MediatrCreatingMessageNotification.cs View File

@@ -0,0 +1,16 @@
// MessageReceivedNotification.cs

using Discord.WebSocket;
using MediatR;

namespace MediatRSample.Notifications;

public class MessageReceivedNotification : INotification
{
public MessageReceivedNotification(SocketMessage message)
{
Message = message ?? throw new ArgumentNullException(nameof(message));
}

public SocketMessage Message { get; }
}

+ 46
- 0
docs/guides/other_libs/samples/MediatrDiscordEventListener.cs View File

@@ -0,0 +1,46 @@
// DiscordEventListener.cs

using Discord.WebSocket;
using MediatR;
using MediatRSample.Notifications;
using Microsoft.Extensions.DependencyInjection;
using System.Threading;
using System.Threading.Tasks;

namespace MediatRSample;

public class DiscordEventListener
{
private readonly CancellationToken _cancellationToken;

private readonly DiscordSocketClient _client;
private readonly IServiceScopeFactory _serviceScope;

public DiscordEventListener(DiscordSocketClient client, IServiceScopeFactory serviceScope)
{
_client = client;
_serviceScope = serviceScope;
_cancellationToken = new CancellationTokenSource().Token;
}

private IMediator Mediator
{
get
{
var scope = _serviceScope.CreateScope();
return scope.ServiceProvider.GetRequiredService<IMediator>();
}
}

public async Task StartAsync()
{
_client.MessageReceived += OnMessageReceivedAsync;

await Task.CompletedTask;
}

private Task OnMessageReceivedAsync(SocketMessage arg)
{
return Mediator.Publish(new MessageReceivedNotification(arg), _cancellationToken);
}
}

+ 17
- 0
docs/guides/other_libs/samples/MediatrMessageReceivedHandler.cs View File

@@ -0,0 +1,17 @@
// MessageReceivedHandler.cs

using System;
using MediatR;
using MediatRSample.Notifications;

namespace MediatRSample;

public class MessageReceivedHandler : INotificationHandler<MessageReceivedNotification>
{
public async Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken)
{
Console.WriteLine($"MediatR works! (Received a message by {notification.Message.Author.Username})");

// Your implementation
}
}

+ 4
- 0
docs/guides/other_libs/samples/MediatrStartListener.cs View File

@@ -0,0 +1,4 @@
// Program.cs

var listener = services.GetRequiredService<DiscordEventListener>();
await listener.StartAsync();

+ 2
- 0
docs/guides/toc.yml View File

@@ -115,6 +115,8 @@
topicUid: Guides.OtherLibs.Serilog
- name: EFCore
topicUid: Guides.OtherLibs.EFCore
- name: MediatR
topicUid: Guides.OtherLibs.MediatR
- name: Emoji
topicUid: Guides.Emoji
- name: Voice


+ 16
- 0
samples/MediatRSample/MediatRSample.sln View File

@@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediatRSample", "MediatRSample\MediatRSample.csproj", "{CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CE066EE5-7ED1-42A0-8DB2-862D44F40EA7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

+ 48
- 0
samples/MediatRSample/MediatRSample/DiscordEventListener.cs View File

@@ -0,0 +1,48 @@
using Discord.WebSocket;
using MediatR;
using MediatRSample.Notifications;
using Microsoft.Extensions.DependencyInjection;

namespace MediatRSample;

public class DiscordEventListener
{
private readonly CancellationToken _cancellationToken;

private readonly DiscordSocketClient _client;
private readonly IServiceScopeFactory _serviceScope;

public DiscordEventListener(DiscordSocketClient client, IServiceScopeFactory serviceScope)
{
_client = client;
_serviceScope = serviceScope;
_cancellationToken = new CancellationTokenSource().Token;
}

private IMediator Mediator
{
get
{
var scope = _serviceScope.CreateScope();
return scope.ServiceProvider.GetRequiredService<IMediator>();
}
}

public Task StartAsync()
{
_client.Ready += OnReadyAsync;
_client.MessageReceived += OnMessageReceivedAsync;

return Task.CompletedTask;
}

private Task OnMessageReceivedAsync(SocketMessage arg)
{
return Mediator.Publish(new MessageReceivedNotification(arg), _cancellationToken);
}
private Task OnReadyAsync()
{
return Mediator.Publish(ReadyNotification.Default, _cancellationToken);
}
}

+ 14
- 0
samples/MediatRSample/MediatRSample/Handlers/MessageReceivedHandler.cs View File

@@ -0,0 +1,14 @@
using MediatR;
using MediatRSample.Notifications;

namespace MediatRSample.Handlers;

public class MessageReceivedHandler : INotificationHandler<MessageReceivedNotification>
{
public async Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken)
{
Console.WriteLine($"MediatR works! (Received a message by {notification.Message.Author.Username})");
// Your implementation
}
}

+ 20
- 0
samples/MediatRSample/MediatRSample/MediatRSample.csproj View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Discord.Net" Version="3.4.1" />
<PackageReference Include="MediatR" Version="10.0.1" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
</ItemGroup>

</Project>

+ 14
- 0
samples/MediatRSample/MediatRSample/Notifications/MessageReceivedNotification.cs View File

@@ -0,0 +1,14 @@
using Discord.WebSocket;
using MediatR;

namespace MediatRSample.Notifications;

public class MessageReceivedNotification : INotification
{
public MessageReceivedNotification(SocketMessage message)
{
Message = message ?? throw new ArgumentNullException(nameof(message));
}

public SocketMessage Message { get; }
}

+ 13
- 0
samples/MediatRSample/MediatRSample/Notifications/ReadyNotification.cs View File

@@ -0,0 +1,13 @@
using MediatR;

namespace MediatRSample.Notifications;

public class ReadyNotification : INotification
{
public static readonly ReadyNotification Default
= new();

private ReadyNotification()
{
}
}

+ 73
- 0
samples/MediatRSample/MediatRSample/Program.cs View File

@@ -0,0 +1,73 @@
using Discord;
using Discord.Interactions;
using Discord.WebSocket;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Serilog.Events;

namespace MediatRSample;

public class Bot
{
private static ServiceProvider ConfigureServices()
{
return new ServiceCollection()
.AddMediatR(typeof(Bot))
.AddSingleton(new DiscordSocketClient(new DiscordSocketConfig
{
AlwaysDownloadUsers = true,
MessageCacheSize = 100,
GatewayIntents = GatewayIntents.AllUnprivileged,
LogLevel = LogSeverity.Info
}))
.AddSingleton<DiscordEventListener>()
.AddSingleton(x => new InteractionService(x.GetRequiredService<DiscordSocketClient>()))
.BuildServiceProvider();
}

public static async Task Main()
{
await new Bot().RunAsync();
}

private async Task RunAsync()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();

await using var services = ConfigureServices();

var client = services.GetRequiredService<DiscordSocketClient>();
client.Log += LogAsync;

var listener = services.GetRequiredService<DiscordEventListener>();
await listener.StartAsync();

await client.LoginAsync(TokenType.Bot, "YOUR_TOKEN_HERE");
await client.StartAsync();

await Task.Delay(Timeout.Infinite);
}

private static Task LogAsync(LogMessage message)
{
var severity = message.Severity switch
{
LogSeverity.Critical => LogEventLevel.Fatal,
LogSeverity.Error => LogEventLevel.Error,
LogSeverity.Warning => LogEventLevel.Warning,
LogSeverity.Info => LogEventLevel.Information,
LogSeverity.Verbose => LogEventLevel.Verbose,
LogSeverity.Debug => LogEventLevel.Debug,
_ => LogEventLevel.Information
};

Log.Write(severity, message.Exception, "[{Source}] {Message}", message.Source, message.Message);

return Task.CompletedTask;
}
}

Loading…
Cancel
Save