IoC comparison: Autoregistration in StructureMap
The StructureMap IoC container has guidance on using Autoregistration which I followed. Earlier I put StructureMap through its paces for basic IoC.
For Autoregistration, StructureMap uses lambdas, even nested lambdas. The interface is not fluent. The basics are quite simple. StructureMap feels a bit like Ninject under the hood, and there are similarities – both do automatic resolution of unregistered types, and both need custom code to do more complicated Autoregistration scenarios. Both are perfectly usable with a bit customisation.
Basic autoregistration is done as follows:
x.Scan(y =>
{
y.WithDefaultConventions();
y.AssemblyContainingType(typeof(BusinessProcess));
}
Adding all types that implement an interface, is simple, as is excluding one of them:
y.AddAllTypesOf<IValidator>(); y.ExcludeType<FailValidator>();
I didn't find a simple way to register some types as singletons to I went into the more advanced features to do that. Somewhat like Ninject, there are registration conventions classes which implement the interface IRegistrationConvention, which has only one method void Process(Type type, Registry registry); called for each type.
There is a default registration convention introduced by
x.Scan(y =>
{
y.WithDefaultConventions();
....
}
This convention will match type names to interface names –so it will automatically register the class Foo as an implementation of IFoo, assuming that the type and interface names match.You can also do RegisterConcreteTypesAgainstTheFirstInterface which does what it says, and SingleImplementationsOfInterface which registers types with are the only implementation of an interface.
Like Ninject, you can supply custom code here, using y.With(registrationConvention); Unlike in Ninject, the registrationConvention instance also configures that instance – e.g. specifies if it is a singleton or not.
I made use of this, and wrote a custom registration convention along the lines of the one for Ninject, with the same rules - if a type has interfaces, register it for those interfaces. If not, register it for itself. System interfaces (e.g. IDisposable) are ignored.
But in this case it is configurable to register a set of types as singletons. I had a go at a simple fluent interface and it worked quite well. The code for this in the registration convention is in CustomRegistrationConvention:
private readonly List<Type> singletonTypes = new List<Type>();
public CustomRegistrationConvention WithSingleton<T>()
{
singletonTypes.Add(typeof(T));
return this;
}
Note that the registration convention returns itself so that these can be chained.
Using it is quite simple:
CustomRegistrationConvention registrationConvention = new CustomRegistrationConvention() .WithSingleton<ICustomerService>().WithSingleton<IOrderService>();
The code is in the repository on github here, but is as below:
namespace IoCComparison.AutoregisterTests
{
using System.Linq;
using AutoregisteredClasses.Interfaces;
using AutoregisteredClasses.Services;
using AutoregisteredClasses.Validators;
using IoCComparison.AutoregisterTests.StructureMapExtensions;
using NUnit.Framework;
using StructureMap;
/// <summary>
/// http://structuremap.net/structuremap/ScanningAssemblies.htm
/// </summary>
public class StructureMapTest
{
[Test]
public void CanMakeBusinessProcess()
{
ObjectFactory.Initialize(x =>
x.Scan(y =>
{
// y.RegisterConcreteTypesAgainstTheFirstInterface(); is equivalent in this case to...
y.WithDefaultConventions();
y.AssemblyContainingType(typeof(BusinessProcess));
}));
BusinessProcess businessProcess = ObjectFactory.GetInstance<BusinessProcess>();
Assert.IsNotNull(businessProcess);
}
[Test]
public void CanMakeSingletonInstance()
{
// must use a custom RegistrationConvention to set some as singletons
// Which is a bit complex
CustomRegistrationConvention registrationConvention = new CustomRegistrationConvention()
.WithSingleton<BusinessProcess>();
ObjectFactory.Initialize(x =>
x.Scan(y =>
{
y.With(registrationConvention);
y.AssemblyContainingType(typeof(BusinessProcess));
}));
BusinessProcess businessProcess1 = ObjectFactory.GetInstance<BusinessProcess>();
BusinessProcess businessProcess2 = ObjectFactory.GetInstance<BusinessProcess>();
Assert.AreSame(businessProcess1, businessProcess2);
}
[Test]
public void CanMakeTransientInstance()
{
ObjectFactory.Initialize(x =>
x.Scan(y =>
{
y.WithDefaultConventions();
y.AssemblyContainingType(typeof(BusinessProcess));
}));
BusinessProcess businessProcess1 = ObjectFactory.GetInstance<BusinessProcess>();
BusinessProcess businessProcess2 = ObjectFactory.GetInstance<BusinessProcess>();
Assert.AreNotSame(businessProcess1, businessProcess2);
}
[Test]
public void CanMakeTransientInstanceWithSingletonDependencies()
{
CustomRegistrationConvention registrationConvention = new CustomRegistrationConvention()
.WithSingleton<ICustomerService>().WithSingleton<IOrderService>();
ObjectFactory.Initialize(x =>
x.Scan(y =>
{
y.With(registrationConvention);
y.AssemblyContainingType(typeof(BusinessProcess));
}));
BusinessProcess businessProcess1 = ObjectFactory.GetInstance<BusinessProcess>();
BusinessProcess businessProcess2 = ObjectFactory.GetInstance<BusinessProcess>();
Assert.AreNotSame(businessProcess1, businessProcess2);
Assert.AreSame(businessProcess1.CustomerService, businessProcess2.CustomerService);
Assert.AreSame(businessProcess1.OrderService, businessProcess2.OrderService);
}
[Test]
public void CanGetAllValidators()
{
ObjectFactory.Initialize(
x => x.Scan(
y =>
{
y.AddAllTypesOf<IValidator>();
y.AssemblyContainingType(typeof(IValidator));
}));
var validators = ObjectFactory.GetAllInstances<IValidator>();
Assert.IsNotNull(validators);
Assert.AreEqual(3, validators.Count());
}
[Test]
public void CanFilterOutValidatorRegistrations()
{
ObjectFactory.Initialize(
x => x.Scan(
y =>
{
y.AddAllTypesOf<IValidator>();
y.ExcludeType<FailValidator>();
y.AssemblyContainingType(typeof(IValidator));
}));
// excluding the FailValidator should leave 2 of them
var validators = ObjectFactory.GetAllInstances<IValidator>();
Assert.IsNotNull(validators);
Assert.AreEqual(2, validators.Count());
}
}
}
And the custom registration convention is in github here, but is as below:
namespace IoCComparison.AutoregisterTests.StructureMapExtensions
{
using System;
using System.Linq;
using System.Collections.Generic;
using StructureMap.Graph;
using StructureMap.Configuration.DSL;
using StructureMap.TypeRules;
using IoCComparison.AutoregisterTests.TypeExtensions;
public class CustomRegistrationConvention : IRegistrationConvention
{
private readonly List<Type> singletonTypes = new List<Type>();
public CustomRegistrationConvention WithSingleton<T>()
{
singletonTypes.Add(typeof(T));
return this;
}
public void Process(Type type, Registry registry)
{
if (!type.IsConcrete() || !type.CanBeCreated())
{
return;
}
IList<Type> interfaceTypes = type.GetInterfaces()
.Where(t => !t.IsSystemType()).ToList();
if (interfaceTypes.Count > 0)
{
foreach (Type interfaceType in interfaceTypes)
{
Register(registry, interfaceType, type);
}
}
else
{
// if the type has no interfaces - bind to self
Register(registry, type, type);
}
}
private void Register(Registry registry, Type sourceType, Type targetType)
{
if (ShouldRegisterAsSingleton(sourceType))
{
registry.For(sourceType).Singleton().Use(targetType);
}
else
{
registry.For(sourceType).Use(targetType);
}
}
private bool ShouldRegisterAsSingleton(Type type)
{
return singletonTypes.Contains(type);
}
}
}
1 Comment
Gregor said
Hi Anthony
I read your article on Windsor and how one
can use it to resolve concrete types and others including structure map
can be confusing when resolving instances not Interfaces.
I am resolving concreate types in structure map and it is quite straightforward.
But I do agree with you , initially I believed i had single instances of objects, when I actually had multiple instances of the objects.
I do it as per below , by injecting a specific instance/ back into the container to be used for Interfaces or concrete instances. Since I am fairly new to structure map, I am not sure this is the "standard" way to do it.
So any comments are appreciated.
public class MyObjectFactory
{
private IContainer ioc;
public MyObjectFactory()
{ ioc = new Container(
x =>
{
x.AddRegistry<ModelRegistry>();
});
}
public CampaignRepository CreateCampaignRepository()
{
CampaignRepository instance = ioc.GetInstance<CampaignRepository>();
ioc.Inject(typeof(CampaignRepository), instance);
ioc.Inject(typeof(ICampaignRepository), instance);
return instance;
}
}