Sdílet prostřednictvím


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

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 10 tohoto článku.

Výstraha

Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v zásadách podpory .NET a .NET Core. Aktuální vydání článku ve verzi .NET 9 najdete zde.

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. Produkty zabezpečení jsou často schopny skenovat 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í.

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 úpravu seznamu publikovaných souborů a definování rozšíření publikování (Publish Extensions). 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ří vícedílný balíček se všemi knihovnami DLL aplikace zabalenými do jednoho souboru, aby bylo možné stáhnout knihovny DLL společně.

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í.

Výstraha

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 také nabízí výhodu zachování privátního kódu jazyka C#, aniž by bylo nutné používat aplikace webového rozhraní API pro ochranu osobních údajů kódu jazyka C# s Blazor WebAssembly aplikacemi.

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 MSBuild cíle pro přizpůsobení výstupu Blazor publikování a JavaScript inicializátor pro použití vlastního zavaděče prostředků při spuštění, které jsou podrobně popsány dále v tomto článku.

Výstraha

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 sekce Blazor WebAssembly se třemi dílčími částmi poskytuje podrobná vysvětlení o konfiguraci a kódu v 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řidáním možnosti k příkazu Blazor WebAssembly (-ho|--hosted). 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 lze najít v části Serve the bundle from the host server app 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

Výstraha

Pokyny v této části, která se skládá ze tří podsekcí, se týkají tvorby balíčku NuGet od úplného začátku pro implementaci vaší vlastní strategie a vlastního procesu načítání. Experimentální Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle 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 .

Blazor Prostředky aplikace jsou zabaleny do vícečástového balíčku a načteny prohlížečem pomocí vlastního inicializátoru JavaScript (JS). Pro aplikaci, která využívá balíček s inicializátorem JS, je nutné, aby byl soubor balíku poskytnut pouze na vyžádání. Všechny ostatní aspekty tohoto přístupu se zpracovávají transparentně.

Pro způsob načítání výchozí publikované Blazor aplikace jsou potřeba čtyři úpravy:

  • Úloha MSBuild pro transformaci publikačních souborů.
  • Balíček NuGet s cílemi 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ě jeden svazek).
  • JS Inicializátor pro aktualizaci zpětného volání zavaděče prostředků Blazor WebAssembly, aby načetl balík a poskytoval aplikaci jednotlivé soubory.
  • Pomocná rutina v hostitelské Server aplikaci, která zajistí, že se balíček na vyžádání obsluhuje klientům.

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 MSBuild a která může se 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í pro 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 WebAssembly (Wasm) (dotnet.wasm)
    • Knihovny DLL aplikací (.dll)
  • Vytvoří se multipart/form-data balíček. 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 s příponou.
    • Cesta URL od kořenového adresáře Blazor WebAssembly aplikace.
    • Název přípony, která seskupuje soubory vytvořené danou příponou.

Po splnění předchozích cílů se vytvoří úloha MSBuild, která přizpůsobí výstup publikování Blazor. Blazor se stará o shromažďování rozšíření a zajišťuje, ž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řte balíček NuGet pro automatickou transformaci výstupu publikace

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.

Výstraha

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.

Přidejte soubor .targets, aby se úloha MSBuild připojila k sestavovacímu kanálu. 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 k Blazor WebAssembly kanálu.
  • Zachyťte vlastnost Extension ve výstupu úkolu a přidejte ji do BlazorPublishExtension, aby se Blazor dozvěděl o rozšíření. Vyvolání úkolu v cíli vytvoří balíček. Seznam publikovaných souborů poskytuje Blazor WebAssembly kanál ve PublishBlazorBootStaticWebAsset skupině položek. Cesta k balíčku je definována prostřednictvím IntermediateOutputPath (typicky uvnitř složky obj). 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á inicializace Blazor ze sady

Balíček NuGet využívá inicializátory JavaScriptu (JS), aby automaticky nastartoval Blazor WebAssembly aplikaci ze sady, místo použití jednotlivých souborů DLL. JSInicializátory se používají ke změně zavaděče Blazorspouštěcího prostředku a použití balíku.

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ávají 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ávají 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ík pro zpracování žádostí je potřebný k tomu, aby při požadavcích klientů sloužil k doručení souboru.

Poznámka:

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

Umístěte kód jazyka C# do Program.csServer 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).