On a recent project, I was adding SignalR to a .Net Core application and ran into something I didn’t like. The Microsoft documentation shows using strings in the client and server to define method names and using open ended arguments to specify parameters going into the method.
This led me to come up with something I feel like makes good use of Blazor Web Assembly’s C# in the browser and I wanted to share.
For this example I’m doing a simple hub based around the default Counter page. The goal will be for the counter button press to send a SignalR event with the new value and update all connected clients to show the new value.
Start by creating a new hosted blazor wasm project.
dotnet new blazorwasm -ho -o src\ -n StronglyTypedWasmClient
Create a hub interface as shown in Microsoft’s server side strongly typed SignalR Example. This has the previously mentioned method that will be called when the count changes and will receive the new value. This interface lives in the shared project, not the server project as shown in the documentation.
Create a hub that implements HubConnection<ICounterHubClient>
Register the new hub in startup.cs. I’m not mentioning all of the other SignalR wireup code as it’s exactly the same as the linked Microsoft documentation.
In our client, we now want to create a strongly typed client that can interact with the hub and be used by the counter page.
Install the SignalR client the same way Microsoft’s documentation suggests
dotnet add src\Client package Microsoft.AspNetCore.SignalR.Client
Create an interface for common SignalR client functions. This isn’t really required for the example but it’s helpful if you want to re-use the common SignalR functions in future clients.
Next, create an interface for the counter SignalR client that inherits from both the common SignalRClient interface and the hub client interface. The purpose of this interface will be to define a “handler” method that clients can use to register what they want to happen on the given SignalR event. The use of Action<int> allows us to have strongly typed “handlers” that we know can/should receive an int, in this case our new value.
Create a generic implementation of the common SignalR Client. Again, not required for this example but worth sharing.
Next, create an implementation of the CounterSignalRClient that brings everything together. Instead of strings, this code utilizes nameof(CountChanged) to support refactoring of the hub method(s). You still have to remember to pass in your arguments to SendAsync but you at least know what you have available thanks to the interface. This also allows for SignalR client re-use that would support refactoring.
Before moving to the UI, add the new client to program.cs for dependency injection. This is another aspect that is off topic from the title, but is worth sharing and made possible thanks to our strongly typed SignalR client.
This is where we should start realizing the cleanliness benefits of the above work.
Add the interfaces namespace to _Imports.razor
Inject the client into the Counter.Razor page
Update the button to use the common SignalR client’s IsConnected property.
Add an OnInitializedAsync method that will add a handler for the CountChanged event and Start our client.
Update the IncrementCount() method so that it is async and calls the CountChanged method on our client on every press.
Now we can test everything to see how it works
I am just now venturing into the world of .Net Core SignalR. There could be shortcomings with this solution that will be identified in the future, but for now I feel this is a great way to make use of all that .Net Core and WASM in particular have to offer.
Full source code available at https://github.com/joshexe/StronglyTypedWasmClient