NInject and Quartz.net problems - c#

I have been reading a lot for DI and Quartz.net using ninject and I can't make it run. I'm trying to follow this link. This is my code:
This is the job:
public class ClientsImportJob : IJob
{
private readonly IUserClientImportService _userClientImportService;
public ClientsImportJob(IUserClientImportService userClientImportService)
{
_userClientImportService = userClientImportService;
}
public void Execute(IJobExecutionContext context)
{
_userClientImportService.ProcessFiles();
}
}
This is the Factory:
public class NInjectJobFactory : SimpleJobFactory
{
readonly IKernel _kernel;
public NInjectJobFactory(IKernel kernel)
{
this._kernel = kernel;
}
public override IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
try
{
return (IJob) this._kernel.Get(bundle.JobDetail.JobType); // will inject dependencies that the job requires
}
catch (Exception e)
{
throw new SchedulerException(string.Format("Problem while instantiating job '{0}' from the NinjectJobFactory.", bundle.JobDetail.Key), e);
}
}
}
And this is where I call the scheduller:
public class QuartzService
{
public static void ScheduleImportTask()
{
var kernel = InitializeNinjectKernel();
var scheduler = kernel.Get<IScheduler>();
scheduler.ScheduleJob(
JobBuilder.Create<ClientsImportJob>().Build(),
TriggerBuilder.Create().WithSimpleSchedule(s => s.WithIntervalInSeconds(10).RepeatForever()).Build());
scheduler.Start();
}
private static IKernel InitializeNinjectKernel()
{
var kernel = new StandardKernel();
kernel.Bind<IScheduler>().ToMethod(x =>
{
var sched = new StdSchedulerFactory().GetScheduler();
sched.JobFactory = new NInjectJobFactory(kernel);
return sched;
});
kernel.Bind<IUserClientImportService>().To<UserClientImportService>();
return kernel;
}
}
What am I doing wrong?

Related

.NET Core Console App | Hangfire With Dependency Injection

Goal:
Fundamentally I am trying to add a background job, that has dependencies injected, to a console application.
Problem:
Although the jobs are queued, they are never executed.
Program.cs
var appSettings = ConfigHelper.GetConfig();
Console.WriteLine("Initialising Hangfire Server...");
GlobalConfiguration.Configuration.UseSqlServerStorage(appSettings.ConnectionString);
using (var server = new BackgroundJobServer())
{
Console.WriteLine("Hangfire Server started.");
var t = serviceProvider.GetService<ITestService>();
t.Test();
Console.ReadKey();
}
ServiceProviderFactory.cs
public static void Setup()
{
IServiceCollection services = new ServiceCollection();
...
services.AddDbContext<Db>(x => x.UseSqlServer(appSettings.ConnectionString));
services.AddTransient<IInsertLogJob, InsertLogJob>();
services.AddTransient<ITestService, TestService>();
_serviceProvider = services.BuildServiceProvider();
}
TestService.cs
public interface ITestService
{
void Test();
}
public class TestService : ITestService
{
private readonly ILogger<TestService> _logger;
public TestService(ILogger<TestService> logger)
{
_logger = logger;
}
public void Test()
{
logger.LogInformation("Test");
_logger.LogError("Error");
}
}
Logger.cs
public class Logger : ILogger
{
...
Log log = new Log()
{
Message = message,
EventId = eventId.Id,
ObjectId = eventId.Name,
LogLevel = logLevel.ToString(),
CreatedTime = DateTime.Now
};
BackgroundJob.Enqueue<IInsertLogJob>(j => j.Insert(log));
}
InsertLogJob.cs
public interface IInsertLogJob
{
void Insert(Log log);
}
public class InsertLogJob : IInsertLogJob
{
private Db _dataContext;
public InsertLogJob(Db context)
{
_dataContext = context;
}
public void Insert(Log log)//<-- this never happens
{
_dataContext.Logs.Add(log);
_dataContext.SaveChanges();
}
}
DB Record
So all the code up to the point where the data has to be inserted into the database runs, the Hangfire job gets inserted as per the picture above, but the code is never executed.

Property injection

