Главная страница

Внедрение зависимостей в. Внедрение зависимостей в .NET. Руководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей


Скачать 5.66 Mb.
НазваниеРуководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
АнкорВнедрение зависимостей в .net
Дата14.12.2019
Размер5.66 Mb.
Формат файлаpdf
Имя файлаВнедрение зависимостей в .NET.pdf
ТипРуководство
#100226
страница18 из 43
1   ...   14   15   16   17   18   19   20   21   ...   43
web.config.
Если вы хотите разместить сервис в вашем собственном приложении, то сейчас вы можете это сделать путем создания нового экземпляра класса
CommerceServiceHostFactory и вызова его метода
CreateServiceHost с корректными параметрами. Метод вернет экземпляр
CommerceServiceHost
, который вы можете открыть, и будет выполнять остальную часть работы за вас, а также размещать
ProductManagementService
Т ем не менее, если вы хотите разместить сервис на IIS, то вы должны выполнить еще один шаг.
Хостинг ProductManagementService в IIS
В IIS мы не создаем новые экземпляр ы
CommerceServiceHostFactory вручную. Вместо этого мы должны сообщить IIS о том, чтобы он сделал это за нас. Это может быть сделано в .svc файле путем добавления атрибута
Factory
:
<%@ ServiceHost=""
Factory = "Ploeh.Samples.CommerceService.CommerceServiceHostFactory,
➥Ploeh.Samples.CommerceService"
Service = "Ploeh.Samples.CommerceService.ProductManagementService"
%>
Данный .svc файл дает IIS указание использовать
CommerceServiceHostFactory всякий раз, когда ему нужно создавать экземпляр класса
ProductManagementService
. То, что рассматриваемый
ServiceHostFactory имеет конструктор по умолчанию, является условием, но в данном примере это именно так.
Разрешение DI в W CF сложнее, нежели это должно было быть, но, по крайней мере, это возможно, и конечного результата вполне достаточно. Мы можем использовать любой желаемый DI-контейнер, и завершаем работу, приобретая правильный Com position Root.
Некоторые фреймворки не предоставляют нам подходящие швы, которые позволили бы нам такую роскошь. Т ем не менее, до того, как мы рассмотрим один такой известный фреймворк, давайте расслабимся и рассмотрим более простой фреймворк.

258 7.4. Построение WPF приложений
Если вы думали, что создавать W CF сервис было сложно (как думал и я), то вы оцените, что создавать W indows Present ation Foundation (WPF) приложение почти настолько же просто, как и консольное приложение.
Т очка входа в W PF приложение является довольно очевидной и не сложной, и, несмотря на то, что она не предоставляет швов, которые явно предназначены для разрешения DI, мы можем с легкостью сформировать приложение любым предпочитаемым нами способом.
WPF композиция
Т очка входа в W PF приложение определяется в его классе
App
. Как и большинство других классов WPF, этот класс разбит на два файла: App.xam l и App.xam l.cs. М ы можем определить, что происходит на стадии начальной загрузки в обоих файлах в зависимости от наших потребностей.
При создании нового WPF проекта в Visual Studio файл App.xam l определяет атрибут
StartupUri
, который устанавливает, какое окно демонстрируется при запуске приложения – в данном примере
Window1
:
StartupUri="Window1.xaml">

Смысл данного стиля объявления заключается в том, что объект
Window1
создается и демонстрируется без какого-либо дополнительного контекста. Когда вы хотите добавить зависимости к окну, наиболее подходящим может стать более явный подход. Вы можете удалить атрибут
StartupUri и присоединить окно с помощью переопределения метода
OnStartup
. Это позволяет вам полностью присоединить первое окно до того, как оно будет продемонстрировано, но вам придется за это заплатить: вы должны не забыть явно вызвать метод
Show для окна.
Метод
OnStartup
, таким образом, становится Com position Root приложения. Вы можете использовать DI-контейнер или Poor Man's DI для создания окна. В следующем примере используется Poor Man's DI для того, чтобы проиллюстрировать, что вам не приходится полагаться на возможности какого-либо конкретного DI-контейнера.
Пример: присоединение ценного клиента управления товарами
В предыдущем примере разрабатывался веб-сервис, который мы можем использовать для управления каталогом товаров в шаблонном приложении Comm erce. В данном примере мы создадим W PF приложение, которое использует этот веб-сервис для управления товарами. Рисунок 7-10 демонстрирует скриншот этого приложения.

