Combining Razor and Blazor Pages in a Single ASP.NET Core 3 Application
This post shows you can add Blazor based pages into an existing Razor Pages application.
Background
First RTM release of Blazor should happen in two weeks. Things are still changing quite rapidly, and the latest preview 9 made the interop between Razor Pages and Blazor components much harder: It’s not possible anymore to pass parameters from a Razor Page to a Blazor component using Html.RenderComponentAsync. This may change in future but it’s quite likely that .NET Core 3.0 will arrive with this limitation.
If you still would like to enhance your existing Razor Pages based application with some Blazor magic, one solution is completely create your pages in Blazor. This post shows you can add Blazor based pages into an existing Razor Pages application, where parts of the app are created using Razor Pages and parts of the app are created using Blazor Pages. Same layout is used for both types of pages.
Step One: Blazor support
We start with an existing Razor Pages application which has been converted to .NET Core 3:
First, you have to add Blazor support into your application. This support will allow you to render Blazor components from a Razor page. The official documentation goes through the process but here’s a quick rundown of it.
Startup.cs:
Services.AddServerSideBlazor is needed in ConfigureServices and endpoints.MapBlazorHub in Configure:
_Layout.cshtml:
Blazor’s JS-library is needed in order to enable server side Blazor. This can be added into _Layout.cshtml:
<script src="_framework/blazor.server.js"></script>
_Imports.razor:
We also need a new file called _Imports.razor. This should be added into the Pages-folder:
_Imports.razor is used to set the using-statements for your Blazor components. We can start with the following:
@using System.Net.Http @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.JSInterop @using Microsoft.AspNetCore.Components.Web
And that’s it.We now should have a working Blazor-support in our existing app. We can test this out by copy-pasting the classic Counter-component into our app:
@page "/counter" <h1>Counter</h1> <p>Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { int currentCount = 0; void IncrementCount() { currentCount++; } }
And then editing Privacy.cshtml to include the Counter component:
@page @model PrivacyModel @{ ViewData["Title"] = "Privacy Policy"; } <h1>@ViewData["Title"]</h1> <p>Use this page to detail your site's privacy policy.</p> <component>@(await Html.RenderComponentAsync<Counter>(RenderMode.Server))</component>
Now when we run the app, we should have a working Counter component inside our page:
Next part of this post show you can change your Razor Pages application so that instead of just adding components into existing pages, you can create full-blown Blazor pages.
Step Two: Blazor Pages support
Our Blazor component defines a route “/counter”:
But navigating there doesn’t work:
Our aim in step two is to make the routing to Blazor Pages work. And we want the Blazor Pages to use the same layout as the Razor Pages. For this we need a few things, starting with a Router.
App.razor:
Create a new App.razor file into Pages-folder:
The Router component is defined in App.razor:
@using Microsoft.AspNetCore.Components.Routing <Router AppAssembly="typeof(Program).Assembly"> <Found Context="routeData"> <RouteView RouteData="routeData"/> </Found> <NotFound> <h1>Page not found</h1> <p>Sorry, but there's nothing here!</p> </NotFound> </Router>
The router automatically goes through all the Blazor Components with the page-directive and adds routes to them.
_Host.cshtml:
We also need a page which will be used to host the Blazor pages. This can be named anyway you want but the default Blazor templates use _Host.cshtml so it’s as good name as any. In the _Host.cshtml we can define the layout which in our case will be the same as the Razor pages are using.
_Host.cshtml contains the call to Html.RenderComponentAsync:
@page "/blazor" @{ Layout = "_Layout"; } <app> @(await Html.RenderComponentAsync<App>(RenderMode.Server)) </app>
Startup.cs:
And last, a small addition to Startup.cs’ Configure-method. We previously added MapBlazorHub but now we also add call to MapFallbackToPage and point it to the new _Host.cshtml:
And that’s it! Now we just need to test our setup. Add Blazor page Counter into your layout by editing Pages/Shared/_Layout.cshtml:
When we now start out application, we should have a working Blazor page in our Razor Pages application:
And we didn’t break the support for adding Blazor components into Razor Pages:
Notes
There’s couple things to note:
- Blazor routes only work when they point into root. If “/counter” is changed to for example “/products/counter”, the page can’t load the required blazor.server.js. Instead it gives 404. It should be possible to modify the script-tag so that it can load the required script no matter the location but this seems to have changed from Preview 8 to Preview 9 and I couldn’t get it to work. Here’s a screenshot of the 404 showing the problem:
- If you get the script to load, you probably encounter the same issues with the Blazor hub: The scripts tries to find the hub from /products/blazor instead of blazor. To get around this, you can manually start the connection between the server and the browser:
<script src="~/_framework/blazor.server.js" autostart="false"></script> <script> Blazor.start({ configureSignalR: function (builder) { builder.withUrl('/_blazor'); } }); </script>
Sample code
Sample code for this project is available from GitHub: https://github.com/mikoskinen/blog/tree/master/blazor-pages-razor-pages-single-app