次の方法で共有



May 2016

Volume 31 Number 5

ASP.NET - 依存関係の挿入による ASP.NET Core でのクリーンなコードの作成

Steve Smith

ASP.NET Core 1.0 は、ASP.NET を全面的に書き直したもので、モジュール構造の設計をさらに進めることを主目標の 1 つにしています。つまり、アプリケーションは、フレームワークが提供する依存関係を必要に応じて使用し、フレームワークの中から必要な部分のみを利用できるようになります。さらに、ASP.NET Core を使用してアプリケーションをビルドしている開発者は、この同じ機能を利用して、アプリケーションをモジュールどうしが疎結合される構造にすることができます。ASP.NET MVC では、ASP.NET チームが、疎結合型のコードを作成するためのフレームワーク サポートを大幅に強化しましたが、依然として簡単に密結合に陥る可能性があり、特にコントローラーのクラスではその可能性が高くなります。

密結合

密結合はデモ ソフトウェアなら問題ありません。ASP.NET MVC (バージョン 3 ~ 5) のサイトをビルドする方法を示す一般的なサンプル アプリケーションを見ると、ほとんどの場合に以下のようなコードが見つかります (NerdDinner MVC 4 サンプルの DinnersController クラス)。

private NerdDinnerContext db = new NerdDinnerContext();
private const int PageSize = 25;
public ActionResult Index(int? page)
{
  int pageIndex = page ?? 1;
  var dinners = db.Dinners
    .Where(d => d.EventDate >= DateTime.Now).OrderBy(d => d.EventDate);
  return View(dinners.ToPagedList(pageIndex, PageSize));
}

