Реализация .ToViewModel () с использованием AutoMapper - PullRequest
3 голосов
/ 01 июля 2011

Я пытаюсь облегчить использование AutoMapper в своем проекте, реализовав метод расширения .ToViewModel(). По сути, это будет просто оболочка для стандартного вызова, но меня часто раздражает то, сколько я должен набирать каждый раз, когда хочу отобразить что-то. Сравните два:

var viewModel = Mapper.Map<DomainEntityType, ViewModelType>(entity);
// or...
var viewModel = entity.ToViewModel();

Я чувствую, что номер два был бы приятен =) Я позволил всем моим сущностям расширяться IEntity, и все view-модели (которые соответствуют сущности) расширяются IViewModel<IEntity>, и написал следующий метод расширения:

public static IViewModel<TEntity> ToViewModel<TEntity>(this TEntity entity) 
    where TEntity : IEntity
{
    return Mapper.Map<TEntity, IViewModel<TEntity>>(entity);
}

Однако я не могу заставить эту муху.

Следующий тест NUnit делает попытку проверить это (хотя я не уверен, действительно ли Assert.AreEqual проверяет то, что я хочу - требует ли оно ссылочного равенства? Если да, то как я могу проверить, «эквивалентны»?). Сбой теста с сообщением

Expected: &ltCastle.Proxies.IViewModel`1Proxy>
But was:  <Castle.Proxies.IViewModel`1Proxy>
[Test]
public void DomainEntityTypeMapsViewModelType()
{
    var entity = new DomainEntityType();
    var oldskool = Mapper.Map<DomainEntityType, IViewModel<Entity>>(entity);
    var extension = inspectionObject.ToViewModel();

    Assert.AreEqual(oldskool, extension);
}

За исключением того факта, что мой тест может проверять не то, упускаю ли я что-то фундаментальное о том, как работает AutoMapper? Сможет ли когда-нибудь AutoMapper правильно сопоставить что-либо с интерфейсом? (У меня есть класс EntityViewModel, который реализует ViewModel<Entity>, но я не сказал AutoMapper специально об этом ...)

1 Ответ

2 голосов
/ 13 июля 2011

Мне наконец удалось сделать это в общем виде. Это потребовало некоторой настройки моих объектов, но вот что я сделал:

С тех пор большинство моих сущностей наследуется от Entity<TId> (обычно Entity<int>, но идентификатор может потенциально быть другого типа данных, например, GUID), который, в свою очередь, реализует интерфейс IEntity<TId> (с одним свойство public TId ID {get;}). Те немногие объекты, которые не наследуют Entity<TId>, по крайней мере, реализуют IEntity<TId>.

Я создал новый класс, который переопределяет Equals и GetHashcode:

public class ViewModel<TEntity, TId> : IEntity<TId>
    where TEntity : IEntity<TId>
{
    public TId ID { get; set; }

    public override bool Equals(object obj)
    {
        var viewModel = obj as ViewModel<TEntity, TId>;
        return viewModel != null && Equals(viewModel);
    }

    public bool Equals(ViewModel<TEntity, TId> other)
    {
        return ID.Equals(other.ID);
    }

    public override int GetHashCode()
    {
        // Not only returning ID.GetHashCode() in case I want to add more
        // properties later...
        var hash = 7;
        hash = (hash * 17) + ID.GetHashCode();

        return hash;
    }
}

Теперь все мои view-модели (и editmodels) наследуются от этого класса:

public class EntityViewModel : ViewModel<EntityType, int>
{
    // data properties
}

Затем я могу расширить свои сущности и модели представления следующими методами расширения:

public static TViewModel To<TViewModel>(this IEntity entity) where TViewModel : class
{
    return Mapper.Map(entity, entity.GetType(), typeof(TViewModel)) as TViewModel;
}

public static TEntity ToEntity<TEntity, TId>(this ViewModel<TEntity, TId> viewmodel) where TEntity : class, IEntity<TId>
{
    return Mapper.Map(viewmodel, viewmodel.GetType(), typeof(TEntity)) as TEntity;
}

Ключевой концепцией этой работы было отказаться от общих перегрузок .Map, поскольку я не знал точных типов, которые я хотел отображать во время компиляции и из нее.

Теперь я могу перемещаться между типами, используя следующий синтаксис:

var entity = new EntityType();
var viewmodel = entity.To<EntityViewModel>();

var backagain = viewmodel.ToEntity();

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

Вы заметите, что существует не универсальная версия IEntity - это просто пустой интерфейс, от которого наследуется IEntity<T>. Это может показаться скучным, но есть простая причина не использовать универсальную версию: если вы это сделаете, вам также придется указать ее в качестве аргумента типа для метода расширения. В итоге вы получите entity.To<EntityViewModel,int>() вместо приведенного выше синтаксиса, поскольку вывод аргументов типа является делом "все или ничего".

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...