Главная страница   /   13.1. Знакомство с Autofac (Внедрение зависимостей в .NET

Внедрение зависимостей в .NET

Внедрение зависимостей в .NET

Марк Симан

13.1. Знакомство с Autofac

Из этого раздела вы узнаете, где можно взять Autofac, что вы при этом получите и как начать его использовать. Кроме того, мы рассмотрим универсальные варианты конфигурирования, а также то, как пакетировать настройки конфигурации в повторно используемые компоненты. В таблице 13-1 содержится основополагающая информация, которая, скорее всего, понадобится вам для того, чтобы приступить к работе с Autofac

Таблица 13-1: Краткая информация об Autofac
Вопрос Ответ
Откуда мне его получить?

Перейдите на страницу http://autofac.org и нажмите на соответствующую ссылку в списке рекомендуемых загрузок.

Из Visual Studio 2010 можно получить его посредством NuGet. Имя пакета – Autofac.

Что находится в загруженном файле? Можно загрузить zip-файл, содержащий предварительно скомпилированные бинарные файлы. Кроме того, можно загрузить исходный код и скомпилить его самостоятельно, хотя при этом будет трудно определить, какие изменения к какому релизу относятся. Последняя составляющая номера сборки (например, для используемой в этой главе сборки это 724) соответствует ревизии исходного кода, но для того, чтобы ее определить, понадобится система управления версиями Mercurial.
Какие платформы поддерживаются? Поддерживаются версии .NET 3.5 SP1, .NET 4, Silverlight 3, Silverlight 4. Кроме того, доступны и другие версии, поддерживающие .NET 2.0, 3.0 и Silverlight 2 (для этого во вкладке Download (Загрузить) выберите пункт All Releases (Все релизы)).
Сколько он стоит? Нисколько. Это программное обеспечение с открытым исходным кодом.
Откуда мне получить помощь?

Коммерческую поддержку можно получить от компаний, связанных с разработчиками Autofac. Подробную информацию можно получить на сайте http://code.google.com/p/autofac/wiki/CommercialSupport.

Невзирая на коммерческую поддержку, Autofac все еще остается программным обеспечением с открытым исходным кодом, которое обладает процветающей экосистемой, поэтому, скорее всего (но не гарантированно), помощь можно получить на официальном форуме http://groups.google.com/group/autofac

На какой версии Autofac основана эта глава? 2.4.5.724.

Процесс использования Autofac слегка отличается от процесса использования других DI-контейнеров. Как показывает рисунок 13-2, это более явный процесс, состоящий из двух шагов: сначала мы конфигурируем ContainerBuilder, а затем с помощью него создаем контейнер, который можно использовать для разрешения компонентов.

Рисунок 13-2: При работе с Autofac сначала создается и конфигурируется экземпляр ContainerBuilder. Затем с помощью него создается контейнер, который впоследствии можно использовать для разрешения компонентов. Обратите внимание на то, что последовательность действий больше похожа на работу с контейнерами Castle Windsor или StructureMap: конфигурируем, а затем разрешаем. Тем не менее, концепция разделения понятий при работе с Autofac гораздо понятнее. ContainerBuilder не умеет разрешать компоненты, и поэтому мы не сможем сконфигурировать контейнер.

После прочтения этого раздела вы приобретете полноценное понимание всей сущности паттерна применения Autofac и сможете начать использовать его в сценариях, в которых все компоненты руководствуются должными DI-паттернами, например, Constructor Injection. Давайте начнем с простейшего сценария и посмотрим, как можно разрешать объекты с помощью контейнера Autofac.

Разрешение объектов

Основная услуга, предоставляемая любым DI-контейнером – разрешение компонентов. В этом разделе мы рассмотрим API, которое позволяет разрешать компоненты при помощи Autofac.

Вспоминая обсуждение процесса разрешения компонентов при помощи Castle Windsor и StructureMap, вы, возможно, припомните, что Windsor требует, чтобы перед тем, как стало возможным разрешение компонентов, все соответствующие компоненты должны быть зарегистрированы. В отличие от Castle Windsor StructureMap делает все возможное, чтобы сделать это за нас, когда мы запрашиваем конкретные типы с открытыми конструкторами. Autofac может вести себя и как Castle Windsor, и как StructureMap, хотя по умолчанию его поведение совпадает с поведением Castle Windsor. Перед разрешением компонентов мы должны их зарегистрировать, поэтому одним из самых простых способов применения Autofac является приведенный ниже пример:

var builder = new ContainerBuilder();
builder.RegisterType<SauceBéarnaise>();
var container = builder.Build();
SauceBéarnaise sauce = container.Resolve<SauceBéarnaise>();

Как уже было замечено ранее на рисунке 13-2, для конфигурирования компонентов необходим экземпляр ContainerBuilder. В примере выше регистрируется конкретный класс SauceBéarnaise с помощью builder, чтобы при запросе создания контейнера выходной экземпляр container компоновался классом SauceBéarnaise. Все это дает возможность разрешать класс SauceBéarnaise из контейнера.

Если компонент SauceBéarnaise не был зарегистрирован, то попытка разрешить его приведет к исключению ComponentNotRegisteredException.

Если сравнивать этот самый простейший из всех сценариев с аналогичными фрагментами кода Castle Windsor и StructureMap, то Autofac покажется слегка более подробным. Однако вся эта подробность возникает, главным образом, благодаря дополнительному шагу создания контейнера из ContainerBuilder, поэтому в более крупных и сложных конфигурациях Autofac будет сравним с другими DI-контейнерами.

По умолчанию Autofac требует явно регистрировать все соответствующие компоненты. Такое поведение присуще и Castle Windsor. Если вам необходимо поведение, более похожее на StructureMap, то можно поступить следующим образом:

var builder = new ContainerBuilder();
builder.RegisterSource(
	new AnyConcreteTypeNotAlreadyRegisteredSource());
var container = builder.Build();
SauceBéarnaise sauce = container.Resolve<SauceBéarnaise>();

Единственное отличие от предыдущего примера – отсутствие явной регистрации класса SauceBéarnaise. Вместо этого вы регистрируете IRegistrationSource, называемый AnyConcreteTypeNotAlreadyRegisteredSource. Это название довольно трудно произносить, но оно более или менее отражает функцию AnyConcreteTypeNotAlreadyRegisteredSource: он выступает в роли источника регистраций любого конкретного типа, который ранее не был зарегистрирован. При добавлении AnyConcreteTypeNotAlreadyRegisteredSource не нужно явно добавлять тип SauceBéarnaise, поскольку SauceBéarnaise является конкретным классом с открытым конструктором, а источник регистраций может автоматически обеспечивать его регистрацию.

Источники регистраций

Дополнительная особенность Autofac – способность предоставлять большее количество дополнительных источников регистраций, нежели при прямом использовании API, которое раскрывается с помощью ContainerBuilder. Это механизм расширяемости, который Autofac использует для реализации различных возможностей, но, поскольку он основан на открытом интерфейсе под названием IRegistrationSource, можно использовать его и как механизм расширяемости.

Единственной открытой реализацией IRegistrationSource, которая входит в состав Autofac, является AnyConcreteTypeNotAlreadyRegisteredSource, которую вы уже видели ранее, но в Autofac есть и другие внутренние реализации интерфейса.

Идея, лежащая в основе IRegistrationSource, заключается в том, что реализации могут выступать в роли резервных механизмов или более эвристических источников регистраций компонентов, чем те, которые может обеспечить обычное API. Помимо вышеупомянутых источников конкретных типов использовать IRegistrationSource можно также и для того, чтобы превратить Autofac в автоматический mock-контейнер. Все это выходит за рамки этой книги, и для эффективного использования Autofac нет необходимости более подробно рассматривать IRegistrationSource.

Фактически, регистрация AnyConcreteTypeNotAlreadyRegisteredSource приводит к тому, что контейнер начинает вести себя как StructureMap, а не как Castle Windsor. Теперь контейнер может не только разрешать конкретные типы, обладающие конструкторами по умолчанию, но и автоматически интегрировать тип с другими конкретными зависимостями, не используя при этом явную регистрацию. Кроме того, сразу после того, как было введено слабое связывание, Autofac необходимо сконфигурировать на преобразование абстракций в конкретные типы.

Преобразование абстракций в конкретные типы

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

В приведенном ниже примере выполняется преобразование интерфейса IIngredient в конкретный класс SauceBéarnaise, что позволяет успешно разрешать IIngredient:

var builder = new ContainerBuilder();
builder.RegisterType<SauceBéarnaise>().As<IIngredient>();
var container = builder.Build();
IIngredient ingredient = container.Resolve<IIngredient>();

Для регистрации типов и определения преобразований используется экземпляр ContainerBuilder. Метод RegisterType дает возможность зарегистрировать конкретный тип. Как вы уже видели в первом примере этой главы, можно остановиться и на этом, если вашей целью является только регистрация класса SauceBéarnaise. Кроме того, при помощи метода As можно продолжить пример и определить, каким образом необходимо регистрировать конкретный тип.

Примечание

При использовании Autofac мы начинаем работать с конкретным типом, а затем преобразовываем его в абстракцию. Такая последовательность полностью противоположна работе с большинством других DI-контейнеров: в них абстракция преобразуется к конкретному типу.

Предупреждение

По сравнению с Castle Windsor и StructureMap в Autofac, в сущности, отсутствуют ограничители generic-типа между типами, определенными методами RegisterType и As. А это означает, что можно преобразовывать несовместимые типы. Код будет компилироваться, но во время выполнения, когда ContainerBuilder будет создавать контейнер, возникнет исключение.

В большинстве случаев нам нужно только generic API. Несмотря на то, что generic API, в отличие от других DI-контейнеров, не обеспечивает должную типовую безопасность, это все равно хороший способ конфигурирования контейнера. Однако в некоторых ситуациях необходим и более слабо типизированный способ разрешения сервисов. Это тоже возможно.

Разрешение слабо типизированных сервисов

В некоторых случаях нельзя использовать generic API, поскольку на этапе проектирования может быть неизвестно, какой тип нужен. Все, что у нас есть – экземпляр Type, но, все же, нам может потребоваться и экземпляр этого типа. Пример такой ситуации приведен в разделе 7.2 "Построение ASP.NET MVC приложений", где мы обсуждали ASP.NET MVC класс DefaultControllerFactory. Соответствующий метод приведен ниже:

protected internal virtual IController GetControllerInstance(
	RequestContext requestContext, Type controllerType);

Поскольку в наличии у нас только экземпляр Type, мы не можем использовать дженерики, и должны прибегнуть к слабо типизированному API. В Autofac есть слабо типизированная перегрузка метода Resolve, которая позволяет реализовывать метод GetControllerInstance следующим образом:

return (IController)this.container.Resolve(controllerType);

Слабо типизированная перегрузка метода Resolve позволяет передавать параметр controllerType прямо в Autofac, но при этом необходимо приводить возвращаемое значение к IController.

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

В предыдущем примере this.container – это экземпляр Autofac.IContainer. Для обеспечения возможности разрешать необходимый тип все слабо связанные зависимости необходимо сначала сконфигурировать. Autofac можно конфигурировать несколькими способами. В следующем разделе приведена информация о самых универсальных из этих способов.

Конфигурирование ContainerBuilder

Как уже говорилось в разделе 3.2 "Конфигурирование DI-контейнеров", существует несколько концептуально разных способов конфигурирования DI-контейнера. На рисунке 13-3 представлен обзор возможных вариантов.

Рисунок 13-3: Концептуально разные варианты конфигурирования. Использование кода в качестве конфигурации подразумевает строгую типизированность и имеет тенденцию к явному определению. XML, с другой стороны, предполагает позднее связывание, но тоже склонно к явному определению. Автоматическая регистрация, напротив, полагается на соглашения, которые могут быть и строго типизированными, и более слабо определенными.

Будучи DI-контейнером второго поколения, Autofac первоначально не имел в своей основе XML конфигурации, но позднее его API было заменено на программное API конфигурации подобно некоторым наиболее развитым контейнерам. Конечно, его создавали с заложенной в него возможностью эффективно использовать множество различных источников конфигурации, и XML – это один из возможных вариантов.

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

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

Конфигурация в коде

В разделе 13.1.1 "Разрешение объектов" уже приводился краткий обзор строго типизированного API конфигурации контейнера Autofac. В этом разделе мы рассмотрим его более подробно.

В Autofac в любом типе конфигурации применяется API, раскрываемое классом ContainerBuilder, несмотря на то, что большинство используемых нами методов являются методами расширений. Один из общепринято используемых методов – это метод RegisterType, о котором уже упоминалось ранее:

builder.RegisterType<SauceBéarnaise>().As<IIngredient>();

Как и при работе с Castle Windsor, регистрация класса SauceBéarnaise в виде IIngredient скрывает конкретный класс так, что разрешать класс SauceBéarnaise с помощью этой регистрации больше невозможно. Тем не менее, с этим легко можно справиться с помощью перегрузки метода As, которая позволяет определить, что конкретный тип преобразуется к нескольким зарегистрированным типам:

builder.RegisterType<SauceBéarnaise>()
	.As<SauceBéarnaise, IIngredient>();

Вместо того чтобы регистрировать класс только в виде IIngredient, вы можете зарегистрировать его и как класс, и как интерфейс, который он реализует. Это позволяет контейнеру разрешать запросы и SauceBéarnaise, и IIngredient.

Помимо этого еще один вариант – это связать в цепочку вызовы метода Call:

builder.RegisterType<SauceBéarnaise>()
	.As<SauceBéarnaise>().As<IIngredient>();

Получаем результат, аналогичный только что рассмотренному примеру.

Существует три generic-перегрузки метода As, которые позволяют задать один, два или три типа соответственно. Если необходимо задать большее количество типов, то для этого существует не generic-перегрузка. С помощью нее можно задать любое количество типов.

Подсказка

Если необходимо с помощью метода As задать более трех типов, то следует рассматривать его как smell-эскиз регистрируемого класса. Если он будет реализовывать столь много интерфейсов, то, скорее всего, он нарушит принцип единичной ответственности.

В подлинных приложениях нам всегда приходится преобразовывать более одной абстракции, поэтому приходится конфигурировать несколько преобразований. Делается это при помощи нескольких вызовов метода RegisterType:

builder.RegisterType<SauceBéarnaise>().As<IIngredient>();
builder.RegisterType<Course>().As<ICourse>();

Здесь IIngredient преобразуется в SauceBéarnaise, а ICourse – в Course. Наложение типов не происходит, поэтому вполне очевидно, что будет происходить. Тем не менее, одну и ту же абстракцию можно зарегистрировать несколько раз:

builder.RegisterType<SauceBéarnaise>().As<IIngredient>();
builder.RegisterType<Steak>().As<IIngredient>();

В этом примере IIngredient регистрируется дважды. При разрешении IIngredient получаем экземпляр Steak. Выигрывает последняя регистрация, но предыдущие регистрации тоже не забыты. Autofac правильно обрабатывает составные конфигурации одной и той же абстракции, но к этому мы еще вернемся в разделе 13.3.

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

Автоматическая регистрация

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

Рассмотрим библиотеку, в которой содержится множество реализаций интерфейса IIngredient. Можно выполнять индивидуальную конфигурацию каждого класса, но это приведет к многочисленным схожим вызовам метода RegisterType. Что еще хуже, всякий раз при добавлении новой реализации IIngredient необходимо явно регистрировать ее вместе с ContainerBuilder, чтобы она была доступна для использования. Было бы более продуктивно установить, что все реализации IIngredient, найденные в этой сборке, необходимо регистрировать.

Это можно выполнить с помощью метода расширения RegisterAssemblyTypes. Метод RegisterAssemblyTypes позволяет указывать сборку и конфигурировать все выбранные классы этой сборки посредством одного единственного оператора. Для получения экземпляра Assembly можно использовать представительский класс. В этом примере таким классом является Steak:

builder.RegisterAssemblyTypes(typeof(Steak).Assembly)
	.As<IIngredient>();

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

Примечание

Несмотря на то, что типы возвращаемых значений совпадают, возвращаемый методом RegisterAssemblyTypes интерфейс – это сложный generic-интерфейс. А поскольку большинство используемых нами API реализуются в виде методов расширений, не все методы можно использовать в любой ситуации. Возможность их использования зависит от типа аргументов возвращаемого generic-интерфейса.

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

builder.RegisterAssemblyTypes(typeof(Steak).Assembly)
	.Where(t => t.Name.StartsWith("Sauce"))
	.As<IIngredient>();

При регистрации всех типов сборки для определения критерия отбора можно использовать предикат. Единственное отличие от предыдущего примера – это импликация метода Where, в котором отбираются только те типы, имена которых начинаются со слова "Sauce". Обратите внимание на то, что это тот же самый синтаксис, который использовался для фильтрации типов и в Castle Windsor, и в StructureMap.

Существует множество других методов, позволяющих задавать различные критерии отбора. Метод Where позволяет отфильтровывать только те типы, которые совпадают с предикатом, но есть еще метод Except, который работает совсем по-другому.

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

Тем не менее, иногда бывает необходимо использовать различные соглашения. Предположим, что вместо интерфейсов мы используем абстрактные базовые классы и собираемся зарегистрировать все типы сборки, имена которых заканчиваются на "Policy". Для этих целей существует несколько перегрузок метода As, включая ту, которая в качестве входного параметра принимает Func<Type, Type>:

builder.RegisterAssemblyTypes(typeof(DiscountPolicy).Assembly)
	.Where(t => t.Name.EndsWith("Policy"))
	.As(t => t.BaseType);

Блок кода, заданный для метода As, будет использоваться для всякого типа, имя которого заканчивается на "Policy". Такой подход гарантирует, что все классы, имеющие суффикс "Policy" будут зарегистрированы относительно их базового класса таким образом, что при запросе базового класса контейнер будет разрешать его в тип, преобразованный с помощью этого соглашения.

В рамках Autofac достаточно легко выполнять регистрацию на основании соглашений, при этом используется API, которое практически отражает API, раскрываемое единичным методом RegisterType.

Подсказка

RegisterAssemblyTypes считается множественной формой метода RegisterType.

Метод RegisterAssemblyTypes принимает в качестве параметра массив params, состоящий из экземпляров Assembly, поэтому в одном соглашении можно использовать сколь угодно много сборок. Поиск сборок в папке и применение всех найденных сборок для реализации дополнительной функциональности, при которой возможно добавлять расширения без повторной компиляции основного приложения – это не надуманная идея. Это один из способов реализации позднего связывания. Еще один способ реализации позднего связывания – использование XML конфигурации.

XML конфигурация

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

Подсказка

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

Самый естественный способ использования XML конфигурации – внедрить ее в стандартный конфигурационный файл .NET приложения. Это возможно, но при этом, чтобы изменить конфигурацию Autofac независимо от стандартного .config файла, придется использовать еще и автономный XML-файл. Независимо от выбранного способа используется практически одинаковое API.

Примечание

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

После добавления ссылки на Autofac.Configuration можно отправить Container.Builder запрос на считывание регистраций компонентов из стандартного .config файла следующим образом:

builder.RegisterModule(new ConfigurationSettingsReader());

Детальное обсуждение модулей Autofac приведено в разделе 13.1.3, а пока все, что вам необходимо знать – это то, что ConfigurationSettingsReader – это класс, который отвечает за объединение XML конфигурации с остальными регистрациями, применяемыми к Container.Builder. При использовании конструктора по умолчанию происходит автоматическое считывание из раздела конфигурации стандартного конфигурационного файла приложения, но с помощью еще одной перегрузки можно указать и другой XML-файл, из которого будет считываться конфигурация.

Примечание

К сожалению, API, которое позволило бы нам считывать XML из других источников, к примеру, из потоков или узлов, не существует.

Чтобы разместить конфигурацию Autofac в конфигурационном файле, для начала необходимо добавить раздел конфигурации с помощью стандартного .NET API, предназначенного для определения пользовательских разделов конфигурации:

<configSections>
	<section name="autofac"
					type="Autofac.Configuration.SectionHandler,
					➥Autofac.Configuration"/>
</configSections>

Этот код позволяет добавить в конфигурационный файл раздел конфигурации autofac. Ниже приведен простой пример преобразования интерфейса IIngredient в класс Steak:

<autofac defaultAssembly="Ploeh.Samples.MenuModel">
	<components>
		<component type="Ploeh.Samples.MenuModel.Steak"
							service="Ploeh.Samples.MenuModel.IIngredient" />
	</components>
</autofac>

В элемент components можно добавить сколько угодно элементов component. В каждом элементе с помощью атрибута type необходимо задать конкретный тип. Это единственный обязательный атрибут, а для преобразования класса Steak в IIngredient можно использовать также необязательный атрибут service. Тип указывается с помощью полностью квалифицированного имени типа, но, если тип задан в сборке по умолчанию, то имя сборки можно опустить. Атрибут defaultAssembly является необязательным, но при этом довольно значительной возможностью, которая в случае наличия в одной и той же сборке множества типов позволяет избавиться от большого количества определений типов.

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

Подсказка

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

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

Пакетирование конфигурации

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

При работе с Autofac конфигурацию можно упаковывать в модули. Модуль – это класс, реализующий интерфейс IModule, но в большинстве случаев проще всего выполнить наследование от абстрактного класса Module. На рисунке 13-4 продемонстрирована иерархия типов.

Рисунок 13-4: Повторно используемые конфигурации можно упаковывать в реализации интерфейса IModule. Самый простой способ реализации IModule – наследование от абстрактного класса Module, что и делает IngredientModule.

Все, что делается в дальнейшем, можно выполнить и внутри модуля. В следующем листинге представлен модуль, который регистрирует все реализации IIngredient.

Листинг 13-1: Реализация модуля в Autofac
public class IngredientModule : Module
{
	protected override void Load(ContainerBuilder builder)
	{
		var a = typeof(Steak).Assembly;
		builder.RegisterAssemblyTypes(a).As<IIngredient>();
	}
}

IngredientModule наследуется от абстрактного класса Module и переопределяет метод Load этого класса. Метод Load – это метод, определенный классом Module для облегчения реализации интерфейса IModule. Посредством метода Load вы получаете экземпляр ContainerBuilder, который можно использовать для регистрации компонентов таким же самым способом, что и без применения Module. Знание способов использования API ContainerBuilder упрощает реализацию Module.

Чтобы применить Module, можно воспользоваться вызовом одной из перегрузок RegisterModule. Когда Module имеет конструктор по умолчанию, можно использовать сокращенную generic-версию:

builder.RegisterModule<IngredientModule>();

Кроме того, существует перегрузка, которая позволяет создать экземпляр, используемый в ситуациях, в которых Module требуется создавать вручную:

builder.RegisterModule(new IngredientModule());

Конфигурировать модули можно и в XML:

<modules>
	<module
		type="Ploeh.Samples.Menu.Autofac.IngredientModule,
		➥Ploeh.Samples.Menu.Autofac" />
</modules>

Эти три примера функционально эквивалентны.

Подсказка

Модули Autofac позволяют пакетировать и структурировать код конфигурации контейнера. Используйте модули вместо однострочной конфигурации, и ваша Composition Root станет более читабельной.

С помощью модулей конфигурацию контейнера Autofac можно выполнять любым из следующих способов: осуществлять конфигурацию в коде, использовать автоматическую регистрацию или XML. Кроме того, можно сочетать между собой указанные подходы. После завершения конфигурации контейнера можно отправлять запросы на разрешение сервисов.

Раздел 13.1 "Знакомство с Autofac" познакомил нас с DI-контейнером Autofac и продемонстрировал фундаментальные принципы: как сконфигурировать ContainerBuilder и впоследствии использовать созданный контейнер для разрешения сервисов. Разрешение сервисов с легкостью выполняется посредством единичного вызова метода Resolve, поэтому вся сложность заключается в конфигурировании контейнера. Конфигурировать контейнер можно несколькими способами, включая императивный код и XML. До настоящего момента мы рассматривали только самое основное API, а более продвинутые вопросы в этом разделе еще не охватывались. Одна из самых важных проблем – управление жизненным циклом компонентов.