Not all modules in Orchard are referenced by the root (startup) project. When Orchard loads a module, it needs to ensure that the other assemblies it depends on can be found. TXT and Theme. TXT, and finally generates an ExtensionLoadingContext, which contains detailed information about the Module assembly path and its dependency path.
The “~/App_Data/Dependencies” directory is the directory where ASP.NET looks for additional assemblies in addition to the bin directory where Orchard copies the extension module and its Dependencies. The “~/App_Data/Dependencies” directory is implemented by adding the following items to the root project’s web.config:
1 <runtime>
2 ...
3 <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
4 <probing privatePath="App_Data/Dependencies" />
5 ...
6 </assemblyBinding>
7 </runtime>
Copy the code
Orchard expands and references them to the App_Data/Dependencies directory, and then loads them. This process will be analyzed in detail.
The following code in ExtensionLoaderCoordinator SetupExtensions method, on the basis of the existing ExtensionLoadingContext its role is to deal with the available.
1 // For all existing extensions in the site, ask each loader if they can 2 // load that extension. 3 foreach (var extension in context.AvailableExtensions) { 4 ProcessExtension(context, extension); 5}Copy the code
Its processing is as follows:
Let’s start with the full code:
1 private void ProcessExtension(ExtensionLoadingContext context, ExtensionDescriptor extension) { 2 var extensionProbes = context.AvailableExtensionsProbes.ContainsKey(extension.Id) ? 3 context.AvailableExtensionsProbes[extension.Id] : 4 Enumerable.Empty<ExtensionProbeEntry>(); 5 // materializes the list 6 extensionProbes = extensionProbes.ToArray(); 7 8 if (Logger.IsEnabled(LogLevel.Debug)) { 9 Logger.Debug("Loaders for extension \"{0}\": ", extension.Id); 10 foreach (var probe in extensionProbes) { 11 Logger.Debug(" Loader: {0}", probe.Loader.Name); 12 Logger.Debug(" VirtualPath: {0}", probe.VirtualPath); 13 Logger.Debug(" VirtualPathDependencies: {0}", string.Join(", ", probe.VirtualPathDependencies)); 14 } 15 } 16 17 var moduleReferences = 18 context.AvailableExtensions 19 .Where(e => 20 context.ReferencesByModule.ContainsKey(extension.Id) && 21 context.ReferencesByModule[extension.Id].Any(r => StringComparer.OrdinalIgnoreCase.Equals(e.Id, r.Name))) 22 .ToList(); 23 24 var processedModuleReferences = 25 moduleReferences 26 .Where(e => context.ProcessedExtensions.ContainsKey(e.Id)) 27 .Select(e => context.ProcessedExtensions[e.Id]) 28 .ToList(); 29 30 var activatedExtension = extensionProbes.FirstOrDefault( 31 e => e.Loader.IsCompatibleWithModuleReferences(extension, processedModuleReferences) 32 ); 33 34 var previousDependency = context.PreviousDependencies.FirstOrDefault( 35 d => StringComparer.OrdinalIgnoreCase.Equals(d.Name, extension.Id) 36 ); 37 38 if (activatedExtension == null) { 39 Logger.Warning("No loader found for extension \"{0}\"!" , extension.Id); 40 } 41 42 var references = ProcessExtensionReferences(context, activatedExtension); 43 44 foreach (var loader in _loaders) { 45 if (activatedExtension ! = null && activatedExtension.Loader.Name == loader.Name) { 46 Logger.Information("Activating extension \"{0}\" with loader \"{1}\"", activatedExtension.Descriptor.Id, loader.Name); 47 loader.ExtensionActivated(context, extension); 48 } 49 else if (previousDependency ! = null && previousDependency.LoaderName == loader.Name) { 50 Logger.Information("Deactivating extension \"{0}\" from loader \"{1}\"", previousDependency.Name, loader.Name); 51 loader.ExtensionDeactivated(context, extension); 52 } 53 } 54 55 if (activatedExtension ! = null) { 56 context.NewDependencies.Add(new DependencyDescriptor { 57 Name = extension.Id, 58 LoaderName = activatedExtension.Loader.Name, 59 VirtualPath = activatedExtension.VirtualPath, 60 References = references 61 }); 62 } 63 64 // Keep track of which loader we use for every extension 65 // This will be needed for processing references from other dependent extensions 66 context.ProcessedExtensions.Add(extension.Id, activatedExtension); 67}Copy the code
View Code
Obtain the corresponding detection information according to the extended information:
1 var extensionProbes = context. AvailableExtensionsProbes. Either ContainsKey (extension. Id)? 2 context.AvailableExtensionsProbes[extension.Id] : 3 Enumerable.Empty<ExtensionProbeEntry>(); 4 5 // materializes the list 6 extensionProbes = extensionProbes.ToArray();Copy the code
Generally, only ~\Modules and custom extension Modules directories will have multiple probes, because they are detected by both the precompiled loader and the dynamically compiled loader (the bin directory and the csproj file).
Handling module references (module references module) :
Iterate through all extension modules to see if they depend on other modules, and if they do, to see if existing dependencies have been handled.
1 var moduleReferences =
2 context.AvailableExtensions
3 .Where(e =>
4 context.ReferencesByModule.ContainsKey(extension.Id) &&
5 context.ReferencesByModule[extension.Id].Any(r => StringComparer.OrdinalIgnoreCase.Equals(e.Id, r.Name)))
6 .ToList();
7
8 var processedModuleReferences =
9 moduleReferences
10 .Where(e => context.ProcessedExtensions.ContainsKey(e.Id))
11 .Select(e => context.ProcessedExtensions[e.Id])
12 .ToList();
Copy the code
If the corresponding dependent module has been processed, it will decide which probe entity to use according to the processed module. Because the Loader of probe entity is different, it may not be compatible with the processed module, so ExtensionProbes have been sorted by priority and latest modification time before. And determine whether compatible is according to the order, the choice and compatible with the highest priority detection information (note: all of the Loader, only the precompiled Loader is not compatible with dynamic compilation Loader (see Loader IsCompatibleWithModuleReferences method), the rest is compatible. So in other words, when only some modules are determined to use the dynamic compiler loader, the detection information of subsequent modules that depend on it using the precompiled loader is not used and is diverted to other loaders with lower priority.
1 var activatedExtension = extensionProbes. FirstOrDefault (2 e = > e.Loader.IsCompatibleWithModuleReferences(extension, processedModuleReferences) 3 );Copy the code
Find out if dependency information was last run:
1 var previousDependency = context. PreviousDependencies. FirstOrDefault (2 d = > StringComparer.OrdinalIgnoreCase.Equals(d.Name, extension.Id) 3 );Copy the code
Handling references:
Step 1. Get the names of all dependencies:
1 var referenceNames = (context. ReferencesByModule. Either ContainsKey (activatedExtension. Descriptor. Id)? 2 context.ReferencesByModule[activatedExtension.Descriptor.Id] : 3 Enumerable.Empty<ExtensionReferenceProbeEntry>()) 4 .Select(r => r.Name) 5 .Distinct(StringComparer.OrdinalIgnoreCase);Copy the code
Step 2. Traverse and process all dependencies by name (pick the most appropriate reference) :
1. To determine whether depend on processing module, if it is to add DependencyReferenceDescriptor and return.
2. If the module is not processed, continue to check whether the dependency exists in the bin directory of the root project, if so, return directly.
3. If the above conditions are not conform to, in the context. ReferencesByName in according to the name lookup, then according to the latest modified time sequencing, choose the latest reference file, if the context ProcessedReferences does not exist the reference, Then add it to the context. The ProcessedReferences, and call the ReferenceActivated method. Finally add DependencyReferenceDescriptor and return.
1 Public override void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry) { 2 if (string.IsNullOrEmpty(referenceEntry.VirtualPath)) 3 return; 4 5 string sourceFileName = _virtualPathProvider.MapPath(referenceEntry.VirtualPath); 6 7 // Copy the assembly if it doesn't exist or if it is older than the source file. 8 bool copyAssembly = 9 ! _assemblyProbingFolder.AssemblyExists(referenceEntry.Name) || 10 File.GetLastWriteTimeUtc(sourceFileName) > _assemblyProbingFolder.GetAssemblyDateTimeUtc(referenceEntry.Name); 11 12 if (copyAssembly) { 13 context.CopyActions.Add(() => _assemblyProbingFolder.StoreAssembly(referenceEntry.Name, sourceFileName)); 14 15 // We need to restart the appDomain if the assembly is loaded 16 if (_hostEnvironment.IsAssemblyLoaded(referenceEntry.Name)) { 17 Logger.Information("ReferenceActivated: Reference \"{0}\" is activated with newer file and its assembly is loaded, forcing AppDomain restart", referenceEntry.Name); 18 context.RestartAppDomain = true; 19} 20} 21}Copy the code
Note: The processing of references is actually for modules detected by the precompiled and dynamically compiled loaders, because all other modules are referenced by the root project. Once the root project is compiled, all referenced projects are automatically compiled and the results are placed in the bin directory. Precompiled and dynamically compiled loaders add a method to context.copyActions that copies the assembly to the App_Data/Dependencies directory via the ReferenceActivated method.
Processing expansion module:
1 foreach (var loader in _loaders) {2 if (activatedExtension! = null && activatedExtension.Loader.Name == loader.Name) { 3 Logger.Information("Activating extension \"{0}\" with loader \"{1}\"", activatedExtension.Descriptor.Id, loader.Name); 4 loader.ExtensionActivated(context, extension); 5 } 6 else if (previousDependency ! = null && previousDependency.LoaderName == loader.Name) { 7 Logger.Information("Deactivating extension \"{0}\" from loader \"{1}\"", previousDependency.Name, loader.Name); 8 loader.ExtensionDeactivated(context, extension); 9 } 10 } 11 12 if (activatedExtension ! = null) { 13 context.NewDependencies.Add(new DependencyDescriptor { 14 Name = extension.Id, 15 LoaderName = activatedExtension.Loader.Name, 16 VirtualPath = activatedExtension.VirtualPath, 17 References = references 18 }); 19 } 20 21 // Keep track of which loader we use for every extension 22 // This will be needed for processing references from other dependent extensions 23 context.ProcessedExtensions.Add(extension.Id, activatedExtension);Copy the code
As you can see from the above code, the processing steps of modules are mainly to traverse all loaders, then match with activatedExtension and previousDependency, and activate and disable the corresponding modules. Call the ExtensionActivated method. Otherwise, the loader corresponding to activatedExtension calls activatedExtension. PreviousDependency calls ExtensionDeactivated to disable the previous module of the same name). Finally, create a DependencyDescriptor and place the corresponding extension in ProcessedExtensions for the subsequent module to determine whether it refers to the already processed module.
ExtensionActivated: Extended activation is only for precompiled and dynamically compiled loaders, where precompiled activation adds a method that copies an assembly to ctx.CopyActions to match the method that handles references, while dynamically compiled activation simply adds a judgment as to whether the application domain needs to be restarted.
1 Public override void ExtensionActivated(ExtensionLoadingContext CTX, ExtensionDescriptor extension) { 2 if (_reloadWorkaround.AppDomainRestartNeeded) { 3 Logger.Information("ExtensionActivated: Module \"{0}\" has changed, forcing AppDomain restart", extension.Id); 4 ctx.RestartAppDomain = _reloadWorkaround.AppDomainRestartNeeded; 6 5}}Copy the code
ExtensionDeactivated: Adds a method to ctx.DeleteActions that removes the existing extended assembly for precompilation and reference loaders.
Precompiled:
1 public override void ExtensionDeactivated(ExtensionLoadingContext ctx, ExtensionDescriptor extension) { 2 if (_assemblyProbingFolder.AssemblyExists(extension.Id)) { 3 ctx.DeleteActions.Add( 4 () => { 5 Logger.Information("ExtensionDeactivated: Deleting assembly \"{0}\" from probing directory", extension.Id); 6 _assemblyProbingFolder.DeleteAssembly(extension.Id); 7}); 8 9 // We need to restart the appDomain if the assembly is loaded 10 if (_hostEnvironment.IsAssemblyLoaded(extension.Id)) { 11 Logger.Information("ExtensionDeactivated: Module \"{0}\" is deactivated and its assembly is loaded, forcing AppDomain restart", extension.Id); 12 ctx.RestartAppDomain = true; 13} 14} 15}Copy the code
View Code
Reference:
1 private void DeleteAssembly(ExtensionLoadingContext ctx, string moduleName) { 2 var assemblyPath = _virtualPathProvider.Combine("~/bin", moduleName + ".dll"); 3 if (_virtualPathProvider.FileExists(assemblyPath)) { 4 ctx.DeleteActions.Add( 5 () => { 6 Logger.Information("ExtensionRemoved: Deleting assembly \"{0}\" from bin directory (AppDomain will restart)", moduleName); 7 File.Delete(_virtualPathProvider.MapPath(assemblyPath)); 8}); 9 ctx.RestartAppDomain = true; 11 10}}Copy the code
View Code
Execute copy and delete commands:
As you can see from the code analysis above, when a reference or module is activated or disabled, the special module loader adds a method to copy or delete an assembly to the corresponding DeleteActions and CopyActions in the ExtensionLoadingContext. The ProcessContextCommands method handles those actions.
1 private void ProcessContextCommands(ExtensionLoadingContext CTX) {2 Logger.Information("Executing List of operations needed for loading extensions..." ); 3 foreach (var action in ctx.DeleteActions) { 4 action(); 5 } 6 7 foreach (var action in ctx.CopyActions) { 8 action(); 10 9}}Copy the code
Save dependency information: store the parsed DependencyDescriptor in the corresponding XML file:
1 // And finally save the new entries in the dependencies Folder 2 _dependenciesFolder.StoreDescriptors(context.NewDependencies); 3 _extensionDependenciesManager.StoreDependencies(context.NewDependencies, desc => GetExtensionHash(context, desc));Copy the code
Application domain restart: If the application domain needs to be restarted at last, the function of automatic application restart can be realized by RestartAppDomain method of DefaultHostEnvironment and by modifying marker. TXT file in bin directory or web. Restart is generally required when references are overwritten and the dynamic compiler loader detects changes to the file.
The above analysis completes Orchard’s entire expansion and dependency process.
Summary:
The Orchard extension process ensures that all extension modules and their Dependencies are stored in the directory App_Data/Dependencies. The Core and reference modules are stored in the bin directory of the root project. Restart the application domain if the reference assembly is overwritten.
The entire process is mainly for extension Modules that are placed in the Modules directory without dependencies. Because the rest of the modules are referenced by the root project, their dependencies and module assemblies are already handled automatically when the root project is compiled.