diff --git a/Backi.sln b/Backi.sln
index 5b52df7..cc289aa 100644
--- a/Backi.sln
+++ b/Backi.sln
@@ -21,6 +21,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dotnet", "dotnet", "{7F2FF8
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backi.Mediatr", "mediator\dotnet\lib\Backi.Mediatr.csproj", "{1AD779EA-2592-48D0-95BB-6E68FDB5276F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backi.Timediatr.Tests", "mediator\dotnet\Backi.Timediatr.Tests\Backi.Timediatr.Tests.csproj", "{C47EDCC3-FF07-4B1E-BDC4-06544C6264F5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backi.Mediatr.Tests", "mediator\dotnet\Backi.Mediatr.Tests\Backi.Mediatr.Tests.csproj", "{2243861D-99F1-427E-80A6-DDED312872D7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backi.Timediatr", "mediator\dotnet\Backi.Timediatr\Backi.Timediatr.csproj", "{28FE8A5E-60D5-43A9-8550-AF7A077B1B35}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -46,6 +52,18 @@ Global
{1AD779EA-2592-48D0-95BB-6E68FDB5276F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1AD779EA-2592-48D0-95BB-6E68FDB5276F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1AD779EA-2592-48D0-95BB-6E68FDB5276F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C47EDCC3-FF07-4B1E-BDC4-06544C6264F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C47EDCC3-FF07-4B1E-BDC4-06544C6264F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C47EDCC3-FF07-4B1E-BDC4-06544C6264F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C47EDCC3-FF07-4B1E-BDC4-06544C6264F5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2243861D-99F1-427E-80A6-DDED312872D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2243861D-99F1-427E-80A6-DDED312872D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2243861D-99F1-427E-80A6-DDED312872D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2243861D-99F1-427E-80A6-DDED312872D7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {28FE8A5E-60D5-43A9-8550-AF7A077B1B35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {28FE8A5E-60D5-43A9-8550-AF7A077B1B35}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {28FE8A5E-60D5-43A9-8550-AF7A077B1B35}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {28FE8A5E-60D5-43A9-8550-AF7A077B1B35}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{ADCEE902-52AF-4E52-9CEA-77450049AA7C} = {BBB56B6A-667F-43C0-8AFE-79530301D381}
@@ -55,5 +73,8 @@ Global
{F9FA4AC6-6E34-4D32-8C9D-573B2321BDE7} = {ADCEE902-52AF-4E52-9CEA-77450049AA7C}
{7F2FF828-62E7-497D-8D5C-F0F4A1540AD1} = {27BF9028-A9AC-4B9E-9A63-5260B70734D0}
{1AD779EA-2592-48D0-95BB-6E68FDB5276F} = {7F2FF828-62E7-497D-8D5C-F0F4A1540AD1}
+ {C47EDCC3-FF07-4B1E-BDC4-06544C6264F5} = {7F2FF828-62E7-497D-8D5C-F0F4A1540AD1}
+ {2243861D-99F1-427E-80A6-DDED312872D7} = {7F2FF828-62E7-497D-8D5C-F0F4A1540AD1}
+ {28FE8A5E-60D5-43A9-8550-AF7A077B1B35} = {7F2FF828-62E7-497D-8D5C-F0F4A1540AD1}
EndGlobalSection
EndGlobal
diff --git a/Backi.sln.DotSettings b/Backi.sln.DotSettings
new file mode 100644
index 0000000..8dac555
--- /dev/null
+++ b/Backi.sln.DotSettings
@@ -0,0 +1,3 @@
+
+ True
+ True
\ No newline at end of file
diff --git a/Backi.sln.DotSettings.user b/Backi.sln.DotSettings.user
new file mode 100644
index 0000000..ecd39c4
--- /dev/null
+++ b/Backi.sln.DotSettings.user
@@ -0,0 +1,7 @@
+
+ <SessionState ContinuousTestingMode="0" IsActive="True" Name="CreateHandlerEachTime" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
+ <TestAncestor>
+ <TestId>MSTest::2243861D-99F1-427E-80A6-DDED312872D7::net8.0::Backi.Tests.SendMediatrRequestShould.CreateHandlerEachTime</TestId>
+ <TestId>MSTest::C47EDCC3-FF07-4B1E-BDC4-06544C6264F5::net8.0::Backi.Tests.TimediatrShould.SendCommandOneFewTimes</TestId>
+ </TestAncestor>
+</SessionState>
\ No newline at end of file
diff --git a/mediator/dotnet/Backi.Mediatr.Tests/Backi.Mediatr.Tests.csproj b/mediator/dotnet/Backi.Mediatr.Tests/Backi.Mediatr.Tests.csproj
new file mode 100644
index 0000000..563bb64
--- /dev/null
+++ b/mediator/dotnet/Backi.Mediatr.Tests/Backi.Mediatr.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mediator/dotnet/Backi.Mediatr.Tests/GlobalUsings.cs b/mediator/dotnet/Backi.Mediatr.Tests/GlobalUsings.cs
new file mode 100644
index 0000000..ab67c7e
--- /dev/null
+++ b/mediator/dotnet/Backi.Mediatr.Tests/GlobalUsings.cs
@@ -0,0 +1 @@
+global using Microsoft.VisualStudio.TestTools.UnitTesting;
\ No newline at end of file
diff --git a/mediator/dotnet/Backi.Mediatr.Tests/Infrastructure.cs b/mediator/dotnet/Backi.Mediatr.Tests/Infrastructure.cs
new file mode 100644
index 0000000..80c6bde
--- /dev/null
+++ b/mediator/dotnet/Backi.Mediatr.Tests/Infrastructure.cs
@@ -0,0 +1,59 @@
+using MediatR;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Backi.Tests;
+
+public static class ServiceCollectionExtensions
+{
+ public static IServiceCollection AddMediatrTestingInfrastructure(this IServiceCollection services)
+ {
+ services.AddSingleton();
+ services.AddMediatR(m => m.RegisterServicesFromAssembly(typeof(CounterCollection).Assembly));
+ services.AddLogging(l =>
+ {
+ l.AddSimpleConsole(c => c.SingleLine = true);
+ l.SetMinimumLevel(LogLevel.Debug);
+ });
+
+ return services;
+ }
+}
+
+public class CounterCollection
+{
+ public readonly Dictionary items = new ();
+
+ public void Increment(string key)
+ {
+ if (!items.TryAdd(key, 1))
+ {
+ items[key] += 1;
+ }
+ }
+
+ public int Get(string key) => items.GetValueOrDefault(key);
+}
+
+public class CommandOne : IRequest
+{
+ public class Handler : IRequestHandler
+ {
+ readonly CounterCollection counters;
+ public const string ConstructorCounterKey = "CommandA.Handler.Constructor";
+ public const string HandleCounterKey = "CommandA.Handler.Handle";
+
+ public Handler(CounterCollection counters)
+ {
+ this.counters = counters;
+
+ counters.Increment(ConstructorCounterKey);
+ }
+
+ public Task Handle(CommandOne request, CancellationToken cancellationToken)
+ {
+ counters.Increment(HandleCounterKey);
+ return Task.CompletedTask;
+ }
+ }
+}
\ No newline at end of file
diff --git a/mediator/dotnet/Backi.Mediatr.Tests/SendMediatrRequestShould.cs b/mediator/dotnet/Backi.Mediatr.Tests/SendMediatrRequestShould.cs
new file mode 100644
index 0000000..abda12f
--- /dev/null
+++ b/mediator/dotnet/Backi.Mediatr.Tests/SendMediatrRequestShould.cs
@@ -0,0 +1,32 @@
+using FluentAssertions;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Backi.Tests;
+
+[TestClass]
+public class SendMediatrRequestShould
+{
+ [TestMethod]
+ public async Task CreateHandlerEachTime()
+ {
+ var services = new ServiceCollection();
+
+ services.AddMediatrTestingInfrastructure();
+
+ var provider = services.BuildServiceProvider();
+
+ var scopeFactory = provider.GetRequiredService();
+ var logger = provider.GetRequiredService>();
+
+ var commandInstance = new CommandOne();
+
+ await scopeFactory.SendMediatorRequest(commandInstance, logger);
+ await scopeFactory.SendMediatorRequest(commandInstance, logger);
+
+ var counters = provider.GetRequiredService();
+
+ counters.Get(CommandOne.Handler.ConstructorCounterKey).Should().Be(2);
+ counters.Get(CommandOne.Handler.HandleCounterKey).Should().Be(2);
+ }
+}
\ No newline at end of file
diff --git a/mediator/dotnet/Backi.Timediatr.Tests/Backi.Timediatr.Tests.csproj b/mediator/dotnet/Backi.Timediatr.Tests/Backi.Timediatr.Tests.csproj
new file mode 100644
index 0000000..3bc88f3
--- /dev/null
+++ b/mediator/dotnet/Backi.Timediatr.Tests/Backi.Timediatr.Tests.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mediator/dotnet/Backi.Timediatr.Tests/GlobalUsings.cs b/mediator/dotnet/Backi.Timediatr.Tests/GlobalUsings.cs
new file mode 100644
index 0000000..ab67c7e
--- /dev/null
+++ b/mediator/dotnet/Backi.Timediatr.Tests/GlobalUsings.cs
@@ -0,0 +1 @@
+global using Microsoft.VisualStudio.TestTools.UnitTesting;
\ No newline at end of file
diff --git a/mediator/dotnet/Backi.Timediatr.Tests/TimediatrShould.cs b/mediator/dotnet/Backi.Timediatr.Tests/TimediatrShould.cs
new file mode 100644
index 0000000..38cd55e
--- /dev/null
+++ b/mediator/dotnet/Backi.Timediatr.Tests/TimediatrShould.cs
@@ -0,0 +1,34 @@
+using FluentAssertions;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Backi.Tests;
+
+[TestClass]
+public class TimediatrShould
+{
+ [TestMethod]
+ public async Task SendCommandOneFewTimes()
+ {
+ var services = new ServiceCollection();
+
+ services.AddMediatrTestingInfrastructure();
+ services.AddTimediatr(timediatr => timediatr.Configure(o =>
+ {
+ o.Schedule.Add(new CommandOne(), TimeSpan.FromSeconds(3));
+ }));
+
+ var provider = services.BuildServiceProvider();
+
+ var backgroundService = (TimediatrBackgroundService)provider.GetRequiredService();
+
+ await backgroundService.StartAsync(CancellationToken.None);
+ await Task.Delay(TimeSpan.FromSeconds(10));
+ await backgroundService.StopAsync(CancellationToken.None);
+
+ var counter = provider.GetRequiredService();
+
+ counter.Get(CommandOne.Handler.HandleCounterKey).Should().Be(4);
+ counter.Get(CommandOne.Handler.ConstructorCounterKey).Should().Be(4);
+ }
+}
\ No newline at end of file
diff --git a/mediator/dotnet/Backi.Timediatr/Backi.Timediatr.csproj b/mediator/dotnet/Backi.Timediatr/Backi.Timediatr.csproj
new file mode 100644
index 0000000..9114ff2
--- /dev/null
+++ b/mediator/dotnet/Backi.Timediatr/Backi.Timediatr.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mediator/dotnet/Backi.Timediatr/Timediatr.cs b/mediator/dotnet/Backi.Timediatr/Timediatr.cs
new file mode 100644
index 0000000..36fc8db
--- /dev/null
+++ b/mediator/dotnet/Backi.Timediatr/Timediatr.cs
@@ -0,0 +1,65 @@
+using Backi.Timers;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Backi;
+
+public class TimediatrBackgroundService(
+ IOptions options,
+ IServiceScopeFactory serviceScopeFactory,
+ ILogger logger) : IHostedService
+{
+ readonly List timers = [];
+
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ var schedule = options.Value.Schedule;
+
+ foreach (var timer in schedule)
+ {
+ logger.LogDebug("Setting {requestType} to run with interval {interval}", timer.Key.GetType(), timer.Value);
+
+ timers.Add(SafeTimer.RunNowAndPeriodically(
+ timer.Value,
+ () => serviceScopeFactory.SendMediatorRequest(timer.Key, logger, cancellationToken),
+ ex => logger.LogError(ex, "Error while processing {requestType}", timer.Key.GetType())
+ ));
+
+ logger.LogInformation("Set {requestType} to run with interval {interval}", timer.Key.GetType(), timer.Value);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ logger.LogDebug("Stopping all timers");
+
+ foreach (var timer in timers)
+ {
+ timer.Stop();
+ }
+
+ logger.LogInformation("Stopped all timers");
+ return Task.CompletedTask;
+ }
+}
+
+public class TimediatrConfiguration
+{
+ public Dictionary