Introduction

Dependency Injection (DI) is a software development design pattern that aims to achieve loose coupling between components and enhance the maintainability, testability, and scalability of applications. In the context of C#, DI is commonly implemented using frameworks like .NET Core's built-in Dependency Injection (DI) container or third-party libraries such as Autofac, Ninject, or Unity.

At its core, Dependency Injection involves injecting dependencies (i.e., objects that a class relies on to perform its tasks) into a class instead of the class creating its own dependencies. This is typically achieved by passing dependencies into a class's constructor, method, or property rather than instantiating them directly within the class.

Here is an overview of how Dependency Injection works in C#:

Dependency injection container

  1. Service registration: Dependencies are registered with a DI container, usually in the application's composition root. These registrations define how dependencies should be created and managed by the container.
  2. Dependency resolution: When a component requests a dependency, the DI container resolves the dependency by providing an instance of the registered type.

Types of Dependency Injection

The benefits of dependency injection

Dependency Injection is a valuable technique for managing dependencies and fostering modular and maintainable software architecture in C# and other object-oriented programming languages.

In Dependency Injection (DI), the terms "scoped", "transient", and "singleton" are used to describe the different lifetimes or lifecycles of objects managed by the DI container. These lifetimes determine how instances of dependencies are created and managed by the container.

  1. Scoped: Scoped dependencies are created once per request or lifetime scope. This means that within the scope of a single request or operation, the container will provide the same instance of a scoped dependency. Scoped dependencies are commonly used in web applications to maintain the consistency of a dependency throughout a web request
  2. Transient: Transient dependencies are created each time they are requested from the container. This means that a new instance of a transient dependency is created every time it is needed. Transient dependencies are typically used for lightweight, stateless services or components.
  3. Singleton: Singleton dependencies are created once and shared throughout the lifetime of the application. This means that the container will provide the same instance of a singleton dependency for all requests made during the application's lifetime. Singleton dependencies are commonly used for stateful services or components that need to be shared across the entire application.

An illustration of the three different life cycle types mentioned above.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WPFExamples
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Scoped registration
        services.AddScoped<IMyScopedService, MyScopedService>();
        // Transient registration
        services.AddTransient<IMyTransientService, MyTransientService>();
        // Singleton registration
        services.AddSingleton<IMySingletonService, MySingletonService>();
    }
}

To set up dependency injection (DI) in WPF applications using Microsoft.Extensions.DependencyInjection library, follow these steps.

Step 1. Installing the NuGet Package

Begin by installing the Microsoft.Extensions.DependencyInjection NuGet package if you haven't done so already. This can be accomplished through the NuGet Package Manager or by executing the following command in the Package Manager Console.

NuGet\Install-Package Microsoft.Extensions.DependencyInjection -Version 8.0.0

Package

Step 2. Set Up DI Container in App.xaml.cs

In the App.xaml.cs file, set up the DI container (IServiceCollection) in the App class constructor.

using DependencyInjectionExmple.Implementation;
using DependencyInjectionExmple.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using System.Windows;
namespace DependencyInjectionExmple
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        public static IServiceProvider serviceProvider;
        public App()
        {
            // Set up DI container
            var serviceCollection = new ServiceCollection();
            ConfigureServices(serviceCollection);
            // Build the service provider
            serviceProvider = serviceCollection.BuildServiceProvider();
        }
        private void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<MainWindowViewModel>();
            // Register your services and dependencies here
            services.AddSingleton<ITestingService, TestingService>(); // Example registration
        }
    }
}

Step 3. Service and Dependency Definition

To define your services and their dependencies, it is recommended to use interfaces and classes. You can register them in the ConfigureServices method of the App class. You can create classes and interfaces like below.

using DependencyInjectionExmple.Interfaces;
using System.Windows;
namespace DependencyInjectionExmple.Implementation
{
    internal class TestingService : ITestingService
    {
        public void TestService()
        {
            MessageBox.Show("Dependency Injection Service implementation");
        }
    }
}

Step 4. Integrate Dependencies into Classes

Integrate dependencies into classes that necessitate them using constructor injection.

using DependencyInjectionExmple.Interfaces;
using System.Windows.Input;
namespace DependencyInjectionExmple
{
    internal class MainWindowViewModel
    {
        private readonly ITestingService testingService = null;
        public ICommand ShowDependencyInjectionCommand { get; }
        public MainWindowViewModel(ITestingService testingService)
        {
            this.testingService = testingService;
            ShowDependencyInjectionCommand = new RelayCommand((obj) => ShowDependencyInjection(obj));
        }
        private void ShowDependencyInjection(object parameter)
        {
            // Handle the command logic here
            testingService.TestService();
        }
    }
}

INjected dependency

Step 5. Implementation's appearance during runtime

Show dependency injection

Repository path: https://github.com/OmatrixTech/DependencyInjectionExmple