The Practical Client
Integrating Blazor into Existing ASP.NET Core Applications
If you want to add server-side Blazor to your existing ASP.NET Core applications, you can. There's not much to it, fortunately. In fact, there's probably more work involved in creating a View or Page that will play well with your component
Personally, I think that if you're planning on using Blazor to replace JavaScript, you're making a great decision. And if what you want to do is create a new all-Blazor, all-the-time project, Visual Studio 2019 has two project templates for: Server-Side Blazor (SSB) where your C# code executes on the server and communicates with the browser using SignalR; and Client-Side Blazor (CSB) where your C# code is downloaded to the browser and runs on the client.
But what if you want to use Blazor in an existing ASP.NET Core project that uses Controllers+Views or Razor Pages? If you just add a Blazor component to an existing ASP.NET Core project, your component won't work. In this column, I'll walk through how to enable support for integrating SSB from both Razor Pages and MVC Views. I'll also provide some guidance on how to call your component from your View or Page.
Be warned: This is a topic whose details have changed frequently. This code works as of May 20, 2019. For this column I used Visual Studio 2019 preview 16.1 and NET Core 3.0.0 Preview 5. After upgrading to those tools, to make sure I had the latest project templates, I opened a command window and used this command:
dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.0.0-preview4-19216-03
You could probably short circuit at least some of those steps by using the vsix package from the Blazor preview page at https://github.com/aspnet/Blazor/releases (it's tucked away underneath the Assets heading).
Initializing the Project
The first step in adding the necessary support for Blazor to an ASP.NET Core Web Application project is to get the latest version of the right NuGet package: Microsoft.AspNetCore.Blazor (for me, the latest version was 3.0.0-preview5-19227-01). Once you've installed that package, check for any updates before continuing.
Your next step is to add to your project's dependency injection container the objects that support SSB. To do that, go to your project's Startup.cs file, find the ConfigureServices method and add this line at the end of the method:
services.AddServerSideBlazor();
At the end of the Configure method you'll find this code:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
You need to add one line to that code to finish enabling SSB:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
endpoints.MapBlazorHub();
});
You're just about ready to add your first Blazor component. Blazor components must go into a Pages subfolder under the Components folder, which aren't included in the project template -- so your final prep step is to add that folder and sub-folder.
That's a fair number of steps ... but you only have to do it once per project.
Adding the Component
To add a Razor Component, right-click on your new Pages folder and select Add | New Item. I didn't find a template for a file with a razor extension so I selected Razor View as my template and overwrote the default extension of .cshtml. You can give your component file any name you want -- just recognize that your file name will be the name of your component (I called mine Customers.razor).
Here's the code I placed in my razor file to test my initial display and the component's ability to respond to a client-side event (because your Blazor component is going to be called from an MVC View or Razor Page, you don't need a page attribute at the top of your component):
Hello, @name
<br/>
<input type="button" onclick="@ChangeName" value="Change Name" />
<br/>
@functions
{
private string name { get; set; }
protected override void OnInit()
{
name = "world";
base.OnInit();
}
private void ChangeName()
{
name = "Peter";
}
}
Invoking Your Component
Now, within your Razor Page or View, you can invoke your component using the RenderComponentAsync method that's been added to the HtmlHandler stashed in your Page or View's Html property. To display my Customers component as part of my page, I used this code (note that I had to provide the fully qualified name of my Blazor component):
@{
Layout = null;
}
<script src="~/_framework/blazor.server.js"></script>
<cust>@(await Html.RenderComponentAsync<BlazorFromMVC.Components.Pages.Customers>())</cust>
The call to RenderComponentAsync must be enclosed inside an element but the actual name of the element doesn't matter. Nor does the script element have to be in the same file as the call to RenderComponentAsync (if you were using Blazor throughout much of your site, you could put that script element in your Layout view).
The code in a Razor Page is almost identical:
@page
<script src="~/_framework/blazor.server.js"></script>
<cust>@(await Html.RenderComponentAsync<BlazorFromMVC.Components.Pages.Customers>())</cust>
In real life this surrounding View or Page would, in addition to providing the context for whatever components the View or Page is hosting, provide any static resources required by your component (for example, script and link tags).
You can also pass parameters from your View/Page to your component. I can, for example, pass the entire object in my View or Page's model property to my component by creating an anonymous object, adding a property to that object (with a name of my choosing), and setting that property to the View/Page's Model property.
That's what this code does:
<cust>@(await Html.RenderComponentAsync<BlazorFromMVC.Components.Pages.Customers>(new {cust = Model}))</cust>
To accept that parameter in my component, I just need to add a property with a matching name ("cust", in this case), and decorate it with the Parameter attribute. Revising my component to work with a Customer object passed in the cust parameter gives me this:
Hello, @name
<br />
<input type="button" onclick="@ChangeName" value="Change Name" />
<br />
@functions
{
private string name { get; set; }
[Parameter] private BlazorFromMVC.Models.Customer cust {get; set;}
protected override void OnInit()
{
name = "world";
base.OnInit();
}
private void ChangeName()
{
name = cust.FirstName;
}
}
Dealing with Disconnects
However, because processing is happening on the server, you should extend your Page or View to handle a loss of connection to the server.
Blazor provides some support for recognizing the problem by leveraging an element with an id of components-reconnect-modal. Blazor will, initially, set the class attribute on this element to components-reconnect-hide. When the connection to the server first fails and while Blazor attempts to reconnect, Blazor will set the class attribute to components-reconnect-show. If Blazor is unable to reconnect, it will (after a while) set the element's class attribute to components-reconnect-failed. Blazor also provides the function window.Blazor.reconnect() that you can call to attempt to reconnect to the server (in my crude testing, I was never able to reconnect).
The following, admittedly crude, set of HTML will give you feedback about your Page or View's current "connected" status and support calling that reconnect method:
Connection Status:
<div id="components-reconnect-modal" class="reconnect-block">
<div class="reconnect-wait">
Connection lost -- please wait, attempting to reconnect
</div>
<div class="reconnect-button">
Unable to reconnect. Click to attempt to reconnect:
<input type="button" value="Reconnect" onclick="Reconnect()" />
</div>
</div>
<script>
function Reconnect() {
window.Blazor.reconnect();
}
</script>
Here's some awful CSS to work with that markup:
.components-reconnect-hide {
visibility: hidden;
}
.components-reconnect-show {
visibility: visible;
}
.components-reconnect-failed {
visibility: visible;
}
.reconnect-block {
visibility: hidden;
}
.components-reconnect-show .reconnect-wait {
visibility: visible;
}
.components-reconnect-failed .reconnect-wait {
visibility: hidden;
}
.components-reconnect-show .reconnect-button {
visibility: hidden;
}
.components-reconnect-failed .reconnect-button {
visibility: visible;
}
You can even, as I'll discuss in my next column, mix Razor-style HTML generation with your C# code (at least you can if you're using server-side Blazor with .NET Core Version 3, preview 6). All in all it's pretty cool.
[Editor's note: This article was edited from the original to reflect changes made in the preview 6 releases of ASP.NET Core 3.0 and NET Core 3.0]
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.