# Share a .NET Coverage Report — ReportGenerator's Whole Folder, One Link

Canonical: https://commareports.com/share-dotnet-coverage-report
Published: 2026-08-28

> `dotnet test` gives you Cobertura XML; ReportGenerator turns it into a browsable HTML tree. Publish the tree to Comma — drill-down intact, one stable URL per branch, comments on the uncovered lines.

# Share a .NET coverage report

The pipeline is three steps, and only the last one produces something a
human reads:

```bash
dotnet test --collect:"XPlat Code Coverage"
# → TestResults/<guid>/coverage.cobertura.xml   (data, not a report)

dotnet tool install -g dotnet-reportgenerator-globaltool
reportgenerator \
  -reports:"**/coverage.cobertura.xml" \
  -targetdir:"coveragereport" \
  -reporttypes:Html
```

What you get is `coveragereport/` — `index.html`, a page per class with
the hit counts in the gutter, `report.css`, `main.js` and icons. The
drill-down _is_ the report, and it's the first thing to die when someone
attaches `index.html` to a message.

## Publish the directory

Drag `coveragereport/` (or a zip of it) into
[Comma](https://commareports.com/):

- `index.html` becomes the **report body**.
- Every per-class page and the CSS/JS upload alongside, and the relative
  links between them are rewritten to the uploaded copies — so clicking
  from the summary into `OrderService` still works.
- The sortable summary table keeps working: scripts run inside a
  sandboxed iframe with no same-origin access. See
  [interactive HTML reports](/interactive-html-reports).

Nothing needs a local web server, which is the other reason these
reports usually stay on the machine that generated them — see
[serving an HTML file locally](/serve-html-file-locally).

## From CI

```bash
reportgenerator -reports:"**/coverage.cobertura.xml" \
  -targetdir:coveragereport -reporttypes:Html

curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
  -H "Authorization: Bearer $COMMA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile html coveragereport/index.html \
        --arg title "Coverage — $GITHUB_SHA" '{title: $title, html: $html}')"
```

One report id per branch keeps one URL and appends a revision per run.
The interesting question about coverage is never the number, it's the
direction — and revisions answer that without buying a dashboard. See
[publishing from CI](/docs/ci) and the
[GitHub Actions](/ci/github-actions-html-report) or
[Azure DevOps](/ci/azure-devops-html-report) walkthroughs.

If your pipeline already uses `-reporttypes:HtmlInline_AzurePipelines`
for the built-in tab, keep it — add the plain `Html` output alongside and
publish that, so the report survives the artifact retention window.

## What review adds

- **Anchored threads** on an uncovered branch, in the source view —
  [commenting on HTML](/comment-on-html).
- **Revisions**, so "coverage dropped" is a diff rather than an
  assertion.
- **Access per report** — private, team, domain-gated or link-based. See
  [sharing & access control](/docs/sharing).

## Limits

- **Entry HTML: 5 MB.** Assets: 25 MB per file, 250 MB and 500 files
  total. A large solution can exceed 500 class pages — ReportGenerator's
  `-classfilters:` and `-assemblyfilters:` keep the published report to
  the part actually under review.
- **60 requests/minute per token.**

## Try it

Comma is free — unlimited reports, unlimited commenters, unlimited
revision history.

**[Publish a coverage report →](https://commareports.com/)**

### Related

- [Share an lcov / genhtml report](/share-lcov-report) · [Share a JaCoCo report](/share-jacoco-report)
- [Share an htmlcov coverage report](/share-coverage-report) · [Share a TestNG report](/share-testng-report)
- [Publish from CI](/docs/ci)
