皆さんごきげんよう。本記事は、2021 年 10 月 12 日(米国時間)ポストされた ASP.NET Core updates in .NET 6 Release Candidate 2
の意訳のようななにかとなります。内容については下記が原文で、正となります。
.NET 6 Release Candidate 2 (RC2) がリリースされました。.NET 6 RC2 は、11/9 (太平洋標準時)にオンラインで開催されるイベント .NET Conf 2021 のタイミングと併せ、11 月に出荷される予定の .NET 6 の最終ビルドに非常に近いものです。
また、この RC2 は "Go live" リリースでもありますので、本番環境使用も可能です。.NET 6 RC2 には、主に品質向上とバグ修正に対応していますが、いくつかの新機能も含まれています。
今回のプレビューリリースでの新機能は以下の通りです。
- Blazor WebAssembly アプリのネイティブな依存関係のサポート
- Minimal API アップデート
はじめに
.NET 6 RC2 で ASP.NET Core を使う準備としてまず、以下から .NET 6 SDK をダウンロードし、インストールします。
- Windows 上で Visual Studio を使用している場合は、.NET 6 RC2 を含む Visual Studio 2022 の最新のプレビュー版をインストールしてください。
- Macユーザーの方は、Visual Studio for Mac 2022の最新プレビュー版をインストールしてください。
既存プロジェクトのアップグレード
既存の ASP.NET Core アプリを .NET 6 Preview RC1 から .NET 6 RC2 にアップグレードするには、以下の手順を実行します。
- 全 Microsoft.AspNetCore.* パッケージの参照先参照を
6.0.0-rc.2.*
に更新します。 - 全 Microsoft.Extensions.* パッケージの参照先を
6.0.0-rc.2.*
に更新します。
ASP.NET Core for .NET 6 の変更点の全リストをご覧ください。
Blazor WebAssembly アプリのネイティブ依存関係のサポート
.NET 6 の Blazor WebAssembly アプリは、WebAssembly 上で動作するようにビルドされたネイティブな依存関係を使用できるようになりました。このツールは、.NET 6 で Blazor アプリを WebAssembly で事前 (AOT:Ahead-Of-Time) コンパイルしたり、ランタイムを再リンクして利用されていない機能を削除したりするツールと同じツールです。
.NET WebAssembly ビルド ツールをインストールするには、Visual Studio インストーラーでオプションのコンポーネントを選択するか、管理者権限で起動したコマンド プロンプトから dotnet workload install wasm-tools
を実行します。なお、.NET WebAssembly ビルド ツールは、Web プラットフォーム用のコンパイラ ツール チェーンである Emscripten をベースにしています。
プロジェクトファイルに NativeFileReference
アイテムを追加することで、Blazor WebAssembly アプリにネイティブの依存関係を追加します。プロジェクトをビルドすると、各 NativeFileReference
は .NET WebAssembly ビルド ツールによって Emscripten に渡され、ランタイムにてコンパイル、リンクされます。その後、.NET コードからネイティブコードに p/invoke することができます。
一般的に、ポータブルなネイティブ コードは、Blazor WebAssembly のネイティブ依存として使用可能です。C/C++ コードや、以前に Emscripten を使ってコンパイルされたコードにネイティブ依存を追加することができます。
(対象 : オブジェクトファイル(.o)、アーカイブファイル(.a)、ビットコード(.bc)、またはスタンドアロンの WebAssembly モジュール(.wasm))
注意 : 構築済みの依存関係は通常、.NET WebAssembly ランタイムの構築に使用される Emscripten と同じバージョン(現在は 2.0.23)を使用して構築する必要があります。
Blazor WebAssembly アプリからネイティブコードを使用する
Blazor WebAssembly アプリに簡単な C 言語のネイティブ関数を追加してみましょう。
手順 :
1. 新しい Blazor WebAssembly プロジェクトを作成
2. プロジェクトに Test.c ファイルを追加
3. Test.c に階乗を計算するC関数を追加
int fact(int n)
{
if (n == 0) return 1;
return n * fact(n - 1);
}
(1) Test.c のプロジェクト ファイルに NativeFileReference
を追加します。
<ItemGroup>
<NativeFileReference Include="Test.c" />
</ItemGroup>
(2) Pages/Index.razor で、生成された Test ライブラリの fact
関数の DllImport
を追加します。
@using System.Runtime.InteropServices
...(中略)...
@code {
[DllImport("Test")]
static extern int fact(int n);
}
(3) .NET コードから fact
関数を呼び出します。
<p>@fact(3)</p>
.NET WebAssembly ビルド ツールをインストールしてアプリをビルドすると、ネイティブ C コードがコンパイルされ、dotnet.wasm
にリンクされます。これには数分かかることがあります。アプリのビルドが完了したら、アプリを実行してレンダリングされた値を確認してみます。
注 : 以降のビルドでは、「出力アセンブリが別のプロセスで使用されている」というビルド エラーが発生する場合があります。これは既知の問題であり、今後の .NET 6 のリリースで対処される予定です。プロジェクトを再度ビルドすることで、この問題を回避することができます。
ネイティブ依存のライブラリを使用する
NuGet パッケージは、WebAssembly で使用するためのネイティブな依存関係を含むことができます。これらのライブラリとそのネイティブ機能は、Blazor WebAssembly アプリから使用可能です。ネイティブ依存のファイルは WebAssembly 用にビルドされ、browser-wasm
アーキテクチャ固有のフォルダにパッケージされている必要があります。
WebAssembly 固有の依存関係は自動的には参照されず、NativeFileReference
として手動で参照していただく必要があります。パッケージ作成者は、パッケージの .props ファイルに参照を含めることで、ネイティブ参照を追加することができます。
SkiaSharp は、ネイティブの Skia グラフィック ライブラリをベースにした、.NET 用のクロス プラットフォームの 2D グラフィックライブラリです。今回、Blazor WebAssembly のプレビューサポートが開始されました。これを早速試してみたいと思います。
■ Blazor WebAssemblyアプリでSkiaSharpを使用する方法
- Blazor WebAssembly プロジェクトから SkiaSharp.Views.Blazor パッケージへのパッケージ参照を追加します。
dotnet add package –prerelease SkiaSharp.Views.Blazor
- アプリに
SKCanvasView
コンポーネントを追加します。
<SKCanvasView OnPaintSurface="OnPaintSurface" />
- 描画ロジックを追加します。
@code {
void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.White);
using var paint = new SKPaint
{
Color = SKColors.Black,
IsAntialias = true,
TextSize = 24
};
canvas.DrawText("SkiaSharp", 0, 24, paint);
}
}
アプリを実行すると、SkiaSharp
を使ったカスタム ドローイングを見ることができます。
SkiaSharp と Blazor WebAssembly を組み合わせたサンプルの完全版は、SkiaSharp repo にあります。
Minimal API アップデート
パラメータバインディング
RC2 では、TryParse
と BindAsync
に継承されたメソッドのサポートを追加しました。また、Public な TryParse
と BindAsync
メソッドが正しい形式でないことをチェックし、エラーをスローするので、関数に間違った構文を使用していることがわかるようになりました。
さらに、BindAsync メソッドには、ParameterInfo(※)を必要としない別のオーバーロードがサポートされています。
(※) public static ValueTask<T?> BindAsync(HttpContext context)
ルート、ヘッダー属性、クエリー文字列から値をバインドしたい場合は、以下のようにカスタム型に静的な TryParse
メソッドを追加することができます。TryParse
メソッドは、以下の形式でなければなりません。
public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);
以下は、Complex 型である Point
の TryParse
の例です。
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");
public class Point
{
public double X { get; set; }
public double Y { get; set; }
public static bool TryParse(string? value, IFormatProvider? provider, out Point? point)
{
// Format is "(12.3,10.1)"
var trimmedValue = value?.TrimStart('(').TrimEnd(')');
var segments = trimmedValue?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& double.TryParse(segments[0], out var x)
&& double.TryParse(segments[1], out var y))
{
point = new Point { X = x, Y = y };
return true;
}
point = null;
return false;
}
}
上記のエンド ポイントに /map?point=(10.1, 11.4)
をリクエストすると、プロパティ値 X=10.1, Y=11.4
を持つ Point
オブジェクトが返されます。
さらに、自分の型のバインド処理をコントロールしたい場合は、以下のような形で BindAsync
を使うことができます。
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo? parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
継承を使って Complex 型をバインドしたいなら、BindAsync
は、以下の例のようにそれを可能にします。
app.MapPost("/widget", (CreateWidgetDTO dto) =>
{
// Use the DTO
});
public abstract class DTO<T>
{
// typeof(T) must equal ParameterInfo.Type otherwise we throw
public static T BindAsync(HttpContext context, ParameterInfo parameter)
{
// Use reflection to bind the properties on T
}
}
public class CreateWidgetDTO : DTO<CreateWidgetDTO>
{
[FromRoute]
public string? Name { get; set; }
[FromQuery]
public int Id { get; set; }
}
また、BindAsync
では、以下のように nullable
構造体を含むオプションのカスタム パラメータ型がサポートされました。
app.MapGet("/webhook", (CloudEventStruct? cloudEvent) =>
{
redis.Publish(cloudEvent);
});
public struct CloudEventStruct
{
public static async ValueTask<CloudEventStruct?> BindAsync(HttpContext context, ParameterInfo parameter)
{
return await CloudEventParser.ReadEventAsync(context, parameter.Name);
}
}
Open API
Accepts<TRequest>()
拡張メソッドや[Consumes(typeof(Todo), "application/json", IsRequired = false)]
属性を使って、リクエスト ボディが必須かどうかを記述できるように強化されました。Accepts
拡張メソッドと Consumes
属性を使えば、以下の例のように、生成された open-api docs に対して、Todo
タイプと application/json
の両方を表現することができます。
app.MapPost("/todo", async (HttpRequest request) =>
{
var todo = await request.Body.ReadAsJsonAsync<Todo>();
return todo is Todo ? Results.Ok(todo) : Results.Ok();
})
.Accepts<Todo>(isRequired: false, contentType: "application/json");
app.MapPost("/todo", HandlePostTodo);
[Consumes(typeof(Todo), "application/json", IsRequired = false)]
IResult HandlePostTodo(HttpRequest request)
{
var todo = await request.Body.ReadAsJsonAsync<Todo>();
return todo is Todo ? Results.Ok(todo) : Results.Ok();
}
OpenAPI ドキュメント(Swagger)で関連するエンド ポイントを異なるコレクションにグループ化したい場合は、グループ化タグのメタデータを提供できる WithTags
拡張メソッドを使用して行うことができます。
以下は使用例です。
app.MapGet("/", () => "Hello World!")
.WithTags("Examples");
app.MapGet("/todos/sample", () => new[]
{
new Todo { Id = 1, Title = "Do this" },
new Todo { Id = 2, Title = "Do this too" }
})
.WithTags("Examples", "TodoApi");
ソースコード解析
RC2 では、ルート ハンドラの問題を素早く発見したり、ミドルウェアの設定ミスを警告したりするのに役立つアナライザをいくつか追加しました。
アナライザは WebApplicationBuilder
のミドルウェアの順序を検証し、誤ったミドルウェアの構成や順序が検出された場合に警告してくれます。
また、IActionResult
を実装した型がルート ハンドラから返された場合に検出し、結果が意図せずに JSON としてシリアル化された場合に警告する機能も追加されました。以下は例となります。
app.Map("/", () => new JsonResult(new { Hello = "World" }));
さらに、ラムダ自身ではなく、ラムダによって呼び出されたメソッドに属性が付けられていることを検出する新しいアナライザが導入されました。例えば、以下のコードでは、警告が発生します。
app.Map("/payment", () => SubmitPayment());
[Authorize]
void SubmitPayment() { }
最後になりましたが、オプションのパラメータ結合について、パラメータのオプション性が一致しない場合に例外をスローするアナライザーを追加しました。以下のルート文字列では、uuid
パラメータがオプションとして定義されていますが、ラムダ関数 `(string uuid) => $"{uuid}"
では必須となっていることがポイントです。
app.MapGet("/todo/{uuid?}", (string uuid) => $"{uuid}");
重要な変更点 (API の名称変更)
以下の API 群の名称を変更することで、内容を明確にし、その意図を適切に説明するようにしました。
変更前 | 変更後 |
---|---|
DelegateEndpointConventionBuilder | RouteHandlerBuilder |
OpenApiDelegateEndpointConventionBuilderExtensions | OpenApiRouteHandlerBuilderExtensions |
-
DelegateEndpointRouteBuilderExtensions
は、既存のEndpointRouteBuilderExtensions
と統合されました。 - 上記の変更により、
DelegateEndpoint
をRouteHandler
に置き換え、クラス名のConvention
を削除しました。
フィードバックお待ちしています
ASP.NET Core in .NET 6 の Preview リリースをお楽しみください。私たちは、このリリースでの皆さんの感想などをぜひお聞きしたいと思っています。お気づきの点がありましたら、GitHub で issue をポストしてください。
以上、Daniel さんでした。