Author • Georgi Marokov

Setup SonarCloud analysis with ASP.NET Core and React SPA in GitLab CI

  • Tools
  • Dotnet
  • SonarCloud
  • Static Code Analysis
  • React

Introduction

SonarCloud is a well-known cloud based tool for static code analysis that supports most of the popular programming languages - JavaScript, TypeScript, Python, C#, Java and more. The tool is also known as SonarQube which is the self hosted version of the analyzer. SonarCloud is completely free for public repositories and SonarQube is even open sourced. These features make it my go-to tool for static code analysis and in this post I'll try to show you how to use with by building a demo project with ASP.NET Core and React single page application.

This post is the second part from the series for static code analysis for .NET applications. In the previous post we explored what static code analysis is and introduced some well-known tools for the job. If you missed that post you can check it out here.

In this post you will find:

  • An overview of the different source control management platforms in SonarCloud.
  • Available options for analyzing your ASP.NET Core SPA app.
  • Build a pipeline in GitLab.

I will use React for the demo, but you can use any framework you prefer. Whether it's React, Angular, Vue, or another, the process remains the same, with only variations in build or test commands.

Shall we begin? Let's dive in!

Different source control management platforms

SonarCloud supports popular source control management (SCM) platforms such as GitHub, GitLab, Bitbucket, and Azure DevOps. While the platforms differ, they share the commonality of declarative YAML pipeline execution.

It's essential to note that SonarCloud provides two scanners: one for .NET projects and one for everything else. The good news is that the dedicated .NET scanner can also analyze files from your frontend app, including JavaScript, TypeScript, CSS, and HTML files.

Let's briefly explore the platforms and focus on setting up SonarCloud in GitLab from scratch.

GitHub

If you use GitHub, you're likely already familiar with GitHub Actions, which makes the setup relatively straightforward. SonarCloud generates the pipeline setup for you. However, if you prefer to use other CI tools like Circle CI or Travis CI, you'll need to set up the dotnet-sonarscanner yourself. Keep an eye on the Build pipeline in GitLab section, as it provides a relevant scenario.

BitBucket

BitBucket doesn't yet support apps targeting .NET Framework directly, but you can use containers for this purpose. SonarCloud doesn't offer ready-to-use templates for .NET Core projects on BitBucket, so you'll need to install and configure everything manually.

Azure DevOps

Azure DevOps is well-integrated with SonarCloud, and the setup is straightforward. First, install the SonarCloud extension from the Visual Studio Marketplace. Then, follow the comprehensive guide, which mainly involves using the GUI builder for configuration.

GitLab

Setting up SonarCloud in GitLab is quite similar to the BitBucket setup. We'll cover a full setup for GitLab later in this post.

Local (Manually)

You have two options for local setups:

  • VSCode Extension: Use the Sonar Dotnet extension in VSCode, which allows you to analyze code directly from the editor. The setup is GUI-based, and the reports are pushed to SonarCloud.
  • CLI: To use the CLI, you'll need the .NET SDK, Java, and the scanner installed. Run the commands from the CI setup directly in the terminal. Refer to the official docs for requirements.

Available options for analysis

When analyzing a combined single-page application, you have two options:

Option 1: Analyze frontend and backend together

The dedicated .NET scanner can scan JS, TS, HTML, CSS, and other frontend files. To include frontend files, add them to your .csproj using a wildcard, as shown below:

<ItemGroup>
    <!-- Don't publish the SPA source files, but do show them in the project files list -->
    <Content Remove="Frontend\**" />
    <None Remove="Frontend\**" />
    <None Include="Frontend\**" Exclude="Frontend\node_modules\**" />
</ItemGroup>

If you're using .NET Core 3.1 or above, the default template includes frontend files in your ASP.NET Core project in a standard way.

Option 2: Analyze frontend and backend separately

This option is suitable for monorepos containing both frontend and backend. When separate teams handle frontend and backend, this option creates two separate projects in SonarCloud. For the frontend, use the default SonarCloud analyzer.

Build pipeline in GitLab

Let's summarize what we've discussed so far and put it into action. I'll guide you through the entire setup for running SonarCloud analysis, using an example project with ASP.NET Core and React SPA. We'll set up separate scan tasks for frontend and backend.

Before we begin, create an empty .gitlab-ci.yml file in the project's root directory. You can refer to the official GitLab CI documentation here for details on the .gitlab-ci.yml file.

Frontend

We'll start by creating our frontend SonarCloud project manually. Provide a descriptive name and project key, and you're ready to go. Once setup is completed, SonarCloud will provide the SONAR_TOKEN and SONAR_HOST_URL. Make sure to add them as environment variables to GitLab CI.

Next, define variables for the CI job in gitlab-ci.yml file:

variables:
  SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"  # Defines the location of the analysis task cache
  GIT_DEPTH: "0"  # Tells git to fetch all the branches of the project, required by the analysis task

Then, define the stages of the job. In this case, we have two stages: one for the frontend and one for the backend:

stages:
  - frontend
  - backend

Next, create the frontend's actual stage definition with the following task. You can add more tasks to a stage if needed, but for this example, we'll stick to just one:

