WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

要求の中断による処理のキャンセル (CORE)

by WebSurfer 11. July 2021 13:07

IIS をリバースプロキシに使ってのインプロセス ホスティング モデル(この記事の下の方の図参照)でホストされる ASP.NET Core Web アプリは、クライアントによる要求の中断を検出してサーバー側の処理をキャンセルすることができます。(.NET Framework 版の ASP.NET の場合は別の記事「要求の中断による処理のキャンセル (MVC5)」を見てください)

要求の中断

クライアントによる要求の中断とは、上の画像の赤丸印の中のブラウザの ✕ ボタンをクリックするとか Esc キーを押す、Ajax を使っての要求の場合は XMLHttpRequest.abort() メソッドを実行することを意味します。

処理のキャンセルには HttpContext.RequestAborted プロパティで取得できる CancellationToken を利用します。クライアントが上に書いた要求の中断操作を行うと、取得した CancellationToken は操作を取り消す通知を配信します。

キャンセル処理は基本的に先の記事「非同期タスクのキャンセル」に書いたことと同様で、以下のようになると思います。

  1. HttpContext.RequestAborted プロパティで CancellationToken を取得しキャンセルをリッスンするタスクに渡す。(CancellationToken の取得先の CancellationTokenSource の初期化等は ASP.NET Core フレームワークがやってくれるようです)  
  2. タスクにはキャンセルをリッスンして適切に処置を行うコードを実装しておく。
  3. クライアントによる要求の中断が検出されると、フレームワークは CancellationTokenSource.Cancel メソッドを呼び出し、CancellationToken を通じてリッスンしているタスクにキャンセルを通知する。
  4. キャンセル通知を受けたタスクは、あらかじめ実装されているコードに従ってキャンセル処置を行う。  

CancellationToken を渡す方法ですが、ネットで見つけた記事 Handling aborted requests in ASP.NET Core に書いてある通り、渡し先のタスクが MVC や Web API のアクションメソッドであれば引数に CancellationToken を追加しておけば、それに HttpContext.RequestAborted から取得した CancellationToken をモデルバインドしてくれます。

検証は Visual Studio 2019 を使って、以下のコードを [デバッグ(D)] ⇒ [デバッグの開始(S)] で IIS 10 Express のインプロセスホスティングで実行して行いました。検証に使用したブラウザは Edge v91.0.864.67, Chrome v91.0.4472.124, Firefox v89.0.2, IE11, Opera v77.0.4054.203 で、いずれもこの記事を書いた時点での最新版です。

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MvcCore5App4.Models;
using System.Diagnostics;
using Microsoft.AspNetCore.Authorization;
using System.Threading;
using System.Threading.Tasks;
using System;
using Microsoft.AspNetCore.Identity;
using MvcCore5App4.Data;

namespace MvcCore5App4.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        // ・・・中略・・・

        public async Task<IActionResult> Cancel(CancellationToken token)
        {
            _logger.LogInformation($"start: {DateTime.Now:ss.fff}");
            await Task.Delay(5000, token);
            _logger.LogInformation($"end: {DateTime.Now:ss.fff}");
            return View();
        }
    }
}

上のコードの Task.Delay(5000, token) では渡した token を Deley メソッドの中で継続的に観察しているようで、このメソッドが実行が開始された後でもそれから 5 秒以内ならブラウザで要求を中断すると TaskCanceledException がスローされて処理が終わります。

Entity Framework を使ってデータベースにアクセスして処置を行うときに使う ToListAsync とか SaveChangesAsync などや、HttpClient の SendAsync とか PostAsync なども同様かというのが問題と思いますが、詳しくは調べてなくて分かりません。

ToListAsync(token) メソッドで少し調べてみた限りでは、ToListAsync(token) の次の行で CancellationTokenSource.Cancel() としても完了してしまいました。Task.Delay(5000, token) と違って即終わってしまうので間に合わないのか、走り出したらキャンセルは効かないということなのかは分かりません。

ホスティングモデルによる違いですが、Visual Studio 2019 で IIS 10 Express を使ってのアウトプロセス ホスティング モデルでは要求の中断による処理のキャンセルはできませんでした。

Microsoft のドキュメント「インプロセスおよびアウトプロセス ホスティングの相違点」にインプロセス ホスティングでは "クライアントの切断が検出されます。 クライアントが切断されると、HttpContext.RequestAborted キャンセル トークンが取り消されます" と書いてあります。裏を返すとアウトプロセスホスティングではダメと言っているようです。