259
Рисунок 7-10: Главное окно приложения Product Managem ent – это список товаров. Вы можете добавлять новые товары, редактировать существующие или удалять их. При добавлении или редактировании товаров используется модальное окно редактирования.
Все операции реализуются посредством вызова соответствующей операции для веб- сервиса управления товарами из раздела 7.3.2.
Приложение реализует Model View ViewModel (MVVM) подход и содержит три уровня, которые продемонстрированы на рисунке 7-11. Обычно мы держим ту составляющую, в которой находится большая часть логики, изолированно от других модулей – в данном примере
PresentationLogic.ProductManagementClient
– это humble-исполнитель
(hum ble executable), который выполняет несколько большую работу, нежели просто определяет пользовательский интерфейс и делегирует реализацию другим модулям.

260
Рисунок 7-11: Приложение состоит из трех отдельных блоков. Блок
ProductManagementClient
– исполняемый, и включает в себя пользовательский интерфейс, реализованный в XAML без выделенного кода (code-behind). Библиотека
PresentationLogic содержит
ViewModels и опорные классы, а библиотека
ProductWcfAgent включает в себя
Adapter между пользовательско й абстракцией
IProductManagementAgent и конкретным WCF прокси, который используется для взаимодействия с веб-сервисом управления товарами. Стрелки-указатели зависимостей означают, что
ProductManagementClient выступает в роли Composition Root , поскольку он соединяет вместе остальные модули.
Благодаря подходу MVVM мы передаем
ViewModel в свойство
DataContext главного окна, а механизм связывания данных и движок шаблонизации данных заботятся о корректном представлении данных, как только мы вплетаем новые
ViewModels или изменяем данные существующих
ViewModels
MVVM
Model View ViewModel (MVVM) – это паттерн проектирования, для которого отлично подходит WPF. Он разделяет код пользовательского интерфейса на три отдельных ответственности.
Model – это основополагающая модель приложения. Ею часто, но не всегда, является доменная модель. Она часто состоит из POCO-объектов. В текущем примере доменная модель реализуется в веб-сервисе, поэтому на данном уровне у вас нет истинной доменной модели. Т ем не менее, приложение функционирует с абстракциями, находящимися поверх прокси веб-сервиса, и это и есть ваша Model. Обратите внимание на то, что Model обычно выражается UI-нейтральным способом. Это не предполагает, что
Model будет раскрыта напрямую пользовательским интерфейсом, поэтому она не раскрывает никакой WPF-специфичной функциональности.
View – это рассматриваемый нами пользовательский интерфейс. В WPF мы можем официально выразить View в XAML и использовать механизм связывания данных и движок шаблонизации данных для представления данных. Можно выразить Views без использования выделенного кода.

261
ViewModel – мост между View и Model. Каждый ViewModel – это класс, который преобразовывает и раскрывает Model конкретным специфическим способом. В WPF это означает, что ViewModel может раскрывать списки как
ObservableCollections
, и тому подобное.
Внедрение зависимостей в главны й ViewModel
MainWindow содержит только XAML разметку и не содержит никакого пользовательского выделенного кода. Зато он использует механизм связывания данных для отображения данных на экране и управления пользовательскими командами. Для того чтобы это позволить мы должны передать
MainWindowViewModel в его свойство
DataContext
MainWindowViewModel раскрывает такие данные, как список товаров, а также команды создания, обновления или удаления товара. Возможность такой функциональност и зависит от сервиса, который обеспечивает доступ к каталогу товаров: абстракция
IProductManagementAgent
Помимо
IProductManagementAgent для
MainWindowViewModel также необходим сервис, который он может использовать для того, чтобы контролировать его оконную среду
(windowin g environment), например, демонстрацию модальных диалоговых окон. Эта зависимость называется
IWindow
MainWindowViewModel использует паттерн Constructor Injection со следующей сигнатурой конструктора: public MainWindowViewModel(IProductManagementAgent agent, IWindow window)
Для соединения приложения мы должны создать
MainWindowViewModel и передать его в свойство
DataContext экземпляра
MainWindow
С оединение MainWindow и Mai nW indowViewModel
Данному примеру придает остроты тот факт, что для корректной реализации
IWindow
, вам нужен указатель на реальное WPF окно (
MainWindow
); но для ViewModel необходим
IWindow
, а свойство
DataContext экземпляра
MainWindow должно быть ViewModel.
Другими словами, вы получаете циклическую зависимост ь.
В главе 6 мы имели дело с циклическими зависимостями и прошлись по соответствующей части этого конкретного примера, поэтому я не буду повторять это в данной главе.
Достаточно сказать, что вводится
MainWindowViewModelFactory
, который является ответственным за создание экземпляров
MainWindowViewModel
Вы используете эту фабрику в рамках реализации
IWindow под названием
MainWindowAdapter для того, чтобы создать
MainWindowViewModel и передать его в свойство
DataContext экземпляра
MainWindow
: var vm = this.vmFactory.Create(this); this.WpfWindow.DataContext = vm;
Переменная члена vmFactory
– это экземпляр
IMainWindowViewModelFactory
, и вы передаете в его метод
Create экземпляр содержащегося класса, который реализует

