ASP.NET Core Middleware Fundamentals - 日本語意訳

- 2017/03/23(Thu) 時点の情報に基づいています
- 公式ドキュメント：[ASP.NET Core Middleware Fundamentals](https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/middleware)


# ASP.NET Core Middleware 基礎編

## Middleware とは何か

Middleware とは、リクエストとレスポンスを処理するためのアプリケーションパイプラインとして組み立てられたソフトウェアです。各コンポーネントは、パイプラインの次のコンポーネントにリクエストを渡すかどうかを選択し、次のコンポーネントが呼び出される前後に任意のアクションを実行することができます。リクエストデリゲートは、リクエストパイプラインを構築するために使用されます。リクエストデリゲートは、各 HTTP リクエストを処理します。

リクエストデリゲートは、`Startup` クラスの `Configure` メソッドに渡される [IApplicationBuilder](https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.builder.iapplicationbuilder) インスタンスの [Run](https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.builder.runextensions), [Map](https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.builder.mapextensions), [Use](https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.builder.useextensions) 拡張メソッドを使用して構成されます。個々のリクエストデリゲートは、匿名メソッド(インライン Middleware と呼ばれます)としてインラインで指定することも、再利用可能なクラスで定義することもできます。これらの再利用可能なクラスおよびインライン匿名メソッドが *Middleware* または *Middleware コンポーネント* です。リクエストパイプライン中の各 Middleware コンポーネントは、パイプライン内の次のコンポーネントを呼び出す役割を担います、あるいは、必要に応じてチェインを短絡します。

[Migrating HTTP Modules to Middleware](../migration/http-modules.md) では、ASP.NET Core におけるリクエストパイプラインの以前のバージョンとの違いを説明し、多くの Middleware サンプルを提供します。

## IApplicationBuilder を使用した Middleware パイプラインの作成

ASP.NET Core のリクエストパイプラインは、次の図に示すように(実行スレッドは黒矢印に従って)、次々に呼び出される一連のリクエストデリゲートで構成されています。：

