- Microsoft Unity
- Ninject
- Autofac
- StructureMap
- Castle
- Spring.Net
Создадим новый проект и добавим в него основные сущности.Смотри предыдущую статью.- Скачаем необходимые библиотеки.
- Добавим references на них в проекте.
- Настроим IoC-контейнер через конфигурационный XML-файл.
- С помощью IoC-контейнера получим экземпляр объекта нашего класса.
Первым делом создайте новый проект под именем UnityExample, в который добавьте, описанные в предыдущей статье, классы и интерфейсы в общее пространство имен UnityExample.
Вторым шагом, нам понадобиться сам контейнер Unity, его можно скачать в составе
Microsoft Enterprise Library 5.0 виде инсталляционного файла, установка которого производиться в режиме wizard installer и не требует наличия особых навыков, и в отличие от ручной установки, она автоматически зарегистрирует необходимые сборки в GAC добавит дополнение к Visual Studio. После установки добавим ссылки на необходимые библиотеки Unity:
- Microsoft.Practices.Unity.dll
- Microsoft.Practices.Unity.Configuration.dll
Вообще, настраивать поведение IoC-контейнера можно и в коде, но все-таки хорошим тоном считается, если изменение конфигурации не приведет к изменению кода и повторной сборки проекта, поэтому всю конфигурацию мы вынесем в отдельный файл, причем не захламляя основной файл конфигурации App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section
name="unity"
type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
</configSections>
<!-- Вынесем все в отдельный файл -->
<unity configSource="Unity.config" />
</configuration>
Сама настройка контейнера будет описана в файле Unity.config (не забудьте выставить опцию "Copy to Output Directory"). Следует отметить, что конфигурационный файл для Unity контейнера имеет XML-формат, при необходимости, если Ваш редактор XML файлов поддерживает XSD схемы и intellisense, то Вы можете упростить процесс конфигурирования, воспользовавшись XSD схемой (ставится вместе с Microsoft Enterprise Library 5.0) "c:\Program Files\Microsoft Visual Studio 9.0\xml\Schemas\UnityConfiguration20.xsd":
<?xml version="1.0" encoding="utf-8" ?>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<!-- Заведем псевдонимы для известных типов -->
<alias alias="IProcessor" type="UnityExample.IProcessor, UnityExample" />
<alias alias="Processor" type="UnityExample.Processor, UnityExample" />
<alias alias="Computer" type="UnityExample.Computer, UnityExample" />
<container>
<!-- Свяжем тип IProcessor с типом Processor -->
<register type="IProcessor" mapTo="Processor"></register>
<!-- Свяжем тип Computer с типом Computer -->
<register type="Computer" mapTo="Computer">
<constructor>
<!-- Объявим аргумент конструктора processor типа IProcessor как зависимый -->
<param name="processor" type="IProcessor">
<dependency/>
</param>
</constructor>
</register>
</container>
</unity>
Так как это все-таки ознакомительный пример, я решил поместить все сущности в один файл, в итоге получилось следующее:
using System;
using System.Configuration;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
namespace UnityExample
{
/// <summary>
/// Интерфейс процессора.
/// </summary>
interface IProcessor
{
/// <summary>
/// Частота процессора.
/// </summary>
int ClockRate
{
get;
}
}
/// <summary>
/// Конкретный процессор с частотой 166 Мгц.
/// </summary>
class Processor : IProcessor
{
public int ClockRate
{
get
{
return 166;
}
}
}
/// <summary>
/// Конкретный компьютер.
/// </summary>
class Computer
{
/// <summary>
/// Процессор.
/// </summary>
protected IProcessor _processor;
public Computer(IProcessor processor)
{
_processor = processor;
}
public void PrintDescription()
{
Console.Write("Clock rate: {0} Mhz", _processor.ClockRate);
}
}
class Program
{
static void Main(string[] args)
{
// IoC контейнер.
IUnityContainer container = new UnityContainer();
// Загрузим конфигурацию для IoC контейнера из секции unity.
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
// Применим конфигурацию.
section.Configure(container);
// Получим экземпляр объекта типа Computer.
Computer computer = container.Resolve<Computer>();
computer.PrintDescription();
}
}
}
И если вы запустите проект, то Вы увидите на консоли сообщение "Clock rate: 166 Mhz", тем самым IoC-контейнер сам разрешил тип Computer, более того, контейнер автоматически разрешил зависимость Computer от IProcessor. Очень важно отметить, что мы НЕ использовали оператор new, который задал бы жесткую зависимость. Все это позволяет строить более гибкие архитектурные решения, которые способны полностью перестраиваться при небольшом изменении в конфигурационном файле.
В следующей статье я отвечу на популярные вопросы, касающиеся конфигурирования Microsoft Unity. Продолжение следует IoC/DI
что то вы напустали здесь
ОтветитьУдалитьregister type="Computer" mapTo="Computer"
вероятно вы имели в виду
register type="IComputer" mapTo="Computer"
Нет, я для простоты не стал добавлять интерфейс IComputer, хотя можно и с ним (так даже правильно делать).
ОтветитьУдалитьА связка "register type="Computer" mapTo="Computer"" определяет self-binding.
Если был бы интерфейс IComputer то, Вы правильно заметили, нужно было бы делать [register type="IComputer" mapTo="Computer"]
ОтветитьУдалитьОзначало бы сконструировать тип Computer при разрешении общего типа IComputer.
И соответственно изменился бы код:
IComputer computer = container.Resolve[IComputer]();
Все таки совсем не хорошо конфигурировать IoC через xml.
ОтветитьУдалитьКонфиг получается крайне сложный, отсутствует типизация, опечатка в нем приведет к падению нашего приложения. "любой залетевший дятел может разрушить цивилизацию".
> Все таки совсем не хорошо конфигурировать IoC через xml.
ОтветитьУдалитьповод для "религиозных войн"))
Help!
ОтветитьУдалитьIoC error: http://i48.tinypic.com/250qv6h.jpg
@Arturo
ОтветитьУдалитьНадо добавить в проекте ссылке на пространство имен System.Configuration.
Спасибо за статью.
ОтветитьУдалитьПробую разобраться с Unity. Помогите с вопросами:
1. В Вашем примере интерфейс (IProcessor) и классы (Processor,Program) находятся в одном неймспейсе UnityExample. Как настроить файл конфигурации если все в разных неймспейсах или в разных сборках. Зачем это нужно? Так понимаю, что container должен быть один на всё приложение, чтобы container.Resolve() выдал мне объект Computer в любом месте приложения где это необходимо (при ContainerControlledLifetimeManager - поведение singleton). Предположим, что Computer должен быть один и тот же в приложении.
2. Как следствие из первого вопроса. Если будет много сборок со многими классы и интерфейсами, то как использовать Unity - он должен быть один на все приложение и знать о всех сборках (классы и интерфейсами, которые он "обслуживает"). Или как?
Большое спасибо.
С первым вопросом разобрался
ОтветитьУдалитьhttp://msdn.microsoft.com/en-us/library/dd203225.aspx
@Slava
ОтветитьУдалитьСмотрите, к конфигурационном файле Вы просто указываете namespace разрешаемого типа. Либо, если Вы конфигурируете unity в runtime, просто делаете "add reference" необходимой сборки, а далее делаете доступными нужные namespace-ы через using.
Так всё-же, будет продолжение?
ОтветитьУдалить@Dzmuh
ОтветитьУдалитьДа, пока статья лежит в черновиках уже давно, а я и подзабыл. Хорошо, тема актуальная и хотелось бы качественно проработать, думаю к концу этой недели выложу.
Илья, как через xml сконфигурирвать в Unity singleton, чтобы он шарился для двух разных интерфейсов. Например
ОтветитьУдалитьclass Impl : IA, IB {}
Это не работает :(
Unity создаёт разные инстансы
блин
ОтветитьУдалить<register type="Impl">
<lifetime type="singleton" />
</register>
<register type="IA" mapTo="Impl"/>
<register type="IB" mapTo="Impl"/>
@aspnetmvcfan
ОтветитьУдалить<?xml version="1.0" encoding="utf-8" ?>
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<!-- Заведем псевдонимы для известных типов -->
<alias alias="IA" type="UnityTest.IA, UnityTest" />
<alias alias="IB" type="UnityTest.IB, UnityTest" />
<alias alias="AB" type="UnityTest.AB, UnityTest" />
<container>
<register type="IA" mapTo="AB">
<lifetime type="singleton"/>
</register>
<register type="IB" mapTo="AB">
<lifetime type="singleton"/>
</register>
</container>
</unity>
public interface IA
{
DateTime A { get; }
}
public interface IB
{
DateTime B { get; }
}
public class AB : IA, IB
{
readonly DateTime _now;
public AB()
{
_now = DateTime.Now;
}
public DateTime A { get { return _now; } }
public DateTime B { get { return _now; } }
}
class Program
{
static void Main(string[] args)
{
IUnityContainer c = new UnityContainer();
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Configure(c);
IA a = c.Resolve[IA]();
// Пауза...
IB b = c.Resolve[IB]();
//a.A.Equals(b.B)==true
}
}
Возможно у вас пересоздается сам Unity контейнер.
В итоге я плюнул на это и сконфигурировал через код :)
ОтветитьУдалитьКонтейнер был один.
У вас конструктор:
ОтветитьУдалитьpublic Computer(IProcessor processor)
{
_processor = processor;
}
а здесь вы объявляете экземпляр класса без аргумента:
static void Main(string[] args){
...
// Получим экземпляр объекта типа Computer.
Computer computer = container.Resolve();
computer.PrintDescription();
}
и как это работает?
какую роль играет UnityContainer ?
По шагам:
ОтветитьУдалить1. Мы запрашиваем у UnityContainer экземпляр Computer (см метод Resolve)
2. Computer имеет конструктор, который требует тип IProcessor, поэтому UnityContainer вынужден внутри себя найти подходящий тип IProcessor, чтобы подсунуть его в конструктор Computer.
Суть в том, что мы запрашиваем у контейнера лишь экземпляр Computer, и при этом не заботимся о всех его зависимостях (в данном примере зависимость на IProcessor)
Для полного понимания темы рекомендую книгу "Dependency Injection in .NET" автора Mark Seemann (http://www.amazon.com/Dependency-Injection-NET-Mark-Seemann/dp/1935182501)