KrakenIoc
KrakenIoc.Editor
KrakenIoc.Unity
Inversion of Control is a design principle in modern software applications. Kraken is a dependency injection framework with extensibility built-in.
Without A D.I. Framework Dependency Injection can be achieved without a framework. Take the following example:
public class MyClass
{
public MyClass(WebService webService) {...}
}
By specifying all the dependencies that a class expects within its constructor, you explicitly state what your class needs to run. This pattern allows your classes to be unit-tested easier because the developer can replace which services your classes use into the constructor.
D.I. Framework By using a D.I. framework, you simplify injections and refactoring by allowing the framework to create you objects. The framework auto-injects the dependencies, thus freeing you up to develop features instead of boilerplate.
A container contains the mappings of bound abstract types to implementation types. The container is the entry point where types are bound and resolved.
The Bind
method returns an object of type IBinding
; of which contains fluent api for adding additional details to the set.
var container = new Container(...);
container.Bind<WebService>().AsSingleton();
Resolve
has a few overrides, but the method returns an instance of the specified binding. Depending on your IBinding
settings, your instance can be Transient
or a Singleton
.
var service = container.Resolve<IWebService>();
Transient: A new instance every time it is resolved (default). Singleton: A new instance if null, otherwise return existing instance.
When abstractions are required, you can pass the implementation type to the To
fluent method of IBinding
.
container.Bind<IWebService>().To<WebService>().AsSingleton();
You can bind multiple interfaces to the same implementation. That way, you can have multiple interfaces referring to the same singleton:
container.Bind(typeof(IWebService), typeof(IProductWebService), typeof(IWebConnectionService)).To<WebService>().AsSingleton();
It is often useful to map different types of implementations. You can do this with categories.
container.Bind<IDashboard>().To<EmailDashboard>().WithCategory(ProductCategories.Email);
container.Bind<IDashboard>().To<HomeDashboard>().WithCategory(ProductCategories.Home);
// Resolve the `Home` category...
var homeDashboard = container.ResolveWithCategory<IDashboard>(ProductCategories.Home);
You can pass a IFactory<T>
or a delegate matching FactoryMethod<T>
to handle how an object is resolved.
class WebServiceFactory : IFactory<WebService>
{
object Create(IInjectContext injectionContext)
{
return Create(injectionContext);
}
}
// From a factory type...
container.Bind<WebService>().FromFactory<WebServiceFactory, WebService>();
// From a factory method...
container.Bind<WebService>().FromFactoryMethod((injectContext) => {
return new WebService();
});
As an alternative, you can decide to bind a non-generic factory. This is useful when you bind multiple interfaces to one implementations.
// From a factory type...
container.Bind(typeof(IWebService), typeof(IProductWebService), typeof(IWebConnectionService)).To<WebService>().FromFactory<WebServiceFactory>().AsSingleton();
Bootstrap
s provide a modular way to bind your services and features.
public class MyBootstrap : IBootstrap
{
public void SetupBindings(IContainer container)
{
...
}
}
container.Bootstrap<MyBootstrap>();
You can extend the Resolve
functionality by implementing an IBindingMiddleware
.
public class MyMiddleware : IBindingMiddleware
{
public object Resolve(IBinding binding, object target = null)
{
// Every type resolved is now MyCoolClass()...
return new MyCoolClass();
}
public object Resolve(IBinding binding, IInjectContext injectContext, object target = null)
{
// Default binding resolve will handle....
return null;
}
}
// Add middleware to all bindings...
Binding.AddBindingMiddleware(new MyMiddleware());
If you have more than one container, and wish to use bindings from one container into another, you can inherit the container as follows:
var containerA = new Container();
containerA.Bind<WebService>().AsSingleton();
var containerB = new Container();
containerB.Inherit(containerA);
var webService = containerB.Resolve<WebService>();
If containerB has a type bound that is found within containerA, then containerB's binding will be used.
var containerA = new Container();
containerA.Bind<WebService>().AsSingleton();
var containerB = new Container();
containerB.Bind<WebService>().AsSingleton();
containerB.Inherit(containerA);
// Resolves containerB WebService...
var webService = containerB.Resolve<WebService>();
If containerA resolves a type that depends on a type within containerB, containerB binding will be used. However, this only works with transient bindings:
public class ApiService
{
public WebService WebService { get; private set; }
public ApiService(WebService webService)
{
_webService = webService;
}
}
var containerA = new Container();
containerA.Bind<ApiService>().AsTransient();
var containerB = new Container();
containerB.Bind<WebService>().AsTransient();
containerB.Inherit(containerA);
var apiService = containerB.Resolve<ApiService>(); // apiService.WebService is new instance of WebService
Singleton bindings will not resolve this way (in fact, they will be resolved from containerA and can not be overridden):
var containerA = new Container();
containerA.Bind<ApiService>().AsSingleton();
var containerB = new Container();
containerB.Bind<WebService>().AsTransient();
containerB.Inherit(containerA);
// apiService resolves and caches, but apiService.WebSerivce is null.
// This happens because singleton instances resolved from ContainerA have no knowledge about ContainerB
var apiService = containerB.Resolve<ApiService>();
When a type is resolved, injection will occur on that type. Parameters in a constructor are automatically injected if the type is bound in the container. For instance, in the following example IWebService will be injected, however ILogService will be null because it was not bound.
class MyClass
{
public MyClass(WebService webService, LogService logService)
{
Assert.IsNotNull(webService);
Assert.IsNull(logService);
}
}
container.Bind<WebService>().AsSingleton();
container.Bind<MyClass>();
var myClass = container.Resolve<MyClass>();
You cannot have circular dependencies. The inject should flow top to bottom; never circular.
The injector is the class that does the work of finding the parameters, constructors for injection during resolving. The default implementation is provided by the Kraken framework. However you can override the default by feeding your custom implementation into the constructor.
var myInjector = new MyInjector();
var container = new Container(myInjector);
IInjectContext
is a special type that can be injected into your classes. This type carries meta information about the request to resolve your type. See the following example:
public class MyConsumingClass
{
public MyConsumingClass(MyProvidingClass myClass) { ... }
}
public class MyProvidingClass
{
public MyClass(IInjectContext injectContext)
{
// The container resolving this...
Assert.NotNull(injectContext.Container);
// The class declaring this instance...
Assert.IsTrue(typeof(MyConsumingClass), injectContext.DeclaringType);
// The parent IInjectContext, for the object resolved before this...
Assert.NotNull(injectContext.ParentContext);
}
}
Kraken.Unity provides a UnityInjector
to replace the default Injector
along with several new concepts.
You can pass a target
into the Resolve
method. In the UnityInjector
this will effectively attempt to AddComponent<T>
onto the target.
container.Bind<ISomeComponent>().To<SomeComponent>();
// Adds the component onto an existing gameObject...
container.Resolve<ISomeComponent>(myGameObject);
// creates a new GameObject since none were passed..
container.Resolve<ISomeComponent>();
Context
is a MonoBehaviour that contains a Container
that is pre-configured to use the UnityInjector
. A Context
serves as the entry point for your bindings and application. In this best-practices example, we bind our services and a standard c# class to act as our main application entry point.
public class MyProductSetup
{
private WebService _webService;
public MyProductSetup(WebService webService)
{
_webService = webService;
}
public void Setup()
{
// Kick off the application...
_webService.Request(...);
}
}
public class MyProductContext : Context
{
protected override void OnSetupBindings()
{
Container.Bind<WebService>().AsSingleton();
Container.Bind<MyProductSetup>().AsSingleton();
}
protected virtual void OnStart()
{
var product = Container.Resolve<MyProductSetup>();
product.Setup();
}
}