Tweak dynamic extension loader

* Add "Assembly Src" directive to WebForms files loaded from vpp
* Notify the OrchardHost when the .csproj file a a dynamic module is changed.
* Make the registration of custom virtual path provider a bit more extensible,
  so that the custom VPP can be instantiated using DI.

--HG--
branch : dev
This commit is contained in:
Renaud Paquay
2010-06-06 21:36:06 -07:00
parent 2c9d0caf0a
commit 9c3f6bfc74
13 changed files with 297 additions and 71 deletions

View File

@@ -31,7 +31,6 @@ namespace Orchard.Web {
RegisterRoutes(RouteTable.Routes);
HostingEnvironment.RegisterVirtualPathProvider(new WebFormsExtensionsVirtualPathProvider());
_host = OrchardStarter.CreateHost(MvcSingletons);
_host.Initialize();

View File

@@ -17,7 +17,7 @@ using Orchard.Mvc.ViewEngines;
using Orchard.Utility.Extensions;
namespace Orchard.Environment {
public class DefaultOrchardHost : IOrchardHost, IShellSettingsManagerEventHandler, IShellDescriptorManagerEventHandler {
public class DefaultOrchardHost : IOrchardHost, IShellSettingsManagerEventHandler, IShellDescriptorManagerEventHandler, IExtensionManagerEvents {
private readonly ControllerBuilder _controllerBuilder;
private readonly IShellSettingsManager _shellSettingsManager;
@@ -147,5 +147,9 @@ namespace Orchard.Environment {
void IShellDescriptorManagerEventHandler.Changed(ShellDescriptor descriptor) {
_current = null;
}
void IExtensionManagerEvents.ModuleChanged(string moduleName) {
_current = null;
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Web.Compilation;
using System.Diagnostics;
using System.Web.Compilation;
namespace Orchard.Environment.Extensions.Compilers {
public class CSharpExtensionBuildProvider : BuildProvider {
@@ -11,6 +12,10 @@ namespace Orchard.Environment.Extensions.Compilers {
public override CompilerType CodeCompilerType { get { return _codeCompilerType; } }
public override void GenerateCode(AssemblyBuilder assemblyBuilder) {
//Debug.WriteLine(string.Format("BuildProvider for file \"{0}\"", this.VirtualPath));
//TODO: It probably would be better to access the OrchardHost container
// to resolve these dependencies.
var virtualPathProvider = new DefaultVirtualPathProvider();
var compiler = new CSharpProjectMediumTrustCompiler(virtualPathProvider);

View File

@@ -0,0 +1,7 @@
using Orchard.Events;
namespace Orchard.Environment.Extensions {
public interface IExtensionManagerEvents : IEventHandler {
void ModuleChanged(string moduleName);
}
}

View File

@@ -28,7 +28,7 @@ namespace Orchard.Environment.Extensions.Loaders {
var assembly = _buildManager.GetCompiledAssembly(projectPath);
if (_hostEnvironment.IsFullTrust) {
_dependenciesFolder.StoreAssemblyFile(descriptor.Name, assembly.Location);
_dependenciesFolder.StoreBuildProviderAssembly(descriptor.Name, projectPath, assembly);
}
return new ExtensionEntry {

View File

@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Orchard.Environment.Extensions.Loaders {
/// <summary>
/// Expose a read-only stream as the concatenation of a list of read-only streams
/// </summary>
public class MergedReadOnlyStreams : Stream {
private class StreamDescriptor {
public int Index { get; set; }
public Stream Stream { get; set; }
public long Offset { get; set; }
public long Length { get; set; }
public long Limit { get { return Offset + Length; } }
}
private readonly List<StreamDescriptor> _streams;
private long _position;
public MergedReadOnlyStreams(params Stream[] streams) {
_streams = CreateDescritors(streams).ToList();
}
private static IEnumerable<StreamDescriptor> CreateDescritors(params Stream[] streams) {
long offset = 0;
int index = 0;
foreach (var stream in streams) {
yield return new StreamDescriptor {
Stream = stream,
Index = index,
Length = stream.Length,
Offset = offset
};
offset += stream.Length;
index++;
}
}
public override void Flush() {
}
public override long Seek(long offset, SeekOrigin origin) {
switch (origin) {
case SeekOrigin.Begin:
_position = offset;
break;
case SeekOrigin.Current:
_position += offset;
break;
case SeekOrigin.End:
_position = Length + offset;
break;
default:
throw new ArgumentOutOfRangeException("origin");
}
return _position;
}
public override void SetLength(long value) {
}
public override int Read(byte[] buffer, int offset, int count) {
int totalRead = 0;
while (count > 0) {
// Find stream for current position (might fail if end of all streams)
var descriptor = GetDescriptor(_position);
if (descriptor == null)
break;
// Read bytes from the current stream
int read = descriptor.Stream.Read(buffer, offset, count);
if (read == 0)
break;
_position += read;
totalRead += read;
count -= read;
offset += read;
}
return totalRead;
}
private StreamDescriptor GetDescriptor(long position) {
return _streams.SingleOrDefault(stream => stream.Offset <= position && position < stream.Limit);
}
public override void Write(byte[] buffer, int offset, int count) {
}
public override bool CanRead {
get { return true; }
}
public override bool CanSeek {
get { return _streams.All(d => d.Stream.CanSeek); }
}
public override bool CanWrite {
get { return false; }
}
public override long Length {
get { return _streams.Aggregate(0L, (prev, desc) => prev + desc.Length); }
}
public override long Position {
get { return _position; }
set {
if (!CanSeek)
throw new InvalidOperationException();
_position = Position;
// Update the position of all streams
foreach(var desc in _streams) {
if (_position < desc.Offset) desc.Stream.Position = 0;
else if (_position > desc.Limit) desc.Stream.Position = desc.Stream.Length;
else desc.Stream.Position = _position - desc.Offset;
}
}
}
}
}

View File

@@ -23,7 +23,7 @@ namespace Orchard.Environment.Extensions.Loaders {
if (!_virtualPathProvider.FileExists(extensionPath))
return null;
_folder.StoreAssemblyFile(descriptor.Name, _virtualPathProvider.MapPath(extensionPath));
_folder.StorePrecompiledAssembly(descriptor.Name, extensionPath);
var assembly = _folder.LoadAssembly(descriptor.Name);
if (assembly == null)

View File

@@ -1,15 +1,15 @@
using System.IO;
using System.Reflection;
using System.Web.Hosting;
using Orchard.FileSystems.Dependencies;
namespace Orchard.Environment.Extensions.Loaders {
public class WebFormsExtensionsVirtualFile : VirtualFile {
private readonly Assembly _assembly;
private readonly DependencyDescriptor _dependencyDescriptor;
private readonly VirtualFile _actualFile;
public WebFormsExtensionsVirtualFile(string virtualPath, Assembly assembly, VirtualFile actualFile)
public WebFormsExtensionsVirtualFile(string virtualPath, DependencyDescriptor dependencyDescriptor, VirtualFile actualFile)
: base(virtualPath) {
_assembly = assembly;
_dependencyDescriptor = dependencyDescriptor;
_actualFile = actualFile;
}
@@ -26,7 +26,9 @@ namespace Orchard.Environment.Extensions.Loaders {
}
public override Stream Open() {
var reader = new StreamReader(_actualFile.Open());
using (var actualStream = _actualFile.Open()) {
var reader = new StreamReader(actualStream);
var memoryStream = new MemoryStream();
int length;
using (var writer = new StreamWriter(memoryStream)) {
@@ -35,7 +37,7 @@ namespace Orchard.Environment.Extensions.Loaders {
for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) {
if (!string.IsNullOrWhiteSpace(line) && !assemblyDirectiveAdded) {
line += string.Format("<%@ Assembly Name=\"{0}\"%>", _assembly);
line += GetAssemblyDirective();
assemblyDirectiveAdded = true;
}
@@ -44,8 +46,17 @@ namespace Orchard.Environment.Extensions.Loaders {
writer.Flush();
length = (int) memoryStream.Length;
}
var result = new MemoryStream(memoryStream.GetBuffer(), 0, length);
return result;
return new MemoryStream(memoryStream.GetBuffer(), 0, length);
}
}
private string GetAssemblyDirective() {
if (_dependencyDescriptor.IsFromBuildProvider) {
return string.Format("<%@ Assembly Src=\"{0}\"%>", _dependencyDescriptor.VirtualPath);
}
else {
return string.Format("<%@ Assembly Name=\"{0}\"%>", _dependencyDescriptor.ModuleName);
}
}
}
}

View File

@@ -4,17 +4,13 @@ using System.Web.Hosting;
using Orchard.FileSystems.Dependencies;
namespace Orchard.Environment.Extensions.Loaders {
public class WebFormsExtensionsVirtualPathProvider : VirtualPathProvider {
private IDependenciesFolder _dependenciesFolder;
private const string _prefix1 = "~/Modules/";
private const string _prefix2 = "/Modules/";
public class WebFormsExtensionsVirtualPathProvider : VirtualPathProvider, ICustomVirtualPathProvider {
private readonly IDependenciesFolder _dependenciesFolder;
private readonly string[] _prefixes = { "~/Modules/", "/Modules/" };
private readonly string[] _extensions = { ".ascx", ".aspx", ".master" };
public WebFormsExtensionsVirtualPathProvider() {
}
protected override void Initialize() {
base.Initialize();
_dependenciesFolder = new DefaultDependenciesFolder(new DefaultVirtualPathProvider());
public WebFormsExtensionsVirtualPathProvider(IDependenciesFolder dependenciesFolder) {
_dependenciesFolder = dependenciesFolder;
}
public override bool DirectoryExists(string virtualDir) {
@@ -28,11 +24,11 @@ namespace Orchard.Environment.Extensions.Loaders {
public override VirtualFile GetFile(string virtualPath) {
var actualFile = Previous.GetFile(virtualPath);
var prefix = PrefixMatch(virtualPath);
var prefix = PrefixMatch(virtualPath, _prefixes);
if (prefix == null)
return actualFile;
var extension = ExtensionMatch(virtualPath, ".ascx", ".aspx", ".master");
var extension = ExtensionMatch(virtualPath, _extensions);
if (extension == null)
return actualFile;
@@ -42,13 +38,13 @@ namespace Orchard.Environment.Extensions.Loaders {
// 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 assembly = _dependenciesFolder.LoadAssembly(moduleName);
if (assembly == null)
var dependencyDescriptor = _dependenciesFolder.GetDescriptor(moduleName);
if (dependencyDescriptor == null)
return actualFile;
// Yes: we need to wrap the VirtualFile to add the <%@ Assembly Name=".."%> directive
// in the content.
return new WebFormsExtensionsVirtualFile(virtualPath, assembly, actualFile);
return new WebFormsExtensionsVirtualFile(virtualPath, dependencyDescriptor, actualFile);
}
private string ModuleMatch(string virtualPath, string prefix) {
@@ -65,13 +61,13 @@ namespace Orchard.Environment.Extensions.Loaders {
.FirstOrDefault(e => virtualPath.EndsWith(e, StringComparison.OrdinalIgnoreCase));
}
private string PrefixMatch(string virtualPath) {
if (virtualPath.StartsWith(_prefix1))
return _prefix1;
if (virtualPath.StartsWith(_prefix2))
return _prefix2;
return null;
private string PrefixMatch(string virtualPath, params string[] prefixes) {
return prefixes
.FirstOrDefault(p => virtualPath.StartsWith(p, StringComparison.OrdinalIgnoreCase));
}
VirtualPathProvider ICustomVirtualPathProvider.Instance {
get { return this; }
}
}
}

View File

@@ -0,0 +1,7 @@
using System.Web.Hosting;
namespace Orchard.Environment {
public interface ICustomVirtualPathProvider {
VirtualPathProvider Instance { get; }
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Web.Hosting;
@@ -33,6 +34,7 @@ namespace Orchard.Environment {
builder.RegisterType<DefaultCacheHolder>().As<ICacheHolder>().SingleInstance();
builder.RegisterType<DefaultHostEnvironment>().As<IHostEnvironment>().SingleInstance();
builder.RegisterType<DefaultBuildManager>().As<IBuildManager>().SingleInstance();
builder.RegisterType<WebFormsExtensionsVirtualPathProvider>().As<ICustomVirtualPathProvider>().SingleInstance();
RegisterVolatileProvider<WebSiteFolder, IWebSiteFolder>(builder);
RegisterVolatileProvider<AppDataFolder, IAppDataFolder>(builder);
@@ -117,6 +119,15 @@ namespace Orchard.Environment {
public static IOrchardHost CreateHost(Action<ContainerBuilder> registrations) {
var container = CreateHostContainer(registrations);
//
// Register Virtual Path Providers
//
foreach (var vpp in container.Resolve<IEnumerable<ICustomVirtualPathProvider>>()) {
HostingEnvironment.RegisterVirtualPathProvider(vpp.Instance);
}
return container.Resolve<IOrchardHost>();
}
}

View File

@@ -1,23 +1,40 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web.Caching;
using System.Web.Hosting;
using System.Xml.Linq;
using Orchard.Caching;
using Orchard.Environment;
using Orchard.Environment.Extensions;
using Orchard.Environment.Topology;
namespace Orchard.FileSystems.Dependencies {
public class DependencyDescriptor {
public string ModuleName { get; set; }
public bool IsFromBuildProvider { get; set; }
public string VirtualPath { get; set; }
public string FileName { get; set; }
}
public interface IDependenciesFolder : IVolatileProvider {
void StoreAssemblyFile(string assemblyName, string assemblyFileName);
void StoreBuildProviderAssembly(string moduleName, string virtualPath, Assembly assembly);
void StorePrecompiledAssembly(string moduleName, string virtualPath);
DependencyDescriptor GetDescriptor(string moduleName);
Assembly LoadAssembly(string assemblyName);
}
public class DefaultDependenciesFolder : IDependenciesFolder {
private readonly string _prefix = Guid.NewGuid().ToString("n");
private const string _basePath = "~/App_Data/Dependencies";
private readonly IVirtualPathProvider _virtualPathProvider;
private readonly IExtensionManagerEvents _events;
public DefaultDependenciesFolder(IVirtualPathProvider virtualPathProvider) {
public DefaultDependenciesFolder(IVirtualPathProvider virtualPathProvider, IExtensionManagerEvents events) {
_virtualPathProvider = virtualPathProvider;
_events = events;
}
private string BasePath {
@@ -32,21 +49,60 @@ namespace Orchard.FileSystems.Dependencies {
}
}
public void StoreAssemblyFile(string assemblyName, string assemblyFileName) {
public void StoreBuildProviderAssembly(string moduleName, string virtualPath, Assembly assembly) {
_virtualPathProvider.CreateDirectory(BasePath);
var descriptor = new DependencyDescriptor {
ModuleName = moduleName,
IsFromBuildProvider = true,
VirtualPath = virtualPath,
FileName = assembly.Location
};
StoreDepencyInformation(descriptor);
#if true
var cacheDependency = HostingEnvironment.VirtualPathProvider.GetCacheDependency(
virtualPath,
new[] { virtualPath },
DateTime.UtcNow);
HostingEnvironment.Cache.Add(
_prefix + virtualPath,
moduleName,
cacheDependency,
Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration,
CacheItemPriority.NotRemovable,
(key, value, reason) => _events.ModuleChanged((string) value));
#endif
}
public void StorePrecompiledAssembly(string moduleName, string virtualPath) {
_virtualPathProvider.CreateDirectory(BasePath);
// Only store assembly if it's more recent that what we have stored already (if anything)
if (IsNewerAssembly(assemblyName, assemblyFileName)) {
var assemblyFileName = _virtualPathProvider.MapPath(virtualPath);
if (IsNewerAssembly(moduleName, assemblyFileName)) {
var destinationFileName = Path.GetFileName(assemblyFileName);
var destinationPath = _virtualPathProvider.MapPath(_virtualPathProvider.Combine(BasePath, destinationFileName));
File.Copy(assemblyFileName, destinationPath, true);
StoreDepencyInformation(assemblyName, destinationFileName);
StoreDepencyInformation(new DependencyDescriptor {
ModuleName = moduleName,
IsFromBuildProvider = false,
VirtualPath = virtualPath,
FileName = destinationFileName
});
}
}
private bool IsNewerAssembly(string assemblyName, string assemblyFileName) {
var dependency = ReadDependencies().SingleOrDefault(d => d.Name == assemblyName);
public DependencyDescriptor GetDescriptor(string moduleName) {
return ReadDependencies().SingleOrDefault(d => d.ModuleName == moduleName);
}
private bool IsNewerAssembly(string moduleName, string assemblyFileName) {
var dependency = ReadDependencies().SingleOrDefault(d => d.ModuleName == moduleName);
if (dependency == null) {
return true;
}
@@ -59,15 +115,15 @@ namespace Orchard.FileSystems.Dependencies {
return (File.GetLastWriteTimeUtc(existingFileName) < File.GetLastWriteTimeUtc(assemblyFileName));
}
private void StoreDepencyInformation(string name, string fileName) {
private void StoreDepencyInformation(DependencyDescriptor descriptor) {
var dependencies = ReadDependencies().ToList();
var dependency = dependencies.SingleOrDefault(d => d.Name == name);
if (dependency == null) {
dependency = new DependencyDescritpor { Name = name, FileName = fileName };
dependencies.Add(dependency);
int index = dependencies.FindIndex(d => d.ModuleName == descriptor.ModuleName);
if (index < 0) {
dependencies.Add(descriptor);
}
else {
dependencies[index] = descriptor;
}
dependency.FileName = fileName;
WriteDependencies(dependencies);
}
@@ -75,7 +131,7 @@ namespace Orchard.FileSystems.Dependencies {
public Assembly LoadAssembly(string assemblyName) {
_virtualPathProvider.CreateDirectory(BasePath);
var dependency = ReadDependencies().SingleOrDefault(d => d.Name == assemblyName);
var dependency = ReadDependencies().SingleOrDefault(d => d.ModuleName == assemblyName);
if (dependency == null)
return null;
@@ -85,30 +141,32 @@ namespace Orchard.FileSystems.Dependencies {
return Assembly.Load(Path.GetFileNameWithoutExtension(dependency.FileName));
}
private class DependencyDescritpor {
public string Name { get; set; }
public string FileName { get; set; }
}
private IEnumerable<DependencyDescritpor> ReadDependencies() {
private IEnumerable<DependencyDescriptor> ReadDependencies() {
if (!_virtualPathProvider.FileExists(PersistencePath))
return Enumerable.Empty<DependencyDescritpor>();
return Enumerable.Empty<DependencyDescriptor>();
using (var stream = _virtualPathProvider.OpenFile(PersistencePath)) {
XDocument document = XDocument.Load(stream);
return document
.Elements(ns("Dependencies"))
.Elements(ns("Dependency"))
.Select(e => new DependencyDescritpor { Name = e.Element("Name").Value, FileName = e.Element("FileName").Value })
.Select(e => new DependencyDescriptor {
ModuleName = e.Element("ModuleName").Value,
VirtualPath = e.Element("VirtualPath").Value,
FileName = e.Element("FileName").Value,
IsFromBuildProvider = bool.Parse(e.Element("IsFromBuildProvider").Value)
})
.ToList();
}
}
private void WriteDependencies(IEnumerable<DependencyDescritpor> dependencies) {
private void WriteDependencies(IEnumerable<DependencyDescriptor> dependencies) {
var document = new XDocument();
document.Add(new XElement(ns("Dependencies")));
var elements = dependencies.Select(d => new XElement("Dependency",
new XElement(ns("Name"), d.Name),
new XElement(ns("ModuleName"), d.ModuleName),
new XElement(ns("VirtualPath"), d.VirtualPath),
new XElement(ns("IsFromBuildProvider"), d.IsFromBuildProvider),
new XElement(ns("FileName"), d.FileName)));
document.Root.Add(elements);

View File

@@ -341,8 +341,11 @@
<Compile Include="Data\DataModule.cs" />
<Compile Include="Data\Orderable.cs" />
<Compile Include="Environment\DefaultOrchardShell.cs" />
<Compile Include="Environment\Extensions\Loaders\MergedReadOnlyStreams.cs" />
<Compile Include="Environment\ICustomVirtualPathProvider.cs" />
<Compile Include="Environment\Extensions\Loaders\WebFormsExtensionsVirtualPathProvider.cs" />
<Compile Include="Environment\Extensions\Loaders\WebFormsExtensionsVirtualFile.cs" />
<Compile Include="Environment\Extensions\IExtensionManagerEvents.cs" />
<Compile Include="Environment\IHostEnvironment.cs" />
<Compile Include="FileSystems\Dependencies\IDependenciesFolder.cs" />
<Compile Include="Environment\Extensions\Loaders\ProbingExtensionLoader.cs" />