Dependency Injection in ASP.NET Core

• By OmerZ Solutions

Modern software applications require flexible architecture, maintainable code, and components that can evolve independently. As applications grow, managing dependencies between classes becomes increasingly difficult.

Dependency Injection is one of the most important design patterns in ASP.NET Core that helps developers build loosely coupled, testable, and scalable applications.

Dependency Injection in ASP.NET Core
Dependency Injection (DI) is a software design technique that provides objects with their required dependencies from an external source instead of creating them internally.

What is Dependency Injection?

Dependency Injection is a design pattern where a class receives the objects it depends on from another component rather than creating those objects itself.

The main purpose of DI is to reduce tight coupling between classes and improve the flexibility of application design.

Traditional Approach Without Dependency Injection

Consider a service that directly creates its own dependency:



public class OrderService
{
    private EmailService _emailService;
    public OrderService()
    {
        _emailService = new EmailService();
    }
    public void CreateOrder()
    {
        _emailService.SendEmail();
    }
}

In this example, OrderService is tightly connected with EmailService. If the email provider changes, the service must also be modified.

Dependency Injection Approach

With Dependency Injection, dependencies are provided from outside the class.



public class OrderService
{
    private readonly IEmailService _emailService;
    public OrderService(IEmailService emailService)
    {
        _emailService = emailService;
    }
    public void CreateOrder()
    {
        _emailService.SendEmail();
    }

}

Now OrderService depends on an abstraction instead of a specific implementation. This makes the code easier to maintain and test.

Understanding Inversion of Control (IoC)

Inversion of Control is a principle where control over object creation is moved from application code to a framework or container.

Dependency Injection is one of the most common implementations of IoC.

Without IoC

  • Classes create their own dependencies
  • Strong coupling exists between components
  • Testing becomes difficult

With IoC

  • Dependencies are provided externally
  • Components become independent
  • Unit testing becomes easier

Why Dependency Injection is Important in ASP.NET Core

ASP.NET Core was designed around Dependency Injection from the beginning. The framework provides a built-in DI container that manages application services automatically.

Benefits of Dependency Injection

  • Loose coupling between components
  • Improved code maintainability
  • Better unit testing support
  • Reusable services
  • Cleaner application architecture
  • Simplified application configuration

ASP.NET Core Built-in Dependency Injection Container

ASP.NET Core includes a built-in service container responsible for creating, managing, and providing application dependencies.

Services are registered inside the Program.cs file.



builder.Services.AddTransient<IEmailService, EmailService>();
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddSingleton<ICacheService, CacheService>();

Important: The service container automatically creates required objects and injects them where they are needed.

Service Registration in ASP.NET Core

Before a dependency can be injected, it must be registered with the ASP.NET Core service container. Service registration tells the framework how objects should be created when they are requested.

Services are usually registered inside the Program.cs file.

Registering a Service



builder.Services.AddScoped<IProductService, ProductService>();

The first parameter represents the abstraction, and the second parameter represents the actual implementation.

Types of Dependency Injection Service Lifetimes

ASP.NET Core provides three built-in service lifetimes. Choosing the correct lifetime is important for application performance and resource management.

  • Transient
  • Scoped
  • Singleton

1. Transient Lifetime

Transient services are created every time they are requested from the service container.

They are suitable for lightweight, stateless services.

Registration Example



builder.Services.AddTransient<IEmailService, EmailService>();

Example Scenario

  • Email sending services
  • Data formatting services
  • Small utility classes

2. Scoped Lifetime

Scoped services are created once per request. The same instance is reused during a single HTTP request.

Scoped lifetime is commonly used with database contexts and business services.

Registration Example



builder.Services.AddScoped<IOrderService, OrderService>();

Common Scoped Services

  • Entity Framework Core DbContext
  • Application business services
  • Repository classes

3. Singleton Lifetime

Singleton services are created only once during the lifetime of the application. The same object instance is reused for all requests.

Registration Example



builder.Services.AddSingleton<ICacheService, CacheService>();

Common Singleton Services

  • Application configuration providers
  • Memory cache services
  • Global application settings
Lifetime Selection Tip: Use Transient for lightweight operations, Scoped for request-based services, and Singleton only for thread-safe shared resources.

Constructor Injection in ASP.NET Core

Constructor Injection is the most commonly used DI technique in ASP.NET Core. Dependencies are provided through a class constructor.

Service Interface



public interface INotificationService
{
    void SendNotification(string message);
}

Service Implementation



public class NotificationService : INotificationService
{
    public void SendNotification(string message)
    {
        Console.WriteLine(message);
    }
}

Injecting Service into Controller



[ApiController]
[Route("api/orders")]

public class OrdersController : ControllerBase
{
	private readonly INotificationService _notificationService;

public OrdersController(INotificationService notificationService)
{
	_notificationService = notificationService;
}

[HttpPost]
public IActionResult Create()
{
	_notificationService.SendNotification("Order Created");
	return Ok();
}

}

