How to configure hangfire with unity? - hangfire

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.

Related

"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.

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>();

Log hangfire events using existing serilog

Im new to hangfire, and im trying to setup a way to log the events using my existing serilog logger in an asp.net web api. Here is my logger class:
public static class LoggerInitializer
{
private static ILogger CreateLog()
{
var settings = Settings.Instance;
Log.Logger = new LoggerConfiguration().
MinimumLevel.Debug().
WriteTo.RollingFile(settings.LoggerDebugDirectory +"Debug-{Date}.txt", restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Debug,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}").
WriteTo.RollingFile(settings.LoggerVerboseDirectory + "Debug-{Date}.txt").
CreateLogger();
return Log.Logger;
}
static ILogger _logger;
public static ILogger GetLogger()
{
if (_logger != null)
return _logger;
return _logger = CreateLog();
}
}
and in my startup file I add the code from the hangfire documentation:
GlobalConfiguration.Configuration
.UseSqlServerStorage(Settings.Instance.NLSContextConnectionString);
app.UseHangfireDashboard();
app.UseHangfireServer();
My hangfire works perfectly, but how do i enable make hangfire use my serilog?
It's possible that Hangfire is initializing and caching its own internal logger before CreateLog() is being called by the application.
To test this theory, try moving the code that initializes Log.Logger to the very beginning of the app's startup code, e.g. in Global.Application_Start() or similar.
In Hangfire 1.6.19 (and maybe before that, I did not check) adding the NuGet Package to your project gives you an extension method on IGlobalConfiguration :
configuration.UseSerilogLogProvider();

SimpleInjector: Injection does not work with MVC 4 ASP.NET Web API

I have this setup:
public static void Initialize(ISessionFactory factory)
{
var container = new Container();
InitializeContainer(container, factory);
container.RegisterMvcControllers(
Assembly.GetExecutingAssembly());
container.RegisterMvcAttributeFilterProvider();
container.Verify();
DependencyResolver.SetResolver(
new SimpleInjectorDependencyResolver(container));
}
private static void InitializeContainer(
Container container, ISessionFactory factory)
{
container.RegisterPerWebRequest<ISession>(
() => factory.OpenSession(), true);
}
The Initialize method is called in Application_Start:
public class WebApiApplication : HttpApplication
{
protected void Application_Start()
{
SimpleInjectorInitializer.Initialize(
new NHibernateHelper(
Assembly.GetCallingAssembly(),
this.Server.MapPath("/"))
.SessionFactory);
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
But when i try to call the controller action I get an ArgumentException:
Type 'PositionReportApi.Controllers.PositionsController' does not have
a default constructor
Stack trace:
at System.Linq.Expressions.Expression.New(Type type) at
System.Web.Http.Internal.TypeActivator.Create[TBase](Type
instanceType) at
System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage
request, HttpControllerDescriptor controllerDescriptor, Type
controllerType)
I can't register an ISession.
How do i register an ISession that is created by a factory?
From the stack trace I can see that you are using the new .NET 4.5 ASP.NET Web API and Simple Injector is not in the presented call graph. This probably means that you haven't configured the Simple Injector for use with the new Web API, which is a different registration than what you need for MVC (for some strange reason, and I sincerely hope they fix this in the final release). Since you didn't register a Simple Injector specific System.Web.Http.Dependencies.IDependencyResolver implementation to the Web API's GlobalConfiguration.Configuration.DependencyResolver, you'll get the default behavior, which will only work with default constructors.
Take a look at this Stackoverflow answer Does Simple Injector supports MVC 4 ASP.NET Web API? to see how to configure Simple Injector with the new ASP.NET Web API.
UPDATE
Note that you can get this exception even if you configured the DependencyResolver correctly, but when you didn't register register your Web API Controllers explicitly. This is caused by the way Web API is designed.
Always register your Controllers explicitly.