Skip to content

Commit

Permalink
v2.0.0
Browse files Browse the repository at this point in the history
* Targets net6.0 and above, with full Dependency Injection support with per queue item scope.
  • Loading branch information
Darran committed Sep 5, 2024
1 parent 7ec970d commit d261b0a
Show file tree
Hide file tree
Showing 21 changed files with 440 additions and 199 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>DalSoft.Hosting.BackgroundQueue.Examples.WebApi</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.3"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DalSoft.Hosting.BackgroundQueue\DalSoft.Hosting.BackgroundQueue.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@DalSoft.Hosting.BackgroundQueue.Test.WebApi_HostAddress = http://localhost:5184

GET {{DalSoft.Hosting.BackgroundQueue.Test.WebApi_HostAddress}}/weatherforecast/
Accept: application/json

###
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace DalSoft.Hosting.BackgroundQueue.Examples.WebApi.Data.Entities;

public class Person
{
public long PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using DalSoft.Hosting.BackgroundQueue.Examples.WebApi.Data.Entities;

namespace DalSoft.Hosting.BackgroundQueue.Examples.WebApi.Data;

using Microsoft.EntityFrameworkCore;

public class PersonDbContext(DbContextOptions<PersonDbContext> options) : DbContext(options)
{
public DbSet<Person> People { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using DalSoft.Hosting.BackgroundQueue.Examples.WebApi.Data;
using DalSoft.Hosting.BackgroundQueue.Examples.WebApi.Data.Entities;

namespace DalSoft.Hosting.BackgroundQueue.Examples.WebApi.Endpoints;

public static class TestBackgroundQueueEndpoint
{
public static void MapWeatherEndpoints(this WebApplication app)
{
app.MapPost("/backgroundtasks", (IBackgroundQueue backgroundQueue) =>
{
// queue background tasks
for (var i = 0; i < 20; i++)
{
backgroundQueue.Enqueue(async ct =>
{
await Task.Delay(60000, ct);
});
}
return new { added = true };
})
.WithName("Add background Tasks")
.WithOpenApi();

// Also test injecting BackgroundQueue instead of IBackgroundQueue
app.MapGet("/backgroundtasks", (BackgroundQueue backgroundQueue) => new { backgroundQueue.Count, backgroundQueue.ConcurrentCount })
.WithName("Get Background Tasks")
.WithOpenApi();

app.MapPost("/backgroundtasks/dependencyinjection", (IBackgroundQueue backgroundQueue) =>
{
backgroundQueue.Enqueue(async (ct, serviceScope) =>
{
await Task.Delay(5000, ct);
var dbContext = serviceScope.ServiceProvider.GetRequiredService<PersonDbContext>();
await dbContext.People.AddAsync(new Person
{
FirstName = $"FirstName {Guid.NewGuid()}",
LastName = $"LastName {Guid.NewGuid()}"
}, ct);
await dbContext.SaveChangesAsync(ct);
});
})
.WithName("Test Dependency Injection")
.WithOpenApi();

app.MapPost("/backgroundtasks/exception", (IBackgroundQueue backgroundQueue) =>
{
backgroundQueue.Enqueue(async ct => throw new Exception("Testing exceptions"));
})
.WithName("Test Exceptions Handling")
.WithOpenApi();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using DalSoft.Hosting.BackgroundQueue.Examples.WebApi.Data;

namespace DalSoft.Hosting.BackgroundQueue.Examples.WebApi.Extensions;

public static class HostExtensions
{
public static void CreateDbIfNotExists(this IHost host)
{
using var scope = host.Services.CreateScope();
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<PersonDbContext>();
context.Database.EnsureCreated();

}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
}
36 changes: 36 additions & 0 deletions DalSoft.Hosting.BackgroundQueue.Examples.WebApi/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using DalSoft.Hosting.BackgroundQueue.Examples.WebApi.Data;
using DalSoft.Hosting.BackgroundQueue.Examples.WebApi.Endpoints;
using DalSoft.Hosting.BackgroundQueue.Examples.WebApi.Extensions;
using DalSoft.Hosting.BackgroundQueue.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
builder.Logging.ClearProviders().AddConsole();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<PersonDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString(nameof(PersonDbContext))));

builder.Services.AddBackgroundQueue((exception, serviceScope) =>
{
serviceScope.ServiceProvider.GetRequiredService<ILogger<Program>>()
.Log(LogLevel.Error, exception, exception.Message);
}, maxConcurrentCount: 10);

var app = builder.Build();

app.CreateDbIfNotExists();

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapWeatherEndpoints();

app.Run();

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:57219",
"sslPort": 44341
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5184",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7020;http://localhost:5184",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
12 changes: 12 additions & 0 deletions DalSoft.Hosting.BackgroundQueue.Examples.WebApi/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"ConnectionStrings": {
"PersonDbContext": "Server=(localdb)\\mssqllocaldb;Database=BackgroundQueueExample;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>

<IsPackable>false</IsPackable>

<RootNamespace>DalSoft.Hosting.BackgroundQueue.Test</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
<PackageReference Include="Moq" Version="4.20.71" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
59 changes: 31 additions & 28 deletions DalSoft.Hosting.BackgroundQueue.Test/MaxConcurrentCountTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,45 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;

namespace DalSoft.Hosting.BackgroundQueue.Test
namespace DalSoft.Hosting.BackgroundQueue.Test;

public class MaxConcurrentCountTest
{
public class MaxConcurrentCountTest
[Fact]
public async Task ServiceShouldRunMaxConcurrentCountTaskWhenExistInQueue()
{
[Fact]
public async Task ServiceShouldRunMaxConcurrentCountTaskWhenExistInQueue()
var tokenSource = new CancellationTokenSource();
var queue = new BackgroundQueue((ex, _) =>
{
var tokenSource = new CancellationTokenSource();
var queue = new BackgroundQueue(ex => throw ex, 10, 10); ;
var queueService = new BackgroundQueueService(queue);
var highwaterMark = 0;


// queue background tasks
for (var i = 0; i < 20; i++)
throw ex;
}, 10, 10); ;
var queueService = new BackgroundQueueService(queue, new Mock<IServiceScopeFactory>().Object);
var highwaterMark = 0;

// queue background tasks
for (var i = 0; i < 20; i++)
{
queue.Enqueue(async ct =>
{
queue.Enqueue(async ct =>
{
highwaterMark = Math.Max(queue.ConcurrentCount, highwaterMark);
await Task.Delay(5, ct);
});
}

// process background tasks
var runningService = Task.Run(async () => await queueService.StartAsync(tokenSource.Token), tokenSource.Token);
highwaterMark = Math.Max(queue.ConcurrentCount, highwaterMark);
await Task.Delay(5, ct);
});
}

// wait for all tasks to be processed
while(queue.Count > 0)
{
await Task.Delay(20, tokenSource.Token);
}
// process background tasks
var runningService = Task.Run(async () => await queueService.StartAsync(tokenSource.Token), tokenSource.Token);

// Check that tasks run concurrently up to the maxConcurrentCount.
highwaterMark.Should().BeGreaterThan(1);
// wait for all tasks to be processed
while(queue.Count > 0)
{
await Task.Delay(20, tokenSource.Token);
}

// Check that tasks run concurrently up to the maxConcurrentCount.
highwaterMark.Should().Be(10);
}
}
6 changes: 6 additions & 0 deletions DalSoft.Hosting.BackgroundQueue.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DalSoft.Hosting.BackgroundQ
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DalSoft.Hosting.BackgroundQueue.Test", "DalSoft.Hosting.BackgroundQueue.Test\DalSoft.Hosting.BackgroundQueue.Test.csproj", "{91F4AEC4-5818-441C-8C47-92F64C9CF1C3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DalSoft.Hosting.BackgroundQueue.Examples.WebApi", "DalSoft.Hosting.BackgroundQueue.Examples.WebApi\DalSoft.Hosting.BackgroundQueue.Examples.WebApi.csproj", "{FC8BEB9B-ABB7-4D06-86E3-DA9FAD8D20B0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,6 +23,10 @@ Global
{91F4AEC4-5818-441C-8C47-92F64C9CF1C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91F4AEC4-5818-441C-8C47-92F64C9CF1C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91F4AEC4-5818-441C-8C47-92F64C9CF1C3}.Release|Any CPU.Build.0 = Release|Any CPU
{FC8BEB9B-ABB7-4D06-86E3-DA9FAD8D20B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC8BEB9B-ABB7-4D06-86E3-DA9FAD8D20B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC8BEB9B-ABB7-4D06-86E3-DA9FAD8D20B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC8BEB9B-ABB7-4D06-86E3-DA9FAD8D20B0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Loading

0 comments on commit d261b0a

Please sign in to comment.