concepts

InjectMe is very much like all the other injection frameworks out there. The difference lies in the implementation, usability and extensibility.

The starting point and the main type that binds everything together is the container. Services are identified by a service identity and are registered in the container using activators. It is possible to manually register activators in the container but it is recommended to use the container configuration which can be accessed either when the container is created using the static Create method or by calling Configure on the container instance.

When resolving a service, either directly using the service locator or indirectly as an injected constructor argument or property, it is the responsibility of the registered activator for that service to actually activate an instance. This usually means that an instance is located in a service cache or constructed using a constructor, but the activation can be done in any way depending on the activator being used.

service identity

The service identity is what uniquely identifies a specific service. It consists of a type and a name, and it makes it possible to handle more than one version of the same type.

It is most often used indirectly. For instance when a service is registered or resolved using a type and an optional name, a service identity is created and passed around internally.

var container = Container.Create(c =>
{
    // ServiceIdentity { ServiceType: IFoo, ServiceName: null }
    c.RegisterSingleton<IFoo, DefaultFoo>();

    // ServiceIdentity { ServiceType: IFoo, ServiceName: "Special" }
    c.RegisterSingleton<IFoo, SpecialFoo>("Special");
});

// ServiceIdentity { ServiceType: IFoo, ServiceName: null }
var defaultFoo = container.ServiceLocator.Resolve<IFoo>();

// ServiceIdentity { ServiceType: IFoo, ServiceName: "Special" }
var specialFoo = container.ServiceLocator.Resolve<IFoo>("Special");

resolving

Once the container has been configured and activators have been registered it is possible to resolve a service.

Resolving a service can be done explicitly, using the service locator, or implicitly, during construction using constructor or property injection.

The steps taken when a service is resolved is:

  1. Create a service identity based on requested service type and service name.
  2. Get the registered activator group for the service type.
  3. Get an activator for the service identity from the activator group.
    See the DefaultActivatorGroup for special cases.
  4. Try to create a dynamic activator for the service if no registered activator was found.
    See activation for more information.
  5. Ask the activator to activate an instance.
  6. Return the activated instance.

prefix resolution

When using prefix resolution the name of a constructor argument or a property is considered when looking up a service. If the arguments name ends with the type name then the prefix of the argument name will be used as service name when creating the service identity to resolve.

In this example an instance of SpecialFoo will be injected when constructing Bar because the constructor argument name 'specialBar' ends with the type name 'Bar' and starts with the service name 'special'.

// Define an interface to be injected
interface IFoo { }

// Define two classes that implements the interface
class DefaultFoo : IFoo { }
class SpecialFoo : IFoo { }

// Define a class that depends on an instance of the interface
interface IBar
{
    IFoo Foo { get; }
}

class Bar : IBar
{
    // Prefix resolution will notice the argument name ends
    // with the type name 'Foo' (the I is ignored).
    // It will then extract the prefix 'special' and use
    // that as service name when resolving the service
    public Bar(IFoo specialFoo)
    {
        Foo = specialFoo;
    }

    public IFoo Foo { get; private set; }
}

void main()
{
        var container = Container.Create(configuration =>
        {
            configuration.
                RegisterInstance(new ConstructionFactorySettings
                {
                    // Turn on prefix resolution
                    UsePrefixResolution = true
                }).
                Scan(scanner =>
                {
                    scanner.
                        // The IFoo services will be registered using names 'Default' and 'Special'
                        UseDefaultConventions().
                        ScanThisAssembly();
                });
        });

        var bar = container.ServiceLocator.Resolve<IBar>();
        var fooTypeName = bar.Foo.GetType().Name;

        Assert.AreEqual("SpecialFoo", fooTypeName);
}

activation

The reason for calling it activation and not creation, or something like that, is that it is entirely up to the activator to decide where the instance comes from; it could be a newly constructed object, an object loaded from a cache, a remoting reference, etc.

Different behaviors can be achieved by using different kind of activators. The most common activator is the ScopedActivator which is used when InjectMe is responsible for the construction of the service.

If a registered activator can't be found for a service InjectMe will try to create a dynamic activator on the fly. This is done when trying to activate specific types of services.

unbound type activation

InjectMe can create closed generic types based on registrations of unbound types.

// Register an unbound type.
// Fluent registration has to be used since it's not possible
// to use unbound types as generic type parameters.
var container = Container.Create(config =>
{
    config.
        Register(typeof(Foo<>)).
        AsSingleton();
});

// Create a closed type using an int as type parameter.
var foo = container.ServiceLocator.Resolve<Foo<int>>();

