Steve Giordano
Steve Giordano

Reputation: 133

Best practice for moving code to Service layer

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

Upvotes: 3

Views: 3296

Answers (2)

Henk Mollema
Henk Mollema

Reputation: 46551

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.

Upvotes: 5

Wiktor Zychla
Wiktor Zychla

Reputation: 48240

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

Upvotes: 4

Related Questions