Autofac について調べてみた　その３　WebApi とのインテグレーション

WebApi のインテグレーションがあるようなので、試してみた。私は、C# に慣れていないので、いくつかつまづいたが、それを含めてシェアしたい。今回は、私のメインマシンが壊れたため、Visual Studio for Mac でコーディングしてみた。.NET がしっかり動作した。今回のコードは、[こちらのリポジトリ](https://github.com/TsuyoshiUshio/AutofacIntegration)に置いている。

# Visual Studio for Mac での準備事項

次の方法で、Visual Studio for Mac での環境を整えた。

## ASP.NET のプロジェクトを作成

<img width="1264" alt="Screen Shot 2017-07-23 at 23.20.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/c27e8d7e-ce24-cb51-4b3e-213c9b55701a.png">

## WebApi を選択

複雑さを低減するために、最も単純にした。

<img width="1264" alt="Screen Shot 2017-07-23 at 23.20.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/6ae50004-4226-c207-8e0e-e9ef560f9f93.png">

## プロジェクト名の指定

<img width="1264" alt="Screen Shot 2017-07-23 at 23.20.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/0858e875-85be-acad-9c93-3b822c5b23bb.png">

## Autofac のインストール
Packages を右クリックして、Autofac.WebApi2 で検索して、インストールする。ただし、そのままではうまく動作しなかった。特定のメソッドが見つからない。どうやら、Autofac.WebApi2 の依存するAutofac が古いらしい。(3.5) インストール後、Package を右クリック して、Update するとワーニングは出るが、動作はする。現在のコードをみると、Autofac がバージョン4.6.0 になっている様子。`ContainerBuilder#Register` で依存が3.5である旨のワーニングが出るが、動作には今のところ支障はない様子。Windows だったらこの辺りは出なかった。

<img width="1264" alt="Screen Shot 2017-07-23 at 23.22.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/f2dc7440-9fbd-9604-0c77-ef2fd002d74f.png">

# WebApi アプリケーションの作成

<img width="282" alt="Screen Shot 2017-07-23 at 23.22.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/32d793dd-96a9-d8af-e812-38ec78096477.png">

Visual Studio Community for Mac では、Windows の Visual Studio ほど賢くないので、Controller のテンプレートなどは存在しない。そのままディレクトリをほって、クラスを書く。

今回書いてみたコードはこんなの。

ProductsContorller.cs

```csharp
using System;
using System.Collections.Generic;
using System.Net;
using System.Web.Http;
using WebApiSample.Models;
using System.Linq;

namespace WebApiSample.Controllers
{
    public class ProductsController : ApiController
    {
        Product[] products = new Product[]
        {
            new Product(){ Id = 1, Name = "Curry", Category = "Dinner", Price = 1},
            new Product(){ Id = 2, Name = "Tandory Chicken", Category = "Sub", Price = 2},
            new Product(){ Id = 3, Name = "Mouse", Category = "PC", Price = 10000}
        };

        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }

        public IHttpActionResult GetProduct(int id)
        {
            var product = products.FirstOrDefault((p) => p.Id == id);
            if (product == null)
            {
                return  NotFound();
            }
            return Ok(product);
        }
    }
}

```

Product.cs

```csharp
using System;
namespace WebApiSample.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }
}

```

とても一般的なコードだ。ここに、Autofac のDI の設定を入れる。
Global.asax.cs に次のコードを書く。

```csharp

protected void Application_Start()
{
  var builder = new ContainerBuilder();
  var config = GlobalConfiguration.Configuration; 
  builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); // アセンブリの中のコントローラを取得してくれる。
  builder.RegisterWebApiFilterProvider(config); // オプションで、フィルタのプロバイダを登録する（オプション）
  var container = builder.Build();
  config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
} // 依存性を解決するためのプロパティにAutofacWebApi のリゾルバを追加する。
  GlobalConfiguration.Configure(WebApiConfig.Register); // ルーティングの処理などを書く
```

