.NET 6 Preview 3 中 ASP.NET Core 的更新和改進

原文:bit.ly/2Qb56NP
作者:Daniel Roth
譯者:精緻碼農-王亮

.NET 6 預覽版 3 現已推出,其中包括許多對新的 ASP.NET Core 改進。以下是本次預覽版的新內容:

  • 更小的 SignalR、Blazor Server、MessagePack 腳本文件
  • 啟用 Redis 分析會話
  • HTTP/3 端點 TLS 配置
  • 初步 .NET 熱重載支援
  • Razor 編譯器不再生成單獨的視圖 Assembly
  • IIS 中的淺拷貝支援
  • 適用於 SignalR C++ 客戶端的 Vcpkg 埠
  • 減少閑置 TLS 連接的記憶體佔用量
  • 從 SlabMemoryPool 中移除板塊
  • 用於 WPF 和 Windows 窗體的 BlazorWebView 控制項

開始

要在.NET 6 Preview 3 中開始使用 ASP.NET Core,請安裝 .NET 6 SDK[1]

如果你在 Windows 上使用 Visual Studio,我們建議安裝 Visual Studio 2019 16.10 的最新預覽版。如果你在 macOS 上,我們建議安裝 Visual Studio 2019 for Mac 8.10 的最新預覽版。

升級現有項目

要將現有的 ASP.NET Core 應用程式從 .NET 6 Preview 2 升級到 .NET 6 Preview 3:

  • 更新所有Microsoft.AspNetCore.*包引用至6.0.0-preview.3.*
  • 更新所有Microsoft.Extensions.*包引用至6.0.0-preview.3.*

查看 ASP.NET Core for .NET 6 中的完整中斷性更改列表[2]

更小的腳本文件

得益於 Ben Adams 的社區貢獻,SignalR、MessagePack 和 Blazor Server 腳本現在明顯變小了,下載體積減少,瀏覽器解析和編譯 JavaScript 的次數減少,啟動速度加快。

這項工作帶來的下載體積減少是非常驚人的:

Library Before After %↓ .br
signalr.min.js 130 KB 39 KB 70% 10 KB
blazor.server.js 212 KB 116 KB 45% 28 KB

現在你也只需要為 MessagePack 提供@microsoft/signalr-protocol-msgpack包,而不需要包含 msgpack5。這意味著你只需要額外的 29 KB 而不是之前的 140 KB 來使用 MessagePack 而不是 JSON。

下面說下我們是如何減少體積的:

  • 更新 TypeScript 和依賴關係到最新版本.
  • 將 uglify-js 換成了 terser,這是 webpack 的默認版本,支援新的 JavaScript 語言特性(比如class)。
  • 將 SignalR 模組標記為"sideEffects":false,這樣 tree-shaking 就更有效了。
  • 丟棄了 "es6-promise/dist/es6-promise.auto.js"的多邊填充。
  • 更改 TypeScript 為輸出es2019而不是es5,並放棄了es2015.promisees2015.iterable的 polyfill。
  • @msgpack/msgpack移到msgpack5,因為它需要更少的 polyfills,並且是 TypeScript 和模組感知的。

你可以在 GitHub 上 Ben 的 PR[3] 中找到更多關於這些變化的細節。

啟用 Redis 分析會話

我們接受了 Gabriel Lucaci 的社區貢獻,在此預覽版中使用Microsoft.Extensions.Caching.StackExchangeRedis啟用 Redis 分析會話。關於 Redis 分析的更多細節,請參見官方文檔[4]。該 API 的使用方法如下:

services.AddStackExchangeRedisCache(options =>
{
    options.ProfilingSession = () => new ProfilingSession();
})

HTTP/3 端點 TLS 配置

HTTP/3 與現有的 HTTP 協議相比具有許多優勢,包括更快的連接設置,以及在低品質網路上的性能改進。

在此預覽版中,新增了使用UseHttps在單個 HTTP/3 埠上配置 TLS 證書的功能。這使得 Kestrel 的 HTTP/3 端點配置與 HTTP/1.1 和 HTTP/2 一致。

.ConfigureKestrel((context, options) =>
{
    options.EnableAltSvc = true;
    options.Listen(IPAddress.Any, 5001, listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http3;
        listenOptions.UseHttps(httpsOptions =>
        {
            httpsOptions.ServerCertificate = LoadCertificate();
        });
    });
})

初步 .NET 熱重載支援

現在,使用dotnet watch的 ASP.NET Core 和 Blazor 項目可以獲得對 .NET 熱重載的早期支援。.NET 熱重載可以在不重新啟動應用程式和不丟失應用程式狀態的情況下將程式碼更改應用到你正在運行的應用程式中。