Interface-Based Dependency Injection

Using interfaces with Dependency Injection improves flexibility because applications depend on abstractions instead of concrete implementations.

Example



public interface IPaymentService
{

bool ProcessPayment(decimal amount);

}



public class StripePaymentService : IPaymentService
{

public bool ProcessPayment(decimal amount)
{

return true;

}

}

Register Implementation



builder.Services.AddScoped<IPaymentService, StripePaymentService>();

Creating a Custom Application Service

In enterprise applications, business logic is commonly placed inside service classes and injected where required.

Customer Service Example



public interface ICustomerService
{
	Customer GetCustomer(int id);
}

public class CustomerService : ICustomerService
{
	public Customer GetCustomer(int id)
	{
		return new Customer{Id = id, Name = "John"};
	}
}

Register Customer Service



builder.Services.AddScoped<ICustomerService, CustomerService>();

Dependency Injection in Minimal APIs

ASP.NET Core Minimal APIs also support dependency injection directly in route handlers.



app.MapGet("/customers",(ICustomerService service) =>
{
	return service.GetCustomers();
});

Dependency Injection with Entity Framework Core

Entity Framework Core DbContext is automatically designed to work with Dependency Injection.

Registering DbContext



builder.Services.AddDbContext<ApplicationDbContext>
(
	options =>options.UseSqlServer(connectionString)
);

The framework automatically creates and manages DbContext instances according to the configured lifetime.

Dependency Injection with Repository Pattern

The Repository Pattern is commonly used in enterprise ASP.NET Core applications to separate database operations from business logic. Dependency Injection makes it easier to provide repository implementations wherever they are required.

Repository Interface



public interface IProductRepository
{
    IEnumerable<Product> GetProducts();
    Product GetById(int id);
    void Add(Product product);
}

Repository Implementation



public class ProductRepository : IProductRepository
{

private readonly ApplicationDbContext _context;

public ProductRepository(ApplicationDbContext context)
{
	_context = context;
}

public IEnumerable<Product> GetProducts()
{
	return _context.Products.ToList();
}

public Product GetById(int id)
{

	return _context.Products.FirstOrDefault(x => x.Id == id);

}

public void Add(Product product)
{
	_context.Products.Add(product);
	_context.SaveChanges();
}

}

Register Repository with Dependency Injection



builder.Services.AddScoped<IProductRepository, ProductRepository>();

Now any service or controller can request IProductRepository without knowing the internal implementation details.

Dependency Injection in Clean Architecture

Clean Architecture heavily depends on Dependency Injection because each layer communicates through abstractions instead of direct dependencies.

A typical ASP.NET Core Clean Architecture solution contains:

  • Presentation Layer
  • Application Layer
  • Domain Layer
  • Infrastructure Layer

The Application layer defines interfaces while the Infrastructure layer provides concrete implementations.

Dependency Injection in Middleware

Custom middleware can also receive dependencies through constructor injection.

Custom Middleware Example



public class RequestLoggingMiddleware
{

private readonly RequestDelegate _next;

private readonly ILogger<RequestLoggingMiddleware> _logger;

public RequestLoggingMiddleware(RequestDelegate next,ILogger<RequestLoggingMiddleware> logger)
{
	_next = next;
	_logger = logger;
}

public async Task Invoke(HttpContext context)
{
	_logger.LogInformation("Request received");
	await _next(context);
}

}

Register Middleware



app.UseMiddleware<RequestLoggingMiddleware>();

Dependency Injection for Configuration

ASP.NET Core provides configuration injection through the IConfiguration interface.

Using IConfiguration



public class EmailService
{

private readonly IConfiguration _configuration;

public EmailService(IConfiguration configuration)
{
	_configuration = configuration;
}

public void Send()
{

var host = _configuration["EmailHost"];

}

}

Options Pattern in ASP.NET Core

The Options Pattern provides a strongly typed approach for accessing application configuration settings.

Configuration Class



public class EmailSettings
{
	public string Host { get; set; }
	public int Port { get; set; }
}

appsettings.json Example



{
"EmailSettings":
{
	"Host":"smtp.example.com",
	"Port":587
}
}

Register Options



builder.Services.Configure<EmailSettings>(builder.Configuration.GetSection("EmailSettings"));

Inject Options



public class EmailService
{

private readonly EmailSettings _settings;

public EmailService(IOptions<EmailSettings> options)
{

	_settings = options.Value;

}

}

Dependency Injection with Logging

Logging is another built-in ASP.NET Core service that uses Dependency Injection.

Injecting Logger



public class PaymentService
{

private readonly ILogger<PaymentService> _logger;

public PaymentService(ILogger<PaymentService> logger)
{
	_logger = logger;
}

public void Process()
{
	_logger.LogInformation("Payment processing started");
}

}

