C
C#2mo ago
Maik

DI Service Usage in Quartz.NET registration

I am using Quartz.NET as my scheduling framework, because Quartz.NET has a built-in support for persistent storage, which solves many problems for me out of the box. The problem is the configuration of the connection string for the database. I got a scoped service which resolves the database connection string, since the appsettings.json only includes the template connection string. The actual user and password comes from a vault during startup. Therefore I have to use the service. As far as I know, Quartz.NET has no overload to utilize the IServiceProvider. Because of that I created this overload method
public static IServiceCollection AddQuartz(
this IServiceCollection services,
Action<IServiceProvider, IServiceCollectionQuartzConfigurator> configure)
{
var serviceProvider = services.BuildServiceProvider();

services.AddQuartz(configurator => configure(serviceProvider, configurator));

return services;
}
public static IServiceCollection AddQuartz(
this IServiceCollection services,
Action<IServiceProvider, IServiceCollectionQuartzConfigurator> configure)
{
var serviceProvider = services.BuildServiceProvider();

services.AddQuartz(configurator => configure(serviceProvider, configurator));

return services;
}
Which allows me to do this
services.AddQuartz((serviceProvider, configuration) =>
{
// Other configuration...

var connectionString = serviceProvider
.GetRequiredService<ISecretConnectionStringProvider>()
.GetDatabaseConnectionString();

configuration.UsePersistentStore(storeOptions =>
{
// Other configuration...

storeOptions.UsePostgres(postgresOptions =>
{
postgresOptions.ConnectionString = connectionString;
});
});
});
services.AddQuartz((serviceProvider, configuration) =>
{
// Other configuration...

var connectionString = serviceProvider
.GetRequiredService<ISecretConnectionStringProvider>()
.GetDatabaseConnectionString();

configuration.UsePersistentStore(storeOptions =>
{
// Other configuration...

storeOptions.UsePostgres(postgresOptions =>
{
postgresOptions.ConnectionString = connectionString;
});
});
});
My question is if there is a better alternative? Somehow I am unhappy with the solution. I dont know why but it feels wrong.
18 Replies
DΣX
DΣX2mo ago
when i last used quartz, there was no way to have it sync job execution with multiple app instances, which made it a big no no for any serious app, as scalability is essentially not given. just want to make sure this is not a concern for you, or they have added support for it?! we use hangfire, as the whole job scheduling is done in a database and you can set up jobs to not run in parallel. i am not certain, but i believe hangfire can be used in a way that allows for services to be injected as usual. i just remember that there was barely any migration effort between the two, so i assum u can use di there as well let me check the code
Maik
MaikOP2mo ago
At least I've tested it several times in the cloud by now and it supports clustering out of the box aswell. Another reason why we are using it at the moment. Nonetheless we didn't have multiple replicas in production yet. We only tested it with multiple replicas so I can't give any warranty. See more here https://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/advanced-enterprise-features.html We also considered using Hangfire but hangfire has a license model when I remember correctly?
Lesson 11: Advanced (Enterprise) Features | Quartz.NET
Open-source scheduling framework for .NET.
Maik
MaikOP2mo ago
Yes Hangfire is not free. Therefore no option for me at the moment. Sadly https://www.hangfire.io/pricing/
Hangfire – Background Jobs for .NET and .NET Core
An easy way to perform fire-and-forget, delayed and recurring tasks in ASP.NET applications. No Windows Service required, backed by persistent storage.
DΣX
DΣX2mo ago
not sure what you are talking about ... basically first sentence is: Hangfire is completely free even for commercial use.
DΣX
DΣX2mo ago
but ok. this is not about hangfire. yes its possible to use DI in hangfire!
No description
DΣX
DΣX2mo ago
but now how to do in quartz. i need to go far back in history ... 😄
Maik
MaikOP2mo ago
Oh bruh you are right. I only saw the prices and were like "nope" But why are there paid plans lol I am not very educated about hangfire - Do they offer a own service?
DΣX
DΣX2mo ago
quarz can do DI as well you have to do it the other way arround though. let the di create the jobs for you, and then start them
Maik
MaikOP2mo ago
Ah now I do understand - I know that Quartz supports DI in the jobs - I am already using it there. But does Quartz also supports DI during it's configuration in the startup process
DΣX
DΣX2mo ago
oh well i mean using the service provider to build everything beforehand is odd.
Maik
MaikOP2mo ago
Yes, but I don't see any other solution yet, since I must use the ISecretConnectionStringProvider service to receive the connectionString For example EntityFramework has a similar feature where you can access the ServiceProvider during .AddDbContext<T>
DΣX
DΣX2mo ago
i'd probably extract that db connection string building into a piece of config codeand use this in your db and in your quartz configuration
Maik
MaikOP2mo ago
I mean I got all parts of the connection string in the appsettings, since the vault secrets get appended to the appsettings during startup. But I cant access IConfiguration either since we already know there is no builtin serviceProvider support
DΣX
DΣX2mo ago
so you are not really about quarz, but rather about the henn-and-egg problem of creating a database then using that database to extract a connection string in order to use this in another database connection pass in iconfiguration during startup you are doing that already?!
Maik
MaikOP2mo ago
Yes and no. I don't have a "first" database there I get the actual connectionString from. The username and password for the quartz connectionString comes from a vault (hashicorp to be exact). But yes the general problem is receiving the connectionString during the quartz configuration. It might be possible to do quartz, but the user and passwords are rotated in the vault. Therefore my service will often use different credentials. Using IConfiguration the credentials would be fixed and couldn't be rotated other than with a service restart. The custom service providing the connection string allows me to always resolve the newest credentials without having to restart the service The appsettings looks like this
{
"ConnectionStrings": {
"Database": "Server=...;Port=....;Database=...;Username=%REPLACE_USERNAME%;Password=%REPLACE_PASSWORD%"
},
"Postgres": {
"Username": "Actual username value",
"Password": "Actual password value"
}
}
{
"ConnectionStrings": {
"Database": "Server=...;Port=....;Database=...;Username=%REPLACE_USERNAME%;Password=%REPLACE_PASSWORD%"
},
"Postgres": {
"Username": "Actual username value",
"Password": "Actual password value"
}
}
But the postgres section is being appended during startup from the vault and it can be rotated. So they are not always the same.
wasabi
wasabi2mo ago
Quartz has supported clustering for like a decade. I'm not 100% otherwise sure what you're asking about configuration. Quartz configuration is stored in the Options pattern now, isn't it? Eh, sorta. Kinda. Everything the DI does is just to wire up SchedulerBuilder. But it does process QuartzOptions So you can just Configure QuartzOptions to set whatever properties you want. Yup. AddQuartz uses SchedulerBuilder to provide the nice extension methods. But all those are doing is compiling the traditional quartz configuration properties collection. And then those get dumped into QuartzOptions. And later the scheduler uses IOptions<QuartzOptions> So you can just set your own connectiojn string in a IConfigure or IPostConfigure.
Maik
MaikOP2mo ago
Why haven't I ever thought about that - The IConfigure<QuartzOptions> is a very good Idea - Thanks a lot, that will solve My Problem!
Unknown User
Unknown User2mo ago
Message Not Public
Sign In & Join Server To View

Did you find this page helpful?