要在現有的基於 .NET 6 的 ASP.NET Core 項目中試用熱重載,請將"hotReloadProfile": "aspnetcore"屬性添加到你的launchSettings.json文件中。對於 Blazor WebAssembly 項目,使用"blazorwasm"熱重載配置文件。

使用dotnet watch運行項目。下面的輸出表明熱重載已經啟用:

watch : Hot reload enabled. For a list of supported edits, see //aka.ms/dotnet/hot-reload. Press "Ctrl + R" to restart.

在任何時候你想強制應用程式重新構建和重啟,你可以在控制台輸入Ctrl+R來實現。

現在你可以開始對你的程式碼進行編輯了。當你保存程式碼更改時,相應的更改幾乎會在瞬間自動熱重載到運行中的應用程式中。運行中的應用程式中的任何狀態都會被保留。

你也可以對你的 CSS 文件進行熱重載更改,而不需要刷新瀏覽器:

有一些程式碼更改不支援 .NET 執重載。你可以在文檔[5]中找到支援的程式碼編輯列表。在 Blazor WebAssembly 中,目前只支援方法體替換。我們正在努力擴展 .NET 6 中支援的編輯集。當dotnet watch檢測到無法使用熱重載應用的更改時,它就會退回重新構建和重新啟動應用程式。

這只是 .NET 6 中熱重載支援的開始。桌面和移動應用程式的熱重載支援將很快在即將到來的預覽版中提供,以及在 Visual Studio 中集成熱重載。

IIS 中的淺拷貝支援

我們在 IIS 的 ASP.NET Core 模組中添加了一個新功能,以增加對淺拷貝應用程式程式集的支援。目前,.NET 在 Windows 上運行時鎖定了應用程式的二進位文件,使得在應用程式仍在運行時無法替換二進位文件。雖然我們的建議仍然是使用應用程式離線文件,但我們認識到在某些情況下(例如 FTP 部署)不可能這樣做。

在這種情況下,你可以通過自定義 ASP.NET Core 模組處理程式設置來啟用淺拷貝。在大多數情況下,ASP.NET Core 應用程式的web.config不在源程式碼版本控制中,你可以修改它(它們通常是由 SDK 生成的)。你可以添加這個web.config示例來開始。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <!-- To customize the asp.net core module uncomment and edit the following section.
  For more info see //go.microsoft.com/fwlink/?linkid=838655 -->

  <system.webServer>
    <handlers>
      <remove name="aspNetCore"/>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModulev2" resourceType="Unspecified"/>
    </handlers>
    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout">
      <handlerSettings>
        <handlerSetting name="experimentalEnableShadowCopy" value="true" />
        <handlerSetting name="shadowCopyDirectory" value="../ShadowCopyDirectory/" />
        <!-- Only enable handler logging if you encounter issues-->
        <!--<handlerSetting name="debugFile" value=".\logs\aspnetcore-debug.log" />-->
        <!--<handlerSetting name="debugLevel" value="FILE,TRACE" />-->
      </handlerSettings>
    </aspNetCore>
  </system.webServer>
</configuration>

你需要一個新版本的 ASP.NET Core 模組來嘗試這個功能。在自託管的 IIS 伺服器上,這需要新版本的託管捆綁包。在 Azure App Services 上,你需要安裝新的 ASP.NET Core 站點運行時擴展。

適用於 SignalR C++ 客戶端的 Vcpkg 埠

Vcpkg 是一個跨平台的 C 和 C++庫的命令行包管理器。最近,我們為 vcpkg 添加了一個移植版本,為 SignalR C++ 客戶端添加了 CMake 本地支援(也適用於 MSBuild 項目)。

你可以用下面的程式碼來添加 SignalR 客戶端到你的 CMake 項目中(假設你已經包含了 vcpkg 工具鏈文件)。

find_package(microsoft-signalr CONFIG REQUIRED)
link_libraries(microsoft-signalr::microsoft-signalr)

在這之後,SignalR C++ 客戶端就可以被#include並用於你的項目中,而不需要任何額外的配置。這個倉庫[6]是一個完整的使用 SignalR C++ 客戶端的 C++ 應用程式的例子。

減少閑置 TLS 連接的記憶體佔用量

對於只偶爾來回發送數據的 TLS 長連接,我們已經大大減少了 .NET 6 中 ASP.NET Core 應用程式的記憶體佔用。這應該有助於提高 WebSocket 伺服器等場景的可擴展性。這得益於System.IO.PipelinesSslStreamKestrel的眾多改進。讓我們來看看促成這一方案的一些改進。

縮減 System.IO.Pipelines.Pipe 大小

