Sdílet prostřednictvím


Rozložení nasazení pro aplikace hostované Blazor WebAssembly v ASP.NET Core

Tento článek vysvětluje, jak povolit hostovaná Blazor WebAssembly nasazení v prostředích, která blokují stahování a spouštění souborů knihovny DLL (Dynamic Link Library).

Poznámka:

Tyto pokyny řeší prostředí, která blokují stahování a spouštění knihoven DLL klientům. V rozhraní .NET 8 nebo novější Blazor používá k vyřešení tohoto problému formát souboru Webcil. Další informace najdete v tématu Hostitel a nasazení ASP.NET Core Blazor WebAssembly. Vícedílné sdružování pomocí experimentálního balíčku NuGet popsaného v tomto článku se nepodporuje pro Blazor aplikace v .NET 8 nebo novějším. Pokyny v tomto článku můžete použít k vytvoření vlastního balíčku NuGet pro vícedílné sdružování pro .NET 8 nebo novější.

Blazor WebAssembly aplikace vyžadují k fungování dynamické knihovny (DLL), ale některá prostředí blokují klientům stahování a spouštění knihoven DLL. V podmnožině těchto prostředí je změna přípony názvu souboru DLL (.dll) dostačující k obejití omezení zabezpečení, ale produkty zabezpečení jsou často schopny kontrolovat obsah souborů procházejících sítí a blokovat nebo umístit soubory DLL do karantény. Tento článek popisuje jeden přístup pro povolení Blazor WebAssembly aplikací v těchto prostředích, kdy se z knihoven DLL aplikace vytvoří soubor sady s více částmi, aby bylo možné knihovny DLL stáhnout společně s obcházením omezení zabezpečení.

Hostovaná Blazor WebAssembly aplikace může přizpůsobit publikované soubory a balení knihoven DLL aplikací pomocí následujících funkcí:

  • Inicializátory JavaScriptu, které umožňují přizpůsobení Blazor procesu spouštění
  • Rozšiřitelnost nástroje MSBuild pro transformaci seznamu publikovaných souborů a definování Blazor rozšíření publikování Blazor Rozšíření publikování jsou soubory definované během procesu publikování, které poskytují alternativní reprezentaci pro sadu souborů potřebných ke spuštění publikované Blazor WebAssembly aplikace. V tomto článku se vytvoří rozšíření pro publikování, Blazor které vytvoří sadu více částí se všemi knihovny DLL aplikace zabalenými do jednoho souboru, aby bylo možné knihovny DLL stáhnout dohromady.

Přístup, který jsme si ukázali v tomto článku, slouží jako výchozí bod pro vývojáře, aby navrhli vlastní strategie a vlastní procesy načítání.

Upozorňující

Každý přístup, který se má vyhnout omezení zabezpečení, je nutné pečlivě zvážit kvůli jeho dopadům na zabezpečení. Než začnete používat přístup v tomto článku, doporučujeme prozkoumat téma s odborníky na zabezpečení sítě ve vaší organizaci. Mezi alternativy, které je potřeba vzít v úvahu, patří:

  • Povolte bezpečnostní zařízení a software zabezpečení, aby mohli síťová klienti stahovat a používat přesné soubory vyžadované Blazor WebAssembly aplikací.
  • Přepněte z Blazor WebAssembly modelu hostování na Blazor Server model hostování, který udržuje veškerý kód jazyka C# aplikace na serveru a nevyžaduje stahování knihoven DLL do klientů. Blazor Server nabízí také výhodu zachování privátního kódu jazyka C#, aniž by bylo nutné používat aplikace webového rozhraní API pro kód privacy jazyka C# s aplikacemi Blazor WebAssembly .

Experimentální balíček NuGet a ukázková aplikace

Přístup popsaný v tomto článku používá experimentálníMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundle balíček (NuGet.org) pro aplikace, které cílí na .NET 6 nebo novější. Balíček obsahuje cíle NÁSTROJE MSBuild pro přizpůsobení výstupu Blazor publikování a inicializátoru JavaScriptu pro použití vlastního zavaděče spouštěcích prostředků, z nichž každý je podrobně popsán dále v tomto článku.

Experimentální kód (zahrnuje zdroj odkazů na balíček NuGet a CustomPackagedApp ukázkovou aplikaci)

Upozorňující

Experimentální a preview funkce jsou k dispozici pro účely shromažďování zpětné vazby a nejsou podporované pro produkční použití.