I'm trying make a telegram bot with reminder. I'm using Telegram.Bot 14.10.0, Quartz 3.0.7, .net core 2.0. The first version should : get message "reminder" from telegram, create job (using Quartz) and send meaasage back in 5 seconds.
My console app with DI looks like:
Program.cs
static IBot _botClient;
public static void Main(string[] args)
{
// it doesn't matter
var servicesProvider = BuildDi(connecionString, section);
_botClient = servicesProvider.GetRequiredService<IBot>();
_botClient.Start(appModel.BotConfiguration.BotToken, httpProxy);
var reminderJob = servicesProvider.GetRequiredService<IReminderJob>();
reminderJob.Bot = _botClient;
Console.ReadLine();
_botClient.Stop();
// it doesn't matter
}
private static ServiceProvider BuildDi(string connectionString, IConfigurationSection section)
{
var rJob = new ReminderJob();
var sCollection = new ServiceCollection()
.AddSingleton<IBot, Bot>()
.AddSingleton<ReminderJob>(rJob)
.AddSingleton<ISchedulerBot>(s =>
{
var schedBor = new SchedulerBot();
schedBor.StartScheduler();
return schedBor;
});
return sCollection.BuildServiceProvider();
}
Bot.cs
public class Bot : IBot
{
static TelegramBotClient _botClient;
public void Start(string botToken, WebProxy httpProxy)
{
_botClient = new TelegramBotClient(botToken, httpProxy);
_botClient.OnReceiveError += BotOnReceiveError;
_botClient.OnMessage += Bot_OnMessage;
_botClient.StartReceiving();
}
private static async void Bot_OnMessage(object sender, MessageEventArgs e)
{
var me = wait _botClient.GetMeAsync();
if (e.Message.Text == "reminder")
{
var map= new Dictionary<string, object> { { ReminderJobConst.ChatId, e.Message.Chat.Id.ToString() }, { ReminderJobConst.HomeWordId, 1} };
var job = JobBuilder.Create<ReminderJob>().WithIdentity($"{prefix}{rnd.Next()}").UsingJobData(new JobDataMap(map)).Build();
var trigger = TriggerBuilder.Create().WithIdentity($"{prefix}{rnd.Next()}").StartAt(DateTime.Now.AddSeconds(5).ToUniversalTime())
.Build();
await bot.Scheduler.ScheduleJob(job, trigger);
}
}
}
Quartz.net not allow use constructor with DI. That's why I'm trying to create property with DI.
ReminderJob.cs
public class ReminderJob : IJob
{
static IBot _bot;
public IBot Bot { get; set; }
public async Task Execute(IJobExecutionContext context)
{
var parameters = context.JobDetail.JobDataMap;
var userId = parameters.GetLongValue(ReminderJobConst.ChatId);
var homeWorkId = parameters.GetLongValue(ReminderJobConst.HomeWordId);
await System.Console.Out.WriteLineAsync("HelloJob is executing.");
}
}
How can I pass _botClient to reminderJob in Program.cs?
If somebody looks for answer, I have one:
Program.cs (in Main)
var schedBor = servicesProvider.GetRequiredService<ISchedulerBot>();
var logger = servicesProvider.GetRequiredService<ILogger<DIJobFactory>>();
schedBor.StartScheduler();
schedBor.Scheduler.JobFactory = new DIJobFactory(logger, servicesProvider);
DIJobFactory.cs
public class DIJobFactory : IJobFactory
{
static ILogger<DIJobFactory> _logger;
static IServiceProvider _serviceProvider;
public DIJobFactory(ILogger<DIJobFactory> logger, IServiceProvider sp)
{
_logger = logger;
_serviceProvider = sp;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
IJobDetail jobDetail = bundle.JobDetail;
Type jobType = jobDetail.JobType;
try
{
_logger.LogDebug($"Producing instance of Job '{jobDetail.Key}', class={jobType.FullName}");
if (jobType == null)
{
throw new ArgumentNullException(nameof(jobType), "Cannot instantiate null");
}
return (IJob)_serviceProvider.GetRequiredService(jobType);
}
catch (Exception e)
{
SchedulerException se = new SchedulerException($"Problem instantiating class '{jobDetail.JobType.FullName}'", e);
throw se;
}
}
// get from https://github.com/quartznet/quartznet/blob/139aafa23728892b0a5ebf845ce28c3bfdb0bfe8/src/Quartz/Simpl/SimpleJobFactory.cs
public void ReturnJob(IJob job)
{
var disposable = job as IDisposable;
disposable?.Dispose();
}
}
ReminderJob.cs
public interface IReminderJob : IJob
{
}
public class ReminderJob : IReminderJob
{
ILogger<ReminderJob> _logger;
IBot _bot;
public ReminderJob(ILogger<ReminderJob> logger, IBot bot)
{
_logger = logger;
_bot = bot;
}
public async Task Execute(IJobExecutionContext context)
{
var parameters = context.JobDetail.JobDataMap;
var userId = parameters.GetLongValue(ReminderJobConst.ChatId);
var homeWorkId = parameters.GetLongValue(ReminderJobConst.HomeWordId);
await _bot.Send(userId.ToString(), "test");
}
}

