I've been spending more time working with Blazor, which means many more learning opportunities for me. That also means more learning opportunities for me to share with YOU! In this particular case, I was battling with trying to hook some event handlers on some MudBlazor list items... But it turns out that the Blazor render mode was the part breaking my dependency injection on the page, which was the culprit of my event hookups never working.
In this article, I'll walk you through the scenario I was facing and how I started debugging. You'll see that I realized I was focused on the wrong concept, and once I realized some details about the configured Blazor render mode, things started to make more sense. I hope that this article (and video) help you -- and I am confident they'll help me the next time I run into this.
What's In This Article: Blazor Render Mode and Dependency Injection
Remember to check out these platforms:
// FIXME: social media icons coming back soon!
Before I Knew It Was Blazor Render Mode...
I was heads-down working on some Blazor UI for an application I'm building, and it's the motivation behind whipping up another quick article on MudBlazor list items. The problem was pretty simple: I just wanted to wire up some event handlers to some buttons that I had on my list items.
Here's what the code looked like:
@page "/items"
@using DevLeader.Services
@using DevLeader.Services.Items
@inject IItemsService ItemsService
<PageTitle>Dev Leader Is Awesome!</PageTitle>
<h1>Dev Leader Is Awesome!</h1>
@if (_itemsCollection == null)
{
<p><em>Loading...</em></p>
}
else if (_itemsCollection.Count == 0)
{
<p><em>No items found.</em></p>
}
else
{
<MudList>
@foreach (var item in _itemsCollection)
{
<MudListItem>
<MudCard>
<MudCardHeader>
@item.Title
</MudCardHeader>
<MudCardContent>
<MudButtonGroup xs="12" sm="6">
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="e => ApproveButton_OnClickAsync(item)">
Approve
</MudButton>
<MudButton Variant="Variant.Filled"
Color="Color.Secondary"
OnClick="async e => await RejectButton_OnClickAsync(item)">
Reject
</MudButton>
</MudButtonGroup>
</MudCardContent>
</MudCard>
</MudListItem>
}
</MudList>
}
@code {
private IReadOnlyList<Item>? _itemsCollection;
protected override async Task OnInitializedAsync()
{
await Task.Yield();
var endDateTimeUtc = DateTime.UtcNow.AddDays(1);
var startDateTimeUtc = DateTime.UtcNow.AddDays(-7);
_itemsCollection = await ItemsService
.GetItemsAsync(
startDateTimeUtc: startDateTimeUtc,
endDateTimeUtc: endDateTimeUtc,
cancellationToken: CancellationToken.None)
.ConfigureAwait(false);
}
private async Task ApproveButton_OnClickAsync(Item feedItem)
{
await Task.Yield();
}
private async Task RejectButton_OnClickAsync(Item feedItem)
{
await Task.Yield();
}
}
You can see that I was even fooling around with trying to see different implementations of the async event handlers -- because they just were NOT triggering no matter what I did. But that turned out to be a red herring because the event handlers not triggering were a symptom of the problem.
For the rest of this article, I'll explain more about that problem (hint: it's how dependency injection and render mode play together). I also made this video about Blazor render modes and how I was debugging this dependency injection issue here:
Dodging the Red Herring
Even though I had set out to go wire up event handlers and prove that they worked, the fact that they seemed busted was a red herring. The async event handlers not firing was not because I was doing MudBlazor setup incorrectly, it's because there was another problem altogether. And here's what was hiding in my console output:
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Cannot provide a value for property 'ServiceImplementation' on type 'MyProject.Pages.MyPage'. There is no registered service of type 'MyProject.IService'.
System.InvalidOperationException: Cannot provide a value for property 'MyService' on type 'MyProject.Pages.MyPage'. There is no registered service of type 'MyProject.IService'.
I figured if I have an exception being thrown on the page, perhaps I shouldn't put my energy into the async event handlers anymore on the MudBlazor list items... Odds are, I had a bigger problem to sort out, and that bigger problem was just causing the rest of the page to not load correctly. Spoiler alert: the odds were correct.
But here's what was VERY weird about this situation:
- The page was showing results in the MudList component as there was an individual MudListItem populated. The only way this can happen, if you check the code shown earlier in the article, is if my dependency is called.
- This means, 100% for certain, that the dependency WAS accessible on the page and we DID call and use it successfully.
- ... Which means now all of a sudden in some cases, we can't seem to get the dependency.
From all of my experience using Autofac and other dependency injection frameworks, this was baffling. Generally speaking, if we can resolve a dependency once, we should be able to resolve it again. One could argue maybe subsequent resolutions have different behaviors, but at least the DI framework could identify that there was something registered.
So... what makes this situation different than when things are working? To the Internet I went... which is also why this article and the video I created exist.
Blazor Render Mode Broke My App!
Full disclaimer: this is not the fault of Blazor or Blazor render modes being bad... Nothing like that. This is 100% an issue that I created due to my misunderstanding, or lack of understanding, of how render modes work in Blazor.
My application is configured to use InteractiveAuto render mode. This is what was configured in the template when I created my application in Visual Studio:
@code {
[CascadingParameter]
private HttpContext HttpContext { get; set; } = default!;
private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
? null
: InteractiveAuto;
}
This means that Blazor will determine when it can do client-side rendering for performance/latency reasons -- in some situations dramatically improving the loading experience for users. The problem? It means that my dependency injection is going to try and take place on the client side!
Not going to work for me. At least, not with a solution I know of yet... But I'm sure we'll get there. The internet suggested that I could toggle the Blazor render mode per page, which seemed to be a very sensible move. In fact, this was a great solution that I would have liked to do, however... Adding the @ directive on the individual pages never worked. The behavior to change the render mode NEVER updated for me. Again, this will be a follow-up article and video when I figure out what the heck was going on.
The fix for me, at least what will work for now for me, is as follows:
@code {
[CascadingParameter]
private HttpContext HttpContext { get; set; } = default!;
// private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
// ? null
// : InteractiveAuto;
private IComponentRenderMode? RenderModeForPage => InteractiveServer;
}
I don't need to rely on any client-side rendering right now. This will at least unblock me to continue to develop my Blazor application. As things evolve, I'll be sure to come back and revisit this Blazor render mode setting to make a better decision.
Wrapping Up Blazor Render Mode and Dependency Injection
Blazor render mode is broken! Okay, I'm kidding, so hopefully that wasn't your takeaway from this article. There are a few key points I wanted to highlight, including that my current solution here may not at all be a viable option for you. And that's why I will be creating follow-ups as I sort through this myself.
My takeaways:
- If your code on page isn't executing when you think it should be, double check for console errors. Something might be preventing the page to load as you expect.
- Blazor server-side rendering vs Blazor client-side rendering will in fact treat dependency injection separately. Give this some thought.
- This is an example of some potential "technical debt" that I will need to pay down later... but I would rather keep building than remain stuck for now.
If you found this useful and you're looking for more learning opportunities, consider subscribing to my free weekly software engineering newsletter and check out my free videos on YouTube! Meet other like-minded software engineers and join my Discord community!
Affiliations
These are products & services that I trust, use, and love. I get a kickback if you decide to use my links. There’s no pressure, but I only promote things that I like to use!
- BrandGhost: My social media content and scheduling tool that I use for ALL of my content!
- RackNerd: Cheap VPS hosting options that I love for low-resource usage!
- Contabo: Affordable VPS hosting options!
- ConvertKit: The platform I use for my newsletter!
- SparkLoop: Helps add value to my newsletter!
- Opus Clip: Tool for creating short-form videos!
- Newegg: For all sorts of computer components!
- Bulk Supplements: Huge selection of health supplements!
- Quora: I answer questions when folks request them!