Blazor寫一次就討好  
  Web & App 開發

老鮑伯

Agenda

  • Blazor Introduction
  • WebRTC demo project

Slides made by Quarto & GitHub Action:

https://github.com/windperson/mwc2023_slide

Blazor Introduction

What is (ASP.NET Core) Blazor

Singla Page Application(SPA) frammework for building interactive client-side web UI using .NET Core/5+ C# without “too much” client-side JavaScript.

  • Component-based architecture
  • Different hosting models:
    • Blazor WebAssembly (Blazor WASM)
      • Blazor PWA (Progressive Web App)
      • Blazor Hybrid
    • Blazor Server
    • United in .NET 8 : static server rendering, streaming rendering, interactive rendering(prerender)

Razor components

A self-contained chunk of user interface (UI), such as a page, dialog, or form.

Includes HTML markup and the processing logic required to inject data or respond to UI events.

  • using .razor file extension:
    • (Optional) routing tag helpers (@page) or @layout directive
    • @using , @implements, @inherits, @inject for C# lang syntax and DI
    • Razor syntax code to present HTML & Razor components (may mixed C# code)
    • @code block for C# code

Razor components example

@page "/fetchdata"
@using BlazorWasmAppDemo.Shared
@inject HttpClient Http

<PageTitle>Weather forecast</PageTitle>

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
    }
}

Tip

  • Component can consist other child components, and control via “Parameters”
  • Component can be nested, reused, shared among projects
  • A component can be made very easily then other traditional UI frameworks

CSS isolation for components

A “scoped” CSS file ( .razor.css ) can be added to a component to provide isolated CSS only for it.

Support CSS Variables, so we can programmatically change CSS style in C# code.

Button.razor.css

button {
    width: var(--btn-width);
    height: var(--btn-height);
    font-size: var(--btn-fontSize);
    color: var(--btn-foreground);
    background-color: var(--btn-background);
}

Button.razor

<button style="--btn-width:@CssAttributes.Width;
               --btn-height:@CssAttributes.Height;
               --btn-fontSize:@CssAttributes.FontSize;
               --btn-foreground:@CssAttributes.ForegroundColor;
               --btn-background:@CssAttributes.BackgroundColor;"
        @onclick="MouseClick">
    @Content
</button>

@code {
    [Parameter]
    public ButtonStyle CssAttributes { get; set; } = null!;

    /* other code */
}

DemoBlazorDynamicCss example project

Data binding

  • One-way data binding
  • Two-way data binding
  • Event binding

Event binding example

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

Routing and Layouts

  • For Browser’s Url routing, using @page directive
  • Kebab-case naming convention
  • Provide a mechanism to show common error page when routing to a non-existing page
<Router AppAssembly="@typeof(App).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

Dependency injection

  • Use the @inject directive to inject services into components.
  • Co-operate with the .NET Core DI container.
var builder = WebAssemblyHostBuilder.CreateDefault(args);

/* other builder config code*/

