.NETでアプリケーションを開発する際、依存性の注入(DI) という設計パターンは、コードをクリーンでテストしやすく保つための強力な武器になります。そのDIを実現するためのライブラリ(DIコンテナ)として特に人気なのが Autofac です。
この記事では、Autofacの核心的な概念である「DIコンテナ」「オブジェクト・グラフ」「生存期間スコープ」を、工場の例え話を交えながら分かりやすく解説し、ASP.NET Coreでの実践的な使い方までを紹介します。
DIコンテナとは? ~オブジェクトを作る専門工場~
DIコンテナは、一言でいうと 「オブジェクトの生成と管理を専門に行う工場」 です。
通常、あるクラスの中で別のクラスを使いたい場合、new
キーワードで自分でインスタンスを生成します。
public class UsersController
{
private readonly UserService _userService;
public UsersController()
{
// 自分でUserServiceを"new"している(密な依存関係)
_userService = new UserService();
}
}
この方法だと、UsersController
はUserService
という具体的なクラスと密接に結びついてしまいます。もしUserService
の作り方が変わったり、テスト用に偽物(モック)と差し替えたくなった場合に、UsersController
のコードを修正する必要が出てきてしまいます。
そこで DIコンテナ(工場) の出番です。私たちは、事前に「このインターフェース(部品の仕様書)が要求されたら、このクラス(部品の設計図)からインスタンス(実物の部品)を作ってください」というルールを工場に登録しておきます。
UsersController
は「IUserService
という仕様の部品をください」と工場に依頼するだけです。工場はルールに従って適切なオブジェクトを生成し、UsersController
に渡してくれます。これにより、UsersController
は具体的なクラスを知らなくてもよくなり、コードの柔軟性が格段に向上するのです。
このオブジェクト工場全体が、Autofacでいうところの コンテナ (IContainer
) にあたります。
オブジェクト・グラフとは? ~製品の組み立て手順書~
次に オブジェクト・グラフ です。これは「あるオブジェクトを完成させるために必要な、依存関係の連鎖全体」を指します。工場の例えで言うなら、「製品の組み立て手順書」 です。
例えば、UsersController
という製品を作るための手順書には、次のように書かれています:
-
UsersController
を組み立てるには、IUserService
という中間部品が必要 -
IUserService
部品を組み立てるには、IUserRepository
という基礎部品が必要 -
IUserRepository
部品は、それ以上他の部品を必要としない
DIコンテナは、私たちが「UsersController
を1つください」と注文すると、この手順書(オブジェクト・グラフ)を自動的に解析し、末端のIUserRepository
から順番に部品を組み立てて、最終的にUsersController
を完成させてくれる、賢い工場長のような存在なのです。
生存期間スコープとは? ~安全な短期作業スペース~
さて、ここがAutofacを理解する上で最も重要な 生存期間スコープ (ILifetimeScope
) の話です。
生成したオブジェクトを使い終えたとき、そのオブジェクトを解放(release)することは重要なことです。しかしながら、Autofacには明示的にオブジェクトを解放するようなメソッド(たとえば、Releaseメソッドなど)は用意されておらず、その代わりに、「生存期間スコープ(lifetime scope)」と呼ばれる概念を用いるようになっています。この生存期間スコープはコンテナの使い捨て可能なコピーとして見ることができ、生成したオブジェクトの再利用を行える境界を定めるものとなります。
なぜ依存を注入するのか DIの原理・原則とパターン
まず、なぜこの仕組みが必要なのか、メモリリークという問題から見ていきましょう。
メモリリークとは? 💧
メモリリーク とは、「使い終わって不要になったオブジェクトが、メモリ上から解放されずに残り続けてしまう現象」です。例えるなら、作業が終わったのに道具を片付け忘れて、作業机の上がどんどん散らかっていく状態です。これが続くと、最終的にアプリケーションのパフォーマンス低下やクラッシュを引き起こします。
スコープの役割 🛠️
巨大な工場(コンテナ)でひっきりなしに来る注文(Webリクエストなど)の製品をすべて作っていると、どの製品がいつ使い終わったのかを管理するのが非常に大変になり、メモリリークにつながりかねません。
そこでAutofacが採用しているのが、「仕事ごとに専用の短期的な作業スペースを作り、仕事が終わったらスペースごと片付ける」 という賢い方法です。
この短期的な作業スペースこそが 生存期間スコープ (ILifetimeScope
) です。
Webアプリケーションの場合、ユーザーからのリクエスト1回が「1つの仕事」にあたります:
- リクエストが来るたびに、新しい生存期間スコープ(作業スペース)を作る
- そのリクエストの処理に必要なオブジェクトは、すべてこのスコープから生成する
- リクエストの処理が終わり、レスポンスを返したら、スコープごと破棄する
スコープを破棄すると、その中で作られたオブジェクトもすべて一緒に破棄されるため、片付け忘れ(メモリリーク)が起こらないのです。これが、「コンテナから直接オブジェクトを取得するのではなく、まずスコープを作ってそこから取得する」という原則の理由です。
実践!ASP.NET CoreでAutofacを使う流れ
それでは、ここまでの概念をASP.NET Core Webアプリケーションのコードで確認してみましょう。
Step 1: 依存関係の定義
まず、各層のインターフェースとクラスをそれぞれのファイルに定義します。
① データアクセス層
Repositories
フォルダに作成します。
// Repositories/IUserRepository.cs
namespace YourWebApp.Repositories;
public interface IUserRepository
{
string GetUserName(int id);
}
// Repositories/UserRepository.cs
namespace YourWebApp.Repositories;
public class UserRepository : IUserRepository
{
public string GetUserName(int id) => $"SampleUser_{id}";
}
② ビジネスロジック層
Services
フォルダに作成します。
// Services/IUserService.cs
namespace YourWebApp.Services;
public interface IUserService
{
string GetUser(int id);
}
// Services/UserService.cs
using YourWebApp.Repositories;
namespace YourWebApp.Services;
public class UserService : IUserService
{
private readonly IUserRepository _repository;
public UserService(IUserRepository repository)
{
_repository = repository;
}
public string GetUser(int id) => _repository.GetUserName(id);
}
③ プレゼンテーション層
Controllers
とModels
フォルダに作成します。
// Models/UserViewModel.cs
namespace YourWebApp.Models;
public class UserViewModel
{
public int Id { get; set; }
public string? Name { get; set; }
}
// Controllers/UsersController.cs
using Microsoft.AspNetCore.Mvc;
using YourWebApp.Services;
using YourWebApp.Models;
namespace YourWebApp.Controllers;
public class UsersController : Controller
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
public IActionResult Index(int id)
{
var userName = _userService.GetUser(id);
var viewModel = new UserViewModel { Id = id, Name = userName };
return View(viewModel);
}
}
Step 2: DIコンテナの登録 (Program.cs)
ASP.NET Coreでは、アプリケーションの起動ファイルであるProgram.cs
で、Autofacを使うことを宣言し、依存関係のルールを登録します。
// Program.cs
using Autofac; // Autofac本体の機能
using Autofac.Extensions.DependencyInjection; // ASP.NET CoreとAutofacを連携させるための機能
using YourWebApp.Repositories;
using YourWebApp.Services;
// 1. アプリケーションの土台を準備
// WebApplicationBuilderは、 DI、ロギング、構成設定など、
// アプリケーションに必要な様々なサービスをあらかじめセットアップしてくれます。
var builder = WebApplication.CreateBuilder(args);
// 2. DIコンテナの差し替えと設定
// ASP.NET Core標準のDIコンテナの代わりに、Autofacを使いますよ、という宣言です。
// これにより、Autofacの高度な機能(モジュール機能など)が利用可能になります。
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
// ここでAutofacに依存関係のルールを登録します。
// ConfigureContainerを使い、AutofacのContainerBuilderに対して直接設定を行います。
builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
// 「IUserRepositoryが要求されたら、UserRepositoryのインスタンスを生成して渡す」というルールを登録
containerBuilder.RegisterType<UserRepository>().As<IUserRepository>();
// 「IUserServiceが要求されたら、UserServiceのインスタンスを生成して渡す」というルールを登録
containerBuilder.RegisterType<UserService>().As<IUserService>();
});
// 3. ASP.NET Coreの標準サービスを登録
// MVC(Model-View-Controller)パターンに必要なコントローラーやビューの機能を有効にします。
builder.Services.AddControllersWithViews();
// 4. アプリケーションを構築
// builderに設定された内容を元に、実際にリクエストを処理するWebアプリケーションオブジェクトを生成します。
var app = builder.Build();
// 5. HTTPリクエストの処理パイプラインを設定
// ここにUseHttpsRedirection, UseStaticFilesなどのミドルウェアを設定します。
// これらはリクエストがコントローラーに届く前に実行される処理です。
// (今回は説明のため省略)
// 6. URLのルーティング設定
// ブラウザから送られてきたURLを、どのコントローラーのどのアクションメソッドに渡すかを定義します。
// 例: /Users/Index/123 -> UsersControllerのIndexメソッドをid=123で呼び出す
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
// 7. アプリケーションの実行
// Webサーバーを起動し、HTTPリクエストの待機を開始します。
// この行以降のコードは、アプリケーションが終了するまで実行されません。
app.Run();
Step 3: 依存関係の自動的な解決
ASP.NET Core環境では、scope.Resolve<T>()
のようなコードを自分で書く必要はほとんどありません。
UsersController
のコンストラクタでIUserService
を要求すると、フレームワークがリクエスト専用のスコープから自動的にUserService
(とそれに必要なUserRepository
)を解決し、インスタンスを渡してくれます。これが コンストラクタインジェクション です。
コンストラクタ経由での注入(Constructor Injection)とは、対象のクラスが必要とするすべての依存をそのクラスのコンストラクタに引数として定義することで、そのクラスはどのような依存を必要としているのか、ということを静的に定義することを指します。
なぜ依存を注入するのか DIの原理・原則とパターン
開発者は依存関係の登録に集中し、解決はフレームワークに任せるのが基本です。これにより、DIコンテナへの直接的な依存コードを業務ロジックから排除でき、よりクリーンな設計が実現できます。
まとめ
Autofacを使いこなすための3つのキーワードを、工場の例えで振り返ってみましょう:
-
コンテナ (
IContainer
):オブジェクトの生成ルールが登録された、アプリケーション全体で一つの巨大な工場 - オブジェクト・グラフ:ある製品を組み立てるための部品の依存関係を示した手順書
-
生存期間スコープ (
ILifetimeScope
):メモリリークを防ぐため、仕事(リクエスト)ごとに作られる短期的な作業スペース
この仕組みを理解することで、「大きな工場(コンテナ)を建て、部品の組み立て手順書(オブジェクト・グラフ)を登録し、実際の作業は安全な短期作業スペース(スコープ)で自動的に行わせる」という、非常にクリーンで管理しやすい開発フローを確立できます。