3

I asked a previous question regarding best practices for mapping ViewModels to Entity Framework models in my controllers and was advised that my code was correct (using LINQ projection), though AutoMapper could be used in addition.

Now I feel like I need/want to move the bulk of what happens in the Controller methods to a new Service layer so I can add business logic when needed at this layer and then just have method calls in my controllers. But I am not exactly sure what to do. My ViewModels would all remain in the web project of course so what should my methods in the service layer look like and where/how do I map the ViewModels?

Here is a sample of a current GET and POST controller method:

    public ActionResult Laboratories()
    {
        var context = new PASSEntities();
        var model = (from a in context.Laboratories
                     select new LaboratoryViewModel()
                     {
                         ID = a.ID,
                         Description = a.Description,
                         LabAdmins = (from b in context.Users_Roles
                                      join c in context.Users on b.User_ID equals c.ID
                                      where b.Laboratory_ID == a.ID
                                      select new LabAdminViewModel()
                                      {
                                          ID = b.ID,
                                          User_ID = b.User_ID,
                                          Role_ID = b.Role_ID,
                                          Laboratory_ID = b.Laboratory_ID,
                                          BNL_ID = c.BNL_ID,
                                          First_Name = c.Pool.First_Name,
                                          Last_Name = c.Pool.Last_Name,
                                          Account = c.Account
                                      })
                     });

        return View(model);
    }

    [HttpPost]
    public ActionResult AddLaboratory(LaboratoryViewModel model)
    {
        try
        {
            using (PASSEntities context = new PASSEntities())
            {
                var laboratory = new Laboratory()
                {
                    ID = model.ID,
                    Description = model.Description
                };

                context.Laboratories.Add(laboratory);
                context.SaveChanges();
            }
            return RedirectToAction("Laboratories");
        }
        catch
        {
            return View();   
        }
    }
Steve Giordano
  • 133
  • 4
  • 10
  • What about Repository Pattern? http://stackoverflow.com/questions/11985736/repository-pattern-step-by-step-explanation – Evgeniy Labunskiy Oct 17 '13 at 19:39
  • 1
    Do you have a `domain layer`, which holds your `domain objects` or is that what your `models` are in the presentation layer? – Karl Anderson Oct 17 '13 at 19:47
  • The PASSEntities context references my models from Entity Framework which are contained in a separate project. I believe this is my domain model...? – Steve Giordano Oct 21 '13 at 13:42

2 Answers2

5

Your service layer should return your domain models. The controller is responsible for mapping them to a view model and return it to the view. A small example:

public ActionResult Laboratories()
{
    // Get the laboratories domain models from the service layer.
    var laboratories = _laboratoryService.GetLaboratories();

    // Map the domain models to view models using AutoMapper.
    var laboratoriesModel = Mapper.Map<List<LaboratoryViewModel>>(laboratories);

    // Return view model to the view.
    return View(laboratoriesModel);
}

With this approach you need a Core/Domain layer where your domain entities live. The service layer contains the business logic and interacts with the domain models (through repositories for example) and return the materialized objects to the controller. Your view models should indeed be in the Website project, as you propose.

Also check out this question where I provided an example of a similar solution.

Update

The GetLaborarties method in the service layer returns a (collection of) domain model(s):

public List<Laboratory> GetLaboratories()
{
    return _db.Laboratories.ToList();
}

Now in your controller you call this method and map it to the view model. You can do this by using the Linq Select method:

public ActionResult Laboratories()
{
    // Get the laboratories domain models from the service layer.
    var laboratories = _laboratoryService.GetLaboratories();

    var laboratoriesModel = laboratories.Select(new LaboratoryViewModel
                                                    {
                                                        // Map here..
                                                    }).ToList();

    return View(laboratoriesModel);
}

Or use AutoMapper as I stated above.


Update 2

Trivial example with navigation properties for related objects:

Assume we have this domain model:

public class Category
{
    public string Name { get; set; }

    public string UrlName { get; set; }

    // Other properties..

    public virtual ICollection<Product> Products { get; set; }
}

We can create a method in the service layer:

public CategoryService : ICategoryService
{
    public Category GetByName(string name)
    {
        return _categoryRepository.Table
                                  .Include(c => c.Products) // Include related products
                                  .FirstOrDefault(c => c.UrlName = name);
    }
}

I configured Entity Framework that a Category contains zero or more products. With the Include method I ask Entity Framework to include the related products in the sql query. Now Products will contain all the related products for the category.

Community
  • 1
  • 1