Note that fluent registration is needed because unbound types can't be used as type arguments.

array activation

When a service of type T[], IList<T>, ICollection<T> or IEnumerable<T> is requested, and the type isn't specifically registered, all registered services will be activated and the instances will be returned as an array.

// Register two different versions of the same type.
var container = Container.Create(c =>
{
    c.RegisterSingleton<IFoo, DefaultFoo>();
    c.RegisterSingleton<IFoo, SpecialFoo>("Special");
});

// Both fooArray and fooCollection will be an array of type IFoo[]
// which will contain one instance each of DefaultFoo and SpecialFoo.
var fooArray = container.ServiceLocator.Resolve<IFoo[]>();
var fooCollection = container.ServiceLocator.Resolve<ICollection<IFoo>>();

If there is an activator registered for the requested type then that activator will be used directly.

var container = Container.Create(c =>
{
    c.RegisterSingleton<IFoo[]>(() => new IFoo[0]);
    c.RegisterSingleton<IFoo, DefaultFoo>();
    c.RegisterSingleton<IFoo, SpecialFoo>("Special");
});

// fooArray will contain an empty array as created by the first registration.
var fooArray = container.ServiceLocator.Resolve<IFoo[]>();

delegate activation

When a service of type Func<T> is requested, and the type isn't specifically registered, a delegate will automatically be created. The actual service will be activated when the delagate is invoked.

// Register a normal service of type Foo
var container = Container.Create(c =>
{
    c.RegisterSingleton<Foo>();
});

// Resolve the delegate. Note that Foo will not be created here, only the delegate.
var fooDelegate = container.ServiceLocator.Resolve<Func<Foo>>();

// Invoking the delegate will create the Foo instance.
var foo = fooDelegate();

If there is an activator registered for the requested type then that activator will be used directly.

// Register a delegate for Foo.
var container = Container.Create(c =>
{
    c.RegisterSingleton<Func<Foo>>(() => () => new Foo());
    c.RegisterSingleton<Foo>(() =>
    {
        throw new NotSupportedException();
    });
});

// Resolve the delegate.
var fooDelegate = container.ServiceLocator.Resolve<Func<Foo>>();

// Calling the delegate will invoke our own registered delagate.
// It will not use the registration that throws an exception.
var foo = fooDelegate();

lazy activation

When a service of type Lazy<T> is requested, and the type isn't specifically registered, a lazy instance will automatically be created. The actual service will be activated when the value of the lazy is accessed.

// Register a normal service of type Foo
var container = Container.Create(c =>
{
    c.RegisterSingleton<Foo>();
});

// Resolve a Lazy<T>. Note that Foo will not be created here, only the Lazy<T>.
var lazyFoo = container.ServiceLocator.Resolve<Lazy<Foo>>();

// Accessing the value will create the Foo instance.
var foo = lazyFoo.Value;

If there is an activator registered for the requested type then that activator will be used directly.

// Register the Lazy<T> for Foo.
var container = Container.Create(c =>
{
    c.RegisterSingleton<Lazy<Foo>>(() => new Lazy<Foo>());
    c.RegisterSingleton<Foo>(() =>
    {
        throw new NotSupportedException();
    });
});

// Resolve the Lazy<T>.
var lazyFoo = container.ServiceLocator.Resolve<Lazy<Foo>>();

// Accessing the value will return our own created Foo.
// It will not use the registration that throws an exception.
var foo = lazyFoo.Value;

construction

Todo

construction factory

Todo

construction factory settings

The construction factory has certain functionality which can be modified. Changing these settings is done by registering an instance of ConstructionFactorySettings in the container.

For instance turning on property injection is done like this.

var factorySettings = new ConstructionFactorySettings
{
    UsePropertyInjection = true
};

var container = Container.Create(configuration =>
{
    configuration.
        // ....
        RegisterInstance(factorySettings);
});

constructor injection

property injection

scopes

Services can be registered using different scopes. The scope determines how the activated instances will be cached and reused.

singleton scope

Services registered as singletons will only ever have one instance created. An instance will be created the first time a service is resolved. A reference to the instance will be stored in a service cache and all subsequent resolving after that will return the same instance from the cache.

// Register a singleton service.
var container = Container.Create(c => c.RegisterSingleton<Foo>());

// foo1 and foo2 will reference the same instance.
var foo1 = container.ServiceLocator.Resolve<Foo>();
var foo2 = container.ServiceLocator.Resolve<Foo>();

