この記事では、一緒に開発するASP.NET CoreのテストWebアプリケーション(組み込みDIを使用)のコンテキストでの「作業単位」および「リポジトリ」の設計パターンについて説明します。 その結果、リポジトリとの相互作用の2つの実装を取得します。SQLiteデータベースに基づく実際の実装と、メモリ内列挙に基づく迅速なテストのための偽の実装です。 これら2つの実装間の切り替えは、1行のコードを変更することにより行われます。

準備する
従来、ASP.NET Coreを使用したことがない場合は、これに必要なすべてへのリンク
があります。
Visual Studioを起動し、新しいWebアプリケーションを作成します。


Webアプリケーションの準備ができました。 必要に応じて実行できます。
降りる
モデル
モデルから始めましょう。 クラスを別のプロジェクト
-AspNetCoreStorage.Data.Modelsクラス
ライブラリに取り込もう 。

クラスを唯一の
Itemモデルに追加します。
public class Item { public int Id { get; set; } public string Name { get; set; } }
この例ではこれで十分です。
ストレージ相互作用の抽象化
次に、リポジトリとの対話に直接進みましょう。これは
、作業単位と
リポジトリという2つの設計パターンを使用して、Webアプリケーションに実装され
ます 。 これらのテンプレートの実装が簡素化されたことにより、単一のリクエストのフレームワーク内でのリポジトリとのやり取りが単一のリポジトリコンテキストで実行されることが保証され、それを操作するために必要なすべてのメソッドを含むモデルごとに個別のリポジトリが作成されます。
リポジトリとの対話のさまざまな実装を簡単に切り替える機能を提供するために、Webアプリケーションは特定の実装を直接使用しないでください。 代わりに、リポジトリとのすべての対話は抽象化レイヤーを介して実行する必要があります。
AspNetCoreStorage.Data.Abstractionsクラス
ライブラリで説明し
ます (対応するプロジェクトを作成します)。
まず、プロパティやメソッドなしで
IStorageContextインターフェイスを追加します。
public interface IStorageContext { }
このインターフェイスを実装するクラスは、リポジトリ(たとえば、接続文字列を含むデータベース)を直接記述します。
次に、
IStorageインターフェイスを追加します。
GetRepositoryと
Saveの 2つのメソッドが含まれてい
ます 。
public interface IStorage { T GetRepository<T>() where T : IRepository; void Save(); }
このインターフェースは、設計単位の設計パターンの実装を記述します。 このインターフェイスを実装するクラスのオブジェクトは、リポジトリへの唯一のアクセスポイントであり、Webアプリケーションへの単一の要求の一部として単一のインスタンスに存在する必要があります。 ASP.NET Core DIに組み込まれたこのオブジェクトを作成する責任があります。
GetRepositoryメソッドは(対応するモデルの)対応するタイプのリポジトリを見つけて返し、Saveメソッドはすべてのリポジトリによって行われ
た変更を
コミットします。
最後に、唯一の
SetStorageContextメソッドを使用して
IRepositoryインターフェイスを追加します。
public interface IRepository { void SetStorageContext(IStorageContext storageContext); }
明らかに、このインターフェイスはリポジトリクラスを記述します。 リポジトリが要求されると、
IStorageインターフェイスを実装するクラスのオブジェクトは、
SetStorageContextメソッドを使用して返されたリポジトリに単一のストレージコンテキストを転送します。これにより、
前述のように、リポジトリへのすべての呼び出しがこの単一のコンテキスト内で行われます。
これについては、一般的なインターフェイスについて説明します。 ここで、唯一の
アイテムモデルである
IItemRepositoryのリポジトリインターフェイスを追加します。 このインターフェイスには、1つのメソッド-Allのみが含まれます。
public interface IItemRepository : IRepository { IEnumerable<Item> All(); }
実際のWebアプリケーションでは、
Createメソッド、
Editメソッド、
Deleteメソッド、さまざまなパラメーターを使用してオブジェクトを抽出するメソッドなどもここで説明できますが、簡単な例では必要ありません。
ストレージの相互作用の特定の実装:インメモリ列挙
上で合意したように、リポジトリとの相互作用の2つの実装があります。SQLiteデータベースに基づくものとメモリ内の列挙に基づくものです。 2番目の方が簡単なので、始めましょう。
AspNetCoreStorage.Data.Mockクラス
ライブラリで説明し
ます (対応するプロジェクトを作成します)。
抽象化レイヤーの3つのインターフェイス、
IStorageContext 、
IStorage、および
IItemRepositoryを実装する必要があります(IItemRepositoryはIRepositoryを拡張するため)。
メモリ内の列挙の場合の
IStorageContextインターフェイスの実装にはコードは含まれません。これは単なる空のクラスなので、
IStorageに直接進みます。 クラスは小さいため、ここではその全体を示します。
public class Storage : IStorage { public StorageContext StorageContext { get; private set; } public Storage() { this.StorageContext = new StorageContext(); } public T GetRepository<T>() where T : IRepository { foreach (Type type in this.GetType().GetTypeInfo().Assembly.GetTypes()) { if (typeof(T).GetTypeInfo().IsAssignableFrom(type) && type.GetTypeInfo().IsClass) { T repository = (T)Activator.CreateInstance(type); repository.SetStorageContext(this.StorageContext); return repository; } } return default(T); } public void Save() {
ご覧のとおり、クラスには
StorageContextプロパティが含まれており、コンストラクターで初期化されます。
GetRepositoryメソッドは、パラメーター
Tで指定されたリポジトリインターフェイスの実装を探して、現在のアセンブリのすべてのタイプを
列挙します
。 適切なタイプが見つかった場合、対応するリポジトリオブジェクトが作成され、その
SetStorageContextメソッドが
呼び出されてから、このオブジェクトが返されます。
Saveメソッドは何もしません。 (実際、この実装ではStorageContextをまったく使用せず、SetStorageContextにnullを渡しますが、一貫性を保つために残します。)
それでは、
IItemRepositoryインターフェイスの実装を見てみましょう。
public class ItemRepository : IItemRepository { public readonly IList<Item> items; public ItemRepository() { this.items = new List<Item>(); this.items.Add(new Item() { Id = 1, Name = "Mock item 1" }); this.items.Add(new Item() { Id = 2, Name = "Mock item 2" }); this.items.Add(new Item() { Id = 3, Name = "Mock item 3" }); } public void SetStorageContext(IStorageContext storageContext) {
すべてが非常に簡単です。
Allメソッドは、コンストラクターで初期化される
items変数から要素のセットを返します。 この場合、コンテキストは必要ないため、
SetStorageContextメソッドは何も行いません。
特定のウェアハウス相互作用の実装:SQLiteデータベース
ここで、同じインターフェイスを実装しますが、SQLiteデータベースを使用します。 今回、
IStorageContextの実装には、いくつかのコードを記述する必要があります。
public class StorageContext : DbContext, IStorageContext { private string connectionString; public StorageContext(string connectionString) { this.connectionString = connectionString; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseSqlite(this.connectionString); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Item>(etb => { etb.HasKey(e => e.Id); etb.Property(e => e.Id); etb.ForSqliteToTable("Items"); } ); } }
ご覧のとおり、
IStorageContextインターフェイスの実装に加えて
、このクラスは
DbContextも継承します
。これは、Entity Framework Coreのデータベースコンテキストを表し、
onConfiguringと
OnModelCreatingをオーバーライドします(
詳しくは説明しません)。
connectionString変数にも注意してください。
IStorageインターフェイスの実装は、
StorageContextクラスのコンストラクターに接続文字列を渡す必要があることを除いて、上記
と同じです(もちろん、実際のアプリケーションでは、接続文字列を指定するのは間違っています。構成パラメーターから取得する必要があります):
this.StorageContext = new StorageContext("Data Source=..\\..\\..\\db.sqlite");
また、
Saveメソッドは、
DbContextから継承したストレージコンテキストの
SaveChangesメソッドを呼び出す必要があり
ます 。
public void Save() { this.StorageContext.SaveChanges(); }
IItemRepositoryインターフェイスの実装は次のようになりました。
public class ItemRepository : IItemRepository { private StorageContext storageContext; private DbSet<Item> dbSet; public void SetStorageContext(IStorageContext storageContext) { this.storageContext = storageContext as StorageContext; this.dbSet = this.storageContext.Set<Item>(); } public IEnumerable<Item> All() { return this.dbSet.OrderBy(i => i.Name); } }
SetStorageContextメソッドは、
IStorageContextインターフェイスを実装するクラスのオブジェクトを
取得し 、それを
StorageContext (つまり、このリポジトリ自体がその一部であるため認識している特定の実装)に
導き 、次に
Setメソッドを使用して、データベース内のテーブルを表す
dbSet変数を初期化しますSQLiteデータ。 今回の
Allメソッドは、
dbSet変数を使用してデータベーステーブルから実際のデータを返します。
もちろん、複数のリポジトリがある場合は、パラメータ
Tがモデルのタイプを記述し、
dbSetをパラメータ
化し 、それをストレージコンテキストの
Setメソッドに渡すいくつかのRepositoryBaseで一般的な実装を作成することは論理的です。
Webアプリケーションとストレージの相互作用
これで、Webアプリケーションを少し変更して、メインページに
Itemクラスのオブジェクトのリストを表示する準備が整いました。
まず、Webアプリケーションのメインプロジェクトの
project.jsonファイルの
依存関係セクションで、リポジトリとの相互作用の両方の特定の実装へのリンクを追加します。 結果は次のようになります。
"dependencies": { "AspNetCoreStorage.Data.Mock": "1.0.0", "AspNetCoreStorage.Data.Sqlite": "1.0.0", "Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.0.1", "Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.NETCore.App": { "version": "1.0.1", "type": "platform" } }
次に、
Startupクラスの
ConfigureServicesメソッドに進み、2つの異なる実装の
IStorageサービス
登録を追加し
ます (そのうちの1つをコメントアウトします
。AddScopedメソッドを使用して実装を登録します。
つまり 、オブジェクトのライフタイムは1つのリクエストです)
public void ConfigureServices(IServiceCollection services) { services.AddMvc();
それでは、
HomeControllerに移りましょう。
public class HomeController : Controller { private IStorage storage; public HomeController(IStorage storage) { this.storage = storage; } public ActionResult Index() { return this.View(this.storage.GetRepository<IItemRepository>().All()); } }
IStorage型の
ストレージ変数を追加し、コンストラクターで初期化します。 組み込みのASP.NET Core DI自体は、作成時にIStorageインターフェイスの登録済み実装をコントローラーコンストラクターに渡します。
さらに、
Indexメソッドでは、
IItemRepositoryインターフェイスを実装するアクセス可能なリポジトリを取得し(この方法で取得されたすべてのリポジトリは、Unit of Designテンプレートの使用により単一のストレージコンテキストを持つことを思い出します)、
Itemクラスのオブジェクトのセットをビューに渡し、リポジトリの
Allメソッドを使用してそれらを受け取ります。
結果のオブジェクトのリストをビューに表示します。 これを行うには、
Itemクラスのオブジェクトの列挙を表示用のビューのモデルとして指定し、ループ内で各オブジェクトの
Nameプロパティの値を表示します。
@model IEnumerable<AspNetCoreStorage.Data.Models.Item> <h1>Items from the storage:</h1> <ul> @foreach (var item in this.Model) { <li>@item.Name</li> } </ul>
ここでWebアプリケーションを起動すると、次の結果が得られます。
IStorageインターフェイス
実装の登録を別のものに変更すると、結果が変わります。

ご覧のとおり、すべてが機能します!
おわりに
ASP.NET Coreに組み込まれた依存性注入(DI)メカニズムは、同様のタスクの実装を大幅に簡素化し、初心者にとってより密接で、より簡単で、より理解しやすいものにします。 作業ユニットとリポジトリに関しては、一般的なWebアプリケーションの場合、これはデータと対話するための最も成功したソリューションであり、チームの開発とテストを簡素化します。
テストプロジェクトは
GitHubに投稿され
ています 。
著者について
Dmitry Sikorskyは、Ubrainiansソフトウェア開発会社の所有者および責任者であり、Kiev Pizza Deliveryサービス「Pizzerium」の共同所有者でもあります。
最新のASP.NET Coreの記事
1.
アプリケーションの外部Webサービスインターフェイスを作成します 。
2.
時代に遅れない:ASP.NET CoreでJWTを使用します 。
3.
Nano Server上のASP.NETコア 。