對於我們建立的每一個連接,我們都會在 Kestrel 中分配兩個管道:一個是從傳輸層到應用的請求,另一個是從應用層到傳輸的響應。通過將System.IO.Pipelines.Pipe的大小從 368 位元組縮減到 264 位元組(約 28.2%),我們為每個連接節省了 208 位元組(每個 Pipe 節省 104 位元組)。

SocketSender 池

SocketSender 對象在運行時約為 350 位元組。與其為每個連接分配一個新的 SocketSender 對象,我們可以將它們集中起來,因為發送通常非常快,我們可以減少每個連接的開銷。現在,我們不再為每個連接分配 350 位元組,而是只為每個 IOQueue 分配 350 位元組(每個隊列一個,以避免爭用)。在擁有 5000 個空閑連接的 WebSocket 伺服器中,我們從分配約 1.75 MB(350 位元組*5000)到現在只分配約 2.8kb(350 位元組*8)給 SocketSender 對象。

SslStream 零位元組讀取

無緩衝讀取是我們已經在 ASP.NET Core 中採用的一種技術,以避免在套接字上沒有可用數據時從記憶體池中租用記憶體。在這一變化之前,我們的 WebSocket 伺服器有 5000 個空閑連接,在沒有 TLS 的情況下需要約 200 MB,而在有 TLS 的情況下需要約 800 MB。其中一些分配(每個連接 4k)是由於 Kestrel 在等待SslStream上的讀取完成時必須保持ArrayPool緩衝區。鑒於這些連接是空閑的,沒有一個讀取完成並將其緩衝區返回給ArrayPool,迫使ArrayPool分配更多的記憶體。剩餘的分配都在SslStream本身。4k 緩衝區用於 TLS 握手,32k 緩衝區用於正常讀取。在預覽版 3 中,當用戶在SslStream上執行零位元組讀取,而它又沒有可用的數據時,SslStream會在內部對底層的封裝流執行零位元組讀取。在最好的情況下(空閑連接),這些變化導致每個連接節省了 40 Kb,同時仍然允許消費者(Kestrel)在數據可用時得到通知,而無需保留任何未使用的緩衝區。

PipeReader 零位元組讀取

一旦SslStream支援無緩衝區讀取,我們就向StreamPipeReader(將Stream適配成PipeReader的內部類型)添加了執行零位元組讀取的選項。在 Kestrel 中,我們使用StreamPipeReader將底層的SslStream適配成PipeReader,有必要在PipeReader上暴露這些零位元組讀取語義。

現在,你可以使用以下 API 創建一個PipeReader,支援在任何支援零位元組讀取語義的Stream上進行零位元組讀取(例如SslStreamNetworkStream等)。

var reader = PipeReader.Create(stream, new StreamPipeReaderOptions(useZeroByteReads: true));

從 SlabMemoryPool 中移除板塊

為了減少堆的碎片,Kestrel 採用了一種技術,它分配了 128 KB 的記憶體板塊作為其記憶體池的一部分。然後,這些板塊被進一步劃分為 4 KB 的塊,供 Kestrel 內部使用。板塊必須大於 85 KB,以便在大對象堆上強制分配,以盡量防止 GC 重新定位這個陣列。然而,隨著新一代 GC 的引入,Pinned Object Heap(POH),在板塊上分配塊已經沒有意義了。在預覽版 3 中,我們現在直接在 POH 上分配塊[7],降低了管理自己的記憶體池所涉及的複雜性。這個變化應該可以更容易地執行未來的改進,比如讓 Kestrel 使用的記憶體池更容易收減。

用於 WPF 和 Windows 窗體的 BlazorWebView 控制項

對於 .NET 6,我們增加了對使用 .NET MAUI 和 Blazor 構建跨平台混合桌面應用程式的支援。混合應用程式是利用 Web 技術實現其功能的本地應用程式。例如,一個混合應用程式可能會使用一個嵌入式的 Web 視圖控制項來渲染 Web UI。這意味著你可以使用 HTML 和 CSS 等 Web 技術編寫應用程式 UI,同時還可以使用本地設備的功能。我們將在即將發布的 .NET 6 預覽版中引入對使用 .NET MAUI 和 Blazor 構建混合應用程式的支援。

在這個版本中,我們為 WPF 和 Windows Forms 應用程式引入了BlazorWebView控制項,該控制項可將 Blazor 功能嵌入到基於 .NET 6 的現有 Windows 桌面應用程式中。使用 Blazor 和混合方式,你可以將你的 UI 與 WPF 和 Windows Forms 解耦。這是一種對現有桌面應用程式進行現代化改造的好方法,可以將其帶到 .NET MAUI 上或在 Web 上使用。你可以使用 Blazor 對現有的 Windows Forms 和 WPF 應用程式進行現代化改造。

