> For the complete documentation index, see [llms.txt](https://developer.harness.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.harness.io/continuous-integration/3.0/troubleshooting-and-resources/tutorials-and-code-samples/ci-dotnet.md).

# C# (.NET Core)

You can build and test a [C#](https://learn.microsoft.com/en-us/dotnet/csharp/tour-of-csharp/) and [.NET Core](https://learn.microsoft.com/en-us/dotnet/core/introduction) application using a Linux or Windows platform on [Harness Cloud](/continuous-integration/3.0/use-harness-ci/use-harness-ci/set-up-build-infrastructure/use-harness-cloud-build-infrastructure.md) or a [self-managed Kubernetes cluster](/continuous-integration/3.0/use-harness-ci/use-harness-ci/set-up-build-infrastructure/k8s-build-infrastructure.md) build infrastructure.

This guide assumes you've [created a Harness CI pipeline](/continuous-integration/3.0/use-harness-ci/use-harness-ci/prep-ci-pipeline-components.md).

### Specify architecture <a href="#specify-architecture" id="specify-architecture"></a>

You can use a Linux or Windows platform to build and test C# (.NET Core) apps. These examples use Linux build infrastructure.

{% tabs %}
{% tab title="Harness Cloud" %}

```yaml
stages:
  - stage:
      name: build
      identifier: build
      type: CI
      spec:
        cloneCodebase: true
        platform:
          os: Linux
          arch: Amd64 ## Can be Amd64 or Arm64
        runtime:
          type: Cloud
          spec: {}
```

{% endtab %}

{% tab title="Self-managed" %}
There are several self-managed build infrastructure options. This example uses a [Kubernetes cluster build infrastructure](/continuous-integration/3.0/use-harness-ci/use-harness-ci/set-up-build-infrastructure/k8s-build-infrastructure.md).

```yaml
stages:
  - stage:
      name: build
      identifier: build
      description: ""
      type: CI
      spec:
        cloneCodebase: true
        infrastructure:
          type: KubernetesDirect
          spec:
            connectorRef: YOUR_KUBERNETES_CLUSTER_CONNECTOR_ID
            namespace: YOUR_NAMESPACE
            automountServiceAccountToken: true
            nodeSelector: {}
            os: Linux
```

{% endtab %}
{% endtabs %}

### Install dependencies <a href="#install-dependencies" id="install-dependencies"></a>

{% tabs %}
{% tab title="Harness Cloud" %}
The .NET Core SDK and other .NET libraries are pre-installed on Harness Cloud runners. For details about all available tools and versions, go to [Platforms and image specifications](/continuous-integration/3.0/use-harness-ci/use-harness-ci/set-up-build-infrastructure/use-harness-cloud-build-infrastructure.md#platforms-and-image-specifications). You can use [Run steps](/continuous-integration/3.0/use-harness-ci/use-harness-ci/run-step-settings.md) to install additional dependencies or run `dotnet restore`.

```yaml
- step:
    type: Run
    identifier: dependencies
    name: Dependencies
    spec:
      shell: Sh
      command: |-
        dotnet add package Newtonsoft.json --version 12.0.1
```

{% endtab %}

{% tab title="Self-managed" %}
You can use [Run steps](/continuous-integration/3.0/use-harness-ci/use-harness-ci/run-step-settings.md) to install dependencies or run commands such as `dotnet restore`.

```yaml
- step:
    type: Run
    identifier: dependencies
    name: Dependencies
    spec:
      connectorRef: YOUR_IMAGE_REGISTRY_CONNECTOR
      image: mcr.microsoft.com/dotnet/sdk:7.0
      command: |-
        dotnet add package Newtonsoft.json --version 12.0.1
```

{% endtab %}
{% endtabs %}

### Cache dependencies <a href="#cache-dependencies" id="cache-dependencies"></a>

Add caching to your Build (`CI`) stage.

{% tabs %}
{% tab title="Cache Intelligence" %}
Cache your .NET dependencies with [Cache Intelligence](/continuous-integration/3.0/use-harness-ci/use-harness-ci/caching-ci-data/cache-intelligence.md).

Add caching to your `stage.spec`:

```yaml
- stage:
    spec:
      caching:
        enabled: true
        key: cache-{{ checksum "packages.lock.json" }}
        paths:
          - "~/.local/share/NuGet/cache"
      sharedPaths:
        - ~/.local/share/NuGet/cache
```

{% endtab %}

{% tab title="Save and Restore Cache steps" %}
You can use built-in steps to:

* [Save and Restore Cache from S3](/continuous-integration/3.0/use-harness-ci/use-harness-ci/caching-ci-data/saving-cache.md)
* [Save and Restore Cache from GCS](/continuous-integration/3.0/use-harness-ci/use-harness-ci/caching-ci-data/save-cache-in-gcs.md)

<details>

<summary>YAML example: Save and restore cache steps</summary>

Here's an example of a pipeline with **Save Cache to S3** and **Restore Cache from S3** steps.

```yaml
            steps:
              - step:
                  type: RestoreCacheS3
                  name: Restore Cache From S3
                  identifier: Restore_Cache_From_S3
                  spec:
                    connectorRef: YOUR_AWS_CONNECTOR_ID
                    region: us-east-1
                    bucket: YOUR_S3_BUCKET
                    key: cache-{{ checksum "packages.lock.json" }}
                    archiveFormat: Tar
              - step:
                  type: Run
                  ...
              - step:
                  type: BuildAndPushDockerRegistry
                  ...
              - step:
                  type: SaveCacheS3
                  name: Save Cache to S3
                  identifier: Save_Cache_to_S3
                  spec:
                    connectorRef: YOUR_AWS_CONNECTOR_ID
                    region: us-east-1
                    bucket: YOUR_S3_BUCKET
                    key: cache-{{ checksum "packages.lock.json" }}
                    sourcePaths:
                      - ~/.local/share/NuGet/cache
                    archiveFormat: Tar
```

</details>
{% endtab %}
{% endtabs %}

### Build and run tests <a href="#build-and-run-tests" id="build-and-run-tests"></a>

Add [Run steps](/continuous-integration/3.0/use-harness-ci/use-harness-ci/run-step-settings.md) to build and run your tests.

{% tabs %}
{% tab title="Harness Cloud" %}

```yaml
- step:
    type: Run
    identifier: build_dotnet_app
    name: Build DotNet App
    spec:
      shell: Sh
      command: |-
        dotnet restore
        dotnet build --no-restore
        dotnet test --no-build --verbosity normal
```

{% endtab %}

{% tab title="Self-managed" %}

```yaml
- step:
    type: Run
    identifier: build_dotnet_app
    name: Build DotNet App
    spec:
      connectorRef: YOUR_IMAGE_REGISTRY_CONNECTOR
      image: mcr.microsoft.com/dotnet/sdk:6.0
      shell: Sh
      command: |-
        dotnet restore
        dotnet build --no-restore
        dotnet test --no-build --verbosity normal
```

{% endtab %}
{% endtabs %}

#### Visualize test results <a href="#visualize-test-results" id="visualize-test-results"></a>

You can [view test results](/continuous-integration/3.0/use-harness-ci/use-harness-ci/run-tests/viewing-tests.md) on the **Tests** tab of your pipeline executions. Harness supports JUnit XML and TRX test report formats.

For .NET projects, you can generate TRX reports natively using `dotnet test -l:trx` without needing a converter. Alternatively, you can use a converter to output JUnit XML reports, such as [NUnit to JUnit](https://github.com/nunit/nunit-transforms/tree/master/nunit3-junit) or [.NET trx2JUnit](https://github.com/gfoidl/trx2junit).

For your pipeline to produce test reports, you need to modify the **Run** step that runs your tests. Make sure the `command` generates test reports and add the `reports` specification.

{% tabs %}
{% tab title="Harness Cloud" %}

```yaml
- step:
    type: Run
    identifier: install_converter
    name: install converter
    spec:
      shell: Sh
      command: |-
        dotnet tool install -g trx2junit
        export PATH="$PATH:/root/.dotnet/tools"
- step:
    type: Run
    identifier: build_dotnet_app
    name: Build DotNet App
    spec:
      shell: Sh
      command: |-
        dotnet restore
        dotnet build
        dotnet test --no-build --verbosity normal
        trx2junit results.trx
      reports:
        type: JUnit
        spec:
          paths:
            - results.xml
```

{% endtab %}

{% tab title="Self-managed" %}

```yaml
- step:
    type: Run
    identifier: install_converter
    name: install converter
    spec:
      connectorRef: YOUR_IMAGE_REGISTRY_CONNECTOR
      image: mcr.microsoft.com/dotnet/sdk:6.0
      shell: Sh
      command: |-
        dotnet tool install -g trx2junit
        export PATH="$PATH:/root/.dotnet/tools"
- step:
    type: Run
    identifier: build_dotnet_app
    name: Build DotNet App
    spec:
      connectorRef: YOUR_IMAGE_REGISTRY_CONNECTOR
      image: mcr.microsoft.com/dotnet/sdk:6.0
      shell: Sh
      command: |-
        dotnet restore
        dotnet build
        dotnet test --no-build --verbosity normal
        trx2junit results.trx
      reports:
        type: JUnit
        spec:
          paths:
            - results.xml
```

{% endtab %}
{% endtabs %}

#### Run tests with Test Intelligence <a href="#run-tests-with-test-intelligence" id="run-tests-with-test-intelligence"></a>

[Test Intelligence](/continuous-integration/3.0/use-harness-ci/use-harness-ci/run-tests/ti-overview.md) is available for C# (.NET Core); however, it is behind the feature flag `TI_DOTNET`. Contact [Harness Support](mailto:support@harness.io) to enable the feature.

With this feature flag enabled, you can use **Run Tests** steps to [run unit tests with Test Intelligence on C# codebases](/continuous-integration/3.0/use-harness-ci/use-harness-ci/run-tests/ti-overview.md).

{% tabs %}
{% tab title="Harness Cloud" %}

```yaml
- step:
    type: RunTests
    identifier: runTestsWithIntelligence
    name: runTestsWithIntelligence
    spec:
      language: Csharp
      buildEnvironment: Core
      frameworkVersion: "6.0"
      buildTool: Dotnet
      args: --no-build --verbosity normal ## Equivalent to 'dotnet test --no-build --verbosity normal' in a Run step or shell.
      namespaces: aw,fc
      runOnlySelectedTests: true
      preCommand: |-
        dotnet tool install -g trx2junit
        export PATH="$PATH:/root/.dotnet/tools"
        dotnet restore
        dotnet build
      postCommand: trx2junit results.trx
      reports:
        type: JUnit
        spec:
          paths:
            - results.xml
```

{% endtab %}

{% tab title="Self-managed" %}

```yaml
- step:
    type: RunTests
    identifier: runTestsWithIntelligence
    name: runTestsWithIntelligence
    spec:
      connectorRef: YOUR_IMAGE_REGISTRY_CONNECTOR
      image: mcr.microsoft.com/dotnet/sdk:6.0
      language: Csharp
      buildEnvironment: Core
      frameworkVersion: "6.0"
      buildTool: Dotnet
      args: --no-build --verbosity normal ## Equivalent to 'dotnet test --no-build --verbosity normal' in a Run step or shell.
      namespaces: aw,fc
      runOnlySelectedTests: true
      preCommand: |-
        dotnet tool install -g trx2junit
        export PATH="$PATH:/root/.dotnet/tools"
        dotnet restore
        dotnet build
      postCommand: trx2junit results.trx
      reports:
        type: JUnit
        spec:
          paths:
            - results.xml
```

{% endtab %}
{% endtabs %}

#### Test splitting <a href="#test-splitting" id="test-splitting"></a>

Harness CI supports [test splitting (parallelism)](/continuous-integration/3.0/use-harness-ci/use-harness-ci/run-tests/speed-up-ci-test-pipelines-using-parallelism.md) for both **Run** and **Run Tests** steps.

### Specify version <a href="#specify-version" id="specify-version"></a>

{% tabs %}
{% tab title="Harness Cloud" %}
The .NET SDK is pre-installed on Hosted Cloud runners. For details about all available tools and versions, go to [Platforms and image specifications](/continuous-integration/3.0/use-harness-ci/use-harness-ci/set-up-build-infrastructure/use-harness-cloud-build-infrastructure.md#platforms-and-image-specifications).

If you need a specific .NET Core SDK version that isn't already installed, you can use a **Run** step to install it.

<details>

<summary>Install one .NET SDK version</summary>

```yaml
- step:
    type: Run
    name: Install dotnet
    identifier: install_dotnet
    spec:
      shell: Sh
      command: |-
        curl -fsSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 8.0
        export DOTNET_WORKSPACE=$HOME/.dotnet
        export PATH=$DOTNET_WORKSPACE:$PATH
        dotnet --info
```

</details>

<details>

<summary>Install multiple .NET SDK versions</summary>

1. Add the [matrix looping strategy](/harness-ai/use-harness-platform/pipelines/looping-strategies/looping-strategies-matrix-repeat-and-parallelism.md) configuration to your stage.

```yaml
strategy:
  matrix:
    dotnetVersion:
      - "8.0"
      - "6.0"
```

2. Reference the matrix variable in your steps.

```yaml
- step:
    type: Run
    name: Install dotnet
    identifier: install_dotnet
    spec:
      shell: Sh
      command: |-
        curl -fsSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel <+matrix.dotnetVersion>
        export DOTNET_WORKSPACE=$HOME/.dotnet
        export PATH=$DOTNET_WORKSPACE:$PATH
        dotnet --info
```

</details>
{% endtab %}

{% tab title="Self-managed" %}
Specify the desired [.NET SDK image](https://mcr.microsoft.com/en-us/product/dotnet/framework/sdk/tags) tag in your steps. There is no need for a separate install step when using Docker.

<details>

<summary>Use one .NET SDK version</summary>

```yaml
- step:
    type: Run
    name: dotnet version
    identifier: dotnet_version
    spec:
      connectorRef: YOUR_IMAGE_REGISTRY_CONNECTOR
      image: mcr.microsoft.com/dotnet/sdk:7.0
      shell: Sh
      command: |-
        dotnet --info
```

On Windows platforms, you might also need to [install Microsoft Build Tools into the container](https://learn.microsoft.com/en-us/visualstudio/install/build-tools-container?view=vs-2019).

</details>

<details>

<summary>Use multiple .NET SDK versions</summary>

1. Add the [matrix looping strategy](/harness-ai/use-harness-platform/pipelines/looping-strategies/looping-strategies-matrix-repeat-and-parallelism.md) configuration to your stage.

```yaml
- stage:
    strategy:
      matrix:
        dotnetVersion:
          - 7.0
          - 6.0
```

2. Reference the matrix variable in the `image` field of your steps.

```yaml
- step:
    type: Run
    name: dotnet Version
    identifier: dotnet_version
    spec:
      connectorRef: YOUR_IMAGE_REGISTRY_CONNECTOR
      image: mcr.microsoft.com/dotnet/sdk:<+stage.matrix.dotnetVersion>
      shell: Sh
      command: |-
        dotnet --info
```

On Windows platforms, you might also need to [install Microsoft Build Tools into the container](https://learn.microsoft.com/en-us/visualstudio/install/build-tools-container?view=vs-2019).

</details>
{% endtab %}
{% endtabs %}

### Full pipeline examples <a href="#full-pipeline-examples" id="full-pipeline-examples"></a>

The following full pipeline examples are based on the partial examples above.

{% tabs %}
{% tab title="Harness Cloud" %}
If you copy this example, replace the placeholder values with appropriate values for your [code repo connector](/continuous-integration/3.0/use-harness-ci/use-harness-ci/codebase-configuration/create-and-configure-a-codebase.md#code-repo-connectors) and repository name. Depending on your project and organization, you may also need to replace `projectIdentifier` and `orgIdentifier`.

<details>

<summary>YAML example</summary>

```yaml
pipeline:
  name: default
  identifier: default
  projectIdentifier: default
  orgIdentifier: default
  properties:
    ci:
      codebase:
        connectorRef: YOUR_CODE_REPO_CONNECTOR_ID
        repoName: YOUR_REPO_NAME
        build: <+input>
  tags: {}
  stages:
    - stage:
        name: build
        identifier: build
        description: ""
        type: CI
        spec:
          cloneCodebase: true
          caching:
            enabled: true
            key: cache-{{ checksum "packages.lock.json" }}
            paths:
              - "~/.local/share/NuGet/cache"
          execution:
            steps:
              - step:
                  type: Run
                  identifier: dependencies
                  name: Dependencies
                  spec:
                    shell: Sh
                    command: |-
                      dotnet add package Newtonsoft.json --version 12.0.1
              - step:
                  type: Run
                  identifier: install_converter
                  name: install converter
                  spec:
                    shell: Sh
                    command: |-
                      dotnet tool install -g trx2junit
                      export PATH="$PATH:/root/.dotnet/tools"
              - step:
                  type: Run
                  identifier: build_dotnet_app
                  name: Build DotNet App
                  spec:
                    shell: Sh
                    command: |-
                      dotnet restore
                      dotnet build
                      dotnet test --no-build --verbosity normal
                      trx2junit results.trx
                    reports:
                      type: JUnit
                      spec:
                        paths:
                          - results.xml
          platform:
            os: Linux
            arch: Amd64
          runtime:
            type: Cloud
            spec: {}
          sharedPaths:
            - ~/.local/share/NuGet/cache
```

</details>
{% endtab %}

{% tab title="Self-managed" %}
If you copy this example, replace the placeholder values with appropriate values for your [code repo connector](/continuous-integration/3.0/use-harness-ci/use-harness-ci/codebase-configuration/create-and-configure-a-codebase.md#code-repo-connectors), [Kubernetes cluster connector](/harness-ai/use-harness-platform/connectors/cloud-providers/add-a-kubernetes-cluster-connector.md), Kubernetes namespace, and repository name. Depending on your project and organization, you may also need to replace `projectIdentifier` and `orgIdentifier`.

<details>

<summary>YAML example</summary>

```yaml
pipeline:
  name: default
  identifier: default
  projectIdentifier: default
  orgIdentifier: default
  properties:
    ci:
      codebase:
        connectorRef: YOUR_CODE_REPO_CONNECTOR_ID
        repoName: YOUR_REPO_NAME
        build: <+input>
  tags: {}
  stages:
    - stage:
        name: build
        identifier: build
        description: ""
        type: CI
        spec:
          cloneCodebase: true
          infrastructure:
            type: KubernetesDirect
            spec:
              connectorRef: YOUR_KUBERNETES_CLUSTER_CONNECTOR_ID
              namespace: YOUR_NAMESPACE
              automountServiceAccountToken: true
              nodeSelector: {}
              os: Linux
          execution:
            steps:
              - step:
                  type: RestoreCacheS3
                  name: Restore Cache From S3
                  identifier: Restore_Cache_From_S3
                  spec:
                    connectorRef: YOUR_AWS_CONNECTOR_ID
                    region: us-east-1
                    bucket: YOUR_S3_BUCKET
                    key: cache-{{ checksum "packages.lock.json" }}
                    archiveFormat: Tar
              - step:
                  type: Run
                  name: dotnet version
                  identifier: dotnet_version
                  spec:
                    connectorRef: YOUR_IMAGE_REGISTRY_CONNECTOR
                    image: mcr.microsoft.com/dotnet/sdk:7.0
                    shell: Sh
                    command: |-
                      dotnet --info
              - step:
                  type: Run
                  identifier: dependencies
                  name: Dependencies
                  spec:
                    connectorRef: YOUR_IMAGE_REGISTRY_CONNECTOR
                    image: mcr.microsoft.com/dotnet/sdk:7.0
                    command: |-
                      dotnet add package Newtonsoft.json -- version 12.0.1
              - step:
                  type: Run
                  identifier: install_converter
                  name: install converter
                  spec:
                    connectorRef: YOUR_IMAGE_REGISTRY_CONNECTOR
                    image: mcr.microsoft.com/dotnet/sdk:7.0
                    shell: Sh
                    command: |-
                      dotnet tool install -g trx2junit
                      export PATH="$PATH:/root/.dotnet/tools"
              - step:
                  type: Run
                  identifier: build_dotnet_app
                  name: Build DotNet App
                  spec:
                    connectorRef: YOUR_IMAGE_REGISTRY_CONNECTOR
                    image: mcr.microsoft.com/dotnet/sdk:7.0
                    shell: Sh
                    command: |-
                      dotnet restore
                      dotnet build
                      dotnet test --no-build --verbosity normal
                      trx2junit results.trx
                    reports:
                      type: JUnit
                      spec:
                        paths:
                          - results.xml
              - step:
                  type: SaveCacheS3
                  name: Save Cache to S3
                  identifier: Save_Cache_to_S3
                  spec:
                    connectorRef: YOUR_AWS_CONNECTOR_ID
                    region: us-east-1
                    bucket: YOUR_S3_BUCKET
                    key: cache-{{ checksum "packages.lock.json" }}
                    sourcePaths:
                      - ~/.local/share/NuGet/cache
                    archiveFormat: Tar
```

</details>
{% endtab %}
{% endtabs %}

### Next steps <a href="#next-steps" id="next-steps"></a>

Now that you have created a pipeline that builds and tests a C# (.NET Core) app, you could:

* Create [triggers](/harness-ai/use-harness-platform/triggers.md) to automatically run your pipeline.
* Add steps to [build and upload artifacts](/continuous-integration/3.0/use-harness-ci/use-harness-ci/build-and-upload-artifacts.md).
* Add a step to [build and push an image to a Docker registry](/continuous-integration/3.0/use-harness-ci/use-harness-ci/build-and-upload-artifacts/build-and-push/build-and-push-to-docker-registry.md).
