diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationPartSorter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationPartSorter.cs index 5c31a438d5..d0ba8b98c0 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationPartSorter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationPartSorter.cs @@ -1,5 +1,5 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; +using System.Reflection; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Volo.Abp.Modularity; @@ -13,24 +13,36 @@ public static class ApplicationPartSorter { public static void Sort(ApplicationPartManager partManager, IModuleContainer moduleContainer) { - var sortedParts = new List(); - foreach (var module in moduleContainer.Modules) - { - var parts = partManager.ApplicationParts.Where(part => - (part is AssemblyPart ap && ap.Assembly == module.Assembly) || - (part is CompiledRazorAssemblyPart crp && crp.Assembly == module.Assembly)) - .ToList(); + var orderedModuleAssemblies = moduleContainer.Modules + .Select((moduleDescriptor, index) => new { moduleDescriptor.Assembly, index }) + .ToDictionary(x => x.Assembly, x => x.index); + + var modulesAssemblies = moduleContainer.Modules.Select(x => x.Assembly).ToList(); + var sortedTypes = partManager.ApplicationParts + .Where(x => modulesAssemblies.Contains(GetApplicationPartAssembly(x))) + .OrderBy(x => orderedModuleAssemblies[GetApplicationPartAssembly(x)]) + .ToList(); + + var sortIndex = 0; + var sortedParts = partManager.ApplicationParts + .Select(x => modulesAssemblies.Contains(GetApplicationPartAssembly(x)) ? sortedTypes[sortIndex++] : x) + .ToList(); - if (!parts.IsNullOrEmpty()) - { - sortedParts.AddRange(parts.OrderBy(x => x is AssemblyPart ? 1 : 0)); - } - } - sortedParts.Reverse(); partManager.ApplicationParts.Clear(); + sortedParts.Reverse(); foreach (var applicationPart in sortedParts) { partManager.ApplicationParts.Add(applicationPart); } } + + private static Assembly GetApplicationPartAssembly(ApplicationPart part) + { + return part switch + { + AssemblyPart assemblyPart => assemblyPart.Assembly, + CompiledRazorAssemblyPart compiledRazorAssemblyPart => compiledRazorAssemblyPart.Assembly, + _ => throw new AbpException("Unknown application part type") + }; + } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApplicationPart/ApplicationPartSorter_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApplicationPart/ApplicationPartSorter_Tests.cs new file mode 100644 index 0000000000..829aa23527 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApplicationPart/ApplicationPartSorter_Tests.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using NSubstitute; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.ApplicationPart; + +public class ApplicationPartSorter_Tests +{ + [Fact] + public void Should_Sort_ApplicationParts_By_Module_Dependencies() + { + var moduleDescriptors = new List(); + var partManager = new ApplicationPartManager(); + + for (var i = 0; i < 10; i++) + { + var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"ModuleA{i}.dll"), AssemblyBuilderAccess.Run); + partManager.ApplicationParts.Add(new AssemblyPart(assembly)); + moduleDescriptors.Add(CreateModuleDescriptor(assembly)); + } + var randomApplicationParts = partManager.ApplicationParts.OrderBy(x => Guid.NewGuid()).ToList(); // Shuffle the parts + + // Additional part + randomApplicationParts.AddFirst(new CompiledRazorAssemblyPart(typeof(AbpAspNetCoreModule).Assembly)); + randomApplicationParts.Insert(5, new CompiledRazorAssemblyPart(typeof(AbpAspNetCoreMvcModule).Assembly)); + randomApplicationParts.AddLast(new CompiledRazorAssemblyPart(typeof(AbpVirtualFileSystemModule).Assembly)); + + partManager.ApplicationParts.Clear(); + foreach (var part in randomApplicationParts) + { + partManager.ApplicationParts.Add(part); + } + + var moduleContainer = CreateFakeModuleContainer(moduleDescriptors); + + ApplicationPartSorter.Sort(partManager, moduleContainer); + + // Act + partManager.ApplicationParts.Count.ShouldBe(13); // 10 modules + 3 additional parts + + var applicationParts = partManager.ApplicationParts.Reverse().ToList(); // Reverse the order to match the expected output + + applicationParts[0].ShouldBeOfType().Assembly.ShouldBe(typeof(AbpAspNetCoreModule).Assembly); + applicationParts[1].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA0"); + applicationParts[2].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA1"); + applicationParts[3].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA2"); + applicationParts[4].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA3"); + applicationParts[5].ShouldBeOfType().Assembly.ShouldBe(typeof(AbpAspNetCoreMvcModule).Assembly); + applicationParts[6].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA4"); + applicationParts[7].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA5"); + applicationParts[8].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA6"); + applicationParts[9].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA7"); + applicationParts[10].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA8"); + applicationParts[11].ShouldBeOfType().Assembly.GetName().Name.ShouldStartWith("ModuleA9"); + applicationParts[12].ShouldBeOfType().Assembly.ShouldBe(typeof(AbpVirtualFileSystemModule).Assembly); + } + + private static IModuleContainer CreateFakeModuleContainer(List moduleDescriptors) + { + var fakeModuleContainer = Substitute.For(); + fakeModuleContainer.Modules.Returns(moduleDescriptors); + return fakeModuleContainer; + } + + private static IAbpModuleDescriptor CreateModuleDescriptor(Assembly assembly) + { + var moduleDescriptor = Substitute.For(); + moduleDescriptor.Assembly.Returns(assembly); + return moduleDescriptor; + } +}