Blazor WebAssembly 漸進式 Web 應用程式 (PWA) 離線處理資料

AlexChow發表於2022-04-12

原文連結:https://www.cnblogs.com/densen2014/p/16133343.html

Window.localStorage

只讀的localStorage 屬性允許你訪問一個Document 源(origin)的物件 Storage;儲存的資料將儲存在瀏覽器會話中。

儲存在 localStorage 的資料可以長期保留

localStorage 中的鍵值對總是以字串的形式儲存。 (需要注意, 和js物件相比, 鍵值對總是以字串的形式儲存意味著數值型別會自動轉化為字串型別).

瀏覽器相容性

桌面:

Chrome,
Edge,
Firefox,
Internet Explorer,
Opera,
Safari

移動端:

WebView Android,
Chrome Android,
Firefox for Android,
Opera Android,
Safari on iOS,
Samsung Internet

Blazor WebAssembly

Blazor WebAssembly 用於使用 .NET 生成互動式客戶端 Web 應用。 Blazor WebAssembly 使用無外掛或將程式碼重新編譯為其他語言的開放式 Web 標準。 Blazor WebAssembly 適用於所有新式 Web 瀏覽器,包括移動瀏覽器。

通過 WebAssembly(縮寫為 wasm),可在 Web 瀏覽器內執行 .NET 程式碼。 WebAssembly 是針對快速下載和最大執行速度優化的壓縮位元組碼格式。 WebAssembly 是開放的 Web 標準,支援用於無外掛的 Web 瀏覽器。

WebAssembly 程式碼可通過 JavaScript(稱為 JavaScript 互操作性,通常簡稱為 JavaScript 互操作或 JS 互操作)訪問瀏覽器的完整功能 。 通過瀏覽器中的 WebAssembly 執行的 .NET 程式碼在瀏覽器的 JavaScript 沙盒中執行,沙盒提供的保護可防禦客戶端計算機上的惡意操作。

當 Blazor WebAssembly 應用生成並在瀏覽器中執行時:

  • C# 程式碼檔案和 Razor 檔案將被編譯為 .NET 程式集。
  • 該程式集和 .NET 執行時將被下載到瀏覽器。
  • Blazor WebAssembly 啟動 .NET 執行時,並配置執行時,以為應用載入程式集。 Blazor WebAssembly 執行時使用 JavaScript 互操作來處理 DOM 操作和瀏覽器 API 呼叫。

已釋出應用的大小(其有效負載大小)是應用可用性的關鍵效能因素。 大型應用需要相對較長的時間才能下載到瀏覽器,這會損害使用者體驗。 Blazor WebAssembly 優化有效負載大小,以縮短下載時間:

  • 在中間語言 (IL) 裁邊器釋出應用時,會從應用刪除未使用的程式碼。
  • 壓縮 HTTP 響應。
  • .NET 執行時和程式集快取在瀏覽器中。

漸進式 Web 應用程式 (PWA)

Blazor 漸進式 Web 應用 (PWA) 是一種單頁應用程式 (SPA),它使用新式瀏覽器 API 和功能以表現得如桌面應用。

Blazor WebAssembly 是基於標準的客戶端 Web 應用平臺,因此它可以使用任何瀏覽器 API,包括以下功能所需的 PWA API:

  • 離線工作並即時載入(不受網路速度影響)。
  • 在自己的應用視窗中執行,而不僅僅是在瀏覽器視窗中執行。
  • 從主機作業系統的開始選單、擴充套件塢或主螢幕啟動。
  • 從後端伺服器接收推送通知,即使使用者沒有在使用該應用。
  • 在後臺自動更新。

使用“漸進式”一詞來描述這些應用的原因如下:

  • 使用者可能先是在其網路瀏覽器中發現應用並使用它,就像任何其他單頁應用程式一樣。
  • 過了一段時間後,使用者進而將其安裝到作業系統中並啟用推送通知。

實現

這次主要以大家談論比較多的 WASM PWA 為例子,其實 wasm 或 ssr 工程都是可以的完整執行的.

1.新建工程n04LocalStorage_wasm

新建專案選擇 Blazor WebAssembly 應用 ,請選中 漸進式 Web 應用 核取方塊, 工程命名為 'n04LocalStorage_wasm'

然後右鍵工程, 管理Nugget程式包新增Blazored.LocalStorage庫到工程中.

或者.NET CLI

dotnet new blazorwasm -o n04LocalStorage_wasm --pwa
dotnet add n04LocalStorage_wasm package Blazored.LocalStorage
dotnet sln add n04LocalStorage_wasm/n04LocalStorage_wasm.csproj

ssr參考

dotnet new blazorserver -o n04LocalStorage
dotnet add n04LocalStorage package Blazored.LocalStorage
dotnet sln add n04LocalStorage/n04LocalStorage.csproj

話不多說,直接上簡單測試程式碼

1. 新增服務

Program.cs

using Blazored.LocalStorage;

builder.Services.AddBlazoredLocalStorage();

2. Index.razor

注入服務,編寫兩個方法

