Index

So far I’ve shown enough to build a working DI system. By registering concrete types for your abstractions you can fully configure the container. However it’s unlikely to have escaped your notice that doing so is a not inconsequential amount of work in anything but small systems. StructureMap provides a solution to this in the form of auto registration.

Auto registration takes advantage of the fact that the structure of most types that we wish to register in a container follows certain patterns or conventions. By taking advantage of these conventions we can locate and register types with the container without having to specify them individually. Further if we adhere to the conventions for new types they will be registered automatically without any additional work. This reduces our configuration to specification of the conventions and individual registration for those few types for which the conventions are inapplicable.

We can start with one of the simplest and most common conventions. There are a wide variety of instances that adhere to the simple pattern of having a concrete class that implements an interface that shares the same name with the addition of the standard .NET I prefix. This abstraction breaks the coupling between the implementation and anything that may use it but for most cases there is not a need to have multiple implementations of the abstraction. They are generally transient instances and any state they carry does not last between uses. A stateless service is a typical example.

Consider an assembly that contains many services, among them ExampleService which implements IExampleService. Using auto registration we can locate and register into the StructureMap container all of these services that adhere to our convention. The code to do so will look like:

ObjectFactory.Initialize(initialise =>
    {
        initialise.Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
            });
    });

This code utilises the type scanner to perform registration. The Scan method takes a delegate (of type Action<IAssemblyScanner>) for which we can supply a lambda. In the lambda we do two things. The first is instruct the scanner where it should be scanning. In this case we specify the assembly in which this code is located by invoking the method TheCallingAssembly. The then tell StructureMap to apply its default conventions. The StructureMap default conventions are conveniently equivalent to the convention described above.

This method may be used to register large numbers of types and is probably the most common usage of the scanner. It will cover most cases but the scanner is capable of significantly more than this. Let’s consider another convention. We have some kind of plug-in system where all plug-ins must implement a common interface. We’d like to register all of our plug-ins with the container but we won’t (and likely can’t) have the close correspondence between the name of the plug-in interface and that of the types that implement it. We can use another convention in the scanner to handle this for us. We can add a call to AddAllTypesOf<T> to our scanner. This will cause it to register any class that implements the specified generic type. For an interface of type IPlugin this would look like:

ObjectFactory.Initialize(initialise =>
    {
        initialise.Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
                scan.AddAllTypesOf<IPlugin>();
            });
    });

(We have kept the call to WithDefaultConventions here but it is not necessary to register the IPlugin implementations)

This supports the case where IPlugin is non-generic. We may also encounter situations where we wish to register anything that implements a generic interface. Let’s assume that for some (probably crazy) reason our system also has a plugin interface IPlugin<T> for which we wish to register any implementations. StructureMap provides inbuilt support for this scenario using the ConnectImplementationsToTypesClosing method.

Before I show the code, a quick diversion through the basics of open and closed generic types. The type IPlugin<T> is an open generic type for which the type parameter is not specified. We cannot (for all practical purposes) have an instance of this type as it is incomplete. We can obtain the type itself using typeof(IPlugin<>). If we want an instance we must get a closed type for which the type parameters have been specified. IPlugin<int> would be an example and we may treat this type for the most part as we would a non-generic type. We can through reflection determine the open generic type for a closed type and create a closed type from an open type. This is annoying and fortunately StructureMap handles it all for us. (We can also have partially closed types, but that’s outside the scope of this post)

The code to register all the concrete classes that implement (and therefore close) our generic interface looks like:

ObjectFactory.Initialize(initialise =>
    {
        initialise.Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.ConnectImplementationsToTypesClosing(typeof(IPlugin<>));
            });
    });

All of these examples scan the assembly in which the code itself resides. This is the most common option but there are cases where scanning of other assemblies may be desirable. This includes cases where you wish to register instances from an assembly you do not control and where you may wish to scan a directory for plug-in or extension assemblies that are unknown at compilation time. To handle these cases StructureMap provides a number of options other that TheCallingAssembly for specifying where it should scan. These include:

  • AssembliesFromApplicationBaseDirectory scans every assembly in (surprisingly) the applications base directory. It has an overload that supports providing a filter to exclude assemblies if desired.
  • AssembliesFromPath scans every assembly in the directory with the specified path. It also has an overload for providing a filter.
  • Assembly takes an individual assembly to be scanned (as either an instance of the Assembly type or by name)
  • AssemblyContainingType supports determining the assembly to be scanned by specification of a type within the assembly (as either a type parameter or instance of the Type class)

It is also possible to control how the scanner treats types within an assembly using Exclude or Include rules. Exclude rules allow patterns to be specified that will prevent any matching type from being registered. Any type matching one or more exclude rules will be ignored by the scanner:

  • Exclude takes a filter delegate than allows individual types to be excluded from the scanner.
  • ExcludeNamespace takes a string defining a namespace. The scanner will exclude all types in this namespace.
  • ExcludeNamespaceContainingType has a type parameter. Any types sharing the namespace of the specified type will be excluded.
  • ExcludeType takes a generic parameter. The specified type for this parameter will be excluded.

Include rules support patterns that all types to be registered must adhere to. A type must match at least one include rule to be registered:

  • Include takes a filter delegate. Any type matching this filter will be included.
  • IncludeNamespace takes a string defining a namespace. Any type in this namespace is included.
  • IncludeNamespaceContainingType has a type parameter. Any type sharing a namespace with this type will be included.

Inside a single Scan invocation you should generally use either all Exclude or all Include statements.

Scanning allows us to significantly simplify the configuration of the container. If you have a system with more than one assembly you may be thinking that you will need to specify the assemblies to scan or invoke StructureMap configuration manually inside each assembly. StructureMap provides a better mechanism for doing so in the form of Registries which will be the subject of the next post in this series.