262
IWindow
. Итоговый экземпляр
ViewModel затем передается в
DataContext
WpfWindow
, который является экземпляром
MainWindow
Примечание
Я не рассматриваю детали, поскольку мы рассматривали их в главе 6. Вернитесь к этой главе и прочтите раздел о циклических зависимостях, если вам нужно освежить в памяти то, что сейчас происходит.
Подсказка
Для механизма связывания WPF данных необходимо, чтобы мы передавали зависимость
(
ViewModel
) в свойство
DataContext
. По моему мнению, это неправильное использование
Propert y Injection, поскольку это сигнализирует о том, что зависимость является необязательной, а это абсолютно не так. Т ем не менее, WPF 4 вводит нечто, имеющее название
XamlSchemaContext
, который может использоваться в качестве шва, который, в свою очередь, дает нам большую гибкость в тех ситуациях, когда дело доходит до создания экземпляров
Views на основании разметки.
Рисунок 7-12 демонстрирует окончательную диаграмму зависимостей приложения.
Рисунок 7-12: Диаграмма зависимостей
MainWindowAdapter
, который является основным объектом приложения.
MainWindowAdapter использует
MainWindowViewModelFactory для создания соответствующего
ViewModel и передачи его в
MainWindow
. Для того чтобы создать
MainWindowViewModel фабрике нужно передать
WcfProductManagementAgent во
ViewModel
. Этот "посредник" является адаптером между
IProductManagementAgent и
WCF прокси. Он требует, чтобы
ProductChannelFactory создал экземпляр ы WCF прокси, а также
IClientContractMapper
, который может выполнять преобразование между
ViewModels и WCF контрактами данных.
Теперь, когда вы идентифицировали все строительные блоки приложения, вы можете скомпоновать их. Для того чтобы сохранить Poor Man's DI код симметричным, используя

263 при этом DI-контейнер, я реализовал это в виде
Resolve метода специализированного класса контейнера. В следующем листинге продемонстрирована реализация.
Листинг 7-10: Композиция главного окна public IWindow ResolveWindow()
{
IProductChannelFactory channelFactory = new ProductChannelFactory();
IClientContractMapper mapper = new ClientContractMapper();
IProductManagementAgent agent = new WcfProductManagementAgent( channelFactory, mapper);
IMainWindowViewModelFactory vmFactory = new MainWindowViewModelFactory(agent);
Window mainWindow = new MainWindow();
IWindow w = new MainWindowAdapter(mainWindow, vmFactory); return w;
}
В конечном итоге вы возвращаете экземпляр
IWindow
, реализованный
MainWindowAdapter
, а для этого вам нужны WPF
Window и
IMainWindowViewModelFactory
. Первым окном, которое вы должны продемонстрировать пользователям, должно быть
MainWindow
, поэтому именно его вы и передаете в
MainWindowAdapter
MainWindowViewModelFactory использует паттерн Constructor Injection для запроса
IProductManagementAgent
, поэтому вы должны скомпоновать
WcfProductManagementAgent с двумя его зависимостями.
Окончательный
MainWindowAdapter
, возвращаемый из метода, обертывает
MainWindow
, поэтому, когда мы вызываем метод
Show
, он делегирует полномочия методу
Show
MainWindow
. Это именно то, что вы и будете делать в Com position Root.
Ре ализация Compositi on Root
Т еперь, когда вы знаете, как подключит ь приложение, вам нужно всего лишь сделать это в правильном месте. Как описывалось в предыдущем разделе, вам для начала нужно открыть App.xam l и удалить атрибут
StartupUri
, поскольку вы хотите самостоятельно явным образом компоновать начальное окно запуска.
После того, как вы это сделали, вам нужно только переопределит ь метод
OnStartup в
App.xam l.cs и вызвать контейнер.
Листинг 7-11: Реализация WPF Com position Root protected override void OnStartup(StartupEventArgs e)
{ base.OnStartup(e); var container = new ProductManagementClientContainer(); container.ResolveWindow().Show();
}

