umbrella-dotnet-install-analyzers
Install the Umbrella Roslyn analyzer packages (Umbrella.Analyzers, Umbrella.DataAccess.Analyzers, Umbrella.Utilities.Mapping.Mapperly.Analyzers, Umbrella.WebUtilities.DynamicImage.Analyzers) and the U
SKILL.md.ai-shared/skills/umbrella-dotnet-install-analyzers/SKILL.md
GitHubInstall Umbrella Analyzers
Purpose
Wire up the four Umbrella Roslyn analyzer packages in a consuming repository (e.g. a Kernel/API/Blazor solution that already references other Umbrella.* packages) so that UA*, UDA*, UMA*, and UWDI* diagnostics documented in .ai-shared\bundles\umbrella\analyzer-compatibility.md are actually enforced at build time. Installing the packages does not change any C#/Razor source — it only adds PackageReference entries. Diagnostics surfaced by a fresh install are a separate follow-up, not part of this skill (see Verification).
Each package has a different correct scope. Do not install them all the same way.
Four of the five are analyzers and are installed as PrivateAssets="All" development dependencies. The fifth,
Umbrella.Analyzers.Abstractions, is not an analyzer — it is an ordinary runtime assembly supplying the model
opt-out attributes, and it must not be private. See Step 1 for why.
| Package | Scope | Rule |
|---|---|---|
Umbrella.Analyzers |
Global | Every project in the solution — general coding-standard/model-immutability rules (UA*) apply everywhere. |
Umbrella.Analyzers.Abstractions |
Global, not private | Supplies [UmbrellaInputModel], [UmbrellaAllowUnsealedModel], [UmbrellaAllowNonRequiredProperty] and [UmbrellaAllowMutableProperty] (namespace Umbrella.Analyzers). Needed by every project that applies them, and must reach publish output. |
Umbrella.Utilities.Mapping.Mapperly.Analyzers |
Global | Every project. It is inert outside Mapperly [Mapper] usage, so global installation is safe and avoids per-project drift as new mapping consumers are added. |
Umbrella.DataAccess.Analyzers |
Per-project | Only projects that implement repositories (UDA* checks naming/encapsulation on concrete repository classes). |
Umbrella.WebUtilities.DynamicImage.Analyzers |
Per-project | Only projects with an actual Dynamic Image surface: Razor/Blazor markup using <UmbrellaDynamicImage>/<UmbrellaFileImagePreviewUpload>, or a mapping project that assigns *Url/*VersionToken model pairs together. |
Discovery (read/run these before editing anything)
- Resolve the shared Umbrella package version. Grep the target repo for an existing
Umbrella.*PackageReferenceversion (e.g.Umbrella.AspNetCore.WebUtilities,Umbrella.DataAccess.Abstractions) in.csprojfiles. Use that exact version string for all five packages — never hardcode a version from this skill or from another repo. If multiple versions are present, use the version that matches the majority ofUmbrella.*references and flag any outliers to the user. - Confirm the shared-injection pattern. Open
Directory.Packages.propsat the repo root. In this org it is not real NuGet Central Package Management (ManagePackageVersionsCentrallyis not set) — it is a shared file whose unconditioned<ItemGroup>of<PackageReference>items is imported into every project, the same wayMicrosoft.CodeAnalysis.Analyzers/Microsoft.VisualStudio.Threading.Analyzersalready are. If the repo instead uses genuine CPM (ManagePackageVersionsCentrally=trueand<PackageVersion>items), adapt: add<PackageVersion>centrally and an unversioned<PackageReference>in each qualifying project. - Check what is already installed. Grep all
.csproj/.propsfiles for each of the five package IDs. Skip any that are already present at the correct version and scope; report a version mismatch rather than silently overwriting it. Treat an existingUmbrella.Analyzers.Abstractionsreference carryingPrivateAssetsas a defect to fix, not as already-installed. - Enumerate repository-implementation projects (for
Umbrella.DataAccess.Analyzers). A project qualifies when it:- has a
Repositories\folder containing concrete repository classes (not just anAbstractions/domain project that merely referencesUmbrella.DataAccess.Abstractionsfor entity/domain types), and - references a concrete Umbrella data-access implementation package, e.g.
Umbrella.DataAccess.EntityFrameworkCore[.SqlServer], or is a library implementingIUmbrellaRepositorydirectly (e.g. a Dataverse/Cosmos/etc. data-access library). Domain/entity-only projects (referencing onlyUmbrella.DataAccess.Abstractions) do not qualify, even though they're part of the data-access layer.
- has a
- Enumerate Dynamic Image projects (for
Umbrella.WebUtilities.DynamicImage.Analyzers). A project qualifies when it:- contains
.razorfiles or Razor views using<UmbrellaDynamicImage>/<UmbrellaFileImagePreviewUpload>tag helpers or components, or - is the Mapperly model-factory/mapping project that assigns
*Urland*VersionTokenproperties together (it participates in the paired-assignment check even without its own markup). A project that merely referencesUmbrella.AspNetCore.WebUtilities.DynamicImage/Umbrella.DynamicImage.*for server-side registration (service registration, middleware, catalog config) but has no Razor/Blazor surface and does not perform the paired model assignment does not automatically qualify — flag it to the user instead of guessing. This situation is common in API-only projects that expose Dynamic Image endpoints without owning any Razor/Blazor markup themselves.
- contains
If step 4 or 5 turns up an ambiguous project (e.g. no .razor files anywhere in the repo, or a repository-shaped project that doesn't cleanly match the pattern), stop and ask the user to confirm scope before editing rather than guessing.
Step 1 — Install the global analyzers and the attributes package
In Directory.Packages.props, add these to the existing unconditioned <ItemGroup> (next to Microsoft.CodeAnalysis.Analyzers etc.), using the version resolved in Discovery step 1:
<PackageReference Include="Umbrella.Analyzers" Version="{{resolvedVersion}}" PrivateAssets="All" />
<PackageReference Include="Umbrella.Utilities.Mapping.Mapperly.Analyzers" Version="{{resolvedVersion}}" PrivateAssets="All" />
<PackageReference Include="Umbrella.Analyzers.Abstractions" Version="{{resolvedVersion}}" />
Never put PrivateAssets on Umbrella.Analyzers.Abstractions, and never rely on the analyzer package to supply the
attributes. Applying [UmbrellaInputModel], [UmbrellaAllowUnsealedModel], [UmbrellaAllowNonRequiredProperty] or [UmbrellaAllowMutableProperty]
writes a permanent assembly reference into the consuming assembly, so the attribute assembly has to reach publish
output. Umbrella.Analyzers is a developmentDependency and is stripped from publish output, so if the attributes
resolve from it the solution builds and every test passes, then the deployed app dies at startup with
FileNotFoundException: Could not load file or assembly 'Umbrella.Analyzers' the moment anything reflects over a
decorated type — ASP.NET Core building model metadata during MapControllers, for instance. This failure is invisible
to dotnet build and dotnet test; only a dotnet publish output check or an actual deployment reveals it.
If the repo produces NuGet packages of its own, note that a global non-private reference becomes a public dependency of
those packages. That is usually acceptable (the assembly is tiny and dependency-free), but if you need to avoid it, add
<PackageReference Update="Umbrella.Analyzers.Abstractions" PrivateAssets="All" /> to the packable projects only —
never to a project that applies the attributes or to a deployed application.
Step 2 — Install Umbrella.DataAccess.Analyzers per repository-implementation project
For each project identified in Discovery step 4, add directly to that project's .csproj:
<PackageReference Include="Umbrella.DataAccess.Analyzers" Version="{{resolvedVersion}}" PrivateAssets="All" />
Do not add this to Directory.Packages.props and do not add it to domain/entity-only projects.
Step 3 — Install Umbrella.WebUtilities.DynamicImage.Analyzers per Dynamic Image project
For each project identified in Discovery step 5, add directly to that project's .csproj:
<PackageReference Include="Umbrella.WebUtilities.DynamicImage.Analyzers" Version="{{resolvedVersion}}" PrivateAssets="All" />
This is independent of the UmbrellaDynamicImageEnableUrlFingerprinting MSBuild property — that property (when the repo uses URL fingerprinting) is typically set once, repo-wide, in Directory.Build.props, and is unrelated to which projects carry the analyzer package itself. Do not conflate the two: setting the property is out of scope for this skill unless the user explicitly asks for it.
Analyzer compatibility
Before finishing, read .ai-shared\bundles\umbrella\analyzer-compatibility.md so you know what each rule actually checks, then build the whole solution.
Verification
- Every
Umbrella.Analyzers/Umbrella.Utilities.Mapping.Mapperly.Analyzersreference lives in the shared injection file (or the CPM equivalent), not duplicated into individual projects. - Every
Umbrella.DataAccess.Analyzersreference is in a project with aRepositories\folder and a concrete data-access implementation package — list the exact projects you added it to. - Every
Umbrella.WebUtilities.DynamicImage.Analyzersreference is in a project with genuine Razor/Blazor Dynamic Image usage or paired*Url/*VersionTokenassignment — list the exact projects you added it to, and separately list any project you considered but excluded, with the reason. dotnet buildthe solution. New diagnostics from these analyzers are expected on a first install into an existing codebase — report them grouped by rule ID and file, but do not silently fix or suppress them as part of this skill; that is separate follow-up work the user should triage.- No project ended up with a duplicate
PackageReferencefor the same analyzer (e.g. once inDirectory.Packages.propsand again locally). Umbrella.Analyzers.Abstractionscarries noPrivateAssetsanywhere it is needed. If the repo has a deployable app project, rundotnet publishon it and confirmUmbrella.Analyzers.Abstractions.dllis present in the publish output andUmbrella.Analyzers.dllis absent. A green build is not evidence — this specific defect only manifests at publish/runtime.