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