Explain built-in dependency injection in ASP.NET Core.?
ASP.NET Core has a built-in Dependency Injection (DI) container, which means you
don’t need third-party frameworks like Autofac or Ninject.
DI allows you to register classes (services) in a central place and then inject them
wherever they’re needed. This promotes loose coupling, testability, and maintainability.
Example:
public interface IEmailService
void Send(string to, string subject, string body);
Follow :
public class EmailService : IEmailService
public void Send(string to, string subject, string body)
=> Console.WriteLine($"Email sent to {to}");
Register it in Program.cs:
builder.Services.AddScoped<IEmailService, EmailService>();
Use it in a controller:
public class AccountController : Controller
private readonly IEmailService _emailService;
public AccountController(IEmailService emailService)
_emailService = emailService;
public IActionResult Register()
_emailService.Send("user@example.com", "Welcome!", "Thanks
for joining!");
return View();
🔁 2. What is the lifetime of services (Transient,
Scoped, Singleton)?
ASP.NET Core provides three lifetimes for services:
Lifetime Description Example Use Case
Follow :
Transien
A new instance is created every time
the service is requested.
Lightweight, stateless services like
formatters or mappers.
Scoped A new instance is created per HTTP
request.
Database contexts, unit of work
patterns.
Singleto
A single instance for the entire app
lifetime.
Configuration, caching, or logging
services.
Example:
builder.Services.AddTransient<IFormatter, TextFormatter>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
builder.Services.AddSingleton<IAppCache, MemoryAppCache>();
Tip: Scoped is most common for web apps — e.g., DbContext.
🧱 3. How do you inject dependencies into controllers?
ASP.NET Core automatically uses constructor injection for controllers.
The DI container will look at the constructor parameters and inject registered services.
Example:
public class OrderController : Controller
private readonly IOrderService _orderService;
public OrderController(IOrderService orderService)
_orderService = orderService;
public IActionResult Index() => View(_orderService.GetAll());
You don’t manually create OrderService; the framework does.
Follow :
🔄 4. How do you resolve dependencies in
middleware?
Middleware is created once at startup, so you can’t inject scoped services directly into its
constructor.
Instead, you resolve them inside the Invoke method using the
HttpContext.RequestServices.
Example:
public class LoggingMiddleware
private readonly RequestDelegate _next;
public LoggingMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
var logger =
context.RequestServices.GetRequiredService<ILogger<LoggingMiddleware
>>();
logger.LogInformation("Request path: " +
context.Request.Path);
await _next(context);
Register it:
app.UseMiddleware<LoggingMiddleware>();
🧰 5. How to use IServiceProvider?
IServiceProvider is the built-in service resolver. You can use it to manually resolve
registered dependencies.
Follow :
Example:
var serviceProvider = builder.Services.BuildServiceProvider();
var emailService =
serviceProvider.GetRequiredService<IEmailService>();
emailService.Send("user@example.com", "Test", "DI working!");
In general:
- Use IServiceProvider sparingly — prefer constructor injection.
- Good for scenarios like custom factories or background tasks.
⚠ 6. What happens if a scoped service is injected into
a singleton?
That’s a lifetime mismatch — it causes problems because:
- The singleton lives for the app’s lifetime.
- The scoped service lives only for a single request.
If a singleton holds a reference to a scoped service, that scoped instance never gets
released — leading to memory leaks and unexpected data sharing across requests.
Solution:
- Don’t inject scoped services into singletons.
- Instead, inject an IServiceProvider and create a scope manually when needed:
public class ReportGenerator
private readonly IServiceProvider _provider;
Follow :
public ReportGenerator(IServiceProvider provider) => _provider =
provider;
public void Generate()
using var scope = _provider.CreateScope();
var db =
scope.ServiceProvider.GetRequiredService<AppDbContext>();
// Use db safely here
🧩 7. How to register multiple implementations for an
interface?
Sometimes you may have multiple implementations of the same interface — you can
register all of them and inject IEnumerable<T>.
Example:
builder.Services.AddTransient<INotificationService,
EmailNotification>();
builder.Services.AddTransient<INotificationService,
SmsNotification>();
Then:
public class NotificationManager
private readonly IEnumerable<INotificationService> _services;
public NotificationManager(IEnumerable<INotificationService>
services)
_services = services;
Follow :
public void NotifyAll(string message)
foreach (var service in _services)
service.Send(message);
This will call both EmailNotification and SmsNotification.
🧱 8. What is TryAdd in dependency injection?
TryAdd methods (from
Microsoft.Extensions.DependencyInjection.Extensions) register a service
only if it’s not already registered.
Example:
builder.Services.TryAddScoped<ILogService, DefaultLogService>();
If another part of the code already registered a custom ILogService, this one won’t
override it.
Use Case:
When writing reusable libraries or frameworks that should allow overriding defaults.
⚙ 9. How do you inject configuration or options into
services?
You can bind configuration sections (from appsettings.json) to strongly typed
classes and inject them using IOptions<T>.
Example:
// appsettings.json
Follow :
"SmtpSettings": {
"Host": "smtp.mailtrap.io",
"Port": 2525
Model:
public class SmtpSettings
public string Host { get; set; }
public int Port { get; set; }
Register:
builder.Services.Configure<SmtpSettings>(
builder.Configuration.GetSection("SmtpSettings"));
Use in service:
public class EmailService
private readonly SmtpSettings _settings;
public EmailService(IOptions<SmtpSettings> options)
_settings = options.Value;
🧠 10. What are IOptions, IOptionsSnapshot, and
IOptionsMonitor?
Interface Scope Use Case
Follow :
IOptions<T> Singleton Read config at startup — doesn’t change at
runtime.
IOptionsSnapshot<T
Scoped (per
request)
Re-reads configuration on each request.
Useful in web apps.
IOptionsMonitor<T> Singleton, listens
for changes
Automatically updates when config changes
(great for dynamic reloading).
Example:
public class MyService
private readonly IOptionsMonitor<SmtpSettings> _settings;
public MyService(IOptionsMonitor<SmtpSettings> settings)
_settings = settings;
public void SendMail()
Console.WriteLine($"Sending using host:
{_settings.CurrentValue.Host}");
If you change appsettings.json and
configuration reload is enabled,
IOptionsMonitor picks up the new
value without restarting the app.
Follow :
Entity Framework Core