microsoft/migrating-mvc-dependency-injection
> Migrates dependency injection configuration from ASP.NET Framework MVC and WebAPI projects to ASP.NET Core built-in DI or modernized third-party container integration. Use when upgrading projects that use DependencyResolver.SetResolver, config.DependencyResolver, custom IControllerFactory, custom IHttpControllerActivator, ServiceLocator.Current, or third-party containers (Autofac, Unity, Ninject, Castle Windsor). Also triggers for "migrate dependency injection", "convert DI container", "replace DependencyResolver", PerRequest lifetime mapping, IControllerActivator migration, and property injection patterns.
npx skills add https://github.com/microsoft/upgrade-agent-plugins --skill migrating-mvc-dependency-injection
Migrate DependencyResolver-based DI from ASP.NET MVC and WebAPI to ASP.NET Core's built-in IServiceCollection/IServiceProvider pattern, or modernize third-party container integration. ASP.NET Core has DI built into the framework — the DependencyResolver and IDependencyScope APIs no longer exist.
> Related skills:
> - To remove Autofac entirely and use built-in DI: see migrating-autofac-to-dotnet-di
> - To keep Autofac but modernize its integration: see integrating-autofac-with-dotnet
Migration Progress:
- [ ] Step 1: Inventory DI usage
- [ ] Step 2: Map service registrations and lifetimes
- [ ] Step 3: Register services in Program.cs
- [ ] Step 4: Migrate or remove third-party container
- [ ] Step 5: Migrate controller factory customizations
- [ ] Step 6: Eliminate Service Locator usage
- [ ] Step 7: Remove obsolete DI code
- [ ] Step 8: Build and verify
Search the project for these patterns to determine the migration scope:
| Pattern | Indicates |
|---------|-----------|
| DependencyResolver.SetResolver | MVC DI resolver |
| config.DependencyResolver = or GlobalConfiguration.Configuration.DependencyResolver | WebAPI DI resolver |
| ContainerBuilder, IUnityContainer, IKernel, IWindsorContainer | Third-party container |
| ServiceLocator.Current | Service Locator anti-pattern |
| IControllerFactory | Custom MVC controller factory |
| IHttpControllerActivator | Custom WebAPI controller activator |
| Property injection ([Dependency], InjectProperty) | Property injection patterns |
Record every service registration and its lifetime before changing anything.
Document all service registrations from the existing container configuration. Map each lifetime to its ASP.NET Core equivalent:
| Framework Lifetime | ASP.NET Core | Method |
|--------------------|-------------|--------|
| Per-request / InstancePerRequest / HierarchicalLifetimeManager | Scoped | AddScoped<TService, TImpl>() |
| Singleton / SingleInstance / ContainerControlledLifetimeManager | Singleton | AddSingleton<TService, TImpl>() |
| Transient / InstancePerDependency / TransientLifetimeManager | Transient | AddTransient<TService, TImpl>() |
| Per-thread / PerThreadLifetimeManager | Scoped | AddScoped<TService, TImpl>() |
| ExternallyControlledLifetimeManager | Transient | AddTransient<TService, TImpl>() |
Per-thread lifetime maps to Scoped because ASP.NET Core processes each request on a single thread from the thread pool, making the semantics equivalent for web scenarios.
Move all service registrations to builder.Services in Program.cs. Create an extension method to keep Program.cs clean when there are many registrations:
Before (Global.asax.cs or App_Start):
var container = new UnityContainer();
container.RegisterType<IOrderService, OrderService>(new HierarchicalLifetimeManager());
container.RegisterType<IRepository, SqlRepository>(new TransientLifetimeManager());
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
After (Program.cs):
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.RegisterApplicationServices();
// Extension method in a separate file
public static class ServiceRegistration
{
public static IServiceCollection RegisterApplicationServices(this IServiceCollection services)
{
services.AddScoped<IOrderService, OrderService>();
services.AddTransient<IRepository, SqlRepository>();
return services;
}
}
If HttpContext.Current was used anywhere in the project, register the accessor:
builder.Services.AddHttpContextAccessor();
Choose the appropriate path based on the container in use and the desired outcome.
Remove the third-party container entirely. Map all registrations to IServiceCollection using the lifetime table in Step 2. Remove all container-specific NuGet packages.
When the project relies on advanced container features (modules, decorators, interceptors, child containers), keep the container but modernize the integration.
Autofac:
// Install: Autofac.Extensions.DependencyInjection
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
containerBuilder.RegisterModule<MyModule>();
});
Unity:
// Install: Unity.Microsoft.DependencyInjection
builder.Host.UseUnityServiceProvider(container =>
{
container.RegisterType<IMyService, MyService>();
});
Castle Windsor:
// Install: Castle.Windsor.MsDependencyInjection
var windsorContainer = new WindsorContainer();
windsorContainer.Install(FromAssembly.This());
builder.Host.UseServiceProviderFactory(
new WindsorServiceProviderFactory(windsorContainer));
Ninject: No official ASP.NET Core integration exists. Migrate all registrations to built-in DI (Option A) or switch to Autofac. Do not attempt to create a custom adapter — the IServiceProvider contract has subtleties that break under a naive wrapper.
ASP.NET Core replaces both IControllerFactory (MVC) and IHttpControllerActivator (WebAPI) with IControllerActivator.
If the custom factory only existed to enable constructor injection, remove it entirely — ASP.NET Core injects constructor dependencies into controllers by default.
If the factory contains custom logic (e.g., selecting controller types dynamically, applying cross-cutting concerns):
Before (MVC):
public class CustomControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(
RequestContext requestContext, Type controllerType)
{
// Custom logic here
return (IController)_container.Resolve(controllerType);
}
}
After:
public class CustomControllerActivator : IControllerActivator
{
public object Create(ControllerContext context)
{
var controllerType = context.ActionDescriptor.ControllerTypeInfo.AsType();
// Custom logic here
return context.HttpContext.RequestServices.GetRequiredService(controllerType);
}
public void Release(ControllerContext context, object controller)
{
(controller as IDisposable)?.Dispose();
}
}
// Register in Program.cs
builder.Services.AddSingleton<IControllerActivator, CustomControllerActivator>();
Replace all ServiceLocator.Current.GetInstance<T>() calls with constructor injection. The Service Locator pattern hides dependencies and makes testing difficult — ASP.NET Core does not support it.
Before:
public class OrderProcessor
{
public void Process()
{
var service = ServiceLocator.Current.GetInstance<IOrderService>();
service.Execute();
}
}
After:
public class OrderProcessor
{
private readonly IOrderService _orderService;
public OrderProcessor(IOrderService orderService)
{
_orderService = orderService;
}
public void Process()
{
_orderService.Execute();
}
}
For locations where constructor injection is not possible (e.g., static methods, legacy code paths that cannot be refactored immediately), inject IServiceProvider and resolve explicitly as a temporary measure:
var service = serviceProvider.GetRequiredService<IOrderService>();
Mark these as technical debt with a TODO comment — they should eventually be refactored to constructor injection.
Remove all Framework-specific DI artifacts:
DependencyResolver.SetResolver(...) callsGlobalConfiguration.Configuration.DependencyResolver = ... assignmentsIDependencyResolver implementationsIDependencyScope implementationsServiceLocator.SetLocatorProvider(...) callsCommonServiceLocator package referenceBuild the project and verify:
InvalidOperationException at first requestIHttpContextAccessor is registered if any service depends on HttpContextThe built-in container does not support property injection. If the project used property injection (Autofac PropertiesAutowired(), Unity [Dependency] attribute, Ninject [Inject] attribute):
PropertiesAutowired() works with ASP.NET Core integration)DependencyResolver, IDependencyScope, or ServiceLocator references remainUseServiceProviderFactoryIControllerActivator or removedIHttpContextAccessor registered if HttpContext access is needed outside controllersTake microsoft/migrating-mvc-dependency-injection from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.