loading...

【ASP.NET Core】Try Blazor(Blazor Server)

masanori_msl profile image Masui Masanori ・5 min read

Intro

Though I have been interested in Blazor, I have never tried it.
But in last update(ASP.NET Core Preview 7), there were many update about Blazor.

So I decided trying Blazor.

Because if I actually use Blazor, I want to add it into existing projects, so I create an MVC project first.

Blazor has two types( Blazor Server and Blazor WebAssembly).

Because I don't try PWA and I feel Blazor Server looks like more easy to understand, so I choose Blazor Server this time.

Environments

  • ASP.NET Core ver.5.0.100-preview.7.20366.6

Base project

I create a empty project.
And I add Controller and Razor files.

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace BlazorSample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseRouting();
            app.UseStaticFiles();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

HomeController.cs

using Microsoft.AspNetCore.Mvc;
using Models;

namespace Controllers
{
    public class HomeController: Controller
    {
        [Route("/")]
        public ActionResult Index()
        {
            return View("Views/Index.cshtml");
        }
    }
}

Product.cs

namespace Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

_ViewStart.cshtml

@{
    Layout = "_Layout";
}

_Layout.cshtml

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>@ViewData["Title"]</title>
    </head>
    <body>
        @RenderBody()
    </body>
</html>

Index.cshtml

@{
    ViewData["Title"] = "Home Page";
}
<h2>This is Index.cshtml</h2>

Add Blazor

According the article, I can add Blazor into the project.

Add middleware and mapping Blazors SignalR hub

Startup.cs

...
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddControllers();
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseRouting();
            app.UseStaticFiles();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapBlazorHub();
            });
        }
...

_Imports.razor

@using System
@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using BlazorSample

BlzrSample.razor

<h1>Hello @Name!</h1>
<p>This is Blazor Components.</p>
<div>@SampleInfo</div>
<button @onclick="ShowConsole">Click</button>
@code {
    [Parameter]
    public string Name {get; set;}
    [Parameter]
    public string SampleInfo { get; set; }
    public string GetName(){
        var name = $"{Name} {DateTime.Now}";
        Console.WriteLine(name);
        return name;
    }
    public void ShowConsole()
    {
        SampleInfo = GetName();
        Console.WriteLine($"{SampleInfo}");
    }
}

Index.cshtml

@using BlazorSample.Views
@{
    ViewData["Title"] = "Home Page";
}
<h2>This is Index.cshtml</h2>
@(await Html.RenderComponentAsync<BlzrSample>(RenderMode.ServerPrerendered, new { Name = "BlazorSample" }))

Result

Alt Text

When I click the button, div element value will be changed dynamically.

IE11

Though IE11 can render the page, the click event isn't worked.
One of the solution is using Blazor.Polyfill.

_Layout.cshtml

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>@ViewData["Title"]</title>
        <base href="/">
        <script type="text/javascript">
            if (/MSIE \d|Trident.*rv:/.test(navigator.userAgent)) {
                document.write('<script src="https://polyfill.io/v3/polyfill.min.js?features=Element.prototype.closest%2CIntersectionObserver%2Cdocument.querySelector%2Cfeatures=Array.prototype.forEach%2CNodeList.prototype.forEach"><\/script>');
                document.write('<script src="js/blazor.polyfill.min.js"><\/script>');
            }
        </script>
    </head>
    <body>
        @RenderBody()
    <script src="_framework/blazor.server.js"></script>
    </body>
</html>

I must add "base" tag or get an error.

Control DOM

C#(Blazor) methods can't access DOM directly except using @foreach , etc.

@* when I add or remove items into the "Numbers",
the numbers of div elements will be changed *@
@foreach(var n in Numbers)
{
    <div>@SampleInfo</div>
}

So I should call JavaScript methods to control DOM elements.

Interacts with JavaScript

Call JavaScript from Blazor

I can call JavaScript methods by IJSRuntime.
Because I usually use, I add webpack and TypeScript.

Samples

package.json

{
    "dependencies": {
        "ts-loader": "^8.0.2",
        "tsc": "^1.20150623.0",
        "typescript": "^3.9.7",
        "webpack": "^4.44.1",
        "webpack-cli": "^3.3.12"
    }
}

webpack.config.js

var path = require('path');