Později v tomto článku proces načítání můžete přizpůsobit Blazor WebAssembly prostřednictvím oddílu balíčku NuGet se třemi dílčími částmi, které obsahují podrobné vysvětlení konfigurace a kódu v Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle balíčku. Podrobné vysvětlení jsou důležitá k pochopení, když vytváříte vlastní strategii a vlastní proces načítání aplikací Blazor WebAssembly . Pokud chcete použít publikovaný, experimentální nepodporovaný balíček NuGet bez přizpůsobení jako místní ukázku, proveďte následující kroky:

  1. Použijte existující hostované řešení nebo vytvořte nové řešení ze Blazor WebAssembly šablony projektu pomocí sady Visual Studio nebo předáním -ho|--hosted možnosti příkazu dotnet new (dotnet new blazorwasm -ho). Blazor WebAssembly Další informace naleznete v tématu Nástroje pro ASP.NET Core Blazor.

  2. Client V projektu přidejte experimentální Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle balíček.

    Poznámka:

    Pokyny k přidávání balíčků do aplikací .NET najdete v článcích v části Instalace a správa balíčků na webu Pracovní postup používání balíčků (dokumentace k NuGetu). Ověřte správné verze balíčků na NuGet.org.

  3. Server V projektu přidejte koncový bod pro obsluhu souboru sady (app.bundle). Ukázkový kód najdete v sadě z části aplikace hostitelského serveru tohoto článku.

  4. Publikujte aplikaci v konfiguraci vydané verze.

Blazor WebAssembly Přizpůsobení procesu načítání prostřednictvím balíčku NuGet

Upozorňující

Pokyny v této části se třemi dílčími částmi se týkají vytvoření balíčku NuGet od nugetu až po implementaci vlastní strategie a vlastního procesu načítání. Experimentální Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle balíček (NuGet.org) pro .NET 6 a 7 vychází z pokynů v této části. Při použití poskytnutého balíčku v místní ukázce přístupu ke stažení balíčku s více částmi nemusíte postupovat podle pokynů v této části. Pokyny k použití poskytnutého balíčku najdete v části Experimentální balíček NuGet a ukázkovou aplikaci .

BlazorProstředky aplikace se zabalí do souboru balíčku s více částmi a načtou se v prohlížeči prostřednictvím vlastního inicializátoru JavaScriptu (JS). Pro aplikaci, která balíček využívá s inicializátorem JS , aplikace vyžaduje, aby se soubor sady obsluhoval pouze při vyžádání. Všechny ostatní aspekty tohoto přístupu se zpracovávají transparentně.

K načtení výchozí publikované Blazor aplikace se vyžadují čtyři vlastní nastavení:

  • Úloha NÁSTROJE MSBuild pro transformaci souborů publikování.
  • Balíček NuGet s cíli MSBuild, který se připojí k Blazor procesu publikování, transformuje výstup a definuje jeden nebo více Blazor souborů rozšíření publikování (v tomto případě jedna sada).
  • JS Inicializátor pro aktualizaci zpětného volání zavaděče Blazor WebAssembly prostředků tak, aby načetl sadu a poskytuje aplikaci s jednotlivými soubory.
  • Pomocná rutina v hostitelské Server aplikaci, která zajistí, že se sada obsluhuje klientům na vyžádání.

Vytvoření úlohy MSBuild pro přizpůsobení seznamu publikovaných souborů a definování nových rozšíření

Vytvořte úlohu MSBuild jako veřejnou třídu jazyka C#, která se dá importovat jako součást kompilace NÁSTROJE MSBuild a která může s sestavením pracovat.

Pro třídu C# jsou vyžadovány následující:

Poznámka:

Balíček NuGet pro příklady v tomto článku je pojmenován po balíčku poskytovaném Microsoftem, Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle. Pokyny k pojmenování a vytváření vlastního balíčku NuGet najdete v následujících článcích o NuGetu:

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>

Zjistěte nejnovější verze balíčků pro {VERSION} zástupné symboly v NuGet.org:

Chcete-li vytvořit úlohu MSBuild, vytvořte veřejnou třídu jazyka C# rozšiřující Microsoft.Build.Utilities.Task (ne System.Threading.Tasks.Task) a deklarujte tři vlastnosti:

  • PublishBlazorBootStaticWebAsset: Seznam souborů, které se mají publikovat pro Blazor aplikaci.
  • BundlePath: Cesta, kde je sada zapsána.
  • Extension: Nová rozšíření publikování, která se mají zahrnout do sestavení.

Následující ukázková BundleBlazorAssets třída je výchozím bodem pro další přizpůsobení:

  • Execute V metodě se sada vytvoří z následujících tří typů souborů:
    • Soubory JavaScriptu (dotnet.js)
    • Soubory WASM (dotnet.wasm)
    • Knihovny DLL aplikací (.dll)
  • Vytvoří se multipart/form-data sada. Každý soubor se přidá do sady s příslušnými popisy prostřednictvím hlavičky Content-Disposition a hlavičky Content-Type.
  • Po vytvoření sady se sada zapíše do souboru.
  • Sestavení je nakonfigurované pro rozšíření. Následující kód vytvoří položku rozšíření a přidá ji do Extension vlastnosti. Každá položka rozšíření obsahuje tři části dat:
    • Cesta k souboru přípony.
    • Cesta URL vzhledem ke kořenovému adresáři Blazor WebAssembly aplikace.
    • Název přípony, která seskupuje soubory vytvořené danou příponou.

Po provedení předchozích cílů se vytvoří úloha MSBuild pro přizpůsobení výstupu Blazor publikování. Blazor dbá na shromažďování rozšíření a ujistěte se, že se rozšíření zkopírují do správného umístění ve výstupní složce publikování (například bin\Release\net6.0\publish). Stejné optimalizace (například komprese) se použijí u souborů JavaScript, WASM a DLL, jak Blazor platí pro ostatní soubory.

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;
        }
    }
}

