C
C#11mo ago
sheru

❔ ✅ How to debug "More than twenty 'IServiceProvider' instances have been created ... "?

Hi, so I started getting this error after I connected my backend to my frontend. It didn't appear before or I didn't do enough testing to encounter it. I checked my dbcontext setup, program.cs, services, etc and can't spot the mistake... here is the full error if someone can give me a pointer on where to look: https://pastebin.com/aUuK5E7C
Pastebin
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddle...
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
26 Replies
Pobiega
Pobiega11mo ago
You'll need to share your code, unsuprisingly.
sheru
sheru11mo ago
Hey, if i was unclear, i meant that in general, how do you go about solving this and where to look in general.
Tvde1
Tvde111mo ago
look at the changes you made before getting the error :)
Pobiega
Pobiega11mo ago
Well you've made a code error somewhere we can't give you more info than that
sheru
sheru11mo ago
fair 🙂 i was thinking along "look in service, maybe theres a x or y" but thanks anyway
Tvde1
Tvde111mo ago
does your DbContext have a constructor?
Pobiega
Pobiega11mo ago
well there is no way to tell for sure, but the link in the error is the most likely problem
sheru
sheru11mo ago
yeah there is a constructor, here is the whole config. also i just learned this whole thing 2 weeks ago so please bear with me 🙂
public class DatabaseContext : DbContext
{
public readonly IConfiguration _config;
public DbSet<User> Users { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderProducts> OrderProducts { get; set; }
public DbSet<Image> Images { get; set; }
private static readonly TimeStampInterceptor Interceptor = new TimeStampInterceptor(); // Create a static instance

public DatabaseContext(DbContextOptions options, IConfiguration config)
: base(options)
{
_config = config;
}

static DatabaseContext()
{
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var builder = new NpgsqlDataSourceBuilder(_config.GetConnectionString("Default"));
optionsBuilder.AddInterceptors(Interceptor);
optionsBuilder.UseNpgsql(builder.Build()).UseSnakeCaseNamingConvention();
builder.MapEnum<Role>();
builder.MapEnum<OrderStatus>();
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasIndex(u => u.Email).IsUnique();
modelBuilder.HasPostgresEnum<Role>();
modelBuilder.HasPostgresEnum<OrderStatus>();
modelBuilder.Entity<OrderProducts>().HasKey("OrderId", "ProductId");
}
}
public class DatabaseContext : DbContext
{
public readonly IConfiguration _config;
public DbSet<User> Users { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderProducts> OrderProducts { get; set; }
public DbSet<Image> Images { get; set; }
private static readonly TimeStampInterceptor Interceptor = new TimeStampInterceptor(); // Create a static instance

public DatabaseContext(DbContextOptions options, IConfiguration config)
: base(options)
{
_config = config;
}

static DatabaseContext()
{
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var builder = new NpgsqlDataSourceBuilder(_config.GetConnectionString("Default"));
optionsBuilder.AddInterceptors(Interceptor);
optionsBuilder.UseNpgsql(builder.Build()).UseSnakeCaseNamingConvention();
builder.MapEnum<Role>();
builder.MapEnum<OrderStatus>();
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasIndex(u => u.Email).IsUnique();
modelBuilder.HasPostgresEnum<Role>();
modelBuilder.HasPostgresEnum<OrderStatus>();
modelBuilder.Entity<OrderProducts>().HasKey("OrderId", "ProductId");
}
}
Pobiega
Pobiega11mo ago
Why are you injecting IConfiguration? thats very unorthodox your connectionstring should be fed via the DbContextOptions, which should be generic public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options)
sheru
sheru11mo ago
I was taught that this is the way to go, using _config to get the default connection string in the appsettings.json. im not really sure how to feed it to dbcontext directly. I'm guessing using builder.services in program.cs? is it very significant to do it this way vs what you recommended?
Pobiega
Pobiega11mo ago
It is the correct way to do it. I'm not sure if it's related to your problem thou, but you should never be creating contexts manually
sheru
sheru11mo ago
I did as you recommended (at least from what I understood) and moved things to program.cs.
var builder = WebApplication.CreateBuilder(args);
var timeStampInterceptor = new TimeStampInterceptor();
var npgsqlBuilder = new NpgsqlDataSourceBuilder(
builder.Configuration.GetConnectionString("Default")
);
builder.Services.AddSingleton(npgsqlBuilder);
builder.Services.AddSingleton(timeStampInterceptor);

...

builder.Services.AddDbContext<DatabaseContext>(
options =>
{
options.AddInterceptors(timeStampInterceptor);
options.UseNpgsql(npgsqlBuilder.Build()).UseSnakeCaseNamingConvention();
npgsqlBuilder.MapEnum<Role>();
npgsqlBuilder.MapEnum<OrderStatus>();
},
ServiceLifetime.Scoped
);
var builder = WebApplication.CreateBuilder(args);
var timeStampInterceptor = new TimeStampInterceptor();
var npgsqlBuilder = new NpgsqlDataSourceBuilder(
builder.Configuration.GetConnectionString("Default")
);
builder.Services.AddSingleton(npgsqlBuilder);
builder.Services.AddSingleton(timeStampInterceptor);

...

builder.Services.AddDbContext<DatabaseContext>(
options =>
{
options.AddInterceptors(timeStampInterceptor);
options.UseNpgsql(npgsqlBuilder.Build()).UseSnakeCaseNamingConvention();
npgsqlBuilder.MapEnum<Role>();
npgsqlBuilder.MapEnum<OrderStatus>();
},
ServiceLifetime.Scoped
);
and now dbcontext is as such:
public class DatabaseContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderProducts> OrderProducts { get; set; }
public DbSet<Image> Images { get; set; }

public DatabaseContext(DbContextOptions options)
: base(options) { }

static DatabaseContext()
{
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasIndex(u => u.Email).IsUnique();
modelBuilder.HasPostgresEnum<Role>();
modelBuilder.HasPostgresEnum<OrderStatus>();
modelBuilder.Entity<OrderProducts>().HasKey("OrderId", "ProductId");
}
}
public class DatabaseContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderProducts> OrderProducts { get; set; }
public DbSet<Image> Images { get; set; }

