Now that we have SignalR integrated into our API, I want to start using it from WPF. So to do this we’re going to add a SignalR chat handler. One of the ideas behind this application was to support multiple types of methods for sending chats using the IMessenger interface provided by CommunityToolkit.Mvvm. That way, in the WPF application, I can pick how I wanted to manage sending those chats. What I want to do now is add a new way of sending chats by using the SignalR hub I created in the last post that exists in the API.

I’ve always made the code available via download but I decided to finally throw it up on GitHub to pull down. Portions of this were inspired by IAmTimCorey’s video, “Intro to SignalR in C# Part 1 – using Blazor, WPF, best practices, and more“.

To start with, I want to add a new method to my hub for receiving messages. Up until now messages have been received and sent via the API but we want to be able to do that with SignalR. Fortunately, I had the foresight to define a static in my hub that will handle that, I just need to define a method to accept the chat message and pass it down to the static. To my ChatterChatHub I’ll add:

    public async Task SendMessageWithSignalR(WriteToChatMessage chatMessage)
    {
        await ChatterChatHub.SendMessage(dbFactory, Clients.All, chatMessage);
    }

This provides a method for SignalR clients to call.

Moving over to my SignalRChatHandler project, let’s start by looking at the how I connect to SignalR. In my constructor I have:

connection = new HubConnectionBuilder()
                .WithUrl("https://localhost:7076/chatterChatHub")
                .WithAutomaticReconnect()
                .Build();

It’s really that easy, use the HubConnectionBuilder, specify the hub end point that I defined in the API, tell it to always attempt to reconnect if I lose connection and build. That connection is a private variable I keep around so I can not only get messages from the hub, but send messages to it. SignalR provides some pretty robust methods it uses in the background when “WithAutomaticReconnect” is defined on the hub including multiple fallback methods. But connections aren’t always perfect and users may not always be connected. Next I want to define how to handle the auto-reconnect events.

connection.Reconnecting += (sender) =>
{
    Messenger.Send(new WriteToChatMessage("system", "Trying to reconnect to server", "Red", "White", "Normal"));
    return Task.CompletedTask;
};
connection.Reconnected += (sender) =>
{
    Messenger.Send(new WriteToChatMessage("system", "Reconnected to server", "Red", "White", "Normal"));
    return Task.CompletedTask;
};
connection.Closed += (sender) =>
{
    Messenger.Send(new WriteToChatMessage("system", "Connection to server closed", "Red", "White", "Normal"));
    return Task.CompletedTask;
};

I kind of cheat here. In production code there would need to be more thought and planning put into this but for the sample app here I just send a message via the IMessenger, telling the user there is a chat from the system and indicate the state of the connection.

The next step is telling the connection what methods I want to listen for and handle.

        connection.On<WriteToChatMessage>("ReceiveMessage", (message) =>
        {
            Messenger.Send(message);
        });

When the connection gets a ReceiveMessage that contains a WriteToChatMessage, invoke the lambda. In the lambda I use the IMessenger to send the message and expect anybody listening for that message to handle it as needed.

Finally I start the connection.

connection.StartAsync();

Now I need to handle and override the abstract SendChatAsync that is defined in the abstract ChatHandler class.

public override async Task<bool> SendChatAsync(WriteToChatMessage message)
{
    try
    {
        await connection.InvokeAsync("SendMessageWithSignalR", message);
        return true;
    }
    catch(Exception ex)
    {
        Messenger.Send(new WriteToChatMessage("system", ex.Message, "Red", "White", "Normal"));
        return false;
    }
}

I tell the connection that I want to invoke the “SendMessageWithSignalR” method on the hub at the end point. This is why, at the start of the post, I had to add that method. If there are any exceptions, log them as a chat.

Going all the way back to the start of this series, I have in my MainWindowViewModel ctor a ChatHandler I’ve been using as I’ve been exploring in this series. Now that the SignalRChatHandler is created I can use that as the ChatHandler instead of the other ones I’ve previously defined.

public MainWindowViewModel(Dispatcher dispatcher, IMessenger messenger)
{
    BindingOperations.EnableCollectionSynchronization(Chats, new());

    Dispatcher = dispatcher;
    Alias = "Unknown " + Guid.NewGuid().ToString();

    Messenger = messenger;
    Messenger.RegisterAll(this);
    //Messenger.Register<MainWindowViewModel, WriteToChatMessage>(this, (r, m) => r.Receive(m));

    //ChatHandler = new SystemMessageChatHandler(Messenger);
    //ChatHandler = new BasicChatHandler(Messenger);
    //ChatHandler = new BackgroundChatHandler(Messenger);
    ChatHandler = new SignalRChatHandler(Messenger);
}

Of course, you could add the hub connection directly in your view model or even define it in host services for dependency injection. This all gets a SignalR connection into WPF.

Next week I’ll add a new Blazor client that does the same thing, but with Blazor. 🙂

Thanks,
Brian

One thought on “Integrating SignalR into WPF

Leave a Reply

Your email address will not be published. Required fields are marked *

FormatException

928 East Plymouth Drive Asbury Park, NJ 07712