diff --git a/Directory.Build.props b/Directory.Build.props index 4e42bb2f8f..8ae2e0baec 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,7 +8,7 @@ all - 3.5.103 + 3.5.107 diff --git a/src/JsonSchema/JsonSchema.csproj b/src/JsonSchema/JsonSchema.csproj index 97608c756b..5ab8db1d99 100644 --- a/src/JsonSchema/JsonSchema.csproj +++ b/src/JsonSchema/JsonSchema.csproj @@ -7,13 +7,16 @@ - - + + - + + + 3.5.107 + diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs index 960ef4451c..0cb4cbcd92 100644 --- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs +++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs @@ -38,6 +38,20 @@ public class SqliteSyntaxProvider : SqlSyntaxProviderBase [typeof(Guid)] = new SqliteGuidScalarMapper(), [typeof(Guid?)] = new SqliteNullableGuidScalarMapper(), }; + + IntColumnDefinition = "INTEGER"; + LongColumnDefinition = "INTEGER"; + BoolColumnDefinition = "INTEGER"; + + GuidColumnDefinition = "TEXT"; + DateTimeColumnDefinition = "TEXT"; + DateTimeOffsetColumnDefinition = "TEXT"; + TimeColumnDefinition = "TEXT"; + DecimalColumnDefinition = "TEXT"; // REAL would be lossy. - https://docs.microsoft.com/en-us/dotnet/standard/data/sqlite/types + + RealColumnDefinition = "REAL"; + + BlobColumnDefinition = "BLOB"; } /// diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj b/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj index 57055ec96b..5aa062df17 100644 --- a/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj +++ b/src/Umbraco.Cms.Persistence.Sqlite/Umbraco.Cms.Persistence.Sqlite.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Umbraco.Cms/Umbraco.Cms.csproj b/src/Umbraco.Cms/Umbraco.Cms.csproj index 0a811cf0b3..23e8febd18 100644 --- a/src/Umbraco.Cms/Umbraco.Cms.csproj +++ b/src/Umbraco.Cms/Umbraco.Cms.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/Umbraco.Cms/buildTransitive/Umbraco.Cms.props b/src/Umbraco.Cms/buildTransitive/Umbraco.Cms.props index ea0b013665..7266a05f6e 100644 --- a/src/Umbraco.Cms/buildTransitive/Umbraco.Cms.props +++ b/src/Umbraco.Cms/buildTransitive/Umbraco.Cms.props @@ -5,4 +5,8 @@ $(DefaultItemExcludes);umbraco/Logs/** $(DefaultItemExcludes);wwwroot/media/** + + + + diff --git a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs index fdb7bac0ef..c791ec6168 100644 --- a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs @@ -41,19 +41,20 @@ public class ModelsBuilderSettings /// public bool FlagOutOfDateModels { - get => _flagOutOfDateModels; - - set + get { - if (!ModelsMode.IsAuto()) + if (ModelsMode == ModelsMode.Nothing ||ModelsMode.IsAuto()) { - _flagOutOfDateModels = false; - return; + return false; + } - _flagOutOfDateModels = value; + return _flagOutOfDateModels; + } + + set =>_flagOutOfDateModels = value; } - } + /// /// Gets or sets a value for the models directory. diff --git a/src/Umbraco.Core/Models/RelationItem.cs b/src/Umbraco.Core/Models/RelationItem.cs index 409776b7e3..111d7e6dfa 100644 --- a/src/Umbraco.Core/Models/RelationItem.cs +++ b/src/Umbraco.Core/Models/RelationItem.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; @@ -18,7 +18,7 @@ public class RelationItem public string? NodeType { get; set; } [DataMember(Name = "udi")] - public Udi NodeUdi => Udi.Create(NodeType, NodeKey); + public Udi? NodeUdi => NodeType == Constants.UdiEntityType.Unknown ? null : Udi.Create(NodeType, NodeKey); [DataMember(Name = "icon")] public string? ContentTypeIcon { get; set; } diff --git a/src/Umbraco.Core/Scoping/ICoreScope.cs b/src/Umbraco.Core/Scoping/ICoreScope.cs index ef3cf91c4c..fe2a9489f3 100644 --- a/src/Umbraco.Core/Scoping/ICoreScope.cs +++ b/src/Umbraco.Core/Scoping/ICoreScope.cs @@ -8,6 +8,14 @@ namespace Umbraco.Cms.Core.Scoping; /// public interface ICoreScope : IDisposable, IInstanceIdentifiable { + /// + /// Gets the distance from the root scope. + /// + /// + /// A zero represents a root scope, any value greater than zero represents a child scope. + /// + public int Depth => -1; + /// /// Gets the scope notification publisher /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8bcead30bb..01183739d5 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -16,10 +16,10 @@ - + - + diff --git a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj index db5a303efd..833e01be50 100644 --- a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj +++ b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj @@ -21,7 +21,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs index e9e1236a9e..6d583151ba 100644 --- a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs +++ b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs @@ -35,11 +35,6 @@ namespace Umbraco.Cms.Core.Configuration public void RemoveConnectionString() { - // Update and reload configuration - _configuration[UmbracoConnectionStringPath] = null; - _configuration[UmbracoConnectionStringProviderNamePath] = null; - (_configuration as IConfigurationRoot)?.Reload(); - // Remove keys from JSON var provider = GetJsonConfigurationProvider(UmbracoConnectionStringPath); @@ -58,11 +53,6 @@ namespace Umbraco.Cms.Core.Configuration public void SaveConnectionString(string connectionString, string? providerName) { - // Update and reload configuration - _configuration[UmbracoConnectionStringPath] = connectionString; - _configuration[UmbracoConnectionStringProviderNamePath] = providerName; - (_configuration as IConfigurationRoot)?.Reload(); - // Save keys to JSON var provider = GetJsonConfigurationProvider(); @@ -84,10 +74,6 @@ namespace Umbraco.Cms.Core.Configuration public void SaveConfigValue(string key, object value) { - // Update and reload configuration - _configuration[key] = value?.ToString(); - (_configuration as IConfigurationRoot)?.Reload(); - // Save key to JSON var provider = GetJsonConfigurationProvider(); @@ -122,10 +108,6 @@ namespace Umbraco.Cms.Core.Configuration public void SaveDisableRedirectUrlTracking(bool disable) { - // Update and reload configuration - _configuration["Umbraco:CMS:WebRouting:DisableRedirectUrlTracking"] = disable.ToString(); - (_configuration as IConfigurationRoot)?.Reload(); - // Save key to JSON var provider = GetJsonConfigurationProvider(); @@ -147,10 +129,6 @@ namespace Umbraco.Cms.Core.Configuration public void SetGlobalId(string id) { - // Update and reload configuration - _configuration["Umbraco:CMS:Global:Id"] = id; - (_configuration as IConfigurationRoot)?.Reload(); - // Save key to JSON var provider = GetJsonConfigurationProvider(); @@ -336,17 +314,21 @@ namespace Umbraco.Cms.Core.Configuration { if (token is JObject obj) { - foreach (var property in obj.Properties()) { if (name is null) + { return property.Value; + } + if (string.Equals(property.Name, name, StringComparison.OrdinalIgnoreCase)) + { return property.Value; + } } } + return null; } - } } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 9252cb7111..3c05e56ac6 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -97,11 +97,14 @@ public static partial class UmbracoBuilderExtensions builder.Mappers()?.AddCoreMappers(); // register the scope provider - builder.Services.AddSingleton(); // implements IScopeProvider, IScopeAccessor + builder.Services.AddSingleton(sp => ActivatorUtilities.CreateInstance(sp, sp.GetRequiredService())); // implements IScopeProvider, IScopeAccessor builder.Services.AddSingleton(f => f.GetRequiredService()); builder.Services.AddSingleton(f => f.GetRequiredService()); builder.Services.AddSingleton(f => f.GetRequiredService()); - builder.Services.AddSingleton(f => f.GetRequiredService()); + + builder.Services.AddSingleton(); + builder.Services.AddSingleton(f => f.GetRequiredService()); + builder.Services.AddSingleton(); builder.Services.AddScoped(); diff --git a/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs b/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs index f58d9d8854..e740ff1b26 100644 --- a/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs +++ b/src/Umbraco.Infrastructure/Logging/MessageTemplates.cs @@ -1,4 +1,4 @@ -using Serilog; +using Serilog; using Serilog.Events; using Serilog.Parsing; @@ -27,14 +27,14 @@ public class MessageTemplates : IMessageTemplates throw new FormatException($"Could not format message \"{messageTemplate}\" with {args.Length} args."); } - var values = boundProperties.ToDictionary(x => x.Name, x => x.Value); + var values = boundProperties!.ToDictionary(x => x.Name, x => x.Value); // this ends up putting every string parameter between quotes // return parsedTemplate.Render(values); // this does not var tw = new StringWriter(); - foreach (MessageTemplateToken? t in parsedTemplate.Tokens) + foreach (MessageTemplateToken? t in parsedTemplate!.Tokens) { if (t is PropertyToken pt && values.TryGetValue(pt.PropertyName, out LogEventPropertyValue? propVal) && diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs index 2f8bd3f4ec..b3188720b0 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs @@ -163,7 +163,27 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Install if (!isTrialRun) { + // File configuration providers use a delay before reloading and triggering changes, so wait + using var isChanged = new ManualResetEvent(false); + using IDisposable? onChange = _connectionStrings.OnChange((options, name) => + { + // Only watch default named option (CurrentValue) + if (name != Options.DefaultName) + { + return; + } + + // Signal change + isChanged.Set(); + }); + + // Update configuration and wait for change _configManipulator.SaveConnectionString(connectionString, providerName); + if (!isChanged.WaitOne(10_000)) + { + throw new InstallException("Didn't retrieve updated connection string within 10 seconds, try manual configuration instead."); + } + Configure(_globalSettings.CurrentValue.InstallMissingDatabase || providerMeta.ForceCreateDatabase); } diff --git a/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs b/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs index fafdf0f346..e96856d0b8 100644 --- a/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs +++ b/src/Umbraco.Infrastructure/Runtime/FileSystemMainDomLock.cs @@ -10,7 +10,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime; internal class FileSystemMainDomLock : IMainDomLock { private readonly CancellationTokenSource _cancellationTokenSource = new(); - private readonly IOptionsMonitor _globalSettings; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IOptionsMonitor _globalSettings; private readonly string _lockFilePath; private readonly ILogger _logger; private readonly string _releaseSignalFilePath; @@ -25,7 +26,8 @@ internal class FileSystemMainDomLock : IMainDomLock IOptionsMonitor globalSettings) { _logger = logger; - _globalSettings = globalSettings; + _hostingEnvironment = hostingEnvironment; + _globalSettings = globalSettings; var lockFileName = $"MainDom_{mainDomKeyGenerator.GenerateKey()}.lock"; _lockFilePath = Path.Combine(hostingEnvironment.LocalTempPath, lockFileName); @@ -41,7 +43,7 @@ internal class FileSystemMainDomLock : IMainDomLock { try { - _logger.LogDebug("Attempting to obtain MainDom lock file handle {lockFilePath}", _lockFilePath); + Directory.CreateDirectory(_hostingEnvironment.LocalTempPath);_logger.LogDebug("Attempting to obtain MainDom lock file handle {lockFilePath}", _lockFilePath); _lockFileStream = File.Open(_lockFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); DeleteLockReleaseSignalFile(); return Task.FromResult(true); diff --git a/src/Umbraco.Infrastructure/Scoping/AmbientScopeContextStack.cs b/src/Umbraco.Infrastructure/Scoping/AmbientScopeContextStack.cs new file mode 100644 index 0000000000..18e68120a6 --- /dev/null +++ b/src/Umbraco.Infrastructure/Scoping/AmbientScopeContextStack.cs @@ -0,0 +1,39 @@ +using System.Collections.Concurrent; +using Umbraco.Cms.Core.Scoping; + +namespace Umbraco.Cms.Infrastructure.Scoping; + +internal class AmbientScopeContextStack : IAmbientScopeContextStack +{ + private static AsyncLocal> _stack = new(); + + public IScopeContext? AmbientContext + { + get + { + if (_stack.Value?.TryPeek(out IScopeContext? ambientContext) ?? false) + { + return ambientContext; + } + + return null; + } + } + + public IScopeContext Pop() + { + if (_stack.Value?.TryPop(out IScopeContext? ambientContext) ?? false) + { + return ambientContext; + } + + throw new InvalidOperationException("No AmbientContext was found."); + } + + public void Push(IScopeContext scope) + { + _stack.Value ??= new ConcurrentStack(); + + _stack.Value.Push(scope); + } +} diff --git a/src/Umbraco.Infrastructure/Scoping/AmbientScopeStack.cs b/src/Umbraco.Infrastructure/Scoping/AmbientScopeStack.cs new file mode 100644 index 0000000000..3ad5e89e51 --- /dev/null +++ b/src/Umbraco.Infrastructure/Scoping/AmbientScopeStack.cs @@ -0,0 +1,39 @@ +using System.Collections.Concurrent; + +namespace Umbraco.Cms.Infrastructure.Scoping +{ + internal class AmbientScopeStack : IAmbientScopeStack + { + private static AsyncLocal> _stack = new (); + + public IScope? AmbientScope + { + get + { + if (_stack.Value?.TryPeek(out IScope? ambientScope) ?? false) + { + return ambientScope; + } + + return null; + } + } + + public IScope Pop() + { + if (_stack.Value?.TryPop(out IScope? ambientScope) ?? false) + { + return ambientScope; + } + + throw new InvalidOperationException("No AmbientScope was found."); + } + + public void Push(IScope scope) + { + _stack.Value ??= new ConcurrentStack(); + + _stack.Value.Push(scope); + } + } +} diff --git a/src/Umbraco.Infrastructure/Scoping/IAmbientScopeContextStack.cs b/src/Umbraco.Infrastructure/Scoping/IAmbientScopeContextStack.cs new file mode 100644 index 0000000000..28da9a6427 --- /dev/null +++ b/src/Umbraco.Infrastructure/Scoping/IAmbientScopeContextStack.cs @@ -0,0 +1,10 @@ +using Umbraco.Cms.Core.Scoping; + +namespace Umbraco.Cms.Infrastructure.Scoping; + +internal interface IAmbientScopeContextStack +{ + IScopeContext? AmbientContext { get; } + IScopeContext Pop(); + void Push(IScopeContext scope); +} diff --git a/src/Umbraco.Infrastructure/Scoping/IAmbientScopeStack.cs b/src/Umbraco.Infrastructure/Scoping/IAmbientScopeStack.cs new file mode 100644 index 0000000000..71cfbf3a03 --- /dev/null +++ b/src/Umbraco.Infrastructure/Scoping/IAmbientScopeStack.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Cms.Infrastructure.Scoping; + +internal interface IAmbientScopeStack : IScopeAccessor +{ + IScope Pop(); + void Push(IScope scope); +} diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index 873e08fc99..000b6a602e 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -33,7 +33,6 @@ namespace Umbraco.Cms.Infrastructure.Scoping private readonly bool? _scopeFileSystem; private readonly ScopeProvider _scopeProvider; - private bool _callContext; private bool? _completed; private IUmbracoDatabase? _database; @@ -86,7 +85,6 @@ namespace Umbraco.Cms.Infrastructure.Scoping _eventDispatcher = eventDispatcher; _notificationPublisher = notificationPublisher; _scopeFileSystem = scopeFileSystems; - _callContext = callContext; _autoComplete = autoComplete; Detachable = detachable; _dictionaryLocker = new object(); @@ -248,24 +246,15 @@ namespace Umbraco.Cms.Infrastructure.Scoping { } + [Obsolete("Scopes are never stored on HttpContext.Items anymore, so CallContext is always true.")] // a value indicating whether to force call-context public bool CallContext { - get + get => true; + set { - if (_callContext) - { - return true; - } - - if (ParentScope != null) - { - return ParentScope.CallContext; - } - - return false; + // NOOP - always true. } - set => _callContext = value; } public bool ScopedFileSystems @@ -467,6 +456,19 @@ namespace Umbraco.Cms.Infrastructure.Scoping } } + public int Depth + { + get + { + if (ParentScope == null) + { + return 0; + } + + return ParentScope.Depth + 1; + } + } + public IScopedNotificationPublisher Notifications { get @@ -540,7 +542,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping } } - _scopeProvider.PopAmbientScope(this); // might be null = this is how scopes are removed from context objects + _scopeProvider.PopAmbientScope(); // might be null = this is how scopes are removed from context objects #if DEBUG_SCOPES _scopeProvider.Disposed(this); @@ -885,7 +887,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping // by Deploy which I don't fully understand since there is limited tests on this in the CMS if (OrigScope != _scopeProvider.AmbientScope) { - _scopeProvider.PopAmbientScope(_scopeProvider.AmbientScope); + _scopeProvider.PopAmbientScope(); } if (OrigContext != _scopeProvider.AmbientContext) diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs index c8c58d9185..31249daa67 100644 --- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs @@ -1,15 +1,17 @@ -using System.Collections.Concurrent; using System.Data; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DistributedLocking; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; +using CoreDebugSettings = Umbraco.Cms.Core.Configuration.Models.CoreDebugSettings; + #if DEBUG_SCOPES using System.Linq; using System.Text; @@ -18,26 +20,52 @@ using System.Text; namespace Umbraco.Cms.Infrastructure.Scoping { /// - /// Implements . + /// Implements . /// internal class ScopeProvider : ICoreScopeProvider, IScopeProvider, Core.Scoping.IScopeProvider, - IScopeAccessor + IScopeAccessor // TODO: No need to implement this here but literally hundreds of our tests cast ScopeProvider to ScopeAccessor { private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; - private readonly IRequestCache _requestCache; + private readonly IEventAggregator _eventAggregator; + private readonly IAmbientScopeStack _ambientScopeStack; + private readonly IAmbientScopeContextStack _ambientContextStack; + private readonly FileSystems _fileSystems; private CoreDebugSettings _coreDebugSettings; private readonly MediaFileManager _mediaFileManager; - private static readonly AsyncLocal> _scopeStack = new(); - private static readonly AsyncLocal> _scopeContextStack = new(); - private static readonly string _scopeItemKey = typeof(Scope).FullName!; - private static readonly string _contextItemKey = typeof(ScopeProvider).FullName!; - private readonly IEventAggregator _eventAggregator; + public ScopeProvider( + IAmbientScopeStack ambientScopeStack, + IAmbientScopeContextStack ambientContextStack, + IDistributedLockingMechanismFactory distributedLockingMechanismFactory, + IUmbracoDatabaseFactory databaseFactory, + FileSystems fileSystems, + IOptionsMonitor coreDebugSettings, + MediaFileManager mediaFileManager, + ILoggerFactory loggerFactory, + IEventAggregator eventAggregator) + { + DistributedLockingMechanismFactory = distributedLockingMechanismFactory; + DatabaseFactory = databaseFactory; + _ambientScopeStack = ambientScopeStack; + _ambientContextStack = ambientContextStack; + _fileSystems = fileSystems; + _coreDebugSettings = coreDebugSettings.CurrentValue; + _mediaFileManager = mediaFileManager; + _logger = loggerFactory.CreateLogger(); + _loggerFactory = loggerFactory; + _eventAggregator = eventAggregator; + // take control of the FileSystems + _fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems; + + coreDebugSettings.OnChange(x => _coreDebugSettings = x); + } + + [Obsolete("Please use an alternative constructor. This constructor is due for removal in v12.")] public ScopeProvider( IDistributedLockingMechanismFactory distributedLockingMechanismFactory, IUmbracoDatabaseFactory databaseFactory, @@ -47,21 +75,17 @@ namespace Umbraco.Cms.Infrastructure.Scoping ILoggerFactory loggerFactory, IRequestCache requestCache, IEventAggregator eventAggregator) + : this( + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService(), + distributedLockingMechanismFactory, + databaseFactory, + fileSystems, + coreDebugSettings, + mediaFileManager, + loggerFactory, + eventAggregator) { - DistributedLockingMechanismFactory = distributedLockingMechanismFactory; - DatabaseFactory = databaseFactory; - _fileSystems = fileSystems; - _coreDebugSettings = coreDebugSettings.CurrentValue; - _mediaFileManager = mediaFileManager; - _logger = loggerFactory.CreateLogger(); - _loggerFactory = loggerFactory; - _requestCache = requestCache; - _eventAggregator = eventAggregator; - - // take control of the FileSystems - _fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems; - - coreDebugSettings.OnChange(x => _coreDebugSettings = x); } public IDistributedLockingMechanismFactory DistributedLockingMechanismFactory { get; } @@ -70,282 +94,32 @@ namespace Umbraco.Cms.Infrastructure.Scoping public ISqlContext SqlContext => DatabaseFactory.SqlContext; - #region Context - - private void MoveHttpContextScopeToCallContext() - { - var source = (ConcurrentStack?)_requestCache.Get(_scopeItemKey); - ConcurrentStack? stack = _scopeStack.Value; - MoveContexts(_scopeItemKey, source, stack, (_, v) => _scopeStack.Value = v); - } - - private void MoveHttpContextScopeContextToCallContext() - { - var source = (ConcurrentStack?)_requestCache.Get(_contextItemKey); - ConcurrentStack? stack = _scopeContextStack.Value; - MoveContexts(_contextItemKey, source, stack, (_, v) => _scopeContextStack.Value = v); - } - - private void MoveCallContextScopeToHttpContext() - { - ConcurrentStack? source = _scopeStack.Value; - var stack = (ConcurrentStack?)_requestCache.Get(_scopeItemKey); - MoveContexts(_scopeItemKey, source, stack, (k, v) => _requestCache.Set(k, v)); - } - - private void MoveCallContextScopeContextToHttpContext() - { - ConcurrentStack? source = _scopeContextStack.Value; - var stack = (ConcurrentStack?)_requestCache.Get(_contextItemKey); - MoveContexts(_contextItemKey, source, stack, (k, v) => _requestCache.Set(k, v)); - } - - private void MoveContexts(string key, ConcurrentStack? source, ConcurrentStack? stack, Action> setter) - where T : class, IInstanceIdentifiable - { - if (source == null) - { - return; - } - - if (stack != null) - { - stack.Clear(); - } - else - { - // TODO: This isn't going to copy it back up the execution context chain - stack = new ConcurrentStack(); - setter(key, stack); - } - - var arr = new T[source.Count]; - source.CopyTo(arr, 0); - Array.Reverse(arr); - foreach (T a in arr) - { - stack.Push(a); - } - - source.Clear(); - } - - private void SetCallContextScope(IScope? value) - { - ConcurrentStack? stack = _scopeStack.Value; - -#if DEBUG_SCOPES - // first, null-register the existing value - if (stack != null && stack.TryPeek(out IScope ambientScope)) - { - RegisterContext(ambientScope, null); - } - - // then register the new value - if (value != null) - { - RegisterContext(value, "call"); - } -#endif - - if (value == null) - { - if (stack != null) - { - stack.TryPop(out _); - } - } - else - { -#if DEBUG_SCOPES - _logger.LogDebug("AddObject " + value.InstanceId.ToString("N").Substring(0, 8)); -#endif - if (stack == null) - { - stack = new ConcurrentStack(); - } - - stack.Push(value); - _scopeStack.Value = stack; - } - } - - private void SetCallContextScopeContext(IScopeContext? value) - { - ConcurrentStack? stack = _scopeContextStack.Value; - - if (value == null) - { - if (stack != null) - { - stack.TryPop(out _); - } - } - else - { - if (stack == null) - { - stack = new ConcurrentStack(); - } - - stack.Push(value); - _scopeContextStack.Value = stack; - } - } - - private T? GetHttpContextObject(string key, bool required = true) - where T : class - { - if (!_requestCache.IsAvailable && required) - { - throw new Exception("Request cache is unavailable."); - } - - var stack = (ConcurrentStack?)_requestCache.Get(key); - return stack != null && stack.TryPeek(out T? peek) ? peek : null; - } - - private bool SetHttpContextObject(string key, T? value, bool required = true) - { - if (!_requestCache.IsAvailable) - { - if (required) - { - throw new Exception("Request cache is unavailable."); - } - - return false; - } - -#if DEBUG_SCOPES - // manage the 'context' that contains the scope (null, "http" or "call") - // only for scopes of course! - if (key == s_scopeItemKey) - { - // first, null-register the existing value - var ambientScope = (IScope)_requestCache.Get(s_scopeItemKey); - if (ambientScope != null) - { - RegisterContext(ambientScope, null); - } - - // then register the new value - if (value is IScope scope) - { - RegisterContext(scope, "http"); - } - } -#endif - var stack = (ConcurrentStack?)_requestCache.Get(key); - - if (value == null) - { - if (stack != null) - { - stack.TryPop(out _); - } - } - else - { - if (stack == null) - { - stack = new ConcurrentStack(); - } - - stack.Push(value); - _requestCache.Set(key, stack); - } - - return true; - } - - #endregion #region Ambient Context /// - /// Get the Ambient (Current) for the current execution context. + /// Get the Ambient (Current) for the current execution context. /// /// - /// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal) + /// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal) /// - public IScopeContext? AmbientContext - { - get - { - // try http context, fallback onto call context - IScopeContext? value = GetHttpContextObject(_contextItemKey, false); - if (value != null) - { - return value; - } - - ConcurrentStack? stack = _scopeContextStack.Value; - if (stack == null || !stack.TryPeek(out IScopeContext? peek)) - { - return null; - } - - return peek; - } - } + public IScopeContext? AmbientContext => _ambientContextStack.AmbientContext; #endregion #region Ambient Scope - IScope? IScopeAccessor.AmbientScope => AmbientScope; - /// - /// Gets or set the Ambient (Current) for the current execution context. + /// Gets or set the Ambient (Current) for the current execution context. /// /// - /// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal) + /// The current execution context may be request based (HttpContext) or on a background thread (AsyncLocal) /// - public Scope? AmbientScope - { - get - { - // try http context, fallback onto call context - IScope? value = GetHttpContextObject(_scopeItemKey, false); - if (value != null) - { - return (Scope)value; - } + public Scope? AmbientScope => (Scope?)_ambientScopeStack.AmbientScope; - ConcurrentStack? stack = _scopeStack.Value; - if (stack == null || !stack.TryPeek(out IScope? peek)) - { - return null; - } + IScope? IScopeAccessor.AmbientScope => _ambientScopeStack.AmbientScope; - return (Scope)peek; - } - } - - public void PopAmbientScope(Scope? scope) - { - // pop the stack from all contexts - SetHttpContextObject(_scopeItemKey, null, false); - SetCallContextScope(null); - - // We need to move the stack to a different context if the parent scope - // is flagged with a different CallContext flag. This is required - // if creating a child scope with callContext: true (thus forcing CallContext) - // when there is actually a current HttpContext available. - // It's weird but is required for Deploy somehow. - var parentScopeCallContext = scope?.ParentScope?.CallContext ?? false; - if ((scope?.CallContext ?? false) && !parentScopeCallContext) - { - MoveCallContextScopeToHttpContext(); - MoveCallContextScopeContextToHttpContext(); - } - else if ((!scope?.CallContext ?? false) && parentScopeCallContext) - { - MoveHttpContextScopeToCallContext(); - MoveHttpContextScopeContextToCallContext(); - } - } + public void PopAmbientScope() => _ambientScopeStack.Pop(); #endregion @@ -356,21 +130,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping throw new ArgumentNullException(nameof(scope)); } - if (scope.CallContext || !SetHttpContextObject(_scopeItemKey, scope, false)) - { - // In this case, always ensure that the HttpContext items - // is transfered to CallContext and then cleared since we - // may be migrating context with the callContext = true flag. - // This is a weird case when forcing callContext when HttpContext - // is available. Required by Deploy. - if (_requestCache.IsAvailable) - { - MoveHttpContextScopeToCallContext(); - MoveHttpContextScopeContextToCallContext(); - } - - SetCallContextScope(scope); - } + _ambientScopeStack.Push(scope); } public void PushAmbientScopeContext(IScopeContext? scopeContext) @@ -379,17 +139,10 @@ namespace Umbraco.Cms.Infrastructure.Scoping { throw new ArgumentNullException(nameof(scopeContext)); } - - SetHttpContextObject(_contextItemKey, scopeContext, false); - SetCallContextScopeContext(scopeContext); + _ambientContextStack.Push(scopeContext); } - public void PopAmbientScopeContext() - { - // pop stack from all contexts - SetHttpContextObject(_contextItemKey, null, false); - SetCallContextScopeContext(null); - } + public void PopAmbientScopeContext() => _ambientContextStack.Pop(); /// public IScope CreateDetachedScope( @@ -398,21 +151,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping IEventDispatcher? eventDispatcher = null, IScopedNotificationPublisher? scopedNotificationPublisher = null, bool? scopeFileSystems = null) - => - new Scope( - this, - _coreDebugSettings, - _mediaFileManager, - _eventAggregator, - _loggerFactory.CreateLogger(), - _fileSystems, - true, - null, - isolationLevel, - repositoryCacheMode, - eventDispatcher, - scopedNotificationPublisher, - scopeFileSystems); + => new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopedNotificationPublisher, scopeFileSystems); /// public void AttachScope(IScope other, bool callContext = false) @@ -457,20 +196,18 @@ namespace Umbraco.Cms.Infrastructure.Scoping throw new InvalidOperationException("Ambient scope is not detachable."); } - PopAmbientScope(ambientScope); + PopAmbientScope(); PopAmbientScopeContext(); Scope? originalScope = AmbientScope; if (originalScope != ambientScope.OrigScope) { - throw new InvalidOperationException( - $"The detatched scope ({ambientScope.GetDebugInfo()}) does not match the original ({originalScope?.GetDebugInfo()})"); + throw new InvalidOperationException($"The detatched scope ({ambientScope.GetDebugInfo()}) does not match the original ({originalScope?.GetDebugInfo()})"); } - IScopeContext? originalScopeContext = AmbientContext; if (originalScopeContext != ambientScope.OrigContext) { - throw new InvalidOperationException("The detatched scope context does not match the original"); + throw new InvalidOperationException($"The detatched scope context does not match the original"); } ambientScope.OrigScope = null; @@ -494,22 +231,7 @@ namespace Umbraco.Cms.Infrastructure.Scoping { IScopeContext? ambientContext = AmbientContext; ScopeContext? newContext = ambientContext == null ? new ScopeContext() : null; - var scope = new Scope( - this, - _coreDebugSettings, - _mediaFileManager, - _eventAggregator, - _loggerFactory.CreateLogger(), - _fileSystems, - false, - newContext, - isolationLevel, - repositoryCacheMode, - eventDispatcher, - notificationPublisher, - scopeFileSystems, - callContext, - autoComplete); + var scope = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, scopeFileSystems, callContext, autoComplete); // assign only if scope creation did not throw! PushAmbientScope(scope); @@ -517,34 +239,17 @@ namespace Umbraco.Cms.Infrastructure.Scoping { PushAmbientScopeContext(newContext); } - return scope; } - var nested = new Scope( - this, - _coreDebugSettings, - _mediaFileManager, - _eventAggregator, - _loggerFactory.CreateLogger(), - _fileSystems, - ambientScope, - isolationLevel, - repositoryCacheMode, - eventDispatcher, - notificationPublisher, - scopeFileSystems, - callContext, - autoComplete); + var nested = new Scope(this, _coreDebugSettings, _mediaFileManager, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, notificationPublisher, scopeFileSystems, callContext, autoComplete); PushAmbientScope(nested); return nested; } + /// public IScopeContext? Context => AmbientContext; - // for testing - internal ConcurrentStack? GetCallContextScopeValue() => _scopeStack.Value; - #if DEBUG_SCOPES // this code needs TLC // diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj index c304f4e99a..fc41bec6c5 100644 --- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj +++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj @@ -18,9 +18,9 @@ - + - + @@ -28,7 +28,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -37,10 +37,10 @@ - + - + @@ -48,15 +48,18 @@ - + - + all + + 3.5.107 + diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs index 6b8afa6626..2d18905ee9 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.LocalizedText.cs @@ -1,110 +1,116 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; -using Umbraco.Cms.Core; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; -namespace Umbraco.Extensions; - -/// -/// Extension methods for for the Umbraco back office -/// -public static partial class UmbracoBuilderExtensions +namespace Umbraco.Extensions { /// - /// Adds the supplementary localized texxt file sources from the various physical and virtual locations supported. + /// Extension methods for for the Umbraco back office /// - private static IUmbracoBuilder AddSupplemenataryLocalizedTextFileSources(this IUmbracoBuilder builder) + public static partial class UmbracoBuilderExtensions { - builder.Services.AddTransient(sp => + + /// + /// Adds the supplementary localized texxt file sources from the various physical and virtual locations supported. + /// + private static IUmbracoBuilder AddSupplemenataryLocalizedTextFileSources(this IUmbracoBuilder builder) { - return GetSupplementaryFileSources( - sp.GetRequiredService()); - }); - - return builder; - } - - - /// - /// Loads the suplimentary localization files from plugins and user config - /// - private static IEnumerable GetSupplementaryFileSources( - IWebHostEnvironment webHostEnvironment) - { - IFileProvider webFileProvider = webHostEnvironment.WebRootFileProvider; - IFileProvider contentFileProvider = webHostEnvironment.ContentRootFileProvider; - - // gets all langs files in /app_plugins real or virtual locations - IEnumerable pluginLangFileSources = - GetPluginLanguageFileSources(webFileProvider, Constants.SystemDirectories.AppPlugins, false); - - // user defined langs that overwrite the default, these should not be used by plugin creators - var userConfigLangFolder = Constants.SystemDirectories.Config - .TrimStart(Constants.CharArrays.Tilde); - - IEnumerable userLangFileSources = contentFileProvider - .GetDirectoryContents(userConfigLangFolder) - .Where(x => x.IsDirectory && x.Name.InvariantEquals("lang")) - .Select(x => new DirectoryInfo(x.PhysicalPath)) - .SelectMany(x => x.GetFiles("*.user.xml", SearchOption.TopDirectoryOnly)) - .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true)); - - return pluginLangFileSources - .Concat(userLangFileSources); - } - - - /// - /// Loads the suplimentary localaization files via the file provider. - /// - /// - /// locates all *.xml files in the lang folder of any sub folder of the one provided. - /// e.g /app_plugins/plugin-name/lang/*.xml - /// - private static IEnumerable GetPluginLanguageFileSources( - IFileProvider fileProvider, string folder, bool overwriteCoreKeys) - { - // locate all the *.xml files inside Lang folders inside folders of the main folder - // e.g. /app_plugins/plugin-name/lang/*.xml - var fileSources = new List(); - - var pluginFolders = fileProvider.GetDirectoryContents(folder) - .Where(x => x.IsDirectory).ToList(); - - foreach (IFileInfo pluginFolder in pluginFolders) - { - // get the full virtual path for the plugin folder - var pluginFolderPath = WebPath.Combine(folder, pluginFolder.Name); - - // get any lang folders in this plugin - IEnumerable langFolders = fileProvider.GetDirectoryContents(pluginFolderPath) - .Where(x => x.IsDirectory && x.Name.InvariantEquals("lang")); - - // loop through the lang folder(s) - // - there could be multiple on case sensitive file system - foreach (IFileInfo langFolder in langFolders) + builder.Services.AddTransient(sp => { - // get the full 'virtual' path of the lang folder - var langFolderPath = WebPath.Combine(pluginFolderPath, langFolder.Name); + return GetSupplementaryFileSources( + sp.GetRequiredService()); + }); - // request all the files out of the path, these will have physicalPath set. - var files = fileProvider.GetDirectoryContents(langFolderPath) - .Where(x => x.Name.InvariantEndsWith(".xml") && !string.IsNullOrEmpty(x.PhysicalPath)) - .Select(x => new FileInfo(x.PhysicalPath)) - .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, overwriteCoreKeys)) - .ToList(); + return builder; + } - // add any to our results - if (files.Count > 0) + + /// + /// Loads the suplimentary localization files from plugins and user config + /// + private static IEnumerable GetSupplementaryFileSources( + IWebHostEnvironment webHostEnvironment) + { + IFileProvider webFileProvider = webHostEnvironment.WebRootFileProvider; + IFileProvider contentFileProvider = webHostEnvironment.ContentRootFileProvider; + + // gets all langs files in /app_plugins real or virtual locations + IEnumerable pluginLangFileSources = GetPluginLanguageFileSources(webFileProvider, Cms.Core.Constants.SystemDirectories.AppPlugins, false); + + // user defined langs that overwrite the default, these should not be used by plugin creators + var userConfigLangFolder = Cms.Core.Constants.SystemDirectories.Config + .TrimStart(Cms.Core.Constants.CharArrays.Tilde); + + IEnumerable userLangFileSources = contentFileProvider.GetDirectoryContents(userConfigLangFolder) + .Where(x => x.IsDirectory && x.Name.InvariantEquals("lang")) + .Select(x => new DirectoryInfo(x.PhysicalPath)) + .SelectMany(x => x.GetFiles("*.user.xml", SearchOption.TopDirectoryOnly)) + .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true)); + + return pluginLangFileSources + .Concat(userLangFileSources); + } + + + /// + /// Loads the suplimentary localaization files via the file provider. + /// + /// + /// locates all *.xml files in the lang folder of any sub folder of the one provided. + /// e.g /app_plugins/plugin-name/lang/*.xml + /// + private static IEnumerable GetPluginLanguageFileSources( + IFileProvider fileProvider, string folder, bool overwriteCoreKeys) + { + IEnumerable pluginFolders = fileProvider + .GetDirectoryContents(folder) + .Where(x => x.IsDirectory); + + foreach (IFileInfo pluginFolder in pluginFolders) + { + // get the full virtual path for the plugin folder + var pluginFolderPath = WebPath.Combine(folder, pluginFolder.Name); + + // loop through the lang folder(s) + // - there could be multiple on case sensitive file system + foreach (var langFolder in GetLangFolderPaths(fileProvider, pluginFolderPath)) { - fileSources.AddRange(files); + // request all the files out of the path, these will have physicalPath set. + IEnumerable localizationFiles = fileProvider + .GetDirectoryContents(langFolder) + .Where(x => !string.IsNullOrEmpty(x.PhysicalPath)) + .Where(x => x.Name.InvariantEndsWith(".xml")) + .Select(x => new FileInfo(x.PhysicalPath)); + + foreach (FileInfo file in localizationFiles) + { + yield return new LocalizedTextServiceSupplementaryFileSource(file, overwriteCoreKeys); + } } } } - return fileSources; + private static IEnumerable GetLangFolderPaths(IFileProvider fileProvider, string path) + { + IEnumerable directories = fileProvider.GetDirectoryContents(path).Where(x => x.IsDirectory); + + foreach (IFileInfo directory in directories) + { + var virtualPath = WebPath.Combine(path, directory.Name); + + if (directory.Name.InvariantEquals("lang")) + { + yield return virtualPath; + } + + foreach (var nested in GetLangFolderPaths(fileProvider, virtualPath)) + { + yield return nested; + } + } + } } } diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj index 13076bd50c..3f24e2717e 100644 --- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj +++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj @@ -28,6 +28,9 @@ all + + 3.5.107 + diff --git a/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs b/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs index 616611a224..3bf8d3a04a 100644 --- a/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs +++ b/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs @@ -11,5 +11,5 @@ public interface IVirtualPageController /// /// Returns the to use as the current page for the request /// - IPublishedContent FindContent(ActionExecutingContext actionExecutingContext); + IPublishedContent? FindContent(ActionExecutingContext actionExecutingContext); } diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index a2c7dcb475..e08f45e5af 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -35,14 +35,14 @@ - - + + - - - + + + diff --git a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs index b1ac11c77d..0b3132b795 100644 --- a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs @@ -128,7 +128,7 @@ public abstract class UmbracoViewPage : RazorPage { // filter / add preview banner // ASP.NET default value is text/html - if (Context.Response.ContentType.InvariantContains("text/html")) + if (Context.Response?.ContentType?.InvariantContains("text/html") ?? false) { if (((UmbracoContext?.IsDebug ?? false) || (UmbracoContext?.InPreviewMode ?? false)) && tagHelperOutput.TagName != null diff --git a/src/Umbraco.Web.UI/Program.cs b/src/Umbraco.Web.UI/Program.cs index b41c61513a..cebb7b0370 100644 --- a/src/Umbraco.Web.UI/Program.cs +++ b/src/Umbraco.Web.UI/Program.cs @@ -1,6 +1,3 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; - namespace Umbraco.Cms.Web.UI { public class Program diff --git a/src/Umbraco.Web.UI/Startup.cs b/src/Umbraco.Web.UI/Startup.cs index 0176974b6b..eba72b6924 100644 --- a/src/Umbraco.Web.UI/Startup.cs +++ b/src/Umbraco.Web.UI/Startup.cs @@ -1,12 +1,3 @@ -using System; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Extensions; - namespace Umbraco.Cms.Web.UI { public class Startup diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 2274c73327..42e894a7f3 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -24,7 +24,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj index 0bb0b6c0ad..176564eaf8 100644 --- a/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj +++ b/src/Umbraco.Web.Website/Umbraco.Web.Website.csproj @@ -35,6 +35,9 @@ all + + 3.5.107 + diff --git a/templates/Umbraco.Templates.csproj b/templates/Umbraco.Templates.csproj index 182b220167..47b2246835 100644 --- a/templates/Umbraco.Templates.csproj +++ b/templates/Umbraco.Templates.csproj @@ -1,7 +1,7 @@  - + net6.0 Template @@ -14,7 +14,7 @@ true false - + diff --git a/templates/UmbracoProject/UmbracoProject.csproj b/templates/UmbracoProject/UmbracoProject.csproj index e34e743acb..efaa0edbb0 100644 --- a/templates/UmbracoProject/UmbracoProject.csproj +++ b/templates/UmbracoProject/UmbracoProject.csproj @@ -1,38 +1,37 @@ + + net6.0 + enable + enable + Umbraco.Cms.Web.UI + - - net6.0 - enable - Umbraco.Cms.Web.UI - + + + - - - + + + + + - - - - - + + + + - - - - + + true + - - true - - - - - - - - - false - false - + + + + + + false + false + diff --git a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index db34088068..29e0a36353 100644 --- a/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/tests/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -29,7 +29,7 @@ 6.0.0 - 4.17.2 + 4.18.1 diff --git a/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj b/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj index 705f3333f9..05a5554db2 100644 --- a/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj +++ b/tests/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj @@ -16,7 +16,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs index cfd9e49583..5febb2819b 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopeTests.cs @@ -226,56 +226,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping Assert.IsNull(scopeProvider.AmbientScope); } - [Test] - public void NestedMigrateScope() - { - // Get the request cache mock and re-configure it to be available and used - var requestCacheDictionary = new Dictionary(); - IRequestCache requestCache = AppCaches.RequestCache; - var requestCacheMock = Mock.Get(requestCache); - requestCacheMock - .Setup(x => x.IsAvailable) - .Returns(true); - requestCacheMock - .Setup(x => x.Set(It.IsAny(), It.IsAny())) - .Returns((string key, object val) => - { - requestCacheDictionary.Add(key, val); - return true; - }); - requestCacheMock - .Setup(x => x.Get(It.IsAny())) - .Returns((string key) => requestCacheDictionary.TryGetValue(key, out var val) ? val : null); - - ScopeProvider scopeProvider = ScopeProvider; - Assert.IsNull(scopeProvider.AmbientScope); - - using (IScope scope = scopeProvider.CreateScope()) - { - Assert.IsInstanceOf(scope); - Assert.IsNotNull(scopeProvider.AmbientScope); - Assert.AreSame(scope, scopeProvider.AmbientScope); - - using (IScope nested = scopeProvider.CreateScope(callContext: true)) - { - Assert.IsInstanceOf(nested); - Assert.IsNotNull(scopeProvider.AmbientScope); - Assert.AreSame(nested, scopeProvider.AmbientScope); - Assert.AreSame(scope, ((Scope)nested).ParentScope); - - // it's moved over to call context - ConcurrentStack callContextScope = scopeProvider.GetCallContextScopeValue(); - - Assert.IsNotNull(callContextScope); - Assert.AreEqual(2, callContextScope.Count); - } - - // it's naturally back in http context - } - - Assert.IsNull(scopeProvider.AmbientScope); - } - [Test] public void NestedCreateScopeContext() { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ThreadSafetyServiceTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ThreadSafetyServiceTest.cs index 88adef3824..4ee52a6869 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ThreadSafetyServiceTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ThreadSafetyServiceTest.cs @@ -134,11 +134,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services { try { - ConcurrentStack - currentStack = ((ScopeProvider)ScopeProvider).GetCallContextScopeValue(); - log.LogInformation("[{ThreadId}] Current Stack? {CurrentStack}", - Thread.CurrentThread.ManagedThreadId, currentStack?.Count); - // NOTE: This is NULL because we have supressed the execution context flow. // If we don't do that we will get various exceptions because we're trying to run concurrent threads // against an ambient context which cannot be done due to the rules of scope creation and completion. @@ -234,11 +229,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services { try { - ConcurrentStack - currentStack = ((ScopeProvider)ScopeProvider).GetCallContextScopeValue(); - log.LogInformation("[{ThreadId}] Current Stack? {CurrentStack}", - Thread.CurrentThread.ManagedThreadId, currentStack?.Count); - // NOTE: This is NULL because we have supressed the execution context flow. // If we don't do that we will get various exceptions because we're trying to run concurrent threads // against an ambient context which cannot be done due to the rules of scope creation and completion. diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index 1954093f66..8397da51b4 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -85,13 +85,13 @@ - - - + + + - + all diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index 7593e52511..8aca86ba80 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -71,7 +71,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components Mock.Of(), Options.Create(new ContentSettings())); IEventAggregator eventAggregator = Mock.Of(); - var scopeProvider = new ScopeProvider(Mock.Of(),f , fs, new TestOptionsMonitor(coreDebug), mediaFileManager, loggerFactory, NoAppCache.Instance, eventAggregator); + var scopeProvider = new ScopeProvider(new AmbientScopeStack(), new AmbientScopeContextStack(), Mock.Of(),f , fs, new TestOptionsMonitor(coreDebug), mediaFileManager, loggerFactory, eventAggregator); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(logger); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(loggerFactory.CreateLogger); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs index 6ddc506753..79d2f7b3d3 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopedNotificationPublisherTests.cs @@ -93,13 +93,14 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Scoping eventAggregatorMock = new Mock(); return new ScopeProvider( + new AmbientScopeStack(), + new AmbientScopeContextStack(), Mock.Of(), Mock.Of(), fileSystems, new TestOptionsMonitor(new CoreDebugSettings()), mediaFileManager, loggerFactory, - Mock.Of(), eventAggregatorMock.Object ); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs index f429f584e5..3d4ded44f2 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Scoping/ScopeUnitTests.cs @@ -71,13 +71,14 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Scoping sqlContext.Setup(x => x.SqlSyntax).Returns(syntaxProviderMock.Object); return new ScopeProvider( + new AmbientScopeStack(), + new AmbientScopeContextStack(), lockingMechanismFactory.Object, databaseFactory.Object, fileSystems, new TestOptionsMonitor(new CoreDebugSettings()), mediaFileManager, loggerFactory, - Mock.Of(), Mock.Of()); } @@ -579,5 +580,34 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Scoping Assert.IsNull(realScope.GetReadLocks()); } } + + [Test] + public void Depth_WhenRootScope_ReturnsZero() + { + var scopeProvider = GetScopeProvider(out var syntaxProviderMock); + + using (var scope = scopeProvider.CreateScope()) + { + Assert.AreEqual(0,scope.Depth); + } + } + + + [Test] + public void Depth_WhenChildScope_ReturnsDepth() + { + var scopeProvider = GetScopeProvider(out var syntaxProviderMock); + + using (scopeProvider.CreateScope()) + { + using (scopeProvider.CreateScope()) + { + using (var c2 = scopeProvider.CreateScope()) + { + Assert.AreEqual(2, c2.Depth); + } + } + } + } } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index 70bf694128..55d875d1bc 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -21,7 +21,7 @@ - +