構成の違いは以下の図(Microsoft のドキュメントから借用)の通りですが、IIS では切断は検出されるものの Kestrel との間は HTTP 通信なので Kestrel に切断を伝えるすべがないということではないかと思います。ちなみに IIS をリバースプロキシとして使わず Kestrel をエッジサーバーとした場合はインプロセスホスティングと同様に切断は検出されキャンセルも効きます。

インプロセス ホスティング

インプロセス ホスティング モデル

アウトプロセス ホスティング

アウトプロセス ホスティング モデル

IIS を使える環境で Kestrel をエッジに使うことはなさそうですし、Linux 系の OS の場合は Nginx とか Apache をリバースプロキシに使って、Kestrel で ASP.NET Core アプリをホストする、即ち IIS を使ってのアウトプロセスホスティングと同じ構成になるので、 結局は IIS を使ってのインプロセス ホスティング モデルでないとキャンセルはできないということではないかと思います。

Tags: , , , ,

CORE

ASP.NET Identity にエンティティ追加

by WebSurfer 23. June 2021 14:54

Visual Studio のテンプレートで認証を「個別のユーザーアカウント」としてプロジェクトを作成すると ASP.NET Identity を使った認証システムが実装されます。それにエンティティを追加する方法を書きます。この記事の例では下の画像の左上の Post エンティティがそれです。

ASP.NET Identity に Post エンティ��ィを追加

Post というのは投稿という意味です。複数のユーザーが複数の投稿をするアプリで、投稿内容を SQL Server データベースで保持し、Post エンティティクラスを定義して Entity Framework 経由で書き込み、読み出し、編集などを行うという想定です。

ユーザーの誰が投稿を書いたかを識別できるように、上の画像のように Post と ApplicationUser をナビゲーションプロパティで紐づけます。(結果、EF Code First で生成されるデータベースでは、ApplicationUser に該当する dbo.AspNetUsers テーブルの Id 列に、dbo.Posts テーブルの ApplicationUserId 列から FK 制約が張られます)

上の画像の Post エンティティ以外の基本のエンティティクラスとコンテキストクラスは ASP.NET Identity の中で定義済みです。(.NET Framework 版の場合は Models/IdentityModels.cs に ApplicationUser クラスと ApplicationDbContext クラスが含まれていて、その継承元で定義されています)

それに Post エンティティを追加してナビゲーションプロパティを張るには Post クラスを追加するだけではダメで、既存の基本のエンティティクラスとコンテキストクラスにも手を加える必要があります。それをどのようにするかというのがこの記事の話です。

.NET Framework 版の場合は自動生成された Models/IdentityModels.cs にコンテキストクラスとエンティティクラスが定義されていますので、それに手を加えて Migration を実行することになります。

手を加えた Models/IdentityModels.cs のサンプルコードは以下の通りです。コードの中で「追加」とコメントした部分を追加しているだけです。(Core 版は場所が違うので注意。この記事の下の方の説明と画像を見てください)

using System.Data.Entity;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;

// 追加
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Mvc5App3.Models
{
    // ApplicationUser クラスにさらにプロパティを追加すると、ユーザーの
    // プロファイル データを追加できます。詳細については、
    // https://go.microsoft.com/fwlink/?LinkID=317594 を参照してください。
    public class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(
                               UserManager<ApplicationUser> manager)
        {
            // authenticationType が CookieAuthenticationOptions
            // .AuthenticationType で定義されているものと一致して
            // いる必要があります
            var userIdentity = await manager.CreateIdentityAsync(
                 this, DefaultAuthenticationTypes.ApplicationCookie);
            // ここにカスタム ユーザー クレームを追加します
            return userIdentity;
        }

        // 追加
        public virtual IList<Post> Posts { get; set; }
    }

    // 追加
    public class Post
    {
        [Key, Required]
        public int PostId { get; set; }

        [Required, MaxLength(128)]
        public string Title { get; set; }

        [Required, MaxLength(1024)]
        public string Content { get; set; }

        [Required, ForeignKey(nameof(User))]
        public string ApplicationUserId { get; set; }

        public virtual ApplicationUser User { get; set; }
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
        }

        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }

        // 追加
        public DbSet<Post> Posts { get; set; }
    }
}

