Structuremap — как зарегистрировать определенные типы в определенных слоях

#c# #inversion-of-control #ioc-container #structuremap

#c# #Инверсия контроля #ioc-контейнер #structuremap

Вопрос:

Я использую пример реестра DSL для настройки structuremap. Но это делает все мои зарегистрированные типы доступными на всех уровнях моего приложения, где я добавляю ссылку на карту структуры. Я не хочу, чтобы мой бизнес-уровень знал что-либо о моем уровне доступа к данным и наоборот. Как мне заставить structuremap регистрировать только определенные типы для каждого из моих слоев?

Вот код в моем файле global.asax:

 ObjectFactory.Initialize(x =>
{
  x.AddRegistry<RegistryIOC>();
});
  

И вот мой класс RegistryIOC:

 public class RegistryIOC : SMRegistry
{

    public RegistryIOC() 
    {
        For<IProfileService>.Use<ProfileService>();
        For<IProctorService>().Use<ProctorService>();

        //Business Logic Objects
        For<IQual>().Use<Qual>();
        For<ITest>().Use<Test>();
        For<IBoldface>().Use<Boldface>();
        For<ITrainingPlan>().Use<TrainingPlan>();
        For<IUnit>().Use<Unit>();

        //Data Transfer Objects
        For<IGenericDTO>().Use<GenericDTO>();
        For<IProfileDTO>().Use<ProfileDTO>();
        For<IQualDTO>().Use<QualDTO>();
        For<IPermissionDTO>().Use<PermissionDTO>();

        //Repository Objects
        For<IProctorRepository>().Use<ProctorRepository>();
        For<IQualsRepository>().Use<QualsRepository>();
        For<ITestRepository>().Use<TestRepository>();
        For<IUnitRepository>().Use<UnitRepository>();
        For<IUserRepository>().Use<UserRepository>();
    }

}
  

Спасибо за помощь.

Комментарии:

1. О каких слоях вы говорите? Разные процессы? Разные машины? Если все они выполняются в одном процессе, вы, вероятно, делаете то, что ваш бизнес-уровень, немного зная о вашем уровне данных, в частности, о его интерфейсе. Неясно, какую проблему вы пытаетесь решить. Что не так с вашим классом RegistryIOC?

2. У нас есть уровень обслуживания, BLL и DAL, которые все являются отдельными проектами. Каждый проект ссылается на StructureMap. Сервисный уровень знает о двух других слоях, но BLL и DAL не знают друг о друге. Я не хочу, чтобы другие разработчики использовали бизнес-объекты из DAL и наоборот, я не хочу, чтобы разработчики использовали объекты репозитория из BLL. Уровень обслуживания управляет всем этим. Таким образом, использование регистрации всех типов таким образом делает все объекты доступными для всех наших слоев (проектов).

3. Ну, не добавляйте ссылку на StructureMap из любого другого слоя, кроме корня композиции…

Ответ №1:

Я использую отражение для выполнения этой (и других) задач. Позвольте мне показать, как это работает.

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

 public interface IConfigurationTask
{
    void Configure();
}
  

Затем создайте один или несколько классов, которые реализуют этот интерфейс. Эти классы будут распределены по всем вашим проектам, что является еще одним способом сказать, что вы можете поместить их «туда, где им место».

 public class RepositoryInitializer : IConfigurationTask
{
    public void Configure()
    {
        // code that does relevant initialization goes here
    }
}
  

Последняя часть головоломки — найти классы, которые реализуют интерфейс IConfigurationTask, создать их экземпляр и выполнить метод Configure. Это цель ConfigurationTaskRunner:

 public static class ConfigurationTaskRunner
{
    public static void Execute( params string[] assemblyNames )
    {
        var assemblies = assemblyNames.Select( Assembly.Load ).Distinct().ToList();
        Execute( assemblies );
    }

    public static void Execute( IEnumerable<Assembly> assemblies )
    {
        var tasks = new List<IConfigurationTask>();
        assemblies.ForEach( a => tasks.AddRange( a.CreateInstances<IConfigurationTask>() ) );

        tasks.ForEach( t => t.Configure() );
    }
}
  

