diff --git a/src/Orchard.Tests/Environment/Loaders/DynamicExtensionLoaderTests.cs b/src/Orchard.Tests/Environment/Loaders/DynamicExtensionLoaderTests.cs index caa3a831b..698986817 100644 --- a/src/Orchard.Tests/Environment/Loaders/DynamicExtensionLoaderTests.cs +++ b/src/Orchard.Tests/Environment/Loaders/DynamicExtensionLoaderTests.cs @@ -6,6 +6,7 @@ using Moq; using NUnit.Framework; using Orchard.Caching; using Orchard.Environment; +using Orchard.Environment.Configuration; using Orchard.Environment.Extensions.Compilers; using Orchard.Environment.Extensions.Loaders; using Orchard.FileSystems.AppData; @@ -151,8 +152,9 @@ namespace Orchard.Tests.Environment.Loaders { IHostEnvironment hostEnvironment, IAssemblyProbingFolder assemblyProbingFolder, IDependenciesFolder dependenciesFolder, - IProjectFileParser projectFileParser) - : base(buildManager, virtualPathProvider, virtualPathMonitor, hostEnvironment, assemblyProbingFolder, dependenciesFolder, projectFileParser) {} + IProjectFileParser projectFileParser, + ExtensionLocations extensionLocations) + : base(buildManager, virtualPathProvider, virtualPathMonitor, hostEnvironment, assemblyProbingFolder, dependenciesFolder, projectFileParser, extensionLocations) {} public IEnumerable GetDependenciesAccessor(string projectPath) { return GetDependencies(projectPath); diff --git a/src/Orchard.Web/Modules/Orchard.Redis/Extensions/RedisConnectionExtensions.cs b/src/Orchard.Web/Modules/Orchard.Redis/Extensions/RedisConnectionExtensions.cs new file mode 100644 index 000000000..cb210aa9f --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.Redis/Extensions/RedisConnectionExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StackExchange.Redis; + +namespace Orchard.Redis.Extensions { + public static class RedisConnectionExtensions { + public static void KeyDeleteWithPrefix(this IConnectionMultiplexer connection, string prefix) { + var keys = GetKeys(connection, prefix); + var database = connection.GetDatabase(); + database.KeyDelete(keys.Select(key => (RedisKey)key).ToArray()); + } + + public static int KeyCount(this IConnectionMultiplexer connection, string prefix) { + return GetKeys(connection, prefix).Count(); + } + + // CYCLES EACH ENDPOINT FOR A CONNECTION SO KEYS ARE FETECHED IN A REDIS CLUSTER CONFIGURATION + // STACKEXCHANGECLIENT .KEYS WILL PERFORM SCAN ON REDIS SERVERS THAT SUPPORT IT + public static IEnumerable GetKeys(this IConnectionMultiplexer connection, string prefix) { + if (connection == null) { + throw new ArgumentException("Connection cannot be null", "connection"); + } + + if (string.IsNullOrWhiteSpace(prefix)) { + throw new ArgumentException("Prefix cannot be empty", "database"); + } + + var keys = new List(); + var databaseId = connection.GetDatabase().Database; + + foreach (var endPoint in connection.GetEndPoints()) { + var server = connection.GetServer(endPoint); + keys.AddRange(server.Keys(pattern: prefix, database: databaseId).Select(x => x.ToString())); + } + + return keys.Distinct(); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Web.config b/src/Orchard.Web/Web.config index 2c2a8dcaf..2dc991d09 100644 --- a/src/Orchard.Web/Web.config +++ b/src/Orchard.Web/Web.config @@ -13,6 +13,14 @@ + + + + + diff --git a/src/Orchard/Environment/Configuration/ExtensionLocations.cs b/src/Orchard/Environment/Configuration/ExtensionLocations.cs new file mode 100644 index 000000000..4701b0b62 --- /dev/null +++ b/src/Orchard/Environment/Configuration/ExtensionLocations.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; + +namespace Orchard.Environment.Configuration { + public class ExtensionLocations : IDependency { + public string[] CoreLocations; + public string[] ModuleLocations; + public string[] ThemeLocations; + public string[] CommonLocations; // locations that should not be common and not related to the current tenant + public string[] ModuleAndThemeLocations; + public string[] ExtensionsVirtualPathPrefixes; // Modules+Themes (no core) + + public ExtensionLocations() { + Init(new DefaultAppConfigurationAccessor()); + } + + // This optional constructor can be used to create an environment that takes AppConfigurations from IAppConfigurationAccessor instead of from the global ConfigurationManager.AppSettings + public ExtensionLocations(IAppConfigurationAccessor appConfigurationAccessor) { + Init(appConfigurationAccessor); + } + + public virtual void Init(IAppConfigurationAccessor appConfigurationAccessor) { + CoreLocations = new string[] {"~/Core"}; + ModuleLocations = GetConfigPaths(appConfigurationAccessor, "Modules", "~/Modules"); + ThemeLocations = GetConfigPaths(appConfigurationAccessor, "Themes", "~/Themes" ); + CommonLocations = GetConfigPaths(appConfigurationAccessor, "Common", "~/Media") + .Concat(ThemeLocations) + .Concat(ModuleLocations) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + ModuleAndThemeLocations = ModuleLocations + .Concat(ThemeLocations) + .Distinct(StringComparer.CurrentCultureIgnoreCase) + .ToArray(); + ExtensionsVirtualPathPrefixes = ModuleAndThemeLocations + .Select(l=>l+"/") + .OrderBy(l=>l.Count(c=>c=='/')) + .Reverse() + .ToArray(); + } + + /// + /// Return module from path that is constructed as Location/Module/relative/path/in/module + /// Path prefixes is expected as list of Location/ (location+trailing "/") + /// + /// Extension locations can contain '/' so they are matched with deeper path first + /// + /// + /// the module - or null of not found + public static string ModuleMatch(string virtualPath, IEnumerable pathPrefixes) { + foreach(string prefix in pathPrefixes) { + if(virtualPath.StartsWith(prefix)) { + int index = virtualPath.IndexOf('/', prefix.Length, virtualPath.Length - prefix.Length); + if (index <= 0) + continue; + var moduleName = virtualPath.Substring(prefix.Length, index - prefix.Length); + return (string.IsNullOrEmpty(moduleName) ? null : moduleName); + } + } + return null; + } + + /// + /// Return module from path that is constructed as ExtensionLocation/Module/relative/path/in/module + /// + /// Extension locations can contain '/' so they are matched with deeper path first + /// + /// + /// the module - or null of not found + public string ExtensionsModuleMatch(string virtualPath) { + ModuleMatch(virtualPath, ExtensionsVirtualPathPrefixes); + return null; + } + + /// + /// Return true if the virtual path starts with any of the prefixes + /// + public static bool PrefixMatch(string virtualPath, IEnumerable pathPrefixes) { + return pathPrefixes.Any(p => virtualPath.StartsWith(p)); + } + + public bool ExtensionsPrefixMatch(string virtualPath) { + return PrefixMatch(virtualPath, ExtensionsVirtualPathPrefixes); + } + + /// + /// Get list of comma separated paths from web.config appSettings + /// Also return the default path + /// + static string[] GetConfigPaths(IAppConfigurationAccessor appConfigurationAccessor, string key, string defaultPath) { + char[] delim = { ',' }; + string configuration = appConfigurationAccessor.GetConfiguration(key) ?? ""; + return configuration.Split(delim, StringSplitOptions.RemoveEmptyEntries).Concat(new string[] { defaultPath }).Select(s => s.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(); + } + + private class DefaultAppConfigurationAccessor : IAppConfigurationAccessor { + public DefaultAppConfigurationAccessor() {} + + public string GetConfiguration(string name) { + return ConfigurationManager.AppSettings[name]; + } + } + + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/Extensions/ExtensionMonitoringCoordinator.cs b/src/Orchard/Environment/Extensions/ExtensionMonitoringCoordinator.cs index ba6b1f468..c51a94566 100644 --- a/src/Orchard/Environment/Extensions/ExtensionMonitoringCoordinator.cs +++ b/src/Orchard/Environment/Extensions/ExtensionMonitoringCoordinator.cs @@ -41,12 +41,14 @@ namespace Orchard.Environment.Extensions { } public void MonitorExtensionsWork(Action monitor) { + var locations = _extensionManager.AvailableExtensions().Select(e => e.Location).Distinct(StringComparer.InvariantCultureIgnoreCase); + Logger.Information("Start monitoring extension files..."); // Monitor add/remove of any module/theme - Logger.Debug("Monitoring virtual path \"{0}\"", "~/Modules"); - monitor(_virtualPathMonitor.WhenPathChanges("~/Modules")); - Logger.Debug("Monitoring virtual path \"{0}\"", "~/Themes"); - monitor(_virtualPathMonitor.WhenPathChanges("~/Themes")); + foreach(string location in locations) { + Logger.Debug("Monitoring virtual path \"{0}\"", location); + monitor(_virtualPathMonitor.WhenPathChanges(location)); + } // Give loaders a chance to monitor any additional changes var extensions = _extensionManager.AvailableExtensions().Where(d => DefaultExtensionTypes.IsModule(d.ExtensionType) || DefaultExtensionTypes.IsTheme(d.ExtensionType)).ToList(); diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs index 54f5386d2..0b1c54c88 100644 --- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Reflection; using Orchard.Caching; +using Orchard.Environment.Configuration; using Orchard.Environment.Extensions.Compilers; using Orchard.Environment.Extensions.Models; using Orchard.FileSystems.Dependencies; @@ -13,7 +14,7 @@ using Orchard.Utility.Extensions; namespace Orchard.Environment.Extensions.Loaders { public class DynamicExtensionLoader : ExtensionLoaderBase { - public static readonly string[] ExtensionsVirtualPathPrefixes = { "~/Modules/", "~/Themes/" }; + private readonly string[] _extensionsVirtualPathPrefixes; // { "~/Modules/", "~/Themes/" }; private readonly IBuildManager _buildManager; private readonly IVirtualPathProvider _virtualPathProvider; @@ -31,7 +32,8 @@ namespace Orchard.Environment.Extensions.Loaders { IHostEnvironment hostEnvironment, IAssemblyProbingFolder assemblyProbingFolder, IDependenciesFolder dependenciesFolder, - IProjectFileParser projectFileParser) + IProjectFileParser projectFileParser, + ExtensionLocations extensionLocations) : base(dependenciesFolder) { _buildManager = buildManager; @@ -43,6 +45,8 @@ namespace Orchard.Environment.Extensions.Loaders { _dependenciesFolder = dependenciesFolder; Logger = NullLogger.Instance; + + _extensionsVirtualPathPrefixes = extensionLocations.ModuleAndThemeLocations; } public ILogger Logger { get; set; } @@ -209,8 +213,8 @@ namespace Orchard.Environment.Extensions.Loaders { } private void AddDependencies(string projectPath, HashSet currentSet) { - // Skip files from locations other than "~/Modules" and "~/Themes" - if (string.IsNullOrEmpty(PrefixMatch(projectPath, ExtensionsVirtualPathPrefixes))) { + // Skip files from locations other than "~/Modules" and "~/Themes" etc. + if (string.IsNullOrEmpty(PrefixMatch(projectPath, _extensionsVirtualPathPrefixes))) { return; } diff --git a/src/Orchard/Environment/Extensions/Loaders/RawThemeExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/RawThemeExtensionLoader.cs index f57894a5a..3d33abcc1 100644 --- a/src/Orchard/Environment/Extensions/Loaders/RawThemeExtensionLoader.cs +++ b/src/Orchard/Environment/Extensions/Loaders/RawThemeExtensionLoader.cs @@ -25,7 +25,8 @@ namespace Orchard.Environment.Extensions.Loaders { if (Disabled) return null; - if (descriptor.Location == "~/Themes") { + // Temporary - theme without own project should be under ~/themes + if (descriptor.Location.StartsWith("~/Themes",StringComparison.InvariantCultureIgnoreCase)) { string projectPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Id, descriptor.Id + ".csproj"); @@ -44,7 +45,7 @@ namespace Orchard.Environment.Extensions.Loaders { return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this, - VirtualPath = "~/Theme/" + descriptor.Id, + VirtualPath = descriptor.VirtualPath, VirtualPathDependencies = Enumerable.Empty(), }; } diff --git a/src/Orchard/Environment/Extensions/Models/ExtensionDescriptor.cs b/src/Orchard/Environment/Extensions/Models/ExtensionDescriptor.cs index 68aeb59c5..1c86d8d93 100644 --- a/src/Orchard/Environment/Extensions/Models/ExtensionDescriptor.cs +++ b/src/Orchard/Environment/Extensions/Models/ExtensionDescriptor.cs @@ -16,6 +16,8 @@ namespace Orchard.Environment.Extensions.Models { /// public string Id { get; set; } + public string VirtualPath { get { return Location + "/" + Id; } } + /// /// The extension type. /// diff --git a/src/Orchard/Environment/OrchardStarter.cs b/src/Orchard/Environment/OrchardStarter.cs index 79736c0be..60253139f 100644 --- a/src/Orchard/Environment/OrchardStarter.cs +++ b/src/Orchard/Environment/OrchardStarter.cs @@ -35,11 +35,18 @@ using Orchard.Mvc.ViewEngines.ThemeAwareness; using Orchard.Services; using Orchard.WebApi; using Orchard.WebApi.Filters; +using System.Linq; +using System.Web.Configuration; namespace Orchard.Environment { public static class OrchardStarter { public static IContainer CreateHostContainer(Action registrations) { + ExtensionLocations extensionLocations = new ExtensionLocations(); + var builder = new ContainerBuilder(); + // Application paths and parameters + builder.RegisterInstance(extensionLocations); + builder.RegisterModule(new CollectionOrderModule()); builder.RegisterModule(new LoggingModule()); builder.RegisterModule(new EventsModule()); @@ -99,11 +106,11 @@ namespace Orchard.Environment { { builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance() - .WithParameter(new NamedParameter("paths", new[] { "~/Modules" })); + .WithParameter(new NamedParameter("paths", extensionLocations.ModuleLocations)); builder.RegisterType().As().SingleInstance() - .WithParameter(new NamedParameter("paths", new[] { "~/Core" })); + .WithParameter(new NamedParameter("paths", extensionLocations.CoreLocations)); builder.RegisterType().As().SingleInstance() - .WithParameter(new NamedParameter("paths", new[] { "~/Themes" })); + .WithParameter(new NamedParameter("paths", extensionLocations.ThemeLocations)); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); @@ -173,7 +180,6 @@ namespace Orchard.Environment { return container; } - private static void RegisterVolatileProvider(ContainerBuilder builder) where TService : IVolatileProvider { builder.RegisterType() .As() diff --git a/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs b/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs index ff6ef7357..1a4c685e8 100644 --- a/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs +++ b/src/Orchard/FileSystems/Dependencies/DynamicModuleVirtualPathProvider.cs @@ -4,6 +4,10 @@ using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Hosting; +using Orchard.Environment.Configuration; +using Orchard.Environment.Descriptor.Models; +using Orchard.Environment.Extensions; +using Orchard.Environment.Extensions.Folders; using Orchard.Environment.Extensions.Loaders; using Orchard.FileSystems.VirtualPath; using Orchard.Logging; @@ -15,10 +19,14 @@ namespace Orchard.FileSystems.Dependencies { /// public class DynamicModuleVirtualPathProvider : VirtualPathProvider, ICustomVirtualPathProvider { private readonly IExtensionDependenciesManager _extensionDependenciesManager; + private string[] _extensionsVirtualPathPrefixes; - public DynamicModuleVirtualPathProvider(IExtensionDependenciesManager extensionDependenciesManager) { + + public DynamicModuleVirtualPathProvider(IExtensionDependenciesManager extensionDependenciesManager, ExtensionLocations extensionLocations) { _extensionDependenciesManager = extensionDependenciesManager; Logger = NullLogger.Instance; + + _extensionsVirtualPathPrefixes = extensionLocations.ExtensionsVirtualPathPrefixes; } public ILogger Logger { get; set; } @@ -42,7 +50,7 @@ namespace Orchard.FileSystems.Dependencies { } private ActivatedExtensionDescriptor GetExtensionDescriptor(string virtualPath) { - var prefix = PrefixMatch(virtualPath, DynamicExtensionLoader.ExtensionsVirtualPathPrefixes); + var prefix = PrefixMatch(virtualPath, _extensionsVirtualPathPrefixes); if (prefix == null) return null; diff --git a/src/Orchard/Localization/Services/DefaultLocalizedStringManager.cs b/src/Orchard/Localization/Services/DefaultLocalizedStringManager.cs index e0a2a29de..ac99f93a8 100644 --- a/src/Orchard/Localization/Services/DefaultLocalizedStringManager.cs +++ b/src/Orchard/Localization/Services/DefaultLocalizedStringManager.cs @@ -17,8 +17,8 @@ namespace Orchard.Localization.Services { private readonly ISignals _signals; const string CoreLocalizationFilePathFormat = "~/Core/App_Data/Localization/{0}/orchard.core.po"; - const string ModulesLocalizationFilePathFormat = "~/Modules/{0}/App_Data/Localization/{1}/orchard.module.po"; - const string ThemesLocalizationFilePathFormat = "~/Themes/{0}/App_Data/Localization/{1}/orchard.theme.po"; + const string ModulesLocalizationFilePathFormat = "{0}/App_Data/Localization/{1}/orchard.module.po"; + const string ThemesLocalizationFilePathFormat = "{0}/App_Data/Localization/{1}/orchard.theme.po"; const string RootLocalizationFilePathFormat = "~/App_Data/Localization/{0}/orchard.root.po"; const string TenantLocalizationFilePathFormat = "~/App_Data/Sites/{0}/Localization/{1}/orchard.po"; @@ -124,7 +124,7 @@ namespace Orchard.Localization.Services { foreach (var module in _extensionManager.AvailableExtensions()) { if (DefaultExtensionTypes.IsModule(module.ExtensionType)) { - string modulePath = string.Format(ModulesLocalizationFilePathFormat, module.Id, culture); + string modulePath = string.Format(ModulesLocalizationFilePathFormat, module.VirtualPath, culture); text = _webSiteFolder.ReadFile(modulePath); if (text != null) { _localizationStreamParser.ParseLocalizationStream(text, translations, true); @@ -139,7 +139,7 @@ namespace Orchard.Localization.Services { foreach (var theme in _extensionManager.AvailableExtensions()) { if (DefaultExtensionTypes.IsTheme(theme.ExtensionType)) { - string themePath = string.Format(ThemesLocalizationFilePathFormat, theme.Id, culture); + string themePath = string.Format(ThemesLocalizationFilePathFormat, theme.VirtualPath, culture); text = _webSiteFolder.ReadFile(themePath); if (text != null) { _localizationStreamParser.ParseLocalizationStream(text, translations, true); diff --git a/src/Orchard/Mvc/ViewEngines/IViewEngineProvider.cs b/src/Orchard/Mvc/ViewEngines/IViewEngineProvider.cs index c091b03c7..293331a5e 100644 --- a/src/Orchard/Mvc/ViewEngines/IViewEngineProvider.cs +++ b/src/Orchard/Mvc/ViewEngines/IViewEngineProvider.cs @@ -8,6 +8,7 @@ namespace Orchard.Mvc.ViewEngines { public class CreateModulesViewEngineParams { public IEnumerable VirtualPaths { get; set; } + public IEnumerable ExtensionLocations { get; set; } } public interface IViewEngineProvider : ISingletonDependency { diff --git a/src/Orchard/Mvc/ViewEngines/Razor/RazorViewEngineProvider.cs b/src/Orchard/Mvc/ViewEngines/Razor/RazorViewEngineProvider.cs index 3d8b3769c..14775ec3c 100644 --- a/src/Orchard/Mvc/ViewEngines/Razor/RazorViewEngineProvider.cs +++ b/src/Orchard/Mvc/ViewEngines/Razor/RazorViewEngineProvider.cs @@ -59,11 +59,8 @@ namespace Orchard.Mvc.ViewEngines.Razor { } public IViewEngine CreateModulesViewEngine(CreateModulesViewEngineParams parameters) { - var areaFormats = new[] { - "~/Core/{2}/Views/{1}/{0}.cshtml", - "~/Modules/{2}/Views/{1}/{0}.cshtml", - "~/Themes/{2}/Views/{1}/{0}.cshtml", - }; + //TBD: It would probably be better to determined the area deterministically from the module of the controller, not by trial and error. + var areaFormats = parameters.ExtensionLocations.Select(location => location + "/{2}/Views/{1}/{0}.cshtml").ToArray(); //Logger.Debug("AreaFormats (module): \r\n\t-{0}", string.Join("\r\n\t-", areaFormats)); diff --git a/src/Orchard/Mvc/ViewEngines/Razor/WebViewPage.cs b/src/Orchard/Mvc/ViewEngines/Razor/WebViewPage.cs index 4a3f5e49f..161b60fd0 100644 --- a/src/Orchard/Mvc/ViewEngines/Razor/WebViewPage.cs +++ b/src/Orchard/Mvc/ViewEngines/Razor/WebViewPage.cs @@ -113,6 +113,9 @@ namespace Orchard.Mvc.ViewEngines.Razor { } } + private string[] _commonLocations; + public string[] CommonLocations { get { return _commonLocations ?? (_commonLocations = WorkContext.Resolve().CommonLocations); } } + public void RegisterImageSet(string imageSet, string style = "", int size = 16) { // hack to fake the style "alternate" for now so we don't have to change stylesheet names when this is hooked up // todo: (heskew) deal in shapes so we have real alternates @@ -182,16 +185,11 @@ namespace Orchard.Mvc.ViewEngines.Razor { _tenantPrefix = WorkContext.Resolve().RequestUrlPrefix ?? ""; } - if (!String.IsNullOrEmpty(_tenantPrefix)) { - - if (path.StartsWith("~/") - && !path.StartsWith("~/Modules", StringComparison.OrdinalIgnoreCase) - && !path.StartsWith("~/Themes", StringComparison.OrdinalIgnoreCase) - && !path.StartsWith("~/Media", StringComparison.OrdinalIgnoreCase) - && !path.StartsWith("~/Core", StringComparison.OrdinalIgnoreCase)) { - - return base.Href("~/" + _tenantPrefix + path.Substring(String.IsNullOrWhiteSpace(_tenantPrefix) ? 2 : 1), pathParts); - } + if (!String.IsNullOrEmpty(_tenantPrefix) + && path.StartsWith("~/") + && !CommonLocations.Any(gpp=>path.StartsWith(gpp, StringComparison.OrdinalIgnoreCase)) + ) { + return base.Href("~/" + _tenantPrefix + path.Substring(2), pathParts); } return base.Href(path, pathParts); diff --git a/src/Orchard/Mvc/ViewEngines/ThemeAwareness/ThemeAwareViewEngine.cs b/src/Orchard/Mvc/ViewEngines/ThemeAwareness/ThemeAwareViewEngine.cs index 0591f8dba..54ba27b23 100644 --- a/src/Orchard/Mvc/ViewEngines/ThemeAwareness/ThemeAwareViewEngine.cs +++ b/src/Orchard/Mvc/ViewEngines/ThemeAwareness/ThemeAwareViewEngine.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Web.Mvc; +using Orchard.Environment.Configuration; using Orchard.Environment.Descriptor.Models; using Orchard.Environment.Extensions; using Orchard.Environment.Extensions.Helpers; @@ -94,12 +95,17 @@ namespace Orchard.Mvc.ViewEngines.ThemeAwareness { .Reverse() // reverse from (C <= B <= A) to (A => B => C) .Where(fd => DefaultExtensionTypes.IsModule(fd.Extension.ExtensionType)); - var allLocations = enabledModules.Concat(enabledModules) - .Select(fd => fd.Extension.Location + "/" + fd.Extension.Id) - .Distinct(StringComparer.OrdinalIgnoreCase) + var moduleVirtualPaths = enabledModules + .Select(fd => fd.Extension.VirtualPath) + .Distinct(StringComparer.OrdinalIgnoreCase) // is Distinct guaranty to keep order? .ToList(); - var moduleParams = new CreateModulesViewEngineParams { VirtualPaths = allLocations }; + var moduleLocations = enabledModules + .Select(fd => fd.Extension.Location) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + var moduleParams = new CreateModulesViewEngineParams { VirtualPaths = moduleVirtualPaths, ExtensionLocations = moduleLocations }; engines = engines.Concat(_viewEngineProviders.Select(vep => vep.CreateModulesViewEngine(moduleParams))); return new ViewEngineCollectionWrapper(engines); diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 877afb7cf..93d44dbdb 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -151,6 +151,7 @@ +