AppTasks registration using IBackgroundJobs

I have a specific use case (one time call from powershell script when new api version is installed) where I need to call a registered task that get some data and then create scheduled tasks (which are deleted later).

Wherever I try to register it, execution fails because it cannot resolve either the IBackgroundJobs or other command or IDbConnectionFactory dependencies.

I have tried something similar from what I see in ConfigureDbMigrations inside the ConfigureBackgroundJobs or AppHost without luck.

By the way, I think this doc snippet might not be correct, task aren’t registered when done in the afterAppHostInit

public class ConfigureDbMigrations : IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureAppHost(afterAppHostInit:appHost => {
            var migrator = new Migrator(appHost.Resolve<IDbConnectionFactory>(), typeof(Migration1000).Assembly);
            AppTasks.Register("migrate", _ => migrator.Run());
            AppTasks.Register("migrate.revert", args => migrator.Revert(args[0]));
            AppTasks.Run();
        });
}

Here is a small snippet of my test task inside the x.ConfigureAppHost

AppTasks.Register("test", (args) => {
    Console.WriteLine("This is a test");
    try
    {
        var jobs = appHost.Resolve<IBackgroundJobs>();
        jobs.ThrowIfNull("IBackgroundJobs is not initialized.  Cannot continue.");
        var r = jobs.GetJob(9999); //should return null and not throw
        if (r == null)
            Console.WriteLine("GetJob(9999) returned null as expected");
        // Create the recurring jobs
        var request = new OrderCloseoutHrmRequest {
            Id = 9999,
            IsManualApproval = false,
            UserName = "System"
        };
        jobs.RecurringCommand<OrderCloseoutHrmCommand>(OrderCloseoutHrmCommandNames.CreateNameForId(request.Id),Schedule.Interval(TimeSpan.FromSeconds(60)),request, new BackgroundJobOptions {
            Tag = $"9999-TEST",
        });
        Console.WriteLine("Created or updated recurring Order Closeout HRM job for process order id: 99999");
    }
    catch (Exception e)
    {
        Console.WriteLine(e);
    }
});

Thanks for your help.

The AppTasks should only run after the IOC is built so it should have access to all your registered dependencies. I’ve just tried it locally and was able to verify that IBackgroundJobs was able to be resolved.

Have you tried debugging to see if the Plugins are registered and IOC is configured before the AppTask is run? Also how are you registering the plugins?

Will do more debugging soon and update the case. Did you try to create a Recurring Command once you have your job resolved? In my test, I can also resolve it but calls to GetJob or RecurringCommand will fail.

This is what I have: not too sure how to see if the Plugins are registered and all available…

public class ConfigureBackgroundJobs : IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureServices((context,services) => {
            services.AddPlugin(new CommandsFeature());
            services.AddPlugin(new BackgroundsJobFeature());
            services.AddHostedService<JobsHostedService>();
        }).ConfigureAppHost(
            (appHost) =>
            {
                var runner = new TaskRunner(appHost.Resolve<IBackgroundJobs>());

                // this is NOT NULL
                var jobFeature = appHost.AssertPlugin<BackgroundsJobFeature>();
                jobFeature.ThrowIfNull("BackgroundsJobFeature is not registered in the AppHost.  Cannot continue.");

                // this is NOT NULL                
                var commandFeature =  appHost.AssertPlugin<CommandsFeature>();
                commandFeature.ThrowIfNull("CommandsFeature is not registered in the AppHost.  Cannot continue.");

                // this is empty
                Console.WriteLine("Registered plugins are: {0}", appHost.PluginsLoaded?.Dump());
               
                // jobFeature.CommandsFeature is NULL here. Is this normal?

                jobFeature.CommandsFeature.ThrowIfNull("CommandsFeature is not registered in the BackgroundJobFeature.  Cannot continue.");
                   
                AppTasks.Register("test", (args) =>
                {
                    runner.SyncOCLTask(args);
                });
            });
}

public class JobsHostedService(ILogger<JobsHostedService> log, IBackgroundJobs jobs) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await jobs.StartAsync(stoppingToken);
        
        using var timer = new PeriodicTimer(TimeSpan.FromSeconds(3));
        while (!stoppingToken.IsCancellationRequested && await timer.WaitForNextTickAsync(stoppingToken))
        {
            await jobs.TickAsync();
        }
    }
}

