skip to content
Khalid Abuhakmeh logo Khalid Abuhakmeh

ASP.NET Core Startup: Resolve Services, Not BuildServiceProvider

Run ASP.NET Core startup work right: a BackgroundService, or GetRequiredService with CreateScope in Program.cs. BuildServiceProvider is an anti-pattern.

5 min read Updated

The best way to run startup work in ASP.NET Core today is a hosted service: derive from BackgroundService (or implement IHostedService), create a scope with IServiceScopeFactory, and register it with AddHostedService<T>(). Only resolve services inline in Program.cs (with app.Services.CreateScope() and GetRequiredService<T>()) when something must run before the app starts handling requests. Either way, never call services.BuildServiceProvider() from application code; it builds a second container and triggers the ASP0000 analyzer warning.

Why Resolve A Service In ASP.NET Core Startup?

ASP.NET Core leans heavily on dependency injection: you register services once, and the framework instantiates them for you. Sometimes you need one of those services before or shortly after the first request, for example, to run EF Core migrations, seed data, warm a cache, or log a message. Be aware that any blocking work slows startup, and any failure prevents the application from starting.

What Is The Best Way To Run Code At Startup?

Reach for a hosted service. Derive from BackgroundService, resolve what you need through a scope, and register it with AddHostedService<T>(). The host starts it as part of application startup; it supports async and cancellation, and, because you create the scope yourself, scoped services like a DbContext get a correct lifetime.

public class StartupWork(IServiceScopeFactory scopeFactory) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var scope = scopeFactory.CreateScope();
var dependency = scope.ServiceProvider.GetRequiredService<Dependency>();
// run EF migrations, seed data, warm a cache...
await dependency.RunAsync(stoppingToken);
}
}
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<Dependency>();
builder.Services.AddHostedService<StartupWork>();
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();

Inject IServiceScopeFactory (not a scoped service directly) so the hosted service, which is a singleton, never captures a scoped dependency. For more on consuming scoped services this way, see accessing background services from ASP.NET Core.

How Do I Resolve A Scoped Service Before The App Starts In Program.cs?

Use this only when something must run before the app handles any requests. A blocking migration or a mandatory config load. Register services on builder.Services (the replacement for ConfigureServices), build the app, then create a scope.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<Dependency>();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var dependency = scope.ServiceProvider.GetRequiredService<Dependency>();
// run EF migrations, seed data, call a remote service...
Console.WriteLine(dependency.Hello);
}
app.MapGet("/", () => "Hello World!");
app.Run();

This runs synchronously before app.Run(), so it blocks startup, which is exactly the point when something MUST happen first. The scope gives scoped services (like a DbContext) a proper lifetime, and disposing it at the end of the block cleans them up. GetRequiredService<T>() throws if the type isn’t registered, which is what you want at startup. GetService<T>() returns null instead.

For a more reusable approach, see one-line scoped initializers with dependency injection.

Can I Resolve A Service Directly From app.Services?

app.Services is the root service provider. Singleton and transient services resolve fine, but scoped services do not:

var app = builder.Build();
// throws for Scoped services
var dependency = app.Services.GetRequiredService<Dependency>();
Terminal window
System.InvalidOperationException: Cannot resolve scoped service 'Dependency' from root provider.

This check is enabled by default in the Development environment. ASP.NET Core throws an exception to prevent captured dependencies: a singleton holding a scoped service meant for a single request. Create a scope first, as shown above.

A captured dependency happens when a longer-lived service holds a reference to a shorter-lived one. Most commonly, a singleton captures a scoped service. Because the singleton is created once and reused for the lifetime of the application, it pins that first scoped instance in place forever. Every later request then shares state that was only ever meant for a single request (a stale DbContext, the first user’s data, a connection that should have been disposed). The bug is subtle and hard to reproduce, so ASP.NET Core validates scopes at startup in Development and throws early rather than letting you ship it.

Why Do I Get “Unable To Resolve Service For Type”?

Short answer: the container can’t find a registration for a type, or for something that type’s constructor needs. The full message looks like Unable to resolve service for type 'X' while attempting to activate 'Y'.

Here’s the real message from the .NET 11 RC runtime when a scoped Consumer depends on an unregistered IMissingDep:

Terminal window
System.InvalidOperationException: Unable to resolve service for type 'IMissingDep' while attempting to activate 'Consumer'.

Common causes and fixes:

  • Missing registration. Add it, e.g. builder.Services.AddScoped<X>(), before builder.Build().
  • Registered after you resolved it. Registrations made after BuildServiceProvider() or Build() are never seen by that provider.
  • Interface registered without an implementation. Use AddScoped<IX, X>().
  • Wrong provider or scope. Resolve scoped services from scope.ServiceProvider, not app.Services. That one gives you “Cannot resolve scoped service … from root provider” instead.

In Development, the host validates your registrations when it builds, so a constructor dependency that isn’t registered fails at startup rather than on the first request. For a concrete EF Core example, see fixing “unable to resolve DbContextOptions”.

Why Shouldn’t I Call BuildServiceProvider?

Calling builder.Services.BuildServiceProvider() (or services.BuildServiceProvider()) builds a second container. Singletons get created twice, and anything resolved from it doesn’t see registrations added later. Current SDKs flag it with analyzer warning ASP0000:

Terminal window
warning ASP0000: Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created.

Prefer app.Services.CreateScope() after Build(), or a hosted service. If you need services to configure options, use IOptions with AddOptions<T>().Configure<TDep>(...); Andrew Lock covers this well.

Conclusion

For startup work, prefer a hosted service: derive from BackgroundService, create a scope with IServiceScopeFactory, and register it with AddHostedService<T>(). Resolve inline in Program.cs (create a scope from app.Services and call GetRequiredService<T>()) only when something must run before the app handles requests, and avoid BuildServiceProvider() either way. Always consider the cost of resolving services at startup, because it directly affects how fast your app starts.

I hope you found this useful. Let me know if you have other ways of resolving types at startup.

Comments

Related Posts