This time, I would like to introduce you Dependency Injection (DI), which is a very common principle in programming. However, let’s start with few connected terms.
Dependency Inversion Principle
Dependency Inversion Principle (DIP) is a principle of software design, where high-level objects should not depend on low-level objects. The principle is based on the idea of protecting high-level objects against any changes in low-level objects.
For example, implementation of Car should not depend on concrete Engine, but on the IEngine interface. If so, Engine can be simply changed for another implementation without any change in Car class.
Inversion of Control
Inversion of Control (IoC) is a set of ways how to implement Dependency Inversion Principle.
Dependency Injection
Dependency Injection (DI) is one of many types of IoC implementation, where object creation is done outside of its parent class and then it’s injected there in some way:
- Constructor Injection
- Interface Injection
- Setter Injection
IoC Container
First of all, we will need to install IoC Container. For this purpose, I chose Autofac, which can be used in .NET Core, ASP.NET Core, .NET 4.5.1+, Universal Windows apps, Xamarin and more. In this step, just install Autofac Nuget Package into your Windows or Xamarin application.
IoC Builder
Now, we will implement container builder, where we specify which types will be automatically resolved and in which way. To simplify the process of type registration, we can create interfaces, which will specify whether its objects should be resolved automatically. Many times we need to work with a single instance of an object, so we can create another interface for objects, that will always have injected the same instance.
1 2 |
public interface IResolvable { } public interface ISingleResolvable { } |
IoC Builder class will be used to build a container and register all classes from application assembly that implements our interfaces IResolvable or ISingleResolvable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public class IoCBuilder { private ContainerBuilder _builder = new ContainerBuilder(); public IContainer BuildContainer() { _builder = new ContainerBuilder(); RegisterTypes(); return _builder.Build(); } public virtual void RegisterTypes() { Register<IResolvable>(); RegisterSingle<ISingleResolvable>(); } public void Register<T>() { var baseType = typeof(T).GetTypeInfo(); _builder.RegisterAssemblyTypes(baseType.Assembly) .Where(t => t.IsAssignableTo<T>() && !t.IsAssignableTo<ISingleResolvable>()) .PropertiesAutowired(); } public void RegisterSingle<T>() { var baseType = typeof(T).GetTypeInfo(); _builder.RegisterAssemblyTypes(baseType.Assembly) .Where(t => t.IsAssignableTo<T>()).SingleInstance() .PropertiesAutowired(); } } |
MVVM Locator
The last, but not least step is creating an MVVM Locator. MVVM Locator will use our IoC Builder to create a new container and resolve ViewModel when it’s needed by calling generic method Resolve<T>.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class MVVMLocator { private IContainer _container; public MVVMLocator() { _container = new IoCBuilder().BuildContainer(); } public T Resolve<T>() { return _container.Resolve<T>(); } } |
..and create an instance in application’s main class.
1 2 3 4 5 6 |
sealed partial class App : Application { public MVVMLocator MVVMLocator = new MVVMLocator(); ... } |
Resolving ViewModels
If we want our ViewModels to automatically resolve all dependencies, implement the IResolvable interface in ViewModel’s base class.
1 2 |
public class ViewModelBase : IResolvable, INotifyPropertyChanged { ... } |
In code-behind file of a view, resolve instance of appropriate ViewModel class.
1 2 3 4 5 6 |
public sealed partial class MainView : Page { public MainViewModel ViewModel = ((App)Application.Current).MVVMLocator.Resolve<MainViewModel>(); ... } |
Dependency Injection usage
Now, when ViewModel is automatically resolved, we need to understand options of dependency injection.
Constructor injection
When classes implementing IResolvable or ISingleResolvable are added to a constructor of a ViewModel as parameters, they will be automatically resolved and we will get an instance that was built outside of the ViewModel.
1 2 3 4 5 6 7 8 9 |
public class ConstructorInjectionViewModel : ViewModelBase { private Resolvable _resolvable; public SecondViewModel(Resolvable resolvable) { _resolvable = resolvable; } } |
Interface injection
Even more simple one is an Interface injection, where class implementing a resolvable interface is added to a ViewModel as public property. Sure, according to IoC Builder created earlier, we are not fulfilling the Dependency Inversion Principle because we are not creating resolvable interfaces for each service that should be resolved. However, for purpose of simple mobile application, it’s sufficient.
1 2 3 4 |
public class MainViewModel : ViewModelBase { public Resolvable Resolvable { get; set; } } |
Conclusion
Now, you’re ready to make a great, testable and low coupling apps! Hope it helps 🙂