DotnetScriptX is a project template / starter kit for the dotnet-script tool. It provides the following additional features:
✔️ Loosely opinionated project template for writing and maintaining multiple asynchronous scripts with a default option for single script mode if you feel like it
✔️ Full blown Dependency Injection for scripting
✔️ Bootstrapper batch / bash scripts to execute script commands with automatic detection and installation of the dotnet-script tool
✔️ JSON based Application Configuration for scripts with environment specific overrides (e.g.: appsettings.prod.json)
✔️ Script Execution Context with Current Command Name, Script Path, Environment (Dev, Prod), OS detection helpers, and a configuration driven Serilog ILogger conveniently pre-registered for DI
✔️ Docker Support! Run or package your scripts inside a linux container with just a couple of commands.
-
Download the latest project template (Source code archive) from the releases section.
-
Extract the downloaded archive and open a command prompt / terminal inside the extracted folder. The template includes two sample commands in the
commandsfolder namedHelloWorldCommand.csxandUsageCommand.csxfor convenience. You can refer these when adding more commands which contains your own logic. -
Execute the following command to view usage:
Windows:
runLinux/MacOS:
./run -
Seriously that's it! The aforementioned command will execute the default script command wired up for execution in
Startup.csx. It will also try to detect and install dotnet-script if not installed already. Tip: You may refer thecommands/UsageCommand.csxto see how it works.
As you may have already guessed, the DotnetScriptX template allows you to write and maintain multiple scripts in the same project folder in the form of commands (IScriptCommand implementations)
To create a new command all you have to do is implement the IScriptCommand interface and register it inside the ConfigureCommands method of the Startup.csx file
-
Just make a copy of the already provided
HelloWorldCommand.csxinside thecommandsfolder and rename the file toYourCommand.csx. Note thatYourCommandcan be anything else you like here. I just used that for simplicity's sake. -
Open
YourCommand.csxand rename the class and constructor names toYourCommand -
Get rid of the code inside the
ExecuteAsyncmethod and add your own logic. -
Remove any unnecessary
usingstatements and#r nuget:references. Note that rules for writing scripts with dotnet-script apply here. DSX does not introduce any additional paradigms. -
Now open
Startup.csxand loadYourCommand.csxfile into it by adding#load "commands/YourCommand.csx"directive at the top of the file.
#load "dsx/IScriptCommandCollection.csx"
#load "commands/HelloWorldCommand.csx"
#load "commands/UsageCommand.csx"
#load "commands/YourCommand.csx"
- Register your command in the
ConfigureCommandsmethod like so:commands.Register<YourCommand>("your-command");
public void ConfigureCommands(IScriptCommandCollection commands)
{
commands.RegisterDefault<UsageCommand>();
commands.Register<UsageCommand>("help");
commands.Register<HelloWorldCommand>("hello-world");
commands.Register<YourCommand>("your-command");
}
-
Congrats! you just implemented your first command. To run it, execute the following command:
Windows:
run your-commandLinux/MacOS:
./run your-command
Note that inside the Startup.csx's ConfigureCommands method, you can specify a default command to get executed when run is executed without any arguments. This is done using the commands.RegisterDefault method like so:
public void ConfigureCommands(IScriptCommandCollection commands)
{
commands.RegisterDefault<YourCommand>();
}
As in the example above, if you register YourCommand as the default command, when run is executed without parameters, YourCommand will get invoked. This is a good option if all you need is a quick way to execute a single script with a simple run command.
-
You can pass command-line arguments to your commands with the
runcommand like so:Windows:
set "DSX_ENVIRONMENT=ye" && run your-command arg1 arg2 arg3Linux/MacOS:
DSX_ENVIRONMENT=ye ./run your-command arg1 arg2 arg3 -
These will be available to
YourCommandvia theargsparameter (string array) of theExecuteAsyncmethod.
Users may mistakenly issue commands which are not registered from time to time. You can handle them by specifying an IScriptCommand implementation type to be executed in such cases via the ConfigureCommands method of Startup.csx
public void ConfigureCommands(IScriptCommandCollection commands)
{
commands.RegisterDefault<UsageCommand>();
commands.Register<UsageCommand>("help");
commands.Register<YourCommand>("your-command");
commands.OnCommandNotFoundAsync = (commandName) =>
{
//Resolves the command type to be executed when
//users specify command names that are not registered
return Task.FromResult(typeof(UsageCommand));
};
}
The OnCommandNotFoundAsync delegate makes the actual command name the user entered available as a method parameter.
As mentioned previously, DI is already setup and ready to use via Microsoft.Extensions.DependencyInjection. To use this, register all your dependencies as follows:
- Open
Startup.csxand locate theConfigureServicesmethod - Register your dependency similar to how you would typically do it in ASP.Net using
IServiceCollectionparameter like so:
public void ConfigureServices(IServiceCollection services)
{
var appSettings = configuration.Get<AppSettings>();
services.AddSingleton<AppSettings>(appSettings);
}
Registered services would be automatically injected into your IScriptCommand implementation constructor like so:
public YourCommand(
AppSettings appSettings,
ILogger logger,
IExecutionContext context
)
{
this.appSettings = appSettings;
this.logger = logger;
this.context = context;
}
Note that certain services such as IExecutionContext are pre-registered for convenience.
You can manage configuration settings using appsettings.json files in the usual way with support for environment specific overrides as well. To add a setting,
- Edit the provided
appsettings.jsonand place your settings as a json object. - Change the provided
AppSettingsclass to match. - Inject the
AppSettingstype into your command as a constructor parameter. Voila! Type safe application settings for your script!
public class YourCommand
{
private readonly AppSettings appSettings;
public YourCommand(AppSettings appSettings)
{
this.appSettings = appSettings;
}
}
Alternatively you can access the underlying IConfiguration object directly by injecting it into YourCommand too
public class YourCommand
{
private readonly IConfiguration config;
public YourCommand(IConfiguration config)
{
this.config = config;
}
}
You can also maintain environment specific configuration override files using this feature. Let's say you have an environment called YourEnvironment. (We'll use ye for short)
- Your typical default settings may or may not reside in the base
appsettings.jsonas follows:
{
"ConnectionStrings": {
"ConnectionString1": "Default Connection String"
}
}
- Add a environment specific configuration file titled
appsettings.ye.jsonand override the same like so:
{
"ConnectionStrings": {
"ConnectionString1": "Your Environment Connection String"
}
}
-
Set the environment variable
DSX_ENVIRONMENTto the valueyeand runYourCommandWindows:
set "DSX_ENVIRONMENT=ye" && run your-command arg1 arg2 arg3Linux/MacOS:
DSX_ENVIRONMENT=ye ./run your-command arg1 arg2 arg3 -
Thats it!. Now when you inject
AppSettingsorIConfigurationintoYourCommand, theyeenvironment specific value should be available inside theConnectionString1property.
Note that environment specific configuration files for common environments such as prod, qa, etc. are included for convenience.
Contextual information about the current script being executed can be obtained by injecting the IExecutionContext service into YourCommand
public class YourCommand
{
private readonly IExecutionContext context;
public YourCommand(IExecutionContext context)
{
this.context = context;
}
}
| IExecutionContext Member | Description |
|---|---|
CommandName |
Returns the current script command name |
ScriptEnvironment |
Returns the script environment under which the script is currently running. (The value specified by the DSX_ENVIRONMENT environment variable) |
IsOSPlatform(OSPlatform platform) |
Allows checking whether the current OS is one of the System.Runtime.InteropServices.OSPlatform values |
GetScriptFilePath() |
Returns the current script command file path |
GetScriptFilePath() |
Returns the current script command file path |
You need to have docker installed (doh). There are two ways you can run your commands using docker.
-
Using the bash prompt inside a container (DockerfileBash):
Quick Image Build -> Slow Initial Command Execution -> Fast Subsequent Command Executions. This approach is great if you are in development mode and quickly need a bash prompt to test out your changes without building a docker image and running it each time. -
Package everything into a self contained docker image (DockerfilePack):
Slow Image Build -> Super Fast Command Execution. This approach is a good fit for production environment where you want to build a docker image containing all your scripts and dependencies and deploy it into docker swarm, etc.
-
Run the following command. This will build a docker image with the dotnet-script tool installed.
Windows:
docker-bash-buildLinux/MacOS:
./docker-bash-build -
Start a container based on the image you just built with the following command.
Windows:
docker-bash-runLinux/MacOS:
./docker-bash-run -
You will be placed inside the bash prompt of the container. The above command maps your current project folder to the containers
/appdirectory. From there you can run dsx in the usual manner mentioned previously. For e.g.:./run hello-world arg1 arg2 arg3
-
Run the following command. This will build a docker image including a copy of all the files in your current project directory inside it. It will also compile your scripts, publish them within the container and install the dotnet-script tool to ensure fast command execution.
Windows:
docker-pack-buildLinux/MacOS:
./docker-pack-build -
You can run a short lived container per command in this mode. Commands and optionally arguments are specified as follows:
Windows:
docker-pack-run your-command arg1 arg2 arg3Linux/MacOS:
./docker-pack-run your-command arg1 arg2 arg3
Feel free to edit the included dockerfiles DockerfileBash and DockerfilePack along with the corresponding bat / bash scripts to achieve what you need. E.g.: You can mount host directories as docker volumes inside the container to have access to the host file system.
MIT
Hope this helps. If you find this useful don't forget the to spread the word!