frontend.build.test.analyze:
  stage: frontend
  image:
    name: sonarsource/sonar-scanner-cli:latest
    entrypoint: [""]
  cache:
    key: "${CI_JOB_NAME}"
    paths:
      - .sonar/cache
  script:
    - cd Frontend
    - npm install
    - npm run build
    - npm test
    - sonar-scanner
        -Dsonar.projectKey=sonar.example.frontend
        -Dsonar.organization=gmarokov-1
        -Dsonar.sources=src
        -Dsonar.exclusions="/node_modules/**,/build/**,**/__tests__/**"
        -Dsonar.tests=src
        -Dsonar.test.inclusions=**/__tests__/**
        -Dsonar.javascript.lcov.reportPaths="coverage/lcov.info"
        -Dsonar.testExecutionReportPaths="reports/test-report.xml"
  only:
    - merge_requests
    - main
    - tags

This task involves several steps:

frontend.build.test.analyze: This is the name of the job.
stage: frontend: Specifies the stage to which this task belongs. We defined this stage earlier.
image: We use a Docker image with sonar-scanner-cli pre-installed for the job.
cache: We specify a cache to avoid downloading the image every time we run the job.
script: The script contains the steps to prepare and analyze the frontend code. It installs dependencies, builds, tests, and runs the SonarScanner. Note that tests are run with coverage report and special jest-sonar-reporter in the package.json which converts test result data to Generic Test Data which is one of the supported formats by SonarCloud.
Make sure to replace the Sonar parameters (-D) with your project-specific details.
More about the parameters can be found here: https://docs.sonarqube.org/latest/analysis/analysis-parameters/
only: This job runs on merge requests, the main branch, and tags.

With this setup, the frontend analysis is ready to go. Now, let's move on to the backend.

Backend

To set up the backend scan, you'll need to create another project manually in SonarCloud. Since we already have an environment variable with the name SONAR_TOKEN, you can save the token for this project as SONAR_TOKEN_BACKEND, for example. We'll manually provide this token in the GitLab CI job.

The backend scan task is a bit different, as we'll use the dedicated .NET scanner. Here's the task for the backend:

backend.build.test.analyze:
  stage: backend
  image: gmarokov/sonar.dotnet:5.0
  script:
   - dotnet sonarscanner begin
        /k:"sonar.example.backend" /o:"gmarokov-1"
        /d:sonar.login="$SONAR_TOKEN_BACKEND"
        /d:sonar.host.url="$SONAR_HOST_URL"
        /d:sonar.exclusions="**/Migrations/**, /Frontend"
        /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml"
        /d:sonar.sources="/Backend/Backend.Api"
        /d:sonar.tests="/Backend/Backend.Api.Tests"
        /d:sonar.testExecutionReportPaths="SonarTestResults.xml"
   - dotnet build Backend/Backend.sln
   - dotnet test Backend/Backend.sln --logger trx /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:ExcludeByFile="**/Migrations/*.cs%2CTemplates/**/*.cshtml%2Ccwwwroot/%2C**/*.resx"
   - dotnet-trx2sonar -d ./ -o ./Backend/SonarTestResults.xml
   - dotnet sonarscanner end /d:sonar.login="$SONAR_TOKEN_BACKEND"
  only:
    - branches
    - master
    - tags

Let's break down this task:

image: gmarokov/sonar.dotnet:5.0 We use the gmarokov/sonar.dotnet:5.0 Docker image, which contains the .NET SDK, Java runtime, SonarDotnet, and Dotnet-Trx2Sonar global tools.

The image can be found on DockerHub which looks like this:

*# Image with Dotnet SDK, Java runtime,* SonarDotnet, Dotnet-Trx2Sonar *dotnet tools*
FROM mcr.microsoft.com/dotnet/sdk:5.0-focal
ENV PATH="$PATH:/root/.dotnet/tools"

*# Install Java Runtime*
RUN apt-get update
RUN apt install default-jre -y

*# Install SonarCloud dotnet tool*
RUN dotnet tool install --global dotnet-sonarscanner

# Install Trx2Sonar converter dotnet tool
RUN dotnet tool install --global dotnet-trx2sonar

After we have our Docker image pulled, the script section performs several tasks, including setting up the SonarScanner for .NET projects, building the backend solution, running tests, and converting test results to a format compatible with SonarCloud. Make sure to replace the SonarCloud parameters (/d) with your project-specific details.

Something to note is the following suspicious parameter: /p:ExcludeByFile="**/Migrations/*.cs%2CTemplates/**/*.cshtml%2Ccwwwroot/%2C**/*.resx"

The reason we use encoded values here is the PowerShell parser which fails to parse the comma as separator.

On another side note is the dotnet-trx2sonar tool which will help us to convert .trx files (Visual Studio Test Results File) generated by XUnit to Generic Test Data which is the expected format by SonarCloud. This will give us the ability to browse the tests in SonarCloud UI. More on the tool can be find on GitHub here.

With this setup, our pipeline is ready to run SonarCloud analysis during every CI build. You can also add badges to indicate the SonarCloud analysis status directly in your GitLab repository. You can find the full demo project on GitLab here.

Conclusion

The benefits of static code analysis are significant, and the setup can be straightforward. While fast delivery is essential, static code analysis enhances it by making your delivery process more predictable, secure, and stable. It helps catch common pitfalls and violations early in the development process, whether developers are writing code or committing changes.

If you haven't used static code analysis tools before, you now have no excuses not to start.

Resources

https://codeburst.io/code-coverage-in-net-core-projects-c3d6536fd7d7 https://community.sonarsource.com/t/coverage-test-data-generate-reports-for-c-vb-net/9871 https://dotnetthoughts.net/static-code-analysis-of-netcore-projects/