.NET 7 Preview 1 における ASP.NET Core のアップデート

みなさんごきげんよう。本日はちょっと時間を空けちゃったんですが、.NET 7 の Preview 1 のリリースとともに、ASP.NET Core の記事も公開されていたので、そこの記事を紹介させていただければと思います。

https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-net-7-preview-1/

----------------------

米国時間 2 月 17 日、[**.NET 7 Preview 1 が発表されました**](https://qiita.com/uikou/items/cd9121579381050dc09b)(元記事は[**こちら**](https://devblogs.microsoft.com/dotnet/announcing-net-7-preview-1/))。昨年 11 月に .NET 6 が GA してから初のメジャーバージョンアップデートの最初のプレビュー版のリリースとなり、ASP.NET Core など Web 開発関連のアップデートも含まれます。

https://devblogs.microsoft.com/dotnet/announcing-net-7-preview-1/

https://qiita.com/uikou/items/cd9121579381050dc09b

.NET 7 では、ASP.NET Core 全体に幅広く投資し、アップデートを行っていく予定です。以下は、現時点でフォーカスしたいと考えているカテゴリーになります。

* **パフォーマンス**: .NET 6 には、多くの ASP.NET Core 関連のパフォーマンス改善が含まれています。.NET 7 では ASP.NET Core をさらに高速に、かつ効率的に利用できるようにするための作業が行われる予定です。
* **HTTP/3**：HTTP/3 サポートは .NET 6 でプレビュー機能としてリリースされましたが、.NET 7 で完成版に仕上げ、既定で有効となるサポート機能にしていきたいと考えています。将来的には、プレビューにおいて、HTTP/3 サポートに高度な TLS 機能の提供と、さらなるパフォーマンスの向上を目指していきます。
* **Minimal API** :  Minimal API のためのコア プリミティブとして、エンドポイント フィルタとルート グループ化のサポートを追加しました。また、一般的な API 認証および認可の設定もシンプルにします。
* **gRPC**: gRPC JSON transcoding に投資しています。この機能により、gRPC サービスは JSON のリクエストとレスポンスで RESTful HTTP API のように呼び出すことが可能になります。
* **SignalR**: Strong-typed クライアント サポートを追加し、クライアントからの呼び出しの結果を返します。
* **Razor**: Razor コンパイラに様々な改良を施すことで、パフォーマンスや耐障害性を向上させ、ツールをどんどん改良していく予定です。
* **Blazor**: .NET MAUI、Windows Presentation Foundation (WPF)、Windows Forms の Blazor Hybrid サポート終了後、Blazor の幅広い改良を行っていこうと考えています。内容は以下の通り。
    * .NET WebAssembly の新機能：ミックスモード AOT、マルチスレッド、Web 暗号化
    * 強化されたホット リロードのサポート
    * データ バインディングの改善
    * より柔軟なプリ レンダリング
    * Blazor Server サーキットのライフサイクルをより細かい制御が可能に
    * マイクロ フロントエンドのサポート強化
* **MVC**: エンドポイント ルーティング、リンク生成、パラメータ バインディングの改良
* **[Orleans](https://dotnet.github.io/orleans/)** (※注) :  ASP.NET Core と Orleans チームは、分散プログラミングモデルである Orleans を ASP.NET Core とさらに連携・統合する方法を模索しています。Orleans 4 は .NET 7 とともにリリースされ、可読性がある ストリーム ID や最適化された新しい[バージョン トレラントなシリアル化](https://docs.microsoft.com/ja-jp/dotnet/standard/serialization/version-tolerant-serialization)など、シンプルさおよび、保守性、パフォーマンスに重きを置いていこうと考えています。

> ※注 : **[Orleans](https://dotnet.github.io/orleans/)**
Orleans は、Microsoft Research によって開発された、.NET によってクロス プラットフォームで分散型アプリケーションを構築するためのフレームワーク。単一のオンプレミス サーバーから、グローバル分散されているクラウド上の高可用性アプリケーションまで拡張可能。最大の特徴は、高度に並列化された分散システム特有の複雑さを、機能制限なしかつ開発者に負担を強いることなく両立させたプログラミング モデル。

.NET 7 で予定されている具体的な ASP.NET Core 作業の詳細については、GitHub で公開されている、[.NET 7 向け ASP.NET Core ロードマップ](https://aka.ms/aspnet/roadmap)の全容をご覧ください。

https://aka.ms/aspnet/roadmap

.NET 7 Preview 1 は、2022 年 11 月の .NET 7 リリースに向けた、.NET 7 プレビュー リリースのうちの最初のものです。最近の On .NET のエピソードで [Daniel Roth](https://devblogs.microsoft.com/dotnet/author/danroth27/) と、[James Montemagno](https://twitter.com/jamesmontemagno) が、.NET 7 と ASP.NET Core で提供される機能などを紐解いています。

<iframe width="701" height="394" src="https://www.youtube.com/embed/VgmsFck3RWU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

このプレビューリリースの新機能の概要は以下のとおりです。

* Minimal API の改善
    * `IFormFile` と `IFormFileCollection` のサポート
    * request ボディを Stream または PipeReaderと してバインド
    * JSON オプション設定
* SignalR クライアント ソース生成機能
* MVC View および Razor ページにおける Null 許容性モデルのサポート
* 検証エラーで JSON プロパティ名を使用する
* dotnet watch のコンソール出力の改善
* rude edit に対して常に再起動する `dotnet watch` の設定
* `ValidationAttribute` で依存関係の注入を使用
* ヘッダーの解析と書き込みの高速化
* gRPC の JSON トランスコーディング

# 早速使ってみる
.NET 7 Preview 1 で ASP.NET Core を使い始めるには、[.NET 7 SDK をインストールします](https://dotnet.microsoft.com/download/dotnet/7.0)。

https://dotnet.microsoft.com/download/dotnet/7.0

Windows 上で Visual Studio をお使いの場合は、[最新版の Visual Studio 2022 プレビュー](https://visualstudio.microsoft.com/vs/preview/)をインストールすることをお勧めします。Visual Studio for Mac では .NET 7 プレビュー サポートはまだ利用できませんが、近日中に提供される予定となっています。

最新の .NET WebAssembly ビルド ツールをインストールするには、昇格したコマンド プロンプトから次のコマンドを実行します。

```
dotnet workload install wasm-tools
```

# 既存のプロジェクトをアップグレードする
既存の ASP.NET Core アプリを .NET 6 から .NET 7 Preview 1 にアップグレードするための対応は以下の通りです。

1. アプリケーションのターゲットフレームワークを `net7.0`  に更新
2. すべての `Microsoft.AspNetCore.*` パッケージ リファレンスを `7.0.0-preview.1.*` に更新します。
3. すべての `Microsoft.Extensions.*` パッケージ リファレンスを `7.0.0-preview.1.*` に更新してください。

ASP.NET Core for .NET 7 の[重大な変更点](https://docs.microsoft.com/dotnet/core/compatibility/7.0#aspnet-core)についてのリストは以下の通りです。

https://docs.microsoft.com/dotnet/core/compatibility/7.0#aspnet-core

# Minimal API の改善
## `IFormFile` と `IFormFileCollection` サポート
`IFormFile` と `IFormFileCollection` を使用して、Minimal API でファイル アップロードを処理できるようになりました。

```csharp
app.MapPost("/upload", async(IFormFile file) =>
{
    using var stream = System.IO.File.OpenWrite("upload.txt");
    await file.CopyToAsync(stream); 
});
```

```csharp
app.MapPost("/upload", async (IFormFileCollection myFiles) => { ... });
```

この機能を認証で使用するには Anti-forgery のサポートが必要ですが、まだ実装されていません。[Minimum API に対する Anti-forgery サポート](https://github.com/dotnet/aspnetcore/issues/38630)は、.NET 7 ロードマップに記載されています。

https://github.com/dotnet/aspnetcore/issues/38630

リクエストに Authorization ヘッダ、クライアント証明書、Cookie ヘッダが含まれる場合の `IFormFile` または `IFormFileCollection` へのバインディングは現時点で無効になっています。本制限は、Anti-forgery サポート関連作業が完了し次第、対応する予定です。(この機能を提供してくれた @martincostello さん、ありがとうございます！)

### `Stream` や `PipeReader` としてバインドできるようになりました
request body を `Stream` または `PipeReader` としてバインドすることで、ユーザーがデータを取り込んで Blob ストレージに保存したり、Azure Queue などのキュー プロバイダにデータを enquere してワーカーやクラウド機能で後処理していくようなシナリオを効率的にサポートすることができるようになります。以下の例は、新たなバインディングのやりかたとなります。

```csharp
app.MapPost("v1/feeds", async (QueueClient queueClient, Stream body, CancellationToken cancellationToken) =>
{
    await queueClient.CreateIfNotExistsAsync(cancellationToken: cancellationToken);
    await queueClient.SendMessageAsync(await BinaryData.FromStreamAsync(body), cancellationToken: cancellationToken);
});
```

`Stream` や `PipeReader` を使用する場合、いくつかの注意点があります。

* データ取り込みの際、`Stream` は `HttpRequest.Body` と同じオブジェクトになります
* request body は、既定ではバッファリングされません。body を読み込んだ後は、巻き戻しすることはできません（Stream を何度も読み込むことはできない）
* `Stream/PipeReader` は、基盤となるバッファは破棄される、あるいは再利用されるため、最小限のアクション ハンドラ以外では使用できません

## JSON オプションの構成
`Microsoft.AspNetCore.Mvc.JsonOptions.ConfigureRouteHandlerJsonOptions` と混同しないようにするため、Minimal API エンドポイントに JSON オプションを構成するための、よりクリーンな新しい API である、`ConfigureRouteHandlerJsonOptions` を導入しました。

```csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureRouteHandlerJsonOptions(options =>
{
    //Ignore Cycles
    options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; 
});    
```

# SignalR クライアント ソース ジェネレータ
@mehmetakbulut さんの貢献により、SignalR 用の新しいクライアントソースジェネレータを追加することができました。

SignalR クライアント ソース ジェネレータは、定義したインターフェースに基づき、厳密に型指定された送受信コードを生成します。厳密に型指定された SignalR のハブから、緩い型指定の `.On("methodName", ...)`  メソッドの代わりに、クライアント上で同じインターフェースを再利用することが可能です。同様に、ハブはそのメソッド用のインターフェースを実装し、クライアントはその同じインターフェースを使ってハブのメソッドを呼び出すことができます。

SignalR クライアント ソース ジェネレータを使用する手順は以下の通りです。

* `Microsoft.AspNetCore.SignalR.Client.SourceGenerator` パッケージへの参照を追加
* プロジェクトに `HubServerProxyAttribute` と `HubClientProxyAttribute` クラスを追加 (※)

> (※) この部分は、おそらく将来のプレビュー版で変更されると思われます。

```csharp
[AttributeUsage(AttributeTargets.Method)]
internal class HubServerProxyAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Method)]
internal class HubClientProxyAttribute : Attribute
{
}
```

* プロジェクトに static partial クラスを追加し、`[HubClientProxy]` および `[HubServerProxy]` 属性で static partial メソッドを記述

```csharp
internal static partial class MyCustomExtensions
{
    [HubClientProxy]
    public static partial IDisposable ClientRegistration<T>(this HubConnection connection, T provider);

    [HubServerProxy]
    public static partial T ServerProxy<T>(this HubConnection connection);
}
```

* コードから partial メソッドを使う

```csharp
public interface IServerHub
{
    Task SendMessage(string message);
    Task<int> Echo(int i);
}

public interface IClient
{
    Task ReceiveMessage(string message);
}

public class Client : IClient
{
    // Equivalent to HubConnection.On("ReceiveMessage", (message) => {});
    Task ReceiveMessage(string message)
    {
        return Task.CompletedTask;
    }
}

HubConnection connection = new HubConnectionBuilder().WithUrl("...").Build();
var stronglyTypedConnection = connection.ServerProxy<IServerHub>();
var registrations = connection.ClientRegistration<IClient>(new Client());

await stronglyTypedConnection.SendMessage("Hello world");
var echo = await stronglyTypedConnection.Echo(10);
```

# MVC ビューおよび Razor ページにおける Null 許容モデルのサポート
ASP.NET Core アプリで NULL 状態チェックを使用する際のエクスペリエンスを向上させるため、NULL 可能なページまたは view モデルの定義を可能にしました。

```csharp
@model Product?
```

# 検証エラーに JSON プロパティ名を使用する
モデル検証で `ModelErrorDictionary` が生成される際、既定ではエラーキーとしてプロパティ名が使用されます。(`"MyClass.PropertyName"`)
モデルのプロパティ名は一般的には実装の詳細であり、単一ページのアプリケーションから扱うには難しい場合があります。新しい  `SystemTextJsonValidationMetadataProvider` (Json.NET 使用時は `NewtonsoftJsonValidationMetadataProvider`) を使用し、代わりに対応する JSON のプロパティ名を使用するよう、検証を設定することができるようになりました。

```csharp
services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider())
});
```

# `dotnet watch` のコンソール出力の改善
`dotnet watch` からのコンソール出力を、ASP.NET Core のログアウトとの整合性を高め、絵文字を目立たせるために、きれいにしました。

絵文字ってこんなです ↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1529732/1c230f2e-5a06-16aa-7402-b856003673b4.png)

以下は、新しい出力がどのようなものかの例です。(ちょっとびっくり…)

```batch
C:BlazorApp> dotnet watch
dotnet watch 🔥 Hot reload enabled. For a list of supported edits, see https://aka.ms/dotnet/hot-reload.
  💡 Press "Ctrl + R" to restart.
dotnet watch 🔧 Building...
  Determining projects to restore...
  All projects are up-to-date for restore.
  You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
  BlazorApp -> C:UsersdarothDesktopBlazorAppbinDebugnet7.0BlazorApp.dll
dotnet watch 🚀 Started
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7148
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5041
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:UsersdarothDesktopBlazorApp
dotnet watch ⌚ File changed: .PagesIndex.razor.
dotnet watch 🔥 Hot reload of changes succeeded.
info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
dotnet watch 🛑 Shutdown requested. Press Ctrl+C again to force exit.
```

# Rude edit に対し、常に再起動するように `dotnet watch` を設定する
`DOTNET_WATCH_RESTART_ON_RUDE_EDIT` 環境変数を `true` に設定することにより、Rude edit（ホット リロードできない編集）に対してプロンプトなしで常に再起動するよう `dotnet watch` を設定します。

# Blazor のカスタム バリデーション属性にサービスを注入する
Blazor のカスタム バリデーション属性にサービスを注入できるようになりました。Blazor は ValidationContext をセットアップし、サービス プロバイダとして使えるようにします。(@MariovanZeist さん、ありがとうございます！)

```csharp
public class SaladChefValidatorAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var saladChef = validationContext.GetRequiredService<SaladChef>();
        if (saladChef.ThingsYouCanPutInASalad.Contains(value.ToString()))
        {
            return ValidationResult.Success;
        }
        return new ValidationResult("You should not put that in a salad!");
    }
}

// Simple class configured as a service for dependency injection
public class SaladChef
{
    public string[] ThingsYouCanPutInASalad = { "Strawberries", "Pineapple", "Honeydew", "Watermelon", "Grapes" };
}
```

# ヘッダ解析・書き込みの高速化
HTTP/2 および HTTP/3 のヘッダ解析および書き込み時のパフォーマンスに対し、いくつかの改善を施しました。詳しくは以下の Pull Request をご覧ください。

* [HTTP/2: Improve incoming header performance](https://github.com/dotnet/aspnetcore/pull/38834)
* [HTTP/3: Optimize validating and setting incoming headers]()
* [HTTP headers enumerator move directly to next](https://github.com/dotnet/aspnetcore/pull/37538)

# gRPC JSON トランスコーディング
gRPC JSON トランスコーディングは、gRPC サービスを RESTful HTTP API のように使用することができるようにしてくれます。一度設定すれば、gRPC JSON トランスコーディングは使い慣れた HTTP の概念と同じように gRPC メソッドを呼び出すことができます。

* HTTP 動詞
* URL パラメータ バインディング
* JSON リクエスト および レスポンス
* 
もちろん gRPC も継続して使用いただけます。gRPC サービスを RESTful API で提供します。重複はありません!

ASP.NET Core では、gRPC HTTP API というライブラリを使って、この機能を試験的にサポートしています。.NET 7 にて、現時点ではまだ含まれていませんが、将来的に本機能を ASP.NET Coreのサポート対象にする予定としており、既存の試験的なパッケージをお試しいただけます。
詳細については、[gRPC HTTP API getting started](https://github.com/aspnet/AspLabs/tree/main/src/GrpcHttpApi) のドキュメントを参照してみてください。

https://github.com/aspnet/AspLabs/tree/main/src/GrpcHttpApi

# フィードバック
.NET 7 における、ASP.NET Core 7のプレビュー リリースをぜひ皆さんにお楽しみいただき、今後の .NET 7 のロードマップにご期待いただければ幸いです。また、ぜひ今回のリリースとロードマップに関するフィードバックについて、.NET の [GitHub issue](https://github.com/dotnet/aspnetcore/issues/new) 、あるいは[ロードマップ](https://aka.ms/aspnet/roadmap)にコメントなどの形でいただけることを楽しみにしています。

https://github.com/dotnet/aspnetcore/issues/new

https://aka.ms/aspnet/roadmap

ASP.NET Core についてぜひお試しくださいませ！

---------------------------

以上、[Daniel Roth](https://devblogs.microsoft.com/dotnet/author/danroth27/) (Principal Program Manager, ASP.NET) さんでした。
それではみなさん、ごきげんよう。