Home > Net >  Unexpected identical object obtained for each http request for scoped service
Unexpected identical object obtained for each http request for scoped service

Time:09-03

I have two endpoints:

  • /x that is defined in Endpoint.Register() (Bipin Joshi's approach to organize endpoints)
  • /y that is defined in Main() (the default of minimal api template)

As I expect that instances of Endpoint and Foo are different for each http request, I register them as scoped.

Unfortunately, Foo object instantiated in /x are always identical for all http requests. It is not what I expect. Foo objects instantiated in /y are different as expected.

Question

What causes this issue?

public class Foo{}

public class Endpoint
{
    private readonly ILogger<Endpoint> logger;
    private readonly Foo foo;

    public Endpoint(ILogger<Endpoint> logger, Foo foo)
    {
        this.logger = logger;
        this.foo = foo;
    }

    public void Register(WebApplication app)
    {
        app.MapGet("/x", () =>
        {
            // it unexpectedly produces the same foo!
            logger.Log(LogLevel.Warning, $"x: {foo.GetHashCode()}");
            return Results.Ok();
        });
    }
}



public  class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

     
        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen();
        builder.Services.AddScoped<Foo>();
        builder.Services.AddScoped<Endpoint>();

        var app = builder.Build();


        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }

        app.UseHttpsRedirection();


        using (var scope = app.Services.CreateScope())
        {
            scope.ServiceProvider.GetService<Endpoint>()?.Register(app);
        }



        app.MapGet("/y", (ILogger<Endpoint> logger, Foo foo) =>
        {
            // it expectedly produces a different foo for each request.
            logger.Log(LogLevel.Warning, $"y: {foo.GetHashCode()}");
            return Results.Ok();
        });
  

        app.Run();
    }
}

CodePudding user response:

// it unexpectedly produces the same foo!

Actually it is pretty much expected. The following code:

using (var scope = app.Services.CreateScope())
{
   scope.ServiceProvider.GetService<Endpoint>()?.Register(app);
}

Creates a scope which creates an instance of Foo which will be captured by lambda (via lambda closure mechanism) and will be reused for the whole app lifetime (as is observed by you). I.e. the following quote:

Unfortunately, Foo object instantiated in /x are always identical for all http requests

is not actually true, there is no Foo object instantiated in /x handler, it reuses the same instance (note that by default for classes GetHashCode is computed based on an object's reference).

I would argue that this registration method is overengineered, cumbersome, confusing and in some cases outright faulty (as the one in the article which will lead to problems with the reused/shared context) and I would recommend against using it.

  • Related