Last post we integrated the steps I had laid out to utilize the plugin architecture with WPF. Up next is applying the same steps to Blazor. But the communication between the client and the back-end is dependent on IMessenger from the CommunityToolkit.Mvvm. How do we use that in Blazor?
As always all code is available in the Github project.
I had started down a path of trying to figure out how to add MVVM to Blazor. I mean, at its core it’s a design pattern. Given that CommunityToolkit.Mvvm successfully implements a framework for easily integrating the MVVM pattern, it shouldn’t be too hard. Except… it is, it really is kind of a pain. Also I was trying to re-invent the wheel. There was some great work done by Kelly Adams on his blazor-mvvm project. This was followed up by Graeme_Grant and his Blazing.Mvvm project. There is also a whole separate project by Christian Klemm titled MvvmBlazor that is a bit of a more light-weight approach to MVVM integration into Blazor.
They both look like great projects but they provide approaches that are beyond the scope of what I was attempting to explain here, how to add IMessenger to Blazor. So I started to try and strip each of them down to a simplified MVVM architecture that would fit into easily digestible bytes like what I post here. I would still be working on that approach if I didn’t come to the realization that I didn’t need to be so thorough. The only thing we need is to be able to use IMessenger in Blazor.
IMessenger is a feature that is included with CommunityToolkit.Mvvm but it’s more an add-on, not directly connected to MVVM. It does facilitate the MVVM pattern and it certainly fits with that library as a tool to make MVVM integration and communication easier to understand and implement but it’s completely stand-alone. So I’m going to take the KISS approach (Keep it simple silly 🙂 and just use the IMessenger.
To start with I added the basic, run-of-the-mill Blazor project and added a new Chatter page.
To my Chatter.razor I added the @page attribute to provide a url:
@page "/chat"
To my NavMenu.razor I removed all the boilerplate stuff except home and added my new page:
<div class="nav-item px-3">
<NavLink class="nav-link" href="chat">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Chat
</NavLink>
</div>
Okay, basics are in place. I can navigate to the page, even if there isn’t anything there yet.
The next step is to modify my build steps on the plugin projects. When I did the original changes to build for the WPF app that worked okay, but it was very limiting. Now I need to support multiple projects so I need to copy to multiple projects when building. This step isn’t strictly necessary but I wanted to so I didn’t have to keep moving around files if I ever changed the plugins.
The problem with using the properties page for a project when editing pre and post builds is that they only represent a single execute command. But we want to do this step for multiple end-point projects. And each plugin project needs to do it.
The steps for each project look like:
- PreBuild
- Make plugin dir for WPF
- Make plugin dir for Blazor
- PostBuild
- Remove existing directory from plugins for project
- Make directory in plugins for project
- Copy all the files needed to project directory
To accomplish this I edited the project file for each of the plugin handlers and changed the PreBuild and PostBuild tags to:
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="rmdir /q /s "$(SolutionDir)Chatter\bin\$(Configuration)\net8.0-windows\Plugins\$(ProjectName)" 2> nul
mkdir "$(SolutionDir)Chatter\bin\$(Configuration)\net8.0-windows\Plugins\$(ProjectName)"
xcopy /s "$(SolutionDir)$(ProjectName)\bin\$(Configuration)\$(TargetFramework)\*.*" "$(SolutionDir)Chatter\bin\$(Configuration)\net8.0-windows\Plugins\$(ProjectName)\"" />
<Exec Command="rmdir /q /s "$(SolutionDir)ChatterBlazor\bin\$(Configuration)\net8.0\Plugins\$(ProjectName)" 2> nul
mkdir "$(SolutionDir)ChatterBlazor\bin\$(Configuration)\net8.0\Plugins\$(ProjectName)"
xcopy /s "$(SolutionDir)$(ProjectName)\bin\$(Configuration)\$(TargetFramework)\*.*" "$(SolutionDir)ChatterBlazor\bin\$(Configuration)\net8.0\Plugins\$(ProjectName)\"" />
</Target>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="mkdir "$(SolutionDir)Chatter\bin\$(Configuration)\net8.0-windows\Plugins"" IgnoreExitCode="true" />
<Exec Command="mkdir "$(SolutionDir)ChatterBlazor\bin\$(Configuration)\net8.0\Plugins"" IgnoreExitCode="true" />
</Target>
I would recommend looking at the project file on Github as html formatting is stripping out the xml characters. The commands themselves are basic command-line stuff. The important bit is the “IgnoreExitCode” attribute on the Exec tag.
Normally when running pre and post build steps Visual Studio will stop on any error. I needed this to not happen as I needed each project to be able to create the plugin directory in the respective target project since I can’t guarantee which will run first. If there already is a plugin directory command line will throw an error saying the directory already exists and the build will stop. But I don’t care if it already exists.
Up next is to add the CommunityToolkit.Mvvm nuget to the Blazor project and register the IMessenger. To my Program.cs I added:
//Add in the messenger so our back-end can access it
builder.Services.AddSingleton<WeakReferenceMessenger>();
builder.Services.AddSingleton<IMessenger, WeakReferenceMessenger>(provider => provider.GetRequiredService<WeakReferenceMessenger>());
Great, now how do I use them? In my code block of Chatter.razor I want to use the [Inject] attribute so that Blazor knows to inject from the services my IMessenger.
[Inject] IJSRuntime JSRuntime { get; set; }
[Inject] protected IMessenger Messenger { get; set; }
I’m also injecting the Javascript runtime here so I can force the div with chat messages to always scroll to the bottom when adding a new chat.
Now I have access to an IMessenger in my Blazor page that I can use for the plugins. Next week I’ll discuss what I do with it, loading the plugins and a more complete dive into everything that is in Chatter.razor.
Thanks,
Brian
One thought on “Using IMessenger with Blazor”