Блог был удален - вот архив Bing сообщения @ DannyDouglass
Упрощение использования данных XML с помощью AutoMapper и Linq-to-Xml
Недавно я столкнулся с рабочим сценарием, который требовал ручного использования нескольких веб-сервисов SOAP, что, я уверен, вы можете себе представить, было довольно монотонным. Сотрудник (Сет Карни) и я попробовали несколько разных подходов, но мы, наконец, остановились на решении, которое упростило использование xml и в конечном итоге сделало код более тестируемым. Это решение было сосредоточено на использовании AutoMapper, инструмента сопоставления объектов с открытым исходным кодом, для создания связи между XElements (http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.aspx), возвращаемыми в сообщениях SOAP и созданными нами пользовательскими контрактами, - способом повторного использования.
Я собрал короткую демонстрацию, которая показывает, как вы можете использовать тот же подход для использования и отображения общедоступной временной шкалы Twitter (http://api.twitter.com/1/statuses/public_timeline.xml) (с использованием типа ответа API Xml).
Примечание. Исходный код для следующего примера можно найти на моей странице GitHub: https://github.com/DannyDouglass/AutoMapperXmlMappingDemo
- Получение настроек проекта
После создания базового проекта MVC3 (загрузка бета-версии) и связанного с ним тестового проекта первым шагом было установить пакет AutoMapper. Я использовал NuGet, недавно объявленную Microsoft систему управления пакетами, для установки любых зависимостей с открытым исходным кодом. Следующая команда была всем, что было необходимо для настройки AutoMapper в моем проекте MVC3 (подробнее о NuGet читайте здесь (http://weblogs.asp.net/scottgu/archive/2010/10/06/announcing-nupack-asp-net-mvc-3-beta-and-webmatrix-beta-2.aspx) и здесь (http://weblogs.asp.net/scottgu/archive/2010/10/06/announcing-nupack-asp-net-mvc-3-beta-and-webmatrix-beta-2.aspx)):
)
PM> add-package AutoMapper
- Создание сопоставления
С установленным AutoMapper я готов приступить к созданию компонентов, необходимых для отображения xml на объект. Первым шагом является создание быстрого контракта, используемого в моем приложении для представления объекта Tweet:
public interface ITweetContract
{
ulong Id { get; set; }
string Name { get; set; }
string UserName { get; set; }
string Body { get; set; }
string ProfileImageUrl { get; set; }
string Created { get; set; }
}
Здесь нет ничего сумасшедшего - просто простая сущность. Это все поля, которые предоставляются в ответе от API Twitter с использованием другого имени для некоторых полей. В простых случаях, когда исходный и целевой объекты имеют одинаковые имена, вы можете очень быстро настроить карту, используя следующий синтаксис:
Mapper.CreateMap<SourceObj, DestinationObj>();
Однако AutoMapper не поддерживает Xml по умолчанию. Я должен указать поля, которые я буду отображать. Используя Fluent API в AutoMapper, я могу связать свои полевые отображения. Взгляните на один пример поля, отображенного в моем примере - тело твита:
Mapper.CreateMap<XElement, ITweetContract>()
.ForMember(
dest => dest.Body,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("text")))
Поначалу это может показаться сложным, но на самом деле все, что здесь происходит, это то, что мы предоставляем AutoMapper подробную информацию о том, какое значение использовать в моем исходном объекте и как сопоставить его со свойством целевого объекта. Есть одна конкретная строка, на которой я хотел бы остановиться в приведенном выше отображении поля Body:
options => options.ResolveUsing<XElementResolver<ulong>>()
.FromMember(source => source.Element("id")))
XElementResolver - это пользовательский преобразователь значений (http://automapper.codeplex.com/wikipage?title=Custom%20Value%20Resolvers), который придумал Сет для обработки синтаксического объекта-источника XmlElement, чтобы получить строго типизированное значение для использования в отображении. момент, но прежде чем мы продолжим, взглянем на мое полное отображение:
Mapper.CreateMap<XElement, ITweetContract>()
.ForMember(
dest => dest.Id,
options => options.ResolveUsing<XElementResolver<ulong>>()
.FromMember(source => source.Element("id")))
.ForMember(
dest => dest.Name,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("user")
.Descendants("name").Single()))
.ForMember(
dest => dest.UserName,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("user")
.Descendants("screen_name").Single()))
.ForMember(
dest => dest.Body,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("text")))
.ForMember(
dest => dest.ProfileImageUrl,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("user")
.Descendants("profile_image_url").Single()))
.ForMember(
dest => dest.Created,
options => options.ResolveUsing<XElementResolver<string>>()
.FromMember(source => source.Element("created_at")));
- Общий XElementResolver
Этот пользовательский преобразователь значений является реальным ключом, который позволил этим картам XElement-to-Contract работать в исходном решении. Я повторно использовал этот преобразователь в этом примере, как мы видели выше. Это было все, что было необходимо для создания пользовательского класса распознавателя:
public class XElementResolver<T> : ValueResolver<XElement, T>
{
protected override T ResolveCore(XElement source)
{
if (source == null || string.IsNullOrEmpty(source.Value))
return default(T);
return (T)Convert.ChangeType(source.Value, typeof(T));
}
}
Этот универсальный XElementResolver позволяет легко передавать тип значения, полученного в нашем отображении выше. Например, следующий синтаксис используется для строгого ввода значения, извлеченного из XmlElement, в объявлении .ForMember () поля Id выше:
ResolveUsing<XElementResolver<ulong>>()
После того, как мое отображение полностью настроено и создано, я готов вызвать API Twitter и использовать AutoMapper для отображения этой последней общедоступной временной шкалы.
- Соединение частей
Я создал простой класс, отвечающий за получение ответа API Twitter:
public class TwitterTimelineRetriever
{
private readonly XDocument _twitterTimelineXml;
public TwitterTimelineRetriever()
{
_twitterTimelineXml = XDocument
.Load("http://api.twitter.com/1/statuses/public_timeline.xml");
}
public IEnumerable<ITweetContract> GetPublicTimeline(int numberOfTweets)
{
var tweets = _twitterTimelineXml.Descendants("status")
.Take(numberOfTweets);
return tweets.Select(Mapper.Map<XElement, ITweetContract>).ToList();
}
}
GetPublicTМетод imeline - это простой метод, возвращающий, как вы уже догадались, публичную временную шкалу Twitter, используя карту, которую мы создали ранее:
return tweets.Select(Mapper.Map<XElement, ITweetContract>).ToList();
В HomeController моего MVC3-сайта я могу быстро вызвать метод поиска, запросив последние 10 результатов:
public class HomeController : Controller
{
private TwitterTimelineRetriever _twitterTimelineRetriever;
public ActionResult Index()
{
_twitterTimelineRetriever = new TwitterTimelineRetriever();
ViewModel.Message = "Twitter Public Timeline";
return View(_twitterTimelineRetriever.GetPublicTimeline(10));
}
}
И, наконец, после небольшого форматирования в моем View с использованием нового движка Razor View от Microsoft у меня отображается общедоступная временная шкала!