上のコードの通りコンテキストクラスとエンティティクラスに手を加えてから Migration 操作 (Add-Migration, Update-Database) を行うと以下のテーブルが SQL Server データベースに生成されます。

Migration 操作で追加された Posts テーブル

dbo.Posts テーブルの ApplicationUserId 列から ApplicationUser に相当するテーブル dbo.AspNetUsers の Id 列に FK 制約が張られます。もちろん Models/IdentityModels.cs のコードに定義したナビゲーションプロパティも期待通り働きます。


以上は .NET Framework 版の話です。Core 版の場合も上記とほぼ同じようにして既存のデータベースに Posts テーブルを追加し、既存の AspNetUsers テーブルの Id 列に FK 制約を張ることができます。

ただし、Core 版では、Visual Studio のテンプレートで「個別のユーザーアカウント」を選んでプロジェクトを作成しても、ASP.NET Core Identity 関係のソースコードは含まれないことに注意してください。Razor Class Library (RCL) として実装されますのでコンテキストクラスとエンティティクラスに手を加えることができません。

なので、Core 版でこの記事のようなことを行う場合は、認証なしでプロジェクトを作成した後でスキャフォールディング機能を使ってソースコードと共に ASP.NET Core Identity を実装するのが良さそうです。

そうした場合、コンテキストクラスとエンティティクラスは下の画像の場所に生成されます。

コンテキストクラスとエンティティクラス

上の画像ではクラス名が ApplicationDbContext, ApplicationUser となっていますが、デフォルトの設定のまま進めると Application の部分がプロジェクト名になります。スキャフォールディングを行う際に任意に設定できますので、この記事では .NET Framework 版と同じ名前に設定しています。

上の画像の ApplicationDbContext, ApplicationUser クラスに、上のサンプルコードで書いたようにナビゲーションプロパティを追加します。

Post クラスはプロジェクトの既存の Models フォルダにクラスファイルを追加してそれに定義するのが良いと思います。

Tags: , ,

MVC

Firefox で Fiddler を使う方法

by WebSurfer 20. June 2021 12:29

ブラウザに Firefox を使った場合に、要求・応答を Fiddler でキャプチャするにはどうしたら良いかということを書きます。

Fiddler

Fiddler はプロキシとして動き、ブラウザとネットの間の HTTP トラフィックを自動的にキャプチャするツールです。

Fiddler を使うたびユーザーが手動でプロキシの設定を行う必要はありません。Fiddler を起動すると、以下のように自動的にシステムのプロキシが設定され、ブラウザがそのプロキシを使うように設定されていれば Fiddler がトラフィックをキャプチャできるようになります。

なので、ブラウザに Firefox を使う場合も、先の記事「Fiddler のお勧め」に書きましたように Firefox のインターネット接続の設定でシステムのプロキシを利用するオプションを選択しておけば、Fiddler を立ち上げただけで要求応答をキャプチャすることができます。

しかしながら、HTTPS 通信を行う場合はプロキシの設定だけではダメです。Fiddler を通すと下の画像のようにサーバー証明書が信頼できないという警告が出ます。無視して続行しても CSS などがダウンロードできないようで表示が崩れてしまい使い物になりません。

証明書の警告画面

その理由は、Telerik のサイトの Configuring Firefox for Fiddler によると、"This message is shown because Firefox does not use the Windows Trusted Certificate Authority list; it instead has its own list of trusted certificates." ということだそうです。

ちなみに、Fiddler の証明書は DO_NOT_TRUST_FiddlerRoot という名前を持つもので、それは OS の「信頼されたルート証明機関」の中に含まれていました。下の画像を見てください。

Fiddler のサーバー証明書

自分ではインストールした覚えはないので、Fiddler により自動的にインストールされたようです。IE11, Edge, Chrome, Opera はこれを使うので Fiddler を使っても Firefox のような証明書の問題は出ないということのようです。

対処方法は上に紹介した Telerik のサイトの記事に書いてあります。簡単に書くと、Fiddler から証明書をエクスポートして、それを Firefox にインポートするということになります。

Telerik のサイトの記事の画像は古いバージョンの Fiddler, Firefox ものですので、この記事を書いている時点での最新版 Fiddler v5.0.20204.45441 と Firefox v89.0.1 の画像を貼って説明しておきます。

