Макет развертывания для размещенных Blazor WebAssembly приложений ASP.NET Core
В этой статье объясняется, как включить размещенные Blazor WebAssembly развертывания в средах, которые блокируют загрузку и выполнение файлов библиотеки динамической компоновки (DLL).
Примечание.
В этом руководстве рассматриваются среды, которые блокируют загрузку и выполнение БИБЛИОТЕК DLL клиентами. В .NET 8 или более поздней версии Blazor используется формат файла Webcil для решения этой проблемы. Дополнительные сведения см. в статье Размещение и развертывание ASP.NET Core Blazor WebAssembly. Многопартийное объединение с помощью экспериментального пакета NuGet, описанного в этой статье, не поддерживается для Blazor приложений в .NET 8 или более поздней версии. Инструкции из этой статьи можно использовать для создания собственного пакета NuGet для .NET 8 или более поздней версии.
Для работы приложений Blazor WebAssembly требуются библиотеки динамической компоновки (DLL), но некоторые среды блокируют клиентам загрузку и запуск библиотек DLL. В подмножестве этих сред изменение расширения имени файла DLL(.dll
) достаточно для обхода ограничений безопасности, но продукты безопасности часто могут сканировать содержимое файлов, просматривающих сеть и блокировать или карантин DLL-файлы. В этой статье описывается один из подходов к включению приложений Blazor WebAssembly в этих средах, где из библиотек DLL приложения создается файл многоэлементного пакета, чтобы можно было загружать библиотеки DLL и одновременно обходить ограничения системы безопасности.
Размещенное приложение Blazor WebAssembly может настроить свои опубликованные файлы и упаковать библиотеки DLL приложения с помощью следующих функций:
- Инициализаторы JavaScript, позволяющие настроить процесс загрузки Blazor.
- Расширяемость MSBuild для преобразования списка опубликованных файлов и определения расширений публикации Blazor. Расширения публикации Blazor — это файлы, определенные во время процесса публикации, которые предоставляют альтернативное представление для набора файлов, необходимых для запуска опубликованного приложения Blazor WebAssembly. В этой статье создается расширение публикации Blazor, которое создает многоэлементный пакет со всеми библиотеками DLL приложения, упакованными в один файл, чтобы можно было скачать библиотеки DLL все вместе.
Подход, продемонстрированный в этой статье, служит отправной точкой для разработчиков, что позволяет разрабатывать собственные стратегии и настраиваемые процессы загрузки.
Предупреждение
Любой подход, предпринятый для обхода ограничений системы безопасности, должен быть тщательно рассмотрен в плане его влияния на безопасность. Перед внедрением подхода, описанного в данной статье рекомендуется изучить тему вместе со специалистами по безопасности сети организации. Возможные альтернативы:
- Включите устройства безопасности и программное обеспечение безопасности, чтобы разрешить сетевым клиентам скачивать и использовать точные файлы, необходимые для приложения Blazor WebAssembly.
- Переключитесь с модели размещения Blazor WebAssembly на модель размещения Blazor Server, которая поддерживает весь код приложения C# на сервере и не требует загрузки библиотек DLL для клиентов. Blazor Server кроме того, предоставляет преимущества сохранения закрытого кода C#, не требуя использования приложений веб-API для кода privacy C# с приложениями Blazor WebAssembly .
Экспериментальный пакет NuGet и пример приложения
Подход, описанный в этой статье, используется экспериментальнымMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundle
пакетом (NuGet.org) для приложений, предназначенных для .NET 6 или более поздней версии. Пакет содержит целевые объекты MSBuild для настройки выходных данных публикации Blazor и инициализатор JavaScript для использования настраиваемого загрузчика ресурсов загрузки, каждый из которых подробно описан далее в этой статье.
Предупреждение
Экспериментальные и предварительные функции предоставляются для сбора отзывов и не поддерживаются для использования в рабочей среде.
Далее в этой статье в разделе Настройка процесса загрузки Blazor WebAssembly с помощью пакета NuGet с тремя подразделами представлены подробные объяснения конфигурации и кода в пакете Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle
. Подробные объяснения важны для понимания того, как вы создаете собственную стратегию и пользовательский процесс загрузки для приложений Blazor WebAssembly. Чтобы использовать опубликованный, экспериментальный, неподдерживаемый пакет NuGet без настройки в качестве локальной демонстрации, выполните следующие действия.
Используйте существующее размещенное Blazor WebAssemblyрешение или создайте новое решение из шаблона проекта Blazor WebAssembly с помощью Visual Studio или путем передачи параметра
-ho|--hosted
в командуdotnet new
(dotnet new blazorwasm -ho
). Дополнительные сведения см. в статье Инструментарий для ASP.NET Core Blazor.В проекте Client добавьте ссылку на экспериментальный пакет
Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle
:Примечание.
Рекомендации по добавлению пакетов в приложения .NET см. в разделе Способы установки пакетов NuGet в статье Рабочий процесс использования пакета (документация по NuGet). Проверьте правильность версий пакета на сайте NuGet.org.
В проекте Server добавьте конечную точку для обслуживания файла пакета (
app.bundle
). Пример кода представлен в разделе Обслуживание пакета из приложения сервера узла этой статьи.Опубликуйте приложение в конфигурации выпуска.
Настройка процесса загрузки Blazor WebAssembly с помощью пакета NuGet
Предупреждение
Рекомендации в этом разделе с тремя подразделами относятся к созданию пакета NuGet с нуля, чтобы реализовать собственную стратегию и настраиваемый процесс загрузки. Экспериментальный Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle
пакет (NuGet.org) для .NET 6 и 7 основан на рекомендациях в этом разделе. При использовании предоставленного пакета в локальной демонстрации подхода с загрузкой многоэлементного пакета вам не нужно следовать указаниям в этом разделе. Рекомендации по использованию предоставленного пакета см. в разделе Экспериментальный пакет NuGet и пример приложения.
Ресурсы приложения Blazor упаковываются в файл многоэлементного пакета и загружаются браузером с помощью настраиваемого инициализатора JavaScript (JS). Для приложения, использующего пакет с инициализатором JS, приложение требует, чтобы файл пакета был обслужен по запросу. Все остальные аспекты этого подхода обрабатываются прозрачно.
Для загрузки опубликованного по умолчанию приложения Blazor необходимо выполнить четыре настройки:
- Задача MSBuild для преобразования файлов публикации.
- Пакет NuGet с целевыми объектами MSBuild, который подключается к процессу публикации Blazor, преобразует выходные данные и определяет один или несколько файлов расширения публикации Blazor (в данном случае один пакет).
- Инициализатор JS для обновления обратного вызова загрузчика ресурсов Blazor WebAssembly для загрузки пакета и предоставления приложению отдельных файлов.
- Вспомогательное приложение в ведущем приложении Server, чтобы обеспечить обслуживание пакета для клиентов по запросу.
Создание задачи MSBuild для настройки списка опубликованных файлов и определения новых расширений
Создайте MSBuild задачу в виде открытого класса C#, который можно импортировать в составе компиляции MSBuild и который может взаимодействовать со сборкой.
Для класса C# необходимо следующее:
- Новый проект библиотеки классов.
- Целевая платформа проекта
netstandard2.0
. - Ссылки на пакеты MSBuild:
Примечание.
Пакет NuGet для примеров в этой статье назван в соответствии с пакетом, предоставленным корпорацией Майкрософт, Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle
. Рекомендации по именованию и созданию собственных пакетов NuGet см. в следующих статьях NuGet:
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>
Определите последние версии пакетов для заполнителей {VERSION}
на сайте NuGet.org:
Чтобы создать задачу MSBuild, создайте открытый класс C#, расширяющий Microsoft.Build.Utilities.Task (не System.Threading.Tasks.Task), и объявите три свойства:
PublishBlazorBootStaticWebAsset
: список файлов для публикации для приложения Blazor.BundlePath
: путь, по которому записывается пакет.Extension
: новые расширения публикации, включаемые в сборку.
Следующий пример класса BundleBlazorAssets
является отправной точкой для дальнейшей настройки:
- В методе
Execute
пакет создается из следующих трех типов файлов:- Файлы JavaScript (
dotnet.js
) - Файлы WASM (
dotnet.wasm
) - Библиотеки DLL приложения (
.dll
)
- Файлы JavaScript (
- Создается пакет
multipart/form-data
. Каждый файл добавляется в пакет с соответствующими описаниями посредством заголовка Content-Disposition и заголовка Content-Type. - После того как пакет создан, он записывается в файл.
- Сборка настраивается для расширения. Следующий код создает элемент расширения и добавляет его к свойству
Extension
. Каждый элемент расширения содержит три части данных:- Путь к файлу расширения.
- Путь URL-адреса относительно корня приложения Blazor WebAssembly.
- Имя расширения, которое группирует файлы, созданные данным расширением.
После выполнения предыдущих задач создается задача MSBuild для настройки выходных данных публикации Blazor. Blazor осуществляет сбор расширений и обеспечивает копирование расширений в правильное расположение в папке выходных данных публикации (например, bin\Release\net6.0\publish
). Те же оптимизации (например, сжатие) применяются и к файлам JavaScript, WASM и DLL, поскольку Blazor применяется к другим файлам.
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;
}
}
}
Создание пакета NuGet для автоматического преобразования выходных данных публикации
Создайте пакет NuGet с целевыми объектами MSBuild, которые автоматически включаются при ссылке на пакет:
- Создайте новый проект библиотеки классов Razor (RCL).
- Создайте файл целевых объектов, следуя соглашениям NuGet, для автоматического импорта пакета в проекты. Например, создайте
build\net6.0\{PACKAGE ID}.targets
, где{PACKAGE ID}
— это идентификатор пакета. - Соберите выходные данные из библиотеки классов, содержащей задачу MSBuild, и убедитесь, что выходные данные упакованы в правильном расположении.
- Добавьте необходимый код MSBuild для присоединения к конвейеру Blazor и вызова задачи MSBuild для создания пакета.
В описанном в этом разделе подходе используется только пакет для доставки целевых объектов и содержимого, который отличается от большинства пакетов, в которых пакет включает библиотеку DLL.
Предупреждение
Пример пакета, описанный в этом разделе, демонстрирует настройку процесса публикации Blazor. Пример пакета NuGet предназначен только для использования в качестве локальной демонстрации. Использование этого пакета в рабочей среде не поддерживается.
Примечание.
Пакет NuGet для примеров в этой статье назван в соответствии с пакетом, предоставленным корпорацией Майкрософт, Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle
. Рекомендации по именованию и созданию собственных пакетов NuGet см. в следующих статьях NuGet:
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>
Примечание.
Свойство <NoWarn>NU5100</NoWarn>
в предыдущем примере подавляет предупреждение о сборках, помещенных в папку tasks
. Дополнительные сведения см. в статье Предупреждение NuGet NU5100.
Добавьте файл .targets
, чтобы подключить задачу MSBuild к конвейеру сборки. В этом файле выполняются следующие задачи.
- Импорт задачи в процесс сборки. Обратите внимание, что путь к библиотеке DLL указывается относительно конечного расположения файла в пакете.
- Свойство
ComputeBlazorExtensionsDependsOn
присоединяет настраиваемый целевой объект к конвейеру Blazor WebAssembly. - Запишите свойство
Extension
в выходных данных задачи и добавьте его вBlazorPublishExtension
, чтобы сообщить Blazor о расширении. При вызове задачи в целевом объекте создается пакет. Список опубликованных файлов предоставляется конвейером Blazor WebAssembly в группе элементовPublishBlazorBootStaticWebAsset
. Путь к пакету определяется с помощьюIntermediateOutputPath
(обычно внутри папкиobj
). В конечном счете, пакет автоматически копируется в правильное расположение в папке выходных данных публикации (например,bin\Release\net6.0\publish
).
При указании ссылки на пакет он создает пакет файлов 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>
Автоматическая начальная загрузка Blazor из пакета
Пакет NuGet использует инициализаторы JavaScript (JS) для автоматической начальной загрузки приложения Blazor WebAssembly из пакета вместо использования отдельных DLL-файлов. Инициализаторы JS используются для изменения Blazorзагрузчика ресурсов загрузки и использования пакета.
Чтобы создать инициализатор JS, добавьте JS-файл с именем {NAME}.lib.module.js
в папку wwwroot
проекта пакета, где заполнитель {NAME}
обозначает идентификатор пакета. Например, файл для пакета Microsoft называется Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js
. Обработку загрузки выполняют экспортированные функции beforeWebAssemblyStart
и afterWebAssemblyStarted
.
Инициализаторы JS выполняют следующие функции.
- Определяют, доступно ли расширение публикации, путем проверки
extensions.multipart
, которое является именем расширения (ExtensionName
), указанным в разделе Создание задачи MSBuild для настройки списка опубликованных файлов и определения новых расширений. - Скачивают пакет и преобразуют содержимое в карту ресурсов с помощью URL-адресов сгенерированных объектов.
- Обновите загрузчик ресурсов загрузки (
options.loadBootResource
) с помощью настраиваемой функции, которая разрешает ресурсы, используя URL-адреса объектов. - После запуска приложения отмените URL-адреса объектов, чтобы освободить память в функции
afterWebAssemblyStarted
.
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);
}
}
Чтобы создать инициализатор JS, добавьте JS-файл с именем {NAME}.lib.module.js
в папку wwwroot
проекта пакета, где заполнитель {NAME}
обозначает идентификатор пакета. Например, файл для пакета Microsoft называется Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js
. Обработку загрузки выполняют экспортированные функции beforeStart
и afterStarted
.
Инициализаторы JS выполняют следующие функции.
- Определяют, доступно ли расширение публикации, путем проверки
extensions.multipart
, которое является именем расширения (ExtensionName
), указанным в разделе Создание задачи MSBuild для настройки списка опубликованных файлов и определения новых расширений. - Скачивают пакет и преобразуют содержимое в карту ресурсов с помощью URL-адресов сгенерированных объектов.
- Обновите загрузчик ресурсов загрузки (
options.loadBootResource
) с помощью настраиваемой функции, которая разрешает ресурсы, используя URL-адреса объектов. - После запуска приложения отмените URL-адреса объектов, чтобы освободить память в функции
afterStarted
.
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);
}
}
Обслуживание пакета из приложения сервера узла
Из-за ограничений безопасности ASP.NET Core не обслуживает app.bundle
файл. Для обслуживания файла, когда он запрашивается клиентами, требуется вспомогательное приложение для обработки запросов.
Примечание.
Поскольку к расширениям публикации прозрачно применяются те же оптимизации, что и к файлам приложения, при публикации автоматически создаются сжатые файлы ресурсов app.bundle.gz
и app.bundle.br
.
Поместите код C# в Program.cs
проекта Server непосредственно перед строкой, которая задает index.html
(app.MapFallbackToFile("index.html");
) в качестве резервного файла, чтобы предоставить ответ на запрос файла пакета (например, 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);
});
Тип содержимого соответствует типу, определенному ранее в задаче сборки. Конечная точка проверяет кодировки содержимого, принимаемые браузером, и обслуживает оптимальный файл, Brotli (.br
) или Gzip (.gz
).
ASP.NET Core