Welcome to the last post of this series and congrats for getting this far. You should now have a solid understanding of Dependecy Injection so let's implement it in dotnet.
Lifetimes
Dotnet support different lifetimes, Transient, Scoped and Singleton.
You would tipically use Scoped in WebApps and Apis where the scoped lifetime is the lifetime of a HTTP Context. If you work in Maui or WPF etc you need to explicity create the scope and that adds extra complexity so normally you would use Transient or Singleton in those types of applications.
SetUp
You do the setup and configuration in Program.cs.
If you work in Maui or ASP.NET Core, you will see that Dependecy injection is already setup but in this example we use a Console App so we need to set it up ourselves.
Start off by downloading the following packages.
Microsoft.Extensions.Hosting
Microsoft.Extensions.DependencyInjection
Create the default builder and configure your services
var host = Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
// Add Your Services here
}).Build();
Add collection of services
So as you can see below you can add a collection of services with a defined lifetime.
var host = Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddSingleton<MySingleTonService>();
services.AddScoped<MyScopedService>();
services.AddTransient<MyTransientService>();
}).Build();
Add interfaces
You can also add a interface to the registered service which is also best practice.
var host = Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddSingleton<IMyService, MyService>();
}).Build();
IServiceProvider
The collection of services we just configured implements the interface IServiceprovider.
var host = Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
// My Collection of registered services
}).Build();
IServiceProvider serviceProvider = host.Services;
Put it all together
Create the Services and interfaces
In this example we have a notification service that is dependent on an email service.
public interface IEmailService
{
public void SendEmail(string email, string message);
}
public class EmailService : IEmailService
{
public void SendEmail(string email, string message)
{
Console.WriteLine($"Sending {message} to {email}");
}
}
public interface INotificationService
{
public void NotifyUser(User user, string message);
}
public class NotficationService : INotificationService
{
IEmailService _emailService;
public NotficationService(IEmailService emailService)
{
_emailService = emailService;
}
public void NotifyUser(User user, string message)
{
_emailService.SendEmail(user.Email, message);
}
}
public class User
{
public string Name { get; set; } = null!;
public string Email { get; set; } = null!;
}
Register the services
var host = Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddTransient<IEmailService, EmailService>();
services.AddTransient<INotificationService, NotficationService>();
}).Build();
Access the Notification service
var notify = host.Services.GetRequiredService<INotificationService>();
User user = new User { Name = "Emanuel", Email = "Emanuel@domain.com" };
notify.NotifyUser(user, "Hello There!");
// output: Sending Hello There! to Emanuel@domain.com
Use IServiceProvider to access services
You can also access services through IServiceProvider. Let us rewrite the Notification Service.
public class NotficationService : INotificationService
{
IServiceProvider _serviceProvider;
public NotficationService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void NotifyUser(User user, string message)
{
var emailService = _serviceProvider.GetRequiredService<IEmailService>();
emailService.SendEmail(user.Email, message);
}
}
This will work in the exact same way and we have more control over what services we want to access.
Different Lifetimes in action.
So in a Console App we need to create the scope explicity so let's do that.
For demostration purpose I will create a model that contains a unique ID and we will see how i singleton returns the same instance throughout the hole program and therefore the same ID. Scoped will return the same instance in the same scope and a transient will return a new instance everytime.
var host = Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddScoped<UniqueIDSingelton>();
services.AddScoped<UniqueIDScoped>();
services.AddTransient<UniqueIDTransient>();
}).Build();
public class UniqueIDSingelton
{
public string ID = Guid.NewGuid().ToString();
}
public class UniqueIDScoped
{
public string ID = Guid.NewGuid().ToString();
}
public class UniqueIDTransient
{
public string ID = Guid.NewGuid().ToString();
}
Create two scopes.
var scope1 = host.Services.CreateScope();
var scope2 = host.Services.CreateScope();
Now let us see how a serviice with scoped lifetime behave.
var scope1 = host.Services.CreateScope();
var UniqueId1Scope1 = scope1.ServiceProvider.GetRequiredService<UniqueIDScoped>();
var UniqueId2Scope1 = scope1.ServiceProvider.GetRequiredService<UniqueIDScoped>();
Console.WriteLine(UniqueId1Scope1.ID == UniqueId2Scope1.ID); // true
var scope2 = host.Services.CreateScope();
var UniqueIdScope2 = scope2.ServiceProvider.GetRequiredService<UniqueIDScoped>();
Console.WriteLine(UniqueId1Scope1.ID == UniqueIdScope2.ID); // false
The instance will be the same in the same scope as you see.
let us move over to Singelton. You will see that even though I get the service in two different scopes the instance and therfore the ID is the same.
var scope1 = host.Services.CreateScope();
var singeltonScope1 = scope1.ServiceProvider.GetRequiredService<UniqueIDSingelton>();
var scope2 = host.Services.CreateScope();
var singeltonScope2 = scope1.ServiceProvider.GetRequiredService<UniqueIDSingelton>();
Console.WriteLine(singeltonScope1.ID == singeltonScope2.ID); // true
Last let's see Transient in action and how it gives a new instance everytime. Below we call the service twice in the same scope but the instance and therefore the ID is different.
var scope1 = host.Services.CreateScope();
var transient1 = scope1.ServiceProvider.GetRequiredService<UniqueIDTransient>();
var transient2 = scope1.ServiceProvider.GetRequiredService<UniqueIDTransient>();
Console.WriteLine(transient1.ID == transient2.ID); // false
Alright there you have Dependency Injection in Dotnet and as I told you in an ASP.NET Core Application you do not need to explicity create the scope because the scope is already configured to be the scope of a HTTP request and in other types of applications like Maui etc, you would rather use Transient and Singelton.
Hope you found it useful and happy coding!
Top comments (0)