ASP.NET MVCでのデータアクセスの分離


35

私は、MVCでの最初の本当のクラックで、業界標準とベストプラクティスに従っていることを確認したいと思います。この場合、C#を使用したASP.NET MVCです。

モデルにEntity Framework 4.1を使用し、コードファーストオブジェクト(データベースが既に存在する)を使用するため、データベースからデータを取得するためのDBContextオブジェクトが存在します。

asp.net Webサイトで行ったデモでは、コントローラーにデータアクセスコードが含まれています。これは、特にDRY(自分自身を繰り返さないでください)プラクティスに従う場合、私には正しくないようです。

たとえば、公共図書館で使用するWebアプリケーションを作成しており、カタログ内の書籍を作成、更新、削除するためのコントローラーがあるとします。

アクションのいくつかはISBNを取得し、「Book」オブジェクトを返す必要がある場合があります(これはおそらく100%有効なコードではないことに注意してください)。

public class BookController : Controller
{
    LibraryDBContext _db = new LibraryDBContext();

    public ActionResult Details(String ISBNtoGet)
    {
        Book currentBook = _db.Books.Single(b => b.ISBN == ISBNtoGet);
        return View(currentBook);
    }

    public ActionResult Edit(String ISBNtoGet)
    {
        Book currentBook = _db.Books.Single(b => b.ISBN == ISBNtoGet);
        return View(currentBook);
    }
}

代わりに、すべき 1つのBookを返すメソッドをdbコンテキストオブジェクトに実際がありますか?それは私にとってより良い分離のようであり、DRYを促進するのに役立ちます。これは、WebアプリケーションのどこかでISBNによってBookオブジェクトを取得する必要があるからです。

public partial class LibraryDBContext: DBContext
{
    public Book GetBookByISBN(String ISBNtoGet)
    {
        return Books.Single(b => b.ISBN == ISBNtoGet);
    }
}

public class BookController : Controller
{
    LibraryDBContext _db = new LibraryDBContext();

    public ActionResult Details(String ISBNtoGet)
    {
        return View(_db.GetBookByISBN(ISBNtoGet));
    }

    public ActionResult Edit(ByVal ISBNtoGet as String)
    {
        return View(_db.GetBookByISBN(ISBNtoGet));
    }
}

これは、アプリケーションのコーディングで従うべき有効なルールのセットですか?

または、より主観的な質問は「これが正しい方法ですか?」

回答:


55

一般に、コントローラーにいくつかのことだけを行わせます。

  1. 着信リクエストを処理する
  2. 処理をいくつかのビジネスオブジェクトに委任する
  3. ビジネス処理の結果をレンダリング用の適切なビューに渡す

そこにすべきではない任意のコントローラでのデータアクセスや複雑なビジネスロジック。

[最も単純なアプリでは、おそらくコントローラーで基本的なデータCRUDアクションを回避できますが、単純なGetおよびUpdate呼び出し以上の追加を開始したら、処理を別のクラスに分割する必要があります。 ]

通常、コントローラーは実際の処理作業を行うために「サービス」に依存します。あなたのサービスクラスであなた自身がデータアクセスに加えて、ビジネスルールをたくさん書いて見つけた場合、あなたはおそらく、あなたのビジネスを分離することになるでしょう、もう一度自分のデータソース(あなたのケースでは、DbContext)で直接動作しますが、データアクセスのロジック。

その時点で、おそらくデータアクセス以外のことは何も行わないクラスになります。これはリポジトリと呼ばれることもありますが、実際にはその名前は重要ではありません。ポイントは、データベースにデータを出し入れするためのすべてのコードが1か所にあるということです。

私が取り組んだすべてのMVCプロジェクトについて、私は常に次のような構造になりました。

コントローラ

public class BookController : Controller
{
    ILibraryService _libraryService;

    public BookController(ILibraryService libraryService)
    {
        _libraryService = libraryService;
    }

    public ActionResult Details(String isbn)
    {
        Book currentBook = _libraryService.RetrieveBookByISBN(isbn);
        return View(ConvertToBookViewModel(currentBook));
    }

