Attendance App: Real-Time Use of DI Lifetimes

Dependency Injection (DI) in ASP.NET Core is more than just a design pattern — it’s a powerful tool for managing service lifetimes and dependencies in real-world applications. In this guide, we'll explore how an Attendance App uses different DI lifetimes (Singleton, Scoped, Transient) to handle services like logging, attendance tracking, OTP generation, and email notifications.

🎯 App Features

  • Students check-in/out
  • Admins manage students
  • Operations are logged (Singleton)
  • Attendance tracked per request (Scoped)
  • Email/OTP as lightweight utilities (Transient)

πŸ” Quick Overview: DI Lifetimes in ASP.NET Core

ASP.NET Core Dependency Injection Lifetimes Diagram

Before diving into the app, here’s a quick refresher on DI lifetimes:

  • Singleton: One instance shared across the entire application lifetime. Good for stateless or shared resources (e.g., logging).
  • Scoped: One instance per client request. Ideal for services that maintain request-specific state or database transactions.
  • Transient: New instance every time the service is requested. Best for lightweight, stateless services.

Note on Transient Services: Transient services should ideally be stateless. Because a new instance is created every time they're requested, keeping them stateless helps avoid unexpected side effects and makes them lightweight and easy to manage.

πŸ” 1. Service Lifetimes in Real Context

Service Lifetime Why?
ILoggingService Singleton One instance logs everything app-wide
AttendanceService Scoped Per HTTP request to avoid stale or shared state
EmailNotificationService Transient Lightweight utility, recreated each time
OtpGeneratorService Transient Stateless; new instance per use
DbContext Scoped Scoped per request to safely manage DB transactions

🧾 2. Interface Definitions

public interface ILoggingService {
    void Log(string message);
}

public interface IAttendanceService {
    string MarkAttendance(int studentId);
}

public interface IEmailNotificationService {
    void Send(string to, string subject, string body);
}

public interface IOtpGeneratorService {
    string GenerateOtp();
}

⚙️ 3. Implementations

public class LoggingService : ILoggingService {
    private readonly string _id = Guid.NewGuid().ToString();
    public void Log(string message) {
        Console.WriteLine($"[{_id}] {DateTime.Now}: {message}");
    }
}

public class AttendanceService : IAttendanceService {
    private readonly ILoggingService _logger;
    public AttendanceService(ILoggingService logger) {
        _logger = logger;
    }
    public string MarkAttendance(int studentId) {
        _logger.Log($"Attendance marked for student ID: {studentId}");
        return $"Attendance marked for student {studentId}";
    }
}

public class EmailNotificationService : IEmailNotificationService {
    private readonly ILoggingService _logger;
    public EmailNotificationService(ILoggingService logger) {
        _logger = logger;
    }
    public void Send(string to, string subject, string body) {
        _logger.Log($"Email sent to {to} with subject '{subject}'");
    }
}

public class OtpGeneratorService : IOtpGeneratorService {
    public string GenerateOtp() {
        return new Random().Next(100000, 999999).ToString();
    }
}

πŸ“¦ 4. Register Services in Program.cs

builder.Services.AddSingleton<ILoggingService, LoggingService>();          // Singleton
builder.Services.AddScoped<IAttendanceService, AttendanceService>();       // Scoped
builder.Services.AddTransient<IEmailNotificationService, EmailNotificationService>();  // Transient
builder.Services.AddTransient<IOtpGeneratorService, OtpGeneratorService>();             // Transient

// EF Core DbContext registration (Scoped by default)
builder.Services.AddDbContext<ApplicationDbContext>(options => 
    options.UseSqlServer(connectionString));

πŸ§‘‍🏫 5. Controller Example

[ApiController]
[Route("api/[controller]")]
public class AttendanceController : ControllerBase {
    private readonly IAttendanceService _attendanceService;
    private readonly IEmailNotificationService _emailService;
    private readonly IOtpGeneratorService _otpService;
    private readonly ILoggingService _logger;

    public AttendanceController(IAttendanceService attendanceService, IEmailNotificationService emailService, IOtpGeneratorService otpService, ILoggingService logger) {
        _attendanceService = attendanceService;
        _emailService = emailService;
        _otpService = otpService;
        _logger = logger;
    }

    [HttpPost("{studentId}")]
    public IActionResult MarkAttendance(int studentId) {
        var attendanceResult = _attendanceService.MarkAttendance(studentId);
        string otp = _otpService.GenerateOtp();
        _emailService.Send("student@school.com", "Attendance Marked", $"Your OTP: {otp}");
        _logger.Log("Attendance workflow completed.");
        return Ok(new {
            Attendance = attendanceResult,
            OtpSent = otp
        });
    }
}

πŸ“€ Final Output Example

{
  "Attendance": "Attendance marked for student 101",
  "OtpSent": "732819"
}

Note: OTP is dynamically generated; this is just a sample output.

🧠 Summary of DI Usage

Service Real Usage Lifetime Reason
ILoggingService Log across layers Singleton = single instance
AttendanceService Track each HTTP request Scoped = prevents state leaks
EmailNotificationService Send email per event Transient = lightweight, short-lived
OtpGeneratorService Generate new OTP each time Transient = stateless

πŸš€ Why This Matters

Choosing the correct lifetimes for your services not only ensures the right behavior but also directly impacts application performance and resource management. For example, using a Singleton for a service that maintains user-specific state could lead to data leaks and hard-to-debug bugs. On the other hand, using Scoped or Transient lifetimes unnecessarily might cause excessive object creation and memory overhead. Thoughtful lifetime management keeps your app efficient, reliable, and easier to maintain.

Comments

Popular posts from this blog

Debouncing & Throttling in RxJS: Optimizing API Calls and User Interactions

Promises in Angular

Comprehensive Guide to C# and .NET Core OOP Concepts and Language Features