MS_OWINMigrationSteps - NetDevInfraWGinOSSConsortium/NetDevInfraWiki GitHub Wiki

OWIN化手順

概要

目的

セルフホストや OWIN ミドルウェアを使用するために、パイプラインを OWIN 化する。

手順

  • ホスティング ランタイムをインストールする。
  • 利用したい、OWIN ミドルウェアをインストールする。
  • Startup クラスを追加して、OWIN 関連の設定を行う。

補足(この作業の位置付けと現在の意味): 本ページは
.NET Framework 上の既存 ASP.NET アプリを OWIN 化する手順である。

【なぜ OWIN 化するのか(当時の動機)】
 ① 【ASP.NET Identity を使いたい】
      → OWIN ミドルウェアとして作られているため、OWIN 化が前提
 ② 【SignalR を使いたい】
 ③ 【セルフホストしたい】(IIS なしで動かす)
 ④ 将来の ASP.NET Core への布石

現在の意味は主に ④ である。

【移行の段階】
   ① System.Web 直結の ASP.NET
        ↓ 本ページの作業
   ② OWIN 化した ASP.NET(IIS 上、Microsoft.Owin.Host.SystemWeb)
        ↓ パイプラインの考え方が同じになる
   ③ ASP.NET Core(Kestrel、ミドルウェア パイプライン)

① → ③ を一気に行うのは難しいため、
② を経由すると、認証まわりの構造がほぼそのまま移せる——
という点で、現在も意味のある中間ステップである
(ASP.NET Coreへの移行)。

Startup.Configuration(IAppBuilder app) と
ASP.NET Core の Program.cs / Startup.Configure(IApplicationBuilder app) は
ほぼ同じ形
をしている。

// OWIN(.NET Framework)
public void Configuration(IAppBuilder app)
{
    app.UseCookieAuthentication(new CookieAuthenticationOptions { ... });
}

// ASP.NET Core
var app = builder.Build();
app.UseAuthentication();

詳細

ホスティング ランタイム

既存の Web アプリケーションは、IIS(System.Web)を使用しているため、
IIS でホストするための Microsoft.Owin.Host.SystemWeb を使用することになる。

IIS

  • IIS でホストするには、Microsoft.Owin.Host.SystemWeb を使用する。

  • NuGet で「Microsoft.Owin.Host.SystemWeb」をインストール。

    Install-package Microsoft.Owin.Host.SystemWeb
  • 必要に応じて、以下もインストールする。

    Install-Package Microsoft.Owin.ja
    Install-Package Microsoft.Owin.Host.SystemWeb.ja
  • System.Web のパイプラインを OWIN のパイプラインに流すことができる。

  • これにより、OWIN のミドルウェアを ASP.NET Web Forms や、MVC 5 でも使用できる。

補足(Microsoft.Owin.Host.SystemWeb の仕組み): このパッケージが
何をしているのかを知っておくと、トラブル時に役立つ。

【仕組み】
   このパッケージは、実体としては
   【HttpModule を 1 つ登録している】だけである

     IIS のパイプライン
       ├ … 各種 HttpModule
       ├ 【OwinHttpModule】 ← ここで OWIN パイプラインに流す
       │     └ Startup.Configuration で組んだミドルウェアを実行
       └ … HttpHandler(.aspx / MVC)

つまり、OWIN 化しても IIS/System.Web の上に載ったままである。

【できるようになること】
   ・OWIN ミドルウェアが使える(ASP.NET Identity 等)

【できないこと】
   ・IIS からの脱却(Windows 依存は残る)
   ・System.Web の除去(HttpContext.Current はまだ存在する)
   ・.NET Core での実行

HttpApplication / HttpModule との共存については
HttpApplication(Global.asax)、HttpModule、HttpHandler
を参照。実行順が問題になることがある。

.ja パッケージは**リソース(メッセージの日本語化)**である。
例外メッセージやログが日本語になるだけで、機能は変わらない。
なお、リソースファイル で述べた通り、
例外メッセージを日本語化すると検索性が落ちるため、
開発者向けのメッセージは英語のままの方が良い場合が多い。

OwinHost.exe

  • Katana の OwinHost.exe でホストするには、OwinHost を使用する。

  • NuGet で「OwinHost」をインストール。

    install-package OwinHost

補足(Katana とは): Katana は
Microsoft による OWIN の実装(ホストとミドルウェア群)の
プロジェクト名である。

