-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
DirectoryModuleCatalog.netcore.cs
210 lines (181 loc) · 9.19 KB
/
DirectoryModuleCatalog.netcore.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using Prism.Properties;
namespace Prism.Modularity
{
/// <summary>
/// Represents a catalog created from a directory on disk.
/// </summary>
/// <remarks>
/// The directory catalog will scan the contents of a directory, locating classes that implement
/// <see cref="IModule"/> and add them to the catalog based on contents in their associated <see cref="ModuleAttribute"/>.
/// Assemblies are loaded into a new application domain with ReflectionOnlyLoad. The application domain is destroyed
/// once the assemblies have been discovered.
///
/// The directory catalog does not continue to monitor the directory after it has created the initialize catalog.
/// </remarks>
public class DirectoryModuleCatalog : ModuleCatalog
{
/// <summary>
/// Directory containing modules to search for.
/// </summary>
public string ModulePath { get; set; }
/// <summary>
/// Drives the main logic of building the child domain and searching for the assemblies.
/// </summary>
protected override void InnerLoad()
{
if (string.IsNullOrEmpty(this.ModulePath))
throw new InvalidOperationException(Resources.ModulePathCannotBeNullOrEmpty);
if (!Directory.Exists(this.ModulePath))
throw new InvalidOperationException(
string.Format(CultureInfo.CurrentCulture, Resources.DirectoryNotFound, this.ModulePath));
AppDomain childDomain = AppDomain.CurrentDomain;
try
{
List<string> loadedAssemblies = new List<string>();
var assemblies = (
from Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()
where !(assembly is System.Reflection.Emit.AssemblyBuilder)
&& assembly.GetType().FullName != "System.Reflection.Emit.InternalAssemblyBuilder"
&& !String.IsNullOrEmpty(assembly.Location)
select assembly.Location
);
loadedAssemblies.AddRange(assemblies);
Type loaderType = typeof(InnerModuleInfoLoader);
if (loaderType.Assembly != null)
{
var loader =
(InnerModuleInfoLoader)
childDomain.CreateInstanceFrom(loaderType.Assembly.Location, loaderType.FullName).Unwrap();
this.Items.AddRange(loader.GetModuleInfos(this.ModulePath));
}
}
catch (Exception ex)
{
throw new Exception("There was an error loading assemblies.", ex);
}
}
private class InnerModuleInfoLoader : MarshalByRefObject
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
internal ModuleInfo[] GetModuleInfos(string path)
{
DirectoryInfo directory = new DirectoryInfo(path);
ResolveEventHandler resolveEventHandler =
delegate (object sender, ResolveEventArgs args) { return OnReflectionOnlyResolve(args, directory); };
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveEventHandler;
Assembly moduleReflectionOnlyAssembly = AppDomain.CurrentDomain.GetAssemblies().First(asm => asm.FullName == typeof(IModule).Assembly.FullName);
Type IModuleType = moduleReflectionOnlyAssembly.GetType(typeof(IModule).FullName);
IEnumerable<ModuleInfo> modules = GetNotAlreadyLoadedModuleInfos(directory, IModuleType);
var array = modules.ToArray();
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= resolveEventHandler;
return array;
}
private static IEnumerable<ModuleInfo> GetNotAlreadyLoadedModuleInfos(DirectoryInfo directory, Type IModuleType)
{
List<Assembly> validAssemblies = new List<Assembly>();
Assembly[] alreadyLoadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().Where(p => !p.IsDynamic).ToArray();
var fileInfos = directory.GetFiles("*.dll")
.Where(file => alreadyLoadedAssemblies.FirstOrDefault(
assembly => String.Compare(Path.GetFileName(assembly.Location),
file.Name, StringComparison.OrdinalIgnoreCase) == 0) == null).ToList();
foreach (FileInfo fileInfo in fileInfos)
{
try
{
validAssemblies.Add(Assembly.LoadFrom(fileInfo.FullName));
}
catch (BadImageFormatException)
{
// skip non-.NET Dlls
}
}
return validAssemblies.SelectMany(assembly => assembly
.GetExportedTypes()
.Where(IModuleType.IsAssignableFrom)
.Where(t => t != IModuleType)
.Where(t => !t.IsAbstract)
.Select(type => CreateModuleInfo(type)));
}
private static Assembly OnReflectionOnlyResolve(ResolveEventArgs args, DirectoryInfo directory)
{
Assembly loadedAssembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault(
asm => string.Equals(asm.FullName, args.Name, StringComparison.OrdinalIgnoreCase));
if (loadedAssembly != null)
{
return loadedAssembly;
}
AssemblyName assemblyName = new AssemblyName(args.Name);
string dependentAssemblyFilename = Path.Combine(directory.FullName, assemblyName.Name + ".dll");
if (File.Exists(dependentAssemblyFilename))
{
return Assembly.ReflectionOnlyLoadFrom(dependentAssemblyFilename);
}
return Assembly.ReflectionOnlyLoad(args.Name);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
internal void LoadAssemblies(IEnumerable<string> assemblies)
{
foreach (string assemblyPath in assemblies)
{
try
{
Assembly.ReflectionOnlyLoadFrom(assemblyPath);
}
catch (FileNotFoundException)
{
// Continue loading assemblies even if an assembly can not be loaded in the new AppDomain
}
}
}
private static ModuleInfo CreateModuleInfo(Type type)
{
string moduleName = type.Name;
List<string> dependsOn = new List<string>();
bool onDemand = false;
var moduleAttribute =
CustomAttributeData.GetCustomAttributes(type).FirstOrDefault(
cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleAttribute).FullName);
if (moduleAttribute != null)
{
foreach (CustomAttributeNamedArgument argument in moduleAttribute.NamedArguments)
{
string argumentName = argument.MemberInfo.Name;
switch (argumentName)
{
case "ModuleName":
moduleName = (string)argument.TypedValue.Value;
break;
case "OnDemand":
onDemand = (bool)argument.TypedValue.Value;
break;
case "StartupLoaded":
onDemand = !((bool)argument.TypedValue.Value);
break;
}
}
}
var moduleDependencyAttributes =
CustomAttributeData.GetCustomAttributes(type).Where(
cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleDependencyAttribute).FullName);
foreach (CustomAttributeData cad in moduleDependencyAttributes)
{
dependsOn.Add((string)cad.ConstructorArguments[0].Value);
}
ModuleInfo moduleInfo = new ModuleInfo(moduleName, type.AssemblyQualifiedName)
{
InitializationMode = onDemand ? InitializationMode.OnDemand : InitializationMode.WhenAvailable,
Ref = type.Assembly.EscapedCodeBase,
};
moduleInfo.DependsOn.AddRange(dependsOn);
return moduleInfo;
}
}
}
}