![Request processing pattern showing a request arriving, processing through three middlewares, and the response leaving the application. Each middleware runs its logic and hands off the request to the next middleware at the next() statement. After the third middleware processes the request, it's handed back through the prior two middlewares for additional processing after the next() statements each in turn before leaving the application as a response to the client.](https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/middleware/_static/request-delegate-pipeline.png)

各デリゲートは、次のデリゲートの前後に操作を実行できます。デリゲートはまた、次のデリゲートにリクエストを渡さないように決定することもでき、これは、リクエストパイプラインの短絡と呼ばれます。短絡はしばしば望ましいものです。なぜなら不必要な作業を回避することができるからです。例えば、Static file Middleware は、静的ファイルのリクエストを返し、残りのパイプラインを短絡することができます。Exception-handling デリゲートは、パイプラインの早い段階で呼ばれる必要があります。そうすれば、パイプラインの後の段階で発生する例外をキャッチすることができます。

可能な限り簡単な ASP.NET Core アプリケーションは、すべてのリクエストを処理するただ一つのリクエストデリゲートをセットアップします。このケースでは、実際のリクエストパイプラインは含まれていません。その代わり、すべての HTTP リクエストに対するレスポンスの中で、単一の無名関数が呼び出されます。

```csharp:C#
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello, World!");
        });
    }
}
```

最初の [app.Run](https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.builder.runextensions) デリゲートは、パイプラインを終了します。

[app.Use](https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.builder.useextensions) メソッドを使って、複数のリクエストデリゲートをつなげることができます。`next` パラメーターは、パイプライン内の次のデリゲートを表します。(*next* パラメーターを**呼ばない**ことによってパイプラインを短絡できることを覚えておきましょう。) 次のサンプルで説明するように、一般的に、次のデリゲートの前後でアクションを実行することができます。：

```csharp:C#
public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}
```

><font color="red">**WARNING**</font>
><font color="red"> `next` を呼び出した後に `HttpResponse` を変更する際は注意してください。なぜなら、レスポンスが既にクライアントに送信されてしまっているかもしれないからです。[HttpResponse.HasStarted](https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.http.features.httpresponsefeature#Microsoft_AspNetCore_Http_Features_HttpResponseFeature_HasStarted) を使ってレスポンスヘッダーが送信されかどうかをチェックできます。</font>

><font color="red">**WARNING**</font>
><font color="red">`write` メソッドを呼んだ後に
 `next.Invoke` を**呼ばない**でください。Middleware コンポーネントは、レスポンスを生成するか、`next.Invoke` を呼ぶかのどちらかを行いますが、両方を行うことはできません。</font>


## 順序付け

`Configure` メソッドの中で Middleware コンポーネントが追加される順序は、そのままリクエストに対してコンポーネントが呼び出される順序となり、また、レスポンスに対してはその逆順となります。この順序付けは、セキュリティ、パフォーマンス、機能にとって非常に重要です。

`Configure` メソッドは(下記参照) 、次の Middleware コンポーネントを追加します。

1. Exception/error handling
2. Static file server
3. Authentication
4. MVC

```csharp:C#
public void Configure(IApplicationBuilder app)
{
    app.UseExceptionHandler("/Home/Error"); // Call first to catch exceptions
                                            // thrown in the following middleware.

    app.UseStaticFiles();                   // Return static files and end pipeline.

    app.UseIdentity();                     // Authenticate before you access
                                           // secure resources.

    app.UseMvcWithDefaultRoute();          // Add MVC to the request pipeline.
}
```

上記のコードでは、`UseExceptionHandler` がパイプラインに追加された最初の Middleware コンポーネントです。従って、後の呼び出しで発生するどんな例外もキャッチします。

Static file Middleware はパイプラインの早い段階で呼ばれるので、残りのコンポーネントを経由することなくリクエストを処理し短絡を行うことができます。Static file Middleware は認証チェックを**行いません**。*wwwroot* 配下にあるファイルを含む Static file Middleware によって提供されるファイルはすべてパブリックに利用可能です。静的ファイルを保護する方法については、[Working with static files](xref:fundamentals/static-files) を参照してください。

リクエストが Static file Middleware で処理されない場合、Identity Middleware(`app.UseIdentity`) に渡され、認証チェックが行われます。Identity Middleware は認証されていないリクエストを短絡しません。Identity Middleware はリクエストを認証しますが、認可(および拒否)は MVC が特定のコントローラーとアクションを選択した後にのみ発生します。

次の例は、Response compression Middleware よりも先に Static file Middleware によって静的ファイルのリクエストが処理される順序を表しています。この Middleware の順序では、静的ファイルは圧縮されません。[UseMvcWithDefaultRoute](https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.builder.mvcapplicationbuilderextensions#Microsoft_AspNetCore_Builder_MvcApplicationBuilderExtensions_UseMvcWithDefaultRoute_Microsoft_AspNetCore_Builder_IApplicationBuilder_) からの MVC  レスポンスは圧縮できます。

```csharp:C#
public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();         // Static files not compressed
                                  // by middleware.
    app.UseResponseCompression();
    app.UseMvcWithDefaultRoute();
}
```

### Run, Map, Use メソッド

`Run`, `Map`, `Use` メソッドを使って HTTP パイプラインを構成します。`Run` メソッドはパイプラインを短絡します(つまり、次のリクエストデリゲートを呼びません)。`Run` メソッドは規約であり、Middleware コンポーネントによっては、パイプラインの最後で実行される `Run[Middleware]` メソッドを公開している場合があります。

`Map*` メソッドはパイプラインを分岐するための規約として使用されます。[Map](https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.builder.mapextensions) メソッドは、指定したパスとリクエストが一致した場合、リクエストパイプラインを分岐します。リクエストパスが指定されたパスで始まる場合、分岐が行われます。

```csharp:C#
public class Startup
{
    private static void HandleMapTest1(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 1");
        });
    }

    private static void HandleMapTest2(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 2");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Map("/map1", HandleMapTest1);

        app.Map("/map2", HandleMapTest2);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
        });
    }
}
```

次の表は、前のコードを使用した http://localhost:1234 からのリクエストとレスポンスを示しています。

| Request | Response |
| --- | --- |
| localhost:1234 | Hello from non-Map delegate.  |
| localhost:1234/map1 | Map Test 1 |
| localhost:1234/map2 | Map Test 2 |
| localhost:1234/map3 | Hello from non-Map delegate.  |

`Map` が使用された場合、一致したURLセグメントは `HttpRequest.Path` から取り除かれ、`HttpRequest.PathBase` に追加されます。

[MapWhen](https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.builder.mapwhenextensions) は、指定した述語(predicate)の結果が true である場合、リクエストパイプラインを分岐します。`Func<HttpContext, bool>` 型であればどんな述語(predicate)でも使うことができます。次の例では、クエリ文字列に `branch` が含まれているかどうかを検出するために述語(predicate)が使われています。

```csharp:C#
public class Startup
{
    private static void HandleBranch(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            var branchVer = context.Request.Query["branch"];
            await context.Response.WriteAsync($"Branch used = {branchVer}");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
                               HandleBranch);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
        });
    }
}
```

次の表は、前のコードを使用した http://localhost:1234 からのリクエストとレスポンスを示しています。

| Request | Response |
| --- | --- |
| localhost:1234 | Hello from non-Map delegate.  |
| localhost:1234/?branch=master | Branch used = master|

`Map` は入れ子をサポートしています。例：

```csharp
app.Map("/level1", level1App => {
       level1App.Map("/level2a", level2AApp => {
           // "/level1/level2a"
           //...
       });
       level1App.Map("/level2b", level2BApp => {
           // "/level1/level2b"
           //...
       });
   });
   ```

`Map` は複数のURLセグメントを一度に照合することもできます。例：

 ```csharp
app.Map("/level1/level2", HandleMultiSeg);
```

## ビルトイン Middleware

ASP.NET Core には、次の Middleware コンポーネントが同梱されています。：

| Middleware | Description |
| ----- | ------- |
| [Authentication](xref:security/authentication/identity) | 認証をサポートします。 |
| [CORS](xref:security/cors) | Cross-Origin Resource Sharing を構成します。 |
| [Response Caching](xref:performance/caching/middleware) | レスポンスのキャッシュサポートを提供します。 |
| [Response Compression](xref:performance/response-compression) | レスポンスの圧縮サポートを提供します。 |
| [Routing](xref:fundamentals/routing) | リクエストのルーティングを定義し制約します。 |
| [Session](xref:fundamentals/app-state) | ユーザーセッションの管理をサポートします。 |
| [Static Files](xref:fundamentals/static-files) | 静的ファイルの提供とディレクトリ参照をサポートします。 |
| [URL Rewriting Middleware](xref:fundamentals/url-rewriting) | URL書き換えとリクエストのリダイレクトをサポートします。 |

## Middleware を作る

Middleware は一般に、クラスにカプセル化され、拡張メソッドで公開されます。次の Middleware を考えてみましょう。この Middleware は、クエリ文字列から現在のリクエストのカルチャをセットします。：

```csharp:C#
public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use((context, next) =>
        {
            var cultureQuery = context.Request.Query["culture"];
            if (!string.IsNullOrWhiteSpace(cultureQuery))
            {
                var culture = new CultureInfo(cultureQuery);

                CultureInfo.CurrentCulture = culture;
                CultureInfo.CurrentUICulture = culture;
            }

            // Call the next delegate/middleware in the pipeline
            return next();
        });

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync(
                $"Hello {CultureInfo.CurrentCulture.DisplayName}");
        });

    }
}
```

注意：上記のサンプルコードは、Middleware コンポーネントの作成を示すために使用されているだけです。ASP.NET Coreのビルトインローカリゼーションサポートについては、[
Globalization and localization](xref:fundamentals/localization) を参照してください。

例えば、`http://localhost:7997/?culture=no` のようにカルチャを渡すことでミドルウェアをテストできます。


次のコードは、前述の Middleware を、インラインの代わりにクラスで作成したものになります。：

```csharp:C#
using Microsoft.AspNetCore.Http;
using System.Globalization;
using System.Threading.Tasks;

namespace Culture
{
    public class RequestCultureMiddleware
    {
        private readonly RequestDelegate _next;

        public RequestCultureMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public Task Invoke(HttpContext context)
        {
            var cultureQuery = context.Request.Query["culture"];
            if (!string.IsNullOrWhiteSpace(cultureQuery))
            {
                var culture = new CultureInfo(cultureQuery);

                CultureInfo.CurrentCulture = culture;
                CultureInfo.CurrentUICulture = culture;

            }

            // Call the next delegate/middleware in the pipeline
            return this._next(context);
        }
    }
}
```

次の拡張メソッドは [IApplicationBuilder](https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.builder.iapplicationbuilder) を介して Middleware を公開します。：

```csharp:C#
using Microsoft.AspNetCore.Builder;

namespace Culture
{
    public static class RequestCultureMiddlewareExtensions
    {
        public static IApplicationBuilder UseRequestCulture(
            this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<RequestCultureMiddleware>();
        }
    }
}
```

次のコードでは、`Configure` 内で Middleware を呼び出します。：

```csharp:C#
public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.UseRequestCulture();

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync(
                $"Hello {CultureInfo.CurrentCulture.DisplayName}");
        });

    }
}
```

Middleware は、[明示的な依存関係の原則](http://deviq.com/explicit-dependencies-principle/)に従い、その依存関係をコンストラクタ引数として公開すべきです。 Middleware は**アプリケーションライフタイム**ごとに一度だけ生成されます。もしリクエスト内でミドルウェアとサービスを共有する必要がある場合は、以下の*リクエストごとの依存関係*を参照してください。

Middleware コンポーネントは、Dependency Injection によってコンストラクタ引数から依存関係を解決することができます。
 [`UseMiddleware<T>`](https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.builder.usemiddlewareextensions#methods_summary) can also accept additional parameters directly.
[`UseMiddleware<T>`](https://docs.microsoft.com/en-us/aspnet/core/api/microsoft.aspnetcore.builder.usemiddlewareextensions#methods_summary) を使えば、追加引数を直接受け取ることもできます。

### リクエストごとの依存関係

Middleware は、リクエストごとではなく、アプリケーションの起動時に生成されるため、 Middleware のコンストラクタで注入された
 Scoped Service(リクエストごとに生成される*限定された*生存期間をもつ Service) は、リクエストごとに他のクラスに注入される  Scoped Service とは別物となり共有されません。もし Middleware と他のクラスで Scoped Service を共有しなければならないときは、`Invoke` メソッドの引数に追加してください。Dependency Injection によって引数で受け取ることができるようになります。例：

```csharp:c#
public class MyMiddleware
{
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
    {
        svc.MyProperty = 1000;
        await _next(httpContext);
    }
}
```

## リソース

* [Sample code used in this doc](https://github.com/aspnet/Docs/tree/master/aspnetcore/fundamentals/middleware/sample)
* [Migrating HTTP Modules to Middleware](../migration/http-modules.md)
* [Application Startup](startup.md)
* [Request Features](request-features.md)