ASP Core 2.1 Dependency Injection to Quartz

I am trying to inject services into my SendEmailJob class.
I use standard ASP Core dependency injection and Quartz library for scheduling.
I am trying to build solution based on this answer. But still I am facing injecting issues.
I have such code setup:
//Startup.cs, ConfigureServices
ServiceAutoConfig.Configure(allServices);
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddTransient<IJobFactory, JobFactory>((provider) => new JobFactory(services.BuildServiceProvider()));
services.AddTransient<SendEmailJob>();
//Startup.cs, Configure
app.UseQuartz((quartz) => quartz.AddJob<SendEmailJob>("SendEmailJob", "Email", mailSettings.EmailSchedulerInterval));
Implementation of SendEmailJob:
public class SendEmailJob : IJob
{
private readonly IMessageService _messageService;
private static bool IsBusy = false;
public SendEmailJob(IMessageService messageService)
{
_messageService = messageService;
}
public async Task Execute(IJobExecutionContext context)
{
try
{
if (IsBusy)
return;
IsBusy = true;
//...
}
catch (Exception error)
{
}
finally
{
IsBusy = false;
}
}
}
Implementation of JobFacctory:
public class JobFactory : IJobFactory
{
protected readonly IServiceProvider _container;
public JobFactory(IServiceProvider container)
{
_container = container;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
try
{
var res = _container.GetService(bundle.JobDetail.JobType) as IJob;
return res;
}
catch (Exception ex)
{
//ERROR- Cannot resolve 'Quartz.Jobs.SendEmailJob' from root provider because it
// requires scoped service 'BLL.Base.UnitOfWork.Interfaces.IUnitOfWork'.
throw;
}
}
public void ReturnJob(IJob job)
{
(job as IDisposable)?.Dispose();
}
}
Implementation of Quartz.cs
public class Quartz
{
private IScheduler _scheduler;
public static IScheduler Scheduler { get { return Instance._scheduler; } }
private static Quartz _instance = null;
public static Quartz Instance
{
get
{
if (_instance == null)
{
_instance = new Quartz();
}
return _instance;
}
}
private Quartz()
{
Init();
}
private async void Init()
{
_scheduler = await new StdSchedulerFactory().GetScheduler();
}
public IScheduler UseJobFactory(IJobFactory jobFactory)
{
Scheduler.JobFactory = jobFactory;
return Scheduler;
}
public async void AddJob<T>(string name, string group, int interval)
where T : IJob
{
IJobDetail job = JobBuilder.Create<T>()
.WithIdentity(name, group)
.Build();
ITrigger jobTrigger = TriggerBuilder.Create()
.WithIdentity(name + "Trigger", group)
.StartNow()
.WithSimpleSchedule(t => t.WithIntervalInSeconds(interval).RepeatForever()) // Mit wiederholung alle interval sekunden
.Build();
await Scheduler.ScheduleJob(job, jobTrigger);
}
public static async void Start()
{
await Scheduler.Start();
}
}
And implementation of UseQuartzExtension:
public static void UseQuartz(this IApplicationBuilder app, Action<Quartz> configuration)
{
var jobFactory = new JobFactory(app.ApplicationServices);
Quartz.Instance.UseJobFactory(jobFactory);
configuration.Invoke(Quartz.Instance);
Quartz.Start();
}
And there is an error while injecting IMessageService into SendMailJob.
Because it requires UnitOfWork or fails on any other scoped service.
Could you please explain me how to inject it correct?
The problem is that you register IUnitOfWork as scoped, but you don't have any scope at the moment when you resolve it. Create it before resolving your job:
public class JobFactory : IJobFactory, IDisposable
{
protected readonly IServiceScope _scope;
public JobFactory(IServiceProvider container)
{
_scope = container.CreateScope();
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
var res = _scope.ServiceProvider.GetService(bundle.JobDetail.JobType) as IJob;
return res;
}
public void ReturnJob(IJob job)
{
(job as IDisposable)?.Dispose();
}
public void Dispose()
{
_scope.Dispose();
}
}
I'm not sure if you were able to resolve the disposed DbContext issue you mentioned in your comment, but I'm working on a to be released .NET core app with the same problem and came up with a solution similar to Alex Riabov but uses a concurrent dictionary to dispose the scope when the job has ended. The result is a new dbcontext is injected into my job when a new job is instantiated.
public class QuartzJobFactory : IJobFactory
{
protected readonly IServiceProvider serviceProvider;
private ConcurrentDictionary<IJob, IServiceScope> scopes = new ConcurrentDictionary<IJob, IServiceScope>();
public QuartzJobFactory(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
// instantiation of new job
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
try {
var scope = serviceProvider.CreateScope();
var job = scope.ServiceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
scopes.TryAdd(job, scope);
return job;
}
catch (Exception ex) {
throw;
}
}
// executes when job is complete
public void ReturnJob(IJob job)
{
try {
(job as IDisposable)?.Dispose();
if (scopes.TryRemove(job, out IServiceScope scope))
scope.Dispose();
}
catch (Exception ex) {
}
}
}

