dotnet/aspnetcore · error · InvalidOperationException
The static asset ' ' is already mapped to .
Error message
The static asset '{property.Value}' is already mapped to {value.Url}. What it means
ResourceAssetCollection builds an index of static assets keyed by their 'label' property (the developer-facing logical name). Two distinct resources sharing the same label collide because the label is the lookup key for resource resolution at runtime, so the constructor throws InvalidOperationException naming both the duplicate label and the previously-mapped URL. This is a startup-time invariant, not a per-request check.
Solutions
- Inspect the resource list passed to ResourceAssetCollection and find the two entries whose 'label' property collides (case-insensitive).
- Give each distinct asset a unique label, or de-duplicate the source manifest so only one entry per logical asset remains.
- If the duplication comes from multiple assemblies, scope or filter the resource providers contributing to the collection.
- Add a unit test that asserts labels are unique over the produced resource set.
Example fix
// before — two assets share the label 'app.css'
resources.Add(new ResourceAsset("app.min.css", new(){ new("label","app.css") }));
resources.Add(new ResourceAsset("app.debug.css", new(){ new("label","app.css") }));
// throws: The static asset 'app.css' is already mapped to app.min.css.
// after — unique labels
resources.Add(new ResourceAsset("app.min.css", new(){ new("label","app.css") }));
resources.Add(new ResourceAsset("app.debug.css", new(){ new("label","app.debug.css") })); Defensive patterns
Strategy: validation
Validate before calling
// De-duplicate resource labels before constructing the ResourceAssetCollection.
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var r in resources)
foreach (var p in r.Properties ?? [])
if (p.Name.Equals("label", StringComparison.OrdinalIgnoreCase))
if (!seen.Add(p.Value))
throw new InvalidOperationException($"Duplicate resource label '{p.Value}'"); Type guard
static bool HasUniqueLabels(IEnumerable<ResourceAsset> resources)
{
var labels = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
return resources.SelectMany(r => r.Properties ?? [])
.Where(p => p.Name.Equals("label", StringComparison.OrdinalIgnoreCase))
.All(p => labels.Add(p.Value));
} Prevention
- Validate the resource list for unique labels before passing it to ResourceAssetCollection.Create.
- Generate resource labels from a deterministic source (file path + hash) to avoid collisions.
- Add a unit test over the produced resource set asserting label uniqueness.
- Audit bundling output when fingerprinting changes — labels should remain stable per logical asset.
When it happens
Trigger: Two ResourceAsset entries passed to ResourceAssetCollection.Create (or via the framework's resource discovery) where both expose a Properties entry with Name == 'label' and identical Value (case-insensitive). Common with fingerprinted assets that retain the same logical name across builds, or with manually-curated resource manifests that list the same JS/CSS twice.
Common situations: Bundling/publishing pipeline emits two assets with the same label (e.g. one minified, one not); static web assets from different assembly roots whose paths collapse to the same label; misconfigured resource manifest JSON; testing with hand-built ResourceAsset arrays that accidentally duplicate.
Related errors
- Component parameters may only be added immediately after…
- Could not find any element matching selector
- Could not load settings from
- Dynamic root components have not been enabled in this…
- requires a non-null value either for ' ' or ' '.
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/0ff7a22da64c0ccc.
Report an issue: GitHub.
Appendix: source
Thrown at src/Components/Components/src/ResourceAssetCollection.cs:40
/// <summary>
/// Initializes a new instance of <see cref="ResourceAssetCollection"/>
/// </summary>
/// <param name="resources">The list of resources available.</param>
public ResourceAssetCollection(IReadOnlyList<ResourceAsset> resources)
{
var mappings = new Dictionary<string, ResourceAsset>(StringComparer.OrdinalIgnoreCase);
var contentSpecificUrls = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
_resources = resources;
foreach (var resource in resources)
{
foreach (var property in resource.Properties ?? [])
{
if (property.Name.Equals("label", StringComparison.OrdinalIgnoreCase))
{
if (mappings.TryGetValue(property.Value, out var value))
{
throw new InvalidOperationException($"The static asset '{property.Value}' is already mapped to {value.Url}.");
}
mappings[property.Value] = resource;
contentSpecificUrls.Add(resource.Url);
}
}
}
_uniqueUrlMappings = mappings.ToFrozenDictionary();
_contentSpecificUrls = contentSpecificUrls.ToFrozenSet();
}
/// <summary>
/// Gets the unique content-based URL for the specified static asset.
/// </summary>
/// <param name="key">The asset name.</param>
/// <returns>The unique URL if available, the same <paramref name="key"/> if not available.</returns>
public string this[string key] => _uniqueUrlMappings.TryGetValue(key, out var value) ? value.Url : key;
View on GitHub (pinned to 3600ca084e)