MIFCore | MIFCore.Common | MIFCore.Hangfire | MIFCore.Hangfire.JobActions | |
---|---|---|---|---|
NuGet |
MIFCore is a framework that leverages the job scheduling power of Hangfire and the extensive functionality of .NET Core/.NET to provide you with the ability to develop custom integrations with ease.
- Supported Platforms
- Integration Host
- Global Default Configuration
- Binding Configuration
- Job Actions
- RecurringJobFactory
- Batch Context Filter
- Reschedule Job By Date Filter
- BackgroundJobContext
- DisableIdenticialQueuedItemsAttribute
- TrackLastSuccessAttribute
MIFCore currently supports the following platforms:
- .NET Standard 2.1
- .NET Core 3.1
- .NET 5
- .NET 6
The IntegrationHost is the main building block of the MIFCore framework and builds a Microsoft.Extensions.Hosting.IHost on launch, similar to a vanilla .NET Core application. The IntegrationHostBuilder is utilized by the IntegrationHost to create a host builder where startup operations can be performed.
The IntegrationHost.CreateDefaultBuilder()
method creates an instance of the IntegrationHostBuilder which allows you to configure a Startup class where you can configure/initialise your services:
IntegrationHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
By default, the IntegrationHost.CreateDefaultBuilder()
method will add Hangfire and if it does not exist, attempt to create the SQL database configured in the ConnectionString
property of the settings.json
file. MIFCore will then initialise the necessary Hangfire JobStorage using GeXiaoguo's MAMQSqlServerStorage extension. The Hangfire database tables will be created with the schema name "job".
The CreateDefaultBuilder method will register the following filters to the global Hangfire configuration:
And, register the RecurringJobFactory, which is MIFCore's version of the Hangfire RecurringJobManager
.
Additionally, the following methods from the Microsoft.Extensions.Hosting namespace are also available in the IntegrationHostBuilder:.
- ConfigureServices(Action<IServiceCollection>)
- ConfigureHostConfiguration(Action<IConfigurationBuilder>)
- ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder>)
IntegrationHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseAspNetCore();
}
Adding the .UseAspNetCore method to your application will enable the Hangfire Dashboard, allowing you to easily view the status of your jobs and perform other job tasks like triggering, rescheduling or cancelling. The dashboard can be accesssed by browsing to http://localhost:{BindingPort}/{BindingPath}/hangfire
.
The .UseAspNetCore()
method will also instantiate a Kestrel web server that can be used to mount custom controllers for your application:
[Route("HelloWorld")]
public class HelloWorldController : Controller
{
public string Index()
{
return "Hello!";
}
}
If the BindingPort
property in the settings.json
file is 80 or 443, a HTTP.sys webserver will be used instead of a Kestrel web server. The advantage to setting your BindingPort
to 80 is that the HTTP.sys web server allows multiple services to share the same port as long as each service has a different BindingPath
.
MIFCore will link to your Microsoft Azure AppInsights resource and automatically start tracking your jobs. To configure this functionality in your application, add the following code:
IntegrationHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseAppInsights();
}
MIFCore will attempt to retrieve the configured InstrumentationKey
property from the settings.json
file and instantiate a Microsoft.ApplicationInsights.TelemetryClient that operates alongside a global IServerFilter. This filter will track an event or exception in the telemetry client each time a job is executed and will provide the following information:
- Event Name/Description
- Application name
- Job Name ($"{Job.Type.Name}.{Job.Method.Name}")
- Job Arguments
- Exception Info
When using MIFCore, the startup of the application goes through 3 stages - the ConfigureServices
, Configure
and PostConfigure
methods:
class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Executed before anything is initialised / configured in the application
}
public void Configure()
{
// Executed after the ConfigureServices method has run and after the IHost is built
}
public void PostConfigure()
{
// Executed after everything has been initialised and the IHost.Run() method is called
}
}
The ConfigureServices
method works the same as the standard ConfigureServices
method in a .NET Core app and should be used to register/initialise the dependencies for the application. It is executed before anything has been initialised in the application.
public void ConfigureServices(IServiceCollection services)
{
services.AddIntegrationSettings<MyConfig>();
services.AddDbContext<MyDbContext>((svc, builder) => builder.UseSqlServer(svc.GetRequiredService<MyConfig>().ConnectionString));
services.AddScoped<MyRecurringJob>();
}
For information regarding services.AddIntegrationSetting<MyConfig>();
see Global Default Configuration.
The Configure
method is executed after the IHost
has been built and the dependencies registered in the ConfigureServices
method have been initialised. This method is useful for performing any other functionality required before the application IHost.Run()
method is called e.g. registering custom global Hangfire configuration:
public void Configure(IGlobalConfiguration hangfireGlobalConfig, MyConfig myConfig)
{
hangfireGlobalConfig.Queues = new[] { myConfig.MyQueueName, "default" };
hangfireGlobalConfig.UseFilter(new MyCoolFilter());
}
The PostConfigure
method is executed after the above methods have finished and the IHost.Run()
method has been called. This method can be used for running any other startup functionality required e.g. running migrations for your DbContext, registering your recurring Hangfire jobs or executing a job immediately:
public void PostConfigure(MyDbContext myDbContext, MyConfig myConfig, IRecurringJobManager recurringJobManager)
{
myDbContext.Database.Migrate();
recurringJobManager.CreateRecurringJob<MyRecurringJob>("MyJobName", y => y.RunMyJob(), Cron.Daily(), myConfig.MyQueueName);
}
MIFCore relies on a settings.json
configuration file being present in the base directory of the application. The first time the application is run, MIFCore will create a settings.json
file with some default properties if no file is found. The default configuration properties created on launch are:
- ConnectionString
- BindingPort
- InstrumentationKey
These configuration items are automatically added to the Globals.DefaultConfiguration
. The ConnectionString
and InstrumentationKey
will be created as blank strings and the BindingPort
will be set to 1337 by default.
Whilst your application is under development and you wish to have configuration that is stored on your local machine, a settings.default.json
file can be created in your project that stores any required configuration. You must add the ConnectionString
and InstrumentationKey
properties to this file manually, the BindingPort
in the application will default to 1337. If no BindingPort
can be found in the settings.json
file, the system will automatically bind to port 666.
You have the option of adding your own custom configuration by creating a class with the required properties and then registering it using the IServiceCollection.AddIntegrationSettings()
extension method:
// settings.json
{
"ConnectionString": "",
"BindingPort": 1337,
"BindingPath": "",
"InstrumentationKey": "",
"MyConfigProperty": true
}
// Custom configuration class
public class MyConfig
{
public bool MyConfigProperty {get; set;}
}
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddIntegrationSettings<MyConfig>();
}
The .AddIntegrationSettings()
method will add your custom configuration plus the other default configuration properties into a single IConfiguration object that can be accessed globally using Globals.DefaultConfiguration
:
var myConfigItem = Globals.DefaultConfiguration["MyConfigProperty"];
The binding configuration within the application is used primarily in the IntegrationHostBuilder.UseAspNetCore()
extension method. When MIFCore launches the Kestrel/HTTP.sys webserver, the BindingPort
and BindingPath
will be used to configure the Url. If the BindingPath
has a value, then the IApplicationBuilder
will add this value to the base path:
{
"BindingPath: "MyBindingPath"
}
Job Actions provides you with the ability to create an execution order for custom sql commands and recurring jobs. To enable Job Actions, run the .UseJobActions()
extension method on the IntegrationHostBuilder
during startup:
IntegrationHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseJobActions();
}
Once enabled, MIFCore will automatically create the JobActions
table in database configured in your ConnectionString
. This table will use the same schema ("job") as the Hangfire sql tables.
The job.JobActions
table consists of the following columns:
JobName
The name of the job you wish to trigger the action.Action
The sql command or recurring job id you wish to execute for the action.Order
The sequence number for this action.Timing
Must be set toBEFORE
orAFTER
. Specifies whether action should be run before or after the job.IsEnabled
Specifies whether this action is enabled.Database
Specifies which sql database this action should be run against.
If the Database
column is left as a null value, MIFCore will use the current database configured in the ConnectionString
property of the settings.json
file.
To create some Job Actions, register your required recurring jobs in the application startup class:
public void PostConfigure(IRecurringJobManager recurringJobManager)
{
recurringJobManager.CreateRecurringJob<MyRecurringJob>("MyJob", y => y.RunMyJob(), Cron.Daily());
recurringJobManager.CreateRecurringJob<MyRecurringJob>("MyOtherJob", y => y.RunMyOtherJob(), Cron.Daily());
}
Then, manually insert the required job actions records into the job.JobActions
table in SQL ensuring to use the same JobName
as the recurring job you registered in the previous step:
INSERT [job].[JobActions] ([JobName],[Action],[Order],[Timing],[IsEnabled],[Database])
VALUES ('MyJob', 'EXECUTE [dbo].[MyStoredProc]', 1, 'BEFORE', 1, 'MyOtherDatabase')
GO
INSERT [job].[JobActions] ([JobName],[Action],[Order],[Timing],[IsEnabled])
VALUES ('MyJob', 'recurring-job:MyRecurringJob', 1, 'AFTER', 1)
GO
In the example above, the first action will execute a sql command of EXECUTE [dbo].[MyStoredProc]
against the 'MyOtherDatabase' database before the recurring job 'MyJob' has been executed. Then, a second action will be run to trigger the 'MyRecurringJob' recurring job after the original job 'MyJob' has been executed.
The Order
value should only be incremented for job actions that have the same Timing
value:
INSERT [job].[JobActions] ([JobName],[Action],[Order],[Timing],[IsEnabled],[Database])
VALUES ('MyJob', 'EXECUTE [dbo].[MyStoredProc]', 1, 'BEFORE', 1, 'MyOtherDatabase')
GO
INSERT [job].[JobActions] ([JobName],[Action],[Order],[Timing],[IsEnabled],[Database])
VALUES ('MyJob', 'EXECUTE [dbo].[MyOtherStoredProc]', 2, 'BEFORE', 1, 'MyOtherDatabase')
GO
INSERT [job].[JobActions] ([JobName],[Action],[Order],[Timing],[IsEnabled])
VALUES ('MyJob', 'recurring-job:MyRecurringJob', 1, 'AFTER', 1)
GO
INSERT [job].[JobActions] ([JobName],[Action],[Order],[Timing],[IsEnabled])
VALUES ('MyJob', 'recurring-job:MyOtherRecurringJob', 2, 'AFTER', 1)
GO
The recurring-job:
prefix is required on the action in order to execute an existing recurring job. The format of the action should be recurring-job:{RecurringJobName}
. By default, Job Actions will be executed against the database configured in the ConnectionString
property of the settings.json
file.
NOTE: IRecurringJobFactory & RecurringJobFactory are now obsolete in MIFCore.Hangfire v1.1.0
The Hangfire.IRecurringJobManager
interface should be used when registering your recurring jobs in MIFCore. When a new job is registered, the IRecurringJobManager
will check for any CRON overrides specified in the settings.json
and create or update your job in Hangfire.
recurringJobManager.CreateRecurringJob<MyRecurringJob>("MyJobName", y => y.RunMyJob(), Cron.Daily());
Set the triggerIfNeverExecuted
parameter to true if you need Hangfire to trigger the job if it has not been run previously:
recurringJobManager.CreateRecurringJob<MyRecurringJob>("MyJobName", y => y.RunMyJob(), Cron.Daily(), triggerIfNeverExecuted: true);
The CRON schedule for a job can be overriden in the settings.json
file by adding a property with the job name as the key and the CRON string as the value:
{
"MyJobName": "0 */3 * * *"
}
If an override is added while the application is running, restart the application for the changes to take effect.
The BatchContextFilter
is a global IClientFilter/IServerFilter that can be added during the startup of your application. When a job is executed with the BatchContextFilter
hangfire filter active, a new Guid will be stored in an Id
batch parameter and the current Utc date will be stored in a Started
batch parameter against the context of the job. You can also add custom batch parameters during execution of the job using the SetBatchParameter
method in the BackgroundJobContext
.
For any nested jobs that are queued during the execution of this initial job, batch parameters can be accessed by using the BackgroundJobContext:
public class MyRecurringJobType
{
private readonly IBackgroundJobClient backgroundJobClient;
public MyRecurringJobType(IBackgroundJobClient backgroundJobClient)
{
this.backgroundJobClient = backgroundJobClient;
}
public void RootJob(IBackgroundJobClient backgroundJobClient)
{
BackgroundJobContext.Current.SetBatchParameter("MyCustomBatchParameter", "ParameterValue");
this.backgroundJobClient.Enqueue<MyRecurringJobType>(y => y.NestedJob1());
}
public void NestedJob1()
{
var batchId = BackgroundJobContext.Current.GetBatchParameter<Guid>("Id");
var started = BackgroundJobContext.Current.GetBatchParameter<DateTime>("Started");
var myValue = BackgroundJobContext.Current.GetBatchParameter<string>("MyCustomBatchParameter");
this.backgroundJobClient.Enqueue<MyRecurringJobType>(y => y.NestedJob2());
}
public void NestedJob2()
{
var batchId = BackgroundJobContext.Current.GetBatchParameter<Guid>("Id");
var started = BackgroundJobContext.Current.GetBatchParameter<DateTime>("Started");
var myValue = BackgroundJobContext.Current.GetBatchParameter<string>("MyCustomBatchParameter");
}
}
The RescheduleJobByDateFilter
is an IElectStateFilter that provides the ability for you to reschedule a job for a specified date without incrementing the RetryCount for the job. In order to trigger this filter, a RescheduleJobException
must be thrown during execution of the job. The RescheduleJobException
has a rescheduleDate
parameter that allows you to specify the date that the job should be retried.
var rescheduleDate = DateTime.UtcNow.AddMinutes(5);
throw new RescheduleJobException(rescheduleDate);
The BackgroundJobContext filter is a global IServerFilter that retrieves and stores the current PerformContext of a job each time it is executed. The PerformContext allows you to access the Hangfire Job Storage, BackgroundJob object and perform a number of other tasks e.g. adding/retrieving parameters during execution of a job:
public void MyRecurringJobMethod()
{
BackgroundJobContext.Current.SetJobParameter("MyString", "test");
var myString = BackgroundJobContext.Current.GetJobParameter<string>("MyString");
}
The DisableIdenticalQueuedItems
attribute ensures that a job is not executed while a previous instance of the job is still running.
The attribute works by creating a SHA384 hash or "fingerprint" and storing it in the IStorageConnection
of the job context. The fingerprint comprises of:
- Job Type Name
- Job Method Name
- Parameter values passed to the method
When the IClientFilter.OnCreating(CreatingContext filterContext) method is invoked, MIFCore will attempt to add a fingerprint to the connnection and will cancel the job if an existing fingerprint is found.
If populated, the optional FingerprintTimeoutMinutes
parameter will determine how long execution of the job is locked for.
[DisableIdenticalQueuedItems(FingerprintTimeoutMinutes = 10)]
public void MyRecurringJobMethod()
{
var lastSuccessDate = BackgroundJobContext.Current.BackgroundJob.GetLastSuccess();
}
By default, jobs that fail will be excluded from this filter however they can be included by setting the IncludeFailedJobs
parameter to true:
[DisableIdenticalQueuedItems(FingerprintTimeoutMinutes = 10, IncludeFailedJobs = true)]
public void MyRecurringJobMethod()
Each time a job with the TrackLastSuccess
attribute is successfully executed, the current date time will be stored as a parameter on the job. To access the last successful run date, use the BackgroundJob.GetLastSuccess()
extension method in the BackgroundJobContext
to retrieve the "LastSuccess" value.
[TrackLastSuccess]
public void MyRecurringJobMethod()
{
var lastSuccessDate = BackgroundJobContext.Current.BackgroundJob.GetLastSuccess();
}