Building Seamless CI/CD Pipelines in C# with ADotNet — Fluent Edition
In the world of DevOps, defining CI/CD pipelines has become a critical part of modern software development. However, many developers often find themselves bogged down by YAML’s syntax and limitations. If you’re a .NET developer, wouldn’t it be great to define your pipelines using a strongly-typed, fluent C# API instead of YAML?
That’s where ADotNet comes in. ADotNet is an innovative .NET library that allows developers to define Azure DevOps and GitHub Actions pipelines in C#, enabling a cleaner and more productive experience.
In this article, we will explore the ADotNet library and compare how pipelines are defined with and without its powerful fluent API.
ADotNet was initially developed by Hassan Habib, who envisioned a better way for .NET developers to create CI/CD pipelines without the complexities of YAML. Recognizing its potential, the library was later extended and enhanced by the Standard Community, bringing in collaborative ideas, advanced features, and robust support for real-world scenarios.
The Challenge with YAML
Imagine you need to define a GitHub Actions pipeline that builds, tests, and deploys your .NET application. Here’s how you would typically do it in YAML:
YAML Implementation
name: Build & Test Pipeline
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
name: Build
runs-on: windows-latest
steps:
- name: Check Out
uses: actions/checkout@v2
- name: Setup Dot Net Version
uses: actions/setup-dotnet@v1
with:
dotnet-version: 9.0.101
include-prerelease: true
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
While YAML is effective, it can be difficult to write, debug, and maintain. Its lack of strong typing makes it prone to syntax errors, and remembering exact commands or parameters can be cumbersome.
Introducing ADotNet’s Fluent API
ADotNet transforms pipeline creation by allowing developers to define pipelines in C#, leveraging the benefits of strong typing, IntelliSense, and reusable code. Let’s see how the same pipeline is defined using ADotNet’s fluent API (v4.1.0 or later).
Fluent API Implementation
GitHubPipelineBuilder.CreateNewPipeline()
.SetName("Build and Test")
.OnPush("main")
.OnPullRequest("main")
.AddJob("build", job => job
.WithName("Build")
.RunsOn(BuildMachines.UbuntuLatest)
.AddCheckoutStep("Checkout Code")
.AddSetupDotNetStep(
version: "6.0",
stepName: "Setup .NET")
.AddRestoreStep() // default step name is Restore
.AddBuildStep() // default step name is Build
.AddTestStep()) // default step name is Test
.SaveToFile(".github/workflows/pipeline.yml");
Key Benefits of the Fluent API
- Strong Typing: Compile-time validation ensures fewer errors.
- Readability: The fluent API is easier to read and write compared to YAML.
- IntelliSense Support: Developers get auto-completion and documentation directly in their IDE.
- Reusability: Pipeline components can be reused or dynamically generated based on project needs.
Example Comparison
To highlight the benefits, here’s a side-by-side comparison of YAML and ADotNet:
ADotNet Fluent API Example
job.AddRestoreStep()
.AddBuildStep()
.AddTestStep();
When the above C# code is executed, it generates the following YAML:
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
Why You Should Consider ADotNet
- Productivity: Spend less time debugging YAML syntax and more time building features.
- Maintainability: Pipelines written in C# are easier to refactor and version-control.
- Flexibility: Define pipelines dynamically based on project configurations or user input.
Getting Started with ADotNet
To start using ADotNet, install the NuGet package:
dotnet add package ADotNet
Or
Install-Package ADotNet
Then, define your pipelines using the fluent API and save them as YAML files:
var pipeline = GitHubPipelineBuilder.CreateNewPipeline()
.SetName("My Custom Pipeline")
.OnPush("main")
.AddJob("build", job => job
.WithName("Build")
.RunsOn(BuildMachines.UbuntuLatest)
.AddRestoreStep()
.AddBuildStep());
pipeline.SaveToFile(".github/workflows/pipeline.yml");
Adding Custom Job Steps
One of the most powerful features of ADotNet is its ability to define custom steps in your pipeline. For example, you might want to provision resources or execute a custom command during your build process. Here’s how you can add a custom job step:
var pipeline = GitHubPipelineBuilder.CreateNewPipeline()
.SetName("Custom Job Pipeline")
.OnPush("main")
.AddJob("custom-job", job => job
.WithName("Custom Job")
.RunsOn(BuildMachines.UbuntuLatest)
.AddGenericStep(
name: "Provision Resources",
runCommand: "dotnet run --project ./Provision/Provision.csproj")
.AddTestStep());
pipeline.SaveToFile(".github/workflows/custom-pipeline.yml");
When this code is executed, it generates the following YAML:
name: Custom Job Pipeline
on:
push:
branches:
- main
jobs:
custom-job:
runs-on: ubuntu-latest
steps:
- name: Provision Resources
run: dotnet run --project ./Provision/Provision.csproj
- name: Test
run: dotnet test --no-build --verbosity normal
Conclusion
ADotNet bridges the gap between the simplicity of YAML and the power of C#. By adopting a fluent API, it empowers .NET developers to define pipelines in a way that is intuitive, maintainable, and less error-prone. If you’re tired of wrestling with YAML, give ADotNet a try and redefine how you build CI/CD pipelines.
Start building smarter pipelines today with ADotNet!