[StaticWebAssets] Conditionally include assets from packages/referenced projects via AssetGroup metadata
- Dominant language
- C#
- Stars
- 3.2k
- Forks
- 1.3k
- PR merge metrics
- PR metrics pending
Description
## Summary
Add conditional asset inclusion to Static Web Assets through a new **Asset Groups** mechanism. Asset Groups allow producers (NuGet packages and project references) to tag assets with named group memberships and allow consumers to declare which groups they accept. The pipeline filters assets based on group matching, including an asset only when every group requirement declared on the asset is satisfied by the consumer.
Today, packages that need conditional asset delivery — such as `Microsoft.AspNetCore.App.Internal.Assets` (Blazor JS files gated on `OutputType` and `UsingMicrosoftNETSdkWeb`) and `Microsoft.AspNetCore.Identity.UI` (Bootstrap 4/5 variant selection) — must implement extensive custom MSBuild targets, disable the standard SWA pipeline's auto-generation, and manually invoke internal SWA tasks like `DefineStaticWebAssets`, `GenerateStaticWebAssetsPropsFile`, and `ComputeStaticWebAssetsTargetPaths`. This custom logic must be duplicated across pack-time and project-reference-time code paths.
Asset Groups replace this per-package custom logic with a general-purpose pipeline feature. Producers tag assets with `AssetGroups` metadata (semicolon-separated `Name=Value` entries) and ship a `.props` file that maps consumer properties to `StaticWebAssetGroup` item declarations using standard MSBuild conditions. The pipeline performs AND-matching at filter time; all complex boolean logic (OR, NOT, nested expressions) is handled by MSBuild conditions in the `.props` file via conjunctive normal form decomposition.
## Goals
- **Enable conditional asset inclusion in the SWA pipeline.** Package and library authors currently have no pipeline-level mechanism to gate which static web assets are delivered to a consuming project. The pipeline treats all declared assets as unconditionally available. This forces authors who need conditional delivery to bypass the standard pipeline entirely and implement custom MSBuild targets. The pipeline should allow producers to declare group requirements on assets and consumers to declare which groups they accept, with the pipeline filtering assets accordingly.
- **Provide a uniform filtering mechanism across package and project reference paths.** Today, conditional inclusion requires separate implementations for NuGet package consumption (via custom `.targets` files) and project reference consumption (via custom `StaticWebAssetsGetBuildAssetsTargets` overrides). Asset Groups should work identically regardless of whether the producer is a NuGet package or a project reference.
- **Eliminate the need for custom pack-time MSBuild targets for asset variants.** Identity UI's `_GenerateIdentityUIPackItems` target manually calls `DefineStaticWebAssets`, `GenerateStaticWebAssetsPropsFile`, `GenerateStaticWebAssetEndpointsPropsFile`, and `ComputeStaticWebAssetsTargetPaths` — each invoked twice, once per Bootstrap version — and disables all five auto-generation flags. The standard SWA pack pipeline should handle multi-variant asset packaging natively when assets carry group metadata.
- **Support multi-dimensional asset selection without requiring an expression language in the pipeline.** The pipeline's matching logic should remain simple (AND-matching of name-value pairs), with all complex boolean evaluation delegated to MSBuild conditions in the producer's `.props` files.
- **Migrate existing packages to Asset Groups.** `Microsoft.AspNetCore.App.Internal.Assets` and `Microsoft.AspNetCore.Identity.UI` should be converted to use Asset Groups, eliminating their custom `_AddBlazorFrameworkStaticWebAssets`, `GetIdentityUIAssets`, and `_GenerateIdentityUIPackItems` targets.
## Non-goals
- **Built-in OR/NOT logic in the pipeline's matching engine.** Complex boolean expressions are the responsibility of the producer's `.props` file via MSBuild conditions and conjunctive normal form decomposition.
- **Runtime asset group resolution.** Asset Groups are a build-time concept. Runtime asset selection remains outside the SWA pipeline scope.
## Proposed solution
The Static Web Assets pipeline will be extended with a three-part mechanism: **group assignment** on the producer, **group declarations** on assets, and **group selections** on consumers.
### Producer-side group assignment
`DefineStaticWebAssets` is extended with a new `StaticWebAssetGroupDefinition` input item group. Each definition item specifies:
- `Include` (item identity) — the group name (e.g., `BootstrapVersion`)
- `Value` — the group value (e.g., `Bootstrap5`)
- `IncludePattern` — semicolon-separated glob patterns matching assets to tag
- `ExcludePattern` — semicolon-separated glob patterns for assets to exclude from tagging
The task evaluates each asset's path against the definitions and appends matching `Name=Value` entries to the asset's `AssetGroups` metadata. Example:
```xml
```
### Asset metadata
A new `AssetGroups` metadata property is added to `StaticWebAsset` items. Its value is a semicolon-separated list of `Name=Value` entries (e.g., `BootstrapVersion=Bootstrap5` or `BlazorFramework=true;Theme=dark`). Assets without `AssetGroups` metadata are unconditional and pass through the pipeline unchanged, preserving full backward compatibility.
### Consumer-side group selection
A new `StaticWebAssetGroup` MSBuild item group allows consuming projects to declare which groups they accept. Each item has `Include` set to the group name and a `Value` metadata property. Package authors ship a `.props` file in the NuGet package that maps consumer properties to `StaticWebAssetGroup` items using MSBuild conditions.
### Pipeline filtering
At two points in the pipeline — `UpdatePackageStaticWebAssets` for NuGet package assets and `ResolveReferencedProjectsStaticWebAssets` for project reference assets — the pipeline evaluates each asset's `AssetGroups` entries against the consumer's `@(StaticWebAssetGroup)` items. Every `Name=Value` entry on the asset must have a matching group item (AND across all entries). Assets that fail the match are excluded from the pipeline entirely, along with their associated endpoints.
### Pack-time preservation
`GenerateStaticWebAssetsPropsFile` emits the `AssetGroups` metadata on `StaticWebAsset` items in the generated props file inside the NuGet package.
### Project reference preservation
`ComputeReferenceStaticWebAssetItems` preserves the `AssetGroups` metadata when flowing assets from a referenced project to a consumer. For Framework-typed assets, `SourceType=Framework` is also preserved instead of being overwritten to `Project`.
## Scenarios
### 1. Single-dimension group filtering (NuGet package)
A Razor Class Library ships Bootstrap 4 and Bootstrap 5 CSS variants. Both sets are packed with group metadata. The consumer selects which variant via a property.
**Producer (`IdentityUI.csproj`):**
```xml
```
**Package `.props`:**
```xml
Bootstrap5
```
Consumer says nothing → defaults to Bootstrap5 → `V5/site.css` included, `V4/site.css` excluded.
### 2. Single-dimension group filtering (project reference)
Same library consumed via `ProjectReference`. Filtering works identically — `DefineStaticWebAssets` tags assets, `ResolveReferencedProjectsStaticWebAssets` filters them.
### 3. Framework assets with group gating (NuGet package)
`Microsoft.AspNetCore.App.Internal.Assets` ships Blazor JS files as **Framework** assets with a `BlazorFramework` group. Combines Framework Assets (`StaticWebAssetFrameworkPattern`) with Asset Groups (`StaticWebAssetGroupDefinition`).
**Producer:**
```xml
**
```
**Package `.props` (`build/Microsoft.AspNetCore.App.Internal.Assets.props`):**
```xml
```
Web Exe projects → assets included with `SourceType=Framework`. Class libraries → assets excluded.
### 4. Framework assets with group gating (project reference)
Same library consumed via `ProjectReference` (aspnetcore inner loop). `ComputeReferenceStaticWebAssetItems` preserves both `SourceType=Framework` and `AssetGroups`.
### 5. Multi-dimensional group filtering
A theming library varies across **theme** (light/dark) and **density** (compact/comfortable). Consumer picks one per dimension. Pipeline AND-matches both.
```xml
```
Consumer sets `Theme=dark`, defaults `Density=comfortable` → only `dark/comfortable/theme.css` included.
### 6. Complex boolean logic via `.props` (OR condition)
Assets gated on Blazor Server OR Blazor WebAssembly. The `.props` evaluates the OR via MSBuild condition and maps to a single group:
```xml
```
The pipeline never sees an OR — only the resulting `StaticWebAssetGroup` item (or its absence).
### 7. Ungrouped assets (backward compatibility)
Assets without `AssetGroups` metadata always pass through (zero requirements = trivially satisfied). A library can mix unconditional assets with grouped variants.
### 8. No matching group (asset exclusion)
If a consumer doesn't declare a `StaticWebAssetGroup` for a group name that appears on assets, those assets are excluded. Gated assets don't leak into projects not intended to consume them.
## Assumptions
- Package `.props` files are evaluated before static web asset resolution.
- Group names are case-sensitive.
- `StaticWebAssetGroupDefinition` glob patterns match against the asset's relative path within the content root.
- A single asset can belong to multiple groups.
- The Framework Assets feature (#53135) is merged before Asset Groups work begins.
## Related
- #53135 — Framework Assets (prerequisite)
- [`Microsoft.AspNetCore.App.Internal.Assets`](https://github.com/dotnet/aspnetcore/tree/main/src/Assets) — primary migration target
- [`Microsoft.AspNetCore.Identity.UI`](https://github.com/dotnet/aspnetcore/tree/main/src/Identity/UI/src) — primary migration target
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.