ObjectDisposedException on DbContext - asp.net-core

I am getting the following error when using DbContext injected into a service from another service:
ObjectDisposedException: Cannot access a disposed object.
The lifetime of my DbContext is Scoped and the lifetimes of my services is Transient, but changing these other than to Singleton (which we don't want) does not solve the issue.
Interestingly, the error occurs seemingly at random. Sometimes there are no errors and everything runs fine.
In relation to this error, I am (also randomly) getting a InvalidOperationException right after startup, when my Angular app starts firing requests to the backend.
"An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point."
My code:
public class MyService1 {
private static IMyService2 _myService2;
public MyService1(IMyService2 myService2){
_myService2 = myService2;
}
public async Task DoSomethingWithMyService2() {
await _myService2.DoSomething(new MyEntity());
}
}
public class MyService2 : IMyService2 {
private MyDbContext _dbContext;
public MyService2(MyDbContext myDbContext) {
_dbContext = myDbContext;
}
public async Task DoSomething(MyEntity myEntity) {
await _dbContext.MySet.AddAsync(myEntity); // <-- ObjectDisposedException
await _dbContext.SaveChangesAsync();
}
}

To answer my own question: the culprit is that MyService2 is stored in a static field after injection into MyService1.
Because the lifetime of DbContext is Scoped, it will be disposed after an initial request to the service. However, the service will live on during the lifetime of the entire app, with a reference to its disposed DbContext.
(I am not entirely sure about the latter (regarding lifetime of the app), because MyService1 itself is also Transient. Perhaps someone else can explain how that works.)

Related

ASP.NET Core WebApplicationFactory Server.Host is not accessible

I am using Microsoft.AspNetCore.Testing.WebApplicationFactory<Startup> in my integration tests. I'm trying to access _fixture.Server.Host property, but it's throwing an exception
"The TestServer constructor was not called with a IWebHostBuilder so IWebHost is not available".
Could anyone tell, why is it so?
Here is my code:
public class OneTest:IClassFixture<WebApplicationFactory<Startup>>{
private readonly WebApplicationFactory<Startup> _fixture;
public OneTest(WebApplicationFactory<Startup> fixture){
_fixture=fixture;
}
[Fact]
public async Task TestCanBeAuthenticated(){
var host=_fixture.Server.Host; // Exception is being thrown
// ... code ...
}
}
If you check the TestServer class,you could found serval different constructors:
The error just indicates the server created by WebApplicationFactory doesn't contain the instance of IWebHostBuilder in it's constructor
So,what's the purpose of accessing the host of TestServer in your test?

"No database provider has been configured for this DbContext"

I'm getting this error message when trying to reach my ASP .NET Core 3.1 Web API with Postman:
InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.
However, I do have configured it using AddDbContext in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
..
string connectionString = Configuration.GetConnectionString("ViewQlikDatabase");
services.AddDbContext<QlikDbContext>(options => options.UseSqlServer(connectionString));
}
I have checked the connection string and it is correctly retrieved.
The DbContext also has the recommended constructor:
public QlikDbContext(DbContextOptions<QlikDbContext> options)
: base(options)
{
}
The exception is raised when I try to call the context in my business class:
public string SedeGetTotaleElementiVista()
{
using (var db = new QlikDbContext())
{
// Exception raised here
int count = db.ViewQlikSede.Count();
return count.ToString();
}
}
Can someone please tell me what's wrong?
The context must be injected. If you new it up yourself, the service registration doesn't come into play at all. Here, you're creating it yourself, and not passing anything into it, so this instance definitely doesn't have a provider configured.

Instantiating IHubContext in ASP.NET Core

I am using SignalR on different places of my web project. In my Controllers and HostedService this seems to be working fine. Clients instantiate connections with my hub and I can communicate with them back using an IHubContext instance, injected in the constructor of every controller/hostedservice.
I am having another singleton, running in Background (No HosteService or BackgroundTask). This class is getting IHubContext injected in the constructor also. Still every time it gets called, it seems like this singleton is having a different instance of IHubContext, since this context has no clients/groups connected to it.
This class is being registered as this in the startup function:
services.AddSingleton<IServiceEventHandler<TourMonitorEvent>, TourMonitorEventHandler>();
To configure SignalR I am doing the following in ConfigureServices:
services.AddSignalR().AddNewtonsoftJsonProtocol();
and the following in configure:
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<MyHubClass>("/hubEndpoint");
endpoints.MapControllers();
});
the IHubContext ist bein injected as following in both Controllers/Hostedservices and the singleton:
public class MySingleton : IHandler<SomeGenericClass>
{
private readonly IHubContext<MyHubClass> _hubContext;
public MySingleton(IHubContext<MyHubClass> hubContext)
{
_hubContext = hubContext;
}
}
Are Controllers/HosteService being instantiated differently than my Singleton, in a way that might affect IHubContext instantiation?
As said in the documentation:
Hubs are transient.
So since you Singleton is not a HostedService or a BackgroundTask, I would recomend to inject the hub using a DI.
private IHubContext<MyHubClass, IMyHubClass> MyHubClass
{
get
{
return this.serviceProvider.GetRequiredService<IHubContext<MyHubClass, IMyHubClass>>();
}
}
Try this and verify if the context now is as you expected.

How to configure hangfire with unity?

