Skip to content

StyletIoC Configuration

HelloWorld183L edited this page Mar 24, 2020 · 18 revisions

Here you'll learn how to create a new StyletIoC container, and register your services on it.

Getting Started - The Builder

To create a container, you must create a new StyletIoCBuilder, and register all of your services on it. StyletIoCBuilder.BuildContainer() will already be called so you do not need to call it to build your container.

For example:

// First, create the builder
var builder = new StyletIoCBuilder();

// Configure the builder
builder.Bind<IVehicle>().To<HotHatchback>();

// The container is then built for you

// Now you can use this to resolve instances of services
var vehicle = ioc.Get<IVehicle>();

Different Ways Of Creating Types

As discussed previously, the purpose of StyletIoC is to take a request for a service type, and return an instance of something implementing that service, using the rules you configured using the StyletIoCBuilder. It can use a couple of different techniques to create that instance, though.

Type Bindings

The simplest is called a "type binding". You tell it the service type, and the type which implements that service, and it will figure out how to construct that type itself. For example:

builder.Bind<IVehicle>().To<HotHatchback>();

This tells StyletIoC "whenever I ask you for an IVehicle, create a new HotHatchback using an appropriate constructor, with all of the necessary dependencies passed in".

You can of course bind a service to itself, so long as that service is a concrete type. This is called a "self binding". For example:

builder.Bind<HotHatchback>().To<HotHatchback>();
// Or, more clearly:
builder.Bind<HotHatchback>().ToSelf();

This tells StyletIoC "whenever I ask you for a HotHatchback, give me a HotHatchback with all of its dependencies already populated".

You can also use the non-generic overloads if you wish:

builder.Bind(typeof(IVehicle)).To(typeof(HotHatchback));

Factory Bindings

If, for some reason, you want to tell StyletIoC exactly how to construct your type, you can pass it a delegate to call. This is called a "factory binding". For example:

builder.Bind<IVehicle>().ToFactory(container => new HotHatchback());

The container parameter there is an IContainer, and you can use it if your constructor needs dependencies injecting into it, for example:

builder.Bind<IVehicle>().ToFactory(container => new HotHatchback(container.Get<Engine>()));

Of course, self-bindings work here too:

builder.Bind<HotHatchback>().ToFactory(container => new HotHatchback());

Instance Bindings

You can construct an instance of a type yourself and give it to the IoC container. This can be useful for things like configuration objects. For example:

builder.Bind<IVehicle>().ToInstance(new HotHatchback());

Instance bindings are automatically singleton - the container doesn't know how to construct new instances of the type, and so has to always return the same instance.

By default the IoC container will dispose IDisposable instance types when it is disposed, but you can configure this using .ToInstance(foo).DisposeWithContainer(false);.

Different Scopes

Transient Scope

By default, StyletIoC will create a new instance of a given type every time you ask it - this is called "transient scope". See:

var car1 = ioc.Get<IVehicle>();
var car2 = ioc.Get<IVehicle>();

// The following statement will succeed.
Assert.AreNotEqual(car1, car2);

The IoC container won't dispose IDisposable transient types - it doesn't claim ownership over them, and doesn't know when they should be disposed.

Singleton Scope

However, you can also tell it to only create one instance of a service, ever, and return that one instance every time you ask it. This is called "singleton scope", and is very useful if your application does have singletons (most do), as it saves you from having a traditional Singleton object, which are notoriously hard to mock for unit tests.

builder.Bind<IConfigurationManager>().To<ConfigurationManager>().InSingletonScope();
var ioc = builder.BuildContainer();

var configManager1 = ioc.Get<IConfigurationManager>();
var configManager2 = ioc.Get<IConfigurationManager();

// The following statement will succeed.
Assert.AreEqual(configManager1, configManager2);

Of course, this also works with factory bindings (where the factory will only be called once, ever):

builder.Bind<IConfigurationManager>().ToFactory(container => new ConfigurationManager()).InSingletonScope();

Note that singletons are scoped to the binding in which they're defined, see:

builder.Bind<IConfigurationManager>().To<ConfigurationManager>().InSingletonScope();
builder.Bind<ConfigurationManager>().ToSelf();

var ioc = builder.BuildContainer();

var configManager1 = ioc.Get<IConfigurationManager>();
var configManager2 = ioc.Get<ConfigurationManager>();

// The following statement will succeed.
Assert.AreNotEqual(configManager1, configManager2);

If you really want these both to return the same instance, you can use this technique:

builder.Bind<ConfigurationManager>().And<IConfigurationManager>().To<ConfigurationManager>().InSingletonScope();

The IoC container will dispose IDisposable singletons when it is disposed.

Binding Multiple Types To A Single Service

