Organize Your ASP.NET Core DI Like a Pro! Move Service Registrations to a Separate Class for Clean, Scalable Code

Organizing your Dependency Injection (DI) setup in ASP.NET Core by moving service registrations to a separate class using extension methods is a simple yet powerful step toward writing cleaner, more maintainable, and scalable applications.

In modern ASP.NET Core (especially .NET 6 and later), the Program.cs file is the entry point of your web application. It:

    Builds your web host

    Sets up services (via DI)

    Configures middleware

    Starts the app

What Goes Wrong?

In small projects, you might only register a few services:

Program.cs looking like this:

builder.Services.AddScoped<IMyService, MyService>();

builder.Services.AddControllers();

But as the app grows:

Business logic gets more complex

The file becomes too long and messy.

It mixes service registration, routing, and app startup logic in one place.

It’s hard to read and harder to maintain.

If you want to update or replace a service, you have to dig through the clutter.

Now, Program.cs starts looking like this:

builder.Services.AddScoped<IMyService, MyService>();

builder.Services.AddScoped<IUserService, UserService>();

builder.Services.AddScoped<IEmailSender, SendGridEmailSender>();

builder.Services.AddScoped<IPaymentProcessor, StripeProcessor>();

builder.Services.AddScoped<IReportGenerator, PdfReportGenerator>();

builder.Services.AddDbContext<AppDbContext>(...);

builder.Services.AddAutoMapper(typeof(Startup));

builder.Services.AddControllers();

Solution

Move the service registrations to a separate file using an extension method. Then in Program.cs, you just call:

                builder.Services.DIScope();

Example 

1. Define service IGreetingService

namespace DIServiceExtension.Service

{

    public interface IGreetingSerice

    { 

        string Greet(string message);

    }

    public class GreetingService : IGreetingSerice

    {

        public string Greet(string message="Hello Developers...")

        {

            return message;

        }

    }

}

2. Define serivce IEmailSerice

using DIServiceExtension.Helper;


namespace DIServiceExtension.Service

{

    public interface IEmailService

    {

        string SendMail();

    }

    public class EmailService:IEmailService

    {

        private readonly HelperService _helperService;

        public EmailService(HelperService helperService)

        {

            _helperService = helperService;

        }

        public string SendMail()

        {

            return $"Mail sent successfully! on {_helperService.GetUTCDateTime()}";

        }

    }

}

3. Define HelperService

namespace DIServiceExtension.Helper

{

    public class HelperService

    {

        public DateTime GetUTCDateTime()

        {

            return DateTime.UtcNow;

        }

    }

}

4. Create an Extension Method 

using DIServiceExtension.Helper;

using DIServiceExtension.Service;

using Microsoft.Extensions.DependencyInjection;

namespace DIServiceExtension

{

    public static class ServiceExtension

    {

        /* public static void DIScope(this IServiceCollection services)

         {

             services.AddScoped<IEmailService, EmailService>();

             services.AddScoped<IGreetingSerice, GreetingService>();

             services.AddScoped<HelperService>();

         }*/

        public static IServiceCollection DIScope(this IServiceCollection services)

        {

            services.AddScoped<IEmailService, EmailService>();

            services.AddScoped<IGreetingSerice, GreetingService>();

            services.AddScoped<HelperService>();


            return services;

        }

    }

}

5. Use the Extension Method in Program.cs

                      builder.Services.DIScope();

The Benefits

1. Cleaner Code

    Program.cs is focused only on application startup

    DI logic is encapsulated in its own file

2. Better Separation of Concerns

    DI setup is not mixed with routing, middleware, or host configuration

    You can reuse or test the DI configuration independently

3. Easier Maintenance

    Adding a new service? Just update one centralized method.

    Need to switch to a different implementation? Update it in one place.

4. Scalability

    Large apps often register hundreds of services

    You can break your DI setup into multiple layers:

builder.Services

    .AddInfrastructureServices()

    .AddApplicationServices()

    .AddCustomServices();

Want to See It in Action?

If you'd like a visual walkthrough of everything covered in this post — with real code examples and a step-by-step explanation —

Check out my YouTube video here: Watch

Don’t forget to like, comment, and subscribe if you find it helpful!


Comments

Popular posts from this blog

Understanding Middleware in .NET Core

Database Approaches in .NET Core: Code-First vs Database-First Explained for Beginners

Dev tunnels