-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
.targetsfiles) and project reference consumption (via customStaticWebAssetsGetBuildAssetsTargetsoverrides). 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
_GenerateIdentityUIPackItemstarget manually callsDefineStaticWebAssets,GenerateStaticWebAssetsPropsFile,GenerateStaticWebAssetEndpointsPropsFile, andComputeStaticWebAssetsTargetPaths— 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
.propsfiles. -
Migrate existing packages to Asset Groups.
Microsoft.AspNetCore.App.Internal.AssetsandMicrosoft.AspNetCore.Identity.UIshould be converted to use Asset Groups, eliminating their custom_AddBlazorFrameworkStaticWebAssets,GetIdentityUIAssets, and_GenerateIdentityUIPackItemstargets.
Non-goals
-
Built-in OR/NOT logic in the pipeline's matching engine. Complex boolean expressions are the responsibility of the producer's
.propsfile 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 tagExcludePattern— 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:
<StaticWebAssetGroupDefinition Include="BootstrapVersion" Value="Bootstrap5" IncludePattern="V5/**" />
<StaticWebAssetGroupDefinition Include="BootstrapVersion" Value="Bootstrap4" IncludePattern="V4/**" />
<StaticWebAssetGroupDefinition Include="BlazorFramework" Value="true" IncludePattern="**/*.js;**/*.js.map" />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):
<StaticWebAssetGroupDefinition Include="BootstrapVersion" Value="Bootstrap5" IncludePattern="V5/**" />
<StaticWebAssetGroupDefinition Include="BootstrapVersion" Value="Bootstrap4" IncludePattern="V4/**" />Package .props:
<PropertyGroup>
<IdentityUIFrameworkVersion Condition="'$(IdentityUIFrameworkVersion)' == ''">Bootstrap5</IdentityUIFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<StaticWebAssetGroup Include="BootstrapVersion" Value="$(IdentityUIFrameworkVersion)" />
</ItemGroup>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:
<StaticWebAssetFrameworkPattern>**</StaticWebAssetFrameworkPattern>
<StaticWebAssetGroupDefinition Include="BlazorFramework" Value="true" IncludePattern="**" />Package .props (build/Microsoft.AspNetCore.App.Internal.Assets.props):
<ItemGroup Condition="'$(UsingMicrosoftNETSdkWeb)' == 'true' and ('$(OutputType)' == 'Exe' or '$(OutputType)' == 'WinExe')">
<StaticWebAssetGroup Include="BlazorFramework" Value="true" />
</ItemGroup>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.
<StaticWebAssetGroupDefinition Include="Theme" Value="light" IncludePattern="light/**" />
<StaticWebAssetGroupDefinition Include="Theme" Value="dark" IncludePattern="dark/**" />
<StaticWebAssetGroupDefinition Include="Density" Value="compact" IncludePattern="**/compact/**" />
<StaticWebAssetGroupDefinition Include="Density" Value="comfortable" IncludePattern="**/comfortable/**" />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:
<ItemGroup Condition="'$(UsingBlazorServer)' == 'true' or '$(UsingBlazorWebAssembly)' == 'true'">
<StaticWebAssetGroup Include="BlazorHosting" Value="true" />
</ItemGroup>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
.propsfiles are evaluated before static web asset resolution. - Group names are case-sensitive.
StaticWebAssetGroupDefinitionglob patterns match against the asset's relative path within the content root.- A single asset can belong to multiple groups.
- The Framework Assets feature (Add Framework SourceType for static web assets #53135) is merged before Asset Groups work begins.
Related
- Add Framework SourceType for static web assets #53135 — Framework Assets (prerequisite)
Microsoft.AspNetCore.App.Internal.Assets— primary migration targetMicrosoft.AspNetCore.Identity.UI— primary migration target