DI (Dependency Injection) with Injector

The injector allows to resolve all types and types for implemented interfaces automatically (views, view models, services)

  • Auto discovery of types and impletation types for interfaces not registered
  • Allows to register and resolve types, instances, factories
  • injection parameters and properties
  • Lifetimes: Transcient, Singleton, InstancePerScope, InstancePerMatchingScope
  • Scopes and child scopes
  • Interceptors allow to intercept value resolution
  • Custom value resolvers
  • Attributes: PreferredConstructor, PreferredImplementation (for interfaces), Dependency (for properties and parameters)

Require to be registered :

  • Singletons
  • Registrations with name
  • Registration with injection parameters/ properties
  • Use the PreferredImplementation attribute with many implementation of interfaces.

Comparison

For example with Autofac

Autofac

Injector

Namespace: required for extensions methods

using MvvmLib.IoC;

Registration

The injector allows to resolve all types and types for implemented interfaces automatically but it's possible to register "manually" the services.

Types

var injector = new Injector();

injector.RegisterType(typeof(MyItem));
injector.RegisterType(typeof(MyItem), "MyName"); // with name
injector.RegisterType(typeof(IMyService), typeof(MyService));
injector.RegisterType(typeof(IMyService), typeof(MyService), "MyName"); // with name

With extension methods

using MvvmLib.IoC;
// ...

var injector = new Injector();

injector.RegisterType<MyItem>();
injector.RegisterType<MyItem>("MyName"); // with name
injector.RegisterType<IMyService, MyService>();
injector.RegisterType<IMyService, MyService>("MyName"); // with name

Instances

var injector = new Injector();

injector.RegisterInstance(typeof(MyItem), new MyItem());
injector.RegisterInstance(typeof(MyItem), new MyItem(), "MyName"); // with name

With extension methods

using MvvmLib.IoC;
// ...

var injector = new Injector();

injector.RegisterInstance<MyItem>(new MyItem());
injector.RegisterInstance<MyItem>(new MyItem(), "MyName", ); // with name

Factories

var injector = new Injector();

injector.RegisterFactory(typeof(MyItem), () => new MyItem());
injector.RegisterFactory(typeof(MyItem), () => new MyItem(), "MyName"); // with name

With extension methods

using MvvmLib.IoC;
// ...

var injector = new Injector();

injector.RegisterFactory<MyItem>(() => new MyItem());
injector.RegisterFactory<MyItem>(() => new MyItem(), "MyName"); // with name

Lifetimes

  • Transcient by default (TranscientLifetime): get always a new instance
  • Singleton (SingletonLifetime): get always the same instance
  • InstancePerScope (InstancePerScopeLifetime): get always the same instance per scope
  • InstancePerMatchingScope (InstancePerMatchingScopeLifetime): get the same instance per matching scope and child scopes.

With Fluent Api

Singleton

Examples:

injector.RegisterType<MyItem>().AsSingleton(); // type
injector.RegisterFactory<MyItem>(() => new MyItem()).AsSingleton(); // factory
// or
injector.RegisterType(typeof(MyItem)).AsSingleton(); // type
injector.RegisterFactory(typeof(MyItem), () => new MyItem()).AsSingleton(); // factory

InstancePerScope

Examples:

injector.RegisterType<MyItem>().InstancePerScope(); // type
injector.RegisterFactory<MyItem>(() => new MyItem()).InstancePerScope(); // factory
// or
injector.RegisterType(typeof(MyItem)).InstancePerScope(); // type
injector.RegisterFactory(typeof(MyItem), () => new MyItem()).InstancePerScope(); // factory
// or
injector.RegisterScoped<MyItem>();
injector.RegisterScoped<MyItem>(() => new MyItem());

InstancePerMatchingScope

Examples:

injector.RegisterType<MyItem>().InstancePerMatchingScope("MyScope"); // with the scope name to match
// or
injector.RegisterType(typeof(MyItem)).InstancePerMatchingScope("MyScope"); // with the scope name to match

Registering Type for all implemented interfaces

public interface ILookupDataServiceType1 { }

public interface ILookupDataServiceType2 { }

public interface ILookupDataServiceType3 { }

public class LookupDataService : ILookupDataServiceType1, ILookupDataServiceType2, ILookupDataServiceType3 
{ }
var injector = new Injector();
var options = injector.RegisterTypeForImplementedInterfaces<LookupDataService>();

