/// <summary> /// Initialize /// </summary> /// <param name="applicationPartManager">Application part manager</param> /// <param name="config">Config</param> public static void Initialize(ApplicationPartManager applicationPartManager, Kj1012Config config) { if (applicationPartManager == null) { throw new ArgumentNullException(nameof(applicationPartManager)); } if (config == null) { throw new ArgumentNullException(nameof(config)); } using (new WriteLockDisposable(Locker)) { // TODO: Add verbose exception handling / raising here since this is happening on app startup and could // prevent app from starting altogether var pluginFolder = new DirectoryInfo(CommonHelper.MapPath(PluginsPath)); _shadowCopyFolder = new DirectoryInfo(CommonHelper.MapPath(ShadowCopyPath)); var referencedPlugins = new List <PluginDescriptor>(); var incompatiblePlugins = new List <string>(); try { var installedPluginSystemNames = GetInstalledPluginNames(CommonHelper.MapPath(InstalledPluginsFilePath)); Debug.WriteLine("Creating shadow copy folder and querying for DLLs"); //ensure folders are created Directory.CreateDirectory(pluginFolder.FullName); Directory.CreateDirectory(_shadowCopyFolder.FullName); //get list of all files in bin var binFiles = _shadowCopyFolder.GetFiles("*", SearchOption.AllDirectories); if (config.ClearPluginShadowDirectoryOnStartup) { //clear out shadow copied plugins foreach (var f in binFiles) { if (f.Name.Equals("placeholder.txt", StringComparison.InvariantCultureIgnoreCase)) { continue; } Debug.WriteLine("Deleting " + f.Name); try { //ignore index.htm var fileName = Path.GetFileName(f.FullName); if (fileName.Equals("index.htm", StringComparison.InvariantCultureIgnoreCase)) { continue; } File.Delete(f.FullName); } catch (Exception exc) { Debug.WriteLine("Error deleting file " + f.Name + ". Exception: " + exc); } } } //load description files foreach (var dfd in GetDescriptionFilesAndDescriptors(pluginFolder)) { var descriptionFile = dfd.Key; var pluginDescriptor = dfd.Value; //ensure that version of plugin is valid if (!pluginDescriptor.SupportedVersions.Contains(Version.CurrentVersion, StringComparer.InvariantCultureIgnoreCase)) { incompatiblePlugins.Add(pluginDescriptor.SystemName); continue; } //some validation if (string.IsNullOrWhiteSpace(pluginDescriptor.SystemName)) { throw new Exception($"A plugin '{descriptionFile.FullName}' has no system name. Try assigning the plugin a unique name and recompiling."); } if (referencedPlugins.Contains(pluginDescriptor)) { throw new Exception($"A plugin with '{pluginDescriptor.SystemName}' system name is already defined"); } //set 'Installed' property pluginDescriptor.Installed = installedPluginSystemNames .FirstOrDefault(x => x.Equals(pluginDescriptor.SystemName, StringComparison.InvariantCultureIgnoreCase)) != null; try { if (descriptionFile.Directory == null) { throw new Exception($"Directory cannot be resolved for '{descriptionFile.Name}' description file"); } //get list of all DLLs in plugins (not in bin!) var pluginFiles = descriptionFile.Directory.GetFiles("*.dll", SearchOption.AllDirectories) //just make sure we're not registering shadow copied plugins .Where(x => !binFiles.Select(q => q.FullName).Contains(x.FullName)) .Where(x => IsPackagePluginFolder(x.Directory)) .ToList(); //other plugin description info var mainPluginFile = pluginFiles .FirstOrDefault(x => x.Name.Equals(pluginDescriptor.AssemblyFileName, StringComparison.InvariantCultureIgnoreCase)); //plugin have wrong directory if (mainPluginFile == null) { incompatiblePlugins.Add(pluginDescriptor.SystemName); continue; } pluginDescriptor.OriginalAssemblyFile = mainPluginFile; //shadow copy main plugin file pluginDescriptor.ReferencedAssembly = PerformFileDeploy(mainPluginFile, applicationPartManager, config); //load all other referenced assemblies now foreach (var plugin in pluginFiles .Where(x => !x.Name.Equals(mainPluginFile.Name, StringComparison.InvariantCultureIgnoreCase)) .Where(x => !IsAlreadyLoaded(x))) { PerformFileDeploy(plugin, applicationPartManager, config); } //init plugin type (only one plugin per assembly is allowed) foreach (var t in pluginDescriptor.ReferencedAssembly.GetTypes()) { if (typeof(IPlugin).IsAssignableFrom(t)) { if (!t.IsInterface) { if (t.IsClass && !t.IsAbstract) { pluginDescriptor.PluginType = t; break; } } } } referencedPlugins.Add(pluginDescriptor); } catch (ReflectionTypeLoadException ex) { //add a plugin name. this way we can easily identify a problematic plugin var msg = $"Plugin '{pluginDescriptor.FriendlyName}'. "; foreach (var e in ex.LoaderExceptions) { msg += e.Message + Environment.NewLine; } var fail = new Exception(msg, ex); throw fail; } catch (Exception ex) { //add a plugin name. this way we can easily identify a problematic plugin var msg = $"Plugin '{pluginDescriptor.FriendlyName}'. {ex.Message}"; var fail = new Exception(msg, ex); throw fail; } } } catch (Exception ex) { var msg = string.Empty; for (var e = ex; e != null; e = e.InnerException) { msg += e.Message + Environment.NewLine; } var fail = new Exception(msg, ex); throw fail; } ReferencedPlugins = referencedPlugins; IncompatiblePlugins = incompatiblePlugins; } }
/// <summary> /// Perform file deploy /// </summary> /// <param name="plug">Plugin file info</param> /// <param name="applicationPartManager">Application part manager</param> /// <param name="config">Config</param> /// <returns>Assembly</returns> private static Assembly PerformFileDeploy(FileInfo plug, ApplicationPartManager applicationPartManager, Kj1012Config config) { if (plug.Directory == null || plug.Directory.Parent == null) { throw new InvalidOperationException("The plugin directory for the " + plug.Name + " file exists in a folder outside of the allowed nopCommerce folder hierarchy"); } //but in order to avoid possible issues we still copy libraries into ~/Plugins/bin/ directory var shadowCopyPlugFolder = Directory.CreateDirectory(_shadowCopyFolder.FullName); var shadowCopiedPlug = ShadowCopyFile(plug, shadowCopyPlugFolder); //we can now register the plugin definition var assemblyName = AssemblyName.GetAssemblyName(shadowCopiedPlug.FullName); Assembly shadowCopiedAssembly; try { shadowCopiedAssembly = Assembly.Load(assemblyName); } catch (FileLoadException) { if (config.UseUnsafeLoadAssembly) { //if an application has been copied from the web, it is flagged by Windows as being a web application, //even if it resides on the local computer.You can change that designation by changing the file properties, //or you can use the<loadFromRemoteSources> element to grant the assembly full trust.As an alternative, //you can use the UnsafeLoadFrom method to load a local assembly that the operating system has flagged as //having been loaded from the web. //see http://go.microsoft.com/fwlink/?LinkId=155569 for more information. shadowCopiedAssembly = Assembly.UnsafeLoadFrom(shadowCopiedPlug.FullName); } else { throw; } } Debug.WriteLine("Adding to ApplicationParts: '{0}'", shadowCopiedAssembly.FullName); applicationPartManager.ApplicationParts.Add(new AssemblyPart(shadowCopiedAssembly)); return(shadowCopiedAssembly); }