3

I have an ASP.NET MVC 3 application.

I have a Model, ViewModel, View, Controller.

I use Ninject as IoC.

My Controller uses a ViewModel to pass data to the View.

I've started to use Services (concrete and interface types) to take information from the ViewModel and query it against the database to manipulate it.

Can I use the same Service to setup the ViewModel? Or is this going against the grain of the design pattern?

I.e. Can I abstract setting up the ViewModel in the Service layer?

Scenario

The scenario is; my Model has lots of references to other Models, so when I setup the ViewModel in the controller it's to verbose, and I feel the Controller is doing too much. So I want to be able to just do something like:

var vm = _serviceProvider.SetupViewModel(Guid model1Id, Guid model2Id, /*etc..*/)

And the SetupViewModel function in the ServiceProvider would look like this:

public MyModelViewModel SetupViewModel(Guid model1Id, Guid model2Id, /*etc...*/)
{
    var vm = new MyModelViewModel();
    var model1 = _repository.Model1s.FirstOrDefault(x => x.Id.Equals(model1Id));
    var model2 = _repository.Model2s.FirstOrDefault(x => x.Id.Equals(model2Id));
// etc....

    vm.Model1 = model1;
    vm.Model2 = model2;

    return vm;
}

By doing this I could also add some null conditions as well, not worrying about making my Controller really really really big!!

I use 1 ViewModel for the Create/Edit actions. I don't reuse the ViewModel elsewhere.

Callum Linington
  • 14,213
  • 12
  • 75
  • 154
  • How about providing a little more information about your particular architecture? Perhaps a brief code sample explaining what you're trying to do? – Robert Harvey Jul 08 '13 at 15:41
  • I've edited my question slightly, but I haven't put code in it – Callum Linington Jul 08 '13 at 15:43
  • 1
    That sounds like you're reusing ViewModels which is bad. – Phill Jul 08 '13 at 15:47
  • @Phil i've added some info to the question – Callum Linington Jul 08 '13 at 15:50
  • Your code looks sensible, but it usually goes in the controller, not the ViewModel. How are you passing _repository to SetupViewModel? Is it one of the method parameters? If you really want to try this, I would create a constructor for ViewModel that accepts _repository as one of the constructor method parameters. – Robert Harvey Jul 08 '13 at 15:54
  • @RobertHarvey there is nothing happening in the `ViewModel` ... it's only holding information that the `View` needs to render ListBox's etc... – Callum Linington Jul 08 '13 at 15:57
  • As another answerer mentioned, Automapper can be good for this sort of thing. I don't see anything wrong in principle with having a helper method in your ViewModel that assists in populating it with data. – Robert Harvey Jul 08 '13 at 16:00
  • In the future, I think you'll get better results from your questions if you pose your problem, provide example code that illustrates the problem, and let the community offer solutions, rather than offering the solution and seeing if everyone agrees. – Robert Harvey Jul 08 '13 at 16:06
  • But unfortunately I find it particularly difficult to understand what I'm asking, so it's not until I get it down and get some input from people till I can fully recognise what it is I'm asking, also I'm not offering a solution, its a design pattern, and just because I can say it doesn't mean I know how to implement it. I'm asking the community if my understanding is correct and that my implementation is also correct. – Callum Linington Jul 08 '13 at 16:09
  • 1
    There's no such thing as "correct." There is only that which best meets your customer's requirements. :) And what might be critical for one project might be completely unnecessary for another. – Robert Harvey Jul 08 '13 at 16:25

2 Answers2

5

I would let the service layer return a Domain Model and map it to a ViewModel in the controller.

This way you can use a service method with multiple ViewModels, for a desktop and mobile view for example.

You can let AutoMapper do the hard work for you or do it manually, by creating a constructor in the ViewModel which takes the Domain Model.

The domain model:

public class Customer
{
    public int Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string Telephone { get; set; }

    public string Email { get; set; }

    public virtual ICollection<Order> Orders { get; set; }
}

The ViewModel:

public class CustomerWithOrdersModel
{
    public CustomerWithOrdersModel(Customer customer)
    {
        Id = customer.Id;
        FullName = string.Format("{0}, {1}", customer.LastName, customer.FirstName);
        Orders = customer.Orders.ToList();
    }

    public int Id { get; set; }

    public string FullName { get; set; }

    public IEnumerable<Order> Orders { get; set; }
}

EDIT: AutoMapper example:

The AutoMapper profile containing the mapping from a Customer to a CustomerWithOrdersModel:

public class ViewModelProfile : Profile
{
    public override string ProfileName
    {
        get { return "ViewModel"; }
    }

    protected override void Configure()
    {
        CreateMap<Customer, CustomerWithOrdersModel>()
            .ForMember(dest => dest.FullName, opt => opt.MapFrom(src => string.Format("{0}, {1}", src.LastName, src.FirstName)))
            .ForMember(dest => dest.Orders, opt => opt.MapFrom(src => src.Orders.ToList()));
    }
}

Id is mapped by convention.

Extension methods for the ViewModelProfile:

public static class ViewModelProfileExtensions
{
    public static CustomerWithOrdersModel ToModel(this Customer customer)
    {
        return Mapper.Map<CustomerWithOrdersModel>(customer);
    }

    public static Customer ToEntity(this CustomerWithOrdersModel customerWithOrdersModel)
    {
        return Mapper.Map<Customer>(customerWithOrdersModel);
    }
}

The controller action:

public ActionResult Details(int customerId)
{
    Customer customer = _customerService.GetById(customerId);
    CustomerWithOrdersModel customerWithOrders = customer.ToModel();
    return View(customerWithOrders);
}

If you create a mapping from CustomerWithOrdersModel to Customer, you can use customerWithOrdersModel.ToEntity() to map it back to the Domain Model.

Thats it! You can remove the constructor with the Customer domain model from the ViewModel.

Henk Mollema
  • 44,194
  • 12
  • 93
  • 104
  • Could you give an example with `AutoMapper` which demonstrates why it is better to use? My current method of creating and using `ViewModels` is far better than the one you're presenting, I just want to move away from having verbose `ViewModels` and `Controllers` when I have a complex `Business Model` – Callum Linington Jul 09 '13 at 09:41
  • that is pretty cool, I will have to start a brand new project to find out all the intricacy's of using AutoMapper. – Callum Linington Jul 09 '13 at 14:52
  • @No1_Melman you should definitely try. You can also create extension methods like `ToEntity` and `ToModel` to abstract AutoMapper away from your controllers, this is also results in less code. – Henk Mollema Jul 09 '13 at 15:05
  • so does this now provide a loose coupling? and can it be "ninjected" – Callum Linington Jul 10 '13 at 08:19
  • @No1_Melman mapping domain models to ViewModels has nothing to do with coupling and doesn't have to be Ninjected. This just makes sure that the implementation of `AutoMapper` is in one place and not all over your controllers. – Henk Mollema Jul 10 '13 at 08:30
1

If you have the view models as their own project and handle the mapping and returning of view models in your service layer, I see nothing wrong with that. For separation of concerns you could always have another component that handles the mapping.

Maess
  • 4,118
  • 20
  • 29
  • So that would be another service with concrete and interface implementations? – Callum Linington Jul 08 '13 at 15:42
  • Yes. That would allow you to inject it into your controller and would aid in unit testing. As you could test the service standalone and not worry about testing each time the same method is used in one of your controllers. In your controller tests you could just mock the service. – Maess Jul 08 '13 at 15:43
  • I've added some information to the question, does still align with your answer? – Callum Linington Jul 08 '13 at 15:52
  • I implemented a service for 1) setting up the `ViewModel` , 2) for getting data from it and `Model` to save. It works for both `Edit` and `Create` actions and it means that there is only 1 line in the controller `var entityToSave = _provider.CreateEntity(vm)` which is genius. thanks @Maess – Callum Linington Jul 09 '13 at 10:11