    public ActionResult DoSomethingComplexWithBook(ComplexBookActionRequest request)
    {
        var responseViewModel = _libraryService.ProcessTheComplexStuff(request);
        return View(responseViewModel);
    }
}

ビジネスサービス

public class LibraryService : ILibraryService
{
     IBookRepository _bookRepository;
     ICustomerRepository _customerRepository;

     public LibraryService(IBookRepository bookRepository, 
                           ICustomerRepository _customerRepository )
     {
          _bookRepository = bookRepository;
          _customerRepository = customerRepository;
     }

     public Book RetrieveBookByISBN(string isbn)
     {
          return _bookRepository.GetBookByISBN(isbn);
     }

     public ComplexBookActionResult ProcessTheComplexStuff(ComplexBookActionRequest request)
     {
          // Possibly some business logic here

          Book book = _bookRepository.GetBookByISBN(request.Isbn);
          Customer customer = _customerRepository.GetCustomerById(request.CustomerId);

          // Probably more business logic here

          _libraryRepository.Save(book);

          return complexBusinessActionResult;

     } 
}

リポジトリ

public class BookRepository : IBookRepository
{
     LibraryDBContext _db = new LibraryDBContext();

     public Book GetBookByIsbn(string isbn)
     {
         return _db.Books.Single(b => b.ISBN == isbn);
     }

     // And the rest of the data access
}

+1全体的に素晴らしいアドバイスですが、リポジトリの抽象化が何らかの価値を提供していたかどうかは疑問です。
MattDavey

3
@MattDaveyええ、最初(または最も単純なアプリ)では、リポジトリレイヤーの必要性を理解するのは難しいですが、ビジネスロジックが中程度のレベルの複雑さであってもすぐにそれは簡単になりますデータアクセスを分離します。しかし、それを簡単な方法で伝えるのは簡単ではありません。
エリックキング

1
@Billy IoCのカーネルはありません持っている MVCプロジェクトであることを。MVCプロジェクトが依存しているが、リポジトリプロジェクトに依存している独自のプロジェクトに含めることができます。必要性を感じないので、私は一般的にそうしません。それでも、MVCプロジェクトでリポジトリクラスを呼び出さないようにする場合は、そうしないでください。私はおそらくに従事する私はないんだけど、プログラミングの実践の可能性から身を守ることができるように自分自身をhamstringingの大ファンではないんだ。
エリック・キング

2
Controller-Service-Repositoryというパターンを使用します。サービス/リポジトリ層にパラメーターオブジェクト(GetBooksParametersなど)を取得させ、ILibraryServiceの拡張メソッドを使用してパラメーターのランリングを行うと非常に役立つことを付け加えます。このように、ILibraryServiceにはオブジェクトを取得する単純なエントリポイントがあり、拡張メソッドは、インターフェイスとクラスを毎回書き換える必要なく、可能な限りパラメーターを狂わせることができます(例:GetBooksByISBN / Customer / Date / WhateverはGetBooksParametersオブジェクトを形成し、サービス)。コンボは素晴らしいです。
BlackjacketMack 14

1
@IsaacKleinman偉人の誰が書いたのか思い出せませんが(Bob Martin?)、それは根本的な質問です。Oven.Bake(pizza)かPizza.Bake(oven)が欲しいですか。そして答えは「それは依存します」です。通常、1つ以上のオブジェクト(またはピザ)を操作する外部サービス(または作業単位)が必要です。しかし、これらの個々のオブジェクトには、焼き付けられているオーブンの種類に反応する能力がないと言う人がいます。Order.Save()よりOrderRepository.Save(order)の方が好きです。しかし、Order.Validate()が好きなのは、注文がそれ自身の理想的な形式を知っているからです。コンテキスト、および個人。
ブラックジャック

2

これは私がやってきた方法ですが、実装を交換できるように、データプロバイダーを汎用データサービスインターフェイスとして注入しています。

私の知る限り、コントローラーはデータの取得、アクションの実行、ビューへのデータの受け渡しを目的としています。


はい、データプロバイダーの「サービスインターフェイス」の使用について読んだことがあります。ユニットテストに役立つからです。
scott.korin
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.