public DatabaseContext(DbContextOptions options)
: base(options) { }

static DatabaseContext()
{
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().HasIndex(u => u.Email).IsUnique();
modelBuilder.HasPostgresEnum<Role>();
modelBuilder.HasPostgresEnum<OrderStatus>();
modelBuilder.Entity<OrderProducts>().HasKey("OrderId", "ProductId");
}
}
I'm not sure if it's necessary to do this line builder.Services.AddSingleton(npgsqlBuilder); but I read somewhere that maybe this needs to be a singleton and shouldn't have an instance created every time. anyway the problem still persists
Pobiega
Pobiega11mo ago
Im not sure why that needs to be a builder at all normally you'd just do... services.AddDbContext<MyDbContext>(x => x.UseNpgsql("My Connection String")); you can ofc replace the connstr with a variable or expression that gets it from the configuration
sheru
sheru11mo ago
services as in from the IServicCollection? also I keep seeing Startup.cs but for some reasons our instructor doesn't use it at all
Pobiega
Pobiega11mo ago
it used to be part of the boilerplate for .net 5 and before since .net 6 we've removed it you still have the same code, just in Program.cs now yeah, so builder.Services for you
sheru
sheru11mo ago
Ah I see, thank you for the info. I am wondering if there's something wrong with how I used HTTP Context since it's mentioned in the error at the very bottom... Atlhough I don't really understand what it's saying. I only used HTTP Context here
public class OrderController : CrudController<Order, OrderReadDto, OrderCreateDto, OrderUpdateDto>
{
private readonly IOrderService _orderService;

public OrderController(IOrderService orderService)
: base(orderService)
{
_orderService = orderService;
}

public override async Task<ActionResult<OrderReadDto>> CreateOne([FromBody] OrderCreateDto dto)
{
var userIdClaim = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier);

if (userIdClaim == null)
{
return StatusCode(400, dto);
}

var userId = userIdClaim.Value;
if (!Guid.TryParse(userId, out Guid newId))
{
return StatusCode(400, dto);
}

var dtoWithId = await _orderService.CreateOrderAndOrderProducts(dto, newId);

return Ok(dtoWithId);
}
}
public class OrderController : CrudController<Order, OrderReadDto, OrderCreateDto, OrderUpdateDto>
{
private readonly IOrderService _orderService;

public OrderController(IOrderService orderService)
: base(orderService)
{
_orderService = orderService;
}

public override async Task<ActionResult<OrderReadDto>> CreateOne([FromBody] OrderCreateDto dto)
{
var userIdClaim = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier);

if (userIdClaim == null)
{
return StatusCode(400, dto);
}

var userId = userIdClaim.Value;
if (!Guid.TryParse(userId, out Guid newId))
{
return StatusCode(400, dto);
}

var dtoWithId = await _orderService.CreateOrderAndOrderProducts(dto, newId);

return Ok(dtoWithId);
}
}
Pobiega
Pobiega11mo ago
nope that seems fine at a glance I'd probably return 401 instead of 400 if the user didnt have a nameidentifier claim thou 😛
sheru
sheru11mo ago
Haha you're absolutely right, will change it to that. Also I solved it!... by adding this line dataSource.Dispose(); and data source referring to var dataSource = npgsqlBuilder.Build();
Pobiega
Pobiega11mo ago
I'm still not sure that builder needs to be a thing But then again, I have not worked with npgsql in a while
sheru
sheru11mo ago
yeah i don't know enough to do it otherwise right now, and I still need to do my front end (hello react my old friend) so I will leave it alone for now ... thank you so much for your help!!!
Pobiega
Pobiega11mo ago
np if you think we are done here, feel free to $close this thread
MODiX
MODiX11mo ago
Use the /close command to mark a forum thread as answered
sheru
sheru11mo ago
thanks, will do
Tvde1
Tvde111mo ago
What's the CrudController? Hmm
sheru
sheru11mo ago
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using WebApiBusiness.Abstraction;
using WebApiDomain.Shared;

