Внедрение зависимостей в. Внедрение зависимостей в .NET. Руководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
Скачать 5.66 Mb.
|
Services (IIS)? Для этого требуется, чтобы сервис запускался в Single InstanceContextM ode, который по разным причинам не желателен. Отличная новость – сообщение об исключении просто вводит нас в заблуждение. Существуют другие способы разрешения внедрения через конструктор в рамках WCF. Расширяемость WCF W CF обладает множеством возможностей для расширяемости, но когда дело доходит до механизма внедрения зависимостей, нам всего лишь необходимо иметь представление об интерфейсе IInstanceProvider и о поведениях контрактов. Поведение контракт а – это шов в W CF, который позволяет нам изменять то, как ведет себя данный контракт (в данном случае – сервис). IInstanceProvider – это интерфейс, который определяет, как создаются экземпляр ы сервиса (и высвобождаются). Ниже приведено определение интерфейса во всей его красе: public interface IInstanceProvider { object GetInstance(InstanceContext instanceContext); object GetInstance(InstanceContext instanceContext, Message message); void ReleaseInstance(InstanceContext instanceContext, object instance); } 249 Две перегрузки GetInstance ответственны за создание соответствующего экземпляра сервиса, а ReleaseInstance при необходимости предоставляет возможность для очистки. Реализация по умолчанию выполняет поиск конструктора по умолчанию для типа сервиса, но мы можем заменить ее какой-либо другой реализацией, использующей механизм внедрения зависимостей. Рисунок 7-7 иллюстрирует весь поток при получении размещенным сервисом сообщения. Рисунок 7-7: Когда в сервисную операцию поступает сообщение (запрос), W CF определяет, какой тип CLR реализует сервис. WCF просит ServiceHostFactory создать соответствующий ServiceHost , который может разместить запрашиваемый сервис. ServiceHost выполняет свою часть работы, применяя поведения и создавая запрашиваемый экземпляр. Когда мы размещаем WCF сервис в IIS, ServiceHostFactory обязателен, несмотря на то, что, если мы явным образом не определим альтернативу, будет использоваться реализация по умолчанию. Если мы размещаем сервис вручную, ServiceHostFactory все еще может быть полезной, но не является необходимой, потому что мы можем создать соответствующий ServiceHost , напрямую в коде. Когда ServiceHost применяет поведения, он собирает их, по крайней мере, из трех различных мест перед тем, как их объединить: Атрибуты Файл .config Объекты, хранящиеся в оперативной памяти Несмотря на то, что мы можем определить поведения в атрибутах, это не очень заманчивая для использования стратегия в тех случаях, когда дело касается механизма внедрения зависимостей, поскольку это означает, что мы компилируем в коде конкретную стратегию создания с конкретными зависимостями. Конечный результат почти такой же, как если бы у нас прямо в сервисе присутствовали жестко закодированные зависимости, только гораздо более изощрённее. Конфигурационный файл может казаться максимально гибким, но это не так, потому что он не позволяет нам обязательно сконфигурировать зависимости, если мы хотим это сделать. Объекты, хранящиеся в оперативной памяти, обеспечивают наилучшую гибкость, потому что мы можем выбрать вариант создания зависимостей напрямую в коде или на основании 250 настроек конфигурации. Если мы используем DI-контейнер, мы получаем обе опции бесплатно. Это означает, что мы должны создать пользовательский ServiceHostFactory , который создает экземпляры пользовательского ServiceHost , который снова может соединить нужный сервис со всеми его зависимостями. Мы можем создать набор универсальных классов, которые делают это на основании выбранного DI-контейнера, или используют один из уже реализованных, многократно используемых ServiceHostFactorys . Т акже мы можем создать специальный ServiceHostFactory для конкретного сервиса. Поскольку создание специального ServiceHostFactory является наилучшей иллюстрацией процесса, в следующем примере используется специализированная фабрика. Пример: подключение сервиса управления продуктами В качестве примера представьте себе, что вас попросили расширить шаблонное приложение Comm erce WCF сервисом, который раскрывает операции, позволяющие другим приложениям управлять данными товара. Это позволяет вам вмонтировать богатого по функциям клиента (вы будете делать это в следующем разделе) или пакетное задание для того, чтобы управлять данными товара. Знакомство с се рвисом управления товарами Для упрощения примера давайте предположим, что вы хотите раскрыть простые операции Create , Read , Update и Delete (CRUD). Рисунок 7-8 демонстрирует диаграмму сервисов и связанных контрактов данных. Рисунок 7-8: IProductManagementService – это W CF сервис, который определяет простые CRUD операции для товаров. Он использует связанные ProductContract и MoneyContract для раскрытия этих операций. Несмотря на то, что это не продемонстрировано на диаграмме, все три типа помечены обычными WCF атрибутами: ServiceContract , OperationContract , DataContract и DataMember 251 Поскольку у вас уже есть существующая доменная модель, вам хотелось бы реализовать этот сервис путем расширения доменной модели, и раскрыть ее операции посредством ее W CF контракта. Точные детали не важны; достаточно сказать, что вы расширяете абстрактный класс ProductRepository , который вы видели в предыдущих главах. П одсказка Несмотря на то, что я не хочу проводить вас здесь по всему доменному коду, вы можете просмотреть детали в коде, загруженном из книги. Доменная модель представляет товар как Entity Product , а сервисный контракт раскрывает его операции в терминах Data Transfer Object (DT O) ProductContract . Для преобразований между этими двумя различными типами вы также вводите интерфейс под названием IContractMapper Итоговый результат заключается в том, что вы завершаете реализацию сервиса двумя зависимостями, и поскольку они являются обязательными, вам хотелось бы использовать внедрение через конструктор. Ниже приведена сигнатура конструктора сервиса: public ProductManagementService(ProductRepository repository, IContractMapper mapper) До настоящего времени мы счастливо игнорировали слона в комнате: как нам получить W CF для того, чтобы корректно подключить экземпляр ProductManagementService ? П рисоединение ProductManagementService к WCF Как показано на рисунке 7-7, Com position Root в W CF – это триплет ServiceHostFactory , ServiceHost и IInstanceProvider . Чтобы подключить к сервису механизм внедрения через конструктор, мы должны обеспечить пользовательские реализации всех трех этих компонентов. П одсказка Вы можете написать полностью пригодные для повторного использования реализации, которые заворачивают ваш любимый DI-контейнер в эти три типа и используют их для реализации IInstanceProvider . Многие люди уже делали это, поэтому вы, скорее всего, можете найти готовый набор для выбранного вами DI-контейнера. П римечание В данном примере реализуется контейнер, жестко присоединенный при помощи Poor Man's DI. Я решил инкапсулировать жестко закодированные зависимости в пользовательском классе контейнера для того, чтобы дать вам хорошее представление о том, как создать многократно используемое решение на основании конкретного DI- контейнера. Entity vs. DTO Предыду щий параграф подбросил вам немного жаргона, поэтому давайте вкратце рассмотрим, что подразумевается под Entity и DTO. 252 Entity – это термин проблемно-ориентированного проектирования (Dom ain-Driven Design), который охватывает объект Domain , имеющий долгосрочную идентификацию, которая не относится к конкретному экземпляру объекта. Это может показаться абстрактным и теоретическим, но это означает, что Ent ity представляет собой объект, который живет за пределами произвольных битов памяти. Любой экземпляр .NET объекта имеет внутренний адрес (идентификаци ю), но Ent ity обладает идентификацией, которая обитает по ту сторону жизненного процесса. Мы часто используем базы данных и первичные ключи для идентификации Ent ities и для того, чтобы убедиться, что мы можем сохранять и читать их, даже если хост-компьютер будет перезагружен. Доменный объект Product – это Entity, поскольку у сущности товара гораздо более продолжительный жизненный цикл, нежели у единичного процесса, и мы используем ID товара для его идентификации в ProductRepository Data Transfer Object (DTO), с другой стороны, существует только для того, чтобы быть переданным с одного уровня приложения на другой. Несмотря на то, что Entity может инкапсулироват ь большую часть поведения, DTO – это структура данных без поведения. При демонстрации доменной модели внешним системам мы часто поступаем так с сервисами и DT Os, поскольку мы никогда не можем быть уверены в том, что другая система сможет использовать нашу типовую систему (она может даже не использовать .NET ). В таких ситуациях нам всегда нужно выполнять преобразования между Ent ity и DTOs. Давайте начнем с пользовательского ServiceHostFactory , который является настоящей точкой входа в WCF сервис. Следующий листинг демонстрирует реализацию. Листинг 7-4: Пользовательский ServiceHostFactory 1. public class CommerceServiceHostFactory : ServiceHostFactory 2. { 3. private readonly ICommerceServiceContainer container; 4. public CommerceServiceHostFactory() 5. { 6. this.container = 7. new CommerceServiceContainer(); 8. } 9. protected override ServiceHost CreateServiceHost( 10. Type serviceType, Uri[] baseAddresses) 11. { 12. if (serviceType == typeof(ProductManagementService)) 13. { 14. return new CommerceServiceHost( 15. this.container, 16. serviceType, baseAddresses); 17. } 18. return base.CreateServiceHost(serviceType, baseAddresses); 19. } 20. } 21. Строка 6-7: Создает экземпляр контейнера Строка 14-16: Создает пользовательский ServiceHost 253 Пользовательский CommerceServiceHostFactory унаследован от ServiceHostFactory с единственной целью – присоединить экземпляры ProductManagementService . Он использует пользовательский CommerceServiceContainer для выполнения реальной работы, поэтому создает экземпляр контейнера в его конструкторе. Вы можете легко расширить этот пример для того, чтобы использовать настоящий DI-контейнер, создавая и конфигурируя вместо этого экземпляр данного контейнера. При получении запроса о создании ServiceHost он возвращает новый CommerceServiceHost с настроенным контейнером, если запрашиваемый тип сервиса подходит. CommerceServiceHost ответственен за определение подходящих поведений для всех типов сервисов, на которых он размещается. В данном примере вы хотите добавить всего лишь одно поведение, которое передает желаемый IInstanceProvider в сервисы. Вы можете завершить всю эту работу в конструкторе, продемонстрированном ниже, а базовый класс позаботится обо всем остальном. Листинг 7-5: Пользовательский ServiceHost 1. public class CommerceServiceHost : ServiceHost 2. { 3. public CommerceServiceHost(ICommerceServiceContainer container, 4. Type serviceType, params Uri[] baseAddresses) 5. : base(serviceType, baseAddresses) 6. { 7. if (container == null) 8. { 9. throw new ArgumentNullException("container"); 10. } 11. var contracts = this.ImplementedContracts.Values; 12. foreach (var c in contracts) 13. { 14. var instanceProvider = 15. new CommerceInstanceProvider( 16. container); 17. c.Behaviors.Add(instanceProvider); 18. } 19. } 20. } Строка 14-16: Создает InstanceProvider Строка 17: Добавляет InstanceProvider в качестве поведения Класс CommerceServiceHost наследуется от ServiceHost , который является конкретным классом, выполняющим всю тяжелую работу. В большинстве случаев вы будете размещать только один вид сервиса (в данном случае, ProductManagementService ), но вам разрешено размещать самые разнообразные сервисы; это означает, что вы должны добавить ко всем этим сервисам IInstanceProvider . Свойство ImplementedContracts – это словарь, поэтому вы можете выполнить цикл по его Values , чтобы пометить их всех. Для каждого вида сервиса вы инициализируете новый экземпляр пользовательского класса CommerceInstanceProvider вместе с контейнером. Поскольку он дублируется в качестве поведения, вы можете добавить его в Behaviors сервиса. Последней составляющей пользовательского W CF триплета является CommerceInstanceProvider , который дублирует как IInstanceProvider , так и 254 IContractBehavior . Это простая реализация, но поскольку она реализует два различных интерфейса со сложными сигнатурами, она может выглядеть слегка устрашающей, если вы видите ее впервые. Вместо нее я продемонстрирую код, отнимающий мало времени; рисунок 7-9 предоставляет краткий обзор. Рисунок 7-9: CommerceInstanceProvider реализует как IInstanceProvider , так и IContractBehavior , поэтому вам нужно реализовать семь методов. Вы можете оставить три этих метода пустыми, а остальные четыре являются однострочными. Листинг 7-6 демонстрирует объявление класса и конструктор. Здесь ничего не происходит, кроме использования Constructor Injection для внедрения контейнера. Обычно мы используем механизм внедрения через конструктор, для того, чтобы объявить DI- контейнеру, что классу нужны некоторые зависимости, но в данном случае это делать поздно, поскольку вы внедряете сам контейнер. Это обычно попахивает большим кодом, потому что он чаще всего указывает на намерение использовать анти-паттерн Service Locator, но здесь это необходимо, поскольку вы реализуете Composition Root. Листинг 7-6: Объявление класса CommerceInstanceProvider и конструктор 1. public partial class CommerceInstanceProvider : 2. IInstanceProvider, IContractBehavior 3. { 4. private readonly ICommerceServiceContainer container; 5. public CommerceInstanceProvider( 6. ICommerceServiceContainer container) 7. { 8. if (container == null) 9. { 10. throw new ArgumentNullException("container"); 11. } 12. this.container = container; 13. } 14. } Строка 2: Реализует W CF интерфейсы Строка 4-13: Constructor Injection CommerceInstanceProvider реализует как IInstanceProvider , так и IContractBehavior Вы дополняете контейнер посредством стандартного Constructor Injection. В данном примере вы используете пользовательский CommerceServiceContainer , но замена его универсальным DI-контейнером – обычная практика. 255 Реализация IInstanceProvider в следующем листинге используется рабочей средой WCF для создания экземпляров класса ProductManagementService Листинг 7-7: Реализация IInstanceProvider 1. public object GetInstance(InstanceContext instanceContext, Message message) 2. { 3. return this.GetInstance(instanceContext); 4. } 5. public object GetInstance(InstanceContext instanceContext) 6. { 7. return this.container 8. .ResolveProductManagementService(); 9. } 10. public void ReleaseInstance(InstanceContext instanceContext, 11. object instance) 12. { 13. this.container.Release(instance); 14. } Строка 3: Делегирует полномочия перегрузке Строка 7-8: Использует контейнер для преобразования Строка 13: Просит контейнер высвободить экземпляр Рабочая среда WCF вызывает один из методов GetInstance для того, чтобы получить экземпляр запрашиваемого вида сервиса, поэтому вы просите контейнер присоединить к ProductManagementService всего его необходимые зависимости. При завершении операции сервиса рабочая среда WCF просит вас высвободить экземпляр, и вы снова делегируете эту работу контейнеру. Остальная часть CommerceInstanceProvider – это реализация IContractBehavior Единственная причина для реализации этого интерфейса – позволить вам добавить его в список поведений, как это продемонстрировано в листинге 7-5. Все методы интерфейса IContractBehavior возвращают void , поэтому вы можете оставить большинство из них пустыми, поскольку вам не нужно их реализовывать. Следующий листинг демонстрирует реализацию единственного метода, о котором вам стоит позаботиться. Листинг 7-8: Основная реализация IContractBehavior public void ApplyDispatchBehavior( ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime) { dispatchRuntime.InstanceProvider = this; } В этом методе вам нужно сделать всего лишь одну очень простую вещь. Рабочая среда W CF вызывает этот метод и передает экземпляр DispatchRuntime , который позволяет вам сказать методу о том, что он должен использовать эту конкретную реализацию IInstanceProvider – помните, что CommerceInstanceProvider также реализует 256 IInstanceProvider . Рабочая среда WCF теперь знает, какой IInstanceProvider использовать, и может впоследствии вызвать метод GetInstance , продемонстрированный в листинге 7-7. Кажется, чтобы разрешить механизм внедрения зависимостей для W CF необходимо реализовать большое количество кода, а я даже не показал вам реализацию CommerceServiceContainer П одсказка Помните, что вы можете легко написать многократно используемые версии этих трех классов, обвертывающих ваш любимый DI-контейнер и упаковывающих эту реализацию в библиотеку. Это делают многие разработчики, поэтому вы, скорее всего, можете найти подходящую готовую библиотеку в интернете. Контейнер – это последний фрагмент W CF DI паззла. Ре ализация специализирова нно го контейнера CommerceServiceContainer – это специализиро ванный контейнер с единственной целью присоединения класса ProductManagementService . Помните, что для этого класса нужны экземпляр ы ProductRepository и IContractMapper в качестве зависимостей. Если исключить всю W CF инфраструктуру, то контейнер свободен для того, чтобы сконцентрироваться на присоединении диаграммы зависимостей. П римечание Кроме соблюдения принципа единичной ответственности, такая концепция разделения демонстрирует, что вы можете заменить этот специализированный контейнер универсальным DI-контейнером, поскольку WCF-специфичный код отсутствует. Метод ResolveProductManagementService связывает экземпляр с Poor Man's DI, что продемонстрировано ниже. Листинг 7-9: Преобразование ProductManagementService 1. public IProductManagementService ResolveProductManagementService() 2. { 3. string connectionString = 4. ConfigurationManager.ConnectionStrings 5. ["CommerceObjectContext"].ConnectionString; 6. ProductRepository repository = 7. new SqlProductRepository(connectionString); 8. IContractMapper mapper = new ContractMapper(); 9. return new ProductManagementService(repository, 10. mapper); 11. } Строка 6-7: Создает репозиторий товаров Строка 8: Создает преобразователь контрактов 257 В некотором смысле, когда дело доходит до преобразования диаграммы зависимостей, часто бывает выгодно вернуться назад. Вы знаете, что вам нужно вернуть экземпляр ProductManagementService с экземпляра ми ProductRepository и IContractMapper Экземпляр IContractMapper легко создать, но для того, чтобы создать ProductRepository нужно потрудиться побольше. Вам хотелось бы использоват ь SqlProductRepository , но для этого вам нужна строка соединения, которую вы можете прочитать из файла |