Skip to content

Commit

Permalink
Simplify servicecollection extensions (#3)
Browse files Browse the repository at this point in the history
- Breaking change: removes DI builder pattern
- Breaking change: drop netcoreapp3.1 support
Other:
- Update to C# 10 features and use of .NET 6 SDK

+semver:major
  • Loading branch information
johnkors authored Sep 11, 2021
1 parent 13dcea4 commit 103830f
Show file tree
Hide file tree
Showing 14 changed files with 197 additions and 248 deletions.
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true

[*]
charset = utf-8

# C# files
[*.cs]

Expand Down
15 changes: 7 additions & 8 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,30 @@ name: CI

on:
push:
branches: [main]
branches:
- "*"
pull_request:
branches: [main]

jobs:
ci-build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@v1
with:
dotnet-version: "3.1.x"
- uses: actions/setup-dotnet@v1
with:
dotnet-version: "5.0.x"
- uses: actions/setup-dotnet@v1
with:
dotnet-version: "6.0.x"
dotnet-version: "6.0.100-rc.2.21458.9"
include-prerelease: true
- run: dotnet --info
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
- name: PackTest
run: dotnet pack
5 changes: 1 addition & 4 deletions .github/workflows/PreRelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,12 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-dotnet@v1
with:
dotnet-version: "3.1.x"
- uses: actions/setup-dotnet@v1
with:
dotnet-version: "5.0.x"
- uses: actions/setup-dotnet@v1
with:
dotnet-version: "6.0.x"
dotnet-version: "6.0.100-rc.2.21458.9"
include-prerelease: true
- name: Restore dependencies
run: dotnet restore
Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/Release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,12 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-dotnet@v1
with:
dotnet-version: "3.1.x"
- uses: actions/setup-dotnet@v1
with:
dotnet-version: "5.0.x"
- uses: actions/setup-dotnet@v1
with:
dotnet-version: "6.0.x"
dotnet-version: "6.0.100-rc.2.21458.9"
include-prerelease: true
- name: Restore dependencies
run: dotnet restore
Expand Down
32 changes: 26 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
[![main](https://github.com/slackbot-net/CronBackgroundServices/workflows/CI/badge.svg)](https://github.com/slackbot-net/CronBackgroundServices/actions) [![NuGet](https://img.shields.io/nuget/v/CronBackgroundServices.svg)](https://www.nuget.org/packages/CronBackgroundServices/)
[![NuGet](https://img.shields.io/nuget/vpre/CronBackgroundServices.svg)](https://www.nuget.org/packages/CronBackgroundServices/)



### CronBackgroundServices

.NET BackgroundService jobs triggered by configured Cron Expressions


### Installation

```bash
Expand All @@ -18,20 +15,43 @@ $ dotnet add package CronBackgroundServices
Jobs are configured during DI registration:

```csharp
services.AddRecurringActions()
services
.AddRecurrer<MyCustomRecurringJob>()
.AddRecurrer<MySecondJob>()
.Build();
```

Each job has to implement `IRecurringAction`. If you want a different TimeZone than UTC you have to override the default interface method `GetTimeZoneId`.

```csharp
public interface IRecurringAction
{


/// <summary>
/// The job to be executed at intervals defined by the Cron expression
/// </summary>
/// <returns></returns>
Task Process(CancellationToken stoppingToken);

/// <summary>
/// The cron expression (including seconds) as defined by the Cronos library:
/// See https://github.com/HangfireIO/Cronos#cron-format
/// Ex: Every second: */1 * * * * *
/// Ex: Every minute: 0 */1 * * * *
/// Ex: Every midnight: 0 0 */1 * * *
/// Ex: First of every month 0 0 0 1 * *
/// </summary>
/// <returns>A valid Cron Expression</returns>
string Cron { get; }

/// <summary>
/// Optional: The TimeZone in which the Cron expression should be based on.
/// Defaults to UTC (Europe/London or GMT Standard Time)
///
/// NB! When overriding this and targeting versions below .NET 6, use platform specific identifiers
/// If your runtime is .NET 6 or above, it's not required. It will handles the conversion:
/// See https://github.com/dotnet/runtime/pull/49412
/// </summary>
/// <returns>timezoneId</returns>
string GetTimeZoneId()
{
return !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Europe/London" : "GMT Standard Time";
Expand Down
2 changes: 2 additions & 0 deletions Samples/ConsoleApp/ConsoleApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
39 changes: 20 additions & 19 deletions Samples/ConsoleApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using CronBackgroundServices;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using CronBackgroundServices;

Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Trace))
.ConfigureServices((_, services) => services.AddRecurringActions().AddRecurrer<MyCustomRecurringJob>().Build())
.ConfigureServices(services => services
.AddRecurrer<EveryThreeSeconds>()
.AddRecurrer<EveryFiveSeconds>())
.Build()
.Run();

public class MyCustomRecurringJob : IRecurringAction
public class EveryFiveSeconds : IRecurringAction
{
private readonly ILogger<MyCustomRecurringJob> _logger;
public string Cron => "*/5 * * * * *";

public MyCustomRecurringJob(ILogger<MyCustomRecurringJob> logger)
{
_logger = logger;
}
public Task Process(CancellationToken stoppingToken)
public Task Process(CancellationToken stoppingToken) => Logger.Log("🕔 Tick 5th second 5️⃣ 🖐");
}

public class EveryThreeSeconds : IRecurringAction
{
public string Cron => "*/3 * * * * *";

public Task Process(CancellationToken stoppingToken) => Logger.Log("🕒 Tick 3rd second 3️⃣ 🥉");
}

static class Logger
{
public static Task Log(string msg)
{
_logger.LogInformation("Tick");
Console.WriteLine(msg);
return Task.CompletedTask;
}

public string Cron => "* * * * * *"; // Every 30 seconds, in the zero-th minute, every hour, https://github.com/HangfireIO/Cronos#usage
}

92 changes: 43 additions & 49 deletions src/CronBackgroundServices/CronBackgroundService.cs
Original file line number Diff line number Diff line change
@@ -1,69 +1,63 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace CronBackgroundServices
namespace CronBackgroundServices;

internal class CronBackgroundService : BackgroundService
{
internal class CronBackgroundService : BackgroundService
protected readonly IRecurringAction Action;
private readonly ILogger _logger;
private readonly Timing _timing;

public CronBackgroundService(IRecurringAction action, ILogger logger)
{
protected readonly IRecurringAction Action;
private readonly ILogger _logger;
private readonly Timing _timing;
_timing = new Timing(action.GetTimeZoneId());
Action = action;
_logger = logger;
Cron = action.Cron;
_logger.LogTrace($"Using {Cron} and timezone '{_timing.TimeZoneInfo.Id}. The time in this timezone: {_timing.RelativeNow()}'");
}

public CronBackgroundService(IRecurringAction action, ILogger logger)
{
_timing = new Timing(action.GetTimeZoneId());
Action = action;
_logger = logger;
Cron = action.Cron;
_logger.LogTrace($"Using {Cron} and timezone '{_timing.TimeZoneInfo.Id}. The time in this timezone: {_timing.RelativeNow()}'");
}
private string Cron { get; }

private string Cron { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
DateTimeOffset? next = null;

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
do
{
DateTimeOffset? next = null;
var now = _timing.RelativeNow();

do
if (next == null)
{
var now = _timing.RelativeNow();
next = _timing.GetNextOccurenceInRelativeTime(Cron);
var uText = _timing.Get10NextOccurrences(Cron);
var logText = $"Ten next occurrences :\n{uText.Aggregate((x, y) => x + "\n" + y)}";
_logger.LogTrace(logText);
}

if (next == null)
{
next = _timing.GetNextOccurenceInRelativeTime(Cron);
var uText = _timing.Get10NextOccurrences(Cron);
var logText = $"Ten next occurrences :\n{uText.Aggregate((x, y) => x + "\n" + y)}";
_logger.LogTrace(logText);
}

if (now > next)
if (now > next)
{
try
{
try
{
await Action.Process(stoppingToken);
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
}

next = _timing.GetNextOccurenceInRelativeTime(Cron);
_logger.LogTrace($"Next at {next.Value.DateTime.ToLongDateString()} {next.Value.DateTime.ToLongTimeString()}");
await Action.Process(stoppingToken);
}
else
catch (Exception e)
{
// needed for graceful shutdown for some reason.
// 100ms chosen so it doesn't affect calculating the next
// cron occurence (lowest possible: every second)
await Task.Delay(100);
_logger.LogError(e, e.Message);
}

} while (!stoppingToken.IsCancellationRequested);
}
next = _timing.GetNextOccurenceInRelativeTime(Cron);
_logger.LogTrace($"Next at {next.Value.DateTime.ToLongDateString()} {next.Value.DateTime.ToLongTimeString()}");
}
else
{
// needed for graceful shutdown for some reason.
// 100ms chosen so it doesn't affect calculating the next
// cron occurence (lowest possible: every second)
await Task.Delay(100);
}

} while (!stoppingToken.IsCancellationRequested);
}
}
17 changes: 6 additions & 11 deletions src/CronBackgroundServices/CronBackgroundServices.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
<RootNamespace>CronBackgroundServices</RootNamespace>
<PackageId>CronBackgroundServices</PackageId>
<Authors>John Korsnes</Authors>
Expand All @@ -16,26 +16,21 @@
<PackageIconUrl>images/cron.png</PackageIconUrl>
<PackageIcon>cron.png</PackageIcon>
<RepositoryType>git</RepositoryType>
<DotNet6Version>6.0.0-preview.4.21253.7</DotNet6Version>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Cronos" Version="0.7.0" />
<PackageReference Include="Cronos" Version="0.7.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(DotNet6Version)" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(DotNet6Version)" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0-*" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0-*" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.0" />
</ItemGroup>



<ItemGroup>
<None Include="images/cron.png" Pack="true" PackagePath="" />
Expand Down
Loading

0 comments on commit 103830f

Please sign in to comment.