Dependency Injection

🟨 Slightly different to Blazor Server

In Blazor, Dependency Injection enables developers to create more modular, flexible, and maintainable code by managing dependencies between components in a controlled and decoupled manner. This results in more efficient and scalable applications that are easier to maintain and extend over time.

  • What is Dependency Injection?
  • How Dependency Injection works?
  • Why do you need the Dependency Injection?
  • How to register a service?
  • Comparing between service scopes.
  • Registering services automatically.
  • Constructor injection.
  • Property injection.
  • Key differences between Blazor WebAssembly and Blazor Server.
You can download the example code used in this topic on GitHub.

What is Dependency Injection?

Using DI, developers can inject dependencies into a class or component, rather than having to create instances of those dependencies within the class. This makes the code more modular, flexible, and maintainable, as changes to one component do not affect other components that depend on it.

For example, if one class depends on another class, instead of creating an instance of the dependent class, the instance can be injected into the constructor of the first class. This allows the first class to use the functionality of the dependent class without having to manage its creation and lifecycle.


How Dependency Injection works?

Dependency Injection working in 3 steps:

  1. A class with business logic is called a service when it is registered with the ServiceProvider in the Program.cs file. This involves defining the type of the service and any configuration options that are required to create the service.
  2. When a component or a service/class need the registered service, it specifies a property to be the injected instance.
  3. The ServiceProvider then injects the dependency into the component or service using the specified constructor or property. The ServiceProvider creates the instance of the dependency, resolves any dependencies required by that dependency, and injects it into the component or service.

dependency-injection-blazor-wasm.png


Why do you need the Dependency Injection?

In Blazor, dependency injection is essential because it allows you to separate the concerns of your application into individual services or components. This helps to make your code more modular, testable, and easier to maintain.

Here are a few reasons why you might need to use DI in Blazor:

  1. Promotes reusability: By using DI, you can create reusable services that can be shared across different components and services in your application.
  2. Makes your code testable: DI makes it easier to test your code because you can easily replace dependencies with mock objects during unit testing.
  3. Allows for better modularity: DI promotes modularity by allowing you to easily swap out dependencies without having to modify the code that uses them.
  4. Simplifies code maintenance: By separating concerns into individual services, your code becomes more organized and easier to maintain over time.
  5. Reduces code complexity: DI can reduce code complexity by enabling developers to break down business logic into smaller pieces and then combine them together

How to register a service?

In Blazor, you need to register a service before you can inject it into a component. There are several ways to register a service, such as by its class or interface name. Typically, services are registered in the Program.cs file, which is where the application's dependency injection container is configured.

To register a service, you need to insert the registration code between the WebApplication.CreateBuilder(args) and builder.Build() methods in the Program.cs file. This ensures that the service is available to the application's dependency injection container when it is needed.

Register a service by class

To register a service in Blazor using its class name, you can follow these steps:

  1. Define the class that you want to register as a service. For example, you might have a MyService class with the following definition:
public class MyService
{
    public string ExampleString { get; set; } = "";
}
  1. In your Program.cs file, configure the application's dependency injection container by adding the following line of code between the WebApplication.CreateBuilder(args) and builder.Build() call:
builder.Services.AddTransient<MyService>();

This code registers MyService as a transient service. There are other types of service such as scoped and singleton which will be introduced in this tutorial later.

Register a service by interface

To register a service in Blazor using its interface name, you can follow these steps:

  1. Define the interface that you want to register as a service. For example, let's say you have an IService interface with the following definition:
public interface IService
{
}
  1. Create a class that implements the IService interface. For example, you can create a ServiceWithInterface class:
public class ServiceWithInterface : IServiceInterface
{
}
  1. Configure the ServiceProvider in your Program.cs file by adding the following line of code:
builder.Services.AddTransient<IServiceInterface, ServiceWithInterface>();

Register a service with parameter

Sometimes, a generic service needs input parameters to function properly. For example, a data access layer service may require a connection string to a database. Consider the following ServiceWithParameter:

public class ServiceWithParameter
{
    public string ExampleString { get; set; } = "";

    public ServiceWithParameter(string exampleString)
    {
        ExampleString = exampleString;
    }
}

If you don't provide the exampleString parameter when constructing ServiceWithParameter, it will not be created. To instruct the ServiceProvider on how to construct the ServiceWithParameter with the necessary parameter, you can use the following code:

builder.Services.AddTransient<ServiceWithParameter>(serviceProvider => new("Blazor School"));

Register a dependent service

A service can depends on another service. For example, if a web application relies on a database service to store and retrieve data, the web application would be considered a dependent service, while the database service would be considered the parent or upstream service. Consider the following service:

public class DependentService
{
    public ServiceWithParameter UpstreamService { get; set; }