If I comment the ThrowIfNull, eventually, when the task runs and call the jobs.RecurringCommand , we end up in the BackgroundJobs.cs and we get the exception:

System.NullReferenceException: Object reference not set to an instance of an object.
at ServiceStack.Jobs.BackgroundJobs.AssertCommand(String command) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.cs:line 1258

private CommandInfo AssertCommand(string? command)
{
ArgumentNullException.ThrowIfNull(command);
return feature.CommandsFeature.AssertCommandInfo(command);
}

And if I set the jobFeature.CommandsFeature with the commandFeature, eventually, it will also fail but this time because if cannot resolve the sqllite connection

System.NullReferenceException: Object reference not set to an instance of an object.
   at ServiceStack.OrmLite.OrmLiteConnectionFactoryExtensions.OpenDbConnection(IDbConnectionFactory connectionFactory, String namedConnection) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack.OrmLite/src/ServiceStack.OrmLite/OrmLiteConnectionFactory.cs:line 270
   at ServiceStack.Jobs.BackgroundsJobFeature.DefaultResolveAppDb(IDbConnectionFactory dbFactory) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Jobs/BackgroundsJobFeature.cs:line 104
   at ServiceStack.Jobs.BackgroundsJobFeature.OpenDb() in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Jobs/BackgroundsJobFeature.cs:line 121
   at ServiceStack.Jobs.BackgroundJobs.CreateOrUpdate(ScheduledTask task) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.ScheduledTasks.cs:line 31
   at ServiceStack.Jobs.BackgroundJobs.RecurringCommand(String taskName, Schedule schedule, String commandName, Object arg, BackgroundJobOptions options) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack.Jobs/BackgroundJobs.ScheduledTasks.cs:line 66
   at ServiceStack.Jobs.JobUtils.RecurringCommand[TCommand](IBackgroundJobs jobs, String taskName, Schedule schedule, Object request, BackgroundJobOptions options) in /home/runner/work/ServiceStack/ServiceStack/ServiceStack/src/ServiceStack/Jobs/JobUtils.cs:line 76

Yeah I can get a job and register a RecurringCommand in an AppTask, why are you not using
.ConfigureAppHost(afterAppHostInit:appHost => ... as documented?

The AppHost and BackgroundJobs feature needs to be initialized before it can be used.

That is what I did originally but the task is not getting registered when using the afterAppHostInit. I get :

WARN: Unknown AppTask 'test' was not registered with this App, ignoring...

I am using version 8.8.0

You should run the tasks after they’re registered:

AppTasks.Register("test", ...);
AppTasks.Run();

Same thing. test works, test2 doesn’t.

.ConfigureAppHost((appHost) =>
        {
            AppTasks.Register("test", (args) => Console.WriteLine("Test task running..."));  
            AppTasks.Run();
        },
            afterAppHostInit: (appHost) =>
            {
                AppTasks.Register("test2", (args) => Console.WriteLine("Test2 task running..."));
                AppTasks.Run();

You can’t run them multiple times, AppTasks immediately exit after they’re run, remove the first configuration.

yeah, that was just to show you that it works only when not using afterAppHostInit. So, if I have this (with or without the AppTasks.Run() ), it works fine.

.ConfigureAppHost((appHost) =>
        {
            AppTasks.Register("test", (args) => Console.WriteLine("Test task running..."));  
            AppTasks.Run();
        }

but if I have this, it doesn’t.

.ConfigureAppHost(
            afterAppHostInit: (appHost) =>
            {
                AppTasks.Register("test2", (args) => Console.WriteLine("Test2 task running..."));
                AppTasks.Run();
             }

Do you have any AppTasks registered anywhere else? e.g. in Configure.Db.Migrations.cs, they should be registered/run in one place.

Ok, I had the migrate and migrate.revert AppTasks registered in the Configure.Db.Migrations.cs
and my test one in Configure.AppHost.cs

I have removed the AppTasks.Run() from the Configure.Db.Migrations.cs and put it in Configure.AppHost.cs

Works now.

Sorry if I am a bit slow… but don’t understand the call to AppTasks.Run(); part…It won’t do anything

It runs the App Tasks if the App was launched with one, it should only be called once after all AppTasks have been registered. Since you can’t rely on the order ASP .NET Executes HostingStartup classes I’d recommend having all your AppTasks registered in one place before calling AppTasks.Run().

Roger that. Thank you very much!

1 Like