diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs
index 49f4481a59..a888e3c42b 100644
--- a/src/Umbraco.Core/Configuration/GlobalSettings.cs
+++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs
@@ -33,7 +33,6 @@ namespace Umbraco.Core.Configuration
///
private static void ResetInternal()
{
- GlobalSettingsExtensions.Reset();
_reservedPaths = null;
_reservedUrls = null;
HasSmtpServer = null;
diff --git a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs
index 6bfb7ea904..bc76caacee 100644
--- a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs
+++ b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs
@@ -1,6 +1,8 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
using System.Web;
using System.Web.Routing;
using Umbraco.Core.IO;
@@ -9,22 +11,9 @@ namespace Umbraco.Core.Configuration
{
public static class GlobalSettingsExtensions
{
- ///
- /// Used in unit testing to reset all config items, this is automatically called by GlobalSettings.Reset()
- ///
- internal static void Reset()
- {
- _reservedUrlsCache = null;
- _mvcArea = null;
- }
-
- private static readonly object Locker = new object();
- //make this volatile so that we can ensure thread safety with a double check lock
- private static volatile string _reservedUrlsCache;
- private static string _reservedPathsCache;
- private static HashSet _reservedList = new HashSet();
private static string _mvcArea;
+
///
/// This returns the string of the MVC Area route.
///
@@ -40,6 +29,13 @@ namespace Umbraco.Core.Configuration
{
if (_mvcArea != null) return _mvcArea;
+ _mvcArea = GetUmbracoMvcAreaNoCache(globalSettings);
+
+ return _mvcArea;
+ }
+
+ internal static string GetUmbracoMvcAreaNoCache(this IGlobalSettings globalSettings)
+ {
if (globalSettings.Path.IsNullOrWhiteSpace())
{
throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified");
@@ -48,95 +44,8 @@ namespace Umbraco.Core.Configuration
var path = globalSettings.Path;
if (path.StartsWith(SystemDirectories.Root)) // beware of TrimStart, see U4-2518
path = path.Substring(SystemDirectories.Root.Length);
- _mvcArea = path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim().ToLower();
- return _mvcArea;
+ return path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim().ToLower();
}
- ///
- /// Determines whether the specified URL is reserved or is inside a reserved path.
- ///
- ///
- /// The URL to check.
- ///
- /// true if the specified URL is reserved; otherwise, false.
- ///
- internal static bool IsReservedPathOrUrl(this IGlobalSettings globalSettings, string url)
- {
- if (_reservedUrlsCache == null)
- {
- lock (Locker)
- {
- if (_reservedUrlsCache == null)
- {
- // store references to strings to determine changes
- _reservedPathsCache = globalSettings.ReservedPaths;
- _reservedUrlsCache = globalSettings.ReservedUrls;
-
- // add URLs and paths to a new list
- var newReservedList = new HashSet();
- foreach (var reservedUrlTrimmed in _reservedUrlsCache
- .Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries)
- .Select(x => x.Trim().ToLowerInvariant())
- .Where(x => x.IsNullOrWhiteSpace() == false)
- .Select(reservedUrl => IOHelper.ResolveUrl(reservedUrl).Trim().EnsureStartsWith("/"))
- .Where(reservedUrlTrimmed => reservedUrlTrimmed.IsNullOrWhiteSpace() == false))
- {
- newReservedList.Add(reservedUrlTrimmed);
- }
-
- foreach (var reservedPathTrimmed in _reservedPathsCache
- .Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries)
- .Select(x => x.Trim().ToLowerInvariant())
- .Where(x => x.IsNullOrWhiteSpace() == false)
- .Select(reservedPath => IOHelper.ResolveUrl(reservedPath).Trim().EnsureStartsWith("/").EnsureEndsWith("/"))
- .Where(reservedPathTrimmed => reservedPathTrimmed.IsNullOrWhiteSpace() == false))
- {
- newReservedList.Add(reservedPathTrimmed);
- }
-
- // use the new list from now on
- _reservedList = newReservedList;
- }
- }
- }
-
- //The url should be cleaned up before checking:
- // * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/'
- // * We shouldn't be comparing the query at all
- var pathPart = url.Split(new[] {'?'}, StringSplitOptions.RemoveEmptyEntries)[0].ToLowerInvariant();
- if (pathPart.Contains(".") == false)
- {
- pathPart = pathPart.EnsureEndsWith('/');
- }
-
- // return true if url starts with an element of the reserved list
- return _reservedList.Any(x => pathPart.InvariantStartsWith(x));
- }
-
- ///
- /// Determines whether the current request is reserved based on the route table and
- /// whether the specified URL is reserved or is inside a reserved path.
- ///
- ///
- ///
- ///
- /// The route collection to lookup the request in
- ///
- internal static bool IsReservedPathOrUrl(this IGlobalSettings globalSettings, string url, HttpContextBase httpContext, RouteCollection routes)
- {
- if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
- if (routes == null) throw new ArgumentNullException(nameof(routes));
-
- //check if the current request matches a route, if so then it is reserved.
- //TODO: This value should be cached! Else this is doing double routing in MVC every request!
- var route = routes.GetRouteData(httpContext);
- if (route != null)
- return true;
-
- //continue with the standard ignore routine
- return globalSettings.IsReservedPathOrUrl(url);
- }
-
-
}
}
diff --git a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs
index 2f6b91edee..281cc2c396 100644
--- a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs
+++ b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs
@@ -11,7 +11,9 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Packaging;
+using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
@@ -25,13 +27,14 @@ namespace Umbraco.Core.Packaging
private readonly ILocalizationService _localizationService;
private readonly IDataTypeService _dataTypeService;
private readonly PropertyEditorCollection _propertyEditors;
+ private readonly IScopeProvider _scopeProvider;
private readonly IEntityService _entityService;
private readonly IContentTypeService _contentTypeService;
private readonly IContentService _contentService;
public PackageDataInstallation(ILogger logger, IFileService fileService, IMacroService macroService, ILocalizationService localizationService,
IDataTypeService dataTypeService, IEntityService entityService, IContentTypeService contentTypeService,
- IContentService contentService, PropertyEditorCollection propertyEditors)
+ IContentService contentService, PropertyEditorCollection propertyEditors, IScopeProvider scopeProvider)
{
_logger = logger;
_fileService = fileService;
@@ -39,12 +42,13 @@ namespace Umbraco.Core.Packaging
_localizationService = localizationService;
_dataTypeService = dataTypeService;
_propertyEditors = propertyEditors;
+ _scopeProvider = scopeProvider;
_entityService = entityService;
_contentTypeService = contentTypeService;
_contentService = contentService;
}
- #region Uninstall
+ #region Install/Uninstall
public UninstallationSummary UninstallPackageData(PackageDefinition package, int userId)
{
@@ -57,93 +61,97 @@ namespace Umbraco.Core.Packaging
var removedDataTypes = new List();
var removedLanguages = new List();
-
- //Uninstall templates
- foreach (var item in package.Templates.ToArray())
+ using (var scope = _scopeProvider.CreateScope())
{
- if (int.TryParse(item, out var nId) == false) continue;
- var found = _fileService.GetTemplate(nId);
- if (found != null)
+ //Uninstall templates
+ foreach (var item in package.Templates.ToArray())
{
- removedTemplates.Add(found);
- _fileService.DeleteTemplate(found.Alias, userId);
+ if (int.TryParse(item, out var nId) == false) continue;
+ var found = _fileService.GetTemplate(nId);
+ if (found != null)
+ {
+ removedTemplates.Add(found);
+ _fileService.DeleteTemplate(found.Alias, userId);
+ }
+ package.Templates.Remove(nId.ToString());
}
- package.Templates.Remove(nId.ToString());
- }
- //Uninstall macros
- foreach (var item in package.Macros.ToArray())
- {
- if (int.TryParse(item, out var nId) == false) continue;
- var macro = _macroService.GetById(nId);
- if (macro != null)
+ //Uninstall macros
+ foreach (var item in package.Macros.ToArray())
{
- removedMacros.Add(macro);
- _macroService.Delete(macro, userId);
+ if (int.TryParse(item, out var nId) == false) continue;
+ var macro = _macroService.GetById(nId);
+ if (macro != null)
+ {
+ removedMacros.Add(macro);
+ _macroService.Delete(macro, userId);
+ }
+ package.Macros.Remove(nId.ToString());
}
- package.Macros.Remove(nId.ToString());
- }
- //Remove Document Types
- var contentTypes = new List();
- var contentTypeService = _contentTypeService;
- foreach (var item in package.DocumentTypes.ToArray())
- {
- if (int.TryParse(item, out var nId) == false) continue;
- var contentType = contentTypeService.Get(nId);
- if (contentType == null) continue;
- contentTypes.Add(contentType);
- package.DocumentTypes.Remove(nId.ToString(CultureInfo.InvariantCulture));
- }
-
- //Order the DocumentTypes before removing them
- if (contentTypes.Any())
- {
- // TODO: I don't think this ordering is necessary
- var orderedTypes = (from contentType in contentTypes
- orderby contentType.ParentId descending, contentType.Id descending
- select contentType).ToList();
- removedContentTypes.AddRange(orderedTypes);
- contentTypeService.Delete(orderedTypes, userId);
- }
-
- //Remove Dictionary items
- foreach (var item in package.DictionaryItems.ToArray())
- {
- if (int.TryParse(item, out var nId) == false) continue;
- var di = _localizationService.GetDictionaryItemById(nId);
- if (di != null)
+ //Remove Document Types
+ var contentTypes = new List();
+ var contentTypeService = _contentTypeService;
+ foreach (var item in package.DocumentTypes.ToArray())
{
- removedDictionaryItems.Add(di);
- _localizationService.Delete(di, userId);
+ if (int.TryParse(item, out var nId) == false) continue;
+ var contentType = contentTypeService.Get(nId);
+ if (contentType == null) continue;
+ contentTypes.Add(contentType);
+ package.DocumentTypes.Remove(nId.ToString(CultureInfo.InvariantCulture));
}
- package.DictionaryItems.Remove(nId.ToString());
- }
- //Remove Data types
- foreach (var item in package.DataTypes.ToArray())
- {
- if (int.TryParse(item, out var nId) == false) continue;
- var dtd = _dataTypeService.GetDataType(nId);
- if (dtd != null)
+ //Order the DocumentTypes before removing them
+ if (contentTypes.Any())
{
- removedDataTypes.Add(dtd);
- _dataTypeService.Delete(dtd, userId);
+ // TODO: I don't think this ordering is necessary
+ var orderedTypes = (from contentType in contentTypes
+ orderby contentType.ParentId descending, contentType.Id descending
+ select contentType).ToList();
+ removedContentTypes.AddRange(orderedTypes);
+ contentTypeService.Delete(orderedTypes, userId);
}
- package.DataTypes.Remove(nId.ToString());
- }
- //Remove Langs
- foreach (var item in package.Languages.ToArray())
- {
- if (int.TryParse(item, out var nId) == false) continue;
- var lang = _localizationService.GetLanguageById(nId);
- if (lang != null)
+ //Remove Dictionary items
+ foreach (var item in package.DictionaryItems.ToArray())
{
- removedLanguages.Add(lang);
- _localizationService.Delete(lang, userId);
+ if (int.TryParse(item, out var nId) == false) continue;
+ var di = _localizationService.GetDictionaryItemById(nId);
+ if (di != null)
+ {
+ removedDictionaryItems.Add(di);
+ _localizationService.Delete(di, userId);
+ }
+ package.DictionaryItems.Remove(nId.ToString());
}
- package.Languages.Remove(nId.ToString());
+
+ //Remove Data types
+ foreach (var item in package.DataTypes.ToArray())
+ {
+ if (int.TryParse(item, out var nId) == false) continue;
+ var dtd = _dataTypeService.GetDataType(nId);
+ if (dtd != null)
+ {
+ removedDataTypes.Add(dtd);
+ _dataTypeService.Delete(dtd, userId);
+ }
+ package.DataTypes.Remove(nId.ToString());
+ }
+
+ //Remove Langs
+ foreach (var item in package.Languages.ToArray())
+ {
+ if (int.TryParse(item, out var nId) == false) continue;
+ var lang = _localizationService.GetLanguageById(nId);
+ if (lang != null)
+ {
+ removedLanguages.Add(lang);
+ _localizationService.Delete(lang, userId);
+ }
+ package.Languages.Remove(nId.ToString());
+ }
+
+ scope.Complete();
}
// create a summary of what was actually removed, for PackagingService.UninstalledPackage
@@ -164,14 +172,40 @@ namespace Umbraco.Core.Packaging
}
+ public InstallationSummary InstallPackageData(CompiledPackage compiledPackage, int userId)
+ {
+ using (var scope = _scopeProvider.CreateScope())
+ {
+ var installationSummary = new InstallationSummary
+ {
+ DataTypesInstalled = ImportDataTypes(compiledPackage.DataTypes.ToList(), userId),
+ LanguagesInstalled = ImportLanguages(compiledPackage.Languages, userId),
+ DictionaryItemsInstalled = ImportDictionaryItems(compiledPackage.DictionaryItems, userId),
+ MacrosInstalled = ImportMacros(compiledPackage.Macros, userId),
+ TemplatesInstalled = ImportTemplates(compiledPackage.Templates.ToList(), userId),
+ DocumentTypesInstalled = ImportDocumentTypes(compiledPackage.DocumentTypes, userId)
+ };
+
+ //we need a reference to the imported doc types to continue
+ var importedDocTypes = installationSummary.DocumentTypesInstalled.ToDictionary(x => x.Alias, x => x);
+
+ installationSummary.StylesheetsInstalled = ImportStylesheets(compiledPackage.Stylesheets, userId);
+ installationSummary.ContentInstalled = ImportContent(compiledPackage.Documents, importedDocTypes, userId);
+
+ scope.Complete();
+
+ return installationSummary;
+ }
+ }
+
#endregion
#region Content
- public IEnumerable ImportContent(IEnumerable docs, IDictionary importedDocumentTypes, int userId)
+ public IReadOnlyList ImportContent(IEnumerable docs, IDictionary importedDocumentTypes, int userId)
{
- return docs.SelectMany(x => ImportContent(x, -1, importedDocumentTypes, userId));
+ return docs.SelectMany(x => ImportContent(x, -1, importedDocumentTypes, userId)).ToList();
}
///
@@ -352,7 +386,7 @@ namespace Umbraco.Core.Packaging
#region DocumentTypes
- public IEnumerable ImportDocumentType(XElement docTypeElement, int userId)
+ public IReadOnlyList ImportDocumentType(XElement docTypeElement, int userId)
{
return ImportDocumentTypes(new[] { docTypeElement }, userId);
}
@@ -363,7 +397,7 @@ namespace Umbraco.Core.Packaging
/// Xml to import
/// Optional id of the User performing the operation. Default is zero (admin).
/// An enumerable list of generated ContentTypes
- public IEnumerable ImportDocumentTypes(IEnumerable docTypeElements, int userId)
+ public IReadOnlyList ImportDocumentTypes(IEnumerable docTypeElements, int userId)
{
return ImportDocumentTypes(docTypeElements.ToList(), true, userId);
}
@@ -375,7 +409,7 @@ namespace Umbraco.Core.Packaging
/// Boolean indicating whether or not to import the
/// Optional id of the User performing the operation. Default is zero (admin).
/// An enumerable list of generated ContentTypes
- public IEnumerable ImportDocumentTypes(IReadOnlyCollection unsortedDocumentTypes, bool importStructure, int userId)
+ public IReadOnlyList ImportDocumentTypes(IReadOnlyCollection unsortedDocumentTypes, bool importStructure, int userId)
{
var importedContentTypes = new Dictionary();
@@ -824,7 +858,7 @@ namespace Umbraco.Core.Packaging
/// Xml to import
/// Optional id of the user
/// An enumerable list of generated DataTypeDefinitions
- public IEnumerable ImportDataTypes(IReadOnlyCollection dataTypeElements, int userId)
+ public IReadOnlyList ImportDataTypes(IReadOnlyCollection dataTypeElements, int userId)
{
var dataTypes = new List();
@@ -953,13 +987,13 @@ namespace Umbraco.Core.Packaging
/// Xml to import
///
/// An enumerable list of dictionary items
- public IEnumerable ImportDictionaryItems(IEnumerable dictionaryItemElementList, int userId)
+ public IReadOnlyList ImportDictionaryItems(IEnumerable dictionaryItemElementList, int userId)
{
var languages = _localizationService.GetAllLanguages().ToList();
return ImportDictionaryItems(dictionaryItemElementList, languages, null, userId);
}
- private IEnumerable ImportDictionaryItems(IEnumerable dictionaryItemElementList, List languages, Guid? parentId, int userId)
+ private IReadOnlyList ImportDictionaryItems(IEnumerable dictionaryItemElementList, List languages, Guid? parentId, int userId)
{
var items = new List();
foreach (var dictionaryItemElement in dictionaryItemElementList)
@@ -1036,7 +1070,7 @@ namespace Umbraco.Core.Packaging
/// Xml to import
/// Optional id of the User performing the operation
/// An enumerable list of generated languages
- public IEnumerable ImportLanguages(IEnumerable languageElements, int userId)
+ public IReadOnlyList ImportLanguages(IEnumerable languageElements, int userId)
{
var list = new List();
foreach (var languageElement in languageElements)
@@ -1065,7 +1099,7 @@ namespace Umbraco.Core.Packaging
/// Xml to import
/// Optional id of the User performing the operation
///
- public IEnumerable ImportMacros(IEnumerable macroElements, int userId)
+ public IReadOnlyList ImportMacros(IEnumerable macroElements, int userId)
{
var macros = macroElements.Select(ParseMacroElement).ToList();
@@ -1155,7 +1189,7 @@ namespace Umbraco.Core.Packaging
#region Stylesheets
- public IEnumerable ImportStylesheets(IEnumerable stylesheetElements, int userId)
+ public IReadOnlyList ImportStylesheets(IEnumerable stylesheetElements, int userId)
{
var result = new List();
@@ -1223,7 +1257,7 @@ namespace Umbraco.Core.Packaging
/// Xml to import
/// Optional user id
/// An enumerable list of generated Templates
- public IEnumerable ImportTemplates(IReadOnlyCollection templateElements, int userId)
+ public IReadOnlyList ImportTemplates(IReadOnlyCollection templateElements, int userId)
{
var templates = new List();
diff --git a/src/Umbraco.Core/Packaging/PackageInstallation.cs b/src/Umbraco.Core/Packaging/PackageInstallation.cs
index d791295b38..a42ee1aeb2 100644
--- a/src/Umbraco.Core/Packaging/PackageInstallation.cs
+++ b/src/Umbraco.Core/Packaging/PackageInstallation.cs
@@ -90,21 +90,8 @@ namespace Umbraco.Core.Packaging
public InstallationSummary InstallPackageData(PackageDefinition packageDefinition, CompiledPackage compiledPackage, int userId)
{
- var installationSummary = new InstallationSummary
- {
- DataTypesInstalled = _packageDataInstallation.ImportDataTypes(compiledPackage.DataTypes.ToList(), userId),
- LanguagesInstalled = _packageDataInstallation.ImportLanguages(compiledPackage.Languages, userId),
- DictionaryItemsInstalled = _packageDataInstallation.ImportDictionaryItems(compiledPackage.DictionaryItems, userId),
- MacrosInstalled = _packageDataInstallation.ImportMacros(compiledPackage.Macros, userId),
- TemplatesInstalled = _packageDataInstallation.ImportTemplates(compiledPackage.Templates.ToList(), userId),
- DocumentTypesInstalled = _packageDataInstallation.ImportDocumentTypes(compiledPackage.DocumentTypes, userId)
- };
+ var installationSummary = _packageDataInstallation.InstallPackageData(compiledPackage, userId);
- //we need a reference to the imported doc types to continue
- var importedDocTypes = installationSummary.DocumentTypesInstalled.ToDictionary(x => x.Alias, x => x);
-
- installationSummary.StylesheetsInstalled = _packageDataInstallation.ImportStylesheets(compiledPackage.Stylesheets, userId);
- installationSummary.ContentInstalled = _packageDataInstallation.ImportContent(compiledPackage.Documents, importedDocTypes, userId);
installationSummary.Actions = CompiledPackageXmlParser.GetPackageActions(XElement.Parse(compiledPackage.Actions), compiledPackage.Name);
installationSummary.MetaData = compiledPackage;
installationSummary.FilesInstalled = packageDefinition.Files;
diff --git a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs
index de6eeb6a42..ac8c9f1be7 100644
--- a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs
+++ b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.ComponentModel;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core
@@ -15,12 +17,8 @@ namespace Umbraco.Core
///
public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory;
- ///
- /// Executes an action with a safe live factory
- ///
- ///
- /// If the factory is a live factory, ensures it is refreshed and locked while executing the action.
- ///
+ [Obsolete("This method is no longer used or necessary and will be removed from future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public static void WithSafeLiveFactory(this IPublishedModelFactory factory, Action action)
{
if (factory is ILivePublishedModelFactory liveFactory)
@@ -37,5 +35,38 @@ namespace Umbraco.Core
action();
}
}
+
+ ///
+ /// Sets a flag to reset the ModelsBuilder models if the is
+ ///
+ ///
+ ///
+ ///
+ /// This does not recompile the pure live models, only sets a flag to tell models builder to recompile when they are requested.
+ ///
+ internal static void WithSafeLiveFactoryReset(this IPublishedModelFactory factory, Action action)
+ {
+ if (factory is ILivePublishedModelFactory liveFactory)
+ {
+ lock (liveFactory.SyncRoot)
+ {
+ // TODO: Fix this in 8.3! - We need to change the ILivePublishedModelFactory interface to have a Reset method and then when we have an embedded MB
+ // version we will publicize the ResetModels (and change the name to Reset).
+ // For now, this will suffice and we'll use reflection, there should be no other implementation of ILivePublishedModelFactory.
+ // Calling ResetModels resets the MB flag so that the next time EnsureModels is called (which is called when nucache lazily calls CreateModel) it will
+ // trigger the recompiling of pure live models.
+ var resetMethod = liveFactory.GetType().GetMethod("ResetModels", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
+ if (resetMethod != null)
+ resetMethod.Invoke(liveFactory, null);
+
+ action();
+ }
+ }
+ else
+ {
+ action();
+ }
+ }
+
}
}
diff --git a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs
index 54080f05de..50ead4b702 100644
--- a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs
+++ b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs
@@ -1,6 +1,4 @@
-using System.Web.Mvc;
-using System.Web.Routing;
-using Moq;
+using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Composing;
@@ -10,6 +8,7 @@ using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Configurations
{
+
[TestFixture]
public class GlobalSettingsTests : BaseWebTest
{
@@ -47,73 +46,18 @@ namespace Umbraco.Tests.Configurations
[TestCase("~/some-wacky/nestedPath", "/MyVirtualDir/NestedVDir/", "some-wacky-nestedpath")]
public void Umbraco_Mvc_Area(string path, string rootPath, string outcome)
{
- var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container
- globalSettingsMock.Setup(x => x.Path).Returns(IOHelper.ResolveUrl(path));
+ var globalSettings = SettingsForTests.GenerateMockGlobalSettings();
+
+ var globalSettingsMock = Mock.Get(globalSettings);
+ globalSettingsMock.Setup(x => x.Path).Returns(() => IOHelper.ResolveUrl(path));
SystemDirectories.Root = rootPath;
- Assert.AreEqual(outcome, Current.Configs.Global().GetUmbracoMvcArea());
+ Assert.AreEqual(outcome, globalSettings.GetUmbracoMvcAreaNoCache());
}
- [TestCase("/umbraco/editContent.aspx")]
- [TestCase("/install/default.aspx")]
- [TestCase("/install/")]
- [TestCase("/install")]
- [TestCase("/install/?installStep=asdf")]
- [TestCase("/install/test.aspx")]
- public void Is_Reserved_Path_Or_Url(string url)
- {
- var globalSettings = TestObjects.GetGlobalSettings();
- Assert.IsTrue(globalSettings.IsReservedPathOrUrl(url));
- }
-
- [TestCase("/base/somebasehandler")]
- [TestCase("/")]
- [TestCase("/home.aspx")]
- [TestCase("/umbraco-test")]
- [TestCase("/install-test")]
- [TestCase("/install.aspx")]
- public void Is_Not_Reserved_Path_Or_Url(string url)
- {
- var globalSettings = TestObjects.GetGlobalSettings();
- Assert.IsFalse(globalSettings.IsReservedPathOrUrl(url));
- }
+
- [TestCase("/Do/Not/match", false)]
- [TestCase("/Umbraco/RenderMvcs", false)]
- [TestCase("/Umbraco/RenderMvc", true)]
- [TestCase("/Umbraco/RenderMvc/Index", true)]
- [TestCase("/Umbraco/RenderMvc/Index/1234", true)]
- [TestCase("/Umbraco/RenderMvc/Index/1234/9876", false)]
- [TestCase("/api", true)]
- [TestCase("/api/WebApiTest", true)]
- [TestCase("/api/WebApiTest/1234", true)]
- [TestCase("/api/WebApiTest/Index/1234", false)]
- public void Is_Reserved_By_Route(string url, bool shouldMatch)
- {
- //reset the app config, we only want to test routes not the hard coded paths
- var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container
- globalSettingsMock.Setup(x => x.ReservedPaths).Returns("");
- globalSettingsMock.Setup(x => x.ReservedUrls).Returns("");
-
- var routes = new RouteCollection();
-
- routes.MapRoute(
- "Umbraco_default",
- "Umbraco/RenderMvc/{action}/{id}",
- new { controller = "RenderMvc", action = "Index", id = UrlParameter.Optional });
- routes.MapRoute(
- "WebAPI",
- "api/{controller}/{id}",
- new { controller = "WebApiTestController", action = "Index", id = UrlParameter.Optional });
-
-
- var context = new FakeHttpContextFactory(url);
-
-
- Assert.AreEqual(
- shouldMatch,
- globalSettingsMock.Object.IsReservedPathOrUrl(url, context.HttpContext, routes));
- }
+
}
}
diff --git a/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs b/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs
index abf61370a8..6dd3f2aacb 100644
--- a/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs
+++ b/src/Umbraco.Tests/Packaging/PackageInstallationTest.cs
@@ -11,6 +11,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.Models.Packaging;
using Umbraco.Core.Packaging;
using Umbraco.Core.PropertyEditors;
+using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
using File = System.IO.File;
@@ -45,7 +46,8 @@ namespace Umbraco.Tests.Packaging
Logger, ServiceContext.FileService, ServiceContext.MacroService, ServiceContext.LocalizationService,
ServiceContext.DataTypeService, ServiceContext.EntityService,
ServiceContext.ContentTypeService, ServiceContext.ContentService,
- Factory.GetInstance());
+ Factory.GetInstance(),
+ Factory.GetInstance());
private IPackageInstallation PackageInstallation => new PackageInstallation(
PackageDataInstallation,
diff --git a/src/Umbraco.Tests/Routing/RoutableDocumentFilterTests.cs b/src/Umbraco.Tests/Routing/RoutableDocumentFilterTests.cs
new file mode 100644
index 0000000000..4079ea6c84
--- /dev/null
+++ b/src/Umbraco.Tests/Routing/RoutableDocumentFilterTests.cs
@@ -0,0 +1,80 @@
+using System.Web.Mvc;
+using System.Web.Routing;
+using Moq;
+using NUnit.Framework;
+using Umbraco.Core;
+using Umbraco.Core.Configuration;
+using Umbraco.Tests.TestHelpers;
+using Umbraco.Web;
+
+namespace Umbraco.Tests.Routing
+{
+ [TestFixture]
+ public class RoutableDocumentFilterTests : BaseWebTest
+ {
+ [TestCase("/umbraco/editContent.aspx")]
+ [TestCase("/install/default.aspx")]
+ [TestCase("/install/")]
+ [TestCase("/install")]
+ [TestCase("/install/?installStep=asdf")]
+ [TestCase("/install/test.aspx")]
+ public void Is_Reserved_Path_Or_Url(string url)
+ {
+ var globalSettings = TestObjects.GetGlobalSettings();
+ var routableDocFilter = new RoutableDocumentFilter(globalSettings);
+ Assert.IsTrue(routableDocFilter.IsReservedPathOrUrl(url));
+ }
+
+ [TestCase("/base/somebasehandler")]
+ [TestCase("/")]
+ [TestCase("/home.aspx")]
+ [TestCase("/umbraco-test")]
+ [TestCase("/install-test")]
+ [TestCase("/install.aspx")]
+ public void Is_Not_Reserved_Path_Or_Url(string url)
+ {
+ var globalSettings = TestObjects.GetGlobalSettings();
+ var routableDocFilter = new RoutableDocumentFilter(globalSettings);
+ Assert.IsFalse(routableDocFilter.IsReservedPathOrUrl(url));
+ }
+
+ [TestCase("/Do/Not/match", false)]
+ [TestCase("/Umbraco/RenderMvcs", false)]
+ [TestCase("/Umbraco/RenderMvc", true)]
+ [TestCase("/Umbraco/RenderMvc/Index", true)]
+ [TestCase("/Umbraco/RenderMvc/Index/1234", true)]
+ [TestCase("/Umbraco/RenderMvc/Index/1234/9876", false)]
+ [TestCase("/api", true)]
+ [TestCase("/api/WebApiTest", true)]
+ [TestCase("/api/WebApiTest/1234", true)]
+ [TestCase("/api/WebApiTest/Index/1234", false)]
+ public void Is_Reserved_By_Route(string url, bool shouldMatch)
+ {
+ //reset the app config, we only want to test routes not the hard coded paths
+ var globalSettingsMock = Mock.Get(Factory.GetInstance()); //this will modify the IGlobalSettings instance stored in the container
+ globalSettingsMock.Setup(x => x.ReservedPaths).Returns("");
+ globalSettingsMock.Setup(x => x.ReservedUrls).Returns("");
+
+ var routableDocFilter = new RoutableDocumentFilter(globalSettingsMock.Object);
+
+ var routes = new RouteCollection();
+
+ routes.MapRoute(
+ "Umbraco_default",
+ "Umbraco/RenderMvc/{action}/{id}",
+ new { controller = "RenderMvc", action = "Index", id = UrlParameter.Optional });
+ routes.MapRoute(
+ "WebAPI",
+ "api/{controller}/{id}",
+ new { controller = "WebApiTestController", action = "Index", id = UrlParameter.Optional });
+
+
+ var context = new FakeHttpContextFactory(url);
+
+
+ Assert.AreEqual(
+ shouldMatch,
+ routableDocFilter.IsReservedPathOrUrl(url, context.HttpContext, routes));
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs
index 3e4c4f1ba9..e95fdfc87c 100644
--- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs
+++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs
@@ -38,15 +38,11 @@ namespace Umbraco.Tests.Routing
_module = new UmbracoInjectedModule
(
globalSettings,
- Mock.Of(),
- Factory.GetInstance(),
- Factory.GetInstance(),
- new UrlProviderCollection(new IUrlProvider[0]),
runtime,
logger,
null, // FIXME: PublishedRouter complexities...
- Mock.Of(),
- Mock.Of()
+ Mock.Of(),
+ new RoutableDocumentFilter(globalSettings)
);
runtime.Level = RuntimeLevel.Run;
diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs
index 31d11952ef..56ad22d414 100644
--- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs
+++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs
@@ -178,7 +178,7 @@ namespace Umbraco.Tests.TestHelpers
new PackagesRepository(contentService.Value, contentTypeService.Value, dataTypeService.Value, fileService.Value, macroService.Value, localizationService.Value,
new EntityXmlSerializer(contentService.Value, mediaService.Value, dataTypeService.Value, userService.Value, localizationService.Value, contentTypeService.Value, urlSegmentProviders), logger, "installedPackages.config"),
new PackageInstallation(
- new PackageDataInstallation(logger, fileService.Value, macroService.Value, localizationService.Value, dataTypeService.Value, entityService.Value, contentTypeService.Value, contentService.Value, propertyEditorCollection),
+ new PackageDataInstallation(logger, fileService.Value, macroService.Value, localizationService.Value, dataTypeService.Value, entityService.Value, contentTypeService.Value, contentService.Value, propertyEditorCollection, scopeProvider),
new PackageFileInstallation(compiledPackageXmlParser, new ProfilingLogger(logger, new TestProfiler())),
compiledPackageXmlParser, Mock.Of(),
new DirectoryInfo(IOHelper.GetRootDirectorySafe())));
diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
index 146cb23c1f..9cc2b97445 100644
--- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
+++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs
@@ -31,6 +31,7 @@ using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Tests.LegacyXmlPublishedCache;
using Umbraco.Tests.Testing.Objects.Accessors;
+using Umbraco.Web.Cache;
namespace Umbraco.Tests.TestHelpers
{
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index f788168ddc..fcf73fbffa 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -120,6 +120,7 @@
+
@@ -145,6 +146,7 @@
+
@@ -455,7 +457,6 @@
-
diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs
index 266cddf6d5..63cfe0df9e 100644
--- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs
+++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs
@@ -1,12 +1,10 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence.Repositories;
-using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Changes;
using Umbraco.Web.PublishedCache;
@@ -86,11 +84,8 @@ namespace Umbraco.Web.Cache
// don't try to be clever - refresh all
MemberCacheRefresher.RefreshMemberTypes(AppCaches);
- // we have to refresh models before we notify the published snapshot
- // service of changes, else factories may try to rebuild models while
- // we are using the database to load content into caches
-
- _publishedModelFactory.WithSafeLiveFactory(() =>
+ // refresh the models and cache
+ _publishedModelFactory.WithSafeLiveFactoryReset(() =>
_publishedSnapshotService.Notify(payloads));
// now we can trigger the event
diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs
index 1d41c0578b..c9caf5aaa7 100644
--- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs
+++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs
@@ -62,11 +62,9 @@ namespace Umbraco.Web.Cache
TagsValueConverter.ClearCaches();
SliderValueConverter.ClearCaches();
- // we have to refresh models before we notify the published snapshot
- // service of changes, else factories may try to rebuild models while
- // we are using the database to load content into caches
+ // refresh the models and cache
- _publishedModelFactory.WithSafeLiveFactory(() =>
+ _publishedModelFactory.WithSafeLiveFactoryReset(() =>
_publishedSnapshotService.Notify(payloads));
base.Refresh(payloads);
diff --git a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs
index a68e137665..7850125970 100644
--- a/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs
+++ b/src/Umbraco.Web/Compose/DatabaseServerRegistrarAndMessengerComponent.cs
@@ -1,12 +1,8 @@
using System;
using System.Threading;
using Umbraco.Core;
-using Umbraco.Core.Compose;
using Umbraco.Core.Composing;
-using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
-using Umbraco.Core.Persistence;
-using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Changes;
using Umbraco.Core.Sync;
diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs
index e99cbdca4a..7c63061159 100644
--- a/src/Umbraco.Web/Editors/ContentTypeController.cs
+++ b/src/Umbraco.Web/Editors/ContentTypeController.cs
@@ -17,9 +17,11 @@ using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
+using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Packaging;
using Umbraco.Core.Persistence;
using Umbraco.Core.PropertyEditors;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Web.Models;
using Umbraco.Web.Models.ContentEditing;
@@ -46,6 +48,7 @@ namespace Umbraco.Web.Editors
{
private readonly IEntityXmlSerializer _serializer;
private readonly PropertyEditorCollection _propertyEditors;
+ private readonly IScopeProvider _scopeProvider;
public ContentTypeController(IEntityXmlSerializer serializer,
ICultureDictionaryFactory cultureDictionaryFactory,
@@ -53,11 +56,13 @@ namespace Umbraco.Web.Editors
IUmbracoContextAccessor umbracoContextAccessor,
ISqlContext sqlContext, PropertyEditorCollection propertyEditors,
ServiceContext services, AppCaches appCaches,
- IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper)
+ IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper,
+ IScopeProvider scopeProvider)
: base(cultureDictionaryFactory, globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper)
{
_serializer = serializer;
_propertyEditors = propertyEditors;
+ _scopeProvider = scopeProvider;
}
public int GetCount()
@@ -520,7 +525,7 @@ namespace Umbraco.Web.Editors
}
var dataInstaller = new PackageDataInstallation(Logger, Services.FileService, Services.MacroService, Services.LocalizationService,
- Services.DataTypeService, Services.EntityService, Services.ContentTypeService, Services.ContentService, _propertyEditors);
+ Services.DataTypeService, Services.EntityService, Services.ContentTypeService, Services.ContentService, _propertyEditors, _scopeProvider);
var xd = new XmlDocument {XmlResolver = null};
xd.Load(filePath);
diff --git a/src/Umbraco.Web/Mvc/ContentModelBinder.cs b/src/Umbraco.Web/Mvc/ContentModelBinder.cs
index c6df52e007..052938807a 100644
--- a/src/Umbraco.Web/Mvc/ContentModelBinder.cs
+++ b/src/Umbraco.Web/Mvc/ContentModelBinder.cs
@@ -135,13 +135,16 @@ namespace Umbraco.Web.Mvc
msg.Append(modelType.FullName);
msg.Append(".");
-// raise event, to give model factories a chance at reporting
+ // raise event, to give model factories a chance at reporting
// the error with more details, and optionally request that
// the application restarts.
var args = new ModelBindingArgs(sourceType, modelType, msg);
ModelBindingException?.Invoke(Instance, args);
+ // TODO: with all of the tests I've done i don't think restarting the app here is required anymore,
+ // when I don't have this code enabled and i get a model binding error and just refresh, it fixes itself.
+ // We'll leave this for now though.
if (args.Restart)
{
msg.Append(" The application is restarting now.");
diff --git a/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs b/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs
index b9161dbea0..5d8fbdac2f 100644
--- a/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs
+++ b/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs
@@ -1,20 +1,29 @@
using System;
+using System.Configuration;
using System.Net;
using System.Text.RegularExpressions;
using System.Web.Mvc;
+using Umbraco.Core;
+using Umbraco.Web.Composing;
namespace Umbraco.Web.Mvc
{
///
- /// An exception filter checking if we get a or with the same model. in which case it returns a redirect to the same page after 1 sec.
+ /// An exception filter checking if we get a or with the same model.
+ /// In which case it returns a redirect to the same page after 1 sec if not in debug mode.
///
+ ///
+ /// This is only enabled when running PureLive
+ ///
internal class ModelBindingExceptionFilter : FilterAttribute, IExceptionFilter
{
private static readonly Regex GetPublishedModelsTypesRegex = new Regex("Umbraco.Web.PublishedModels.(\\w+)", RegexOptions.Compiled);
public void OnException(ExceptionContext filterContext)
{
- if (!filterContext.ExceptionHandled
+ if (Current.PublishedModelFactory.IsLiveFactory()
+ && ConfigurationManager.AppSettings["Umbraco.Web.DisableModelBindingExceptionFilter"] != "true"
+ && !filterContext.ExceptionHandled
&& ((filterContext.Exception is ModelBindingException || filterContext.Exception is InvalidCastException)
&& IsMessageAboutTheSameModelType(filterContext.Exception.Message)))
{
diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs
index 7379277be4..5f8e81fd31 100644
--- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs
+++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs
@@ -75,17 +75,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (draftData == null && publishedData == null)
throw new ArgumentException("Both draftData and publishedData cannot be null at the same time.");
- if (draftData != null)
- {
- DraftContent = new PublishedContent(this, draftData, publishedSnapshotAccessor, variationContextAccessor);
- DraftModel = DraftContent.CreateModel();
- }
+ _publishedSnapshotAccessor = publishedSnapshotAccessor;
+ _variationContextAccessor = variationContextAccessor;
- if (publishedData != null)
- {
- PublishedContent = new PublishedContent(this, publishedData, publishedSnapshotAccessor, variationContextAccessor);
- PublishedModel = PublishedContent.CreateModel();
- }
+ _draftData = draftData;
+ _publishedData = publishedData;
}
// clone
@@ -105,14 +99,10 @@ namespace Umbraco.Web.PublishedCache.NuCache
CreateDate = origin.CreateDate;
CreatorId = origin.CreatorId;
- var originDraft = origin.DraftContent;
- var originPublished = origin.PublishedContent;
-
- DraftContent = originDraft == null ? null : new PublishedContent(this, originDraft);
- PublishedContent = originPublished == null ? null : new PublishedContent(this, originPublished);
-
- DraftModel = DraftContent?.CreateModel();
- PublishedModel = PublishedContent?.CreateModel();
+ _draftData = origin._draftData;
+ _publishedData = origin._publishedData;
+ _publishedSnapshotAccessor = origin._publishedSnapshotAccessor;
+ _variationContextAccessor = origin._variationContextAccessor;
}
// everything that is common to both draft and published versions
@@ -131,15 +121,41 @@ namespace Umbraco.Web.PublishedCache.NuCache
public readonly DateTime CreateDate;
public readonly int CreatorId;
- // draft and published version (either can be null, but not both)
- // are the direct PublishedContent instances
- public PublishedContent DraftContent;
- public PublishedContent PublishedContent;
+ private ContentData _draftData;
+ private ContentData _publishedData;
+ private IVariationContextAccessor _variationContextAccessor;
+ private IPublishedSnapshotAccessor _publishedSnapshotAccessor;
+
+ public bool HasPublished => _publishedData != null;
+ public bool HasPublishedCulture(string culture) => _publishedData != null && _publishedData.CultureInfos.ContainsKey(culture);
// draft and published version (either can be null, but not both)
// are models not direct PublishedContent instances
- public IPublishedContent DraftModel;
- public IPublishedContent PublishedModel;
+ private IPublishedContent _draftModel;
+ private IPublishedContent _publishedModel;
+
+ private IPublishedContent GetModel(ref IPublishedContent model, ContentData contentData)
+ {
+ if (model != null) return model;
+ if (contentData == null) return null;
+
+ // create the model - we want to be fast, so no lock here: we may create
+ // more than 1 instance, but the lock below ensures we only ever return
+ // 1 unique instance - and locking is a nice explicit way to ensure this
+
+ var m = new PublishedContent(this, contentData, _publishedSnapshotAccessor, _variationContextAccessor).CreateModel();
+
+ // locking 'this' is not a best-practice but ContentNode is internal and
+ // we know what we do, so it is fine here and avoids allocating an object
+ lock (this)
+ {
+ return model = model ?? m;
+ }
+ }
+
+ public IPublishedContent DraftModel => GetModel(ref _draftModel, _draftData);
+
+ public IPublishedContent PublishedModel => GetModel(ref _publishedModel, _publishedData);
public ContentNodeKit ToKit()
=> new ContentNodeKit
@@ -147,8 +163,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
Node = this,
ContentTypeId = ContentType.Id,
- DraftData = DraftContent?.ContentData,
- PublishedData = PublishedContent?.ContentData
+ DraftData = _draftData,
+ PublishedData = _publishedData
};
}
}
diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs
index fb4520c5c0..c7fc389cb1 100644
--- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs
+++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs
@@ -470,7 +470,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
private bool BuildKit(ContentNodeKit kit, out LinkedNode parent)
{
// make sure parent exists
- parent = GetParentLink(kit.Node);
+ parent = GetParentLink(kit.Node, null);
if (parent == null)
{
_logger.Warn($"Skip item id={kit.Node.Id}, could not find parent id={kit.Node.ParentContentId}.");
@@ -506,6 +506,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
public int Count => _contentNodes.Count;
+ ///
+ /// Get the most recent version of the LinkedNode stored in the dictionary for the supplied key
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
private static LinkedNode GetHead(ConcurrentDictionary> dict, TKey key)
where TValue : class
{
@@ -795,7 +803,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
var id = content.FirstChildContentId;
while (id > 0)
{
- var link = GetRequiredLinkedNode(id, "child");
+ var link = GetRequiredLinkedNode(id, "child", null);
ClearBranchLocked(link.Value);
id = link.Value.NextSiblingContentId;
}
@@ -806,11 +814,16 @@ namespace Umbraco.Web.PublishedCache.NuCache
///
///
///
+ /// the generation requested, null for the latest stored
///
- private LinkedNode GetRequiredLinkedNode(int id, string description)
+ private LinkedNode GetRequiredLinkedNode(int id, string description, long? gen)
{
- if (_contentNodes.TryGetValue(id, out var link) && link.Value != null)
- return link;
+ if (_contentNodes.TryGetValue(id, out var link))
+ {
+ link = GetLinkedNodeGen(link, gen);
+ if (link != null && link.Value != null)
+ return link;
+ }
throw new PanicException($"failed to get {description} with id={id}");
}
@@ -818,11 +831,18 @@ namespace Umbraco.Web.PublishedCache.NuCache
///
/// Gets the parent link node, may be null or root if ParentContentId is less than 0
///
- private LinkedNode GetParentLink(ContentNode content)
+ /// the generation requested, null for the latest stored
+ private LinkedNode GetParentLink(ContentNode content, long? gen)
{
- if (content.ParentContentId < 0) return _root;
+ if (content.ParentContentId < 0)
+ {
+ var root = GetLinkedNodeGen(_root, gen);
+ return root;
+ }
- _contentNodes.TryGetValue(content.ParentContentId, out var link);
+ if (_contentNodes.TryGetValue(content.ParentContentId, out var link))
+ link = GetLinkedNodeGen(link, gen);
+
return link;
}
@@ -830,17 +850,37 @@ namespace Umbraco.Web.PublishedCache.NuCache
/// Gets the linked parent node and if it doesn't exist throw a
///
///
+ /// the generation requested, null for the latest stored
///
- private LinkedNode GetRequiredParentLink(ContentNode content)
+ private LinkedNode GetRequiredParentLink(ContentNode content, long? gen)
{
- return content.ParentContentId < 0 ? _root : GetRequiredLinkedNode(content.ParentContentId, "parent");
+ return content.ParentContentId < 0 ? _root : GetRequiredLinkedNode(content.ParentContentId, "parent", gen);
+ }
+
+ ///
+ /// Iterates over the LinkedNode's generations to find the correct one
+ ///
+ ///
+ /// The generation requested, use null to avoid the lookup
+ ///
+ private LinkedNode GetLinkedNodeGen(LinkedNode link, long? gen)
+ where TValue : class
+ {
+ if (!gen.HasValue) return link;
+
+ //find the correct snapshot, find the first that is <= the requested gen
+ while (link != null && link.Gen > gen)
+ {
+ link = link.Next;
+ }
+ return link;
}
private void RemoveTreeNodeLocked(ContentNode content)
{
var parentLink = content.ParentContentId < 0
? _root
- : GetRequiredLinkedNode(content.ParentContentId, "parent");
+ : GetRequiredLinkedNode(content.ParentContentId, "parent", null);
var parent = parentLink.Value;
@@ -863,14 +903,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (content.NextSiblingContentId > 0)
{
- var nextLink = GetRequiredLinkedNode(content.NextSiblingContentId, "next sibling");
+ var nextLink = GetRequiredLinkedNode(content.NextSiblingContentId, "next sibling", null);
var next = GenCloneLocked(nextLink);
next.PreviousSiblingContentId = content.PreviousSiblingContentId;
}
if (content.PreviousSiblingContentId > 0)
{
- var prevLink = GetRequiredLinkedNode(content.PreviousSiblingContentId, "previous sibling");
+ var prevLink = GetRequiredLinkedNode(content.PreviousSiblingContentId, "previous sibling", null);
var prev = GenCloneLocked(prevLink);
prev.NextSiblingContentId = content.NextSiblingContentId;
}
@@ -883,9 +923,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
if (kit.Node.ParentContentId < 0)
return true;
- var link = GetParentLink(kit.Node);
+ var link = GetParentLink(kit.Node, null);
var node = link?.Value;
- return node?.PublishedModel != null;
+ return node != null && node.HasPublished;
}
private ContentNode GenCloneLocked(LinkedNode link)
@@ -909,10 +949,12 @@ namespace Umbraco.Web.PublishedCache.NuCache
///
private void AddTreeNodeLocked(ContentNode content, LinkedNode parentLink = null)
{
- parentLink = parentLink ?? GetRequiredParentLink(content);
+ parentLink = parentLink ?? GetRequiredParentLink(content, null);
var parent = parentLink.Value;
+ var currentGen = parentLink.Gen;
+
// if parent has no children, clone parent + add as first child
if (parent.FirstChildContentId < 0)
{
@@ -923,7 +965,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
// get parent's first child
- var childLink = GetRequiredLinkedNode(parent.FirstChildContentId, "first child");
+ var childLink = GetRequiredLinkedNode(parent.FirstChildContentId, "first child", currentGen);
var child = childLink.Value;
// if first, clone parent + insert as first child
@@ -943,7 +985,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
// get parent's last child
- var lastChildLink = GetRequiredLinkedNode(parent.LastChildContentId, "last child");
+ var lastChildLink = GetRequiredLinkedNode(parent.LastChildContentId, "last child", currentGen);
var lastChild = lastChildLink.Value;
// if last, clone parent + append as last child
@@ -968,7 +1010,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
while (child.NextSiblingContentId > 0)
{
// get next child
- var nextChildLink = GetRequiredLinkedNode(child.NextSiblingContentId, "next child");
+ var nextChildLink = GetRequiredLinkedNode(child.NextSiblingContentId, "next child", currentGen);
var nextChild = nextChildLink.Value;
// if here, clone previous + append/insert
@@ -1072,21 +1114,15 @@ namespace Umbraco.Web.PublishedCache.NuCache
public IEnumerable GetAtRoot(long gen)
{
- var z = _root;
- while (z != null)
- {
- if (z.Gen <= gen)
- break;
- z = z.Next;
- }
- if (z == null)
+ var root = GetLinkedNodeGen(_root, gen);
+ if (root == null)
yield break;
- var id = z.Value.FirstChildContentId;
+ var id = root.Value.FirstChildContentId;
while (id > 0)
{
- var link = GetRequiredLinkedNode(id, "sibling");
+ var link = GetRequiredLinkedNode(id, "root", gen);
yield return link.Value;
id = link.Value.NextSiblingContentId;
}
@@ -1097,13 +1133,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
// look ma, no lock!
var link = GetHead(dict, key);
- while (link != null)
- {
- if (link.Gen <= gen)
- return link.Value; // may be null
- link = link.Next;
- }
- return null;
+ link = GetLinkedNodeGen(link, gen);
+ return link?.Value; // may be null
}
public IEnumerable GetAll(long gen)
@@ -1113,17 +1144,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
var links = _contentNodes.Values.ToArray();
foreach (var l in links)
{
- var link = l;
- while (link != null)
- {
- if (link.Gen <= gen)
- {
- if (link.Value != null)
- yield return link.Value;
- break;
- }
- link = link.Next;
- }
+ var link = GetLinkedNodeGen(l, gen);
+ if (link?.Value != null)
+ yield return link.Value;
}
}
@@ -1131,14 +1154,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
var has = _contentNodes.Any(x =>
{
- var link = x.Value;
- while (link != null)
- {
- if (link.Gen <= gen && link.Value != null)
- return true;
- link = link.Next;
- }
- return false;
+ var link = GetLinkedNodeGen(x.Value, gen);
+ return link?.Value != null;
});
return has == false;
}
diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs
index 8fc2dc1006..bf4975714d 100644
--- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs
+++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs
@@ -233,8 +233,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// invariant content items)
// if there is no 'published' published content, no culture can be published
- var hasPublished = _contentNode.PublishedContent != null;
- if (!hasPublished)
+ if (!_contentNode.HasPublished)
return false;
// if there is a 'published' published content, and does not vary = published
@@ -247,7 +246,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// there is a 'published' published content, and varies
// = depends on the culture
- return _contentNode.PublishedContent.ContentData.CultureInfos.ContainsKey(culture);
+ return _contentNode.HasPublishedCulture(culture);
}
#endregion
diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs
index 15e6574b40..4239eda9f5 100755
--- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs
+++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs
@@ -160,7 +160,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
_domainStore = new SnapDictionary();
- publishedModelFactory.WithSafeLiveFactory(LoadCachesOnStartup);
+ LoadCachesOnStartup();
Guid GetUid(ContentStore store, int id) => store.LiveSnapshot.Get(id)?.Uid ?? default;
int GetId(ContentStore store, Guid uid) => store.LiveSnapshot.Get(uid)?.Id ?? default;
diff --git a/src/Umbraco.Web/RoutableDocumentFilter.cs b/src/Umbraco.Web/RoutableDocumentFilter.cs
new file mode 100644
index 0000000000..9725ce0e14
--- /dev/null
+++ b/src/Umbraco.Web/RoutableDocumentFilter.cs
@@ -0,0 +1,207 @@
+using System;
+using System.IO;
+using System.Web;
+using System.Web.Routing;
+using Umbraco.Core;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Configuration;
+using System.Threading;
+using System.Collections.Generic;
+using System.Linq;
+using Umbraco.Core.IO;
+using System.Collections.Concurrent;
+using Umbraco.Core.Collections;
+
+namespace Umbraco.Web
+{
+ ///
+ /// Utility class used to check if the current request is for a front-end request
+ ///
+ ///
+ /// There are various checks to determine if this is a front-end request such as checking if the request is part of any reserved paths or existing MVC routes.
+ ///
+ public sealed class RoutableDocumentFilter
+ {
+ public RoutableDocumentFilter(IGlobalSettings globalSettings)
+ {
+ _globalSettings = globalSettings;
+ }
+
+ private static readonly ConcurrentDictionary RouteChecks = new ConcurrentDictionary();
+ private readonly IGlobalSettings _globalSettings;
+ private object _locker = new object();
+ private bool _isInit = false;
+ private int? _routeCount;
+ private HashSet _reservedList;
+
+ ///
+ /// Checks if the request is a document request (i.e. one that the module should handle)
+ ///
+ ///
+ ///
+ ///
+ public bool IsDocumentRequest(HttpContextBase httpContext, Uri uri)
+ {
+ var maybeDoc = true;
+ var lpath = uri.AbsolutePath.ToLowerInvariant();
+
+ // handle directory-urls used for asmx
+ // TODO: legacy - what's the point really?
+ var asmxPos = lpath.IndexOf(".asmx/", StringComparison.OrdinalIgnoreCase);
+ if (asmxPos >= 0)
+ {
+ // use uri.AbsolutePath, not path, 'cos path has been lowercased
+ httpContext.RewritePath(uri.AbsolutePath.Substring(0, asmxPos + 5), // filePath
+ uri.AbsolutePath.Substring(asmxPos + 5), // pathInfo
+ uri.Query.TrimStart('?'));
+ maybeDoc = false;
+ }
+
+ // a document request should be
+ // /foo/bar/nil
+ // /foo/bar/nil/
+ // /foo/bar/nil.aspx
+ // where /foo is not a reserved path
+
+ // if the path contains an extension that is not .aspx
+ // then it cannot be a document request
+ var extension = Path.GetExtension(lpath);
+ if (maybeDoc && extension.IsNullOrWhiteSpace() == false && extension != ".aspx")
+ maybeDoc = false;
+
+ // at that point, either we have no extension, or it is .aspx
+
+ // if the path is reserved then it cannot be a document request
+ if (maybeDoc && IsReservedPathOrUrl(lpath, httpContext, RouteTable.Routes))
+ maybeDoc = false;
+
+ //NOTE: No need to warn, plus if we do we should log the document, as this message doesn't really tell us anything :)
+ //if (!maybeDoc)
+ //{
+ // Logger.Warn("Not a document");
+ //}
+ return maybeDoc;
+ }
+
+ ///
+ /// Determines whether the specified URL is reserved or is inside a reserved path.
+ ///
+ /// The URL to check.
+ ///
+ /// true if the specified URL is reserved; otherwise, false.
+ ///
+ internal bool IsReservedPathOrUrl(string url)
+ {
+ LazyInitializer.EnsureInitialized(ref _reservedList, ref _isInit, ref _locker, () =>
+ {
+ // store references to strings to determine changes
+ var reservedPathsCache = _globalSettings.ReservedPaths;
+ var reservedUrlsCache = _globalSettings.ReservedUrls;
+
+ // add URLs and paths to a new list
+ var newReservedList = new HashSet();
+ foreach (var reservedUrlTrimmed in reservedUrlsCache
+ .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(x => x.Trim().ToLowerInvariant())
+ .Where(x => x.IsNullOrWhiteSpace() == false)
+ .Select(reservedUrl => IOHelper.ResolveUrl(reservedUrl).Trim().EnsureStartsWith("/"))
+ .Where(reservedUrlTrimmed => reservedUrlTrimmed.IsNullOrWhiteSpace() == false))
+ {
+ newReservedList.Add(reservedUrlTrimmed);
+ }
+
+ foreach (var reservedPathTrimmed in NormalizePaths(reservedPathsCache.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)))
+ {
+ newReservedList.Add(reservedPathTrimmed);
+ }
+
+ foreach (var reservedPathTrimmed in NormalizePaths(ReservedPaths))
+ {
+ newReservedList.Add(reservedPathTrimmed);
+ }
+
+ // use the new list from now on
+ return newReservedList;
+ });
+
+ //The url should be cleaned up before checking:
+ // * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/'
+ // * We shouldn't be comparing the query at all
+ var pathPart = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0].ToLowerInvariant();
+ if (pathPart.Contains(".") == false)
+ {
+ pathPart = pathPart.EnsureEndsWith('/');
+ }
+
+ // return true if url starts with an element of the reserved list
+ return _reservedList.Any(x => pathPart.InvariantStartsWith(x));
+ }
+
+ private IEnumerable NormalizePaths(IEnumerable paths)
+ {
+ return paths
+ .Select(x => x.Trim().ToLowerInvariant())
+ .Where(x => x.IsNullOrWhiteSpace() == false)
+ .Select(reservedPath => IOHelper.ResolveUrl(reservedPath).Trim().EnsureStartsWith("/").EnsureEndsWith("/"))
+ .Where(reservedPathTrimmed => reservedPathTrimmed.IsNullOrWhiteSpace() == false);
+ }
+
+ ///
+ /// Determines whether the current request is reserved based on the route table and
+ /// whether the specified URL is reserved or is inside a reserved path.
+ ///
+ ///
+ ///
+ /// The route collection to lookup the request in
+ ///
+ internal bool IsReservedPathOrUrl(string url, HttpContextBase httpContext, RouteCollection routes)
+ {
+ if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
+ if (routes == null) throw new ArgumentNullException(nameof(routes));
+
+ //This is some rudimentary code to check if the route table has changed at runtime, we're basically just keeping a count
+ //of the routes. This isn't fail safe but there's no way to monitor changes to the route table. Else we need to create a hash
+ //of all routes and then recompare but that will be annoying to do on each request and then we might as well just do the whole MVC
+ //route on each request like we were doing before instead of caching the result of GetRouteData.
+ var changed = false;
+ using (routes.GetReadLock())
+ {
+ if (!_routeCount.HasValue || _routeCount.Value != routes.Count)
+ {
+ //the counts are not set or have changed, need to reset
+ changed = true;
+ }
+ }
+ if (changed)
+ {
+ using (routes.GetWriteLock())
+ {
+ _routeCount = routes.Count;
+
+ //try clearing each entry
+ foreach(var r in RouteChecks.Keys.ToList())
+ RouteChecks.TryRemove(r, out _);
+ }
+ }
+
+ var absPath = httpContext?.Request?.Url.AbsolutePath;
+
+ if (absPath.IsNullOrWhiteSpace())
+ return false;
+
+ //check if the current request matches a route, if so then it is reserved.
+ var hasRoute = RouteChecks.GetOrAdd(absPath, x => routes.GetRouteData(httpContext) != null);
+ if (hasRoute)
+ return true;
+
+ //continue with the standard ignore routine
+ return IsReservedPathOrUrl(url);
+ }
+
+ ///
+ /// This is used internally to track any registered callback paths for Identity providers. If the request path matches
+ /// any of the registered paths, then the module will let the request keep executing
+ ///
+ internal static readonly ConcurrentHashSet ReservedPaths = new ConcurrentHashSet();
+ }
+}
diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs
index e2b6313ca6..87c0f46fba 100644
--- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs
+++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs
@@ -73,7 +73,7 @@ namespace Umbraco.Web.Runtime
// register accessors for cultures
composition.RegisterUnique();
composition.RegisterUnique();
-
+
// register the http context and umbraco context accessors
// we *should* use the HttpContextUmbracoContextAccessor, however there are cases when
// we have no http context, eg when booting Umbraco or in background threads, so instead
@@ -125,6 +125,8 @@ namespace Umbraco.Web.Runtime
// register distributed cache
composition.RegisterUnique(f => new DistributedCache());
+ composition.RegisterUnique();
+
// replace some services
composition.RegisterUnique();
composition.RegisterUnique();
diff --git a/src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs b/src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs
index 33aabbaf94..b1b2755f4c 100644
--- a/src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs
+++ b/src/Umbraco.Web/Security/AuthenticationOptionsExtensions.cs
@@ -101,7 +101,7 @@ namespace Umbraco.Web.Security
var path = (PathString) prop.GetValue(options);
if (path.HasValue)
{
- UmbracoModule.ReservedPaths.TryAdd(path.ToString());
+ RoutableDocumentFilter.ReservedPaths.TryAdd(path.ToString());
}
}
}
@@ -112,7 +112,7 @@ namespace Umbraco.Web.Security
}
else
{
- UmbracoModule.ReservedPaths.TryAdd(callbackPath);
+ RoutableDocumentFilter.ReservedPaths.TryAdd(callbackPath);
}
}
}
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index f696178a86..c6945a6b15 100755
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -230,6 +230,7 @@
+
diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs
index c0306e0a81..347f79e51b 100644
--- a/src/Umbraco.Web/UmbracoContext.cs
+++ b/src/Umbraco.Web/UmbracoContext.cs
@@ -1,16 +1,21 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Web;
+using System.Web.Routing;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
+using Umbraco.Core.Events;
using Umbraco.Core.Models.PublishedContent;
+using Umbraco.Web;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Security;
namespace Umbraco.Web
{
+
///
/// Class that encapsulates Umbraco information of a specific HTTP request
///
@@ -285,7 +290,7 @@ namespace Umbraco.Web
_previewing = _previewToken.IsNullOrWhiteSpace() == false;
}
-
+
// say we render a macro or RTE in a give 'preview' mode that might not be the 'current' one,
// then due to the way it all works at the moment, the 'current' published snapshot need to be in the proper
// default 'preview' mode - somehow we have to force it. and that could be recursive.
diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs
index db5372efa2..6dc41d39ee 100644
--- a/src/Umbraco.Web/UmbracoInjectedModule.cs
+++ b/src/Umbraco.Web/UmbracoInjectedModule.cs
@@ -1,8 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
-using System.IO;
-using System.Text;
using System.Web;
using System.Web.Routing;
using Umbraco.Core;
@@ -10,14 +8,9 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Web.Routing;
-using Umbraco.Web.Security;
using Umbraco.Core.Exceptions;
-using Umbraco.Core.Models.PublishedContent;
-using Umbraco.Core.Persistence.FaultHandling;
using Umbraco.Core.Security;
-using Umbraco.Core.Services;
using Umbraco.Web.Composing;
-using Umbraco.Web.PublishedCache;
namespace Umbraco.Web
{
@@ -39,40 +32,26 @@ namespace Umbraco.Web
public class UmbracoInjectedModule : IHttpModule
{
private readonly IGlobalSettings _globalSettings;
- private readonly IUmbracoContextAccessor _umbracoContextAccessor;
- private readonly IPublishedSnapshotService _publishedSnapshotService;
- private readonly IUserService _userService;
- private readonly UrlProviderCollection _urlProviders;
private readonly IRuntimeState _runtime;
private readonly ILogger _logger;
private readonly IPublishedRouter _publishedRouter;
- private readonly IVariationContextAccessor _variationContextAccessor;
private readonly IUmbracoContextFactory _umbracoContextFactory;
+ private readonly RoutableDocumentFilter _routableDocumentLookup;
public UmbracoInjectedModule(
IGlobalSettings globalSettings,
- IUmbracoContextAccessor umbracoContextAccessor,
- IPublishedSnapshotService publishedSnapshotService,
- IUserService userService,
- UrlProviderCollection urlProviders,
IRuntimeState runtime,
ILogger logger,
IPublishedRouter publishedRouter,
- IVariationContextAccessor variationContextAccessor,
- IUmbracoContextFactory umbracoContextFactory)
+ IUmbracoContextFactory umbracoContextFactory,
+ RoutableDocumentFilter routableDocumentLookup)
{
- _combinedRouteCollection = new Lazy(CreateRouteCollection);
-
_globalSettings = globalSettings;
- _umbracoContextAccessor = umbracoContextAccessor;
- _publishedSnapshotService = publishedSnapshotService;
- _userService = userService;
- _urlProviders = urlProviders;
_runtime = runtime;
_logger = logger;
_publishedRouter = publishedRouter;
- _variationContextAccessor = variationContextAccessor;
_umbracoContextFactory = umbracoContextFactory;
+ _routableDocumentLookup = routableDocumentLookup;
}
#region HttpModule event handlers
@@ -182,18 +161,18 @@ namespace Umbraco.Web
var reason = EnsureRoutableOutcome.IsRoutable;
// ensure this is a document request
- if (EnsureDocumentRequest(httpContext, uri) == false)
+ if (!_routableDocumentLookup.IsDocumentRequest(httpContext, context.OriginalRequestUrl))
{
reason = EnsureRoutableOutcome.NotDocumentRequest;
}
// ensure the runtime is in the proper state
// and deal with needed redirects, etc
- else if (EnsureRuntime(httpContext, uri) == false)
+ else if (!EnsureRuntime(httpContext, uri))
{
reason = EnsureRoutableOutcome.NotReady;
}
// ensure Umbraco has documents to serve
- else if (EnsureHasContent(context, httpContext) == false)
+ else if (!EnsureHasContent(context, httpContext))
{
reason = EnsureRoutableOutcome.NoContent;
}
@@ -201,55 +180,7 @@ namespace Umbraco.Web
return Attempt.If(reason == EnsureRoutableOutcome.IsRoutable, reason);
}
- ///
- /// Ensures that the request is a document request (i.e. one that the module should handle)
- ///
- ///
- ///
- ///
- private bool EnsureDocumentRequest(HttpContextBase httpContext, Uri uri)
- {
- var maybeDoc = true;
- var lpath = uri.AbsolutePath.ToLowerInvariant();
-
- // handle directory-urls used for asmx
- // TODO: legacy - what's the point really?
- var asmxPos = lpath.IndexOf(".asmx/", StringComparison.OrdinalIgnoreCase);
- if (asmxPos >= 0)
- {
- // use uri.AbsolutePath, not path, 'cos path has been lowercased
- httpContext.RewritePath(uri.AbsolutePath.Substring(0, asmxPos + 5), // filePath
- uri.AbsolutePath.Substring(asmxPos + 5), // pathInfo
- uri.Query.TrimStart('?'));
- maybeDoc = false;
- }
-
- // a document request should be
- // /foo/bar/nil
- // /foo/bar/nil/
- // /foo/bar/nil.aspx
- // where /foo is not a reserved path
-
- // if the path contains an extension that is not .aspx
- // then it cannot be a document request
- var extension = Path.GetExtension(lpath);
- if (maybeDoc && extension.IsNullOrWhiteSpace() == false && extension != ".aspx")
- maybeDoc = false;
-
- // at that point, either we have no extension, or it is .aspx
-
- // if the path is reserved then it cannot be a document request
- if (maybeDoc && _globalSettings.IsReservedPathOrUrl(lpath, httpContext, _combinedRouteCollection.Value))
- maybeDoc = false;
-
- //NOTE: No need to warn, plus if we do we should log the document, as this message doesn't really tell us anything :)
- //if (!maybeDoc)
- //{
- // Logger.Warn("Not a document");
- //}
-
- return maybeDoc;
- }
+
private bool EnsureRuntime(HttpContextBase httpContext, Uri uri)
{
@@ -505,36 +436,6 @@ namespace Umbraco.Web
#endregion
- ///
- /// This is used to be passed into the GlobalSettings.IsReservedPathOrUrl and will include some 'fake' routes
- /// used to determine if a path is reserved.
- ///
- ///
- /// This is basically used to reserve paths dynamically
- ///
- private readonly Lazy _combinedRouteCollection;
-
- private RouteCollection CreateRouteCollection()
- {
- var routes = new RouteCollection();
-
- foreach (var route in RouteTable.Routes)
- routes.Add(route);
-
- foreach (var reservedPath in UmbracoModule.ReservedPaths)
- {
- try
- {
- routes.Add("_umbreserved_" + reservedPath.ReplaceNonAlphanumericChars(""),
- new Route(reservedPath.TrimStart('/'), new StopRoutingHandler()));
- }
- catch (Exception ex)
- {
- _logger.Error("Could not add reserved path route", ex);
- }
- }
-
- return routes;
- }
+
}
}
diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs
index 048e521969..9065341ab9 100644
--- a/src/Umbraco.Web/UmbracoModule.cs
+++ b/src/Umbraco.Web/UmbracoModule.cs
@@ -103,10 +103,5 @@ namespace Umbraco.Web
return end;
}
- ///
- /// This is used internally to track any registered callback paths for Identity providers. If the request path matches
- /// any of the registered paths, then the module will let the request keep executing
- ///
- internal static readonly ConcurrentHashSet ReservedPaths = new ConcurrentHashSet();
}
}
diff --git a/src/umbraco.sln b/src/umbraco.sln
index 0522b56c0d..938532beb0 100644
--- a/src/umbraco.sln
+++ b/src/umbraco.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27004.2005
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29209.152
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web.UI", "Umbraco.Web.UI\Umbraco.Web.UI.csproj", "{4C4C194C-B5E4-4991-8F87-4373E24CC19F}"
EndProject
@@ -123,6 +123,7 @@ Global
{31785BC3-256C-4613-B2F5-A1B0BDDED8C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31785BC3-256C-4613-B2F5-A1B0BDDED8C1}.Release|Any CPU.Build.0 = Release|Any CPU
{5D3B8245-ADA6-453F-A008-50ED04BFE770}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5D3B8245-ADA6-453F-A008-50ED04BFE770}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5D3B8245-ADA6-453F-A008-50ED04BFE770}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5D3B8245-ADA6-453F-A008-50ED04BFE770}.Release|Any CPU.Build.0 = Release|Any CPU
{07FBC26B-2927-4A22-8D96-D644C667FECC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -130,6 +131,7 @@ Global
{07FBC26B-2927-4A22-8D96-D644C667FECC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07FBC26B-2927-4A22-8D96-D644C667FECC}.Release|Any CPU.Build.0 = Release|Any CPU
{3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection