Adding a layer for dependency injection and hosted services

This commit is contained in:
Tyler Coles 2023-10-13 14:22:05 -04:00
parent f1275079a6
commit e3d387b2cd
9 changed files with 214 additions and 15 deletions

View File

@ -0,0 +1,72 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace crondotnet.Extensions.DependencyInjection
{
public class CronDaemonHostedService : IHostedService
{
public CronDaemonHostedService(
IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
private AsyncServiceScope Scope { get; set; }
private IServiceProvider ServiceProvider { get; }
private ICronDaemon CronDaemon { get; set; }
public async Task StartAsync(CancellationToken cancellationToken)
{
Scope = ServiceProvider.CreateAsyncScope();
CronDaemon = Scope.ServiceProvider.GetRequiredService<ICronDaemon>();
var options = Scope.ServiceProvider.GetRequiredService<IOptions<CronDaemonOptions>>();
foreach (var job in options.Value.Jobs)
{
if (job.StaticTask != null)
{
CronDaemon.AddJob(job.Expression, job.StaticTask);
}
else if (job.ServiceType != null)
{
var service = Scope.ServiceProvider.GetRequiredService(job.ServiceType) as IThinService;
if (service != null)
{
CronDaemon.AddJob(job.Expression, new ThinServiceCronWrapper(service).RunService);
}
}
else
{
// nothing set up for the run task.
}
}
await CronDaemon.StartAsync(cancellationToken);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await CronDaemon.StopAsync();
await Scope.DisposeAsync();
}
private class ThinServiceCronWrapper
{
public ThinServiceCronWrapper(IThinService service)
{
Service = service;
}
private IThinService Service { get; }
public async Task RunService(CancellationToken cancellationToken)
{
await Service.Run(cancellationToken);
}
}
}
}

View File

@ -0,0 +1,8 @@
namespace Microsoft.Extensions.DependencyInjection
{
internal class CronDaemonOptions
{
public List<JobOptions> Jobs { get; set; } = new List<JobOptions>();
}
}

View File

@ -0,0 +1,60 @@
using crondotnet;
using crondotnet.Extensions.DependencyInjection;
namespace Microsoft.Extensions.DependencyInjection
{
public static class CronServiceProverExtensions
{
public static IServiceCollection AddCron(this IServiceCollection services, Action<ICronDaemonOptionsBuilder>? optionsAction = null)
{
services.AddSingleton<CronDaemonHostedService>();
services.AddScoped<ICronDaemon, CronDaemon>();
var cronDaemonOptionsBuilder = new CronDaemonOptionsBuilder();
optionsAction?.Invoke(cronDaemonOptionsBuilder);
services.ConfigureOptions(cronDaemonOptionsBuilder.Build());
return services;
}
}
public interface ICronDaemonOptionsBuilder
{
ICronDaemonOptionsBuilder AddJob(string cronExpression, ExecuteCronJob job);
ICronDaemonOptionsBuilder AddJob<TService>(string cronExpression) where TService : IThinService;
}
public class CronDaemonOptionsBuilder : ICronDaemonOptionsBuilder
{
private readonly CronDaemonOptions Options = new CronDaemonOptions();
public ICronDaemonOptionsBuilder AddJob(string cronExpression, ExecuteCronJob job)
{
Options.Jobs.Add(new JobOptions
{
Expression = cronExpression,
StaticTask = job
});
return this;
}
public ICronDaemonOptionsBuilder AddJob<TService>(string cronExpression)
where TService : IThinService
{
Options.Jobs.Add(new JobOptions
{
Expression = cronExpression,
ServiceType = typeof(TService),
});
return this;
}
internal CronDaemonOptions Build()
{
return Options;
}
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace crondotnet
{
public interface IThinService
{
Task Run(CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,11 @@
using crondotnet;
namespace Microsoft.Extensions.DependencyInjection
{
public class JobOptions
{
public string Expression { get; set; }
public ExecuteCronJob StaticTask { get; set; }
public Type ServiceType { get; internal set; }
}
}

View File

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6;net7</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<RootNamespace>crondotnet</RootNamespace>
<AssemblyName>crondotnet.Extensions.Hosting</AssemblyName>
<Authors>kevinkolyar, iminet, tycoff</Authors>
<Product>C# library for running cron jobs on .NET</Product>
<PackageId>crondotnet.Extensions.Hosting</PackageId>
<PackageProjectUrl>https://github.com/tycoff/crondotnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/tycoff/crondotnet</RepositoryUrl>
<PackageTags>dotnet Cron Crondaemon Crontab</PackageTags>
<Version>0.1.1.0</Version>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\crondotnet\crondotnet.csproj" />
</ItemGroup>
</Project>

View File

@ -7,10 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "crondotnet", "crondotnet\cr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "crondotnet.Tests", "crondotnet.Tests\crondotnet.Tests.csproj", "{6FCFBDF4-ECB7-4FC2-A376-E962B11D487D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6753321E-D4F3-4199-872D-8986A596453E}"
ProjectSection(SolutionItems) = preProject
Readme.md = Readme.md
EndProjectSection
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "crondotnet.Extensions.Hosting", "crondotnet.Extensions.DependencyInjection\crondotnet.Extensions.Hosting.csproj", "{5A6713CE-4366-497C-AA82-C2CEB3276CFA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -26,6 +23,10 @@ Global
{6FCFBDF4-ECB7-4FC2-A376-E962B11D487D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6FCFBDF4-ECB7-4FC2-A376-E962B11D487D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FCFBDF4-ECB7-4FC2-A376-E962B11D487D}.Release|Any CPU.Build.0 = Release|Any CPU
{5A6713CE-4366-497C-AA82-C2CEB3276CFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A6713CE-4366-497C-AA82-C2CEB3276CFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A6713CE-4366-497C-AA82-C2CEB3276CFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A6713CE-4366-497C-AA82-C2CEB3276CFA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -52,13 +52,20 @@ namespace crondotnet
{
while (!cancellationToken.IsCancellationRequested)
{
await timer.WaitForNextTickAsync(cancellationToken);
if (!cancellationToken.IsCancellationRequested && DateTime.Now.Minute != _last.Minute)
try
{
_last = DateTime.Now;
foreach (ICronJob job in cronJobs)
job.Execute(DateTime.Now, cancellationToken);
await timer.WaitForNextTickAsync(cancellationToken);
if (!cancellationToken.IsCancellationRequested && DateTime.Now.Minute != _last.Minute)
{
_last = DateTime.Now;
foreach (ICronJob job in cronJobs)
job.Execute(DateTime.Now, cancellationToken);
}
}
catch (TaskCanceledException)
{
break;
}
}
}

View File

@ -10,11 +10,7 @@
<PackageProjectUrl>https://github.com/tycoff/crondotnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/tycoff/crondotnet</RepositoryUrl>
<PackageTags>dotnet Cron Crondaemon Crontab</PackageTags>
<Version>0.1.0.0</Version>
<Version>0.1.2.0</Version>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<None Include="..\Readme.md" Pack="true" PackagePath="\"/>
</ItemGroup>
</Project>