36

I want to make sure I'm following industry standards and best practices with my first real crack at MVC. In this case, it's ASP.NET MVC, using C#.

I will be using Entity Framework 4.1 for my model, with code-first objects (the database already exists), so there will be a DBContext object for retrieving data from the database.

In the demos I've gone through on the asp.net website, controllers have data access code in them. This doesn't seem right to me, especially when following the DRY (don't repeat yourself) practices.

For example, let's say I am writing a web application to be used in a public library, and I have a controller for creating, updating, and deleting books in a catalog.

Several of the actions may take an ISBN and need want to return a "Book" object (note this is probably not 100% valid code):

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

Instead, should I actually have a method in my db context object to return one Book? That seems like it is a better separation to me, and helps promote DRY, because I might need to get a Book object by ISBN somewhere else in my web application.

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

Is this a valid set of rules to follow in the coding of my application?

Or, I guess a more subjective question would be: "is this the right way to do it?"

scott.korin
  • 543
  • 1
  • 5
  • 9

2 Answers2

58

Generally, you want your Controllers to do only a few things:

  1. Handle the incoming request
  2. Delegate the processing to some business object
  3. Pass the result of the business processing to the appropriate view for rendering

There shouldn't be any data access or complex business logic in the controller.

[In the simplest of apps, you can probably get away with basic data CRUD actions in your controller, but once you start adding in more than simple Get and Update calls, you are going to want to break out your processing into a separate class.]

Your controllers will usually depend on a 'Service' to do the actual processing work. In your service class you may work directly with your data source (in your case, the DbContext), but once again, if you find yourself writing a lot of business rules in addition to the data access, you will probably want to separate your business logic from your data access.

At that point, you will probably have a class that does nothing but the data access. Sometimes this is called a Repository, but it doesn't really matter what the name is. The point is that all of the code for getting data into and out of the database is in one place.

For every MVC project I've worked on, I've always ended up with a structure like:

Controller

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

Business Service

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;

     } 
}

