ASP.NET MVC
Controller Lifecycle
Web Development
Request Handling
Software Architecture

ASP.NET MVC Is Controller created for every request?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In ASP.NET MVC, controllers are normally created per request and discarded after the response is produced. That design keeps request handling isolated and makes it much safer to write controller code without worrying that one user's request will leak state into another user's request.

The Short Answer

Yes, a new controller instance is typically created for each incoming request. The routing system selects the controller and action, the framework creates the controller, executes the action, writes the response, and then the instance becomes eligible for cleanup.

That lifecycle is important because it explains what you should and should not store in controller fields.

What Per-Request Creation Means in Practice

Consider this controller:

csharp
1public class ProductsController : Controller
2{
3    private readonly IProductRepository _repository;
4    private int _requestCounter;
5
6    public ProductsController(IProductRepository repository)
7    {
8        _repository = repository;
9    }
10
11    public ActionResult Details(int id)
12    {
13        _requestCounter++;
14        var product = _repository.Find(id);
15        return View(product);
16    }
17}

Because the controller is created for one request, _requestCounter is not shared across requests. It will start from zero each time. That is why controller instance fields are fine for request-specific helpers, but not for cross-request application state.

If you need state that survives beyond one request, use the right storage location instead:

  • a database for durable business data
  • session state for per-user session data
  • cache for shared application data
  • dependency-injected services for reusable logic

Dependency Lifetimes Are Separate From Controller Lifetimes

This is where confusion often starts. Even though the controller instance is usually per request, the services injected into it may have different lifetimes.

For example:

  • transient services are created frequently
  • scoped services are usually shared within one request
  • singleton services live for the application lifetime

So the controller itself is short-lived, but it may depend on long-lived collaborators. That means thread safety still matters for singleton services even if the controller is recreated every time.

A simple example:

csharp
1public interface IClock
2{
3    DateTime UtcNow();
4}
5
6public class SystemClock : IClock
7{
8    public DateTime UtcNow() => DateTime.UtcNow;
9}
10
11public class OrdersController : Controller
12{
13    private readonly IClock _clock;
14
15    public OrdersController(IClock clock)
16    {
17        _clock = clock;
18    }
19
20    public ContentResult Ping()
21    {
22        return Content("Handled at " + _clock.UtcNow().ToString("O"));
23    }
24}

Every request gets a fresh OrdersController, but the IClock service lifetime depends on how the container is configured.

Why the Framework Works This Way

Per-request controller creation matches the stateless nature of HTTP. It gives the framework a clean execution context each time and avoids accidental sharing of controller fields between concurrent requests.

That has several benefits:

  • simpler mental model
  • fewer thread-safety problems in controller code
  • easier unit testing because each test starts with a new controller instance
  • clearer separation between request state and application state

It also explains why putting expensive reusable state directly on the controller is usually the wrong design. Reusable state belongs in services or caches, not in the controller object.

When Developers Get Tripped Up

Many bugs happen because developers assume a controller field will preserve values between requests. It will not. If you set a field during one request and expect it to exist for the next page load, that design is already broken.

Another source of confusion is static state. Static fields are shared across requests because they belong to the type, not the controller instance. That makes them very different from normal controller members and much more dangerous in web code.

Common Pitfalls

The biggest mistake is storing mutable cross-request state in controller instance fields. Those values disappear after the request ends, so the behavior becomes inconsistent and difficult to debug.

Another mistake is assuming that per-request controllers make the entire application thread-safe. They do not. Shared services, caches, and static members still need correct concurrency handling.

Developers also sometimes put business logic directly into controllers because the controller is recreated frequently. That hurts maintainability. Controllers should coordinate request flow, not become the main home for domain rules.

Finally, do not confuse classic ASP.NET MVC controller creation with service lifetimes in your dependency injection container. They are related during a request, but they are not the same concept.

Summary

  • ASP.NET MVC controllers are usually created anew for each request.
  • Controller instance fields are request-scoped in practice, not shared across users.
  • Persisted or shared state belongs in databases, session, caches, or services.
  • Injected dependencies may have longer lifetimes than the controller itself.
  • Per-request controller creation simplifies request isolation but does not remove all concurrency concerns.

Course illustration
Course illustration

All Rights Reserved.