I am using Testcontainers to set up integration tests but my container dies as soon as it starts.
Here is the code:
public class IntegTest : IAsyncLifetime
{
private readonly TestcontainerDatabase _dbContainer =
new TestcontainersBuilder<PostgreSqlTestcontainer>()
.WithDatabase(new PostgreSqlTestcontainerConfiguration
{
Database = "db",
Username = "user",
Password = "pass1"
}).Build();
[Fact]
public async Task myTest()
{
// Test Code here
}
public async Task InitializeAsync()
{
await _dbContainer.StartAsync();
}
public new async Task DisposeAsync()
{
await _dbContainer.DisposeAsync();
}
}
There is no error in the output console except the following:
Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll
The container comes up for a second and then shuts down. The code never enters myTest() but DisposeAsync() gets called as soon as the container shuts down.
NuGet Testcontainers version: 1.6.0,
.NET version: 6.0
Related
how are you?.
I have a web api in net core 3.1, this in turn contains a service that every X minutes has to run to perform data migrations (I'm just testing it), but I have 2 problems.
For the service to run, I must first run some url of my apis. The question is: How can I make this service start automatically, without the need to run any api?
When I stop using the apis for a few minutes, the service stops working. The question is: How can I keep the service "Forever" alive?
I must emphasize that my web api is hosted in a web hosting, where I do not have access to all the features of IIS
This is my code, and in advance I appreciate your help.
MySuperService.cs
public class MySuperService : IHostedService, IDisposable
{
private bool _stopping;
private Task _backgroundTask;
private static readonly log4net.ILog log =log4net.LogManager.GetLogger(typeof(MySuperService));
public Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine("MySuperService is starting.");
log.Info("MySuperService is starting.");
_backgroundTask = BackgroundTask();
return Task.CompletedTask;
}
private async Task BackgroundTask()
{
int contador = 1;
while (!_stopping)
{
Console.WriteLine("MySuperService is working--> " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
log.Info("MySuperService is working--> " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
await Task.Delay(TimeSpan.FromMinutes(3));
contador++;
}
Console.WriteLine("MySuperService background task is stopping.");
log.Info("MySuperService background task is stopping.");
}
public async Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine("MySuperService is stopping.");
log.Info("MySuperService is stopping.");
_stopping = true;
if (_backgroundTask != null)
{
// TODO: cancellation
await BackgroundTask();
//await _backgroundTask;
}
}
public void Dispose()
{
Console.WriteLine("MySuperService is disposing.");
log.Info("MySuperService is disposing.");
}
}
Program.cs
public class Program
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(Program));
public static void Main(string[] args)
{
...
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
}).ConfigureServices((hostContext, services) =>
{
services.AddHostedService<MySuperService>();
});
}
Inherit from BackgroundService instead of implemented IHostedService. That will take care of the machinery of starting, running and stopping your service for you.
However the problem you are facing is that IIS isn't starting your c# process until the first request of the service. Then the default application pool settings will shut it down again if there are no requests. I'd suggest setting up some kind of scheduled task to periodically request a url and monitor that the service is running. You'll want to be notified if it stops anyway right?
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.0&tabs=visual-studio
If you inherit your infinite job service from the BackgroundService class and implement your logic inside a loop and the needed
await Task.Delay(TimeSpan.FromMinutes(x miutes))
the job will run as soon as the application starts without any API call, and stops when the app stops.
public class MyService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine("test");
//await run job
await Task.Delay(TimeSpan.FromSeconds(1));
}
}
public override async Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine("start");
await ExecuteAsync(cancellationToken);
}
public override Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine("stop");
return Task.CompletedTask;
}
}
The approach I came up with goes as follows:
public class PingPongHostedService : IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine(">>>>> Hosted service starting at {0}", DateTimeOffset.Now.ToUnixTimeMilliseconds());
int count = 0;
try
{
Task.Run(async () => { // run in background and return completed task
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(1_766, cancellationToken);
Console.WriteLine("loop no. {0}", ++count);
}
}, cancellationToken);
}
catch (OperationCanceledException e){} // Prevent throwing if the Delay is cancelled
return Task.CompletedTask; // return ASAP
}
public Task StopAsync(CancellationToken cancellationToken)
{
Console.WriteLine(">>>>> Hosted service stopped at {0}", DateTimeOffset.Now.ToUnixTimeMilliseconds());
return Task.CompletedTask;
}
}
The important thing here is: StartAsync must exit ASAP, so that IGenericHost bootstrap can continue. That's why I'm using Task.run to transfer real work to another thread, allowing caller thread to continue.
I'm well aware that it is quite an old question and there have been already a few answers already but I would like to provide my input on the matter. I have used a brilliant library to schedule tasks called FluentScheduler. Before we start, add this in your Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<AppLifetimeEventsService>();
}
This is how I solved the above issue:
public class AppLifetimeEventsService : IHostedService
{
private readonly ILogger _logger;
public AppLifetimeEventsService(IServiceProvider services)
{
_logger = services.GetRequiredService<ILogger<AppLifetimeEventsService>>();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("The Web API has been started...");
//FluentScheduler
var registry = new Registry();
//For example let's run our method every 1 hour or 10 seconds
registry.Schedule(async () => await SomeBackgroundTask()).ToRunNow().AndEvery(1).Hours();
//registry.Schedule(async () => await SomeBackgroundTask()).ToRunNow().AndEvery(10).Seconds();
//FluentScheduler
JobManager.Initialize(registry);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
//Needed to remove all jobs from our Job manager when our Web API is shutting down
JobManager.RemoveAllJobs();
_logger.LogInformation("The Web API is stopping now...");
return Task.CompletedTask;
}
private async Task SomeBackgroundTask()
{
//Your long task goes here... In my case, I used a method with await here.
}
}
If you want to run a method every X seconds/minutes/hours/days/etc. you will have to implement IHostedService: info and docs. However, if you want to execute the method only once, you should implement BackgroundService.
I'm unit testing a part of my WPF project that uses async code and Application.Current.MainWindow to set the title of the main window.
public class ClassUnderTest
{
// Get and apply title async
public async Task GetAndApplyWindowTitleFromDbAsync()
{
string windowTitle = await GetWindowTitleFromDbAsync();
Application.Current.MainWindow.Title = windowTitle;
}
public async Task<string> GetWindowTitleFromDbAsync()
{
await Task.Delay(2000);
return "Async Window Title";
}
// Get and apply title sync
public void GetAndApplyWindowTitleFromDb()
{
Application.Current.MainWindow.Title = "Sync Window Title";
}
}
The unit test with the synchronous method succeeds while the async method throws the following exception when accessing Application.Current.MainWindow after the await:
System.InvalidOperationException: The calling thread cannot access
this object because a different thread owns it.
[TestClass]
public class TestClass
{
[TestMethod]
public void SyncMethodWithUICodeTest()
{
InitializeApplication();
Assert.AreEqual("Window Title", Application.Current.MainWindow.Title);
var classUnderTest = new ClassUnderTest();
classUnderTest.GetAndApplyWindowTitleFromDb();
// Test succeeds
Assert.AreEqual("Sync Window Title", Application.Current.MainWindow.Title);
}
[TestMethod]
public async Task AsyncMethodWithUICodeTest()
{
InitializeApplication();
Assert.AreEqual("Window Title", Application.Current.MainWindow.Title);
var classUnderTest = new ClassUnderTest();
await classUnderTest.GetAndApplyWindowTitleFromDbAsync();
// throws InvalidOperationException
Assert.AreEqual("Async Window Title", Application.Current.MainWindow.Title);
}
private void InitializeApplication()
{
if (Application.Current == null)
{
var app = new Application();
var window = new Window();
app.MainWindow = window;
}
Application.Current.MainWindow.Title = "Window Title";
}
}
I was under the impression that the statement after await returns to the "original" context (where Application.Current.MainWindow was known).
Why does the async unit test throw an exception?
Is the reason unit test specific or can the same exception be thrown in my WPF application too?
I was under the impression that the statement after await returns to the "original" context (where Application.Current.MainWindow was known).
It resumes on the captured SynchronizationContext provided that there is any context to capture by the time you call await.
In the context of an MSTest unit test, System.Threading.SynchronizationContext.Current returns null so there is no context to resume on once your Task has completed.
You could try to set the SynchronizationContext by calling SynchronizationContext.SetSynchronizationContext. A WPF application uses a System.Windows.Threading.DispatcherSynchronizationContext by default.
Environment
Windows 10 Professional
.NET Core Console Application
Code
I have an abstracted message receiver that looks like this. In this code the entity is the name of the Subscription (e.g. user).
public class AzureMessageReceiver : ITdlMessageReceiver
{
private readonly ServiceBusConnection serviceBusConnection;
private readonly ILogger<AzureMessageReceiver> logger;
public AzureMessageReceiver(ServiceBusConnection serviceBusConnection, ILogger<AzureMessageReceiver> logger)
{
this.serviceBusConnection = serviceBusConnection;
this.logger = logger;
}
public async Task<TdlMessage<T>> ReceiveAsync<T>(string topic, string entity) where T : class
{
try
{
var subscriptionPath = EntityNameHelper.FormatSubscriptionPath(topic, entity);
var messageReceiver = new MessageReceiver(serviceBusConnection, subscriptionPath, ReceiveMode.ReceiveAndDelete);
var message = await messageReceiver.ReceiveAsync();
if (message == null)
{
return null;
}
var messageString = Encoding.UTF8.GetString(message.Body);
return JsonConvert.DeserializeObject<TdlMessage<T>>(messageString);
}
catch (Exception ex)
{
logger.LogError(ex, "Error receiving Azure message.");
return null;
}
}
}
The injected ServiceBusConnection is constructed like this. NOTE: this same connection initialization works to write messages to the same Topic and Subscription.
services.AddSingleton(serviceProvider =>
new ServiceBusConnection(configuration[$"{DurableCommunicationKey}:AzureConnectionString"]));
UPDATE: here is the code that wraps the call to the receiver class and is the controller for receiving messages:
static async void Receive(ITdlMessageReceiver receiver, ILogger logger)
{
while (true)
{
var message = await receiver.ReceiveAsync<TdlMessage<object>>(topic, entity);
if (message != null)
{
logger.LogDebug($"Message received. Topic: {topic}. Action: {Enum.GetName(typeof(TopicActions), message.Action)}. Message: {JsonConvert.SerializeObject(message)}.");
}
Thread.Sleep(sleepTime);
}
}
Problem
Every time I execute this line var message = await messageReceiver.ReceiveAsync(); it just crashes the Console app. No Exception and nothing in Event Viewer.
What I've Tried
Using the Secondary Connection String from the ASB
Providing a timeout like messageReceiver.ReceiveAsync(TimeSpan.FromMinutes(1));
Changing the injected topic from just the name of the topic to the entire URL of the topic (e.g. https://{...}.servicebus.windows.net/{topicName})
Changing the ReceiveMode to PeekLock
Tacking on ConfigureAwait(false) to the ReceiveAsync call.
Changing the timeout to TimeSpan.Zero. NOTE: this does not crash the app but actually throws an Exception that gets logged.
async void should be converted to an async Task as well as you should be awaiting Task.Delay instead of invoking Thread.Sleep. If going async you need to go async all the way
static async Task Receive(ITdlMessageReceiver receiver, ILogger logger) {
while (true) {
var message = await receiver.ReceiveAsync<TdlMessage<object>>(topic, entity);
if (message != null) {
logger.LogDebug($"Message received. Topic: {topic}. Action: {Enum.GetName(typeof(TopicActions), message.Action)}. Message: {JsonConvert.SerializeObject(message)}.");
}
await Task.Delay(sleepTime);
}
}
Try making the code async all the way through, yes, but as a console application (single thread) you will be allowed to call Wait() on the Receive method in Main as it is not mixing calls that would cause problem with the async flow.
public static void Main(string[] args) {
//...
//...
//...
Receive(receiver, logger).Wait();
}
Reference Async/Await - Best Practices in Asynchronous Programming
I'm currently attempting to write unit tests for my bot, however, the test always fail when attempting to get a response. I've created a mock test which inherits DialogTestBase.
[TestClass]
public class Tests : DialogTestBase
{
[TestMethod]
public async Task TestDialogTest()
{
await TestDialogFlow(new TestDialog());
}
private async Task TestDialogFlow(IDialog<object> echoDialog)
{
// arrange
var toBot = DialogTestBase.MakeTestMessage();
toBot.From.Id = Guid.NewGuid().ToString();
toBot.Text = "Hi";
Func<IDialog<object>> MakeRoot = () => echoDialog;
using (new FiberTestBase.ResolveMoqAssembly(echoDialog))
using (var container = Build(Options.MockConnectorFactory | Options.ScopedQueue, echoDialog))
{
IMessageActivity toUser = await GetResponse(container, MakeRoot, toBot);
Assert.IsTrue(toUser.Text.StartsWith("Hello"));
}
}
private async Task<IMessageActivity> GetResponse(IContainer container, Func<IDialog<object>> makeRoot, IMessageActivity toBot)
{
using (var scope = DialogModule.BeginLifetimeScope(container, toBot))
{
DialogModule_MakeRoot.Register(scope, makeRoot);
// act: sending the message
await Conversation.SendAsync(toBot, makeRoot);
return scope.Resolve<Queue<IMessageActivity>>().Dequeue();
}
}
}
The dialog i'm testing is:
[Serializable]
public class TestDialog : IDialog
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(ProcessMessage);
}
public async Task ProcessMessage(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
await context.PostAsync("Hello. I'm a bot");
await context.PostAsync("How can I help?");
context.Wait(ProcessRequest);
}
public async Task ProcessRequest(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var request = await argument;
await context.PostAsync($"You asked the following question: {request}");
context.Done(true);
}
}
When I run the test I get the following error:
Autofac.Core.DependencyResolutionException: An exception was thrown while executing a resolve operation. See the InnerException for details. ---> Invalid URI: The format of the URI could not be determined. (See inner exception for details.) --->System.UriFormatException: Invalid URI: The format of the URI could not be determined.
The problem happens in the GetResponse method when we send the request. Any help would be greatly appreciated.
The GetResponse method should actually call SendAsync using scope and toBot as parameters:
await Conversation.SendAsync(toBot, makeRoot);
I tried the code you've shared and after making this change, the test passes.
We are working with .NET Core Web Api, and looking for a lightweight solution to log requests with variable intensity into database, but don't want client's to wait for the saving process.
Unfortunately there's no HostingEnvironment.QueueBackgroundWorkItem(..) implemented in dnx, and Task.Run(..) is not safe.
Is there any elegant solution?
As #axelheer mentioned IHostedService is the way to go in .NET Core 2.0 and above.
I needed a lightweight like for like ASP.NET Core replacement for HostingEnvironment.QueueBackgroundWorkItem, so I wrote DalSoft.Hosting.BackgroundQueue which uses.NET Core's 2.0 IHostedService.
PM> Install-Package DalSoft.Hosting.BackgroundQueue
In your ASP.NET Core Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddBackgroundQueue(onException:exception =>
{
});
}
To queue a background Task just add BackgroundQueue to your controller's constructor and call Enqueue.
public EmailController(BackgroundQueue backgroundQueue)
{
_backgroundQueue = backgroundQueue;
}
[HttpPost, Route("/")]
public IActionResult SendEmail([FromBody]emailRequest)
{
_backgroundQueue.Enqueue(async cancellationToken =>
{
await _smtp.SendMailAsync(emailRequest.From, emailRequest.To, request.Body);
});
return Ok();
}
QueueBackgroundWorkItem is gone, but we've got IApplicationLifetime instead of IRegisteredObject, which is being used by the former one. And it looks quite promising for such scenarios, I think.
The idea (and I'm still not quite sure, if it's a pretty bad one; thus, beware!) is to register a singleton, which spawns and observes new tasks. Within that singleton we can furthermore register a "stopped event" in order to proper await still running tasks.
This "concept" could be used for short running stuff like logging, mail sending, and the like. Things, that should not take much time, but would produce unnecessary delays for the current request.
public class BackgroundPool
{
protected ILogger<BackgroundPool> Logger { get; }
public BackgroundPool(ILogger<BackgroundPool> logger, IApplicationLifetime lifetime)
{
if (logger == null)
throw new ArgumentNullException(nameof(logger));
if (lifetime == null)
throw new ArgumentNullException(nameof(lifetime));
lifetime.ApplicationStopped.Register(() =>
{
lock (currentTasksLock)
{
Task.WaitAll(currentTasks.ToArray());
}
logger.LogInformation(BackgroundEvents.Close, "Background pool closed.");
});
Logger = logger;
}
private readonly object currentTasksLock = new object();
private readonly List<Task> currentTasks = new List<Task>();
public void SendStuff(Stuff whatever)
{
var task = Task.Run(async () =>
{
Logger.LogInformation(BackgroundEvents.Send, "Sending stuff...");
try
{
// do THE stuff
Logger.LogInformation(BackgroundEvents.SendDone, "Send stuff returns.");
}
catch (Exception ex)
{
Logger.LogError(BackgroundEvents.SendFail, ex, "Send stuff failed.");
}
});
lock (currentTasksLock)
{
currentTasks.Add(task);
currentTasks.RemoveAll(t => t.IsCompleted);
}
}
}
Such a BackgroundPool should be registered as a singleton and can be used by any other component via DI. I'm currently using it for sending mails and it works fine (tested mail sending during app shutdown too).
Note: accessing stuff like the current HttpContext within the background task should not work. The old solution uses UnsafeQueueUserWorkItem to prohibit that anyway.
What do you think?
Update:
With ASP.NET Core 2.0 there's new stuff for background tasks, which get's better with ASP.NET Core 2.1: Implementing background tasks in .NET Core 2.x webapps or microservices with IHostedService and the BackgroundService class
You can use Hangfire (http://hangfire.io/) for background jobs in .NET Core.
For example :
var jobId = BackgroundJob.Enqueue(
() => Console.WriteLine("Fire-and-forget!"));
Here is a tweaked version of Axel's answer that lets you pass in delegates and does more aggressive cleanup of completed tasks.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
namespace Example
{
public class BackgroundPool
{
private readonly ILogger<BackgroundPool> _logger;
private readonly IApplicationLifetime _lifetime;
private readonly object _currentTasksLock = new object();
private readonly List<Task> _currentTasks = new List<Task>();
public BackgroundPool(ILogger<BackgroundPool> logger, IApplicationLifetime lifetime)
{
if (logger == null)
throw new ArgumentNullException(nameof(logger));
if (lifetime == null)
throw new ArgumentNullException(nameof(lifetime));
_logger = logger;
_lifetime = lifetime;
_lifetime.ApplicationStopped.Register(() =>
{
lock (_currentTasksLock)
{
Task.WaitAll(_currentTasks.ToArray());
}
_logger.LogInformation("Background pool closed.");
});
}
public void QueueBackgroundWork(Action action)
{
#pragma warning disable 1998
async Task Wrapper() => action();
#pragma warning restore 1998
QueueBackgroundWork(Wrapper);
}
public void QueueBackgroundWork(Func<Task> func)
{
var task = Task.Run(async () =>
{
_logger.LogTrace("Queuing background work.");
try
{
await func();
_logger.LogTrace("Background work returns.");
}
catch (Exception ex)
{
_logger.LogError(ex.HResult, ex, "Background work failed.");
}
}, _lifetime.ApplicationStopped);
lock (_currentTasksLock)
{
_currentTasks.Add(task);
}
task.ContinueWith(CleanupOnComplete, _lifetime.ApplicationStopping);
}
private void CleanupOnComplete(Task oldTask)
{
lock (_currentTasksLock)
{
_currentTasks.Remove(oldTask);
}
}
}
}
I know this is a little late, but we just ran into this issue too. So after reading lots of ideas, here's the solution we came up with.
/// <summary>
/// Defines a simple interface for scheduling background tasks. Useful for UnitTesting ASP.net code
/// </summary>
public interface ITaskScheduler
{
/// <summary>
/// Schedules a task which can run in the background, independent of any request.
/// </summary>
/// <param name="workItem">A unit of execution.</param>
[SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
void QueueBackgroundWorkItem(Action<CancellationToken> workItem);
/// <summary>
/// Schedules a task which can run in the background, independent of any request.
/// </summary>
/// <param name="workItem">A unit of execution.</param>
[SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
}
public class BackgroundTaskScheduler : BackgroundService, ITaskScheduler
{
public BackgroundTaskScheduler(ILogger<BackgroundTaskScheduler> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogTrace("BackgroundTaskScheduler Service started.");
_stoppingToken = stoppingToken;
_isRunning = true;
try
{
await Task.Delay(-1, stoppingToken);
}
catch (TaskCanceledException)
{
}
finally
{
_isRunning = false;
_logger.LogTrace("BackgroundTaskScheduler Service stopped.");
}
}
public void QueueBackgroundWorkItem(Action<CancellationToken> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
if (!_isRunning)
throw new Exception("BackgroundTaskScheduler is not running.");
_ = Task.Run(() => workItem(_stoppingToken), _stoppingToken);
}
public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
if (!_isRunning)
throw new Exception("BackgroundTaskScheduler is not running.");
_ = Task.Run(async () =>
{
try
{
await workItem(_stoppingToken);
}
catch (Exception e)
{
_logger.LogError(e, "When executing background task.");
throw;
}
}, _stoppingToken);
}
private readonly ILogger _logger;
private volatile bool _isRunning;
private CancellationToken _stoppingToken;
}
The ITaskScheduler (which we already defined in our old ASP.NET client code for UTest test purposes) allows a client to add a background task. The main purpose of the BackgroundTaskScheduler is to capture the stop cancellation token (which is own by the Host) and to pass it into all the background Tasks; which by definition, runs in the System.Threading.ThreadPool so there is no need to create our own.
To configure Hosted Services properly see this post.
Enjoy!
I have used Quartz.NET (does not require SQL Server) with the following extension method to easily set up and run a job:
public static class QuartzUtils
{
public static async Task<JobKey> CreateSingleJob<JOB>(this IScheduler scheduler,
string jobName, object data) where JOB : IJob
{
var jm = new JobDataMap { { "data", data } };
var jobKey = new JobKey(jobName);
await scheduler.ScheduleJob(
JobBuilder.Create<JOB>()
.WithIdentity(jobKey)
.Build(),
TriggerBuilder.Create()
.WithIdentity(jobName)
.UsingJobData(jm)
.StartNow()
.Build());
return jobKey;
}
}
Data is passed as an object that must be serializable. Create an IJob that processes the job like this:
public class MyJobAsync :IJob
{
public async Task Execute(IJobExecutionContext context)
{
var data = (MyDataType)context.MergedJobDataMap["data"];
....
Execute like this:
await SchedulerInstance.CreateSingleJob<MyJobAsync>("JobTitle 123", myData);
The original HostingEnvironment.QueueBackgroundWorkItem was a one-liner and very convenient to use.
The "new" way of doing this in ASP Core 2.x requires reading pages of cryptic documentation and writing considerable amount of code.
To avoid this you can use the following alternative method
public static ConcurrentBag<Boolean> bs = new ConcurrentBag<Boolean>();
[HttpPost("/save")]
public async Task<IActionResult> SaveAsync(dynamic postData)
{
var id = (String)postData.id;
Task.Run(() =>
{
bs.Add(Create(id));
});
return new OkResult();
}
private Boolean Create(String id)
{
/// do work
return true;
}
The static ConcurrentBag<Boolean> bs will hold a reference to the object, this will prevent garbage collector from collecting the task after the controller returns.