mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Update WebFormVirtualPathProvider GetFileHash method
When a WebForm view file is served through the WebFormVirtualPathProvider, we override GetFileHash to include the virtual path of all the dependencies used to compile the form. For example, if we had a "Assembly Name=..." directive, we want to include the file hash of the assembly file, so that if the assembly file is updated with a newer version, the file hash of the WebForm view file will be different, which will tell ASP.NET the view file needs to be recompiled. --HG-- branch : dev
This commit is contained in:
@@ -94,7 +94,7 @@ namespace Orchard.Tests.Stubs {
|
||||
}
|
||||
|
||||
public void CreateDirectory(string path) {
|
||||
var entry = _fileSystem.CreateDirectoryEntry(path);
|
||||
_fileSystem.CreateDirectoryEntry(path);
|
||||
}
|
||||
|
||||
public IVolatileToken WhenPathChanges(string path) {
|
||||
@@ -105,6 +105,10 @@ namespace Orchard.Tests.Stubs {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetVirtualPath(string path) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public DateTime GetLastWriteTimeUtc(string path) {
|
||||
var entry = _fileSystem.GetFileEntry(path);
|
||||
if (entry == null)
|
||||
|
@@ -4,6 +4,7 @@ using System.Reflection;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
using Orchard.FileSystems.VirtualPath;
|
||||
using Orchard.Logging;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Loaders {
|
||||
public class AreaExtensionLoader : ExtensionLoaderBase {
|
||||
@@ -11,8 +12,11 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
|
||||
public AreaExtensionLoader(IDependenciesFolder dependenciesFolder) {
|
||||
_dependenciesFolder = dependenciesFolder;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public override int Order { get { return 50; } }
|
||||
|
||||
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
@@ -30,6 +34,7 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
|
||||
if (dependency != null && dependency.LoaderName == this.Name) {
|
||||
Logger.Information("Loading extension \"{0}\"", dependency.Name);
|
||||
|
||||
var assembly = Assembly.Load("Orchard.Web");
|
||||
|
||||
|
@@ -5,6 +5,7 @@ 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>
|
||||
@@ -15,8 +16,11 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
|
||||
public CoreExtensionLoader(IDependenciesFolder dependenciesFolder) {
|
||||
_dependenciesFolder = dependenciesFolder;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public override int Order { get { return 10; } }
|
||||
|
||||
public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) {
|
||||
@@ -34,6 +38,7 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
public override ExtensionEntry Load(ExtensionDescriptor descriptor) {
|
||||
var dependency = _dependenciesFolder.GetDescriptor(descriptor.Name);
|
||||
if (dependency != null && dependency.LoaderName == this.Name) {
|
||||
Logger.Information("Loading extension \"{0}\"", dependency.Name);
|
||||
|
||||
var assembly = Assembly.Load("Orchard.Core");
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
@@ -30,10 +31,14 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
|
||||
public override int Order { get { return 100; } }
|
||||
|
||||
public override string GetAssemblyDirective(DependencyDescriptor dependency) {
|
||||
public override string GetWebFormAssemblyDirective(DependencyDescriptor dependency) {
|
||||
return string.Format("<%@ Assembly Src=\"{0}\"%>", dependency.VirtualPath);
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetWebFormVirtualDependencies(DependencyDescriptor dependency) {
|
||||
yield return 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);
|
||||
@@ -75,6 +80,7 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
if (dependency != null && dependency.LoaderName == this.Name) {
|
||||
|
||||
var assembly = _buildManager.GetCompiledAssembly(dependency.VirtualPath);
|
||||
Logger.Information("Loading extension \"{0}\": assembly name=\"{1}\"", dependency.Name, assembly.GetName().Name);
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = descriptor,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
@@ -17,16 +17,16 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
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 virtual string GetWebFormAssemblyDirective(DependencyDescriptor dependency) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool IsAssemblyLoaded(string moduleName) {
|
||||
public virtual IEnumerable<string> GetWebFormVirtualDependencies(DependencyDescriptor dependency) {
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
protected static bool IsAssemblyLoaded(string moduleName) {
|
||||
return AppDomain.CurrentDomain.GetAssemblies().Any(a => a.GetName().Name == moduleName);
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
@@ -24,6 +25,7 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
|
||||
void Monitor(ExtensionDescriptor extension, Action<IVolatileToken> monitor);
|
||||
|
||||
string GetAssemblyDirective(DependencyDescriptor dependency);
|
||||
string GetWebFormAssemblyDirective(DependencyDescriptor dependency);
|
||||
IEnumerable<string> GetWebFormVirtualDependencies(DependencyDescriptor dependency);
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Orchard.Caching;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
@@ -29,10 +30,14 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
|
||||
public override int Order { get { return 30; } }
|
||||
|
||||
public override string GetAssemblyDirective(DependencyDescriptor dependency) {
|
||||
public override string GetWebFormAssemblyDirective(DependencyDescriptor dependency) {
|
||||
return string.Format("<%@ Assembly Name=\"{0}\"%>", dependency.Name);
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetWebFormVirtualDependencies(DependencyDescriptor dependency) {
|
||||
yield return _assemblyProbingFolder.GetAssemblyVirtualPath(dependency.Name);
|
||||
}
|
||||
|
||||
public override void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency) {
|
||||
if (_assemblyProbingFolder.AssemblyExists(dependency.Name)) {
|
||||
ctx.DeleteActions.Add(() => _assemblyProbingFolder.DeleteAssembly(dependency.Name));
|
||||
@@ -52,7 +57,7 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
bool copyAssembly =
|
||||
!_assemblyProbingFolder.AssemblyExists(extension.Name) ||
|
||||
File.GetLastWriteTimeUtc(sourceFileName) > _assemblyProbingFolder.GetAssemblyDateTimeUtc(extension.Name);
|
||||
|
||||
|
||||
if (copyAssembly) {
|
||||
ctx.CopyActions.Add(() => _assemblyProbingFolder.StoreAssembly(extension.Name, sourceFileName));
|
||||
// We need to restart the appDomain if the assembly is loaded
|
||||
@@ -104,6 +109,8 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
if (assembly == null)
|
||||
return null;
|
||||
|
||||
Logger.Information("Loading extension \"{0}\"", dependency.Name);
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = descriptor,
|
||||
Assembly = assembly,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
using Orchard.Logging;
|
||||
@@ -22,10 +23,14 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
|
||||
public override int Order { get { return 40; } }
|
||||
|
||||
public override string GetAssemblyDirective(DependencyDescriptor dependency) {
|
||||
public override string GetWebFormAssemblyDirective(DependencyDescriptor dependency) {
|
||||
return string.Format("<%@ Assembly Name=\"{0}\"%>", dependency.Name);
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetWebFormVirtualDependencies(DependencyDescriptor dependency) {
|
||||
yield return _assemblyProbingFolder.GetAssemblyVirtualPath(dependency.Name);
|
||||
}
|
||||
|
||||
public override void ExtensionRemoved(ExtensionLoadingContext ctx, DependencyDescriptor dependency) {
|
||||
if (_assemblyProbingFolder.AssemblyExists(dependency.Name)) {
|
||||
ctx.DeleteActions.Add(() => _assemblyProbingFolder.DeleteAssembly(dependency.Name));
|
||||
@@ -74,6 +79,8 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
if (assembly == null)
|
||||
return null;
|
||||
|
||||
Logger.Information("Loading extension \"{0}\"", dependency.Name);
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = descriptor,
|
||||
Assembly = assembly,
|
||||
|
@@ -6,6 +6,7 @@ using System.Web.Hosting;
|
||||
using Orchard.Environment.Extensions.Models;
|
||||
using Orchard.FileSystems.Dependencies;
|
||||
using Orchard.FileSystems.VirtualPath;
|
||||
using Orchard.Logging;
|
||||
|
||||
namespace Orchard.Environment.Extensions.Loaders {
|
||||
/// <summary>
|
||||
@@ -18,8 +19,11 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
public ReferencedExtensionLoader(IDependenciesFolder dependenciesFolder, IVirtualPathProvider virtualPathProvider) {
|
||||
_dependenciesFolder = dependenciesFolder;
|
||||
_virtualPathProvider = virtualPathProvider;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public override int Order { get { return 20; } }
|
||||
|
||||
public override void ExtensionDeactivated(ExtensionLoadingContext ctx, bool isNewExtension, ExtensionDescriptor extension) {
|
||||
@@ -59,6 +63,11 @@ namespace Orchard.Environment.Extensions.Loaders {
|
||||
.OfType<Assembly>()
|
||||
.FirstOrDefault(x => x.GetName().Name == descriptor.Name);
|
||||
|
||||
if (assembly == null)
|
||||
return null;
|
||||
|
||||
Logger.Information("Loading extension \"{0}\"", dependency.Name);
|
||||
|
||||
return new ExtensionEntry {
|
||||
Descriptor = descriptor,
|
||||
Assembly = assembly,
|
||||
|
@@ -85,8 +85,11 @@ namespace Orchard.FileSystems.AppData {
|
||||
}
|
||||
|
||||
public IVolatileToken WhenPathChanges(string path) {
|
||||
var replace = Combine(AppDataPath, path);
|
||||
return _virtualPathMonitor.WhenPathChanges(replace);
|
||||
return _virtualPathMonitor.WhenPathChanges(GetVirtualPath(path));
|
||||
}
|
||||
|
||||
public string GetVirtualPath(string path) {
|
||||
return Combine(AppDataPath, path);
|
||||
}
|
||||
|
||||
public void CreateFile(string path, string content) {
|
||||
|
@@ -30,5 +30,6 @@ namespace Orchard.FileSystems.AppData {
|
||||
IVolatileToken WhenPathChanges(string path);
|
||||
|
||||
string MapPath(string path);
|
||||
string GetVirtualPath(string path);
|
||||
}
|
||||
}
|
@@ -21,6 +21,14 @@ namespace Orchard.FileSystems.Dependencies {
|
||||
return _appDataFolder.GetFileLastWriteTimeUtc(path);
|
||||
}
|
||||
|
||||
public string GetAssemblyVirtualPath(string moduleName) {
|
||||
var path = PrecompiledAssemblyPath(moduleName);
|
||||
if (!_appDataFolder.FileExists(path))
|
||||
return null;
|
||||
|
||||
return _appDataFolder.GetVirtualPath(path);
|
||||
}
|
||||
|
||||
public Assembly LoadAssembly(string moduleName) {
|
||||
var path = PrecompiledAssemblyPath(moduleName);
|
||||
if (!_appDataFolder.FileExists(path))
|
||||
|
@@ -22,6 +22,11 @@ namespace Orchard.FileSystems.Dependencies {
|
||||
/// </summary>
|
||||
DateTime GetAssemblyDateTimeUtc(string moduleName);
|
||||
|
||||
/// <summary>
|
||||
/// Return the virtual path of the assembly (optional)
|
||||
/// </summary>
|
||||
string GetAssemblyVirtualPath(string moduleName);
|
||||
|
||||
/// <summary>
|
||||
/// Load the assembly corresponding to "moduleName" if the assembly file
|
||||
/// is present in the folder.
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web.Hosting;
|
||||
@@ -30,16 +31,73 @@ namespace Orchard.FileSystems.Dependencies {
|
||||
return Previous.FileExists(virtualPath);
|
||||
}
|
||||
|
||||
public override string GetFileHash(string virtualPath, System.Collections.IEnumerable virtualPathDependencies) {
|
||||
var result = GetFileHashWorker(virtualPath, virtualPathDependencies);
|
||||
Logger.Information("GetFileHash(\"{0}\"): {1}", virtualPath, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private string GetFileHashWorker(string virtualPath, IEnumerable virtualPathDependencies) {
|
||||
// We override the "GetFileHash()" behavior to take into account the dependencies folder
|
||||
// state. This ensures that if any dependency changes, ASP.NET will recompile views that
|
||||
// have been customized to include custom assembly directives.
|
||||
var file = GetModuleVirtualOverride(virtualPath) ?? GetThemeVirtualOverride(virtualPath);
|
||||
if (file == null) {
|
||||
return base.GetFileHash(virtualPath, virtualPathDependencies);
|
||||
}
|
||||
|
||||
var dependencies =
|
||||
virtualPathDependencies
|
||||
.OfType<string>()
|
||||
.Concat(file.Loaders.SelectMany(dl => dl.Loader.GetWebFormVirtualDependencies(dl.Descriptor)));
|
||||
|
||||
if (Logger.IsEnabled(LogLevel.Debug)) {
|
||||
Logger.Debug("GetFilHash(\"{0}\") - virtual path dependencies:", virtualPath);
|
||||
foreach(var dependency in dependencies) {
|
||||
Logger.Debug(" Dependency: \"{0}\"", dependency);
|
||||
}
|
||||
}
|
||||
return base.GetFileHash(virtualPath, dependencies);
|
||||
}
|
||||
|
||||
public override VirtualFile GetFile(string virtualPath) {
|
||||
Logger.Debug("GetFile(\"{0}\")", virtualPath);
|
||||
var actualFile = Previous.GetFile(virtualPath);
|
||||
|
||||
return
|
||||
GetModuleCustomVirtualFile(virtualPath, actualFile) ??
|
||||
GetThemeCustomVirtualFile(virtualPath, actualFile) ??
|
||||
actualFile;
|
||||
return GetModuleCustomVirtualFile(virtualPath, actualFile) ??
|
||||
GetThemeCustomVirtualFile(virtualPath, actualFile) ??
|
||||
actualFile;
|
||||
}
|
||||
|
||||
private VirtualFile GetModuleCustomVirtualFile(string virtualPath, VirtualFile actualFile) {
|
||||
var file = GetModuleVirtualOverride(virtualPath);
|
||||
if (file == null)
|
||||
return null;
|
||||
|
||||
if (Logger.IsEnabled(LogLevel.Debug)) {
|
||||
Logger.Debug("Virtual file from module \"{0}\" served with specific assembly directive:", file.ModuleName);
|
||||
Logger.Debug(" Virtual path: {0}", virtualPath);
|
||||
Logger.Debug(" Assembly directive: {0}", file.Directive);
|
||||
}
|
||||
|
||||
return new WebFormsExtensionsVirtualFile(virtualPath, actualFile, file.Directive);
|
||||
}
|
||||
|
||||
private VirtualFile GetThemeCustomVirtualFile(string virtualPath, VirtualFile actualFile) {
|
||||
var file = GetThemeVirtualOverride(virtualPath);
|
||||
if (file == null)
|
||||
return null;
|
||||
|
||||
if (Logger.IsEnabled(LogLevel.Debug)) {
|
||||
Logger.Debug("Virtual file from theme served with specific assembly directive:");
|
||||
Logger.Debug(" Virtual path: {0}", virtualPath);
|
||||
Logger.Debug(" Assembly directive: {0}", file.Directive);
|
||||
}
|
||||
|
||||
return new WebFormsExtensionsVirtualFile(virtualPath, actualFile, file.Directive);
|
||||
}
|
||||
|
||||
private VirtualFileOverride GetModuleVirtualOverride(string virtualPath) {
|
||||
var prefix = PrefixMatch(virtualPath, _modulesPrefixes);
|
||||
if (prefix == null)
|
||||
return null;
|
||||
@@ -60,20 +118,18 @@ namespace Orchard.FileSystems.Dependencies {
|
||||
if (loader == null)
|
||||
return null;
|
||||
|
||||
var directive = loader.GetAssemblyDirective(dependencyDescriptor);
|
||||
var directive = loader.GetWebFormAssemblyDirective(dependencyDescriptor);
|
||||
if (string.IsNullOrEmpty(directive))
|
||||
return null;
|
||||
|
||||
if (Logger.IsEnabled(LogLevel.Debug)) {
|
||||
Logger.Debug("Virtual file from module \"{0}\" served with specific assembly directive:", moduleName);
|
||||
Logger.Debug(" Virtual path: {0}", virtualPath);
|
||||
Logger.Debug(" Assembly directive: {0}", directive);
|
||||
}
|
||||
|
||||
return new WebFormsExtensionsVirtualFile(virtualPath, actualFile, directive);
|
||||
return new VirtualFileOverride {
|
||||
ModuleName = moduleName,
|
||||
Directive = directive,
|
||||
Loaders = new[] { new DependencyLoader { Loader = loader, Descriptor = dependencyDescriptor } }
|
||||
};
|
||||
}
|
||||
|
||||
private VirtualFile GetThemeCustomVirtualFile(string virtualPath, VirtualFile actualFile) {
|
||||
private VirtualFileOverride GetThemeVirtualOverride(string virtualPath) {
|
||||
var prefix = PrefixMatch(virtualPath, _themesPrefixes);
|
||||
if (prefix == null)
|
||||
return null;
|
||||
@@ -82,24 +138,23 @@ namespace Orchard.FileSystems.Dependencies {
|
||||
if (extension == null)
|
||||
return null;
|
||||
|
||||
string directive = _dependenciesFolder.LoadDescriptors().Aggregate("", (s, desc) => {
|
||||
var loader = _loaders.Where(l => l.Name == desc.LoaderName).FirstOrDefault();
|
||||
if (loader == null)
|
||||
return s;
|
||||
else {
|
||||
return s + loader.GetAssemblyDirective(desc);
|
||||
}});
|
||||
var loaders = _loaders
|
||||
.SelectMany(loader => _dependenciesFolder
|
||||
.LoadDescriptors()
|
||||
.Where(d => d.LoaderName == loader.Name),
|
||||
(loader, desr) => new DependencyLoader { Loader = loader, Descriptor = desr });
|
||||
|
||||
var directive = loaders
|
||||
.Aggregate("", (s, dl) => s + dl.Loader.GetWebFormAssemblyDirective(dl.Descriptor));
|
||||
|
||||
if (string.IsNullOrEmpty(directive))
|
||||
return null;
|
||||
|
||||
if (Logger.IsEnabled(LogLevel.Debug)) {
|
||||
Logger.Debug("Virtual file from theme served with specific assembly directive:");
|
||||
Logger.Debug(" Virtual path: {0}", virtualPath);
|
||||
Logger.Debug(" Assembly directive: {0}", directive);
|
||||
}
|
||||
|
||||
return new WebFormsExtensionsVirtualFile(virtualPath, actualFile, directive);
|
||||
return new VirtualFileOverride {
|
||||
ModuleName = "",
|
||||
Directive = directive,
|
||||
Loaders = loaders
|
||||
};
|
||||
}
|
||||
|
||||
private string ModuleMatch(string virtualPath, string prefix) {
|
||||
@@ -124,5 +179,16 @@ namespace Orchard.FileSystems.Dependencies {
|
||||
VirtualPathProvider ICustomVirtualPathProvider.Instance {
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
private class VirtualFileOverride {
|
||||
public string ModuleName { get; set; }
|
||||
public string Directive { get; set; }
|
||||
public IEnumerable<DependencyLoader> Loaders { get; set; }
|
||||
}
|
||||
|
||||
private class DependencyLoader {
|
||||
public IExtensionLoader Loader { get; set; }
|
||||
public DependencyDescriptor Descriptor { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user