builder.Services.AddScoped(sp => 
    new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<IPlatformInfo, PlatformInfo>();
builder.Services.AddScoped<BrowserService>();

await builder.Build().RunAsync();

JavaScript interop

We can use Js Interop to co-operate something that Blazor doesn’t support yet, such as WebRTC or WebGL.

Three.js interop demo

Blazor Hybrid on .NET MAUI

VS 2022 project template

The Power of Blazor Hybrid on .NET MAUI is that we can call .NET MAUI API from Blazor @code block.

Reuse UI for Web & App via RCL

By using Razor Class Library(RCL) to share UI components between Web and App.

Project setup

  1. 建立 MAUI (with Hybrid Blazor) 專案
  2. 建立 RCL(Razor Class Library) 專案,並將其加入 MAUI 專案的專案參考 (Project Reference)
  3. 將 MAUI 專案中 Blazor 相關目錄&檔案搬移到 RCL 專案中
  4. 修改還留在 MAUI 專案中的必要 Blazor 所需檔案內容,包括網頁靜態資源引用路徑、Blazor 路由元件增加 AdditionalAssemblies 屬性並指定至搬移到 RCL 專案的 MainLayout 元件
  5. 啟動 MAUI 專案驗證功能是否正常
  6. 用 Visual Studio 2022 專案精靈建立 Blazor WASM 專案及搭配的 asp.net core 測試後端專案
  7. 將 RCL 專案加入到 Blazor WASM 專案的專案參考 (Project Reference)
  8. 搬移 Blazor WASM 專案與 Razor 頁面相關之目錄&檔案,與 RCL 專案中既有的合併或調整
  9. 修改還留在 Blazor WASM 專案組合中的必要 Blazor 所需檔案內容,包括網頁靜態資源引用路徑、Blazor 路由元件增加 AdditionalAssemblies 屬性設定,以及配套 asp.net core 後端專案移除掉不使用的 API Controller, Program.cs 修改為正確的 DI 服務設定
  10. 個別啟動 Blazor WASM、MAUI 專案,驗證功能均可正常運作

Project setup - continued

  • 步驟 6 在專案精靈記得不要選到空白專案,並且最後一步要指定使用 .NET 7,還有勾選『ASP.NET Core託管 (ASP.NET Core Hosted) 』以便產生完整含執行 WASM 之用的 ASP.NET Core 後端專案組合
    • 這麼做的目的是,.NET 7 的 Blazor WASM 完整專案範本產出來預設有『載入進度條』的新功能,此功能所需之 CSS 設定,得合併到 RCL 專案原本從 MAUI 專案搬過來的 app.css 之中
  • 步驟 8 細部動作:
    1. 由專案精靈新建立的 Blazor WASM Client 專案中的 PagesShared 目錄都刪除
    2. 將另一個用來在 Blazor WASM 專案組合中前後端共用的 Data Model (名稱末尾為 .Shared)類別庫(Class Library)專案也刪除
    3. Blazor WASM Client 專案中 wwwroot/css 目錄內除了 app.css 之外的檔案複製到 RCL 專案中的 wwwroot/css 目錄內
    4. Blazor WASM Client 專案中 wwwroot/css/app.css 檔案的部分內容抄寫到 RCL 專案中 wwwroot/css/app.css 檔案內

app.css 21~23 行

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

app.css 70~101 行

.loading-progress {
    position: relative;
    display: block;
    width: 8rem;
    height: 8rem;
    margin: 20vh auto 1rem auto;
}

    .loading-progress circle {
        fill: none;
        stroke: #e0e0e0;
        stroke-width: 0.6rem;
        transform-origin: 50% 50%;
        transform: rotate(-90deg);
    }

        .loading-progress circle:last-child {
            stroke: #1b6ec2;
            stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
            transition: stroke-dasharray 0.05s ease-in-out;
        }

.loading-progress-text {
    position: absolute;
    text-align: center;
    font-weight: bold;
    inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
}

    .loading-progress-text:after {
        content: var(--blazor-load-percentage-text, "Loading");
    }

在 App, Web 都共用 RCL 專案裡定義的 Razor UI 頁面元件,寫一次就同時討好 Web & App UI 開發

原始碼GitHub

WebRTC demo project

Project structure

source: https://github.com/windperson/MWC2023WebRtcDemo

Try to follow the BlazorRtc a Blazor WASM WebRTC demo project, but apply on Blazor Hybrid.

  • Use routing to switch to different Razor components without dependency
  • Layout Icon is from OpenIconic , not Bootstrap Icons.
  • You will got lot of trouble when trying to use some bizarre Js API or need to get around permission problems on MAUI side.

WebRTC

  • The WebRTC API is accessed via Js Interop

  • The js code is messy, GitHub Copilot Chat is merrily to help you 🧑‍💻

{r-stretch}

Conclusion  
  Q & A

Reference