Dependency Injection (DI) is a key design principle that enhances modularity and testability by decoupling class dependencies. While third-party DI frameworks like Autofac offer extensive features, the DI container built into .NET Core (and later versions) provides a simple, performant, and tightly integrated solution. In this blog post, we’ll create a sample airline booking application using .NET’s built-in DI capabilities. We’ll explore its benefits, limitations, and compare it to Autofac.
Why Use .NET’s Built-in DI?
.NET’s built-in DI container is:
- Lightweight: Optimized for performance and simplicity.
- Integrated: Seamlessly works with ASP.NET Core, console applications, and other .NET project types.
- Extensible: Supports common DI patterns and can integrate with third-party frameworks when necessary.
Sample Airline Application: Requirements
We’ll build a simplified airline booking application with the following services:
- FlightSearchService: Searches for available flights.
- BookingService: Handles flight bookings.
- PaymentProcessor: Processes payments.
- NotificationService: Sends notifications to customers.
Step-by-Step Implementation
Step 1: Define Interfaces and Implementations
Create interfaces and corresponding implementations for the services.
FlightSearchService
public interface IFlightSearchService
{
IEnumerable<Flight> SearchFlights(string origin, string destination, DateTime travelDate);
}
public class FlightSearchService : IFlightSearchService
{
public IEnumerable<Flight> SearchFlights(string origin, string destination, DateTime travelDate)
{
return new List<Flight>
{
new Flight { FlightNumber = "AI123", Origin = origin, Destination = destination, TravelDate = travelDate }
};
}
}
BookingService
public interface IBookingService
{
Booking CreateBooking(Flight flight, Passenger passenger);
}
public class BookingService : IBookingService
{
public Booking CreateBooking(Flight flight, Passenger passenger)
{
return new Booking { Flight = flight, Passenger = passenger, BookingId = Guid.NewGuid() };
}
}
PaymentProcessor
public interface IPaymentProcessor
{
bool ProcessPayment(string cardNumber, decimal amount);
}
public class PaymentProcessor : IPaymentProcessor
{
public bool ProcessPayment(string cardNumber, decimal amount)
{
return true; // Simulate payment success
}
}
NotificationService
public interface INotificationService
{
void SendNotification(string email, string message);
}
public class NotificationService : INotificationService
{
public void SendNotification(string email, string message)
{
Console.WriteLine($"Email sent to {email}: {message}");
}
}
Step 2: Configure Services in .NET’s DI Container
In the Program.cs
file (or Startup.cs
in older versions), register the services with the DI container.
var builder = WebApplication.CreateBuilder(args);
// Register services
builder.Services.AddTransient<IFlightSearchService, FlightSearchService>();
builder.Services.AddTransient<IBookingService, BookingService>();
builder.Services.AddSingleton<IPaymentProcessor, PaymentProcessor>();
builder.Services.AddScoped<INotificationService, NotificationService>();
var app = builder.Build();
app.MapControllers();
app.Run();
Step 3: Inject Dependencies into Controllers
Use constructor injection to access the services in your controllers.
[ApiController]
[Route("api/[controller]")]
public class BookingController : ControllerBase
{
private readonly IFlightSearchService _flightSearchService;
private readonly IBookingService _bookingService;
private readonly IPaymentProcessor _paymentProcessor;
private readonly INotificationService _notificationService;
public BookingController(
IFlightSearchService flightSearchService,
IBookingService bookingService,
IPaymentProcessor paymentProcessor,
INotificationService notificationService)
{
_flightSearchService = flightSearchService;
_bookingService = bookingService;
_paymentProcessor = paymentProcessor;
_notificationService = notificationService;
}
[HttpPost("book")]
public IActionResult BookFlight(BookingRequest request)
{
var flights = _flightSearchService.SearchFlights(request.Origin, request.Destination, request.TravelDate);
var selectedFlight = flights.FirstOrDefault();
if (selectedFlight == null) return NotFound("Flight not found.");
var booking = _bookingService.CreateBooking(selectedFlight, request.Passenger);
if (!_paymentProcessor.ProcessPayment(request.PaymentDetails.CardNumber, selectedFlight.Price))
return BadRequest("Payment failed.");
_notificationService.SendNotification(request.Passenger.Email, "Booking confirmed!");
return Ok(booking);
}
}
Comparing .NET DI to Autofac
Feature | .NET DI | Autofac |
---|---|---|
Integration | Built into .NET Core | Requires additional setup |
Ease of Use | Simple and minimalistic | More complex but feature-rich |
Advanced Features | Limited | Supports interceptors, decorators |
Lifetime Scopes | Basic (Transient, Scoped, Singleton) | Highly customizable |
Performance | Lightweight and fast | Slightly more overhead |
Community Support | Official Microsoft support | Large open-source community |
Summary
Using the built-in DI container in .NET is a practical choice for most applications, offering simplicity, tight integration, and excellent performance. For our airline booking application, it was straightforward to configure and use the required services.
However, if your application requires advanced DI features like decorators, interceptors, or dynamic module loading, Autofac might be a better fit. Each tool has its strengths and trade-offs, so the choice depends on your project’s complexity and specific needs.
By leveraging .NET’s DI or Autofac appropriately, you can create clean, maintainable, and testable code, ensuring a solid foundation for your application’s success.