Automapper: хотите повторно использовать код через сопоставления интерфейса - PullRequest
2 голосов
/ 01 февраля 2012

Я создаю CMS как приложение.

Например, моя страница BlogPost содержит несколько областей виджетов. Каждый виджет содержит серию "связанных" сообщений в блоге.

typical widget

Все мои представления являются чисто презентационными, я строю URL, конвертирую дату и время в целые строки в моем слое обслуживания. Я считаю, что этот подход легче поддерживать, так как представления имеют логику нуля. Вся логика объединена в резольверы, преобразователи и пользовательскую логику преобразования AutoMapper.

Итак, давайте подойдем ближе к проблеме. Чтобы создать URL, мне нужно 2 параметра: BlogId и BlogSlug, мои URL выглядят как b / {id} / {slug} .html. Я очень доволен этим.

В моем CSM я использую так называемые «исходные модели», модель, которая является не моделью представления, а ее промежуточным представлением. Почему я должен прибегать к таким злым решениям? Что ж, давайте посмотрим, как типичный код поиска данных может выглядеть в моем проекте:

.Select(x => 
{
    Id = x.Id,
    BlogId = x.Blog.Id,
    BlogSlug = x.Blog.Slug,

    // Here is the trap, LINQ provider will throw an exception, since he doesn't know how to translate function into expression
    BlogUrl = Url.Action("RenderPost", "BlogController", new { Id = x.Blog.Id, slug = x.Blog.Slug }) 
}

Так что это не вариант. К счастью, мы можем сделать это

.Select(x => new
{
    Id = x.Id,
    BlogId = x.Blog.Id,
    BlogSlug = x.Blog.Slug
}
.ToList()
.Select(x => new
{
    // This works
    BlogUrl = Url.Action("RenderPost", "BlogController", new { Id = x.BlogId, slug = x.BlogSlug })
}

Копировать, вставить этот материал в каждый метод действия, который отображает разные части "интересного блога" (они имеют различное визуальное представление, а также не могут использовать одну и ту же модель представления)? Не очень хороший способ, поэтому я нашел решение. Я создал «исходную модель», поэтому код будет

.Select(x => new BlogPostSourceViewModel
{
    Id = x.Id,
    BlogId = x.Blog.Id,
    BlogSlug = x.Blog.Slug
}
.ToList()
.Select(x => x.ToBlogPostViewModel())  // Extension method { return Mapper.Map<>() }
.ToList();

Это, конечно, выглядит лучше, но у меня есть много разных моделей, таких как BlogPostSourceViewModel, BlogAuthorSourceViewModel, BlogCommentSourceViewModel. Им всем нужна эта логика построения ссылок. Хорошо, я извлекаю необходимые исходные данные (BlogId, BlogSlug) в интерфейс

BlogPostSourceViewModel : IBlogPostUrl
BlogAuthorSourceViewModel: IBlogPostUrl
BlogCommentSourceViewModel : IBlogPostUrl

Тогда я определяю отображения

Mapper.CreateMap<BlogPostSourceViewModel, BlogPostViewModel>
    .ForMember(dest => dest.BlogUrl, opt => opt.ResolveUsing<BlogPostUrlResolver>())
Mapper.CreateMap<BlogAuthorSourceViewModel, BlogAuthorViewModel>
    .ForMember(dest => dest.BlogUrl, opt => opt.ResolveUsing<BlogPostUrlResolver>())
Mapper.CreateMap<BlogCommentSourceViewModel, BlogCommentViewModel>
    .ForMember(dest => dest.BlogUrl, opt => opt.ResolveUsing<BlogPostUrlResolver>())

Резольвер:

BlogPostUrlResolver : ValueResolver<IBlogPostUrl, String>
// Here goes the url building logic

Как вы видите, чем больше у меня моделей, которым нужен URL-адрес блога, тем больше идентичных сопоставлений я должен добавить. Пока это нормально, но по мере роста проекта это будет болезненно. В идеале я бы хотел, чтобы это было так:

Mapper.CreateMap<IBlogPostUrl, SomeOtherInterfaceWithBlogUrlAsString>
    .ForMember(dest => dest.BlogUrl, opt => opt.ResolveUsing<BlogPostUrlResolver>())

но Automapper не понимает этого. И я не знаю, как, если есть другой способ сделать это.

Есть идеи?

1 Ответ

0 голосов
/ 02 марта 2012

Если я правильно понял вопрос ...

Чтобы использовать возможности отображения AutoMapper во всех ваших классах, вы можете предоставить базовый класс, который будет использовать Generics:

public abstract class Base<Entity, ViewModel>   
    where Entity : EntityObject         
    where ViewModel : BaseViewModel     
    {
        // you will call this method from your operations class using base
        public SomeViewModel GetData()
        {
            public Entity entityObject = db.Entity.SingleOrDefault();
            public ViewModel yourViewModelName = base.Map(entityObject);

            return yourViewModelName;
        }
        ....
        // this will be defined only once for each mapping direction
        // ie. there will be multiple of these.
        public static Entity Map(ViewModel typeViewModel)
        {
            try
            {
                Mapper.CreateMap<ViewModel, Entity>();
                Entity t = Mapper.Map<ViewModel, Entity>(typeViewModel);

                return t;
            }
            catch (Exception exc)
            {
                throw exc;
            }
        }
    }

// this will be your class for database operations on specific Entity
// that inherits generic base, with its AutoMapping setup.
public class DataBaseOperationsClass : Base<SomeEntity, SomeViewModel>
{
    public SomeViewModel Get()
    { 
        return base.GetData();
    }
}


Надеюсь, это поможет!

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