    public DependentService(ServiceWithParameter upstreamService)
    {
        UpstreamService = upstreamService;
    }
}

Then the upstream service is automatically injected to the dependent service. You just need to register as follows:

builder.Services.AddTransient<DependentService>();

Register a dependent service with parameter

When a service depends on another service and requires input parameters, you need to provide instructions to the ServiceProvider on how to create the service. Consider the following code for a dependent service that requires input parameters:

public class DependentServiceWithParameter
{
    public string ExampleString { get; set; } = "";
    public ServiceWithParameter UpstreamService { get; set; }

    public DependentServiceWithParameter(string exampleString, ServiceWithParameter upstreamService)
    {
        ExampleString = exampleString;
        UpstreamService = upstreamService;
    }
}

To register the dependent service with its upstream service and input parameters, you can use the following code:

builder.Services.AddTransient<DependentServiceWithParameter>(serviceProvider=> new("Blazor School", serviceProvider.GetRequiredService<ServiceWithParameter>()));

Comparing between service scopes

When registering a service, it's necessary to specify its scope. Each scope has its own lifetime, you can choose from three different scopes:

  • Singleton
  • Scoped
  • Transient

Singleton and scoped service scope

Although services with singleton and scoped scopes have different scopes, they have the same lifetime. The ServiceProvider creates one instance per browser tab for both scopes. The following image illustrates the singleton and scoped service scopes:

singleton-scoped-service.png

Transient service scope

With transient services, the ServiceProvider creates a new instance every time it's injected. The following image illustrates the transient service scope:

transient-scope.png


Registering services automatically

When registering a service, you need to specify the scope for the service. In case your website has many services, you can simplify the process by registering services automatically by following these steps:

  1. Create an interface for scoped, singleton, and transient services.
public interface IScopedService
{
}
public interface ISingletonService
{
}
public interface ITransientService
{
}
  1. Use reflection to scan and registering the services in Program.cs file:
...
ScanAndRegisterServices(builder.Services);
...

static void ScanAndRegisterServices(IServiceCollection services)
{
    var currentAssembly = Assembly.GetExecutingAssembly();
    var allTypes = currentAssembly.GetTypes().Concat(
        currentAssembly
        .GetReferencedAssemblies()
        .SelectMany(assemblyName => Assembly.Load(assemblyName).GetTypes()))
        .Where(type => !type.IsInterface && !type.IsAbstract);

    var scopedServices = allTypes.Where(type => typeof(IScopedService).IsAssignableFrom(type));

    foreach (var type in scopedServices)
    {
        services.AddScoped(type);
    }

    var transientServices = allTypes.Where(type => typeof(ITransientService).IsAssignableFrom(type));

    foreach (var type in transientServices)
    {
        services.AddTransient(type);
    }

    var singletonServices = allTypes.Where(type => typeof(ISingletonService).IsAssignableFrom(type));

    foreach (var type in singletonServices)
    {
        services.AddTransient(type);
    }
}
  1. Implement one of the interfaces to create a service:
public class AutoTransientService : ITransientService
{
    public Guid ExampleId { get; set; } = Guid.NewGuid();
}

This way, the ServiceProvider will register the services automatically based on their scope, without the need for explicit registration for each service.


Constructor injection

Constructor injection is a technique used in object-oriented programming, where a registered service is injected into the constructor of a dependent service. In this approach, the dependent service declares a dependency on the upstream service through its constructor parameter.

public class DependentService
{
    public ServiceWithParameter UpstreamService { get; set; }

    public DependentService(ServiceWithParameter upstreamService)
    {
        UpstreamService = upstreamService;
    }
}

In the code above, the constructor of DependentService takes an instance of ServiceWithParameter as a parameter. This allows the dependent service to access and use the functionality provided by the upstream service.


Property injection

In Blazor, property injection is a technique used to provide a component with a dependency, such as a service or data source, by assigning it to a property of the component. This can be achieved through the use of either the [Inject] attribute or the @inject directive in the component.

For example, to use the @inject directive in the component as follows:

@inject MyService MyService

Alternatively, you can use the [Inject] attribute for a property in the component as follows:

[Inject]
public MyService MyService { get; set; }

Key differences between Blazor WebAssembly and Blazor Server

The key differences between Blazor WebAssembly and Blazor Server is the lifetime of their service scopes. In Blazor Server, the service scopes have a distinct lifespan compared to Blazor WebAssembly.

BLAZOR SCHOOL
Designed and built with care by our dedicated team, with contributions from a supportive community. We strive to provide the best learning experience for our users.
Docs licensed CC-BY-SA-4.0
Copyright © 2021-2024 Blazor School
An unhandled error has occurred. Reload 🗙