付録N 依存性注入(DI)入門 ― 現場のお作法へ
この付録の位置づけ
Section titled “この付録の位置づけ”第 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 コンテナ)が用意して、コンストラクタに渡すこの付録では、MvcEmployeeApp を 3 ステップ で DI 方式に変えます。
- 接続文字列を
appsettings.jsonに出す - Repository を DI コンテナに 登録 する
- 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 化 します。
手順:
KadaiWebAppソリューションに、MvcEmployeeAppを コピーした新プロジェクトExt_EmployeeAppDiを作る(フォルダごとコピー → ソリューションに追加、または新規プロジェクトにファイルをコピー)- namespace は
MvcEmployeeAppのまま変えない ことを勧めます(変えるのはプロジェクト名Ext_EmployeeAppDiだけ)。こうすると、以降のコード例(namespace MvcEmployeeApp.Dataなど)が そのまま使え、usingの読み替えで迷いません。 - まず コピーがそのまま動く(
/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 をやめ、コンストラクタで受け取る)
EmployeeRepository と DepartmentRepository の両方を、次の形に変えます。
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.Services | DI コンテナ(部品の登録先) |
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を作ろうとします。コンストラクタがEmployeeRepositoryとDepartmentRepositoryを要求しているのを見て、登録済みの作り方(N-4) で 2 つを用意し、コンストラクタに渡します。EmployeeRepositoryのコンストラクタが要るIConfigurationも、同じ仕組みで渡されます。芋づる式に必要なものを揃えてくれる ── これが DI コンテナの仕事です。
N-6 動かして確認する
Section titled “N-6 動かして確認する”F5 で実行し、/Employees が これまでと同じように動く(一覧・検索・新規・編集・削除)ことを確認します。
画面・動作は一切変わりません。 変わったのは「接続文字列の置き場所」と「Repository を誰が用意するか」という 内部構造だけ です。
わざと外して確かめる(おすすめ)
Program.csのAddScoped<EmployeeRepository>();を コメントアウト して実行すると、/Employeesアクセス時に「EmployeeRepositoryを解決できない」というエラーが出ます。 「登録していないものは渡せない」── DI コンテナの動きが、エラーの形で見えます(確認したら戻してください)。
N-7 before / after で振り返る
Section titled “N-7 before / after で振り返る”| 観点 | これまで(第 27〜30 章) | DI 方式(本付録) |
|---|---|---|
| Repository の入手 | new EmployeeRepository() | コンストラクタで受け取る |
| 接続文字列 | Repository 内に const 直書き | appsettings.json |
| 登録 | 不要 | Program.cs の AddScoped |
| 差し替え | できない(new で固定) | できる(登録を変えるだけ) |
| 読みやすさ | 素直で分かりやすい(初学者向き) | 慣れが要る(現場標準) |
第 27〜30 章で あえて new にした理由(「必要なものを自分で作る」のは C# として素直で、初学者がデバッグしやすい)も、こうして両方を体験すると腑に落ちます。どちらが偉いかではなく、目的で選ぶ ものです。
N-8 発展(読み物):インターフェイスで「差し替え」できるようにする
Section titled “N-8 発展(読み物):インターフェイスで「差し替え」できるようにする”DI の本当のうれしさは、実装を差し替えられる ことです。そのために インターフェイス を挟みます。
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);}EmployeeRepositoryをpublic class EmployeeRepository : IEmployeeRepositoryにする- 登録を
builder.Services.AddScoped<IEmployeeRepository, EmployeeRepository>();にする - Controller は
IEmployeeRepositoryを受け取る(実体は知らなくてよい)
こうすると、たとえばテスト時に「本物の DB の代わりに、決まったデータを返す偽物(FakeEmployeeRepository : IEmployeeRepository)」を登録して、DB なしで Controller を試せます。これが「差し替え=テストしやすさ」です。
本研修ではここまで(読み物)
実際のテストには xUnit などのテスト用ライブラリが要りますが、本研修の NuGet 範囲外です(第 30 章でも触れたとおり)。 「インターフェイス + DI で、実装を差し替えられる」という 考え方 を知っておけば十分です。第 14 章で学んだインターフェイス(「○○できるという約束」)が、ここで活きていることに気づけたら上出来です。
N-9 ILogger も DI だった
Section titled “N-9 ILogger も DI だった”付録 M-5-2 で、EmployeesController のコンストラクタに ILogger<EmployeesController> を受け取りました。あれも DI です。
| 種類 | 例 | 登録 |
|---|---|---|
| フレームワークが最初から用意 | ILogger<T>、IConfiguration | 登録不要(ASP.NET Core が標準で登録済み) |
| 自分で用意したもの | EmployeeRepository など | 自分で AddScoped 登録(N-4) |
どちらも「コンストラクタで受け取る」点は同じ。ILogger が自分で new しなくても来たのは、フレームワークが裏で登録してくれていたから ── これで「自分で登録しないのに来る DI」の謎が解けました。
学んだことチェック
Section titled “学んだことチェック”-
new EmployeeRepository()方式の不便を 1 つ以上挙げられる - DI=「必要なものを外(DI コンテナ)から渡してもらう」考え方だと説明できる
- 接続文字列を
appsettings.json+IConfigurationで読める -
Program.csのAddScoped<T>()で Repository を登録できる - Controller のコンストラクタ注入で
newを消せる - (発展)インターフェイス + DI で実装を差し替えられる考え方を説明できる
-
ILogger/IConfigurationが「登録不要で渡る DI」だと説明できる
new EmployeeRepository()をやめて DI にすると、何がうれしいですか(1 つ以上)。- 接続文字列を
appsettings.jsonに出す利点は何ですか。 Program.csのAddScopedは何をしている行ですか。- Controller は、Repository をどうやって受け取っていますか。
ILoggerを自分でnewしなくても使えたのは、なぜですか。- 第 27〜30 章で あえて
newにしていた理由は何だと思いますか。
この付録のまとめ
Section titled “この付録のまとめ”- DI(依存性注入) は「必要なものを 自分で
newせず、外から渡してもらう」現場標準のお作法 - 3 ステップで変えられる:① 接続文字列を
appsettings.jsonへ / ②Program.csでAddScoped登録 / ③ Controller でコンストラクタ注入(newを消す) - 画面・動作は変わらない。変わるのは 接続文字列の置き場所 と 部品を誰が用意するか という内部構造
- インターフェイス + DI で実装を差し替えられる(テストしやすさ)── 第 14 章のインターフェイスが活きる(本研修は考え方まで)
ILogger/IConfigurationは「登録不要で渡る DI」。付録 M-5-2 の謎が解ける- 第 27〜30 章で
newにしたのは初学者の読みやすさ優先。どちらも目的で選ぶ
ここまで来たら、現場の ASP.NET Core アプリのコードを開いても、「Program.cs で登録して、コンストラクタで受け取っているんだな」と 読み解ける はずです。お疲れさまでした。