DEV Community

loading...

Analyze ASP.NET Core with React SPA in SonarCloud

Georgi Marokov
FullStack developer, who is really into resolving complex issues, dive deep in to the problem and finding elegant solution. I spend my free time with family and friends, riding bikes and hiking peaks.
Originally published at worldwildweb.dev ・8 min read

SonarCloud is well known cloud based tool for Static Code Analysis which supports most of the popular programming languages - JavaScript, TypeScript, Python, C#, Java and counting. 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 characteristics make it my go-to tool for static code analysis for this project - setup SonarCloud for ASP.NET Core with React single page application.

This post is the second part from the series for Static Code Analysis for .NET Core projects. In the previous post we learned what Static Code Analysis is and introduced well known tools for the job. If you missed that post you can check it out here.

The agenda for today is:

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

I will use React for the demo, but you can use whatever framework you need for the job. React/Angular/Vue or any other - it doesn't really matter, the flow stays the same, only the build or test running commands may differ.

Shall we begin? Let's deep dive!

Different source control management platforms

SonarCloud works with the most popular SCM platforms - GitHub, GitLab, BitBucket and Azure DevOps. Different platforms but the declarative yml pipeline execution is what they all have in common.

Good to know is that SonarCloud provides 2 scanners - 1 for Dotnet projects and 1 for everything else. The good news is that the dedicated Dotnet scanner can also analyze files from your frontend app - JavaScript, TypeScript, CSS and HTML files.

Lets quickly go over the platforms and focus on GitLab with a full blown setup from scratch.

GitHub

If you are using GitHub there is huge chance that you are already using GitHub Actions.

This is the easiest setup because SonarCloud generates pipeline setup for you. Of course you can use other CI tools as Circle CI, Travis CI or any other but you have to setup the dotnet-sonarscanner yourself. Check the Build pipeline in GitLab section as it has pretty relevant scenario.

BitBucket

Before going into BitBucket beware that the platform (not yet?) supports apps targeting .NET Framework directly, but of course you can always use containers for the purpose.

SonarCloud doesn't provide any ready to go templates for .NET Core projects and BitBucket's pipeline. You still need to install and configure everything yourself.

Azure DevOps

I read somewhere that dotnet-sonarscanner was developed with the partnership of Microsoft so no wonder the best integration with SonarCloud is with the famous Azure DevOps platform.

To enable SonarCloud in your pipelines first you need to install SonarCloud extension from Visual Studio marketplace and then follow the super descriptive guide which mostly involved clicking and can be easily accomplished with the GUI builder.

GitLab

Nothing differs from the BitBucket setup. Later in the post comes full setup in GitLab.

Local (Manually)

  • Using the VSCode extension Sonar Dotnet gives you the ability to directly analyze from the editor. All the setup is through the GUI and reports are pushed to SonarCloud.
  • Using the CLI - To use the CLI you must have .NET SDK, Java and the scanner installed and run the commands from the CI setup directly in the terminal. Check the requirements in the official docs.

Available options for analysis

On the road to analyze the combined single page application, we have two paths we can choose to take.

Option 1: Analyze frontend and backend at once

The dedicated scanner for .NET projects possess the power to also scan JS, TS, HTML, CSS etc. files. We only need to include frontend's files with wildcard in the .csproj as follows:

<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>
Enter fullscreen mode Exit fullscreen mode

Or if you are using .NET Core 3.1 and above, the default template includes the frontend in your ASP.NET Core project in a common way.

Option 2: Analyze frontend and backend separately

This option is useful when you have a monorepo with your backend and frontend in it, but they have a separate startup process or even different teams working on them. This option will require to create a 2 separate projects in SonarCloud. The option will also require to use the default SonarCloud analyzer for your frontend.

Build pipeline in GitLab

Let's recap everything we discussed so far and put it to work. To cover most of the cases for setuping SonarCloud analysis, I will try to walk you through the whole setup with a example project from the ASP.NET Core with React SPA sample with a separate scan tasks for frontend and backend.

Before we start lets create empty .gitlab-ci.yml file in the root directory.

