Fork me on GitHub

Auto-Registration and Conventions Edit on GitHub


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:

  1. One or more assemblies to scan for types
  2. One or more registration conventions
  3. 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:

  1. 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
  2. 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)));
    });
});