... Avoid to do:

injector.RegisterTypeForImplementedInterfaces<ILookupDataServiceType1, LookupDataService>();
injector.RegisterTypeForImplementedInterfaces<ILookupDataServiceType2, LookupDataService>();
injector.RegisterTypeForImplementedInterfaces<ILookupDataServiceType3, LookupDataService>();

It's possible to change the lifetime for one registration

var options2 = options[typeof(ILookupDataServiceType2)];
options2.AsSingleton();

Registering Singleton for all implemented interfaces

Allows to get the same instance for all "services".

var injector = new Injector();
injector.RegisterSingletonForImplementedInterfaces<LookupDataService>();

Registering Scoped for all implemented interfaces

Allows to get the same instance for all "services" by scope.

var injector = new Injector();
injector.RegisterScopedForImplementedInterfaces<LookupDataService>();

PreferredImplementationAttribute

The injector resolves not registered types for interfaces. For many implementations use the PreferredImplementationAttribute Example:

public interface IMyService
{ }

public class MyService1 : IMyService
{ }

[PreferredImplementation]
public class MyService2 : IMyService
{ }

public class MyService3 : IMyService
{ }

Change the option

var injector = new Injector(new ContainerOptions
{
    AutoDiscoveryOptions = new ScanOptions
    {
        MultipleImplementationTypeHandling = MultipleImplementationTypeHandling.PreferredImplementationAttribute
    }
});

Change the lifetime On discovery (Transcitent by default)

var injector = new Injector();
injector.Context.AutoDiscoverer.LifetimeOnDiscovery = LifetimeOnDiscovery.Singleton;

Add additional types for resolution (for services registered in another assembly)

injector.Context.AutoDiscoverer.AdditionalTypes.AddRange(typeof(IService).Assembly.ExportedTypes); 

Injection Parameters

1 - Allow to provide values for value types, string, enumerables...

Example

public class MyItem
{
    public MyItem(string myString, int myInt, List<string> myList)
    {

    }
}

Inject values

var injector = new Injector();

injector.RegisterType<MyItem>()
    .WithInjectionParameters(new InjectionParameter("myString", value: "My value"), 
                             new InjectionParameter("myInt", value: 100), 
                             new InjectionParameter("myList", value: new List<string> { "A", "B" }));

2 - Other example allow to select a named registration

public class MyInnerItem { }

public class MyItem
{
    public MyItem(MyInnerItem myInnerItem)
    {

    }
}
var injector = new Injector();

injector.RegisterType<MyInnerItem>("Key1"); 

injector.RegisterType<MyItem>()
    .WithInjectionParameters(new InjectionParameter("myInnerItem", name: "Key1")); // provide the name of the registration to use

Alternative with Dependency Attribute

public class MyInnerItem { }

public class MyItem
{
    public MyItem([Dependency(Name ="Key1")] MyInnerItem myInnerItem)
    {

    }
}
var injector = new Injector();

injector.RegisterType<MyInnerItem>("Key1"); 

injector.RegisterType<MyItem>();

Resolve

var injector = new Injector();

// resolve type, instance, factory ... with the lifetime
var value = injector.Resolve(typeof(MyItem));
var value = injector.Resolve(typeof(MyItem), "MyName"); // with name
var value = injector.Resolve(typeof(IMyService)); 
var value = injector.Resolve(typeof(IMyService), "MyName"); // with name

With extension methods

using MvvmLib.IoC;
// ...

var injector = new Injector();

var value = injector.Resolve<MyItem>();
var value = injector.Resolve<MyItem>("MyName"); // with name
var value = injector.Resolve<IMyService>();
var value = injector.Resolve<IMyService>("MyName"); // with name

InstancePerScope

var injector = new Injector();
injector.RegisterScoped<MyItem>();

using (var scope1 = injector.CreateScope())
{
    var instance1 = scope1.Resolve<MyItem>(); 
    var instance2 = scope1.Resolve<MyItem>(); // instance1 equals instance2
}

using (var scope2 = injector.CreateScope())
{
    var instance3 = scope2.Resolve<MyItem>(); 
    var instance4 = scope2.Resolve<MyItem>(); // instance3 equals instance4
}

Named scope:

using (var scope1 = injector.CreateScope("MyScope"))
{
}

