Я создаю CMS как приложение.
Например, моя страница BlogPost содержит несколько областей виджетов. Каждый виджет содержит серию "связанных" сообщений в блоге.
![typical widget](https://i.stack.imgur.com/DxEVx.png)
Все мои представления являются чисто презентационными, я строю 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 не понимает этого. И я не знаю, как, если есть другой способ сделать это.
Есть идеи?