module.exports = {
    mode: 'development',
    entry: {
        'blazorSample': './wwwroot/ts/blazor-sample.ts',
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/
            }
        ]
    },
    resolve: {
        extensions: [ '.tsx', '.ts', '.js' ]
    },
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, './wwwroot/js'),
        library: 'Page',
        libraryTarget: 'umd'
    }
};

blazor-sample.ts

export function callFromBlazor(name: string): string
{
    console.log("Hello Blazor");
    return `Hello Blazor ${name}`;
}

BlzrSample.razor

@inject IJSRuntime JSRuntime;
...
<button @onclick="CallFromBlazor">Click2</button>

@code {
...
    private async Task CallFromBlazor()
    {
        var result = await JSRuntime.InvokeAsync<string>("Page.callFromBlazor", SampleInfo);
        Console.WriteLine(result);
    }
}

Call C# static methods from JavaScript

I can call c# static methods by DotNet.invokeMethodAsync.
First, I add type definition.

npm install --save @types/blazor__javascript-interop

blazor-sample.ts

...
export function callStaticFromJs() {
    // 1. Assembly name, 2. Method name, 3. Arguments
    DotNet.invokeMethodAsync("BlazorSample", "CallStaticFromJs", "FromJS")
        .then(result => console.log(result));
}

BlzrSample.razor

...
<button onclick="Page.callStaticFromJs()">Click3</button>

@code {
...
    // must add JSInvokable and set public static method.
    [JSInvokable]
    public static async Task<string> CallStaticFromJs(string message)
    {
        Console.WriteLine($"Hello JS {message}");
        return await Task.FromResult("Hello static");
    }
...

Call C# instance methods from JavaScript

To call C# instance methods, I shall create instance in C# methods, and send it to JavaScript methods.

HelloHelper.cs

using Microsoft.JSInterop;

public class HelloHelper
{
    public HelloHelper(string name)
    {
        Name = name;
    }
    public string Name { get; set; }
    // call from JavaScript
    [JSInvokable]
    public string SayHello() => $"Hello, {Name}!";
}

blazor-sample.ts

...
// argument: HelloHelper instance
export function callInstanceFromJs(dotnetHelper: any) {
    return dotnetHelper.invokeMethodAsync('SayHello')
      .then((r: any) => console.log(r));
}

BlzrSample.razor

@inject IJSRuntime JSRuntime;
...
<button @onclick="CallInstanceFromJs">Click4</button>

@code {
...
    public async Task CallInstanceFromJs()
    {
        using(var helperRef = DotNetObjectReference.Create(new HelloHelper("Js instance")))
        {
            await JSRuntime.InvokeAsync<string>(
                "Page.callInstanceFromJs", helperRef);
        }  
    }
...

Though I use using clause to create instance, in actual usages, I may separate disposing the instance from the creating method.

Call server side methods

How to call server side methods from Blazor?
Blazor can use DI.
So I can inject service classes or any other classes.

IBlazorService.cs

using System.Threading.Tasks;
using Models;

namespace Services
{
    public interface IBlazorService
    {
        Task<Product> GetMessageAsync(string Name);
    }
}

BlazorService.cs

using System.Threading.Tasks;
using Models;
namespace Services
{
    public class BlazorService: IBlazorService
    {
        public async Task<Product> GetMessageAsync(string name)
        {
            return await Task.FromResult(new Product
            {
                Id = 2,
                Name = name,
            });
        }
    }
}

Startup.cs

...
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
        services.AddServerSideBlazor();
        services.AddControllers();
        services.AddScoped<IBlazorService, BlazorService>();
    }
...

BlzrSample.razor

@inject Services.IBlazorService _blazor
...
<button @onclick="CallServerside">Click5</button>

@code {
...
    public async Task CallServerside()
    {
        var result = await _blazor.GetMessageAsync(Name);
        if (result == null)
        {
            Console.WriteLine("Cannot get message");
            return;
        }
        Console.WriteLine($"Success ID: {result.Id} Name: {result.Name}");
    }
}

Discussion

pic
Editor guide
Collapse
emadsaber profile image
emadsaber

I love Blazor and I started applying it in a real project. This post is a great help for me.

Thanks ♥

Collapse
masanori_msl profile image
Masui Masanori Author

Thank you for reading my post.
Because I'm new to writing "Blazor" application, so I want to try more :)