-
-
Notifications
You must be signed in to change notification settings - Fork 4
π Dependency Injection
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 theIInjector
in your application (singleton). -
[Dependency] Attribute
: Marks fields or properties as dependencies to be injected by theInjectionResolver
. -
InjectionResolver
: Provides functions for initializing fields, properties or constructors. Uses theIInjector
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):
class Instances
{
private static IInjector _injector;
public static IInjector Injector => _injector ?? (_injector = new Injector());
}
partial class App : Application
{
App()
{
Instances.Injector.Register<..>();
}
}
To register a type for dependency injection, use the Register
overloads in the IInjector
interface.
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
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
The Injector will assign a pre-defined value (Singleton) to the property an initialization request is made on:
Injector.Register<IUser>(myUser);
To initialize a property using the Injector, the Initialize<T>()
type-to-instance binding lookup function is called.
You can manually call Initialize<T>()
to initialize the given type (T
).
IUser user = Injector.Initialize<IUser>();
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.
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 π