要使用新的BlazorWebView控制項,你首先需要確保你已經安裝了 WebView2[8]

要將 Blazor 功能添加到現有的 Windows Forms 應用程式中,需要:

  • 更新 Windows Forms 應用程式,使其 Target 為 .NET 6。

  • 把應用程式項目文件中的 SDK 更新為 Microsoft.NET.Sdk.Razor。

  • 添加Microsoft.AspNetCore.Components.WebView.WindowsForms包引用。

  • 在項目中添加以下wwwroot/index.html文件,用實際的項目名稱替換{PROJECT NAME}

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
        />
        <title>Blazor app</title>
        <base href="/" />
        <link href="{PROJECT NAME}.styles.css" rel="stylesheet" />
        <link href="app.css" rel="stylesheet" />
      </head>
    
      <body>
        <div id="app"></div>
    
        <div id="blazor-error-ui">
          An unhandled error has occurred.
          <a href="" class="reload">Reload</a>
          <a class="dismiss">🗙</a>
        </div>
    
        <script src="_framework/blazor.webview.js"></script>
      </body>
    </html>
    
  • 在 wwwroot 文件夾中添加以下app.css文件,包含一些基本樣式:

    html,
    body {
      font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
    }
    
    .valid.modified:not([type='checkbox']) {
      outline: 1px solid #26b050;
    }
    
    .invalid {
      outline: 1px solid red;
    }
    
    .validation-message {
      color: red;
    }
    
    #blazor-error-ui {
      background: lightyellow;
      bottom: 0;
      box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
      display: none;
      left: 0;
      padding: 0.6rem 1.25rem 0.7rem 1.25rem;
      position: fixed;
      width: 100%;
      z-index: 1000;
    }
    
    #blazor-error-ui .dismiss {
      cursor: pointer;
      position: absolute;
      right: 0.75rem;
      top: 0.5rem;
    }
    
  • 對於 wwwroot 文件夾中的所有文件,將Copy to Output Directory屬性設置為Copy if newer

  • 在項目中添加一個 Blazor 根組件Counter.razor

    @using Microsoft.AspNetCore.Components.Web
    
    <h1>Counter</h1>
    
    <p>The current count is: @currentCount</p>
    <button @onclick="IncrementCount">Count</button>
    
    @code {
        int currentCount = 0;
    
        void IncrementCount()
        {
            currentCount++;
        }
    }
    
  • BlazorWebView控制項添加到所需的表單中,以渲染 Blazor 根組件:

    var serviceCollection = new ServiceCollection();
    serviceCollection.AddBlazorWebView();
    var blazor = new BlazorWebView()
    {
        Dock = DockStyle.Fill,
        HostPage = "wwwroot/index.html",
        Services = serviceCollection.BuildServiceProvider(),
    };
    blazor.RootComponents.Add<Counter>("#app");
    Controls.Add(blazor);
    
  • 運行該應用程式,查看BlazorWebView的運行情況。

要將 Blazor 功能添加到現有的 WPF 應用程式中,請按照上面列出的 Windows 窗體應用程式的相同步驟進行操作。另外:

  • Microsoft.AspNetCore.Components.WebView.Wpf替換包引用。

  • 在 XAML 中添加BlazorWebView控制項:

    <Window x:Class="WpfApp1.MainWindow"
            xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="//schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="//schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="//schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1"
            xmlns:blazor="clr-namespace:Microsoft.AspNetCore.Components.WebView.Wpf;assembly=Microsoft.AspNetCore.Components.WebView.Wpf"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
        <Grid>
            <blazor:BlazorWebView HostPage="wwwroot/index.html" Services="{StaticResource services}">
                <blazor:BlazorWebView.RootComponents>
                    <blazor:RootComponent Selector="#app" ComponentType="{x:Type local:Counter}" />
                </blazor:BlazorWebView.RootComponents>
            </blazor:BlazorWebView>
        </Grid>
    </Window>
    
  • 將服務提供者設置為靜態資源:

    var serviceCollection = new ServiceCollection();
    serviceCollection.AddBlazorWebView();
    Resources.Add("services", serviceCollection.BuildServiceProvider());
    
  • 為了解決 WPF 運行時構建時找不到 Razor 組件類型的問題,在Counter.razor.cs中為組件添加一個空的局部類:

    public partial class Counter { }
    
  • 構建並運行基於 Blazor 的 WPF 應用:

提供回饋

我們希望你喜歡這個 .NET 6 預覽版中的 ASP.NET Core 部分。我們渴望聽到你對這個版本的體驗。請在 GitHub 上提交 Issue,讓我們知道你的想法。

謝謝你試用 ASP.NET Core!

Tags: