Quartz job does not run with autofac and a constructor with a service - c#

I have the Quartz Job shown below
public class ExtractTradesJob: IJob
{
private ITradeExtractor _tradeExtractor;
public ExtractTradesJob(ITradeExtractor tradeExtractor)
{
_tradeExtractor = tradeExtractor;
}
public async Task GetTradesAsync(DateTime dateTime)
{
Console.WriteLine(dateTime);
}
void IJob.Execute(IJobExecutionContext context)
{
Task.Run(async () => await GetTradesAsync(DateTime.Now));
}
}
I have 2 issues
As I need to use .NET 4.5, Async support is not available out of the box so is the method I have used for calling my async method correct?
It appears as though if I have a constructor as in my case, the job does not fire. I have checked and I know the the ITradeExtractor service is registered correctly. So how can I have a job that takes a service in its constructor? If I remove the constructor, my Execute method is called correctly
I am using AutoFac 3.5.2 and Quartz 2.6.2 and AutoFac.Extras.Quartz 3.5.0
I am using the code below to setup AutoFac
public static void RegisterWithAutofac(ContainerBuilder builder)
{
builder.RegisterType<TradeExtractor>()
.As<ITradeExtractor>()
.SingleInstance();
builder.Register(x => new StdSchedulerFactory().GetScheduler()).As<IScheduler>();
}
I know these are old packages, but my limitation of having to use .NET 4.5 means this is out of my control
Paul

Related

Register Autofac Service Provider Factory from services instead of CreateHostBuilder(ASP.Net 5)

I am trying to use autofac to register my open bound generic Mediator handlers, which works in my main project but does not in my tests(using Mytest.Aspnet.Mvc library)
EDIT
The only difference being that my project calls UseServiceProviderFactory, while the test project cannot(no API for it sadly).
Upon combing through the github repo, I found these lines here
var provider = serviceCollection.BuildServiceProvider();
var factory = provider.GetService<IServiceProviderFactory<IServiceCollection>>();
Which leads me to believe that, if I can change this to AutofacServiceProviderFactory would solve my problem.
What I've tried
services.AddTransient(x => (IServiceProviderFactory<IServiceCollection>)new AutofacServiceProviderFactory());
But it throws Invalid Cast Exception System.InvalidCastException : Unable to cast object of type 'Autofac.Extensions.DependencyInjection.AutofacServiceProviderFactory' to type 'Microsoft.Extensions.DependencyInjection.IServiceProviderFactory1[Microsoft.Extensions.DependencyInjection.IServiceCollection]`
ORIGINAL QUESTION
I've used breakpoints to check even if my ConfigureTestContainer method is invoked, it is but still Mediator throws Handler not found for... please register your handler with container.
The fun fact is that everything works on my ASP project.
Autofac Version: 6.3.0
MyTested.AspNetCore.Mvc Version: 5.0.0
.net 5
My constraints are to class which inherits my main layout Model.
public class Main
{
public class Query<T> : IRequest<Result<T>> where T : MainLayout, new()
{
//some props
}
public class Handler<T> : IRequestHandler<Query<T>, Result<T>> where T : MainLayout, new()
{
public async Task<Result<T>> Handle(Query<T> request, CancellationToken cancellationToken)
{
}
}
}
and this is my Test Startup class to add autofac
public class TestStartup : Startup
{
static TestStartup()
=> MyApplication
.IsRunningOn(server => server
.WithServices(services =>
{
services.AddAutofac();
}));
public void ConfigureTestContainer(ContainerBuilder builder)
{
builderbuilder.RegisterGeneric(typeof(Sunspot.Application.Main.Main.Handler<>)).AsImplementedInterfaces();
}
}

How can an Azure EventProcessor access a SignalR HubContext?

I have a .net core 3.0 web application. In Startup.cs, I register an EventProcessor (from Microsoft.Azure.EventHubs.Processor) that listens to an Azure EventHub for events. I do it like this:
await eventProcessorHost.RegisterEventProcessorAsync<TwinChangesEventHandler>();
I'm interested in device twin changes in an IoT Hub that's connected to the EventHub.
So, in the EventProcessor, I want to access the SignalR IHubContext interface (from Microsoft.AspNetCore.SignalR - the new version of SignalR) to be able to notify connected browsers of a device twin property change. My problem is that the EventProcessor can't get a handle to IHubContext. How can I get it?
I see online that people are using dependency injection but because my EventProcessor is created by RegisterEventProcessorAsync() like I showed above, its default constructor is ALWAYS called and NOT the one with IHubContext as a parameter! Even if I use a factory to create the EventProcessor and call RegisterEventProcessorFactoryAsync() in Startup.cs, I can't get the IHubContext handle in the factory, because the call is not originating from a controller. It either originates from Startup.ConfigureServices() or a callback from whenever something happens in the EventHub, which is not a controller method. I'm really stuck, so any help would be much appreciated. Does anyone know the answer to this?
You can add your Factory and processor to services
.ConfigureServices((hostContext, services) =>
{
...
services.AddSingleton<IEventProcessorFactory, EventProcessorFactory>();
services.AddSingleton<IEventProcessor, TwinChangesEventHandler>();
...
});
public class EventProcessorFactory : IEventProcessorFactory
{
private readonly IEventProcessor _fluxEventProcessor;
public EventProcessorFactory(IEventProcessor fluxEventProcessor)
{
_fluxEventProcessor = fluxEventProcessor;
}
public IEventProcessor CreateEventProcessor(PartitionContext context)
{
return _fluxEventProcessor;
}
}
Then in your handler you can have access to the injected hub
public class TwinChangesEventHandler : IEventProcessor
{
private readonly IHubContext<MyHub> _myHubContext;
public TwinChangesEventHandler(IHubContext<MyHub> myHubContext)
{
_myHubContext= myHubContext;
}
...
async Task IEventProcessor.ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages)
{
foreach (var eventData in messages)
{
await _myHubContext.Clients.All.SendAsync("Update", eventData);
}
//Call checkpoint every 5 minutes, so that worker can resume processing from 5 minutes back if it restarts.
if (_checkpointStopWatch.Elapsed > TimeSpan.FromMinutes(5))
{
await context.CheckpointAsync();
_checkpointStopWatch.Restart();
}
}
}

How to initialize scoped dependencies for consumers using MassTransit filters?

I would like to initialize some dependencies resolved from the MassTransit serviceProvider in the same way Asp.Net Core does with the pipeline's middlewares.
In particular I would like to inspect the incoming message before the consumer is called and extract the tenant from it (I'm currently working on a multitenant web application with single database per tenant).
With this informations I need to initialize some scoped instances (Ef Core DbContext for example).
I know that I can inject them in the Consumer through constructor but this means that I must do that everytime I write a new one, so I suppose that a filter should be the right place (correct me if I'm wrong).
The problem raises when I need to access the current consumer scope to resolve the dependencies that I need. I was thinking that the behavior of the MassTransit' pipeline was similar to the Asp.Net one regarding middleware injection but I was probably wrong.
I haven't found any documentation on how to do that clearly without cluttering the code of the filter, so any suggestion is going to be really appreciated.
This is the filter that I need to modify:
public class TenantContextInitializerFilter<T> : IFilter<T> where T : class, ConsumeContext
{
public void Probe(ProbeContext context) { }
public async Task Send(T context, IPipe<T> next)
{
//Resolve scoped instance here and do something before Consumer is called
var connectionStringProvider = scope.GetService<IConnectionStringProvider>();
await next.Send(context);
}
}
public class RegistrationsDeliveredEventConsumer : IConsumer<IRegistrationsDelivered>
{
private readonly IConnectionStringProvider _connectionStringProvider;
public RegistrationsDeliveredEventConsumer(IConnectionStringProvider connectionStringProvider)
{
//This should be the same instance that has been resolved in the filter' Send() method
_connectionStringProvider = connectionStringProvider;
}
public async Task Consume(ConsumeContext<IRegistrationsDelivered> context)
{
}
}
This is a simplified example of my code but this should be enough
There's two facets to consider: 1) are filters registered as services/pulled from the service collection when using the ASP.NET Core integration and 2) what lifetime do the filters have if they are. I'm not familiar with the MassTransit ASP.NET Core integration, but it looks like you should be good based on a cursory review. You'll need to confirm that both of those requirements are met.
For dependency injection, in general, constructor injection is the way to go unless there's a very specific need to do something different, which does not seem to be the case here. In short, you need a constructor for your filter.
What exactly you need to inject is a function of the lifetime of the filter. If it has a transient lifetime, then you can inject your scoped dependencies directly. If it has a singleton lifetime, then you'll need to inject IServiceProvider instead, and do the following whenever you need to use one of those dependencies:
using (var scope = _serviceProvider.CreateScope())
{
var dep = scope.ServiceProvider.GetRequiredService<MyDependency>();
// do something with `dep`
}
Here's a draft... I'm sure there are missing pieces, so let me know if you have questions.
public class TenantContextInitializerFilter<T> : IFilter<T> where T : class, ConsumeContext
{
private readonly Func<string, IDbConnection> _dbContextAccessor;
public void Probe(ProbeContext context) { }
public TenantContextInitializerFilter(Func<string, IDbConnection> dbContextAccessor)
{
_dbContextAccessor = dbContextAccessor;
}
public async Task Send(T context, IPipe<T> next)
{
var tenantId = ""; // place holder
using (var dbContext = _dbContextAccessor(tenantId))
{
//... do db logic
}
await next.Send(context);
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IConnectionStringProvider>(
provider => null /* TODO figure out how to fetch scoped instance from a cache or some storage mechanism*/);
services.AddScoped(provider =>
{
IDbConnection Accessor(string tenantId)
{
if (provider.GetService<IConnectionStringProvider>()
.TryGetConnectionString(tenantId, out var connectionString, out var providerName))
return new SqlConnection(connectionString);
throw new Exception();
}
return (Func<string, IDbConnection>)Accessor;
});
}
}

Ninject with EF multithreading

Good morning everyone!
I just started working in the project where I see there is an memory leak.
The situtation is as below. There is a console application which basically runs all the time in the while(true) loop.
There are bunch on classes which does some logic in the loop.
Each class has Execute() method where inside create uses Task.Run() method where the call is not awaited by anyone.
The list of above classes are called Engines. All engines are stateless classes which are stored in in array in main Program.cs class.
The code basically looks like:
private static List<BaseEngine> Engines;
public static void Main(string[] args)
{
InitializeDI();
RunProgram();
}
private static void RunProgram()
{
while (true)
{
try
{
foreach (var engine in Engines)
{
engine.Execute();
}
}
catch (Exception ex)
{
//handle
}
finally
{
Thread.Sleep(TimeSpan.FromSeconds(3));
}
}
}
private static void InitializeDI()
{
_kernel = new StandardKernel();
ServiceLocator.SetLocatorProvider(() => new NinjectServiceLocator(_kernel));
NinjectConfig.Setup(_kernel);
}
The sample engine looks like:
public class SampleEngine : BaseEngine
{
public override void Execute(Task task)
{
var someService = ServiceLocator.Current.GetInstance<IDbContext>();
System.Threading.Tasks.Task.Run(() =>
{
// some action using dbcontext
});
}
}
In above example of SampleEngine it uses to get IDbContext from Ninject DI. However other engines could use another services regiestred in DI.
All the dependencies are registered as InCallScope()
Basically its like mostly all engine its about fire and forget the given method using Task.Run().
What I did is changed Execute method to return the Task and after this task ran to completion I used to Dispose() this task. This did not bring any value.
I did some investigations and I saw that the problem is inside Ninject.Activation.Cache. I can do the manual cache clean which helps but I know the problem is somewhere in the code but I cannot find it.
Since every dependency is registered as InCallScope() they should be disposed after each task begin to the end. I dont see anything holding reference to these objects because every engine is stateless .
I used ANTS to see the some information and this just keeps growing each minute:
And this points to the Ninject caching as below:
Looks like the DbContext is not disposed and still exist in Ninject cache. Is it a problem of alot of tasks in the system or I do anything wrong ?
Thanks in advance
Cheers!
The most simple approach seems to be embedding the using in your task. But it is a blind shot, as it seems your code is simplified. You don't use the task parameter in your method.
public class SampleEngine : BaseEngine
{
public override void Execute(Task task)
{
System.Threading.Tasks.Task.Run(() =>
{
using (var someService = ServiceLocator.Current.GetInstance<IDbContext>())
{
// some action using dbcontext
}
});
}
}
For a more advanced approach, here is an interesting link. It features a InTaskScope binding. It is based on AsyncLocal and custom tasks through extensions of TaskFactory

How can I pass a SignalR hub context to a Hangfire job on ASP .NET Core 2.1?

How can I pass a SignalR hub context to a Hangfire job on ASP .NET Core 2.1?
It seems that since passing arguments to Hangfire is done via serialization/deserialization, it seems that Hangfire has hard-time reconstructing the SignalR hub context.
I schedule the job (in my controller) using :
BackgroundJob.Schedule(() => _hubContext.Clients.All.SendAsync(
"MyMessage",
"MyMessageContent",
System.Threading.CancellationToken.None),
TimeSpan.FromMinutes(2));
Then after 2 minutes, when the job tries to execute, I have the error :
Newtonsoft.Json.JsonSerializationException: Could not create an
instance of type Microsoft.AspNetCore.SignalR.IClientProxy. Type is an
interface or abstract class and cannot be instantiated.
Any idea?
Update 1
I ended up using a static context defined in Startup.cs, and assigned from Configure()
hbctx = app.ApplicationServices.GetRequiredService<IHubContext<MySignalRHub>>();
So now Hangfire schedules instead a hub helper that uses the static context :
BackgroundJob.Schedule(() => new MyHubHelper().Send(), TimeSpan.FromMinutes(2));
and the hub helper gets the context with Startup.hbctx
Even though this is working, it is a little smelly
Update 2
I tried also using the approach in Access SignalR Hub without Constructor Injection:
My background job scheduling became :
BackgroundJob.Schedule(() => Startup.GetService().SendOutAlert(2), TimeSpan.FromMinutes(2));
However this time, I have an exception when I reach the above line:
An unhandled exception has occurred while executing the request
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'IServiceProvider'.
Update 3
Thanks all. The solution was to create a helper that gets the hubcontext via its constructor via DI, and then using hangfire to schedule the helper method Send as the background job.
public interface IMyHubHelper
{
void SendOutAlert(String userId);
}
public class MyHubHelper : IMyHubHelper
{
private readonly IHubContext<MySignalRHub> _hubContext;
public MyHubHelper(IHubContext<MySignalRHub> hubContext)
{
_hubContext = hubContext;
}
public void SendOutAlert(String userId)
{
_hubContext.Clients.All.SendAsync("ReceiveMessage", userId, "msg");
}
}
Then launching the background job from anywhere with :
BackgroundJob.Schedule<MyHubHelper>( x => x.SendOutAlert(userId), TimeSpan.FromMinutes(2));
The answer from Nkosi suggesting to use Schedule<T> generics pointed me to the final solution I used:
First, my MySignalRHub is just an empty class inheriting from Hub.
public class MySignalRHub
{
}
Then, I created a hub helper which maintains a hubcontext on my MySignalRHub. The hubcontext is injected in the helper class via the ASP.Net Core built-in DI mechanism (as explained here).
The helper class:
public class MyHubHelper : IMyHubHelper
{
private readonly IHubContext<MySignalRHub> _hubContext;
public MyHubHelper(IHubContext<MySignalRHub> hubContext)
{
_hubContext = hubContext;
}
public void SendData(String data)
{
_hubContext.Clients.All.SendAsync("ReceiveMessage", data);
}
}
The helper interface:
public interface IMyHubHelper
{
void SendData(String data);
}
Finally, I can use hangfire to schedule from anywhere in the code the method SendData() of the hub helper as a background job with:
BackgroundJob.Schedule<MyHubHelper>(h => h.SendData(myData), TimeSpan.FromMinutes(2));
Using Schedule<T> generics you should be able to take advantage of the dependency injection capabilities of the framework.
BackgroundJob.Schedule<IHubContext<MySignalRHub>>(hubContext =>
hubContext.Clients.All.SendAsync(
"MyMessage",
"MyMessageContent",
System.Threading.CancellationToken.None),
TimeSpan.FromMinutes(2));

Categories