Bagikan melalui


Tata letak penyebaran untuk aplikasi yang dihosting Blazor WebAssembly ASP.NET Core

Artikel ini menjelaskan cara mengaktifkan penyebaran yang dihosting Blazor WebAssembly di lingkungan yang memblokir pengunduhan dan eksekusi file pustaka tautan dinamis (DLL).

Catatan

Panduan ini membahas lingkungan yang memblokir klien agar tidak mengunduh dan menjalankan DLL. Di .NET 8 atau yang lebih baru, Blazor menggunakan format file Webcil untuk mengatasi masalah ini. Untuk informasi selengkapnya, lihat Host dan sebarkan ASP.NET Core Blazor WebAssembly. Bundling multipihak menggunakan paket NuGet eksperimental yang dijelaskan oleh artikel ini tidak didukung untuk Blazor aplikasi di .NET 8 atau yang lebih baru. Untuk informasi selengkapnya, lihat Meningkatkan paket Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle untuk menentukan format bundel kustom (dotnet/aspnetcore #36978). Anda dapat menggunakan panduan dalam artikel ini untuk membuat paket NuGet bundling multipart Anda sendiri untuk .NET 8 atau yang lebih baru.

Blazor WebAssembly aplikasi memerlukan pustaka tautan dinamis (DLL) agar berfungsi, tetapi beberapa lingkungan memblokir klien agar tidak mengunduh dan menjalankan DLL. Dalam subset lingkungan ini, mengubah ekstensi nama file file DLL (.dll) cukup untuk melewati pembatasan keamanan, tetapi produk keamanan sering kali dapat memindai konten file yang melintas jaringan dan memblokir atau mengkarantina file DLL. Artikel ini menjelaskan satu pendekatan untuk mengaktifkan Blazor WebAssembly aplikasi di lingkungan ini, di mana file bundel multibagian dibuat dari DLL aplikasi sehingga DLL dapat diunduh bersama-sama melewati pembatasan keamanan.

Aplikasi yang dihosting Blazor WebAssembly dapat menyesuaikan file dan pengemasan DLL aplikasi yang diterbitkan menggunakan fitur berikut:

  • Penginisialisasi JavaScript yang memungkinkan penyesuaian Blazor proses boot.
  • Ekstensibilitas MSBuild untuk mengubah daftar file yang diterbitkan dan menentukan Blazor Publish Extensions. Blazor Ekstensi Publikasi adalah file yang ditentukan selama proses penerbitan yang memberikan representasi alternatif untuk kumpulan file yang diperlukan untuk menjalankan aplikasi yang diterbitkan Blazor WebAssembly . Dalam artikel ini, Blazor Ekstensi Publikasi dibuat yang menghasilkan bundel multibagian dengan semua DLL aplikasi yang dikemas ke dalam satu file sehingga DLL dapat diunduh bersama-sama.

Pendekatan yang ditunjukkan dalam artikel ini berfungsi sebagai titik awal bagi pengembang untuk merancang strategi mereka sendiri dan proses pemuatan kustom.

Peringatan

Setiap pendekatan yang diambil untuk menghindari pembatasan keamanan harus dipertimbangkan dengan hati-hati karena implikasi keamanannya. Sebaiknya jelajahi subjek lebih lanjut dengan profesional keamanan jaringan organisasi Anda sebelum mengadopsi pendekatan dalam artikel ini. Alternatif yang perlu dipertimbangkan meliputi:

  • Aktifkan appliance keamanan dan perangkat lunak keamanan untuk mengizinkan klien jaringan mengunduh dan menggunakan file yang tepat yang Blazor WebAssembly diperlukan oleh aplikasi.
  • Beralih dari Blazor WebAssembly model hosting ke Blazor Server model hosting, yang mempertahankan semua kode C# aplikasi di server dan tidak memerlukan pengunduhan DLL ke klien. Blazor Server juga menawarkan keuntungan menjaga kode C# tetap privat tanpa memerlukan penggunaan aplikasi API web untuk privasi kode C# dengan Blazor WebAssembly aplikasi.

Paket NuGet eksperimental dan aplikasi sampel

Pendekatan yang dijelaskan dalam artikel ini digunakan oleh paket eksperimentalMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundle (NuGet.org) untuk aplikasi yang menargetkan .NET 6 atau yang lebih baru. Paket berisi target MSBuild untuk menyesuaikan Blazor output penerbitan dan penginisialisasi JavaScript untuk menggunakan pemuat sumber daya boot kustom, yang masing-masing dijelaskan secara rinci nanti dalam artikel ini.

Kode eksperimental (termasuk sumber referensi paket NuGet dan CustomPackagedApp aplikasi sampel)

Peringatan

Fitur eksperimental dan pratinjau disediakan untuk tujuan mengumpulkan umpan balik dan tidak didukung untuk penggunaan produksi.

Kemudian dalam artikel ini, bagian Kustomisasi Blazor WebAssembly proses pemuatan melalui paket NuGet dengan tiga subbagiannya memberikan penjelasan terperinci tentang konfigurasi dan kode dalam Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle paket. Penjelasan terperinci penting untuk dipahami saat Anda membuat strategi anda sendiri dan proses pemuatan kustom untuk Blazor WebAssembly aplikasi. Untuk menggunakan paket NuGet yang diterbitkan, eksperimental, tidak didukung tanpa kustomisasi sebagai demonstrasi lokal, lakukan langkah-langkah berikut:

  1. Gunakan solusi yang dihosting Blazor WebAssemblyyang ada atau buat solusi baru dari Blazor WebAssembly templat proyek menggunakan Visual Studio atau dengan meneruskan -ho|--hosted opsi ke dotnet new perintah (dotnet new blazorwasm -ho). Untuk informasi selengkapnya, lihat Alat untuk ASP.NET Core Blazor.

  2. Client Dalam proyek, tambahkan paket eksperimentalMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundle.

    Catatan

    Untuk panduan tentang menambahkan paket ke aplikasi .NET, lihat artikel di bagian Menginstal dan mengelola paket di Alur kerja konsumsi paket (dokumentasi NuGet). Konfirmasikan versi paket yang benar di NuGet.org.

  3. Server Dalam proyek, tambahkan titik akhir untuk melayani file bundel (app.bundle). Contoh kode dapat ditemukan di bagian Sajikan bundel dari aplikasi server host di artikel ini.

  4. Terbitkan aplikasi dalam Konfigurasi rilis.

Blazor WebAssembly Menyesuaikan proses pemuatan melalui paket NuGet

Peringatan

Panduan di bagian ini dengan tiga subbagiannya berkaitan dengan membangun paket NuGet dari awal untuk menerapkan strategi Anda sendiri dan proses pemuatan kustom. Paket eksperimentalMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundle (NuGet.org) untuk .NET 6 dan 7 didasarkan pada panduan di bagian ini. Saat menggunakan paket yang disediakan dalam demonstrasi lokal pendekatan unduhan bundel multibagian, Anda tidak perlu mengikuti panduan di bagian ini. Untuk panduan tentang cara menggunakan paket yang disediakan, lihat bagian Paket NuGet Eksperimental dan aplikasi sampel.

Blazorsumber daya aplikasi dikemas ke dalam file bundel multipihak dan dimuat oleh browser melalui penginisialisasi JavaScript (JS) kustom. Untuk aplikasi yang menggunakan paket dengan JS penginisialisasi, aplikasi hanya mengharuskan file bundel dilayani saat diminta. Semua aspek lain dari pendekatan ini ditangani secara transparan.

Empat penyesuaian diperlukan untuk bagaimana aplikasi default yang diterbitkan Blazor dimuat:

  • Tugas MSBuild untuk mengubah file terbitkan.
  • Paket NuGet dengan target MSBuild yang terhubung ke dalam Blazor proses penerbitan, mengubah output, dan menentukan satu atau beberapa Blazor file Publish Extension (dalam hal ini, satu bundel).
  • Penginisialisasi JS untuk memperbarui Blazor WebAssembly panggilan balik pemuat sumber daya sehingga memuat bundel dan menyediakan aplikasi dengan file individual.
  • Pembantu pada aplikasi host Server untuk memastikan bahwa bundel dilayani kepada klien berdasarkan permintaan.

Membuat tugas MSBuild untuk mengkustomisasi daftar file yang diterbitkan dan menentukan ekstensi baru

Buat tugas MSBuild sebagai kelas C# publik yang dapat diimpor sebagai bagian dari kompilasi MSBuild dan yang dapat berinteraksi dengan build.

Berikut ini diperlukan untuk kelas C#:

Catatan

Paket NuGet untuk contoh dalam artikel ini dinamai sesuai dengan paket yang disediakan oleh Microsoft, Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle. Untuk panduan tentang penamaan dan pembuatan paket NuGet Anda sendiri, lihat artikel NuGet berikut ini:

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>8.0</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Build.Framework" Version="{VERSION}" />
    <PackageReference Include="Microsoft.Build.Utilities.Core" Version="{VERSION}" />
  </ItemGroup>

</Project>

Tentukan versi paket terbaru untuk {VERSION} tempat penampung di NuGet.org:

Untuk membuat tugas MSBuild, buat perluasan Microsoft.Build.Utilities.Task kelas C# publik (bukan System.Threading.Tasks.Task) dan nyatakan tiga properti:

  • PublishBlazorBootStaticWebAsset: Daftar file yang akan diterbitkan untuk Blazor aplikasi.
  • BundlePath: Jalur tempat bundel ditulis.
  • Extension: Ekstensi Terbitkan baru untuk disertakan dalam build.

Contoh kelas berikut BundleBlazorAssets adalah titik awal untuk penyesuaian lebih lanjut:

  • Dalam metode , Execute bundel dibuat dari tiga jenis file berikut:
    • File JavaScript (dotnet.js)
    • File WASM (dotnet.wasm)
    • DLL Aplikasi (.dll)
  • Bundel multipart/form-data dibuat. Setiap file ditambahkan ke bundel dengan deskripsi masing-masing melalui header Content-Disposition dan header Content-Type.
  • Setelah bundel dibuat, bundel ditulis ke file.
  • Build dikonfigurasi untuk ekstensi. Kode berikut membuat item ekstensi dan menambahkannya ke Extension properti . Setiap item ekstensi berisi tiga bagian data:
    • Jalur ke file ekstensi.
    • Jalur URL relatif terhadap akar Blazor WebAssembly aplikasi.
    • Nama ekstensi, yang mengelompokkan file yang dihasilkan oleh ekstensi tertentu.

Setelah mencapai tujuan sebelumnya, tugas MSBuild dibuat untuk menyesuaikan Blazor output penerbitan. Blazor mengurus pengumpulan ekstensi dan memastikan bahwa ekstensi disalin ke lokasi yang benar di folder output penerbitan (misalnya, bin\Release\net6.0\publish). Pengoptimalan yang sama (misalnya, kompresi) diterapkan ke file JavaScript, WASM, dan DLL seperti Blazor yang berlaku untuk file lain.

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks/BundleBlazorAssets.cs:

using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks
{
    public class BundleBlazorAssets : Task
    {
        [Required]
        public ITaskItem[]? PublishBlazorBootStaticWebAsset { get; set; }

        [Required]
        public string? BundlePath { get; set; }

        [Output]
        public ITaskItem[]? Extension { get; set; }

        public override bool Execute()
        {
            var bundle = new MultipartFormDataContent(
                "--0a7e8441d64b4bf89086b85e59523b7d");

            foreach (var asset in PublishBlazorBootStaticWebAsset)
            {
                var name = Path.GetFileName(asset.GetMetadata("RelativePath"));
                var fileContents = File.OpenRead(asset.ItemSpec);
                var content = new StreamContent(fileContents);
                var disposition = new ContentDispositionHeaderValue("form-data");
                disposition.Name = name;
                disposition.FileName = name;
                content.Headers.ContentDisposition = disposition;
                var contentType = Path.GetExtension(name) switch
                {
                    ".js" => "text/javascript",
                    ".wasm" => "application/wasm",
                    _ => "application/octet-stream"
                };
                content.Headers.ContentType = 
                    MediaTypeHeaderValue.Parse(contentType);
                bundle.Add(content);
            }

            using (var output = File.Open(BundlePath, FileMode.OpenOrCreate))
            {
                output.SetLength(0);
                bundle.CopyToAsync(output).ConfigureAwait(false).GetAwaiter()
                    .GetResult();
                output.Flush(true);
            }

            var bundleItem = new TaskItem(BundlePath);
            bundleItem.SetMetadata("RelativePath", "app.bundle");
            bundleItem.SetMetadata("ExtensionName", "multipart");

            Extension = new ITaskItem[] { bundleItem };

            return true;
        }
    }
}

Menulis paket NuGet untuk mengubah output penerbitan secara otomatis

Buat paket NuGet dengan target MSBuild yang secara otomatis disertakan saat paket dirujuk:

  • Buat proyek pustaka kelas (RCL) baru.Razor
  • Buat file target setelah konvensi NuGet untuk mengimpor paket secara otomatis dalam mengonsumsi proyek. Misalnya, buat build\net6.0\{PACKAGE ID}.targets, di mana {PACKAGE ID} adalah pengidentifikasi paket paket.
  • Kumpulkan output dari pustaka kelas yang berisi tugas MSBuild dan konfirmasi output dikemas di lokasi yang tepat.
  • Tambahkan kode MSBuild yang diperlukan untuk dilampirkan ke Blazor alur dan panggil tugas MSBuild untuk menghasilkan bundel.

Pendekatan yang dijelaskan di bagian ini hanya menggunakan paket untuk mengirimkan target dan konten, yang berbeda dari sebagian besar paket di mana paket menyertakan DLL pustaka.

Peringatan

Paket sampel yang dijelaskan di bagian ini menunjukkan cara menyesuaikan proses penerbitan Blazor . Paket NuGet sampel hanya untuk digunakan sebagai demonstrasi lokal. Penggunaan paket ini dalam produksi tidak didukung.

Catatan

Paket NuGet untuk contoh dalam artikel ini dinamai sesuai dengan paket yang disediakan oleh Microsoft, Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle. Untuk panduan tentang penamaan dan pembuatan paket NuGet Anda sendiri, lihat artikel NuGet berikut ini:

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.csproj:

<Project Sdk="Microsoft.NET.Sdk.Razor">

  <PropertyGroup>
    <NoWarn>NU5100</NoWarn>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <Description>
      Sample demonstration package showing how to customize the Blazor publish 
      process. Using this package in production is not supported!
    </Description>
    <IsPackable>true</IsPackable>
    <IsShipping>true</IsShipping>
    <IncludeBuildOutput>false</IncludeBuildOutput>
  </PropertyGroup>

  <ItemGroup>
    <None Update="build\**" 
          Pack="true" 
          PackagePath="%(Identity)" />
    <Content Include="_._" 
             Pack="true" 
             PackagePath="lib\net6.0\_._" />
  </ItemGroup>

  <Target Name="GetTasksOutputDlls" 
          BeforeTargets="CoreCompile">
    <MSBuild Projects="..\Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks\Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.csproj" 
             Targets="Publish;PublishItemsOutputGroup" 
             Properties="Configuration=Release">
      <Output TaskParameter="TargetOutputs" 
              ItemName="_TasksProjectOutputs" />
    </MSBuild>
    <ItemGroup>
      <Content Include="@(_TasksProjectOutputs)" 
               Condition="'%(_TasksProjectOutputs.Extension)' == '.dll'" 
               Pack="true" 
               PackagePath="tasks\%(_TasksProjectOutputs.TargetPath)" 
               KeepMetadata="Pack;PackagePath" />
    </ItemGroup>
  </Target>

</Project>

Catatan

Properti <NoWarn>NU5100</NoWarn> dalam contoh sebelumnya menekan peringatan tentang rakitan yang ditempatkan di tasks folder. Untuk informasi selengkapnya, lihat NuGet Warning NU5100.

.targets Tambahkan file untuk menyambungkan tugas MSBuild ke alur build. Dalam file ini, tujuan berikut dicapai:

  • Impor tugas ke dalam proses build. Perhatikan bahwa jalur ke DLL relatif terhadap lokasi utama file dalam paket.
  • Properti ComputeBlazorExtensionsDependsOn melampirkan target kustom ke Blazor WebAssembly alur.
  • Extension Ambil properti pada output tugas dan tambahkan ke untuk BlazorPublishExtension memberi tahu Blazor tentang ekstensi. Memanggil tugas di target menghasilkan bundel. Daftar file yang diterbitkan disediakan oleh Blazor WebAssembly alur dalam PublishBlazorBootStaticWebAsset grup item. Jalur bundel didefinisikan menggunakan IntermediateOutputPath (biasanya di obj dalam folder). Pada akhirnya, bundel disalin secara otomatis ke lokasi yang benar di folder output penerbitan (misalnya, bin\Release\net6.0\publish).

Ketika paket dirujuk, paket menghasilkan bundel file selama penerbitan Blazor .

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/build/net6.0/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.targets:

<Project>
  <UsingTask 
    TaskName="Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.BundleBlazorAssets" 
    AssemblyFile="$(MSBuildThisProjectFileDirectory)..\..\tasks\Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.dll" />

  <PropertyGroup>
    <ComputeBlazorExtensionsDependsOn>
      $(ComputeBlazorExtensionsDependsOn);_BundleBlazorDlls
    </ComputeBlazorExtensionsDependsOn>
  </PropertyGroup>

  <Target Name="_BundleBlazorDlls">
    <BundleBlazorAssets
      PublishBlazorBootStaticWebAsset="@(PublishBlazorBootStaticWebAsset)"
      BundlePath="$(IntermediateOutputPath)bundle.multipart">
      <Output TaskParameter="Extension" 
              ItemName="BlazorPublishExtension"/>
    </BundleBlazorAssets>
  </Target>

</Project>

Bootstrap Blazor secara otomatis dari bundel

Paket NuGet memanfaatkan penginisialisasi JavaScript (JS) untuk secara otomatis melakukan bootstrap Blazor WebAssembly aplikasi dari bundel alih-alih menggunakan file DLL individual. JS penginisialisasi digunakan untuk mengubah Blazorpemuat sumber daya boot dan menggunakan bundel.

Untuk membuat JS penginisialisasi, tambahkan JS file dengan nama {NAME}.lib.module.js ke wwwroot folder proyek paket, di mana {NAME} tempat penampung adalah pengidentifikasi paket. Misalnya, file untuk paket Microsoft diberi nama Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js. Fungsi yang beforeWebAssemblyStart diekspor dan afterWebAssemblyStarted menangani pemuatan.

Penginisialisasi JS :

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/wwwroot/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js:

const resources = new Map();

export async function beforeWebAssemblyStart(options, extensions) {
  if (!extensions || !extensions.multipart) {
    return;
  }

  try {
    const integrity = extensions.multipart['app.bundle'];
    const bundleResponse = 
      await fetch('app.bundle', { integrity: integrity, cache: 'no-cache' });
    const bundleFromData = await bundleResponse.formData();
    for (let value of bundleFromData.values()) {
      resources.set(value, URL.createObjectURL(value));
    }
    options.loadBootResource = function (type, name, defaultUri, integrity) {
      return resources.get(name) ?? null;
    }
  } catch (error) {
    console.log(error);
  }
}

export async function afterWebAssemblyStarted(blazor) {
  for (const [_, url] of resources) {
    URL.revokeObjectURL(url);
  }
}

Untuk membuat JS penginisialisasi, tambahkan JS file dengan nama {NAME}.lib.module.js ke wwwroot folder proyek paket, di mana {NAME} tempat penampung adalah pengidentifikasi paket. Misalnya, file untuk paket Microsoft diberi nama Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js. Fungsi yang beforeStart diekspor dan afterStarted menangani pemuatan.

Penginisialisasi JS :

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/wwwroot/Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js:

const resources = new Map();

export async function beforeStart(options, extensions) {
  if (!extensions || !extensions.multipart) {
    return;
  }

  try {
    const integrity = extensions.multipart['app.bundle'];
    const bundleResponse = 
      await fetch('app.bundle', { integrity: integrity, cache: 'no-cache' });
    const bundleFromData = await bundleResponse.formData();
    for (let value of bundleFromData.values()) {
      resources.set(value, URL.createObjectURL(value));
    }
    options.loadBootResource = function (type, name, defaultUri, integrity) {
      return resources.get(name) ?? null;
    }
  } catch (error) {
    console.log(error);
  }
}

export async function afterStarted(blazor) {
  for (const [_, url] of resources) {
    URL.revokeObjectURL(url);
  }
}

Menyajikan bundel dari aplikasi server host

Karena pembatasan keamanan, ASP.NET Core tidak melayani app.bundle file secara default. Pembantu pemrosesan permintaan diperlukan untuk melayani file saat diminta oleh klien.

Catatan

Karena pengoptimalan yang sama diterapkan secara transparan ke Ekstensi Terbitkan yang diterapkan ke file aplikasi, app.bundle.gz file aset yang dikompresi dan app.bundle.br diproduksi secara otomatis saat diterbitkan.

Tempatkan kode C# dalam Program.csServer proyek segera sebelum baris yang mengatur file fallback ke index.html (app.MapFallbackToFile("index.html");) untuk merespons permintaan file bundel (misalnya, app.bundle):

app.MapGet("app.bundle", (HttpContext context) =>
{
    string? contentEncoding = null;
    var contentType = 
        "multipart/form-data; boundary=\"--0a7e8441d64b4bf89086b85e59523b7d\"";
    var fileName = "app.bundle";

    var acceptEncodings = context.Request.Headers.AcceptEncoding;

    if (Microsoft.Net.Http.Headers.StringWithQualityHeaderValue
        .StringWithQualityHeaderValue
        .TryParseList(acceptEncodings, out var encodings))
    {
        if (encodings.Any(e => e.Value == "br"))
        {
            contentEncoding = "br";
            fileName += ".br";
        }
        else if (encodings.Any(e => e.Value == "gzip"))
        {
            contentEncoding = "gzip";
            fileName += ".gz";
        }
    }

    if (contentEncoding != null)
    {
        context.Response.Headers.ContentEncoding = contentEncoding;
    }

    return Results.File(
        app.Environment.WebRootFileProvider.GetFileInfo(fileName)
            .CreateReadStream(), contentType);
});

Tipe konten cocok dengan jenis yang ditentukan sebelumnya dalam tugas build. Titik akhir memeriksa pengodean konten yang diterima oleh browser dan melayani file optimal, Brotli (.br) atau Gzip (.gz).