Главная страница   /   11.3. Основы AutoMapper (ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

Джеффри Палермо

11.3. Основы AutoMapper

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

Инициализация AutoMapper

Прежде чем использовать AutoMapper, его нужно инициализировать при запуске приложения. Для приложений ASP.NET MVC мы делаем это в Global.asax.cs.

Вот пример класса, который инициализирует AutoMapper.

public class AutoMapperConfiguration
{
	public static void Configure()
	{
		Mapper.Initialize(x => x.AddProfile<ExampleProfile>());
	}
}

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

Профили AutoMapper

Профиль представляет собой набор определений типов преобразования, в том числе правила, которые применяются ко всем сущностям, определенным в профиле. Профили AutoMapper – это классы, которые происходят из класса Profile.

Профили эффективны для группировки наборов сущностей по контексту. Приложение может иметь один профиль для преобразования доменной модели в презентационную модель, и другой профиль для другой цели. Следующий листинг показывает богатый профиль с несколькими указаниями по конфигурации.

Листинг 11-5: Создание примера профиля
public class ExampleProfile : Profile
{
	protected override void Configure()
	{
		ForSourceType<Name>().AddFormatter<NameFormatter>();
		ForSourceType<decimal>().AddFormatExpression(context =>
			((decimal)context.SourceValue).ToString("c"));
		CreateMap<Customer, CustomerInfo>()
			.ForMember(x => x.ShippingAddress, opt =>
			{
				opt.AddFormatter<AddressFormatter>();
			});
	}
}

Строка 1: Наследование от Profile

Строка 6: Применение AddFormatter к исходному типу

Строка 8: Применение встроенного форматирования к исходному типу

Давайте исследуем этот профиль по частям. Во-первых, каждый профиль должен происходить от Profile.

Метод Configure содержит указания по конфигурации. Первое указание по форматированию дает AutoMapper инструкцию использовать NameFormatter всякий раз, когда он преобразует объект Name (мы исследуем NameFormatter подробнее далее в этой главе). Также есть указание, предоставляющее специальное выражение форматирования, которое AutoMapper должен использовать, когда он пытается преобразовать объекты decimal. Это выражение использует стандартную строку форматирования, когда отображает decimal как валюту.

Наконец, инструкция CreateMap указывает AutoMapper, чтобы он планировал преобразовать Customer в CustomerInfo. Вызов метода ForMember сообщает AutoMapper, чтобы он применил AddressFormatter при преобразовании в свойство назначения ShippingAddress.

Остальные свойства CustomerInfo не указаны, так как они преобразовываются по соглашению.

Проверка работоспособности

Опора на соглашение - палка о двух концах. С одной стороны, оно очень кстати устраняет обязательства разработчика указывать преобразование для каждого члена. Но есть опасность того, что свойство будет переименовано. Если элемент-источник переименован, он больше не будет соответствовать определенному элементу назначения, и соглашение будет нарушено. Разработчикам нужно быстрое уведомление, когда происходят подобные изменения. Ошибка выполнения не приемлема.

AutoMapper предоставляет метод, который гарантирует работоспособность конфигурации, проверяя, преобразуется ли каждый элемент назначения в элемент-источник на основе соглашения или конфигурации. Следующий листинг показывает профиль, который не будет работать из-за чьей-то опечатки.

Листинг 11-6: Потенциально опасная опечатка
public class Destination
{
	public string Name { get; set; }
	public string Typo { get; set; }
}
public class Source
{
	public string Name { get; set; }
	public int Number { get; set; }
}
public class BrokenProfile : Profile
{
	protected override void Configure()
	{
		CreateMap<Source, Destination>();
	}
}

Строка 4: Строка должна называться "Number"

Чтобы избежать подобных опечаток, мы можем запустить специальный вспомогательный тест как часть автоматизированного комплексного теста. Этот вспомогательный тест, AutoMapperConfigurationTester, показан в следующем листинге.

Листинг 11-7: Подтверждение правильной конфигурации AutoMapper
[TestFixture]
public class AutoMapperConfigurationTester
{
	[Test]
	public void Should_map_everything()
	{
		AutoMapperConfiguration.Configure();
		Mapper.AssertConfigurationIsValid();
	}
}

Строка 8: Проверяет конфигурацию маппинга

Когда этот тест запускается для неработающего профиля из листинга 11-6, мы получим сообщение о том, что свойство Typo не преобразовано.

Сокращение повторяющегося кода форматирования

Ранее в этой главе мы говорили о применении специальных средств форматирования к преобразованиям элементов. Эти средства форматирования являются реализациями IValueFormatter, интерфейса AutoMapper, который определяет контракт между AutoMapper и нашим пользовательским кодом форматирования:

public interface IValueFormatter
{
	string FormatValue(ResolutionContext context);
}

Наша пользовательская реализация форматирования примет ResolutionContext, который предоставляет значение свойства модели представления и другие метаданные. Мы можем предоставить любые трансформации или преобразования, которые считаем необходимыми, и просто вернуть строку результата.

Чтобы облегчить разработку клиентской части, можно реализовать простой базовый класс. В следующем листинге показан ValueFormatter, включенный в AutoMapper, который извлекает исходное значение из контекста и проверяет его на значение null.

Листинг 11-8: Реализация интерфейса IValueFormatter в классе ValueFormatter
public abstract class ValueFormatter<T> : IValueFormatter
{
	public string FormatValue(ResolutionContext context)
	{
		if (context.SourceValue == null)
			return null;
		if (!(context.SourceValue is T))
		{
			object value = context.SourceValue;
			return value == null ? string.Empty : value.ToString();
		}

		return FormatValueCore((T)context.SourceValue);
	}
	protected abstract string FormatValueCore(T value);
}

Строка 7: Пробует ToString при неправильном типе

Строка 13: Возвращает результат абстрактного метода

Строка 15: Требует наследников для замещения метода

Пользовательское средство форматирования писать просто, так как оно происходит от ValueFormatter. Все, что нам нужно сделать, это реализовать его абстрактный метод FormatValueCore, который получает строго типизированное исходное значения. AutoMapper перехватит все исключения при обращении к null при использовании средств форматирования или при обычном сопоставлении и вместо них вернет пустую строку или значение по умолчанию.

В следующем листинге показан NameFormatter, который мы настроили в листинге 11-5.

Листинг 11-9: Получение NameFormatter для обработки комбинирования свойств
public class NameFormatter : ValueFormatter<Name>
{
	protected override string FormatValueCore(Name value)
	{
		var sb = new StringBuilder();
		if (!string.IsNullOrEmpty(value.First))
		{
			sb.Append(value.First);
		}
		if (!string.IsNullOrEmpty(value.Middle))
		{
			sb.Append(" " + value.Middle);
		}
		if (!string.IsNullOrEmpty(value.Last))
		{
			sb.Append(" " + value.Last);
		}
		if (value.Suffix != null)
		{
			sb.Append(", " + value.Suffix.DisplayName);
		}
		return sb.ToString();
	}
}

Строка 5: Использует StringBuilder для создания результата

Строки 6-9: Применяет базовую логику форматирования

Использование AutoMapper позволяет разработчикам написать этот код один раз и применять его во многих местах, всего лишь объявив его. Когда это средство форматирования настроено так же, как профиль из листинга 11-5, оно будет применяться ко всем элементам-источникам типа Name.

Возвращаемся к представлению

Когда конфигурация завершена, наша разметка сосредоточена только на расположении элементов. Мы заменили рутинную логику из листинга 11-1. Вот окончательное представление.

Листинг 11-10: Окончательная разметка представления
<h2>Customer: @Model.Name</h2>
<div class="customerdetails">
	<p>Status: @Model.Status</p>
	<p>Total Amount Paid: @Model.TotalAmountPaid</p>
	<p>Address: @Model.ShippingAddress</p>
</div>