For GitLab CI file reference checkout official docs: https://docs.gitlab.com/ee/ci/yaml/gitlab_ci_yaml.html

Frontend

Starting with the creation of our frontend Sonar project which needs to be done manually. Just throw some descriptive name and a project key and you are ready to go. Once done, Sonar will provide SONAR_TOKEN and SONAR_HOST_URL. Make sure to add them as Environment variables.

Next step is to define the variables for the CI job:

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
Enter fullscreen mode Exit fullscreen mode

After that comes the stage definitions of the job. In this case we will have two - one for frontend and one for the backend:

stages:
  - frontend
  - backend
Enter fullscreen mode Exit fullscreen mode

Create the frontend's actual stage definition with the following task. You can have as many task for a stage as you like but we will 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
    - master
    - tags
Enter fullscreen mode Exit fullscreen mode

A lot is happening in this task so lets walkthrough:

frontend.build.test.analyze

The name of the job, its up to you to give it a descriptive name

stage: frontend

The name of the stage which this task belongs to. Must be predefined which we did above.

image: # We can use existing docker images 
    name: sonarsource/sonar-scanner-cli:latest
    entrypoint: [""] 
Enter fullscreen mode Exit fullscreen mode

Here we specify a Docker image which comes with sonar-scanner-cli preinstalled. This Scanner CLI is used for all languages except for Dotnet as I mentioned above.

cache:
    key: "${CI_JOB_NAME}"
    paths:
      - .sonar/cache 
Enter fullscreen mode Exit fullscreen mode

We specify the cache and not download the image every time we run the job. This should be good.

script:
    - cd Frontend
    - npm install
    - npm run build
    - npm test
Enter fullscreen mode Exit fullscreen mode

Nothing fancy here, regular npm stuff but 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.

 - 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"
Enter fullscreen mode Exit fullscreen mode

Here comes the actual scan. Required parameters are projectKey, organization and the early added SONAR_TOKEN and SONAR_HOST_URL which are taken from the env variables.

Then comes the configuration of the source directories, directories to exclude, test directories and the paths to the generated reports for coverage and test execution.

More about the parameters can be found here: https://docs.sonarqube.org/latest/analysis/analysis-parameters/

And our frontend is good to go. Coming next is the backend.

Backend

For the backend another project needs to be created manually. Since we already have environment variable with the name of SONAR_TOKEN, you can save the token for this project as SONAR_TOKEN_BACKEND for example. We will manually provide it anyway.

When it comes to the backend scan, it will be a little different since we will use the dedicated scanner for Dotnet.

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
Enter fullscreen mode Exit fullscreen mode

Let's walkthrough the whole task:

image: gmarokov/sonar.dotnet:5.0

Again Docker image which will be used to spin a container on which we will execute our task. This image have Dotnet 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
Enter fullscreen mode Exit fullscreen mode

You might spot the following suspicious parameter:

/p:ExcludeByFile="**/Migrations/*.cs%2CTemplates/**/*.cshtml%2Ccwwwroot/%2C**/*.resx"

That's because of the the underling PowerShell parser fails to parse the comma as separator so we need to use encoded value.

dotnet-trx2sonar -d ./ -o ./Backend/SonarTestResults.xml

The dotnet-trx2sonar tool will help us to convert .trx files (Visual Studio Test Results File) generated by Xunit to Generic Test Data which is the format specified by SonarCloud. The converted file will help us to browse the tests in SonarCloud UI.

Anddd that's it! Pipeline is ready to go and provide analyzes on every CI run. I also added some nice badges to indicate the SonarCloud analysis status directly in the repo.

The full demo project can found on GitLab here.

Conclusion

Benefits of these type of analyses are enormous and setup can be dead simple. Yes, delivery is important, but static code analysis compliments it perfectly making delivery more predictable, secure and stable by catching common pitfalls and violations as early as the developer writes code or commits.

If you haven't used any static code analysis tools before, now you don't have any excuse not to!

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/

https://sonarcloud.io/documentation/analysis/scan/sonarscanner-for-msbuild/

https://sonarcloud.io/documentation/analysis/scan/sonarscanner/

Discussion (0)