88532
ASP.NET Core MVC（C# .NET 5）で構築したWebアプリケーションにおいてユーザー入力チェックを行いたいと考えています。

単純な入力チェックはModelの検証機能を用いたValidationAttributeを用いることで非常に簡単に行うことができます。しかしDatabaseアクセスを伴うようなValidationAttributeを作った場合、ASP.NET
Core MVCには非同期Validationの仕組みがありません。どのような対応方法を取るのが適切でしょうか。

以下３案は思いついた案ですが、もっと適切な方法があるのではないかと考えています。

  1. Validationの場合はDatabaseアクセスも同期処理として実装する  
`AnyAsync()`, `ToListAsync()`などを用いず、`Any()`, `ToList()`を用いる

  2. 非同期処理を待つ

```

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var model = validationContext.ObjectInstance as SomeModel;
        var domainService = validationContext.GetService<IMyDomainService>();
        var task = domainService.SomeCheckAsync(model);
        Task.WaitAll(task);
        var result = task.Result;
        if (!result)
        {
            return new ValidationResult(FormatErrorMessage(ErrorMessageString));
        }
        return ValidationResult.Success;
    }
    
```

  3. Taskで実行

```

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var model = validationContext.ObjectInstance as SomeModel;
        var domainService = validationContext.GetService<IMyDomainService>();
        var result = Task.Run(() => {
            return domainService.SomeCheckAsync(model);
        }).GetAwaiter().GetResult();
        if (!result)
        {
            return new ValidationResult(FormatErrorMessage(ErrorMessageString));
        }
        return ValidationResult.Success;
    }
    
```

いまいち煮え切らないのは下記のように考えたからです。

  1. ASP.NET Coreは非同期処理を使うことでブロッキングが発生せずに適切なパフォーマンスで動作するという基本設計に反していると思いました。Micorosoft技術者のBlogで同期と非同期を混在させるべきではないとの記載もあったかと思います。
  2. おそらくデッドロックが発生するのではないか（お試しでは発生していない）。
  3. ASP.NETの通常の処理でTask.Run()を使うのはご法度とどこかで読んだ気がするのですが…探せませんでした。

個人的に、ModelのAnnotationとして設定することができるData Annotation Model
Validatorは、ビジネスロジックと分離した検証機構として非常に優れた設計だと考えていますので、ビジネスロジックとして入力チェックを行う案はできる限り避けたいと考えています（そうするのが適切なら、そうするしかないですが）。
CC BY-SA 4.0
2022-02-08T04:56:15.107
86199
2022-04-27T14:35:35.723
8647
question
c# mvc asp.net-core
ASP.NET Core MVCでの非同期Validation
質問に対するコメント欄で「いつまでもこのスレッドを放置しておかないで、あなたの解決策を書いてクローズしてください」とお願いしたのですがやってもらえないので私が書いておきます。

ASP.NET アプリでは Web
サーバーがクライアントから要求を受けるとスレッドプールからスレッドを確保して要求を処置します。非同期操作をしなければ、要求を受けてから応答を返すまで最初に確保したスレッドを保持し続けます。

IO バウンドの処理 (外部データベースや Web API などにアクセスしてデータを取得するなど)
に時間がかかる場合は一旦使っていたスレッドはスレッドプールに戻し、IO
バウンドの処理が終わったらスレッドプールから新たにスレッドを取得してその後の処理を行うようにすればスレッドプールのスレッドの有効利用が可能です。

そのあたりの詳細は以下の Microsoft のドキュメントに図解入りで説明されているので見てください。

ASP.NET の非同期/待機の概要  
<https://docs.microsoft.com/ja-jp/archive/msdn-magazine/2014/october/async-
programming-introduction-to-async-await-on-asp-net>

>   1. Validationの場合はDatabaseアクセスも同期処理として実装する
>

上に書いた理由で IO
バウンドの処理を行う場合は非同期を考えるべきで、データアノテーション属性を使いたいから非同期処理を諦めるというのは考え直した方が良さそうです。

例えば、コントローラーでユーザー入力を処理する過程でデータベースアクセスして検証できるなら、その過程で検証すれば非同期で処理できます。

以下の記事はその例です。データベースにあるデータとの重複を避けるために PK / Unique
制約違反例外をキャッチし、重複していたら差し戻しエラーメッセージを表示して再入力を促すようにしています。

EF Core で PK / Unique 制約違反例外をキャッチ  
<http://surferonwww.info/BlogEngine/post/2021/03/25/aspnet-core-validation-
for-pk-and-unique-restrictions-of-sql-server.aspx>

>   2. 非同期処理を待つ
>

以下の Microsoft のドキュメントの Avoid blocking calls のセクションを見てください
(日本語版もありますが翻訳がアレですので英語版を読むことをお勧めします)。

ASP.NET Core Performance Best Practices  
<https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-
practices?view=aspnetcore-6.0>

やってはいけないこととして以下のように書いてあります。

"Block asynchronous execution by calling Task.Wait or Task.Result."

>   3. Taskで実行
>

これも同じ Microsoft のドキュメントにやってはいけないこととして以下のように書いてあります。

"Call Task.Run and immediately await it. ASP.NET Core already runs app code on
normal Thread Pool threads, so calling Task.Run only results in extra
unnecessary Thread Pool scheduling. Even if the scheduled code would block a
thread, Task.Run does not prevent that."

以上のような訳で、私の回答は「 **検証にデーターベースへのアクセスが必要ならデータアノテーション属性の使用にこだわる必要はなく、上の 1
に対するレスで書いた通りコントローラーその他非同期処理が容易に可能な過程で行えばよい** 」です。
CC BY-SA 4.0
2022-04-27T14:35:35.723
88532
2022-04-27T14:35:35.723
51338
86199
answer