web-dev-qa-db-fra.com

Séparation de l'accès aux données dans ASP.NET MVC

Je veux m'assurer que je respecte les normes et les meilleures pratiques de l'industrie avec ma première vraie fissure chez MVC. Dans ce cas, c'est ASP.NET MVC, en utilisant C #.

J'utiliserai Entity Framework 4.1 pour mon modèle, avec des objets code-first (la base de données existe déjà), donc il y aura un objet DBContext pour récupérer les données de la base de données.

Dans les démos que j'ai vues sur le site asp.net, les contrôleurs contiennent du code d'accès aux données. Cela ne me semble pas juste, surtout lorsque vous suivez les pratiques DRY (ne vous répétez pas)).

Par exemple, disons que j'écris une application Web à utiliser dans une bibliothèque publique et que j'ai un contrôleur pour créer, mettre à jour et supprimer des livres dans un catalogue.

Plusieurs des actions peuvent prendre un ISBN et doivent retourner un objet "Book" (notez que ce n'est probablement pas un code valide à 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);
    }
}

Au lieu de cela, devrait J'ai en fait une méthode dans mon objet de contexte db pour retourner un livre? Cela semble être une meilleure séparation pour moi et aide à promouvoir DRY, car je pourrais avoir besoin d'obtenir un objet Book par ISBN ailleurs dans mon application Web.

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

Est-ce un ensemble de règles valides à suivre dans le codage de ma candidature?

Ou, je suppose qu'une question plus subjective serait: "est-ce la bonne façon de le faire?"

35
scott.korin

En règle générale, vous souhaitez que vos contrôleurs ne fassent que quelques opérations:

  1. Gérer la demande entrante
  2. Déléguer le traitement à un objet métier
  3. Transférer le résultat du traitement métier à la vue appropriée pour le rendu

Il ne doit y avoir aucun accès aux données ou logique métier complexe dans le contrôleur.

[Dans la plus simple des applications, vous pouvez probablement vous en sortir avec des actions CRUD de données de base dans votre contrôleur, mais une fois que vous commencerez à ajouter plus que de simples appels Get et Update, vous voudrez répartir votre traitement dans une classe distincte. ]

Vos contrôleurs dépendent généralement d'un "service" pour effectuer le travail de traitement réel. Dans votre classe de service, vous pouvez travailler directement avec votre source de données (dans votre cas, le DbContext), mais encore une fois, si vous vous retrouvez en train d'écrire beaucoup des règles métier en plus de l'accès aux données, vous souhaiterez probablement séparer votre logique métier de votre accès aux données.

À ce stade, vous aurez probablement une classe qui ne fait rien d'autre que l'accès aux données. Parfois, cela s'appelle un référentiel, mais peu importe le nom. Le fait est que tout le code pour entrer et sortir des données de la base de données est en un seul endroit.

Pour chaque projet MVC sur lequel j'ai travaillé, je me suis toujours retrouvé avec une structure comme:

Contrôleur

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

Service aux entreprises

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;

     } 
}

Référentiel

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
}
55
Eric King

C'est ainsi que je l'ai fait, bien que j'injecte le fournisseur de données en tant qu'interface de service de données générique afin de pouvoir échanger des implémentations.

Autant que je sache, le contrôleur est destiné à être l'endroit où vous obtenez des données, effectuez des actions et transmettez des données à la vue.

2
Nathan Craddock