Rabbit MQ

Ćwiczenie 8 - Konfiguracja biblioteki RawRabbit

Przygotowanie projektów

Do wykonania tego ćwiczenia będziesz potrzebować stworzyć więcej niż jeden projekt. Stwórz dwa nowye projekt MVC. i jeden ClassLib. Projekty możesz nazwać odpowiednio:

  • cw8-client (Aplikacja MVC)
  • cw8-server (Aplikacja MVC)
  • cw8-common (ClassLib)

Musisz pamiętać, aby tym dwóm projektom ustawić różne porty do uruchomienia. Inaczej te dwie aplikacji będą się "gryźć", w sensie ze będą zajmować sobie porty nawzajem, dlatego tylko jedna z nich wstanie. Aby przejść przez ten tutorial obie aplikacje muszą być up & running.

Zarówno w nowym jak i starymWe wszystkich stworzonych projekcietach zainstaluj poniższe paczki nuget-a.

$ dotnet add package RawRabbit
$ dotnet add package RawRabbit.vNext

D Do aplikacji cw8-common dodaj jeszcze jedną paczkę nuget:

$ dotnet add package Microsoft.AspNetCore.Hosting.Abstractions

Następnie dodaj referencję do projektu cw8-common w projektach cw8-client i cw8-server.

Konfiguracja połączenia z RabbitMQ

W aplikacjach cw8-client i cw8-server do pliku appsettings.json dodaj konfigurację potrzebną do połączenie się z Rabbit-em.

"rabbitmq": {
    "Username": "guest",
    "Password": "guest",
    "VirtualHost": "/",
    "Port": 5672,
    "Hostnames": [ "localhost" ],
    "RequestTimeout": "00:00:10",
    "PublishConfirmTimeout": "00:00:01",
    "RecoveryInterval": "00:00:10",
    "PersistentDeliveryMode": true,
    "AutoCloseConnection": true,
    "AutomaticRecovery": true,
    "TopologyRecovery": true,
    "Exchange": {
      "Durable": true,
      "AutoDelete": true,
      "Type": "Topic"
    },
    "Queue": {
      "AutoDelete": true,
      "Durable": true,
      "Exclusive": true
    }
  }

W obu projektach, w metodach ConfigureServices, dodaj inicjalizację clienta RabbitMQ i dodawanie go jako Singletona do kontenera DI.

var section = Configuration.GetSection("rabbitmq");
var options = new RawRabbitConfiguration();
section.Bind(options);

var client = BusClientFactory.CreateDefault(options);
services.AddSingleton<IBusClient>(_ => client);

Stwórz interfejs, którym będziesz oznaczał przesyłane wiadomości

Definiowanie wiadomości

W projekcie cw8-common utwórz interfejs znacznikowy, którym posłuży do oznaczania klas, które chcemy przesyłaneć jako wiadomości przez szynę RabbitMQ:

public interface IMessage { }

Przejdź do stworzenia przykładowego obiektu wiadomości:, ta klasa nie musimy być bardzo rozbudowana. Wystarczy nam pojedyncze pole Message typu string.

public class SendMessage : IMessage
{
    public string Message { get; }
    public SendMessage(string message)
    {
        Message = message;
    }
}

Obsługa Wiadomości

Następnie zdefiniuj generyczny interfejs do oznaczania klas, które będą obsługiwały wiadomości:

public interface IHandler<in T> where T : IMessage
{
    Task HandleAsync(T message, CancellationToken token);
}

Od razu dodajW projekcie cw8-server utwórz klasę, która obsłuży wcześniej zdefiniowana wiadomość:, najlepiej utwórz katalog Handlers gdzie będziesz przechowywać wszystkie klasy odpowiedzialne za przetwarzanie wiadomości pobranych przez szynę wiadomości.

public class SendMessageHandler : IHandler<SendMessage>
{   
    public async Task HandleAsync(SendMessage @event, CancellationToken token)
    {          
        Console.WriteLine($"Receive: {@event.Message}");
        return Task.CompletedTask;
    }
}

W projekcie cw8-server, który będzie odbierał tą wiadomości, musisz zarejestrować ten handler w kontenerze DI

services.AddTransient<IHandler<SomeendMessage>>, SendMessageHandler>();

Zdefiniuj e### Integracja z .NET Core MVC

Wróć na chwilę do projektu cw8-common. Tutaj utwórz Extension mMethod do la interfejsu IApplicationBuilder, który pozwoli Ci na sprawę dodawania handlerow.na automatyczne uruchamianiae handlerow.-ów, dla przychodzących wiadomości.

public static class ApplicationBuilderExtensions
{
    public static IApplicationBuilder AddHandler<T>(this IApplicationBuilder app, IBusClient client)
        where T : IMessage
    {
        if (!(app.ApplicationServices.GetService(typeof(IHandler<T>)) is IHandler<T> handler))
            throw new NullReferenceException();

        client
            .SubscribeAsync<T>(async (msg, context) =>
            {
                await handler.HandleAsync(msg, CancellationToken.None);
            });
        return app;
    }
    public static IApplicationBuilder AddHandler<T>(this IApplicationBuilder app)
        where T : IMessage
    {
        if (!(app.ApplicationServices.GetService(typeof(IBusClient)) is IBusClient busClient))
            throw new NullReferenceException();

        return AddHandler<T>(app, busClient);
    }
}

Dzięki temu, w metodzie Configure, w pliku Startup.cs, będziesz mógł się zapisać na wiadomości w postaci:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // ...
    app.UseMvc();
    app.AddHandler<SomeMessage>()
        .AddHandler<SomeAnotherMessage>()
        .AddHandler<MayberOneMoreMessage>();
}

Na koniec, w drugim projekcie, wyślij wiadomości z kontrolera. Dla przykładu:

public class MessageController : Controller
{
    private readonly IBusClient _client;
    public ProfilesMessageController(IBusClient client)
    {
        _client = client;
    }
    [HttpGet]
    [Route("Creat/sendMessage")]
    public async Task<IActionResult> Create()
    {
        await _client.PublishAsync(new SomeMessage("Test Message"));

        return Accepted();
    }
}

Ostatnie uwagi

Jeżeli planujesz, aby połączenia webSocket były obsługiwane przez inną aplikację i dopiero potem wysyłane przez szynę RabbitMQ do serwisu (albo w drugą stronę, żeby wydzielić taki "notification service") poza scope aplikacji MVC, pamiętaj o odpowiednim ustawieniu nagłówków CORS. W lokalny środowisku będziesz używać aplikacji na różnych portach, co powoduje złamanie cors policy.

Dlatego w pliku Startup.cs w metodzie Configure dodaj poniższa konfiguracje middleware do obsługi CORS-a

app.UseCors(builder => builder
    .WithOrigins(
        "https://localhost:5001"
        )
    .AllowAnyMethod()
    .AllowAnyHeader()
    .AllowCredentials());

Pamiętaj tez ze nie można ze sobąpowinno się łączyć funkcji AllowAnyOrigin i AllowCredentials.