Skip to content

Solution & Build Structure

This page explains how the Cargonerds repository is organised at the build level: the multiple solution files, the central package- and version-management files, the NuGet feed setup, the ABP Studio metadata, and the top-level folder layout. The goal is that you can open any .csproj, .props, .sln or .abpsln file in the repo and know what it does and why.

For the conceptual layering of the projects (Domain / Application / EntityFrameworkCore / HttpApi / hosts) see Layered Architecture; for the framework patterns underneath them see ABP Framework Patterns. This page is about the build envelope around those projects.

The big picture

Cargonerds is a layered, tiered ABP commercial application generated from the ABP app template (UI = Blazor WebAssembly, DB = SQL Server, distributed event bus = RabbitMQ, multi-tenancy on). All first-party projects target .NET 10 (net10.0) and use ABP 10.1.1, LeptonX 5.1.1, and Aspire 13.2.2 orchestration.

It is a monorepo holding the host application plus two custom ABP modules, and a separate frontend workspace:

Part Location Notes
Cargonerds (main app) src/, test/ The ABP app template host.
Hub module modules/hub/ Shipments, Organizations, Tracking. Own ConnectionString Hub.
Pricing module modules/pricing/ Pricing domain + its own Blazor WASM bundling project.
frontend/ frontend/ npm workspace (Next.js realtime app + ui-library). Not part of the .NET build.

Three mechanisms keep the build cohesive across all of that:

  1. One central package-version fileDirectory.Packages.props governs the version of every NuGet package across src/, test/, and both modules.
  2. One root common.props<Import>-ed by every first-party .csproj; it carries <Version>, LangVersion, the ABP/Aspire/LeptonX version variables, and shared MSBuild targets.
  3. Multiple solution files — each solution file describes a different slice of the same project set (root app-only, .All = everything, per-module).
flowchart TD
    CP["common.props<br/>(Version, AbpVersion, LeptonXVersion,<br/>AspireVersion, test conventions)"]
    DPP["Directory.Packages.props<br/>(~292 PackageVersion entries)"]
    NC["NuGet.Config<br/>(nuget.org + ABP commercial feed)"]

    CSPROJ["every first-party .csproj<br/>(Import common.props,<br/>versionless PackageReferences)"]

    CP -->|"&lt;Import&gt;"| CSPROJ
    DPP -->|"resolves PackageReference versions"| CSPROJ
    CP -.->|"AbpVersion/etc. variables"| DPP
    NC -->|"feed + source mapping"| CSPROJ

    CSPROJ --> SLN["Cargonerds.sln (app only)"]
    CSPROJ --> ALL["Cargonerds.All.sln / .slnx (everything)"]
    CSPROJ --> HUB["modules/hub/Hub.sln"]
    CSPROJ --> PRI["modules/pricing/Pricing.sln"]

No Directory.Build.props at the root

There is no root Directory.Build.props. The explicit <Import Project="..\..\common.props" /> line near the top of each .csproj is the deliberate equivalent — it makes the dependency on shared props visible per project rather than implicit. (The only Directory.Build.props files in the tree live inside the git-ignored abpSrc/ dumps and affect only those copies.)

Solution files: .sln vs .All vs .slnx

The same set of projects is described by several solution files for different working scopes. Pick the one that matches what you want loaded into the IDE or built on the command line.

