I have two endpoints:
/x
that is defined inEndpoint.Register()
(Bipin Joshi's approach to organize endpoints)/y
that is defined inMain()
(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.