Singletons should only be used for stateless types or where the state is thread safe.

transient scope

When a service is registered as transient a new instance will be created every time it is resolved.

// Register a transient service.
var container = Container.Create(c => c.RegisterTransient<Foo>());

// foo1 and foo2 will reference two different instances.
var foo1 = container.ServiceLocator.Resolve<Foo>();
var foo2 = container.ServiceLocator.Resolve<Foo>();

There is one exception to this and that is if the same service is referenced in different places in the same object hierarchy.

// Create three different hierarchical classes.
class Foo
{
    public Bar Bar { get; set; }
    public Baz Baz { get; set; }
}

class Bar
{
    public Baz Baz { get; set; }
}

class Baz
{
}

void Main()
{
    // Register the types as transient.
    var container = Container.Create(c =>
    {
        c.RegisterInstance(new ConstructionFactorySettings { UsePropertyInjection = true });
        c.RegisterTransient<Foo>();
        c.RegisterTransient<Bar>();
        c.RegisterTransient<Baz>();
    });

    // Resolve the top level service.
    var foo = container.ServiceLocator.Resolve<Foo>();

    // foo.Baz and foo.Bar.Baz will reference the same 
    // instance since they were resolved at the same time.
    Assert.AreSame(foo.Baz, foo.Bar.Baz);

    // Resolve the two sub services specifically in two different calls.
    var bar = container.ServiceLocator.Resolve<Bar>();
    var baz = container.ServiceLocator.Resolve<Baz>();

    // bar and baz will reference two different instances since
    // they were resolved in two different calls.
    Assert.AreNotSame(bar.Baz, baz);
}

http request scope

Services registered using an http request scope will be cached for the duration of an http request.

Activating a service several times during a single http request will return the same instance, but activating a service in different http requests will return different instances.

The service cache that keeps track of the activated services is stored in the HttpContext.Items dictionary.

http session scope

Services registered using an http session scope will be cached for the duration of a session.

Activating a service in different requests but in the same session will return the same instance, but activating a service in different sessions will return different instances.

The service cache that keeps track of the activated services is stored in the HttpContext.Session object.

registration

Todo

The following services are automatically registered when a container is created: IContainer, IServiceCache and IServiceLocator.

var container = Container.Create();
var serviceLocator = (IServiceLocator)container.Resolve(typeof(IServiceLocator));

scanning

Todo

fluent registration

Todo

extensibility

service loader

One easy way to extend the functionality is to use service loaders. A service loader is a type that is responsible for creating a specific service, similar to an activator but easier to use as an extension point.

It is a small interface with a single purpose of creating an instance:

interface IServiceLoader<out T>
{
    T LoadService();
}

See the using service loaders example for details about how to implement a service loader.

scan convention

Another way to extend the functionality is to create your own scan conventions. Scan conventions are used by the assembly scanner to register services. The scanner will iterate through all types in the assemblies passed to the scanner and pass them on to all registered scan conventions.

interface IScanConvention
{
    void ProcessType(IContainerConfiguration container, TypeInfo type);
}

A scan convention is more powerful than a service loader because there are no restrictions on the services that can be registered. The scan convention will be passed a TypeInfo and it can choose to either register that type as a service, or it can choose to register a completely different service based on the passed type.

See the custom scan conventions example for details about how to implement your own scan convention.

examples

usage 101

The most basic example of using InjectMe. Here we register two services manually, and then resolve the root instance.

interface IFoo { }
interface IBar { }

class Foo : IFoo
{
    IBar _bar;

    public Foo(IBar bar)
    {
        _bar = bar;
    }
}

class Bar : IBar
{
}

void Main()
{
    var container = Container.Create(c =>
    {
        c.RegisterSingleton<IBar, Bar>();
        c.RegisterTransient<IFoo, Foo>();
    });

    var foo = container.ServiceLocator.Resolve<IFoo>();
}

using service loaders

In this example we will implement functionality making it possible to inject IPrincipal and IIdentity from the current thread. This will be done using service loaders.

First we'll create service loaders for each type we want to inject.

class PrincipalServiceLoader : IServiceLoader<IPrincipal>
{
    public IPrincipal LoadService()
    {
        return Thread.CurrentPrincipal;
    }
}

class IdentityServiceLoader : IServiceLoader<IIdentity>
{
    private readonly IPrincipal _principal;

    // Service loaders are constructed the same as all other services,
    // which means we can inject other services as usual
    public IdentityServiceLoader(IPrincipal principal)
    {
        _principal = principal;
    }

