Skip to content

πŸ’‰ Dependency Injection

Marc Rousavy edited this page Aug 23, 2018 · 16 revisions

Jellyfish allows quick and easy dependency injection (or auto-wiring).

This can be useful when you want to loosely couple dependencies such as properties, fields, constructor parameters and basic variables.

There are 3 major components in the Jellyfish dependency injection system:

  • IInjector: Keeps track of all type-to-instance bindings to initialize a given type at any point (e.g.: IUser gets initialized with () => new User()). In most cases, you only need one instance of the IInjector in your application (singleton).
  • [Dependency] Attribute: Marks fields or properties as dependencies to be injected by the InjectionResolver.
  • InjectionResolver: Provides functions for initializing fields, properties or constructors. Uses the IInjector for actual type-to-instance binding lookups.

It is recommended to use one IInjector (implementation: Injector) instance throughout the application and register any types as soon as possible (application startup):

Singleton
class Instances
{
    private static IInjector _injector;
    public static IInjector Injector => _injector ?? (_injector = new Injector());
}
App initialization
partial class App : Application
{
    App()
    {
        Instances.Injector.Register<..>();
    }
}

Registering

To register a type for dependency injection, use the Register overloads in the IInjector interface.

Binding

The Injector will automatically call the matching constructor when using type bindings:

Injector.Register<IUser, User>();                // bind generic types
Injector.Register<IUser, User>("John", "Smith"); // bind with constructor arguments
Injector.Register(typeof(IUser), typeof(User));  // bind types

Templating

The Injector will call the template function everytime an initialization request for the given type is made:

Injector.Register<IUser>(() => new User("John", "Smith")); // template generic types
Injector.Register(typeof(IUser), () => new User());        // template types

Defining

The Injector will assign a pre-defined value (Singleton) to the property an initialization request is made on:

Injector.Register<IUser>(myUser);

Initializing

To initialize a property using the Injector, the Initialize<T>() type-to-instance binding lookup function is called.

Manually

You can manually call Initialize<T>() to initialize the given type (T).

IUser user = Injector.Initialize<IUser>();

Auto-Inject extension

The Inject<T>() extension method automatically initializes all fields and properties with the [Dependency] attribute:

class LoginViewModel : ViewModel
{
    [Dependency]
    IUser User { get; set; }
    [Dependency]
    IDatabaseService _service;

    LoginViewModel()
    {
        this.Inject(Injector);
    }
}

Initialize<T>() will call the following methods:

If the field or property is not null, the Injector will ignore it to prevent overriding of existing values.

Constructor Injection

Some classes (e.g.: View-Models) require a service or some other sort of dependency. This dependency is often required upon object initialization (constructor parameter):

class UsersViewModel : ViewModel
{
    // required parameter
    UsersViewModel(IDatabaseService service)
    { ... }
}

To inject parameters, use the InjectConstructor<T>(IInjector) function:

var viewModel = InjectionResolver.InjectConstructor<UsersViewModel>(Injector);

The InjectionResolver will automatically look up any public constructors and tries to find the parameter types in the Injector instance. If no valid constructors could be found, an exception is thrown.

See also: View Model Locator πŸ“–