//Contributor: Umbraco (http://www.umbraco.com). Thanks a lot!
//SEE THIS POST for full details of what this does - http://shazwazza.com/post/Developing-a-plugin-framework-in-ASPNET-with-medium-trust.aspx
[assembly: PreApplicationStartMethod(typeof(PluginManager), "Initialize")]
namespace Nop.Core.Plugins
{
/// <summary>
/// Sets the application up for the plugin referencing
/// </summary>
public class PluginManager
{
#region Const
private const string InstalledPluginsFilePath = "~/App_Data/InstalledPlugins.txt";
private const string PluginsPath = "~/Plugins";
private const string ShadowCopyPath = "~/Plugins/bin";
#endregion
#region Fields
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
private static DirectoryInfo _shadowCopyFolder;
private static bool _clearShadowDirectoryOnStartup;
#endregion
#region Methods
/// <summary>
/// Returns a collection of all referenced plugin assemblies that have been shadow copied
/// </summary>
public static IEnumerable<PluginDescriptor> ReferencedPlugins { get; set; }
/// <summary>
/// Returns a collection of all plugin which are not compatible with the current version
/// </summary>
public static IEnumerable<string> IncompatiblePlugins { get; set; }
/// <summary>
/// Initialize
/// </summary>
public static void Initialize()
{
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(HostingEnvironment.MapPath(PluginsPath));
_shadowCopyFolder = new DirectoryInfo(HostingEnvironment.MapPath(ShadowCopyPath));
var referencedPlugins = new List<PluginDescriptor>();
var incompatiblePlugins = new List<string>();
_clearShadowDirectoryOnStartup = !String.IsNullOrEmpty(ConfigurationManager.AppSettings["ClearPluginsShadowDirectoryOnStartup"]) &&
Convert.ToBoolean(ConfigurationManager.AppSettings["ClearPluginsShadowDirectoryOnStartup"]);
try
{
var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(GetInstalledPluginsFilePath());
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 (_clearShadowDirectoryOnStartup)
{
//clear out shadow copied plugins
foreach (var f in binFiles)
{
Debug.WriteLine("Deleting " + f.Name);
try
{
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(NopVersion.CurrentVersion, StringComparer.InvariantCultureIgnoreCase))
{
incompatiblePlugins.Add(pluginDescriptor.SystemName);
continue;
}
//some validation
if (String.IsNullOrWhiteSpace(pluginDescriptor.SystemName))
throw new Exception(string.Format("A plugin '{0}' has no system name. Try assigning the plugin a unique name and recompiling.", descriptionFile.FullName));
if (referencedPlugins.Contains(pluginDescriptor))
throw new Exception(string.Format("A plugin with '{0}' system name is already defined", pluginDescriptor.SystemName));
//set 'Installed' property
pluginDescriptor.Installed = installedPluginSystemNames
.Where(x => x.Equals(pluginDescriptor.SystemName, StringComparison.InvariantCultureIgnoreCase))
.FirstOrDefault() != null;
try
{
//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.Where(x => x.Name.Equals(pluginDescriptor.PluginFileName, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
pluginDescriptor.OriginalAssemblyFile = mainPluginFile;
//shadow copy main plugin file
pluginDescriptor.ReferencedAssembly = PerformFileDeploy(mainPluginFile);
//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);
//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)
{
var msg = string.Empty;
foreach (var e in ex.LoaderExceptions)
msg += e.Message + Environment.NewLine;
var fail = new Exception(msg, ex);
Debug.WriteLine(fail.Message, fail);
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);
Debug.WriteLine(fail.Message, fail);
throw fail;
}
ReferencedPlugins = referencedPlugins;
IncompatiblePlugins = incompatiblePlugins;
}
}