Transitioning from Razor Pages to Blazor
I wanted to experiment with Blazor, so I decided to rewrite a small single page app into the new framework. I used the Interactive Server render mode to keep things simple. The first thing I noticed is how much more straightforward it is to accomplish the things I was doing with Razor Pages.
Components
The nomenclature is confusing, but Blazor web pages are made up of “Razor Components” (.razor files) which are reusable and referenced like HTML tags. Razor Components look like this in your code:
<ComponentName @key=Record CurrentRecord="@Record" />
The name of the .razor file becomes an HTML tag and model data is passed in as named HTML attribute values. Optional directives like @key
(which specifies a primary key to be used in place of an index to reference items in a list) can also be included.
These Razor Components are comparable to the “View Components” (a combo of .cshtml and .cs files in their own folder) that I’ve been creating for Razor Pages. View Components look like this in your code:
@await Component.InvokeAsync("ComponentName", Model.CurrentRecord)
The name of the folder becomes the first parameter in the method call, and model data is passed in as the second parameter.
Both types of components are modular, allow you to pass in values, run functions, and display output. But the Blazor version is more concise, more intuitive, and easier to just drop in anywhere. I love how the HTML and code-behind are in the same file!
Virtualization
Even better, Blazor comes with a collection of ready-made built-in components that you can use. When I was researching how to make my app more performant, I discovered the Virtualize component. These are meant to be used in places where you want to repeat content on the page, like displaying all the items in a list.
Traditionally, I’d iterate through my list with a @foreach
loop, such as the one in this simple example:
@foreach (ListItem listItem in @ListName)
{
<span>@listItem.ItemNumber.ToString() + " " + @listItem.ItemName</span>
}
I had a bunch of these loops on my page. I replaced them all with Virtualize components that look like this:
<Virtualize Items="@ListName" Context="listItem" ItemSize="25">
<ItemContent>
<span>@listItem.ItemNumber.ToString() + " " + @listItem.ItemName</span>
</ItemContent>
</Virtualize>
Virtualize components will make your website appear like it is loading faster because it will start to display some initial content before all of the content been rendered. Then it gradually fills the rest in. The effect is like watching an interlaced JPG sharpen.
This functionality works best if you place the component inside of a div with a specified height
and overflow=scroll
, and you also specify an estimated ItemSize
(height in pixels) for everything inside of the <ItemContent>
tag. This gives the application a sketch of where to place the early data.
There’s some fun additional tags that you can use:
1. Placeholder
<Placeholder>
is supposed to display the <Placeholder>
content immediately and then refresh with the <ItemContent>
when the application has retrieved all the items in the list.
<Placeholder>
Loading...
</Placeholder>
While I have been testing my app, I haven’t seen it ever show the placeholder content. It skips directly to displaying partially loaded lists. But I keep it there in case of a rainy day.
2. EmptyContent
Additionally, there’s an <EmptyContent>
tag that you can add which I have seen work:
<EmptyContent>
No items
</EmptyContent>
The empty content option is nice to have, but I don’t use it in most places, because I’ve decided to display @ListName.Count.ToString()
in parentheses after my content header. It is cool to see the count refresh from (0)
to the number of items automatically!
Injection
With Blazor, you can reference things you need to run your application on any page using the @inject
statement. Here’s three powerful things I learned how to do:
1. Navigation Manager
@inject NavigationManager Navigation
If you put this statement at the top of your page, you can get the URL of your website whenever you need it. I used Navigation Manager in my HTML to get the absolute path to the location where my website is running. Then I concatenate the absolute path with the relative path to a file in a subdirectory.
<a href=@(Navigation.BaseUri + @listItem.FilePath)>@listItem.FileName</a>
I also used Navigation Manager in my @code {}
block to retrieve URL parameters from a query string, e.g. (https://website.com/petsearch?type=cat&color=grey). My legacy app uses the query string style, but Blazor apps are natively expected to use the route parameter style with everything separated by slashes, e.g. (https://website.com/petsearch/cat/grey). If I switched styles, I’d have to update all of the applications that integrate with this one, and the URL would be harder to read. So I used the Navigation Manager to grab the parameters:
var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("type", out var type))
{
string searchType = type;
//Do stuff with the parameter
}
Thanks to Chris Sainty for teaching me how to do this.
2. Database Context
@inject IDbContextFactory<DbContextName> IDbFactoryName
If you put this statement at the top of your page, you can reference your database context in your @code {}
block. I looked at the BlazorWebAppEFCore sample project in order to learn how to connect to a database with Entity Framework in Blazor. In that project’s Program.cs, there is a builder.Services.AddDbContextFactory()
command. My Razor Pages projects have builder.Services.AddDbContext()
commands instead. I wasn’t familiar with IDbContext factories, but now I know they are the preferred way to do things in Blazor, because they prevent threading issues.
Thankfully, the factory is easy to use. I just need to place a “using” statement in my code right before I want to access my database. It creates the DBContext on demand just in time for me to perform database operations.
using DbContextName _DbContextName = IDbFactoryName.CreateDbContext();
After this statement, I can execute LINQ queries the exact same way I always have.
3. App Settings
@inject IConfiguration Configuration
If you put this statement at the top of your page, you can easily access configuration settings that are in your appsettings.json file. I had two settings that I wanted to make editable outside of my application code so they can easily be changed when it moves to a different server. I added the settings to my appsettings file as “name” : “value” pairs in quotes.
"URLOfWebsite": "https://companysite.com/query",
"PathToLocalFiles": "C:\\sharedfolder\\files\\"
In my razor file’s HTML code, I get the first config value using this syntax:
<a href=@(Configuration["URLOfWebsite"] + "?queryparameter=" + Model.ItemID)>
@Model.ItemName</a>
In my razor file’s @code {}
block, I get the second config value using this syntax:
string PathToLocalFiles = Configuration.GetValue<string>("PathToLocalFiles");
The values are pulled in as string variables that replace the hard coded text I was previously using.
Events
I think the coolest thing about Blazor is that it allows me to write my entire website with a single coding language. There’s no longer a need to mix in JavaScript for the interactive parts. This is largely because Blazor comes with a long list of “onevent” listeners that are available for you to respond to things that happen on your webpage. Blazor University put together a complete list of all the events that can be handled with Blazor (onclick, onchange, onload, etc.)
You can attach C#.NET methods to these events just like you can attach JavaScript functions. There’s only the slightest difference in the syntax.
<button onclick="JavaScriptFunctionName()">Click Me</button>
<button @onclick="DotNetMethodName">Click Me</button>
You can define the named C# method in your @code {}
block the same way would define the named JavaScript functions in the <script>
area. You can also define anonymous C# methods right in the HTML, as I do the two examples below.
1. Collapsible Section
I used the @onclick
capability to create a button that collapses and expands items in my list tree.
@if (@ListItem.Collapsed == false)
{
<svg @onclick="e => ListItem.Collapsed = !ListItem.Collapsed"
@*image of a minus sign*@ /></svg>
<!-- Additional HTML content here -->
}
else
{
<svg @onclick="e => ListItem.Collapsed = !ListItem.Collapsed"
@*image of a plus sign*@ /></svg>
}
Clicking on the svg icon invokes a method that flips a Boolean variable. (Note: I passed event arguments “e” into the method with a lambda although I did not use them). An if statement checks the value of the variable to determine which button to show and whether to display additional content.
2. Loading Spinner
I also used an @onclick
handler to show a “wait” spinner for a few seconds when the user clicks on hyperlink.
<a @onclick="async () => { ListItem.IsSpinning = true; await Task.Delay(2000);
ListItem.IsSpinning = false; }"
href=@ListItem.LinkURL>@ListItem.LinkText</a>
Yes, you can make the anonymous method asynchronous so you can use Task.Delay()
the way you would a JavaScript setTimeout()
!
<div style="display: @(ListItem.IsSpinning? "inline-block" : "none" )"
class="spinner-border spinner-border-sm text-secondary" role="status">
I love that I could add these little bells and whistles to my single page site. It gives me confidence I will be able to convert my larger JavaScript-heavy websites to Blazor someday!
Error Handling
I’m catching any database errors whenever I perform queries, and displaying them as color coded messages in my list. But I really like the error message that displays in the footer when the Blazor sample apps crash. I wanted to do something similar in my application for all other types of exceptions.
I decided to use an Error Boundary to make this simple.
Putting this in my MainLayout.razor is kinda like putting my entire application inside of a try/catch statement. If anything goes wrong, I display the exception error message instead of @Body
.
<ErrorBoundary>
<ChildContent>
@Body
</ChildContent>
<ErrorContent Context="exception">
@exception.Message
</ErrorContent>
</ErrorBoundary>
If you don’t want to display the detailed error message in production or to public users, you can add logic right inside the <ErrorContent>
section to change what’s shown under certain conditions. I didn’t have to write any additional code anywhere else to make this work.
Base Path
When I’d finished writing the Blazor app and published it to our test server, I had a lot of “Not Found” errors because my routes weren’t being created correctly. In the past, I have struggled to create relative links that can survive the application moving from localhost to a remote web host, because the URL has an additional level of nesting (https://localhost:port/ vs https://www.servername.com/applicationname/).
Previously, I have solved the problem by telling the debugger what URL to debug at, but that option isn’t available with all types of debuggers (such as IIS Express) or in all versions of Visual Studio. I discovered that Blazor has introduced a <base>
tag located in the App.razor file that is a better solution to this problem.
In the sample apps, the <base>
tag is set to <base href=”/” />
When I changed it to be set to my application name instead, the application URL and paths were constructed the same way locally as they are on the server. I didn’t have to do anything else to make it work everywhere.
<base href="/applicationname/" />
It’s very nice to be able to set this in one place and forget about it.
In conclusion: I really like Blazor! So far, it makes it easier to do everything I need to, and that’s refreshing. I plan to try out the other render modes (WebAssembly and Auto) and see how they compare to Interactive Server. At this point, I think it’s my new favorite framework!