Search code examples
dependency-injectioninversion-of-controlioc-containerstructuremapstructuremap3

How to register as singleton or transient all types implements one interface with StructureMap


I have two interfaces that says if a service have to be singleton or transient:

public interface ITransient {}
public interface ISingleton {}

I implement this interfaces in other interfaces and classes:

public interface ISession : ISingleton
{
    int? UserId {get;set;}
}

public class Session : ISession
{
    public int? UserId {get;set;}
}

Then I inject Session in others services:

public interface IBookService : ITransient
{
    ...
}

public class BookService : IBookService
{
    public BookService(ISession session) { ... }
    ...
}

How to configure StructureMap to make that all instance requests of types that implements ISingleton have to create with Singleton lifecycle??

I have tried it:

Container.Configure(conf => {
    conf.For<ITransient>().Transient();
    conf.For<ISingleton>().Singleton();
}

But nothing ... don't work, create a Session object as Transient.

I have tried it too:

        Container.Configure(conf =>
        {
            conf.Scan(s =>
            {
                s.Assembly(assembly);
                s.LookForRegistries();

                s.AddAllTypesOf<ISingletonDependency>();
                s.AddAllTypesOf<ITransientDependency>();
            });

            conf.For<ITransientDependency>().Transient();
            conf.For<ISingletonDependency>().Singleton();
        });

And nothing ...

I have seen how to do it using Windsor Castle:

        context.IocContainer.Register(
            Classes.FromAssembly(context.Assembly)
                .IncludeNonPublicTypes()
                .BasedOn<ITransient>()
                .WithService.Self()
                .WithService.DefaultInterfaces()
                .LifestyleTransient()
        );

        //Singleton
        context.IocContainer.Register(
            Classes.FromAssembly(context.Assembly)
                .IncludeNonPublicTypes()
                .BasedOn<ISingleton>()
                .WithService.Self()
                .WithService.DefaultInterfaces()
                .LifestyleSingleton()

But I don't know how to do using StructureMap ...

Other posibility is using conventions (IRegistrationConvention), but I don't know how to do, example:

    public class LifecycleConvention : IRegistrationConvention
    {
        public void Process(Type type, Registry registry)
        {
            if (type.GetInterface(typeOf(ISingleton) != null)
                **???? what to do ??!!**
        }
    }

Somebody can help me please?

UPDATE

I have build a convention:

public class BasicConvention : IRegistrationConvention
{
    public void Process(Type type, Registry registry)
    {
        if (!type.IsAbstract && typeof(ISingleton).IsAssignableFrom(type))
        {
            registry.For(type, new SingletonLifecycle());
        }
        if (!type.IsAbstract && typeof(ITransient).IsAssignableFrom(type))
        {
            registry.For(type, new TransientLifecycle());
        }
    }
}

And that seem work but it register each class as plugin type, in this case:

Session => Session [Singleton] BookService => BookService [Transient]

But if I inject the Session as ISession ... don't found the instance due to ISession is not registered ... But I can use default convetions ... and then work BUT retrieve the instance as transient ...

Calling WhatDoIHave() I can see it:

===============================================================================================================================================================================================================================================================================
PluginType                  Namespace                          Lifecycle     Description                                                                                                                                               Name                                    
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
....
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ISession                     Paf.Application.Session            Transient     Paf.Application.Session ('Paf.Application.Session, Paf.Modules.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null')                        Paf.Application.Session,... (Default)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
.....
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Session                     Paf.Application                    Singleton     Paf.Application.Session                                                                                                                                (Default)                               
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
...
===============================================================================================================================================================================================================================================================================

Can I solve this it?


Solution

  • Ok, I have gone to StructureMap sources to see how to work the default contention.

    I found DefaultConventionScanner class:

    public class DefaultConventionScanner : ConfigurableRegistrationConvention
    {
        public override void Process(Type type, Registry registry)
        {
            if (!type.IsConcrete()) return;
    
            var pluginType = FindPluginType(type);
            if (pluginType != null && type.HasConstructors())
            {
                registry.AddType(pluginType, type);
                ConfigureFamily(registry.For(pluginType));
            }
        }
    
        public virtual Type FindPluginType(Type concreteType)
        {
            var interfaceName = "I" + concreteType.Name;
            return concreteType.GetInterfaces().FirstOrDefault(t => t.Name == interfaceName);
        }
    }
    

    I can deduce that I could change the plugin type registration (registry.AddType(pluginType, type) line of code) and write it:

                if(typeof(ISingleton).IsAssignableFrom(type))
                    registry.For(pluginType).Use(type).Singleton();
                else if (typeof(ITransient).IsAssignableFrom(type))
                    registry.For(pluginType).Use(type).Transient();
                else
                {
                    registry.AddType(pluginType, type);
                }
                ConfigureFamily(registry.For(pluginType));
    

    Ergo, if the pluginType (interface type) is ISingleton or ITransient I registry the new plugin type as Singleton or Transient otherwise registry the pluginType as allwais.

    I have try and know work!! yeah!

    Only one consideration, I don't know if exists some diference between:

                registry.AddType(pluginType, type);
    

    and: registry.For(pluginType).Use(type);

    I have compare the results of WhaDoIHave() before and after, and I can see a only one difference.

    Old result:

    ===============================================================================================================================================================================================================================================================================
    PluginType                  Namespace                          Lifecycle     Description                                                                                                                                               Name                                    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    ISession                    Paf.Application.Session            Transient     Paf.Application.Session ('Paf.Application.Session, Paf.Modules.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null')                              Paf.Application.Session,... (Default)
    ===============================================================================================================================================================================================================================================================================
    

    New result:

    ===============================================================================================================================================================================================================================================================================
    PluginType                  Namespace                          Lifecycle     Description                                                                                                                                               Name                                    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    ISession                    Paf.Application.Session            Singleton     Paf.Application.Session                                                                                                                                (Default)                               
    ===============================================================================================================================================================================================================================================================================
    

    The new result is Ok, is Singleton, the diference is only in description, I think is no important think.

    UPDATE

    According to conversation with Steve in the comments of my question, I have decide not use ITransient and ISingleton interfaces. I have decided to use Attributes in the implementation classes.

    I've created 2 attributes:

    [AttributeUsage(AttributeTargets.Class)]
    public class SingletonLifecycleAttribute : Attribute
    {
    }
    [AttributeUsage(AttributeTargets.Class)]
    public class TransientLifecycleAttribute : Attribute
    {
    }
    

    And I've assiged its to my classes:

    public interface ISession   {       int? UserId {get;set;}  }
    
    [SingletonLifecycle]
    public class Session : ISession {       public int? UserId {get;set;}   }   
    
    public interface IBookService { ... }
    
    [TransientLifecycle]
    public class BookService : IBookService { public BookService(ISession session) { ... }  }
    

    Then I have modified my convention:

    public class BasicConvention : ConfigurableRegistrationConvention
    {
        public override void Process(Type type, Registry registry)
        {
            if (!type.IsConcrete()) return;
    
            var pluginType = FindPluginType(type);
            if (pluginType != null && type.HasConstructors())
            {
                var ci = registry.For(pluginType).Use(type);
                if (type.GetCustomAttributes(true).FirstOrDefault(a => a is TransientLifecycleAttribute) != null)
                    ci.Transient();
                if (type.GetCustomAttributes(true).FirstOrDefault(a => a is SingletonLifecycleAttribute) != null)
                    ci.Singleton();             
                ConfigureFamily(registry.For(pluginType));
            }
        }
    
        public virtual Type FindPluginType(Type concreteType)
        {
            var interfaceName = "I" + concreteType.Name;
            return concreteType.GetInterfaces().FirstOrDefault(t => t.Name == interfaceName);
        }
    }
    

    I think all is now fine and better ;) Thanks Steve!