Проекция с использованием контекстных значений в AutoMapper
В настоящее время я оцениваю, может ли AutoMapper принести пользу нашему проекту. Я работаю над RESTful Web API с использованием ASP.NET Web API, и одна из вещей, которые я должен вернуть, - это ресурс, содержащий ссылки. Рассмотрим этот упрощенный пример, используя следующий объект домена:
public class Customer
{
public string Name { get; set; }
}
Мне нужно отобразить это в объект ресурса, вроде DTO, но с добавленными свойствами для облегчения REST. Вот как может выглядеть мой объект ресурса:
public class CustomerResource
{
public string Name { get; set; }
public Dictionary<string, string> Links { get; set; }
}
Свойство Links должно содержать ссылки на связанные ресурсы. Прямо сейчас я мог бы построить их, используя следующий подход:
public IEnumerable<CustomerResource> Get()
{
Func<Customer, CustomerResource> map = customer =>
new CustomerResource
{
Name = customer.Name,
Links = new Dictionary<string, string>()
{
{"self", Url.Link("DefaultApi", new { controller = "Customers", name = customer.Name })}
}
}
var customers = Repository.GetAll();
return customers.Select(map);
}
... но это довольно утомительно, и у меня много вложенных ресурсов и тому подобное. Проблема, которую я вижу, состоит в том, что я не могу использовать AutoMapper, потому что он не позволяет мне предоставлять определенные вещи, необходимые во время проецирования, которые ограничены областью, где выполняется операция отображения. В этом случае свойство Url ApiController предоставляет экземпляр UrlHelper, который мне нужен для создания ссылок для меня, но могут быть и другие случаи.
Как бы вы решили эту головоломку?
PS Я набрал этот код специально для этого вопроса, и он скомпилирован в вашей голове, но может не сработать в вашей любимой IDE.
2 ответа
Это не очень хорошее решение, но после прочтения документации кажется, что ее нет... В настоящее время мы добавляем контекстную информацию, отображая Tuple<TDomainType, TContextStuff>
в TDataTransfer
, Так что в вашем случае вы бы Mapper.CreateMap<Tuple<Customer, Controller>, CustomerResource>
,
Не красиво, но это работает.
Я хотел бы взглянуть на использование Custom Type Converter. Преобразователь типов может иметь контекстную информацию, внедренную через контейнер IOC. Или, поскольку преобразователь создается во время конфигурирования, он может иметь ссылку на фабрику, которая будет возвращать контекстную информацию при каждом запуске преобразователя типа.
Простой пример
Вы могли бы определить интерфейс для получения вашего текущего "контекста" (что это означает, зависит от того, что вы делаете и как вы реализуете вещи, поэтому для этого примера я просто буду использовать текущий HttpContext, который дает вам доступ к Session, Server, Items, так далее...):
public interface IContextFactory
{
HttpContext GetContext();
}
И реализация просто:
public class WebContextFactory : IContextFactory
{
public HttpContext GetContext()
{
return HttpContext.Current;
}
}
Ваш пользовательский преобразователь типов может взять экземпляр IContextFactory из вашего контейнера IOC, и каждый раз, когда выполняется сопоставление, вы можете вызывать GetContext(), чтобы получить контекст для текущего запроса.
Доступ к свойству URL
UrlHelper происходит от объекта Request, прикрепленного к контексту текущего контроллера. К сожалению, это не доступно в HttpContext. Однако вы можете переопределить метод Initialize на вашем ApiController и сохранить controllerContext в коллекции HttpContext.Items:
protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
HttpContext.Current.Items["controllerContext"] = controllerContext;
base.Initialize(controllerContext);
}
Вы можете получить доступ к этому из текущего HttpContext:
var helper = ((HttpControllerContext) HttpContext.Current.Items["controllerContext"]).Request.GetUrlHelper();
Я не уверен, что это лучшее решение, но оно может помочь вам получить экземпляр UrlHelper в вашем устройстве отображения пользовательских типов.