Creating Checks
A plugin consists of many checks. A check is used to see if a user's machine passes a requirement for the plugin or current project.
The simplest form of check is a class that implements the ICheck
interface.
public interface ICheck
{
string Name { get; }
Task Run();
}
- The
Name
property is a friendly identifier that will be shown to users. - The
Run
method is called when the check gets executed.
Once a check is written, it needs to be registered with your plugin using the CreateCheck
extension method on the plugin's IServiceCollection
.
public class MyPlugin : IGetSetUpPlugin
{
public void ConfigureServices(IConfiguration configuration, IServiceCollection services)
{
services.CreateCheck<MyCheck>();
}
}
Check are run in the order that they are registered in, not the order of their respective config values. This allows plugin creators to control the order in which checks are run.
Success or Failure
A check 'passes' if it completes without throwing an exception. Throw a CheckFailureException
to indicate that your check has failed.
Resolving a Failed Check
Determining that a user's machine is not correctly set up is only half the battle. The real power of GetSetUp is resolving failed checks automatically so that a user doesn't need to fix things manually.
Other Types of Checks
More often than not you'll want to use more specific types of checks so that you have finer control over when and how they run.
Conditional Checks
Conditional checks are those which only execute if a certain condition is met. Usually this will be based on the config file of the current project, but checks can be optionally run based on anything, from machine architecture to the current time.
To create a conditional check, implement the IConditionalCheck
interface.
public interface IConditionalCheck : ICheck
{
Task<bool> ShouldRun();
}
This requires the same implementation as ICheck
but also requires you to implement the ShouldRun
method.
If this method returns false
then the check will be silently skipped.
For example, to conditionally run a check based on a value in the current project's config file, inject your plugin's config into the check.
public class MyConditionalCheck : IConditionalCheck
{
private readonly MyPluginConfig _config;
public MyCheck(IOptions<MyPluginConfig> config)
{
_config = config.Value;
}
public string Name => "Example Conditional Check";
public Task<bool> ShouldRun() => Task.FromResult(_config.ShouldRunMyCheck);
public Task Run() => throw new NotImplementedException();
}
Optional Checks
Optional checks are those which can be skipped by the user. This is useful for checks that are not essential to the running of the project, but might be required for certain features.
To create an optional check, implement the IOptionalCheck
interface.
public interface IOptionalCheck : ICheck
{
string Description { get; }
}
This requires the same implementation as ICheck
but also requires you to implement the Description
property.
This is used to describe the check to the user when they are prompted to optionally run it.
Checks that implement both IConditionalCheck
and IOptionalCheck
will have their IConditionalCheck
condition evaluated before
the user is prompted to optionally run the check.
Dependency Injection
Checks are able to leverage the full power of .NET Core's dependency injection system .
The IServiceCollection
that is passed to the ConfigureServices
method of your plugin can be used to register services to be injected
into one of more checks. This not only helps you manage your checks as they grow in size, but it also helps to keep them unit testable.
// #1 Create your service
public class DateTimeService
{
public DateTime Now => DateTime.Now;
}
public class MyPlugin : IGetSetUpPlugin
{
public void ConfigureServices(IConfiguration configuration, IServiceCollection services)
{
// #2 Register your service with your plugin's service collection
services.AddSingleton<DateTimeService>();
services.CreateCheck<OnlyOnTuesdaysCheck>();
}
}
public class OnlyOnTuesdaysCheck : IOptionalCheck
{
private readonly DateTimeService _dateTimeService;
// #3 Inject your service into your check
public OnlyOnTuesdaysCheck(DateTimeService dateTimeService)
{
_dateTimeService = dateTimeService;
}
public string Name => "Example Check";
// #4 Use your service in methods like `ShouldRun` and `Run`
public Task<bool> ShouldRun() => Task.FromResult(_dateTimeService.Now.DayOfWeek == DayOfWeek.Tuesday);
public Task Run() => throw new NotImplementedException();
}
Service Lifetimes
Services can be registered with one of the following lifetimes, each with their own respective method call on the IServiceCollection
:
- Transient
- Scoped
- Singleton
Transient
Transient lifetime services are created each time they're requested from the service container.
This lifetime works best for lightweight, stateless services.
Register transient services with serviceCollection.AddTransient<TransientService>()
.
Scoped
Scoped lifetime services are created once per ICheck
instance.
This lifetime works best for services that are stateful where the state needs to be shared across multiple services all used within a single check.
Register scoped services with serviceCollection.AddScoped<ScopedService>()
.
Singleton
Singleton lifetime services are created once per IGetSetUpPlugin
instance.
This lifetime works best for services that are stateful where the state needs to be shared across multiple services across different checks
within the same plugin.
Register singleton services with serviceCollection.AddSingleton<SingletonService>()
.
IOptions<T>
When you created your plugin, you specified a ConfigurationSectionName
field.
This is used as a key to retrieve the configuration section for your plugin from the current project's config file.
In the ConfigureServices
method of your plugin, you can bind options models to this IConfiguration
section.
// #1 Create your options model
public class MyPluginsOptions
{
public bool ShouldRunMyCheck { get; set; }
}
public class MyPlugin : IGetSetUpPlugin
{
// #2 Specify the name of your config section
public string ConfigurationSectionName => "MyPlugin";
public void ConfigureServices(IConfiguration configuration, IServiceCollection services)
{
// #3 Bind your options model to the config section
services.AddOptions<MyPluginsOptions>().Bind(configuration);
services.CreateCheck<MyCheck>();
}
}
public class MyCheck : IConditionalCheck
{
private readonly MyPluginsOptions _options;
// #4 Inject your options model into your check
public MyCheck(IOptions<MyPluginsOptions> options)
{
_options = options.Value;
}
public string Name => "Example Check";
public Task<bool> ShouldRun() => Task.FromResult(_options.ShouldRunMyCheck);
public Task Run() => throw new NotImplementedException();
}