OrchardCMS/OrchardCore · error · InvalidOperationException

Circular dependency of type

Error message

Circular dependency of type '{settings.Type}' detected between '{settings.Name}' and '{resource.Name}'

What it means

ResourceDictionary.AddExpandingResource detects cycles while expanding resource dependency graphs in ResourceManagement. When a resource being expanded is encountered again (i.e., it depends, directly or transitively, on itself), an InvalidOperationException is thrown naming the two resources involved. This prevents infinite recursion when building the require/expansion lists.

Solutions

  1. Read the exception to identify the two resources forming the cycle ('settings.Name' and 'resource.Name').
  2. Fix the ResourceManifest declarations so Dependencies form a DAG — remove one direction of the mutual Requires, or extract shared code into a third resource both depend on.
  3. Check for a self-dependency (a resource depending on its own name, often a copy-paste typo).
  4. After fixing, clear cached resource state and reload the page to confirm the graph resolves.

Example fix

// before (circular)
manifest.DefineScript("A").SetDependencies("B");
manifest.DefineScript("B").SetDependencies("A");

// after (acyclic: shared base)
manifest.DefineScript("Shared").SetUrl("/js/shared.js");
manifest.DefineScript("A").SetDependencies("Shared");
manifest.DefineScript("B").SetDependencies("Shared");
Defensive patterns

Strategy: validation

Validate before calling

// Validate the manifest dependency graph is acyclic before requesting resources:
bool HasCycle(Dictionary<string, string[]> deps) =>
    deps.Keys.Any(n => Visit(n, deps, new HashSet<string>(), new HashSet<string>()));

bool Visit(string n, Dictionary<string, string[]> deps, HashSet<string> visiting, HashSet<string> done)
{
    if (visiting.Contains(n)) return true;
    if (done.Contains(n) || !deps.TryGetValue(n, out var d)) return false;
    visiting.Add(n);
    var cycle = d.Any(x => Visit(x, deps, visiting, done));
    visiting.Remove(n); done.Add(n);
    return cycle;
}

Try / catch

try
{
    await resourceManager.RegisterResourcesAsync();
}
catch (InvalidOperationException ex) when (ex.Message.Contains("Circular dependency"))
{
    logger.LogError(ex, "Resource manifest declares a dependency cycle");
}

Prevention

When it happens

Trigger: Registering resource definitions (manifest) whose Dependencies form a cycle — e.g., resource A requires B and B requires A — then a page RequireSettings call expands dependencies via ExpandDependenciesImplementation and hits AddExpandingResource with a resource already on the expansion stack.

Common situations: A custom module's ResourceManifest declares A depends on B and B depends on A (often after a refactor); two scripts/styles mutually requiring each other for shared utilities; typo in a dependency name accidentally pointing at the resource itself.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13). Data as JSON: /api/errors/dca850cda2ffdaba. Report an issue: GitHub.

Appendix: source

Thrown at src/OrchardCore/OrchardCore.ResourceManagement/ResourceDictionary.cs:18

using System.Collections.Specialized;

namespace OrchardCore.ResourceManagement;

#pragma warning disable CA1010 // Type 'ResourceDictionary' directly or indirectly inherits 'ICollection' without implementing any of 'ICollection<T>', 'IReadOnlyCollection<T>'. Publicly-visible types should implement the generic version to broaden usability.
public class ResourceDictionary : OrderedDictionary
#pragma warning restore CA1010
{
    private readonly Stack<ResourceDefinition> _expanding = new();

    public int FirstCount { get; private set; }
    public int LastCount { get; private set; }

    public void AddExpandingResource(ResourceDefinition resource, RequireSettings settings)
    {
        if (_expanding.Contains(resource))
        {
            throw new InvalidOperationException($"Circular dependency of type '{settings.Type}' detected between '{settings.Name}' and '{resource.Name}'");
        }

        _expanding.Push(resource);
    }

    public void AddExpandedResource(ResourceDefinition resource, RequireSettings settings)
    {
        _expanding.Pop();

        if (settings.Position != ResourcePosition.ByDependency)
        {
            var existing = (RequireSettings)this[resource];
            if (existing == null || existing.Position == ResourcePosition.ByDependency)
            {
                if (settings.Position == ResourcePosition.First)
                {
                    FirstCount++;
                }

View on GitHub (pinned to 4306c0717f)