Vytvoření balíčku NuGet pro automatickou transformaci výstupu publikování

Vygenerujte balíček NuGet s cíli MSBuild, které se automaticky zahrnou při odkazování na balíček:

  • Vytvořte nový Razor projekt knihovny tříd (RCL).
  • Vytvořte soubor cílů podle konvencí NuGet, který automaticky naimportuje balíček do náročných projektů. Můžete například vytvořit build\net6.0\{PACKAGE ID}.targets, kde {PACKAGE ID} je identifikátor balíčku.
  • Shromážděte výstup z knihovny tříd obsahující úlohu MSBuild a ověřte, že je výstup zabalený do správného umístění.
  • Přidejte potřebný kód MSBuild pro připojení ke Blazor kanálu a vyvolání úlohy MSBuild pro vygenerování sady.

Přístup popsaný v této části používá pouze balíček k doručování cílů a obsahu, což se liší od většiny balíčků, kde balíček obsahuje knihovnu DLL.

Upozorňující

Ukázkový balíček popsaný v této části ukazuje, jak přizpůsobit Blazor proces publikování. Ukázkový balíček NuGet se používá pouze jako místní ukázka. Použití tohoto balíčku v produkčním prostředí není podporováno.

Poznámka:

Balíček NuGet pro příklady v tomto článku je pojmenován po balíčku poskytovaném Microsoftem, Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle. Pokyny k pojmenování a vytváření vlastního balíčku NuGet najdete v následujících článcích o NuGetu:

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>

Poznámka:

Vlastnost <NoWarn>NU5100</NoWarn> v předchozím příkladu potlačí upozornění na sestavení umístěná ve tasks složce. Další informace najdete v tématu Upozornění NuGet NU5100.

.targets Přidejte soubor pro připojení úlohy MSBuild do kanálu sestavení. V tomto souboru jsou splněny následující cíle:

  • Importujte úlohu do procesu sestavení. Všimněte si, že cesta k knihovně DLL je relativní vzhledem k konečnému umístění souboru v balíčku.
  • Vlastnost ComputeBlazorExtensionsDependsOn připojí vlastní cíl ke Blazor WebAssembly kanálu.
  • Extension Zachyťte vlastnost ve výstupu úkolu a přidejte ji, aby BlazorPublishExtension se o rozšíření dozvědělaBlazor. Vyvolání úkolu v cíli vytvoří sadu. Seznam publikovaných souborů poskytuje Blazor WebAssembly kanál ve PublishBlazorBootStaticWebAsset skupině položek. Cesta k sadě je definována pomocí IntermediateOutputPath složky (obvykle uvnitř obj složky). Nakonec se sada automaticky zkopíruje do správného umístění ve výstupní složce publikování (například bin\Release\net6.0\publish).

Při odkazování na balíček vygeneruje během publikování sadu Blazor souborů.

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>

Automatické spouštění Blazor ze sady

Balíček NuGet využívá inicializátory JavaScriptu (JS) k automatickému spouštění Blazor WebAssembly aplikace ze sady místo použití jednotlivých souborů DLL. JSInicializátory se používají ke změně zavaděče spouštěcího Blazorprostředku a použití sady.

Chcete-li vytvořit JS inicializátor, přidejte JS soubor s názvem {NAME}.lib.module.js do wwwroot složky projektu balíčku, kde {NAME} zástupný symbol je identifikátor balíčku. Například soubor balíčku Společnosti Microsoft má název Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js. Exportované funkce beforeWebAssemblyStart a afterWebAssemblyStarted zpracování načítání.

Inicializátory 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);
  }
}

Chcete-li vytvořit JS inicializátor, přidejte JS soubor s názvem {NAME}.lib.module.js do wwwroot složky projektu balíčku, kde {NAME} zástupný symbol je identifikátor balíčku. Například soubor balíčku Společnosti Microsoft má název Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js. Exportované funkce beforeStart a afterStarted zpracování načítání.

Inicializátory 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);
  }
}

Obsluha sady z aplikace hostitelského serveru

Kvůli omezením zabezpečení ASP.NET Core soubor neobslouží app.bundle . Pomocná rutina pro zpracování žádostí je nutná k tomu, aby soubor obsluhoval klienty.

Poznámka:

Vzhledem k tomu, že stejné optimalizace se transparentně aplikují na rozšíření publikování, která se použijí na soubory aplikace, app.bundle.gz vytvoří se komprimované app.bundle.br soubory assetů automaticky při publikování.

Umístěte kód jazyka C# do Program.cs Server projektu bezprostředně před řádek, který nastaví záložní soubor na index.html (app.MapFallbackToFile("index.html");) pro odpověď na žádost o soubor sady (například 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);
});

Typ obsahu odpovídá typu definovanému dříve v úloze sestavení. Koncový bod kontroluje kódování obsahu přijaté prohlížečem a obsluhuje optimální soubor, Brotli (.br) nebo Gzip (.gz).