Skip to content

付録N 依存性注入(DI)入門 ― 現場のお作法へ

第 27〜30 章では、Repository を new EmployeeRepository() のように その場で作って 使いました。接続文字列も Repository の中に private const string で直書きしていました。 これは「読みやすさ優先」で選んだやり方で、研修としては十分です(第 27 章 27-11 で「現場では別のお作法がある」と予告だけしておきました)。

この付録では、その予告を 実際に手を動かして 確かめます。テーマは DI(依存性注入 / Dependency Injection) ── 現場の ASP.NET Core アプリでほぼ必ず出会う、最も基本的な「お作法」です。

これは「新しい学び」です

DI は、これまでの章で あえて避けてきた 新しい考え方です。「必要なものを 自分で作る(new)」のをやめ、「外から渡してもらう」に変えます。 付録 M-5-2 で使った ILogger(自分で new していないのに渡ってきた)を覚えていれば、その正体がこれです。

時間があれば取り組む位置づけ

本編(第 28〜30 章)の完成と、付録 M の仕上げが優先です。余裕があるときに、この付録で「現場のお作法」を覗いてみてください。


N-1 なぜ DI か(new だらけの何が困る?)

Section titled “N-1 なぜ DI か(new だらけの何が困る?)”

第 30 章の EmployeesController は、アクションのたびにこう書いていました。

EmployeeRepository empRepo = new EmployeeRepository();
DepartmentRepository deptRepo = new DepartmentRepository();

動きますが、現場の視点では次の不便があります。

困りごと中身
接続文字列がコードに散らばるRepository の中に直書き。変更や秘密管理がしづらい
差し替えできないテスト時に「本物の DB の代わり」を入れられない(new で固定)
同じ生成が何度もどの Controller も自分で new。一元管理できない

DI は、これらを「必要なものは、外(DI コンテナ)から渡してもらう」ことで解決します。

これまで: Controller が自分で new EmployeeRepository()
DI: Controller は「EmployeeRepository をください」とだけ言う
→ ASP.NET Core(DI コンテナ)が用意して、コンストラクタに渡す

この付録では、MvcEmployeeApp3 ステップ で DI 方式に変えます。

  1. 接続文字列を appsettings.json に出す
  2. Repository を DI コンテナに 登録 する
  3. Controller で コンストラクタ注入 に変える(new を消す)

N-2 準備:本体のコピーを作る(Ext_EmployeeAppDi)

Section titled “N-2 準備:本体のコピーを作る(Ext_EmployeeAppDi)”

重要:本体 MvcEmployeeApp は触りません。コピーで実験します。

DI 化は Program.cs・すべての Repository・Controller のコンストラクタを書き換える、構造の大きな変更 です。 本体 MvcEmployeeApp は、第 28〜30 章で 提出した成果物 であり、new EmployeeRepository() に付けた 「なぜ」コメント もそのコードに紐づいています。これを書き換えると成果物が崩れます。 そこで、第 28〜30 章の発展課題(Ext_*)と同じく、コピーを作ってそこで DI 化 します。

手順:

  1. KadaiWebApp ソリューションに、MvcEmployeeAppコピーした新プロジェクト Ext_EmployeeAppDi を作る(フォルダごとコピー → ソリューションに追加、または新規プロジェクトにファイルをコピー)
  2. namespace は MvcEmployeeApp のまま変えない ことを勧めます(変えるのはプロジェクト名 Ext_EmployeeAppDi だけ)。こうすると、以降のコード例(namespace MvcEmployeeApp.Data など)が そのまま使えusing の読み替えで迷いません。
  3. まず コピーがそのまま動く(/Employees が表示できる)ことを確認してから、DI 化に進む

付録 M をやっている場合:M で本体に足した機能(ILogger・トランザクションなど)も、コピーすれば一緒に入ってきます。以降の N のコード例は 第 30 章完了時点(M 適用前) を基準に書いているので、M の追加分は適宜読み替えてください(DI 化のやり方自体は同じです)。

以降のコードは、この Ext_EmployeeAppDi での作業です。


N-3 ステップ1:接続文字列を appsettings.json

Section titled “N-3 ステップ1:接続文字列を appsettings.json へ”

Repository の中に直書きしていた接続文字列を、設定ファイルに出します(第 27 章 27-11 お作法①の実践)。

appsettings.json(ConnectionStrings を足す)

{
"ConnectionStrings": {
"TrainingDb": "Server=localhost;Database=TrainingDB;User Id=app_user;Password=AppUserPass1!;TrustServerCertificate=true;"
},
"Logging": {
"LogLevel": {
"Default": "Information"
}
},
"AllowedHosts": "*"
}

Repository(const をやめ、コンストラクタで受け取る)