種類 パッケージ 内容
IIS 上でホスト Microsoft.Owin.Host.SystemWeb 既存アプリの OWIN 化(本ページの主題)
セルフホスト(実行ファイル) Microsoft.Owin.SelfHost HttpListener で自前でホスト
OwinHost.exe OwinHost コマンドから起動する汎用ホスト
【セルフホストの意味】
   IIS を立てずに、コンソール アプリや Windows サービスとして
   Web API を動かせる
     → 開発時の起動が速い
     → テストが容易(プロセス内で立てて叩ける)
     → 【ASP.NET Core の Kestrel の先駆け】★

移行メモ(Katana の現況): Katana は開発停止しており、
ASP.NET Core に統合された。
新規に「セルフホストしたい」なら、
ASP.NET Core を素直に使うのが正しい。

// ASP.NET Core:これだけでセルフホストになる(Kestrel)
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello");
app.Run();

OWIN ミドルウェアのインストール

NuGet で「AspNet.Identity」をインストール。

Install-Package Microsoft.AspNet.Identity.Owin
Install-Package Microsoft.AspNet.Identity.EntityFramework
Update-Package

Microsoft.AspNet.Identity.EntityFramework は、
UserStore クラスを自前で実装する場合、不要になる。

補足(UserStore を自前で実装する意味): 原文の
この一文は重要な設計上の選択を指している。

【ASP.NET Identity の構造】

   UserManager<TUser>          … 業務ロジック(パスワード検証、ロック等)
        │ 依存
        ▼
   IUserStore<TUser>           … 【永続化の抽象】★
        │ 実装
        ├ UserStore(EF 版)    … Microsoft.AspNet.Identity.EntityFramework
        └ 自前の UserStore      … Dapper、ADO.NET、既存の認証 DB

依存性反転原則 が
綺麗に適用されている例
であり、

・EF を使いたくない場合([Entity Framework の懸念] 参照)
・【既存の会員テーブルをそのまま使いたい】場合 ★ 実務ではこれが多い
・パスワードのハッシュ形式を既存に合わせたい場合

  → IUserStore(と必要な派生インターフェイス)を実装すれば済む
// 必要なものだけ実装すればよい(機能ごとにインターフェイスが分かれている)
public class MyUserStore :
    IUserStore<MyUser>,
    IUserPasswordStore<MyUser>,      // パスワード認証を使うなら
    IUserLockoutStore<MyUser, string>,   // ロックアウトを使うなら
    IUserTwoFactorStore<MyUser, string>  // 2 要素を使うなら
{ ... }

インターフェイス分離の原則の実例でもある
(使わない機能は実装しなくてよい)。

NuGet で「SignalR」をインストール。

Install-Package Microsoft.AspNet.SignalR
Update-Package

NuGet で「WebApi.OwinSelfHost」をインストール。
※ セルフホストの場合

Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
Update-Package

OWIN Startupクラスの追加・登録

OWIN パイプラインでミドルウェアをつなげ全体を処理する。

Startup クラスは、

  • ホスティング ランタイムと
  • ミドルウェア

を指定して使用できるようにする。

補足(Startup クラスの検出方法): どうやって OWIN が
Startup を見つけるのか
は、はまりやすい点なので補っておく。

【検出の優先順位】
 ① Web.config / app.config の appSettings
      <add key="owin:AppStartup" value="MyApp.MyStartup" />
 ② OwinStartup 属性
      [assembly: OwinStartup(typeof(MyApp.MyStartup))]
 ③ 【規約による検出】
      名前空間 "Startup" または 型名 "Startup" のクラスで、
      void Configuration(IAppBuilder app) を持つもの
// ② の書き方(Startup.cs の先頭に置く)
[assembly: OwinStartup(typeof(MyApp.Startup))]
namespace MyApp
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app) { ... }
    }
}

典型的な障害:

・「アセンブリに OwinStartup 属性が見つかりません」
    → 規約にも合っていない。属性を付けるか、名前を Startup にする
・Startup が【2 つ見つかる】
    → 参照している別アセンブリにも Startup がある
    → appSettings の owin:AppStartup で明示する ★
・OWIN 自体を無効化したい
    → <add key="owin:AutomaticAppStartup" value="false" />

ASP.NET Core では、この検出の仕組みはなくなった
(Program.cs で明示的に組み立てるため)。

共通

既存の Bundle、Routing 等を Global.asax.cs から OWIN Startup.cs に移動してもイイが、
必要性は無い。

補足(「移動しても良いが必要性はない」の理由): 原文の判断は
妥当である。理由を補う。

【Global.asax.cs(Application_Start)】
   ・System.Web が起動時に 1 回呼ぶ
   ・RouteConfig / BundleConfig / FilterConfig など
     【System.Web に属する設定】の置き場

【Startup.Configuration】
   ・OWIN パイプラインの構築
   ・【OWIN ミドルウェア】の登録

 → 責務が違う。無理に片方へ寄せる必要はない

ただし、寄せる利点もある。

