Tách quyền truy cập dữ liệu trong ASP.NET MVC


35

Tôi muốn đảm bảo rằng tôi đang tuân theo các tiêu chuẩn ngành và thực tiễn tốt nhất với vết nứt thực sự đầu tiên của tôi tại MVC. Trong trường hợp này, đó là ASP.NET MVC, sử dụng C #.

Tôi sẽ sử dụng Entity Framework 4.1 cho mô hình của mình, với các đối tượng mã đầu tiên (cơ sở dữ liệu đã tồn tại), do đó sẽ có một đối tượng DBContext để lấy dữ liệu từ cơ sở dữ liệu.

Trong các bản demo tôi đã trải qua trên trang web asp.net, bộ điều khiển có mã truy cập dữ liệu trong đó. Điều này có vẻ không đúng với tôi, đặc biệt là khi tuân theo các thực hành DRY (không lặp lại chính mình).

Ví dụ: giả sử tôi đang viết một ứng dụng web sẽ được sử dụng trong thư viện công cộng và tôi có một bộ điều khiển để tạo, cập nhật và xóa sách trong danh mục.

Một số hành động có thể mất một số ISBN và muốn trả về một đối tượng "Sách" (lưu ý đây có thể không phải là mã hợp lệ 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);
    }
}

Thay vào đó, tôi có thực sự nên có một phương thức trong đối tượng ngữ cảnh db của mình để trả về một Sách không? Điều đó có vẻ như là một sự tách biệt tốt hơn đối với tôi và giúp thúc đẩy DRY, bởi vì tôi có thể cần phải có một đối tượng Sách bằng ISBN ở một nơi khác trong ứng dụng web của mình.

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

Đây có phải là một bộ quy tắc hợp lệ để tuân theo trong mã hóa ứng dụng của tôi không?

Hoặc, tôi đoán một câu hỏi chủ quan hơn sẽ là: "đây có phải là cách đúng đắn để làm điều đó?"

Câu trả lời:


55

Nói chung, bạn muốn Bộ điều khiển của mình chỉ thực hiện một số điều:

  1. Xử lý yêu cầu đến
  2. Ủy quyền xử lý cho một số đối tượng kinh doanh
  3. Truyền kết quả xử lý nghiệp vụ vào chế độ xem phù hợp để hiển thị

Không nên có bất kỳ quyền truy cập dữ liệu hoặc logic kinh doanh phức tạp nào trong bộ điều khiển.

[Trong các ứng dụng đơn giản nhất, bạn có thể thoát khỏi các hành động CRUD dữ liệu cơ bản trong bộ điều khiển của mình, nhưng một khi bạn bắt đầu thêm các cuộc gọi Nhận và Cập nhật đơn giản hơn, bạn sẽ muốn tách ra xử lý thành một lớp riêng. ]

Bộ điều khiển của bạn thường sẽ phụ thuộc vào 'Dịch vụ' để thực hiện công việc xử lý thực tế. Trong lớp dịch vụ của bạn, bạn có thể làm việc trực tiếp với nguồn dữ liệu của mình (trong trường hợp của bạn là DbContext), nhưng một lần nữa, nếu bạn thấy mình viết nhiều quy tắc kinh doanh ngoài việc truy cập dữ liệu, có thể bạn sẽ muốn tách doanh nghiệp của mình logic từ truy cập dữ liệu của bạn.

Tại thời điểm đó, bạn có thể sẽ có một lớp không có gì ngoài việc truy cập dữ liệu. Đôi khi điều này được gọi là Kho lưu trữ, nhưng thực sự không quan trọng tên đó là gì. Vấn đề là tất cả các mã để nhận dữ liệu vào và ra khỏi cơ sở dữ liệu đều ở một nơi.

Đối với mọi dự án MVC mà tôi đã làm việc, tôi luôn kết thúc với một cấu trúc như:

Bộ điều khiển

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

Dịch vụ kinh doanh

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;

     } 
}

Kho

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 Lời khuyên tuyệt vời nói chung, mặc dù tôi đặt câu hỏi liệu việc trừu tượng hóa kho lưu trữ có cung cấp bất kỳ giá trị nào không.
MattDavey

3
@MattDavey Vâng, ngay từ đầu (hoặc đơn giản nhất là các ứng dụng), thật khó để thấy sự cần thiết của một lớp kho lưu trữ, nhưng ngay khi bạn có mức độ phức tạp vừa phải trong logic kinh doanh của mình, nó sẽ trở nên vô nghĩa phân tách truy cập dữ liệu. Tuy nhiên, không dễ để truyền đạt điều đó một cách đơn giản.
Eric King

1
@Billy IOC hạt nhân không phải là trong dự án MVC. Bạn có thể có nó trong một dự án của riêng nó, dự án MVC phụ thuộc vào, nhưng đến lượt nó phụ thuộc vào dự án kho lưu trữ. Tôi thường không làm điều đó bởi vì tôi không cảm thấy cần thiết. Mặc dù vậy, nếu bạn không muốn dự án MVC của bạn gọi các lớp kho lưu trữ của bạn ... thì không. Tôi không phải là một fan hâm mộ lớn của bản thân mình để tôi có thể bảo vệ bản thân khỏi khả năng thực hành lập trình mà tôi không có khả năng tham gia.
Eric King

2
Chúng tôi sử dụng chính xác mẫu này: Bộ điều khiển-Dịch vụ-Kho lưu trữ. Tôi muốn nói thêm rằng chúng tôi rất hữu ích khi có tầng dịch vụ / kho lưu trữ lấy các đối tượng tham số (ví dụ: GetBooksParameter) và sau đó sử dụng các phương thức mở rộng trên IL LibraryService để thực hiện thay đổi tham số. Theo cách đó, IL LibraryService có một điểm nhập đơn giản lấy một đối tượng và phương thức mở rộng có thể trở nên điên rồ nhất có thể mà không phải viết lại giao diện và các lớp mỗi lần (ví dụ: GetBooksByISBN / Khách hàng / Ngày / Dù chỉ tạo thành đối tượng GetBooksParameter và gọi dịch vụ). Các kết hợp là tuyệt vời.
BlackjquetMack

1
@IsaacKleinman Tôi không thể nhớ những người vĩ đại nào đã viết nó (Bob Martin?) Nhưng đó là một câu hỏi cơ bản: bạn có muốn Lò nướng.Bake (pizza) hay Pizza.Bake (lò nướng). Và câu trả lời là "nó phụ thuộc". Thông thường chúng tôi muốn một dịch vụ bên ngoài (hoặc đơn vị công việc) thao túng một hoặc nhiều đối tượng (hoặc pizza!). Nhưng ai cũng nói rằng những vật thể riêng lẻ đó không có khả năng phản ứng với loại Lò nướng mà chúng đang được nướng. Tôi thích OrderRep repository.Save (order) hơn Order.Save (). Tuy nhiên, tôi thích Order.Validate () vì đơn hàng có thể biết đó là hình thức lý tưởng của riêng nó. Bối cảnh, và cá nhân.
BlackjquetMack

2

Đây là cách tôi đã thực hiện, mặc dù tôi đang tiêm nhà cung cấp dữ liệu làm giao diện dịch vụ dữ liệu chung để tôi có thể trao đổi triển khai.

Theo tôi biết, bộ điều khiển được dự định là nơi bạn lấy dữ liệu, thực hiện bất kỳ hành động nào và chuyển dữ liệu đến chế độ xem.


Có, tôi đã đọc về việc sử dụng "giao diện dịch vụ" cho nhà cung cấp dữ liệu, bởi vì nó giúp kiểm tra đơn vị.
scott.korin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.