@using Blazored.LocalStorage;

@code{
    [Inject] ILocalStorageService? localStore { get; set; }


    const string noteKey = "note";
    string? noteContent;

    public async void UpdateLocalStorage()
    {
        await localStore!.SetItemAsync(noteKey, noteContent);
    }

    public async void ClearLocalStorage()
    {
        noteContent = "";
        await localStore!.ClearAsync();
    }
}

3. 頁面使用js需要在OnAfterRenderAsync裡執行, 如果在不對的生命週期裡面執行,會有這句報錯提示,剛開始學blazor的同學應該都有遇到過

InvalidOperationException: JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendered. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method.

4. 讀取LocalStorage的物件到noteContent,然後重新整理頁面.

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            noteContent = await localStore!.GetItemAsync<string>(noteKey);
            StateHasChanged();
        }
    }

5. 文字域繫結變數,儲存到LocalStorage

<textarea @bind="noteContent" />
<br />
<button @onclick="UpdateLocalStorage">Save</button>
<button @onclick="ClearLocalStorage">Clear</button>

瀏覽器按F12,檢視應用,本地儲存空間,每次儲存按下,觀察效果.

重啟程式,看看是否能保持上次寫入的文字

cun

6.改造 FetchData

WeatherForecast類定義

public class WeatherForecast
{
    public DateTime Date { get; set; }=DateTime.Now;

    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    public string? Summary { get; set; } = "陽光明媚";

    public string? SkyColor { get; set; } 

}

常規CRUD操作

    [Inject] ILocalStorageService? localStore { get; set; }


    private List<WeatherForecast>? forecasts;
    private WeatherForecast? one = new WeatherForecast();

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {

            forecasts = await localStore!.GetItemAsync<List<WeatherForecast>>("forecasts");
            if (forecasts == null)
            {
                forecasts = new List<WeatherForecast>();
                await localStore!.SetItemAsync("forecasts", forecasts);
            }

            StateHasChanged();

        }
    }

    async void Add()
    {
        forecasts!.Add(one!);
        one = new WeatherForecast();
        await localStore!.SetItemAsync("forecasts", forecasts);
    }
    async void Edit()
    {
        await localStore!.SetItemAsync("forecasts", forecasts);
    }
    async void Delete(WeatherForecast weather)
    {
        forecasts!.Remove(weather);
        await localStore!.SetItemAsync("forecasts", forecasts);
    }

    async void Clear()
    {
        forecasts!.Clear();
        await localStore!.ClearAsync();
    }

頁面


<div style="background-color :lightblue">

    <p>
        日期
        <input type="datetime-local" @bind-value="one!.Date" />
    </p>
    <p>
        溫度
        <input type="number" @bind-value="one!.TemperatureC" />
    </p>
    <p>
        <input @bind-value="one!.Summary" />
    </p>
    <p>
        <input type="color" @bind-value="one!.SkyColor" />
    </p>
    <button @onclick="Add" class="btn btn-primary">新添</button>
</div>

@if (forecasts == null)
{
    <p><em>無資料...</em></p>
}
else
{


    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
                <th>SkyColor</th>
                <th></th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>
                        <input @bind-value="forecast.Date" />
                    </td>
                    <td>
                        <input @bind-value="forecast.TemperatureC" />
                    </td>
                    <td>@forecast.TemperatureF</td>
                    <td>
                        <input @bind-value="forecast.Summary" />
                    </td>
                    <td>
                        <input type="color" @bind-value="forecast.SkyColor" />
                    </td>
                    <td>
                        <button @onclick="Edit" class="btn btn-primary">編輯</button>
                    </td>
                    <td>
                        <button @onclick="(()=>Delete(forecast))" class="btn btn-warning">刪除</button>
                    </td>
                </tr>
            }
        </tbody>
    </table>

    <button @onclick="Clear" class="btn btn-danger">清空</button>

}

執行看看效果吧

cun2

7. 把頁面弄到手機上試試, 傳送到桌面還可以假裝成APP :->

QQ截圖20220411193800

Properties , launchSettings.json修改這句

 "applicationUrl": "https://localhost:7286;http://localhost:5274;https://0.0.0.0:7286;http://0.0.0.0:5274",

手機訪問 http://192.168.1.103:5274/ 192.168.1.103替換為你機器ip

8. 離線執行PWA

據我測試,需要部署到域名, demo https://testbrpwa.app1.es/

離線

參考資料 :
PWA 網站離線訪問 https://www.jianshu.com/p/f10e72797d25
PWA離線化技術介紹 https://juejin.cn/post/6990937987697606669

專案原始碼

Github | Gitee

關聯專案

FreeSql QQ群:4336577(已滿)、8578575(已滿)、52508226(線上)

BA & Blazor QQ群:795206915、675147445

知識共享許可協議

本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名AlexChow(包含連結: https://github.com/densen2014 ),不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。如有任何疑問,請與我聯絡

AlexChow

今日頭條 | 部落格園 | 知乎 | Gitee | GitHub

相關文章