
MediatR
MediatR is one of the most popular libraries in the .NET environment and describes itself as “Simple, unambitious mediator implementation in .NET”. In the end, MediatR is a perfect library for in-process message handling in terms of the Mediator pattern .
Azure Functions
Azure Functions is a great, lean technology, but does not have Dependency Injection on board by default. In the meantime, however, thanks to FunctionsStartup it is possible to use this anyway.
Commands and Command Handlers
MediatR works according to the mediator pattern with the separation of the respective responsibilities; e.g. the separation into commands and command handlers. This allows maximum independence to be achieved.
In code form, for example, it looks like this:
First, we have a command that’s supposed to do something. For example, we simply send something by mail.
1public class EMailSendCommand : IRequest<bool>
2{
3 public string ToAddress { get; }
4 public string ToUsername { get; }
5 public IMailTemplate MailTemplate { get; }
6
7 public EMailSendCommand(string toAddress, string toUsername, IMailTemplate mailTemplate)
8 {
9 ToAddress = toAddress;
10 ToUsername = toUsername;
11 MailTemplate = mailTemplate;
12 }
13}
The description of the command (in MediatR language a “Request”) is that we now have certain parameters that are necessary to send the mail and the return of the command is a bool. Everybody who wants to send an e-mail needs only this command. The exact knowledge of how or with what the mail is sent is not necessary.
The counterpart to the Request is the RequestHandler: this is where the implementation takes place, which is to be executed and, if necessary, returned when a corresponding event occurs.
1public class EMailSendCommandHandler : IRequestHandler<EMailSendCommand, bool>
2{
3 private readonly IMailProvider _mailProvider;
4
5 public DTSensorTemperatureRangeValidationNotificationHandler(IMailProvider mailProvider)
6 {
7 _mailProvider = mailProvider;
8 }
9
10 public async Task Handle(EMailSendCommand request, CancellationToken cancellationToken)
11 {
12 var address = request.ToAddress;
13
14 // some code..
15 await _mailProvider.SendAsync(.. mail parameters..);
16
17 return true;
18 }
19}
MediatR and Azure Functions
In itself, Azure Functions are already a certain separation of responsibilities: Triggers decouple the exact triggering and the function itself is only the event handler. Nevertheless, it is quite practical to keep the Function very slim with the help of MediatR, so that code can be implemented independently of its runtime (functions, ASP…)
What is therefore necessary in the configuration of the FunctionStartup is the configuration of MediatR. MediatR brings the Dependency Injection capabilities in a dedicated NuGet package: MediatR.Extensions.Microsoft.DependencyInjection .
1[assembly: FunctionsStartup(typeof(Startup))]
2namespace FunctionsApp
3{
4 public class Startup : FunctionsStartup
5 {
6 public override void Configure(IFunctionsHostBuilder builder)
7 {
8 if (builder != null)
9 {
10 // ... your config code here...
11
12 // Analytics
13 builder.Services.Configure<AnalyticsOptions>(o =>
14 {
15 o.Url = Environment.GetEnvironmentVariable("ANALYTICS_SERVICE_API");
16 });
17 builder.Services.AddScoped<IAnalyticsService, NoiseAnalyticsService>();
18
19 // Mail
20 builder.Services.AddScoped<IMailProvider, Office365MailProvider>();
21
22 // ... your config code here...
23
24 // MediatR
25 builder.Services.AddMediatR();
26 // or builder.Services.AddMediatR(typeof(TypeOfYourExternalAssembylHere));
27 }
28 else
29 {
30 throw new ArgumentNullException(nameof(builder));
31 }
32 }
33 }
34}
Due to the basic idea of separating abstract descriptions (the Requests and the Notifications in MediatR), the real implementation is mostly that commands are located in different namespaces or even projects than the corresponding handlers. Therefore MediatR offers the possibility to search the handlers in different assemblies. However, this must be defined manually.
1[FunctionName("sensor-message-processor")]
2public async Task Run(
3 [EventHubTrigger("events", Connection = "AZURE_EVENT_HUB_CONNECTIONSTRING")] EventData[] eventData,
4 ILogger log)
5{
6 // we support multiple messages here
7 foreach (EventData eventMessage in eventData)
8 {
9 DateTimeOffset receivedOn = eventMessage.SystemProperties.EnqueuedTimeUtc;
10
11 // we try to serialize the event data
12 SensorEvent sensorEvent;
13 try
14 {
15 string requestBody = Encoding.UTF8.GetString(eventMessage.Body);
16 sensorEvent = JsonConvert.DeserializeObject<SensorEvent>(requestBody);
17 }
18 catch (Exception e)
19 {
20 log.LogError(e, $"Failed to read event data from hub message.");
21 return;
22 }
23
24 if (sensorEvent is null)
25 {
26 log.LogError($"No event data found in hub message.");
27 return;
28 }
29
30 log.LogInformation("Received {@SensorEvent} from Event Hub", sensorEvent);
31
32 try
33 {
34 // now use mediatr and your handler to run handle the message
35 await _mediator.Publish(new ProcessSensorEventMessageCommand(sensorEvent, receivedOn)).ConfigureAwait(false);
36 }
37 catch (Exception e)
38 {
39 log.LogError(e, $"Failed to handle event data: {e.Message}");
40 throw;
41 }
42 }
43}
Conclusion
Event Processing is an integral part of modern software development and MediatR in this sense an important part of the .NET ecosystem. Also in Azure Functions, MediatR has its authority to ensure that our functions also remain clean and code can be reused in a lean manner.

Comments