Repository

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
}
Eric King
  • 10,876
  • 3
  • 41
  • 55
  • +1 Overall great advice, although I'd question whether the repository abstraction was providing any value. – MattDavey Mar 02 '12 at 09:51
  • 3
    @MattDavey Yeah, at the very beginning (or for the simplest of apps) it's hard to see the need for a repository layer, but as soon as you have even a moderate level of complexity in your business logic it becomes a no-brainer to separate the data access. It's not easy to convey that in a simple way, though. – Eric King Mar 02 '12 at 13:13
  • yeah I wouldn't advocate using the DBContext directly in the application layer - but I think the contexts/sessions etc generated by ORMS are usually enough of a 'repository' in their own right and don't warrant an extra abstraction. In this case I'd think about having LibraryDBContext implement IBookRepository.. In any case I'd never say that a repository layer was ever a *bad* architecture :) – MattDavey Mar 02 '12 at 14:28
  • @MattDavey Sure, I can agree with that. – Eric King Mar 02 '12 at 14:44
  • @EricKing: I use the same setup. I have a Data Project and the Application Project. In the data project I currently have only the repository - but I'm beginning to think that the service layer should also go in the data project. What do you think? – Cody Jan 12 '13 at 21:48
  • @EricKing Based on your example, I 'm guessing you are using a DI framework to get ILibraryService into your controller along with IBookRepository and ICustomerRepository into your service? I see your controller is taking in an interface, is that so you can unit test your controller? Is there a lot of benefit to that since your controller shouldn't be doing much, couldn't you just test your service class or am I missing something? – Billy Jan 28 '14 at 02:27
  • @Billy No, you're not missing anything... It's not _necessary_, but I generally try to write my classes nicely decoupled, unless there's no other way. In this case, it's just a few lines of code (depending on the DI framework) to get a testable controller, so why not? – Eric King Jan 28 '14 at 03:55
  • @EricKing By creating an additional interface (for your service class), and probably one more line in your DI framework, you can now test your controller if you'd like. Got it. Since your DI framework is injecting the concrete repository class into your service, does that mean your MVC project now needs to reference your repository project? It seems like that could open up the ability to directly call methods on the repository classes instead of running everything through the service. – Billy Jan 28 '14 at 13:47
  • 1
    @Billy The IoC kernel doesn't _have_ to be in the MVC project. You could have it in a project of its own, which the MVC project depends on, but which in turn depends on the repository project. I don't generally do that because I don't feel the need to. Even so, if you don't want your MVC project to call your repository classes... then don't. I'm not a big fan of hamstringing myself so that I can protect myself from the possibility of programming practices I'm not likely to engage in. – Eric King Jan 28 '14 at 14:54
  • @MattDavey Regarding "whether the repository abstraction was providing any value" I can add that you need that abstraction to make sure you business logic does not depend on any specific data access implementation. Basically your business logic layer should be totally blind about HOW the date is being accessed. – kaptan Feb 20 '14 at 00:37
  • @kaptan yeah I totally understand the principle. I'm referring to those common scenarios we see, where for example a service layer delegates directly to a repository with identical method signatures, or a repository delegates directly to an ORM context with no actual work being performed. In this case we're not talking about abstraction, we're talking about indirection. That indirection can usually be better achieved simply by implementing an interface on the class which actually does the work (and eliminating the middleman). – MattDavey Feb 20 '14 at 10:00
  • 2
    We use exactly this pattern: Controller-Service-Repository. I'd like to add that it's very useful for us to have the service/repository tier take parameters objects (e.g. GetBooksParameters) and then use extension methods on ILibraryService to do parameter wrangling. That way ILibraryService has a simple entry point that takes an object, and the extension method can go as parameter crazy as possible without having to rewrite interfaces and classes every time (e.g. GetBooksByISBN/Customer/Date/Whatever just forms the GetBooksParameters object and calls the service). The combo is been great. – BlackjacketMack Apr 16 '14 at 11:45
  • Your `LibraryService` class references a `_libraryRepository` object, where is that defined? – Isaac Kleinman Nov 21 '14 at 18:07
  • You have a separate repository class for each entity/resource? What about if you want a book operation and a customer operation to be atomic i.e. part of one transaction? – Isaac Kleinman Nov 21 '14 at 18:09
  • @BlackjacketMack: How does this whole notion of a 'service' work with OOP? Shouldn't the service's `ProcessTheComplexStuff` method be defined on the `Book` object itself? – Isaac Kleinman Mar 12 '15 at 15:41
  • @IsaacKleinman Not if the processing includes doing something to the Book, the LibraryInventory, and the Customer record in one transaction. Such processing doesn't belong in any of the individual objects, but in a service designed to manage such interaction. – Eric King Mar 12 '15 at 16:49
  • @EricKing: Maybe. Others would argue that your controller should accomplish the complex processing by calling the `Book`, `LibraryInventory` and `Customer` to do their parts. – Isaac Kleinman Mar 12 '15 at 16:57
  • @IsaacKleinman Others would be wrong, in my opinion. :-) The controller has specific responsibilities, and business logic isn't one of them. http://programmers.stackexchange.com/a/26442/4127 – Eric King Mar 12 '15 at 18:07
  • 1
    @IsaacKleinman I can't remember which of the greats wrote it (Bob Martin?) but it's a fundamental question: do you want Oven.Bake(pizza) or Pizza.Bake(oven). And the answer is 'it depends'. Usually we want an outside service (or unit of work) manipulating one or more objects (or pizzas!). But who's to say that those individual objects don't have the ability to react to the type of Oven they're being baked in. I prefer OrderRepository.Save(order) to Order.Save(). However, I do like Order.Validate() because the order can know it's own ideal form. Contextual, and personal. – BlackjacketMack Mar 13 '15 at 14:28
  • What about ACL (Access Control)? Where does that go? i.e. whether the requesting user has rights to view, or edit or see nothing for a MVC action. – Shiva Sep 04 '15 at 23:07
  • @EricKing (old post I know, but if you still read here I have a question) I'm interested in your implementation of `return View(ConvertToBookViewModel(currentBook));` **ConvertToBookViewModel**, where do you keep this, in the Service project or a separate project for handling ViewModels? – Iztoksson Jan 17 '16 at 14:54
  • @Uporabnik003 A viewmodel in this context represents the formatted and processed data that an MVC view needs to properly display itself. It's specifically an MVC concept, and would be in the MVC (web) project, not the service project, if you've placed your services in their own project. The ConvertToBookViewModel method itself I'd probably just have in the controller itself, or in a class next to the viewmodels if it needs to be shared between controllers. – Eric King Jan 17 '16 at 22:20
2

This is the way I've been doing it, although I'm injecting the data provider as a generic data service interface so that I can swap out implementations.

As far as I know, the controller is intended to be where you get data, perform any actions, and pass data to the view.