{"id":437,"date":"2013-08-06T20:53:00","date_gmt":"2013-08-06T20:53:00","guid":{"rendered":"https:\/\/staging.infragistics.com\/blogs\/?p=437"},"modified":"2025-02-25T13:20:07","modified_gmt":"2025-02-25T13:20:07","slug":"discover-load-modules-at-runtime","status":"publish","type":"post","link":"https:\/\/www.infragistics.com\/blogs\/discover-load-modules-at-runtime","title":{"rendered":"Prism: Dynamically Discover &amp; Load Modules at Runtime"},"content":{"rendered":"\n<p>Loading a module starts with what is called a ModuleCatalog.&nbsp; You can\u2019t load a module unless it has been added to a ModuleCatalog.&nbsp; Once the module has been added to a ModuleCatalog, Prism will then take care of loading the module assembly for you.&nbsp; Prism even comes with a handful of module catalogs to give you flexibility in how you register your modules with your Prism application.&nbsp; You can populate a module catalog from code, from XAML, with XML in an app.config, or from a directory.&nbsp; Heck, you can even use a combination of all these options to populate your module catalog.<\/p>\n\n\n\n<p>When I am giving a Prism talk at a public event or an internal lunch and learn at a company, I am sure to explain all the different ways of loading your modules and which catalog to use.&nbsp; This is about the time where the questions really start getting interesting.&nbsp; Of these questions, the most common is about the DirectoryModuleCatalog.&nbsp; This particular catalog allows you to specify a folder path to load your modules from.&nbsp; Now the interesting question\u2026 \u201cbut, what happens when a new module assembly is dropped into the folder?&nbsp; Will it automatically be loaded into the app while it is running?\u201d&nbsp; That is a great question, and the answer is <strong>NO<\/strong>.&nbsp; The DirectoryModuleCatalog does a one time scan of the directory and then loads all the modules that it finds.&nbsp; If you drop a new module assembly into the directory, it will not be loaded until the application is restarted.&nbsp; Now the follow-up question\u2026 \u201cwell, is it possible to dynamically discover the modules and load them from the directory as well?\u201d&nbsp; Answer; well of course it is.&nbsp; If you\u2019re using MEF, it\u2019s easy.&nbsp; If you\u2019re using a container such as Unity, you will need to write the code to handle it yourself.&nbsp; \u201cWell, we don\u2019t use MEF, so can you show us how?\u201d&nbsp; This is where my reply is always the same, \u201ca simple web search (Google or Bing) should help you find what you are looking for\u201d.<\/p>\n\n\n\n<p>Well, it turns out, that\u2019s not the case.&nbsp; It seems that no one has blogged about or shared any code that handles the dynamic discovery and loading of modules using a DI container such as Unity.&nbsp; Not that I could find, nor anyone who is asking me to show them could find.&nbsp; Which leads me to this post.&nbsp; I am going to show you an approach that I have used to support such a scenario.&nbsp; I am actually going to give you two approaches.&nbsp; One is the \u201cQuick and Dirty\u201d way.&nbsp; Basically, I will throw together the simplest sample to achieve the goal.&nbsp; Then I will show you \u201cA Better Way\u201d in which we will encapsulate this functionality into a custom ModuleCatalog that will handle everything for us.<\/p>\n\n\n\n<p>Here is the Prism app we are using to test our code.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><a href=\"\/community\/cfs-file\/__key\/CommunityServer.Blogs.Components.WeblogFiles\/blagunas.metablogapi\/7455.image_5F00_2.png\"><img decoding=\"async\" src=\"\/community\/cfs-file\/__key\/CommunityServer.Blogs.Components.WeblogFiles\/blagunas.metablogapi\/1184.image_5F00_thumb.png\" alt=\"Dynamically Discover and Load Modules at Runtime - Solution\" title=\"Dynamically Discover and Load Modules at Runtime - Solution\"\/><\/a><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>It\u2019s a Prism application that contains a Shell with a single region and one module that contains a single view.&nbsp; When the module is loaded properly, this will be the final result.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><a href=\"\/community\/cfs-file\/__key\/CommunityServer.Blogs.Components.WeblogFiles\/blagunas.metablogapi\/1172.image_5F00_4.png\"><img decoding=\"async\" src=\"\/community\/cfs-file\/__key\/CommunityServer.Blogs.Components.WeblogFiles\/blagunas.metablogapi\/5126.image_5F00_thumb_5F00_1.png\" alt=\"Dynamically Discover and Load Modules at Runtime - Result\" title=\"Dynamically Discover and Load Modules at Runtime - Result\"\/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"quick-and-dirty\">Quick and Dirty<\/h2>\n\n\n\n<p>The \u201cQuick and Dirty\u201d way, is well\u2026. quick and dirty.&nbsp; First we need to determine what mechanism we are going to use to detect when a new module assembly has been added to our modules directory.&nbsp; This is a no brainer.&nbsp; We will be using the FileSystemWatcher class.&nbsp; The FileSystemWatcher monitors a given directory for changes and notifies us via events that a change has occurred.&nbsp; Such as a file being added to the directory.&nbsp; Let\u2019s create an instance of this class in our Bootstrapper constructor and listen for it\u2019s Created event.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">public Bootstrapper()\n{\n    \/\/ we need to watch our folder for newly added modules\n    FileSystemWatcher fileWatcher = new FileSystemWatcher(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, \"Modules\"), \"*.dll\");\n    fileWatcher.Created += fileWatcher_Created;\n    fileWatcher.EnableRaisingEvents = true;\n}<\/pre>\n\n\n\n<p>Notice that in the constructor of the FileSystemWatcher, it takes the location of the directory we want to monitor, as well as a second parameter which allows us to specify a filter.&nbsp; In this case we only care about DLLs.&nbsp; We also need to set the FileSystemWatcher.EnableRaisingEvents = true in order to start the monitoring of the directory.&nbsp; Now anytime a new DLL is added to our directory, our event handler will execute.&nbsp; Time to check out our event handler<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">void fileWatcher_Created(object sender, FileSystemEventArgs e)\n{\n    if (e.ChangeType == WatcherChangeTypes.Created)\n    {\n        \/\/get the Prism assembly that IModule is defined in\n        Assembly moduleAssembly = AppDomain.CurrentDomain.GetAssemblies().First(asm => asm.FullName == typeof(IModule).Assembly.FullName);\n        Type IModuleType = moduleAssembly.GetType(typeof(IModule).FullName);\n\n        \/\/load our newly added assembly\n        Assembly assembly = Assembly.LoadFile(e.FullPath);\n\n        \/\/look for all the classes that implement IModule in our assembly and create a ModuleInfo class from it\n        var moduleInfos = assembly.GetExportedTypes()\n            .Where(IModuleType.IsAssignableFrom)\n            .Where(t => t != IModuleType)\n            .Where(t => !t.IsAbstract).Select(t => CreateModuleInfo(t));\n\n\n        \/\/create an instance of our module manager\n        var moduleManager = Container.Resolve&lt;IModuleManager>();\n\n        foreach (var moduleInfo in moduleInfos)\n        {\n            \/\/add the ModuleInfo to the catalog so it can be loaded\n            ModuleCatalog.AddModule(moduleInfo);\n\n            \/\/now load the module using the Dispatcher because the FileSystemWatcher.Created even occurs on a separate thread\n            \/\/and we need to load our module into the main thread.\n            var d = Application.Current.Dispatcher;\n            if (d.CheckAccess())\n                moduleManager.LoadModule(moduleInfo.ModuleName);\n            else\n                d.BeginInvoke((Action)delegate { moduleManager.LoadModule(moduleInfo.ModuleName); });\n        }\n    }\n}\n\nprivate static ModuleInfo CreateModuleInfo(Type type)\n{\n    string moduleName = type.Name;\n\n    var moduleAttribute = CustomAttributeData.GetCustomAttributes(type).FirstOrDefault(cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleAttribute).FullName);\n\n    if (moduleAttribute != null)\n    {\n        foreach (CustomAttributeNamedArgument argument in moduleAttribute.NamedArguments)\n        {\n            string argumentName = argument.MemberInfo.Name;\n            if (argumentName == \"ModuleName\")\n            {\n                moduleName = (string)argument.TypedValue.Value;\n                break;\n            }\n        }\n    }\n\n    ModuleInfo moduleInfo = new ModuleInfo(moduleName, type.AssemblyQualifiedName)\n    {\n        InitializationMode = InitializationMode.OnDemand,\n        Ref = type.Assembly.CodeBase,\n    };\n\n    return moduleInfo;\n}<\/pre>\n\n\n\n<p>This code takes the newly added assembly and loads it into our application.&nbsp; Next we search the assembly for all classes that implement IModule, which is the interface that specifics it represents a Prism module.&nbsp; Next we loop through all the found modules and add them to the ModuleCatalog.&nbsp; We have to do this because we can\u2019t load a module that is not registered in the module catalog.&nbsp; Now we use the IModuleManager to load the module using the Dispatcher.&nbsp; We have to use the Dispatcher because the FileSystemWatcher.Created event listens on a separate thread, and we need to load the modules on the main thread.&nbsp; The Dispatchers allows us to push the modules from a different thread to the main thread.&nbsp; Now lets go ahead and run the application and copy the ModuleA.DLL into the application&#8217;s Modules folder directory and see what happens.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Before:<\/h4>\n\n\n\n<figure class=\"wp-block-image\"><a href=\"\/community\/cfs-file\/__key\/CommunityServer.Blogs.Components.WeblogFiles\/blagunas.metablogapi\/1184.image_5F00_6.png\"><img decoding=\"async\" src=\"\/community\/cfs-file\/__key\/CommunityServer.Blogs.Components.WeblogFiles\/blagunas.metablogapi\/2161.image_5F00_thumb_5F00_2.png\" alt=\"Dynamically Discover and Load Modules at Runtime - Before\" title=\"Dynamically Discover and Load Modules at Runtime - Before\"\/><\/a><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Run the application and open the application\u2019s \/Modules directory location and the ModuleA\u2019s Bin\/Debug\/ModuleA.dll file location.&nbsp; As you can see, there are no modules loaded for the application and the Prism application is showing an empty shell.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">After:<\/h4>\n\n\n\n<figure class=\"wp-block-image\"><a href=\"\/community\/cfs-file\/__key\/CommunityServer.Blogs.Components.WeblogFiles\/blagunas.metablogapi\/0508.image_5F00_8.png\"><img decoding=\"async\" src=\"\/community\/cfs-file\/__key\/CommunityServer.Blogs.Components.WeblogFiles\/blagunas.metablogapi\/5126.image_5F00_thumb_5F00_3.png\" alt=\"Dynamically Discover and Load Modules at Runtime - After\" title=\"Dynamically Discover and Load Modules at Runtime - After\"\/><\/a><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Now, copy the ModuleA.dll from the module\u2019s Bin\/Debug directory into the Prism application\u2019s \/Modules directory.&nbsp; As soon as the copy operation has completed, the ModuleA.dll assembly is loaded, and the ModuleAView is injected into the Shell;s region.&nbsp; All while the app is running.&nbsp; No need to shutdown and restart the app.<\/p>\n\n\n\n<p>So that was the quick and dirty way.&nbsp; Now let\u2019s look at how we can make a custom ModuleCatalog that will not only load modules from a directory just like the default Prism DirectoryModuleCatalog does, but also allow us to monitor the directory for newly added modules at runtime.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"a-better-way\">A Better Way<\/h2>\n\n\n\n<p>We just saw how, with a few lines of code, we could dynamically discover and load modules.&nbsp; Now, let\u2019s create a custom ModuleCatalog class that will not only register and load existing modules from a directory, but also monitor that same directory for newly added modules at runtime.&nbsp; This class should be a little more stable and do proper app domain and evidence creations and in memory reflection without loading the assemblies into the main app domain until actually needed.&nbsp; We are also going to remove the dependency on the Dispatcher and instead use the SynchronizationContext class.&nbsp; I am not going to walk through all of the code.&nbsp; I am just going to provide the code and you can read through it.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">public class DynamicDirectoryModuleCatalog : ModuleCatalog\n{\n    SynchronizationContext _context;\n\n    \/\/\/ &lt;summary>\n    \/\/\/ Directory containing modules to search for.\n    \/\/\/ &lt;\/summary>\n    public string ModulePath { get; set; }\n\n    public DynamicDirectoryModuleCatalog(string modulePath)\n    {\n        _context = SynchronizationContext.Current;\n\n        ModulePath = modulePath;\n\n        \/\/ we need to watch our folder for newly added modules\n        FileSystemWatcher fileWatcher = new FileSystemWatcher(ModulePath, \"*.dll\");\n        fileWatcher.Created += FileWatcher_Created;\n        fileWatcher.EnableRaisingEvents = true;\n    }\n\n    \/\/\/ &lt;summary>\n    \/\/\/ Rasied when a new file is added to the ModulePath directory\n    \/\/\/ &lt;\/summary>\n    void FileWatcher_Created(object sender, FileSystemEventArgs e)\n    {\n        if (e.ChangeType == WatcherChangeTypes.Created)\n        {\n            LoadModuleCatalog(e.FullPath, true);\n        }\n    }\n\n    \/\/\/ &lt;summary>\n    \/\/\/ Drives the main logic of building the child domain and searching for the assemblies.\n    \/\/\/ &lt;\/summary>\n    protected override void InnerLoad()\n    {\n        LoadModuleCatalog(ModulePath);\n    }\n\n    void LoadModuleCatalog(string path, bool isFile = false)\n    {\n        if (string.IsNullOrEmpty(path))\n            throw new InvalidOperationException(\"Path cannot be null.\");\n\n        if (isFile)\n        {\n            if (!File.Exists(path))\n                throw new InvalidOperationException(string.Format(\"File {0} could not be found.\", path));\n        }\n        else\n        {\n            if (!Directory.Exists(path))\n                throw new InvalidOperationException(string.Format(\"Directory {0} could not be found.\", path));\n        }\n\n        AppDomain childDomain = this.BuildChildDomain(AppDomain.CurrentDomain);\n\n        try\n        {\n            List&lt;string> loadedAssemblies = new List&lt;string>();\n\n            var assemblies = (\n                                 from Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()\n                                 where !(assembly is System.Reflection.Emit.AssemblyBuilder)\n                                    &amp;&amp; assembly.GetType().FullName != \"System.Reflection.Emit.InternalAssemblyBuilder\"\n                                    &amp;&amp; !String.IsNullOrEmpty(assembly.Location)\n                                 select assembly.Location\n                             );\n\n            loadedAssemblies.AddRange(assemblies);\n\n            Type loaderType = typeof(InnerModuleInfoLoader);\n            if (loaderType.Assembly != null)\n            {\n                var loader = (InnerModuleInfoLoader)childDomain.CreateInstanceFrom(loaderType.Assembly.Location, loaderType.FullName).Unwrap();\n                loader.LoadAssemblies(loadedAssemblies);\n\n                \/\/get all the ModuleInfos\n                ModuleInfo[] modules = loader.GetModuleInfos(path, isFile);\n\n                \/\/add modules to catalog\n                this.Items.AddRange(modules);\n\n                \/\/we are dealing with a file from our file watcher, so let's notify that it needs to be loaded\n                if (isFile)\n                {\n                    LoadModules(modules);\n                }\n            }\n        }\n        finally\n        {\n            AppDomain.Unload(childDomain);\n        }\n    }\n\n    \/\/\/ &lt;summary>\n    \/\/\/ Uses the IModuleManager to load the modules into memory\n    \/\/\/ &lt;\/summary>\n    \/\/\/ &lt;param name=\"modules\">&lt;\/param>\n    private void LoadModules(ModuleInfo[] modules)\n    {\n        if (_context == null)\n            return;\n\n        IModuleManager manager = ServiceLocator.Current.GetInstance&lt;IModuleManager>();\n\n        _context.Send(new SendOrPostCallback(delegate(object state)\n        {\n            foreach (var module in modules)\n            {\n                manager.LoadModule(module.ModuleName);\n            }\n        }), null);\n    }\n\n    \/\/\/ &lt;summary>\n    \/\/\/ Creates a new child domain and copies the evidence from a parent domain.\n    \/\/\/ &lt;\/summary>\n    \/\/\/ &lt;param name=\"parentDomain\">The parent domain.&lt;\/param>\n    \/\/\/ &lt;returns>The new child domain.&lt;\/returns>\n    \/\/\/ &lt;remarks>\n    \/\/\/ Grabs the &lt;paramref name=\"parentDomain\"\/> evidence and uses it to construct the new\n    \/\/\/ &lt;see cref=\"AppDomain\"\/> because in a ClickOnce execution environment, creating an\n    \/\/\/ &lt;see cref=\"AppDomain\"\/> will by default pick up the partial trust environment of\n    \/\/\/ the AppLaunch.exe, which was the root executable. The AppLaunch.exe does a\n    \/\/\/ create domain and applies the evidence from the ClickOnce manifests to\n    \/\/\/ create the domain that the application is actually executing in. This will\n    \/\/\/ need to be Full Trust for Composite Application Library applications.\n    \/\/\/ &lt;\/remarks>\n    \/\/\/ &lt;exception cref=\"ArgumentNullException\">An &lt;see cref=\"ArgumentNullException\"\/> is thrown if &lt;paramref name=\"parentDomain\"\/> is null.&lt;\/exception>\n    protected virtual AppDomain BuildChildDomain(AppDomain parentDomain)\n    {\n        if (parentDomain == null) throw new System.ArgumentNullException(\"parentDomain\");\n\n        Evidence evidence = new Evidence(parentDomain.Evidence);\n        AppDomainSetup setup = parentDomain.SetupInformation;\n        return AppDomain.CreateDomain(\"DiscoveryRegion\", evidence, setup);\n    }\n\n    private class InnerModuleInfoLoader : MarshalByRefObject\n    {\n        [System.Diagnostics.CodeAnalysis.SuppressMessage(\"Microsoft.Performance\", \"CA1822:MarkMembersAsStatic\")]\n        internal ModuleInfo[] GetModuleInfos(string path, bool isFile = false)\n        {\n            Assembly moduleReflectionOnlyAssembly =\n                AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().First(\n                    asm => asm.FullName == typeof(IModule).Assembly.FullName);\n\n            Type IModuleType = moduleReflectionOnlyAssembly.GetType(typeof(IModule).FullName);\n\n            FileSystemInfo info = null;\n            if (isFile)\n                info = new FileInfo(path);\n            else\n                info = new DirectoryInfo(path);\n\n            ResolveEventHandler resolveEventHandler = delegate(object sender, ResolveEventArgs args) { return OnReflectionOnlyResolve(args, info); };\n            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveEventHandler;\n            IEnumerable&lt;ModuleInfo> modules = GetNotAllreadyLoadedModuleInfos(info, IModuleType);\n            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= resolveEventHandler;\n\n            return modules.ToArray();\n        }\n\n        private static IEnumerable&lt;ModuleInfo> GetNotAllreadyLoadedModuleInfos(FileSystemInfo info, Type IModuleType)\n        {\n            List&lt;FileInfo> validAssemblies = new List&lt;FileInfo>();\n            Assembly[] alreadyLoadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();\n\n            FileInfo fileInfo = info as FileInfo;\n            if (fileInfo != null)\n            {\n                if (alreadyLoadedAssemblies.FirstOrDefault(assembly => String.Compare(Path.GetFileName(assembly.Location), fileInfo.Name, StringComparison.OrdinalIgnoreCase) == 0) == null)\n                {\n                    var moduleInfos = Assembly.ReflectionOnlyLoadFrom(fileInfo.FullName).GetExportedTypes()\n                    .Where(IModuleType.IsAssignableFrom)\n                    .Where(t => t != IModuleType)\n                    .Where(t => !t.IsAbstract).Select(t => CreateModuleInfo(t));\n\n                    return moduleInfos;\n                }\n            }\n\n            DirectoryInfo directory = info as DirectoryInfo;\n\n            var files = directory.GetFiles(\"*.dll\").Where(file => alreadyLoadedAssemblies.\n                FirstOrDefault(assembly => String.Compare(Path.GetFileName(assembly.Location), file.Name, StringComparison.OrdinalIgnoreCase) == 0) == null);\n\n            foreach (FileInfo file in files)\n            {\n                try\n                {\n                    Assembly.ReflectionOnlyLoadFrom(file.FullName);\n                    validAssemblies.Add(file);\n                }\n                catch (BadImageFormatException)\n                {\n                    \/\/ skip non-.NET Dlls\n                }\n            }\n\n            return validAssemblies.SelectMany(file => Assembly.ReflectionOnlyLoadFrom(file.FullName)\n                                        .GetExportedTypes()\n                                        .Where(IModuleType.IsAssignableFrom)\n                                        .Where(t => t != IModuleType)\n                                        .Where(t => !t.IsAbstract)\n                                        .Select(type => CreateModuleInfo(type)));\n        }\n\n\n        private static Assembly OnReflectionOnlyResolve(ResolveEventArgs args, FileSystemInfo info)\n        {\n            Assembly loadedAssembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault(\n                asm => string.Equals(asm.FullName, args.Name, StringComparison.OrdinalIgnoreCase));\n            if (loadedAssembly != null)\n            {\n                return loadedAssembly;\n            }\n\n            DirectoryInfo directory = info as DirectoryInfo;\n            if (directory != null)\n            {\n                AssemblyName assemblyName = new AssemblyName(args.Name);\n                string dependentAssemblyFilename = Path.Combine(directory.FullName, assemblyName.Name + \".dll\");\n                if (File.Exists(dependentAssemblyFilename))\n                {\n                    return Assembly.ReflectionOnlyLoadFrom(dependentAssemblyFilename);\n                }\n            }\n\n            return Assembly.ReflectionOnlyLoad(args.Name);\n        }\n\n        [System.Diagnostics.CodeAnalysis.SuppressMessage(\"Microsoft.Performance\", \"CA1822:MarkMembersAsStatic\")]\n        internal void LoadAssemblies(IEnumerable&lt;string> assemblies)\n        {\n            foreach (string assemblyPath in assemblies)\n            {\n                try\n                {\n                    Assembly.ReflectionOnlyLoadFrom(assemblyPath);\n                }\n                catch (FileNotFoundException)\n                {\n                    \/\/ Continue loading assemblies even if an assembly can not be loaded in the new AppDomain\n                }\n            }\n        }\n\n        private static ModuleInfo CreateModuleInfo(Type type)\n        {\n            string moduleName = type.Name;\n            List&lt;string> dependsOn = new List&lt;string>();\n            bool onDemand = false;\n            var moduleAttribute = CustomAttributeData.GetCustomAttributes(type).FirstOrDefault(cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleAttribute).FullName);\n\n            if (moduleAttribute != null)\n            {\n                foreach (CustomAttributeNamedArgument argument in moduleAttribute.NamedArguments)\n                {\n                    string argumentName = argument.MemberInfo.Name;\n                    switch (argumentName)\n                    {\n                        case \"ModuleName\":\n                            moduleName = (string)argument.TypedValue.Value;\n                            break;\n\n                        case \"OnDemand\":\n                            onDemand = (bool)argument.TypedValue.Value;\n                            break;\n\n                        case \"StartupLoaded\":\n                            onDemand = !((bool)argument.TypedValue.Value);\n                            break;\n                    }\n                }\n            }\n\n            var moduleDependencyAttributes = CustomAttributeData.GetCustomAttributes(type).Where(cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleDependencyAttribute).FullName);\n            foreach (CustomAttributeData cad in moduleDependencyAttributes)\n            {\n                dependsOn.Add((string)cad.ConstructorArguments[0].Value);\n            }\n\n            ModuleInfo moduleInfo = new ModuleInfo(moduleName, type.AssemblyQualifiedName)\n            {\n                InitializationMode =\n                    onDemand\n                        ? InitializationMode.OnDemand\n                        : InitializationMode.WhenAvailable,\n                Ref = type.Assembly.CodeBase,\n            };\n            moduleInfo.DependsOn.AddRange(dependsOn);\n            return moduleInfo;\n        }\n    }\n}\n\n\/\/\/ &lt;summary>\n\/\/\/ Class that provides extension methods to Collection\n\/\/\/ &lt;\/summary>\npublic static class CollectionExtensions\n{\n    \/\/\/ &lt;summary>\n    \/\/\/ Add a range of items to a collection.\n    \/\/\/ &lt;\/summary>\n    \/\/\/ &lt;typeparam name=\"T\">Type of objects within the collection.&lt;\/typeparam>\n    \/\/\/ &lt;param name=\"collection\">The collection to add items to.&lt;\/param>\n    \/\/\/ &lt;param name=\"items\">The items to add to the collection.&lt;\/param>\n    \/\/\/ &lt;returns>The collection.&lt;\/returns>\n    \/\/\/ &lt;exception cref=\"System.ArgumentNullException\">An &lt;see cref=\"System.ArgumentNullException\"\/> is thrown if &lt;paramref name=\"collection\"\/> or &lt;paramref name=\"items\"\/> is &lt;see langword=\"null\"\/>.&lt;\/exception>\n    public static Collection&lt;T> AddRange&lt;T>(this Collection&lt;T> collection, IEnumerable&lt;T> items)\n    {\n        if (collection == null) throw new System.ArgumentNullException(\"collection\");\n        if (items == null) throw new System.ArgumentNullException(\"items\");\n\n        foreach (var each in items)\n        {\n            collection.Add(each);\n        }\n\n        return collection;\n    }\n}<\/pre>\n\n\n\n<p>This is how you would use the newly created DynamicDirectoryModuleCatalog in our Prism Bootstrapper.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">protected override IModuleCatalog CreateModuleCatalog()\n{\n    DynamicDirectoryModuleCatalog catalog = new DynamicDirectoryModuleCatalog(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, \"Modules\"));\n    return catalog;\n}<\/pre>\n\n\n\n<div id=\"scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:0626ba3a-52d5-40a3-bf4e-7771af4360d9\" class=\"wlWriterEditableSmartContent\" style=\"display: inline; float: none; margin: 0px; padding: 0px;\">\n<div style=\"border: #000080 1px solid; color: #000; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;\">\n<div style=\"background-color: #ffffff; overflow: auto; padding: 2px 5px;\"><span style=\"background: #ffffff; color: #0000ff;\">protected<\/span> <span style=\"background: #ffffff; color: #0000ff;\">override<\/span> <span style=\"background: #ffffff; color: #2b91af;\">IModuleCatalog<\/span><span style=\"background: #ffffff; color: #000000;\"> CreateModuleCatalog()<\/span><br><span style=\"background: #ffffff; color: #000000;\">{<\/span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style=\"background: #ffffff; color: #2b91af;\">DynamicDirectoryModuleCatalog<\/span><span style=\"background: #ffffff; color: #000000;\"> catalog = <\/span><span style=\"background: #ffffff; color: #0000ff;\">new<\/span> <span style=\"background: #ffffff; color: #2b91af;\">DynamicDirectoryModuleCatalog<\/span><span style=\"background: #ffffff; color: #000000;\">(<\/span><span style=\"background: #ffffff; color: #2b91af;\">Path<\/span><span style=\"background: #ffffff; color: #000000;\">.Combine(System.<\/span><span style=\"background: #ffffff; color: #2b91af;\">AppDomain<\/span><span style=\"background: #ffffff; color: #000000;\">.CurrentDomain.BaseDirectory, <\/span><span style=\"background: #ffffff; color: #a31515;\">&#8220;Modules&#8221;<\/span><span style=\"background: #ffffff; color: #000000;\">));<\/span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style=\"background: #ffffff; color: #0000ff;\">return<\/span><span style=\"background: #ffffff; color: #000000;\"> catalog;<\/span><br><span style=\"background: #ffffff; color: #000000;\">}<\/span><\/div>\n<\/div>\n<\/div>\n\n\n\n<p>You might not know this, but you can even have multiple instances of your Prism application monitor the same directory and load the same modules.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><a href=\"\/community\/cfs-file\/__key\/CommunityServer.Blogs.Components.WeblogFiles\/blagunas.metablogapi\/8422.image_5F00_10.png\"><img decoding=\"async\" src=\"\/community\/cfs-file\/__key\/CommunityServer.Blogs.Components.WeblogFiles\/blagunas.metablogapi\/4380.image_5F00_thumb_5F00_4.png\" alt=\"Dynamically Discover and Load Modules at Runtime - Multiple Apps\" title=\"Dynamically Discover and Load Modules at Runtime - Mutliple Apps\"\/><\/a><\/figure>\n\n\n\n<p>Pretty cool huh?&nbsp; You can now dynamically discover and load your Prism modules at runtime.&nbsp;<\/p>\n\n\n\n<p><a href=\"http:\/\/brianlagunas.com\/downloads\/source\/dynamicallydiscoverandloadmodules.zip\" rel=\"noopener\">Download the source<\/a><\/p>\n\n\n\n<p>As always, feel free contact me on my <a href=\"http:\/\/brianlagunas.com\/\" rel=\"noopener\">blog<\/a>, connect with me on Twitter (<a href=\"http:\/\/twitter.com\/brianlagunas\" rel=\"noopener\">@brianlagunas<\/a>), or leave a comment below for any questions or comments you may have.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you develop WPF applications with Prism, then you are probably already aware of the many ways in which you can load a module.\u00a0<\/p>\n","protected":false},"author":43,"featured_media":2370,"comment_status":"publish","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[14],"tags":[],"class_list":["post-437","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-wpf"],"_links":{"self":[{"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/posts\/437","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/users\/43"}],"replies":[{"embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/comments?post=437"}],"version-history":[{"count":4,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/posts\/437\/revisions"}],"predecessor-version":[{"id":2018,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/posts\/437\/revisions\/2018"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/media\/2370"}],"wp:attachment":[{"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/media?parent=437"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/categories?post=437"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/tags?post=437"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}