StructureMap has rich support for registering types by scanning assemblies and applying conventional registrations. Between scanning and default conventions, configurations are often just a few lines.
Also see Type Scanning Diagnostics for help in understanding the assembly scanning behavior in your system.
Registry.Scan()
Assembly scanning operations are defined by the Registry.Scan()
method demonstrated below:
public class BasicScanning : Registry
{
public BasicScanning()
{
Scan(_ =>
{
// Declare which assemblies to scan
_.Assembly("StructureMap.Testing");
_.AssemblyContainingType<IWidget>();
// Filter types
_.Exclude(type => type.Name.Contains("Bad"));
// A custom registration convention
_.Convention<MySpecialRegistrationConvention>();
// Built in registration conventions
_.AddAllTypesOf<IWidget>().NameBy(x => x.Name.Replace("Widget", ""));
_.WithDefaultConventions();
});
}
}
Please note (because I've been asked this several times over the years) that each call to Registry.Scan()
is an entirely atomic operation that has no impact on previous or subsequent calls.
Any given call to Registry.Scan()
consists of three different things:
- One or more assemblies to scan for types
- One or more registration conventions
- Optionally, set filters to only include certain types or exclude other types from being processed by the scanning operation
Scan the Calling Assembly
One of the easiest ways to register types is by scanning the assembly your registry is placed in.
Note if you have other registries, StructureMap will not automatically find them.
[Fact]
public void scan_but_ignore_registries_by_default()
{
Scan(x => { x.TheCallingAssembly(); });
TestingRegistry.WasUsed.ShouldBeFalse();
}
Note that this method is an extension method in the StructureMap.Net4 assembly and cannot be used if you target PCL compliance.
Scan for Registries
StructureMap can automatically include other registries with theLookForRegistries
method.
[Fact]
public void Search_for_registries_when_explicitly_told()
{
Scan(x =>
{
x.TheCallingAssembly();
x.LookForRegistries();
});
TestingRegistry.WasUsed.ShouldBeTrue();
}
As of 4.0, this operation is now recursive and StructureMap has always been idempotent about adding Registry types
Search for Assemblies on the File System
StructureMap provides facilities for registering types by finding assemblies in the application bin path:
[Fact]
public void scan_all_assemblies_in_a_folder()
{
Scan(x => x.AssembliesFromPath(assemblyScanningFolder));
shouldHaveFamilyWithSameName<IInterfaceInWidget5>();
shouldHaveFamilyWithSameName<IWorker>();
shouldNotHaveFamilyWithSameName<IDefinedInExe>();
}
[Fact]
public void scan_all_assemblies_in_a_folder_with_path_filter()
{
Scan(x => x.AssembliesFromPath(assemblyScanningFolder, path => !path.Contains("Widget5")));
shouldNotHaveFamilyWithSameName<IInterfaceInWidget5>();
shouldHaveFamilyWithSameName<IWorker>();
shouldNotHaveFamilyWithSameName<IDefinedInExe>();
}
[Fact]
public void scan_all_assemblies_in_application_base_directory()
{
Scan(x => x.AssembliesFromApplicationBaseDirectory());
shouldHaveFamilyWithSameName<IInterfaceInWidget5>();
shouldHaveFamilyWithSameName<IWorker>();
shouldNotHaveFamilyWithSameName<IDefinedInExe>();
}
[Fact]
public void scan_all_assemblies_in_application_base_directory_with_path_filter()
{
Scan(x => x.AssembliesFromApplicationBaseDirectory(path => !path.Contains("Widget5")));
shouldNotHaveFamilyWithSameName<IInterfaceInWidget5>();
shouldHaveFamilyWithSameName<IWorker>();
shouldNotHaveFamilyWithSameName<IDefinedInExe>();
}
Do note that StructureMap 4.0 does not search for .exe
files in the assembly search. The StructureMap team felt this was
problematic and "nobody would ever actually want to do that." We were wrong, and due to many user requests, you can now
opt in to scanning .exe
files with a new public method on AssemblyScanner
shown below:
[Fact]
public void scan_all_assemblies_in_a_folder_including_exe()
{
Scan(x => x.AssembliesAndExecutablesFromPath(assemblyScanningFolder));
shouldHaveFamilyWithSameName<IInterfaceInWidget5>();
shouldHaveFamilyWithSameName<IWorker>();
shouldHaveFamilyWithSameName<IDefinedInExe>();
}
[Fact]
public void scan_all_assemblies_in_a_folder_including_exe_with_path_filter()
{
Scan(x => x.AssembliesAndExecutablesFromPath(assemblyScanningFolder, path => !path.Contains("Widget5")));
shouldNotHaveFamilyWithSameName<IInterfaceInWidget5>();
shouldHaveFamilyWithSameName<IWorker>();
shouldHaveFamilyWithSameName<IDefinedInExe>();
}
[Fact]
public void scan_all_assemblies_in_application_base_directory_including_exe()
{
Scan(x => x.AssembliesAndExecutablesFromApplicationBaseDirectory());
shouldHaveFamilyWithSameName<IInterfaceInWidget5>();
shouldHaveFamilyWithSameName<IWorker>();
shouldHaveFamilyWithSameName<IDefinedInExe>();
}
[Fact]
public void scan_all_assemblies_in_application_base_directory_including_exe_with_path_filter()
{
Scan(x => x.AssembliesAndExecutablesFromApplicationBaseDirectory(path => !path.Contains("Widget5")));
shouldNotHaveFamilyWithSameName<IInterfaceInWidget5>();
shouldHaveFamilyWithSameName<IWorker>();
shouldHaveFamilyWithSameName<IDefinedInExe>();
}
Do be aware that while this technique is very powerful for extensibility, it's been extremely problematic for some folks in the past. The StructureMap team's recommendation for using this feature is to:
- Make sure you have some kind of filter on the assemblies scanned for performance and predictability reasons. Either a naming convention or filter by an assembly attribute to narrow where StructureMap looks
- Get familiar with the new type scanning diagnostics introduced in 4.0;-)
Behind the scenes, StructureMap is using the Assembly.GetExportedTypes()
method from the .Net CLR to find types and this
mechanism is very sensitive to missing dependencies. Again, thanks to the new type scanning diagnostics,
you now have some visibility into assembly loading failures that used to be silently swallowed internally.
Excluding Types
StructureMap also makes it easy to exclude types, either individually or by namespace. The following examples also show how StructureMap can register an assembly by providing a type within that assembly.
Excluding additional types or namespaces is as easy as calling the corresponding method again.
[Fact]
public void use_a_single_exclude_of_type()
{
Scan(x =>
{
x.AssemblyContainingType<ITypeThatHasAttributeButIsNotInRegistry>();
x.ExcludeType<ITypeThatHasAttributeButIsNotInRegistry>();
});
shouldHaveFamily<IInterfaceInWidget5>();
shouldNotHaveFamily<ITypeThatHasAttributeButIsNotInRegistry>();
}
[Fact]
public void use_a_single_exclude2()
{
Scan(x =>
{
x.AssemblyContainingType<ITypeThatHasAttributeButIsNotInRegistry>();
x.ExcludeNamespace("StructureMap.Testing.Widget5");
});
shouldNotHaveFamily<IInterfaceInWidget5>();
shouldNotHaveFamily<ITypeThatHasAttributeButIsNotInRegistry>();
}
[Fact]
public void use_a_single_exclude3()
{
Scan(x =>
{
x.AssemblyContainingType<ITypeThatHasAttributeButIsNotInRegistry>();
x.ExcludeNamespaceContainingType<ITypeThatHasAttributeButIsNotInRegistry>();
});
shouldNotHaveFamily<IInterfaceInWidget5>();
shouldNotHaveFamily<ITypeThatHasAttributeButIsNotInRegistry>();
}
Custom Registration Conventions
It's just not possible (or desirable) for StructureMap to include every possible type of auto registration
convention users might want, but that's okay because StructureMap allows you to create and use your own
conventions through the IRegistrationConvention
interface:
public interface IRegistrationConvention
{
void ScanTypes(TypeSet types, Registry registry);
}
Let's say that you'd like a custom convention that just registers a concrete type against all the interfaces
that it implements. You could then build a custom IRegistrationConvention
class like the following example:
public interface IFoo
{
}
public interface IBar
{
}
public interface IBaz
{
}
public class BusyGuy : IFoo, IBar, IBaz
{
}
// Custom IRegistrationConvention
public class AllInterfacesConvention : IRegistrationConvention
{
public void ScanTypes(TypeSet types, Registry registry)
{
// Only work on concrete types
types.FindTypes(TypeClassification.Concretes | TypeClassification.Closed).Each(type =>
{
// Register against all the interfaces implemented
// by this concrete class
type.GetInterfaces().Each(@interface => registry.For(@interface).Use(type));
});
}
}
[Fact]
public void use_custom_registration_convention()
{
var container = new Container(_ =>
{
_.Scan(x =>
{
// You're probably going to want to tightly filter
// the Type's that are applicable to avoid unwanted
// registrations
x.TheCallingAssembly();
x.IncludeNamespaceContainingType<BusyGuy>();
// Register the custom policy
x.Convention<AllInterfacesConvention>();
});
});
container.GetInstance<IFoo>().ShouldBeOfType<BusyGuy>();
container.GetInstance<IBar>().ShouldBeOfType<BusyGuy>();
container.GetInstance<IBaz>().ShouldBeOfType<BusyGuy>();
}
Composite Decorators
Sometimes you need to have many implementations of an interface, and to have all of them used together, which usually involves creating another implementation of the interface which takes all the others as dependencies.
If you try this in StructureMap, it will throw an exception as it detects a bi-directional dependency. This is because by default an IEnumerable<T>
constructor parameter will be populated with all instances of T, which would include the implementation with the IEnumerable<T>
parameter!
We can use a custom IRegistrationConvention
however to tell StructureMap that when it constructs a Composite, it should only include implementations which are not the Composite
:
public interface ISomething
{
IEnumerable<string> GetNames();
}
public class One : ISomething
{
public IEnumerable<string> GetNames() => new[] { "one" };
}
public class Two : ISomething
{
public IEnumerable<string> GetNames() => new[] { "two" };
}
public class SomethingComposite : ISomething
{
private readonly IEnumerable<ISomething> _others;
public SomethingComposite(IEnumerable<ISomething> others)
{
_others = others;
}
public IEnumerable<string> GetNames() => _others.SelectMany(other => other.GetNames());
}
// Custom IRegistrationConvention
public class CompositeDecorator<TComposite, TDependents> : IRegistrationConvention
where TComposite : TDependents
{
public void ScanTypes(TypeSet types, Registry registry)
{
var dependents = types
.FindTypes(TypeClassification.Concretes)
.Where(t => t.CanBeCastTo<TDependents>() && t.HasConstructors())
.Where(t => t != typeof(TComposite))
.ToList();
registry.For<TDependents>()
.Use<TComposite>()
.EnumerableOf<TDependents>().Contains(x => dependents.ForEach(t => x.Type(t)));
}
}
[Fact]
public void use_custom_registration_convention_2()
{
var container = new Container(_ =>
{
_.Scan(x =>
{
x.AssemblyContainingType<ISomething>();
x.Convention<CompositeDecorator<SomethingComposite, ISomething>>();
});
});
var composite = container.GetInstance<ISomething>();
composite.ShouldBeOfType<SomethingComposite>();
composite.GetNames().ShouldBe(new[] { "one", "two" }, ignoreOrder: true);
}
The Default ISomething/Something Convention
The "default" convention simply tries to connect concrete classes to interfaces using the I[Something]/[Something] naming convention as shown in this sample:
public interface ISpaceship { }
public class Spaceship : ISpaceship { }
public interface IRocket { }
public class Rocket : IRocket { }
[Fact]
public void default_scanning_in_action()
{
var container = new Container(_ =>
{
_.Scan(x =>
{
x.Assembly("StructureMap.Testing");
x.WithDefaultConventions();
});
});
container.GetInstance<ISpaceship>().ShouldBeOfType<Spaceship>();
container.GetInstance<IRocket>().ShouldBeOfType<Rocket>();
}
The StructureMap team contains some VB6 veterans who hate Hungarian Notation, but can't shake the "I" nomenclature.
Registering the Single Implementation of an Interface
To tell StructureMap to automatically register any interface that only has one concrete implementation, use this method:
public interface ISong { }
public class TheOnlySong : ISong { }
[Fact]
public void only_implementation()
{
var container = new Container(_ =>
{
_.Scan(x =>
{
x.TheCallingAssembly();
x.SingleImplementationsOfInterface();
});
});
container.GetInstance<ISong>()
.ShouldBeOfType<TheOnlySong>();
}
Register all Concrete Types of an Interface
To add all concrete types that can be cast to a named plugin type, use this syntax:
public interface IFantasySeries { }
public class WheelOfTime : IFantasySeries { }
public class GameOfThrones : IFantasySeries { }
public class BlackCompany : IFantasySeries { }
[Fact]
public void register_all_types_of_an_interface()
{
var container = new Container(_ =>
{
_.Scan(x =>
{
x.TheCallingAssembly();
x.AddAllTypesOf<IFantasySeries>();
// or
x.AddAllTypesOf(typeof(IFantasySeries))
.NameBy(type => type.Name.ToLower());
});
});
container.Model.For<IFantasySeries>()
.Instances.Select(x => x.ReturnedType)
.OrderBy(x => x.Name)
.ShouldHaveTheSameElementsAs(typeof(BlackCompany), typeof(GameOfThrones), typeof(WheelOfTime));
}
Note, "T" does not have to be an interface, it's all based on the ability to cast a concrete type to the "T"
Generic Types
See Generic Types for an example of using the ConnectImplementationsToTypesClosing
mechanism for generic types.
Register Concrete Types against the First Interface
The last built in registration convention is a mechanism to register all concrete types that implement at least one interface against the first interface that they implement.
container = new Container(x =>
{
x.Scan(o =>
{
o.TheCallingAssembly();
o.RegisterConcreteTypesAgainstTheFirstInterface();
o.Exclude(t => t.CanBeCastTo(typeof(IGateway)));
});
});