Somewhere during the development on Passwordless.dev, I was working on the AdminConsole, and stumbled upon an interesting discovery. Whenever we make a request, or visit a new page we store the application we’re currently viewing in a scoped object that inherits from ICurrentContext
.
This object would then hold for the scoped lifetime duration things such as the application identifier, API keys, API secrets and so on.
But then in the IHttpClientFactory integration using PasswordlessManagementClient
we weren’t leveraging the power of dependency injection, basically manually adding the API secret into the header every time.
I decided to write a HttpMessageHandler
for it, given that decoupling it from our main code would make everything more readable and maintainable down the line.
To my surprise, we would never be inside an application context. So I was constantly getting unauthorized responses back from the back-end. It was when I started to debug, that I realized the object was basically just containing default values, and didn’t appear to be set.
Eventually, I pinpointed the problem down to just where we started to use our PasswordlessManagementClient
that everything was working fine.
Why does this matter?
For most people, this likely does not matter. If you’re using stateless DelegateHandlers, you’ll never encounter any problems or notice that different instances of scoped serviced are being used.
If you want to share a state between a scoped service from ASP.NET’s request scope and your HttpMessageHandlers that’s when the surprises start bubbling up.
Warming up
ASP.NET allows you to register services with the dependency injection (DI) container using three different lifetimes:
- Singleton: The service has only one instance throughout the application’s lifetime. The same instance is returned for every service request.
- Scoped: The service has one instance per defined “scope”. Service requests within the same scope get the same instance. Service requests from different scopes get different instances.
- Transient: The service has a new instance for every service request. No two service requests get the same instance.
When are those scopes created for scoped lifetimes? In ASP.NET, a new scope is created for each request. So each request uses a different instance of a scoped service.
Finding out what’s going on
Lets imagine you have a scoped service which stores an instance id. We would expect every instance to return the same instance identifier for a given request scope in ASP.NET.
Now let’s configure our dependency injection.
If you inject now ScopedContext
in a controller log the InstanceId
property, you should be able to verify that the instance id are not the same as the one in the ScopedMessageHandler
.
info: Passwordless.Controllers.TestController[0]
Controller: da5c6faf6af7474da5d9bfe7d63c38ff
info: Passwordless.ScopedMessageHandler[0]
HttpMessageHandler: 7119d057a7ae437283384249ee232499
The solution
To use the IHttpContextAccessor, you’ll need to register this typically in your Startup.cs
or Program.cs
with the IServiceCollection
.
Or if you’re using WebApplicationBuilder
:
Then finally you inject IHttpContextAccessor
in your HttpMessageHandler.