EmployeeRepositoryDepartmentRepository の両方を、次の形に変えます。

using Microsoft.Data.SqlClient;
using MvcEmployeeApp.Models; // ← コピー元の namespace に合わせる
namespace MvcEmployeeApp.Data;
public class EmployeeRepository
{
private readonly string _connectionString;
// IConfiguration は ASP.NET Core が用意してくれる(これも DI)
public EmployeeRepository(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("TrainingDb");
}
// 各メソッド内の new SqlConnection(ConnectionString) を
// new SqlConnection(_connectionString) に置き換えるだけ
// (SQL の中身・ロジックは一切変えない)
}
変更点内容
private const string ConnectionString = "...";削除
private readonly string _connectionString;追加(コンストラクタで設定)
new SqlConnection(ConnectionString)new SqlConnection(_connectionString) に置換

appsettings.json でも本番はパスワードを書かない

設定ファイルに出すだけでも「コードから分離」できますが、本番では appsettings.json にもパスワードを書かず、環境変数や Azure Key Vault / AWS Secrets Manager などから読みます(第 27 章 27-11 の再確認)。


N-4 ステップ2:Repository を DI コンテナに登録する

Section titled “N-4 ステップ2:Repository を DI コンテナに登録する”

EmployeeRepository をくださいと言われたら、これを渡す」と、起動時に 登録 します。

Program.cs(2 行足す)

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddScoped<EmployeeRepository>(); // ← 追加
builder.Services.AddScoped<DepartmentRepository>(); // ← 追加
var app = builder.Build();
// 以下はテンプレートのまま
キーワード意味
builder.ServicesDI コンテナ(部品の登録先)
AddScoped<T>()T をくださいと言われたら作って渡す」と登録。リクエスト 1 回ごとに 1 つ 作る
(登録だけで動く理由)Repository のコンストラクタが要る IConfiguration は、最初から登録済みなので自動で渡る

AddScoped / AddSingleton / AddTransient

部品の「寿命」を選べます。本研修は AddScoped(リクエストごとに 1 つ) でOK。DB アクセスはリクエスト単位で完結するので自然な選択です。違いは現場で必要になったときに学べば十分です。


N-5 ステップ3:Controller でコンストラクタ注入する(new を消す)

Section titled “N-5 ステップ3:Controller でコンストラクタ注入する(new を消す)”

EmployeesControllerコンストラクタ を足し、new をやめて 受け取ったものを使う 形にします。

public class EmployeesController : Controller
{
private readonly EmployeeRepository _employeeRepository;
private readonly DepartmentRepository _departmentRepository;
// DI コンテナが、登録済みの Repository を自動で渡してくれる
public EmployeesController(
EmployeeRepository employeeRepository,
DepartmentRepository departmentRepository)
{
_employeeRepository = employeeRepository;
_departmentRepository = departmentRepository;
}
public IActionResult Index(string keyword = "", int departmentId = -1)
{
// 変更前: EmployeeRepository empRepo = new EmployeeRepository();
// DepartmentRepository deptRepo = new DepartmentRepository();
// 変更後: フィールドの _employeeRepository / _departmentRepository を使う
EmployeeSearchViewModel model = new EmployeeSearchViewModel();
model.Departments = _departmentRepository.GetAll();
// ... 以下、empRepo → _employeeRepository、deptRepo → _departmentRepository に置換
return View(model);
}
// Create / Edit / Delete も同様に、各アクション内の
// new EmployeeRepository() → _employeeRepository
// new DepartmentRepository() → _departmentRepository
// に置き換える(中の処理は変えない)
}

ポイント:各アクションの中の new ...() を消し、コンストラクタで受け取ったフィールドに置き換えるだけ。ロジックや SQL は一切変えません。

DI コンテナはどうやって渡す?

/Employees にリクエストが来ると、ASP.NET Core は EmployeesController を作ろうとします。コンストラクタが EmployeeRepositoryDepartmentRepository を要求しているのを見て、登録済みの作り方(N-4) で 2 つを用意し、コンストラクタに渡します。EmployeeRepository のコンストラクタが要る IConfiguration も、同じ仕組みで渡されます。芋づる式に必要なものを揃えてくれる ── これが DI コンテナの仕事です。


F5 で実行し、/Employeesこれまでと同じように動く(一覧・検索・新規・編集・削除)ことを確認します。 画面・動作は一切変わりません。 変わったのは「接続文字列の置き場所」と「Repository を誰が用意するか」という 内部構造だけ です。

わざと外して確かめる(おすすめ)

Program.csAddScoped<EmployeeRepository>();コメントアウト して実行すると、/Employees アクセス時に「EmployeeRepository を解決できない」というエラーが出ます。 「登録していないものは渡せない」── DI コンテナの動きが、エラーの形で見えます(確認したら戻してください)。


