Bald Bearded Builder
aspnetcore/should-my-services-be-transient-scoped-or-singleton

Should my Services be Transient, Scoped, or Singleton?

.NET dependency injection lifetimes are explained through transient, scoped, and singleton services, including the risks of mutable singletons and injecting shorter-lived dependencies into longer-lived services.

Loads from YouTube when you press play

Transcript 4 topics

Dependency injection lifetimes

0:00

Dependency injection in dot net is really cool and I've used it for years injecting services and entity framework context into my controllers or even my methods in minimal API. But I have certainly fought bugs and I'm sure you have too that have come from people registering services incorrectly or with the wrong service lifetimes.

Transient services

0:16

So let's look at dependency injection lifetime scopes and when you should use them. And stick around, there's a big gotcha at the end you're not gonna wanna miss. First up is the shortest lived of the scopes, transient. Transient services are a little bit like snowflakes. Every time you inject them or they come in via property, they're always gonna be a new version of that service.

So even if you inject it twice into the same method, each one of those services are gonna be unique. Why would you want that? Well, you could have a class that has basic calculations in it or a random number generator or other helper functions that you wanna pass around and have available. But you do need to think about memory usage because if you're injecting that multiple times within one request lifecycle, you're taking up that many instances of memory of that class. The next longest lived is scoped lifetimes.

Scoped services

1:05

Scoped services are the middle ground. They get one instance per request. And when I say request, think like hitting an API endpoint. From the request to the response, that whole life cycle gets one instance of the service. So what does that mean?

It means if you inject it into multiple different things, they're all gonna get that same instance of the service. Now that's really good when you need something to be stateful or you have data that needs to be shared across a context like a web request or a unit of work. Another reason this is nice is because each request gets its own isolated instance which prevents interference from other contexts.

Singletons and gotchas

1:42

And then the longest lifetime context is called singleton. And this is exactly what it sounds like. We're gonna create one service to rule all requests over the whole application life cycle. So what does that mean? Well, in the context of say an API, when your application fires up, any request that hits that API before it stops is gonna get the same instance of that service.

Now these are great for services that are stateless and have shared resources that are really agnostic to things like request or user. A good example would be like a logging service. But you really have to be wary of these if their state is mutable. That can lead to all kind of unexpected behavior, concurrent access issues, or even race conditions. Let's take a look at a quick example.

I've created three interfaces here that all provide a GUID name value. I named them iSingleton, iScoped, and iTransient. Then I'm registering each with the appropriate lifetime cycle in the HTTP client builder. And then finally, I've got this minimal API endpoint that's gonna receive multiple versions of each of these inside of it. Now you'll notice when we hit that endpoint, the singleton is always the same.

The scoped changes with every request and the transients are always different. And now you know the basics of dot net's dependency injection's lifetime scopes. Oh, and about that big gotcha. Listen, you never wanna inject a service into another service that has a longer life cycle than it. For instance, don't inject transient services into scoped or singleton.

Don't inject scoped into singleton. Why not? Well, it would actually change the behavior of that item. So if you create a transient service that is injected into your scoped service, that scoped service lasts the lifetime of the request. It's gonna keep the same transient service inside of it that whole time rather than getting a new one anytime you wanna call it.

Have you ever found a bug because of these being registered incorrectly? Let me know in the comments below. I love these war stories. Until next time.

Sign in to join in. Reading needs nothing.