How to inject SignalR hub to a regular class using SimpleInjector

In a console app I'm using
OWIN
Signalr
SimpleInjector
I have an Engine class which needs NotificationHub and NotificationHub needs ClientWriter to work. I managed to setup dependencies but it seems that the Engine cannot directly use the hub. Later I found out that the hub engine is using is different than the one clients are being connected to. Here are my files:
Engine.cs
public class Engine
{
private readonly NotificationHub _hub;
private readonly Timer _timer;
public Engine(NotificationHub hub)
{
_hub = hub;
_timer = new System.Timers.Timer(1000);
_timer.Elapsed += (o, arg) =>
{
_timer.Enabled = false;
Inform(arg.SignalTime.ToString());
_timer.Enabled = true;
};
_timer.Start();
}
public void Inform(string str)
{
try
{
var ctx = GlobalHost.ConnectionManager.GetHubContext("notification");
ctx.Clients.All.Notify($"{str} ====="); //works why?
_hub.Inform(str); // does not work why?
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
NotificationHub.cs
public class NotificationHub : Hub
{
ClientWriter _writer;
public NotificationHub(ClientWriter writer)
{
_writer = writer;
}
public override Task OnConnected()
{
Console.WriteLine($"connected {Context.ConnectionId}");
Clients.All.Notify($"{Context.ConnectionId} has joined");
return base.OnConnected();
}
public DateTime Inform(string msg)
{
_writer.Received(msg);
return DateTime.Now;
}
}
ClientWriter.cs
public class ClientWriter
{
public void Received(string arg)
{
Console.WriteLine(arg);
}
}
HubActivator
class SimpleInjectorSignalRHubActivator : IHubActivator
{
Container _container;
public SimpleInjectorSignalRHubActivator(Container container)
{
_container = container;
}
public IHub Create(HubDescriptor descriptor)
{
return _container.GetInstance(descriptor.HubType) as IHub;
}
}
Program.cs
static void Main(string[] args)
{
var _container = new Container();
_container.RegisterSingleton<NotificationHub>();
_container.RegisterSingleton<Engine>();
_container.RegisterSingleton<ClientWriter>();
var options = new StartOptions();
options.Urls.Add("http://localhost:12345");
using (var server = WebApp.Start(options, (app) =>
{
var activator = new SimpleInjectorSignalRHubActivator(_container);
GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => activator);
GlobalHost.DependencyResolver.Register(typeof(NotificationHub), () => _container.GetInstance<NotificationHub>());
app.RunSignalR(new HubConfiguration { EnableDetailedErrors = true });
app.UseWelcomePage();
}))
{
var ctx = GlobalHost.ConnectionManager.GetHubContext("notification");
var engine = _container.GetInstance<Engine>();
engine.Inform("Engine has started");
Console.ReadLine();
}
}
What am I doing wrong. Why the Engine is not able to send to the clients

Constructor injection with Quartz.NET and Simple Injector

Currently I am writing a service using Quartz.NET to schedule the running of it.
I was wondering if anyone has any experience of using constructor injection with Quartz.NET and Simple Injector.
Below is essentially what I wish to achieve
public class JobImplementation: IJob
{
private readonly IInjectedClass injectedClass;
public JobImplementation(IInjectedClass _injectedClass)
{
injectedClass = _injectedClass
}
public void Execute(IJobExecutionContext _context)
{
//Job code
}
According to this blog post, you would need to implement a custom IJobFactory, like this:
public class SimpleInjectorJobFactory : IJobFactory
{
private readonly Container container;
private readonly Dictionary<Type, InstanceProducer> jobProducers;
public SimpleInjectorJobFactory(
Container container, params Assembly[] assemblies)
{
this.container = container;
// By creating producers, jobs can be decorated.
var transient = Lifestyle.Transient;
this.jobProducers =
container.GetTypesToRegister(typeof(IJob), assemblies).ToDictionary(
type => type,
type => transient.CreateProducer(typeof(IJob), type, container));
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler _)
{
var jobProducer = this.jobProducers[bundle.JobDetail.JobType];
return new AsyncScopedJobDecorator(
this.container, () => (IJob)jobProducer.GetInstance());
}
public void ReturnJob(IJob job)
{
// This will be handled automatically by Simple Injector
}
private sealed class AsyncScopedJobDecorator : IJob
{
private readonly Container container;
private readonly Func<IJob> decorateeFactory;
public AsyncScopedJobDecorator(
Container container, Func<IJob> decorateeFactory)
{
this.container = container;
this.decorateeFactory = decorateeFactory;
}
public async Task Execute(IJobExecutionContext context)
{
using (AsyncScopedLifestyle.BeginScope(this.container))
{
var job = this.decorateeFactory();
await job.Execute(context);
}
}
}
}
Furthermore, you'll need the following registrations:
var container = new Container();
container.Options.ScopedLifestyle = new AsyncScopedLifestyle();
var factory = new StdSchedulerFactory();
IScheduler scheduler = await factory.GetScheduler();
scheduler.JobFactory = new SimpleInjectorJobFactory(
container,
Assembly.GetExecutingAssembly()); // assemblies that contain jobs
// Optional: register some decorators
container.RegisterDecorator(typeof(IJob), typeof(LoggingJobDecorator));
container.Verify();
Late to the party, but https://github.com/hbiarge/Quartz.Unity works well for combining Quartz.NET and Unity.
IUnityContainer container = new UnityContainer();
container.AddNewExtension<Quartz.Unity.QuartzUnityExtension>();
// do your other Unity registrations
IScheduler scheduler = container.Resolve<IScheduler>();
scheduler.ScheduleJob(
new JobDetailImpl(myCommandName, typeof(MyCommand)),
TriggerBuilder.Create()
.WithCronSchedule(myCronSchedule)
.StartAt(startTime)
.Build()
);
scheduler.Start();
There are few steps to use Quartz.net with dependency injection engine from asp.net core.
Add nuget package to your project:
Microsoft.Extensions.DependencyInjection
Create custom JobFactory:
public class JobFactory : IJobFactory
{
protected readonly IServiceProvider _serviceProvider;
public JobFactory(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider;
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
=> _serviceProvider.GetService(bundle.JobDetail.JobType) as IJob;
public void ReturnJob(IJob job)
=> (job as IDisposable)?.Dispose();
}
Specify JobFactory when configuring scheduler:
var scheduler = await StdSchedulerFactory.GetDefaultScheduler();
scheduler.JobFactory = new JobFactory(_serviceProvider);
For someone can be usefull example of win service with Quartz.net and DI (from asp.net core) on the board:
public class WinService : ServiceBase
{
private Scheduler _scheduleManager;
private readonly Startup _startup;
public WinService()
{
ServiceName = "SomeWinService";
_startup = new Startup();
}
static void Main(string[] args)
{
var service = new WinService();
// Working as Windows-service
if (Console.IsInputRedirected && Console.IsOutputRedirected)
{
ServiceBase.Run(service);
}
// Working as console app
else
{
service.OnStart(args);
Console.WriteLine("Press any key to stop...");
Console.ReadKey();
service.OnStop();
}
}
protected override void OnStart(string[] args)
{
_startup.RegisterServices();
_scheduleManager = new Scheduler(_startup.ServiceProvider);
_scheduleManager.StartTracking().Wait();
}
protected override void OnPause()
=> _scheduleManager.PauseTracking().Wait();
protected override void OnContinue()
=> _scheduleManager.ResumeTracking().Wait();
protected override void OnStop()
{
_scheduleManager.StopTracking().Wait();
_startup.DisposeServices();
}
}
public class Startup
{
private IServiceProvider _serviceProvider;
public IServiceProvider ServiceProvider => _serviceProvider;
public void RegisterServices()
{
_serviceProvider = new ServiceCollection()
//.AddTransient(...)
//.AddScoped(...)
//.AddSingleton(...)
.BuildServiceProvider();
}
public void DisposeServices()
{
if (_serviceProvider == null)
return;
if (_serviceProvider is IDisposable)
{
((IDisposable)_serviceProvider).Dispose();
}
}
}
public class Scheduler
{
private readonly IServiceProvider _serviceProvider;
private IScheduler _scheduler;
public Scheduler(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider;
public async Task StartTracking()
{
_scheduler = await StdSchedulerFactory.GetDefaultScheduler();
_scheduler.JobFactory = new JobFactory(_serviceProvider);
await _scheduler.Start();
// Schedule your jobs here
}
public async Task PauseTracking() => await _scheduler?.PauseAll();
public async Task ResumeTracking() => await _scheduler?.ResumeAll();
public async Task StopTracking() => await _scheduler?.Shutdown();
}

Categories

Resources