File Format Scope Project entries
Cargonerds.sln Classic .sln (Format 12.00, VS 17) App onlysrc/ + test/ of the main app + the two Aspire projects 28 Project( lines (incl. solution folders)
Cargonerds.All.sln Classic .sln (Format 12.00, VS 18) Everything — app + Hub + Pricing 66 Project( lines
Cargonerds.All.slnx Modern XML .slnx Everything (the readable, authoritative listing) grouped under solution folders
modules/hub/Hub.sln Classic .sln Hub module only 16 Project( lines
modules/pricing/Pricing.sln Classic .sln Pricing module only 17 Project( lines

Key points:

  • Cargonerds.sln is the day-to-day app solution. It contains the main app's src/ and test/ projects plus Cargonerds.AppHost and Cargonerds.ServiceDefaults. It does not list the Hub/Pricing module projects as solution entries (those are pulled in transitively via ProjectReference, but they are not editable nodes in this solution tree).
  • Cargonerds.All.sln is the superset that additionally lists every modules/hub/** and modules/pricing/** project. This is the solution CI and the test command use — the README runs dotnet test "Cargonerds.All.sln".
  • Cargonerds.All.slnx is the new XML form of the All solution. It is the most readable listing and groups projects under solution folders such as /Cargonerds/src/, /Cargonerds/test/, /Cargonerds/aspire/, /Hub/src/, /Hub/test/, /Pricing/src/, /Pricing/test/, plus /Solution Items/ (props, README, NuGet.Config, .editorconfig, the .abpmdl/.abpsln, the .github/workflows/, and the .scripts/) and /SqlQueries/.
  • The per-module solutions surface projects the All solution does not list as separate folders — e.g. Hub.sln includes Hub.SourceGenerators, Hub.Installer, and Hub.UI; Pricing.sln includes Pricing.Blazor.WebAssembly, Pricing.Blazor.WebAssembly.Bundling, and Pricing.Installer. Use a module solution when you want to work on just that module in isolation.

Which solution should I open?

  • Working on the host app UI/API/domain → Cargonerds.sln.
  • Touching Hub or Pricing alongside the app, or running the full test suite → Cargonerds.All.slnx / Cargonerds.All.sln.
  • Hacking on one module in isolation → that module's Hub.sln / Pricing.sln.

.slnx vs .sln drift, and no app-only .slnx

The All solution exists in both classic .sln and modern .slnx form, and the two must be kept in sync by hand — adding a project to one does not update the other. The root app-only Cargonerds.sln has no .slnx twin. When you add a project, update every solution file it belongs to.

Solution membership is not the same as disk contents

Some folders on disk are not members of any solution and therefore will not build or test via the All solution: test/Cargonerds.Integration.Tests and test/Cargonerds.ServiceDefaults.Tests exist as folders but are absent from Cargonerds.All.slnx/.sln. Don't assume "it's in the repo" means "it builds with the solution."

Directory.Packages.props: central package management

Cargonerds uses NuGet Central Package Management (CPM). Package versions are declared once in the root Directory.Packages.props; individual .csproj files reference packages without a version.

The root of the file turns CPM on and disables transitive pinning:

<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
    <CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="Aspire.Hosting.AppHost" Version="$(AspireVersion)" />
    <!-- ~292 PackageVersion entries total -->
    <PackageVersion Include="Riok.Mapperly" Version="4.3.1" />
  </ItemGroup>
</Project>

There are roughly 292 <PackageVersion> entries. Note how the ABP / Aspire / LeptonX entries do not hard-code a number — they reference MSBuild variables:

  • Volo.* packages use Version="$(AbpVersion)"
  • LeptonX packages use $(LeptonXVersion)
  • Aspire packages use $(AspireVersion)
  • ABP Studio client uses $(AbpStudioVersion)

Those variables are defined in common.props, not here (see the next section). That is the single most important thing to understand about this file.

A consuming project then references the package with no version at all:

<!-- src/Cargonerds.Domain/Cargonerds.Domain.csproj -->
<PackageReference Include="Riok.Mapperly" />
<PackageReference Include="Volo.Abp.Emailing" />
<PackageReference Include="Volo.Abp.Caching" />

The version (4.3.1 for Mapperly, $(AbpVersion)10.1.1 for the Volo packages) is resolved from Directory.Packages.props. Bumping ABP across the entire repo is therefore a one-line edit to AbpVersion in common.props.

Where CPM version variables actually live

Editing $(AbpVersion) / $(LeptonXVersion) / $(AspireVersion) / $(AbpStudioVersion) means editing common.props, not Directory.Packages.props. Because every .csproj imports common.props before CPM is evaluated, those variables resolve correctly inside the <PackageVersion> entries.

Transitive pinning is off

With CentralPackageTransitivePinningEnabled=false, transitive dependencies are not auto-pinned to CPM — only your direct <PackageReference>s require a matching <PackageVersion>. A known transitive mismatch (HtmlSanitizer pinning an older AngleSharp than other Volo packages pull in) is pre-suppressed via NU1608 in common.props.

common.props: shared MSBuild properties

common.props (at the repo root) is <Import>-ed by every first-party project. It is small but carries a lot of policy. Here is the full file:

<Project>
  <PropertyGroup>
    <LangVersion>latest</LangVersion>
    <Version>3.1.4.0</Version>
    <AssemblyVersion>$(Version)</AssemblyVersion>
    <SourceRevisionId>build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss"))</SourceRevisionId>
    <!--
      CS1591 : XML comment on public members (too noisy on generated DTOs).
      NU1608 : HtmlSanitizer pins an older AngleSharp than other ABP/Volo deps pull in;
               the higher version is compatible and ships in the final WASM bundle regardless.
    -->
    <NoWarn>$(NoWarn);CS1591;NU1608</NoWarn>
    <AbpProjectType>app</AbpProjectType>
    <AbpVersion>10.1.1</AbpVersion>
    <AspireVersion>13.2.2</AspireVersion>
    <LeptonXVersion>5.1.1</LeptonXVersion>
    <AbpStudioVersion>2.2.1</AbpStudioVersion>
  </PropertyGroup>

  <PropertyGroup>
      <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>

  <PropertyGroup Condition="'$(IsTestProject)' == 'true' and '$(RunSettingsFilePath)' == ''">
    <RunSettingsFilePath>$(MSBuildThisFileDirectory)test.runsettings</RunSettingsFilePath>
  </PropertyGroup>

    <ItemGroup Condition="'$(IsTestProject)' == 'true'">
        <ProjectReference Include="$(MSBuildThisFileDirectory)\test\Cargonerds.Tests.Shared\Cargonerds.Tests.Shared.csproj" />
    </ItemGroup>
    <PropertyGroup Condition="'$(IsTestProject)' == 'true'">
        <TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
    </PropertyGroup>

  <Target Name="NoWarnOnRazorViewImportedTypeConflicts" BeforeTargets="RazorCoreCompile">
    <PropertyGroup>
      <NoWarn>$(NoWarn);0436</NoWarn>
    </PropertyGroup>
  </Target>

  <ItemGroup>
    <None Remove="**\*.abppkg" />
    <None Remove="**\*.abppkg.analyze.json" />
    <Content Remove="$(UserProfile)\.nuget\packages\*\*\contentFiles\any\*\*.abppkg*" />
  </ItemGroup>
</Project>

What it does, section by section:

  • Versioning<Version>3.1.4.0</Version> is the single app version (this is what .scripts/UpdateVersion.ps1 bumps, producing the recurring chore: bump version to ... commits). AssemblyVersion mirrors it, and SourceRevisionId stamps a UTC build timestamp.
  • ABP / Aspire / LeptonX version variablesAbpVersion, AspireVersion, LeptonXVersion, AbpStudioVersion. These are the source of truth that Directory.Packages.props references.
  • AbpProjectType=app — marks the host app for ABP tooling (the modules use module; see below).
  • CPM enabled here too (belt and braces) and warning suppressionsCS1591 (missing XML doc, noisy on generated DTOs) and NU1608. A second target appends 0436 right before RazorCoreCompile to silence Razor "imported type conflict" warnings.
  • Test-project conventions keyed on '$(IsTestProject)' == 'true':
    • points RunSettingsFilePath at the root test.runsettings,
    • auto-adds a ProjectReference to test/Cargonerds.Tests.Shared,
    • enables Microsoft.Testing.Platform (TestingPlatformDotnetTestSupport=true).
  • ABP package hygiene — removes *.abppkg / *.abppkg.analyze.json from the default None / Content globs so they don't get treated as build content.

Per-module common.props

Each module has its own common.props (modules/hub/common.props, modules/pricing/common.props). They mirror the root file but differ in a few ways:

<!-- modules/hub/common.props (excerpt) -->
<Project>
  <PropertyGroup>
    <LangVersion>latest</LangVersion>
    <Version>1.0.0</Version>
    <NoWarn>$(NoWarn);CS1591</NoWarn>
    <AbpProjectType>module</AbpProjectType>
    <AbpVersion>10.1.1</AbpVersion>
    <LeptonXVersion>5.1.1</LeptonXVersion>
    <AbpStudioVersion>2.2.1</AbpStudioVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="ConfigureAwait.Fody" PrivateAssets="All" />
    <PackageReference Include="Fody">
      <PrivateAssets>All</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>
  </ItemGroup>
  <!-- test projects point RunSettingsFilePath back to ..\..\test.runsettings -->
</Project>

Differences worth noting:

  • <AbpProjectType>module</AbpProjectType> instead of app.
  • Module-wide Fody + ConfigureAwait.Fody package references (with PrivateAssets=All) — the modules weave ConfigureAwait(false) automatically; the host app does not.
  • Test projects' RunSettingsFilePath and the Cargonerds.Tests.Shared reference point back to the root (..\..\test.runsettings, ..\..\test\...), so module tests share the app's test config.
  • <Version>1.0.0</Version> — see the gotcha below; module versions are independent of the app.

Module <Version> is independent of the app version

Hub and Pricing common.props both declare 1.0.0. Only the root common.props <Version> (3.1.4.0) is the application version and the one UpdateVersion.ps1 bumps. Don't read a module DLL's version as "the app version."

NuGet.Config: feeds and Package Source Mapping

The repo restores packages from two feeds and uses Package Source Mapping to decide which feed serves which package:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="nuget.abp.io" value="https://nuget.abp.io/1faf36a2-7070-4b97-aabc-1da41d63ed15/v3/index.json" />
  </packageSources>
  <packageSourceMapping>
    <packageSource key="nuget.org">
      <package pattern="*" />
      <package pattern="Microsoft.*" />
      <package pattern="Volo.*" />
    </packageSource>
    <packageSource key="nuget.abp.io">
      <package pattern="Volo.*" />
    </packageSource>
  </packageSourceMapping>
</configuration>
  • nuget.org serves everything (*), including Microsoft.* and — note — also Volo.*.
  • nuget.abp.io/<license-guid> is the ABP commercial feed; the GUID in the URL is the organisation's license key. It serves the commercial Volo.* (e.g. Volo.Abp.Identity.Pro, Volo.CmsKit.Pro, Volo.Saas) packages that are not on nuget.org.

Because Volo.* is mapped to both feeds, NuGet will look on nuget.org for the open-source Volo packages and fall through to the ABP feed for the commercial ones. You need access to the ABP commercial feed (a valid license) to restore the solution.

Restoring without commercial access fails fast

If dotnet restore cannot reach nuget.abp.io, the *.Pro / commercial packages will not resolve. This is expected — the build requires the ABP commercial license configured for the feed.

Local dotnet tools

.config/dotnet-tools.json pins the repo's local CLI tools, restored with dotnet tool restore:

{
  "version": 1, "isRoot": true,
  "tools": {
    "csharpier": { "version": "1.2.6", "commands": ["csharpier"], "rollForward": false },
    "husky":     { "version": "0.7.2", "commands": ["husky"] }
  }
}
  • CSharpier 1.2.6 is the C# formatter, pinned with rollForward: false so formatting output is reproducible across machines (a newer CSharpier could reformat differently and break the CI format check).
  • Husky.Net 0.7.2 drives the git hooks. The pre-commit hook runs dotnet husky run --group pre-commit (CSharpier over .cs, Prettier over staged frontend/ files) and re-stages the formatted files. For the full hook story see the Development Workflow page.

ABP Studio & CLI metadata files

ABP Studio (and the abp CLI) keep their own description of the solution alongside MSBuild's. These files drive ABP tooling — not dotnet build — so they can drift from the real build.

Cargonerds.abpmdl — main package manifest

The module manifest for the main package. It records "template": "app", the imports (installed Volo modules and the versions ABP Studio thinks are installed), the folders (src, test), and a packages map (every main-app project → its .abppkg file). It also imports the two custom modules by path:

"Hub":     { "path": "modules/hub/Hub.abpmdl",     "isInstalled": true },
"Pricing": { "path": "modules/pricing/Pricing.abpmdl", "isInstalled": true }

Each custom module has its own .abpmdl (modules/hub/Hub.abpmdl, modules/pricing/Pricing.abpmdl) mapping its Domain, Domain.Shared, Application(.Contracts), EntityFrameworkCore, HttpApi(.Client), Installer, UI, and test packages.

Cargonerds.abpsln — ABP Studio solution descriptor

The ABP Studio solution file. It carries the solution id, a versions block, the three modules, an Aspire runProfile and a Kubernetes k8sProfile, the Helm/Kubernetes commands ABP Studio exposes (helmBuildDotnetImage, helmInstallChart, kubernetesRedeploy, …, all rooted at etc/helm), the full Helm chart tree (authserver, blazor, dbmigrator, httpapihost, webpublic, plus rabbitmq/redis/sqlserver), and the original createCommand:

abp new Cargonerds -t app --tiered --ui-framework blazor --mobile react-native
  --database-provider ef --database-management-system sqlserver --theme leptonx
  --public-website ...

It also records "useLocalReferences": "false" — confirming the app consumes ABP via NuGet, not via the local source under abpSrc/.

.abpsln and .abpmdl versions are STALE — trust common.props for the real TFM/versions

The metadata files lag the real build. Cargonerds.abpsln versions says AbpFramework 10.0.0, AbpCommercial 10.0.0, LeptonX 5.0.0, TargetDotnetFramework net9.0, and the imports in Cargonerds.abpmdl list module versions like 9.3.1 / 4.3.1. The actual build is ABP 10.1.1 on net10.0 (from common.props and the <TargetFramework>net10.0</TargetFramework> in every .csproj). When in doubt about a version or the target framework, read common.props and the .csproj, not the .abpsln/.abpmdl (or the README, which still says ".NET 9").

abpSrc/: local ABP framework source

abpSrc/ holds local checkouts of ABP framework/module source — one directory per module (Volo.Abp.Account, Volo.CmsKit, Volo.Saas, …). It is produced by abpSrc/abp-get-all-src.ps1, which runs abp get-source list and then abp get-source <Module> into a folder per module.

It is git-ignored except the fetch script:

# .gitignore
/abpSrc/*
!/abpSrc/abp-get-all-src.ps1

abpSrc/ is read-only debugging material — never reference it

No first-party project references abpSrc/. The app consumes ABP via NuGet (useLocalReferences: false). abpSrc/ exists purely so you can read/step into framework internals locally. Do not add ProjectReferences into it. Each abpSrc/*/Directory.Build.props only affects those dumped trees and has no effect on the Cargonerds build.

SolutionPacker.exe

A standalone binary at the repo root (SolutionPacker.exe, dated January 2022, ~228 KB).

Orphaned — not part of the build

SolutionPacker.exe is not referenced by any .csproj, .props, git hook, or workflow. A repository-wide search for SolutionPacker returns only the binary itself. Treat it as a leftover utility, not part of packing, building, or CI.

Top-level folder layout

Folder Contents
src/ Main-app projects (Domain.Shared → … → hosts) plus Cargonerds.AppHost, Cargonerds.ServiceDefaults, Cargonerds.UI.Shared.
modules/hub/, modules/pricing/ The two custom ABP modules, each with its own src/ + test/ + .sln + common.props + .abpmdl.
test/ Main-app tests. More projects on disk than in the solutionsCargonerds.Integration.Tests and Cargonerds.ServiceDefaults.Tests are not in the solutions.
frontend/ npm workspaces root ("workspaces": ["realtime","ui-library"]). realtime is a Next.js app; ui-library a shared TS component lib. Independent of MSBuild.
etc/ Ops: abp-studio/ (run & k8s profiles), bruno/ (API client), docker/, docker-compose/, helm/ (per-service charts wired in Cargonerds.abpsln).
.scripts/ Azure/DNS deploy PowerShell + UpdateVersion.ps1 (bumps <Version> in root common.props).
.githooks/ core.hooksPath target — pre-commit formatting; install.ps1 writes the worktree excludes to .git/info/exclude.
docs/ This MkDocs site (mkdocs.yml, requirements-docs.txt, gen_releases.py).
abpSrc/ Git-ignored ABP source dumps (see above).

Main-app project graph

The host follows the canonical ABP app layering, with the two custom modules wired in by ProjectReference:

flowchart LR
    DS["Domain.Shared"]
    D["Domain"]
    AC["Application.Contracts"]
    A["Application"]
    EF["EntityFrameworkCore<br/>(CargonerdsDbContext / Default)"]
    HA["HttpApi"]
    HAC["HttpApi.Client"]

    HubD["Hub.Domain"]
    PriD["Pricing.Domain"]
    HubEF["Hub.EntityFrameworkCore"]
    PriEF["Pricing.EntityFrameworkCore"]

    AS["AuthServer"]
    HAH["HttpApi.Host"]
    WP["Web.Public"]
    BL["Blazor"]
    BLC["Blazor.Client (WASM)"]
    DBM["DbMigrator"]

    DS --> D
    AC --> D
    HubD --> D
    PriD --> D
    D --> A
    D --> EF
    HubEF --> EF
    PriEF --> EF
    A --> HA --> HAC
    EF --> HAH
    HA --> HAH
    HA --> AS
    HAC --> BL
    BL --> BLC
    EF --> DBM

Notable wiring (verified in the .csproj files):

  • Cargonerds.Domain references the modules' domain layers (Hub.Domain, Pricing.Domain, Pricing.Application.Contracts) in addition to the app's own Domain.Shared/Application.Contracts.
  • Cargonerds.EntityFrameworkCore references Hub.EntityFrameworkCore + Pricing.EntityFrameworkCore and is where CargonerdsDbContext (the Default connection) lives. The Hub module owns its own HubDbContext (Hub connection) — see Hub Module.
  • ABP package references are deliberately split across multiple ItemGroups by concern (framework infra in one group, the commercial "pro/feature modules" in another) — visible in Cargonerds.Domain.csproj and Cargonerds.EntityFrameworkCore.csproj.

For how custom modules are structured and consumed, see Module Structure.

Aspire orchestration projects

Two src/ projects belong to .NET Aspire rather than the ABP layering:

  • Cargonerds.AppHost — the Aspire host (<IsAspireHost>true</IsAspireHost>). It ProjectReferences the five runnable apps (AuthServer, Blazor, HttpApi.Host, Web.Public, DbMigrator) so Aspire can launch them, and it links shared source files instead of referencing whole projects (to avoid heavy graph pulls):
<Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0" />
...
<Compile Include="..\..\modules\hub\src\Hub.Domain.Shared\WhiteLabeling\WhiteLabelingOptions.cs"
         Link="LinkedFiles\WhiteLabelingOptions.cs" />
<Compile Include="..\Cargonerds.Domain.Shared\CargonerdsConsts.cs"
         Link="LinkedFiles\CargonerdsConsts.cs" />
  • Cargonerds.ServiceDefaults — the standard Aspire shared OpenTelemetry / health-check / service-discovery project.

AspireVersion (13.2.2) is not the AppHost SDK version (13.1.0)

These are two different knobs. The Aspire packages are pinned via $(AspireVersion)=13.2.2 in CPM, but Cargonerds.AppHost.csproj hard-codes <Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0" />. Updating one does not update the other.

See .NET Aspire Integration for the full AppHost story (linked-file pattern, ServiceDefaults, Nextended.Aspire).

How central versioning plays out in practice

  • A project never specifies a version on a <PackageReference> — the version always comes from Directory.Packages.props.
  • Bumping ABP across the whole repo is a one-line edit to AbpVersion in common.props (and the matching value in each module's common.props), because every Volo.* entry in CPM is Version="$(AbpVersion)".
  • Bumping the app version is a one-line edit to <Version> in the root common.props — automated by .scripts/UpdateVersion.ps1.

Gotchas (quick reference)

Build-truth checklist

  • .abpsln / .abpmdl / README are stale for versions and TFM. Real values are in common.props + .csproj (ABP 10.1.1, net10.0).
  • CPM version variables live in common.props, not Directory.Packages.props.
  • CentralPackageTransitivePinningEnabled=false — only direct references need a <PackageVersion>; NU1608 is pre-suppressed for the AngleSharp/HtmlSanitizer mismatch.
  • AspireVersion (13.2.2) ≠ AppHost SDK (13.1.0).
  • Module <Version> is 1.0.0, independent of the app's 3.1.4.0.
  • .slnx and .sln (All) drift and must be hand-synced; app-only .sln has no .slnx twin.
  • Solution membership ≠ disk contentsCargonerds.Integration.Tests and Cargonerds.ServiceDefaults.Tests are on disk but not in the solutions.
  • SolutionPacker.exe is dead weight; abpSrc/ is local-only and must never be referenced.