Memisahkan akses data dalam ASP.NET MVC


35

Saya ingin memastikan saya mengikuti standar industri dan praktik terbaik dengan celah nyata pertama saya di MVC. Dalam hal ini, itu ASP.NET MVC, menggunakan C #.

Saya akan menggunakan Entity Framework 4.1 untuk model saya, dengan objek kode-pertama (database sudah ada), jadi akan ada objek DBContext untuk mengambil data dari database.

Dalam demo yang saya lalui di situs web asp.net, pengontrol memiliki kode akses data di dalamnya. Ini tampaknya tidak benar bagi saya, terutama ketika mengikuti latihan KERING (jangan ulangi sendiri).

Sebagai contoh, katakanlah saya sedang menulis aplikasi web untuk digunakan di perpustakaan umum, dan saya memiliki pengontrol untuk membuat, memperbarui, dan menghapus buku dalam katalog.

Beberapa tindakan mungkin memerlukan ISBN dan ingin mengembalikan objek "Buku" (perhatikan ini mungkin bukan kode yang 100% valid):

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);
    }
}

Sebaliknya, haruskah saya benar-benar memiliki metode dalam objek konteks db saya untuk mengembalikan satu Buku? Itu sepertinya pemisahan yang lebih baik bagi saya, dan membantu mempromosikan KERING, karena saya mungkin perlu mendapatkan objek Buku dengan ISBN di tempat lain di aplikasi web saya.

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));
    }
}

Apakah ini seperangkat aturan yang valid untuk diikuti dalam pengkodean aplikasi saya?

Atau, saya kira pertanyaan yang lebih subyektif adalah: "Apakah ini cara yang tepat untuk melakukannya?"

Jawaban:


55

Secara umum, Anda ingin Pengontrol Anda melakukan hanya beberapa hal:

  1. Tangani permintaan yang masuk
  2. Delegasikan pemrosesan ke beberapa objek bisnis
  3. Berikan hasil pemrosesan bisnis ke tampilan yang sesuai untuk rendering

Seharusnya tidak ada akses data atau logika bisnis yang kompleks di controller.

[Dalam aplikasi yang paling sederhana, Anda mungkin bisa lolos dengan tindakan CRUD data dasar di controller Anda, tetapi begitu Anda mulai menambahkan lebih dari sekadar Dapatkan dan Perbarui panggilan, Anda akan ingin membagi pemrosesan Anda ke kelas yang terpisah. ]

Pengontrol Anda biasanya akan bergantung pada 'Layanan' untuk melakukan pekerjaan pemrosesan yang sebenarnya. Di kelas layanan Anda, Anda dapat bekerja secara langsung dengan sumber data Anda (dalam kasus Anda, DbContext), tetapi sekali lagi, jika Anda menemukan diri Anda menulis banyak aturan bisnis di samping akses data, Anda mungkin ingin memisahkan bisnis Anda logika dari akses data Anda.

Pada titik itu, Anda mungkin akan memiliki kelas yang tidak melakukan apa pun kecuali akses data. Kadang-kadang ini disebut Repositori, tetapi tidak terlalu penting apa namanya. Intinya adalah bahwa semua kode untuk mendapatkan data masuk dan keluar dari database ada di satu tempat.

Untuk setiap proyek MVC yang saya kerjakan, saya selalu berakhir dengan struktur seperti:

Pengendali

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);
    }
}

Layanan Bisnis

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;

     } 
}

Gudang

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 Secara keseluruhan saran bagus, meskipun saya akan mempertanyakan apakah abstraksi repositori memberikan nilai.
MattDavey

3
@MattDavey Ya, pada awalnya (atau untuk aplikasi yang paling sederhana) sulit untuk melihat perlunya lapisan repositori, tetapi segera setelah Anda memiliki tingkat kompleksitas moderat dalam logika bisnis Anda, menjadi no-brainer untuk pisahkan akses data. Tidak mudah untuk menyampaikannya dengan cara yang sederhana.
Eric King

1
@Billy Kernel IoC tidak harus dalam proyek MVC. Anda bisa memilikinya dalam proyek sendiri, yang bergantung pada proyek MVC, tetapi yang pada gilirannya tergantung pada proyek repositori. Saya biasanya tidak melakukan itu karena saya tidak merasa perlu. Meski begitu, jika Anda tidak ingin proyek MVC Anda memanggil kelas repositori Anda ... maka jangan. Saya bukan penggemar hamstringing diri sendiri sehingga saya bisa melindungi diri dari kemungkinan praktik pemrograman yang tidak mungkin saya lakukan.
Eric King

2
Kami menggunakan persis pola ini: Controller-Service-Repository. Saya ingin menambahkan bahwa sangat berguna bagi kita untuk memiliki tier layanan / repositori mengambil parameter objek (misalnya GetBooksParameters) dan kemudian menggunakan metode ekstensi pada ILibraryService untuk melakukan pertengkaran parameter. Dengan cara itu ILibraryService memiliki titik masuk sederhana yang mengambil objek, dan metode ekstensi dapat menjadi parameter gila mungkin tanpa harus menulis ulang antarmuka dan kelas setiap kali (misalnya GetBooksByISBN / Pelanggan / Tanggal / Apa pun yang membentuk objek GetBooksParameters dan memanggil layanan). Kombo sangat bagus.
BlackjacketMack

1
@IsaacKleinman Saya tidak ingat yang hebat mana yang menulisnya (Bob Martin?) Tapi itu pertanyaan mendasar: apakah Anda ingin Oven.Bake (pizza) atau Pizza.Bake (oven). Dan jawabannya adalah 'itu tergantung'. Biasanya kami ingin layanan luar (atau unit kerja) memanipulasi satu atau lebih objek (atau pizza!). Tapi siapa yang mengatakan bahwa objek-objek individu tidak memiliki kemampuan untuk bereaksi terhadap jenis Oven mereka dipanggang. Saya lebih suka OrderRepository.Save (pesanan) ke Order.Save (). Namun, saya suka Order.Validate () karena pesanan bisa tahu itu bentuk ideal sendiri. Kontekstual, dan pribadi.
BlackjacketMack

2

Ini adalah cara saya telah melakukannya, meskipun saya menyuntikkan penyedia data sebagai antarmuka layanan data generik sehingga saya bisa menukar implementasi.

Sejauh yang saya tahu, controller dimaksudkan untuk menjadi tempat Anda mendapatkan data, melakukan tindakan apa pun, dan meneruskan data ke tampilan.


Ya, saya pernah membaca tentang menggunakan "antarmuka layanan" untuk penyedia data, karena ini membantu dengan pengujian unit.
scott.korin
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.