ASP.NET Core:デザインパターン作業単位とリポジトリの実装例

この記事では、一緒に開発する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インターフェイスを追加します。 GetRepositorySaveの 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つのインターフェイス、 IStorageContextIStorage、および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() { // Do nothing } }
      
      





ご覧のとおり、クラスには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) { // Do nothing } public IEnumerable<Item> All() { return this.items.OrderBy(i => i.Name); } }
      
      





すべてが非常に簡単です。 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のデータベースコンテキストを表し、 onConfiguringOnModelCreatingをオーバーライドします( 詳しくは説明しません)。 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(); // Uncomment to use mock storage services.AddScoped(typeof(IStorage), typeof(AspNetCoreStorage.Data.Mock.Storage)); // Uncomment to use SQLite storage //services.AddScoped(typeof(IStorage), typeof(AspNetCoreStorage.Data.Sqlite.Storage)); }
      
      





それでは、 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コア



All Articles