6

In my ASP.net mvc app I am using a Service Layer and Repositories to keep my controllers thin. A typical details read only view looks like this:

public ActionResult Details(int id)
{
    var project = _projectService.GetById(id);

    return View(Mapper.Map<Project, ProjectDetails>(project));
}

Service Layer:

public class ProjectService : IProjectService
{
    public Project GetById(int id)
    {
        var project = _projectRepository.GetProject(id);

        // do some stuff

        return project;
    }
}

public class ProjectRepository : IProjectRepository
{
    public Project GetProject(int id)
    {
        return context.Projects.Find(id);
    }
}

Moving from the service layer to the view model is pretty easy because of automapper, which can flatten things pretty easily. Moving the other direct, from the view model to pass into my service layer is where I struggle to come up with a good solution.

In a situation like a Create action, what is a good approach for this?

[HttpPost]
public ActionResult Create(CreateProjectViewModel model)
{
    if(!ModelState.IsValid)
    {
        return View(model);
    }

    // TODO

    return RedirectToAction("Index");
}

I'm pretty sure that the service layer should not know anything about view models, but I also don't think that AutoMapper works well in this scenario either, since it's not good at taking a flat model and making it into a complex object.

What should my controller look like to communicate with the service layer? I want to keep the code in the controller as light as possible.

Dismissile
  • 32,564
  • 38
  • 174
  • 263
  • 1
    You know, i've always found AutoMapper to be cumbersome in this manner as well, it would be nice if something like Resharper could implement a way to create mappings on complex objects automatically. Might make a good plug-in – Erik Funkenbusch Feb 17 '12 at 15:58
  • About unflattening with AutoMapper: did you take a look at http://stackoverflow.com/questions/3145062/using-automapper-to-unflatten-a-dto? – rsenna Feb 17 '12 at 16:02

3 Answers3

6

You could define a bidirectional mapping and then go the other way around:

[HttpPost]
public ActionResult Create(CreateProjectViewModel model)
{
    if(!ModelState.IsValid)
    {
        return View(model);
    }

    Project project = Mapper.Map<CreateProjectViewModel, Project>(model);
    // pass the project entity to your service layer
    _projectService.Create(project);

    return RedirectToAction("Index");
}

or if you are updating an entity you might first want to fetch the existing entity that you want to update from the service:

[HttpPost]
public ActionResult Update(CreateProjectViewModel model)
{
    if(!ModelState.IsValid)
    {
        return View(model);
    }

    Project project = _projectService.GetById(model.Id);
    Mapper.Map<CreateProjectViewModel, Project>(model, project);

    // pass the project entity to your service layer
    _projectService.Update(project);

    return RedirectToAction("Index");
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    It seems that sometimes this doesn't work very well because AutoMapper can't easily go from a flattened model to a complex model. – Dismissile Feb 17 '12 at 15:40
  • 1
    @Dismissile, well, all will depend on how you define your mapping. It's true that it's easier to flatten with AutoMapper but you could also go the other way round and reconstruct the original domain model from the view model. – Darin Dimitrov Feb 17 '12 at 15:41
  • true. Is this the approach you usually take in your projects? – Dismissile Feb 17 '12 at 15:44
  • @Dismissile, it will depend on the specific requirements and context of the project. But in general it is the approach I prefer. – Darin Dimitrov Feb 17 '12 at 15:45
  • One last question. In the projects that you do use automapper to go from a view model to a domain model, is your domain model ever the same as your entity framework model? View Model <--> Entity (Service Layer) <--> Entity (Repository). Basically the repository and service layer are both dealing with the same objects? – Dismissile Feb 17 '12 at 15:49
  • @Dismissile, I don't use Entity Framework in my projects so I cannot speak for myself. But if I had to use it, I would probably reuse the EF autogenerated classes as domain models in the service layer. But in more complex projects where you already have some existing domain models, the EF classes should be considered as an ORM implementation specific and probably be used only at the data access level. Personally I wouldn't support my domain models to contain data access specific classes such as `DbSet` and stuff. – Darin Dimitrov Feb 17 '12 at 17:33
1

The only way I have seen this done so far is to manually create a bunch of model transformation classes, for example:

public interface ITransformer<out To, in From>
    where To : class
{
    To Transform(From instance);
}

public class SomeDataToSomeViewModelTransformer : ITransformer<SomeViewModel, SomeDataModel>
{
    public SomeViewModel Transform(SomeDataModel instance)
    {
        return new SomeViewModel
            {
                InvitationId = instance.Id,
                Email = instance.EmailAddress,
                GroupId = instance.Group.Id
            };
    }
}

And another Transformer implementation to go back the other way (ViewModel -> DataModel). And having the Controller know to call the correct transformer.

I +1 your question because I would love to see a nice clean way to do this too, without having to manually write a bunch of code to map models.

CodingWithSpike
  • 42,906
  • 18
  • 101
  • 138
  • I think this could easily work. It would be easy to wire up the transformers with the Dependency Injection framework. – Dismissile Feb 17 '12 at 15:47
0

If your service layer is solely dedicated to support your MVC application and no other clients you could consider using the objects passed through and from your service layer as part of your viewmodels. This would obviate the need to automap the inbound calls as you'd be sending in the actual objects required from the controller.

You could also consider not having the services return domain objects, this would mean that the automapping should be invoked with the service methods rather than the controller actions.

Nick Ryan
  • 2,662
  • 1
  • 17
  • 24