Показанный здесь код использует пользовательское расширение для перебора всех элементов в списке и выполнения действия для каждого элемента (метод ForEach). Я также использую библиотеку отражения, чтобы сделать задачу поиска и создания экземпляров однострочной (метод CreateInstances), но вы могли бы добиться того же, используя просто отражение (как показано в коде ниже).

 public static IList<T> CreateInstances<T>( this Assembly assembly )
{
     var query = from type in assembly.GetTypes().Where( t => typeof(T).IsAssignableFrom( t ) amp;amp; typeof(T) != t ) 
                 where type.IsClass amp;amp; ! type.IsAbstract amp;amp; type.GetConstructor( Type.EmptyTypes ) != null 
                 select (T) Activator.CreateInstance( type );
     return query.ToList();
}    
  

Последняя часть головоломки — запустить выполнение ConfigurationTaskRunner. Например, в веб-приложении это вошло бы в Application_Start в Global.asax:

 // pass in the names of the assemblies we want to scan, hardcoded here as an example 
ConfigurationTaskRunner.Execute( "Foo.dll", "Foo.Domain.dll" );
  

Я также нашел это полезным с производным IPrioritizedConfigurationTask (который добавляет свойство приоритета), чтобы обеспечить правильное упорядочение задач перед их выполнением. Это не показано в приведенном выше примере кода, но добавить это довольно тривиально.

Надеюсь, это поможет!

Комментарии:

1. В этом примере (который потрясающий) как вы предлагаете связать зависимости? Если бы я хотел экземпляр IProdRepo, который поддерживался ProdCacheRepo, который поддерживался ProdRepo … как бы вы сделали эту цепочку? Любопытно!

2. @AndrewSiemer Я полагаю, что это было бы задачей IoC framework (в данном случае StructureMap) на основе конфигураций, которые вы предоставляете ему (в отдельных методах настройки). Предоставленный мной код — это просто механизм для «распределенной конфигурации» контейнера IoC (или чего-либо еще, что вам нужно инициализировать при запуске приложения).

3. Я не могу использовать библиотеку отражения. Когда я пробую этот код с помощью обычного отражения, a.CreateInstance(«IConfigurationTask»), я ничего не получаю в результате. Может быть, я где-то чего-то не хватает … 🙁

4. @PearWeb Я добавил метод, который выполняет часть отражения без использования сторонней библиотеки. Я надеюсь, что он компилируется, поскольку я не запускал его сначала через VS 🙂

Ответ №2:

Вы можете создать и настроить несколько независимых Container экземпляров и вообще не использовать static ObjectFactory смотрите эту статью. Тогда вы будете нести ответственность за предоставление надлежащих контейнеров для соответствующих слоев.

Кстати, как вы хотите обрабатывать межуровневую связь? Разве это не было бы как-то сложно? Я бы предпочел разделить реестры (возможно, на отдельные сборки) и сохранить их разделенными «вручную» вместо того, чтобы применять разделение на уровне инфраструктуры.

Комментарии:

1. Я вообще не против изменить свою текущую реализацию. Если лучше всего, чтобы все реестры были разделены на классы на каждом из моих слоев, это круто для меня. Часть моей проблемы заключается в том, что я не совсем понимаю, как «предоставить надлежащие контейнеры для надлежащих слоев». Что именно я помещаю в глобальный загрузчик.asax?

2. Ну, как правило, у вас должны быть разные точки входа для разных слоев, и у каждого слоя должны быть свои собственные фабричные методы, использующие соответствующий контейнер — таким образом, у вас будут отдельные объектные графики для каждого слоя. Но я действительно не вижу, как выполнить какие-либо взаимодействия между такими графами, поэтому вопросы во втором абзаце таковы. Наконец, у вас должны быть какие-то компоненты верхнего уровня X, которые используют компонент нижнего уровня Y. Если X является средним уровнем и создается контейнером среднего уровня, то же самое относится и к его зависимости Y, даже если он находится на нижнем уровне.

3. Итак, как правило, ответ заключается в том, что трудно иметь полностью разделенные объектные графики. Вот почему я предложил развязку на уровне реестра / сборки, а не на уровне фабрики объектов.