まず Fiddler の証明書をエクスポートします。Fiddler を起動し、メニューバーの[Tools]⇒[Options...]をクリックして Options ダイアログを表示し[HTTPS]タブを選択します。

[HTTPS]タブの中の[Actions]ボタンをクリックすると下の画像のようにリストが表示されますので、リストの中の[Export Root Certificate to Desktop]クリックすると FiddlerRoot.cer という名前の証明書が PC のデスクトップにエクスポートされます。

Fiddler の証明書のエクスポート

次に、Fiddler からエクスポートした証明書 FiddlerRoot.cer を Firefox にインストールします。

Firefox を起動し、設定の「プライバシーとセキュリティ」メニューの「証明書」の項目にある[証明書を表示...(C)]ボタンをクリックし「証明書マネージャー」を表示します。

Firefox の証明書マネージャー

「証明書マネージャー」の[認証局証明書]タブを選択し[インポート(M)...]ボタンをクリックすると証明書を選択するダイアログが表示されるので、デスクトップにエクスポートした Fiddler の証明書 FiddlerRoot.cer を選択します。

選択すると「証明書のインポート」ダイアログが表示されるので、[この認証局によるウェブサイトの識別を信頼する]にチェックを入れて[OK]ボタンをクリックします。

証明書のインポート

これにより、「証明書マネージャー」の証明書一覧の中に DO_NOT_TRUST_FiddlerRoot という名前の証明書が含まれているはずですので確認してください。

ここまでの操作で Firefox の HTTPS 通信で Fiddler を使っても警告は出なくなり、Fiddler によりトラフィックを自動的にキャプチャできるようになります。


ただ、問題がまだ残っていて、Visual Studio から開発中の Web アプリを立ち上げて IIS Express で動かす場合、HTTPS 通信ではトラフィックがキャプチャできません

違いは localhost が相手になるということと、サーバー証明書は Visual Studio が発行した開発用のものになることぐらいだと思います。いろいろ調べてみたのですが原因が分からず解決できていませんが、とりあえず調べたことを以下に書いておきます。

Visual Studio が発行したサーバー証明書は以下の通りです。IIS Express が使うのは一番下の IIS Express Develpment Certificate です (その上は多分 Kestrel が使うものだと思います)。

開発用のサーバー証明書

試しに、一番下の IIS Express Develpment Certificate をエクスポートして Firefox にインポートしようとしたのですが、「この証明書は認証局に証明書ではないため、認証局の一覧には追加できません。」と表示されてインポートできませんでし���。

Microsoft のドキュメント Trust the HTTPS certificate with Firefox to prevent SEC_ERROR_INADEQUATE_KEY_USAGE error によると "The Firefox browser uses it's own certificate store, and therefore doesn't trust the IIS Express or Kestrel developer certificates." とのことで、開発用サーバー証明書は Firefox では使えないということのようです。

その記事の少し下のセクション Configure trust of HTTPS certificate using Firefox browser に書いてある通り、Firefox の設定で security.enterprise_roots.enabled = true としてみました。しかし、依然として Fiddler では Firefox の HTTPS 通信はキャプチャできませんでした。

ちなみに、security.enterprise_roots.enabled = false (デフォルト) の場合、Firefox で開発中の Web アプリにアクセスすると以下の警告が出ます。Fiddler を通しても通さなくても同じ警告になります

localhost への接続の安全性

上の画像の[詳細の表示]をクリックすると以下のダイアログが表示されます。そこに表示されている「検証され信頼できる運営者情報ではありません」というところが問題になっているようです。

Firefox のページ情報

security.enterprise_roots.enabled = true に設定すると、表示は以下のように「接続は安全」と変わります。ただし、Mozilla は証明書の発行者を承認していないそうですし、[詳細の表示]をクリックして表示されるダイアログの情報の「検証され信頼できる運営者情報ではありません」というのも変わりません。

localhost への接続の安全性

開発用のサーバー証明書が「検証され信頼できる運営者情報ではありません」ということが Fiddler でキャプチャできない原因であろうと思いますが、localhost が相手ということにも何かあるのかもしれません。(IE と .NET Framework は localhost の要求をプロキシを通して送らないようにハードコードされているということらしいですが、Firefox にも何かあっても不思議ではないですし)

今日はもうこれ以上調べる気力がなくなったので、今後の課題ということにしたいと思います。(汗)

Tags: , ,

DevelopmentTools

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  September 2021  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

View posts in large calendar