work on extension loading

* Add a "ExtensionLoaderCoordinator" class
* Rework the way loaders are notified of extensions being loaded,
  (de)activated, etc.

--HG--
branch : dev
This commit is contained in:
Renaud Paquay
2010-06-14 09:01:31 -07:00
parent ad54ffdbb2
commit cd3a537396
22 changed files with 605 additions and 344 deletions

View File

@@ -43,23 +43,19 @@ namespace Orchard.Tests.Environment.Extensions {
}
}
public class StubLoaders : IExtensionLoader {
public class StubLoaders : ExtensionLoaderBase {
#region Implementation of IExtensionLoader
public int Order {
public override int Order {
get { return 1; }
}
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this };
}
public ExtensionEntry Load(ExtensionProbeEntry entry) {
return new ExtensionEntry { Descriptor = entry.Descriptor, ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } };
}
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
throw new NotImplementedException();
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
return new ExtensionEntry { Descriptor = descriptor, ExportedTypes = new[] { typeof(Alpha), typeof(Beta), typeof(Phi) } };
}
#endregion

View File

@@ -143,10 +143,6 @@
<Project>{17F86780-9A1F-4AA1-86F1-875EEC2730C7}</Project>
<Name>Orchard.Modules</Name>
</ProjectReference>
<ProjectReference Include="Modules\Orchard.MultiTenancy\Orchard.MultiTenancy.csproj">
<Project>{72457126-E118-4171-A08F-9A709EE4B7FC}</Project>
<Name>Orchard.MultiTenancy</Name>
</ProjectReference>
<ProjectReference Include="Modules\Orchard.Pages\Orchard.Pages.csproj">
<Project>{4A9C04A6-0986-4A92-A610-5F59FF273FB9}</Project>
<Name>Orchard.Pages</Name>

View File

