Background job system for .NET applications
Branch: dev
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
nuspecs Updated year in nuspec files Jan 10, 2019
samples
src
.gitignore
LICENSE
Minion.sln
Minion.v3.ncrunchsolution
README.md
_config.yml

README.md

Minion

Minion is a modern, testable background job scheduler for .NET applications. Minion will handle running your background job in a reliable way with SQL Server backed storage.

Example of scenarios when you can use Minion:

  • Fire web hooks
  • Database cleanup
  • Creating recurring automated reports
  • Batch sending emails
  • ... and more

Built with dotnet

Installation

Minion is available as a NuGet package. You can install it using the NuGet Package Console windows in Visual Studio:

PM> Install-Package Froda.Minion

Usage

To run the server, add the following lines of code:

MinionConfiguration.Configuration.UseSqlStorage("<connection string>");

using (var engine = new BatchEngine())
{
    Console.WriteLine("Starting ...");

    engine.Start();

    Console.ReadKey();
}

All jobs need to inherit from Job or Job<TInput>:

public class SimpleJob : Job
{
    public override async Task<JobResult> ExecuteAsync()
    {
        Console.WriteLine("Hello from simple job");

        return Finished();
    }
}

public class JobWithInput : Job<JobWithInput.Input>
{
    public class Input
    {
        public string Text { get; set; }
    }

    public override async Task<JobResult> ExecuteAsync(Input input)
    {
        Console.WriteLine("Hello from job with input: " + input.Text);

        return Finished();
    }
}

To schedule jobs you first need an instance of JobScheduler:

var scheduler = new JobScheduler();

Fire-and-forget

The job will be executed as soon as possible.

await scheduler.QueueAsync<SimpleJob>();

var input = new JobWithInput.Input { Text = "This is awesome" };
await scheduler.QueueAsync<JobWithInput, JobWithInput.Input>(input);

Scheduled job

The job will execute at the given time.

var date = new Date(2019, 04, 20);

await scheduler.QueueAsync<SimpleJob>(date);

var input = new JobWithInput.Input { Text = "This is awesome" };
await scheduler.QueueAsync<JobWithInput, JobWithInput.Input>(input, date);

Recurring jobs

Recurring jobs are scheduled as normal jobs, but need to return a new DueTime:

public class RecurringJob : Job
{
    private readonly IDateService _dateService;

    public RecurringJob(IDateService dateService) {
        _dateService = dateService;
    }

    public override async Task<JobResult> ExecuteAsync()
    {
        Console.WriteLine("Hello from recurring job, I will execute every 2 seconds");

        return Reschedule(_dateService.GetNow().AddSeconds(2));
    }
}

Sequence

This will force the jobs to run in sequnce:

var sequence = new Sequence();

sequence.Add<FirstJob>(); //This will run first
sequence.Add<SecondJob>(); //When the FirstJob is finished this will run

await scheduler.QueueAsync(sequence);

Set

These jobs will run in parallel if possible:

var set = new Set();

set.Add<FirstJob>();
set.Add<SecondJob>();

await scheduler.QueueAsync(set);

More advanced jobs

You can run a set in a sequence, and a sequence in a set:

var sequence = new Sequence();

sequence.Add<FirstJob>(); //This will run first

var set = new Set();

//Second and third job will run in parallel if possible
set.Add<SecondJob>(); 
set.Add<ThirdJob>(); 

sequence.Add(set);

sequence.Add<FourthJob>(); //Only when both second and third job is finished, this will run

await scheduler.QueueAsync(sequence);

Testing

With the TestingBatchEngine you can simulate your jobs with the ability to time travel.

For an example, if you schedule a job to send an email two days from now, you can fast forward and the batch engine will act as if two days have passed.

[Fact]
public async Task Can_Send_Email() {

    //Setup
    ...

    var startDate = new DateTime(2018, 04, 20);
    var dateSimulationService = new SimpleDateSimulationService(startDate);
    var store = new InMemoryStorage(dateSimulationService);

    var eninge = new TestingBatchEngine(store, dateSimulationService);
    var scheduler = new JobScheduler(store, dateSimulationService)

    await scheduler.QueueAsync<SendEmailJob>(startDate.AddDays(2)); //Schedule job to two days from now

    await engine.AdvanceToDateAsync(startDate.AddDays(2)); //Fast forward to two days from now

    //Assert
    ...
}

Dependency Injection

To hook up your IoC container to, you need to create a class that inherits from IDependencyResolver:

public class CustomDependencyResolver : IDependencyResolver 
{
    private readonly IContainer _container;

    public CustomDependencyResolver(IContainer container) 
    {
        _container = container;
    }

    public bool Resolve(Type type, out object resolvedType)
    {
        resolvedType = _container.Resolve(type);

        if(resolvedType == null)
        {
            return false;    
        }

        return true;
    }
}

Then you need to pass your resolver to the batch engine:

...
var container = new Container();
var resolver = new CustomDependencyResolver(container);

using(var engine = new BatchEngine(store, resolver)) {

    engine.Start();

    Console.ReadKey();

}

License

Minion goes under The MIT License (MIT).