0 Comments

32d51f27-44ec-4fa8-8801-3dc8692e3ae7[5]

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:

image

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:

image

_Layout.cshtml:

Blazor’s JS-library is needed in order to enable server side Blazor. This can be added into _Layout.cshtml:

image

    <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:

image

_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:

image

@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:

9cae988e-879b-4bf5-80cf-c5622a5e0a36

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”:

image

But navigating there doesn’t work:

image

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:

image

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.

image

_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:

image

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:

image

When we now start out application, we should have a working Blazor page in our Razor Pages application:

b8af1ec4-9549-4489-a15f-0b62e11ae2a7

And we didn’t break the support for adding Blazor components into Razor Pages:

32d51f27-44ec-4fa8-8801-3dc8692e3ae7

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:

image

  • 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

0 Comments
Blazor.EventAggregator now works with Blazor 0.9. The library was first released back in February when Blazor was still (partly) called Razor Components and we were still using .cshtml-files. Blazor.EventAggegator 1.1.0 release updates the library to work with latest preview bits of Blazor.

For those not familiar with Blazor.EventAggregator, it is a lightweight Event Aggregator for Blazor. Event aggregator is used for indirect component to component communication. In event aggregator pattern you have message/event publishers and subscribers. In the case of Blazor, component can publish its events and other component(s) can react to those events.

Project home: https://github.com/mikoskinen/Blazor.EventAggregator

0 Comments

Your Blazor Components can implement interfaces even if you’re not using code-behind files. Given that a basic components looks like the following:

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

@functions {

}

It’s not directly obvious how the implementation happens. But for that, we have the “@implements” directive:

@page "/"
@implements IMainPage

We can do the actual implementation in the functions-block:

@functions {
    public void UpdateText()
    {
        // Implementation logic
    }
}

Here’s a full example:

@page "/"
@implements IMainPage

<h1>Hello, world!</h1>

Welcome to your new app.

@functions {
    public void UpdateText()
    {
        // Implementation logic
    }
}

0 Comments

In some child & parent component situations it would be good if you could render or at least get an access to an instance of a Blazor Component.

Given the following scenario where we try to render an instance of the Counter-component inside the Index-component:

<h1>Hello, world!</h1>

Welcome to your new app.

@counterInstance

@functions{
    Counter counterInstance = new Counter();
}

The following doesn’t work as it only draws the type name of the component:

image

There are couple options around this.

Capturing reference

In many (if not most?) cases you should let Razor Components to take care of creating the instance of your component and then use the “ref” keyword to capture the rendered instance:

<h1>Hello, world!</h1>

Welcome to your new app.

@DrawCounter()

@functions{

    private Counter _myCounter = null;

    RenderFragment DrawCounter()
    {
        return new RenderFragment(builder =>
        {
            builder.OpenComponent<Counter>(0);
            builder.AddComponentReferenceCapture(1, inst => { _myCounter = (Counter)inst; });
            builder.CloseComponent();
        });
    }

Reflection

If you really want, you can access the RenderFragment of the instance using reflection and then draw that:

Welcome to your new app.

@RenderContent(counterInstance)

@functions{
    Counter counterInstance = new Counter();

    RenderFragment RenderContent(ComponentBase instance)
    {
        var fragmentField = GetPrivateField(instance.GetType(), "_renderFragment");

        var value = (RenderFragment)fragmentField.GetValue(instance);

        return value;
    }

    //https://stackoverflow.com/a/48551735/66988
    private static FieldInfo GetPrivateField(Type t, String name)
    {
        const BindingFlags bf = BindingFlags.Instance |
                                BindingFlags.NonPublic |
                                BindingFlags.DeclaredOnly;

        FieldInfo fi;
        while ((fi = t.GetField(name, bf)) == null && (t = t.BaseType) != null) ;

        return fi;
    }