Search code examples
c#dependency-injectionunity-containercastle-windsor

Register Interface in Unity Container based on Marker interface into different Lifetime


I have multiple projects in my solution:

Core
Repository
Service
WebAPi

My Unity registration is on WebAPi Project.

What I am trying to achieve is something I have done in Castle Windsor in the past, now I want to do that via Unity: register interface using convention https://github.com/castleproject/Windsor/blob/master/docs/registering-components-by-conventions.md

I have two Marker interfaces.

public interface ISingletonDependency{}
public interface ITransientDependency{}

For classes in my Repository or Service I do:

public interface IPersonRepository:ISingletonDependency{
...
...

}

public class PersonRepository: IPersonRepository{
...
...
...
}

In my service class:

public interface IPersonService:ISingletonDependency{
...
...    
}

public class PersonService: IPersonService{
...
...
...
}

I do this for all of my classes in similar way for DI and during registration I used to do:

container.Register(
    Classes.NamedAssembly("SolutionName.Repository")
        .BasedOn<ISingletonDependency>()
        .WithService.FromInterface().LifestyleSingleton()
);


container.Register(
    Classes.NamedAssembly("SolutionName.Service")
        .BasedOn<ISingletonDependency>()
        .WithService.FromInterface().LifestyleSingleton()
);


container.Register(
    Classes.NamedAssembly("SolutionName.Repository")
        .BasedOn<ITransientDependency>()
        .WithService.FromInterface().LifestyleTansient()
);


container.Register(
    Classes.NamedAssembly("SolutionName.Service")
        .BasedOn<ITransientDependency>()
        .WithService.FromInterface().LifestyleTansient()
);

This way I do not need to do this for each and every one, I saw approach in Unity but it does based on naming convention and you cannot specify singleton or transient lifestyle separately.

https://blogs.msdn.microsoft.com/agile/2013/03/12/unity-configuration-registration-by-convention/

Is there a way to do what you can do in Castle Windsor, as in the above example I gave, based on marker interfaces?


Solution

  • Given:

    public interface ISingletonDependency { }
    public interface ITransientDependency { }
    
    public interface IPersonRepository : ISingletonDependency { }
    public class PersonRepository : IPersonRepository { }
    
    public interface IPersonService : ITransientDependency { }
    public class PersonService : IPersonService { }
    

    Then you could register the types using:

    var container = new UnityContainer();
    
    container.RegisterTypes(
        AllClasses.FromAssembliesInBasePath().Where(
            // Could also match on namespace etc. here
            t => t.Assembly.GetName().Name.StartsWith("SolutionName")),
        WithMappings.FromMatchingInterface,
        WithName.Default,
        WithLifetime.FromMarkerInterface);
    
    // Singleton
    Debug.Assert(ReferenceEquals(container.Resolve<IPersonRepository>(), container.Resolve<IPersonRepository>()));
    
    // Transient
    Debug.Assert(!ReferenceEquals(container.Resolve<IPersonService>(), container.Resolve<IPersonService>()));
    

    WithLifetime.FromMarkerInterface is a custom convention that uses the marker interface to select the correct LifetimeManager:

    public static class WithLifetime
    {
        public static LifetimeManager FromMarkerInterface(Type type)
        {
            if (typeof(ISingletonDependency).IsAssignableFrom(type))
            {
                return new ContainerControlledLifetimeManager();
            }
    
            return new TransientLifetimeManager();
        }
    }
    

    In this case everything is transient unless it is an ISingletonDependency but you could make the rules more explicit (e.g. throw if no valid marker interface is found).

    If you could narrow your where clause in AllClasses.FromAssembliesInBasePath().Where then you could probably get away with a one liner:

    container.RegisterTypes(
    AllClasses.FromAssembliesInBasePath().Where(
        t => t.Namespace.StartsWith("SolutionName.Repository") || 
             t.Namespace.StartsWith("SolutionName.Service")),
        WithMappings.FromMatchingInterface,
        WithName.Default,
        WithLifetime.FromMarkerInterface);