Skip to content

Creating Simple Tasks in .NET with Bullseye

Sponsor: Using RabbitMQ or Azure Service Bus in your .NET systems? Well, you could just use their SDKs and roll your own serialization, routing, outbox, retries, and telemetry. I mean, seriously, how hard could it be?

Learn more about Software Architecture & Design.
Join thousands of developers getting weekly updates to increase your understanding of software architecture and design concepts.


How many times have you created a console application to run specific tasks in .NET?  These tasks could be processing a file, making some HTTP call to an external service or even helping in your build process.  Ultimately ending up with a collection of different tasks which often have dependencies on each other. I just discovered a project called Bullseye by Adam Ralph which really feels like a simple task runner.  But better yet, is just a library you add to your own console application and not its own process itself.

Bullseye

Bullseye is a .NET package for describing and running targets and their dependencies. Bullseye can be used to write targets that do anything. It is not coupled to building .NET projects. Platform support: .NET Standard 1.3 and upwards.

Examples

The simplest example which is described on the Bullseye readme is similar to as follows:
using System;
using static Bullseye.Targets;
namespace bulltest
{
class Program
{
static void Main(string[] args)
{
Target("default", () => {
Console.WriteLine("My Default Task!");
});
RunTargets(args);
}
}
}
view raw defaultTask.cs hosted with ❤ by GitHub
There’s really only two components needed.  Define your target(s) using the Target() methods and then specify which targets to run via RunTargets() If you do not specify a target to run then the “default” target is used. If you simply run dotnet run will produce the output: This would have been the equivalent of running dotnet run default

Dependencies

Here’s a simple example of having one target depend on another.  In one target I’m hitting a service to get the exchange rate from CAD to USD.  In another, I want to save that rate to a file.
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using Newtonsoft.Json;
using static Bullseye.Targets;
namespace bulltest
{
class Program
{
private static decimal _exchangeRate;
private static HttpClient _httpClient = new HttpClient();
static void Main(string[] args)
{
Target("getexchangerate", async () => {
var response = await _httpClient.GetStringAsync("https://api.exchangeratesapi.io/latest?base=CAD&symbols=USD");
var data = JsonConvert.DeserializeObject<ApiData>(response);
_exchangeRate = data.Rates["USD"];
Console.WriteLine($"Rate: {_exchangeRate}");
});
Target("saveexchangerate", DependsOn("getexchangerate"), async () => {
File.WriteAllText("currentRate.txt", _exchangeRate.ToString());
});
RunTargets(args);
}
}
public class ApiData
{
public string Base {get;set;}
public DateTime Date {get;set;}
public Dictionary<string, decimal> Rates {get;set;}
}
}
view raw dependsOn.cs hosted with ❤ by GitHub
When run with dotnet run saveexchangerate Now if I just want to get the rate but not save it, I could just run dotnet run getexchangerate

Targets

How are you handling running small tasks/targets?  Simple console applications?  Using the new global tools? Let me know on Twitter or in the comments.

Learn more about Software Architecture & Design.
Join thousands of developers getting weekly updates to increase your understanding of software architecture and design concepts.