Henk Mollema
  • 44,194
  • 12
  • 93
  • 104
  • So in your example, the GetLaboratories method would live in the Service layer. In my example I use LINQ the data from my domain model into a ViewModel. I would I go about doing that in the GetLaboratories method in the Service layer. If the ViewModels would remain in the website project, what do I project the LINQ into in the Service layer that will then get mapped back in the controller? Do I define another model in the service layer that is identical to my Entity Framework model from my domain layer? – Steve Giordano Oct 21 '13 at 13:54
  • @SteveGiordano the service layer returns the domain model to the controller and the controller maps it to a view model. – Henk Mollema Oct 21 '13 at 16:28
  • @HenkMillema If I need to return a single domain model that can be mapped to a viewmodel in the controller, can you point me to what the code would look like based on my Laboratories() controller method above? Would I still be using LINQ? – Steve Giordano Oct 23 '13 at 18:44
  • @SteveGiordano you can use the LINQ `Select` method to project a view model. Or use [AutoMapper](https://github.com/AutoMapper/AutoMapper). – Henk Mollema Oct 24 '13 at 08:17
  • @HenkMillema Yes that is what I am doing in my controller currently. But if I move that code to the service layer I would no longer be projecting into the view model in the service layer (since the view models should stay in the website layer) so what would the LINQ be doing in the service layer? Do I instantiate a new domain model and somehow project multiple models into that? – Steve Giordano Oct 24 '13 at 14:53
  • @HankMillema ok that's what I thought but what about if the GetLaboratories method needs to query multiple database tables? Like in my original example for the LabAdmins. It does a join on multiple tables. Can this be achieved in the service layer? I guess this is what the other answers above were saying, when you need to do more manipulation on the domain models before returning them to the controller from the service layer. And what if what I need to return as a single model contains more properties than what my domain model has? – Steve Giordano Oct 24 '13 at 15:35
  • @SteveGiordano my domain models have navigation properties to related objects, they're eagerly loaded in my service layer. I never do such joins myself. And what do you mean with your last question? You view models can contain any properties you want, not just the ones from your domain model. – Henk Mollema Oct 24 '13 at 18:05
  • What I meant was, in the service layer, if I need to add some business logic that requires returning additional information from the database and that information is not part of the model I am returning, how do I do that? I know the view models can contain any properties but at the service layer I won't have view models yet, only the domain model. So is there a way to return that additional information with the domain model from the service layer? – Steve Giordano Oct 29 '13 at 15:50
  • If you have the time, can you show me an example of a service layer method where you are referencing navigation properties? Thanks again for all of your help. Just trying to figure out what the best thing to do is before I get too far into this project and have to do too much refactoring. – Steve Giordano Oct 29 '13 at 15:52
  • @SteveGiordano I added an example. Also, if your domain model still does not fit, you can always create a class between the service layer and controller, this class (a Data Transfer Object?) is just a simple combination of the properties you need in the controller where you map them to a view model. – Henk Mollema Oct 29 '13 at 16:04
  • @HenkMillema Thank you that makes a lot of sense. The DTO class was also what I was wondering about. If I had to go that route would the DTO classes be in the same project as the service layer or would it make more sense to have those in their own project? – Steve Giordano Oct 30 '13 at 15:19
  • @SteveGiordano the DTO class could live in the service layer or higher, next to the domain models for example. By the way, I'm wondering, where does HenkM _i_ llema comes from? It's M _o_ llema hehe. :-) – Henk Mollema Oct 30 '13 at 15:36
  • Ha sorry about that! Typo the first time and then copied and pasted. Thanks for your help! – Steve Giordano Nov 05 '13 at 15:48
4

I don't think it makes sense to refactor such simple code to yet another layer, however the question makes sense in other contexts, where more complicated processing occurs, for example, you create an account, set a default password for it and assign roles which makes few inserts and possibly few selects in a single business transaction.

A service layer consists of services. Your services operate at the domain entities level.

public class AccountService
{
    private PASSEntities _context { get; set; }

    public AccountService( PASSEntities context )
    {
       this._context = context;
    }

    public User CreateAccount( string username, string password )
    {
       // implementation here
    }

You access services from controllers and this is where the translation between view models and models occur so that services are unaware of the view model layer:

[HttpPost]
public ActionResult CreateUser( UserViewModel model )
{
   using ( PASSEntities context = new PASSEntities() )
   {
      AccountService service = new AccountService( context );
      service.CreateUser( model.Username, model.Password );

      // return something appropriate here     
   }
}
Wiktor Zychla
  • 47,367
  • 6
  • 74
  • 106
  • Wiktor - I respectfully disagree that it does not make sense to put non-complex logic into a service layer. I believe the better question is what and how many consumers of the functionality exist or will exist? Case in point, it is certainly reasonable to have a "service" that returns the conversion of US dollars to euros, which is not a terribly complex calculation, but there are a multitude of potential clients for this service, thus exposing it as a service makes perfect sense. That said, completely agree the OP needs to work with domain objects in this "layer". – Karl Anderson Oct 17 '13 at 20:00
  • 1
    @KarlAnderson: I understand your reasoning and agree with you. By "non-complex" I only mean a simple CRUD which doesn't make much sense it YET another layer. Your example makes perfect sense. – Wiktor Zychla Oct 17 '13 at 20:04
  • Ok I understand your definition of the Service layer and how the mapping would still occur in the controller. And yes I will have more complex logic in the service layer than what I have presented here, I just haven't gotten to it yet in my project. But I felt like if I was going to go that route, then everything should be done there as opposed to CRUD ops staying in the controller and only more complex logic moving to the service layer. Doesn't it make it cleaner to have all operations happening in the same place? – Steve Giordano Oct 21 '13 at 13:46
  • Though I suppose then with my current example the Service layer would have nothing to return since in this case I am only manipulating one domain model at a time? – Steve Giordano Oct 21 '13 at 13:49
  • For the sake of clarity, you can have everything out in the service layer. – Wiktor Zychla Oct 21 '13 at 13:57