InstancePerMatchingScope

var injector = new Injector();
injector.RegisterType<MyItem>().InstancePerMatchingScope("MyScope");

using (var scope = injector.CreateScope("MyScope"))
{
    var instance1 = scope.Resolve<MyItem>();
    using (var scope1 = scope.CreateScope())
    {
        var instance2 = scope1.Resolve<MyItem>();
        using (var scope2 = scope1.CreateScope())
        {
            var instance3 = scope2.Resolve<MyItem>(); // instance1, instance2 and instance3 are equals
        }
    }
}

using (var scope = injector.CreateScope("MyScope"))
{
    var instance4 = scope.Resolve<MyItem>();
    using (var scope1 = scope.CreateScope())
    {
       var instance5 = scope1.Resolve<MyItem>(); // instance4 equals instance5 but instance1 (...) and instance4 (...) are not equals
    }
}

Func

Sample 1

var injector = new Injector();
var resolver = injector.Resolve<Func<MyItem>>();

MyItem item = resolver();

Sample 2

public class MyItemWithFunc
{
    public MyItemWithFunc(Func<MyItem> resolver)
    {
        MyItem item = resolver();
    }
}
var injector = new Injector();

injector.RegisterType<MyItem>();
injector.RegisterType<MyItemWithFunc>();

var instance = injector.Resolve<MyItemWithFunc>();

Sample 3

With Func<string,T>. Useful for conditional resolution in the constructor.

var injector = new Injector();
injector.RegisterType<IMyService, MyService1>("Key1");
injector.RegisterType<IMyService, MyService2>("Key2");
injector.RegisterType<MyItem>();

var instance1 = injector.Resolve<MyItem>();
public interface IMyService { }
public class MyService1 : IMyService { }
public class MyService2 : IMyService { }

public class MyItem
{
    public MyItem(Func<string, IMyService> resolver)
    {
        var name = /* condition */ ? "Key1": "Key2";
        var instance = resolver(name); // resolve by name
    }
}

Lazy

Sample 1

var injector = new Injector();
var lazy = injector.Resolve<Lazy<MyItem>>();

MyItem item = lazy.Value;

Sample 2

public class MyItemWithFuncAndLazy
{
    public MyItemWithFuncAndLazy(Func<MyItem> func1, Lazy<MyItem> lazy1)
    {
        MyItem MyItem1 = func1();
        MyItem MyItem2 = lazy1.Value;
    }
}

Note: dependency attribute and injection parameter are availables for constructor parameters with name.

Property Injection

1 - define properties to include

With InjectionProperty

var injector = new Injector();
injector.RegisterType<MyInnerItem>();
injector.RegisterType<MyItem>().WithInjectionProperties(new InjectionProperty("MyInner")); // propertyName

var instance = injector.BuildUp<MyItem>();

Alternative with the Dependency Attribute

public class MyInnerItem { }

public class MyItem
{
    public string MyString { get; set; } // not included

    [Dependency]
    public MyInnerItem MyInner { get; set; } // included
}

2 - Provide a value with InjectionProperty

var injector = new Injector();

injector.RegisterType<MyItem>().WithInjectionProperties(new InjectionProperty("MyString", value: "My value")); // propertyName, value

var instance = injector.BuildUp<MyItem>();
public class MyItem
{
    public string MyString { get; set; }
}

3 - Provide the name the name of the registration to use

With InjectionProperty

var injector = new Injector();

injector.RegisterType<MyInnerItem>("Key1");

injector.RegisterType<MyItem>().WithInjectionProperties(new InjectionProperty("MyInner", name: "Key1")); // propertyName, name

var instance = injector.BuildUp<MyItem>();
public class MyInnerItem { }

public class MyItem
{
    public MyInnerItem MyInner { get; set; }
}

Alternative with the Dependency Attribute

public class MyInnerItem { }

public class MyItem
{
    [Dependency(Name ="Key1")]
    public MyInnerItem MyInner { get; set; }
}

4 - BuildUp with an existing instance

var item = new MyItem();
var instance = injector.BuildUp<MyItem>(item);

OnResolved Callback

Allows to handle circular references for example.

public class CircularPropertyItem
{
    public CircularPropertyItem Item { get; set; } // circular reference