Testing Dependency Injection Based Applications

Applications built using Dependency Injection are easier to test because dependencies can be replaced with mock implementations.

Mocking a Service



var paymentMock = new Mock<IPaymentService>();
paymentMock.Setup(x => x.ProcessPayment(100)).Returns(true);
var service = new OrderService(paymentMock.Object);
var result = service.CreateOrder();
Assert.True(result);

Advantages of Dependency Injection in Enterprise Applications

  • Improves application maintainability
  • Supports modular architecture
  • Makes unit testing easier
  • Reduces code duplication
  • Improves code readability
  • Allows replacing implementations easily
  • Supports scalable application design

Common Dependency Injection Mistakes

  • Registering services with incorrect lifetimes
  • Using Singleton for non-thread-safe services
  • Creating service instances manually
  • Injecting too many dependencies into one class
  • Making services depend directly on concrete classes
  • Ignoring interface-based design
Architecture Tip: A class with too many injected dependencies often indicates that the class has too many responsibilities and should be redesigned.

Dependency Injection Best Practices

Following recommended Dependency Injection practices helps developers create cleaner, more maintainable, and scalable ASP.NET Core applications.

1. Depend on Abstractions Instead of Implementations

Classes should depend on interfaces rather than concrete classes. This reduces coupling and makes replacing implementations easier.



public class ReportService
{

private readonly IReportGenerator _generator;

public ReportService(IReportGenerator generator)
{
	_generator = generator;
}

}

2. Choose Service Lifetimes Carefully

Incorrect lifetime selection can cause memory issues, unexpected behavior, or performance problems.

Service Lifetime Recommended Usage
Transient Lightweight stateless services
Scoped Request-based services and database operations
Singleton Shared application-wide services

3. Avoid Service Locator Pattern

The Service Locator pattern hides dependencies and makes code harder to understand and test.

Prefer constructor injection because dependencies are clearly visible.

4. Keep Services Focused

A service should have one clear responsibility. Large services with many dependencies become difficult to maintain.

Following the Single Responsibility Principle creates cleaner DI designs.

Dependency Injection in Large Enterprise Applications

Large applications usually contain hundreds of services. Proper organization helps maintain a clean dependency structure.

Extension Methods for Service Registration

In enterprise projects, service registration can become large. Extension methods keep Program.cs clean and organized.

Creating Service Extension



public static class ServiceRegistration
{

public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{

services.AddScoped<IProductService,ProductService>();
services.AddScoped<IOrderService,OrderService>();
return services;

}

}

Using Extension Method



builder.Services.AddApplicationServices();

Dependency Injection and SOLID Principles

Dependency Injection supports several SOLID design principles, especially the Dependency Inversion Principle.

Dependency Inversion Principle

High-level modules should not depend on low-level modules. Both should depend on abstractions.

This principle allows applications to remain flexible as requirements change.

Frequently Asked Questions About Dependency Injection

What is Dependency Injection in ASP.NET Core?

Dependency Injection is a built-in design approach in ASP.NET Core that allows classes to receive required dependencies from the framework instead of creating them manually.

Does ASP.NET Core have a built-in DI container?

Yes. ASP.NET Core includes a built-in dependency injection container that manages service registration, object creation, and lifetime management.

Which Dependency Injection lifetime should I use with Entity Framework Core?

DbContext is normally registered with a Scoped lifetime because one instance is used throughout a single HTTP request.

What is the difference between Scoped and Singleton services?

Scoped services are created once per HTTP request, while Singleton services are created once for the entire application lifetime.

Can Dependency Injection improve unit testing?

Yes. DI allows developers to replace real dependencies with mock objects, making unit tests faster and more reliable.

Is Dependency Injection mandatory in ASP.NET Core?

It is not mandatory, but ASP.NET Core is designed around Dependency Injection, and using it is considered a best practice for modern .NET applications.

Dependency Injection Checklist

  • Register services using appropriate lifetimes.
  • Use interfaces for important application services.
  • Avoid creating dependencies manually.
  • Keep constructors simple.
  • Use options pattern for configuration.
  • Use DI for logging and database access.
  • Test services using mocked dependencies.
  • Organize service registration in large applications.

Conclusion

Dependency Injection is a fundamental concept in ASP.NET Core application development. It helps developers create loosely coupled, maintainable, and testable software systems.

By understanding service registration, lifetime management, constructor injection, and architectural best practices, developers can build applications that are easier to extend and maintain.

Whether you are developing a small Web API or a large enterprise platform, proper use of Dependency Injection creates a strong foundation for scalable .NET solutions.

Need Help Building Scalable ASP.NET Core Applications?

OmerZ Solutions helps businesses build secure, scalable, and maintainable .NET applications using modern software architecture practices.

Contact Us