@@ -23,6 +23,7 @@ namespace Orchard.Environment {
private readonly IRunningShellTable _runningShellTable;
private readonly IProcessingEngine _processingEngine;
private readonly IExtensionManager _extensionManager;
private readonly IExtensionLoaderCoordinator _extensionLoaderCoordinator;
private readonly ICacheManager _cacheManager;
private IEnumerable<ShellContext> _current;
@@ -33,6 +34,7 @@ namespace Orchard.Environment {
IRunningShellTable runningShellTable,
IProcessingEngine processingEngine,
IExtensionManager extensionManager,
IExtensionLoaderCoordinator extensionLoaderCoordinator,
ICacheManager cacheManager,
ControllerBuilder controllerBuilder) {
_shellSettingsManager = shellSettingsManager;
@@ -40,6 +42,7 @@ namespace Orchard.Environment {
_runningShellTable = runningShellTable;
_processingEngine = processingEngine;
_extensionManager = extensionManager;
_extensionLoaderCoordinator = extensionLoaderCoordinator;
_cacheManager = cacheManager;
_controllerBuilder = controllerBuilder;
Logger = NullLogger.Instance;
@@ -78,10 +81,17 @@ namespace Orchard.Environment {
}
IEnumerable<ShellContext> BuildCurrent() {
if (_current == null) {
lock (this) {
return _current ?? (_current = CreateAndActivate().ToArray());
if (_current == null) {
SetupExtensions();
MonitorExtensions();
_current = CreateAndActivate().ToArray();
}
}
}
return _current;
}
IEnumerable<ShellContext> CreateAndActivate() {
var allSettings = _shellSettingsManager.LoadSettings();
@@ -119,13 +129,21 @@ namespace Orchard.Environment {
return _shellContextFactory.CreateShellContext(settings);
}
protected virtual void BeginRequest() {
private void SetupExtensions() {
_extensionLoaderCoordinator.SetupExtensions();
}
private void MonitorExtensions() {
_cacheManager.Get("OrchardHost_Extensions",
ctx => {
_extensionManager.Monitor(ctx.Monitor);
_extensionLoaderCoordinator.MonitorExtensions(ctx.Monitor);
_current = null;
return "";
});
}
protected virtual void BeginRequest() {
MonitorExtensions();
BuildCurrent();
}

View File

@@ -0,0 +1,200 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Orchard.Caching;
using Orchard.Environment.Extensions.Loaders;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Services;
namespace Orchard.Environment.Extensions {
public class ExtensionLoaderCoordinator : IExtensionLoaderCoordinator {
private readonly IDependenciesFolder _dependenciesFolder;
private readonly IExtensionManager _extensionManager;
private readonly IEnumerable<IExtensionLoader> _loaders;
private readonly IVirtualPathProvider _virtualPathProvider;
private readonly IClock _clock;
public ExtensionLoaderCoordinator(
IDependenciesFolder dependenciesFolder,
IExtensionManager extensionManager,
IEnumerable<IExtensionLoader> loaders,
IVirtualPathProvider virtualPathProvider,
IClock clock) {
_dependenciesFolder = dependenciesFolder;
_extensionManager = extensionManager;
_loaders = loaders.OrderBy(l => l.Order);
_virtualPathProvider = virtualPathProvider;
_clock = clock;
T = NullLocalizer.Instance;
Logger = NullLogger.Instance;
}
public Localizer T { get; set; }
public ILogger Logger { get; set; }
public void SetupExtensions() {
Logger.Information("Loading extensions.");
var extensions = _extensionManager.AvailableExtensions().Where(d => d.ExtensionType == "Module").ToList();
var existingDependencies = _dependenciesFolder.LoadDescriptors().ToList();
var sameExtensions = extensions.Where(e => existingDependencies.Any(e2 => e2.Name == e.Name)).ToList();
var deletedDependencies = existingDependencies.Where(e => !extensions.Any(e2 => e2.Name == e.Name)).ToList();
var newExtensions = extensions.Except(sameExtensions).ToList();
var ctx = new ExtensionLoadingContext { DependenciesFolder = _dependenciesFolder };
// Notify all loaders about extensions removed from the web site
foreach (var dependency in deletedDependencies) {
Logger.Information("Extension {0} has been removed from site", dependency.Name);
foreach (var loader in _loaders) {
if (dependency.LoaderName == loader.Name) {
loader.ExtensionRemoved(ctx, dependency);
}
}
}
// For all existing extensions in the site, ask each loader if they can
// load that extension.
var newDependencies = new List<DependencyDescriptor>();
foreach (var extension in extensions) {
bool isNewExtension = newExtensions.Any(e => e.Name == extension.Name);
ProcessExtension(ctx, extension, isNewExtension, existingDependencies, newDependencies);
}
// Execute all the work need by "ctx"
ProcessContextCommands(ctx);
// And finally save the new entries in the dependencies folder
_dependenciesFolder.StoreDescriptors(newDependencies);
Logger.Information("Done loading extensions.");
}
private void ProcessExtension(
ExtensionLoadingContext ctx,
ExtensionDescriptor extension,
bool isNewExtension,
IEnumerable<DependencyDescriptor> existingDependencies,
List<DependencyDescriptor> newDependencies) {
var extensionProbes = _loaders
.Select(loader => loader.Probe(extension))
.Where(probe => probe != null)
.OrderByDescending(probe => probe.LastModificationTimeUtc)
.ThenBy(probe => probe.Loader.Order)
.ToList();
if (Logger.IsEnabled(LogLevel.Debug)) {
Logger.Debug("Loaders for extension \"{0}\": ", extension.Name);
foreach (var probe in extensionProbes) {
Logger.Debug(" Loader: {0}", probe.Loader.Name);
Logger.Debug(" VirtualPath: {0}", probe.VirtualPath);
Logger.Debug(" DateTimeUtc: {0}", probe.LastModificationTimeUtc);
}
}
var activatedExtension = extensionProbes.FirstOrDefault();
var previousDependency = existingDependencies.Where(d => d.Name == extension.Name).FirstOrDefault();
if (activatedExtension == null) {
Logger.Warning("No loader found for extension {0}!", extension.Name);
}
foreach (var loader in _loaders) {
if (activatedExtension != null && activatedExtension.Loader.Name == loader.Name) {
Logger.Information("Activating extension \"{0}\" with loader \"{1}\"", activatedExtension.Descriptor.Name, loader.Name);
loader.ExtensionActivated(ctx, isNewExtension, extension);
}
else if (previousDependency != null && previousDependency.LoaderName == loader.Name) {
Logger.Information("Deactivating extension \"{0}\" from loader \"{1}\"", previousDependency.Name, loader.Name);
loader.ExtensionDeactivated(ctx, isNewExtension, extension);
}
}
if (activatedExtension != null) {
newDependencies.Add(new DependencyDescriptor {
Name = extension.Name,
LoaderName = activatedExtension.Loader.Name,
VirtualPath = activatedExtension.VirtualPath
});
}
}
private void ProcessContextCommands(ExtensionLoadingContext ctx) {
Logger.Information("Executing list of operations needed for loading extensions...");
foreach (var fileName in ctx.FilesToDelete) {
Logger.Information("Deleting file \"{0}\"", fileName);
File.Delete(fileName);
}
foreach (var entry in ctx.FilesToCopy) {
Logger.Information("Copying file from \"{0}\" to \"{1}\"", entry.Key, entry.Value);
MakeDestinationFileNameAvailable(entry.Value);
File.Copy(entry.Key, entry.Value);
}
foreach (var entry in ctx.FilesToRename) {
Logger.Information("Moving file from \"{0}\" to \"{1}\"", entry.Key, entry.Value);
MakeDestinationFileNameAvailable(entry.Value);
File.Move(entry.Key, entry.Value);
}
if (ctx.RestartAppDomain || ctx.ResetSiteCompilation) {
if (ctx.RestartAppDomain)
Logger.Information("AppDomain restart required.");
if (ctx.ResetSiteCompilation)
Logger.Information("Reset site compilation state required.");
// Touch web.config
File.SetLastWriteTimeUtc(_virtualPathProvider.MapPath("~/web.config"), _clock.UtcNow);
}
}
private void MakeDestinationFileNameAvailable(string destinationFileName) {
// Try deleting the destination first
try {
File.Delete(destinationFileName);
} catch {
// We land here if the file is in use, for example. Let's move on.
}
// If destination doesn't exist, we are good
if (!File.Exists(destinationFileName))
return;
// Try renaming destination to a unique filename
const string extension = "deleted";
for (int i = 0; i < 100; i++) {
var newExtension = (i == 0 ? extension : string.Format("{0}{1}", extension, i));
var newFileName = Path.ChangeExtension(destinationFileName, newExtension);
try {
File.Delete(newFileName);
File.Move(destinationFileName, newFileName);
// If successful, we are done...
return;
}
catch (Exception) {
// We need to try with another extension
}
}
// Everything failed, throw an exception
throw new OrchardException(T("Unable to make room for file {0} in dependencies folder: too many conflicts.", destinationFileName).Text);
}
public void MonitorExtensions(Action<IVolatileToken> monitor) {
var extensions = _extensionManager.AvailableExtensions().Where(d => d.ExtensionType == "Module").ToList();
foreach (var extension in extensions) {
foreach (var loader in _loaders) {
loader.Monitor(extension, monitor);
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
using System.Collections.Generic;
using Orchard.FileSystems.Dependencies;
namespace Orchard.Environment.Extensions {
public class ExtensionLoadingContext {
public ExtensionLoadingContext() {
FilesToDelete = new HashSet<string>();
FilesToCopy = new Dictionary<string, string>();
FilesToRename = new Dictionary<string, string>();
}
public IDependenciesFolder DependenciesFolder { get; set; }
public HashSet<string> FilesToDelete { get; set; }
public Dictionary<string, string> FilesToCopy { get; set; }
public Dictionary<string, string> FilesToRename { get; set; }
public bool RestartAppDomain { get; set; }
public bool ResetSiteCompilation { get; set; }
}
}

View File

@@ -4,7 +4,6 @@ using System.IO;
using System.Linq;
using System.Web;
using ICSharpCode.SharpZipLib.Zip;
using Orchard.Caching;
using Orchard.Environment.Extensions.Folders;
using Orchard.Environment.Extensions.Helpers;
using Orchard.Environment.Extensions.Loaders;
@@ -154,33 +153,13 @@ namespace Orchard.Environment.Extensions {
Directory.Delete(targetFolder, true);
}
public void Monitor(Action<IVolatileToken> monitor) {
foreach (var descriptor in AvailableExtensions()) {
if (string.Equals(descriptor.ExtensionType, "Module", StringComparison.OrdinalIgnoreCase)) {
foreach (var loader in _loaders) {
loader.Monitor(descriptor, monitor);
}
}
}
}
private ExtensionEntry BuildEntry(ExtensionDescriptor descriptor) {
var loaders = _loaders.ToList();
var moreRecentEntry = loaders
.Select(loader => loader.Probe(descriptor))
.Where(entry => entry != null)
.OrderByDescending(entry => entry.LastModificationTimeUtc)
.FirstOrDefault();
ExtensionEntry result = null;
foreach (var loader in loaders) {
ExtensionEntry entry = loader.Load(moreRecentEntry);
if (entry != null && result == null) {
result = entry;
foreach (var loader in _loaders) {
ExtensionEntry entry = loader.Load(descriptor);
if (entry != null)
return entry;
}
}
return result;
return null;
}
}
}

View File

@@ -0,0 +1,9 @@
using System;
using Orchard.Caching;
namespace Orchard.Environment.Extensions {
public interface IExtensionLoaderCoordinator {
void SetupExtensions();
void MonitorExtensions(Action<IVolatileToken> monitor);
}
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Web;
using Orchard.Caching;
using Orchard.Environment.Extensions.Models;
namespace Orchard.Environment.Extensions {
@@ -10,6 +8,5 @@ namespace Orchard.Environment.Extensions {
IEnumerable<Feature> LoadFeatures(IEnumerable<FeatureDescriptor> featureDescriptors);
void InstallExtension(string extensionType, HttpPostedFileBase extensionBundle);
void UninstallExtension(string extensionType, string extensionName);
void Monitor(Action<IVolatileToken> monitor);
}
}

View File

@@ -1,29 +1,21 @@
using System;
using System.Linq;
using System.Reflection;
using Orchard.Caching;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath;
namespace Orchard.Environment.Extensions.Loaders {
public class AreaExtensionLoader : IExtensionLoader {
public class AreaExtensionLoader : ExtensionLoaderBase {
private readonly IDependenciesFolder _dependenciesFolder;
private readonly IVirtualPathProvider _virtualPathProvider;
public AreaExtensionLoader(IDependenciesFolder dependenciesFolder, IVirtualPathProvider virtualPathProvider) {
public AreaExtensionLoader(IDependenciesFolder dependenciesFolder) {
_dependenciesFolder = dependenciesFolder;
_virtualPathProvider = virtualPathProvider;
}
public int Order { get { return 50; } }
public override int Order { get { return 50; } }
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
// We don't need to monitor anything since we are loaded
// from the application assembly itself.
}
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
if (descriptor.Location == "~/Areas") {
return new ExtensionProbeEntry {
Descriptor = descriptor,
@@ -35,25 +27,20 @@ namespace Orchard.Environment.Extensions.Loaders {
return null;
}
public ExtensionEntry Load(ExtensionProbeEntry entry) {
if (entry.Loader == this) {
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
if (dependency != null && dependency.LoaderName == this.Name) {
var assembly = Assembly.Load("Orchard.Web");
_dependenciesFolder.Store(new DependencyDescriptor { ModuleName = entry.Descriptor.Name, LoaderName = this.GetType().FullName});
return new ExtensionEntry {
Descriptor = entry.Descriptor,
Descriptor = descriptor,
Assembly = assembly,
ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, entry.Descriptor))
ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, descriptor))
};
}
else {
// If the extension is not loaded by us, there is no cached state
// we need to invalidate
_dependenciesFolder.Remove(entry.Descriptor.Name, this.GetType().FullName);
return null;
}
}
private static bool IsTypeFromModule(Type type, ExtensionDescriptor descriptor) {
return (type.Namespace + ".").StartsWith("Orchard.Web.Areas." + descriptor.Name + ".");

View File

@@ -10,23 +10,16 @@ namespace Orchard.Environment.Extensions.Loaders {
/// <summary>
/// Load an extension by looking into specific namespaces of the "Orchard.Core" assembly
/// </summary>
public class CoreExtensionLoader : IExtensionLoader {
public class CoreExtensionLoader : ExtensionLoaderBase {
private readonly IDependenciesFolder _dependenciesFolder;
private readonly IVirtualPathProvider _virtualPathProvider;
public CoreExtensionLoader(IDependenciesFolder dependenciesFolder, IVirtualPathProvider virtualPathProvider) {
public CoreExtensionLoader(IDependenciesFolder dependenciesFolder) {
_dependenciesFolder = dependenciesFolder;
_virtualPathProvider = virtualPathProvider;
}
public int Order { get { return 10; } }
public override int Order { get { return 10; } }
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
// We don't need to monitor anything since we are loaded
// from the core assembly.
}
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
if (descriptor.Location == "~/Core") {
return new ExtensionProbeEntry {
Descriptor = descriptor,
@@ -38,25 +31,20 @@ namespace Orchard.Environment.Extensions.Loaders {
return null;
}
public ExtensionEntry Load(ExtensionProbeEntry entry) {
if (entry.Loader == this) {
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
if (dependency != null && dependency.LoaderName == this.Name) {
var assembly = Assembly.Load("Orchard.Core");
_dependenciesFolder.Store(new DependencyDescriptor { ModuleName = entry.Descriptor.Name, LoaderName = this.GetType().FullName });
return new ExtensionEntry {
Descriptor = entry.Descriptor,
Descriptor = descriptor,
Assembly = assembly,
ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, entry.Descriptor))
ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, descriptor))
};
}
else {
// If the extension is not loaded by us, there is no cached state
// we need to invalidate
_dependenciesFolder.Remove(entry.Descriptor.Name, this.GetType().FullName);
return null;
}
}
private static bool IsTypeFromModule(Type type, ExtensionDescriptor descriptor) {
return (type.Namespace + ".").StartsWith("Orchard.Core." + descriptor.Name + ".");

View File

@@ -4,36 +4,60 @@ using Orchard.Caching;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath;
using Orchard.Logging;
namespace Orchard.Environment.Extensions.Loaders {
public class DynamicExtensionLoader : IExtensionLoader {
private readonly IHostEnvironment _hostEnvironment;
public class DynamicExtensionLoader : ExtensionLoaderBase {
private readonly IBuildManager _buildManager;
private readonly IVirtualPathProvider _virtualPathProvider;
private readonly IVirtualPathMonitor _virtualPathMonitor;
private readonly IDependenciesFolder _dependenciesFolder;
public DynamicExtensionLoader(IHostEnvironment hostEnvironment,
public DynamicExtensionLoader(
IBuildManager buildManager,
IVirtualPathProvider virtualPathProvider,
IVirtualPathMonitor virtualPathMonitor,
IDependenciesFolder dependenciesFolder) {
_hostEnvironment = hostEnvironment;
_buildManager = buildManager;
_virtualPathProvider = virtualPathProvider;
_virtualPathMonitor = virtualPathMonitor;
_dependenciesFolder = dependenciesFolder;
Logger = NullLogger.Instance;
}
public int Order { get { return 100; } }
public ILogger Logger { get; set; }
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
public override int Order { get { return 100; } }
public override string GetAssemblyDirective(DependencyDescriptor dependency) {
return string.Format("<%@ Assembly Src=\"{0}\"%>", dependency.VirtualPath);
}
public override void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
// We need to monitor the path to the ".csproj" file.
string projectPath = GetProjectPath(descriptor);
if (projectPath != null)
if (projectPath != null) {
Logger.Information("Monitoring virtual path {0}", projectPath);
monitor(_virtualPathMonitor.WhenPathChanges(projectPath));
}
}
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
public override void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency) {
// Since a dynamic assembly is not active anymore, we need to notify ASP.NET
// that a new site compilation is needed (since ascx files may be referencing
// this now removed extension).
ctx.ResetSiteCompilation = true;
}
public override void ExtensionDeactivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension) {
// Since a dynamic assembly is not active anymore, we need to notify ASP.NET
// that a new site compilation is needed (since ascx files may be referencing
// this now removed extension).
ctx.ResetSiteCompilation = true;
}
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
string projectPath = GetProjectPath(descriptor);
if (projectPath == null)
return null;
@@ -46,6 +70,21 @@ namespace Orchard.Environment.Extensions.Loaders {
};
}
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
if (dependency != null && dependency.LoaderName == this.Name) {
var assembly = _buildManager.GetCompiledAssembly(dependency.VirtualPath);
return new ExtensionEntry {
Descriptor = descriptor,
Assembly = assembly,
ExportedTypes = assembly.GetExportedTypes(),
};
}
return null;
}
private string GetProjectPath(ExtensionDescriptor descriptor) {
string projectPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Name,
descriptor.Name + ".csproj");
@@ -56,33 +95,5 @@ namespace Orchard.Environment.Extensions.Loaders {
return projectPath;
}
public ExtensionEntry Load(ExtensionProbeEntry entry) {
if (entry.Loader == this) {
var assembly = _buildManager.GetCompiledAssembly(entry.VirtualPath);
_dependenciesFolder.Store(new DependencyDescriptor {
ModuleName = entry.Descriptor.Name,
LoaderName = this.GetType().FullName,
VirtualPath = entry.VirtualPath,
FileName = assembly.Location
});
return new ExtensionEntry {
Descriptor = entry.Descriptor,
Assembly = assembly,
ExportedTypes = assembly.GetExportedTypes(),
};
}
else {
// If the extension is not loaded by us, there is some cached state we need to invalidate
// 1) The webforms views which have been compiled with ".csproj" assembly source
// 2) The modules which contains features which depend on us
//TODO
_dependenciesFolder.Remove(entry.Descriptor.Name, this.GetType().FullName);
return null;
}
}
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.IO;
using System.Linq;
using Orchard.Caching;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies;
namespace Orchard.Environment.Extensions.Loaders {
public abstract class ExtensionLoaderBase : IExtensionLoader {
public abstract int Order { get; }
public string Name { get { return this.GetType().Name; } }
public abstract ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
public abstract ExtensionEntry Load(ExtensionDescriptor descriptor);
public virtual void ExtensionActivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension) {}
public virtual void ExtensionDeactivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension){}
public virtual void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency) { }
public virtual void Monitor(ExtensionDescriptor extension, Action<IVolatileToken> monitor) { }
public virtual string GetAssemblyDirective(DependencyDescriptor dependency) { return null; }
public static bool FileIsNewer(string sourceFileName, string destinationFileName) {
if (!File.Exists(destinationFileName))
return true;
return File.GetLastWriteTimeUtc(sourceFileName) > File.GetLastWriteTimeUtc(destinationFileName);
}
public static bool IsAssemblyLoaded(string moduleName) {
return AppDomain.CurrentDomain.GetAssemblies().Any(a => a.GetName().Name == moduleName);
}
}
}

View File

@@ -1,19 +1,29 @@
using System;
using Orchard.Caching;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies;
namespace Orchard.Environment.Extensions.Loaders {
public interface IExtensionLoader {
int Order { get; }
ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
ExtensionEntry Load(ExtensionProbeEntry descriptor);
void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor);
}
public class ExtensionProbeEntry {
public ExtensionDescriptor Descriptor { get; set; }
public IExtensionLoader Loader { get; set; }
public string VirtualPath { get; set; }
public DateTime LastModificationTimeUtc { get; set; }
}
public interface IExtensionLoader {
int Order { get; }
string Name { get; }
ExtensionProbeEntry Probe(ExtensionDescriptor descriptor);
ExtensionEntry Load(ExtensionDescriptor descriptor);
void ExtensionActivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension);
void ExtensionDeactivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension);
void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency);
void Monitor(ExtensionDescriptor extension, Action<IVolatileToken> monitor);
string GetAssemblyDirective(DependencyDescriptor dependency);
}
}

View File

@@ -1,16 +1,18 @@
using System;
using System.IO;
using System.Linq;
using Orchard.Caching;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath;
using Orchard.Logging;
namespace Orchard.Environment.Extensions.Loaders {
/// <summary>
/// Load an extension by looking into the "bin" subdirectory of an
/// extension directory.
/// </summary>
public class PrecompiledExtensionLoader : IExtensionLoader {
public class PrecompiledExtensionLoader : ExtensionLoaderBase {
private readonly IDependenciesFolder _dependenciesFolder;
private readonly IVirtualPathProvider _virtualPathProvider;
private readonly IVirtualPathMonitor _virtualPathMonitor;
@@ -19,26 +21,64 @@ namespace Orchard.Environment.Extensions.Loaders {
_dependenciesFolder = dependenciesFolder;
_virtualPathProvider = virtualPathProvider;
_virtualPathMonitor = virtualPathMonitor;
Logger = NullLogger.Instance;
}
public int Order { get { return 30; } }
public ILogger Logger { get; set; }
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
public override int Order { get { return 30; } }
public override string GetAssemblyDirective(DependencyDescriptor dependency) {
return string.Format("<%@ Assembly Name=\"{0}\"%>", dependency.Name);
}
public override void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency) {
var assemblyFileName = _dependenciesFolder.GetProbingAssemblyPhysicalFileName(dependency.Name);
if (File.Exists(assemblyFileName)) {
ctx.FilesToDelete.Add(assemblyFileName);
// We need to restart the appDomain if the assembly is loaded
if (IsAssemblyLoaded(dependency.Name)) {
Logger.Information("Extension removed: Setting AppDomain for restart because assembly {0} is loaded", dependency.Name);
ctx.RestartAppDomain = true;
}
}
}
public override void ExtensionActivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension) {
string sourceFileName = _virtualPathProvider.MapPath(GetAssemblyPath(extension));
string destinationFileName = _dependenciesFolder.GetProbingAssemblyPhysicalFileName(extension.Name);
if (FileIsNewer(sourceFileName, destinationFileName)) {
ctx.FilesToCopy.Add(sourceFileName, destinationFileName);
// We need to restart the appDomain if the assembly is loaded
if (IsAssemblyLoaded(extension.Name)) {
Logger.Information("Extension activated: Setting AppDomain for restart because assembly {0} is loaded", extension.Name);
ctx.RestartAppDomain = true;
}
}
}
public override void ExtensionDeactivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension) {
var assemblyFileName = _dependenciesFolder.GetProbingAssemblyPhysicalFileName(extension.Name);
if (File.Exists(assemblyFileName)) {
ctx.FilesToDelete.Add(assemblyFileName);
// We need to restart the appDomain if the assembly is loaded
if (IsAssemblyLoaded(extension.Name)) {
Logger.Information("Extension deactivated: Setting AppDomain for restart because assembly {0} is loaded", extension.Name);
ctx.RestartAppDomain = true;
}
}
}
public override void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
string assemblyPath = GetAssemblyPath(descriptor);
if (assemblyPath != null)
if (assemblyPath != null) {
Logger.Information("Monitoring virtual path \"{0}\"", assemblyPath);
monitor(_virtualPathMonitor.WhenPathChanges(assemblyPath));
}
public string GetAssemblyPath(ExtensionDescriptor descriptor) {
var assemblyPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Name, "bin",
descriptor.Name + ".dll");
if (!_virtualPathProvider.FileExists(assemblyPath))
return null;
return assemblyPath;
}
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
var assemblyPath = GetAssemblyPath(descriptor);
if (assemblyPath == null)
return null;
@@ -51,28 +91,30 @@ namespace Orchard.Environment.Extensions.Loaders {
};
}
public ExtensionEntry Load(ExtensionProbeEntry entry) {
if (entry.Loader == this) {
_dependenciesFolder.StorePrecompiledAssembly(entry.Descriptor.Name, entry.VirtualPath, this.GetType().FullName);
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
if (dependency != null && dependency.LoaderName == this.Name) {
var assembly = _dependenciesFolder.LoadAssembly(entry.Descriptor.Name);
var assembly = _dependenciesFolder.GetProbingAssembly(descriptor.Name);
if (assembly == null)
return null;
return new ExtensionEntry {
Descriptor = entry.Descriptor,
Assembly = assembly,
ExportedTypes = assembly.GetExportedTypes()
Descriptor = descriptor,
Assembly = assembly.Assembly(),
ExportedTypes = assembly.Assembly().GetExportedTypes()
};
}
else {
// If the extension is not loaded by us, there is some cached state we need to invalidate
// 1) The webforms views which have been compiled with ".csproj" assembly source
// 2) The modules which contains features which depend on us
//TODO
_dependenciesFolder.Remove(entry.Descriptor.Name, this.GetType().FullName);
return null;
}
public string GetAssemblyPath(ExtensionDescriptor descriptor) {
var assemblyPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Name, "bin",
descriptor.Name + ".dll");
if (!_virtualPathProvider.FileExists(assemblyPath))
return null;
return assemblyPath;
}
}
}

View File

@@ -1,32 +1,62 @@
using System;
using System.IO;
using System.Linq;
using Orchard.Caching;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath;
using Orchard.Logging;
namespace Orchard.Environment.Extensions.Loaders {
/// <summary>
/// Load an extension using the "Assembly.Load" method if the
/// file can be found in the "App_Data/Dependencies" folder.
/// </summary>
public class ProbingExtensionLoader : IExtensionLoader {
public class ProbingExtensionLoader : ExtensionLoaderBase {
private readonly IDependenciesFolder _dependenciesFolder;
private readonly IVirtualPathProvider _virtualPathProvider;
public ProbingExtensionLoader(IDependenciesFolder dependenciesFolder) {
public ProbingExtensionLoader(IDependenciesFolder dependenciesFolder, IVirtualPathProvider virtualPathProvider) {
_dependenciesFolder = dependenciesFolder;
_virtualPathProvider = virtualPathProvider;
Logger = NullLogger.Instance;
}
public int Order { get { return 40; } }
public ILogger Logger { get; set; }
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
// We don't monitor assemblies loaded from this probing directory,
// because they are just a copy of the assemblies from the module
// bin directory.
public override int Order { get { return 40; } }
public override string GetAssemblyDirective(DependencyDescriptor dependency) {
return string.Format("<%@ Assembly Name=\"{0}\"%>", dependency.Name);
}
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
if (!_dependenciesFolder.HasPrecompiledAssembly(descriptor.Name))
public override void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency) {
var assemblyFileName = _dependenciesFolder.GetProbingAssemblyPhysicalFileName(dependency.Name);
if (File.Exists(assemblyFileName)) {
ctx.FilesToDelete.Add(assemblyFileName);
// We need to restart the appDomain if the assembly is loaded
if (IsAssemblyLoaded(dependency.Name)) {
Logger.Information("Extension removed: Setting AppDomain for restart because assembly {0} is loaded", dependency.Name);
ctx.RestartAppDomain = true;
}
}
}
public override void ExtensionDeactivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension) {
var assemblyFileName = _dependenciesFolder.GetProbingAssemblyPhysicalFileName(extension.Name);
if (File.Exists(assemblyFileName)) {
ctx.FilesToDelete.Add(assemblyFileName);
// We need to restart the appDomain if the assembly is loaded
if (IsAssemblyLoaded(extension.Name)) {
Logger.Information("Extension deactivated: Setting AppDomain for restart because assembly {0} is loaded", extension.Name);
ctx.RestartAppDomain = true;
}
}
}
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
var probingAssembly = _dependenciesFolder.GetProbingAssembly(descriptor.Name);
if (probingAssembly == null)
return null;
var desc = _dependenciesFolder.GetDescriptor(descriptor.Name);
@@ -35,33 +65,27 @@ namespace Orchard.Environment.Extensions.Loaders {
return new ExtensionProbeEntry {
Descriptor = descriptor,
LastModificationTimeUtc = File.GetLastWriteTimeUtc(desc.FileName),
LastModificationTimeUtc = probingAssembly.LastWriteTimeUtc(),
Loader = this,
VirtualPath = desc.VirtualPath
};
}
public ExtensionEntry Load(ExtensionProbeEntry entry) {
if (entry.Loader == this) {
var assembly = _dependenciesFolder.LoadAssembly(entry.Descriptor.Name);
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
if (dependency != null && dependency.LoaderName == this.Name) {
var assembly = _dependenciesFolder.GetProbingAssembly(descriptor.Name);
if (assembly == null)
return null;
return new ExtensionEntry {
Descriptor = entry.Descriptor,
Assembly = assembly,
ExportedTypes = assembly.GetExportedTypes()
Descriptor = descriptor,
Assembly = assembly.Assembly(),
ExportedTypes = assembly.Assembly().GetExportedTypes()
};
}
else {
// If the extension is not loaded by us, there is some cached state we need to invalidate
// 1) The webforms views which have been compiled with "Assembly Name=""" directive
// 2) The modules which contains features which depend on us
// 3) The binary from the App_Data directory
//TODO
_dependenciesFolder.Remove(entry.Descriptor.Name, this.GetType().FullName);
return null;
}
}
}
}

View File

@@ -1,32 +1,35 @@
using System;
using System.IO;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web.Compilation;
using System.Web.Hosting;
using Orchard.Caching;
using Orchard.Environment.Extensions.Models;
using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.VirtualPath;
namespace Orchard.Environment.Extensions.Loaders {
/// <summary>
/// Load an extension by looking through the BuildManager referenced assemblies
/// </summary>
public class ReferencedExtensionLoader : IExtensionLoader {
public class ReferencedExtensionLoader : ExtensionLoaderBase {
private readonly IDependenciesFolder _dependenciesFolder;
private readonly IVirtualPathProvider _virtualPathProvider;
public ReferencedExtensionLoader(IDependenciesFolder dependenciesFolder) {
public ReferencedExtensionLoader(IDependenciesFolder dependenciesFolder, IVirtualPathProvider virtualPathProvider) {
_dependenciesFolder = dependenciesFolder;
_virtualPathProvider = virtualPathProvider;
}
public int Order { get { return 20; } }
public override int Order { get { return 20; } }
public void Monitor(ExtensionDescriptor descriptor, Action<IVolatileToken> monitor) {
// We don't monitor assemblies loaded from the "~/bin" directory,
// because they are monitored by the ASP.NET runtime.
public override void ExtensionDeactivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension) {
var assemblyPath = _virtualPathProvider.Combine("~/bin", extension.Name + ".dll");
if (_virtualPathProvider.FileExists(assemblyPath)) {
ctx.FilesToDelete.Add(_virtualPathProvider.MapPath(assemblyPath));
}
}
public ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
if (HostingEnvironment.IsHosted == false)
return null;
@@ -45,36 +48,24 @@ namespace Orchard.Environment.Extensions.Loaders {
};
}
public ExtensionEntry Load(ExtensionProbeEntry entry) {
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
if (HostingEnvironment.IsHosted == false)
return null;
if (entry.Loader == this) {
var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
if (dependency != null && dependency.LoaderName == this.Name) {
var assembly = BuildManager.GetReferencedAssemblies()
.OfType<Assembly>()
.FirstOrDefault(x => x.GetName().Name == entry.Descriptor.Name);
_dependenciesFolder.Store(new DependencyDescriptor {
ModuleName = entry.Descriptor.Name,
LoaderName = this.GetType().FullName,
VirtualPath = entry.VirtualPath,
FileName = assembly.Location
});
.FirstOrDefault(x => x.GetName().Name == descriptor.Name);
return new ExtensionEntry {
Descriptor = entry.Descriptor,
Descriptor = descriptor,
Assembly = assembly,
ExportedTypes = assembly.GetExportedTypes()
};
}
else {
// If the extension is not loaded by us, there is no cached state
// we need to invalidate
_dependenciesFolder.Remove(entry.Descriptor.Name, this.GetType().FullName);
return null;
}
}
}
}

View File

@@ -57,6 +57,7 @@ namespace Orchard.Environment {
.As<ICompositionStrategy>()
.SingleInstance();
{
builder.RegisterType<ExtensionLoaderCoordinator>().As<IExtensionLoaderCoordinator>().SingleInstance();
builder.RegisterType<ExtensionManager>().As<IExtensionManager>().SingleInstance();
{
builder.RegisterType<ModuleFolders>().As<IExtensionFolders>()

View File

@@ -6,11 +6,12 @@ using System.Reflection;
using System.Xml.Linq;
using Orchard.Caching;
using Orchard.FileSystems.AppData;
using Orchard.Localization;
namespace Orchard.FileSystems.Dependencies {
public class DefaultDependenciesFolder : IDependenciesFolder {
private readonly string _basePath = "Dependencies";
private readonly string _persistanceFileName = "dependencies.xml";
private const string BasePath = "Dependencies";
private const string FileName = "dependencies.xml";
private readonly ICacheManager _cacheManager;
private readonly IAppDataFolder _appDataFolder;
private readonly InvalidationToken _writeThroughToken;
@@ -19,11 +20,14 @@ namespace Orchard.FileSystems.Dependencies {
_cacheManager = cacheManager;
_appDataFolder = appDataFolder;
_writeThroughToken = new InvalidationToken();
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
private string PersistencePath {
get {
return _appDataFolder.Combine(_basePath, _persistanceFileName);
return _appDataFolder.Combine(BasePath, FileName);
}
}
@@ -31,106 +35,47 @@ namespace Orchard.FileSystems.Dependencies {
get {
return _cacheManager.Get(PersistencePath,
ctx => {
_appDataFolder.CreateDirectory(_basePath);
_appDataFolder.CreateDirectory(BasePath);
ctx.Monitor(_appDataFolder.WhenPathChanges(ctx.Key));
ctx.Monitor(_writeThroughToken);
_appDataFolder.CreateDirectory(_basePath);
_appDataFolder.CreateDirectory(BasePath);
return ReadDependencies(ctx.Key).ToList();
});
}
}
public class InvalidationToken : IVolatileToken {
public bool IsCurrent { get; set; }
public IEnumerable<DependencyDescriptor> LoadDescriptors() {
return Descriptors;
}
public void StorePrecompiledAssembly(string moduleModuleName, string virtualPath, string loaderName) {
_appDataFolder.CreateDirectory(_basePath);
// Only store assembly if it's more recent that what we have stored already (if anything)
var assemblyFileName = _appDataFolder.MapPath(virtualPath);
if (IsNewerAssembly(moduleModuleName, assemblyFileName)) {
var destinationFileName = Path.GetFileName(assemblyFileName);
var destinationPath = _appDataFolder.MapPath(_appDataFolder.Combine(_basePath, destinationFileName));
File.Copy(assemblyFileName, destinationPath, true);
StoreDepencyInformation(new DependencyDescriptor {
ModuleName = moduleModuleName,
LoaderName = loaderName,
VirtualPath = virtualPath,
FileName = destinationFileName
});
}
}
public void Remove(string moduleName, string loaderName) {
Func<DependencyDescriptor, bool> predicate = (d => d.ModuleName == moduleName && d.LoaderName == loaderName);
if (Descriptors.Any(predicate)) {
var newDescriptors = Descriptors.Where(e => !predicate(e));
WriteDependencies(PersistencePath, newDescriptors);
}
}
public void Store(DependencyDescriptor descriptor) {
StoreDepencyInformation(descriptor);
public void StoreDescriptors(IEnumerable<DependencyDescriptor> dependencyDescriptors) {
WriteDependencies(PersistencePath, dependencyDescriptors);
}
public DependencyDescriptor GetDescriptor(string moduleName) {
return Descriptors.SingleOrDefault(d => d.ModuleName == moduleName);
return Descriptors.SingleOrDefault(d => d.Name == moduleName);
}
private bool IsNewerAssembly(string moduleName, string assemblyFileName) {
var dependency = Descriptors.SingleOrDefault(d => d.ModuleName == moduleName);
if (dependency == null) {
return true;
}
var existingFileName = _appDataFolder.MapPath(_appDataFolder.Combine(_basePath, dependency.FileName));
if (!File.Exists(existingFileName)) {
return true;
}
return (File.GetLastWriteTimeUtc(existingFileName) < File.GetLastWriteTimeUtc(assemblyFileName));
}
private void StoreDepencyInformation(DependencyDescriptor descriptor) {
var dependencies = Descriptors.ToList();
int index = dependencies.FindIndex(d => d.ModuleName == descriptor.ModuleName);
if (index < 0) {
dependencies.Add(descriptor);
}
else {
dependencies[index] = descriptor;
}
WriteDependencies(PersistencePath, dependencies);
}
public Assembly LoadAssembly(string moduleName) {
_appDataFolder.CreateDirectory(_basePath);
var dependency = Descriptors.SingleOrDefault(d => d.ModuleName == moduleName);
if (dependency == null)
public ProbingAssembly GetProbingAssembly(string moduleName) {
var path = PrecompiledAssemblyPath(moduleName);
if (!_appDataFolder.FileExists(path))
return null;
if (!_appDataFolder.FileExists(_appDataFolder.Combine(_basePath, dependency.FileName)))
return null;
return Assembly.Load(Path.GetFileNameWithoutExtension(dependency.FileName));
return new ProbingAssembly {
Path = path,
Assembly = () => Assembly.Load(moduleName),
LastWriteTimeUtc = () => File.GetLastWriteTimeUtc(_appDataFolder.MapPath(path)),
};
}
public bool HasPrecompiledAssembly(string moduleName) {
var dependency = Descriptors.SingleOrDefault(d => d.ModuleName == moduleName);
if (dependency == null)
return false;
public string GetProbingAssemblyPhysicalFileName(string moduleName) {
return _appDataFolder.MapPath(PrecompiledAssemblyPath(moduleName));
}
if (!_appDataFolder.FileExists(_appDataFolder.Combine(_basePath, dependency.FileName)))
return false;
return true;
private string PrecompiledAssemblyPath(string moduleName) {
return _appDataFolder.Combine(BasePath, moduleName + ".dll");
}
private IEnumerable<DependencyDescriptor> ReadDependencies(string persistancePath) {
@@ -146,9 +91,8 @@ namespace Orchard.FileSystems.Dependencies {
.Elements(ns("Dependencies"))
.Elements(ns("Dependency"))
.Select(e => new DependencyDescriptor {
ModuleName = elem(e, "ModuleName"),
Name = elem(e, "ModuleName"),
VirtualPath = elem(e, "VirtualPath"),
FileName = elem(e, "FileName"),
LoaderName = elem(e, "LoaderName")
})
.ToList();
@@ -161,10 +105,9 @@ namespace Orchard.FileSystems.Dependencies {
var document = new XDocument();
document.Add(new XElement(ns("Dependencies")));
var elements = dependencies.Select(d => new XElement("Dependency",
new XElement(ns("ModuleName"), d.ModuleName),
new XElement(ns("ModuleName"), d.Name),
new XElement(ns("VirtualPath"), d.VirtualPath),
new XElement(ns("LoaderName"), d.LoaderName),
new XElement(ns("FileName"), d.FileName)));
new XElement(ns("LoaderName"), d.LoaderName)));
document.Root.Add(elements);
using (var stream = _appDataFolder.CreateFile(persistancePath)) {
@@ -174,5 +117,9 @@ namespace Orchard.FileSystems.Dependencies {
// Ensure cache is invalidated right away, not waiting for file change notification to happen
_writeThroughToken.IsCurrent = false;
}
private class InvalidationToken : IVolatileToken {
public bool IsCurrent { get; set; }
}
}
}

View File

@@ -1,20 +1,27 @@
using System.Reflection;
using System;
using System.Collections.Generic;
using System.Reflection;
using Orchard.Caching;
namespace Orchard.FileSystems.Dependencies {
public class DependencyDescriptor {
public string ModuleName { get; set; }
public string Name { get; set; }
public string LoaderName { get; set; }
public string VirtualPath { get; set; }
public string FileName { get; set; }
}
public class ProbingAssembly {
public string Path { get; set; }
public Func<DateTime> LastWriteTimeUtc { get; set; }
public Func<Assembly> Assembly { get; set; }
}
public interface IDependenciesFolder : IVolatileProvider {
void Store(DependencyDescriptor descriptor);
void StorePrecompiledAssembly(string moduleName, string virtualPath, string loaderName);
void Remove(string moduleName, string loaderName);
DependencyDescriptor GetDescriptor(string moduleName);
bool HasPrecompiledAssembly(string moduleName);
Assembly LoadAssembly(string moduleName);
IEnumerable<DependencyDescriptor> LoadDescriptors();
void StoreDescriptors(IEnumerable<DependencyDescriptor> dependencyDescriptors);
ProbingAssembly GetProbingAssembly(string moduleName);
string GetProbingAssemblyPhysicalFileName(string moduleName);
}
}

View File

@@ -1,16 +1,15 @@
using System.IO;
using System.Web.Hosting;
using Orchard.Environment.Extensions.Loaders;
namespace Orchard.FileSystems.Dependencies {
public class WebFormsExtensionsVirtualFile : VirtualFile {
private readonly DependencyDescriptor _dependencyDescriptor;
private readonly VirtualFile _actualFile;
private readonly string _assemblyDirective;
public WebFormsExtensionsVirtualFile(string virtualPath, DependencyDescriptor dependencyDescriptor, VirtualFile actualFile)
public WebFormsExtensionsVirtualFile(string virtualPath, VirtualFile actualFile, string assemblyDirective)
: base(virtualPath) {
_dependencyDescriptor = dependencyDescriptor;
_actualFile = actualFile;
_assemblyDirective = assemblyDirective;
}
public override string Name {
@@ -37,7 +36,7 @@ namespace Orchard.FileSystems.Dependencies {
for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) {
if (!string.IsNullOrWhiteSpace(line) && !assemblyDirectiveAdded) {
line += GetAssemblyDirective();
line += _assemblyDirective;
assemblyDirectiveAdded = true;
}
@@ -49,14 +48,5 @@ namespace Orchard.FileSystems.Dependencies {
return new MemoryStream(memoryStream.GetBuffer(), 0, length);
}
}
private string GetAssemblyDirective() {
if (_dependencyDescriptor.LoaderName == typeof(DynamicExtensionLoader).FullName) {
return string.Format("<%@ Assembly Src=\"{0}\"%>", _dependencyDescriptor.VirtualPath);
}
else {
return string.Format("<%@ Assembly Name=\"{0}\"%>", _dependencyDescriptor.ModuleName);
}
}
}
}

View File

@@ -1,16 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Hosting;
using Orchard.Environment.Extensions.Loaders;
using Orchard.FileSystems.VirtualPath;
namespace Orchard.FileSystems.Dependencies {
public class WebFormsExtensionsVirtualPathProvider : VirtualPathProvider, ICustomVirtualPathProvider {
private readonly IDependenciesFolder _dependenciesFolder;
private readonly IEnumerable<IExtensionLoader> _loaders;
private readonly string[] _prefixes = { "~/Modules/", "/Modules/" };
private readonly string[] _extensions = { ".ascx", ".aspx", ".master" };
public WebFormsExtensionsVirtualPathProvider(IDependenciesFolder dependenciesFolder) {
public WebFormsExtensionsVirtualPathProvider(IDependenciesFolder dependenciesFolder, IEnumerable<IExtensionLoader> loaders) {
_dependenciesFolder = dependenciesFolder;
_loaders = loaders;
}
public override bool DirectoryExists(string virtualDir) {
@@ -24,27 +28,35 @@ namespace Orchard.FileSystems.Dependencies {
public override VirtualFile GetFile(string virtualPath) {
var actualFile = Previous.GetFile(virtualPath);
return GetCustomVirtualFile(virtualPath, actualFile) ?? actualFile;
}
private VirtualFile GetCustomVirtualFile(string virtualPath, VirtualFile actualFile) {
var prefix = PrefixMatch(virtualPath, _prefixes);
if (prefix == null)
return actualFile;
return null;
var extension = ExtensionMatch(virtualPath, _extensions);
if (extension == null)
return actualFile;
return null;
var moduleName = ModuleMatch(virtualPath, prefix);
if (moduleName == null)
return actualFile;
return null;
// It looks like we have a module name. Is this one of this modules
// with its assembly stored in the "App_Data/Dependencies" folder?
var dependencyDescriptor = _dependenciesFolder.GetDescriptor(moduleName);
if (dependencyDescriptor == null)
return actualFile;
return null;
// Yes: we need to wrap the VirtualFile to add the <%@ Assembly Name=".."%> directive
// in the content.
return new WebFormsExtensionsVirtualFile(virtualPath, dependencyDescriptor, actualFile);
var loader = _loaders.Where(l => l.Name == dependencyDescriptor.LoaderName).FirstOrDefault();
if (loader == null)
return null;
var directive = loader.GetAssemblyDirective(dependencyDescriptor);
if (string.IsNullOrEmpty(directive))
return null;
return new WebFormsExtensionsVirtualFile(virtualPath, actualFile, directive);
}
private string ModuleMatch(string virtualPath, string prefix) {

View File

@@ -353,6 +353,10 @@
<Compile Include="Data\DataModule.cs" />
<Compile Include="Data\Orderable.cs" />
<Compile Include="Environment\DefaultOrchardShell.cs" />
<Compile Include="Environment\Extensions\ExtensionLoaderCoordinator.cs" />
<Compile Include="Environment\Extensions\ExtensionLoadingContext.cs" />
<Compile Include="Environment\Extensions\IExtensionLoaderCoordinator.cs" />
<Compile Include="Environment\Extensions\Loaders\ExtensionLoaderBase.cs" />
<Compile Include="FileSystems\AppData\IAppDataFolderRoot.cs" />
<Compile Include="FileSystems\Dependencies\DefaultDependenciesFolder.cs" />
<Compile Include="FileSystems\VirtualPath\DefaultVirtualPathMonitor.cs" />