Playground: MediatR
Tags: csharp, dotnet, mediatr, braziliandevs
Published: 2020-11-14 17:16:12

Olá!

Este é mais um post da seção Playground e, neste artigo, demonstraremos o uso de uma biblioteca muito conhecida pela comunidade .NET: o MediatR.

## Apresentando o MediatR

MediatR é uma biblioteca inspirada no [Mediator Pattern](https://en.wikipedia.org/wiki/Mediator_pattern) (em inglês), e atua como um sistema desacoplado de notificação. Ou seja, os objetos que o utilizam como meio de notificação desconhecem os consumidores da mensagem enviada, tendo conhecimento apenas do mediador.

Vamos entender um pouco sobre como o MediatR funciona com uma aplicação: a MediatR Drink Machine!

Para começar, você vai precisar de:

- Um editor ou IDE, como o [VS Code](https://code.visualstudio.com/download) ou o [Visual Studio](https://visualstudio.microsoft.com/downloads/) (versão 16.8+)
- Caso esteja utilizando o VS Code, [.Net SDK 5.0](https://dotnet.microsoft.com/download/dotnet/5.0) (o Visual Studio 16.8+ já o traz junto à sua instalação).
- Os pacotes [MediatR](https://www.nuget.org/packages/MediatR/) e [MediatR.Extensions.Microsoft.DependencyInjection](https://www.nuget.org/packages/MediatR.Extensions.Microsoft.DependencyInjection/)

## Construindo a aplicação

Nossa aplicação de exemplo será uma máquina de servir bebidas quentes, como aquelas que encontramos em escritórios ou lojas de conveniência. Ela vai oferecer apenas quatro tipos de bebiba (café, latte, cappuccino e chá), e apresentará um contador de doses restantes para cada bebiba. Como a intenção é demonstrar as formas mais básicas de uso do MediatR, tomamos o cuidado de deixar a aplicação o mais simples possível.

Para começar, vamos criar a infraestrutura da aplicação. Caso você opte pelo uso do Visual Studio, basta criar um projeto Asp.Net Core vazio. Caso esteja usando o VS Code, crie um projeto do tipo Web API e remova do projeto a pasta `Controllers` e o arquivo `WheaterForecast.cs`.

```
PS X:\code\playground-mediatr> dotnet new webapi -o Playground.MediatR.Cafe
```

## Criando nossas bebibas

Para a criação dos objetos que representam as bebidas disponíveis na máquina, vamos criar uma pasta chamada `Models` na raíz do projeto, um arquivo chamado `Drinks.cs` e inserir o seguinte conteúdo:

```c#
namespace Playground.MediatR.Cafe.Models
{
    public record Drink(string Name);

    public record Coffee : Drink { public Coffee() : base("Coffee") {} }
    public record Latte : Drink { public Latte() : base("Latte") {} }
    public record Cappuccino : Drink { public Cappuccino() : base("Cappuccino") {} }
    public record Tea : Drink { public Tea() : base("Tea") {} }
}
```

> **Novidade!** O C# 9, lançado oficialmente na semana de publicação deste artigo, oferece um novo tipo de objeto para representarmos elementos de nossa aplicação, os `records`. Um `record` nada mais é que uma classe, mas com algumas características interessantes: é imutável por padrão (ou seja, não pode ter suas propriedades alteradas depois de instanciado), oferece métodos de comparação baseados no valor de suas propriedades (assim como `structs`), e podem ser declarados de modo posicional (como no caso de `Drink`), o que significa que os parâmetros declarados no construtor serão traduzidos em propriedades. No caso de `Drink`, teríamos o seguinte:

```c#
var drink = new Drink("Refrigerante");
Console.Write(drink.Name); //Refrigerante
```

> Por ser uma classe, `record` oferece suporte a herança e, por isso, todas as nossas bebidas herdam de `Drink`, tendo a propriedade `Name` definida em seu construtor padrão.

## Criando os dispensers

Agora que temos nossas bebidas, precisamos armazená-las para poder servi-las. Para tanto, vamos criar os dispensers de nossa máquina.

Ainda na pasta `Models`, crie uma pasta chamada `Dispensers`, um arquivo chamado `AbstractDispenser.cs` e cole o seguinte conteúdo:

```c#
using System;

namespace Playground.MediatR.Cafe.Models
{
    public abstract class AbstractDispenser
    {
        public int AvailableDoses { get; private set; } = 10;

        public Drink Dispense()
        {
            if (AvailableDoses <= 0)
                throw new InvalidOperationException("There is no available doses to dispense.");

            AvailableDoses--;

            return DispenseCore();
        }

        protected abstract Drink DispenseCore();
    }
}
```

Assim como em uma máquina de verdade, nosso dispenser é apenas uma forma de armazenamento que desconhece a bebida que carrega e que, quando acionado, entrega uma dose. Por este motivo o criamos como uma classe abstrada. Assim, sua operação ganha a especificidade de servir cada bebida a partir de cada especialização.

Agora vamos conhecer um dispenser especializado. Ainda na pasta `Dispensers`, crie um arquivo chamado `CoffeeDispenser.cs` e cole o seguinte conteúdo:

```c#
namespace Playground.MediatR.Cafe.Models
{
    public class CoffeeDispenser : AbstractDispenser
    {
        protected override Drink DispenseCore() =>
            new Coffee();
    }
}
```

Agora temos um dispenser que deverá servir café!

> **Novidade!** Aqui temos mais uma novidade do C# 9, o *covariant return types*. Repare que o método sobrecarregado `DispenseCore` espera como retorno o tipo `Drink`. No entanto, retornamos o tipo `Coffee` que é extendido de `Drink`. O tipo de retorno covariante nos permite retornar não apenas o tipo declarado como retorno de um método, como também suas especializações. Desta forma é possível, por exemplo, dispensarmos uma interface `IDrink` como retorno do método abstrato, o que novamente nos faz economizar código!

Por brevidade, vou demonstrar apenas o dispenser de café. Todos os demais serão iguais, a exceção da bebida que servem. Portanto, ainda na pasta `Dispenser`, crie os seguintes arquivos: `LatteDispenser.cs`, `CappuccinoDispenser.cs`, e `TeaDispenser.cs`, cole o mesmo conteúdo de `CoffeeDispenser` alterando o nome da classe e o retorno do método `DispenseCore` de acordo com cada bebida.

Agora precisamos informar à máquina qual dispenser serve qual bebida, para que, ao apertarmos um botão escolhendo nossa bebida, a máquina saiba o que servir. Para isso, na pasta `Models`, crie o arquivo `MachineSlot.cs` e cole o seguinte conteúdo:

```c#
namespace Playground.MediatR.Cafe.Models
{
    public enum MachineSlot
    {
        Coffee = 1,
        Latte,
        Cappuccino,
        Tea
    }
}
```

Agora nossa máquina sabe que o primeiro botão servirá café, e que os demais servirão as demais bebidas em ordem.

## Acionando os dispensers

A partir de agora o MediatR entra em ação. Ele será responsável por expor os comandos disponíveis na máquina para acionar os dispensers e entregar as bebidas.

Crie, na pasta raíz do projeto, uma pasta chamada `MediatR`, em seguida um arquivo chamado `Requests.cs`, e cole o seguinte conteúdo:

```c#
using MediatR;
using Playground.MediatR.Cafe.Models;

namespace Playground.MediatR.Cafe.MediatR
{
    public record CoffeeRequest : IRequest;
    public record LatteRequest : IRequest;
    public record CappuccinoRequest : IRequest;
    public record TeaRequest : IRequest;

    public record AvailableDosesRequest : IRequest<AvailableDoses>;
}
```

Repare que temos novamente a presença dos *records*, desta vez implementando a interface de marcação `IRequest`. Por ser uma interface que não oferece métodos, records acabam sendo mais convenientes que classes comuns para representar nossos comandos (principalmente porque comandos devem ser imutáveis), e tem essa sintaxe mais simples, que economiza várias linhas de código.

Esta interface `IRequest` é uma interface do MediatR que indica que um tipo é uma requisição, uma das duas formas de notificação disponíveis na biblioteca. Essas requisições podem ser comandos, requisições que não retornam valor (o caso das primeiras quatro requisições), ou consultas, onde o valor esperado como retorno é `T` em `IRequest<T>`, como no caso de `AvailableDosesRequest`.

Sobre `AvailableDosesRequest`, não nos preocupemos com ele por enquanto, mas ele será o responsável por informar ao cliente quantas doses de cada bebida temos disponíveis ao ligar a máquina. Em breve retornaremos à ele!

Agora, vamos criar uma pasta em `MediatR` chamada `RequestHandlers`, onde teremos as classes responsáveis por responder aos comandos enviados pelos botões da máquina. Dentro desta nova pasta, crie o arquivo `DrinkRequestHandlerBase.cs`, com o seguinte conteúdo:

```c#
using MediatR;
using Playground.MediatR.Cafe.Models;
using System.Threading.Tasks;

namespace Playground.MediatR.Cafe.MediatR
{
    public class DrinkRequestHandlerBase<TDispenser>
        where TDispenser : AbstractDispenser
    {
        private readonly IMediator _mediator;
        private readonly TDispenser _dispenser;

        public DrinkRequestHandlerBase(IMediator mediator, TDispenser dispenser) =>
            (_mediator, _dispenser) = (mediator, dispenser);

        public Task<Unit> Handle()
        {
            var drink = _dispenser.Dispense();

            _mediator.Publish(new DrinkDispensed(drink, _dispenser.AvailableDoses));

            return Task.FromResult(Unit.Value);
        }
    }
}
```

Aqui vemos na prática o tipo de retorno covariante. Por conta desta funcionalidade, podemos receber uma especialização de `AbstractDispenser` e ativar seu método `Dispense` para trazer a bebida que precisamos. 

Além disso, podemos ver a injeção da interface `IMediatR`, que é a responsável por invocar as funcionalidades de notificação da biblioteca. Neste caso, a utilizamos para a publicação de notificações, a segunda forma de notificação da biblioteca.

A publicação de notificações aqui é usada como uma forma de publicar um evento, o que indica que uma bebida foi servida (indicando qual bebida e quantas doses da mesma restam no dispenser). Com isso, temos como manter o cliente informado sobre o estado da máquina. Veremos a implementação do tratamento deste evento mais adiante, quando tratarmos da interação com o cliente. Por ora, vamos concluir o processo de servir a bebida escolhida pelo cliente.

> **Importante!** Uma notificação de publicação tem uma relação 1:N com seus *handlers*. Ou seja, diversos objetos de uma aplicação podem reagir a este tipo de notificação, desacoplados de seu publicador, e esta funcionalidade corresponde à implementação do padrão Mediator.
>
> **Nota:** Repare que no método `Handle` temos como tipo de retorno `Unit`. Este é um tipo do MediatR utilizado para sinalizar a ausência de um tipo de retorno para o método. É esse tipo que diferencia comandos de consulta, e é usado pela biblioteca como uma representação de `void`.

Ainda na pasta `RequestHandlers`, crie o arquivo `CoffeeRequestHandler.cs` e cole o seguinte conteúdo:

```c#
using MediatR;
using Playground.MediatR.Cafe.Models;
using System.Threading;
using System.Threading.Tasks;

namespace Playground.MediatR.Cafe.MediatR
{
    public class CoffeeRequestHandler : DrinkRequestHandlerBase<CoffeeDispenser>,
                                        IRequestHandler<CoffeeRequest>
    {
        public CoffeeRequestHandler(IMediator mediator, CoffeeDispenser dispenser) : base(mediator, dispenser) { }

        Task<Unit> IRequestHandler<CoffeeRequest, Unit>.Handle(CoffeeRequest request, CancellationToken cancellationToken) =>
            Handle();
    }
}
```

Aqui informamos de fato ao MediatR que vamos tratar uma mensagem de requisição, por meio da implementação da interface `IRequestHandler<CoffeeRequest>`. Aqui estamos dizendo ao MediatR que, ao receber uma mensagem do tipo `CoffeeRequest`, este *handler* deverá ser acionado e a mensagem a ele encaminhada. Repare que nosso *handler* estende nosso *handler* abstrato, informando que o dispenser que será utilizado será o de café.

> **Importante!** Ao contrário da manipulação de notificações, no caso do atendimento a requisições existe uma relação 1:1 entre o tipo da requisição e seu respectivo *handler*. Ou seja, não é possível que dois ou mais *handlers* trate um mesmo tipo de mensagem. A razão para esta restrição é consistência. Não faria sentido ter uma consulta de um mesmo tipo com dois resultados, tampouco um mesmo comando executado mais de uma vez, e potencialmente de formas diferentes!

Mais uma vez por brevidade, vamos demonstrar apenas o *handler* responsável por servir café. Para implementar os demais, basta criar os arquivos `LatteRequestHandler.cs`, `CappuccinoRequestHandler.cs` e `TeaRequestHandler`, substituindo os dispensers e tipos de requisição de acordo com a bebida servida.

## Inicializando

Agora que vimos como servir nossas bebidas, vamos atender à requisição inicial da máquina quando ligada: a que busca a quantidade de doses disponíveis para cada bebida.

Ainda na pasta `RequestHandlers`, crie o arquivo `AvailableDosesRequestHandler.cs`, e cole o seguinte conteúdo:

```c#
using MediatR;
using Playground.MediatR.Cafe.Models;
using System.Threading;
using System.Threading.Tasks;

namespace Playground.MediatR.Cafe.MediatR
{
    public class AvailableDosesRequestHandler : IRequestHandler<AvailableDosesRequest, AvailableDoses>
    {
        private readonly CoffeeDispenser _coffeeDispenser;
        private readonly LatteDispenser _latteDispenser;
        private readonly CappuccinoDispenser _cappuccinoDispenser;
        private readonly TeaDispenser _teaDispenser;

        public AvailableDosesRequestHandler(CoffeeDispenser coffeeDispenser,
                                            LatteDispenser latteDispenser,
                                            CappuccinoDispenser cappuccinoDispenser,
                                            TeaDispenser teaDispenser)
        {
            _coffeeDispenser = coffeeDispenser;
            _latteDispenser = latteDispenser;
            _cappuccinoDispenser = cappuccinoDispenser;
            _teaDispenser = teaDispenser;
        }

        public Task<AvailableDoses> Handle(AvailableDosesRequest request, CancellationToken cancellationToken)
        {
            return Task.FromResult(new AvailableDoses(_coffeeDispenser.AvailableDoses,
                                                      _latteDispenser.AvailableDoses,
                                                      _cappuccinoDispenser.AvailableDoses,
                                                      _teaDispenser.AvailableDoses));
        }
    }
}
```

Repare que acima temos uma implementação de `IRequestHandler<AvailableDosesRequest, AvailableDoses>`. O tipo `AvailableDoses` aqui indica que este será o tipo de retorno de nossa consulta quando uma requisição do tipo `AvailableDosesRequest` for enviada.

Agora todas as nossas requisições podem ser atendidas. Vamos ver como faremos para receber as requisições dos clientes para que as sejam.

## Comunicando com o cliente

Para a comunicação como cliente, foi escolhido o uso do SignalR, biblioteca para comunicação em tempo real do Asp.Net Core. Caso não tenha familiaridade com a biblioteca, recomendo [um artigo, também da série Playground, onde a exploramos](https://dev.to/wsantosdev/playground-asp-net-core-signalr-5218).

Na pasta raíz do projeto, vamos criar uma pasta chamada `Hubs`, e criar o arquivo `IDrinkMachineHub.cs` com o seguinte conteúdo:

```c#
using Playground.MediatR.Cafe.Models;
using System.Threading.Tasks;

namespace Playground.MediatR.Cafe.Hubs
{
    public interface IDrinkMachineHub
    {
        Task Ready(AvailableDoses availableDoses);
        Task Serve(string drinkName, int remainingDoses);
    }
}
```

Uma das facilidades oferecidas pelo SignalR é a implementação automática de métodos de envio de mensagens. Ou seja, não precisaremos implementar esta interface! O próprio SignalR vai implementá-la para nós automaticamente.

Agora, vamos criar o `Hub` que receberá as requisições do cliente. Ainda na pasta `Hubs`, crie o arquivo `DrinkMachine.cs` com o seguinte conteúdo:

```c#
using MediatR;
using Microsoft.AspNetCore.SignalR;
using Playground.MediatR.Cafe.MediatR;
using Playground.MediatR.Cafe.Models;
using System;
using System.Threading.Tasks;

namespace Playground.MediatR.Cafe.Hubs
{
    public class DrinkMachineHub : Hub<IDrinkMachineHub>
    {
        private readonly IMediator _mediator;

        public DrinkMachineHub(IMediator mediator) =>
            _mediator = mediator;

        public async Task GetAvailableDoses() =>
            await Clients.All.Ready(await _mediator.Send(new AvailableDosesRequest()));

        public async Task RequestDrink(int machineSlot)
        {
            switch((MachineSlot)machineSlot)
            {
                case MachineSlot.Coffee:
                    await _mediator.Send(new CoffeeRequest());
                    break;
                case MachineSlot.Latte:
                    await _mediator.Send(new LatteRequest());
                    break;
                case MachineSlot.Cappuccino:
                    await _mediator.Send(new CappuccinoRequest());
                    break;
                case MachineSlot.Tea:
                    await _mediator.Send(new TeaRequest());
                    break;
                default:
                    throw new ArgumentException("Invalid option.");
            }
        }
    }
}
```

Aqui temos novamente a injeção da interface `IMediatR`, desta vez para encaminhar nossas requisições. Temos, também, dois métodos, sendo o primeiro `GetAvailableDoses` o método invocado na inicialização da máquina para informar a quantidade de doses restantes de cada bebida. E o segundo, `RequestDrink`, pelo qual o cliente solicita sua bebida.

> **Nota:** Repare que nos primeiro método invocamos um dos métodos declarados na interface `IDrinkMachineHub`, já contando com a implementação automática do SignalR para notificar os clientes conectados!

Agora que temos como nos comunicar com o cliente, vamos para a última parte da implementação de nosso *backend*, que servirá de fato a bebida e atualizará a quantidade de doses restantes após serví-la.

Na pasta `MediatR`, crie uma nova pasta chamada `EventHandlers`, e o arquivo `DrinkDispensedEventHandler.cs` com o seguinte conteúdo:

```c#
using MediatR;
using Microsoft.AspNetCore.SignalR;
using Playground.MediatR.Cafe.Hubs;
using Playground.MediatR.Cafe.Models;
using System.Threading;
using System.Threading.Tasks;

namespace Playground.MediatR.Cafe.MediatR
{
    public record DrinkDispensed(Drink Drink, int RemainingDoses) : INotification;

    public class DrinkDispensedEventHandler : INotificationHandler<DrinkDispensed>
    {
        private readonly IHubContext<DrinkMachineHub, IDrinkMachineHub> _hubContext;

        public DrinkDispensedEventHandler(IHubContext<DrinkMachineHub, IDrinkMachineHub> hubContext) =>
            _hubContext = hubContext;

        public async Task Handle(DrinkDispensed notification, CancellationToken cancellationToken) =>
            await _hubContext.Clients.All.Serve(notification.Drink.Name, notification.RemainingDoses);
    }
}
```

Aqui, por comodidade, foi criado o tipo que representa a notificação `DrinkDispensed` e seu *handler* no mesmo arquivo, já que este será o único.

Aqui temos injetado o contexto de nosso *Hub* do SignalR, e o invocamos para notificar o mesmo evento ao cliente, enviando uma mensagem ao nosso *frontend*, que veremos como construir a partir de agora.

## Construindo o *frontend*

Agora que temos nosso *backend* quase pronto, é hora de darmos atenção ao frontend, por onde o cliente solicitará suas bebidas. O primeiro passo para isso é criar uma pasta `Pages` na pasta raíz do projeto, e criar o arquivo `Index.cshtml` (uma *Razor Page*) com o seguinte conteúdo:

```html
@page

<link rel="stylesheet" type="text/css" href="~/css/drink-machine.css" />

<div id="container">
    <div id="title">Welcome to the MediatR Drink Machine</div>

    <div id="separator"></div>
    <div><button id="buttonCoffee">Coffe</button></div>
    <div><button id="buttonLatte">Latte</button></div>
    <div><button id="buttonCappuccino">Cappuccino</button></div>
    <div><button id="buttonTea">Tea</button></div>

    <div id="separator"></div>
    <div><span id="spanServerResponse"></span></div>

    <div id="separator"></div>
    <div><span id="spanError"></span></div>
</div>

<script src="~/js/signalr.min.js"></script>
<script src="~/js/drink-machine.js"></script>
```

Temos aqui uma estrutura bem simples. Um arquivo de estilos, o título, os botões pelos quais o cliente pedirá sua bebiba, e alguns spans para mensagens do servidor. Temos também a inclusão de um script JS com a biblioteca cliente do SignalR, seguido do que contém nossa implementação de controle do estado do *frontend* e comunicação com o *backend*.

Para servirmos os arquivos estáticos (css e js) precisamos inserí-los na pasta adequada. Para isso, vamos voltar à raíz de nosso projeto, criar a pasta `wwwroot` e, dentro dela, as pastas `css` e `js`. 

Vamos agora criar o arquivo `drink-machine.css`, com o seguinte conteúdo:

```css
body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

#container {
    width: 600px;
    margin: auto;
    text-align: center;
}

#title {
    font-size: larger;
    margin-bottom: 10px;
}

#separator {
    margin-bottom: 5px;
}

#spanError {
    color: darkred;
    font-weight: bold;
}

button {
    font-size: large;
    cursor: pointer;
    border: 1px solid #CCC;
    border-radius: 10px;
    width: 250px;
    padding: 7px 0 7px 0;
    margin: 5px 0 5px 0;
}
```

Em seguida, na pasta `js`, vamos criar o arquivo `drink-machine.js`, com o seguinte conteúdo:

```javascript
"use strict";

(function () {
    var machineConn = new signalR.HubConnectionBuilder().withUrl("/machine").build();
    machineConn.serverTimeoutMilliseconds = 10000;

    var buttonCoffee = document.querySelector('#buttonCoffee');
    buttonCoffee.disabled = true;
    buttonCoffee.addEventListener("click", function (event) {
        machineConn.invoke("RequestDrink", 1);
    });

    var buttonLatte = document.querySelector('#buttonLatte');
    buttonLatte.disabled = true;
    buttonLatte.addEventListener("click", function (event) {
        machineConn.invoke("RequestDrink", 2);
    });

    var buttonCappuccino = document.querySelector('#buttonCappuccino');
    buttonCappuccino.disabled = true;
    buttonCappuccino.addEventListener("click", function (event) {
        machineConn.invoke("RequestDrink", 3);
    });

    var buttonTea = document.querySelector('#buttonTea');
    buttonTea.disabled = true;
    buttonTea.addEventListener("click", function(event) {
        machineConn.invoke("RequestDrink", 4);
    });

    var spanServerResponse = document.querySelector('#spanServerResponse');
    var spanError = document.querySelector('#spanError');

    machineConn.on("Ready", function (dosesPerSlot) {
        buttonCoffee.innerHTML = 'Coffee (' + dosesPerSlot.coffee + ' left)';
        if (dosesPerSlot.coffee > 0)
            buttonCoffee.disabled = false;

        buttonLatte.innerHTML = 'Latte (' + dosesPerSlot.latte + ' left)';
        if (dosesPerSlot.latte > 0)
            buttonLatte.disabled = false;

        buttonCappuccino.innerHTML = 'Cappuccino (' + dosesPerSlot.cappuccino + ' left)';
        if (dosesPerSlot.cappuccino > 0)
            buttonCappuccino.disabled = false;

        buttonTea.innerHTML = 'Tea (' + dosesPerSlot.tea + ' left)';
        if (dosesPerSlot.tea > 0)
            buttonTea.disabled = false;
    });

    machineConn.on("Serve", function (drinkName, remainingDoses) {
        spanServerResponse.innerHTML = 'Your ' + drinkName + ' is ready!';

        document.querySelector('#button' + drinkName).innerHTML = drinkName + ' (' + remainingDoses + ' left)';

        if (remainingDoses == 0)
            document.querySelector('#button' + drinkName).disabled = true;
    });

    machineConn.start()
        .then(function () {
            machineConn.invoke("GetAvailableDoses");
        })
        .catch(function (error) {
            spanError.innerHTML = 'Unable to connect to the server. Please press F5.';
        });

    machineConn.onclose(function (error) {
        spanError.innerHTML = 'Connection lost. Please press F5';
    })
})();
```

Aqui temos nosso cliente. Nele especificamos que, ao carregar nossa página, construiremos e iniciaremos uma conexão com nosso *backend* a partir do SignalR. Nossos botões são configurados como desabilitados por padrão, sendo habilitados apenas após o atendimento à requisição de `GetAvailableDoses`, e desde que o método retorne uma quantidade de doses superior a zero à sua respectiva bebida.

Além disso, configuramos o evento `click` de nossos botões para acionar a máquina, solicitando a bebida correspondente. Temos métodos para tratar as mensagens recebidas do *backend* avisando que a bebida foi servida, atualizando o rótulo do botão com a quantidade de doses restantes e desabilitando-o caso não haja mais doses disponíveis. E, por fim, temos métodos de tratamento de falhas na conexão, que notificarão o cliente da necessidade de recarregar nossa página.

>  **Nota:** Por comodidade, a biblioteca do SignalR será inclusa no repositório da aplicação. Para maiores detalhes sobre como gerá-lo, acesse nosso artigo mencionado acima sobre SignalR.

## Toques finais!

Nossa máquina já está quase pronta. Nos resta apenas configurar nosso *backend* para juntar todas as suas peças.

Abra o arquivo `Startup.cs`, na pasta raíz do projeto, e substitua seu conteúdo pelo seguinte:

```c#
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Playground.MediatR.Cafe.Hubs;
using Playground.MediatR.Cafe.Models;

namespace Playground.MediatR.Cafe
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMediatR(typeof(Startup));
            services.AddRazorPages();
            services.AddSignalR();
                        
            services.AddSingleton<DrinkMachineHub>();

            services.AddSingleton<CoffeeDispenser>()
                    .AddSingleton<LatteDispenser>()
                    .AddSingleton<CappuccinoDispenser>()
                    .AddSingleton<TeaDispenser>();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapHub<DrinkMachineHub>("/machine");
                endpoints.MapRazorPages();
            });
        }
    }
}
```

Repare que não fazemos referência a quaisquer dos handlers acessados pelo MediatR. Isso porque quando invocamos o método `services.MediatR(typeof(Startup))`, a biblioteca de injeção de dependências do MediatR se encarrega obter junto ao contêiner de componentes do Asp.Net os tipos marcados com suas interfaces, carregando-os automaticamente quando necessário, reduzindo assim a quantidade de código necessário para configurá-lo para uso.

## E lá vamos nós!

Se estiver tudo certo com seu código, ao executar a aplicação você terá o seguinte resultado:

![](https://dev-to-uploads.s3.amazonaws.com/i/jm1c43w2r91hroc27jc5.png)

## Considerações finais

O MediatR é um excelente meio de notificar sua aplicação sobre requisições e eventos. Em aplicações complexas, onde haja o interesse de desacoplar seus controllers dos executores de comandos (ou consultas), ou mesmo caso queira apenas notificar eventos ocorridos em seu domínio, é uma ferramenta que facilita muito o trabalho!

Curiosidade: a biblioteca oferece também uma outra funcionalidade, chamada [*behaviors*](https://github.com/jbogard/MediatR/wiki/Behaviors) (em inglês), que implementa um pipeline de execução para seus *handlers*, mas entendemos que a mesma estava fora do escopo deste artigo. Podemos demonstrá-la em um artigo futuro se for de seu interesse (nos deixe saber pelos comentários).

[Aqui](https://github.com/wsantosdev/playground-mediatr) você encontra o repositório do Github deste projeto. Fique à vontade para cloná-lo e modificá-lo como quiser!

Caso queira conhecer mais a fundo as novidades do C# 9, veja [este artigo da Microsoft](https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9) (em inglês). Ele ainda está em desenvolvimento, mas contém logo no início um índice com todas as mudanças. Vale a pena pesquisar para se familiarizar, são recursos excelentes!

Gostou? Se sim, me deixe saber pelos indicadores. Caso tenha chegado a este artigo por um post do [Twiiter](https://twitter.com/wsantosdev) ou do [LinkedIn](https://www.linkedin.com/in/wsantosdev/), peço que compartilhe o post com seus amigos e contatos.

Por favor, deixe um feedback nos comentários. É muito importante para saber se este conteúdo está sendo útil pra você!

Muito obrigado, e até a próxima!