Home > Software design >  Scoped service creating two different instances for a request
Scoped service creating two different instances for a request

Time:07-28

I'm fairly new to Asp.Net core 6 and am working on an GraphQL API that receives a bearer token in the request. The API then invokes another Web API and passes the same bearer token in the header. Below is what my code looks like-

Program.cs:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<HeaderHandler>();
builder.Services.AddScoped<TokenContainer>();
//builder.Services.AddScoped<IFooGateway, FooGateway>();

builder.Services.AddHttpClient<IFooGateway, FooGateway>((c) =>
{
    c.BaseAddress = new Uri(builder.Configuration["FooApiUrl"]);
})
.AddHttpMessageHandler<HeaderHandler>();

builder.Services.AddTransient<GraphApiService>();

var app = builder.Build();
app.UseMiddleware<HeaderMiddleware>();
app.MapGraphQL();
app.Run();

HeaderMiddleware.cs

public class HeaderMiddleware
    {
        //TokenContainer _tokenContainer;
        
        private readonly RequestDelegate _requestDelegate;
        public HeaderMiddleware()
        {
        }
        public HeaderMiddleware(RequestDelegate requestDelegate)
        {
            _requestDelegate = requestDelegate;
            //_tokenContainer = tokenContainer;
        }

        public async Task InvokeAsync(HttpContext context, TokenContainer tokenContainer)
        {
            var header = context.Request.Headers.Authorization;
            tokenContainer.SetToken(header);
            await _requestDelegate(context);
        }

TokenContainer.cs:

public class TokenContainer
{
    public string BearerToken { get; private set; }

    public void SetToken(string token) => BearerToken = token;
}

HeaderHandler.cs:

public class HeaderHandler : DelegatingHandler
    {
        TokenContainer _tokenContainer;
        public HeaderHandler()
        {

        }

        public HeaderHandler(TokenContainer tokenContainer)
        {
            _tokenContainer = tokenContainer;
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            // for every request sent via the http client, intercept & add the bearer token header.
            request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", _tokenContainer.BearerToken);
            // continue with request pipeline
            return await base.SendAsync(request, cancellationToken);
        }
    }

FooGateway.cs:

public class FooGateway : IFooGateway
    {
        private readonly HttpClient _httpClient;
        private readonly IConfiguration _configuration;
        private readonly string _context = String.Empty;

        public FooGateway(HttpClient httpClient, IConfiguration configuration)
        {
            _configuration = configuration;
            _context = configuration["FooContext"];
            _httpClient = httpClient;
        }
       public void DoSomething()
       {
        _httpClient.PostAsync("/blabla");
       }

    }

So, the idea was that the bearer token for every incoming request will be stored in a class called TokenContainer and the HttpHandler will add it to all the outgoing requests. However, what is happening is that the token is stored in the TokenContainer but the HeaderHandler gets a different instance of TokenContainer in its constructor with its BearerToken property set to null. Can someone please explain why the same instance of TokenContainer from the middleware is not being passed into the HeaderHandler?

CodePudding user response:

The issue you are seeing is because the lifetime of the HttpMessageHandler is not the same as the lifetime of the request: usually, the same handler will be reused across many requests and be controlled separately on expiration timers and such.

You should not expect that a service injected into your message handler will be the same object that is injected outside it when it is registered as scoped.

https://andrewlock.net/understanding-scopes-with-ihttpclientfactory-message-handlers/#scope-duration-in-ihttpclientfactory

As the article suggests, to use the same scoped instance as you do outside the handler, you have to rely on IHttpContextAccessor to access the current HttpContext and fetch the service from there. So your handler implementation would look something like this:

public class HeaderHandler : DelegatingHandler
    {
        IHttpContextAccessor _httpContextAccessor;
        public HeaderHandler()
        {

        }

        public HeaderHandler(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var tokenContainer = _httpContextAccessor
                .HttpContext
                .RequestServices
                .GetRequiredService<TokenContainer>();


            // for every request sent via the http client, intercept & add the bearer token header.
            request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", tokenContainer.BearerToken);
            // continue with request pipeline
            return await base.SendAsync(request, cancellationToken);
        }
    }

This should make sure that the TokenContainer instance is the same across your current request and http calls.

Remember that to add this functionality you need to add the accessor like this:

services.AddHttpContextAccessor();
  • Related