How to load CAL modules from a website (or other locations)
Loading CAL modules from the web (or any other location)
For the last 2 weeks, I’ve done some work for the CAL team. (Composite Application Library, previously known as WPF Composite or Prism). One of the questions that came up was, how can we load assemblies from different locations like from the web or from the database.
Quick into into CAL’s module loading
The following diagram shows how CAL loads it’s modules
1. The bootstrapper creates the ModuleLoader. This class is responsible for loading the assemblies and loading and initializing the modules within those assemblies.
2. The bootstrapper creates an instance of a IModuleEnumerator. This class is responsible for returning a list of modules to load. CAL provides 2 implementations, the ConfigurationModuleEnumerator, which retrieves the list from the configuration file, or the DirectoryLookupModuleEnumerator, which is passed a foldername and then returns all the modules within that folder.
3. The ModuleEnumerator creates a list of modules to load and passes that list on to the ModuleLoader. The moduleLoader then proceeds to load the assemblies and then loads and initializes the modules in those assemblies.
How to load remote modules
So how can you load remote modules, for example from a database or a website. The default implementation of the ModuleLoader only loads files that are present on the filesystem.
The easiest way to do this is to create your own IModuleEnumerator implementation that retrieves the files from a location and then cashes them on the filesystem. Then it returns the ModuleInfo files that describe where the assemblies can be found.
Follow the following steps:
1. Add a custom IModuleEnumerator (I’ve called it StaticWebModuleEnumerator)
/// <summary>
/// Retrieve specified modules from the web.
/// </summary>
public class StaticWebModuleEnumerator : IModuleEnumerator
{
private List<ModuleInfo> _modules = new List<ModuleInfo>();
/// <summary>
/// Get all modules.
/// </summary>
/// <returns></returns>
public ModuleInfo[] GetModules()
{
return _modules.ToArray();
}
/// <summary>
/// Returns all modules that should be loaded at starttime.
/// </summary>
/// <returns>All modules to load at starttime. </returns>
public ModuleInfo[] GetStartupLoadedModules()
{
return _modules.FindAll(m => m.StartupLoaded).ToArray();
}
/// <summary>
/// Gets a module.
/// </summary>
/// <param name="moduleName">The name of the module.</param>
/// <returns>The module itself.</returns>
public ModuleInfo GetModule(string moduleName)
{
return _modules.Find(m => m.ModuleName == moduleName);
}
/// <summary>
/// Adds an url to download an assembly from
/// </summary>
/// <param name="url">The Url to the assembly file.</param>
/// <param name="moduleType">The type of the module to load from the assembly</param>
/// <param name="moduleName">The name of the module.</param>
/// <param name="dependsOn">What other modules does this module depend on</param>
/// <returns>itself for quickly adding multiple urls</returns>
public StaticWebModuleEnumerator Add(Uri url, string moduleType, string moduleName, params string[] dependsOn)
{
// Write the file to the filesystem, in the bin folder, along with all other assemblies.
string assemblyFile = moduleName + ".dll";
// Create the module info
var moduleInfo = new ModuleInfo(assemblyFile, moduleType, moduleName, dependsOn);
_modules.Add(moduleInfo);
// See if the file allready exists. This is a crude form of cashing. You might want to think of a more
// advanced method of caching that can handle updates to the file or at least some form of expiration
if (!File.Exists(assemblyFile))
{
// Write the file to the filesystem.
SaveAssemblyToFileSystem(assemblyFile, DownloadAssembly(url));
}
// Return this so you can easily add multiple modules: .Add().Add().Add()
return this;
}
/// <summary>
/// Download the assembly as bytes
/// </summary>
/// <param name="url">The url to download the assemblies from. </param>
/// <returns></returns>
private byte[] DownloadAssembly(Uri url)
{
// Open a webrequest to the url to download the file.
var webRequest = WebRequest.Create(url);
var response = webRequest.GetResponse();
var responseStream = response.GetResponseStream();
// Create a buffer large enough to read the content and fill it
// Sometimes a webserver doesn't return the contentlength. Then you have no choice but to
// read the data in chunks.
var buffer = new Byte[response.ContentLength];
responseStream.Read(buffer, 0, (int)response.ContentLength);
return buffer;
}
/// <summary>
/// Save the file to the filesystem.
/// </summary>
/// <param name="fileName">The name of the file.</param>
/// <param name="assemblyBytes">the bytes to write away. </param>
private void SaveAssemblyToFileSystem(string fileName, byte[] assemblyBytes)
{
using (var fs = new FileStream(fileName, FileMode.Create))
{
fs.Write(assemblyBytes, 0, assemblyBytes.Length);
fs.Flush();
}
}
}
2. Instantiate the StaticWebModuleEnumerator in the Bootstrapper
You can also opt to create the StaticWebModuleEnumerator somewhere else, for instance when you want to download assemblies at a later time. But then you must call the ModuleLoader yourself.
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
var shell = Container.Resolve<Shell>();
shell.Show();
return shell;
}
/// <summary>
/// Override the GetModuleEnumerator to return modules that should be loaded from the web.
/// </summary>
/// <returns></returns>
protected override IModuleEnumerator GetModuleEnumerator()
{
// Instantiate the StaticWebModuleEnumerator()
return new StaticWebModuleEnumerator()
.Add(new Uri("https://somesite/AssemblyToDownload.dll"),
"MyModule.MyModuleModule",
"MyModule");
}
}
How to load files without saving them on the filesystem
You might not want to save the files to the filesystem, but load them directly from memory. The way to do this is to create a custom ModuleLoader, or just derive from the existing ModuleLoader and override the LoadAssembly method.
Note, if you create your own ModuleLoader, you have to register it with the Unity Container in the CreateShell() method in the bootstrapper.
Comments
- Anonymous
July 03, 2008
PingBack from http://www.ademiller.com/blogs/tech/2008/07/new-developer-on-the-pp-team/