I have ASP.NET Web API application. The application is using Unity as IoC container. The application is also using Hangfire and I am trying to configure Hangfire to use Unity.
So based on documentation i am using Hangfire.Unity which registers the unity container as a current job activator in Hangfire.
I have a class which has dependency on IBackgroundJobClient
public class MyService
{
private MyDBContext _dbContext = null;
private IBackgroundJobClient _backgroundJobClient = null;
public MyService(MyDbContext dbContext, IBackgroundJobClient backgroundJobClient)
{
_dbContext = dbContext;
_backgroundJobClient = backgroundJobClient;
}
}
However even after configuring Hangfire.Unity it could not create & pass instance of BackgroundJobClient
So i had to register every dependency of BackgroundJobClient with unity container.
Unity Registration
public class UnityConfig
{
private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer GetConfiguredContainer()
{
return container.Value;
}
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<MyDbContext>(new HierarchicalLifetimeManager(), new InjectionFactory(x => new MyDbContext()));
// register hangfire dependencies
container.RegisterType<IBackgroundJobClient, BackgroundJobClient>();
container.RegisterType<JobStorage, SqlServerStorage>(new InjectionConstructor("HangfireConnectionString"));
container.RegisterType<IJobFilterProvider, JobFilterAttributeFilterProvider>(new InjectionConstructor(true));
container.RegisterType<IBackgroundJobFactory, BackgroundJobFactory>();
container.RegisterType<IRecurringJobManager, RecurringJobManager>();
container.RegisterType<IBackgroundJobStateChanger, BackgroundJobStateChanger>();
}
}
OWIN Startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
var container = UnityConfig.GetConfiguredContainer();
Hangfire.GlobalConfiguration.Configuration.UseSqlServerStorage("HangfireConnectionString");
Hangfire.GlobalConfiguration.Configuration.UseUnityActivator(container);
// if i dont call UseSqlServerStorage() above then UseHangfireDashboard() method fails with exception
//JobStorage.Current property value has not been initialized. You must set it before using Hangfire Client or Server API.
app.UseHangfireDashboard();
app.UseHangfireServer();
RecurringJob.AddOrUpdate<MyService>(x => x.Prepare(), Cron.MinuteInterval(10));
}
}
Code is working with such configuration. However i have questions:
Is this the correct way of configuring Unity with Hangfire?
Why do i need to invoke Hangfire.GlobalConfiguration.Configuration.UseSqlServerStorage("HangfireConnectionString") in OWIN startup even though SqlServerStorage is already registered with Unity container as JobStorage?
If i dont invoke UseSqlServerStorage() method in OWIN startup then i get exception on app.UseHangfireDashboard() method.
JobStorage.Current property value has not been initialized. You must
set it before using Hangfire Client or Server API.
I believe there is a problem where you want to kick off Hangfire outside of the Unity ecosystem, but also want Unity to understand how to instantiate the appropriate Hangfire interfaces with the associated implementations. Since Hangfire itself doesn't use Unity, you will need to start up Hangfire with the appropriate configuration, such as the SQL Server connection string, and then use that configuration to inform Unity how to instantiate the Hangfire interfaces. I was able to solve this problem by setting the global Hangfire configuration for SQL and then use that same Hangfire static instance to set up Unity.
Here's example code where first you will see how I start the hangfire dashboard and server with a connection string:
public void Configuration(IAppBuilder app)
{
var configuration = new Configuration(); // whatever this is for you
GlobalConfiguration.Configuration.UseSqlServerStorage(
configuration.GetConnectionString());
GlobalConfiguration.Configuration.UseActivator(
new HangfireContainerActivator(UnityConfig.GetConfiguredContainer()));
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] {new HangfireAuthorizationFilter()}
});
app.UseHangfireServer();
}
As the second example, here's the configuration of Unity for Hangfire; notice how this code is using the static JobStorage Hangfire object to instantiate any requests for JobStorage.
public static void RegisterHangfire(IUnityContainer container)
{
container.RegisterType<JobStorage>(new InjectionFactory(c => JobStorage.Current));
container.RegisterType<IJobFilterProvider, JobFilterAttributeFilterProvider>(new InjectionConstructor(true));
container.RegisterType<IBackgroundJobFactory, BackgroundJobFactory>();
container.RegisterType<IRecurringJobManager, RecurringJobManager>();
container.RegisterType<IBackgroundJobClient, BackgroundJobClient>();
container.RegisterType<IBackgroundJobStateChanger, BackgroundJobStateChanger>();
}
I believe this approach gives you the best of both worlds where you only set up your SQL Server connection once and you do it early to kick off Hangfire, but then you use that instance to tell Unity how to behave.

Resolving dependencies in Integration test in ASP.NET Core

I have ASP.NET Core API. I have already gone through documentation here that shows how to do integration testing in asp.net core. The example sets up a test server and then invoke controller method.
However I want to test a particular class method directly (not a controller method)? For example:
public class MyService : IMyService
{
private readonly DbContext _dbContext;
public MyService(DbContext dbContext)
{
_dbContext = dbContext;
}
public void DoSomething()
{
//do something here
}
}
When the test starts I want startup.cs to be called so all the dependencies will get register. (like dbcontext) but I am not sure in integration test how do I resolve IMyService?
Note: The reason I want to test DoSomething() method directly because this method will not get invoked by any controller. I am using Hangfire inside this API for background processing. The Hangfire's background processing job will call DoSomething() method. So for integration test I want to avoid using Hangfire and just directly call DoSomething() method
You already have a TestServer when you run integration tests, from here you can easily access the application wide container. You can't access the RequestServices for obvious reason (it's only available in HttpContext, which is created once per request).
var testServer = new TestServer(new WebHostBuilder()
.UseStartup<Startup>()
.UseEnvironment("DevelopmentOrTestingOrWhateverElse"));
var myService = testServer.Host.Services.GetRequiredService<IMyService>();