您是否也有想在瀏覽器中實時的編輯程式碼並且渲染的想法?

2023-01-22 06:00:08

不知道是否有人跟我一樣想在瀏覽器上直接可以動態的編譯blazor的一些元件庫?而不是通過參照NuGet以後才能檢視到效果,並且在使用別人的元件的時候可以在動態的調整元件的一些樣式

不說了開始正文:

本文我們將使用Masa提供的一個元件實現動態編譯github.com直通車 ,執行環境將在WebAssembly中執行,為什麼使用WebAssembly而不是Server呢?首先我們需要先了解這倆種模式的執行原理

WebAssembly:

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

Server:

  • Blazor ServerASP.NET Core 應用中支援在伺服器上託管 Razor 元件。 可通過 SignalR 連線處理 UI 更新。

    執行時停留在伺服器上並處理:

    • 執行應用的 C# 程式碼。
    • 將 UI 事件從瀏覽器傳送到伺服器。
    • 將 UI 更新應用於伺服器傳送回的已呈現的元件。

由於編譯是完全可操作的,存在安全問題,在Server的模式下使用者編譯的環境就是伺服器的環境,這樣使用者就可以通過動態編譯程式碼實現操作侵入安全,問題很嚴重,如果有心人使用對於安全影響過於嚴重,不建議在Server中使用動態編譯

實現我們來建立一個空的WebAssembly專案

mkdir compileRazor
cd compileRazor
dotnet new blazorwasm-empty

使用vs開啟專案新增Masa.Blazor.Extensions.Languages.Razor ,將一下程式碼新增到專案檔案中

    <PackageReference Include="Masa.Blazor.Extensions.Languages.Razor" Version="0.0.1" />

修改Program.cs檔案的程式碼

using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using compileRazor;
using Masa.Blazor.Extensions.Languages.Razor;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

var app = builder.Build();

// 初始化RazorCompile
RazorCompile.Initialized(await GetReference(app.Services), await GetRazorExtension());

await app.RunAsync();

// 新增程式集參照
async Task<List<PortableExecutableReference>?> GetReference(IServiceProvider services)
{
    #region WebAsembly

    // need to add Service
    var httpClient = services.GetService<HttpClient>();

    var portableExecutableReferences = new List<PortableExecutableReference>();
    foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
    {
        try
        {
            //你需要通過網路獲取程式集,應為無法通過程式集目錄獲取
            var stream = await httpClient!.GetStreamAsync($"_framework/{assembly.GetName().Name}.dll");
            if (stream.Length > 0)
            {
                portableExecutableReferences?.Add(MetadataReference.CreateFromStream(stream));
            }
        }
        catch (Exception e) // There may be a 404
        {
            Console.WriteLine(e.Message);
        }
    }

    #endregion

    // 由於WebAssembly和Server返回portableexecutablerreference機制不同,需要分開處理
    return portableExecutableReferences;
}

async Task<List<RazorExtension>> GetRazorExtension()
{
    var razorExtension = new List<RazorExtension>();

    foreach (var asm in typeof(Program).Assembly.GetReferencedAssemblies())
    {
        razorExtension.Add(new AssemblyExtension(asm.FullName, AppDomain.CurrentDomain.Load(asm.FullName)));
    }

    return razorExtension;
}

修改Pages\Index.razor的程式碼


@page "/"
@using Masa.Blazor.Extensions.Languages.Razor;

<button class="button" @onclick="Run">重新整理</button>

<div class="input-container">
    <textarea @bind="Code" type="text" class="input-box" placeholder="請輸入執行程式碼" >
    </textarea>
</div>

@if (ComponentType != null)
{
    <DynamicComponent Type="ComponentType"></DynamicComponent>
}

@code{

    private string Code = @"<body>
    <div id='app'>
        <header>
            <h1>Doctor Who&trade; Episode Database</h1>
        </header>

        <nav>
            <a href='main-list'>Main Episode List</a>
            <a href='search'>Search</a>
            <a href='new'>Add Episode</a>
        </nav>

        <h2>Episodes</h2>

        <ul>
            <li>...</li>
            <li>...</li>
            <li>...</li>
        </ul>

        <footer>
            Doctor Who is a registered trademark of the BBC. 
            https://www.doctorwho.tv/
        </footer>
    </div>
</body>";

    private Type? ComponentType;

    private void Run()
    {
        ComponentType = RazorCompile.CompileToType(new CompileRazorOptions()
        {
            Code = Code // TODO: 在WebAssembly下保證ConcurrentBuild是false 因為Webassembly不支援多執行緒
        });
        StateHasChanged();
    }

}

<style>
    .button{
        width: 100%;
        font-size: 22px;
        background-color: cornflowerblue;
        border: 0px;
        margin: 5px;
        border-radius: 5px;
        height: 40px;
    }
    .input-container {
        width: 500px;
        margin: 0 auto;
        padding: 10px;
        border: 1px solid #ccc;
        border-radius: 5px;
    } 
    .input-box {
        width: 100%;
        height: 100px;
        border: 1px solid #ccc;
        border-radius: 5px;
        font-size: 14px;
    }
</style>

然後啟動程式效果如圖:

首次編譯會比較慢,在WebAssembly下還可以因為電腦問題造成卡頓,如果是需要提供開發效率可以使用Server偵錯,在Server偵錯的話是比WebAssembly快很多,而且WebAssembly還沒有做Aot,效能不會太好

來自token的分享

技術交流群:737776595

推薦一個超級好用的Blazor UI元件 MASA Blazor 開源協定 MIT 商用完全沒問題