Шаблоны для отображения данных между моделями домена

Это обычная вещь, которую мне нужно делать в последнее время, и я искал любые общие шаблоны, чтобы сделать это немного легче.

Основной смысл всего этого в том, что у меня есть некоторые модели данных, которые смоделированы для удовлетворения ORM и просто выполняют операции CRUD для объектов. Эти модели в настоящее время открыты через репозитории/фабрики (в зависимости от того, являются ли их C или RUD).

Затем у меня есть модель представления, которая немного читаема и посыпана проблемами пользовательского интерфейса, такими как данные проверки и сопоставления между представлением (это сценарий ASP.MVC, но эта ситуация может быть абстрагирована в большинстве ситуаций).

Итак, скажем, я перехожу к localhost/user/1, который должен пойти и получить мне пользователя с Id 1 в БД, а затем отобразить его в пользовательском интерфейсе. В конечном итоге это должно вывести данные из области данных, а затем отобразить их в модели ui для показа.

Вот пример сценария:

public class OrmUser
{
    public int Id {get;set;}
    public string Name {get;set;}
    public IList<Permission> Permissions {get;set;}
}

public class UiUser
{
    [Required]
    public int Id {get;set;}
    [Required]
    public string Name {get;set;}
    public bool IsUserAdmin {get;set;}
}

public class UserMapper : IMapper<UiUser>
{
    public UiUser Get(int id)
    {
        var ormUser = UserRepository.Get(id);
        var uiUser = new UiUser
        {
            Id = ormUser.Id,
            Name = ormUser.Name,
            IsUserAdmin = IsUserAdmin(ormUser.Permissions)
        }
    }

    private bool IsUserAdmin(IList<Permission> permissions)
    {
        return permissions.SomeLinq(ToFindIfTheyAreAnAdmin);
    }
}

Это простой пример, но показывает, как модель данных содержит много одинаковой информации, но в этом вопросе вы не заботитесь обо всей информации, просто ее подмножестве. Таким образом, имея сопоставитель, вы можете абстрагировать не только отображение, но и связь с доменом данных, однако вам нужно написать класс сопоставления для каждого типа, и вышеупомянутое предполагает, что это одностороннее сопоставление, а не одностороннее отображение, которое потребуется еще немного кода.

Итак, как вы все собираетесь выполнять это сопоставление? Поскольку в настоящее время я только что писал матчи абстракции, которые в основном позволяют слою пользовательского интерфейса запускать запрос и возвращать модель представления, абстрагировать репозитории и копировать данные из одной модели в другую, и она просто чувствует, что должно быть лучше способ сделать это.

Ответ 1

В .net вы должны, вероятно, взглянуть на Automapper

https://github.com/AutoMapper/AutoMapper

Это, по сути, позволяет:

CreateMap<Domain.Customer, ViewModel.Customer>()

И вы можете затем сопоставить между ними, сказав:

var vmCustomer = Mapper.Map<Domain.Customer, ViewModel.Customer>(domainCustomer);

Скорее всего, это сэкономит вам тонну кода плиты котла.

Ответ 2

Да. AutoMapper - это инструмент для вас. Есть тонна статьи об этом, но идите к источнику - Джимми Богарду. Он мастерский ум за AutoMapper.

Посмотрите на эту статью в блоге http://lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models/

В настоящее время AutoMapper поддерживает MVC очень эффективным способом, при котором вы можете украсить свои классы атрибутами, на которых должно выполняться сопоставление.

/С наилучшими пожеланиями Магнус

Ответ 3

В этом конкретном случае я мог бы просто написать метод расширения для пользователя на моем уровне представления.

public static class UserPresentationExtensions{
 public bool IsAdmin(this User user){
  return permissions.SomeLinq(ToFindIfTheyAreAnAdmin);
 }
}

Вид будет выглядеть примерно так:

@model User
<fieldset>
 <legend>User: @Model.Name</legend>
 @if(user.IsAdmin()){
  User is an admin
 }
</fieldset>

Чтобы избежать неоднократного импорта пространства имен, для этого используйте web.config:

<configuration>
 <system.web.webPages.razor>
  <pages pageBaseType="System.Web.Mvc.WebViewPage">
   <namespaces>
    <add namespace="MyProject.PresentationExtensions" />
   ...

У вас есть точка, однако, скажем, вы хотите добавить валидацию к вашим моделям, поэтому вы решили добавить атрибут [Обязательный] к свойству Name. Хотя тогда, если вы используете общую модель, то ваш уровень данных должен знать об аннотации, и если ваш уровень данных должен иметь некоторые атрибуты, ваш уровень пользовательского интерфейса должен знать о них. В самых простых ситуациях вы правы, иногда проще просто иметь 1 модель, которая является правдой, и просто получить доступ к ней по-другому, но для большинства сложных проектов вы столкнетесь с обеими сторонами, чтобы попытаться сохранить несколько строк кода.

Я сказал - в этом конкретном случае. Этот подход применяется только в том случае, если сопоставление действительно не стоит, и есть только однонаправленная связь (вам просто нужно ее отобразить).

Когда дело доходит до получения сообщений, оно становится немного другим. В большинстве случаев подход "один размер подходит всем" будет применять так называемый принцип Thunderdome. То есть:

Все методы контроллера берут в один объект ViewModel (или в некоторых случаях нулевые объекты) и возвращают один объект ViewModel (один объект входит, один объект уходит).

Однако нередко я предпочитаю использовать метод extension/html helper и просто передавать аргументы в действие следующим образом:

public void BatheCat(int id /* cat id */, int bathId, string shampoo){
 ...
}

Если счетчик параметров выходит из-под контроля (я не беспокоюсь, когда он равен <= 3), я просто инкапсулирую они (вот пример из моего проекта).