    public IIdentity LoadService()
    {
        return _principal.Identity;
    }
}

Next, we'll create a type that needs the identity.

private interface IUserhandler
{
    IIdentity Identity { get; }
}

class Userhandler : IUserhandler
{
    public Userhandler(IIdentity identity)
    {
        Identity = identity;
    }

    public IIdentity Identity { get; private set; }
}

Finally, we have to register the service loaders and our handler.

var container = Container.Create(config =>
{
    config.Scan(scanner =>
    {
        scanner.
            // Default conventions are used to register our handler
            UseDefaultConventions().
            // This will tell the scanner to register all types that implements IServiceLoader<T>
            RegisterServiceLoaders().
            ScanThisAssembly();
    });
});

Then we can resolve our handler and use the injected identity.

var handler = container.ServiceLocator.Resolve<IUserhandler>();
var name = handler.Identity.Name;

custom scan conventions

In this example we are going to create a scan convention that will read app settings from a .config file and set the values on properties of a normal poco class which will then be injected.

First we'll add our settings to our .config file.

<appSettings>
    <add key="Ftp.HostName" value="ftp.server.com" />
    <add key="Ftp.Port" value="1337" />
</appSettings>

We're using the type name as our prefix and the property name as the suffix for the key.

Then we'll create a settings class that we want to inject and a business class that needs the settings.

// Note that this class is a regular class without any marker interfaces or attributes.
class FtpSettings
{
    public string HostName { get; set; }
    public int Port { get; set; }
}

interface IFtpClient
{
    // ...
}

class FtpClient : IFtpClient
{
    private readonly FtpSettings _settings;

    // Inject the settings using constructor injection
    public FtpClient(FtpSettings settings)
    {
        _settings = settings;
    }

    // ...
}

Running this example with default conventions will give us a runtime error because the container will not know how to create an instance of FtpSettings.

To configure this using conventions we're going to need a factory that will construct our settings class, a convention that will register it as a service and optionally an extension method to help with setup.

First, the factory that will construct our settings class. We're going to make our factory inherit from ConstructionFactory which is the default factory responsible for constructing types in InjectMe. We're inheriting from this for two reasons; we'll get all the default injection behavior when the instance is constructed and it is faster than using for instance Activator.CreateInstance(...).

class AppSettingsFactory : ConstructionFactory
{
    private readonly Type _settingsType;

    public AppSettingsFactory(Type settingsType)
        : base(settingsType)
    {
        _settingsType = settingsType;
    }

    public override object CreateService(IActivationContext context)
    {
        // Create the instance using default functionality in ConstructionFactory
        var instance = base.CreateService(context);

        // Read values from the config file for each property on the settings type
        // Use the type and property names as key for the app settings
        // Convert the text value to the correct type and set it to the property
        var typeName = Regex.Replace(_settingsType.Name, "Settings$", string.Empty);
        var properties = _settingsType.GetProperties();

        foreach (var property in properties)
        {
            var key = string.Format("{0}.{1}", typeName, property.Name);
            var textValue = ConfigurationManager.AppSettings[key];
            var propertyValue = Convert.ChangeType(textValue, property.PropertyType);

            property.SetValue(instance, propertyValue);
        }

        return instance;
    }
}

Note that it is not required to inherit from ConstructionFactory. All we need is an implementation of IFactory which has a single method responsible for returning an instance.

Next, we'll create our implementation of IScanConvention. This will register all types with names ending in 'Settings' as services using our factory for construction. We'll also add an extension method that we'll use during setup.

class AppSettingsScanConvention : IScanConvention
{
    public void ProcessType(IContainerConfiguration configuration, TypeInfo type)
    {
        if (type.Name.EndsWith("Settings"))
        {
            var serviceType = type.AsType();
            var factory = new AppSettingsFactory(serviceType);

            configuration.
                Register(serviceType).
                AsSingleton().
                UsingFactory(factory);
        }
    }
}

public static class AppSettingsAssemblyScannerExtensions
{
    public static IAssemblyScanner RegisterSettings(this IAssemblyScanner scanner)
    {
        return scanner.UseConvention<AppSettingsScanConvention>();
    }
}

Services are registered as singletons because changes to the config file will reload the appdomain which means that settings can't change during the app lifetime.

Now we have everything needed to inject our app settings.

var container = Container.Create(config =>
{
    config.Scan(scanner =>
    {
        scanner.
            // Default conventions are used to register our ftp client
            UseDefaultConventions().
            // Register the app settings scan conventions
            RegisterSettings().
            ScanThisAssembly();
    });
});