Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes missing dependencies in the ShellBlueprint #16379

Merged
merged 9 commits into from
Jul 2, 2024
62 changes: 22 additions & 40 deletions src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class ExtensionManager : IExtensionManager

private FrozenDictionary<string, ExtensionEntry> _extensions;
private List<IExtensionInfo> _extensionsInfos;
private Dictionary<string, IFeatureInfo> _features;
private FrozenDictionary<string, IFeatureInfo> _features;
private IFeatureInfo[] _featureInfos;

private readonly ConcurrentDictionary<string, Lazy<IEnumerable<IFeatureInfo>>> _featureDependencies = new();
Expand Down Expand Up @@ -105,7 +105,8 @@ public Task<IEnumerable<IFeatureInfo>> LoadFeaturesAsync()
{
EnsureInitialized();

return Task.FromResult<IEnumerable<IFeatureInfo>>(_features.Values);
// Must return the features ordered by dependencies.
return Task.FromResult<IEnumerable<IFeatureInfo>>(_featureInfos);
}

public Task<IEnumerable<IFeatureInfo>> LoadFeaturesAsync(string[] featureIdsToLoad)
Expand All @@ -114,10 +115,11 @@ public Task<IEnumerable<IFeatureInfo>> LoadFeaturesAsync(string[] featureIdsToLo

var features = new HashSet<string>(GetFeatures(featureIdsToLoad).Select(f => f.Id));

var loadedFeatures = _features.Values
// Must return the features ordered by dependencies.
var loadedFeatures = _featureInfos
.Where(f => features.Contains(f.Id));

return Task.FromResult<IEnumerable<IFeatureInfo>>(loadedFeatures);
return Task.FromResult(loadedFeatures);
}

public IEnumerable<IFeatureInfo> GetFeatureDependencies(string featureId)
Expand Down Expand Up @@ -298,58 +300,38 @@ private void EnsureInitialized()
loadedExtensions.TryAdd(module.Name, entry);
});

var loadedFeatures = new Dictionary<string, IFeatureInfo>();

// Get all types from all extension and add them to the type feature provider.
var allTypesByExtension = loadedExtensions
.SelectMany(extension =>
extension
.Value
.ExportedTypes
.Where(IsComponentType)
.Select(type => new
{
Extension = extension.Value,
Type = type
}));

var typesByFeature = allTypesByExtension
.GroupBy(typeByExtension => GetSourceFeatureNameForType(
typeByExtension.Type,
typeByExtension.Extension.ExtensionInfo.Id))
.ToDictionary(
group => group.Key,
group => group.Select(typesByExtension => typesByExtension.Type));

foreach (var loadedExtension in loadedExtensions)
{
var extension = loadedExtension.Value;

foreach (var feature in extension.ExtensionInfo.Features)
foreach (var exportedType in extension.ExportedTypes.Where(IsComponentType))
{
// Features can have no types.
if (typesByFeature.TryGetValue(feature.Id, out var featureTypes))
if (!SkipExtensionFeatureRegistration(exportedType))
{
// This is adding the types to the main feature for backward compatibility.
// In the future we could stop doing it as we don't expect this to be necessary, and remove the FeatureTypeDiscovery attribute.
foreach (var type in featureTypes)
var sourceFeature = GetSourceFeatureNameForType(exportedType, extension.ExtensionInfo.Id);

var feature = extension.ExtensionInfo.Features.FirstOrDefault(f => f.Id == sourceFeature);

if (feature == null)
{
// If the attribute is present then we explicitly ignore the backward compatibility and skip the registration
// in the main feature.
if (!SkipExtensionFeatureRegistration(type))
// Type has no specific feature, add it to all features
foreach (var curFeature in extension.ExtensionInfo.Features)
{
_typeFeatureProvider.TryAdd(type, feature);
_typeFeatureProvider.TryAdd(exportedType, curFeature);
}
}
else
{
_typeFeatureProvider.TryAdd(exportedType, feature);
}
}

loadedFeatures.Add(feature.Id, feature);
}
}

// Feature infos and entries are ordered by priority and dependencies.
_featureInfos = Order(loadedFeatures.Values);
_features = _featureInfos.ToDictionary(f => f.Id, f => loadedFeatures[f.Id]);
_featureInfos = Order(loadedExtensions.SelectMany(extension => extension.Value.ExtensionInfo.Features));
_features = _featureInfos.ToFrozenDictionary(f => f.Id, f => f);

// Extensions are also ordered according to the weight of their first features.
_extensionsInfos = _featureInfos
Expand Down