Skip to content

Commit

Permalink
Merge pull request #1 from maitlandmarshall/chore/no-startup_backgrou…
Browse files Browse the repository at this point in the history
…ndJobContext-refactor

Chore/no startup background job context refactor and unit tests
  • Loading branch information
maitlandmarshall authored Sep 9, 2020
2 parents 93fb6e3 + b0c22db commit cd9e041
Show file tree
Hide file tree
Showing 20 changed files with 292 additions and 135 deletions.
14 changes: 14 additions & 0 deletions MAD.Integration.Common.EFCore/MAD.Integration.Common.EFCore.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.1</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.7" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>

</Project>
20 changes: 20 additions & 0 deletions MAD.Integration.Common.EFCore/UseDesignDefaultsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace MAD.Integration.Common.EFCore
{
public static class UseDesignDefaultsExtensions
{
public static DbContextOptionsBuilder UseDesignDefaults(this DbContextOptionsBuilder dbContextOptionsBuilder)
{
var json = JObject.Parse(File.ReadAllText("settings.default.json"));
dbContextOptionsBuilder.UseSqlServer(json["connectionString"].ToString());

return dbContextOptionsBuilder;
}
}
}
45 changes: 45 additions & 0 deletions MAD.Integration.Common.Tests/BackgroundJobContextTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Hangfire;
using Hangfire.MemoryStorage;
using MAD.Integration.Common.Jobs;
using Microsoft.Extensions.Hosting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading.Tasks;

namespace MAD.Integration.Common.Tests
{
[TestClass]
public class BackgroundJobContextTests
{
private static readonly TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
public static async Task BackgroundJobContext_Job()
{
try
{
await Task.Delay(5);
var bg = BackgroundJobContext.Current;

taskCompletionSource.SetResult(bg != null);
}
catch (Exception)
{
taskCompletionSource.SetResult(false);
}
}

[TestMethod]
public async Task CurrentJob_InAsyncExecutingJob_NotNull()
{
var host = IntegrationHost.CreateDefaultBuilder()
.UseHangfire(cfg => cfg.UseMemoryStorage())
.Build();

BackgroundJob.Enqueue(() => BackgroundJobContext_Job());

_ = host.RunAsync();

var result = await taskCompletionSource.Task;
Assert.IsTrue(result);
}
}
}
21 changes: 21 additions & 0 deletions MAD.Integration.Common.Tests/MAD.Integration.Common.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Hangfire.MemoryStorage" Version="1.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.0" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.0" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\MAD.Integration.Common\MAD.Integration.Common.csproj" />
</ItemGroup>

</Project>
19 changes: 18 additions & 1 deletion MAD.Integration.Common.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30413.136
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MAD.Integration.Common", "MAD.Integration.Common\MAD.Integration.Common.csproj", "{4FB18911-6D4A-4895-A724-4F4F3C4DEC1C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MAD.Integration.Common", "MAD.Integration.Common\MAD.Integration.Common.csproj", "{4FB18911-6D4A-4895-A724-4F4F3C4DEC1C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MAD.Integration.Common.EFCore", "MAD.Integration.Common.EFCore\MAD.Integration.Common.EFCore.csproj", "{06CDE6FD-43A1-4FD5-AE87-69021DF1A2BC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MAD.Integration.Common.Tests", "MAD.Integration.Common.Tests\MAD.Integration.Common.Tests.csproj", "{B4B03402-85EA-49BE-BC98-D3AF0903F005}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{E057276C-548A-4190-AF5A-9A92E08268A1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -15,10 +21,21 @@ Global
{4FB18911-6D4A-4895-A724-4F4F3C4DEC1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FB18911-6D4A-4895-A724-4F4F3C4DEC1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FB18911-6D4A-4895-A724-4F4F3C4DEC1C}.Release|Any CPU.Build.0 = Release|Any CPU
{06CDE6FD-43A1-4FD5-AE87-69021DF1A2BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{06CDE6FD-43A1-4FD5-AE87-69021DF1A2BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{06CDE6FD-43A1-4FD5-AE87-69021DF1A2BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{06CDE6FD-43A1-4FD5-AE87-69021DF1A2BC}.Release|Any CPU.Build.0 = Release|Any CPU
{B4B03402-85EA-49BE-BC98-D3AF0903F005}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B4B03402-85EA-49BE-BC98-D3AF0903F005}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B4B03402-85EA-49BE-BC98-D3AF0903F005}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B4B03402-85EA-49BE-BC98-D3AF0903F005}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{B4B03402-85EA-49BE-BC98-D3AF0903F005} = {E057276C-548A-4190-AF5A-9A92E08268A1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {70D42A47-49DB-4784-8B40-89988134D6C8}
EndGlobalSection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ namespace MAD.Integration.Common.Hangfire
{
internal static class AspNetCoreServiceCollectionExtensions
{
public static IServiceCollection AddAspNetCore(this IServiceCollection serviceCollection)
public static IServiceCollection AddAspNetCore(this IServiceCollection serviceCollection, AspNetCoreConfig aspNetCoreConfig = null)
{
serviceCollection.AddSingleton<AspNetCoreConfig>();
aspNetCoreConfig ??= new AspNetCoreConfig();

serviceCollection.AddSingleton<AspNetCoreConfig>(aspNetCoreConfig);

return serviceCollection;
}
Expand Down
9 changes: 7 additions & 2 deletions MAD.Integration.Common/Http/WebHostStartup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,18 @@ public void ConfigureServices(IServiceCollection serviceDescriptors)

public void Configure(IApplicationBuilder app)
{
var dashboardOptions = new DashboardOptions
{
DashboardTitle = $"{Assembly.GetEntryAssembly().GetName().Name} Dashboard"
};

app.UseRouting();
app.UseEndpoints(cfg => {
cfg.MapControllers();
cfg.MapHangfireDashboard();
cfg.MapHangfireDashboard(dashboardOptions);
});

app.UseHangfireDashboard();
app.UseHangfireDashboard(options: dashboardOptions);
}
}
}
5 changes: 5 additions & 0 deletions MAD.Integration.Common/IIntegrationHostBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Hangfire;
using MAD.Integration.Common.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
Expand All @@ -13,7 +14,11 @@ public interface IIntegrationHostBuilder
IIntegrationHostBuilder ConfigureServices(Action<IServiceCollection> configureDelegate);

IIntegrationHostBuilder UseHangfire();
IIntegrationHostBuilder UseHangfire(Action<IGlobalConfiguration> configureDelegate);

IIntegrationHostBuilder UseAspNetCore();
IIntegrationHostBuilder UseAspNetCore(Action<AspNetCoreConfig> configureDelegate);

IIntegrationHostBuilder UseAppInsights();

IHost Build();
Expand Down
15 changes: 12 additions & 3 deletions MAD.Integration.Common/IntegrationHostBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Hangfire;
using MAD.Integration.Common.Analytics;
using MAD.Integration.Common.Hangfire;
using MAD.Integration.Common.Http;
Expand Down Expand Up @@ -118,16 +119,22 @@ private void HandleStartupConfigure(IServiceProvider serviceProvider)
return this;
}

public IIntegrationHostBuilder UseHangfire()
public IIntegrationHostBuilder UseHangfire() => this.UseHangfire(null);
public IIntegrationHostBuilder UseHangfire(Action<IGlobalConfiguration> configureDelegate)
{
configureDelegate?.Invoke(GlobalConfiguration.Configuration);
this.ConfigureServices(y => y.AddHangfire());

return this;
}

public IIntegrationHostBuilder UseAspNetCore()
public IIntegrationHostBuilder UseAspNetCore() => this.UseAspNetCore(null);
public IIntegrationHostBuilder UseAspNetCore(Action<AspNetCoreConfig> configureDelegate)
{
this.ConfigureServices(y => y.AddAspNetCore());
var config = new AspNetCoreConfig();
configureDelegate?.Invoke(config);

this.ConfigureServices(y => y.AddAspNetCore(config));

return this;
}
Expand All @@ -138,5 +145,7 @@ public IIntegrationHostBuilder UseAppInsights()

return this;
}


}
}
63 changes: 63 additions & 0 deletions MAD.Integration.Common/Jobs/AutofacLifecycleJobActivator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Autofac;
using Hangfire;
using Hangfire.Annotations;
using System;

namespace MAD.Integration.Common.Jobs
{
public class AutofacLifecycleJobActivator : JobActivator
{
public const string LifetimeScopeTag = "BackgroundJobScope";

private readonly ILifetimeScope lifetimeScope;

public AutofacLifecycleJobActivator([NotNull] ILifetimeScope lifetimeScope)
{
this.lifetimeScope = lifetimeScope;
}

public override object ActivateJob(Type jobType)
{
var jobInstance = this.lifetimeScope.Resolve(jobType);

if (jobInstance is IJobActivated jobInitialize)
{
jobInitialize.Activated();
}

return jobInstance;
}

public override JobActivatorScope BeginScope(JobActivatorContext context)
{
return new AutofacScope(this.lifetimeScope.BeginLifetimeScope(LifetimeScopeTag));
}

private class AutofacScope : JobActivatorScope
{
private readonly ILifetimeScope lifetimeScope;

public AutofacScope(ILifetimeScope lifetimeScope)
{
this.lifetimeScope = lifetimeScope;
}

public override object Resolve(Type type)
{
var jobInstance = this.lifetimeScope.Resolve(type);

if (jobInstance is IJobActivated jobInitialize)
{
jobInitialize.Activated();
}

return jobInstance;
}

public override void DisposeScope()
{
this.lifetimeScope.Dispose();
}
}
}
}
31 changes: 6 additions & 25 deletions MAD.Integration.Common/Jobs/BackgroundJobContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,20 @@ namespace MAD.Integration.Common.Jobs
{
public class BackgroundJobContext : JobFilterAttribute, IServerFilter
{
public static BackgroundJob CurrentJob
public static PerformContext Current
{
get => ThreadStaticValue<BackgroundJob>.Current;
set => ThreadStaticValue<BackgroundJob>.Current = value;
get => AsyncLocalValue<PerformContext>.Current;
set => AsyncLocalValue<PerformContext>.Current = value;
}

public static IServiceProvider CurrentJobServices
void IServerFilter.OnPerformed(PerformedContext filterContext)
{
get => ThreadStaticValue<ILifetimeScope>.Current as IServiceProvider;
Current = filterContext;
}

internal static ILifetimeScope CurrentLifetimeScope
{
get => ThreadStaticValue<ILifetimeScope>.Current;
set => ThreadStaticValue<ILifetimeScope>.Current = value;
}

internal static Action<ILifetimeScope> CurrentLifetimeScopeChanged
{
get => ThreadStaticValue<ILifetimeScope>.OnCurrentChanged;
set => ThreadStaticValue<ILifetimeScope>.OnCurrentChanged = value;
}

internal static LifetimeScope ParentBackgroundJobScope
{
get => ThreadStaticValue<LifetimeScope>.Current;
set => ThreadStaticValue<LifetimeScope>.Current = value;
}

void IServerFilter.OnPerformed(PerformedContext filterContext) { }
void IServerFilter.OnPerforming(PerformingContext filterContext)
{
CurrentJob = filterContext.BackgroundJob;
Current = filterContext;
}
}
}
35 changes: 35 additions & 0 deletions MAD.Integration.Common/Jobs/BackgroundJobExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Hangfire;
using Hangfire.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MAD.Integration.Common.Jobs
{
public static class BackgroundJobExtensions
{
public static BackgroundJob SetJobParameter(this BackgroundJob job, string name, object value)
{
if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));

JobStorage.Current.GetConnection().SetJobParameter(job.Id, name, SerializationHelper.Serialize(value, SerializationOption.User));
return job;
}

public static T GetJobParameter<T>(this BackgroundJob job, string name)
{
if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));

try
{
return SerializationHelper.Deserialize<T>(JobStorage.Current.GetConnection().GetJobParameter(job.Id, name), SerializationOption.User);
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Could not get a value of the job parameter `{name}`. See inner exception for details.", ex);
}
}
}
}
Loading

0 comments on commit cd9e041

Please sign in to comment.