Reputation: 1065
At the moment I know that, for MVC applications, Views should be populated out of ViewModels, Controllers should be kept slim, and that you shouldn't ever really expose your Entity Framework entities directly to the controllers.
Where I'm getting stuck, is where to put the functions that take the Model (from the database) and translate it into ViewModel data.
At the moment I have an entity that represents a series of tasks in the database. This is the class:
public class Task
{
public int ID { get; set; }
public string Name { get; set; }
public Contact Contact { get; set; }
public string Description { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
public int? QuotedHours { get; set; }
public int? UsedHours { get; set; }
public virtual ICollection<Attachment> Attachments { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
public virtual Status Status { get; set; }
public int RecoveryStatusID { get; set; }
public virtual RecoveryStatus RecoveryStatus { get; set; }
}
I have a DTO class which looks exactly the same, but uses Lists instead of virtual ICollections. It looks like this:
public class Task
{
public int ID { get; set; }
public string Name { get; set; }
public Contact Contact { get; set; }
public string Description { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
public int? QuotedHours { get; set; }
public int? UsedHours { get; set; }
public List<Attachment> Attachments { get; set; }
public List<Comment> Comments { get; set; }
public virtual Status Status { get; set; }
public int RecoveryStatusID { get; set; }
public RecoveryStatus RecoveryStatus { get; set; }
}
Then I have a View Model that I would like to populate.
public class TaskIndexViewModel
{
public string CategoryName { get; set; }
public List<DTO.Task> Tasks { get; set; }
}
So:
How do I map the EF entity to the DTO? I think it involves using the Linq Select statement. I would dearly love to use something like AutoMapper but I can't wrap my head around how it works (a lot of the documentation is outdated and seems to focus mainly on the old static implementation, and apparently it has been updated to an instance based implementation?)
Once I have the data in the DTO, where does the translation go from the DTO into the TaskIndexViewModel class? I know this would involve the Linq GroupBy statement, and also the Select statement to put this into a new List, but where should this kind of logic reside? I thought creating a new Service folder and putting this kind of functionality in there, but I feel dirty instantiating new objects in the controller, and then calling functions from that new class. Unless this isn't such a bad thing?
Any help would be greatly appreciated :)
Upvotes: 0
Views: 840
Reputation: 1567
I use AutoMapper, and it is a very easy and convenient way to map a lot of classes together.
Mapper.CreateMap<Task, DTO.Task>()
.ForMember(dest => dest.Attachments, opt => opt.MapFrom(src => src.ToList<Attachment>())
.ForMember(dest => dest.Comments, opt => opt.MapFrom(src => src.ToList<Comment>())
.ReverseMap()
.ForMember(dest => dest.Attachments, opt => opt.MapFrom(src => src as ICollection<Attachment>)
.ForMember(dest => dest.Comments, opt => opt.MapFrom(src => src as ICollection<Comment>);
Now you can say
var taskDto = Mapper.Map<Task, DTO.Task>(task);
But the reversemap part also lets you do the reverse as well.
var task = Mapper.Map<DTO.Task, Task>(taskDto);
Ideally you will want to put your mappings into a profile.
Also, there is no harm in not creating DTOs. If you decide not to create DTOs, you are binding your controllers to your entities, and in a small application this may never be a problem but if the application is to ever grow, you may find implementing differing logic may become difficult and you may be forced to put something in the controller that technically doesn't belong there. So if you decide not to use DTOs, you should be sure that the application will remain small.
Upvotes: 0
Reputation: 6565
I disagree with the last section of @raykrow's answer. This kind of structure:
var vm = new TaskIndexViewModel
{
Tasks = db.Tasks.ToList(),
CategoryName = "This Cool Cat"
};
...results in the Tasks
viewmodel property becoming tightly bound to the EF entity it's doing a .ToList()
on. What happens if you decide to make the switch from EF to another ORM?
Having a viewmodel which is (even) identical to the domain class is still beneficial. As to how can you easily map the results from your model to your viewmodel, this is why libraries like AutoMapper exist. Without AutoMapper:
// tasks becomes an IEnumerable<TaskViewModel>
var tasks = db.Tasks.Select(x => new TaskViewModel { A = x.A, B = x.B }).ToList();
var vm = new TaskIndexViewModel
{
Tasks = tasks,
Foo = bar
};
Upvotes: 0
Reputation: 2092
Check out this blog post I made
http://krow.tech/posts/Essentials-of-AutoMapper
(I'm going to mock some code here)
var db = new MyEfContext(); // Or however you create or get your ef context IoC maybe?
IEnumerable<Task> tasks = db.Tasks
.Select(e => Mapper.Map<DTO.Task>(e));
var vm = new TaskIndexViewModel
{
Tasks = tasks,
CategoryName = "This Cool Cat"
};
With this you can map your Task Entity to your Task DTO. Your mapping might look like...
config.CreateMap<Task, DTO.Task>()
.ForMember(dest => dest.Attachments.ToList(), opt => opt.MapFrom(src => src.Attachments))
However, given the names on your Entity and DTO are identical you might be better off writing some TypeResolvers
(see link above) for AutoMapper to use when converting ICollection
to IList
then simply tell AutoMapper:
config.CreateMap<Task, DTO.Task>();
I hope that answers your question. As we talked about in comments I still think the mapping to a designated DTO is overkill, even in this case where you pass the Entity directly to the VM (anyone feel free to correct me). I would simply to this:
var vm = new TaskIndexViewModel
{
Tasks = db.Tasks.ToList(),
CategoryName = "This Cool Cat"
};
See the post linked above for more context if needed.
Upvotes: 2