Merge pull request #6347 from umbraco/v8/bugfix/AB2684-purelive-model-regen
Lazily recompile PureLive models and lazily create models for nucache
This commit is contained in:
@@ -33,7 +33,6 @@ namespace Umbraco.Core.Configuration
|
||||
/// </summary>
|
||||
private static void ResetInternal()
|
||||
{
|
||||
GlobalSettingsExtensions.Reset();
|
||||
_reservedPaths = null;
|
||||
_reservedUrls = null;
|
||||
HasSmtpServer = null;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Used in unit testing to reset all config items, this is automatically called by GlobalSettings.Reset()
|
||||
/// </summary>
|
||||
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<string> _reservedList = new HashSet<string>();
|
||||
private static string _mvcArea;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This returns the string of the MVC Area route.
|
||||
/// </summary>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified URL is reserved or is inside a reserved path.
|
||||
/// </summary>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="url">The URL to check.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified URL is reserved; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
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<string>();
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <param name="routes">The route collection to lookup the request in</param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IDataType>();
|
||||
var removedLanguages = new List<ILanguage>();
|
||||
|
||||
|
||||
//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<IContentType>();
|
||||
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<IContentType>();
|
||||
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<IContent> ImportContent(IEnumerable<CompiledPackageDocument> docs, IDictionary<string, IContentType> importedDocumentTypes, int userId)
|
||||
public IReadOnlyList<IContent> ImportContent(IEnumerable<CompiledPackageDocument> docs, IDictionary<string, IContentType> importedDocumentTypes, int userId)
|
||||
{
|
||||
return docs.SelectMany(x => ImportContent(x, -1, importedDocumentTypes, userId));
|
||||
return docs.SelectMany(x => ImportContent(x, -1, importedDocumentTypes, userId)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -352,7 +386,7 @@ namespace Umbraco.Core.Packaging
|
||||
|
||||
#region DocumentTypes
|
||||
|
||||
public IEnumerable<IContentType> ImportDocumentType(XElement docTypeElement, int userId)
|
||||
public IReadOnlyList<IContentType> ImportDocumentType(XElement docTypeElement, int userId)
|
||||
{
|
||||
return ImportDocumentTypes(new[] { docTypeElement }, userId);
|
||||
}
|
||||
@@ -363,7 +397,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="docTypeElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
|
||||
/// <returns>An enumerable list of generated ContentTypes</returns>
|
||||
public IEnumerable<IContentType> ImportDocumentTypes(IEnumerable<XElement> docTypeElements, int userId)
|
||||
public IReadOnlyList<IContentType> ImportDocumentTypes(IEnumerable<XElement> docTypeElements, int userId)
|
||||
{
|
||||
return ImportDocumentTypes(docTypeElements.ToList(), true, userId);
|
||||
}
|
||||
@@ -375,7 +409,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="importStructure">Boolean indicating whether or not to import the </param>
|
||||
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
|
||||
/// <returns>An enumerable list of generated ContentTypes</returns>
|
||||
public IEnumerable<IContentType> ImportDocumentTypes(IReadOnlyCollection<XElement> unsortedDocumentTypes, bool importStructure, int userId)
|
||||
public IReadOnlyList<IContentType> ImportDocumentTypes(IReadOnlyCollection<XElement> unsortedDocumentTypes, bool importStructure, int userId)
|
||||
{
|
||||
var importedContentTypes = new Dictionary<string, IContentType>();
|
||||
|
||||
@@ -824,7 +858,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="dataTypeElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the user</param>
|
||||
/// <returns>An enumerable list of generated DataTypeDefinitions</returns>
|
||||
public IEnumerable<IDataType> ImportDataTypes(IReadOnlyCollection<XElement> dataTypeElements, int userId)
|
||||
public IReadOnlyList<IDataType> ImportDataTypes(IReadOnlyCollection<XElement> dataTypeElements, int userId)
|
||||
{
|
||||
var dataTypes = new List<IDataType>();
|
||||
|
||||
@@ -953,13 +987,13 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="dictionaryItemElementList">Xml to import</param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns>An enumerable list of dictionary items</returns>
|
||||
public IEnumerable<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dictionaryItemElementList, int userId)
|
||||
public IReadOnlyList<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dictionaryItemElementList, int userId)
|
||||
{
|
||||
var languages = _localizationService.GetAllLanguages().ToList();
|
||||
return ImportDictionaryItems(dictionaryItemElementList, languages, null, userId);
|
||||
}
|
||||
|
||||
private IEnumerable<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dictionaryItemElementList, List<ILanguage> languages, Guid? parentId, int userId)
|
||||
private IReadOnlyList<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dictionaryItemElementList, List<ILanguage> languages, Guid? parentId, int userId)
|
||||
{
|
||||
var items = new List<IDictionaryItem>();
|
||||
foreach (var dictionaryItemElement in dictionaryItemElementList)
|
||||
@@ -1036,7 +1070,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="languageElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the User performing the operation</param>
|
||||
/// <returns>An enumerable list of generated languages</returns>
|
||||
public IEnumerable<ILanguage> ImportLanguages(IEnumerable<XElement> languageElements, int userId)
|
||||
public IReadOnlyList<ILanguage> ImportLanguages(IEnumerable<XElement> languageElements, int userId)
|
||||
{
|
||||
var list = new List<ILanguage>();
|
||||
foreach (var languageElement in languageElements)
|
||||
@@ -1065,7 +1099,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="macroElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the User performing the operation</param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<IMacro> ImportMacros(IEnumerable<XElement> macroElements, int userId)
|
||||
public IReadOnlyList<IMacro> ImportMacros(IEnumerable<XElement> macroElements, int userId)
|
||||
{
|
||||
var macros = macroElements.Select(ParseMacroElement).ToList();
|
||||
|
||||
@@ -1155,7 +1189,7 @@ namespace Umbraco.Core.Packaging
|
||||
|
||||
#region Stylesheets
|
||||
|
||||
public IEnumerable<IFile> ImportStylesheets(IEnumerable<XElement> stylesheetElements, int userId)
|
||||
public IReadOnlyList<IFile> ImportStylesheets(IEnumerable<XElement> stylesheetElements, int userId)
|
||||
{
|
||||
var result = new List<IFile>();
|
||||
|
||||
@@ -1223,7 +1257,7 @@ namespace Umbraco.Core.Packaging
|
||||
/// <param name="templateElements">Xml to import</param>
|
||||
/// <param name="userId">Optional user id</param>
|
||||
/// <returns>An enumerable list of generated Templates</returns>
|
||||
public IEnumerable<ITemplate> ImportTemplates(IReadOnlyCollection<XElement> templateElements, int userId)
|
||||
public IReadOnlyList<ITemplate> ImportTemplates(IReadOnlyCollection<XElement> templateElements, int userId)
|
||||
{
|
||||
var templates = new List<ITemplate>();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
/// <returns></returns>
|
||||
public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Executes an action with a safe live factory
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>If the factory is a live factory, ensures it is refreshed and locked while executing the action.</para>
|
||||
/// </remarks>
|
||||
[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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a flag to reset the ModelsBuilder models if the <see cref="IPublishedModelFactory"/> is <see cref="ILivePublishedModelFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <remarks>
|
||||
/// This does not recompile the pure live models, only sets a flag to tell models builder to recompile when they are requested.
|
||||
/// </remarks>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IGlobalSettings>()); //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<IGlobalSettings>()); //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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PropertyEditorCollection>());
|
||||
Factory.GetInstance<PropertyEditorCollection>(),
|
||||
Factory.GetInstance<IScopeProvider>());
|
||||
|
||||
private IPackageInstallation PackageInstallation => new PackageInstallation(
|
||||
PackageDataInstallation,
|
||||
|
||||
80
src/Umbraco.Tests/Routing/RoutableDocumentFilterTests.cs
Normal file
80
src/Umbraco.Tests/Routing/RoutableDocumentFilterTests.cs
Normal file
@@ -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<IGlobalSettings>()); //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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,15 +38,11 @@ namespace Umbraco.Tests.Routing
|
||||
_module = new UmbracoInjectedModule
|
||||
(
|
||||
globalSettings,
|
||||
Mock.Of<IUmbracoContextAccessor>(),
|
||||
Factory.GetInstance<IPublishedSnapshotService>(),
|
||||
Factory.GetInstance<IUserService>(),
|
||||
new UrlProviderCollection(new IUrlProvider[0]),
|
||||
runtime,
|
||||
logger,
|
||||
null, // FIXME: PublishedRouter complexities...
|
||||
Mock.Of<IVariationContextAccessor>(),
|
||||
Mock.Of<IUmbracoContextFactory>()
|
||||
Mock.Of<IUmbracoContextFactory>(),
|
||||
new RoutableDocumentFilter(globalSettings)
|
||||
);
|
||||
|
||||
runtime.Level = RuntimeLevel.Run;
|
||||
|
||||
@@ -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<IPackageActionRunner>(),
|
||||
new DirectoryInfo(IOHelper.GetRootDirectorySafe())));
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
<Compile Include="Composing\CompositionTests.cs" />
|
||||
<Compile Include="Composing\LightInjectValidation.cs" />
|
||||
<Compile Include="Composing\ContainerConformingTests.cs" />
|
||||
<Compile Include="Configurations\GlobalSettingsTests.cs" />
|
||||
<Compile Include="CoreThings\CallContextTests.cs" />
|
||||
<Compile Include="Components\ComponentTests.cs" />
|
||||
<Compile Include="CoreThings\EnumExtensionsTests.cs" />
|
||||
@@ -145,6 +146,7 @@
|
||||
<Compile Include="PublishedContent\SolidPublishedSnapshot.cs" />
|
||||
<Compile Include="PublishedContent\NuCacheTests.cs" />
|
||||
<Compile Include="Routing\MediaUrlProviderTests.cs" />
|
||||
<Compile Include="Routing\RoutableDocumentFilterTests.cs" />
|
||||
<Compile Include="Runtimes\StandaloneTests.cs" />
|
||||
<Compile Include="Routing\GetContentUrlsTests.cs" />
|
||||
<Compile Include="Services\AmbiguousEventTests.cs" />
|
||||
@@ -455,7 +457,6 @@
|
||||
<Compile Include="Cache\DistributedCache\DistributedCacheTests.cs" />
|
||||
<Compile Include="TestHelpers\TestWithDatabaseBase.cs" />
|
||||
<Compile Include="TestHelpers\SettingsForTests.cs" />
|
||||
<Compile Include="Configurations\GlobalSettingsTests.cs" />
|
||||
<Compile Include="Routing\ContentFinderByAliasTests.cs" />
|
||||
<Compile Include="Routing\ContentFinderByIdTests.cs" />
|
||||
<Compile Include="Routing\ContentFinderByPageIdQueryTests.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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception filter checking if we get a <see cref="ModelBindingException" /> or <see cref="InvalidCastException" /> 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 <see cref="ModelBindingException" /> or <see cref="InvalidCastException" /> with the same model.
|
||||
/// In which case it returns a redirect to the same page after 1 sec if not in debug mode.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only enabled when running PureLive
|
||||
/// </remarks>
|
||||
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)))
|
||||
{
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,7 +470,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
private bool BuildKit(ContentNodeKit kit, out LinkedNode<ContentNode> parent)
|
||||
{
|
||||
// make sure parent exists
|
||||
parent = GetParentLink(kit.Node);
|
||||
parent = GetParentLink(kit.Node, null);
|
||||
if (parent == null)
|
||||
{
|
||||
_logger.Warn<ContentStore>($"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;
|
||||
|
||||
/// <summary>
|
||||
/// Get the most recent version of the LinkedNode stored in the dictionary for the supplied key
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey"></typeparam>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
/// <param name="dict"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
private static LinkedNode<TValue> GetHead<TKey, TValue>(ConcurrentDictionary<TKey, LinkedNode<TValue>> 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
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="description"></param>
|
||||
/// <param name="gen">the generation requested, null for the latest stored</param>
|
||||
/// <returns></returns>
|
||||
private LinkedNode<ContentNode> GetRequiredLinkedNode(int id, string description)
|
||||
private LinkedNode<ContentNode> 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
|
||||
/// <summary>
|
||||
/// Gets the parent link node, may be null or root if ParentContentId is less than 0
|
||||
/// </summary>
|
||||
private LinkedNode<ContentNode> GetParentLink(ContentNode content)
|
||||
/// <param name="gen">the generation requested, null for the latest stored</param>
|
||||
private LinkedNode<ContentNode> 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 <see cref="PanicException"/>
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="gen">the generation requested, null for the latest stored</param>
|
||||
/// <returns></returns>
|
||||
private LinkedNode<ContentNode> GetRequiredParentLink(ContentNode content)
|
||||
private LinkedNode<ContentNode> GetRequiredParentLink(ContentNode content, long? gen)
|
||||
{
|
||||
return content.ParentContentId < 0 ? _root : GetRequiredLinkedNode(content.ParentContentId, "parent");
|
||||
return content.ParentContentId < 0 ? _root : GetRequiredLinkedNode(content.ParentContentId, "parent", gen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates over the LinkedNode's generations to find the correct one
|
||||
/// </summary>
|
||||
/// <param name="link"></param>
|
||||
/// <param name="gen">The generation requested, use null to avoid the lookup</param>
|
||||
/// <returns></returns>
|
||||
private LinkedNode<TValue> GetLinkedNodeGen<TValue>(LinkedNode<TValue> 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<ContentNode> link)
|
||||
@@ -909,10 +949,12 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
/// </summary>
|
||||
private void AddTreeNodeLocked(ContentNode content, LinkedNode<ContentNode> 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<ContentNode> 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<ContentNode> 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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
|
||||
_domainStore = new SnapDictionary<int, Domain>();
|
||||
|
||||
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;
|
||||
|
||||
207
src/Umbraco.Web/RoutableDocumentFilter.cs
Normal file
207
src/Umbraco.Web/RoutableDocumentFilter.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class used to check if the current request is for a front-end request
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
public sealed class RoutableDocumentFilter
|
||||
{
|
||||
public RoutableDocumentFilter(IGlobalSettings globalSettings)
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<string, bool> RouteChecks = new ConcurrentDictionary<string, bool>();
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private object _locker = new object();
|
||||
private bool _isInit = false;
|
||||
private int? _routeCount;
|
||||
private HashSet<string> _reservedList;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the request is a document request (i.e. one that the module should handle)
|
||||
/// </summary>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <param name="uri"></param>
|
||||
/// <returns></returns>
|
||||
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<UmbracoModule>("Not a document");
|
||||
//}
|
||||
return maybeDoc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified URL is reserved or is inside a reserved path.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL to check.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified URL is reserved; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
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<string>();
|
||||
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<string> NormalizePaths(IEnumerable<string> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <param name="routes">The route collection to lookup the request in</param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
internal static readonly ConcurrentHashSet<string> ReservedPaths = new ConcurrentHashSet<string>();
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ namespace Umbraco.Web.Runtime
|
||||
// register accessors for cultures
|
||||
composition.RegisterUnique<IDefaultCultureAccessor, DefaultCultureAccessor>();
|
||||
composition.RegisterUnique<IVariationContextAccessor, HybridVariationContextAccessor>();
|
||||
|
||||
|
||||
// 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<RoutableDocumentFilter>();
|
||||
|
||||
// replace some services
|
||||
composition.RegisterUnique<IEventMessagesFactory, DefaultEventMessagesFactory>();
|
||||
composition.RegisterUnique<IEventMessagesAccessor, HybridEventMessagesAccessor>();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
<Compile Include="PublishedCache\NuCache\Snap\GenObj.cs" />
|
||||
<Compile Include="PublishedCache\NuCache\Snap\GenRef.cs" />
|
||||
<Compile Include="PublishedCache\NuCache\Snap\LinkedNode.cs" />
|
||||
<Compile Include="RoutableDocumentFilter.cs" />
|
||||
<Compile Include="Routing\DefaultMediaUrlProvider.cs" />
|
||||
<Compile Include="Routing\IMediaUrlProvider.cs" />
|
||||
<Compile Include="Routing\IPublishedRouter.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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Class that encapsulates Umbraco information of a specific HTTP request
|
||||
/// </summary>
|
||||
@@ -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.
|
||||
|
||||
@@ -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<RouteCollection>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the request is a document request (i.e. one that the module should handle)
|
||||
/// </summary>
|
||||
/// <param name="httpContext"></param>
|
||||
/// <param name="uri"></param>
|
||||
/// <returns></returns>
|
||||
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<UmbracoModule>("Not a document");
|
||||
//}
|
||||
|
||||
return maybeDoc;
|
||||
}
|
||||
|
||||
|
||||
private bool EnsureRuntime(HttpContextBase httpContext, Uri uri)
|
||||
{
|
||||
@@ -505,36 +436,6 @@ namespace Umbraco.Web
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// This is used to be passed into the GlobalSettings.IsReservedPathOrUrl and will include some 'fake' routes
|
||||
/// used to determine if a path is reserved.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is basically used to reserve paths dynamically
|
||||
/// </remarks>
|
||||
private readonly Lazy<RouteCollection> _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<UmbracoModule>("Could not add reserved path route", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,10 +103,5 @@ namespace Umbraco.Web
|
||||
return end;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
internal static readonly ConcurrentHashSet<string> ReservedPaths = new ConcurrentHashSet<string>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user