Orchard详解--第八篇 拓展模块及引用的预处理
Posted 7m鱼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Orchard详解--第八篇 拓展模块及引用的预处理相关的知识,希望对你有一定的参考价值。
从上一篇可以看出Orchard在处理拓展模块时主要有两个组件,一个是Folder另一个是Loader,前者用于搜索后者用于加载。
其中Folder一共有三个:Module Folder、Core Folder、ThemeFolder。Loader有引用加载器(Referenced Module Loader)、核心模块加载器(Core Module Loader)、预编译模块加载器(Precompiled Module Loader)、动态模块加载器(Dynamic Module Loader)。它们在代码里可以看到,在创建容器时对与Folder和Loader的注册:
1 builder.RegisterType<ExtensionLoaderCoordinator>().As<IExtensionLoaderCoordinator>().SingleInstance(); 2 builder.RegisterType<ExtensionMonitoringCoordinator>().As<IExtensionMonitoringCoordinator>().SingleInstance(); 3 builder.RegisterType<ExtensionManager>().As<IExtensionManager>().SingleInstance(); 4 { 5 builder.RegisterType<ExtensionHarvester>().As<IExtensionHarvester>().SingleInstance(); 6 builder.RegisterType<ModuleFolders>().As<IExtensionFolders>().SingleInstance() 7 .WithParameter(new NamedParameter("paths", extensionLocations.ModuleLocations)); 8 builder.RegisterType<CoreModuleFolders>().As<IExtensionFolders>().SingleInstance() 9 .WithParameter(new NamedParameter("paths", extensionLocations.CoreLocations)); 10 builder.RegisterType<ThemeFolders>().As<IExtensionFolders>().SingleInstance() 11 .WithParameter(new NamedParameter("paths", extensionLocations.ThemeLocations)); 12 13 builder.RegisterType<CoreExtensionLoader>().As<IExtensionLoader>().SingleInstance(); 14 builder.RegisterType<ReferencedExtensionLoader>().As<IExtensionLoader>().SingleInstance(); 15 builder.RegisterType<PrecompiledExtensionLoader>().As<IExtensionLoader>().SingleInstance(); 16 builder.RegisterType<DynamicExtensionLoader>().As<IExtensionLoader>().SingleInstance(); 17 builder.RegisterType<RawThemeExtensionLoader>().As<IExtensionLoader>().SingleInstance(); 18 }
这里需要注意的是,除了以上的Loader外,这里还有一个Raw Theme Extension Loader。
接下来看Folder是如何工作的:
三个Folder的实现可以说是一致的,都是通过ExtensionHarvester去获取一个ExtensionDescriptior列表:
1 public IEnumerable<ExtensionDescriptor> AvailableExtensions() { 2 return _extensionHarvester.HarvestExtensions(_paths, DefaultExtensionTypes.Module, "Module.txt", false/*isManifestOptional*/); 3 }
仅仅是路径和文件名称(Module.txt和Theme.txt)不同。所以可以这样说,模块和主题的搜索工作是通过ExtensionHarvester完成的。
1 public IEnumerable<ExtensionDescriptor> HarvestExtensions(IEnumerable<string> paths, string extensionType, string manifestName, bool manifestIsOptional) { 2 return paths 3 .SelectMany(path => HarvestExtensions(path, extensionType, manifestName, manifestIsOptional)) 4 .ToList(); 5 } 6 private IEnumerable<ExtensionDescriptor> HarvestExtensions(string path, string extensionType, string manifestName, bool manifestIsOptional) { 7 string key = string.Format("{0}-{1}-{2}", path, manifestName, extensionType); 8 return _cacheManager.Get(key, true, ctx => { 9 if (!DisableMonitoring) { 10 Logger.Debug("Monitoring virtual path \\"{0}\\"", path); 11 ctx.Monitor(_webSiteFolder.WhenPathChanges(path)); 12 } 13 return AvailableExtensionsInFolder(path, extensionType, manifestName, manifestIsOptional).ToReadOnlyCollection(); 14 }); 15 }
从上面代码可以看出Harvester内部还使用了缓存机制,以路径、文件名称和类型(模块还是主题)来作为关键字。它缓存的内容是当前目录下的一个拓展描述列表。
描述信息包括:
1 public string Location { get; set; } 2 public string Id { get; set; } 3 public string VirtualPath { get { return Location + "/" + Id; } } 4 public string ExtensionType { get; set; } 5 public string Name { get; set; } 6 public string Path { get; set; } 7 public string Description { get; set; } 8 public string Version { get; set; } 9 public string OrchardVersion { get; set; } 10 public string Author { get; set; } 11 public string WebSite { get; set; } 12 public string Tags { get; set; } 13 public string AntiForgery { get; set; } 14 public string Zones { get; set; } 15 public string BaseTheme { get; set; } 16 public string SessionState { get; set; } 17 public LifecycleStatus LifecycleStatus { get; set; } 18 19 public IEnumerable<FeatureDescriptor> Features { get; set; }
功能描述信息包括:
1 public ExtensionDescriptor Extension { get; set; } 2 public string Id { get; set; } 3 public string Name { get; set; } 4 public string Description { get; set; } 5 public string Category { get; set; } 6 public int Priority { get; set; } 7 public IEnumerable<string> Dependencies { get; set; }
Harvester中的GetDescriptorForExtension和GetFeaturesForExtension两个方法分别用于通过Module.txt或Theme.txt文件来获取拓展描述和功能描述信息(此处暂未对功能描述信息的依赖列表赋值)。其中拓展会作为一个默认的功能。
Loader
当Folder完成描述信息的构建后,Loader将通过这些描述信息来探测和加载模块。以下是Loader的接口定义:
1 public interface IExtensionLoader { 2 int Order { get; } 3 string Name { get; } 4 5 IEnumerable<ExtensionReferenceProbeEntry> ProbeReferences(ExtensionDescriptor extensionDescriptor); 6 Assembly LoadReference(DependencyReferenceDescriptor reference); 7 void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry); 8 void ReferenceDeactivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry); 9 bool IsCompatibleWithModuleReferences(ExtensionDescriptor extension, IEnumerable<ExtensionProbeEntry> references); 10 11 ExtensionProbeEntry Probe(ExtensionDescriptor descriptor); 12 ExtensionEntry Load(ExtensionDescriptor descriptor); 13 14 void ExtensionActivated(ExtensionLoadingContext ctx, ExtensionDescriptor extension); 15 void ExtensionDeactivated(ExtensionLoadingContext ctx, ExtensionDescriptor extension); 16 void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency); 17 18 void Monitor(ExtensionDescriptor extension, Action<IVolatileToken> monitor); 19 20 /// <summary> 21 /// Return a list of references required to compile a component (e.g. a Razor or WebForm view) 22 /// depending on the given module. 23 /// Each reference can either be an assembly name or a file to pass to the 24 /// IBuildManager.GetCompiledAssembly() method (e.g. a module .csproj project file). 25 /// </summary> 26 IEnumerable<ExtensionCompilationReference> GetCompilationReferences(DependencyDescriptor dependency); 27 /// <summary> 28 /// Return the list of dependencies (as virtual path) of the given module. 29 /// If any of the dependency returned in the list is updated, a component depending 30 /// on the assembly produced for the module must be re-compiled. 31 /// For example, Razor or WebForms views needs to be recompiled when a dependency of a module changes. 32 /// </summary> 33 IEnumerable<string> GetVirtualPathDependencies(DependencyDescriptor dependency); 34 }
从上面接口可以看到Loader有很多方法,它们具有什么功能?
根据接口,可以将其方法分为以下几类:
● 拓展模块:
○ 探测:ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
○ 加载:ExtensionEntry Load(ExtensionDescriptor descriptor);
● 模块引用:
○ 探测:IEnumerable<ExtensionReferenceProbeEntry> ProbeReferences(ExtensionDescriptor extensionDescriptor);
○ 加载:Assembly LoadReference(DependencyReferenceDescriptor reference);
● 激活与停用:
○ 模块激活:void ExtensionActivated(ExtensionLoadingContext ctx, ExtensionDescriptor extension);
○ 模块停用:void ExtensionDeactivated(ExtensionLoadingContext ctx, ExtensionDescriptor extension);
○ 模块删除:void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency);
○ 引用激活:void ReferenceActivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry);
○ 引用停用:void ReferenceDeactivated(ExtensionLoadingContext context, ExtensionReferenceProbeEntry referenceEntry);
● 监控:void Monitor(ExtensionDescriptor extension, Action<IVolatileToken> monitor);
● 编译需要的引用:IEnumerable<ExtensionCompilationReference> GetCompilationReferences(DependencyDescriptor dependency);
● 依赖路径支持:IEnumerable<string> GetVirtualPathDependencies(DependencyDescriptor dependency);
按照上面的分类,可以大致猜测Loader的使用过程:探测(模块和引用)---->加载(模块和引用)----->激活引用----->激活模块。然后其它接口用于管理拓展的启用和停用,以及编译时的一些辅助。
从前一篇文章告诉我们的是Orchard的模块激活是以"Module.txt"文件作为输入然后输出一个System.Type的列表。之前通过Folder已经搜索了所有相关目录,解析每一个目录下的Module.txt或Theme.txt文件,生成了一个Extension Descriptor对象列表。
现在Orchard将通过这些对象使用Loader来探测实际模块以及实际引用的相关信息:
先放上代码(位于ExtensionLoaderCoordinator类型的CreateLoadingContext方法):
1 Logger.Information("Probing extensions"); 2 var availableExtensionsProbes1 = _parallelCacheContext 3 .RunInParallel(availableExtensions, extension => 4 _loaders.Select(loader => loader.Probe(extension)).Where(entry => entry != null).ToArray()) 5 .SelectMany(entries => entries) 6 .GroupBy(entry => entry.Descriptor.Id); 7 8 var availableExtensionsProbes = _parallelCacheContext 9 .RunInParallel(availableExtensionsProbes1, g => 10 new { Id = g.Key, Entries = SortExtensionProbeEntries(g, virtualPathModficationDates)}) 11 .ToDictionary(g => g.Id, g => g.Entries, StringComparer.OrdinalIgnoreCase); 12 Logger.Information("Done probing extensions"); 13 14 var deletedDependencies = previousDependencies 15 .Where(e => !availableExtensions.Any(e2 => StringComparer.OrdinalIgnoreCase.Equals(e2.Id, e.Name))) 16 .ToList(); 17 18 // Collect references for all modules 19 Logger.Information("Probing extension references"); 20 var references = _parallelCacheContext 21 .RunInParallel(availableExtensions, extension => _loaders.SelectMany(loader => loader.ProbeReferences(extension)).ToList()) 22 .SelectMany(entries => entries) 23 .ToList(); 24 Logger.Information("Done probing extension references");
从上面代码可以看到它做了三件事:1、探测所有的拓展信息。2、根据路径修改时间对探测信息进行排序。3、探测所有引用信息。
探测拓展信息:
1 .RunInParallel(availableExtensions, extension => 2 _loaders.Select(loader => loader.Probe(extension)).Where(entry => entry != null).ToArray())
根据代码可以看出,对于每一个可用拓展,需要通过所有loader的探测:
1. CoreExtensionLoader:很直接的判断是否来至于Core目录下,然后直接组建ExtensionProbeEntry。
1 public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) { 2 if (Disabled) 3 return null; 4 5 if (descriptor.Location == "~/Core") { 6 return new ExtensionProbeEntry { 7 Descriptor = descriptor, 8 Loader = this, 9 Priority = 100, // Higher priority because assemblies in ~/bin always take precedence 10 VirtualPath = "~/Core/" + descriptor.Id, 11 VirtualPathDependencies = Enumerable.Empty<string>(), 12 }; 13 } 14 return null; 15 }
2. PrecompiledExtensionLoader:根据Id(模块名称)获取程序集文件路径,并组建ExtensionProbeEntry。它的依赖列表也是当前程序集。
1 ... 2 Logger.Information("Probing for module \'{0}\'", descriptor.Id); 3 4 var assemblyPath = GetAssemblyPath(descriptor); 5 if (assemblyPath == null) 6 return null; 7 8 var result = new ExtensionProbeEntry { 9 Descriptor = descriptor, 10 Loader = this, 11 VirtualPath = assemblyPath, 12 VirtualPathDependencies = new[] { assemblyPath }, 13 }; 14 15 Logger.Information("Done probing for module \'{0}\'", descriptor.Id); 16 ...
3. DynamicExtensionLoader:与PrecompiledExtensionLoader相似,只不过这里获取的是模块的csproj文件。但是这里要注意的是它的依赖信息通过GetDependencies方法获取了相关的文件信息。
1 Logger.Information("Probing for module \'{0}\'", descriptor.Id); 2 3 string projectPath = GetProjectPath(descriptor); 4 if (projectPath == null) 5 return null; 6 7 var result = new ExtensionProbeEntry { 8 Descriptor = descriptor, 9 Loader = this, 10 VirtualPath = projectPath, 11 VirtualPathDependencies = GetDependencies(projectPath).ToList(), 12 }; 13 14 Logger.Information("Done probing for module \'{0}\'", descriptor.Id); 15 --- 16 public ProjectFileDescriptor Parse(TextReader reader) { 17 var document = XDocument.Load(XmlReader.Create(reader)); 18 return new ProjectFileDescriptor { 19 AssemblyName = GetAssemblyName(document), 20 SourceFilenames = GetSourceFilenames(document).ToArray(), 21 References = GetReferences(document).ToArray() 22 }; 23 } 24 private static string GetAssemblyName(XDocument document) { 25 return document 26 .Elements(ns("Project")) 27 .Elements(ns("PropertyGroup")) 28 .Elements(ns("AssemblyName")) 29 .Single() 30 .Value; 31 } 32 33 private static IEnumerable<string> GetSourceFilenames(XDocument document) { 34 return document 35 .Elements(ns("Project")) 36 .Elements(ns("ItemGroup")) 37 .Elements(ns("Compile")) 38 .Attributes("Include") 39 .Select(c => c.Value); 40 } 41 42 private static IEnumerable<ReferenceDescriptor> GetReferences(XDocument document) { 43 var assemblyReferences = document 44 .Elements(ns("Project")) 45 .Elements(ns("ItemGroup")) 46 .Elements(ns("Reference")) 47 .Where(c => c.Attribute("Include") != null) 48 .Select(c => { 49 string path = null; 50 XElement attribute = c.Elements(ns("HintPath")).FirstOrDefault(); 51 if (attribute != null) { 52 path = attribute.Value; 53 } 54 55 return new ReferenceDescriptor { 56 SimpleName = ExtractAssemblyName(c.Attribute("Include").Value), 57 FullName = c.Attribute("Include").Value, 58 Path = path, 59 ReferenceType = ReferenceType.Library 60 }; 61 }); 62 63 var projectReferences = document 64 .Elements(ns("Project")) 65 .Elements(ns("ItemGroup")) 66 .Elements(ns("ProjectReference")) 67 .Attributes("Include") 68 .Select(c => new ReferenceDescriptor { 69 SimpleName = Path.GetFileNameWithoutExtension(c.Value), 70 FullName = Path.GetFileNameWithoutExtension(c.Value), 71 Path = c.Value, 72 ReferenceType = ReferenceType.Project 73 }); 74 75 return assemblyReferences.Union(projectReferences); 76 }
获取依赖信息,实际上就是解析csproj这个xml文件。
4. RawThemeExtensionLoader:这个Loader比较特殊,它用于加载~/themes目录下没有代码的主题。如果存在csproj和bin目录下的程序集都会被忽略。
1 // Temporary - theme without own project should be under ~/themes 2 if (descriptor.Location.StartsWith("~/Themes",StringComparison.InvariantCultureIgnoreCase)) { 3 string projectPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Id, 4 descriptor.Id + ".csproj"); 5 6 // ignore themes including a .csproj in this loader 7 if ( _virtualPathProvider.FileExists(projectPath) ) { 8 return null; 9 } 10 11 var assemblyPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Id, "bin", 12 descriptor.Id + ".dll"); 13 14 // ignore themes with /bin in this loader 15 if ( _virtualPathProvider.FileExists(assemblyPath) ) 16 return null; 17 18 return new ExtensionProbeEntry { 19 Descriptor = descriptor, 20 Loader = this, 21 VirtualPath = descriptor.VirtualPath, 22 VirtualPathDependencies = Enumerable.Empty<string>(), 23 }; 24 }
5. ReferencedExtensionLoader:引用拓展比较特殊,因为引用拓展是直接被根项目引用的模块项目,这些模块当根项目编译时,它们也会被编译并放置到根项目的bin目录下。
1 var assembly = _buildManager.GetReferencedAssembly(descriptor.Id); 2 if (assembly == null) 3 return null; 4 5 var assemblyPath = _virtualPathProvider.Combine("~/bin", descriptor.Id + ".dll"); 6 7 return new ExtensionProbeEntry { 8 Descriptor = descriptor, 9 Loader = this, 10 Priority = 100, // Higher priority because assemblies in ~/bin always take precedence 11 VirtualPath = assemblyPath, 12 VirtualPathDependencies = new[] { assemblyPath }, 13 };
注:这里BulidManager中是通过一个DefaultAssemblyLoader来加载程序集的,DefaultAssemblyLoader类型自身维护了一个ConcurrentDictionary<string, Assembly> _loadedAssemblies字典,用于通过程序集的Short Name来存取程序集,如果程序集不存在时就通过程序集的Full Name、Short Name甚至通过程序集解析器来解析出程序集的名称,最后通过这些名称依次通过.net的Assembly调用Load(name)的方法加载返回并对结果进行缓存。
Orchard中有三种名称解析器:AppDomainAssemblyNameResolver、OrchardFrameworkAssemblyNameResolver以及GacAssemblyNameResolver。 它们分别从AppDomain、OrchardFramework依赖的DefaultAssemblyLoader中以及.Net全局程序集中查找程序集。
从上面的分析可知,它主要的操作是通过ExtensionDescriptor来获取对应模块的程序集路径和优先级,并以当前的加载器打包,以便后续用于加载对应程序集。最终ExtensionProbeEntry是以描述Id进行分组的,意味这同一个拓展存在多个探测实体(来自于不同Loader,主要是有不同的优先级以及路径修改时间)。
探测信息排序:
这里主要是将上面获取的ExtensionProbeEntry,针对每一个Key(即ExtensionDescriptor.Id)通过优先级来分组,然后取出优先级最高的组,如果改组中存在多个项(如预编译加载器和动态加载器都探测到相应模块,且它们优先级一致)时,根据它们依赖路径的最新修改时间再次进行排序(上面创建ExtensionProbeEntry时VirtualPathDependencies一般为程序集路径,但是动态加载器是所有依赖文件路径,句话说只要任意文件的修改日期大于编译库的信息,那么动态加载器对应的探测实体就会排在前面优先使用)。