    // other properties
    public string MyString { get; set; }
    public SubItem MySubItem { get; set; }
}
injector.RegisterType<CircularPropertyItem>().OnResolved((registration, instance) =>
{
    var item = instance as CircularPropertyItem;
    item.Item = item; // circular reference

    // other properties
    item.MyString = "My value";
    item.MySubItem = injector.Resolve<SubItem>();
});

var instance = injector.Resolve<CircularPropertyItem>();

Resolve All

Resolves and returns all values for a type.

Example:

var injector = new Injector();

injector.RegisterInstance<MyItem>(new MyItem());
injector.RegisterInstance<MyItem>("Key1", new MyItem());

var items = injector.ResolveAll<MyItem>();

AutoDiscovery

By default, non registered types are resolved. To change this behavior:

injector.AutoDiscoverer.AutoDiscovery = false;

NonPublicConstructors

By default, non public constructors/ properties are found. To change this behavior:

var injector = new Injector(new ContainerOptions { NonPublicConstructors = false });

PreferredConstructorAttribute

Example

public class MyViewModel
{
    public MyViewModel()
    {

    }

    public MyViewModel(IMyService1 service1)
    {

    }

    [PreferredConstructor]
    public MyViewModel(IMyService1 service1, IMyService2 service2)
    {

    }
}

Constructor Selector

It's possible to create a custom selector (for example a MostParametersConstructorSelector).

public class MyConstructorSelector : IConstructorSelector
{
    public ConstructorInfo ResolveConstructor(Type implementationType, bool nonPublic)
    {
        // implements ...
    }
}
var injector = new Injector(new ContainerOptions { ConstructorSelector = new MyConstructorSelector() });

DelegateFactory

The ExpressionDelegateFactory is used by default to create instances (with Expressions Linq).

Change to ReflectionDelegateFactory (less performant)

var injector = new Injector(new ContainerOptions { DelegateFactory = new ReflectionDelegateFactory() });

Note: It's possible to create a custom DelegateFactory (implements IDelegateFactory interface). For example with System.Reflection.Emit.

Interceptors

Create an interceptor

public class MyInterceptor1 : InterceptorBase
{
    protected override Task Process(InterceptionContext input, Func<InterceptionContext, bool, Task> next)
    {     
        // do things before
        next(input, true); // <= Cancels next interceptors execution with false
        // do things after
        return Task.FromResult<object>(null);


        // - or - 
        // do things before
        // return next(input, true);
    }
}

Register the interceptors

injector.RegisterType<MyItem>().WithInterceptors(new MyInterceptor1(), new MyInterceptor2()); // ...

var instance = injector.Resolve<MyItem>(); // => interceptors executed

Note: the instance is resolved by the last interceptor (ResolveInterceptor). input.Result contains the instance after resolution. So at beginning the result is null, it's possible to provide the result and cancel next interceptors execution.

Note 2: interceptors with MvvmLib.IoC are not invoked on method calls (unlike Unity for example).

Async

public class MyInterceptor2 : InterceptorBase
{
    protected override async Task Process(InterceptionContext input, Func<InterceptionContext, bool, Task> next)
    {
        // do things before

        await next(input, true);

        // do things after
    }
}

Resolvers

Default resolvers:

  • ValueResolver: for ValueType and string, returns default values
  • EnumerableTypeResolver: for IEnumerable, returns default values
  • FuncResolver: for Func<T>
  • FuncWithNameResolver: for Func<string,T> used to resolve a type by name. Useful for conditional resolution in the constructor.
  • LazyResolver: for Lazy<T>
  • TypeResolver: the default resolver used to resolve values for Types

It's possible to create custom resolvers

public class MyItemResolver : IValueResolver
{
    /// <summary>
    /// Checks if a registration is required.
    /// </summary>
    public bool RequireRegistration
    {
        get { return true; }
    }

    public bool CanResolve(Type type)
    {
        // used to determine if the resolver can be used
        return type == typeof(MyItem);
    }

    public object Resolve(Type type, string name)
    {
        // the method used to resolve the value
        return new MyItem();
    }
}

Register and use the custom resolver

var injector = new Injector();

// register the custom resolver
injector.RootScope.ResolverProvider.AddCustomResolver(new MyItemResolver());

var item = injector.Resolve<MyItem>(); // the custom resolver is used

Note: the custom resolvers are copied on new scope creation.

Events

  • Registering
  • Registered
  • Resolved