これだけで、すでに、Autofac が追加された上で、WebApiが動作している。Visual Studio for Mac の実行ボタンを押して、`http://localhost:xxxx/api/products` や、`http://localhost:xxxx/api/product/1` などにアクセスすると、動作しているのがわかるだろう。

# コントローラーの直接登録

先ほどは、アセンブリから、コントローラのリストを取得して、登録していたが、個別にコントローラを登録する方法がある。

```csharp
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); 
```

こちらの方法だ

```csharp
builder.RegisterType<ProductsController>().InstancePerRequest();
```
これは通常の DI の書き方そのものだ。

# フィルタの追加

さて、このままだと、DI の旨味が全く感じられないので、フィルタを作ってみる。ログをコンソールに出力するフィルタを書いてみる。それをDIしてみる。

LoggingActionFilter.cs

```csharp
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Autofac.Integration.WebApi;

namespace WebApiSample.Filters
{
    public class LoggingActionFilter : IAutofacActionFilter
    {
        readonly ILogger logger;

        public LoggingActionFilter(ILogger logger) {
            this.logger = logger;
        }


        public Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
        {
            logger.Write(actionExecutedContext.ActionContext.ActionDescriptor.ActionName);
            return Task.FromResult(0);
        }

        public Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
        {
			logger.Write(actionContext.ActionDescriptor.ActionName);
			return Task.FromResult(0);
        }
    }

    public interface ILogger
    {
        void Write(string message);
    }

    public class ConsoleLogger : ILogger
    {


public void Write(string message)
    {
        Console.WriteLine("[Logging]:" + message);
    }
    }
}

```
`ILogger` インターフェイスと、その実装 `ConsoleLogger` を提供する。IAutofacActionFilter を実装したクラスを作成してフィルタのインジェクションの準備環境。
Global.asax.cs を変更する。

```csharp
        protected void Application_Start()
        {
            var builder = new ContainerBuilder();
            var config = GlobalConfiguration.Configuration;
            builder.RegisterWebApiFilterProvider(config);
           
            builder.RegisterType<ProductsController>().InstancePerRequest();
            builder.RegisterType<ConsoleLogger>().As<ILogger>();
            builder.Register(c => new LoggingActionFilter(c.Resolve<ILogger>()))
                   .AsWebApiActionFilterFor<ProductsController>(c => c.GetProduct(default(int)))													.InstancePerRequest();
            
            var container = builder.Build();
            config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

            GlobalConfiguration.Configure(WebApiConfig.Register);
        }
```

ILogger を登録してあげて、`ContainerBuilder#Register` のラムダ式を使って、フィルタをかけたいメソッドを登録している。これで、アプリを実行すると、動作は変わらないが、Application Output をみると

```
#[Logging]:GetProduct
#[Logging]:GetProduct

```

と出力される。GetAllProducts を実行したとしても、GetProduct にしかフィルターがかかっていないからこのような動作になる。

# 複数のフィルタ適用

では、複数のメソッドにフィルタをかけたい場合はどうするのだろう？ すべてのメソッドが対象なら、ラムダ式を書かなければ良い。

```csharp
builder.Register(c => new LoggingActionFilter(c.Resolve<ILogger>()))
                    .AsWebApiActionFilterFor<ProductsController>().InstancePerRequest();
```

実行結果

```
#[Logging]:GetAllProducts
#[Logging]:GetAllProducts
#[Logging]:GetProduct
#[Logging]:GetProduct

```


じゃあ、複数のメソッドや、複数のコントローラにかけたい場合は？単に列挙すれば良い。

```csharp
builder.Register(c => new LoggingActionFilter(c.Resolve<ILogger>()))
                .AsWebApiActionFilterFor<ProductsController>(c => c.GetProduct(default(int)))
　　　　　　　　　　.InstancePerRequest();
builder.Register(c => new LoggingActionFilter(c.Resolve<ILogger>()))
                 .AsWebApiActionFilterFor<ProductsController>(c => c.GetAllProducts())
                   .InstancePerRequest();
```

実行結果

```
#[Logging]:GetAllProducts
#[Logging]:GetAllProducts
#[Logging]:GetProduct
#[Logging]:GetProduct

```

以上です