Interview Questions: Configuration, Dependency Injection, and Hosted Services

Understand lifetimes, appsettings layering, and background tasks

Posted by Rodrigo Castro on June 27, 2025

Continuing our series on ASP.NET Core interview essentials, let’s dig deeper into five important topics that deal with how your application is configured, structured, and executed.

How does appsettings.json configuration layering work?

ASP.NET Core supports a hierarchical and override-friendly configuration system. Configuration sources are layered in order: where the last loaded source can override values from earlier ones.

Most common sources:

1. appsettings.json

1
2
3
4
5
6
{
  "AppSettings": {
    "FeatureToggle": true,
    "Title": "Default Title"
  }
}

Loaded with:

1
builder.Configuration.AddJsonFile("appsettings.json", optional: false);

2. appsettings.{Environment}.json

For example, in appsettings.Production.json:

1
2
3
4
5
{
  "AppSettings": {
    "Title": "Production Title"
  }
}

Loaded with:

1
builder.Configuration.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

3. Environment Variables

1
set AppSettings__Title="FromEnv"

Note the __ is used to navigate nested sections.

4. Command-Line Arguments

1
--AppSettings:Title="FromCommandLine"

Loaded via:

1
builder.Configuration.AddCommandLine(args);

5. User Secrets (only in Development)

1
dotnet user-secrets set "AppSettings:Title" "FromSecrets"

Enabled with:

1
builder.Configuration.AddUserSecrets<Program>();

6. Azure Key Vault

Use secure cloud-stored secrets:

1
builder.Configuration.AddAzureKeyVault(...);

Custom Provider Example: Load From Database

You can implement IConfigurationProvider to load configuration from a database table:

1
2
3
4
5
6
7
8
9
10
public class DbConfigurationProvider : ConfigurationProvider
{
    private readonly MyDbContext _db;
    public DbConfigurationProvider(MyDbContext db) => _db = db;

    public override void Load()
    {
        Data = _db.Configs.ToDictionary(x => x.Key, x => x.Value);
    }
}

Register with:

1
builder.Configuration.Add(new DbConfigurationSource());

What is the difference between Singleton, Scoped, and Transient services?

These determine the lifetime and reuse of service instances in dependency injection.

Lifetime Instance Created Shared Across Example Use Case
Singleton Once on app startup All requests and threads Logging, configuration, caching
Scoped Once per HTTP request A single request Repositories, DbContexts
Transient Every time it’s requested Never reused Lightweight utilities, formatters

Code Example:

1
2
3
builder.Services.AddSingleton<ISingletonService, SingletonService>();
builder.Services.AddScoped<IScopedService, ScopedService>();
builder.Services.AddTransient<ITransientService, TransientService>();

How to use Scoped service inside a Singleton service?

Never inject a scoped service directly into a singleton: it leads to lifetime mismatch issues.

Don’t do this:

1
2
3
4
public class BadSingleton
{
    public BadSingleton(IMyScopedService scopedService) { ... } // Wrong
}

Correct way using IServiceScopeFactory:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SafeSingleton
{
    private readonly IServiceScopeFactory _scopeFactory;

    public SafeSingleton(IServiceScopeFactory scopeFactory) => _scopeFactory = scopeFactory;

    public void DoWork()
    {
        using var scope = _scopeFactory.CreateScope();
        var scopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>();
        scopedService.Execute();
    }
}

📌 Be careful with manual scope management: it adds complexity and risk.

How to execute code when application is starting and stopping?

ASP.NET Core exposes application lifecycle hooks via IHostApplicationLifetime or the new IHostApplicationBuilder.Lifetime.

Option 1: Simple Events

1
2
app.Lifetime.ApplicationStarted.Register(() => Console.WriteLine("Started!"));
app.Lifetime.ApplicationStopping.Register(() => Console.WriteLine("Stopping..."));

Option 2: Use IHostedService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LifecycleLogger : IHostedService
{
    public Task StartAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine("Startup logic here");
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine("Cleanup logic here");
        return Task.CompletedTask;
    }
}

Register it:

1
builder.Services.AddHostedService<LifecycleLogger>();

What is BackgroundService?

BackgroundService is a base class for long-running hosted services.

It runs independently of HTTP requests, ideal for:

  • Polling APIs
  • Processing queues
  • Scheduled cleanup jobs

Example:

1
2
3
4
5
6
7
8
9
10
11
public class MyWorker : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            Console.WriteLine($"Running at {DateTime.Now}");
            await Task.Delay(1000, stoppingToken);
        }
    }
}

Register it:

1
builder.Services.AddHostedService<MyWorker>();

In the past, developers often relied on external scripts, scheduled tasks, or standalone services to perform background processing. Nowadays, with modern dotnet technologies, you are able to ship a BackgroundService alongside your application to execute background tasks.

✅ When it’s a good practice

You can include a BackgroundService in the same Web API project when:

  • The background job is tightly coupled with the business logic of the API
  • You need quick deployment and simpler architecture
  • It doesn’t require independent scaling or fault isolation
  • You’re doing lightweight background tasks like sending emails, queue processing, scheduled cache refreshing

⚠️ When you should consider splitting

Split into a separate project/service if:

  • The background task is resource-intensive or long-running
  • You want separate scaling, resiliency, or failover mechanisms
  • It requires different deployment/release cycles
  • It should run even if the Web API is offline (e.g., in a worker container or queue processor)

Best practice guideline

For simple tasks, keep them together. For complex, critical, or independently-scalable jobs, isolate them into dedicated worker services or microservices.

🧠 Background services integrate into the hosting lifecycle, supporting graceful shutdown.

With these five concepts under your belt, you’re much better equipped to build, debug, and scale ASP.NET Core apps. If you’re preparing for an interview, try building a sample CRUD project that incorporates configuration layers, DI lifetimes, and hosted services. Applying these concepts in practice will solidify your understanding and boost your confidence.