Модели, ViewModels, DTO в приложении MVC 3
У меня есть веб-решение (в VS2010) с двумя подпроектами:
-
Domain
, который содержит классы Model
(сопоставленные с таблицами базы данных через Entity Framework) и Services
, которые (помимо других вещей) несут ответственность за операции CRUD
-
WebUI
, который ссылается на проект домена
Для первых страниц, которые я создал, я использовал классы Model из проекта Domain непосредственно как Model в своих сильно типизированных представлениях, потому что классы были небольшими, и я хотел отображать и изменять все свойства.
Теперь у меня есть страница, которая должна работать только с небольшой частью всех свойств соответствующей модели домена. Я извлекаю эти свойства, используя проекцию результата запроса в моем классе Service. Но мне нужно проект в тип - и вот мои вопросы о решениях, о которых я могу думать:
-
Я представляю ViewModels
, которые живут в проекте WebUI
и выставляют IQueryables
и EF data context
из службы в проект WebUI. Тогда я мог бы напрямую проецировать на эти ViewModels.
-
Если я не хочу раскрывать IQueryables и контекст данных EF, я помещаю классы ViewModel
в проект Domain
, тогда я могу вернуть ViewModels напрямую в результате запросов и прогнозов из Сервисные классы.
-
В дополнение к ViewModels
в проекте WebUI
я представляю Data transfer objects
, который перемещает данные из запросов в классах службы в ViewModels
.
Решение 1 и 2 похоже на то же количество работы, и я склонен предпочитать решение 2, чтобы сохранить все проблемы с базой данных в отдельном проекте. Но почему-то кажется неправильным, что в проекте Domain есть модели View-Models.
Решение 3 звучит намного больше, поскольку у меня есть больше классов для создания и ухода за отображением Model-DTO-ViewModel. Я также не понимаю, какая разница между DTO и ViewModels. Не являются ли ViewModels именно коллекцией выбранных свойств моего класса Model, которые я хочу отобразить? Разве они не будут содержать те же члены, что и DTO? Почему я хочу различать ViewModels и DTO?
Какое из этих трех вариантов предпочтительнее и каковы преимущества и недостатки? Существуют ли другие варианты?
Благодарим вас за отзыв!
Изменить (потому что у меня была, возможно, слишком длинная стена текста и была запрошена код)
Пример: у меня есть Customer
Entity...
public class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public City { get; set; }
// ... and many more properties
}
... и хотите создать представление, которое отображает (и, возможно, позволяет редактировать) Name
клиентов в списке. В классе Service я извлекаю данные, которые мне нужны для представления через проекцию:
public class CustomerService
{
public List<SomeClass1> GetCustomerNameList()
{
using (var dbContext = new MyDbContext())
{
return dbContext.Customers
.Select(c => new SomeClass1
{
ID = c.ID,
Name = c.Name
})
.ToList();
}
}
}
Тогда есть CustomerController с методом действия. Как это должно выглядеть?
Либо этот способ (а)...
public ActionResult Index()
{
List<SomeClass1> list = _service.GetCustomerNameList();
return View(list);
}
... или лучше (b):
public ActionResult Index()
{
List<SomeClass1> list = _service.GetCustomerNameList();
List<SomeClass2> newList = CreateNewList(list);
return View(newList);
}
Что касается варианта 3 выше, я бы сказал: SomeClass1
(живет в проекте Domain
) - это DTO и SomeClass2
(живет в проекте WebUI
) - это ViewModel.
Мне интересно, имеет ли смысл различать два класса. Почему бы мне не выбрать вариант (а) для действия контроллера (потому что это проще)? Есть ли причины ввести ViewModel (SomeClass2
) в дополнение к DTO (SomeClass1
)?
Ответы
Ответ 1
ввести ViewModels, которые живут в Проект WebUI и выставить IQueryables и контекст данных EF из сервис для проекта WebUI. Затем я может напрямую проектировать в те ViewModels.
Проблема с этим заключается в том, что вы вскоре сталкиваетесь с проблемами, использующими EF, пытающиеся "сгладить" модели. Я столкнулся с чем-то подобным, когда у меня был класс CommentViewModel
, который выглядел следующим образом:
public class CommentViewModel
{
public string Content { get; set; }
public string DateCreated { get; set; }
}
Следующая проекция запроса EF4 на CommentViewModel
не будет работать, поскольку не может перевести метод ToString() в SQL:
var comments = from c in DbSet where c.PostId == postId
select new CommentViewModel()
{
Content = c.Content,
DateCreated = c.DateCreated.ToShortTimeString()
};
Использование чего-то вроде Automapper - хороший выбор, особенно если у вас много конверсий. Тем не менее, вы также можете создавать свои собственные конвертеры, которые в основном конвертируют вашу модель домена в вашу модель просмотра. В моем случае я создал свои собственные методы расширения для преобразования моей модели домена Comment
в my CommentViewModel
следующим образом:
public static class ViewModelConverters
{
public static CommentViewModel ToCommentViewModel(this Comment comment)
{
return new CommentViewModel()
{
Content = comment.Content,
DateCreated = comment.DateCreated.ToShortDateString()
};
}
public static IEnumerable<CommentViewModel> ToCommentViewModelList(this IEnumerable<Comment> comments)
{
List<CommentViewModel> commentModels = new List<CommentViewModel>(comments.Count());
foreach (var c in comments)
{
commentModels.Add(c.ToCommentViewModel());
}
return commentModels;
}
}
В основном, я выполняю стандартный запрос EF, чтобы вернуть модель домена, а затем использовать методы расширения для преобразования результатов в модель представления. Например, следующие методы иллюстрируют использование:
public Comment GetComment(int commentId)
{
return CommentRepository.GetById(commentId);
}
public CommentViewModel GetCommentViewModel(int commentId)
{
return CommentRepository.GetById(commentId).ToCommentViewModel();
}
public IEnumerable<Comment> GetCommentsForPost(int postId)
{
return CommentRepository.GetCommentsForPost(postId);
}
public IEnumerable<CommentViewModel> GetCommentViewModelsForPost(int postId)
{
return CommentRepository.GetCommentsForPost(postId).ToCommentViewModelList();
}
Ответ 2
Я бы решил вашу проблему, используя инструмент автоматического сопоставления (например, AutoMapper), чтобы выполнить сопоставление для вас. В тех случаях, когда сопоставление легко (например, если все свойства одного класса должны быть сопоставлены с свойствами с тем же именем в другом классе), AutoMapper сможет выполнить всю работу для вас, и вам придется дайте пару строк кода, чтобы отметить, что между ними вообще должна быть карта.
Таким образом, вы можете иметь свои сущности в Domain
, а несколько классов модели представлений в вашем WebUI
и где-нибудь (желательно в WebUI
или подпространстве имен того же самого) определяют карты между ними. Ваши модели просмотра фактически будут DTO, но вам не придется беспокоиться о процессе преобразования между доменом и вашими классами DTO.
Примечание. Я бы настоятельно рекомендовал против предоставить сущности домена прямо в представлениях вашего веб-интерфейса MVC. Вы не хотите, чтобы EF "придерживался" до самого уровня интерфейса, если вы позже захотите использовать что-то другое, кроме EF.
Ответ 3
Говоря о моделях, ViewModels и DTO сбивает с толку, лично я не люблю использовать эти термины. Я предпочитаю говорить о Сущности домена, Доменные службы, Операция ввода/результат (также известный как DTO). Все эти типы находятся в доменном слое. Операции - это поведение объектов и служб. Если вы не создаете чисто CRUD-приложение, уровень представления относится только к типам ввода/результата, а не к объектам. Вам не нужны дополнительные типы ViewModel, это ViewModels (другими словами, модель представления). В представлении отображается перевод результатов операции в HTML, но тот же результат может быть сериализован как XML или JSON. То, что вы используете как ViewModel, является частью домена, а не уровня представления.