So far, we've experimented with binding a single type to a single service. That's not how it has to be, though. You can bind multiple types to a single service, for example:

interface IVehicle { ... }
class HotHatchback : IVehicle { ... }
class OldBanger : IVehicle { ... }

builder.Bind<IVehicle>().To<HotHatchback>();
builder.Bind<IVehicle>().To<OldBanger>();

var ioc = builder.BuildContainer();

// This will throw an exception
ioc.Get<IVehicle>();

// This will return { new HotHatchback(), new OldBanger() }
IEnumerable<IVehicle> vehicles = ioc.GetAll<IVehicle>();

As you can see, if you want to fetch an array of items, you need to use IContainer.GetAll - if you try a fetch a single IVehicle with IContainer.Get, StyletIoC doesn't know which to give you, so it throws an exception.

This also works with constructor and parameter injection, see:

class Garage
{
   public Garage(IEnumerable<IVehicle> vehicles) { ... }
}

// And

class Garage
{
   [Inject]
   public IEnumerable<IVehicle> Vehicles { get; set; }
}

Binding Generic Types

StyletIoC handles bound generic types (where the exact types of the type parameters is known) the same as ordinary types, for example:

interface IValidator<T> { ... }
class IntValidator : IValidator<int> { ... }
builder.Bind<IValidator<int>>().To<IntValidator>();

and

interface IValidator<T> { ... }
class Validator<T> : IValidator<T> { ... }
builder.Bind<IValidator<int>>().To<Validator<int>>();

The fun begins when you want to create bindings for generic types where the exact types of the type parameters aren't known, for example:

interface IValidator<T> { ... }
class Validator<T> : IValidator<T> { ... }
builder.Bind(typeof(IValidator<>)).To(typeof(Validator<>));

var ioc = builder.BuildContainer();

var intValidator = ioc.Get<IValidator<int>>(); // Returns a Validator<int>

Both the service and its implementation can have as many type parameters as you want, but they must have the same number of type parameters (it makes sense if you think it through). However, the type parameters can appear in any order:

interface ISomeInterface<T, U> { ... }
class SomeClass<U, T> : ISomeInterface<T, U> { ... }
builder.Bind(typeof(ISomeInterface<,>)).To(typeof(SomeClass<,>));

StyletIoC does not take type constraints into account - if you have a interface IMyInterface<T> where T : class and request an IMyInterface<int>, you'll get an exception.

Autobinding

StyletIoC is capable of creating bindings automatically for you.

Autobinding All Concrete Types

Autobinding means that if you request a concrete type which hasn't been registered with Stylet, Stylet will attempt to construct it as a transient instance for you. It will only do for types in assemblies which you specify: those in StyletIoCBuilder.Assemblies, and in any assemblies you pass to the Autobind method.

Note that explicit bindings always take precedence over autobindings.

It is a bad idea to pass assemblies such as mscorlib to Autobind, otherwise Stylet will attempt to instantiate types such as System.String.

builder.Autobind();

This is useful in an MVVM application, as it allows StyletIoC to resolve any of your ViewModels. The Stylet bootstrapper calls Autobind(), which means Autobinding is enabled by default.

Binding a service to all implementations

You can also bind a service to all types which implement it, for example:

interface IVehicle { ... }
class HotHatchback : IVehicle { ... }
class OldBanger : IVehicle { ... }

builder.Bind<IVehicle>().ToAllImplementations();

var ioc = builder.BuildContainer();

IEnumerable<IVehicle> vehicles = ioc.GetAll<IVehicle>(); // Returns { new HotHatchback(), new OldBanger() }

There are also overloads allowing you to specify which assemblies to search.

This can be useful on its own (think finding all plugins), but is particularly useful when combined with unbound generics. For example:

interface IValidator<T> { ... }
class IntValidator : IValidator<int> { ... }
class StringValidator : IValidator<string> { ... }

builder.Bind(typeof(IValidator<>)).ToAllImplementations();

var ioc = builder.BuildContainer();

var intValidator = ioc.Get<IValidator<int>>(); // Returns an IntValidator
var stringValidator = ioc.Get<IValidator<string>>(); // Returns a StringValidator

If you want more complex binding rules, StyletIoC doesn't provide an API for you - it barely takes any effort to do it yourself, and providing an API is just adding a lot of complexity for very little gain.

However, StyletIoC does define a couple of extension methods on Type which may make your life easier:

// Returns all base types
someType.GetBaseTypes();

// Returns all base types and interfaces
someType.GetBaseTypesAndInterfaces();

// Returns true if someType implements someServiceType
someType.Implements(someServiceType)
// Also takes into account generics - so this is true:
typeof(Validator<int>>.Implements(typeof(IValidator<>));
Clone this wiki locally