・起動時の処理が【1 箇所に集まる】ので読みやすい
・ASP.NET Core への移行時に、Startup に寄せておくと移しやすい ★
  (Core では Global.asax がないため、いずれ移すことになる)

ASP.NET MVC の Modernization では
実際に Startup へ寄せた例
が示されている
(RouteConfig.RegisterRoutes 等を Configuration から呼んでいる)。
移行を見据えるなら、こちらの方が良い。

Startup クラスで ConfigureAuth(app) メソッドを実行する。

public partial class Startup
{
  public void Configuration(IAppBuilder app)
  {
    ConfigureAuth(app);
    app.MapSignalR();
  }
}

ConfigureAuth メソッドはテンプレート上、Partial クラスに定義されている。

結構大きめの実装なので割愛

必要に応じて、実際に[認証の変更]→[個人のユーザアカウント]のオプションで
プロジェクトテンプレートを使用してプロジェクトを生成して確認すること。

補足(ConfigureAuth の中身の要点): 「割愛」とされている部分の
骨格だけ押さえておくと、ASP.NET Core との対応が見える。

// App_Start/Startup.Auth.cs(テンプレートが生成する)
public partial class Startup
{
    public void ConfigureAuth(IAppBuilder app)
    {
        // ① DI 的な登録(リクエストごとに 1 インスタンス)
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

        // ② Cookie 認証ミドルウェア
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            ExpireTimeSpan = TimeSpan.FromMinutes(30),
        });

        // ③ 二要素・外部ログイン用の一時 Cookie
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        // ④ 外部プロバイダー
        // app.UseGoogleAuthentication(...);
        // app.UseMicrosoftAccountAuthentication(...);
    }
}

ASP.NET Core との対応:

OWIN(.NET Framework) ASP.NET Core
app.CreatePerOwinContext<T> services.AddScoped<T>()(標準の DI)★
app.UseCookieAuthentication services.AddAuthentication().AddCookie() + app.UseAuthentication()
app.UseExternalSignInCookie AddCookie(IdentityConstants.ExternalScheme)
app.UseGoogleAuthentication .AddGoogle(options => ...)

CreatePerOwinContext は「OWIN 版の DI」であり、
Core では標準の DI コンテナに置き換わった
(.NET Core における DI)。
ここが移行時に最も書き換えが必要な箇所
である。

Startup クラスで app.MapSignalR() メソッドを実行する。

public partial class Startup
{
  public void Configuration(IAppBuilder app)
  {
    ConfigureAuth(app);
    app.MapSignalR();
  }
}

Startup クラスで Routing 定義を行い app.UseWebApi() メソッドを実行する。
※ セルフホストの場合

public static class Startup
 {
   public static void ConfigureApp(IAppBuilder app)
   {
     // Configure Web API for self-host. 
     HttpConfiguration config = new HttpConfiguration();

     config.Routes.MapHttpRoute(
      name: "DefaultApi",
      routeTemplate: "api/{controller}/{id}",
      defaults: new { id = RouteParameter.Optional }
    );

    app.UseWebApi(config);
  }
}

.NET Framework のテンプレートでは、Routing 定義は、
Global.aspx の Application_Start から呼びだされる
RouteConfig クラスの RegisterRoutes メソッドで実行されれている。

移行メモ(原文の記述の細部): 最終段落の
「Global.aspx」は Global.asax が正しい
(.aspx はページ、.asax がアプリケーション ファイル)。

また、Web API の場合、Routing 定義を行うのは
RouteConfig.RegisterRoutes ではなく
WebApiConfig.Register である
(App_Start/WebApiConfig.cs)。

// .NET Framework のテンプレート(IIS ホスト時)
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional });
    }
}
// Global.asax.cs から
GlobalConfiguration.Configure(WebApiConfig.Register);

ASP.NET MVC の Modernization の
Startup の例では、これを Configuration から呼んでいる

(WebApiConfig.Register(GlobalConfiguration.Configuration))。

補足(サンプルの ConfigureApp という名前): OWIN の規約で
検出されるメソッド名は Configuration である。
このサンプルが ConfigureApp なのは、
セルフホスト時に WebApp.Start<Startup>() から
明示的に呼ぶ
形を想定しているためと読める。

// セルフホストの起動
using (WebApp.Start<Startup>("http://localhost:8080/"))
{
    Console.WriteLine("started");
    Console.ReadLine();
}

この形ならメソッド名は Configuration である必要がある。
名前を変えるなら、起動側で明示的に呼ぶ。

参考

OWIN ミドルウェア

手順

Microsoft Learn


Tags: 移行, .NET開発, OWIN

⚠️ **GitHub.com Fallback** ⚠️