From 0ca7aaedb5673ebf8f6d609b8324275141579ef1 Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Fri, 19 Apr 2024 13:07:53 +0200 Subject: [PATCH 01/20] Update type mappings in TypeFeatureProvider when the service collections are being build. --- .../OrchardCore.Roles/Services/RoleUpdater.cs | 2 +- .../Features/ITypeFeatureProvider.cs | 2 ++ .../Features/TypeFeatureProvider.cs | 22 ++++++++++++++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Roles/Services/RoleUpdater.cs b/src/OrchardCore.Modules/OrchardCore.Roles/Services/RoleUpdater.cs index 3651293739c..6b1b734cd5d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Roles/Services/RoleUpdater.cs +++ b/src/OrchardCore.Modules/OrchardCore.Roles/Services/RoleUpdater.cs @@ -144,7 +144,7 @@ private async Task UpdateRoleForInstalledFeaturesAsync(string roleName) // And defining at least one 'IPermissionProvider'. rolesDocument.MissingFeaturesByRole[roleName] = (await _extensionManager.LoadFeaturesAsync(missingFeatures)) - .Where(entry => entry.ExportedTypes.Any(type => type.IsAssignableTo(typeof(IPermissionProvider)))) + .Where(entry => _typeFeatureProvider.GetTypesForFeature(entry.FeatureInfo).Any(type => type.IsAssignableTo(typeof(IPermissionProvider)))) .Select(entry => entry.FeatureInfo.Id) .ToList(); diff --git a/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs b/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs index ce9aa247803..7dae940f8a0 100644 --- a/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs +++ b/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using OrchardCore.Environment.Extensions.Features; namespace OrchardCore.Environment.Extensions @@ -10,6 +11,7 @@ namespace OrchardCore.Environment.Extensions public interface ITypeFeatureProvider { IFeatureInfo GetFeatureForDependency(Type dependency); + IEnumerable GetTypesForFeature(IFeatureInfo feature); void TryAdd(Type type, IFeatureInfo feature); } } diff --git a/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs b/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs index 23caf77694c..fb7793893e8 100644 --- a/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs +++ b/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using OrchardCore.Environment.Extensions.Features; namespace OrchardCore.Environment.Extensions @@ -18,9 +21,26 @@ public IFeatureInfo GetFeatureForDependency(Type dependency) throw new InvalidOperationException($"Could not resolve feature for type {dependency.Name}"); } + public IEnumerable GetTypesForFeature(IFeatureInfo feature) + { + return _features.Where(kv => kv.Value == feature).Select(kv => kv.Key); + } + public void TryAdd(Type type, IFeatureInfo feature) { - _features.TryAdd(type, feature); + _ = _features.AddOrUpdate(type, (t, f) => f, (curType, curFeature, newFeature) => + { + // If the type is currently only mapped to the module, update it with the more specific feature. + if (curFeature != newFeature && + curFeature.Extension.Manifest.ModuleInfo.Id == curFeature.Id && + curFeature.Extension.Features.Contains(newFeature)) + { + Debug.WriteLine($"TypeFeatureProvider changed mapping of type '{curType}' from '{curFeature.Id}' to '{newFeature.Id}'."); + return newFeature; + } + + return curFeature; + }, feature); } } } From f0834d13a4ec8debfc966cf26cb747f123c66c61 Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Fri, 19 Apr 2024 13:41:04 +0200 Subject: [PATCH 02/20] Remove unnecessary FeatureEntry type. --- .../OrchardCore.Roles/Services/RoleUpdater.cs | 4 +-- .../Extensions/Features/FeatureEntry.cs | 22 ---------------- .../Extensions/IExtensionManager.cs | 4 +-- .../Shell/Builders/Models/ShellBlueprint.cs | 2 +- .../Shell/ShellDescriptorManager.cs | 2 +- .../Extensions/ExtensionManager.cs | 26 +++++++------------ .../Shell/Builders/CompositionStrategy.cs | 7 +++-- .../Shell/Builders/ShellContainerFactory.cs | 4 +-- ...onfiguredFeaturesShellDescriptorManager.cs | 2 +- .../SetFeaturesShellDescriptorManager.cs | 2 +- .../DefaultShapeTableManagerTests.cs | 12 ++++----- .../Shell/ShellContainerFactoryTests.cs | 4 +-- .../Stubs/StubExtensionManager.cs | 4 +-- 13 files changed, 34 insertions(+), 61 deletions(-) delete mode 100644 src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/FeatureEntry.cs diff --git a/src/OrchardCore.Modules/OrchardCore.Roles/Services/RoleUpdater.cs b/src/OrchardCore.Modules/OrchardCore.Roles/Services/RoleUpdater.cs index 6b1b734cd5d..774da790b11 100644 --- a/src/OrchardCore.Modules/OrchardCore.Roles/Services/RoleUpdater.cs +++ b/src/OrchardCore.Modules/OrchardCore.Roles/Services/RoleUpdater.cs @@ -144,8 +144,8 @@ private async Task UpdateRoleForInstalledFeaturesAsync(string roleName) // And defining at least one 'IPermissionProvider'. rolesDocument.MissingFeaturesByRole[roleName] = (await _extensionManager.LoadFeaturesAsync(missingFeatures)) - .Where(entry => _typeFeatureProvider.GetTypesForFeature(entry.FeatureInfo).Any(type => type.IsAssignableTo(typeof(IPermissionProvider)))) - .Select(entry => entry.FeatureInfo.Id) + .Where(entry => _typeFeatureProvider.GetTypesForFeature(entry).Any(type => type.IsAssignableTo(typeof(IPermissionProvider)))) + .Select(entry => entry.Id) .ToList(); await _documentManager.UpdateAsync(rolesDocument); diff --git a/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/FeatureEntry.cs b/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/FeatureEntry.cs deleted file mode 100644 index 342961aa9ef..00000000000 --- a/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/FeatureEntry.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace OrchardCore.Environment.Extensions.Features -{ - public class FeatureEntry - { - public FeatureEntry(IFeatureInfo featureInfo) - { - FeatureInfo = featureInfo; - } - - public FeatureEntry(IFeatureInfo featureInfo, IEnumerable exportedTypes) - { - FeatureInfo = featureInfo; - ExportedTypes = exportedTypes; - } - - public IFeatureInfo FeatureInfo { get; } - public IEnumerable ExportedTypes { get; } = []; - } -} diff --git a/src/OrchardCore/OrchardCore.Abstractions/Extensions/IExtensionManager.cs b/src/OrchardCore/OrchardCore.Abstractions/Extensions/IExtensionManager.cs index 985225f384c..c34fb79ef04 100644 --- a/src/OrchardCore/OrchardCore.Abstractions/Extensions/IExtensionManager.cs +++ b/src/OrchardCore/OrchardCore.Abstractions/Extensions/IExtensionManager.cs @@ -14,7 +14,7 @@ public interface IExtensionManager IEnumerable GetFeatures(string[] featureIdsToLoad); IEnumerable GetFeatureDependencies(string featureId); IEnumerable GetDependentFeatures(string featureId); - Task> LoadFeaturesAsync(); - Task> LoadFeaturesAsync(string[] featureIdsToLoad); + Task> LoadFeaturesAsync(); + Task> LoadFeaturesAsync(string[] featureIdsToLoad); } } diff --git a/src/OrchardCore/OrchardCore.Abstractions/Shell/Builders/Models/ShellBlueprint.cs b/src/OrchardCore/OrchardCore.Abstractions/Shell/Builders/Models/ShellBlueprint.cs index 8ade2c8f70d..9103457fd66 100644 --- a/src/OrchardCore/OrchardCore.Abstractions/Shell/Builders/Models/ShellBlueprint.cs +++ b/src/OrchardCore/OrchardCore.Abstractions/Shell/Builders/Models/ShellBlueprint.cs @@ -15,6 +15,6 @@ public class ShellBlueprint public ShellSettings Settings { get; set; } public ShellDescriptor Descriptor { get; set; } - public IDictionary Dependencies { get; set; } + public IDictionary Dependencies { get; set; } } } diff --git a/src/OrchardCore/OrchardCore.Infrastructure/Shell/ShellDescriptorManager.cs b/src/OrchardCore/OrchardCore.Infrastructure/Shell/ShellDescriptorManager.cs index 9c7b6ba54c9..d9fd9c07cd0 100644 --- a/src/OrchardCore/OrchardCore.Infrastructure/Shell/ShellDescriptorManager.cs +++ b/src/OrchardCore/OrchardCore.Infrastructure/Shell/ShellDescriptorManager.cs @@ -83,7 +83,7 @@ public async Task GetShellDescriptorAsync() var featureIds = features.Select(sf => sf.Id).ToArray(); var missingDependencies = (await _extensionManager.LoadFeaturesAsync(featureIds)) - .Select(entry => entry.FeatureInfo.Id) + .Select(entry => entry.Id) .Except(featureIds) .Select(id => new ShellFeature(id)); diff --git a/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs b/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs index cc01db9ba35..258005e4a5d 100644 --- a/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs +++ b/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs @@ -30,7 +30,7 @@ public class ExtensionManager : IExtensionManager private FrozenDictionary _extensions; private List _extensionsInfos; - private Dictionary _features; + private Dictionary _features; private IFeatureInfo[] _featureInfos; private readonly ConcurrentDictionary>> _featureDependencies = new(); @@ -102,20 +102,20 @@ public Task LoadExtensionAsync(IExtensionInfo extensionInfo) return Task.FromResult(extension); } - public async Task> LoadFeaturesAsync() + public async Task> LoadFeaturesAsync() { await EnsureInitializedAsync(); return _features.Values; } - public async Task> LoadFeaturesAsync(string[] featureIdsToLoad) + public async Task> LoadFeaturesAsync(string[] featureIdsToLoad) { await EnsureInitializedAsync(); var features = new HashSet(GetFeatures(featureIdsToLoad).Select(f => f.Id)); var loadedFeatures = _features.Values - .Where(f => features.Contains(f.FeatureInfo.Id)); + .Where(f => features.Contains(f.Id)); return loadedFeatures; } @@ -131,9 +131,7 @@ public IEnumerable GetFeatureDependencies(string featureId) return []; } - var feature = entry.FeatureInfo; - - return GetFeatureDependencies(feature, _featureInfos); + return GetFeatureDependencies(entry, _featureInfos); })).Value; } @@ -148,9 +146,7 @@ public IEnumerable GetDependentFeatures(string featureId) return []; } - var feature = entry.FeatureInfo; - - return GetDependentFeatures(feature, _featureInfos); + return GetDependentFeatures(entry, _featureInfos); })).Value; } @@ -323,7 +319,7 @@ await modules.ForEachAsync((module) => return Task.CompletedTask; }); - var loadedFeatures = new Dictionary(); + var loadedFeatures = new Dictionary(); // Get all valid types from any extension var allTypesByExtension = loadedExtensions.SelectMany(extension => @@ -356,17 +352,13 @@ await modules.ForEachAsync((module) => _typeFeatureProvider.TryAdd(type, feature); } } - else - { - featureTypes = []; - } - loadedFeatures.Add(feature.Id, new FeatureEntry(feature, featureTypes)); + loadedFeatures.Add(feature.Id, feature); } } // Feature infos and entries are ordered by priority and dependencies. - _featureInfos = Order(loadedFeatures.Values.Select(f => f.FeatureInfo)); + _featureInfos = Order(loadedFeatures.Values); _features = _featureInfos.ToDictionary(f => f.Id, f => loadedFeatures[f.Id]); // Extensions are also ordered according to the weight of their first features. diff --git a/src/OrchardCore/OrchardCore/Shell/Builders/CompositionStrategy.cs b/src/OrchardCore/OrchardCore/Shell/Builders/CompositionStrategy.cs index a75445aea2c..ba552062a03 100644 --- a/src/OrchardCore/OrchardCore/Shell/Builders/CompositionStrategy.cs +++ b/src/OrchardCore/OrchardCore/Shell/Builders/CompositionStrategy.cs @@ -14,13 +14,16 @@ namespace OrchardCore.Environment.Shell.Builders public class CompositionStrategy : ICompositionStrategy { private readonly IExtensionManager _extensionManager; + private readonly ITypeFeatureProvider _typeFeatureProvider; private readonly ILogger _logger; public CompositionStrategy( IExtensionManager extensionManager, + ITypeFeatureProvider typeFeatureProvider, ILogger logger) { _extensionManager = extensionManager; + _typeFeatureProvider = typeFeatureProvider; _logger = logger; } @@ -35,11 +38,11 @@ public async Task ComposeAsync(ShellSettings settings, ShellDesc var features = await _extensionManager.LoadFeaturesAsync(featureNames); - var entries = new Dictionary(); + var entries = new Dictionary(); foreach (var feature in features) { - foreach (var exportedType in feature.ExportedTypes) + foreach (var exportedType in _typeFeatureProvider.GetTypesForFeature(feature)) { var requiredFeatures = RequireFeaturesAttribute.GetRequiredFeatureNamesForType(exportedType); diff --git a/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs b/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs index 4ae3c1281de..ec5184b3de2 100644 --- a/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs +++ b/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs @@ -68,7 +68,7 @@ public async Task CreateContainerAsync(ShellSettings settings, } // Ignore Startup class from main application - if (blueprint.Dependencies.TryGetValue(rawStartup, out var startupFeature) && startupFeature.FeatureInfo.Id == _applicationFeature.Id) + if (blueprint.Dependencies.TryGetValue(rawStartup, out var startupFeature) && startupFeature.Id == _applicationFeature.Id) { continue; } @@ -118,7 +118,7 @@ public async Task CreateContainerAsync(ShellSettings settings, // Let any module add custom service descriptors to the tenant foreach (var startup in startups) { - var feature = blueprint.Dependencies.FirstOrDefault(x => x.Key == startup.GetType()).Value?.FeatureInfo; + var feature = blueprint.Dependencies.FirstOrDefault(x => x.Key == startup.GetType()).Value; // If the startup is not coming from an extension, associate it to the application feature. // For instance when Startup classes are registered with Configure() from the application. diff --git a/src/OrchardCore/OrchardCore/Shell/Descriptor/Settings/ConfiguredFeaturesShellDescriptorManager.cs b/src/OrchardCore/OrchardCore/Shell/Descriptor/Settings/ConfiguredFeaturesShellDescriptorManager.cs index 53f2882fcca..d2d88eac1b8 100644 --- a/src/OrchardCore/OrchardCore/Shell/Descriptor/Settings/ConfiguredFeaturesShellDescriptorManager.cs +++ b/src/OrchardCore/OrchardCore/Shell/Descriptor/Settings/ConfiguredFeaturesShellDescriptorManager.cs @@ -43,7 +43,7 @@ public async Task GetShellDescriptorAsync() var featureIds = features.Select(sf => sf.Id).ToArray(); var missingDependencies = (await _extensionManager.LoadFeaturesAsync(featureIds)) - .Select(entry => entry.FeatureInfo.Id) + .Select(entry => entry.Id) .Except(featureIds) .Select(id => new ShellFeature(id)); diff --git a/src/OrchardCore/OrchardCore/Shell/Descriptor/Settings/SetFeaturesShellDescriptorManager.cs b/src/OrchardCore/OrchardCore/Shell/Descriptor/Settings/SetFeaturesShellDescriptorManager.cs index bbbc349b007..010289ecd46 100644 --- a/src/OrchardCore/OrchardCore/Shell/Descriptor/Settings/SetFeaturesShellDescriptorManager.cs +++ b/src/OrchardCore/OrchardCore/Shell/Descriptor/Settings/SetFeaturesShellDescriptorManager.cs @@ -30,7 +30,7 @@ public async Task GetShellDescriptorAsync() var featureIds = _shellFeatures.Distinct().Select(sf => sf.Id).ToArray(); var missingDependencies = (await _extensionManager.LoadFeaturesAsync(featureIds)) - .Select(entry => entry.FeatureInfo.Id) + .Select(entry => entry.Id) .Except(featureIds) .Select(id => new ShellFeature(id)); diff --git a/test/OrchardCore.Tests/DisplayManagement/Decriptors/DefaultShapeTableManagerTests.cs b/test/OrchardCore.Tests/DisplayManagement/Decriptors/DefaultShapeTableManagerTests.cs index 391cb7cf62c..6e9b84d605c 100644 --- a/test/OrchardCore.Tests/DisplayManagement/Decriptors/DefaultShapeTableManagerTests.cs +++ b/test/OrchardCore.Tests/DisplayManagement/Decriptors/DefaultShapeTableManagerTests.cs @@ -241,14 +241,14 @@ public Task> LoadExtensionsAsync(IEnumerable LoadFeatureAsync(IFeatureInfo feature) + public Task LoadFeatureAsync(IFeatureInfo feature) { - return Task.FromResult(new FeatureEntry(feature)); + return Task.FromResult(feature); } - public Task> LoadFeaturesAsync(IEnumerable features) + public Task> LoadFeaturesAsync(IEnumerable features) { - return Task.FromResult(features.Select(x => new FeatureEntry(x))); + return Task.FromResult(features); } #pragma warning restore CA1822 // Mark members as static @@ -267,12 +267,12 @@ public IEnumerable GetDependentFeatures(string featureId) throw new NotImplementedException(); } - public Task> LoadFeaturesAsync() + public Task> LoadFeaturesAsync() { throw new NotImplementedException(); } - public Task> LoadFeaturesAsync(string[] featureIdsToLoad) + public Task> LoadFeaturesAsync(string[] featureIdsToLoad) { throw new NotImplementedException(); } diff --git a/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs b/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs index 3b8c6f93484..5ba8829f37d 100644 --- a/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs +++ b/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs @@ -154,14 +154,14 @@ private static ShellBlueprint CreateBlueprint() { Settings = new ShellSettings(), Descriptor = new ShellDescriptor(), - Dependencies = new Dictionary() + Dependencies = new Dictionary() }; } public static IFeatureInfo AddStartup(ShellBlueprint shellBlueprint, Type startupType) { var featureInfo = new FeatureInfo(startupType.Name, startupType.Name, 1, "Tests", null, null, null, false, false, false); - shellBlueprint.Dependencies.Add(startupType, new FeatureEntry(featureInfo)); + shellBlueprint.Dependencies.Add(startupType, featureInfo); return featureInfo; } diff --git a/test/OrchardCore.Tests/Stubs/StubExtensionManager.cs b/test/OrchardCore.Tests/Stubs/StubExtensionManager.cs index 7ddaff5ff6a..1b54732ad52 100644 --- a/test/OrchardCore.Tests/Stubs/StubExtensionManager.cs +++ b/test/OrchardCore.Tests/Stubs/StubExtensionManager.cs @@ -40,12 +40,12 @@ public Task LoadExtensionAsync(IExtensionInfo extensionInfo) throw new NotImplementedException(); } - public Task> LoadFeaturesAsync() + public Task> LoadFeaturesAsync() { throw new NotImplementedException(); } - public Task> LoadFeaturesAsync(string[] featureIdsToLoad) + public Task> LoadFeaturesAsync(string[] featureIdsToLoad) { throw new NotImplementedException(); } From 7f5352dc32dd4c68b53190d34560be858dd251ab Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Sat, 20 Apr 2024 13:57:40 +0200 Subject: [PATCH 03/20] Moves populating the `ITypeFeatureProvider` completely into the `ShellContainerFactory`. Before, that responsibility was split between the `ExtensionManager` and shell container factory. --- .../Extensions/IExtensionManager.cs | 2 + .../Extensions/ExtensionManager.cs | 54 ++++----------- .../Features/TypeFeatureProvider.cs | 15 +--- .../Shell/Builders/ShellContainerFactory.cs | 69 ++++++++++++++++--- 4 files changed, 75 insertions(+), 65 deletions(-) diff --git a/src/OrchardCore/OrchardCore.Abstractions/Extensions/IExtensionManager.cs b/src/OrchardCore/OrchardCore.Abstractions/Extensions/IExtensionManager.cs index c34fb79ef04..11854790320 100644 --- a/src/OrchardCore/OrchardCore.Abstractions/Extensions/IExtensionManager.cs +++ b/src/OrchardCore/OrchardCore.Abstractions/Extensions/IExtensionManager.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Threading.Tasks; using OrchardCore.Environment.Extensions.Features; @@ -8,6 +9,7 @@ public interface IExtensionManager { IExtensionInfo GetExtension(string extensionId); IEnumerable GetExtensions(); + IEnumerable GetExportedExtensionTypes(IExtensionInfo extensionInfo); Task LoadExtensionAsync(IExtensionInfo extensionInfo); IEnumerable GetFeatures(); diff --git a/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs b/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs index 258005e4a5d..9c3bab36d8a 100644 --- a/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs +++ b/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs @@ -3,7 +3,6 @@ using System.Collections.Frozen; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -25,7 +24,6 @@ public class ExtensionManager : IExtensionManager private readonly IExtensionDependencyStrategy[] _extensionDependencyStrategies; private readonly IExtensionPriorityStrategy[] _extensionPriorityStrategies; - private readonly ITypeFeatureProvider _typeFeatureProvider; private readonly IFeaturesProvider _featuresProvider; private FrozenDictionary _extensions; @@ -43,14 +41,12 @@ public ExtensionManager( IApplicationContext applicationContext, IEnumerable extensionDependencyStrategies, IEnumerable extensionPriorityStrategies, - ITypeFeatureProvider typeFeatureProvider, IFeaturesProvider featuresProvider, ILogger logger) { _applicationContext = applicationContext; _extensionDependencyStrategies = extensionDependencyStrategies as IExtensionDependencyStrategy[] ?? extensionDependencyStrategies.ToArray(); _extensionPriorityStrategies = extensionPriorityStrategies as IExtensionPriorityStrategy[] ?? extensionPriorityStrategies.ToArray(); - _typeFeatureProvider = typeFeatureProvider; _featuresProvider = featuresProvider; L = logger; } @@ -93,6 +89,18 @@ public IEnumerable GetFeatures(string[] featureIdsToLoad) } } + public IEnumerable GetExportedExtensionTypes(IExtensionInfo extensionInfo) + { + EnsureInitialized(); + + if(_extensions.TryGetValue(extensionInfo.Id, out var extension)) + { + return extension.ExportedTypes; + } + + return []; + } + public Task LoadExtensionAsync(IExtensionInfo extensionInfo) { EnsureInitialized(); @@ -257,13 +265,6 @@ private static List GetFeatureDependenciesFunc(IFeatureInfo curren return list; } - private static string GetSourceFeatureNameForType(Type type, string extensionId) - { - var attribute = type.GetCustomAttributes(false).FirstOrDefault(); - - return attribute?.FeatureName ?? extensionId; - } - private void EnsureInitialized() { if (_isInitialized) @@ -321,38 +322,12 @@ await modules.ForEachAsync((module) => var loadedFeatures = new Dictionary(); - // Get all valid types from any extension - var allTypesByExtension = loadedExtensions.SelectMany(extension => - extension.Value.ExportedTypes.Where(IsComponentType) - .Select(type => new - { - ExtensionEntry = extension.Value, - Type = type - })).ToArray(); - - var typesByFeature = allTypesByExtension - .GroupBy(typeByExtension => GetSourceFeatureNameForType( - typeByExtension.Type, - typeByExtension.ExtensionEntry.ExtensionInfo.Id)) - .ToDictionary( - group => group.Key, - group => group.Select(typesByExtension => typesByExtension.Type).ToArray()); - foreach (var loadedExtension in loadedExtensions) { var extension = loadedExtension.Value; foreach (var feature in extension.ExtensionInfo.Features) { - // Features can have no types - if (typesByFeature.TryGetValue(feature.Id, out var featureTypes)) - { - foreach (var type in featureTypes) - { - _typeFeatureProvider.TryAdd(type, feature); - } - } - loadedFeatures.Add(feature.Id, feature); } } @@ -377,11 +352,6 @@ await modules.ForEachAsync((module) => } } - private static bool IsComponentType(Type type) - { - return type.IsClass && !type.IsAbstract && type.IsPublic; - } - private IFeatureInfo[] Order(IEnumerable featuresToOrder) { return featuresToOrder diff --git a/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs b/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs index fb7793893e8..0f7b2202b6a 100644 --- a/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs +++ b/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using OrchardCore.Environment.Extensions.Features; @@ -28,19 +27,7 @@ public IEnumerable GetTypesForFeature(IFeatureInfo feature) public void TryAdd(Type type, IFeatureInfo feature) { - _ = _features.AddOrUpdate(type, (t, f) => f, (curType, curFeature, newFeature) => - { - // If the type is currently only mapped to the module, update it with the more specific feature. - if (curFeature != newFeature && - curFeature.Extension.Manifest.ModuleInfo.Id == curFeature.Id && - curFeature.Extension.Features.Contains(newFeature)) - { - Debug.WriteLine($"TypeFeatureProvider changed mapping of type '{curType}' from '{curFeature.Id}' to '{newFeature.Id}'."); - return newFeature; - } - - return curFeature; - }, feature); + _features.TryAdd(type, feature); } } } diff --git a/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs b/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs index ec5184b3de2..74d07282a58 100644 --- a/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs +++ b/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs @@ -132,9 +132,27 @@ public async Task CreateContainerAsync(ShellSettings settings, // Rebuild the service provider from the updated collection. shellServiceProvider = tenantServiceCollection.BuildServiceProvider(true); - // Register all DIed types in ITypeFeatureProvider var typeFeatureProvider = shellServiceProvider.GetRequiredService(); + PopulateTypeFeatureProvider(typeFeatureProvider, featureAwareServiceCollection); + return shellServiceProvider; + } + + private void EnsureApplicationFeature() + { + if (_applicationFeature is null) + { + lock (this) + { + _applicationFeature ??= _extensionManager.GetFeatures() + .FirstOrDefault(f => f.Id == _hostingEnvironment.ApplicationName); + } + } + } + + private void PopulateTypeFeatureProvider(ITypeFeatureProvider typeFeatureProvider, FeatureAwareServiceCollection featureAwareServiceCollection) + { + // Register all DIed types in ITypeFeatureProvider first. These are then already feature specific. foreach (var featureServiceCollection in featureAwareServiceCollection.FeatureCollections) { foreach (var serviceDescriptor in featureServiceCollection.Value) @@ -162,19 +180,52 @@ public async Task CreateContainerAsync(ShellSettings settings, } } - return shellServiceProvider; - } + // Get all other valid types from all extension and add them to the type feature provider as well. + var extensions = _extensionManager.GetExtensions(); - private void EnsureApplicationFeature() - { - if (_applicationFeature is null) + var allTypesByExtension = extensions + .SelectMany(extension => + _extensionManager.GetExportedExtensionTypes(extension).Where(IsComponentType) + .Select(type => new + { + Extension = extension, + Type = type + })); + + var typesByFeature = allTypesByExtension + .GroupBy(typeByExtension => GetSourceFeatureNameForType( + typeByExtension.Type, + typeByExtension.Extension.Id)) + .ToDictionary( + group => group.Key, + group => group.Select(typesByExtension => typesByExtension.Type)); + + foreach (var extension in extensions) { - lock (this) + foreach (var feature in extension.Features) { - _applicationFeature ??= _extensionManager.GetFeatures() - .FirstOrDefault(f => f.Id == _hostingEnvironment.ApplicationName); + // Features can have no types + if (typesByFeature.TryGetValue(feature.Id, out var featureTypes)) + { + foreach (var type in featureTypes) + { + typeFeatureProvider.TryAdd(type, feature); + } + } } } } + + private static string GetSourceFeatureNameForType(Type type, string extensionId) + { + var attribute = type.GetCustomAttributes(false).FirstOrDefault(); + + return attribute?.FeatureName ?? extensionId; + } + + private static bool IsComponentType(Type type) + { + return type.IsClass && !type.IsAbstract && type.IsPublic; + } } } From b65b1e092f5cf87320c061764511b8950c348d7c Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Mon, 22 Apr 2024 13:35:12 +0200 Subject: [PATCH 04/20] Fixes unit tests. --- .../Decriptors/DefaultShapeTableManagerTests.cs | 5 +++++ test/OrchardCore.Tests/Extensions/ExtensionManagerTests.cs | 3 --- test/OrchardCore.Tests/Stubs/StubExtensionManager.cs | 7 ++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/test/OrchardCore.Tests/DisplayManagement/Decriptors/DefaultShapeTableManagerTests.cs b/test/OrchardCore.Tests/DisplayManagement/Decriptors/DefaultShapeTableManagerTests.cs index 6e9b84d605c..65890cef9fc 100644 --- a/test/OrchardCore.Tests/DisplayManagement/Decriptors/DefaultShapeTableManagerTests.cs +++ b/test/OrchardCore.Tests/DisplayManagement/Decriptors/DefaultShapeTableManagerTests.cs @@ -276,6 +276,11 @@ public Task> LoadFeaturesAsync(string[] featureIdsToLo { throw new NotImplementedException(); } + + public IEnumerable GetExportedExtensionTypes(IExtensionInfo extensionInfo) + { + throw new NotImplementedException(); + } } public class TestShapeProvider : IShapeTableProvider diff --git a/test/OrchardCore.Tests/Extensions/ExtensionManagerTests.cs b/test/OrchardCore.Tests/Extensions/ExtensionManagerTests.cs index 1ae8e2bfd45..47b5711f518 100644 --- a/test/OrchardCore.Tests/Extensions/ExtensionManagerTests.cs +++ b/test/OrchardCore.Tests/Extensions/ExtensionManagerTests.cs @@ -31,7 +31,6 @@ public ExtensionManagerTests() _applicationContext, new[] { new ExtensionDependencyStrategy() }, new[] { new ExtensionPriorityStrategy() }, - new TypeFeatureProvider(), _moduleFeatureProvider, new NullLogger() ); @@ -40,7 +39,6 @@ public ExtensionManagerTests() _applicationContext, new[] { new ExtensionDependencyStrategy() }, new[] { new ExtensionPriorityStrategy() }, - new TypeFeatureProvider(), _themeFeatureProvider, new NullLogger() ); @@ -49,7 +47,6 @@ public ExtensionManagerTests() _applicationContext, new IExtensionDependencyStrategy[] { new ExtensionDependencyStrategy(), new ThemeExtensionDependencyStrategy() }, new[] { new ExtensionPriorityStrategy() }, - new TypeFeatureProvider(), _themeFeatureProvider, new NullLogger() ); diff --git a/test/OrchardCore.Tests/Stubs/StubExtensionManager.cs b/test/OrchardCore.Tests/Stubs/StubExtensionManager.cs index 1b54732ad52..2b81df56ef3 100644 --- a/test/OrchardCore.Tests/Stubs/StubExtensionManager.cs +++ b/test/OrchardCore.Tests/Stubs/StubExtensionManager.cs @@ -10,6 +10,11 @@ public IEnumerable GetDependentFeatures(string featureId) throw new NotImplementedException(); } + public IEnumerable GetExportedExtensionTypes(IExtensionInfo extensionInfo) + { + throw new NotImplementedException(); + } + public IExtensionInfo GetExtension(string extensionId) { throw new NotImplementedException(); @@ -17,7 +22,7 @@ public IExtensionInfo GetExtension(string extensionId) public IEnumerable GetExtensions() { - throw new NotImplementedException(); + return []; } public IEnumerable GetFeatureDependencies(string featureId) From 6e509a62b46f7d997457d71995ef7917a0a91f52 Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Tue, 23 Apr 2024 12:14:51 +0200 Subject: [PATCH 05/20] Changes the `ITypeFeatureProvider` to allow types that are used by multiple features. --- .../Controllers/AdminController.cs | 4 +- .../OrchardCore.Roles/Services/RoleUpdater.cs | 4 +- .../Features/ITypeFeatureProvider.cs | 2 +- .../Shell/Builders/Models/ShellBlueprint.cs | 2 +- .../Migration/DataMigrationManager.cs | 4 +- .../Descriptors/DefaultShapeTableManager.cs | 2 +- .../ShapeAttributeBindingStrategy.cs | 2 +- .../ModularApplicationModelProvider.cs | 6 +- .../Services/RecipeMigrator.cs | 2 +- .../Features/TypeFeatureProvider.cs | 14 ++--- .../Shell/Builders/CompositionStrategy.cs | 13 +++- .../Shell/Builders/ShellContainerFactory.cs | 62 +++++++++---------- .../ShapeDescriptorIndexBenchmark.cs | 3 +- .../Shell/ShellContainerFactoryTests.cs | 8 +-- 14 files changed, 71 insertions(+), 57 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs index 41b1bbb1a87..4fa7bc35499 100644 --- a/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs @@ -243,7 +243,9 @@ private async Task>> Get var installedPermissions = new Dictionary>(); foreach (var permissionProvider in _permissionProviders) { - var feature = _typeFeatureProvider.GetFeatureForDependency(permissionProvider.GetType()); + // Note: This uses the last feature, because that is likely the most specific. The first feature entry + // is always the extension itself, if multiple permission providers are sharing the same assembly. + var feature = _typeFeatureProvider.GetFeaturesForDependency(permissionProvider.GetType()).Last(); var permissions = await permissionProvider.GetPermissionsAsync(); foreach (var permission in permissions) diff --git a/src/OrchardCore.Modules/OrchardCore.Roles/Services/RoleUpdater.cs b/src/OrchardCore.Modules/OrchardCore.Roles/Services/RoleUpdater.cs index 774da790b11..c4440bbfb87 100644 --- a/src/OrchardCore.Modules/OrchardCore.Roles/Services/RoleUpdater.cs +++ b/src/OrchardCore.Modules/OrchardCore.Roles/Services/RoleUpdater.cs @@ -54,7 +54,7 @@ private async Task UpdateRolesForInstalledFeatureAsync(IFeatureInfo feature) _installedFeatures.Add(feature.Id); var providers = _permissionProviders - .Where(provider => _typeFeatureProvider.GetFeatureForDependency(provider.GetType()).Id == feature.Id); + .Where(provider => _typeFeatureProvider.GetFeaturesForDependency(provider.GetType()).Any(p => p.Id == feature.Id)); if (!providers.Any()) { @@ -98,7 +98,7 @@ private async Task UpdateRolesForEnabledFeatureAsync(IFeatureInfo feature) } var providers = _permissionProviders - .Where(provider => _typeFeatureProvider.GetFeatureForDependency(provider.GetType()).Id == feature.Id); + .Where(provider => _typeFeatureProvider.GetFeaturesForDependency(provider.GetType()).Any(p => p.Id == feature.Id)); if (!providers.Any()) { diff --git a/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs b/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs index 7dae940f8a0..a526acf4c9e 100644 --- a/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs +++ b/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs @@ -10,7 +10,7 @@ namespace OrchardCore.Environment.Extensions /// public interface ITypeFeatureProvider { - IFeatureInfo GetFeatureForDependency(Type dependency); + IEnumerable GetFeaturesForDependency(Type dependency); IEnumerable GetTypesForFeature(IFeatureInfo feature); void TryAdd(Type type, IFeatureInfo feature); } diff --git a/src/OrchardCore/OrchardCore.Abstractions/Shell/Builders/Models/ShellBlueprint.cs b/src/OrchardCore/OrchardCore.Abstractions/Shell/Builders/Models/ShellBlueprint.cs index 9103457fd66..35d19b59b09 100644 --- a/src/OrchardCore/OrchardCore.Abstractions/Shell/Builders/Models/ShellBlueprint.cs +++ b/src/OrchardCore/OrchardCore.Abstractions/Shell/Builders/Models/ShellBlueprint.cs @@ -15,6 +15,6 @@ public class ShellBlueprint public ShellSettings Settings { get; set; } public ShellDescriptor Descriptor { get; set; } - public IDictionary Dependencies { get; set; } + public IDictionary> Dependencies { get; set; } } } diff --git a/src/OrchardCore/OrchardCore.Data.YesSql/Migration/DataMigrationManager.cs b/src/OrchardCore/OrchardCore.Data.YesSql/Migration/DataMigrationManager.cs index 3a8530abaa4..a4f532e7186 100644 --- a/src/OrchardCore/OrchardCore.Data.YesSql/Migration/DataMigrationManager.cs +++ b/src/OrchardCore/OrchardCore.Data.YesSql/Migration/DataMigrationManager.cs @@ -89,7 +89,7 @@ public async Task> GetFeaturesThatNeedUpdateAsync() return GetCreateMethod(dataMigration) != null; }); - return outOfDateMigrations.Select(m => _typeFeatureProvider.GetFeatureForDependency(m.GetType()).Id).ToArray(); + return outOfDateMigrations.Select(m => _typeFeatureProvider.GetFeaturesForDependency(m.GetType()).First().Id).ToArray(); } public async Task Uninstall(string feature) @@ -257,7 +257,7 @@ private static async Task InvokeMethod(MethodInfo method, IDataMigration mi private IDataMigration[] GetDataMigrations(string featureId) { var migrations = _dataMigrations - .Where(dm => _typeFeatureProvider.GetFeatureForDependency(dm.GetType()).Id == featureId) + .Where(dm => _typeFeatureProvider.GetFeaturesForDependency(dm.GetType()).First().Id == featureId) .ToArray(); return migrations; diff --git a/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/DefaultShapeTableManager.cs b/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/DefaultShapeTableManager.cs index c9987c42d31..f2247f74ae5 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/DefaultShapeTableManager.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/DefaultShapeTableManager.cs @@ -91,7 +91,7 @@ private async Task BuildShapeTableAsync(string themeId) foreach (var bindingStrategy in bindingStrategies) { - var strategyFeature = typeFeatureProvider.GetFeatureForDependency(bindingStrategy.GetType()); + var strategyFeature = typeFeatureProvider.GetFeaturesForDependency(bindingStrategy.GetType()).First(); var builder = new ShapeTableBuilder(strategyFeature, excludedFeatures); await bindingStrategy.DiscoverAsync(builder); diff --git a/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs b/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs index 697a27983a5..b24cd0f9e63 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs @@ -52,7 +52,7 @@ public override ValueTask DiscoverAsync(ShapeTableBuilder builder) var occurrence = iter; var shapeType = occurrence.ShapeAttribute.ShapeType ?? occurrence.MethodInfo.Name; builder.Describe(shapeType) - .From(_typeFeatureProvider.GetFeatureForDependency(occurrence.ServiceType)) + .From(_typeFeatureProvider.GetFeaturesForDependency(occurrence.ServiceType).First()) .BoundAs( occurrence.MethodInfo.DeclaringType.FullName + "::" + occurrence.MethodInfo.Name, CreateDelegate(occurrence)); diff --git a/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs b/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs index 4a5946af9b2..455bf1bcb6f 100644 --- a/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs +++ b/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs @@ -1,3 +1,4 @@ +using System.Linq; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.Extensions.Hosting; using OrchardCore.Environment.Extensions; @@ -42,10 +43,11 @@ public void OnProvidersExecuted(ApplicationModelProviderContext context) foreach (var controller in context.Result.Controllers) { var controllerType = controller.ControllerType.AsType(); - var blueprint = _typeFeatureProvider.GetFeatureForDependency(controllerType); + + var blueprint = _typeFeatureProvider.GetFeaturesForDependency(controllerType).FirstOrDefault(); if (blueprint != null) - { + { if (blueprint.Extension.Id == _hostingEnvironment.ApplicationName && !_shellSettings.IsRunning()) { // Don't serve any action of the application'module which is enabled during a setup. diff --git a/src/OrchardCore/OrchardCore.Recipes.Core/Services/RecipeMigrator.cs b/src/OrchardCore/OrchardCore.Recipes.Core/Services/RecipeMigrator.cs index ce453ef4086..de08d54509f 100644 --- a/src/OrchardCore/OrchardCore.Recipes.Core/Services/RecipeMigrator.cs +++ b/src/OrchardCore/OrchardCore.Recipes.Core/Services/RecipeMigrator.cs @@ -39,7 +39,7 @@ public RecipeMigrator( public async Task ExecuteAsync(string recipeFileName, IDataMigration migration) { - var featureInfo = _typeFeatureProvider.GetFeatureForDependency(migration.GetType()); + var featureInfo = _typeFeatureProvider.GetFeaturesForDependency(migration.GetType()).First(); var recipeBasePath = Path.Combine(featureInfo.Extension.SubPath, "Migrations").Replace('\\', '/'); var recipeFilePath = Path.Combine(recipeBasePath, recipeFileName).Replace('\\', '/'); diff --git a/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs b/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs index 0f7b2202b6a..e856a7ba956 100644 --- a/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs +++ b/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs @@ -8,26 +8,26 @@ namespace OrchardCore.Environment.Extensions { public class TypeFeatureProvider : ITypeFeatureProvider { - private readonly ConcurrentDictionary _features = new(); + private readonly ConcurrentDictionary> _features = new(); - public IFeatureInfo GetFeatureForDependency(Type dependency) + public IEnumerable GetFeaturesForDependency(Type dependency) { - if (_features.TryGetValue(dependency, out var feature)) + if (_features.TryGetValue(dependency, out var features)) { - return feature; + return features; } - throw new InvalidOperationException($"Could not resolve feature for type {dependency.Name}"); + throw new InvalidOperationException($"Could not resolve features for type {dependency.Name}"); } public IEnumerable GetTypesForFeature(IFeatureInfo feature) { - return _features.Where(kv => kv.Value == feature).Select(kv => kv.Key); + return _features.Where(kv => kv.Value.Contains(feature)).Select(kv => kv.Key); } public void TryAdd(Type type, IFeatureInfo feature) { - _features.TryAdd(type, feature); + _features.AddOrUpdate(type, (key, value) => [value], (key, features, value) => features.Contains(value) ? features : features.Append(value).ToArray(), feature); } } } diff --git a/src/OrchardCore/OrchardCore/Shell/Builders/CompositionStrategy.cs b/src/OrchardCore/OrchardCore/Shell/Builders/CompositionStrategy.cs index ba552062a03..aedbfa8eb44 100644 --- a/src/OrchardCore/OrchardCore/Shell/Builders/CompositionStrategy.cs +++ b/src/OrchardCore/OrchardCore/Shell/Builders/CompositionStrategy.cs @@ -38,7 +38,7 @@ public async Task ComposeAsync(ShellSettings settings, ShellDesc var features = await _extensionManager.LoadFeaturesAsync(featureNames); - var entries = new Dictionary(); + var entries = new Dictionary>(); foreach (var feature in features) { @@ -48,7 +48,16 @@ public async Task ComposeAsync(ShellSettings settings, ShellDesc if (requiredFeatures.All(x => featureNames.Contains(x))) { - entries.Add(exportedType, feature); + if (entries.TryGetValue(exportedType, out var featureDependencies)) + { + featureDependencies = featureDependencies.Append(feature).ToArray(); + } + else + { + featureDependencies = [feature]; + } + + entries[exportedType] = featureDependencies; } } } diff --git a/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs b/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs index 74d07282a58..0c9834fcdf9 100644 --- a/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs +++ b/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs @@ -68,7 +68,7 @@ public async Task CreateContainerAsync(ShellSettings settings, } // Ignore Startup class from main application - if (blueprint.Dependencies.TryGetValue(rawStartup, out var startupFeature) && startupFeature.Id == _applicationFeature.Id) + if (blueprint.Dependencies.TryGetValue(rawStartup, out var startupFeatures) && startupFeatures.Any(f => f.Id == _applicationFeature.Id)) { continue; } @@ -118,7 +118,7 @@ public async Task CreateContainerAsync(ShellSettings settings, // Let any module add custom service descriptors to the tenant foreach (var startup in startups) { - var feature = blueprint.Dependencies.FirstOrDefault(x => x.Key == startup.GetType()).Value; + var feature = blueprint.Dependencies.FirstOrDefault(x => x.Key == startup.GetType()).Value?.FirstOrDefault(); // If the startup is not coming from an extension, associate it to the application feature. // For instance when Startup classes are registered with Configure() from the application. @@ -152,35 +152,7 @@ private void EnsureApplicationFeature() private void PopulateTypeFeatureProvider(ITypeFeatureProvider typeFeatureProvider, FeatureAwareServiceCollection featureAwareServiceCollection) { - // Register all DIed types in ITypeFeatureProvider first. These are then already feature specific. - foreach (var featureServiceCollection in featureAwareServiceCollection.FeatureCollections) - { - foreach (var serviceDescriptor in featureServiceCollection.Value) - { - var type = serviceDescriptor.GetImplementationType(); - - if (type is not null) - { - var feature = featureServiceCollection.Key; - - if (feature == _applicationFeature) - { - var attribute = type.GetCustomAttributes(false).FirstOrDefault(); - - if (attribute is not null) - { - feature = featureServiceCollection.Key.Extension.Features - .FirstOrDefault(f => f.Id == attribute.FeatureName) - ?? feature; - } - } - - typeFeatureProvider.TryAdd(type, feature); - } - } - } - - // Get all other valid types from all extension and add them to the type feature provider as well. + // Get all types from all extension and add them to the type feature provider. var extensions = _extensionManager.GetExtensions(); var allTypesByExtension = extensions @@ -214,6 +186,34 @@ private void PopulateTypeFeatureProvider(ITypeFeatureProvider typeFeatureProvide } } } + + // Register all DIed types in ITypeFeatureProvider + foreach (var featureServiceCollection in featureAwareServiceCollection.FeatureCollections) + { + foreach (var serviceDescriptor in featureServiceCollection.Value) + { + var type = serviceDescriptor.GetImplementationType(); + + if (type is not null) + { + var feature = featureServiceCollection.Key; + + if (feature == _applicationFeature) + { + var attribute = type.GetCustomAttributes(false).FirstOrDefault(); + + if (attribute is not null) + { + feature = featureServiceCollection.Key.Extension.Features + .FirstOrDefault(f => f.Id == attribute.FeatureName) + ?? feature; + } + } + + typeFeatureProvider.TryAdd(type, feature); + } + } + } } private static string GetSourceFeatureNameForType(Type type, string extensionId) diff --git a/test/OrchardCore.Benchmarks/ShapeDescriptorIndexBenchmark.cs b/test/OrchardCore.Benchmarks/ShapeDescriptorIndexBenchmark.cs index 35543892307..617a1a1af8f 100644 --- a/test/OrchardCore.Benchmarks/ShapeDescriptorIndexBenchmark.cs +++ b/test/OrchardCore.Benchmarks/ShapeDescriptorIndexBenchmark.cs @@ -28,7 +28,8 @@ await content.UsingTenantScopeAsync(async scope => var typeFeatureProvider = scope.ServiceProvider.GetRequiredService(); foreach (var bindingStrategy in bindingStrategies) { - var strategyFeature = typeFeatureProvider.GetFeatureForDependency(bindingStrategy.GetType()); + var strategyFeature = typeFeatureProvider.GetFeaturesForDependency(bindingStrategy.GetType()).First(); + var builder = new ShapeTableBuilder(strategyFeature, []); await bindingStrategy.DiscoverAsync(builder); BuildDescriptors(bindingStrategy, builder.BuildAlterations()); diff --git a/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs b/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs index 5ba8829f37d..2df22a213d7 100644 --- a/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs +++ b/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs @@ -56,7 +56,7 @@ public async Task CanRegisterDefaultServiceWithFeatureInfo() var typeFeatureProvider = _applicationServiceProvider.GetService(); Assert.IsType(container.GetRequiredService(typeof(ITestService))); - Assert.Same(expectedFeatureInfo, typeFeatureProvider.GetFeatureForDependency(typeof(TestService))); + Assert.Same(expectedFeatureInfo, typeFeatureProvider.GetFeaturesForDependency(typeof(TestService)).FirstOrDefault()); } [Fact] @@ -76,7 +76,7 @@ public async Task CanReplaceDefaultServiceWithCustomService() // Check that the default service has been replaced with the custom service and that the feature info is correct. Assert.IsType(container.GetRequiredService(typeof(ITestService))); - Assert.Same(expectedFeatureInfo, typeFeatureProvider.GetFeatureForDependency(typeof(CustomTestService))); + Assert.Same(expectedFeatureInfo, typeFeatureProvider.GetFeaturesForDependency(typeof(CustomTestService)).FirstOrDefault()); } [Fact] @@ -154,14 +154,14 @@ private static ShellBlueprint CreateBlueprint() { Settings = new ShellSettings(), Descriptor = new ShellDescriptor(), - Dependencies = new Dictionary() + Dependencies = new Dictionary>() }; } public static IFeatureInfo AddStartup(ShellBlueprint shellBlueprint, Type startupType) { var featureInfo = new FeatureInfo(startupType.Name, startupType.Name, 1, "Tests", null, null, null, false, false, false); - shellBlueprint.Dependencies.Add(startupType, featureInfo); + shellBlueprint.Dependencies.Add(startupType, [featureInfo]); return featureInfo; } From 1a744963f63ff38c478a2dc4255da83280ddc486 Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Tue, 23 Apr 2024 13:18:53 +0200 Subject: [PATCH 06/20] Added a basic unit test. --- .../Shell/ShellContainerFactoryTests.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs b/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs index 2df22a213d7..bbd2e98b3ed 100644 --- a/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs +++ b/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs @@ -148,6 +148,24 @@ public async Task WhenHostSingletonAndScoped_GetServices_Returns_CorrectImplemen Assert.IsType(services.ElementAt(1)); } + [Fact] + public async Task AssignsTypeToMultipleFeatures() + { + var shellBlueprint = CreateBlueprint(); + + var expectedFeatureInfos = AddStartups(shellBlueprint, typeof(RegisterServiceStartup), typeof(RegisterSecondServiceStartup)); + + var container = (await _shellContainerFactory + .CreateContainerAsync(_uninitializedDefaultShell, shellBlueprint)) + .CreateScope() + .ServiceProvider; + + var typeFeatureProvider = _applicationServiceProvider.GetService(); + + Assert.IsType(container.GetRequiredService(typeof(ITestService))); + Assert.Equal(expectedFeatureInfos, typeFeatureProvider.GetFeaturesForDependency(typeof(TestService))); + } + private static ShellBlueprint CreateBlueprint() { return new ShellBlueprint @@ -166,6 +184,16 @@ public static IFeatureInfo AddStartup(ShellBlueprint shellBlueprint, Type startu return featureInfo; } + public static IFeatureInfo[] AddStartups(ShellBlueprint shellBlueprint, Type startupType1, Type startupType2) + { + var featureInfo1 = new FeatureInfo(startupType1.Name, startupType1.Name, 1, "Tests", null, null, null, false, false, false); + var featureInfo2 = new FeatureInfo(startupType2.Name, startupType2.Name, 1, "Tests", null, null, null, false, false, false); + shellBlueprint.Dependencies.Add(startupType1, [featureInfo1]); + shellBlueprint.Dependencies.Add(startupType2, [featureInfo2]); + + return [featureInfo1, featureInfo2]; + } + private interface ITestService { } @@ -188,6 +216,16 @@ public override void ConfigureServices(IServiceCollection services) } } + private sealed class RegisterSecondServiceStartup : StartupBase + { + public override int Order => 1; + + public override void ConfigureServices(IServiceCollection services) + { + services.AddScoped(); + } + } + private sealed class ReplaceServiceStartup : StartupBase { public override int Order => 2; From 8d78e67d0d00e81780960bd7c627dc2f1c1431c7 Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Mon, 6 May 2024 15:52:07 +0200 Subject: [PATCH 07/20] Update src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Zoltán Lehóczky --- src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs b/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs index 2ab287f297f..b4be25ec6f9 100644 --- a/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs +++ b/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs @@ -93,7 +93,7 @@ public IEnumerable GetExportedExtensionTypes(IExtensionInfo extensionInfo) { EnsureInitialized(); - if(_extensions.TryGetValue(extensionInfo.Id, out var extension)) + if (_extensions.TryGetValue(extensionInfo.Id, out var extension)) { return extension.ExportedTypes; } From a4a3c858e48ac243a70efda2b6a6d292898e02a3 Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Thu, 16 May 2024 15:15:45 +0200 Subject: [PATCH 08/20] Adds GetFeatureForDependency() back and also introduces GetExtensionForDependency() on the ITypeFeatureProvider interface. --- .../Controllers/AdminController.cs | 4 +-- .../Features/ITypeFeatureProvider.cs | 31 +++++++++++++++++++ .../Migration/DataMigrationManager.cs | 4 +-- .../Descriptors/DefaultShapeTableManager.cs | 2 +- .../ShapeAttributeBindingStrategy.cs | 2 +- .../ModularApplicationModelProvider.cs | 29 ++++++++--------- .../Services/RecipeMigrator.cs | 4 +-- .../Features/TypeFeatureProvider.cs | 22 +++++++++++++ .../ShapeDescriptorIndexBenchmark.cs | 2 +- .../Shell/ShellContainerFactoryTests.cs | 10 +++--- 10 files changed, 79 insertions(+), 31 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs index 4fa7bc35499..41b1bbb1a87 100644 --- a/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs @@ -243,9 +243,7 @@ private async Task>> Get var installedPermissions = new Dictionary>(); foreach (var permissionProvider in _permissionProviders) { - // Note: This uses the last feature, because that is likely the most specific. The first feature entry - // is always the extension itself, if multiple permission providers are sharing the same assembly. - var feature = _typeFeatureProvider.GetFeaturesForDependency(permissionProvider.GetType()).Last(); + var feature = _typeFeatureProvider.GetFeatureForDependency(permissionProvider.GetType()); var permissions = await permissionProvider.GetPermissionsAsync(); foreach (var permission in permissions) diff --git a/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs b/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs index a526acf4c9e..ec3e840e97a 100644 --- a/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs +++ b/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs @@ -10,8 +10,39 @@ namespace OrchardCore.Environment.Extensions /// public interface ITypeFeatureProvider { + /// + /// Gets the extension for the specified dependent type. + /// + /// + /// + IExtensionInfo GetExtensionForDependency(Type dependency); + + /// + /// Gets the first feature for the specified dependent type. + /// + /// + /// + IFeatureInfo GetFeatureForDependency(Type dependency); + + /// + /// Gets all features for the specified dependent type. + /// + /// + /// IEnumerable GetFeaturesForDependency(Type dependency); + + /// + /// Gets all dependent types for the specified feature. + /// + /// + /// IEnumerable GetTypesForFeature(IFeatureInfo feature); + + /// + /// Adds a type to the specified feature. + /// + /// + /// void TryAdd(Type type, IFeatureInfo feature); } } diff --git a/src/OrchardCore/OrchardCore.Data.YesSql/Migration/DataMigrationManager.cs b/src/OrchardCore/OrchardCore.Data.YesSql/Migration/DataMigrationManager.cs index d85c3ef276e..a9ce8703cfc 100644 --- a/src/OrchardCore/OrchardCore.Data.YesSql/Migration/DataMigrationManager.cs +++ b/src/OrchardCore/OrchardCore.Data.YesSql/Migration/DataMigrationManager.cs @@ -89,7 +89,7 @@ public async Task> GetFeaturesThatNeedUpdateAsync() return GetMethod(dataMigration, "Create") != null; }); - return outOfDateMigrations.Select(m => _typeFeatureProvider.GetFeaturesForDependency(m.GetType()).First().Id).ToArray(); + return outOfDateMigrations.Select(m => _typeFeatureProvider.GetFeatureForDependency(m.GetType()).Id).ToArray(); } public async Task Uninstall(string feature) @@ -276,7 +276,7 @@ private static async Task InvokeMethodAsync(MethodInfo method, IDataMigrati private IDataMigration[] GetDataMigrations(string featureId) { var migrations = _dataMigrations - .Where(dm => _typeFeatureProvider.GetFeaturesForDependency(dm.GetType()).First().Id == featureId) + .Where(dm => _typeFeatureProvider.GetFeaturesForDependency(dm.GetType()).Any(feature => feature.Id == featureId)) .ToArray(); return migrations; diff --git a/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/DefaultShapeTableManager.cs b/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/DefaultShapeTableManager.cs index f2247f74ae5..c9987c42d31 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/DefaultShapeTableManager.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/DefaultShapeTableManager.cs @@ -91,7 +91,7 @@ private async Task BuildShapeTableAsync(string themeId) foreach (var bindingStrategy in bindingStrategies) { - var strategyFeature = typeFeatureProvider.GetFeaturesForDependency(bindingStrategy.GetType()).First(); + var strategyFeature = typeFeatureProvider.GetFeatureForDependency(bindingStrategy.GetType()); var builder = new ShapeTableBuilder(strategyFeature, excludedFeatures); await bindingStrategy.DiscoverAsync(builder); diff --git a/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs b/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs index b24cd0f9e63..697a27983a5 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement/Descriptors/ShapeAttributeStrategy/ShapeAttributeBindingStrategy.cs @@ -52,7 +52,7 @@ public override ValueTask DiscoverAsync(ShapeTableBuilder builder) var occurrence = iter; var shapeType = occurrence.ShapeAttribute.ShapeType ?? occurrence.MethodInfo.Name; builder.Describe(shapeType) - .From(_typeFeatureProvider.GetFeaturesForDependency(occurrence.ServiceType).First()) + .From(_typeFeatureProvider.GetFeatureForDependency(occurrence.ServiceType)) .BoundAs( occurrence.MethodInfo.DeclaringType.FullName + "::" + occurrence.MethodInfo.Name, CreateDelegate(occurrence)); diff --git a/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs b/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs index 455bf1bcb6f..3634ded013f 100644 --- a/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs +++ b/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs @@ -44,25 +44,22 @@ public void OnProvidersExecuted(ApplicationModelProviderContext context) { var controllerType = controller.ControllerType.AsType(); - var blueprint = _typeFeatureProvider.GetFeaturesForDependency(controllerType).FirstOrDefault(); + var blueprint = _typeFeatureProvider.GetExtensionForDependency(controllerType); - if (blueprint != null) - { - if (blueprint.Extension.Id == _hostingEnvironment.ApplicationName && !_shellSettings.IsRunning()) + if (blueprint.Id == _hostingEnvironment.ApplicationName && !_shellSettings.IsRunning()) + { + // Don't serve any action of the application'module which is enabled during a setup. + foreach (var action in controller.Actions) { - // Don't serve any action of the application'module which is enabled during a setup. - foreach (var action in controller.Actions) - { - action.Selectors.Clear(); - } - - controller.Selectors.Clear(); - } - else - { - // Add an "area" route value equal to the module id. - controller.RouteValues.Add("area", blueprint.Extension.Id); + action.Selectors.Clear(); } + + controller.Selectors.Clear(); + } + else + { + // Add an "area" route value equal to the module id. + controller.RouteValues.Add("area", blueprint.Id); } } } diff --git a/src/OrchardCore/OrchardCore.Recipes.Core/Services/RecipeMigrator.cs b/src/OrchardCore/OrchardCore.Recipes.Core/Services/RecipeMigrator.cs index de08d54509f..2ae164a9897 100644 --- a/src/OrchardCore/OrchardCore.Recipes.Core/Services/RecipeMigrator.cs +++ b/src/OrchardCore/OrchardCore.Recipes.Core/Services/RecipeMigrator.cs @@ -39,9 +39,9 @@ public RecipeMigrator( public async Task ExecuteAsync(string recipeFileName, IDataMigration migration) { - var featureInfo = _typeFeatureProvider.GetFeaturesForDependency(migration.GetType()).First(); + var extensionInfo = _typeFeatureProvider.GetExtensionForDependency(migration.GetType()); - var recipeBasePath = Path.Combine(featureInfo.Extension.SubPath, "Migrations").Replace('\\', '/'); + var recipeBasePath = Path.Combine(extensionInfo.SubPath, "Migrations").Replace('\\', '/'); var recipeFilePath = Path.Combine(recipeBasePath, recipeFileName).Replace('\\', '/'); var recipeFileInfo = _hostingEnvironment.ContentRootFileProvider.GetFileInfo(recipeFilePath); var recipeDescriptor = await _recipeReader.GetRecipeDescriptorAsync(recipeBasePath, recipeFileInfo, _hostingEnvironment.ContentRootFileProvider); diff --git a/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs b/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs index e856a7ba956..b9430fd17bd 100644 --- a/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs +++ b/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs @@ -10,6 +10,28 @@ public class TypeFeatureProvider : ITypeFeatureProvider { private readonly ConcurrentDictionary> _features = new(); + public IExtensionInfo GetExtensionForDependency(Type dependency) + { + if (_features.TryGetValue(dependency, out var features)) + { + return features.First().Extension; + } + + throw new InvalidOperationException($"Could not resolve extension for type {dependency.Name}"); + } + + public IFeatureInfo GetFeatureForDependency(Type dependency) + { + if(_features.TryGetValue(dependency, out var features)) + { + // Gets the first feature that has the same ID as the extension, if any. Otherwise returns the + // first feature. + return features.FirstOrDefault(feature => feature.Extension.Id == feature.Id) ?? features.First(); + } + + throw new InvalidOperationException($"Could not resolve main feature for type {dependency.Name}"); + } + public IEnumerable GetFeaturesForDependency(Type dependency) { if (_features.TryGetValue(dependency, out var features)) diff --git a/test/OrchardCore.Benchmarks/ShapeDescriptorIndexBenchmark.cs b/test/OrchardCore.Benchmarks/ShapeDescriptorIndexBenchmark.cs index 617a1a1af8f..9bcab1ee7f3 100644 --- a/test/OrchardCore.Benchmarks/ShapeDescriptorIndexBenchmark.cs +++ b/test/OrchardCore.Benchmarks/ShapeDescriptorIndexBenchmark.cs @@ -28,7 +28,7 @@ await content.UsingTenantScopeAsync(async scope => var typeFeatureProvider = scope.ServiceProvider.GetRequiredService(); foreach (var bindingStrategy in bindingStrategies) { - var strategyFeature = typeFeatureProvider.GetFeaturesForDependency(bindingStrategy.GetType()).First(); + var strategyFeature = typeFeatureProvider.GetFeatureForDependency(bindingStrategy.GetType()); var builder = new ShapeTableBuilder(strategyFeature, []); await bindingStrategy.DiscoverAsync(builder); diff --git a/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs b/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs index bbd2e98b3ed..40a9b60d4ca 100644 --- a/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs +++ b/test/OrchardCore.Tests/Shell/ShellContainerFactoryTests.cs @@ -56,7 +56,7 @@ public async Task CanRegisterDefaultServiceWithFeatureInfo() var typeFeatureProvider = _applicationServiceProvider.GetService(); Assert.IsType(container.GetRequiredService(typeof(ITestService))); - Assert.Same(expectedFeatureInfo, typeFeatureProvider.GetFeaturesForDependency(typeof(TestService)).FirstOrDefault()); + Assert.Same(expectedFeatureInfo, typeFeatureProvider.GetFeatureForDependency(typeof(TestService))); } [Fact] @@ -76,7 +76,7 @@ public async Task CanReplaceDefaultServiceWithCustomService() // Check that the default service has been replaced with the custom service and that the feature info is correct. Assert.IsType(container.GetRequiredService(typeof(ITestService))); - Assert.Same(expectedFeatureInfo, typeFeatureProvider.GetFeaturesForDependency(typeof(CustomTestService)).FirstOrDefault()); + Assert.Same(expectedFeatureInfo, typeFeatureProvider.GetFeatureForDependency(typeof(CustomTestService))); } [Fact] @@ -178,7 +178,7 @@ private static ShellBlueprint CreateBlueprint() public static IFeatureInfo AddStartup(ShellBlueprint shellBlueprint, Type startupType) { - var featureInfo = new FeatureInfo(startupType.Name, startupType.Name, 1, "Tests", null, null, null, false, false, false); + var featureInfo = new FeatureInfo(startupType.Name, startupType.Name, 1, "Tests", null, new ExtensionInfo(startupType.Name), null, false, false, false); shellBlueprint.Dependencies.Add(startupType, [featureInfo]); return featureInfo; @@ -186,8 +186,8 @@ public static IFeatureInfo AddStartup(ShellBlueprint shellBlueprint, Type startu public static IFeatureInfo[] AddStartups(ShellBlueprint shellBlueprint, Type startupType1, Type startupType2) { - var featureInfo1 = new FeatureInfo(startupType1.Name, startupType1.Name, 1, "Tests", null, null, null, false, false, false); - var featureInfo2 = new FeatureInfo(startupType2.Name, startupType2.Name, 1, "Tests", null, null, null, false, false, false); + var featureInfo1 = new FeatureInfo(startupType1.Name, startupType1.Name, 1, "Tests", null, new ExtensionInfo(startupType1.Name), null, false, false, false); + var featureInfo2 = new FeatureInfo(startupType2.Name, startupType2.Name, 1, "Tests", null, new ExtensionInfo(startupType2.Name), null, false, false, false); shellBlueprint.Dependencies.Add(startupType1, [featureInfo1]); shellBlueprint.Dependencies.Add(startupType2, [featureInfo2]); From 471c62a042440eb68ae07c59458adebbdde9ce03 Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Thu, 16 May 2024 15:51:09 +0200 Subject: [PATCH 09/20] Formatting. --- .../OrchardCore/Extensions/Features/TypeFeatureProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs b/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs index b9430fd17bd..cf658086f08 100644 --- a/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs +++ b/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs @@ -22,7 +22,7 @@ public IExtensionInfo GetExtensionForDependency(Type dependency) public IFeatureInfo GetFeatureForDependency(Type dependency) { - if(_features.TryGetValue(dependency, out var features)) + if (_features.TryGetValue(dependency, out var features)) { // Gets the first feature that has the same ID as the extension, if any. Otherwise returns the // first feature. From b6ec58e9697d8ad8c13b3eaf86649afcc601f332 Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Fri, 17 May 2024 10:11:48 +0200 Subject: [PATCH 10/20] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Zoltán Lehóczky Co-authored-by: Mike Alhayek --- .../OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs | 2 +- .../OrchardCore/Shell/Builders/ShellContainerFactory.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs b/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs index 3634ded013f..1cd5913b433 100644 --- a/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs +++ b/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs @@ -48,7 +48,7 @@ public void OnProvidersExecuted(ApplicationModelProviderContext context) if (blueprint.Id == _hostingEnvironment.ApplicationName && !_shellSettings.IsRunning()) { - // Don't serve any action of the application'module which is enabled during a setup. + // Don't serve any action of the application's module which is enabled during a setup. foreach (var action in controller.Actions) { action.Selectors.Clear(); diff --git a/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs b/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs index 0c9834fcdf9..6e6a5f3331b 100644 --- a/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs +++ b/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs @@ -176,7 +176,7 @@ private void PopulateTypeFeatureProvider(ITypeFeatureProvider typeFeatureProvide { foreach (var feature in extension.Features) { - // Features can have no types + // Features can have no types. if (typesByFeature.TryGetValue(feature.Id, out var featureTypes)) { foreach (var type in featureTypes) @@ -187,7 +187,7 @@ private void PopulateTypeFeatureProvider(ITypeFeatureProvider typeFeatureProvide } } - // Register all DIed types in ITypeFeatureProvider + // Register all DIed types in ITypeFeatureProvider. foreach (var featureServiceCollection in featureAwareServiceCollection.FeatureCollections) { foreach (var serviceDescriptor in featureServiceCollection.Value) From ca4e21b8095b9228bf078e0a1dffc34006cecfc0 Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Fri, 17 May 2024 10:13:51 +0200 Subject: [PATCH 11/20] React to code review. --- .../Features/ITypeFeatureProvider.cs | 18 ++++++++---------- .../ModularApplicationModelProvider.cs | 6 +++--- .../Shell/Builders/ShellContainerFactory.cs | 13 +++++++------ 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs b/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs index ec3e840e97a..da4339569b2 100644 --- a/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs +++ b/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs @@ -13,36 +13,34 @@ public interface ITypeFeatureProvider /// /// Gets the extension for the specified dependent type. /// - /// - /// IExtensionInfo GetExtensionForDependency(Type dependency); /// /// Gets the first feature for the specified dependent type. /// - /// - /// + /// + /// If a type with more than one feature has been registered, + /// GetFeatureForDependency returns the first feature that has + /// the same ID as the parent extension. + /// Use this method when you only need one feature of a module, such + /// as when applying migrations for the entire module as opposed to + /// functionality of individual features. + /// IFeatureInfo GetFeatureForDependency(Type dependency); /// /// Gets all features for the specified dependent type. /// - /// - /// IEnumerable GetFeaturesForDependency(Type dependency); /// /// Gets all dependent types for the specified feature. /// - /// - /// IEnumerable GetTypesForFeature(IFeatureInfo feature); /// /// Adds a type to the specified feature. /// - /// - /// void TryAdd(Type type, IFeatureInfo feature); } } diff --git a/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs b/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs index 1cd5913b433..a9a52c04be6 100644 --- a/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs +++ b/src/OrchardCore/OrchardCore.Mvc.Core/ModularApplicationModelProvider.cs @@ -44,9 +44,9 @@ public void OnProvidersExecuted(ApplicationModelProviderContext context) { var controllerType = controller.ControllerType.AsType(); - var blueprint = _typeFeatureProvider.GetExtensionForDependency(controllerType); + var extension = _typeFeatureProvider.GetExtensionForDependency(controllerType); - if (blueprint.Id == _hostingEnvironment.ApplicationName && !_shellSettings.IsRunning()) + if (extension.Id == _hostingEnvironment.ApplicationName && !_shellSettings.IsRunning()) { // Don't serve any action of the application's module which is enabled during a setup. foreach (var action in controller.Actions) @@ -59,7 +59,7 @@ public void OnProvidersExecuted(ApplicationModelProviderContext context) else { // Add an "area" route value equal to the module id. - controller.RouteValues.Add("area", blueprint.Id); + controller.RouteValues.Add("area", extension.Id); } } } diff --git a/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs b/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs index 6e6a5f3331b..54bb1be82d1 100644 --- a/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs +++ b/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs @@ -157,12 +157,13 @@ private void PopulateTypeFeatureProvider(ITypeFeatureProvider typeFeatureProvide var allTypesByExtension = extensions .SelectMany(extension => - _extensionManager.GetExportedExtensionTypes(extension).Where(IsComponentType) - .Select(type => new - { - Extension = extension, - Type = type - })); + _extensionManager.GetExportedExtensionTypes(extension) + .Where(IsComponentType) + .Select(type => new + { + Extension = extension, + Type = type + })); var typesByFeature = allTypesByExtension .GroupBy(typeByExtension => GetSourceFeatureNameForType( From 85b942167c91b9cab19b88826393a1694e74c18c Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Fri, 17 May 2024 10:18:16 +0200 Subject: [PATCH 12/20] Suggestions from code review applied. --- .../OrchardCore/Extensions/Features/TypeFeatureProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs b/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs index cf658086f08..f825b6e41c5 100644 --- a/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs +++ b/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs @@ -17,7 +17,7 @@ public IExtensionInfo GetExtensionForDependency(Type dependency) return features.First().Extension; } - throw new InvalidOperationException($"Could not resolve extension for type {dependency.Name}"); + throw new InvalidOperationException($"Could not resolve extension for type {dependency.Name}."); } public IFeatureInfo GetFeatureForDependency(Type dependency) @@ -29,7 +29,7 @@ public IFeatureInfo GetFeatureForDependency(Type dependency) return features.FirstOrDefault(feature => feature.Extension.Id == feature.Id) ?? features.First(); } - throw new InvalidOperationException($"Could not resolve main feature for type {dependency.Name}"); + throw new InvalidOperationException($"Could not resolve main feature for type {dependency.Name}."); } public IEnumerable GetFeaturesForDependency(Type dependency) @@ -39,7 +39,7 @@ public IEnumerable GetFeaturesForDependency(Type dependency) return features; } - throw new InvalidOperationException($"Could not resolve features for type {dependency.Name}"); + throw new InvalidOperationException($"Could not resolve features for type {dependency.Name}."); } public IEnumerable GetTypesForFeature(IFeatureInfo feature) From 58fe1e6758c0368afd79582fed04ac166cb23c6c Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Fri, 17 May 2024 15:10:37 +0200 Subject: [PATCH 13/20] Removes the FeatureAttribute from classes that are now automatically assigned to the correct feature. --- .../OrchardCore.ContentFields/Indexing/SQL/Migrations.cs | 2 -- .../Indexing/SQL/UserPickerMigrations.cs | 2 -- .../OrchardCore.ContentLocalization/AdminMenu.cs | 2 -- .../ExportContentToDeploymentTargetMigrations.cs | 2 -- .../Login/Configuration/FacebookLoginConfiguration.cs | 2 -- .../OrchardCore.Facebook/Widgets/Services/LiquidShapes.cs | 2 -- .../OrchardCore.Facebook/Widgets/WidgetMigrations.cs | 2 -- .../OrchardCore.GitHub/AdminMenuGitHubLogin.cs | 2 -- .../OrchardCore.Google/GoogleAuthenticationAdminMenu.cs | 4 ---- .../OrchardCore.Media/SecureMediaPermissions.cs | 2 -- .../AdminMenuMicrosoftAccount.cs | 3 --- .../Configuration/OpenIdClientConfiguration.cs | 2 -- .../Configuration/OpenIdServerConfiguration.cs | 2 -- .../Configuration/OpenIdValidationConfiguration.cs | 4 +--- .../Shapes/LuceneContentPickerShapeProvider.cs | 2 -- .../Services/TenantFeatureProfileShapeTableProvider.cs | 2 -- .../OrchardCore.Twitter/AdminMenuSignin.cs | 3 --- src/OrchardCore.Modules/OrchardCore.Users/AdminMenu.cs | 4 ---- .../Drivers/ChangeEmailSettingsDisplayDriver.cs | 2 -- .../Drivers/RegistrationSettingsDisplayDriver.cs | 2 -- .../Drivers/ResetPasswordSettingsDisplayDriver.cs | 2 -- .../OrchardCore.DisplayManagement/Shapes/CoreShapes.cs | 2 -- .../OrchardCore.DisplayManagement/Shapes/DateTimeShapes.cs | 1 - .../OrchardCore.DisplayManagement/Shapes/GroupShapes.cs | 2 -- .../OrchardCore.DisplayManagement/Shapes/PageTitleShapes.cs | 2 -- .../OrchardCore.DisplayManagement/Zones/ZoneShapes.cs | 2 -- .../Shapes/ElasticContentPickerShapeProvider.cs | 2 -- 27 files changed, 1 insertion(+), 60 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/Indexing/SQL/Migrations.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/Indexing/SQL/Migrations.cs index 19ae0d9a242..66f13a1d57d 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/Indexing/SQL/Migrations.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/Indexing/SQL/Migrations.cs @@ -3,12 +3,10 @@ using Microsoft.Extensions.Logging; using OrchardCore.ContentManagement.Records; using OrchardCore.Data.Migration; -using OrchardCore.Modules; using YesSql.Sql; namespace OrchardCore.ContentFields.Indexing.SQL { - [Feature("OrchardCore.ContentFields.Indexing.SQL")] public class Migrations : DataMigration { private readonly ILogger _logger; diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/Indexing/SQL/UserPickerMigrations.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/Indexing/SQL/UserPickerMigrations.cs index 06fd0dac97d..1bc6ec0e99a 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/Indexing/SQL/UserPickerMigrations.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/Indexing/SQL/UserPickerMigrations.cs @@ -1,12 +1,10 @@ using System.Threading.Tasks; using OrchardCore.ContentManagement.Records; using OrchardCore.Data.Migration; -using OrchardCore.Modules; using YesSql.Sql; namespace OrchardCore.ContentFields.Indexing.SQL { - [Feature("OrchardCore.ContentFields.Indexing.SQL.UserPicker")] public class UserPickerMigrations : DataMigration { public async Task CreateAsync() diff --git a/src/OrchardCore.Modules/OrchardCore.ContentLocalization/AdminMenu.cs b/src/OrchardCore.Modules/OrchardCore.ContentLocalization/AdminMenu.cs index 0b8726ff7db..3c89f9fecfa 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentLocalization/AdminMenu.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentLocalization/AdminMenu.cs @@ -2,12 +2,10 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Localization; using OrchardCore.ContentLocalization.Drivers; -using OrchardCore.Modules; using OrchardCore.Navigation; namespace OrchardCore.ContentLocalization { - [Feature("OrchardCore.ContentLocalization.ContentCulturePicker")] public class AdminMenu : INavigationProvider { private static readonly RouteValueDictionary _providersRouteValues = new() diff --git a/src/OrchardCore.Modules/OrchardCore.Contents/Deployment/ExportContentToDeploymentTarget/ExportContentToDeploymentTargetMigrations.cs b/src/OrchardCore.Modules/OrchardCore.Contents/Deployment/ExportContentToDeploymentTarget/ExportContentToDeploymentTargetMigrations.cs index 997b545e2d9..d49520fe82c 100644 --- a/src/OrchardCore.Modules/OrchardCore.Contents/Deployment/ExportContentToDeploymentTarget/ExportContentToDeploymentTargetMigrations.cs +++ b/src/OrchardCore.Modules/OrchardCore.Contents/Deployment/ExportContentToDeploymentTarget/ExportContentToDeploymentTargetMigrations.cs @@ -3,14 +3,12 @@ using OrchardCore.Data.Migration; using OrchardCore.Deployment; using OrchardCore.Entities; -using OrchardCore.Modules; using OrchardCore.Recipes; using OrchardCore.Recipes.Services; using OrchardCore.Settings; namespace OrchardCore.Contents.Deployment.ExportContentToDeploymentTarget { - [Feature("OrchardCore.Contents.Deployment.ExportContentToDeploymentTarget")] public class ExportContentToDeploymentTargetMigrations : DataMigration { private readonly IRecipeMigrator _recipeMigrator; diff --git a/src/OrchardCore.Modules/OrchardCore.Facebook/Login/Configuration/FacebookLoginConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.Facebook/Login/Configuration/FacebookLoginConfiguration.cs index d400a12d7c6..b70d3048814 100644 --- a/src/OrchardCore.Modules/OrchardCore.Facebook/Login/Configuration/FacebookLoginConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.Facebook/Login/Configuration/FacebookLoginConfiguration.cs @@ -11,11 +11,9 @@ using OrchardCore.Facebook.Login.Services; using OrchardCore.Facebook.Login.Settings; using OrchardCore.Facebook.Settings; -using OrchardCore.Modules; namespace OrchardCore.Facebook.Login.Configuration { - [Feature(FacebookConstants.Features.Login)] public class FacebookLoginConfiguration : IConfigureOptions, IConfigureNamedOptions diff --git a/src/OrchardCore.Modules/OrchardCore.Facebook/Widgets/Services/LiquidShapes.cs b/src/OrchardCore.Modules/OrchardCore.Facebook/Widgets/Services/LiquidShapes.cs index acb857d1ed1..ff496ece983 100644 --- a/src/OrchardCore.Modules/OrchardCore.Facebook/Widgets/Services/LiquidShapes.cs +++ b/src/OrchardCore.Modules/OrchardCore.Facebook/Widgets/Services/LiquidShapes.cs @@ -7,11 +7,9 @@ using OrchardCore.DisplayManagement.Implementation; using OrchardCore.Facebook.Widgets.ViewModels; using OrchardCore.Liquid; -using OrchardCore.Modules; namespace OrchardCore.Facebook.Widgets.Services; -[Feature(FacebookConstants.Features.Widgets)] public class LiquidShapes(HtmlEncoder htmlEncoder) : ShapeTableProvider { private readonly HtmlEncoder _htmlEncoder = htmlEncoder; diff --git a/src/OrchardCore.Modules/OrchardCore.Facebook/Widgets/WidgetMigrations.cs b/src/OrchardCore.Modules/OrchardCore.Facebook/Widgets/WidgetMigrations.cs index f36c0845fac..5ffac237891 100644 --- a/src/OrchardCore.Modules/OrchardCore.Facebook/Widgets/WidgetMigrations.cs +++ b/src/OrchardCore.Modules/OrchardCore.Facebook/Widgets/WidgetMigrations.cs @@ -3,13 +3,11 @@ using OrchardCore.ContentManagement.Metadata.Settings; using OrchardCore.Data.Migration; using OrchardCore.Facebook.Widgets.Models; -using OrchardCore.Modules; using OrchardCore.Recipes; using OrchardCore.Recipes.Services; namespace OrchardCore.Facebook.Widgets { - [Feature(FacebookConstants.Features.Widgets)] public class WidgetMigrations : DataMigration { private readonly IRecipeMigrator _recipeMigrator; diff --git a/src/OrchardCore.Modules/OrchardCore.GitHub/AdminMenuGitHubLogin.cs b/src/OrchardCore.Modules/OrchardCore.GitHub/AdminMenuGitHubLogin.cs index 3bf0dbd3176..ec2420226e2 100644 --- a/src/OrchardCore.Modules/OrchardCore.GitHub/AdminMenuGitHubLogin.cs +++ b/src/OrchardCore.Modules/OrchardCore.GitHub/AdminMenuGitHubLogin.cs @@ -1,12 +1,10 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Localization; -using OrchardCore.Modules; using OrchardCore.Navigation; namespace OrchardCore.GitHub { - [Feature(GitHubConstants.Features.GitHubAuthentication)] public class AdminMenuGitHubLogin : INavigationProvider { private static readonly RouteValueDictionary _routeValues = new() diff --git a/src/OrchardCore.Modules/OrchardCore.Google/GoogleAuthenticationAdminMenu.cs b/src/OrchardCore.Modules/OrchardCore.Google/GoogleAuthenticationAdminMenu.cs index 425dfc883e9..6c91ce2dfe7 100644 --- a/src/OrchardCore.Modules/OrchardCore.Google/GoogleAuthenticationAdminMenu.cs +++ b/src/OrchardCore.Modules/OrchardCore.Google/GoogleAuthenticationAdminMenu.cs @@ -1,12 +1,10 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Localization; -using OrchardCore.Modules; using OrchardCore.Navigation; namespace OrchardCore.Google { - [Feature(GoogleConstants.Features.GoogleAuthentication)] public class GoogleAuthenticationAdminMenu : INavigationProvider { private static readonly RouteValueDictionary _routeValues = new() @@ -46,7 +44,6 @@ public Task BuildNavigationAsync(string name, NavigationBuilder builder) } } - [Feature(GoogleConstants.Features.GoogleAnalytics)] public class GoogleAnalyticsAdminMenu : INavigationProvider { private static readonly RouteValueDictionary _routeValues = new() @@ -85,7 +82,6 @@ public Task BuildNavigationAsync(string name, NavigationBuilder builder) } } - [Feature(GoogleConstants.Features.GoogleTagManager)] public class GoogleTagManagerAdminMenu : INavigationProvider { private static readonly RouteValueDictionary _routeValues = new() diff --git a/src/OrchardCore.Modules/OrchardCore.Media/SecureMediaPermissions.cs b/src/OrchardCore.Modules/OrchardCore.Media/SecureMediaPermissions.cs index 6e958017d46..6c14167c504 100644 --- a/src/OrchardCore.Modules/OrchardCore.Media/SecureMediaPermissions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Media/SecureMediaPermissions.cs @@ -7,12 +7,10 @@ using Microsoft.Extensions.Options; using OrchardCore.Environment.Cache; using OrchardCore.Media.Services; -using OrchardCore.Modules; using OrchardCore.Security.Permissions; namespace OrchardCore.Media { - [Feature("OrchardCore.Media.Security")] public class SecureMediaPermissions : IPermissionProvider { // Note: The ManageMediaFolder permission grants all access, so viewing must be implied by it too. diff --git a/src/OrchardCore.Modules/OrchardCore.Microsoft.Authentication/AdminMenuMicrosoftAccount.cs b/src/OrchardCore.Modules/OrchardCore.Microsoft.Authentication/AdminMenuMicrosoftAccount.cs index 3fcd818deb8..4f6d3ab1450 100644 --- a/src/OrchardCore.Modules/OrchardCore.Microsoft.Authentication/AdminMenuMicrosoftAccount.cs +++ b/src/OrchardCore.Modules/OrchardCore.Microsoft.Authentication/AdminMenuMicrosoftAccount.cs @@ -1,12 +1,10 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Localization; -using OrchardCore.Modules; using OrchardCore.Navigation; namespace OrchardCore.Microsoft.Authentication { - [Feature(MicrosoftAuthenticationConstants.Features.MicrosoftAccount)] public class AdminMenuMicrosoftAccount : INavigationProvider { private static readonly RouteValueDictionary _routeValues = new() @@ -46,7 +44,6 @@ public Task BuildNavigationAsync(string name, NavigationBuilder builder) } } - [Feature(MicrosoftAuthenticationConstants.Features.AAD)] public class AdminMenuAAD : INavigationProvider { private static readonly RouteValueDictionary _routeValues = new() diff --git a/src/OrchardCore.Modules/OrchardCore.OpenId/Configuration/OpenIdClientConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.OpenId/Configuration/OpenIdClientConfiguration.cs index a70f67751eb..bc00032bf3b 100644 --- a/src/OrchardCore.Modules/OrchardCore.OpenId/Configuration/OpenIdClientConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.OpenId/Configuration/OpenIdClientConfiguration.cs @@ -9,13 +9,11 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OrchardCore.Environment.Shell; -using OrchardCore.Modules; using OrchardCore.OpenId.Services; using OrchardCore.OpenId.Settings; namespace OrchardCore.OpenId.Configuration { - [Feature(OpenIdConstants.Features.Client)] public class OpenIdClientConfiguration : IConfigureOptions, IConfigureNamedOptions diff --git a/src/OrchardCore.Modules/OrchardCore.OpenId/Configuration/OpenIdServerConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.OpenId/Configuration/OpenIdServerConfiguration.cs index cd31fcafa01..c2d8ca2d955 100644 --- a/src/OrchardCore.Modules/OrchardCore.OpenId/Configuration/OpenIdServerConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.OpenId/Configuration/OpenIdServerConfiguration.cs @@ -11,14 +11,12 @@ using OpenIddict.Server.AspNetCore; using OpenIddict.Server.DataProtection; using OrchardCore.Environment.Shell; -using OrchardCore.Modules; using OrchardCore.OpenId.Services; using OrchardCore.OpenId.Settings; using static OpenIddict.Abstractions.OpenIddictConstants; namespace OrchardCore.OpenId.Configuration { - [Feature(OpenIdConstants.Features.Server)] public class OpenIdServerConfiguration : IConfigureOptions, IConfigureOptions, IConfigureOptions, diff --git a/src/OrchardCore.Modules/OrchardCore.OpenId/Configuration/OpenIdValidationConfiguration.cs b/src/OrchardCore.Modules/OrchardCore.OpenId/Configuration/OpenIdValidationConfiguration.cs index 20ebb006db4..73757417f56 100644 --- a/src/OrchardCore.Modules/OrchardCore.OpenId/Configuration/OpenIdValidationConfiguration.cs +++ b/src/OrchardCore.Modules/OrchardCore.OpenId/Configuration/OpenIdValidationConfiguration.cs @@ -16,16 +16,14 @@ using OpenIddict.Validation.DataProtection; using OrchardCore.Environment.Shell; using OrchardCore.Environment.Shell.Scope; -using OrchardCore.Modules; using OrchardCore.OpenId.Services; using OrchardCore.OpenId.Settings; using OrchardCore.Security; -using SystemEnvironment = System.Environment; using static OpenIddict.Abstractions.OpenIddictConstants; +using SystemEnvironment = System.Environment; namespace OrchardCore.OpenId.Configuration { - [Feature(OpenIdConstants.Features.Validation)] public class OpenIdValidationConfiguration : IConfigureOptions, IConfigureOptions, IConfigureOptions, diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Lucene/Shapes/LuceneContentPickerShapeProvider.cs b/src/OrchardCore.Modules/OrchardCore.Search.Lucene/Shapes/LuceneContentPickerShapeProvider.cs index 733824360cc..a8c3c7ea184 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Lucene/Shapes/LuceneContentPickerShapeProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Search.Lucene/Shapes/LuceneContentPickerShapeProvider.cs @@ -3,11 +3,9 @@ using OrchardCore.DisplayManagement; using OrchardCore.DisplayManagement.Descriptors; using OrchardCore.DisplayManagement.Shapes; -using OrchardCore.Modules; namespace OrchardCore.Search.Lucene { - [Feature("OrchardCore.Search.Lucene.ContentPicker")] public class LuceneContentPickerShapeProvider : IShapeAttributeProvider { protected readonly IStringLocalizer S; diff --git a/src/OrchardCore.Modules/OrchardCore.Tenants/Services/TenantFeatureProfileShapeTableProvider.cs b/src/OrchardCore.Modules/OrchardCore.Tenants/Services/TenantFeatureProfileShapeTableProvider.cs index 26a8bec69ee..3287a2ef7af 100644 --- a/src/OrchardCore.Modules/OrchardCore.Tenants/Services/TenantFeatureProfileShapeTableProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Tenants/Services/TenantFeatureProfileShapeTableProvider.cs @@ -2,12 +2,10 @@ using OrchardCore.DisplayManagement; using OrchardCore.DisplayManagement.Descriptors; using OrchardCore.DisplayManagement.Views; -using OrchardCore.Modules; using OrchardCore.Tenants.ViewModels; namespace OrchardCore.Tenants.Services; -[Feature("OrchardCore.Tenants.FeatureProfiles")] public class TenantFeatureProfileShapeTableProvider : ShapeTableProvider { public override ValueTask DiscoverAsync(ShapeTableBuilder builder) diff --git a/src/OrchardCore.Modules/OrchardCore.Twitter/AdminMenuSignin.cs b/src/OrchardCore.Modules/OrchardCore.Twitter/AdminMenuSignin.cs index 16106ab6107..c94861b43d2 100644 --- a/src/OrchardCore.Modules/OrchardCore.Twitter/AdminMenuSignin.cs +++ b/src/OrchardCore.Modules/OrchardCore.Twitter/AdminMenuSignin.cs @@ -1,12 +1,10 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Localization; -using OrchardCore.Modules; using OrchardCore.Navigation; namespace OrchardCore.Twitter { - [Feature(TwitterConstants.Features.Signin)] public class AdminMenuSignin : INavigationProvider { private static readonly RouteValueDictionary _routeValues = new() @@ -45,7 +43,6 @@ public Task BuildNavigationAsync(string name, NavigationBuilder builder) } } - [Feature(TwitterConstants.Features.Twitter)] public class AdminMenu : INavigationProvider { private static readonly RouteValueDictionary _routeValues = new() diff --git a/src/OrchardCore.Modules/OrchardCore.Users/AdminMenu.cs b/src/OrchardCore.Modules/OrchardCore.Users/AdminMenu.cs index 03d6e4a9ce5..a129fcee209 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/AdminMenu.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/AdminMenu.cs @@ -1,7 +1,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Localization; -using OrchardCore.Modules; using OrchardCore.Navigation; using OrchardCore.Users.Drivers; using OrchardCore.Users.Models; @@ -55,7 +54,6 @@ public Task BuildNavigationAsync(string name, NavigationBuilder builder) } } - [Feature("OrchardCore.Users.ChangeEmail")] public class ChangeEmailAdminMenu : INavigationProvider { private static readonly RouteValueDictionary _routeValues = new() @@ -93,7 +91,6 @@ public Task BuildNavigationAsync(string name, NavigationBuilder builder) } } - [Feature(UserConstants.Features.UserRegistration)] public class RegistrationAdminMenu : INavigationProvider { private static readonly RouteValueDictionary _routeValues = new() @@ -131,7 +128,6 @@ public Task BuildNavigationAsync(string name, NavigationBuilder builder) } } - [Feature(UserConstants.Features.ResetPassword)] public class ResetPasswordAdminMenu : INavigationProvider { private static readonly RouteValueDictionary _routeValues = new() diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ChangeEmailSettingsDisplayDriver.cs b/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ChangeEmailSettingsDisplayDriver.cs index 4f44c0e58be..5bba6fbc7bf 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ChangeEmailSettingsDisplayDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ChangeEmailSettingsDisplayDriver.cs @@ -5,13 +5,11 @@ using OrchardCore.DisplayManagement.Entities; using OrchardCore.DisplayManagement.Handlers; using OrchardCore.DisplayManagement.Views; -using OrchardCore.Modules; using OrchardCore.Settings; using OrchardCore.Users.Models; namespace OrchardCore.Users.Drivers { - [Feature("OrchardCore.Users.ChangeEmail")] public class ChangeEmailSettingsDisplayDriver : SectionDisplayDriver { public const string GroupId = "userChangeEmail"; diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Drivers/RegistrationSettingsDisplayDriver.cs b/src/OrchardCore.Modules/OrchardCore.Users/Drivers/RegistrationSettingsDisplayDriver.cs index 64c71abfd92..2bc38975813 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Drivers/RegistrationSettingsDisplayDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Drivers/RegistrationSettingsDisplayDriver.cs @@ -5,13 +5,11 @@ using OrchardCore.DisplayManagement.Entities; using OrchardCore.DisplayManagement.Handlers; using OrchardCore.DisplayManagement.Views; -using OrchardCore.Modules; using OrchardCore.Settings; using OrchardCore.Users.Models; namespace OrchardCore.Users.Drivers { - [Feature(UserConstants.Features.UserRegistration)] public class RegistrationSettingsDisplayDriver : SectionDisplayDriver { public const string GroupId = "userRegistration"; diff --git a/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ResetPasswordSettingsDisplayDriver.cs b/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ResetPasswordSettingsDisplayDriver.cs index 59e7e19e664..7e21dbbc9a8 100644 --- a/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ResetPasswordSettingsDisplayDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Users/Drivers/ResetPasswordSettingsDisplayDriver.cs @@ -5,13 +5,11 @@ using OrchardCore.DisplayManagement.Entities; using OrchardCore.DisplayManagement.Handlers; using OrchardCore.DisplayManagement.Views; -using OrchardCore.Modules; using OrchardCore.Settings; using OrchardCore.Users.Models; namespace OrchardCore.Users.Drivers { - [Feature(UserConstants.Features.ResetPassword)] public class ResetPasswordSettingsDisplayDriver : SectionDisplayDriver { public const string GroupId = "userResetPassword"; diff --git a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/CoreShapes.cs b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/CoreShapes.cs index c68ec5e1c05..ed19775478e 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/CoreShapes.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/CoreShapes.cs @@ -7,11 +7,9 @@ using Microsoft.AspNetCore.Mvc.Rendering; using OrchardCore.DisplayManagement.Descriptors; using OrchardCore.DisplayManagement.Implementation; -using OrchardCore.Modules; namespace OrchardCore.DisplayManagement.Shapes { - [Feature(Application.DefaultFeatureId)] public class CoreShapes : IShapeAttributeProvider { [Shape] diff --git a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/DateTimeShapes.cs b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/DateTimeShapes.cs index a54ef37848c..6f3bb341d11 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/DateTimeShapes.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/DateTimeShapes.cs @@ -10,7 +10,6 @@ namespace OrchardCore.DisplayManagement.Shapes { - [Feature(Application.DefaultFeatureId)] public class DateTimeShapes : IShapeAttributeProvider { private const string LongDateTimeFormat = "dddd, MMMM d, yyyy h:mm:ss tt"; diff --git a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/GroupShapes.cs b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/GroupShapes.cs index 0a320bce025..9375ed63846 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/GroupShapes.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/GroupShapes.cs @@ -5,12 +5,10 @@ using Microsoft.AspNetCore.Mvc.Rendering; using OrchardCore.DisplayManagement.Descriptors; using OrchardCore.DisplayManagement.ViewModels; -using OrchardCore.Modules; using OrchardCore.Mvc.Utilities; namespace OrchardCore.DisplayManagement.Shapes { - [Feature(Application.DefaultFeatureId)] public class GroupShapes : IShapeAttributeProvider { [Shape] diff --git a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/PageTitleShapes.cs b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/PageTitleShapes.cs index 25d46bc3154..c2e3d219069 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/PageTitleShapes.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/PageTitleShapes.cs @@ -6,12 +6,10 @@ using OrchardCore.DisplayManagement.Title; using OrchardCore.Environment.Shell.Scope; using OrchardCore.Liquid; -using OrchardCore.Modules; using OrchardCore.Settings; namespace OrchardCore.DisplayManagement.Shapes { - [Feature(Application.DefaultFeatureId)] public class PageTitleShapes : IShapeAttributeProvider { private IPageTitleBuilder _pageTitleBuilder; diff --git a/src/OrchardCore/OrchardCore.DisplayManagement/Zones/ZoneShapes.cs b/src/OrchardCore/OrchardCore.DisplayManagement/Zones/ZoneShapes.cs index c56a80ed73f..b255ede3820 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement/Zones/ZoneShapes.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement/Zones/ZoneShapes.cs @@ -5,12 +5,10 @@ using OrchardCore.DisplayManagement.Descriptors; using OrchardCore.DisplayManagement.Shapes; using OrchardCore.DisplayManagement.ViewModels; -using OrchardCore.Modules; using OrchardCore.Mvc.Utilities; namespace OrchardCore.DisplayManagement.Zones { - [Feature(Application.DefaultFeatureId)] public class ZoneShapes : IShapeAttributeProvider { // By convention all placement delimiters default to the name 'Content' when not specified during placement. diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Shapes/ElasticContentPickerShapeProvider.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Shapes/ElasticContentPickerShapeProvider.cs index ef58bcf9be6..d22e9ec2f19 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Shapes/ElasticContentPickerShapeProvider.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Shapes/ElasticContentPickerShapeProvider.cs @@ -3,11 +3,9 @@ using OrchardCore.DisplayManagement; using OrchardCore.DisplayManagement.Descriptors; using OrchardCore.DisplayManagement.Shapes; -using OrchardCore.Modules; namespace OrchardCore.Search.Elasticsearch { - [Feature("OrchardCore.Search.Elasticsearch.ContentPicker")] public class ElasticContentPickerShapeProvider : IShapeAttributeProvider { protected readonly IStringLocalizer S; From 1c0d8a372019d96bd7664b6d484f8fa855a322f6 Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Fri, 17 May 2024 15:13:53 +0200 Subject: [PATCH 14/20] Typo --- .../Extensions/Features/ITypeFeatureProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs b/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs index da4339569b2..7f132c8c111 100644 --- a/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs +++ b/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs @@ -19,7 +19,7 @@ public interface ITypeFeatureProvider /// Gets the first feature for the specified dependent type. /// /// - /// If a type with more than one feature has been registered, + /// If a type has been registered for more than one feature, /// GetFeatureForDependency returns the first feature that has /// the same ID as the parent extension. /// Use this method when you only need one feature of a module, such From c4e8a3eb0e84de8b73d988be0de04ed3a519b19d Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Fri, 17 May 2024 15:19:03 +0200 Subject: [PATCH 15/20] Check if feature is enabled when displaying permissions registered for multiple features. --- .../OrchardCore.Roles/Controllers/AdminController.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs index 41b1bbb1a87..3ecb5d96054 100644 --- a/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs @@ -13,6 +13,7 @@ using OrchardCore.DisplayManagement.Notify; using OrchardCore.Environment.Extensions; using OrchardCore.Environment.Extensions.Features; +using OrchardCore.Environment.Shell; using OrchardCore.Roles.ViewModels; using OrchardCore.Security; using OrchardCore.Security.Permissions; @@ -28,6 +29,7 @@ public class AdminController : Controller private readonly IAuthorizationService _authorizationService; private readonly IEnumerable _permissionProviders; private readonly ITypeFeatureProvider _typeFeatureProvider; + private readonly IShellFeaturesManager _shellFeaturesManager; private readonly IRoleService _roleService; private readonly INotifier _notifier; protected readonly IStringLocalizer S; @@ -39,6 +41,7 @@ public AdminController( IAuthorizationService authorizationService, IEnumerable permissionProviders, ITypeFeatureProvider typeFeatureProvider, + IShellFeaturesManager shellFeaturesManager, IRoleService roleService, INotifier notifier, IStringLocalizer stringLocalizer, @@ -49,6 +52,7 @@ public AdminController( _authorizationService = authorizationService; _permissionProviders = permissionProviders; _typeFeatureProvider = typeFeatureProvider; + _shellFeaturesManager = shellFeaturesManager; _roleService = roleService; _notifier = notifier; S = stringLocalizer; @@ -241,9 +245,13 @@ private RoleEntry BuildRoleEntry(IRole role) private async Task>> GetInstalledPermissionsAsync() { var installedPermissions = new Dictionary>(); + var enabledFeatures = await _shellFeaturesManager.GetEnabledFeaturesAsync(); foreach (var permissionProvider in _permissionProviders) { - var feature = _typeFeatureProvider.GetFeatureForDependency(permissionProvider.GetType()); + var feature = _typeFeatureProvider + .GetFeaturesForDependency(permissionProvider.GetType()) + .FirstOrDefault(feature => enabledFeatures.Any(enabledFeature => feature.Id == enabledFeature.Id)); + var permissions = await permissionProvider.GetPermissionsAsync(); foreach (var permission in permissions) From b6a988ac27fc63d66bd030f86c4a4fe49c72c1f0 Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Fri, 17 May 2024 16:00:59 +0200 Subject: [PATCH 16/20] Added FeatureAttribute back for some classes that should belong to the default application feature. --- .../OrchardCore.DisplayManagement/Shapes/CoreShapes.cs | 2 ++ .../OrchardCore.DisplayManagement/Shapes/DateTimeShapes.cs | 1 + .../OrchardCore.DisplayManagement/Shapes/GroupShapes.cs | 2 ++ .../OrchardCore.DisplayManagement/Shapes/PageTitleShapes.cs | 2 ++ .../OrchardCore.DisplayManagement/Zones/ZoneShapes.cs | 2 ++ 5 files changed, 9 insertions(+) diff --git a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/CoreShapes.cs b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/CoreShapes.cs index ed19775478e..c68ec5e1c05 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/CoreShapes.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/CoreShapes.cs @@ -7,9 +7,11 @@ using Microsoft.AspNetCore.Mvc.Rendering; using OrchardCore.DisplayManagement.Descriptors; using OrchardCore.DisplayManagement.Implementation; +using OrchardCore.Modules; namespace OrchardCore.DisplayManagement.Shapes { + [Feature(Application.DefaultFeatureId)] public class CoreShapes : IShapeAttributeProvider { [Shape] diff --git a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/DateTimeShapes.cs b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/DateTimeShapes.cs index 6f3bb341d11..a54ef37848c 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/DateTimeShapes.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/DateTimeShapes.cs @@ -10,6 +10,7 @@ namespace OrchardCore.DisplayManagement.Shapes { + [Feature(Application.DefaultFeatureId)] public class DateTimeShapes : IShapeAttributeProvider { private const string LongDateTimeFormat = "dddd, MMMM d, yyyy h:mm:ss tt"; diff --git a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/GroupShapes.cs b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/GroupShapes.cs index 9375ed63846..0a320bce025 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/GroupShapes.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/GroupShapes.cs @@ -5,10 +5,12 @@ using Microsoft.AspNetCore.Mvc.Rendering; using OrchardCore.DisplayManagement.Descriptors; using OrchardCore.DisplayManagement.ViewModels; +using OrchardCore.Modules; using OrchardCore.Mvc.Utilities; namespace OrchardCore.DisplayManagement.Shapes { + [Feature(Application.DefaultFeatureId)] public class GroupShapes : IShapeAttributeProvider { [Shape] diff --git a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/PageTitleShapes.cs b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/PageTitleShapes.cs index c2e3d219069..25d46bc3154 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/PageTitleShapes.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement/Shapes/PageTitleShapes.cs @@ -6,10 +6,12 @@ using OrchardCore.DisplayManagement.Title; using OrchardCore.Environment.Shell.Scope; using OrchardCore.Liquid; +using OrchardCore.Modules; using OrchardCore.Settings; namespace OrchardCore.DisplayManagement.Shapes { + [Feature(Application.DefaultFeatureId)] public class PageTitleShapes : IShapeAttributeProvider { private IPageTitleBuilder _pageTitleBuilder; diff --git a/src/OrchardCore/OrchardCore.DisplayManagement/Zones/ZoneShapes.cs b/src/OrchardCore/OrchardCore.DisplayManagement/Zones/ZoneShapes.cs index b255ede3820..c56a80ed73f 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement/Zones/ZoneShapes.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement/Zones/ZoneShapes.cs @@ -5,10 +5,12 @@ using OrchardCore.DisplayManagement.Descriptors; using OrchardCore.DisplayManagement.Shapes; using OrchardCore.DisplayManagement.ViewModels; +using OrchardCore.Modules; using OrchardCore.Mvc.Utilities; namespace OrchardCore.DisplayManagement.Zones { + [Feature(Application.DefaultFeatureId)] public class ZoneShapes : IShapeAttributeProvider { // By convention all placement delimiters default to the name 'Content' when not specified during placement. From ed7f37e357d6a28bd88d8f77111ccf74d1b8a8c4 Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Sun, 19 May 2024 13:34:21 +0200 Subject: [PATCH 17/20] Use the last enabled feature instead of the first one in the permissions editor. --- .../OrchardCore.Roles/Controllers/AdminController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs index 3ecb5d96054..a8ead3b149f 100644 --- a/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs @@ -250,7 +250,7 @@ private async Task>> Get { var feature = _typeFeatureProvider .GetFeaturesForDependency(permissionProvider.GetType()) - .FirstOrDefault(feature => enabledFeatures.Any(enabledFeature => feature.Id == enabledFeature.Id)); + .LastOrDefault(feature => enabledFeatures.Any(enabledFeature => feature.Id == enabledFeature.Id)); var permissions = await permissionProvider.GetPermissionsAsync(); From e73cb9494443b33eaf3962eb2b06e734a53098b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Thu, 23 May 2024 10:22:50 -0700 Subject: [PATCH 18/20] Update AdminController.cs --- .../OrchardCore.Roles/Controllers/AdminController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs index a8ead3b149f..1d40ed6ca95 100644 --- a/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs @@ -248,6 +248,7 @@ private async Task>> Get var enabledFeatures = await _shellFeaturesManager.GetEnabledFeaturesAsync(); foreach (var permissionProvider in _permissionProviders) { + // Two features could use the same permission. var feature = _typeFeatureProvider .GetFeaturesForDependency(permissionProvider.GetType()) .LastOrDefault(feature => enabledFeatures.Any(enabledFeature => feature.Id == enabledFeature.Id)); From a6bd9cdf30088f3fed3527f261f10532c652e379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Thu, 23 May 2024 10:23:32 -0700 Subject: [PATCH 19/20] Update src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Zoltán Lehóczky --- .../Extensions/Features/ITypeFeatureProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs b/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs index 7f132c8c111..850de1357f1 100644 --- a/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs +++ b/src/OrchardCore/OrchardCore.Abstractions/Extensions/Features/ITypeFeatureProvider.cs @@ -20,7 +20,7 @@ public interface ITypeFeatureProvider /// /// /// If a type has been registered for more than one feature, - /// GetFeatureForDependency returns the first feature that has + /// returns the first feature that has /// the same ID as the parent extension. /// Use this method when you only need one feature of a module, such /// as when applying migrations for the entire module as opposed to From 5037727050f72db1af7d2053c51dcb154669ff62 Mon Sep 17 00:00:00 2001 From: Mike Alhayek Date: Fri, 24 May 2024 09:20:07 -0700 Subject: [PATCH 20/20] minor cleanup and add documentation --- .../OrchardCore.Roles/Controllers/AdminController.cs | 2 ++ src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs | 1 + .../OrchardCore/Extensions/Features/TypeFeatureProvider.cs | 4 ++-- .../OrchardCore/Shell/Builders/ShellContainerFactory.cs | 4 ++-- src/docs/releases/2.0.0.md | 4 ++++ 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs index 1d40ed6ca95..1fff5e39075 100644 --- a/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Roles/Controllers/AdminController.cs @@ -32,6 +32,7 @@ public class AdminController : Controller private readonly IShellFeaturesManager _shellFeaturesManager; private readonly IRoleService _roleService; private readonly INotifier _notifier; + protected readonly IStringLocalizer S; protected readonly IHtmlLocalizer H; @@ -246,6 +247,7 @@ private async Task>> Get { var installedPermissions = new Dictionary>(); var enabledFeatures = await _shellFeaturesManager.GetEnabledFeaturesAsync(); + foreach (var permissionProvider in _permissionProviders) { // Two features could use the same permission. diff --git a/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs b/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs index b4be25ec6f9..d942df73b7c 100644 --- a/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs +++ b/src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs @@ -113,6 +113,7 @@ public Task LoadExtensionAsync(IExtensionInfo extensionInfo) public Task> LoadFeaturesAsync() { EnsureInitialized(); + return Task.FromResult>(_features.Values); } diff --git a/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs b/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs index f825b6e41c5..4acf71f17a1 100644 --- a/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs +++ b/src/OrchardCore/OrchardCore/Extensions/Features/TypeFeatureProvider.cs @@ -24,8 +24,8 @@ public IFeatureInfo GetFeatureForDependency(Type dependency) { if (_features.TryGetValue(dependency, out var features)) { - // Gets the first feature that has the same ID as the extension, if any. Otherwise returns the - // first feature. + // Gets the first feature that has the same ID as the extension, if any. + // Otherwise returns the first feature. return features.FirstOrDefault(feature => feature.Extension.Id == feature.Id) ?? features.First(); } diff --git a/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs b/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs index 54bb1be82d1..e5be3cca4e9 100644 --- a/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs +++ b/src/OrchardCore/OrchardCore/Shell/Builders/ShellContainerFactory.cs @@ -67,7 +67,7 @@ public async Task CreateContainerAsync(ShellSettings settings, continue; } - // Ignore Startup class from main application + // Ignore Startup class from main application. if (blueprint.Dependencies.TryGetValue(rawStartup, out var startupFeatures) && startupFeatures.Any(f => f.Id == _applicationFeature.Id)) { continue; @@ -115,7 +115,7 @@ public async Task CreateContainerAsync(ShellSettings settings, // OrderBy performs a stable sort so order is preserved among equal Order values. startups = startups.OrderBy(s => s.Order); - // Let any module add custom service descriptors to the tenant + // Let any module add custom service descriptors to the tenant. foreach (var startup in startups) { var feature = blueprint.Dependencies.FirstOrDefault(x => x.Key == startup.GetType()).Value?.FirstOrDefault(); diff --git a/src/docs/releases/2.0.0.md b/src/docs/releases/2.0.0.md index 5a1f1bfd675..cc00e15fd5c 100644 --- a/src/docs/releases/2.0.0.md +++ b/src/docs/releases/2.0.0.md @@ -495,3 +495,7 @@ public class ReverseProxySettingsDisplayDriver : SectionDisplayDriver