観点これまで(第 27〜30 章)DI 方式(本付録)
Repository の入手new EmployeeRepository()コンストラクタで受け取る
接続文字列Repository 内に const 直書きappsettings.json
登録不要Program.csAddScoped
差し替えできない(new で固定)できる(登録を変えるだけ)
読みやすさ素直で分かりやすい(初学者向き)慣れが要る(現場標準)

第 27〜30 章で あえて new にした理由(「必要なものを自分で作る」のは C# として素直で、初学者がデバッグしやすい)も、こうして両方を体験すると腑に落ちます。どちらが偉いかではなく、目的で選ぶ ものです。


N-8 発展(読み物):インターフェイスで「差し替え」できるようにする

Section titled “N-8 発展(読み物):インターフェイスで「差し替え」できるようにする”

DI の本当のうれしさは、実装を差し替えられる ことです。そのために インターフェイス を挟みます。

IEmployeeRepository.cs
namespace MvcEmployeeApp.Data;
public interface IEmployeeRepository
{
List<Employee> GetAll();
List<Employee> Search(string keyword, int departmentId);
Employee GetById(int employeeId);
int Insert(Employee employee);
int Update(Employee employee);
int Delete(int employeeId);
}
  • EmployeeRepositorypublic class EmployeeRepository : IEmployeeRepository にする
  • 登録を builder.Services.AddScoped<IEmployeeRepository, EmployeeRepository>(); にする
  • Controller は IEmployeeRepository を受け取る(実体は知らなくてよい)

こうすると、たとえばテスト時に「本物の DB の代わりに、決まったデータを返す偽物(FakeEmployeeRepository : IEmployeeRepository)」を登録して、DB なしで Controller を試せます。これが「差し替え=テストしやすさ」です。

本研修ではここまで(読み物)

実際のテストには xUnit などのテスト用ライブラリが要りますが、本研修の NuGet 範囲外です(第 30 章でも触れたとおり)。 「インターフェイス + DI で、実装を差し替えられる」という 考え方 を知っておけば十分です。第 14 章で学んだインターフェイス(「○○できるという約束」)が、ここで活きていることに気づけたら上出来です。


付録 M-5-2 で、EmployeesController のコンストラクタに ILogger<EmployeesController> を受け取りました。あれも DI です。

種類登録
フレームワークが最初から用意ILogger<T>IConfiguration登録不要(ASP.NET Core が標準で登録済み)
自分で用意したものEmployeeRepository など自分で AddScoped 登録(N-4)

どちらも「コンストラクタで受け取る」点は同じ。ILogger が自分で new しなくても来たのは、フレームワークが裏で登録してくれていたから ── これで「自分で登録しないのに来る DI」の謎が解けました。


  • new EmployeeRepository() 方式の不便を 1 つ以上挙げられる
  • DI=「必要なものを外(DI コンテナ)から渡してもらう」考え方だと説明できる
  • 接続文字列を appsettings.json + IConfiguration で読める
  • Program.csAddScoped<T>() で Repository を登録できる
  • Controller のコンストラクタ注入で new を消せる
  • (発展)インターフェイス + DI で実装を差し替えられる考え方を説明できる
  • ILogger / IConfiguration が「登録不要で渡る DI」だと説明できる

  1. new EmployeeRepository() をやめて DI にすると、何がうれしいですか(1 つ以上)。
  2. 接続文字列を appsettings.json に出す利点は何ですか。
  3. Program.csAddScoped は何をしている行ですか。
  4. Controller は、Repository をどうやって受け取っていますか。
  5. ILogger を自分で new しなくても使えたのは、なぜですか。
  6. 第 27〜30 章で あえて new にしていた理由は何だと思いますか。

  • DI(依存性注入) は「必要なものを 自分で new せず、外から渡してもらう」現場標準のお作法
  • 3 ステップで変えられる:① 接続文字列を appsettings.json へ / ② Program.csAddScoped 登録 / ③ Controller でコンストラクタ注入(new を消す)
  • 画面・動作は変わらない。変わるのは 接続文字列の置き場所部品を誰が用意するか という内部構造
  • インターフェイス + DI で実装を差し替えられる(テストしやすさ)── 第 14 章のインターフェイスが活きる(本研修は考え方まで)
  • ILogger / IConfiguration は「登録不要で渡る DI」。付録 M-5-2 の謎が解ける
  • 第 27〜30 章で new にしたのは初学者の読みやすさ優先。どちらも目的で選ぶ

ここまで来たら、現場の ASP.NET Core アプリのコードを開いても、「Program.cs で登録して、コンストラクタで受け取っているんだな」と 読み解ける はずです。お疲れさまでした。