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
commands
folder namedHelloWorldCommand.csx
andUsageCommand.csx
for convenience. You can refer these when adding more commands which contains your own logic. -
Execute the following command to view usage:
Windows:
run
Linux/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.csx
to 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.csx
inside thecommands
folder and rename the file toYourCommand.csx
. Note thatYourCommand
can be anything else you like here. I just used that for simplicity's sake. -
Open
YourCommand.csx
and rename the class and constructor names toYourCommand
-
Get rid of the code inside the
ExecuteAsync
method and add your own logic. -
Remove any unnecessary
using
statements 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.csx
and loadYourCommand.csx
file 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
ConfigureCommands
method 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-command
Linux/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
run
command like so:Windows:
set "DSX_ENVIRONMENT=ye" && run your-command arg1 arg2 arg3
Linux/MacOS:
DSX_ENVIRONMENT=ye ./run your-command arg1 arg2 arg3
-
These will be available to
YourCommand
via theargs
parameter (string array) of theExecuteAsync
method.
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.csx
and locate theConfigureServices
method - Register your dependency similar to how you would typically do it in ASP.Net using
IServiceCollection
parameter 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.json
and place your settings as a json object. - Change the provided
AppSettings
class to match. - Inject the
AppSettings
type 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.json
as follows:
{
"ConnectionStrings": {
"ConnectionString1": "Default Connection String"
}
}
- Add a environment specific configuration file titled
appsettings.ye.json
and override the same like so:
{
"ConnectionStrings": {
"ConnectionString1": "Your Environment Connection String"
}
}
-
Set the environment variable
DSX_ENVIRONMENT
to the valueye
and runYourCommand
Windows:
set "DSX_ENVIRONMENT=ye" && run your-command arg1 arg2 arg3
Linux/MacOS:
DSX_ENVIRONMENT=ye ./run your-command arg1 arg2 arg3
-
Thats it!. Now when you inject
AppSettings
orIConfiguration
intoYourCommand
, theye
environment specific value should be available inside theConnectionString1
property.
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-build
Linux/MacOS:
./docker-bash-build
-
Start a container based on the image you just built with the following command.
Windows:
docker-bash-run
Linux/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
/app
directory. 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-build
Linux/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 arg3
Linux/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!