NerdDinnerContext は、クラスを構築する一環として作成され、データベースとの接続が必要になるため、この種のコードの単体テストは非常に困難です。当然、このようなデモ アプリケーションには、通常単体テストは含まれていません。ですが、テスト駆動型の開発ではなくても、アプリケーションは単体テストからメリットを得ることができるため、テスト可能なコードを作成するよう心がけます。それ以外にも、このコードでは Entity Framework (EF) のデータベース コンテキストを作成するコードが、データにアクセスするすべてのコントローラー クラスに含まれることになるため、「同じことを繰り返さない (DRY: Don't Repeat Yourself)」の原則にも違反しています。その結果、特に、長期にわたってアプリケーションを拡張していくにつれて、変更のコストが高くなり、間違いを起こしやすくなります。

コードを見て密結合か疎結合かを評価するときは、「new は密接な結び付きを生み出す」と考えてください。つまり、"new" キーワードを使ってクラスのインスタンスを作成している場合、その実装は特定の実装コードに結び付いています。「依存関係の逆転の原則」(bit.ly/DI-Principle、英語) には、「抽象化は詳細に依存すべきではなく、詳細が抽象化に依存すべきだ」と記載されています。上記の例では、コントローラーがデータをまとめてビューに渡す方法の詳細が、そのデータを取得する方法 (EF) の詳細に依存しています。

new キーワード以外に、「静的な結び付き」も、アプリケーションのテストやメンテナンスを困難にする密結合の要因です。上記の例では、DateTime.Now を呼び出すことで、コンピューターで稼働中のシステム クロックとの依存関係が生じます。つまり、EventDate のプロパティは現在のクロック設定を基準に相対値を設定する必要が生じ、こうした結び付きが、単体テストで使用する一連の Dinners テストの作成を困難にします。こうした結合をメソッドから取り除く方法はいくつかあります。最もシンプルなのは、Dinners が必要とするすべてのものを新しい抽象化から返すようにして、このメソッドには含めないようにする方法です。または、値をパラメーターとして渡します。そうすれば、提供された DateTime パラメーター以降のすべて Dinners を返すことができ、DateTime.Now を使用する必要はなくなります。さらに、現在時刻を抽象化し、この抽象化を使って現在時刻を参照する方法もあります。この方法は、アプリケーションで DateTime.Now を頻繁に参照している場合に適しています (このような Dinners は、おそらくさまざまなタイム ゾーンで実行されることが予想されるため、実際のアプリケーションでは、DateTimeOffset 型が適切な選択肢になるかもしれません)。

信頼性の確保

このようにコードの保守性に関する問題には、もう 1 つ、コードの連携相手から信頼されないことがあります。無効状態のインスタンスを作成できるクラスは作成しないようにします。こうしたクラスは、エラーの原因になることが多いためです。したがって、クラスの作業の実行に必要なものはすべて、クラスのコンストラクターを使って提供します。「明示的依存関係の原則」(bit.ly/ED-Principle、英語) には、「メソッドやクラスでは、正しく機能するために必要なコラボレーション オブジェクトをすべて明示的に要求すべきである」と記載されています。DinnersController クラスには既定のコンストラクターしかありません。これは、正しく機能するためにコラボレーション オブジェクトを必要とすべきではないことを意味します。では、ここにテストを含めるとどうなるでしょう。MVC プロジェクトを参照する新しいコンソール アプリケーションからテストを実行すると、このコードはどのように動作するでしょう。

var controller = new DinnersController();
var result = controller.Index(1);

この場合、最初に失敗するのは、EF コンテキストのインスタンスの作成を試みたときです。コードは InvalidOperationException をスローし、「'NerdDinnerContext' という接続文字列がアプリケーション構成ファイルに見つかりませんでした」と示されます。このメッセージは思い違いを引き起こします。このクラスを機能させるには、コンストラクターが宣言している以外のものが必要になります。クラスが Dinner インスタンスのコレクションにアクセスする方法が必要な場合、コンストラクターを使って (または、メソッドのパラメーターとして) 要求すべきです。

依存関係の挿入

依存関係の挿入 (DI) とは、"new" または静的呼び出しを使って依存関係をハードコーディングするのではなく、クラスやメソッドの依存関係をパラメーターとして渡す手法です。関係を利用するアプリケーションから依存関係を分離できることから、この手法は .NET 開発でますます一般的になっています。以前のバージョンの ASP.NET では DI を利用できず、ASP.NET MVC や Web API は DI をサポートする動きを見せていましたが、どちらの製品も完全なサポート (依存関係と依存関係オブジェクトのライフサイクルを管理するコンテナーなど) を組み込むまでには至りませんでした。ASP.NET Core 1.0 は DI を直接サポートしているだけでなく、製品自体でも広く使用しています。

ASP.NET Core には、DI のサポートだけでなく、DI コンテナーも含まれています。DI コンテナーは、制御の反転 (IoC) コンテナーやサービス コンテナーとも呼ばれます。すべての ASP.NET Core アプリケーションは、Startup クラスの ConfigureServices メソッドで、DI コンテナーを使用して依存関係を構成します。DI コンテナーは、必要な基本サポートを提供しますが、必要に応じてカスタマイズした実装に置き換えることもできます。また、EF Core にも DI の組み込みサポートが含まれているため、ASP.NET Core アプリケーション内では、拡張メソッドを呼び出す程度の簡単な操作で依存関係を構成できます。ここでは、NerdDinner を土台に GeekDinner を作成しました。EF Core は以下のように構成します。

public void ConfigureServices(IServiceCollection services)
{
  services.AddEntityFramework()
    .AddSqlServer()
    .AddDbContext<GeekDinnerDbContext>(options =>
      options.UseSqlServer(ConnectionString));
  services.AddMvc();
}

このコードを用意しておけば、DinnersController のようなコントローラー クラスから GeekDinnerDbContext のインスタンスを要求するのは、DI を使用するだけの単純な操作になります。

public class DinnersController : Controller
{
  private readonly GeekDinnerDbContext _dbContext;
  public DinnersController(GeekDinnerDbContext dbContext)
  {
    _dbContext = dbContext;
  }
  public IActionResult Index()
  {
    return View(_dbContext.Dinners.ToList());
  }
}

new キーワードをまったく使用していません。コントローラーが必要とする依存関係はすべてコンストラクターを通じて渡され、ASP.NET の DI コンテナーが自動的にこの処理を行います。ここではアプリケーションの作成に重点を置いていますが、クラスがコンストラクターを通じて要求する依存関係を遂行するのに必要なコーディングを考える必要はありません。当然、必要であればこの動作はカスタマイズでき、既定のコンテナーをまったく別の実装に置き換えることも可能です。このコントローラー クラスは明示的な依存関係の原則に従うようになったため、これを機能させるには GeekDinnerDbContext のインスタンスを提供する必要があります。以下のコンソール アプリケーションで示すように、DbContext をいくつかセットアップして、コントローラーのインスタンス作成を分離することができます。

var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlServer(Startup.ConnectionString);
var dbContext = new GeekDinnerDbContext(optionsBuilder.Options);
var controller = new DinnersController(dbContext);
var result = (ViewResult) controller.Index();

EF Core DbContext の構築には、接続文字列だけを受け取るだけの EF6 での構築に比べて若干多くの作業が必要になります。これは、ASP.NET Core と同様、EF Core がよりモジュール構造になるように設計されているためです。通常、AddEntityFramework や AddSqlServer のような拡張メソッドを使って EF を構成する際に、DbContextOptionsBuilder が背後で使用されているため、これを直接操作する必要はありません。

テストを可能に

アプリケーションを手動でテストすることは重要です。アプリケーションを実行して、実際に動作することや、想定する出力が得られることを確認しておくことは大切です。とは言え、アプリケーションを変更するたびにテストするのは時間の無駄です。疎結合アプリケーションの優れたメリットの 1 つは、どちらかと言えば、密結合アプリケーションに比べて単体テストに適している点です。都合のよいことに、ASP.NET Core と EF Core はどちらも、以前のフレームワークに比べてテストが容易になっています。まず、インメモリ ストアを使用するように構成した DbContext を渡すことによって、コントローラーを直接ターゲットにするシンプルなテストを作成します。テストをセットアップするコードの一部として、コンストラクターから公開する DbContextOptions パラメーターを使用して、GeekDinnerDbContext を構成します。

var optionsBuilder = new DbContextOptionsBuilder<GeekDinnerDbContext>();
optionsBuilder.UseInMemoryDatabase();
_dbContext = new GeekDinnerDbContext(optionsBuilder.Options);
// Add sample data
_dbContext.Dinners.Add(new Dinner() { Title = "Title 1" });
_dbContext.Dinners.Add(new Dinner() { Title = "Title 2" });
_dbContext.Dinners.Add(new Dinner() { Title = "Title 3" });
_dbContext.SaveChanges();

テスト クラスで構成することで、ViewResult のモデルに正しいデータが返されたことを示すテストを簡単に作成できます。

[Fact]
public void ReturnsDinnersInViewModel()
{
  var controller = new OriginalDinnersController(_dbContext);
  var result = controller.Index();
  var viewResult = Assert.IsType<ViewResult>(result);
  var viewModel = Assert.IsType<IEnumerable<Dinner>>(
    viewResult.ViewData.Model).ToList();
  Assert.Equal(1, viewModel.Count(d => d.Title == "Title 1"));
  Assert.Equal(3, viewModel.Count);
}

もちろん、まだテスト対象のロジックを少ししか作成していないため、実際にテストすることもあまり多くはありません。あまり有益でないテストだという評論家的主張には賛成ですが、これは出発点であり、すぐに多くのロジックを配置することになります。EF Core はインメモリ オプションを使用して単体テストをサポートできるとはいえ、コントローラーでは、依然として EF に直接結び付けないようにします。UI の関心事項とデータ アクセス インフラストラクチャの関心事項を結び付ける理由はなく、実際に「関心の分離」という別の原則に違反します。

使用しないものには依存しない

「インターフェイス分離の原則」(bit.ly/LS-Principle、英語) には、「クラスは実際に使用する機能にのみ依存すべきである」と記載されています。DI 対応にした新しい DinnersController では、依然として DbContext 全体に依存しています。コントローラーの実装を EF に結び付ける代わりに、必要な機能だけを提供する (それ以外は提供しない) 抽象化を使用します。

機能するために、このアクション メソッドが実際に必要とするのは何でしょう。DbContext 全体が必要だとは考えられません。コンテキストの Dinners プロパティすべてにアクセスする必要があるとも思えません。必要なのは、適切なページの Dinner インスタンスを表示する機能だけです。これを表す最もシンプルな .NET 抽象化は、IEnumerable<Dinner> です。そこで、Index メソッドの (大部分の) 要件を満たし、IEnumerable<Dinner> を返すだけのインターフェイスを定義します。

public interface IDinnerRepository
{
  IEnumerable<Dinner> List();
}

ここではこのインターフェイスをリポジトリと呼ぶことにします。コレクションのようなインターフェイスの背後にデータ アクセスを抽象化するパターンに従っているためです。何か理由あってこのリポジトリ パターンや名前が気に入らなければ、IGetDinners、IDinnerService など、好みの名前で呼んでもかまいません (技術レビュー担当者は ICanHasDinner という名前を提案しています)。どのような名前で呼んだとしても、この種のインターフェイスが果たす目的は変わりません。

このインターフェイスを用意した後、DinnersController を調整し、コンストラクターのパラメーターとして GeekDinnerDbContext の代わりに IDinnerRepository を受け取るようにして、Dinners DbSet に直接アクセスするのではなく List メソッドを呼び出します。

private readonly IDinnerRepository _dinnerRepository;
public DinnersController(IDinnerRepository dinnerRepository)
{
  _dinnerRepository = dinnerRepository;
}
public IActionResult Index()
{
  return View(_dinnerRepository.List());
}

これで、この Web アプリケーションをビルドして実行することはできますが、/Dinners にナビゲーションすると、「InvalidOperationException: Unable to resolve service for type ‘Geek­Dinner.Core.Interfaces.IdinnerRepository’ while attempting to activate GeekDinner.Controllers.DinnersController (InvalidOperationException: 'GeekDinner.Controllers.DinnersController' をアクティブにしようとしましたが、型 'Geek­Dinner.Core.Interfaces.IdinnerRepository' のサービスを解決できません)」という例外が発生します。まだ、インターフェイスを実装していません。インターフェイスを実装したら、DI が IDinnerRepository の要求を満たした場合にこの実装を使用するように構成する必要があります。インターフェイスの実装は簡単です。

public class DinnerRepository : IDinnerRepository
{
  private readonly GeekDinnerDbContext _dbContext;
  public DinnerRepository(GeekDinnerDbContext dbContext)
  {
    _dbContext = dbContext;
  }
  public IEnumerable<Dinner> List()
  {
    return _dbContext.Dinners;
  }
}

リポジトリの実装を EF に直接結び付けてもまったく問題はありません。EF を交換する必要がある場合は、このインターフェイスの新しい実装を作成するだけです。この実装クラスはアプリケーションのインフラストラクチャの一部になるため、このクラスはアプリケーションの中でも特定の実装に依存する場所の 1 つになります。

クラスが IDinnerRepository を要求するときに正しい実装を挿入するように ASP.NET Core を構成するには、前述の ConfigureServices メソッドの末尾に以下のコード行を追加する必要があります。

services.AddScoped<IDinnerRepository, DinnerRepository>();

このステートメントは ASP.NET Core DI コンテナーに対して、IDinnerRepository インスタンスに依存する型をコンテナーが解決するときは必ず DinnerRepository インスタンスを使用するよう指示します。Scoped の意味は、ASP.NET が処理する Web 要求ごとに 1 つのインスタンスを使用するということです。サービスを追加する際に、有効期間に Transient または Singleton を使用することもできます。今回の DinnerRepository は、有効期間に Scoped も使用する DbContext に依存するため、Scoped が適切です。オブジェクトに使用できる有効期間を以下に示します。

  • Transient: 型が要求されるたびに、型の新しいインスタンスが使用されます。
  • Scoped: 特定の HTTP 要求内で初めて要求される際に型の新しいインスタンスが作成され、その HTTP 要求中にその後解決されるすべての型にそのインスタンスが再利用されます。
  • Singleton: 型のインスタンスが 1 つ、一度だけ作成され、その型に対するその後のすべての要求で使用されます。

組み込みのコンテナーは、自身が提供する型の構築方法を複数サポートします。最も一般的なのは、単純にある型のコンテナーを提供する方法です。このコンテナーがその型のインスタンスの作成を試み、その後その型を必要とするすべての依存関係に提供します。型の構築用にラムダ式を用意することもできます。また、有効期間に Singleton を使用している場合は、型の登録時に ConfigureServices ですべての構築を終えたインスタンスを提供することもできます。

依存関係の挿入を使用しても、アプリケーションは以前とまったく同じように実行されます。テスト コードでは直接 EF に依存するのではなく、IDinnerRepository インターフェイスの擬似実装やモック実装を使用し、適切な箇所で new による抽象化を使ってアプリケーションをテストできます (図 1 参照)。

図 1 モック オブジェクトを使用した DinnersController のテスト

public class DinnersControllerIndex
{
  private List<Dinner> GetTestDinnerCollection()
  {
    return new List<Dinner>()
    {
      new Dinner() {Title = "Test Dinner 1" },
      new Dinner() {Title = "Test Dinner 2" },
    };
  }
  [Fact]
  public void ReturnsDinnersInViewModel()
  {
    var mockRepository = new Mock<IDinnerRepository>();
    mockRepository.Setup(r =>
      r.List()).Returns(GetTestDinnerCollection());
    var controller = new DinnersController(mockRepository.Object, null);
    var result = controller.Index();
    var viewResult = Assert.IsType<ViewResult>(result);
    var viewModel = Assert.IsType<IEnumerable<Dinner>>(
      viewResult.ViewData.Model).ToList();
    Assert.Equal("Test Dinner 1", viewModel.First().Title);
    Assert.Equal(2, viewModel.Count);
  }
}

このテストは、Dinner インスタンスのリストをどこから取得するかに関係なく機能します。データ アクセス コードを作り直し、別のデータベース、Azure Table Storage、または XML ファイルを使用しても、コントローラーは同じように機能します。もちろん、今回はこれらすべてに対応するわけではありません。

実際のロジック

今のところ、実際のビジネス ロジックを実装していません。ビジネス ロジックは、データの簡単なコレクションを返すシンプルなメソッドにすぎません。テストの本当の価値は、想定どおりに動作すると確信をもつ必要があるロジックや特殊なケースがあるときにわかります。これをデモするため、今回の GeekDinner サイトにいくつか要件を追加するつもりです。サイトでは API を公開し、だれでもディナー (Dinner) を予約できるようにします。ただし、予約できる人数には上限があり、これを超えてはいけません。上限を超えて予約した場合は、待機リストに追加されます。最後に、食事の開始時刻を基準に予約可能期限を指定でき、その時刻を過ぎると予約の受付は停止します。

このロジックをすべて 1 つのアクションにしてもかまいませんが、それでは 1 つのメソッドの役割としては多すぎると思います。ビジネス ロジックではなく、特に UI の関心事項に注目する UI メソッドの場合は多すぎます。コントローラーは、受け取った入力が有効であることを確認し、適切な応答をクライアントに返します。これ以上の判断 (特にビジネス ロジック) はコントローラーに含めないようにします。

ビジネス ロジックを置く最適な場所は、アプリケーションのドメイン モデルです。ドメイン モデルは、インフラストラクチャの関心事項 (データベースや UI) に依存すべきではありません。Dinner クラスは予約数の上限を格納し、現在までに受理した予約数を把握するようにして、要件で示されている予約についての関心事項を管理します。ただし、ロジックの一部は予約が行われる時間帯 (期限を過ぎているかどうか) に依存するため、現在時刻にアクセスする必要もあります。

今回は単に DateTime.Now を使用していますが、こうするとロジックのテストが難しくなり、ドメイン モデルとシステム クロックに結び付きが生じます。もう 1 つの選択肢としては、IDateTime の抽象化を使用して Dinner エンティティにこれを挿入する方法があります。ただし、経験上、EF のような O/RM ツールを使用して永続層からエンティティを取得することを計画している場合は特に、Dinner のようなエンティティに依存関係を持たせないようにするのが最適です。このプロセスの一貫としてエンティティの依存関係を設定しなければならないのは論外です。EF では、利用側にコードを追加しなければ、ほぼ確実にこれを行うことができません。この時点での一般的なアプローチは、Dinner エンティティからロジックを取り出し、簡単に依存関係を挿入できる何らかの種類のサービス (DinnerService や RsvpService など) にそのロジックを配置することです。これはドメイン モデル貧血症アンチパターン (bit.ly/anemic-model、英語) に陥りがちです。つまり、エンティティには (ほとんど) まったく動作がなく、状態を収容するだけになります。ただし、今回はわかりやすいソリューションです。メソッドはパラメーターとして現在時刻を受け取り、呼び出し元のコードから現在時刻を渡すようにするだけです。

このアプローチにより、予約を追加するロジックが簡単になります (図 2 参照)。このメソッドには、想定どおりの動作をデモするテストがいくつかあり、このテストは、本稿付属のサンプル プロジェクトから入手できます。

図 2 ドメイン モデルのビジネス ロジック

public RsvpResult AddRsvp(string name, string email, DateTime currentDateTime)
{
  if (currentDateTime > RsvpDeadlineDateTime())
  {
    return new RsvpResult("Failed - Past deadline.");
  }
  var rsvp = new Rsvp()
  {
    DateCreated = currentDateTime,
    EmailAddress = email,
    Name = name
  };
  if (MaxAttendees.HasValue)
  {
    if (Rsvps.Count(r => !r.IsWaitlist) >= MaxAttendees.Value)
    {
      rsvp.IsWaitlist = true;
      Rsvps.Add(rsvp);
      return new RsvpResult("Waitlist");
    }
  }
  Rsvps.Add(rsvp);
  return new RsvpResult("Success");
}

このロジックをドメイン モデルに移すことによって、コントローラーの API メソッドを小さなサイズに保ち、それ自身の関心事項に専念できるようにしました。その結果、メソッドからのパスが比較的少なくなるため、コントローラーがなすべき処理を実行していることを簡単にテストできます。

コントローラーの役割

コントローラーには、ModelState をチェックして有効かどうかを確認する役割があります。明確にするために、今回はこの処理をアクション メソッドで実行しますが、アプリケーションの規模が大きい場合は、アクション フィルターを使用して、各アクション内の反復コードを取り除くことになります。

[HttpPost]
public IActionResult AddRsvp([FromBody]RsvpRequest rsvpRequest)
{
  if (!ModelState.IsValid)
  {
    return HttpBadRequest(ModelState);
  }

ModelState が有効だとすると、次は要求で提供される ID を使用して適切な Dinner インスタンスを取得します。その ID に一致する Dinner インスタンスが見つからない場合は、Not Found (見つかりません) という結果を返します。

var dinner = _dinnerRepository.GetById(rsvpRequest.DinnerId);
if (dinner == null)
{
  return HttpNotFound("Dinner not found.");
}

こうしたチェックを完了したら、ドメイン モデルへの要求によって表される業務活動をアクションから自由にデリゲートします。つまり、前述の Dinner クラスの AddRsvp メソッドを呼び出して、更新されたドメイン モデルの状態 (今回の場合は、Dinner インスタンスと予約のコレクション) を保存してから OK 応答を返します。

var result = dinner.AddRsvp(rsvpRequest.Name,
    rsvpRequest.Email,
    _systemClock.Now);
  _dinnerRepository.Update(dinner);
  return Ok(result);
}

前述のように、Dinner クラスにはシステム クロックとの依存関係を持たせず、代わりに、現在時刻をメソッドに渡すことにしました。コントローラーでは、currentDateTime パラメーターに _systemClock.Now を渡します。これは、DI を介して設定されるローカル フィールドで、コントローラーがシステム クロックに密結合されないようにします。コントローラーは常に ASP.NET サービス コンテナーによって作成されるため、ドメイン エンティティとは対照的に、コントローラーで DI を使用するのは適切です。これにより、コントローラーはコンストラクター内ですべての依存関係を宣言するという要件を満たします。_systemClock は、IDateTime 型のフィールドで、以下の数行のコードで定義して実装します。

public interface IDateTime
{
  DateTime Now { get; }
}
public class MachineClockDateTime : IDateTime
{
  public DateTime Now { get { return System.DateTime.Now; } }
}

もちろん、クラスが IDateTime のインスタンスを必要とする場合は必ず、MachineClockDateTime を使用して ASP.NET コンテナーを構成することも必要です。これは、Startup クラスの ConfigureServices で行います。今回の場合、任意のオブジェクト有効期間で機能しますが、MachineClockDateTime の 1 つのインスタンスがアプリケーション全体で機能するため、Singleton を使用することにしました。

services.AddSingleton<IDateTime, MachineClockDateTime>();

この簡単な抽象化を用意すると、予約の期限が切れたかどうかに基づいてコントローラーの動作をテストでき、適切な結果が返されるようになります。想定どおりに動作するかどうかを確認する Dinner.AddRsvp メソッド関連のテストを既に用意しているので、このコントローラーを使って多くの同じ動作をテストしなくても、コントローラーとドメイン モデルが連携して正しく動作するという確信が持てます。

次のステップ

付属のサンプル プロジェクトをダウンロードして、Dinner と DinnersController の単体テストを確認してください。疎結合のコードは、"new" や静的メソッド呼び出しがたくさん使われ、インフラストラクチャの関心事項に依存する密結合のコードよりも、一般に、単体テストははるかに簡単になります。「new は密接な結び付きを生み出す」ことを念頭に置いて、アプリケーション new キーワードを使用する場合は、つい使用してしまうのではなく、意図をもって使用するようにします。ASP.NET Core の詳細や、ASP.NET Core がサポートする依存関係の挿入については、docs.asp.net (英語) を参照してください。


Steve Smith は、独立系のトレーナー、指導者兼コンサルタントで、ASP.NET の MVP でもあります。彼は、ASP.NET Core の公式ドキュメント (docs.asp.net、英語) に多くの記事を寄稿し、このテクノロジを学習するチームと連携しています。彼の連絡先は ardalis.com (英語) で、Twitter は @ardalis (英語) です。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Doug Bunting に心より感謝いたします。
Doug Bunting は、マイクロソフトの MVC チームに所属する開発者です。しばらく前から MVC に取り組んでおり、MVC Core で書き直した新しい DI パラダイムがお気に入りです。