OrchardCMS/OrchardCore · error · InvalidOperationException

The type can only be assigned to a single feature. Make…

Error message

The type {type} can only be assigned to a single feature. Make sure the type is not added to DI by multiple startup classes.

What it means

Types marked with FeatureTypeDiscovery(SingleFeatureOnly = true) must be owned by exactly one feature. TryAdd detects that such a type is being associated with a second feature and throws InvalidOperationException, because multiple startup classes are registering the same type into DI under different features.

Solutions

  1. Find the second startup class registering the type (exception names the type) and remove or disable its registration.
  2. Ensure the class is declared in only one module/feature, removing duplicate copies after refactors.
  3. If shared ownership is intended, drop the SingleFeatureOnly attribute requirement or use a shared abstraction package instead.
  4. Check base classes of the startup — discovery may pick up both the base and derived types.

Example fix

// before
// ModuleA/Startup.cs and ModuleB/Startup.cs both do
services.AddScoped<IEmailService, EmailService>();

// after
// Keep the registration in ModuleA only; ModuleB consumes IEmailService via constructor injection.
Defensive patterns

Strategy: validation

Validate before calling

// Before adding a new startup registration for a SingleFeatureOnly type:
var existing = featureMap.Where(kv => kv.Key == typeof(MyService)).ToList();
if (existing.Count > 0) throw new InvalidOperationException($"{typeof(MyService)} is already registered by {existing[0].Value.Id}.");

Try / catch

try { typeFeatureProvider.TryAdd(type, feature); }
catch (InvalidOperationException ex) when (ex.Message.Contains("single feature"))
{
    _logger.LogError(ex, "Duplicate registration of {Type} across features", type);
}

Prevention

When it happens

Trigger: Calling TryAdd(type, feature) where type has the [FeatureTypeDiscovery(SingleFeatureOnly = true)] attribute and is already mapped to a different feature — typically two Startup classes in different modules exposing the same service type.

Common situations: Two modules each define a startup class contributing the same singleton service type; a class was moved between modules but the old registration still exists; an inherited StartupBase gets picked up by discovery in two features.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


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

Appendix: source

Thrown at src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs:53

        {
            return features;
        }

        throw new InvalidOperationException($"Could not resolve features for type {dependency.Name}.");
    }

    public IEnumerable<Type> GetTypesForFeature(IFeatureInfo feature)
    {
        return _features.Where(kv => kv.Value.Contains(feature)).Select(kv => kv.Key);
    }

    public void TryAdd(Type type, IFeatureInfo feature)
    {
        var features = _features.AddOrUpdate(type, (key, value) => [value], (key, features, value) => features.Contains(value) ? features : features.Append(value).ToArray(), feature);

        if (features.Count() > 1 && (FeatureTypeDiscoveryAttribute.GetFeatureTypeDiscoveryForType(type)?.SingleFeatureOnly ?? false))
        {
            throw new InvalidOperationException($"The type {type} can only be assigned to a single feature. Make sure the type is not added to DI by multiple startup classes.");
        }
    }
}

View on GitHub (pinned to 4306c0717f)