The problem: In my WP app, I don't want to deploy my entire application through clickonce. Instead I would like to install he shell and download all other modules from a nuget repository and then dynamically discover and initialize modules.
Prerequisite:
Nuget Package Explorer
Local NuGet Repository e.g. Artifactory
1. Create your nuget package and put all files as a content.
2. Upload to the repository.
3. Get the URL of the package.
4. Create a file called NuGetDownloadedModuleCatalog.xml and put inside your Shell application. The format has to be:-
<?xml version="1.0" encoding="utf-8" ?><Modules><Module name="Module1" url="http://.....1.0.0.nupkg"/><Module name="Module2" url="http://.....1.0.0.nupkg"/></Modules>
5. Copy paste given code in the same app.
public class NuGetDownloadedModuleCatalog : ModuleCatalog{private const string NugetCatalogFileName = "NugetModules.catalog";private readonly Dictionary<string, string> _packagedModules = new Dictionary<string, string>();readonly SynchronizationContext _context;/// <summary>/// Directory containing modules to search for./// </summary>public string NugetModulesDirectory { get; set; }public NuGetDownloadedModuleCatalog(string nugetModulesDirectory){_context = SynchronizationContext.Current;NugetModulesDirectory = nugetModulesDirectory;GetPackagedModules();StartPackageDownload();// we need to watch our folder for newly added modulesvar fileWatcher = new FileSystemWatcher(NugetModulesDirectory);fileWatcher.Created += FileWatcher_Created;fileWatcher.EnableRaisingEvents = true;}private void GetPackagedModules(){XDocument doc = XDocument.Load("NuGetDownloadedModuleCatalog.xml");if (doc.Root != null){var moduleNodes = doc.Root.Elements().Where(e => e.HasAttributes);moduleNodes.ForEach(m =>{var name = m.Attributes().FirstOrDefault(a => a.Name == "name");var url = m.Attributes().FirstOrDefault(a => a.Name == "url");if (name != null && url != null)_packagedModules[name.Value] = url.Value;});}}private void StartPackageDownload(){if (!Directory.Exists(NugetModulesDirectory))Directory.CreateDirectory(NugetModulesDirectory);Task.Factory.StartNew(() =>{using (var client = new WebClient()){try{foreach (var packagedModule in _packagedModules){var moduleDestinationDir = Path.Combine(NugetModulesDirectory, packagedModule.Key);if(ModuleDownloaded(moduleDestinationDir)) continue;var zipFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".zip");var unarchivedPath = Path.Combine(Path.GetTempPath(), packagedModule.Key);client.DownloadFile(packagedModule.Value, zipFile);if (Directory.Exists(unarchivedPath))Directory.Delete(unarchivedPath, true);Directory.CreateDirectory(unarchivedPath);ArchiveManager.UnArchive(zipFile, unarchivedPath);var contentPath = Path.Combine(unarchivedPath, "content");CopyDir(contentPath, moduleDestinationDir);}}// ReSharper disable EmptyGeneralCatchClausecatch (Exception) { }// ReSharper restore EmptyGeneralCatchClause}});}public static void CopyDir(string source, string target){if (!Directory.Exists(target)) Directory.CreateDirectory(target);string[] sysEntries = Directory.GetFileSystemEntries(source);foreach (string sysEntry in sysEntries){string fileName = Path.GetFileName(sysEntry);if(fileName==null) continue;string targetPath = Path.Combine(target, fileName);if (Directory.Exists(sysEntry))CopyDir(sysEntry, targetPath);else{File.Copy(sysEntry, targetPath, true);}}UpdateCatalog(target);}private static readonly object SynchObject = new object();private static void UpdateCatalog(string target){lock (SynchObject){if (!File.Exists(NugetCatalogFileName))File.WriteAllText(NugetCatalogFileName, string.Empty);var text = File.ReadAllText(NugetCatalogFileName);text += Environment.NewLine;text += target;File.WriteAllText(NugetCatalogFileName, text);}}private bool ModuleDownloaded(string target){lock (SynchObject){if (!File.Exists(NugetCatalogFileName))File.WriteAllText(NugetCatalogFileName,string.Empty);var text = File.ReadAllText(NugetCatalogFileName);return text.Contains(target);}}/// <summary>/// Raised when a new file is added to the ModulePath directory/// </summary>void FileWatcher_Created(object sender, FileSystemEventArgs e){if (e.ChangeType == WatcherChangeTypes.Created){LoadModuleCatalog(e.FullPath, true);}}/// <summary>/// Drives the main logic of building the child domain and searching for the assemblies./// </summary>protected override void InnerLoad(){LoadModuleCatalog(NugetModulesDirectory);}void LoadModuleCatalog(string path, bool isFile = false){if (string.IsNullOrEmpty(path))throw new InvalidOperationException("Path cannot be null.");if (isFile){if (!File.Exists(path))throw new InvalidOperationException(string.Format("File {0} could not be found.", path));}else{if (!Directory.Exists(path))throw new InvalidOperationException(string.Format("Directory {0} could not be found.", path));}AppDomain childDomain = BuildChildDomain(AppDomain.CurrentDomain);try{var 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);var loader =(InnerModuleInfoLoader)childDomain.CreateInstanceFrom(loaderType.Assembly.Location, loaderType.FullName).Unwrap();loader.LoadAssemblies(loadedAssemblies);//get all the ModuleInfosModuleInfo[] modules = loader.GetModuleInfos(path, isFile);//add modules to catalogItems.AddRange(modules);//we are dealing with a file from our file watcher, so let's notify that it needs to be loadedif (isFile){LoadModules(modules);}}finally{AppDomain.Unload(childDomain);}}/// <summary>/// Uses the IModuleManager to load the modules into memory/// </summary>/// <param name="modules"></param>private void LoadModules(IEnumerable<ModuleInfo> modules){if (_context == null)return;var moduleManager = ServiceLocator.Current.GetInstance<IModuleManager>();var modulecatalog = ServiceLocator.Current.GetInstance<IModuleCatalog>();_context.Send(delegate{foreach (var module in modules){if (moduleManager != null && modulecatalog != null && modulecatalog.Modules.Any(m => m != null && m.ModuleName == module.ModuleName))moduleManager.LoadModule(module.ModuleName);}}, null);}/// <summary>/// Creates a new child domain and copies the evidence from a parent domain./// </summary>/// <param name="parentDomain">The parent domain.</param>/// <returns>The new child domain.</returns>/// <remarks>/// Grabs the <paramref name="parentDomain"/> evidence and uses it to construct the new/// <see cref="AppDomain"/> because in a ClickOnce execution environment, creating an/// <see cref="AppDomain"/> will by default pick up the partial trust environment of/// the AppLaunch.exe, which was the root executable. The AppLaunch.exe does a/// create domain and applies the evidence from the ClickOnce manifests to/// create the domain that the application is actually executing in. This will/// need to be Full Trust for Composite Application Library applications./// </remarks>/// <exception cref="ArgumentNullException">An <see cref="ArgumentNullException"/> is thrown if <paramref name="parentDomain"/> is null.</exception>protected virtual AppDomain BuildChildDomain(AppDomain parentDomain){if (parentDomain == null) throw new ArgumentNullException("parentDomain");var evidence = new Evidence(parentDomain.Evidence);AppDomainSetup setup = parentDomain.SetupInformation;return AppDomain.CreateDomain("DiscoveryRegion", evidence, setup);}private class InnerModuleInfoLoader : MarshalByRefObject{[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]internal ModuleInfo[] GetModuleInfos(string path, bool isFile = false){Assembly moduleReflectionOnlyAssembly =AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().First(asm => asm.FullName == typeof(IModule).Assembly.FullName);var moduleType = moduleReflectionOnlyAssembly.GetType(typeof(IModule).FullName);FileSystemInfo info;if (isFile)info = new FileInfo(path);elseinfo = new DirectoryInfo(path);ResolveEventHandler resolveEventHandler = delegate(object sender, ResolveEventArgs args) { return OnReflectionOnlyResolve(args, info); };AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveEventHandler;IEnumerable<ModuleInfo> modules = GetNotAllreadyLoadedModuleInfos(info, moduleType);AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= resolveEventHandler;return modules.ToArray();}private static IEnumerable<ModuleInfo> GetNotAllreadyLoadedModuleInfos(FileSystemInfo info, Type moduleType){var validAssemblies = new List<FileInfo>();Assembly[] alreadyLoadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();var fileInfo = info as FileInfo;if (fileInfo != null){if (alreadyLoadedAssemblies.FirstOrDefault(assembly => String.Compare(Path.GetFileName(assembly.Location), fileInfo.Name, StringComparison.OrdinalIgnoreCase) == 0) == null){var moduleInfos = Assembly.ReflectionOnlyLoadFrom(fileInfo.FullName).GetExportedTypes().Where(moduleType.IsAssignableFrom).Where(t => t != moduleType).Where(t => !t.IsAbstract).Select(CreateModuleInfo);return moduleInfos;}}var directory = info as DirectoryInfo;if (directory == null)return Enumerable.Empty<ModuleInfo>();var files = directory.GetFiles("*.dll").Where(file => alreadyLoadedAssemblies.FirstOrDefault(assembly => String.Compare(Path.GetFileName(assembly.Location), file.Name, StringComparison.OrdinalIgnoreCase) == 0) == null);foreach (FileInfo file in files){try{Assembly.ReflectionOnlyLoadFrom(file.FullName);validAssemblies.Add(file);}catch (BadImageFormatException){// skip non-.NET Dlls}}return validAssemblies.SelectMany(file => Assembly.ReflectionOnlyLoadFrom(file.FullName).GetExportedTypes().Where(moduleType.IsAssignableFrom).Where(t => t != moduleType).Where(t => !t.IsAbstract).Select(CreateModuleInfo));}private static Assembly OnReflectionOnlyResolve(ResolveEventArgs args, FileSystemInfo info){Assembly loadedAssembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault(asm => string.Equals(asm.FullName, args.Name, StringComparison.OrdinalIgnoreCase));if (loadedAssembly != null){return loadedAssembly;}var directory = info as DirectoryInfo;if (directory != null){var 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;bool onDemand = false;var moduleAttribute = CustomAttributeData.GetCustomAttributes(type).Where(cad=>cad.Constructor!=null && cad.Constructor.DeclaringType!=null).FirstOrDefault(cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleAttribute).FullName);if (moduleAttribute != null && moduleAttribute.NamedArguments!=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!=null && cad.Constructor.DeclaringType!=null&& cad.Constructor.DeclaringType.FullName == typeof(ModuleDependencyAttribute).FullName);var dependsOn = moduleDependencyAttributes.Select(cad => (string) cad.ConstructorArguments[0].Value).ToList();var moduleInfo = new ModuleInfo(moduleName, type.AssemblyQualifiedName){InitializationMode =onDemand? InitializationMode.OnDemand: InitializationMode.WhenAvailable,Ref = type.Assembly.CodeBase,};moduleInfo.DependsOn.AddRange(dependsOn);return moduleInfo;}}}/// <summary>/// Class that provides extension methods to Collection/// </summary>public static class CollectionExtensions{/// <summary>/// Add a range of items to a collection./// </summary>/// <typeparam name="T">Type of objects within the collection.</typeparam>/// <param name="collection">The collection to add items to.</param>/// <param name="items">The items to add to the collection.</param>/// <returns>The collection.</returns>/// <exception cref="System.ArgumentNullException">An <see cref="System.ArgumentNullException"/> is thrown if <paramref name="collection"/> or <paramref name="items"/> is <see langword="null"/>.</exception>public static Collection<T> AddRange<T>(this Collection<T> collection, IEnumerable<T> items){if (collection == null) throw new ArgumentNullException("collection");if (items == null) throw new ArgumentNullException("items");foreach (var each in items){collection.Add(each);}return collection;}}
6. In your bootstrapper, add following code:-
protected override IModuleCatalog CreateModuleCatalog()
{
var catalog = new NuGetDownloadedModuleCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "NuGetModules"));
return catalog;
}
Done!
Enjoy.
No comments:
Post a Comment