namespace WebApiController.Controllers;

[ApiController]
[EnableCors("Policy1")]
[Route("api/v1/[controller]s")]
public class CrudController<T, TReadDto, TCreateDto, TUpdateDto> : ControllerBase
{
private readonly IBaseService<T, TReadDto, TCreateDto, TUpdateDto> _baseService;

public CrudController(IBaseService<T, TReadDto, TCreateDto, TUpdateDto> baseService)
{
_baseService = baseService;
}

[HttpGet]
public virtual async Task<ActionResult<IEnumerable<TReadDto>>> GetAll(
[FromQuery] QueryOptions queryOptions
)
{
return Ok(await _baseService.GetAll(queryOptions));
}

[HttpGet("{id:Guid}")]
public virtual async Task<ActionResult<TReadDto>> GetOneById([FromRoute] Guid id)
{
return Ok(await _baseService.GetOneById(id));
}

[HttpPost]
public virtual async Task<ActionResult<TReadDto>> CreateOne([FromBody] TCreateDto dto)
{
var createdObject = await _baseService.CreateOne(dto);
return CreatedAtAction(nameof(CreateOne), createdObject);
}

[HttpPatch("{id:Guid}")]
public virtual async Task<ActionResult<TReadDto>> UpdateOneById(
[FromRoute] Guid id,
[FromForm] TUpdateDto update
)
{
var updatedObject = await _baseService.UpdateOneById(id, update);
return Ok(updatedObject);
}

[HttpDelete("{id}")]
public virtual async Task<ActionResult<bool>> DeleteOneById([FromRoute] Guid id)
{
var deletedObject = await _baseService.DeleteOneById(id);
return Ok(deletedObject);
}
}
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using WebApiBusiness.Abstraction;
using WebApiDomain.Shared;

namespace WebApiController.Controllers;

[ApiController]
[EnableCors("Policy1")]
[Route("api/v1/[controller]s")]
public class CrudController<T, TReadDto, TCreateDto, TUpdateDto> : ControllerBase
{
private readonly IBaseService<T, TReadDto, TCreateDto, TUpdateDto> _baseService;

public CrudController(IBaseService<T, TReadDto, TCreateDto, TUpdateDto> baseService)
{
_baseService = baseService;
}

[HttpGet]
public virtual async Task<ActionResult<IEnumerable<TReadDto>>> GetAll(
[FromQuery] QueryOptions queryOptions
)
{
return Ok(await _baseService.GetAll(queryOptions));
}

[HttpGet("{id:Guid}")]
public virtual async Task<ActionResult<TReadDto>> GetOneById([FromRoute] Guid id)
{
return Ok(await _baseService.GetOneById(id));
}

[HttpPost]
public virtual async Task<ActionResult<TReadDto>> CreateOne([FromBody] TCreateDto dto)
{
var createdObject = await _baseService.CreateOne(dto);
return CreatedAtAction(nameof(CreateOne), createdObject);
}

[HttpPatch("{id:Guid}")]
public virtual async Task<ActionResult<TReadDto>> UpdateOneById(
[FromRoute] Guid id,
[FromForm] TUpdateDto update
)
{
var updatedObject = await _baseService.UpdateOneById(id, update);
return Ok(updatedObject);
}

[HttpDelete("{id}")]
public virtual async Task<ActionResult<bool>> DeleteOneById([FromRoute] Guid id)
{
var deletedObject = await _baseService.DeleteOneById(id);
return Ok(deletedObject);
}
}
Accord
Accord11mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.