🟨 Slightly different to Blazor WebAssembly
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.
You can download the example code used in this topic on GitHub.
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.
Dependency Injection working in 3 steps:
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.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.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:
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.
To register a service in Blazor using its class name, you can follow these steps:
MyService
class with the following definition:public class MyService { public string ExampleString { get; set; } = ""; }
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.
To register a service in Blazor using its interface name, you can follow these steps:
IService
interface with the following definition:public interface IService { }
IService
interface. For example, you can create a ServiceWithInterface
class:public class ServiceWithInterface : IServiceInterface { }
ServiceProvider
in your Program.cs file by adding the following line of code:builder.Services.AddTransient<IServiceInterface, ServiceWithInterface>();
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"));
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>();
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>()));
When registering a service, it's necessary to specify its scope. Each scope has its own lifetime, you can choose from three different scopes:
The singleton scope is used to register services that should be created only once and shared across the entire application. This means that the same instance of the service will be used for all users that require it, which can be useful for performance optimization and reducing memory usage. The following image illustrates the singleton service scope:
The scoped service scope is used to register services that should be created once per browser tab. The following image illustrates the scoped service scope:
With transient services, the ServiceProvider
creates a new instance every time it's injected. The following image illustrates the transient service scope:
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:
public interface IScopedService { }
public interface ISingletonService { }
public interface ITransientService { }
... 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); } }
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 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.
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; }
In Blazor WebAssembly, the lifespan of the scoped service and the singleton service instances is the same.