264
В данном примере вы используете специализированный
ProductManagementClientContainer
, но вы также могли использовать и универсальный
DI-контейнер, например, Unity или StructureMap. Вы просите контейнер преобразовать экземпляр
IWindow
, а затем вызвать его метод
Show
. Возвращаемый экземпляр
IWindow
– это
MainWindowAdapter
, когда вы вызывает е его метод
Show
, он вызывает метод
Show инкапсулированного
MainWindow
, который становится причиной того, что желаемое окно демонстрируется пользователю.
W PF предлагает простое место для Com position Root. Все, что вам нужно сделать – удалить
StartupUri из App.xam l, переопределить
OnStartup в App.xaml.cs и скомпоновать там приложение.
До настоящего момента вы встречались с примерами, в которых фреймворки предоставляют швы, позволяющие нам перенять жизненный цикл экземпляров ключевых объектов (веб-страниц, экземпляров сервисов, окна и других). Во многих случаях это довольно просто; но даже когда это настолько усложняется, как например, в W CF, мы все еще можем достичь цели и реализовать подлинный механизм внедрения зависимостей, не подвергая при этом риску наши принципы.
Некоторые фреймворки, тем не менее, не предоставляют нам такой роскоши.

265 7.5. Построение ASP.NET приложений
Некоторые фреймворки настаивают на создании и управлении жизненным циклом написанных нами классов. Самый популярный фреймворк – ASP.NET (W eb Forms, в противоположность MVC).
Примечание
Некоторые другие фреймворки, разделяющие эту особенность, – это Microsoft
Management Console (MMC), управляемый SDK, и такая недавняя разработка, как
PowerShell.
Наиболее очевидным симптомом таких фреймворков является то, что, чтобы им подойти, наши классы должны обладать конструктором по умолчанию. В ASP.NET , например, любой реализуемый нами класс Page должен обладать непараметризованным конструктором. Мы не можем использовать Constructor Injection в рамках этих фреймворков, поэтому давайте рассмотрим наши возможности.
ASP.NET композиция
Паттерн Constructor Injection был бы предпочтительным, поскольку он позволял бы удостовериться, что наши классы
Page будут правильно инициализиро ваться совместно с их зависимостями. Т ак как это невозможно, мы должны выбирать между следующими альтернативными вариантами:

Перемещать и дублировать наши Com position Root'ы в пределах каждого класса
Page

Использовать Service Locator для того, чтобы преобразовать все зависимости в пределах каждого класса
Page
Тем не менее, не забывайте о том, что Service Locator – это анти-паттерн, поэтому такой вариант не желателен. Наилучшая альтернатива – пойти на компромисс в вопросе расположения нашего Composition Root.
В идеале, мы бы предпочли сценарий, продемонстрированный на рисунке 7-13, при котором мы имеем всего один Com position Root для приложения, но в ASP.NET это невозможно, поскольку мы не можем компоновать экземпляры Page извне. Другими словами, фреймворк Web Forms вынуждает нас компоновать приложение в пределах каждого
Page
Рисунок 7-13: В идеальном мире нам хотелось бы уметь компоновать объекты
Page из
Com position Root приложения. При получении запроса мы должны уметь использовать определенную конфигурацию зависимостей для того, чтобы скомпоновать соответствующий объект
Page
. Тем не менее, это невозможно, поскольку ASP.NET управляет жизненным циклом объектов
Page от нашего имени.

266
Примечание
До настоящего времени я говорил только об объектах
Page
, но ASP.NET требует, чтобы большинство объектов обладали конструкторами по умолчанию в случае, если мы хотим использовать фреймворк. Еще одним примером является Object Data Sources. Обсуждение, имеющее место в данном разделе, в равной степени хорошо применяется ко всем остальным типам, которые должны обладать конструктором по умолчанию.
Чтобы обратиться к этой проблеме, мы должны подвергнуть риску наши идеалы, но компромисс в вопросе расположения наших Com position Root'ов кажется мне более безопасным, чем позволить Service Locator вступить в игру.
По существу мы преобразуем каждый
Page в Com position Root , как это продемонстрировано на рисунке 7-14. Принцип единичной ответственности напоминает нам о том, что каждый класс должен иметь только одну ответственность; теперь, когда мы используем
Page для того, чтобы скомпоновать все необходимые зависимости, мы должны делегировать ответственность за реализацию
1   ...   14   15   16   17   18   19   20   21   ...   43


написать администратору сайта