Fix: Unable to resolve service for type 'X' while attempting to activate 'Y'
ASP.NET Core throws this when a constructor asks for a type that was never registered, was registered on the wrong container, or was added after the host was built. Three concrete fixes cover almost every case.
The fix: ASP.NET Core’s ActivatorUtilities walked the constructor of Y, asked the IServiceProvider for a X, and got back nothing. Either you forgot to call services.AddScoped<X, XImpl>() (or AddSingleton / AddTransient), you registered the implementation but asked for an interface or base class the container does not know about, or the registration lives in a different IServiceCollection than the one the host actually built. Add the missing registration in Program.cs before builder.Build(), and double-check the type names line up exactly.
System.InvalidOperationException: Unable to resolve service for type 'MyApp.Data.IUserRepository' while attempting to activate 'MyApp.Api.Controllers.UsersController'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Controllers.ServiceBasedControllerActivator.Create(ControllerContext context)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider.<>c__DisplayClass4_0.<CreateActivator>b__0(ControllerContext controllerContext)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
This guide is written against .NET 11 preview 4, Microsoft.AspNetCore.App 11.0.0-preview.4, and Microsoft.Extensions.DependencyInjection 11.0.0-preview.4. The exception text has been stable since ASP.NET Core 2.1, so every fix below applies cleanly back to .NET Core 3.1, .NET 5, 6, 8, and 10.
The two type names in the message are the most useful part: the first name (X) is the type the container could not find, and the second name (Y) is the consumer that asked for it. Read them in that order before doing anything else, because the search query that landed you here is going to match the wrong half of the message half the time.
Why the container could not find the type
Three causes account for almost every occurrence:
- Nothing registered for that type at all. You wrote
public UsersController(IUserRepository repo)but never calledservices.AddScoped<IUserRepository, UserRepository>(). The container has no mapping from the interface to the implementation. - You registered the wrong key. You called
services.AddScoped<UserRepository>()(concrete type) but the controller asks forIUserRepository(interface). The container only resolves what you registered, by the exact type used as the generic parameter or theserviceTypeargument. - You registered into a different
IServiceCollection. Common in tests where the test host builds its own collection, or in unusual cases where you mutatebuilder.Servicesafterbuilder.Build(). The host snapshots the collection at build time.
There are a handful of less common variants worth naming: a generic open type registered without the matching closed type (AddScoped(typeof(IRepo<>), typeof(Repo<>)) is fine; AddScoped<IRepo<User>>(...) is not the same registration), a keyed service requested by a non-keyed consumer, and an Action or factory delegate passed to a constructor that DI cannot synthesise. Cover the first three first.
Minimal repro
This is the smallest .NET 11 minimal API that throws the exception:
// .NET 11 preview 4, Microsoft.AspNetCore.App 11.0.0-preview.4
using Microsoft.AspNetCore.Builder;
var builder = WebApplication.CreateBuilder(args);
// Notice: no services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
public interface IUserRepository
{
string GetName(int id);
}
public sealed class UserRepository : IUserRepository
{
public string GetName(int id) => $"user-{id}";
}
// .NET 11 preview 4
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("users")]
public sealed class UsersController(IUserRepository repo) : ControllerBase
{
[HttpGet("{id:int}")]
public IActionResult Get(int id) => Ok(repo.GetName(id));
}
Hit GET /users/1 and the request fails with the exception in the section above. The container never saw IUserRepository, so when MVC’s ServiceBasedControllerActivator tries to build the controller, the constructor parameter cannot be satisfied.
Fix one: register the missing service
The first thing to try, and the answer 80% of the time:
// .NET 11 preview 4
builder.Services.AddScoped<IUserRepository, UserRepository>();
Pick the lifetime that matches how the service is used:
AddScopedis the right default for anything that touches per-request state (an EF CoreDbContext, aHttpContextaccessor wrapper, a tenant resolver). One instance per request scope.AddSingletonis for stateless or thread-safe shared state (caches, options, HTTP clients viaIHttpClientFactory). One instance per process.AddTransientis for cheap, stateless objects you want a fresh copy of every time (IValidator<T>, mappers). New instance per resolve.
Get this wrong and you trade today’s exception for tomorrow’s InvalidOperationException: Cannot consume scoped service ... from singleton ..., or worse, for a silent thread-safety bug where two requests share a DbContext. The official guidance in the Microsoft.Extensions.DependencyInjection docs is the reference; default to AddScoped when in doubt and a request scope exists.
Fix two: register the service type the constructor actually asks for
If you have a registration but still get the exception, the registered type and the consumed type do not match. Check the constructor against the registration:
// Wrong: only the concrete type is registered
builder.Services.AddScoped<UserRepository>();
// Right: register both the interface and the implementation,
// or just the interface mapped to the implementation
builder.Services.AddScoped<IUserRepository, UserRepository>();
The container does not perform interface inference. If UserRepository implements IUserRepository, registering only UserRepository does not register IUserRepository. If the consumer asks for the interface, register the interface.
If you genuinely need both (“inject UserRepository here, but IUserRepository there”), register both, and forward the interface to the concrete:
// .NET 11 preview 4
builder.Services.AddScoped<UserRepository>();
builder.Services.AddScoped<IUserRepository>(sp => sp.GetRequiredService<UserRepository>());
This pattern matters for hosted services and the options pattern, where consumers sometimes ask for the concrete MyOptions and sometimes for IOptions<MyOptions>.
Fix three: register before the host is built
builder.Build() is the cut-off line. Anything you add to builder.Services after that point is silently discarded for the running host:
// .NET 11 preview 4
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Too late. The container was snapshotted in builder.Build().
builder.Services.AddScoped<IUserRepository, UserRepository>();
app.MapControllers();
app.Run();
Reorder so every registration runs before Build. This pattern bites most often when a refactor moves an Add* call into a method, and the method is called from the wrong place. A handy pattern is to put every services.Add* call inside an extension method on IServiceCollection and call it from one place near the top of Program.cs:
// .NET 11 preview 4
public static class DependencyInjectionExtensions
{
public static IServiceCollection AddDataServices(this IServiceCollection services)
{
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IOrderRepository, OrderRepository>();
return services;
}
}
// Program.cs
builder.Services.AddDataServices();
var app = builder.Build();
In integration tests against WebApplicationFactory<TEntryPoint>, the equivalent rule is that ConfigureTestServices runs before the test host is built. If you mutate the container from inside a test method body after factory.CreateClient(), you are mutating a discarded collection.
Variants that look like the same error
Several lookalikes resolve differently and waste time if you treat them as the same bug:
Cannot consume scoped service '<X>' from singleton '<Y>'. The registration exists, but the lifetime is wrong. The fix is to makeYscoped, or to takeIServiceScopeFactory/IServiceProviderinYand create a scope on demand. Covered in the singleton-to-scoped capture problem.No service for type 'IOptions<MyOptions>' has been registered. Same root cause, different message: you skippedservices.Configure<MyOptions>(...)orservices.AddOptions<MyOptions>().Bind(...). AddingConfigureregistersIOptions<MyOptions>for free.Implementation type 'X' can't be converted to service type 'Y'. You wroteservices.AddScoped(typeof(IFoo), typeof(Bar))andBardoes not implementIFoo. The compiler cannot catch this because both arguments areType. Fix the type or use the generic overload.A suitable constructor for type 'X' could not be located. The type is registered but DI cannot construct it: every public constructor parameter must be resolvable, and there must be exactly one constructor (or[ActivatorUtilitiesConstructor]on the chosen one). This is not theUnable to resolve serviceerror.- Keyed services: in .NET 8 and later,
AddKeyedScoped<IFoo, Foo>("primary")requires[FromKeyedServices("primary")] IFoo fooon the consumer. Asking for a plainIFoowill throw the resolve-service exception even though a keyed registration exists. The two namespaces are separate.
How to confirm the registration is wired correctly
Three quick checks beat any amount of guessing:
-
IServiceProvider.GetService<T>()from the dev console. Drop a one-liner just afterapp.Build()and beforeapp.Run():// .NET 11 preview 4 - remove before commit using (var scope = app.Services.CreateScope()) { var repo = scope.ServiceProvider.GetService<IUserRepository>(); Console.WriteLine(repo is null ? "NOT REGISTERED" : repo.GetType().FullName); }If
GetService<T>()returnsnull, the registration is missing or scoped to a different container.GetRequiredService<T>()would throw the sameInvalidOperationExceptionyou are debugging. -
Validate scopes at startup. Pass
ValidateScopes = trueandValidateOnBuild = trueto the service-provider factory and the host will refuse to start if any registration is broken:// .NET 11 preview 4 builder.Host.UseDefaultServiceProvider(options => { options.ValidateScopes = true; options.ValidateOnBuild = true; });ValidateOnBuildwalks every registration once at build time and fails fast if any constructor parameter cannot be satisfied. In the development environment ASP.NET Core enables this for you, which is why the exception often appears the moment you start the app rather than the moment you hit an endpoint. -
Print the registrations. When the registration looks correct but the exception still fires, dump the collection itself before
Build:// .NET 11 preview 4 foreach (var sd in builder.Services.Where(s => s.ServiceType.Name.Contains("UserRepository"))) { Console.WriteLine($"{sd.Lifetime}: {sd.ServiceType.FullName} -> {sd.ImplementationType?.FullName ?? "factory"}"); }This catches the case where you registered
MyApp.OldNamespace.IUserRepositoryand the controller importsMyApp.NewNamespace.IUserRepository. The exception message shows the full namespace, but the eye glides past it.
Edge cases that catch experienced developers
A few patterns trigger this exception in code that looks correct on inspection:
IConfigurationinjected into aConfigure<TOptions>callback. The callback runs lazily. If you reference a service inside the callback that is later removed, the exception fires the first time the options are resolved, not at startup.- Background services that resolve scoped dependencies in their constructor. A
BackgroundServiceis a singleton. Its constructor cannot ask for anIUserRepositoryif the latter is scoped. InjectIServiceScopeFactory, create a scope insideExecuteAsync, and resolve from the scope. - Generic hosted services.
services.AddHostedService<MyHostedService<MyArg>>()requires that the closed genericMyHostedService<MyArg>be constructible by DI, which meansMyArgmust also be resolvable. Open generics needservices.AddTransient(typeof(IRepo<>), typeof(Repo<>)). - Source-generated DI containers. Strawberry Shake, Refit, and gRPC source generators sometimes register their own clients with
IServiceCollection. If you call theirAdd*extension afterBuild, the same silent-discard rule applies. - Native AOT builds. The trim-friendly default container in .NET 11 still resolves through reflection by default. If you ship with
<PublishTrimmed>true</PublishTrimmed>and the trimmer removes the implementation type, you will see the resolve-service exception at runtime even though the source compiles fine. The fix is[DynamicDependency]or registering the type via a typed factory.
Related
- Wiring up logging and DI together cleanly: structured logging with Serilog and Seq in .NET 11.
- The next exception you will hit after this one if you pick the wrong lifetime: singleton consuming scoped DbContext.
- A related EF Core misuse that surfaces when DI hands the same
DbContextto two requests: a second operation was started on this context instance. - Test-time DI swaps that bypass this exception by registering a fake: pooled DbContext factory in EF Core 11 tests.
- For the related
IConfigurationfailure mode: no connection string named ‘DefaultConnection’ could be found.
Sources
- Microsoft Learn, Dependency injection in .NET.
- Microsoft Learn, Dependency injection guidelines.
- Microsoft Learn, Keyed services in .NET.
- Microsoft Learn, Use scope validation.
- ASP.NET Core source,
ActivatorUtilities.cswhere the exception is thrown.
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.